diff --git a/dsh/bundle/index.js b/dsh/bundle/index.js index a0bb9a3..eefaa10 100644 --- a/dsh/bundle/index.js +++ b/dsh/bundle/index.js @@ -73,10 +73,11 @@ export function resolveRolesDir(bundleDir, configured, home = homedir()) { function isSubagent(agent) { - if (!agent) return false - if (agent.parent || agent.parentAgent || agent.parentSessionId) return true - if (typeof agent.depth === 'number' && agent.depth > 0) return true - return false + return Boolean( + agent?.session?.header?.delegationDepth > 0 || + agent?.session?.header?.parentSession != null || + agent?.options?.subagentDepth > 0, + ) } function sessionCwd(workingDirectory, agent) { diff --git a/dsh/tests/prestep.test.mjs b/dsh/tests/prestep.test.mjs index 972275c..0ddff3f 100644 --- a/dsh/tests/prestep.test.mjs +++ b/dsh/tests/prestep.test.mjs @@ -377,3 +377,56 @@ test('apply: bootstrap:false leaves harness-check wired (the gate is ctx.on, not cleanup(repo) } }) + +// --------------------------------------------------------------------------- +// Subagent skip (T2.1): real runtime delegation signals +// --------------------------------------------------------------------------- + +/** + * Agent shapes that the runtime uses for a delegated step. The old + * parent/parentAgent/parentSessionId/depth checks were dead; these three are + * the live ones. + */ +const SUBAGENT_AGENTS = [ + { session: { header: { delegationDepth: 1 } } }, + { session: { header: { parentSession: 's0' } } }, + { options: { subagentDepth: 1 } }, +] + +test('apply: a delegated pre-step injects nothing (bootstrap, harness-check, role-hint)', async () => { + const repo = nonAdoptedRepo() + const skill = tempDir('dsh-harness-skill-') + try { + write(join(skill, 'SKILL.md'), '---\nname: using-superpowers\n---\nbody\n') + const harness = prestepHarness({ bootstrapSkillPath: join(skill, 'SKILL.md') }, repo) + + for (const agent of SUBAGENT_AGENTS) { + const decision = await harness.run({ agent, messages: [userMessage('Напиши тест')] }) + assert.equal(decision.messages.length, 1, `no injection for ${JSON.stringify(agent)}`) + assert.equal(messageText(decision.messages[0]), 'Напиши тест') + } + } finally { + cleanup(repo) + cleanup(skill) + } +}) + +test('apply: a plain pre-step still injects bootstrap, harness-check, and role-hint', async () => { + const repo = nonAdoptedRepo() + const skill = tempDir('dsh-harness-skill-') + try { + write(join(skill, 'SKILL.md'), '---\nname: using-superpowers\n---\nbody\n') + const harness = prestepHarness({ bootstrapSkillPath: join(skill, 'SKILL.md') }, repo) + const decision = await harness.run({ agent: { session: 's1' }, messages: [userMessage('Напиши тест')] }) + + assert.equal(decision.messages.length, 4) + assert.equal(messageText(decision.messages[0]), 'Напиши тест') + const texts = decision.messages.map(messageText) + assert.ok(texts.some((text) => text.includes('DSH-HARNESS-BOOTSTRAP')), 'bootstrap injected') + assert.ok(texts.some((text) => text.includes('не подключён к обвязке')), 'harness-check injected') + assert.ok(texts.some((text) => text.includes('Роль сессии: dev')), 'role-hint injected') + } finally { + cleanup(repo) + cleanup(skill) + } +})