Skip to content

Commit e79c3da

Browse files
author
konghuanjun
committed
Merge branch 'feat/tower-worker-subagent-denial-subagent01'
2 parents 8d6938c + 195d78d commit e79c3da

4 files changed

Lines changed: 3 additions & 112 deletions

File tree

‎packages/agent-core-v2/src/features/tower/towerService.ts‎

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -299,37 +299,6 @@ export class AgentTowerService extends Disposable implements IAgentTowerService
299299
);
300300
}),
301301
);
302-
this._register(
303-
toolExecutor.onBeforeExecuteTool(async (event) => {
304-
if (event.toolCall.name !== 'Agent') return;
305-
const args = event.args;
306-
if (typeof args !== 'object' || args === null) return;
307-
const resume = (args as { readonly resume?: unknown }).resume;
308-
if (typeof resume !== 'string') return;
309-
const resumeId = resume.trim();
310-
if (resumeId.length === 0) return;
311-
const mainCheckout = resolveTowerRepoRoot(this.sessionCtx.cwd);
312-
if (!(await towerWorkspaceOwned(join(mainCheckout, STATE_FILE)))) return;
313-
const store = new TowerStore(mainCheckout);
314-
const forbidden = await store.load().then(
315-
(state) => {
316-
const caller = store.resolveAgent(state, this.agentCtx.agentId);
317-
if (caller?.kind !== 'reviewer') return undefined;
318-
if (resumeId === 'main') return 'main';
319-
return store.resolveAgent(state, resumeId)?.name;
320-
},
321-
() => undefined,
322-
);
323-
if (forbidden === undefined) return;
324-
event.veto(
325-
denyToolExecution(
326-
this.toolApproval.formatDenyMessage(
327-
`Reviewer agents cannot resume "${forbidden}" — main and every roster agent are orchestration identities; reviewers finish with TowerReview instead. Non-roster explore/plan subagents are still allowed.`,
328-
),
329-
),
330-
);
331-
}),
332-
);
333302
this._register(
334303
toolExecutor.onBeforeExecuteTool(async (event) => {
335304
if (!this.flags.enabled(TOWER_FLAG_ID)) return;

‎packages/agent-core-v2/src/features/tower/workerProfile.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { TOWER_WORKER_PROFILE } from './tower';
1212
import TOWER_WORKER_ROLE_OVERLAY from './tower-worker-overlay.md?raw';
1313

1414
const TOWER_WORKER_TOOLS = [
15-
'Agent',
1615
'Bash',
1716
'TowerComplete',
1817
'TowerFinding',
@@ -61,7 +60,6 @@ export const TOWER_WORKER_PROFILE_DEF: AgentProfile = normalizeAgentProfile({
6160
whenToUse:
6261
'Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.',
6362
tools: TOWER_WORKER_TOOLS,
64-
subagents: ['explore', 'plan'],
6563
renderSystemPrompt: (context) =>
6664
renderSystemPromptResult(TOWER_WORKER_ROLE, context, {
6765
skillActive: skillActiveFor(TOWER_WORKER_TOOLS),

‎packages/agent-core-v2/test/features/tower/towerService.test.ts‎

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3733,82 +3733,6 @@ describe('AgentTowerService', () => {
37333733
expect(permissionGateRan).toBe(true);
37343734
expect(formatDenyMessage).not.toHaveBeenCalled();
37353735
});
3736-
3737-
async function useReviewer(): Promise<void> {
3738-
const store = new TowerStore(repo);
3739-
await store.registerAgent({
3740-
name: 'r1',
3741-
kind: 'reviewer',
3742-
agentId: 'agent-r1',
3743-
sessionId: 'session-main',
3744-
reviewTarget: 'feat/leased',
3745-
reviewMissionId: 'M1',
3746-
spawnedAt: new Date().toISOString(),
3747-
});
3748-
ix.stub(
3749-
IAgentScopeContext,
3750-
makeAgentScopeContext({
3751-
agentId: 'agent-r1',
3752-
agentScope: testWireScope('wire', 'tower-test'),
3753-
generation: 0,
3754-
}),
3755-
);
3756-
ix.get(IAgentTowerService);
3757-
}
3758-
3759-
it.each([
3760-
['main', false],
3761-
['main', true],
3762-
['agent-w1', false],
3763-
['agent-w1', true],
3764-
])('vetoes reviewer resume of %s with run_in_background=%s', async (resumeId, background) => {
3765-
await useReviewer();
3766-
3767-
const decision = await fire(
3768-
agentHookContext({
3769-
resume: resumeId,
3770-
run_in_background: background,
3771-
prompt: 'review again',
3772-
description: 'forbidden reviewer resume',
3773-
}),
3774-
);
3775-
3776-
expect(decision?.veto?.isError).toBe(true);
3777-
expect(decision?.veto?.output).toContain('Reviewer agents cannot resume');
3778-
expect(decision?.veto?.output).toContain('Non-roster explore/plan subagents are still allowed');
3779-
expect(permissionGateRan).toBe(false);
3780-
});
3781-
3782-
it('allows a reviewer to resume a non-roster agent', async () => {
3783-
await useReviewer();
3784-
3785-
const decision = await fire(
3786-
agentHookContext({
3787-
resume: 'agent-stranger',
3788-
run_in_background: true,
3789-
prompt: 'keep exploring',
3790-
description: 'non-roster resume',
3791-
}),
3792-
);
3793-
3794-
expect(decision).toBeUndefined();
3795-
expect(permissionGateRan).toBe(true);
3796-
});
3797-
3798-
it.each(['explore', 'plan'])('allows a fresh non-roster %s subagent for a reviewer', async (type) => {
3799-
await useReviewer();
3800-
3801-
const decision = await fire(
3802-
agentHookContext({
3803-
subagent_type: type,
3804-
prompt: 'inspect the branch',
3805-
description: 'fresh analysis',
3806-
}),
3807-
);
3808-
3809-
expect(decision).toBeUndefined();
3810-
expect(permissionGateRan).toBe(true);
3811-
});
38123736
});
38133737
describe('bash destructive guard', () => {
38143738
let repo: string;

‎packages/agent-core-v2/test/features/tower/workerProfile.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ function builtinProfile(name: string) {
1111
}
1212

1313
describe('tower-worker profile', () => {
14-
it('gives tower-worker the coder tools (minus AgentSwarm) plus the seven shared tower tools', () => {
14+
it('gives tower-worker the coder tools (minus Agent and AgentSwarm) plus the seven shared tower tools', () => {
1515
const coder = builtinProfile('coder');
1616
const tools = TOWER_WORKER_PROFILE_DEF.tools ?? [];
1717

1818
for (const name of coder.tools ?? []) {
19-
if (name === 'AgentSwarm') continue;
19+
if (name === 'Agent' || name === 'AgentSwarm') continue;
2020
expect(tools).toContain(name);
2121
}
22+
expect(tools).not.toContain('Agent');
2223
expect(tools).not.toContain('AgentSwarm');
23-
expect(TOWER_WORKER_PROFILE_DEF.subagents).toEqual(['explore', 'plan']);
2424
for (const name of [
2525
'TowerComplete',
2626
'TowerSend',

0 commit comments

Comments
 (0)