@doingdev/opencode-claude-manager-plugin 0.1.57 → 0.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manager/team-orchestrator.d.ts +3 -2
- package/dist/manager/team-orchestrator.js +32 -9
- package/dist/plugin/agent-hierarchy.d.ts +1 -54
- package/dist/plugin/agent-hierarchy.js +2 -123
- package/dist/plugin/agents/browser-qa.d.ts +14 -0
- package/dist/plugin/agents/browser-qa.js +27 -0
- package/dist/plugin/agents/common.d.ts +37 -0
- package/dist/plugin/agents/common.js +59 -0
- package/dist/plugin/agents/cto.d.ts +9 -0
- package/dist/plugin/agents/cto.js +39 -0
- package/dist/plugin/agents/engineers.d.ts +9 -0
- package/dist/plugin/agents/engineers.js +11 -0
- package/dist/plugin/agents/index.d.ts +6 -0
- package/dist/plugin/agents/index.js +5 -0
- package/dist/plugin/agents/team-planner.d.ts +10 -0
- package/dist/plugin/agents/team-planner.js +23 -0
- package/dist/plugin/claude-manager.plugin.js +45 -23
- package/dist/plugin/service-factory.d.ts +4 -3
- package/dist/plugin/service-factory.js +4 -1
- package/dist/prompts/registry.js +37 -2
- package/dist/src/manager/team-orchestrator.d.ts +3 -2
- package/dist/src/manager/team-orchestrator.js +32 -9
- package/dist/src/plugin/agent-hierarchy.d.ts +1 -54
- package/dist/src/plugin/agent-hierarchy.js +2 -123
- package/dist/src/plugin/agents/browser-qa.d.ts +14 -0
- package/dist/src/plugin/agents/browser-qa.js +27 -0
- package/dist/src/plugin/agents/common.d.ts +37 -0
- package/dist/src/plugin/agents/common.js +59 -0
- package/dist/src/plugin/agents/cto.d.ts +9 -0
- package/dist/src/plugin/agents/cto.js +39 -0
- package/dist/src/plugin/agents/engineers.d.ts +9 -0
- package/dist/src/plugin/agents/engineers.js +11 -0
- package/dist/src/plugin/agents/index.d.ts +6 -0
- package/dist/src/plugin/agents/index.js +5 -0
- package/dist/src/plugin/agents/team-planner.d.ts +10 -0
- package/dist/src/plugin/agents/team-planner.js +23 -0
- package/dist/src/plugin/claude-manager.plugin.js +45 -23
- package/dist/src/plugin/service-factory.d.ts +4 -3
- package/dist/src/plugin/service-factory.js +4 -1
- package/dist/src/prompts/registry.js +37 -2
- package/dist/src/team/roster.d.ts +3 -2
- package/dist/src/team/roster.js +2 -1
- package/dist/src/types/contracts.d.ts +25 -1
- package/dist/src/types/contracts.js +2 -1
- package/dist/team/roster.d.ts +3 -2
- package/dist/team/roster.js +2 -1
- package/dist/test/claude-manager.plugin.test.js +60 -0
- package/dist/test/prompt-registry.test.js +15 -0
- package/dist/test/report-claude-event.test.js +44 -3
- package/dist/test/team-orchestrator.test.js +47 -8
- package/dist/types/contracts.d.ts +25 -1
- package/dist/types/contracts.js +2 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import type { ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapt
|
|
|
2
2
|
import type { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
3
|
import type { TeamStateStore } from '../state/team-state-store.js';
|
|
4
4
|
import type { TranscriptStore } from '../state/transcript-store.js';
|
|
5
|
-
import type { EngineerFailureResult, EngineerName, EngineerTaskResult, EngineerWorkMode, SynthesizedPlanResult, TeamRecord } from '../types/contracts.js';
|
|
5
|
+
import type { EngineerFailureResult, EngineerName, EngineerTaskResult, EngineerWorkMode, SynthesizedPlanResult, TeamRecord, WorkerCapabilities } from '../types/contracts.js';
|
|
6
6
|
interface DispatchEngineerInput {
|
|
7
7
|
teamId: string;
|
|
8
8
|
cwd: string;
|
|
@@ -19,7 +19,8 @@ export declare class TeamOrchestrator {
|
|
|
19
19
|
private readonly transcriptStore;
|
|
20
20
|
private readonly engineerSessionPrompt;
|
|
21
21
|
private readonly planSynthesisPrompt;
|
|
22
|
-
|
|
22
|
+
private readonly workerCapabilities;
|
|
23
|
+
constructor(sessions: ClaudeSessionService, teamStore: TeamStateStore, transcriptStore: TranscriptStore, engineerSessionPrompt: string, planSynthesisPrompt: string, workerCapabilities: Partial<Record<EngineerName, WorkerCapabilities>>);
|
|
23
24
|
getOrCreateTeam(cwd: string, teamId: string): Promise<TeamRecord>;
|
|
24
25
|
listTeams(cwd: string): Promise<TeamRecord[]>;
|
|
25
26
|
recordWrapperSession(cwd: string, teamId: string, engineer: EngineerName, wrapperSessionId: string): Promise<void>;
|
|
@@ -7,12 +7,14 @@ export class TeamOrchestrator {
|
|
|
7
7
|
transcriptStore;
|
|
8
8
|
engineerSessionPrompt;
|
|
9
9
|
planSynthesisPrompt;
|
|
10
|
-
|
|
10
|
+
workerCapabilities;
|
|
11
|
+
constructor(sessions, teamStore, transcriptStore, engineerSessionPrompt, planSynthesisPrompt, workerCapabilities) {
|
|
11
12
|
this.sessions = sessions;
|
|
12
13
|
this.teamStore = teamStore;
|
|
13
14
|
this.transcriptStore = transcriptStore;
|
|
14
15
|
this.engineerSessionPrompt = engineerSessionPrompt;
|
|
15
16
|
this.planSynthesisPrompt = planSynthesisPrompt;
|
|
17
|
+
this.workerCapabilities = workerCapabilities;
|
|
16
18
|
}
|
|
17
19
|
async getOrCreateTeam(cwd, teamId) {
|
|
18
20
|
const existing = await this.teamStore.getTeam(cwd, teamId);
|
|
@@ -89,6 +91,13 @@ export class TeamOrchestrator {
|
|
|
89
91
|
}));
|
|
90
92
|
}
|
|
91
93
|
async dispatchEngineer(input, retryCount = 0) {
|
|
94
|
+
const workerCaps = this.workerCapabilities[input.engineer];
|
|
95
|
+
// Reject write-restricted workers in implement mode
|
|
96
|
+
if (workerCaps?.restrictWriteTools && input.mode === 'implement') {
|
|
97
|
+
throw new Error(`${input.engineer} is a browser QA specialist and does not support implement mode. ` +
|
|
98
|
+
'It can only verify and explore (test browser interactions via Playwright). ' +
|
|
99
|
+
'For code changes, use a general engineer (Tom, John, Maya, Sara, Alex).');
|
|
100
|
+
}
|
|
92
101
|
const team = await this.getOrCreateTeam(input.cwd, input.teamId);
|
|
93
102
|
const engineerState = this.getEngineerState(team, input.engineer);
|
|
94
103
|
await this.reserveEngineer(input.cwd, input.teamId, input.engineer);
|
|
@@ -107,15 +116,19 @@ export class TeamOrchestrator {
|
|
|
107
116
|
const result = await this.sessions.runTask({
|
|
108
117
|
cwd: input.cwd,
|
|
109
118
|
prompt: engineerState.claudeSessionId
|
|
110
|
-
? this.buildEngineerPrompt(input.mode, input.message)
|
|
111
|
-
: `${this.buildSessionSystemPrompt(input.engineer, input.mode)}\n\n${this.buildEngineerPrompt(input.mode, input.message)}`,
|
|
119
|
+
? this.buildEngineerPrompt(input.mode, input.message, input.engineer)
|
|
120
|
+
: `${this.buildSessionSystemPrompt(input.engineer, input.mode)}\n\n${this.buildEngineerPrompt(input.mode, input.message, input.engineer)}`,
|
|
112
121
|
resumeSessionId: engineerState.claudeSessionId ?? undefined,
|
|
113
122
|
persistSession: true,
|
|
114
123
|
includePartialMessages: true,
|
|
115
124
|
permissionMode: 'acceptEdits',
|
|
116
|
-
restrictWriteTools: input.mode === 'explore',
|
|
125
|
+
restrictWriteTools: input.mode === 'explore' || (workerCaps?.restrictWriteTools ?? false),
|
|
117
126
|
model: input.model,
|
|
118
|
-
effort:
|
|
127
|
+
effort: (workerCaps?.restrictWriteTools ?? false)
|
|
128
|
+
? 'medium'
|
|
129
|
+
: input.mode === 'implement'
|
|
130
|
+
? 'high'
|
|
131
|
+
: 'medium',
|
|
119
132
|
settingSources: ['user', 'project', 'local'],
|
|
120
133
|
abortSignal: input.abortSignal,
|
|
121
134
|
}, input.onEvent);
|
|
@@ -321,9 +334,10 @@ export class TeamOrchestrator {
|
|
|
321
334
|
}
|
|
322
335
|
normalizeTeamRecord(team) {
|
|
323
336
|
const engineerMap = new Map(team.engineers.map((engineer) => [engineer.name, engineer]));
|
|
337
|
+
const emptyTeam = createEmptyTeamRecord(team.id, team.cwd);
|
|
324
338
|
return {
|
|
325
339
|
...team,
|
|
326
|
-
engineers:
|
|
340
|
+
engineers: emptyTeam.engineers.map((engineer) => engineerMap.get(engineer.name) ?? engineer),
|
|
327
341
|
};
|
|
328
342
|
}
|
|
329
343
|
getAvailableEngineers(team) {
|
|
@@ -355,9 +369,11 @@ export class TeamOrchestrator {
|
|
|
355
369
|
}
|
|
356
370
|
async selectPlanEngineers(cwd, teamId, preferredLead, preferredChallenger) {
|
|
357
371
|
const team = await this.getOrCreateTeam(cwd, teamId);
|
|
358
|
-
const
|
|
372
|
+
const allAvailable = this.getAvailableEngineers(team);
|
|
373
|
+
// Filter to only planner-eligible engineers (specialists with plannerEligible: false are excluded)
|
|
374
|
+
const available = allAvailable.filter((e) => this.workerCapabilities[e]?.plannerEligible !== false);
|
|
359
375
|
if (available.length < 2) {
|
|
360
|
-
throw new Error(`Not enough available engineers for dual planning. Need 2, found ${available.length}.`);
|
|
376
|
+
throw new Error(`Not enough available engineers for dual planning. Need 2 general engineers (specialists excluded), found ${available.length}.`);
|
|
361
377
|
}
|
|
362
378
|
const lead = preferredLead ?? available[0];
|
|
363
379
|
const foundChallenger = preferredChallenger ?? available.find((e) => e !== lead);
|
|
@@ -368,6 +384,10 @@ export class TeamOrchestrator {
|
|
|
368
384
|
return { lead, challenger };
|
|
369
385
|
}
|
|
370
386
|
buildSessionSystemPrompt(engineer, mode) {
|
|
387
|
+
const specialistPrompt = this.workerCapabilities[engineer]?.sessionPrompt;
|
|
388
|
+
if (specialistPrompt) {
|
|
389
|
+
return specialistPrompt;
|
|
390
|
+
}
|
|
371
391
|
return [
|
|
372
392
|
this.engineerSessionPrompt,
|
|
373
393
|
'',
|
|
@@ -377,7 +397,10 @@ export class TeamOrchestrator {
|
|
|
377
397
|
.join('\n')
|
|
378
398
|
.trim();
|
|
379
399
|
}
|
|
380
|
-
buildEngineerPrompt(mode, message) {
|
|
400
|
+
buildEngineerPrompt(mode, message, engineer) {
|
|
401
|
+
if (this.workerCapabilities[engineer]?.skipModeInstructions) {
|
|
402
|
+
return message;
|
|
403
|
+
}
|
|
381
404
|
return `${buildModeInstruction(mode)}\n\n${message}`;
|
|
382
405
|
}
|
|
383
406
|
}
|
|
@@ -1,54 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const AGENT_CTO = "cto";
|
|
3
|
-
export declare const AGENT_TEAM_PLANNER = "team-planner";
|
|
4
|
-
export declare const ENGINEER_AGENT_IDS: {
|
|
5
|
-
readonly Tom: "tom";
|
|
6
|
-
readonly John: "john";
|
|
7
|
-
readonly Maya: "maya";
|
|
8
|
-
readonly Sara: "sara";
|
|
9
|
-
readonly Alex: "alex";
|
|
10
|
-
};
|
|
11
|
-
export declare const ENGINEER_AGENT_NAMES: readonly ["Tom", "John", "Maya", "Sara", "Alex"];
|
|
12
|
-
type ToolPermission = 'allow' | 'ask' | 'deny';
|
|
13
|
-
type AgentPermission = {
|
|
14
|
-
'*'?: ToolPermission;
|
|
15
|
-
read?: ToolPermission;
|
|
16
|
-
grep?: ToolPermission;
|
|
17
|
-
glob?: ToolPermission;
|
|
18
|
-
list?: ToolPermission;
|
|
19
|
-
codesearch?: ToolPermission;
|
|
20
|
-
webfetch?: ToolPermission;
|
|
21
|
-
websearch?: ToolPermission;
|
|
22
|
-
lsp?: ToolPermission;
|
|
23
|
-
todowrite?: ToolPermission;
|
|
24
|
-
todoread?: ToolPermission;
|
|
25
|
-
question?: ToolPermission;
|
|
26
|
-
task?: ToolPermission | Record<string, ToolPermission>;
|
|
27
|
-
bash?: ToolPermission | Record<string, ToolPermission>;
|
|
28
|
-
[tool: string]: ToolPermission | Record<string, ToolPermission> | undefined;
|
|
29
|
-
};
|
|
30
|
-
export declare function buildCtoAgentConfig(prompts: ManagerPromptRegistry): {
|
|
31
|
-
description: string;
|
|
32
|
-
mode: "primary";
|
|
33
|
-
color: string;
|
|
34
|
-
permission: AgentPermission;
|
|
35
|
-
prompt: string;
|
|
36
|
-
};
|
|
37
|
-
export declare function buildEngineerAgentConfig(prompts: ManagerPromptRegistry, engineer: EngineerName): {
|
|
38
|
-
description: string;
|
|
39
|
-
mode: "subagent";
|
|
40
|
-
hidden: boolean;
|
|
41
|
-
color: string;
|
|
42
|
-
permission: AgentPermission;
|
|
43
|
-
prompt: string;
|
|
44
|
-
};
|
|
45
|
-
export declare function buildTeamPlannerAgentConfig(prompts: ManagerPromptRegistry): {
|
|
46
|
-
description: string;
|
|
47
|
-
mode: "subagent";
|
|
48
|
-
hidden: boolean;
|
|
49
|
-
color: string;
|
|
50
|
-
permission: AgentPermission;
|
|
51
|
-
prompt: string;
|
|
52
|
-
};
|
|
53
|
-
export declare function denyRestrictedToolsGlobally(permissions: Record<string, ToolPermission>): void;
|
|
54
|
-
export {};
|
|
1
|
+
export * from './agents/index.js';
|
|
@@ -1,123 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export const AGENT_TEAM_PLANNER = 'team-planner';
|
|
4
|
-
export const ENGINEER_AGENT_IDS = {
|
|
5
|
-
Tom: 'tom',
|
|
6
|
-
John: 'john',
|
|
7
|
-
Maya: 'maya',
|
|
8
|
-
Sara: 'sara',
|
|
9
|
-
Alex: 'alex',
|
|
10
|
-
};
|
|
11
|
-
export const ENGINEER_AGENT_NAMES = TEAM_ENGINEERS;
|
|
12
|
-
const CTO_ONLY_TOOL_IDS = [
|
|
13
|
-
'team_status',
|
|
14
|
-
'reset_engineer',
|
|
15
|
-
'list_transcripts',
|
|
16
|
-
'list_history',
|
|
17
|
-
'git_diff',
|
|
18
|
-
'git_commit',
|
|
19
|
-
'git_reset',
|
|
20
|
-
'git_status',
|
|
21
|
-
'git_log',
|
|
22
|
-
'approval_policy',
|
|
23
|
-
'approval_decisions',
|
|
24
|
-
'approval_update',
|
|
25
|
-
];
|
|
26
|
-
const ENGINEER_TOOL_IDS = ['claude'];
|
|
27
|
-
const ALL_RESTRICTED_TOOL_IDS = [...CTO_ONLY_TOOL_IDS, ...ENGINEER_TOOL_IDS];
|
|
28
|
-
const CTO_READONLY_TOOLS = {
|
|
29
|
-
read: 'allow',
|
|
30
|
-
grep: 'allow',
|
|
31
|
-
glob: 'allow',
|
|
32
|
-
list: 'allow',
|
|
33
|
-
codesearch: 'allow',
|
|
34
|
-
webfetch: 'allow',
|
|
35
|
-
websearch: 'allow',
|
|
36
|
-
lsp: 'allow',
|
|
37
|
-
todowrite: 'allow',
|
|
38
|
-
todoread: 'allow',
|
|
39
|
-
question: 'allow',
|
|
40
|
-
};
|
|
41
|
-
function buildTeamPlannerPermissions() {
|
|
42
|
-
const denied = {};
|
|
43
|
-
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
44
|
-
denied[toolId] = 'deny';
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
'*': 'deny',
|
|
48
|
-
plan_with_team: 'allow',
|
|
49
|
-
question: 'allow',
|
|
50
|
-
...denied,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function buildCtoPermissions() {
|
|
54
|
-
const denied = {};
|
|
55
|
-
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
56
|
-
denied[toolId] = 'deny';
|
|
57
|
-
}
|
|
58
|
-
const allowed = {};
|
|
59
|
-
for (const toolId of CTO_ONLY_TOOL_IDS) {
|
|
60
|
-
allowed[toolId] = 'allow';
|
|
61
|
-
}
|
|
62
|
-
const taskPermissions = { '*': 'deny' };
|
|
63
|
-
for (const engineer of ENGINEER_AGENT_NAMES) {
|
|
64
|
-
const agentId = ENGINEER_AGENT_IDS[engineer];
|
|
65
|
-
// Support both uppercase (user-friendly) and lowercase (canonical) agent IDs.
|
|
66
|
-
// This ensures both task({ subagent_type: 'Tom' }) and task({ subagent_type: 'tom' }) work.
|
|
67
|
-
taskPermissions[engineer] = 'allow'; // 'Tom', 'John', etc.
|
|
68
|
-
taskPermissions[agentId] = 'allow'; // 'tom', 'john', etc.
|
|
69
|
-
}
|
|
70
|
-
taskPermissions[AGENT_TEAM_PLANNER] = 'allow';
|
|
71
|
-
return {
|
|
72
|
-
'*': 'deny',
|
|
73
|
-
...CTO_READONLY_TOOLS,
|
|
74
|
-
...denied,
|
|
75
|
-
...allowed,
|
|
76
|
-
task: taskPermissions,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function buildEngineerPermissions() {
|
|
80
|
-
const denied = {};
|
|
81
|
-
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
82
|
-
denied[toolId] = 'deny';
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
'*': 'deny',
|
|
86
|
-
...denied,
|
|
87
|
-
claude: 'allow',
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
export function buildCtoAgentConfig(prompts) {
|
|
91
|
-
return {
|
|
92
|
-
description: 'Principal engineer who orchestrates AI-powered engineers. Decomposes work, asks clarifying questions, delegates precisely, reviews diffs, and owns the outcome.',
|
|
93
|
-
mode: 'primary',
|
|
94
|
-
color: '#D97757',
|
|
95
|
-
permission: buildCtoPermissions(),
|
|
96
|
-
prompt: prompts.ctoSystemPrompt,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
export function buildEngineerAgentConfig(prompts, engineer) {
|
|
100
|
-
return {
|
|
101
|
-
description: `${engineer} is a persistent engineer who works through one Claude Code session and remembers prior turns. Receives structured assignments (goal, mode, context, acceptance criteria, relevant paths, constraints, verification).`,
|
|
102
|
-
mode: 'subagent',
|
|
103
|
-
hidden: false,
|
|
104
|
-
color: '#D97757',
|
|
105
|
-
permission: buildEngineerPermissions(),
|
|
106
|
-
prompt: `You are ${engineer}.\n\n${prompts.engineerAgentPrompt}`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
export function buildTeamPlannerAgentConfig(prompts) {
|
|
110
|
-
return {
|
|
111
|
-
description: 'Runs dual-engineer planning by calling plan_with_team. Automatically selects two non-overlapping available engineers if engineer names are not provided.',
|
|
112
|
-
mode: 'subagent',
|
|
113
|
-
hidden: false,
|
|
114
|
-
color: '#D97757',
|
|
115
|
-
permission: buildTeamPlannerPermissions(),
|
|
116
|
-
prompt: prompts.teamPlannerPrompt,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
export function denyRestrictedToolsGlobally(permissions) {
|
|
120
|
-
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
121
|
-
permissions[toolId] ??= 'deny';
|
|
122
|
-
}
|
|
123
|
-
}
|
|
1
|
+
// Re-export barrel — all symbols now live in src/plugin/agents/.
|
|
2
|
+
export * from './agents/index.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EngineerName, ManagerPromptRegistry, WorkerCapabilities } from '../../types/contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the worker capabilities map for all specialist workers.
|
|
4
|
+
* Called once at service-factory construction time to avoid re-building on each tool call.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildWorkerCapabilities(prompts: ManagerPromptRegistry): Partial<Record<EngineerName, WorkerCapabilities>>;
|
|
7
|
+
export declare function buildBrowserQaAgentConfig(prompts: ManagerPromptRegistry): {
|
|
8
|
+
description: string;
|
|
9
|
+
mode: "subagent";
|
|
10
|
+
hidden: boolean;
|
|
11
|
+
color: string;
|
|
12
|
+
permission: import("./common.js").AgentPermission;
|
|
13
|
+
prompt: string;
|
|
14
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildEngineerPermissions } from './common.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the worker capabilities map for all specialist workers.
|
|
4
|
+
* Called once at service-factory construction time to avoid re-building on each tool call.
|
|
5
|
+
*/
|
|
6
|
+
export function buildWorkerCapabilities(prompts) {
|
|
7
|
+
return {
|
|
8
|
+
BrowserQA: {
|
|
9
|
+
sessionPrompt: prompts.browserQaSessionPrompt,
|
|
10
|
+
restrictWriteTools: true,
|
|
11
|
+
skipModeInstructions: true,
|
|
12
|
+
plannerEligible: false,
|
|
13
|
+
isRuntimeUnavailableResponse: (text) => text.trimStart().startsWith('PLAYWRIGHT_UNAVAILABLE:'),
|
|
14
|
+
runtimeUnavailableTitle: '❌ Playwright unavailable',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function buildBrowserQaAgentConfig(prompts) {
|
|
19
|
+
return {
|
|
20
|
+
description: 'Browser QA specialist who uses the Playwright skill/command to test web features and user flows. Maintains a persistent Claude Code session that remembers prior verification runs.',
|
|
21
|
+
mode: 'subagent',
|
|
22
|
+
hidden: false,
|
|
23
|
+
color: '#D97757',
|
|
24
|
+
permission: buildEngineerPermissions(), // Same permissions as engineers (claude tool only)
|
|
25
|
+
prompt: prompts.browserQaAgentPrompt,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const AGENT_CTO = "cto";
|
|
2
|
+
export declare const AGENT_TEAM_PLANNER = "team-planner";
|
|
3
|
+
export declare const AGENT_BROWSER_QA = "browser-qa";
|
|
4
|
+
export declare const ENGINEER_AGENT_IDS: {
|
|
5
|
+
readonly Tom: "tom";
|
|
6
|
+
readonly John: "john";
|
|
7
|
+
readonly Maya: "maya";
|
|
8
|
+
readonly Sara: "sara";
|
|
9
|
+
readonly Alex: "alex";
|
|
10
|
+
readonly BrowserQA: "browser-qa";
|
|
11
|
+
};
|
|
12
|
+
/** General named engineers only (Tom/John/Maya/Sara/Alex). BrowserQA is a specialist registered separately. */
|
|
13
|
+
export declare const ENGINEER_AGENT_NAMES: readonly ["Tom", "John", "Maya", "Sara", "Alex"];
|
|
14
|
+
export declare const CTO_ONLY_TOOL_IDS: readonly ["team_status", "reset_engineer", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update"];
|
|
15
|
+
export declare const ENGINEER_TOOL_IDS: readonly ["claude"];
|
|
16
|
+
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["team_status", "reset_engineer", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update", "claude"];
|
|
17
|
+
export type ToolPermission = 'allow' | 'ask' | 'deny';
|
|
18
|
+
export type AgentPermission = {
|
|
19
|
+
'*'?: ToolPermission;
|
|
20
|
+
read?: ToolPermission;
|
|
21
|
+
grep?: ToolPermission;
|
|
22
|
+
glob?: ToolPermission;
|
|
23
|
+
list?: ToolPermission;
|
|
24
|
+
codesearch?: ToolPermission;
|
|
25
|
+
webfetch?: ToolPermission;
|
|
26
|
+
websearch?: ToolPermission;
|
|
27
|
+
lsp?: ToolPermission;
|
|
28
|
+
todowrite?: ToolPermission;
|
|
29
|
+
todoread?: ToolPermission;
|
|
30
|
+
question?: ToolPermission;
|
|
31
|
+
task?: ToolPermission | Record<string, ToolPermission>;
|
|
32
|
+
bash?: ToolPermission | Record<string, ToolPermission>;
|
|
33
|
+
[tool: string]: ToolPermission | Record<string, ToolPermission> | undefined;
|
|
34
|
+
};
|
|
35
|
+
export declare const CTO_READONLY_TOOLS: Record<string, ToolPermission>;
|
|
36
|
+
export declare function buildEngineerPermissions(): AgentPermission;
|
|
37
|
+
export declare function denyRestrictedToolsGlobally(permissions: Record<string, ToolPermission>): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { PLANNER_ELIGIBLE_ENGINEERS } from '../../team/roster.js';
|
|
2
|
+
export const AGENT_CTO = 'cto';
|
|
3
|
+
export const AGENT_TEAM_PLANNER = 'team-planner';
|
|
4
|
+
export const AGENT_BROWSER_QA = 'browser-qa';
|
|
5
|
+
export const ENGINEER_AGENT_IDS = {
|
|
6
|
+
Tom: 'tom',
|
|
7
|
+
John: 'john',
|
|
8
|
+
Maya: 'maya',
|
|
9
|
+
Sara: 'sara',
|
|
10
|
+
Alex: 'alex',
|
|
11
|
+
BrowserQA: 'browser-qa',
|
|
12
|
+
};
|
|
13
|
+
/** General named engineers only (Tom/John/Maya/Sara/Alex). BrowserQA is a specialist registered separately. */
|
|
14
|
+
export const ENGINEER_AGENT_NAMES = PLANNER_ELIGIBLE_ENGINEERS;
|
|
15
|
+
export const CTO_ONLY_TOOL_IDS = [
|
|
16
|
+
'team_status',
|
|
17
|
+
'reset_engineer',
|
|
18
|
+
'list_transcripts',
|
|
19
|
+
'list_history',
|
|
20
|
+
'git_diff',
|
|
21
|
+
'git_commit',
|
|
22
|
+
'git_reset',
|
|
23
|
+
'git_status',
|
|
24
|
+
'git_log',
|
|
25
|
+
'approval_policy',
|
|
26
|
+
'approval_decisions',
|
|
27
|
+
'approval_update',
|
|
28
|
+
];
|
|
29
|
+
export const ENGINEER_TOOL_IDS = ['claude'];
|
|
30
|
+
export const ALL_RESTRICTED_TOOL_IDS = [...CTO_ONLY_TOOL_IDS, ...ENGINEER_TOOL_IDS];
|
|
31
|
+
export const CTO_READONLY_TOOLS = {
|
|
32
|
+
read: 'allow',
|
|
33
|
+
grep: 'allow',
|
|
34
|
+
glob: 'allow',
|
|
35
|
+
list: 'allow',
|
|
36
|
+
codesearch: 'allow',
|
|
37
|
+
webfetch: 'allow',
|
|
38
|
+
websearch: 'allow',
|
|
39
|
+
lsp: 'allow',
|
|
40
|
+
todowrite: 'allow',
|
|
41
|
+
todoread: 'allow',
|
|
42
|
+
question: 'allow',
|
|
43
|
+
};
|
|
44
|
+
export function buildEngineerPermissions() {
|
|
45
|
+
const denied = {};
|
|
46
|
+
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
47
|
+
denied[toolId] = 'deny';
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
'*': 'deny',
|
|
51
|
+
...denied,
|
|
52
|
+
claude: 'allow',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function denyRestrictedToolsGlobally(permissions) {
|
|
56
|
+
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
57
|
+
permissions[toolId] ??= 'deny';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ManagerPromptRegistry } from '../../types/contracts.js';
|
|
2
|
+
import type { AgentPermission } from './common.js';
|
|
3
|
+
export declare function buildCtoAgentConfig(prompts: ManagerPromptRegistry): {
|
|
4
|
+
description: string;
|
|
5
|
+
mode: "primary";
|
|
6
|
+
color: string;
|
|
7
|
+
permission: AgentPermission;
|
|
8
|
+
prompt: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AGENT_BROWSER_QA, AGENT_TEAM_PLANNER, ALL_RESTRICTED_TOOL_IDS, CTO_ONLY_TOOL_IDS, CTO_READONLY_TOOLS, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, } from './common.js';
|
|
2
|
+
function buildCtoPermissions() {
|
|
3
|
+
const denied = {};
|
|
4
|
+
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
5
|
+
denied[toolId] = 'deny';
|
|
6
|
+
}
|
|
7
|
+
const allowed = {};
|
|
8
|
+
for (const toolId of CTO_ONLY_TOOL_IDS) {
|
|
9
|
+
allowed[toolId] = 'allow';
|
|
10
|
+
}
|
|
11
|
+
const taskPermissions = { '*': 'deny' };
|
|
12
|
+
for (const engineer of ENGINEER_AGENT_NAMES) {
|
|
13
|
+
const agentId = ENGINEER_AGENT_IDS[engineer];
|
|
14
|
+
// Support both uppercase (user-friendly) and lowercase (canonical) agent IDs.
|
|
15
|
+
// This ensures both task({ subagent_type: 'Tom' }) and task({ subagent_type: 'tom' }) work.
|
|
16
|
+
taskPermissions[engineer] = 'allow'; // 'Tom', 'John', etc.
|
|
17
|
+
taskPermissions[agentId] = 'allow'; // 'tom', 'john', etc.
|
|
18
|
+
}
|
|
19
|
+
// BrowserQA is a specialist registered separately from ENGINEER_AGENT_NAMES; add both forms.
|
|
20
|
+
taskPermissions['BrowserQA'] = 'allow'; // uppercase (user-friendly)
|
|
21
|
+
taskPermissions[AGENT_BROWSER_QA] = 'allow'; // 'browser-qa' canonical
|
|
22
|
+
taskPermissions[AGENT_TEAM_PLANNER] = 'allow';
|
|
23
|
+
return {
|
|
24
|
+
'*': 'deny',
|
|
25
|
+
...CTO_READONLY_TOOLS,
|
|
26
|
+
...denied,
|
|
27
|
+
...allowed,
|
|
28
|
+
task: taskPermissions,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function buildCtoAgentConfig(prompts) {
|
|
32
|
+
return {
|
|
33
|
+
description: 'Principal engineer who orchestrates AI-powered engineers. Decomposes work, asks clarifying questions, delegates precisely, reviews diffs, and owns the outcome.',
|
|
34
|
+
mode: 'primary',
|
|
35
|
+
color: '#D97757',
|
|
36
|
+
permission: buildCtoPermissions(),
|
|
37
|
+
prompt: prompts.ctoSystemPrompt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EngineerName, ManagerPromptRegistry } from '../../types/contracts.js';
|
|
2
|
+
export declare function buildEngineerAgentConfig(prompts: ManagerPromptRegistry, engineer: EngineerName): {
|
|
3
|
+
description: string;
|
|
4
|
+
mode: "subagent";
|
|
5
|
+
hidden: boolean;
|
|
6
|
+
color: string;
|
|
7
|
+
permission: import("./common.js").AgentPermission;
|
|
8
|
+
prompt: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { buildEngineerPermissions } from './common.js';
|
|
2
|
+
export function buildEngineerAgentConfig(prompts, engineer) {
|
|
3
|
+
return {
|
|
4
|
+
description: `${engineer} is a persistent engineer who works through one Claude Code session and remembers prior turns. Receives structured assignments (goal, mode, context, acceptance criteria, relevant paths, constraints, verification).`,
|
|
5
|
+
mode: 'subagent',
|
|
6
|
+
hidden: false,
|
|
7
|
+
color: '#D97757',
|
|
8
|
+
permission: buildEngineerPermissions(),
|
|
9
|
+
prompt: `You are ${engineer}.\n\n${prompts.engineerAgentPrompt}`,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AGENT_BROWSER_QA, AGENT_CTO, AGENT_TEAM_PLANNER, ALL_RESTRICTED_TOOL_IDS, buildEngineerPermissions, CTO_ONLY_TOOL_IDS, CTO_READONLY_TOOLS, denyRestrictedToolsGlobally, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, ENGINEER_TOOL_IDS, } from './common.js';
|
|
2
|
+
export type { AgentPermission, ToolPermission } from './common.js';
|
|
3
|
+
export { buildCtoAgentConfig } from './cto.js';
|
|
4
|
+
export { buildTeamPlannerAgentConfig } from './team-planner.js';
|
|
5
|
+
export { buildEngineerAgentConfig } from './engineers.js';
|
|
6
|
+
export { buildBrowserQaAgentConfig, buildWorkerCapabilities } from './browser-qa.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AGENT_BROWSER_QA, AGENT_CTO, AGENT_TEAM_PLANNER, ALL_RESTRICTED_TOOL_IDS, buildEngineerPermissions, CTO_ONLY_TOOL_IDS, CTO_READONLY_TOOLS, denyRestrictedToolsGlobally, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, ENGINEER_TOOL_IDS, } from './common.js';
|
|
2
|
+
export { buildCtoAgentConfig } from './cto.js';
|
|
3
|
+
export { buildTeamPlannerAgentConfig } from './team-planner.js';
|
|
4
|
+
export { buildEngineerAgentConfig } from './engineers.js';
|
|
5
|
+
export { buildBrowserQaAgentConfig, buildWorkerCapabilities } from './browser-qa.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ManagerPromptRegistry } from '../../types/contracts.js';
|
|
2
|
+
import type { AgentPermission } from './common.js';
|
|
3
|
+
export declare function buildTeamPlannerAgentConfig(prompts: ManagerPromptRegistry): {
|
|
4
|
+
description: string;
|
|
5
|
+
mode: "subagent";
|
|
6
|
+
hidden: boolean;
|
|
7
|
+
color: string;
|
|
8
|
+
permission: AgentPermission;
|
|
9
|
+
prompt: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ALL_RESTRICTED_TOOL_IDS } from './common.js';
|
|
2
|
+
function buildTeamPlannerPermissions() {
|
|
3
|
+
const denied = {};
|
|
4
|
+
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
5
|
+
denied[toolId] = 'deny';
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
'*': 'deny',
|
|
9
|
+
plan_with_team: 'allow',
|
|
10
|
+
question: 'allow',
|
|
11
|
+
...denied,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function buildTeamPlannerAgentConfig(prompts) {
|
|
15
|
+
return {
|
|
16
|
+
description: 'Runs dual-engineer planning by calling plan_with_team. Automatically selects two non-overlapping available engineers if engineer names are not provided.',
|
|
17
|
+
mode: 'subagent',
|
|
18
|
+
hidden: false,
|
|
19
|
+
color: '#D97757',
|
|
20
|
+
permission: buildTeamPlannerPermissions(),
|
|
21
|
+
prompt: prompts.teamPlannerPrompt,
|
|
22
|
+
};
|
|
23
|
+
}
|