Hermes installs brand-swapped "Claude Code" -> "Hermes Agent" in shipped workflows/*.md but never projected the Agent(...) dispatch calls onto Hermes's delegate_task contract, so installed workflows kept literal Agent(...) syntax and falsely asserted "The Agent tool IS available" (Hermes exposes delegate_task, not Agent). Dispatch projection: a generic named-dispatch engine (projectNamedDispatchToStructuralDelegate) wired into the per-runtime RUNTIME_CONTENT_DISPATCH.hermes.md converter, branching entirely on the documentation-sourced hostIntegration.dispatch facts read via _hostIntegrationDispatch (capability.json unchanged): namedDispatch:false -> resolve the gsd-* role and embed a load-its-prompt instruction in the payload; background:true -> map onto delegate_task background; read-only / maxDepth:1 -> no nested delegation to leaf roles; per-call model dropped. Span detection uses literal Agent( scanning + local balanced paren/quote matching (immune to upstream document quote imbalance) and handles all three corpus call forms (multi-line, object-literal, single-line compact). An independent, mask-free post-projection guard fails the install loud on any residual Agent(/subagent_type/leaked model. Fail-closed: install throws if a literal gsd-* role reference cannot be resolved. commands -> skill path untouched. No literal Agent( survives in installed Hermes workflows. Folded in (maintainer-directed) a pre-existing cross-cutting branding defect: the "Claude Code" -> brand swap corrupted <runtime_compatibility> comparison tables (where "Claude Code" is a compared-runtime label) for every branding runtime. New shared applyClaudeCodeBrandSwap helper protects <runtime_compatibility> regions via split-and-rejoin (no sentinel token) while still rebranding genuine self-references; adopted by all six branding .md converters. Also a surgical prose-consistency fix so plan-review-convergence.md's dispatch-adjacent terminology is coherent post-projection (no broad bare-word rename). Golden install-parity regenerated for the six branding runtimes (dispatch/branding scope only); other runtimes unchanged. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -573,6 +573,20 @@ describe('uninstall skills cleanup — hermes', () => {
|
||||
|
||||
// ─── Section 4: No Claude references leak into non-Claude runtimes ────────────
|
||||
|
||||
// #2284(b): a `<runtime_compatibility>...</runtime_compatibility>` block (e.g.
|
||||
// execute-phase.md/plan-phase.md's "**Subagent spawning is runtime-specific:**"
|
||||
// table) INTENTIONALLY retains "Claude Code" as a COMPARISON-RUNTIME LABEL —
|
||||
// "- **Claude Code:** Uses `Agent(subagent_type=..., ...)`" documents how
|
||||
// Claude Code behaves for a reader on ANY installed runtime; it is never a
|
||||
// host-self-reference the install is supposed to rebrand away. Strip these
|
||||
// blocks before the leak scan below so that legitimate retention doesn't trip
|
||||
// the "zero Claude references" invariant — everything OUTSIDE a
|
||||
// `runtime_compatibility` block is still held to zero, so an actual leaked
|
||||
// self-reference elsewhere in the file still fails this test.
|
||||
function stripRuntimeCompatibilityBlocks(content) {
|
||||
return content.replace(/<runtime_compatibility>[\s\S]*?<\/runtime_compatibility>/g, '');
|
||||
}
|
||||
|
||||
for (const runtime of ['hermes', 'qwen']) {
|
||||
describe(`no Claude references leak into ${runtime} install`, () => {
|
||||
let tmpDir;
|
||||
@@ -628,7 +642,9 @@ for (const runtime of ['hermes', 'qwen']) {
|
||||
path.basename(f) !== 'CHANGELOG.md'
|
||||
);
|
||||
const leaks = allFiles.filter(f => {
|
||||
const c = fs.readFileSync(f, 'utf8');
|
||||
// #2284(b): exempt `<runtime_compatibility>` comparison-table content
|
||||
// — see the exemption's rationale above the `for` loop.
|
||||
const c = stripRuntimeCompatibilityBlocks(fs.readFileSync(f, 'utf8'));
|
||||
return /\bCLAUDE\.md\b/.test(c) || /\bClaude Code\b/.test(c) || /\.claude\//.test(c);
|
||||
}).map(f => path.relative(tmpDir, f));
|
||||
assert.strictEqual(leaks.length, 0, `Leaking: ${leaks.join(', ')}`);
|
||||
|
||||
Reference in New Issue
Block a user