@doingdev/opencode-claude-manager-plugin 0.1.49 → 0.1.51
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/claude/claude-agent-sdk-adapter.d.ts +2 -3
- package/dist/claude/claude-agent-sdk-adapter.js +38 -48
- package/dist/claude/claude-session.service.d.ts +1 -2
- package/dist/claude/claude-session.service.js +0 -3
- package/dist/claude/tool-approval-manager.d.ts +9 -6
- package/dist/claude/tool-approval-manager.js +43 -6
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/manager/context-tracker.d.ts +0 -1
- package/dist/manager/context-tracker.js +0 -3
- package/dist/manager/git-operations.d.ts +1 -4
- package/dist/manager/git-operations.js +7 -12
- package/dist/manager/persistent-manager.d.ts +3 -53
- package/dist/manager/persistent-manager.js +3 -135
- package/dist/manager/team-orchestrator.d.ts +9 -4
- package/dist/manager/team-orchestrator.js +84 -31
- package/dist/plugin/agent-hierarchy.d.ts +0 -1
- package/dist/plugin/agent-hierarchy.js +4 -2
- package/dist/plugin/claude-manager.plugin.js +170 -24
- package/dist/plugin/service-factory.d.ts +5 -6
- package/dist/plugin/service-factory.js +9 -17
- package/dist/prompts/registry.js +58 -39
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +2 -3
- package/dist/src/claude/claude-agent-sdk-adapter.js +38 -48
- package/dist/src/claude/claude-session.service.d.ts +1 -2
- package/dist/src/claude/claude-session.service.js +0 -3
- package/dist/src/claude/tool-approval-manager.d.ts +9 -6
- package/dist/src/claude/tool-approval-manager.js +43 -6
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +0 -1
- package/dist/src/manager/context-tracker.d.ts +0 -1
- package/dist/src/manager/context-tracker.js +0 -3
- package/dist/src/manager/git-operations.d.ts +1 -4
- package/dist/src/manager/git-operations.js +7 -12
- package/dist/src/manager/persistent-manager.d.ts +3 -53
- package/dist/src/manager/persistent-manager.js +3 -135
- package/dist/src/manager/team-orchestrator.d.ts +9 -4
- package/dist/src/manager/team-orchestrator.js +84 -31
- package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
- package/dist/src/plugin/agent-hierarchy.js +4 -2
- package/dist/src/plugin/claude-manager.plugin.js +170 -24
- package/dist/src/plugin/service-factory.d.ts +5 -6
- package/dist/src/plugin/service-factory.js +9 -17
- package/dist/src/prompts/registry.js +58 -39
- package/dist/src/state/team-state-store.js +4 -1
- package/dist/src/team/roster.js +1 -0
- package/dist/src/types/contracts.d.ts +18 -57
- package/dist/state/team-state-store.js +4 -1
- package/dist/team/roster.js +1 -0
- package/dist/test/claude-agent-sdk-adapter.test.js +103 -11
- package/dist/test/claude-manager.plugin.test.js +6 -1
- package/dist/test/context-tracker.test.js +0 -8
- package/dist/test/git-operations.test.js +0 -21
- package/dist/test/persistent-manager.test.js +4 -164
- package/dist/test/prompt-registry.test.js +4 -9
- package/dist/test/report-claude-event.test.js +4 -4
- package/dist/test/team-orchestrator.test.js +7 -5
- package/dist/test/tool-approval-manager.test.js +17 -17
- package/dist/types/contracts.d.ts +18 -57
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { tool } from '@opencode-ai/plugin';
|
|
2
2
|
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
3
3
|
import { isEngineerName } from '../team/roster.js';
|
|
4
|
-
import {
|
|
4
|
+
import { TeamOrchestrator } from '../manager/team-orchestrator.js';
|
|
5
5
|
import { AGENT_CTO, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, buildCtoAgentConfig, buildEngineerAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
|
|
6
6
|
import { getActiveTeamSession, getOrCreatePluginServices, getPersistedActiveTeam, getWrapperSessionMapping, setActiveTeamSession, setPersistedActiveTeam, setWrapperSessionMapping, } from './service-factory.js';
|
|
7
7
|
const MODEL_ENUM = ['claude-opus-4-6', 'claude-sonnet-4-6'];
|
|
8
8
|
const MODE_ENUM = ['explore', 'implement', 'verify'];
|
|
9
9
|
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
const services = getOrCreatePluginServices(worktree);
|
|
11
|
+
await services.approvalManager.loadPersistedPolicy();
|
|
12
12
|
return {
|
|
13
13
|
config: async (config) => {
|
|
14
14
|
config.agent ??= {};
|
|
@@ -103,6 +103,69 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
103
103
|
return JSON.stringify(team, null, 2);
|
|
104
104
|
},
|
|
105
105
|
}),
|
|
106
|
+
plan_with_team: tool({
|
|
107
|
+
description: 'Run dual-engineer plan synthesis. Two engineers explore in parallel (lead + challenger), then their plans are synthesized into one stronger plan.',
|
|
108
|
+
args: {
|
|
109
|
+
request: tool.schema.string().min(1),
|
|
110
|
+
leadEngineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']),
|
|
111
|
+
challengerEngineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']),
|
|
112
|
+
model: tool.schema.enum(MODEL_ENUM).optional(),
|
|
113
|
+
},
|
|
114
|
+
async execute(args, context) {
|
|
115
|
+
const teamId = getActiveTeamSession(context.worktree) ?? context.sessionID;
|
|
116
|
+
annotateToolRun(context, 'Running dual-engineer plan synthesis', {
|
|
117
|
+
teamId,
|
|
118
|
+
lead: args.leadEngineer,
|
|
119
|
+
challenger: args.challengerEngineer,
|
|
120
|
+
});
|
|
121
|
+
const result = await services.orchestrator.planWithTeam({
|
|
122
|
+
teamId,
|
|
123
|
+
cwd: context.worktree,
|
|
124
|
+
request: args.request,
|
|
125
|
+
leadEngineer: args.leadEngineer,
|
|
126
|
+
challengerEngineer: args.challengerEngineer,
|
|
127
|
+
model: args.model,
|
|
128
|
+
abortSignal: context.abort,
|
|
129
|
+
});
|
|
130
|
+
context.metadata({
|
|
131
|
+
title: '✅ Plan synthesis complete',
|
|
132
|
+
metadata: {
|
|
133
|
+
teamId: result.teamId,
|
|
134
|
+
lead: result.leadEngineer,
|
|
135
|
+
challenger: result.challengerEngineer,
|
|
136
|
+
hasQuestion: result.recommendedQuestion !== null,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
synthesis: result.synthesis,
|
|
141
|
+
recommendedQuestion: result.recommendedQuestion,
|
|
142
|
+
recommendedAnswer: result.recommendedAnswer,
|
|
143
|
+
}, null, 2);
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
reset_engineer: tool({
|
|
147
|
+
description: 'Reset a stuck or corrupted engineer. Clears the busy flag. Optionally clears the Claude session (starts fresh) and/or wrapper history.',
|
|
148
|
+
args: {
|
|
149
|
+
engineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']),
|
|
150
|
+
clearSession: tool.schema.boolean().optional(),
|
|
151
|
+
clearHistory: tool.schema.boolean().optional(),
|
|
152
|
+
},
|
|
153
|
+
async execute(args, context) {
|
|
154
|
+
const teamId = getActiveTeamSession(context.worktree) ?? context.sessionID;
|
|
155
|
+
annotateToolRun(context, `Resetting ${args.engineer}`, {
|
|
156
|
+
teamId,
|
|
157
|
+
clearSession: args.clearSession,
|
|
158
|
+
clearHistory: args.clearHistory,
|
|
159
|
+
});
|
|
160
|
+
await services.orchestrator.resetEngineer(context.worktree, teamId, args.engineer, {
|
|
161
|
+
clearSession: args.clearSession,
|
|
162
|
+
clearHistory: args.clearHistory,
|
|
163
|
+
});
|
|
164
|
+
const team = await services.orchestrator.getOrCreateTeam(context.worktree, teamId);
|
|
165
|
+
const engineer = team.engineers.find((e) => e.name === args.engineer);
|
|
166
|
+
return JSON.stringify({ reset: true, engineer: engineer ?? args.engineer }, null, 2);
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
106
169
|
git_diff: tool({
|
|
107
170
|
description: 'Show diff of uncommitted changes. Use paths to filter to specific files or use ref to compare against another branch, tag, or commit.',
|
|
108
171
|
args: {
|
|
@@ -125,13 +188,17 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
125
188
|
},
|
|
126
189
|
}),
|
|
127
190
|
git_commit: tool({
|
|
128
|
-
description: '
|
|
191
|
+
description: 'Create a commit. Stages all changes by default, or only the specified paths if provided.',
|
|
129
192
|
args: {
|
|
130
193
|
message: tool.schema.string().min(1),
|
|
194
|
+
paths: tool.schema.string().array().optional(),
|
|
131
195
|
},
|
|
132
196
|
async execute(args, context) {
|
|
133
|
-
annotateToolRun(context, 'Committing changes', {
|
|
134
|
-
|
|
197
|
+
annotateToolRun(context, 'Committing changes', {
|
|
198
|
+
message: args.message,
|
|
199
|
+
paths: args.paths,
|
|
200
|
+
});
|
|
201
|
+
const result = await services.manager.gitCommit(args.message, args.paths);
|
|
135
202
|
return JSON.stringify(result, null, 2);
|
|
136
203
|
},
|
|
137
204
|
}),
|
|
@@ -249,7 +316,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
249
316
|
error: 'addRule requires ruleId, toolPattern, and ruleAction',
|
|
250
317
|
});
|
|
251
318
|
}
|
|
252
|
-
services.approvalManager.addRule({
|
|
319
|
+
await services.approvalManager.addRule({
|
|
253
320
|
id: args.ruleId,
|
|
254
321
|
toolPattern: args.toolPattern,
|
|
255
322
|
inputPattern: args.inputPattern,
|
|
@@ -262,20 +329,20 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
262
329
|
if (!args.ruleId) {
|
|
263
330
|
return JSON.stringify({ error: 'removeRule requires ruleId' });
|
|
264
331
|
}
|
|
265
|
-
const removed = services.approvalManager.removeRule(args.ruleId);
|
|
332
|
+
const removed = await services.approvalManager.removeRule(args.ruleId);
|
|
266
333
|
return JSON.stringify({ removed }, null, 2);
|
|
267
334
|
}
|
|
268
335
|
else if (args.action === 'setDefault') {
|
|
269
336
|
if (!args.defaultAction) {
|
|
270
337
|
return JSON.stringify({ error: 'setDefault requires defaultAction' });
|
|
271
338
|
}
|
|
272
|
-
services.approvalManager.setDefaultAction(args.defaultAction);
|
|
339
|
+
await services.approvalManager.setDefaultAction(args.defaultAction);
|
|
273
340
|
}
|
|
274
341
|
else if (args.action === 'setEnabled') {
|
|
275
342
|
if (args.enabled === undefined) {
|
|
276
343
|
return JSON.stringify({ error: 'setEnabled requires enabled' });
|
|
277
344
|
}
|
|
278
|
-
services.approvalManager.setEnabled(args.enabled);
|
|
345
|
+
await services.approvalManager.setEnabled(args.enabled);
|
|
279
346
|
}
|
|
280
347
|
else if (args.action === 'clearDecisions') {
|
|
281
348
|
services.approvalManager.clearDecisions();
|
|
@@ -292,16 +359,35 @@ async function runEngineerAssignment(input, context) {
|
|
|
292
359
|
teamId: input.teamId,
|
|
293
360
|
mode: input.mode,
|
|
294
361
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
362
|
+
let result;
|
|
363
|
+
try {
|
|
364
|
+
result = await services.orchestrator.dispatchEngineer({
|
|
365
|
+
teamId: input.teamId,
|
|
366
|
+
cwd: context.worktree,
|
|
367
|
+
engineer: input.engineer,
|
|
368
|
+
mode: input.mode,
|
|
369
|
+
message: input.message,
|
|
370
|
+
model: input.model,
|
|
371
|
+
abortSignal: context.abort,
|
|
372
|
+
onEvent: (event) => reportClaudeEvent(context, input.engineer, event),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
const failure = TeamOrchestrator.classifyError(error);
|
|
377
|
+
failure.teamId = input.teamId;
|
|
378
|
+
failure.engineer = input.engineer;
|
|
379
|
+
failure.mode = input.mode;
|
|
380
|
+
context.metadata({
|
|
381
|
+
title: `❌ ${input.engineer} failed (${failure.failureKind})`,
|
|
382
|
+
metadata: {
|
|
383
|
+
teamId: failure.teamId,
|
|
384
|
+
engineer: failure.engineer,
|
|
385
|
+
failureKind: failure.failureKind,
|
|
386
|
+
message: failure.message.slice(0, 200),
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
305
391
|
await services.orchestrator.recordWrapperExchange(context.worktree, input.teamId, input.engineer, context.sessionID, input.mode, input.message, result.finalText);
|
|
306
392
|
context.metadata({
|
|
307
393
|
title: `✅ ${input.engineer} finished`,
|
|
@@ -335,6 +421,60 @@ function isEngineerAgent(agentId) {
|
|
|
335
421
|
async function resolveTeamId(worktree, sessionID) {
|
|
336
422
|
return (await getPersistedActiveTeam(worktree)) ?? getActiveTeamSession(worktree) ?? sessionID;
|
|
337
423
|
}
|
|
424
|
+
function formatToolDescription(toolName, toolArgs) {
|
|
425
|
+
if (!toolArgs || typeof toolArgs !== 'object')
|
|
426
|
+
return undefined;
|
|
427
|
+
const args = toolArgs;
|
|
428
|
+
switch (toolName) {
|
|
429
|
+
case 'Read':
|
|
430
|
+
case 'read': {
|
|
431
|
+
const filePath = args.file_path;
|
|
432
|
+
return typeof filePath === 'string' ? `Reading: ${filePath}` : undefined;
|
|
433
|
+
}
|
|
434
|
+
case 'Grep':
|
|
435
|
+
case 'grep': {
|
|
436
|
+
const pattern = args.pattern;
|
|
437
|
+
return typeof pattern === 'string' ? `Searching: ${pattern}` : undefined;
|
|
438
|
+
}
|
|
439
|
+
case 'Write':
|
|
440
|
+
case 'write': {
|
|
441
|
+
const filePath = args.file_path;
|
|
442
|
+
return typeof filePath === 'string' ? `Writing: ${filePath}` : undefined;
|
|
443
|
+
}
|
|
444
|
+
case 'Edit':
|
|
445
|
+
case 'edit': {
|
|
446
|
+
const filePath = args.file_path;
|
|
447
|
+
return typeof filePath === 'string' ? `Editing: ${filePath}` : undefined;
|
|
448
|
+
}
|
|
449
|
+
case 'Bash':
|
|
450
|
+
case 'bash':
|
|
451
|
+
case 'Run':
|
|
452
|
+
case 'run': {
|
|
453
|
+
const command = args.command;
|
|
454
|
+
return typeof command === 'string' ? `Running: ${command.slice(0, 80)}` : undefined;
|
|
455
|
+
}
|
|
456
|
+
case 'WebFetch':
|
|
457
|
+
case 'webfetch': {
|
|
458
|
+
const url = args.url;
|
|
459
|
+
return typeof url === 'string' ? `Fetching: ${url}` : undefined;
|
|
460
|
+
}
|
|
461
|
+
case 'Glob':
|
|
462
|
+
case 'glob': {
|
|
463
|
+
const pattern = args.pattern;
|
|
464
|
+
return typeof pattern === 'string' ? `Matching: ${pattern}` : undefined;
|
|
465
|
+
}
|
|
466
|
+
case 'TodoWrite':
|
|
467
|
+
case 'todowrite':
|
|
468
|
+
return args.content ? `Updating todos` : undefined;
|
|
469
|
+
case 'NotebookEdit':
|
|
470
|
+
case 'notebook_edit': {
|
|
471
|
+
const cellPath = args.notebook_cell_path;
|
|
472
|
+
return typeof cellPath === 'string' ? `Editing notebook: ${cellPath}` : undefined;
|
|
473
|
+
}
|
|
474
|
+
default:
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
338
478
|
function reportClaudeEvent(context, engineer, event) {
|
|
339
479
|
if (event.type === 'error') {
|
|
340
480
|
context.metadata({
|
|
@@ -382,10 +522,13 @@ function reportClaudeEvent(context, engineer, event) {
|
|
|
382
522
|
catch {
|
|
383
523
|
// event.text is not valid JSON — fall back to generic title
|
|
384
524
|
}
|
|
525
|
+
const toolDescription = formatToolDescription(toolName ?? '', toolArgs);
|
|
385
526
|
context.metadata({
|
|
386
|
-
title:
|
|
387
|
-
? `⚡ ${engineer} → ${
|
|
388
|
-
:
|
|
527
|
+
title: toolDescription
|
|
528
|
+
? `⚡ ${engineer} → ${toolDescription}`
|
|
529
|
+
: toolName
|
|
530
|
+
? `⚡ ${engineer} → ${toolName}`
|
|
531
|
+
: `⚡ ${engineer} is using Claude Code tools`,
|
|
389
532
|
metadata: {
|
|
390
533
|
engineer,
|
|
391
534
|
sessionId: event.sessionId,
|
|
@@ -397,12 +540,15 @@ function reportClaudeEvent(context, engineer, event) {
|
|
|
397
540
|
return;
|
|
398
541
|
}
|
|
399
542
|
if (event.type === 'assistant' || event.type === 'partial') {
|
|
543
|
+
const isThinking = event.text.startsWith('<thinking>');
|
|
544
|
+
const stateLabel = event.type === 'partial' && isThinking ? 'is thinking' : 'is working';
|
|
400
545
|
context.metadata({
|
|
401
|
-
title: `⚡ ${engineer}
|
|
546
|
+
title: `⚡ ${engineer} ${stateLabel}`,
|
|
402
547
|
metadata: {
|
|
403
548
|
engineer,
|
|
404
549
|
sessionId: event.sessionId,
|
|
405
550
|
preview: event.text.slice(0, 160),
|
|
551
|
+
isThinking,
|
|
406
552
|
},
|
|
407
553
|
});
|
|
408
554
|
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
2
|
-
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
3
2
|
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
4
|
-
import { TeamStateStore } from '../state/team-state-store.js';
|
|
5
3
|
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
6
4
|
import { TeamOrchestrator } from '../manager/team-orchestrator.js';
|
|
7
|
-
import
|
|
8
|
-
|
|
5
|
+
import { TeamStateStore } from '../state/team-state-store.js';
|
|
6
|
+
import type { EngineerName } from '../types/contracts.js';
|
|
7
|
+
interface ClaudeManagerPluginServices {
|
|
9
8
|
manager: PersistentManager;
|
|
10
9
|
sessions: ClaudeSessionService;
|
|
11
10
|
approvalManager: ToolApprovalManager;
|
|
12
|
-
liveTailer: SessionLiveTailer;
|
|
13
11
|
teamStore: TeamStateStore;
|
|
14
12
|
orchestrator: TeamOrchestrator;
|
|
15
13
|
}
|
|
16
|
-
export declare function getOrCreatePluginServices(worktree: string
|
|
14
|
+
export declare function getOrCreatePluginServices(worktree: string): ClaudeManagerPluginServices;
|
|
17
15
|
export declare function clearPluginServices(): void;
|
|
18
16
|
export declare function setActiveTeamSession(worktree: string, teamId: string): void;
|
|
19
17
|
export declare function getActiveTeamSession(worktree: string): string | null;
|
|
@@ -27,3 +25,4 @@ export declare function getWrapperSessionMapping(worktree: string, wrapperSessio
|
|
|
27
25
|
teamId: string;
|
|
28
26
|
engineer: EngineerName;
|
|
29
27
|
} | null;
|
|
28
|
+
export {};
|
|
@@ -1,42 +1,34 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
|
|
2
3
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
|
-
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
4
4
|
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
5
|
-
import { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
6
|
-
import { TeamStateStore } from '../state/team-state-store.js';
|
|
7
|
-
import { TranscriptStore } from '../state/transcript-store.js';
|
|
8
|
-
import { ContextTracker } from '../manager/context-tracker.js';
|
|
9
5
|
import { GitOperations } from '../manager/git-operations.js';
|
|
10
|
-
import { SessionController } from '../manager/session-controller.js';
|
|
11
6
|
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
12
|
-
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
13
7
|
import { TeamOrchestrator } from '../manager/team-orchestrator.js';
|
|
8
|
+
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
9
|
+
import { TeamStateStore } from '../state/team-state-store.js';
|
|
10
|
+
import { TranscriptStore } from '../state/transcript-store.js';
|
|
14
11
|
const serviceRegistry = new Map();
|
|
15
12
|
const activeTeamRegistry = new Map();
|
|
16
13
|
const wrapperSessionRegistry = new Map();
|
|
17
|
-
export function getOrCreatePluginServices(worktree
|
|
14
|
+
export function getOrCreatePluginServices(worktree) {
|
|
18
15
|
const existing = serviceRegistry.get(worktree);
|
|
19
16
|
if (existing) {
|
|
20
17
|
return existing;
|
|
21
18
|
}
|
|
22
|
-
const
|
|
19
|
+
const approvalPolicyPath = path.join(worktree, '.claude-manager', 'approval-policy.json');
|
|
20
|
+
const approvalManager = new ToolApprovalManager(undefined, undefined, approvalPolicyPath);
|
|
23
21
|
const sdkAdapter = new ClaudeAgentSdkAdapter(undefined, approvalManager);
|
|
24
22
|
const sessionService = new ClaudeSessionService(sdkAdapter);
|
|
25
|
-
const contextTracker = new ContextTracker();
|
|
26
|
-
const sessionController = new SessionController(sdkAdapter, contextTracker, undefined, // session prompt is now constructed dynamically by the wrapper
|
|
27
|
-
'default', worktree, managerPromptRegistry.modePrefixes);
|
|
28
23
|
const gitOps = new GitOperations(worktree);
|
|
29
|
-
const stateStore = new FileRunStateStore();
|
|
30
24
|
const teamStore = new TeamStateStore();
|
|
31
25
|
const transcriptStore = new TranscriptStore();
|
|
32
|
-
const manager = new PersistentManager(
|
|
33
|
-
const
|
|
34
|
-
const orchestrator = new TeamOrchestrator(sessionService, teamStore, transcriptStore, managerPromptRegistry.engineerSessionPrompt, projectClaudeFiles);
|
|
26
|
+
const manager = new PersistentManager(gitOps, transcriptStore);
|
|
27
|
+
const orchestrator = new TeamOrchestrator(sessionService, teamStore, transcriptStore, managerPromptRegistry.engineerSessionPrompt);
|
|
35
28
|
const services = {
|
|
36
29
|
manager,
|
|
37
30
|
sessions: sessionService,
|
|
38
31
|
approvalManager,
|
|
39
|
-
liveTailer,
|
|
40
32
|
teamStore,
|
|
41
33
|
orchestrator,
|
|
42
34
|
};
|
|
@@ -1,52 +1,71 @@
|
|
|
1
1
|
export const managerPromptRegistry = {
|
|
2
2
|
ctoSystemPrompt: [
|
|
3
|
-
'You are
|
|
4
|
-
'',
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'-
|
|
9
|
-
'-
|
|
10
|
-
'-
|
|
3
|
+
'You are a principal engineer orchestrating a team of AI-powered engineers.',
|
|
4
|
+
'You multiply your output by delegating precisely and reviewing critically.',
|
|
5
|
+
'Every prompt you send to an engineer costs time and tokens. Make each one count.',
|
|
6
|
+
'',
|
|
7
|
+
'Understand first:',
|
|
8
|
+
'- Ask questions. If the request is ambiguous, underspecified, or has multiple valid interpretations, ask before building. Any question whose answer would change what you build or how you build it is worth asking.',
|
|
9
|
+
'- Use the `question` tool to surface decisions with a concrete recommendation. Prefer one precise question over many vague ones.',
|
|
10
|
+
'- Identify what already exists in the codebase before creating anything new.',
|
|
11
|
+
'- Think about what could go wrong and address it upfront.',
|
|
12
|
+
'- When a bug is reported, always explore the root cause before implementing a fix. No fix without investigation. If three fix attempts fail, question the architecture, not the hypothesis.',
|
|
13
|
+
'',
|
|
14
|
+
'Challenge the framing:',
|
|
15
|
+
'- Before planning, ask what the user is actually trying to achieve, not just what they asked for.',
|
|
16
|
+
'- If the request sounds like a feature ("add photo upload"), ask what job-to-be-done it serves. The real feature might be larger or different.',
|
|
17
|
+
'- One good reframe question saves more time than ten implementation questions.',
|
|
18
|
+
'',
|
|
19
|
+
'Plan and decompose:',
|
|
20
|
+
'- Break work into independent pieces that can run in parallel. Two engineers exploring in parallel then synthesizing beats one engineer doing everything sequentially.',
|
|
21
|
+
'- For medium or large tasks, dispatch two engineers with complementary perspectives (lead plan + challenger review), then synthesize.',
|
|
22
|
+
'- Define clear success criteria before delegating. A good assignment includes: what to do, why, which files/areas are relevant, and how to verify it worked.',
|
|
23
|
+
'',
|
|
24
|
+
'Delegate through the Task tool:',
|
|
25
|
+
'- Tom, John, Maya, Sara, and Alex are persistent engineers. Each keeps a Claude Code session that remembers prior turns.',
|
|
26
|
+
'- Reuse the same engineer when follow-up work belongs to their prior context.',
|
|
27
|
+
'- Only one implementing engineer should modify the worktree at a time. Parallelize exploration freely.',
|
|
28
|
+
'- Do not delegate without telling the engineer what done looks like.',
|
|
29
|
+
'',
|
|
30
|
+
'Review and iterate:',
|
|
11
31
|
'- Review diffs with `git_diff`, inspect changed files with `git_status`, and use `git_log` for recent context.',
|
|
12
|
-
'-
|
|
13
|
-
'',
|
|
14
|
-
'
|
|
15
|
-
'-
|
|
16
|
-
'-
|
|
17
|
-
'-
|
|
18
|
-
'
|
|
19
|
-
'',
|
|
20
|
-
'
|
|
21
|
-
'- Do not
|
|
22
|
-
'
|
|
23
|
-
'
|
|
32
|
+
'- Give specific, actionable feedback. Not "this could be better" but "this is wrong because X, fix it by doing Y."',
|
|
33
|
+
'- Trust engineer findings but verify critical claims. Do not re-examine every file they already reviewed.',
|
|
34
|
+
'- If something fails, figure out what you missed in the assignment, not just what the engineer got wrong.',
|
|
35
|
+
'- After an engineer reports implementation done, review the diff looking for issues that pass tests but break in production: race conditions, N+1 queries, missing error handling, trust boundary violations, stale reads, forgotten enum cases.',
|
|
36
|
+
'- Auto-fix mechanical issues by sending a follow-up to the same engineer. Surface genuinely ambiguous issues to the user.',
|
|
37
|
+
'- Check scope: did the engineer build what was asked — nothing more, nothing less?',
|
|
38
|
+
'',
|
|
39
|
+
'Verify before declaring done:',
|
|
40
|
+
'- After review passes, dispatch an engineer in verify mode to run the most relevant checks (tests, lint, typecheck, build) for what changed.',
|
|
41
|
+
'- Do not declare a task complete until verification passes. If it fails, fix and re-verify.',
|
|
42
|
+
'',
|
|
43
|
+
'Constraints:',
|
|
44
|
+
'- Do not edit files or run bash directly. Engineers do the hands-on work.',
|
|
45
|
+
'- Do not read files or grep when an engineer can answer the question faster.',
|
|
46
|
+
'- Communicate proactively. If the plan changes or you discover something unexpected, tell the user.',
|
|
24
47
|
].join('\n'),
|
|
25
48
|
engineerAgentPrompt: [
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
49
|
+
"You are a named engineer on the CTO's team.",
|
|
50
|
+
'Your job is to run assignments through the `claude` tool, which connects to a persistent Claude Code session that remembers your prior turns.',
|
|
51
|
+
'',
|
|
52
|
+
'Frame each assignment well:',
|
|
53
|
+
'- Include relevant context, file paths, and constraints the CTO provided.',
|
|
54
|
+
'- Specify the work mode: explore (investigate, no edits), implement (make changes and verify), or verify (run checks and report).',
|
|
55
|
+
"- If the CTO's assignment is unclear, ask for clarification before sending it to Claude Code.",
|
|
56
|
+
'',
|
|
57
|
+
'Your wrapper context from prior turns is reloaded automatically. Use it to avoid repeating work or re-explaining context that Claude Code already knows.',
|
|
58
|
+
"Return the tool result directly. Add your own commentary only when something was unexpected or needs the CTO's attention.",
|
|
32
59
|
].join('\n'),
|
|
33
60
|
engineerSessionPrompt: [
|
|
34
61
|
'You are an expert software engineer working inside Claude Code.',
|
|
35
|
-
'
|
|
36
|
-
'Follow repository conventions and
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'Report blockers
|
|
62
|
+
'Start with the smallest investigation that resolves the key uncertainty, then act.',
|
|
63
|
+
'Follow repository conventions, AGENTS.md, and any project-level instructions.',
|
|
64
|
+
'Verify your own work before reporting done. Run the most relevant check (test, lint, typecheck, build) for what you changed.',
|
|
65
|
+
'Review your own diff before reporting done. Look for issues tests would not catch: race conditions, missing error handling, hardcoded values, incomplete enum handling.',
|
|
66
|
+
'Report blockers immediately with exact error output. Do not retry silently more than once.',
|
|
40
67
|
'Do not run git commit, git push, git reset, git checkout, or git stash.',
|
|
41
68
|
].join('\n'),
|
|
42
|
-
modePrefixes: {
|
|
43
|
-
plan: [
|
|
44
|
-
'[PLAN MODE] Read-only.',
|
|
45
|
-
'Do not create or edit files.',
|
|
46
|
-
'Analyze the codebase and return the plan inline.',
|
|
47
|
-
].join(' '),
|
|
48
|
-
free: '',
|
|
49
|
-
},
|
|
50
69
|
contextWarnings: {
|
|
51
70
|
moderate: 'Engineer context is getting full ({percent}% estimated). Reuse is still fine, but keep the next prompt focused.',
|
|
52
71
|
high: 'Engineer context is heavy ({percent}% estimated, {turns} turns, ${cost}). Prefer a narrowly scoped follow-up or internal compaction.',
|
|
@@ -31,12 +31,15 @@ export class TeamStateStore {
|
|
|
31
31
|
const directory = this.getTeamsDirectory(cwd);
|
|
32
32
|
try {
|
|
33
33
|
const entries = await fs.readdir(directory);
|
|
34
|
-
const
|
|
34
|
+
const results = await Promise.allSettled(entries
|
|
35
35
|
.filter((entry) => entry.endsWith('.json'))
|
|
36
36
|
.map(async (entry) => {
|
|
37
37
|
const content = await fs.readFile(path.join(directory, entry), 'utf8');
|
|
38
38
|
return JSON.parse(content);
|
|
39
39
|
}));
|
|
40
|
+
const teams = results
|
|
41
|
+
.filter((r) => r.status === 'fulfilled')
|
|
42
|
+
.map((r) => r.value);
|
|
40
43
|
return teams.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
41
44
|
}
|
|
42
45
|
catch (error) {
|
package/dist/src/team/roster.js
CHANGED
|
@@ -2,10 +2,6 @@ export interface ManagerPromptRegistry {
|
|
|
2
2
|
ctoSystemPrompt: string;
|
|
3
3
|
engineerAgentPrompt: string;
|
|
4
4
|
engineerSessionPrompt: string;
|
|
5
|
-
modePrefixes: {
|
|
6
|
-
plan: string;
|
|
7
|
-
free: string;
|
|
8
|
-
};
|
|
9
5
|
contextWarnings: {
|
|
10
6
|
moderate: string;
|
|
11
7
|
high: string;
|
|
@@ -22,14 +18,14 @@ export interface WrapperHistoryEntry {
|
|
|
22
18
|
mode?: EngineerWorkMode;
|
|
23
19
|
text: string;
|
|
24
20
|
}
|
|
25
|
-
|
|
21
|
+
interface ClaudeCommandMetadata {
|
|
26
22
|
name: string;
|
|
27
23
|
description: string;
|
|
28
24
|
argumentHint?: string;
|
|
29
25
|
source: 'sdk' | 'skill' | 'command';
|
|
30
26
|
path?: string;
|
|
31
27
|
}
|
|
32
|
-
|
|
28
|
+
interface ClaudeAgentMetadata {
|
|
33
29
|
name: string;
|
|
34
30
|
description: string;
|
|
35
31
|
model?: string;
|
|
@@ -52,6 +48,12 @@ export interface RunClaudeSessionInput {
|
|
|
52
48
|
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
53
49
|
mode?: SessionMode;
|
|
54
50
|
permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk';
|
|
51
|
+
/**
|
|
52
|
+
* When true, the canUseTool callback denies write tools (Edit, Write, MultiEdit,
|
|
53
|
+
* NotebookEdit) and destructive bash commands. Used instead of SDK plan mode so
|
|
54
|
+
* the agent can still exit plan mode if needed.
|
|
55
|
+
*/
|
|
56
|
+
restrictWriteTools?: boolean;
|
|
55
57
|
/** Merged with `Skill` by the SDK adapter unless `Skill` appears in `disallowedTools`. */
|
|
56
58
|
allowedTools?: string[];
|
|
57
59
|
disallowedTools?: string[];
|
|
@@ -110,12 +112,21 @@ export interface TeamEngineerRecord {
|
|
|
110
112
|
wrapperSessionId: string | null;
|
|
111
113
|
claudeSessionId: string | null;
|
|
112
114
|
busy: boolean;
|
|
115
|
+
busySince: string | null;
|
|
113
116
|
lastMode: EngineerWorkMode | null;
|
|
114
117
|
lastTaskSummary: string | null;
|
|
115
118
|
lastUsedAt: string | null;
|
|
116
119
|
wrapperHistory: WrapperHistoryEntry[];
|
|
117
120
|
context: SessionContextSnapshot;
|
|
118
121
|
}
|
|
122
|
+
export type EngineerFailureKind = 'sdkError' | 'contextExhausted' | 'toolDenied' | 'aborted' | 'engineerBusy' | 'unknown';
|
|
123
|
+
export interface EngineerFailureResult {
|
|
124
|
+
teamId: string;
|
|
125
|
+
engineer: EngineerName;
|
|
126
|
+
mode: EngineerWorkMode;
|
|
127
|
+
failureKind: EngineerFailureKind;
|
|
128
|
+
message: string;
|
|
129
|
+
}
|
|
119
130
|
export interface TeamRecord {
|
|
120
131
|
id: string;
|
|
121
132
|
cwd: string;
|
|
@@ -163,57 +174,6 @@ export interface GitOperationResult {
|
|
|
163
174
|
output: string;
|
|
164
175
|
error?: string;
|
|
165
176
|
}
|
|
166
|
-
export type PersistentRunStatus = 'running' | 'completed' | 'failed';
|
|
167
|
-
export interface PersistentRunMessageRecord {
|
|
168
|
-
timestamp: string;
|
|
169
|
-
direction: 'sent' | 'received';
|
|
170
|
-
text: string;
|
|
171
|
-
turns?: number;
|
|
172
|
-
totalCostUsd?: number;
|
|
173
|
-
inputTokens?: number;
|
|
174
|
-
outputTokens?: number;
|
|
175
|
-
}
|
|
176
|
-
export interface PersistentRunActionRecord {
|
|
177
|
-
timestamp: string;
|
|
178
|
-
type: 'git_diff' | 'git_commit' | 'git_reset' | 'git_status' | 'git_log' | 'compact' | 'clear';
|
|
179
|
-
result: string;
|
|
180
|
-
}
|
|
181
|
-
export interface PersistentRunRecord {
|
|
182
|
-
id: string;
|
|
183
|
-
cwd: string;
|
|
184
|
-
task: string;
|
|
185
|
-
status: PersistentRunStatus;
|
|
186
|
-
createdAt: string;
|
|
187
|
-
updatedAt: string;
|
|
188
|
-
sessionId: string | null;
|
|
189
|
-
sessionHistory: string[];
|
|
190
|
-
messages: PersistentRunMessageRecord[];
|
|
191
|
-
actions: PersistentRunActionRecord[];
|
|
192
|
-
commits: string[];
|
|
193
|
-
context: SessionContextSnapshot;
|
|
194
|
-
finalSummary?: string;
|
|
195
|
-
}
|
|
196
|
-
export interface PersistentRunResult {
|
|
197
|
-
run: PersistentRunRecord;
|
|
198
|
-
}
|
|
199
|
-
export interface LiveTailEvent {
|
|
200
|
-
type: 'line' | 'error' | 'end';
|
|
201
|
-
sessionId: string;
|
|
202
|
-
data?: unknown;
|
|
203
|
-
rawLine?: string;
|
|
204
|
-
error?: string;
|
|
205
|
-
}
|
|
206
|
-
export interface ToolOutputPreview {
|
|
207
|
-
toolUseId: string;
|
|
208
|
-
content: string;
|
|
209
|
-
isError: boolean;
|
|
210
|
-
}
|
|
211
|
-
export interface DiscoveredClaudeFile {
|
|
212
|
-
/** Relative path from the project root (forward slashes, deterministic order). */
|
|
213
|
-
relativePath: string;
|
|
214
|
-
/** Trimmed UTF-8 text content. */
|
|
215
|
-
content: string;
|
|
216
|
-
}
|
|
217
177
|
export interface ToolApprovalRule {
|
|
218
178
|
id: string;
|
|
219
179
|
description?: string;
|
|
@@ -240,3 +200,4 @@ export interface ToolApprovalDecision {
|
|
|
240
200
|
denyMessage?: string;
|
|
241
201
|
agentId?: string;
|
|
242
202
|
}
|
|
203
|
+
export {};
|
|
@@ -31,12 +31,15 @@ export class TeamStateStore {
|
|
|
31
31
|
const directory = this.getTeamsDirectory(cwd);
|
|
32
32
|
try {
|
|
33
33
|
const entries = await fs.readdir(directory);
|
|
34
|
-
const
|
|
34
|
+
const results = await Promise.allSettled(entries
|
|
35
35
|
.filter((entry) => entry.endsWith('.json'))
|
|
36
36
|
.map(async (entry) => {
|
|
37
37
|
const content = await fs.readFile(path.join(directory, entry), 'utf8');
|
|
38
38
|
return JSON.parse(content);
|
|
39
39
|
}));
|
|
40
|
+
const teams = results
|
|
41
|
+
.filter((r) => r.status === 'fulfilled')
|
|
42
|
+
.map((r) => r.value);
|
|
40
43
|
return teams.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
41
44
|
}
|
|
42
45
|
catch (error) {
|