@doingdev/opencode-claude-manager-plugin 0.1.10 → 0.1.11
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/README.md +26 -29
- package/dist/claude/claude-agent-sdk-adapter.js +248 -61
- package/dist/index.d.ts +1 -1
- package/dist/manager/context-tracker.d.ts +33 -0
- package/dist/manager/context-tracker.js +108 -0
- package/dist/manager/git-operations.d.ts +12 -0
- package/dist/manager/git-operations.js +76 -0
- package/dist/manager/manager-orchestrator.d.ts +1 -4
- package/dist/manager/manager-orchestrator.js +37 -53
- package/dist/manager/persistent-manager.d.ts +64 -0
- package/dist/manager/persistent-manager.js +152 -0
- package/dist/manager/session-controller.d.ts +38 -0
- package/dist/manager/session-controller.js +135 -0
- package/dist/manager/task-planner.d.ts +2 -2
- package/dist/manager/task-planner.js +4 -31
- package/dist/plugin/claude-manager.plugin.d.ts +2 -2
- package/dist/plugin/claude-manager.plugin.js +150 -192
- package/dist/plugin/service-factory.d.ts +2 -2
- package/dist/plugin/service-factory.js +12 -4
- package/dist/prompts/registry.js +42 -8
- package/dist/state/file-run-state-store.d.ts +5 -5
- package/dist/types/contracts.d.ts +68 -45
- package/dist/util/transcript-append.d.ts +7 -0
- package/dist/util/transcript-append.js +29 -0
- package/package.json +10 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ManagedSubtaskPlan
|
|
1
|
+
import type { ManagedSubtaskPlan } from '../types/contracts.js';
|
|
2
2
|
export declare class TaskPlanner {
|
|
3
|
-
plan(
|
|
3
|
+
plan(tasks: string[], maxSubagents: number): ManagedSubtaskPlan[];
|
|
4
4
|
private createPlan;
|
|
5
5
|
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
export class TaskPlanner {
|
|
3
|
-
plan(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const splitCandidates = extractTaskCandidates(task).slice(0, maxSubagents);
|
|
8
|
-
if (mode === 'split' && splitCandidates.length === 0) {
|
|
9
|
-
return [this.createPlan('Primary task', task)];
|
|
10
|
-
}
|
|
11
|
-
if (mode === 'auto' && splitCandidates.length < 2) {
|
|
12
|
-
return [this.createPlan('Primary task', task)];
|
|
13
|
-
}
|
|
14
|
-
if (splitCandidates.length === 0) {
|
|
15
|
-
return [this.createPlan('Primary task', task)];
|
|
16
|
-
}
|
|
17
|
-
return splitCandidates.map((candidate, index) => this.createPlan(`Subtask ${index + 1}`, candidate));
|
|
3
|
+
plan(tasks, maxSubagents) {
|
|
4
|
+
return tasks
|
|
5
|
+
.slice(0, maxSubagents)
|
|
6
|
+
.map((task, index) => this.createPlan(tasks.length === 1 ? 'Primary task' : `Subtask ${index + 1}`, task));
|
|
18
7
|
}
|
|
19
8
|
createPlan(title, prompt) {
|
|
20
9
|
return {
|
|
@@ -24,19 +13,3 @@ export class TaskPlanner {
|
|
|
24
13
|
};
|
|
25
14
|
}
|
|
26
15
|
}
|
|
27
|
-
function extractTaskCandidates(task) {
|
|
28
|
-
const listMatches = task
|
|
29
|
-
.split(/\r?\n/)
|
|
30
|
-
.map((line) => line.trim())
|
|
31
|
-
.filter((line) => /^(-|\*|\d+\.)\s+/.test(line))
|
|
32
|
-
.map((line) => line.replace(/^(-|\*|\d+\.)\s+/, '').trim())
|
|
33
|
-
.filter(Boolean);
|
|
34
|
-
if (listMatches.length > 0) {
|
|
35
|
-
return listMatches;
|
|
36
|
-
}
|
|
37
|
-
const sentenceMatches = task
|
|
38
|
-
.split(/;|\n{2,}/)
|
|
39
|
-
.map((part) => part.trim())
|
|
40
|
-
.filter((part) => part.length > 20);
|
|
41
|
-
return sentenceMatches.length > 1 ? sentenceMatches : [];
|
|
42
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PersistentRunRecord } from '../types/contracts.js';
|
|
3
3
|
export declare const ClaudeManagerPlugin: Plugin;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function formatRunToolResult(run: PersistentRunRecord): string;
|
|
@@ -2,11 +2,15 @@ import { tool } from '@opencode-ai/plugin';
|
|
|
2
2
|
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
3
3
|
import { getOrCreatePluginServices } from './service-factory.js';
|
|
4
4
|
const MANAGER_TOOL_IDS = [
|
|
5
|
-
'
|
|
5
|
+
'claude_manager_send',
|
|
6
|
+
'claude_manager_git_diff',
|
|
7
|
+
'claude_manager_git_commit',
|
|
8
|
+
'claude_manager_git_reset',
|
|
9
|
+
'claude_manager_clear',
|
|
10
|
+
'claude_manager_status',
|
|
6
11
|
'claude_manager_metadata',
|
|
7
12
|
'claude_manager_sessions',
|
|
8
13
|
'claude_manager_runs',
|
|
9
|
-
'claude_manager_cleanup_run',
|
|
10
14
|
];
|
|
11
15
|
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
12
16
|
const services = getOrCreatePluginServices(worktree);
|
|
@@ -16,37 +20,29 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
16
20
|
config.command ??= {};
|
|
17
21
|
config.permission ??= {};
|
|
18
22
|
const globalPermissions = config.permission;
|
|
19
|
-
const managerPermissions = {
|
|
20
|
-
|
|
21
|
-
claude_manager_metadata: 'allow',
|
|
22
|
-
claude_manager_sessions: 'allow',
|
|
23
|
-
claude_manager_runs: 'allow',
|
|
24
|
-
claude_manager_cleanup_run: 'allow',
|
|
25
|
-
};
|
|
26
|
-
const researchPermissions = {
|
|
27
|
-
claude_manager_run: 'deny',
|
|
28
|
-
claude_manager_metadata: 'allow',
|
|
29
|
-
claude_manager_sessions: 'allow',
|
|
30
|
-
claude_manager_runs: 'allow',
|
|
31
|
-
claude_manager_cleanup_run: 'deny',
|
|
32
|
-
};
|
|
23
|
+
const managerPermissions = {};
|
|
24
|
+
const researchPermissions = {};
|
|
33
25
|
for (const toolId of MANAGER_TOOL_IDS) {
|
|
34
26
|
globalPermissions[toolId] ??= 'deny';
|
|
27
|
+
managerPermissions[toolId] = 'allow';
|
|
28
|
+
// Research agent can inspect but not send or modify
|
|
29
|
+
researchPermissions[toolId] =
|
|
30
|
+
toolId === 'claude_manager_send' ||
|
|
31
|
+
toolId === 'claude_manager_git_commit' ||
|
|
32
|
+
toolId === 'claude_manager_git_reset' ||
|
|
33
|
+
toolId === 'claude_manager_clear'
|
|
34
|
+
? 'deny'
|
|
35
|
+
: 'allow';
|
|
35
36
|
}
|
|
36
37
|
config.agent['claude-manager'] ??= {
|
|
37
|
-
description: 'Primary agent that
|
|
38
|
+
description: 'Primary agent that operates Claude Code through a persistent session, reviews work via git diff, and commits/resets changes.',
|
|
38
39
|
mode: 'primary',
|
|
39
40
|
color: 'accent',
|
|
40
41
|
permission: {
|
|
41
42
|
'*': 'deny',
|
|
42
43
|
...managerPermissions,
|
|
43
44
|
},
|
|
44
|
-
prompt:
|
|
45
|
-
managerPromptRegistry.managerSystemPrompt,
|
|
46
|
-
'When Claude Code delegation is useful, prefer the claude_manager_run tool instead of simulating the work yourself.',
|
|
47
|
-
'Use claude_manager_metadata to inspect available Claude commands, skills, and hooks before making assumptions.',
|
|
48
|
-
'Use claude_manager_sessions and claude_manager_runs to inspect prior work before starting a new Claude session.',
|
|
49
|
-
].join(' '),
|
|
45
|
+
prompt: managerPromptRegistry.managerSystemPrompt,
|
|
50
46
|
};
|
|
51
47
|
config.agent['claude-manager-research'] ??= {
|
|
52
48
|
description: 'Subagent that inspects Claude metadata, prior sessions, and manager runs without changing repository state.',
|
|
@@ -57,11 +53,20 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
57
53
|
...researchPermissions,
|
|
58
54
|
},
|
|
59
55
|
prompt: [
|
|
60
|
-
managerPromptRegistry.subagentSystemPrompt,
|
|
61
56
|
'Focus on inspection and summarization.',
|
|
62
|
-
'
|
|
57
|
+
'Use claude_manager_status, claude_manager_metadata, claude_manager_sessions, and claude_manager_runs to gather information.',
|
|
63
58
|
].join(' '),
|
|
64
59
|
};
|
|
60
|
+
config.command['claude-run'] ??= {
|
|
61
|
+
description: 'Send a task to the persistent Claude Code session.',
|
|
62
|
+
agent: 'claude-manager',
|
|
63
|
+
subtask: true,
|
|
64
|
+
template: [
|
|
65
|
+
'Use claude_manager_send to send the following task to Claude Code:',
|
|
66
|
+
'$ARGUMENTS',
|
|
67
|
+
'After it completes, review the result and use claude_manager_git_diff if changes were expected. Commit or reset accordingly.',
|
|
68
|
+
].join('\n\n'),
|
|
69
|
+
};
|
|
65
70
|
config.command['claude-metadata'] ??= {
|
|
66
71
|
description: 'Inspect bundled Claude commands, skills, hooks, and agents.',
|
|
67
72
|
agent: 'claude-manager-research',
|
|
@@ -71,16 +76,6 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
71
76
|
'Summarize Claude commands, skills, hooks, discovered agents, and important config files.',
|
|
72
77
|
].join(' '),
|
|
73
78
|
};
|
|
74
|
-
config.command['claude-run'] ??= {
|
|
75
|
-
description: 'Delegate a task to Claude Code through the manager plugin.',
|
|
76
|
-
agent: 'claude-manager',
|
|
77
|
-
subtask: true,
|
|
78
|
-
template: [
|
|
79
|
-
'Call claude_manager_run immediately for the following task:',
|
|
80
|
-
'$ARGUMENTS',
|
|
81
|
-
'Avoid planning narration before the tool call. After it completes, return a concise result summary.',
|
|
82
|
-
].join('\n\n'),
|
|
83
|
-
};
|
|
84
79
|
config.command['claude-sessions'] ??= {
|
|
85
80
|
description: 'Inspect Claude session history and manager run records.',
|
|
86
81
|
agent: 'claude-manager-research',
|
|
@@ -99,41 +94,103 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
99
94
|
}
|
|
100
95
|
},
|
|
101
96
|
tool: {
|
|
102
|
-
|
|
103
|
-
description: '
|
|
97
|
+
claude_manager_send: tool({
|
|
98
|
+
description: 'Send a message to the persistent Claude Code session. ' +
|
|
99
|
+
'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
|
|
100
|
+
'Returns the assistant response and current context health snapshot.',
|
|
104
101
|
args: {
|
|
105
|
-
|
|
106
|
-
mode: tool.schema.enum(['auto', 'single', 'split']).default('auto'),
|
|
107
|
-
maxSubagents: tool.schema.number().int().min(1).max(8).default(3),
|
|
108
|
-
useWorktrees: tool.schema.boolean().default(true),
|
|
109
|
-
includeProjectSettings: tool.schema.boolean().default(true),
|
|
102
|
+
message: tool.schema.string().min(1),
|
|
110
103
|
model: tool.schema.string().optional(),
|
|
111
104
|
cwd: tool.schema.string().optional(),
|
|
112
105
|
},
|
|
113
106
|
async execute(args, context) {
|
|
114
|
-
annotateToolRun(context, '
|
|
115
|
-
|
|
116
|
-
mode: args.mode,
|
|
107
|
+
annotateToolRun(context, 'Sending to Claude Code session', {
|
|
108
|
+
hasActiveSession: services.manager.getStatus().sessionId !== null,
|
|
117
109
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}, async (run) => {
|
|
128
|
-
const progressView = buildRunProgressView(run);
|
|
129
|
-
const signature = JSON.stringify(progressView);
|
|
130
|
-
if (signature === lastProgressSignature) {
|
|
131
|
-
return;
|
|
110
|
+
const result = await services.manager.sendMessage(args.cwd ?? context.worktree, args.message, { model: args.model }, (event) => {
|
|
111
|
+
if (event.type === 'assistant' || event.type === 'tool_call') {
|
|
112
|
+
context.metadata({
|
|
113
|
+
title: 'Claude Code working...',
|
|
114
|
+
metadata: {
|
|
115
|
+
type: event.type,
|
|
116
|
+
preview: event.text.slice(0, 200),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
132
119
|
}
|
|
133
|
-
lastProgressSignature = signature;
|
|
134
|
-
context.metadata(progressView);
|
|
135
120
|
});
|
|
136
|
-
return
|
|
121
|
+
return JSON.stringify({
|
|
122
|
+
sessionId: result.sessionId,
|
|
123
|
+
finalText: result.finalText,
|
|
124
|
+
turns: result.turns,
|
|
125
|
+
totalCostUsd: result.totalCostUsd,
|
|
126
|
+
context: result.context,
|
|
127
|
+
contextWarning: formatContextWarning(result.context),
|
|
128
|
+
}, null, 2);
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
claude_manager_git_diff: tool({
|
|
132
|
+
description: 'Run git diff to see all current changes (staged + unstaged) relative to HEAD.',
|
|
133
|
+
args: {
|
|
134
|
+
cwd: tool.schema.string().optional(),
|
|
135
|
+
},
|
|
136
|
+
async execute(_args, context) {
|
|
137
|
+
annotateToolRun(context, 'Running git diff', {});
|
|
138
|
+
const result = await services.manager.gitDiff();
|
|
139
|
+
return JSON.stringify(result, null, 2);
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
claude_manager_git_commit: tool({
|
|
143
|
+
description: 'Stage all changes and commit with the given message.',
|
|
144
|
+
args: {
|
|
145
|
+
message: tool.schema.string().min(1),
|
|
146
|
+
cwd: tool.schema.string().optional(),
|
|
147
|
+
},
|
|
148
|
+
async execute(args, context) {
|
|
149
|
+
annotateToolRun(context, 'Committing changes', {
|
|
150
|
+
message: args.message,
|
|
151
|
+
});
|
|
152
|
+
const result = await services.manager.gitCommit(args.message);
|
|
153
|
+
return JSON.stringify(result, null, 2);
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
claude_manager_git_reset: tool({
|
|
157
|
+
description: 'Run git reset --hard HEAD and git clean -fd to discard ALL uncommitted changes and untracked files.',
|
|
158
|
+
args: {
|
|
159
|
+
cwd: tool.schema.string().optional(),
|
|
160
|
+
},
|
|
161
|
+
async execute(_args, context) {
|
|
162
|
+
annotateToolRun(context, 'Resetting working directory', {});
|
|
163
|
+
const result = await services.manager.gitReset();
|
|
164
|
+
return JSON.stringify(result, null, 2);
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
claude_manager_clear: tool({
|
|
168
|
+
description: 'Clear the active Claude Code session. The next send will start a fresh session. ' +
|
|
169
|
+
'Use when context is full, the session is confused, or starting a different task.',
|
|
170
|
+
args: {
|
|
171
|
+
cwd: tool.schema.string().optional(),
|
|
172
|
+
reason: tool.schema.string().optional(),
|
|
173
|
+
},
|
|
174
|
+
async execute(args, context) {
|
|
175
|
+
annotateToolRun(context, 'Clearing session', {
|
|
176
|
+
reason: args.reason,
|
|
177
|
+
});
|
|
178
|
+
const clearedId = await services.manager.clearSession(args.cwd ?? context.worktree);
|
|
179
|
+
return JSON.stringify({ clearedSessionId: clearedId });
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
claude_manager_status: tool({
|
|
183
|
+
description: 'Get the current persistent session status: context usage %, turns, cost, active session ID.',
|
|
184
|
+
args: {
|
|
185
|
+
cwd: tool.schema.string().optional(),
|
|
186
|
+
},
|
|
187
|
+
async execute(_args, context) {
|
|
188
|
+
annotateToolRun(context, 'Checking session status', {});
|
|
189
|
+
const status = services.manager.getStatus();
|
|
190
|
+
return JSON.stringify({
|
|
191
|
+
...status,
|
|
192
|
+
contextWarning: formatContextWarning(status),
|
|
193
|
+
}, null, 2);
|
|
137
194
|
},
|
|
138
195
|
}),
|
|
139
196
|
claude_manager_metadata: tool({
|
|
@@ -169,7 +226,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
169
226
|
},
|
|
170
227
|
}),
|
|
171
228
|
claude_manager_runs: tool({
|
|
172
|
-
description: 'List manager
|
|
229
|
+
description: 'List persistent manager run records.',
|
|
173
230
|
args: {
|
|
174
231
|
cwd: tool.schema.string().optional(),
|
|
175
232
|
runId: tool.schema.string().optional(),
|
|
@@ -184,20 +241,6 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
184
241
|
return JSON.stringify(runs, null, 2);
|
|
185
242
|
},
|
|
186
243
|
}),
|
|
187
|
-
claude_manager_cleanup_run: tool({
|
|
188
|
-
description: 'Explicitly remove git worktrees created for a recorded manager run.',
|
|
189
|
-
args: {
|
|
190
|
-
runId: tool.schema.string().min(1),
|
|
191
|
-
cwd: tool.schema.string().optional(),
|
|
192
|
-
},
|
|
193
|
-
async execute(args, context) {
|
|
194
|
-
annotateToolRun(context, 'Cleaning manager worktrees', {
|
|
195
|
-
runId: args.runId,
|
|
196
|
-
});
|
|
197
|
-
const run = await services.manager.cleanupRunWorktrees(args.cwd ?? context.worktree, args.runId);
|
|
198
|
-
return JSON.stringify(run, null, 2);
|
|
199
|
-
},
|
|
200
|
-
}),
|
|
201
244
|
},
|
|
202
245
|
};
|
|
203
246
|
};
|
|
@@ -208,11 +251,11 @@ function buildCommandText(command, rawArguments) {
|
|
|
208
251
|
const argumentsText = rawArguments.trim();
|
|
209
252
|
if (command === 'claude-run') {
|
|
210
253
|
return [
|
|
211
|
-
'
|
|
254
|
+
'Use `claude_manager_send` to send the following task to Claude Code:',
|
|
212
255
|
argumentsText
|
|
213
|
-
?
|
|
214
|
-
: '
|
|
215
|
-
'
|
|
256
|
+
? argumentsText
|
|
257
|
+
: 'Inspect the current repository and wait for follow-up instructions.',
|
|
258
|
+
'After it completes, review the result. If code changes were expected, use `claude_manager_git_diff` to review, then `claude_manager_git_commit` or `claude_manager_git_reset` accordingly.',
|
|
216
259
|
].join('\n\n');
|
|
217
260
|
}
|
|
218
261
|
if (command === 'claude-metadata') {
|
|
@@ -237,125 +280,40 @@ function buildCommandText(command, rawArguments) {
|
|
|
237
280
|
}
|
|
238
281
|
function rewriteCommandParts(parts, text) {
|
|
239
282
|
let hasRewrittenText = false;
|
|
240
|
-
|
|
283
|
+
return parts.map((part) => {
|
|
241
284
|
if (part.type !== 'text' || hasRewrittenText) {
|
|
242
285
|
return part;
|
|
243
286
|
}
|
|
244
287
|
hasRewrittenText = true;
|
|
245
|
-
return {
|
|
246
|
-
...part,
|
|
247
|
-
text,
|
|
248
|
-
};
|
|
288
|
+
return { ...part, text };
|
|
249
289
|
});
|
|
250
|
-
return rewrittenParts;
|
|
251
290
|
}
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
291
|
+
function formatContextWarning(context) {
|
|
292
|
+
const { warningLevel, estimatedContextPercent, totalTurns, totalCostUsd } = context;
|
|
293
|
+
if (warningLevel === 'ok' || estimatedContextPercent === null) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const templates = managerPromptRegistry.contextWarnings;
|
|
297
|
+
const template = warningLevel === 'critical'
|
|
298
|
+
? templates.critical
|
|
299
|
+
: warningLevel === 'high'
|
|
300
|
+
? templates.high
|
|
301
|
+
: templates.moderate;
|
|
302
|
+
return template
|
|
303
|
+
.replace('{percent}', String(estimatedContextPercent))
|
|
304
|
+
.replace('{turns}', String(totalTurns))
|
|
305
|
+
.replace('{cost}', totalCostUsd.toFixed(2));
|
|
306
|
+
}
|
|
307
|
+
export function formatRunToolResult(run) {
|
|
257
308
|
return JSON.stringify({
|
|
258
309
|
runId: run.id,
|
|
259
310
|
status: run.status,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
worktreeMode: session.worktreeMode,
|
|
268
|
-
branchName: session.branchName,
|
|
269
|
-
turns: session.turns,
|
|
270
|
-
totalCostUsd: session.totalCostUsd,
|
|
271
|
-
})),
|
|
272
|
-
inspectRun: {
|
|
273
|
-
tool: 'claude_manager_runs',
|
|
274
|
-
runId: run.id,
|
|
275
|
-
},
|
|
311
|
+
task: run.task,
|
|
312
|
+
sessionId: run.sessionId,
|
|
313
|
+
finalSummary: run.finalSummary,
|
|
314
|
+
messageCount: run.messages.length,
|
|
315
|
+
actionCount: run.actions.length,
|
|
316
|
+
commits: run.commits,
|
|
317
|
+
context: run.context,
|
|
276
318
|
}, null, 2);
|
|
277
319
|
}
|
|
278
|
-
function buildRunProgressView(run) {
|
|
279
|
-
const completed = run.sessions.filter((session) => session.status === 'completed').length;
|
|
280
|
-
const failed = run.sessions.filter((session) => session.status === 'failed').length;
|
|
281
|
-
const running = run.sessions.filter((session) => session.status === 'running').length;
|
|
282
|
-
const pending = run.sessions.filter((session) => session.status === 'pending').length;
|
|
283
|
-
const total = run.sessions.length;
|
|
284
|
-
return {
|
|
285
|
-
title: buildRunProgressTitle(run, { completed, failed, running, total }),
|
|
286
|
-
metadata: {
|
|
287
|
-
runId: run.id,
|
|
288
|
-
status: run.status,
|
|
289
|
-
progress: `${completed}/${total} completed`,
|
|
290
|
-
active: running,
|
|
291
|
-
pending,
|
|
292
|
-
failed,
|
|
293
|
-
sessions: run.sessions.map(formatSessionActivity),
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
function buildRunProgressTitle(run, counts) {
|
|
298
|
-
const suffix = `(${counts.completed}/${counts.total} complete` +
|
|
299
|
-
(counts.running > 0 ? `, ${counts.running} active` : '') +
|
|
300
|
-
(counts.failed > 0 ? `, ${counts.failed} failed` : '') +
|
|
301
|
-
')';
|
|
302
|
-
if (run.status === 'completed') {
|
|
303
|
-
return `Claude manager completed ${suffix}`;
|
|
304
|
-
}
|
|
305
|
-
if (run.status === 'failed') {
|
|
306
|
-
return `Claude manager failed ${suffix}`;
|
|
307
|
-
}
|
|
308
|
-
if (counts.running > 0) {
|
|
309
|
-
return `Claude manager running ${suffix}`;
|
|
310
|
-
}
|
|
311
|
-
return `Claude manager queued ${suffix}`;
|
|
312
|
-
}
|
|
313
|
-
function formatSessionActivity(session) {
|
|
314
|
-
const parts = [session.title, session.status];
|
|
315
|
-
if (session.claudeSessionId) {
|
|
316
|
-
parts.push(session.claudeSessionId);
|
|
317
|
-
}
|
|
318
|
-
const latestEvent = findLatestDisplayEvent(session.events);
|
|
319
|
-
if (session.finalText) {
|
|
320
|
-
parts.push(truncateForDisplay(session.finalText, 120));
|
|
321
|
-
}
|
|
322
|
-
else if (session.error) {
|
|
323
|
-
parts.push(truncateForDisplay(session.error, 120));
|
|
324
|
-
}
|
|
325
|
-
else if (latestEvent) {
|
|
326
|
-
parts.push(`${latestEvent.type}: ${truncateForDisplay(latestEvent.text, 120)}`);
|
|
327
|
-
}
|
|
328
|
-
return parts.join(' | ');
|
|
329
|
-
}
|
|
330
|
-
function findLatestDisplayEvent(events) {
|
|
331
|
-
const reversedEvents = [...events].reverse();
|
|
332
|
-
const preferredEvent = reversedEvents.find((event) => (event.type === 'result' ||
|
|
333
|
-
event.type === 'error' ||
|
|
334
|
-
event.type === 'assistant') &&
|
|
335
|
-
Boolean(event.text.trim()));
|
|
336
|
-
if (preferredEvent) {
|
|
337
|
-
return preferredEvent;
|
|
338
|
-
}
|
|
339
|
-
return [...events]
|
|
340
|
-
.reverse()
|
|
341
|
-
.find((event) => event.type !== 'partial' && Boolean(event.text.trim()));
|
|
342
|
-
}
|
|
343
|
-
function truncateForDisplay(text, maxLength) {
|
|
344
|
-
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
345
|
-
if (normalized.length <= maxLength) {
|
|
346
|
-
return normalized;
|
|
347
|
-
}
|
|
348
|
-
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
349
|
-
}
|
|
350
|
-
function summarizeSessionOutputs(sessions) {
|
|
351
|
-
return sessions
|
|
352
|
-
.map((session) => `${session.title}: ${resolveSessionOutput(session)}`)
|
|
353
|
-
.join('\n');
|
|
354
|
-
}
|
|
355
|
-
function resolveSessionOutput(session) {
|
|
356
|
-
const latestEvent = findLatestDisplayEvent(session.events);
|
|
357
|
-
return (session.finalText?.trim() ||
|
|
358
|
-
session.error?.trim() ||
|
|
359
|
-
latestEvent?.text.trim() ||
|
|
360
|
-
session.status);
|
|
361
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
3
3
|
export interface ClaudeManagerPluginServices {
|
|
4
|
-
manager:
|
|
4
|
+
manager: PersistentManager;
|
|
5
5
|
sessions: ClaudeSessionService;
|
|
6
6
|
}
|
|
7
7
|
export declare function getOrCreatePluginServices(worktree: string): ClaudeManagerPluginServices;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
|
|
2
2
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
|
-
import { ManagerOrchestrator } from '../manager/manager-orchestrator.js';
|
|
4
|
-
import { TaskPlanner } from '../manager/task-planner.js';
|
|
5
3
|
import { ClaudeMetadataService } from '../metadata/claude-metadata.service.js';
|
|
6
4
|
import { RepoClaudeConfigReader } from '../metadata/repo-claude-config-reader.js';
|
|
7
5
|
import { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
8
|
-
import {
|
|
6
|
+
import { ContextTracker } from '../manager/context-tracker.js';
|
|
7
|
+
import { GitOperations } from '../manager/git-operations.js';
|
|
8
|
+
import { SessionController } from '../manager/session-controller.js';
|
|
9
|
+
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
10
|
+
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
9
11
|
const serviceCache = new Map();
|
|
10
12
|
export function getOrCreatePluginServices(worktree) {
|
|
11
13
|
const cachedServices = serviceCache.get(worktree);
|
|
@@ -15,7 +17,13 @@ export function getOrCreatePluginServices(worktree) {
|
|
|
15
17
|
const sdkAdapter = new ClaudeAgentSdkAdapter();
|
|
16
18
|
const metadataService = new ClaudeMetadataService(new RepoClaudeConfigReader(), sdkAdapter);
|
|
17
19
|
const sessionService = new ClaudeSessionService(sdkAdapter, metadataService);
|
|
18
|
-
const
|
|
20
|
+
const contextTracker = new ContextTracker();
|
|
21
|
+
const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.claudeCodeSessionPrompt);
|
|
22
|
+
const gitOps = new GitOperations(worktree);
|
|
23
|
+
const stateStore = new FileRunStateStore();
|
|
24
|
+
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker);
|
|
25
|
+
// Try to restore active session state (fire and forget)
|
|
26
|
+
manager.tryRestore(worktree).catch(() => { });
|
|
19
27
|
const services = {
|
|
20
28
|
manager,
|
|
21
29
|
sessions: sessionService,
|
package/dist/prompts/registry.js
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
export const managerPromptRegistry = {
|
|
2
2
|
managerSystemPrompt: [
|
|
3
|
-
'You
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
|
|
3
|
+
'You orchestrate Claude Code through a persistent session. You are a user proxy —',
|
|
4
|
+
'your job is to make Claude Code do the work, not to do it yourself.',
|
|
5
|
+
'',
|
|
6
|
+
'## Workflow',
|
|
7
|
+
'1. claude_manager_send — send clear, specific instructions',
|
|
8
|
+
'2. claude_manager_git_diff — review what changed',
|
|
9
|
+
'3. claude_manager_git_commit — checkpoint good work',
|
|
10
|
+
'4. claude_manager_git_reset — discard bad work',
|
|
11
|
+
'5. claude_manager_clear — fresh session when needed',
|
|
12
|
+
'',
|
|
13
|
+
'## Context management',
|
|
14
|
+
'Check the context snapshot in each send result:',
|
|
15
|
+
'- Under 50%: proceed freely',
|
|
16
|
+
'- 50-70%: consider if next task should reuse or start fresh',
|
|
17
|
+
'- Over 70%: compact or clear before heavy work',
|
|
18
|
+
'- Over 85% or 200k tokens: clear immediately',
|
|
19
|
+
'',
|
|
20
|
+
'## Delegation principles',
|
|
21
|
+
'- Write specific task descriptions with file paths, function names, error messages',
|
|
22
|
+
'- For large features, send sequential focused instructions',
|
|
23
|
+
'- Tell Claude Code to use subagents for parallel/independent parts',
|
|
24
|
+
'- After implementation, always review with git diff before committing',
|
|
25
|
+
'- If work is wrong, send a correction (specific, not "try again") or reset',
|
|
26
|
+
].join('\n'),
|
|
27
|
+
claudeCodeSessionPrompt: [
|
|
28
|
+
'You are being directed by an expert automated operator. Treat each message',
|
|
29
|
+
'as a precise instruction from a skilled Claude Code user.',
|
|
30
|
+
'',
|
|
31
|
+
'Key behaviors:',
|
|
32
|
+
'- Execute instructions directly without asking for clarification',
|
|
33
|
+
'- Use the Agent tool to spawn subagents for parallel/independent work',
|
|
34
|
+
'- Be concise — no preamble, no restating the task',
|
|
35
|
+
'- Do NOT run git commit, git push, or git reset — the operator handles git',
|
|
36
|
+
'- After completing work, end with a brief verification summary',
|
|
37
|
+
'- When context is heavy, prefer targeted file reads over reading entire files',
|
|
38
|
+
'- Report blockers immediately and specifically',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
contextWarnings: {
|
|
41
|
+
moderate: 'Session context is filling up ({percent}% estimated). Consider whether a fresh session would be more efficient.',
|
|
42
|
+
high: 'Session context is heavy ({percent}% estimated, {turns} turns, ${cost}). Start a new session or compact first.',
|
|
43
|
+
critical: 'Session context is near capacity ({percent}% estimated). Clear the session immediately before continuing.',
|
|
44
|
+
},
|
|
11
45
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PersistentRunRecord } from '../types/contracts.js';
|
|
2
2
|
export declare class FileRunStateStore {
|
|
3
3
|
private readonly baseDirectoryName;
|
|
4
4
|
private readonly writeQueues;
|
|
5
5
|
constructor(baseDirectoryName?: string);
|
|
6
|
-
saveRun(run:
|
|
7
|
-
getRun(cwd: string, runId: string): Promise<
|
|
8
|
-
listRuns(cwd: string): Promise<
|
|
9
|
-
updateRun(cwd: string, runId: string, update: (run:
|
|
6
|
+
saveRun(run: PersistentRunRecord): Promise<void>;
|
|
7
|
+
getRun(cwd: string, runId: string): Promise<PersistentRunRecord | null>;
|
|
8
|
+
listRuns(cwd: string): Promise<PersistentRunRecord[]>;
|
|
9
|
+
updateRun(cwd: string, runId: string, update: (run: PersistentRunRecord) => PersistentRunRecord): Promise<PersistentRunRecord>;
|
|
10
10
|
private getRunKey;
|
|
11
11
|
private getRunsDirectory;
|
|
12
12
|
private getRunPath;
|