@doingdev/opencode-claude-manager-plugin 0.1.29 → 0.1.31
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.js +7 -18
- package/dist/claude/claude-session.service.d.ts +2 -7
- package/dist/claude/claude-session.service.js +1 -6
- package/dist/claude/tool-approval-manager.js +3 -9
- package/dist/index.d.ts +1 -1
- package/dist/manager/context-tracker.js +2 -4
- package/dist/plugin/agent-hierarchy.d.ts +1 -1
- package/dist/plugin/agent-hierarchy.js +35 -14
- package/dist/plugin/claude-manager.plugin.js +210 -211
- package/dist/plugin/service-factory.js +1 -4
- package/dist/prompts/registry.js +105 -144
- package/dist/state/file-run-state-store.js +2 -4
- package/dist/state/transcript-store.js +2 -2
- package/dist/types/contracts.d.ts +1 -24
- package/dist/util/fs-helpers.js +1 -3
- package/package.json +1 -2
|
@@ -19,9 +19,7 @@ function mergeSkillIntoAllowedTools(allowedTools, disallowedTools) {
|
|
|
19
19
|
if (allowedTools === undefined) {
|
|
20
20
|
return ['Skill'];
|
|
21
21
|
}
|
|
22
|
-
return allowedTools.includes('Skill')
|
|
23
|
-
? allowedTools
|
|
24
|
-
: [...allowedTools, 'Skill'];
|
|
22
|
+
return allowedTools.includes('Skill') ? allowedTools : [...allowedTools, 'Skill'];
|
|
25
23
|
}
|
|
26
24
|
export class ClaudeAgentSdkAdapter {
|
|
27
25
|
sdkFacade;
|
|
@@ -262,11 +260,8 @@ function normalizeSdkMessages(message, includePartials) {
|
|
|
262
260
|
return normalizeUserSdkMessage(message, sessionId);
|
|
263
261
|
}
|
|
264
262
|
if (message.type === 'tool_progress') {
|
|
265
|
-
const toolName = 'tool_name' in message && typeof message.tool_name === 'string'
|
|
266
|
-
|
|
267
|
-
: 'tool';
|
|
268
|
-
const elapsed = 'elapsed_time_seconds' in message &&
|
|
269
|
-
typeof message.elapsed_time_seconds === 'number'
|
|
263
|
+
const toolName = 'tool_name' in message && typeof message.tool_name === 'string' ? message.tool_name : 'tool';
|
|
264
|
+
const elapsed = 'elapsed_time_seconds' in message && typeof message.elapsed_time_seconds === 'number'
|
|
270
265
|
? message.elapsed_time_seconds
|
|
271
266
|
: 0;
|
|
272
267
|
return [
|
|
@@ -279,9 +274,7 @@ function normalizeSdkMessages(message, includePartials) {
|
|
|
279
274
|
];
|
|
280
275
|
}
|
|
281
276
|
if (message.type === 'tool_use_summary') {
|
|
282
|
-
const summary = 'summary' in message && typeof message.summary === 'string'
|
|
283
|
-
? message.summary
|
|
284
|
-
: '';
|
|
277
|
+
const summary = 'summary' in message && typeof message.summary === 'string' ? message.summary : '';
|
|
285
278
|
return [
|
|
286
279
|
{
|
|
287
280
|
type: 'tool_summary',
|
|
@@ -359,9 +352,7 @@ function normalizeUserSdkMessage(message, sessionId) {
|
|
|
359
352
|
if (message.tool_use_result !== undefined) {
|
|
360
353
|
const extra = truncateJsonish(message.tool_use_result, 1500);
|
|
361
354
|
if (extra) {
|
|
362
|
-
payload = payload
|
|
363
|
-
? `${payload}\n[tool_use_result] ${extra}`
|
|
364
|
-
: `[tool_use_result] ${extra}`;
|
|
355
|
+
payload = payload ? `${payload}\n[tool_use_result] ${extra}` : `[tool_use_result] ${extra}`;
|
|
365
356
|
}
|
|
366
357
|
}
|
|
367
358
|
payload = truncateString(payload, USER_MESSAGE_MAX);
|
|
@@ -439,8 +430,7 @@ function extractPartialEventText(event) {
|
|
|
439
430
|
if (typeof delta.text === 'string' && delta.text.length > 0) {
|
|
440
431
|
return delta.text;
|
|
441
432
|
}
|
|
442
|
-
if (typeof delta.partial_json === 'string' &&
|
|
443
|
-
delta.partial_json.length > 0) {
|
|
433
|
+
if (typeof delta.partial_json === 'string' && delta.partial_json.length > 0) {
|
|
444
434
|
return delta.partial_json;
|
|
445
435
|
}
|
|
446
436
|
}
|
|
@@ -462,8 +452,7 @@ function extractText(payload) {
|
|
|
462
452
|
if (typeof contentPart.text === 'string') {
|
|
463
453
|
return contentPart.text;
|
|
464
454
|
}
|
|
465
|
-
if (contentPart.type === 'tool_use' &&
|
|
466
|
-
typeof contentPart.name === 'string') {
|
|
455
|
+
if (contentPart.type === 'tool_use' && typeof contentPart.name === 'string') {
|
|
467
456
|
return `[tool:${contentPart.name}]`;
|
|
468
457
|
}
|
|
469
458
|
return '';
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import type { ClaudeCapabilitySnapshot,
|
|
2
|
-
import type { ClaudeMetadataService } from '../metadata/claude-metadata.service.js';
|
|
1
|
+
import type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, RunClaudeSessionInput } from '../types/contracts.js';
|
|
3
2
|
import type { ClaudeAgentSdkAdapter, ClaudeSessionEventHandler } from './claude-agent-sdk-adapter.js';
|
|
4
3
|
export declare class ClaudeSessionService {
|
|
5
4
|
private readonly sdkAdapter;
|
|
6
|
-
|
|
7
|
-
constructor(sdkAdapter: ClaudeAgentSdkAdapter, metadataService: ClaudeMetadataService);
|
|
5
|
+
constructor(sdkAdapter: ClaudeAgentSdkAdapter);
|
|
8
6
|
runTask(input: RunClaudeSessionInput, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
|
|
9
7
|
listSessions(cwd?: string): Promise<ClaudeSessionSummary[]>;
|
|
10
8
|
getTranscript(sessionId: string, cwd?: string): Promise<ClaudeSessionTranscriptMessage[]>;
|
|
11
|
-
inspectRepository(cwd: string, options?: {
|
|
12
|
-
includeSdkProbe?: boolean;
|
|
13
|
-
}): Promise<ClaudeMetadataSnapshot>;
|
|
14
9
|
probeCapabilities(cwd: string): Promise<ClaudeCapabilitySnapshot>;
|
|
15
10
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export class ClaudeSessionService {
|
|
2
2
|
sdkAdapter;
|
|
3
|
-
|
|
4
|
-
constructor(sdkAdapter, metadataService) {
|
|
3
|
+
constructor(sdkAdapter) {
|
|
5
4
|
this.sdkAdapter = sdkAdapter;
|
|
6
|
-
this.metadataService = metadataService;
|
|
7
5
|
}
|
|
8
6
|
runTask(input, onEvent) {
|
|
9
7
|
return this.sdkAdapter.runSession(input, onEvent);
|
|
@@ -14,9 +12,6 @@ export class ClaudeSessionService {
|
|
|
14
12
|
getTranscript(sessionId, cwd) {
|
|
15
13
|
return this.sdkAdapter.getTranscript(sessionId, cwd);
|
|
16
14
|
}
|
|
17
|
-
inspectRepository(cwd, options) {
|
|
18
|
-
return this.metadataService.collect(cwd, options);
|
|
19
|
-
}
|
|
20
15
|
probeCapabilities(cwd) {
|
|
21
16
|
return this.sdkAdapter.probeCapabilities(cwd);
|
|
22
17
|
}
|
|
@@ -137,9 +137,7 @@ export class ToolApprovalManager {
|
|
|
137
137
|
const matchedRule = this.findMatchingRule(toolName, inputJson);
|
|
138
138
|
const action = matchedRule?.action ?? this.policy.defaultAction;
|
|
139
139
|
const denyMessage = action === 'deny'
|
|
140
|
-
? (matchedRule?.denyMessage ??
|
|
141
|
-
this.policy.defaultDenyMessage ??
|
|
142
|
-
'Denied by policy.')
|
|
140
|
+
? (matchedRule?.denyMessage ?? this.policy.defaultDenyMessage ?? 'Denied by policy.')
|
|
143
141
|
: undefined;
|
|
144
142
|
this.recordDecision({
|
|
145
143
|
timestamp: new Date().toISOString(),
|
|
@@ -174,9 +172,7 @@ export class ToolApprovalManager {
|
|
|
174
172
|
this.policy = { ...policy, rules: [...policy.rules] };
|
|
175
173
|
}
|
|
176
174
|
addRule(rule, position) {
|
|
177
|
-
if (position !== undefined &&
|
|
178
|
-
position >= 0 &&
|
|
179
|
-
position < this.policy.rules.length) {
|
|
175
|
+
if (position !== undefined && position >= 0 && position < this.policy.rules.length) {
|
|
180
176
|
this.policy.rules.splice(position, 0, rule);
|
|
181
177
|
}
|
|
182
178
|
else {
|
|
@@ -223,9 +219,7 @@ function matchesToolPattern(pattern, toolName) {
|
|
|
223
219
|
if (!pattern.includes('*')) {
|
|
224
220
|
return pattern === toolName;
|
|
225
221
|
}
|
|
226
|
-
const regex = new RegExp('^' +
|
|
227
|
-
pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') +
|
|
228
|
-
'$');
|
|
222
|
+
const regex = new RegExp('^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$');
|
|
229
223
|
return regex.test(toolName);
|
|
230
224
|
}
|
|
231
225
|
function safeJsonStringify(value) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
2
|
import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
|
|
3
|
-
export type { ClaudeCapabilitySnapshot,
|
|
3
|
+
export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, ActiveSessionState, ContextWarningLevel, SessionMode, LiveTailEvent, ToolOutputPreview, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
|
|
4
4
|
export { SessionLiveTailer } from './claude/session-live-tailer.js';
|
|
5
5
|
export { ClaudeManagerPlugin };
|
|
6
6
|
export declare const plugin: Plugin;
|
|
@@ -19,8 +19,7 @@ export class ContextTracker {
|
|
|
19
19
|
}
|
|
20
20
|
if (result.inputTokens !== undefined) {
|
|
21
21
|
// If input tokens dropped significantly, compaction likely occurred
|
|
22
|
-
if (this.latestInputTokens !== null &&
|
|
23
|
-
result.inputTokens < this.latestInputTokens * 0.5) {
|
|
22
|
+
if (this.latestInputTokens !== null && result.inputTokens < this.latestInputTokens * 0.5) {
|
|
24
23
|
this.compactionCount++;
|
|
25
24
|
}
|
|
26
25
|
this.latestInputTokens = result.inputTokens;
|
|
@@ -85,8 +84,7 @@ export class ContextTracker {
|
|
|
85
84
|
return null;
|
|
86
85
|
}
|
|
87
86
|
isAboveTokenThreshold(thresholdTokens = 200_000) {
|
|
88
|
-
return
|
|
89
|
-
this.latestInputTokens >= thresholdTokens);
|
|
87
|
+
return this.latestInputTokens !== null && this.latestInputTokens >= thresholdTokens;
|
|
90
88
|
}
|
|
91
89
|
reset() {
|
|
92
90
|
this.totalTurns = 0;
|
|
@@ -11,7 +11,7 @@ export declare const AGENT_CTO = "cto";
|
|
|
11
11
|
export declare const AGENT_ENGINEER_PLAN = "engineer_plan";
|
|
12
12
|
export declare const AGENT_ENGINEER_BUILD = "engineer_build";
|
|
13
13
|
/** All restricted tool IDs (union of all domain groups) */
|
|
14
|
-
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["engineer_send", "
|
|
14
|
+
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["engineer_send", "engineer_send_plan", "engineer_send_build", "engineer_compact", "engineer_clear", "engineer_status", "engineer_sessions", "engineer_runs", "git_diff", "git_commit", "git_reset", "approval_policy", "approval_decisions", "approval_update"];
|
|
15
15
|
type ToolPermission = 'allow' | 'ask' | 'deny';
|
|
16
16
|
type AgentPermission = {
|
|
17
17
|
'*'?: ToolPermission;
|
|
@@ -15,24 +15,29 @@ export const AGENT_ENGINEER_BUILD = 'engineer_build';
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Tool IDs — grouped by domain
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
|
-
/**
|
|
19
|
-
const
|
|
20
|
-
'engineer_send',
|
|
18
|
+
/** Shared engineer session tools (compact, clear, status, diagnostics) */
|
|
19
|
+
const ENGINEER_SHARED_TOOL_IDS = [
|
|
21
20
|
'engineer_compact',
|
|
22
21
|
'engineer_clear',
|
|
23
22
|
'engineer_status',
|
|
24
|
-
'engineer_metadata',
|
|
25
23
|
'engineer_sessions',
|
|
26
24
|
'engineer_runs',
|
|
27
25
|
];
|
|
26
|
+
/** All engineer tools — generic send + mode-locked sends + shared session tools */
|
|
27
|
+
const ENGINEER_TOOL_IDS = [
|
|
28
|
+
'engineer_send',
|
|
29
|
+
'engineer_send_plan',
|
|
30
|
+
'engineer_send_build',
|
|
31
|
+
...ENGINEER_SHARED_TOOL_IDS,
|
|
32
|
+
];
|
|
33
|
+
/** Tools for the engineer_plan wrapper (plan-mode send + shared) */
|
|
34
|
+
const ENGINEER_PLAN_TOOL_IDS = ['engineer_send_plan', ...ENGINEER_SHARED_TOOL_IDS];
|
|
35
|
+
/** Tools for the engineer_build wrapper (build-mode send + shared) */
|
|
36
|
+
const ENGINEER_BUILD_TOOL_IDS = ['engineer_send_build', ...ENGINEER_SHARED_TOOL_IDS];
|
|
28
37
|
/** Git tools — owned by CTO */
|
|
29
38
|
const GIT_TOOL_IDS = ['git_diff', 'git_commit', 'git_reset'];
|
|
30
39
|
/** Approval tools — owned by CTO */
|
|
31
|
-
const APPROVAL_TOOL_IDS = [
|
|
32
|
-
'approval_policy',
|
|
33
|
-
'approval_decisions',
|
|
34
|
-
'approval_update',
|
|
35
|
-
];
|
|
40
|
+
const APPROVAL_TOOL_IDS = ['approval_policy', 'approval_decisions', 'approval_update'];
|
|
36
41
|
/** All restricted tool IDs (union of all domain groups) */
|
|
37
42
|
export const ALL_RESTRICTED_TOOL_IDS = [
|
|
38
43
|
...ENGINEER_TOOL_IDS,
|
|
@@ -82,14 +87,30 @@ function buildCtoPermissions() {
|
|
|
82
87
|
},
|
|
83
88
|
};
|
|
84
89
|
}
|
|
85
|
-
/** Engineer wrapper:
|
|
86
|
-
function
|
|
90
|
+
/** Engineer plan wrapper: engineer_send_plan + shared session tools. */
|
|
91
|
+
function buildEngineerPlanPermissions() {
|
|
92
|
+
const denied = {};
|
|
93
|
+
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
94
|
+
denied[toolId] = 'deny';
|
|
95
|
+
}
|
|
96
|
+
const allowed = {};
|
|
97
|
+
for (const toolId of ENGINEER_PLAN_TOOL_IDS) {
|
|
98
|
+
allowed[toolId] = 'allow';
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
'*': 'deny',
|
|
102
|
+
...denied,
|
|
103
|
+
...allowed,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Engineer build wrapper: engineer_send_build + shared session tools. */
|
|
107
|
+
function buildEngineerBuildPermissions() {
|
|
87
108
|
const denied = {};
|
|
88
109
|
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
89
110
|
denied[toolId] = 'deny';
|
|
90
111
|
}
|
|
91
112
|
const allowed = {};
|
|
92
|
-
for (const toolId of
|
|
113
|
+
for (const toolId of ENGINEER_BUILD_TOOL_IDS) {
|
|
93
114
|
allowed[toolId] = 'allow';
|
|
94
115
|
}
|
|
95
116
|
return {
|
|
@@ -115,7 +136,7 @@ export function buildEngineerPlanAgentConfig(prompts) {
|
|
|
115
136
|
description: 'Engineer that manages a Claude Code session in plan mode for read-only investigation and analysis.',
|
|
116
137
|
mode: 'subagent',
|
|
117
138
|
color: '#D97757',
|
|
118
|
-
permission:
|
|
139
|
+
permission: buildEngineerPlanPermissions(),
|
|
119
140
|
prompt: prompts.engineerPlanPrompt,
|
|
120
141
|
};
|
|
121
142
|
}
|
|
@@ -124,7 +145,7 @@ export function buildEngineerBuildAgentConfig(prompts) {
|
|
|
124
145
|
description: 'Engineer that manages a Claude Code session in free mode for implementation and execution.',
|
|
125
146
|
mode: 'subagent',
|
|
126
147
|
color: '#D97757',
|
|
127
|
-
permission:
|
|
148
|
+
permission: buildEngineerBuildPermissions(),
|
|
128
149
|
prompt: prompts.engineerBuildPrompt,
|
|
129
150
|
};
|
|
130
151
|
}
|
|
@@ -4,6 +4,177 @@ import { AGENT_CTO, AGENT_ENGINEER_BUILD, AGENT_ENGINEER_PLAN, buildCtoAgentConf
|
|
|
4
4
|
import { getOrCreatePluginServices } from './service-factory.js';
|
|
5
5
|
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
6
6
|
const services = getOrCreatePluginServices(worktree);
|
|
7
|
+
async function executeEngineerSend(args, context) {
|
|
8
|
+
const cwd = args.cwd ?? context.worktree;
|
|
9
|
+
if (args.freshSession) {
|
|
10
|
+
await services.manager.clearSession(cwd);
|
|
11
|
+
}
|
|
12
|
+
const hasActiveSession = services.manager.getStatus().sessionId !== null;
|
|
13
|
+
const promptPreview = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
14
|
+
context.metadata({
|
|
15
|
+
title: hasActiveSession ? 'Claude Code: Resuming session...' : 'Claude Code: Initializing...',
|
|
16
|
+
metadata: {
|
|
17
|
+
sessionId: services.manager.getStatus().sessionId,
|
|
18
|
+
prompt: promptPreview,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
let turnsSoFar = 0;
|
|
22
|
+
let costSoFar = 0;
|
|
23
|
+
const result = await services.manager.sendMessage(cwd, args.message, {
|
|
24
|
+
model: args.model,
|
|
25
|
+
effort: args.effort,
|
|
26
|
+
mode: args.mode,
|
|
27
|
+
abortSignal: context.abort,
|
|
28
|
+
}, (event) => {
|
|
29
|
+
if (event.turns !== undefined) {
|
|
30
|
+
turnsSoFar = event.turns;
|
|
31
|
+
}
|
|
32
|
+
if (event.totalCostUsd !== undefined) {
|
|
33
|
+
costSoFar = event.totalCostUsd;
|
|
34
|
+
}
|
|
35
|
+
const costLabel = `$${costSoFar.toFixed(4)}`;
|
|
36
|
+
if (event.type === 'tool_call') {
|
|
37
|
+
let toolName = 'tool';
|
|
38
|
+
let inputPreview = '';
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(event.text);
|
|
41
|
+
toolName = parsed.name ?? 'tool';
|
|
42
|
+
if (parsed.input) {
|
|
43
|
+
const inputStr = typeof parsed.input === 'string' ? parsed.input : JSON.stringify(parsed.input);
|
|
44
|
+
inputPreview = inputStr.length > 150 ? inputStr.slice(0, 150) + '...' : inputStr;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// ignore parse errors
|
|
49
|
+
}
|
|
50
|
+
context.metadata({
|
|
51
|
+
title: `Claude Code: Running ${toolName}... (${turnsSoFar} turns, ${costLabel})`,
|
|
52
|
+
metadata: {
|
|
53
|
+
sessionId: event.sessionId,
|
|
54
|
+
type: event.type,
|
|
55
|
+
tool: toolName,
|
|
56
|
+
input: inputPreview,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (event.type === 'assistant') {
|
|
61
|
+
const thinkingPreview = event.text.length > 150 ? event.text.slice(0, 150) + '...' : event.text;
|
|
62
|
+
context.metadata({
|
|
63
|
+
title: `Claude Code: Thinking... (${turnsSoFar} turns, ${costLabel})`,
|
|
64
|
+
metadata: {
|
|
65
|
+
sessionId: event.sessionId,
|
|
66
|
+
type: event.type,
|
|
67
|
+
thinking: thinkingPreview,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (event.type === 'init') {
|
|
72
|
+
context.metadata({
|
|
73
|
+
title: `Claude Code: Session started`,
|
|
74
|
+
metadata: {
|
|
75
|
+
sessionId: event.sessionId,
|
|
76
|
+
prompt: promptPreview,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else if (event.type === 'user') {
|
|
81
|
+
const preview = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
|
|
82
|
+
context.metadata({
|
|
83
|
+
title: `Claude Code: Tool result (${turnsSoFar} turns, ${costLabel})`,
|
|
84
|
+
metadata: {
|
|
85
|
+
sessionId: event.sessionId,
|
|
86
|
+
type: event.type,
|
|
87
|
+
output: preview,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else if (event.type === 'tool_progress') {
|
|
92
|
+
let toolName = 'tool';
|
|
93
|
+
let elapsed = 0;
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(event.text);
|
|
96
|
+
toolName = parsed.name ?? 'tool';
|
|
97
|
+
elapsed = parsed.elapsed ?? 0;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// ignore
|
|
101
|
+
}
|
|
102
|
+
context.metadata({
|
|
103
|
+
title: `Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}... (${turnsSoFar} turns, ${costLabel})`,
|
|
104
|
+
metadata: {
|
|
105
|
+
sessionId: event.sessionId,
|
|
106
|
+
type: event.type,
|
|
107
|
+
tool: toolName,
|
|
108
|
+
elapsed,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else if (event.type === 'tool_summary') {
|
|
113
|
+
const summary = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
|
|
114
|
+
context.metadata({
|
|
115
|
+
title: `Claude Code: Tool done (${turnsSoFar} turns, ${costLabel})`,
|
|
116
|
+
metadata: {
|
|
117
|
+
sessionId: event.sessionId,
|
|
118
|
+
type: event.type,
|
|
119
|
+
summary,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else if (event.type === 'partial') {
|
|
124
|
+
const delta = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
|
|
125
|
+
context.metadata({
|
|
126
|
+
title: `Claude Code: Writing... (${turnsSoFar} turns, ${costLabel})`,
|
|
127
|
+
metadata: {
|
|
128
|
+
sessionId: event.sessionId,
|
|
129
|
+
type: event.type,
|
|
130
|
+
delta,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else if (event.type === 'error') {
|
|
135
|
+
context.metadata({
|
|
136
|
+
title: `Claude Code: Error`,
|
|
137
|
+
metadata: {
|
|
138
|
+
sessionId: event.sessionId,
|
|
139
|
+
error: event.text.slice(0, 200),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const costLabel = `$${(result.totalCostUsd ?? 0).toFixed(4)}`;
|
|
145
|
+
const turns = result.turns ?? 0;
|
|
146
|
+
const contextWarning = formatContextWarning(result.context);
|
|
147
|
+
if (contextWarning) {
|
|
148
|
+
context.metadata({
|
|
149
|
+
title: `Claude Code: Context at ${result.context.estimatedContextPercent}% (${turns} turns)`,
|
|
150
|
+
metadata: { sessionId: result.sessionId, contextWarning },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
context.metadata({
|
|
155
|
+
title: `Claude Code: Complete (${turns} turns, ${costLabel})`,
|
|
156
|
+
metadata: { sessionId: result.sessionId },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
let toolOutputs = [];
|
|
160
|
+
if (result.sessionId) {
|
|
161
|
+
try {
|
|
162
|
+
toolOutputs = await services.liveTailer.getToolOutputPreview(result.sessionId, cwd, 3);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Non-critical — the JSONL file may not exist yet.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return JSON.stringify({
|
|
169
|
+
sessionId: result.sessionId,
|
|
170
|
+
finalText: result.finalText,
|
|
171
|
+
turns: result.turns,
|
|
172
|
+
totalCostUsd: result.totalCostUsd,
|
|
173
|
+
context: result.context,
|
|
174
|
+
contextWarning,
|
|
175
|
+
toolOutputs: toolOutputs.length > 0 ? toolOutputs : undefined,
|
|
176
|
+
}, null, 2);
|
|
177
|
+
}
|
|
7
178
|
return {
|
|
8
179
|
config: async (config) => {
|
|
9
180
|
config.agent ??= {};
|
|
@@ -15,12 +186,45 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
15
186
|
},
|
|
16
187
|
tool: {
|
|
17
188
|
engineer_send: tool({
|
|
18
|
-
description: 'Send a message to the persistent Claude Code session. ' +
|
|
189
|
+
description: 'Send a message to the persistent Claude Code session with explicit mode control. ' +
|
|
190
|
+
'Most agents should use engineer_send_plan or engineer_send_build instead.',
|
|
191
|
+
args: {
|
|
192
|
+
message: tool.schema.string().min(1),
|
|
193
|
+
model: tool.schema
|
|
194
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5'])
|
|
195
|
+
.optional(),
|
|
196
|
+
effort: tool.schema.enum(['low', 'medium', 'high', 'max']).default('high'),
|
|
197
|
+
mode: tool.schema.enum(['plan', 'free']).default('free'),
|
|
198
|
+
freshSession: tool.schema.boolean().default(false),
|
|
199
|
+
cwd: tool.schema.string().optional(),
|
|
200
|
+
},
|
|
201
|
+
async execute(args, context) {
|
|
202
|
+
return executeEngineerSend(args, context);
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
engineer_send_plan: tool({
|
|
206
|
+
description: 'Send a read-only investigation message to the Claude Code session in plan mode. ' +
|
|
207
|
+
'The engineer will analyze code without making edits. ' +
|
|
208
|
+
'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
|
|
209
|
+
'Returns the assistant response and current context health snapshot.',
|
|
210
|
+
args: {
|
|
211
|
+
message: tool.schema.string().min(1),
|
|
212
|
+
model: tool.schema
|
|
213
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5'])
|
|
214
|
+
.optional(),
|
|
215
|
+
effort: tool.schema.enum(['low', 'medium', 'high', 'max']).default('high'),
|
|
216
|
+
freshSession: tool.schema.boolean().default(false),
|
|
217
|
+
cwd: tool.schema.string().optional(),
|
|
218
|
+
},
|
|
219
|
+
async execute(args, context) {
|
|
220
|
+
return executeEngineerSend({ ...args, mode: 'plan' }, context);
|
|
221
|
+
},
|
|
222
|
+
}),
|
|
223
|
+
engineer_send_build: tool({
|
|
224
|
+
description: 'Send an implementation message to the Claude Code session in free mode. ' +
|
|
225
|
+
'The engineer can read, edit, and create files. ' +
|
|
19
226
|
'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
|
|
20
227
|
'Returns the assistant response and current context health snapshot. ' +
|
|
21
|
-
'Use mode "plan" for read-only investigation and planning (no edits), ' +
|
|
22
|
-
'or "free" (default) for normal execution with edit permissions. ' +
|
|
23
|
-
'Set freshSession to clear the active session before sending (use for unrelated tasks). ' +
|
|
24
228
|
'Prefer claude-opus-4-6 (default) for most coding work; use a Sonnet model for faster/lighter tasks. ' +
|
|
25
229
|
'Prefer effort "high" (default) for most work; use "medium" for lighter tasks and "max" for especially hard problems.',
|
|
26
230
|
args: {
|
|
@@ -28,201 +232,12 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
28
232
|
model: tool.schema
|
|
29
233
|
.enum(['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5'])
|
|
30
234
|
.optional(),
|
|
31
|
-
effort: tool.schema
|
|
32
|
-
.enum(['low', 'medium', 'high', 'max'])
|
|
33
|
-
.default('high'),
|
|
34
|
-
mode: tool.schema.enum(['plan', 'free']).default('free'),
|
|
235
|
+
effort: tool.schema.enum(['low', 'medium', 'high', 'max']).default('high'),
|
|
35
236
|
freshSession: tool.schema.boolean().default(false),
|
|
36
237
|
cwd: tool.schema.string().optional(),
|
|
37
238
|
},
|
|
38
239
|
async execute(args, context) {
|
|
39
|
-
|
|
40
|
-
if (args.freshSession) {
|
|
41
|
-
await services.manager.clearSession(cwd);
|
|
42
|
-
}
|
|
43
|
-
const hasActiveSession = services.manager.getStatus().sessionId !== null;
|
|
44
|
-
const promptPreview = args.message.length > 100
|
|
45
|
-
? args.message.slice(0, 100) + '...'
|
|
46
|
-
: args.message;
|
|
47
|
-
context.metadata({
|
|
48
|
-
title: hasActiveSession
|
|
49
|
-
? 'Claude Code: Resuming session...'
|
|
50
|
-
: 'Claude Code: Initializing...',
|
|
51
|
-
metadata: {
|
|
52
|
-
sessionId: services.manager.getStatus().sessionId,
|
|
53
|
-
prompt: promptPreview,
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
let turnsSoFar = 0;
|
|
57
|
-
let costSoFar = 0;
|
|
58
|
-
const result = await services.manager.sendMessage(cwd, args.message, {
|
|
59
|
-
model: args.model,
|
|
60
|
-
effort: args.effort,
|
|
61
|
-
mode: args.mode,
|
|
62
|
-
abortSignal: context.abort,
|
|
63
|
-
}, (event) => {
|
|
64
|
-
if (event.turns !== undefined) {
|
|
65
|
-
turnsSoFar = event.turns;
|
|
66
|
-
}
|
|
67
|
-
if (event.totalCostUsd !== undefined) {
|
|
68
|
-
costSoFar = event.totalCostUsd;
|
|
69
|
-
}
|
|
70
|
-
const costLabel = `$${costSoFar.toFixed(4)}`;
|
|
71
|
-
if (event.type === 'tool_call') {
|
|
72
|
-
let toolName = 'tool';
|
|
73
|
-
let inputPreview = '';
|
|
74
|
-
try {
|
|
75
|
-
const parsed = JSON.parse(event.text);
|
|
76
|
-
toolName = parsed.name ?? 'tool';
|
|
77
|
-
if (parsed.input) {
|
|
78
|
-
const inputStr = typeof parsed.input === 'string'
|
|
79
|
-
? parsed.input
|
|
80
|
-
: JSON.stringify(parsed.input);
|
|
81
|
-
inputPreview =
|
|
82
|
-
inputStr.length > 150
|
|
83
|
-
? inputStr.slice(0, 150) + '...'
|
|
84
|
-
: inputStr;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// ignore parse errors
|
|
89
|
-
}
|
|
90
|
-
context.metadata({
|
|
91
|
-
title: `Claude Code: Running ${toolName}... (${turnsSoFar} turns, ${costLabel})`,
|
|
92
|
-
metadata: {
|
|
93
|
-
sessionId: event.sessionId,
|
|
94
|
-
type: event.type,
|
|
95
|
-
tool: toolName,
|
|
96
|
-
input: inputPreview,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
else if (event.type === 'assistant') {
|
|
101
|
-
const thinkingPreview = event.text.length > 150
|
|
102
|
-
? event.text.slice(0, 150) + '...'
|
|
103
|
-
: event.text;
|
|
104
|
-
context.metadata({
|
|
105
|
-
title: `Claude Code: Thinking... (${turnsSoFar} turns, ${costLabel})`,
|
|
106
|
-
metadata: {
|
|
107
|
-
sessionId: event.sessionId,
|
|
108
|
-
type: event.type,
|
|
109
|
-
thinking: thinkingPreview,
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
else if (event.type === 'init') {
|
|
114
|
-
context.metadata({
|
|
115
|
-
title: `Claude Code: Session started`,
|
|
116
|
-
metadata: {
|
|
117
|
-
sessionId: event.sessionId,
|
|
118
|
-
prompt: promptPreview,
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
else if (event.type === 'user') {
|
|
123
|
-
const preview = event.text.length > 200
|
|
124
|
-
? event.text.slice(0, 200) + '...'
|
|
125
|
-
: event.text;
|
|
126
|
-
context.metadata({
|
|
127
|
-
title: `Claude Code: Tool result (${turnsSoFar} turns, ${costLabel})`,
|
|
128
|
-
metadata: {
|
|
129
|
-
sessionId: event.sessionId,
|
|
130
|
-
type: event.type,
|
|
131
|
-
output: preview,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
else if (event.type === 'tool_progress') {
|
|
136
|
-
let toolName = 'tool';
|
|
137
|
-
let elapsed = 0;
|
|
138
|
-
try {
|
|
139
|
-
const parsed = JSON.parse(event.text);
|
|
140
|
-
toolName = parsed.name ?? 'tool';
|
|
141
|
-
elapsed = parsed.elapsed ?? 0;
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
// ignore
|
|
145
|
-
}
|
|
146
|
-
context.metadata({
|
|
147
|
-
title: `Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}... (${turnsSoFar} turns, ${costLabel})`,
|
|
148
|
-
metadata: {
|
|
149
|
-
sessionId: event.sessionId,
|
|
150
|
-
type: event.type,
|
|
151
|
-
tool: toolName,
|
|
152
|
-
elapsed,
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
else if (event.type === 'tool_summary') {
|
|
157
|
-
const summary = event.text.length > 200
|
|
158
|
-
? event.text.slice(0, 200) + '...'
|
|
159
|
-
: event.text;
|
|
160
|
-
context.metadata({
|
|
161
|
-
title: `Claude Code: Tool done (${turnsSoFar} turns, ${costLabel})`,
|
|
162
|
-
metadata: {
|
|
163
|
-
sessionId: event.sessionId,
|
|
164
|
-
type: event.type,
|
|
165
|
-
summary,
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
else if (event.type === 'partial') {
|
|
170
|
-
const delta = event.text.length > 200
|
|
171
|
-
? event.text.slice(0, 200) + '...'
|
|
172
|
-
: event.text;
|
|
173
|
-
context.metadata({
|
|
174
|
-
title: `Claude Code: Writing... (${turnsSoFar} turns, ${costLabel})`,
|
|
175
|
-
metadata: {
|
|
176
|
-
sessionId: event.sessionId,
|
|
177
|
-
type: event.type,
|
|
178
|
-
delta,
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
else if (event.type === 'error') {
|
|
183
|
-
context.metadata({
|
|
184
|
-
title: `Claude Code: Error`,
|
|
185
|
-
metadata: {
|
|
186
|
-
sessionId: event.sessionId,
|
|
187
|
-
error: event.text.slice(0, 200),
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
const costLabel = `$${(result.totalCostUsd ?? 0).toFixed(4)}`;
|
|
193
|
-
const turns = result.turns ?? 0;
|
|
194
|
-
const contextWarning = formatContextWarning(result.context);
|
|
195
|
-
if (contextWarning) {
|
|
196
|
-
context.metadata({
|
|
197
|
-
title: `Claude Code: Context at ${result.context.estimatedContextPercent}% (${turns} turns)`,
|
|
198
|
-
metadata: { sessionId: result.sessionId, contextWarning },
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
context.metadata({
|
|
203
|
-
title: `Claude Code: Complete (${turns} turns, ${costLabel})`,
|
|
204
|
-
metadata: { sessionId: result.sessionId },
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
// Fetch recent tool output from the JSONL file for richer feedback.
|
|
208
|
-
let toolOutputs = [];
|
|
209
|
-
if (result.sessionId) {
|
|
210
|
-
try {
|
|
211
|
-
toolOutputs = await services.liveTailer.getToolOutputPreview(result.sessionId, cwd, 3);
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
// Non-critical — the JSONL file may not exist yet.
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return JSON.stringify({
|
|
218
|
-
sessionId: result.sessionId,
|
|
219
|
-
finalText: result.finalText,
|
|
220
|
-
turns: result.turns,
|
|
221
|
-
totalCostUsd: result.totalCostUsd,
|
|
222
|
-
context: result.context,
|
|
223
|
-
contextWarning,
|
|
224
|
-
toolOutputs: toolOutputs.length > 0 ? toolOutputs : undefined,
|
|
225
|
-
}, null, 2);
|
|
240
|
+
return executeEngineerSend({ ...args, mode: 'free' }, context);
|
|
226
241
|
},
|
|
227
242
|
}),
|
|
228
243
|
engineer_compact: tool({
|
|
@@ -323,22 +338,6 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
323
338
|
}, null, 2);
|
|
324
339
|
},
|
|
325
340
|
}),
|
|
326
|
-
engineer_metadata: tool({
|
|
327
|
-
description: 'Inspect Claude slash commands, skills, hooks, and repo settings.',
|
|
328
|
-
args: {
|
|
329
|
-
cwd: tool.schema.string().optional(),
|
|
330
|
-
includeSdkProbe: tool.schema.boolean().default(false),
|
|
331
|
-
},
|
|
332
|
-
async execute(args, context) {
|
|
333
|
-
annotateToolRun(context, 'Collecting Claude metadata', {
|
|
334
|
-
includeSdkProbe: args.includeSdkProbe,
|
|
335
|
-
});
|
|
336
|
-
const metadata = await services.sessions.inspectRepository(args.cwd ?? context.worktree, {
|
|
337
|
-
includeSdkProbe: args.includeSdkProbe,
|
|
338
|
-
});
|
|
339
|
-
return JSON.stringify(metadata, null, 2);
|
|
340
|
-
},
|
|
341
|
-
}),
|
|
342
341
|
engineer_sessions: tool({
|
|
343
342
|
description: 'List Claude sessions or inspect a saved transcript. ' +
|
|
344
343
|
'When sessionId is provided, returns both SDK transcript and local events.',
|
|
@@ -2,8 +2,6 @@ import { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
|
|
|
2
2
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
3
|
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
4
4
|
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
5
|
-
import { ClaudeMetadataService } from '../metadata/claude-metadata.service.js';
|
|
6
|
-
import { RepoClaudeConfigReader } from '../metadata/repo-claude-config-reader.js';
|
|
7
5
|
import { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
8
6
|
import { TranscriptStore } from '../state/transcript-store.js';
|
|
9
7
|
import { ContextTracker } from '../manager/context-tracker.js';
|
|
@@ -19,8 +17,7 @@ export function getOrCreatePluginServices(worktree) {
|
|
|
19
17
|
}
|
|
20
18
|
const approvalManager = new ToolApprovalManager();
|
|
21
19
|
const sdkAdapter = new ClaudeAgentSdkAdapter(undefined, approvalManager);
|
|
22
|
-
const
|
|
23
|
-
const sessionService = new ClaudeSessionService(sdkAdapter, metadataService);
|
|
20
|
+
const sessionService = new ClaudeSessionService(sdkAdapter);
|
|
24
21
|
const contextTracker = new ContextTracker();
|
|
25
22
|
const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.engineerSessionPrompt, managerPromptRegistry.modePrefixes);
|
|
26
23
|
const gitOps = new GitOperations(worktree);
|
package/dist/prompts/registry.js
CHANGED
|
@@ -1,84 +1,76 @@
|
|
|
1
1
|
export const managerPromptRegistry = {
|
|
2
2
|
ctoSystemPrompt: [
|
|
3
|
-
'You are
|
|
4
|
-
'You
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'',
|
|
8
|
-
'
|
|
9
|
-
'',
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'-
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'',
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'1.
|
|
24
|
-
'2.
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'',
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'',
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'',
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'3. If correct: spawn `engineer_build` to run tests/lint/typecheck.',
|
|
54
|
-
'4. If tests pass: git_commit to checkpoint. Update the todo list.',
|
|
55
|
-
'5. If wrong: spawn `engineer_build` with a specific correction.',
|
|
56
|
-
' On second failure for the same issue: git_reset, then rewrite the prompt',
|
|
57
|
-
' incorporating lessons from both failures.',
|
|
3
|
+
'You are a cracked AI-native engineer who uses Claude Code better than anyone.',
|
|
4
|
+
'You build the right thing, with the right quality, on the first try.',
|
|
5
|
+
'',
|
|
6
|
+
'## Core principle: verification-first',
|
|
7
|
+
'Every delegation MUST include how to verify success.',
|
|
8
|
+
'Tests, expected outputs, lint/typecheck commands, or before/after behavior.',
|
|
9
|
+
'This is the single highest-leverage thing you do.',
|
|
10
|
+
'Never delegate without telling the engineer how to prove it worked.',
|
|
11
|
+
'',
|
|
12
|
+
'## Right-size your approach',
|
|
13
|
+
'Not every task needs a plan. Assess complexity, then act:',
|
|
14
|
+
'',
|
|
15
|
+
'**Simple tasks** (typo, rename, add a log line, one-file fix):',
|
|
16
|
+
' Skip investigation. Delegate directly with specific context.',
|
|
17
|
+
'',
|
|
18
|
+
'**Medium tasks** (bug fix, small feature, refactor one module):',
|
|
19
|
+
' Read the relevant code yourself. Understand the current state.',
|
|
20
|
+
' Then delegate with file paths, line numbers, patterns, and verification.',
|
|
21
|
+
'',
|
|
22
|
+
'**Complex tasks** (multi-file feature, architecture change, large refactor):',
|
|
23
|
+
' 1. Investigate: read code, grep for patterns, spawn `engineer_plan` to explore.',
|
|
24
|
+
' 2. If requirements are unclear, ask the user ONE specific question.',
|
|
25
|
+
' Prefer the question tool when discrete options exist.',
|
|
26
|
+
' 3. Write a plan to the todo list (todowrite). Share it with the user.',
|
|
27
|
+
' 4. Execute steps sequentially, committing after each.',
|
|
28
|
+
'',
|
|
29
|
+
'## How to delegate effectively',
|
|
30
|
+
'The engineer does not have your context. Every instruction must be self-contained.',
|
|
31
|
+
'Include:',
|
|
32
|
+
'- Exact files, functions, and line numbers to change.',
|
|
33
|
+
'- Current behavior and desired behavior.',
|
|
34
|
+
'- Code snippets showing the pattern or convention to follow.',
|
|
35
|
+
'- How to verify: "Run `npm test`, expect all green." or',
|
|
36
|
+
' "The function should return null instead of throwing."',
|
|
37
|
+
'',
|
|
38
|
+
'Bad: "Fix the auth bug"',
|
|
39
|
+
'Good: "In src/auth/session.ts, `validateToken` (line 42) throws on expired',
|
|
40
|
+
' tokens instead of returning null. Change it to return null.',
|
|
41
|
+
' Update the caller in src/routes/login.ts:87.',
|
|
42
|
+
' Follow the pattern in src/auth/refresh.ts:23.',
|
|
43
|
+
' Run `npm test -- --grep auth` to verify. All tests should pass."',
|
|
44
|
+
'',
|
|
45
|
+
'## Review every change',
|
|
46
|
+
'After each delegation:',
|
|
47
|
+
'1. git_diff — read the FULL diff. Check for unintended changes, missing tests,',
|
|
48
|
+
' style violations.',
|
|
49
|
+
'2. If correct: spawn `engineer_build` to run tests/lint/typecheck.',
|
|
50
|
+
'3. If tests pass: git_commit to checkpoint.',
|
|
51
|
+
'4. If wrong: spawn `engineer_build` with a specific correction.',
|
|
52
|
+
' On second failure: git_reset and rewrite the prompt from scratch.',
|
|
58
53
|
' Never send three corrections for the same problem.',
|
|
59
54
|
'',
|
|
60
|
-
'Then return to Phase 3 for the next plan step.',
|
|
61
|
-
'',
|
|
62
55
|
'## Engineers (via the Task tool)',
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'-
|
|
67
|
-
' Use for: all code changes, test runs, validation, and fixes.',
|
|
56
|
+
'- `engineer_plan` — read-only investigation. Use for: exploring unfamiliar code,',
|
|
57
|
+
' mapping dependencies, analyzing impact, asking "how does X work?"',
|
|
58
|
+
'- `engineer_build` — implementation. Use for: all code changes, test runs, fixes.',
|
|
59
|
+
'- If steps are independent, spawn multiple engineers in parallel.',
|
|
68
60
|
'',
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
61
|
+
'## Context efficiency',
|
|
62
|
+
'- Use `engineer_plan` for broad exploration so your own context stays clean.',
|
|
63
|
+
'- When spawning engineers for unrelated tasks, tell them to use freshSession:true.',
|
|
64
|
+
'- Keep delegations focused — one concern per engineer invocation.',
|
|
73
65
|
'',
|
|
74
66
|
'## What you must NOT do',
|
|
75
|
-
'- Do NOT call any engineer_* tools directly. Use the Task tool
|
|
67
|
+
'- Do NOT call any engineer_* tools directly. Use the Task tool.',
|
|
76
68
|
'- Do NOT edit files or run bash commands yourself.',
|
|
77
|
-
'- Do NOT
|
|
78
|
-
'- Do NOT
|
|
69
|
+
'- Do NOT skip review — always git_diff after delegation.',
|
|
70
|
+
'- Do NOT delegate without verification criteria.',
|
|
79
71
|
'',
|
|
80
72
|
'## Tools reference',
|
|
81
|
-
'todowrite / todoread — track
|
|
73
|
+
'todowrite / todoread — track multi-step work',
|
|
82
74
|
'question — ask the user structured questions with options',
|
|
83
75
|
'git_diff — review all uncommitted changes',
|
|
84
76
|
'git_commit — stage all + commit',
|
|
@@ -95,113 +87,82 @@ export const managerPromptRegistry = {
|
|
|
95
87
|
'State the blocker, what you need, and a concrete suggestion to unblock.',
|
|
96
88
|
].join('\n'),
|
|
97
89
|
engineerPlanPrompt: [
|
|
98
|
-
'You manage a Claude Code engineer for read-only investigation
|
|
99
|
-
'You receive objectives from the CTO and operate the engineer session to fulfill them.',
|
|
90
|
+
'You manage a Claude Code engineer for read-only investigation.',
|
|
100
91
|
'',
|
|
101
92
|
'## Behavior',
|
|
102
|
-
'-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
'- Send the objective to the engineer using engineer_send_plan.',
|
|
94
|
+
"- Return the engineer's response verbatim. Do not summarize.",
|
|
95
|
+
'- Use freshSession:true on engineer_send_plan when the task is unrelated to prior work.',
|
|
105
96
|
'',
|
|
106
97
|
'## Context management',
|
|
107
|
-
'
|
|
108
|
-
'-
|
|
109
|
-
'
|
|
110
|
-
'
|
|
111
|
-
'-
|
|
112
|
-
'-
|
|
113
|
-
'-
|
|
114
|
-
'',
|
|
115
|
-
'## Model and effort selection',
|
|
116
|
-
'Choose model and effort deliberately:',
|
|
117
|
-
'- claude-opus-4-6 + high effort: default for complex analysis.',
|
|
118
|
-
'- claude-sonnet-4-6 or claude-sonnet-4-5: lighter analysis tasks.',
|
|
119
|
-
'- effort "medium": acceptable for simple lookups or straightforward analysis.',
|
|
120
|
-
"- Do not use Haiku for this plugin's coding-agent role.",
|
|
121
|
-
'',
|
|
122
|
-
'## Diagnostics',
|
|
123
|
-
'- Use engineer_metadata to inspect repo Claude config when needed.',
|
|
124
|
-
'- Use engineer_sessions to review prior session transcripts.',
|
|
125
|
-
'- Use engineer_runs to review prior run records.',
|
|
98
|
+
'- Check engineer_status before sending.',
|
|
99
|
+
'- Under 50%: proceed. Over 70%: engineer_compact. Over 85%: engineer_clear.',
|
|
100
|
+
'',
|
|
101
|
+
'## Model selection',
|
|
102
|
+
'- claude-opus-4-6 + high: complex analysis (default).',
|
|
103
|
+
'- claude-sonnet-4-6: lighter analysis.',
|
|
104
|
+
'- effort "medium": simple lookups.',
|
|
126
105
|
'',
|
|
127
106
|
'## What you must NOT do',
|
|
128
107
|
'- Do NOT investigate on your own — no read, grep, glob, or web tools.',
|
|
129
108
|
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
130
|
-
'- Do NOT add
|
|
109
|
+
'- Do NOT add commentary to the engineer response.',
|
|
131
110
|
].join('\n'),
|
|
132
111
|
engineerBuildPrompt: [
|
|
133
|
-
'You manage a Claude Code engineer for implementation
|
|
134
|
-
'You receive objectives from the CTO and operate the engineer session to fulfill them.',
|
|
112
|
+
'You manage a Claude Code engineer for implementation.',
|
|
135
113
|
'',
|
|
136
114
|
'## Behavior',
|
|
137
|
-
'-
|
|
138
|
-
|
|
139
|
-
|
|
115
|
+
'- Send the objective to the engineer using engineer_send_build.',
|
|
116
|
+
"- Return the engineer's response verbatim. Do not summarize.",
|
|
117
|
+
'- Use freshSession:true on engineer_send_build when the task is unrelated to prior work.',
|
|
140
118
|
'',
|
|
141
119
|
'## Context management',
|
|
142
|
-
'
|
|
143
|
-
'-
|
|
144
|
-
'
|
|
145
|
-
'
|
|
146
|
-
'-
|
|
147
|
-
'-
|
|
148
|
-
'-
|
|
149
|
-
'',
|
|
150
|
-
'## Model and effort selection',
|
|
151
|
-
'Choose model and effort deliberately:',
|
|
152
|
-
'- claude-opus-4-6 + high effort: default for most coding tasks.',
|
|
153
|
-
'- claude-sonnet-4-6 or claude-sonnet-4-5: faster/lighter work (simple renames,',
|
|
154
|
-
' formatting, test scaffolding).',
|
|
155
|
-
'- effort "medium": acceptable for lighter tasks that do not require deep reasoning.',
|
|
156
|
-
'- effort "max": reserve for unusually hard problems (complex refactors,',
|
|
157
|
-
' subtle concurrency bugs, large cross-cutting changes).',
|
|
158
|
-
"- Do not use Haiku for this plugin's coding-agent role.",
|
|
159
|
-
'',
|
|
160
|
-
'## Diagnostics',
|
|
161
|
-
'- Use engineer_metadata to inspect repo Claude config when needed.',
|
|
162
|
-
'- Use engineer_sessions to review prior session transcripts.',
|
|
163
|
-
'- Use engineer_runs to review prior run records.',
|
|
120
|
+
'- Check engineer_status before sending.',
|
|
121
|
+
'- Under 50%: proceed. Over 70%: engineer_compact. Over 85%: engineer_clear.',
|
|
122
|
+
'',
|
|
123
|
+
'## Model selection',
|
|
124
|
+
'- claude-opus-4-6 + high: most coding tasks (default).',
|
|
125
|
+
'- claude-sonnet-4-6: simple renames, formatting, scaffolding.',
|
|
126
|
+
'- effort "max": complex refactors, subtle bugs, cross-cutting changes.',
|
|
164
127
|
'',
|
|
165
128
|
'## What you must NOT do',
|
|
166
129
|
'- Do NOT investigate on your own — no read, grep, glob, or web tools.',
|
|
167
130
|
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
168
|
-
'- Do NOT add
|
|
131
|
+
'- Do NOT add commentary to the engineer response.',
|
|
169
132
|
].join('\n'),
|
|
170
133
|
engineerSessionPrompt: [
|
|
171
|
-
'You are
|
|
172
|
-
'Treat each message as a precise instruction from the CTO.',
|
|
134
|
+
'You are an expert engineer. Execute instructions precisely.',
|
|
173
135
|
'',
|
|
174
|
-
'##
|
|
175
|
-
'- Execute
|
|
176
|
-
'- Be concise — no preamble, no restating the task.',
|
|
136
|
+
'## Rules',
|
|
137
|
+
'- Execute directly. No preamble, no restating the task.',
|
|
177
138
|
'- Prefer targeted file reads over reading entire files.',
|
|
178
|
-
'- Use
|
|
179
|
-
'',
|
|
180
|
-
'## Quality expectations',
|
|
139
|
+
'- Use subagents for independent parallel work.',
|
|
181
140
|
'- Follow existing repo conventions (naming, style, patterns).',
|
|
182
|
-
'- When creating or modifying code, consider edge cases and error handling.',
|
|
183
|
-
'- When modifying existing code, preserve surrounding style and structure.',
|
|
184
|
-
'- If asked to implement a feature, include relevant tests unless told otherwise.',
|
|
185
|
-
'- Run tests/lint/typecheck when instructed; report exact output on failure.',
|
|
186
141
|
'',
|
|
187
|
-
'##
|
|
142
|
+
'## Verification',
|
|
143
|
+
'- Always verify your own work before reporting done.',
|
|
144
|
+
'- Run tests, lint, or typecheck when the instruction says to.',
|
|
145
|
+
'- If no verification was specified, still run relevant tests if they exist.',
|
|
146
|
+
'- Report exact output on failure.',
|
|
147
|
+
'',
|
|
148
|
+
'## Git boundary — do NOT run:',
|
|
188
149
|
'git commit, git push, git reset, git checkout, git stash.',
|
|
189
|
-
'
|
|
150
|
+
'Git operations are handled externally.',
|
|
190
151
|
'',
|
|
191
152
|
'## Reporting',
|
|
192
|
-
'- End with
|
|
193
|
-
'- Report blockers immediately with specifics: file, line, error
|
|
194
|
-
'- If
|
|
153
|
+
'- End with: what was done, what was verified, what passed/failed.',
|
|
154
|
+
'- Report blockers immediately with specifics: file, line, error.',
|
|
155
|
+
'- If partially complete, state exactly what remains.',
|
|
195
156
|
].join('\n'),
|
|
196
157
|
modePrefixes: {
|
|
197
158
|
plan: [
|
|
198
|
-
'[PLAN MODE]
|
|
199
|
-
'Do NOT use ExitPlanMode or write
|
|
159
|
+
'[PLAN MODE] Read-only. Do NOT create or edit any files.',
|
|
160
|
+
'Do NOT use ExitPlanMode or write artifacts to disk.',
|
|
200
161
|
'Use read, grep, glob, and search tools only.',
|
|
201
162
|
'Analyze the codebase and produce a detailed implementation plan:',
|
|
202
163
|
'files to change, functions to modify, new files to create, test strategy,',
|
|
203
|
-
'and
|
|
204
|
-
'Return the entire plan inline in your response
|
|
164
|
+
'and risks. End with a numbered step-by-step plan.',
|
|
165
|
+
'Return the entire plan inline in your response.',
|
|
205
166
|
].join(' '),
|
|
206
167
|
free: '',
|
|
207
168
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { isFileNotFoundError, writeJsonAtomically
|
|
3
|
+
import { isFileNotFoundError, writeJsonAtomically } from '../util/fs-helpers.js';
|
|
4
4
|
export class FileRunStateStore {
|
|
5
5
|
baseDirectoryName;
|
|
6
6
|
writeQueues = new Map();
|
|
@@ -70,9 +70,7 @@ export class FileRunStateStore {
|
|
|
70
70
|
}
|
|
71
71
|
async enqueueWrite(key, operation) {
|
|
72
72
|
const previousOperation = this.writeQueues.get(key) ?? Promise.resolve();
|
|
73
|
-
const resultPromise = previousOperation
|
|
74
|
-
.catch(() => undefined)
|
|
75
|
-
.then(operation);
|
|
73
|
+
const resultPromise = previousOperation.catch(() => undefined).then(operation);
|
|
76
74
|
const settledPromise = resultPromise.then(() => undefined, () => undefined);
|
|
77
75
|
this.writeQueues.set(key, settledPromise);
|
|
78
76
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { appendTranscriptEvents, stripTrailingPartials
|
|
4
|
-
import { isFileNotFoundError, writeJsonAtomically
|
|
3
|
+
import { appendTranscriptEvents, stripTrailingPartials } from '../util/transcript-append.js';
|
|
4
|
+
import { isFileNotFoundError, writeJsonAtomically } from '../util/fs-helpers.js';
|
|
5
5
|
export class TranscriptStore {
|
|
6
6
|
baseDirectoryName;
|
|
7
7
|
constructor(baseDirectoryName = '.claude-manager') {
|
|
@@ -13,7 +13,6 @@ export interface ManagerPromptRegistry {
|
|
|
13
13
|
critical: string;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
export type ClaudeSettingSource = 'user' | 'project' | 'local';
|
|
17
16
|
export type SessionMode = 'plan' | 'free';
|
|
18
17
|
export interface ClaudeCommandMetadata {
|
|
19
18
|
name: string;
|
|
@@ -22,34 +21,12 @@ export interface ClaudeCommandMetadata {
|
|
|
22
21
|
source: 'sdk' | 'skill' | 'command';
|
|
23
22
|
path?: string;
|
|
24
23
|
}
|
|
25
|
-
export interface ClaudeSkillMetadata {
|
|
26
|
-
name: string;
|
|
27
|
-
description: string;
|
|
28
|
-
path: string;
|
|
29
|
-
source: 'skill' | 'command';
|
|
30
|
-
}
|
|
31
|
-
export interface ClaudeHookMetadata {
|
|
32
|
-
name: string;
|
|
33
|
-
matcher?: string;
|
|
34
|
-
sourcePath: string;
|
|
35
|
-
commandCount: number;
|
|
36
|
-
}
|
|
37
24
|
export interface ClaudeAgentMetadata {
|
|
38
25
|
name: string;
|
|
39
26
|
description: string;
|
|
40
27
|
model?: string;
|
|
41
28
|
source: 'sdk' | 'filesystem';
|
|
42
29
|
}
|
|
43
|
-
export interface ClaudeMetadataSnapshot {
|
|
44
|
-
collectedAt: string;
|
|
45
|
-
cwd: string;
|
|
46
|
-
commands: ClaudeCommandMetadata[];
|
|
47
|
-
skills: ClaudeSkillMetadata[];
|
|
48
|
-
hooks: ClaudeHookMetadata[];
|
|
49
|
-
agents: ClaudeAgentMetadata[];
|
|
50
|
-
claudeMdPath?: string;
|
|
51
|
-
settingsPaths: string[];
|
|
52
|
-
}
|
|
53
30
|
export interface ClaudeSessionEvent {
|
|
54
31
|
type: 'init' | 'assistant' | 'partial' | 'user' | 'tool_call' | 'tool_progress' | 'tool_summary' | 'status' | 'system' | 'result' | 'error';
|
|
55
32
|
sessionId?: string;
|
|
@@ -75,7 +52,7 @@ export interface RunClaudeSessionInput {
|
|
|
75
52
|
forkSession?: boolean;
|
|
76
53
|
persistSession?: boolean;
|
|
77
54
|
includePartialMessages?: boolean;
|
|
78
|
-
settingSources?:
|
|
55
|
+
settingSources?: Array<'user' | 'project' | 'local'>;
|
|
79
56
|
maxTurns?: number;
|
|
80
57
|
abortSignal?: AbortSignal;
|
|
81
58
|
}
|
package/dist/util/fs-helpers.js
CHANGED
|
@@ -6,7 +6,5 @@ export async function writeJsonAtomically(filePath, data) {
|
|
|
6
6
|
await fs.rename(tempPath, filePath);
|
|
7
7
|
}
|
|
8
8
|
export function isFileNotFoundError(error) {
|
|
9
|
-
return (error instanceof Error &&
|
|
10
|
-
'code' in error &&
|
|
11
|
-
error.code === 'ENOENT');
|
|
9
|
+
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
12
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doingdev/opencode-claude-manager-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "OpenCode plugin that orchestrates Claude Code sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@anthropic-ai/claude-agent-sdk": "^0.2.81",
|
|
33
33
|
"@opencode-ai/plugin": "^1.2.27",
|
|
34
|
-
"json5": "^2.2.3",
|
|
35
34
|
"zod": "^4.1.8"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|