@doingdev/opencode-claude-manager-plugin 0.1.35 → 0.1.43
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 +1 -0
- package/dist/manager/git-operations.d.ts +10 -1
- package/dist/manager/git-operations.js +18 -3
- package/dist/manager/persistent-manager.d.ts +19 -3
- package/dist/manager/persistent-manager.js +21 -9
- package/dist/manager/session-controller.d.ts +8 -5
- package/dist/manager/session-controller.js +25 -20
- package/dist/metadata/claude-metadata.service.d.ts +12 -0
- package/dist/metadata/claude-metadata.service.js +38 -0
- package/dist/metadata/repo-claude-config-reader.d.ts +7 -0
- package/dist/metadata/repo-claude-config-reader.js +154 -0
- package/dist/plugin/agent-hierarchy.d.ts +9 -9
- package/dist/plugin/agent-hierarchy.js +25 -25
- package/dist/plugin/claude-manager.plugin.js +83 -46
- package/dist/plugin/orchestrator.plugin.d.ts +2 -0
- package/dist/plugin/orchestrator.plugin.js +116 -0
- package/dist/plugin/service-factory.js +3 -8
- package/dist/prompts/registry.js +100 -103
- package/dist/providers/claude-code-wrapper.d.ts +13 -0
- package/dist/providers/claude-code-wrapper.js +13 -0
- package/dist/safety/bash-safety.d.ts +21 -0
- package/dist/safety/bash-safety.js +62 -0
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +27 -0
- package/dist/src/claude/claude-agent-sdk-adapter.js +517 -0
- package/dist/src/claude/claude-session.service.d.ts +10 -0
- package/dist/src/claude/claude-session.service.js +18 -0
- package/dist/src/claude/session-live-tailer.d.ts +51 -0
- package/dist/src/claude/session-live-tailer.js +269 -0
- package/dist/src/claude/tool-approval-manager.d.ts +27 -0
- package/dist/src/claude/tool-approval-manager.js +232 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -0
- package/dist/src/manager/context-tracker.d.ts +33 -0
- package/dist/src/manager/context-tracker.js +106 -0
- package/dist/src/manager/git-operations.d.ts +12 -0
- package/dist/src/manager/git-operations.js +76 -0
- package/dist/src/manager/persistent-manager.d.ts +77 -0
- package/dist/src/manager/persistent-manager.js +170 -0
- package/dist/src/manager/session-controller.d.ts +44 -0
- package/dist/src/manager/session-controller.js +147 -0
- package/dist/src/plugin/agent-hierarchy.d.ts +60 -0
- package/dist/src/plugin/agent-hierarchy.js +157 -0
- package/dist/src/plugin/claude-manager.plugin.d.ts +2 -0
- package/dist/src/plugin/claude-manager.plugin.js +563 -0
- package/dist/src/plugin/service-factory.d.ts +12 -0
- package/dist/src/plugin/service-factory.js +38 -0
- package/dist/src/prompts/registry.d.ts +11 -0
- package/dist/src/prompts/registry.js +260 -0
- package/dist/src/state/file-run-state-store.d.ts +14 -0
- package/dist/src/state/file-run-state-store.js +85 -0
- package/dist/src/state/transcript-store.d.ts +15 -0
- package/dist/src/state/transcript-store.js +44 -0
- package/dist/src/types/contracts.d.ts +200 -0
- package/dist/src/types/contracts.js +1 -0
- package/dist/src/util/fs-helpers.d.ts +2 -0
- package/dist/src/util/fs-helpers.js +10 -0
- package/dist/src/util/project-context.d.ts +10 -0
- package/dist/src/util/project-context.js +105 -0
- package/dist/src/util/transcript-append.d.ts +7 -0
- package/dist/src/util/transcript-append.js +29 -0
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +1 -0
- package/dist/test/claude-agent-sdk-adapter.test.js +459 -0
- package/dist/test/claude-manager.plugin.test.d.ts +1 -0
- package/dist/test/claude-manager.plugin.test.js +331 -0
- package/dist/test/context-tracker.test.d.ts +1 -0
- package/dist/test/context-tracker.test.js +138 -0
- package/dist/test/file-run-state-store.test.d.ts +1 -0
- package/dist/test/file-run-state-store.test.js +82 -0
- package/dist/test/git-operations.test.d.ts +1 -0
- package/dist/test/git-operations.test.js +90 -0
- package/dist/test/persistent-manager.test.d.ts +1 -0
- package/dist/test/persistent-manager.test.js +208 -0
- package/dist/test/project-context.test.d.ts +1 -0
- package/dist/test/project-context.test.js +92 -0
- package/dist/test/prompt-registry.test.d.ts +1 -0
- package/dist/test/prompt-registry.test.js +256 -0
- package/dist/test/session-controller.test.d.ts +1 -0
- package/dist/test/session-controller.test.js +149 -0
- package/dist/test/session-live-tailer.test.d.ts +1 -0
- package/dist/test/session-live-tailer.test.js +313 -0
- package/dist/test/tool-approval-manager.test.d.ts +1 -0
- package/dist/test/tool-approval-manager.test.js +264 -0
- package/dist/test/transcript-append.test.d.ts +1 -0
- package/dist/test/transcript-append.test.js +37 -0
- package/dist/test/transcript-store.test.d.ts +1 -0
- package/dist/test/transcript-store.test.js +50 -0
- package/dist/types/contracts.d.ts +3 -4
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import { composeWrapperPrompt, managerPromptRegistry } from '../prompts/registry.js';
|
|
3
|
+
import { discoverProjectClaudeFiles } from '../util/project-context.js';
|
|
4
|
+
import { AGENT_CTO, AGENT_ENGINEER_BUILD, AGENT_ENGINEER_PLAN, buildCtoAgentConfig, buildEngineerBuildAgentConfig, buildEngineerPlanAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
|
|
5
|
+
import { getOrCreatePluginServices } from './service-factory.js';
|
|
6
|
+
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
7
|
+
const services = getOrCreatePluginServices(worktree);
|
|
8
|
+
async function executeDelegate(args, context) {
|
|
9
|
+
const cwd = args.cwd ?? context.worktree;
|
|
10
|
+
if (args.freshSession) {
|
|
11
|
+
await services.manager.clearSession(cwd);
|
|
12
|
+
}
|
|
13
|
+
const hasActiveSession = services.manager.getStatus().sessionId !== null;
|
|
14
|
+
const promptPreview = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
15
|
+
context.metadata({
|
|
16
|
+
title: hasActiveSession
|
|
17
|
+
? '⚡ Claude Code: Resuming session...'
|
|
18
|
+
: '⚡ Claude Code: Initializing...',
|
|
19
|
+
metadata: {
|
|
20
|
+
status: 'running',
|
|
21
|
+
sessionId: services.manager.getStatus().sessionId,
|
|
22
|
+
prompt: promptPreview,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
let turnsSoFar;
|
|
26
|
+
let costSoFar;
|
|
27
|
+
const result = await services.manager.sendMessage(cwd, args.message, {
|
|
28
|
+
model: args.model,
|
|
29
|
+
effort: args.effort,
|
|
30
|
+
mode: args.mode,
|
|
31
|
+
abortSignal: context.abort,
|
|
32
|
+
}, (event) => {
|
|
33
|
+
if (event.turns !== undefined) {
|
|
34
|
+
turnsSoFar = event.turns;
|
|
35
|
+
}
|
|
36
|
+
if (event.totalCostUsd !== undefined) {
|
|
37
|
+
costSoFar = event.totalCostUsd;
|
|
38
|
+
}
|
|
39
|
+
const usageSuffix = formatLiveUsage(turnsSoFar, costSoFar);
|
|
40
|
+
if (event.type === 'tool_call') {
|
|
41
|
+
let toolName = 'tool';
|
|
42
|
+
let inputPreview = '';
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(event.text);
|
|
45
|
+
toolName = parsed.name ?? 'tool';
|
|
46
|
+
if (parsed.input) {
|
|
47
|
+
const inputStr = typeof parsed.input === 'string' ? parsed.input : JSON.stringify(parsed.input);
|
|
48
|
+
inputPreview = inputStr.length > 150 ? inputStr.slice(0, 150) + '...' : inputStr;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore parse errors
|
|
53
|
+
}
|
|
54
|
+
context.metadata({
|
|
55
|
+
title: `⚡ Claude Code: Running ${toolName}...${usageSuffix}`,
|
|
56
|
+
metadata: {
|
|
57
|
+
status: 'running',
|
|
58
|
+
sessionId: event.sessionId,
|
|
59
|
+
type: event.type,
|
|
60
|
+
tool: toolName,
|
|
61
|
+
input: inputPreview,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (event.type === 'assistant') {
|
|
66
|
+
const thinkingPreview = event.text.length > 150 ? event.text.slice(0, 150) + '...' : event.text;
|
|
67
|
+
context.metadata({
|
|
68
|
+
title: `⚡ Claude Code: Thinking...${usageSuffix}`,
|
|
69
|
+
metadata: {
|
|
70
|
+
status: 'running',
|
|
71
|
+
sessionId: event.sessionId,
|
|
72
|
+
type: event.type,
|
|
73
|
+
thinking: thinkingPreview,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (event.type === 'init') {
|
|
78
|
+
context.metadata({
|
|
79
|
+
title: `⚡ Claude Code: Session started`,
|
|
80
|
+
metadata: {
|
|
81
|
+
status: 'running',
|
|
82
|
+
sessionId: event.sessionId,
|
|
83
|
+
prompt: promptPreview,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (event.type === 'user') {
|
|
88
|
+
const preview = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
|
|
89
|
+
const outputPreview = formatToolOutputPreview(event.text);
|
|
90
|
+
context.metadata({
|
|
91
|
+
title: `⚡ Claude Code: ${outputPreview}${usageSuffix}`,
|
|
92
|
+
metadata: {
|
|
93
|
+
status: 'running',
|
|
94
|
+
sessionId: event.sessionId,
|
|
95
|
+
type: event.type,
|
|
96
|
+
output: preview,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (event.type === 'tool_progress') {
|
|
101
|
+
let toolName = 'tool';
|
|
102
|
+
let elapsed = 0;
|
|
103
|
+
let progressCurrent;
|
|
104
|
+
let progressTotal;
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(event.text);
|
|
107
|
+
toolName = parsed.name ?? 'tool';
|
|
108
|
+
elapsed = parsed.elapsed ?? 0;
|
|
109
|
+
progressCurrent = parsed.current;
|
|
110
|
+
progressTotal = parsed.total;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// ignore
|
|
114
|
+
}
|
|
115
|
+
const progressInfo = progressCurrent !== undefined && progressTotal !== undefined
|
|
116
|
+
? ` [${progressCurrent}/${progressTotal}]`
|
|
117
|
+
: '';
|
|
118
|
+
context.metadata({
|
|
119
|
+
title: `⚡ Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}${progressInfo}...${usageSuffix}`,
|
|
120
|
+
metadata: {
|
|
121
|
+
status: 'running',
|
|
122
|
+
sessionId: event.sessionId,
|
|
123
|
+
type: event.type,
|
|
124
|
+
tool: toolName,
|
|
125
|
+
elapsed,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else if (event.type === 'tool_summary') {
|
|
130
|
+
const summary = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
|
|
131
|
+
context.metadata({
|
|
132
|
+
title: `✅ Claude Code: Tool done${usageSuffix}`,
|
|
133
|
+
metadata: {
|
|
134
|
+
status: 'success',
|
|
135
|
+
sessionId: event.sessionId,
|
|
136
|
+
type: event.type,
|
|
137
|
+
summary,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else if (event.type === 'partial') {
|
|
142
|
+
const delta = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
|
|
143
|
+
context.metadata({
|
|
144
|
+
title: `⚡ Claude Code: Writing...${usageSuffix}`,
|
|
145
|
+
metadata: {
|
|
146
|
+
status: 'running',
|
|
147
|
+
sessionId: event.sessionId,
|
|
148
|
+
type: event.type,
|
|
149
|
+
delta,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (event.type === 'error') {
|
|
154
|
+
context.metadata({
|
|
155
|
+
title: `❌ Claude Code: Error`,
|
|
156
|
+
metadata: {
|
|
157
|
+
status: 'error',
|
|
158
|
+
sessionId: event.sessionId,
|
|
159
|
+
error: event.text.slice(0, 200),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
showToastIfAvailable(context, `Claude Code error: ${event.text.slice(0, 100)}`);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
const costLabel = `$${(result.totalCostUsd ?? 0).toFixed(4)}`;
|
|
166
|
+
const turns = result.turns ?? 0;
|
|
167
|
+
const contextWarning = formatContextWarning(result.context);
|
|
168
|
+
if (contextWarning) {
|
|
169
|
+
context.metadata({
|
|
170
|
+
title: `⚠️ Claude Code: Context at ${result.context.estimatedContextPercent}% (${turns} turns)`,
|
|
171
|
+
metadata: { status: 'warning', sessionId: result.sessionId, contextWarning },
|
|
172
|
+
});
|
|
173
|
+
showToastIfAvailable(context, `⚠️ Context usage at ${result.context.estimatedContextPercent}% — consider compacting`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
context.metadata({
|
|
177
|
+
title: `✅ Claude Code: Complete (${turns} turns, ${costLabel})`,
|
|
178
|
+
metadata: { status: 'success', sessionId: result.sessionId },
|
|
179
|
+
});
|
|
180
|
+
showToastIfAvailable(context, `✅ Session complete (${turns} turns, ${costLabel})`);
|
|
181
|
+
}
|
|
182
|
+
let toolOutputs = [];
|
|
183
|
+
if (result.sessionId) {
|
|
184
|
+
try {
|
|
185
|
+
toolOutputs = await services.liveTailer.getToolOutputPreview(result.sessionId, cwd, 3);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Non-critical — the JSONL file may not exist yet.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return JSON.stringify({
|
|
192
|
+
sessionId: result.sessionId,
|
|
193
|
+
finalText: result.finalText,
|
|
194
|
+
turns: result.turns,
|
|
195
|
+
totalCostUsd: result.totalCostUsd,
|
|
196
|
+
inputTokens: result.inputTokens,
|
|
197
|
+
outputTokens: result.outputTokens,
|
|
198
|
+
contextWindowSize: result.contextWindowSize,
|
|
199
|
+
context: result.context,
|
|
200
|
+
contextWarning,
|
|
201
|
+
toolOutputs: toolOutputs.length > 0 ? toolOutputs : undefined,
|
|
202
|
+
}, null, 2);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
config: async (config) => {
|
|
206
|
+
config.agent ??= {};
|
|
207
|
+
config.permission ??= {};
|
|
208
|
+
denyRestrictedToolsGlobally(config.permission);
|
|
209
|
+
// Discover project Claude files and build derived wrapper prompts.
|
|
210
|
+
const claudeFiles = await discoverProjectClaudeFiles(worktree);
|
|
211
|
+
const derivedPrompts = {
|
|
212
|
+
...managerPromptRegistry,
|
|
213
|
+
engineerPlanPrompt: composeWrapperPrompt(managerPromptRegistry.engineerPlanPrompt, claudeFiles),
|
|
214
|
+
engineerBuildPrompt: composeWrapperPrompt(managerPromptRegistry.engineerBuildPrompt, claudeFiles),
|
|
215
|
+
};
|
|
216
|
+
config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
|
|
217
|
+
config.agent[AGENT_ENGINEER_PLAN] ??= buildEngineerPlanAgentConfig(derivedPrompts);
|
|
218
|
+
config.agent[AGENT_ENGINEER_BUILD] ??= buildEngineerBuildAgentConfig(derivedPrompts);
|
|
219
|
+
},
|
|
220
|
+
tool: {
|
|
221
|
+
explore: tool({
|
|
222
|
+
description: 'Investigate and analyze code without making edits. ' +
|
|
223
|
+
'Read-only exploration of the codebase. ' +
|
|
224
|
+
'Preferred first step before implementation.',
|
|
225
|
+
args: {
|
|
226
|
+
message: tool.schema.string().min(1),
|
|
227
|
+
model: tool.schema
|
|
228
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5'])
|
|
229
|
+
.optional(),
|
|
230
|
+
effort: tool.schema.enum(['low', 'medium', 'high', 'max']).default('high'),
|
|
231
|
+
freshSession: tool.schema.boolean().default(false),
|
|
232
|
+
cwd: tool.schema.string().optional(),
|
|
233
|
+
},
|
|
234
|
+
async execute(args, context) {
|
|
235
|
+
return executeDelegate({ ...args, mode: 'plan' }, context);
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
implement: tool({
|
|
239
|
+
description: 'Implement code changes - can read, edit, and create files. ' +
|
|
240
|
+
'Use after exploration to make changes.',
|
|
241
|
+
args: {
|
|
242
|
+
message: tool.schema.string().min(1),
|
|
243
|
+
model: tool.schema
|
|
244
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5'])
|
|
245
|
+
.optional(),
|
|
246
|
+
effort: tool.schema.enum(['low', 'medium', 'high', 'max']).default('high'),
|
|
247
|
+
freshSession: tool.schema.boolean().default(false),
|
|
248
|
+
cwd: tool.schema.string().optional(),
|
|
249
|
+
},
|
|
250
|
+
async execute(args, context) {
|
|
251
|
+
return executeDelegate({ ...args, mode: 'free' }, context);
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
compact_context: tool({
|
|
255
|
+
description: 'Compress session history to reclaim context window space. ' +
|
|
256
|
+
'Preserves state while reducing token usage.',
|
|
257
|
+
args: {
|
|
258
|
+
cwd: tool.schema.string().optional(),
|
|
259
|
+
},
|
|
260
|
+
async execute(args, context) {
|
|
261
|
+
const cwd = args.cwd ?? context.worktree;
|
|
262
|
+
annotateToolRun(context, 'Compacting session', {});
|
|
263
|
+
const result = await services.manager.compactSession(cwd);
|
|
264
|
+
const snap = services.manager.getStatus();
|
|
265
|
+
const contextWarning = formatContextWarning(snap);
|
|
266
|
+
context.metadata({
|
|
267
|
+
title: contextWarning
|
|
268
|
+
? `⚠️ Claude Code: Compacted — context at ${snap.estimatedContextPercent}%`
|
|
269
|
+
: `✅ Claude Code: Compacted (${snap.totalTurns} turns, $${(snap.totalCostUsd ?? 0).toFixed(4)})`,
|
|
270
|
+
metadata: {
|
|
271
|
+
status: contextWarning ? 'warning' : 'success',
|
|
272
|
+
sessionId: result.sessionId,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
return JSON.stringify({
|
|
276
|
+
sessionId: result.sessionId,
|
|
277
|
+
finalText: result.finalText,
|
|
278
|
+
turns: result.turns,
|
|
279
|
+
totalCostUsd: result.totalCostUsd,
|
|
280
|
+
context: snap,
|
|
281
|
+
contextWarning,
|
|
282
|
+
}, null, 2);
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
git_diff: tool({
|
|
286
|
+
description: 'Run git diff to see all current changes (staged + unstaged) relative to HEAD.',
|
|
287
|
+
args: {
|
|
288
|
+
cwd: tool.schema.string().optional(),
|
|
289
|
+
},
|
|
290
|
+
async execute(_args, context) {
|
|
291
|
+
annotateToolRun(context, 'Running git diff', {});
|
|
292
|
+
const result = await services.manager.gitDiff();
|
|
293
|
+
return JSON.stringify(result, null, 2);
|
|
294
|
+
},
|
|
295
|
+
}),
|
|
296
|
+
git_commit: tool({
|
|
297
|
+
description: 'Stage all changes and commit with the given message.',
|
|
298
|
+
args: {
|
|
299
|
+
message: tool.schema.string().min(1),
|
|
300
|
+
cwd: tool.schema.string().optional(),
|
|
301
|
+
},
|
|
302
|
+
async execute(args, context) {
|
|
303
|
+
annotateToolRun(context, 'Committing changes', {
|
|
304
|
+
message: args.message,
|
|
305
|
+
});
|
|
306
|
+
const result = await services.manager.gitCommit(args.message);
|
|
307
|
+
return JSON.stringify(result, null, 2);
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
git_reset: tool({
|
|
311
|
+
description: 'Run git reset --hard HEAD and git clean -fd to discard ALL uncommitted changes and untracked files.',
|
|
312
|
+
args: {
|
|
313
|
+
cwd: tool.schema.string().optional(),
|
|
314
|
+
},
|
|
315
|
+
async execute(_args, context) {
|
|
316
|
+
annotateToolRun(context, 'Resetting working directory', {});
|
|
317
|
+
const result = await services.manager.gitReset();
|
|
318
|
+
return JSON.stringify(result, null, 2);
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
clear_session: tool({
|
|
322
|
+
description: 'Clear the active session to start fresh. ' +
|
|
323
|
+
'Use when context is full or starting a new task.',
|
|
324
|
+
args: {
|
|
325
|
+
cwd: tool.schema.string().optional(),
|
|
326
|
+
reason: tool.schema.string().optional(),
|
|
327
|
+
},
|
|
328
|
+
async execute(args, context) {
|
|
329
|
+
annotateToolRun(context, 'Clearing session', {
|
|
330
|
+
reason: args.reason,
|
|
331
|
+
});
|
|
332
|
+
const clearedId = await services.manager.clearSession(args.cwd ?? context.worktree);
|
|
333
|
+
return JSON.stringify({ clearedSessionId: clearedId });
|
|
334
|
+
},
|
|
335
|
+
}),
|
|
336
|
+
session_health: tool({
|
|
337
|
+
description: 'Check session health metrics: context usage %, turn count, cost, and session ID.',
|
|
338
|
+
args: {
|
|
339
|
+
cwd: tool.schema.string().optional(),
|
|
340
|
+
},
|
|
341
|
+
async execute(_args, context) {
|
|
342
|
+
annotateToolRun(context, 'Checking session status', {});
|
|
343
|
+
const status = services.manager.getStatus();
|
|
344
|
+
return JSON.stringify({
|
|
345
|
+
...status,
|
|
346
|
+
transcriptFile: status.sessionId
|
|
347
|
+
? `.claude-manager/transcripts/${status.sessionId}.json`
|
|
348
|
+
: null,
|
|
349
|
+
contextWarning: formatContextWarning(status),
|
|
350
|
+
}, null, 2);
|
|
351
|
+
},
|
|
352
|
+
}),
|
|
353
|
+
list_transcripts: tool({
|
|
354
|
+
description: 'List available session transcripts or inspect a specific transcript by ID.',
|
|
355
|
+
args: {
|
|
356
|
+
cwd: tool.schema.string().optional(),
|
|
357
|
+
sessionId: tool.schema.string().optional(),
|
|
358
|
+
},
|
|
359
|
+
async execute(args, context) {
|
|
360
|
+
annotateToolRun(context, 'Inspecting Claude session history', {});
|
|
361
|
+
const cwd = args.cwd ?? context.worktree;
|
|
362
|
+
if (args.sessionId) {
|
|
363
|
+
const [sdkTranscript, localEvents] = await Promise.all([
|
|
364
|
+
services.sessions.getTranscript(args.sessionId, cwd),
|
|
365
|
+
services.manager.getTranscriptEvents(cwd, args.sessionId),
|
|
366
|
+
]);
|
|
367
|
+
return JSON.stringify({
|
|
368
|
+
sdkTranscript,
|
|
369
|
+
localEvents: localEvents.length > 0 ? localEvents : undefined,
|
|
370
|
+
}, null, 2);
|
|
371
|
+
}
|
|
372
|
+
const sessions = await services.sessions.listSessions(cwd);
|
|
373
|
+
return JSON.stringify(sessions, null, 2);
|
|
374
|
+
},
|
|
375
|
+
}),
|
|
376
|
+
list_history: tool({
|
|
377
|
+
description: 'List persistent run records from the manager or inspect a specific run.',
|
|
378
|
+
args: {
|
|
379
|
+
cwd: tool.schema.string().optional(),
|
|
380
|
+
runId: tool.schema.string().optional(),
|
|
381
|
+
},
|
|
382
|
+
async execute(args, context) {
|
|
383
|
+
annotateToolRun(context, 'Reading manager run state', {});
|
|
384
|
+
if (args.runId) {
|
|
385
|
+
const run = await services.manager.getRun(args.cwd ?? context.worktree, args.runId);
|
|
386
|
+
return JSON.stringify(run, null, 2);
|
|
387
|
+
}
|
|
388
|
+
const runs = await services.manager.listRuns(args.cwd ?? context.worktree);
|
|
389
|
+
return JSON.stringify(runs, null, 2);
|
|
390
|
+
},
|
|
391
|
+
}),
|
|
392
|
+
approval_policy: tool({
|
|
393
|
+
description: 'View the current tool approval policy: rules, default action, and enabled status.',
|
|
394
|
+
args: {},
|
|
395
|
+
async execute(_args, context) {
|
|
396
|
+
annotateToolRun(context, 'Reading approval policy', {});
|
|
397
|
+
return JSON.stringify(services.approvalManager.getPolicy(), null, 2);
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
400
|
+
approval_decisions: tool({
|
|
401
|
+
description: 'View recent tool approval decisions. Shows what tools were allowed or denied. ' +
|
|
402
|
+
'Use deniedOnly to see only denied calls.',
|
|
403
|
+
args: {
|
|
404
|
+
limit: tool.schema.number().optional(),
|
|
405
|
+
deniedOnly: tool.schema.boolean().optional(),
|
|
406
|
+
},
|
|
407
|
+
async execute(args, context) {
|
|
408
|
+
annotateToolRun(context, 'Reading approval decisions', {});
|
|
409
|
+
const decisions = args.deniedOnly
|
|
410
|
+
? services.approvalManager.getDeniedDecisions(args.limit)
|
|
411
|
+
: services.approvalManager.getDecisions(args.limit);
|
|
412
|
+
return JSON.stringify({ total: decisions.length, decisions }, null, 2);
|
|
413
|
+
},
|
|
414
|
+
}),
|
|
415
|
+
approval_update: tool({
|
|
416
|
+
description: 'Update the tool approval policy. Add/remove rules, change default action, or enable/disable. ' +
|
|
417
|
+
'Rules are evaluated top-to-bottom; first match wins.',
|
|
418
|
+
args: {
|
|
419
|
+
action: tool.schema.enum([
|
|
420
|
+
'addRule',
|
|
421
|
+
'removeRule',
|
|
422
|
+
'setDefault',
|
|
423
|
+
'setEnabled',
|
|
424
|
+
'clearDecisions',
|
|
425
|
+
]),
|
|
426
|
+
ruleId: tool.schema.string().optional(),
|
|
427
|
+
toolPattern: tool.schema.string().optional(),
|
|
428
|
+
inputPattern: tool.schema.string().optional(),
|
|
429
|
+
ruleAction: tool.schema.enum(['allow', 'deny']).optional(),
|
|
430
|
+
denyMessage: tool.schema.string().optional(),
|
|
431
|
+
description: tool.schema.string().optional(),
|
|
432
|
+
position: tool.schema.number().optional(),
|
|
433
|
+
defaultAction: tool.schema.enum(['allow', 'deny']).optional(),
|
|
434
|
+
enabled: tool.schema.boolean().optional(),
|
|
435
|
+
},
|
|
436
|
+
async execute(args, context) {
|
|
437
|
+
annotateToolRun(context, `Updating approval: ${args.action}`, {});
|
|
438
|
+
if (args.action === 'addRule') {
|
|
439
|
+
if (!args.ruleId || !args.toolPattern || !args.ruleAction) {
|
|
440
|
+
return JSON.stringify({
|
|
441
|
+
error: 'addRule requires ruleId, toolPattern, and ruleAction',
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
services.approvalManager.addRule({
|
|
445
|
+
id: args.ruleId,
|
|
446
|
+
toolPattern: args.toolPattern,
|
|
447
|
+
inputPattern: args.inputPattern,
|
|
448
|
+
action: args.ruleAction,
|
|
449
|
+
denyMessage: args.denyMessage,
|
|
450
|
+
description: args.description,
|
|
451
|
+
}, args.position);
|
|
452
|
+
}
|
|
453
|
+
else if (args.action === 'removeRule') {
|
|
454
|
+
if (!args.ruleId) {
|
|
455
|
+
return JSON.stringify({ error: 'removeRule requires ruleId' });
|
|
456
|
+
}
|
|
457
|
+
const removed = services.approvalManager.removeRule(args.ruleId);
|
|
458
|
+
return JSON.stringify({ removed });
|
|
459
|
+
}
|
|
460
|
+
else if (args.action === 'setDefault') {
|
|
461
|
+
if (!args.defaultAction) {
|
|
462
|
+
return JSON.stringify({
|
|
463
|
+
error: 'setDefault requires defaultAction',
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
services.approvalManager.setDefaultAction(args.defaultAction);
|
|
467
|
+
}
|
|
468
|
+
else if (args.action === 'setEnabled') {
|
|
469
|
+
if (args.enabled === undefined) {
|
|
470
|
+
return JSON.stringify({ error: 'setEnabled requires enabled' });
|
|
471
|
+
}
|
|
472
|
+
services.approvalManager.setEnabled(args.enabled);
|
|
473
|
+
}
|
|
474
|
+
else if (args.action === 'clearDecisions') {
|
|
475
|
+
services.approvalManager.clearDecisions();
|
|
476
|
+
}
|
|
477
|
+
return JSON.stringify(services.approvalManager.getPolicy(), null, 2);
|
|
478
|
+
},
|
|
479
|
+
}),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
function annotateToolRun(context, title, metadata, status) {
|
|
484
|
+
const emoji = status ? `${formatStatusEmoji(status)} ` : '';
|
|
485
|
+
context.metadata({
|
|
486
|
+
title: `${emoji}${title}`,
|
|
487
|
+
metadata: { ...metadata, ...(status ? { status } : {}) },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function formatLiveUsage(turns, cost) {
|
|
491
|
+
if (turns === undefined && cost === undefined) {
|
|
492
|
+
return '';
|
|
493
|
+
}
|
|
494
|
+
const parts = [];
|
|
495
|
+
if (turns !== undefined) {
|
|
496
|
+
parts.push(`🔄 ${turns} turns`);
|
|
497
|
+
}
|
|
498
|
+
if (cost !== undefined) {
|
|
499
|
+
parts.push(`💰 $${cost.toFixed(4)}`);
|
|
500
|
+
}
|
|
501
|
+
return ` (${parts.join(', ')})`;
|
|
502
|
+
}
|
|
503
|
+
function formatContextWarning(context) {
|
|
504
|
+
const { warningLevel, estimatedContextPercent, totalTurns, totalCostUsd } = context;
|
|
505
|
+
if (warningLevel === 'ok' || estimatedContextPercent === null) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
const templates = managerPromptRegistry.contextWarnings;
|
|
509
|
+
const template = warningLevel === 'critical'
|
|
510
|
+
? templates.critical
|
|
511
|
+
: warningLevel === 'high'
|
|
512
|
+
? templates.high
|
|
513
|
+
: templates.moderate;
|
|
514
|
+
return template
|
|
515
|
+
.replace('{percent}', String(estimatedContextPercent))
|
|
516
|
+
.replace('{turns}', String(totalTurns))
|
|
517
|
+
.replace('{cost}', totalCostUsd.toFixed(2));
|
|
518
|
+
}
|
|
519
|
+
function formatStatusEmoji(status) {
|
|
520
|
+
switch (status) {
|
|
521
|
+
case 'running':
|
|
522
|
+
return '⚡';
|
|
523
|
+
case 'success':
|
|
524
|
+
return '✅';
|
|
525
|
+
case 'error':
|
|
526
|
+
return '❌';
|
|
527
|
+
case 'warning':
|
|
528
|
+
return '⚠️';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function formatToolOutputPreview(text) {
|
|
532
|
+
const lower = text.toLowerCase();
|
|
533
|
+
let prefix;
|
|
534
|
+
if (lower.includes('"tool":"read"') ||
|
|
535
|
+
lower.includes('"name":"read"') ||
|
|
536
|
+
lower.includes('file contents')) {
|
|
537
|
+
prefix = '↳ Read: ';
|
|
538
|
+
}
|
|
539
|
+
else if (lower.includes('"tool":"grep"') ||
|
|
540
|
+
lower.includes('"name":"grep"') ||
|
|
541
|
+
lower.includes('matches found')) {
|
|
542
|
+
prefix = '↳ Found: ';
|
|
543
|
+
}
|
|
544
|
+
else if (lower.includes('"tool":"write"') ||
|
|
545
|
+
lower.includes('"name":"write"') ||
|
|
546
|
+
lower.includes('"tool":"edit"') ||
|
|
547
|
+
lower.includes('"name":"edit"') ||
|
|
548
|
+
lower.includes('file written') ||
|
|
549
|
+
lower.includes('file updated')) {
|
|
550
|
+
prefix = '↳ Wrote: ';
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
prefix = '↳ Result: ';
|
|
554
|
+
}
|
|
555
|
+
const snippet = text.replace(/\s+/g, ' ').trim();
|
|
556
|
+
const truncated = snippet.length > 60 ? snippet.slice(0, 60) + '...' : snippet;
|
|
557
|
+
return `${prefix}${truncated}`;
|
|
558
|
+
}
|
|
559
|
+
function showToastIfAvailable(context, message) {
|
|
560
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
561
|
+
const client = context.client;
|
|
562
|
+
client?.tui?.showToast?.(message);
|
|
563
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
2
|
+
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
3
|
+
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
4
|
+
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
5
|
+
interface ClaudeManagerPluginServices {
|
|
6
|
+
manager: PersistentManager;
|
|
7
|
+
sessions: ClaudeSessionService;
|
|
8
|
+
approvalManager: ToolApprovalManager;
|
|
9
|
+
liveTailer: SessionLiveTailer;
|
|
10
|
+
}
|
|
11
|
+
export declare function getOrCreatePluginServices(worktree: string): ClaudeManagerPluginServices;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
|
|
2
|
+
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
|
+
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
4
|
+
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
5
|
+
import { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
6
|
+
import { TranscriptStore } from '../state/transcript-store.js';
|
|
7
|
+
import { ContextTracker } from '../manager/context-tracker.js';
|
|
8
|
+
import { GitOperations } from '../manager/git-operations.js';
|
|
9
|
+
import { SessionController } from '../manager/session-controller.js';
|
|
10
|
+
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
11
|
+
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
12
|
+
const serviceCache = new Map();
|
|
13
|
+
export function getOrCreatePluginServices(worktree) {
|
|
14
|
+
const cachedServices = serviceCache.get(worktree);
|
|
15
|
+
if (cachedServices) {
|
|
16
|
+
return cachedServices;
|
|
17
|
+
}
|
|
18
|
+
const approvalManager = new ToolApprovalManager();
|
|
19
|
+
const sdkAdapter = new ClaudeAgentSdkAdapter(undefined, approvalManager);
|
|
20
|
+
const sessionService = new ClaudeSessionService(sdkAdapter);
|
|
21
|
+
const contextTracker = new ContextTracker();
|
|
22
|
+
const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.engineerSessionPrompt, managerPromptRegistry.modePrefixes);
|
|
23
|
+
const gitOps = new GitOperations(worktree);
|
|
24
|
+
const stateStore = new FileRunStateStore();
|
|
25
|
+
const transcriptStore = new TranscriptStore();
|
|
26
|
+
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker, transcriptStore);
|
|
27
|
+
// Try to restore active session state (fire and forget)
|
|
28
|
+
manager.tryRestore(worktree).catch(() => { });
|
|
29
|
+
const liveTailer = new SessionLiveTailer();
|
|
30
|
+
const services = {
|
|
31
|
+
manager,
|
|
32
|
+
sessions: sessionService,
|
|
33
|
+
approvalManager,
|
|
34
|
+
liveTailer,
|
|
35
|
+
};
|
|
36
|
+
serviceCache.set(worktree, services);
|
|
37
|
+
return services;
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DiscoveredClaudeFile, ManagerPromptRegistry } from '../types/contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compose a wrapper agent prompt from the base prompt plus discovered
|
|
4
|
+
* project Claude files. Returns the base prompt unchanged when no
|
|
5
|
+
* files are provided.
|
|
6
|
+
*
|
|
7
|
+
* Each file is rendered under a clear path-labeled section so the
|
|
8
|
+
* wrapper agent knows exactly where each instruction came from.
|
|
9
|
+
*/
|
|
10
|
+
export declare function composeWrapperPrompt(basePrompt: string, claudeFiles: DiscoveredClaudeFile[]): string;
|
|
11
|
+
export declare const managerPromptRegistry: ManagerPromptRegistry;
|