@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
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@ This package provides an OpenCode plugin that lets an OpenCode-side manager agen
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Use this when you want OpenCode to act as a manager over Claude Code instead of talking to Claude directly. The plugin gives OpenCode a stable tool surface for discovering Claude metadata, delegating work to Claude sessions, splitting tasks into subagents
|
|
7
|
+
Use this when you want OpenCode to act as a manager over Claude Code instead of talking to Claude directly. The plugin gives OpenCode a stable tool surface for discovering Claude metadata, delegating work to Claude sessions, and splitting tasks into subagents (all using the same working directory).
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- Runs Claude Code tasks from OpenCode through `@anthropic-ai/claude-agent-sdk`.
|
|
12
12
|
- Discovers repo-local Claude metadata from `.claude/skills`, `.claude/commands`, `CLAUDE.md`, and settings hooks.
|
|
13
|
-
- Splits multi-step tasks into subagents
|
|
13
|
+
- Splits multi-step tasks into subagents that run sequentially in the same repository directory.
|
|
14
14
|
- Persists manager runs under `.claude-manager/runs` so sessions can be inspected later.
|
|
15
15
|
- Exposes manager-facing tools instead of relying on undocumented plugin-defined slash commands.
|
|
16
16
|
|
|
@@ -19,21 +19,20 @@ Use this when you want OpenCode to act as a manager over Claude Code instead of
|
|
|
19
19
|
- Node `22+`
|
|
20
20
|
- OpenCode with plugin loading enabled
|
|
21
21
|
- Access to Claude Code / Claude Agent SDK on the machine where OpenCode is running
|
|
22
|
-
- A git repository if you want automatic worktree allocation
|
|
23
22
|
|
|
24
23
|
## Installation
|
|
25
24
|
|
|
26
|
-
Install from npm:
|
|
25
|
+
Install from the npm registry:
|
|
27
26
|
|
|
28
27
|
```bash
|
|
29
|
-
|
|
28
|
+
pnpm add @doingdev/opencode-claude-manager-plugin
|
|
30
29
|
```
|
|
31
30
|
|
|
32
31
|
Or for local development in this repo:
|
|
33
32
|
|
|
34
33
|
```bash
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
pnpm install
|
|
35
|
+
pnpm run build
|
|
37
36
|
```
|
|
38
37
|
|
|
39
38
|
## OpenCode Config
|
|
@@ -50,11 +49,10 @@ If you are testing locally, point OpenCode at the local package or plugin file u
|
|
|
50
49
|
|
|
51
50
|
## OpenCode tools
|
|
52
51
|
|
|
53
|
-
- `claude_manager_run` - run a task through Claude with optional splitting
|
|
52
|
+
- `claude_manager_run` - run a task through Claude with optional splitting into subagents; returns a compact output summary and a `runId` for deeper inspection
|
|
54
53
|
- `claude_manager_metadata` - inspect available Claude commands, skills, hooks, and settings
|
|
55
54
|
- `claude_manager_sessions` - list Claude sessions or inspect a saved transcript
|
|
56
55
|
- `claude_manager_runs` - inspect persisted manager run records
|
|
57
|
-
- `claude_manager_cleanup_run` - explicitly remove worktrees created for a prior manager run
|
|
58
56
|
|
|
59
57
|
## Plugin-provided agents and commands
|
|
60
58
|
|
|
@@ -77,7 +75,7 @@ Typical flow inside OpenCode:
|
|
|
77
75
|
Example task:
|
|
78
76
|
|
|
79
77
|
```text
|
|
80
|
-
Use claude_manager_run to split this implementation into subagents
|
|
78
|
+
Use claude_manager_run to split this implementation into subagents and summarize the final result.
|
|
81
79
|
```
|
|
82
80
|
|
|
83
81
|
## Local Development
|
|
@@ -85,11 +83,11 @@ Use claude_manager_run to split this implementation into subagents, use worktree
|
|
|
85
83
|
Clone the repo and run:
|
|
86
84
|
|
|
87
85
|
```bash
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
pnpm install
|
|
87
|
+
pnpm run lint
|
|
88
|
+
pnpm run typecheck
|
|
89
|
+
pnpm run test
|
|
90
|
+
pnpm run build
|
|
93
91
|
```
|
|
94
92
|
|
|
95
93
|
The compiled plugin output is written to `dist/`.
|
|
@@ -119,13 +117,13 @@ Notes for trusted publishing:
|
|
|
119
117
|
Release flow:
|
|
120
118
|
|
|
121
119
|
```bash
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
pnpm login
|
|
121
|
+
pnpm whoami
|
|
122
|
+
pnpm version patch
|
|
123
|
+
pnpm run lint
|
|
124
|
+
pnpm run typecheck
|
|
125
|
+
pnpm run test
|
|
126
|
+
pnpm run build
|
|
129
127
|
```
|
|
130
128
|
|
|
131
129
|
Then publish from GitHub by either:
|
|
@@ -138,14 +136,13 @@ After trusted publishing is working, you can tighten npm package security by dis
|
|
|
138
136
|
## Limitations
|
|
139
137
|
|
|
140
138
|
- Claude slash commands and skills come primarily from filesystem discovery; SDK probing is available but optional.
|
|
141
|
-
-
|
|
142
|
-
- Worktrees are preserved by default so you can inspect changes; clean them up explicitly with `claude_manager_cleanup_run`.
|
|
139
|
+
- Multiple subagents share one working directory and run one after another to avoid overlapping edits.
|
|
143
140
|
- Run state is local to the repo under `.claude-manager/` and is ignored by git.
|
|
144
141
|
|
|
145
142
|
## Scripts
|
|
146
143
|
|
|
147
|
-
- `
|
|
148
|
-
- `
|
|
149
|
-
- `
|
|
150
|
-
- `
|
|
151
|
-
- `
|
|
144
|
+
- `pnpm run build`
|
|
145
|
+
- `pnpm run typecheck`
|
|
146
|
+
- `pnpm run lint`
|
|
147
|
+
- `pnpm run format`
|
|
148
|
+
- `pnpm run test`
|
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
import { getSessionMessages, listSessions, query, } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { appendTranscriptEvents } from '../util/transcript-append.js';
|
|
2
3
|
const defaultFacade = {
|
|
3
4
|
query,
|
|
4
5
|
listSessions,
|
|
5
6
|
getSessionMessages,
|
|
6
7
|
};
|
|
8
|
+
const TOOL_INPUT_PREVIEW_MAX = 2000;
|
|
9
|
+
const USER_MESSAGE_MAX = 4000;
|
|
7
10
|
export class ClaudeAgentSdkAdapter {
|
|
8
11
|
sdkFacade;
|
|
9
12
|
constructor(sdkFacade = defaultFacade) {
|
|
10
13
|
this.sdkFacade = sdkFacade;
|
|
11
14
|
}
|
|
12
15
|
async runSession(input, onEvent) {
|
|
16
|
+
const options = this.buildOptions(input);
|
|
17
|
+
const includePartials = options.includePartialMessages === true;
|
|
13
18
|
const sessionQuery = this.sdkFacade.query({
|
|
14
19
|
prompt: input.prompt,
|
|
15
|
-
options
|
|
20
|
+
options,
|
|
16
21
|
});
|
|
17
|
-
|
|
22
|
+
let events = [];
|
|
18
23
|
let finalText = '';
|
|
19
24
|
let sessionId;
|
|
20
25
|
let turns;
|
|
21
26
|
let totalCostUsd;
|
|
27
|
+
let inputTokens;
|
|
28
|
+
let outputTokens;
|
|
29
|
+
let contextWindowSize;
|
|
22
30
|
try {
|
|
23
31
|
for await (const message of sessionQuery) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
const batch = normalizeSdkMessages(message, includePartials);
|
|
33
|
+
for (const event of batch) {
|
|
34
|
+
sessionId ??= event.sessionId;
|
|
35
|
+
if (event.type === 'result') {
|
|
36
|
+
finalText = event.text;
|
|
37
|
+
turns = event.turns;
|
|
38
|
+
totalCostUsd = event.totalCostUsd;
|
|
39
|
+
}
|
|
40
|
+
events = appendTranscriptEvents(events, [event]);
|
|
41
|
+
if (onEvent) {
|
|
42
|
+
await onEvent(event);
|
|
43
|
+
}
|
|
27
44
|
}
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
// Extract usage data from result messages
|
|
46
|
+
if (message.type === 'result') {
|
|
47
|
+
const usage = extractUsageFromResult(message);
|
|
48
|
+
if (usage.inputTokens !== undefined) {
|
|
49
|
+
inputTokens = usage.inputTokens;
|
|
50
|
+
}
|
|
51
|
+
if (usage.outputTokens !== undefined) {
|
|
52
|
+
outputTokens = usage.outputTokens;
|
|
53
|
+
}
|
|
54
|
+
if (usage.contextWindowSize !== undefined) {
|
|
55
|
+
contextWindowSize = usage.contextWindowSize;
|
|
56
|
+
}
|
|
37
57
|
}
|
|
38
58
|
}
|
|
39
59
|
}
|
|
@@ -46,6 +66,9 @@ export class ClaudeAgentSdkAdapter {
|
|
|
46
66
|
finalText,
|
|
47
67
|
turns,
|
|
48
68
|
totalCostUsd,
|
|
69
|
+
inputTokens,
|
|
70
|
+
outputTokens,
|
|
71
|
+
contextWindowSize,
|
|
49
72
|
};
|
|
50
73
|
}
|
|
51
74
|
async listSavedSessions(cwd) {
|
|
@@ -125,73 +148,210 @@ export class ClaudeAgentSdkAdapter {
|
|
|
125
148
|
return options;
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
|
-
function
|
|
151
|
+
function normalizeSdkMessages(message, includePartials) {
|
|
129
152
|
const sessionId = 'session_id' in message ? message.session_id : undefined;
|
|
130
153
|
if (message.type === 'assistant') {
|
|
131
|
-
return
|
|
132
|
-
type: 'assistant',
|
|
133
|
-
sessionId,
|
|
134
|
-
text: extractText(message.message),
|
|
135
|
-
rawType: message.type,
|
|
136
|
-
};
|
|
154
|
+
return normalizeAssistantSdkMessage(message, sessionId);
|
|
137
155
|
}
|
|
138
156
|
if (message.type === 'stream_event') {
|
|
157
|
+
if (!includePartials) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
139
160
|
const partialText = extractPartialEventText(message.event);
|
|
140
161
|
if (!partialText) {
|
|
141
|
-
return
|
|
162
|
+
return [];
|
|
142
163
|
}
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
type: 'partial',
|
|
167
|
+
sessionId,
|
|
168
|
+
text: partialText,
|
|
169
|
+
rawType: message.type,
|
|
170
|
+
},
|
|
171
|
+
];
|
|
149
172
|
}
|
|
150
173
|
if (message.type === 'result') {
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
type: message.is_error ? 'error' : 'result',
|
|
177
|
+
sessionId,
|
|
178
|
+
text: message.subtype === 'success'
|
|
179
|
+
? message.result
|
|
180
|
+
: message.errors.join('\n') || message.subtype,
|
|
181
|
+
turns: message.num_turns,
|
|
182
|
+
totalCostUsd: message.total_cost_usd,
|
|
183
|
+
rawType: `${message.type}:${message.subtype}`,
|
|
184
|
+
},
|
|
185
|
+
];
|
|
161
186
|
}
|
|
162
187
|
if (message.type === 'system') {
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
type: message.subtype === 'init' ? 'init' : 'system',
|
|
191
|
+
sessionId,
|
|
192
|
+
text: message.subtype,
|
|
193
|
+
rawType: `${message.type}:${message.subtype}`,
|
|
194
|
+
},
|
|
195
|
+
];
|
|
169
196
|
}
|
|
170
197
|
if (message.type === 'auth_status') {
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
type: 'system',
|
|
201
|
+
sessionId,
|
|
202
|
+
text: message.output.join('\n') || 'auth_status',
|
|
203
|
+
rawType: message.type,
|
|
204
|
+
},
|
|
205
|
+
];
|
|
177
206
|
}
|
|
178
207
|
if (message.type === 'prompt_suggestion') {
|
|
179
|
-
return
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
type: 'system',
|
|
211
|
+
sessionId,
|
|
212
|
+
text: message.suggestion,
|
|
213
|
+
rawType: message.type,
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
if (message.type === 'user') {
|
|
218
|
+
return normalizeUserSdkMessage(message, sessionId);
|
|
219
|
+
}
|
|
220
|
+
return [
|
|
221
|
+
{
|
|
180
222
|
type: 'system',
|
|
181
223
|
sessionId,
|
|
182
|
-
text: message.
|
|
224
|
+
text: message.type,
|
|
183
225
|
rawType: message.type,
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
}
|
|
229
|
+
function normalizeAssistantSdkMessage(message, sessionId) {
|
|
230
|
+
const raw = message.message;
|
|
231
|
+
if (!isRecord(raw) || !Array.isArray(raw.content)) {
|
|
232
|
+
const text = extractText(raw);
|
|
233
|
+
return text.trim()
|
|
234
|
+
? [
|
|
235
|
+
{
|
|
236
|
+
type: 'assistant',
|
|
237
|
+
sessionId,
|
|
238
|
+
text,
|
|
239
|
+
rawType: 'assistant',
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
: [];
|
|
188
243
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
244
|
+
const out = [];
|
|
245
|
+
let textBuffer = '';
|
|
246
|
+
const flushText = () => {
|
|
247
|
+
if (textBuffer.trim()) {
|
|
248
|
+
out.push({
|
|
249
|
+
type: 'assistant',
|
|
250
|
+
sessionId,
|
|
251
|
+
text: textBuffer,
|
|
252
|
+
rawType: 'assistant',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
textBuffer = '';
|
|
194
256
|
};
|
|
257
|
+
for (const block of raw.content) {
|
|
258
|
+
if (!isRecord(block)) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
262
|
+
textBuffer += block.text;
|
|
263
|
+
}
|
|
264
|
+
else if (block.type === 'tool_use' && typeof block.name === 'string') {
|
|
265
|
+
flushText();
|
|
266
|
+
const id = typeof block.id === 'string' ? block.id : '';
|
|
267
|
+
const preview = truncateJsonish(block.input, TOOL_INPUT_PREVIEW_MAX);
|
|
268
|
+
out.push({
|
|
269
|
+
type: 'tool_call',
|
|
270
|
+
sessionId,
|
|
271
|
+
text: JSON.stringify({
|
|
272
|
+
name: block.name,
|
|
273
|
+
id,
|
|
274
|
+
input: preview,
|
|
275
|
+
}),
|
|
276
|
+
rawType: 'assistant:tool_use',
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
flushText();
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
function normalizeUserSdkMessage(message, sessionId) {
|
|
284
|
+
let payload = serializeUserMessageContent(message.message);
|
|
285
|
+
if (message.tool_use_result !== undefined) {
|
|
286
|
+
const extra = truncateJsonish(message.tool_use_result, 1500);
|
|
287
|
+
if (extra) {
|
|
288
|
+
payload = payload
|
|
289
|
+
? `${payload}\n[tool_use_result] ${extra}`
|
|
290
|
+
: `[tool_use_result] ${extra}`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
payload = truncateString(payload, USER_MESSAGE_MAX);
|
|
294
|
+
if (!payload.trim()) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
return [
|
|
298
|
+
{
|
|
299
|
+
type: 'user',
|
|
300
|
+
sessionId,
|
|
301
|
+
text: payload,
|
|
302
|
+
rawType: 'user',
|
|
303
|
+
},
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
function serializeUserMessageContent(message) {
|
|
307
|
+
if (typeof message === 'string') {
|
|
308
|
+
return message;
|
|
309
|
+
}
|
|
310
|
+
if (!isRecord(message)) {
|
|
311
|
+
return '';
|
|
312
|
+
}
|
|
313
|
+
const content = message.content;
|
|
314
|
+
if (typeof content === 'string') {
|
|
315
|
+
return content;
|
|
316
|
+
}
|
|
317
|
+
if (!Array.isArray(content)) {
|
|
318
|
+
return truncateJsonish(message, USER_MESSAGE_MAX);
|
|
319
|
+
}
|
|
320
|
+
const lines = [];
|
|
321
|
+
for (const part of content) {
|
|
322
|
+
if (!isRecord(part)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (part.type === 'text' && typeof part.text === 'string') {
|
|
326
|
+
lines.push(part.text);
|
|
327
|
+
}
|
|
328
|
+
else if (part.type === 'tool_result') {
|
|
329
|
+
const id = typeof part.tool_use_id === 'string' ? part.tool_use_id : '';
|
|
330
|
+
const body = truncateJsonish(part.content, TOOL_INPUT_PREVIEW_MAX);
|
|
331
|
+
lines.push(`[tool_result:${id}] ${body}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return lines.filter(Boolean).join('\n');
|
|
335
|
+
}
|
|
336
|
+
function truncateJsonish(value, max) {
|
|
337
|
+
if (value === undefined || value === null) {
|
|
338
|
+
return '';
|
|
339
|
+
}
|
|
340
|
+
if (typeof value === 'string') {
|
|
341
|
+
return truncateString(value, max);
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
return truncateString(JSON.stringify(value), max);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return truncateString(String(value), max);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function truncateString(s, max) {
|
|
351
|
+
if (s.length <= max) {
|
|
352
|
+
return s;
|
|
353
|
+
}
|
|
354
|
+
return s.slice(0, max);
|
|
195
355
|
}
|
|
196
356
|
function isRecord(value) {
|
|
197
357
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -257,3 +417,30 @@ function mapAgent(agent) {
|
|
|
257
417
|
source: 'sdk',
|
|
258
418
|
};
|
|
259
419
|
}
|
|
420
|
+
function extractUsageFromResult(message) {
|
|
421
|
+
if (message.type !== 'result') {
|
|
422
|
+
return {};
|
|
423
|
+
}
|
|
424
|
+
const result = {};
|
|
425
|
+
// Extract from usage field
|
|
426
|
+
const usage = message.usage;
|
|
427
|
+
if (isRecord(usage)) {
|
|
428
|
+
if (typeof usage.input_tokens === 'number') {
|
|
429
|
+
result.inputTokens = usage.input_tokens;
|
|
430
|
+
}
|
|
431
|
+
if (typeof usage.output_tokens === 'number') {
|
|
432
|
+
result.outputTokens = usage.output_tokens;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Extract contextWindow from modelUsage
|
|
436
|
+
const modelUsage = message.model_usage;
|
|
437
|
+
if (isRecord(modelUsage)) {
|
|
438
|
+
for (const model of Object.values(modelUsage)) {
|
|
439
|
+
if (isRecord(model) && typeof model.context_window === 'number') {
|
|
440
|
+
result.contextWindowSize = model.context_window;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return result;
|
|
446
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
2
|
import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
|
|
3
|
-
export type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage,
|
|
3
|
+
export type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, ActiveSessionState, ContextWarningLevel, } from './types/contracts.js';
|
|
4
4
|
export { ClaudeManagerPlugin };
|
|
5
5
|
export declare const plugin: Plugin;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ContextWarningLevel, SessionContextSnapshot } from '../types/contracts.js';
|
|
2
|
+
export declare class ContextTracker {
|
|
3
|
+
private totalTurns;
|
|
4
|
+
private totalCostUsd;
|
|
5
|
+
private latestInputTokens;
|
|
6
|
+
private latestOutputTokens;
|
|
7
|
+
private contextWindowSize;
|
|
8
|
+
private compactionCount;
|
|
9
|
+
private sessionId;
|
|
10
|
+
recordResult(result: {
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
turns?: number;
|
|
13
|
+
totalCostUsd?: number;
|
|
14
|
+
inputTokens?: number;
|
|
15
|
+
outputTokens?: number;
|
|
16
|
+
contextWindowSize?: number;
|
|
17
|
+
}): void;
|
|
18
|
+
recordCompaction(): void;
|
|
19
|
+
snapshot(): SessionContextSnapshot;
|
|
20
|
+
warningLevel(): ContextWarningLevel;
|
|
21
|
+
estimateContextPercent(): number | null;
|
|
22
|
+
isAboveTokenThreshold(thresholdTokens?: number): boolean;
|
|
23
|
+
reset(): void;
|
|
24
|
+
/** Restore from persisted active session state. */
|
|
25
|
+
restore(state: {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
totalTurns: number;
|
|
28
|
+
totalCostUsd: number;
|
|
29
|
+
estimatedContextPercent: number | null;
|
|
30
|
+
contextWindowSize: number | null;
|
|
31
|
+
latestInputTokens: number | null;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
2
|
+
export class ContextTracker {
|
|
3
|
+
totalTurns = 0;
|
|
4
|
+
totalCostUsd = 0;
|
|
5
|
+
latestInputTokens = null;
|
|
6
|
+
latestOutputTokens = null;
|
|
7
|
+
contextWindowSize = null;
|
|
8
|
+
compactionCount = 0;
|
|
9
|
+
sessionId = null;
|
|
10
|
+
recordResult(result) {
|
|
11
|
+
if (result.sessionId) {
|
|
12
|
+
this.sessionId = result.sessionId;
|
|
13
|
+
}
|
|
14
|
+
if (result.turns !== undefined) {
|
|
15
|
+
this.totalTurns = result.turns;
|
|
16
|
+
}
|
|
17
|
+
if (result.totalCostUsd !== undefined) {
|
|
18
|
+
this.totalCostUsd = result.totalCostUsd;
|
|
19
|
+
}
|
|
20
|
+
if (result.inputTokens !== undefined) {
|
|
21
|
+
// If input tokens dropped significantly, compaction likely occurred
|
|
22
|
+
if (this.latestInputTokens !== null &&
|
|
23
|
+
result.inputTokens < this.latestInputTokens * 0.5) {
|
|
24
|
+
this.compactionCount++;
|
|
25
|
+
}
|
|
26
|
+
this.latestInputTokens = result.inputTokens;
|
|
27
|
+
}
|
|
28
|
+
if (result.outputTokens !== undefined) {
|
|
29
|
+
this.latestOutputTokens = result.outputTokens;
|
|
30
|
+
}
|
|
31
|
+
if (result.contextWindowSize !== undefined) {
|
|
32
|
+
this.contextWindowSize = result.contextWindowSize;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
recordCompaction() {
|
|
36
|
+
this.compactionCount++;
|
|
37
|
+
}
|
|
38
|
+
snapshot() {
|
|
39
|
+
return {
|
|
40
|
+
sessionId: this.sessionId,
|
|
41
|
+
totalTurns: this.totalTurns,
|
|
42
|
+
totalCostUsd: this.totalCostUsd,
|
|
43
|
+
latestInputTokens: this.latestInputTokens,
|
|
44
|
+
latestOutputTokens: this.latestOutputTokens,
|
|
45
|
+
contextWindowSize: this.contextWindowSize,
|
|
46
|
+
estimatedContextPercent: this.estimateContextPercent(),
|
|
47
|
+
warningLevel: this.warningLevel(),
|
|
48
|
+
compactionCount: this.compactionCount,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
warningLevel() {
|
|
52
|
+
const percent = this.estimateContextPercent();
|
|
53
|
+
if (percent === null) {
|
|
54
|
+
return 'ok';
|
|
55
|
+
}
|
|
56
|
+
if (percent >= 85) {
|
|
57
|
+
return 'critical';
|
|
58
|
+
}
|
|
59
|
+
if (percent >= 70) {
|
|
60
|
+
return 'high';
|
|
61
|
+
}
|
|
62
|
+
if (percent >= 50) {
|
|
63
|
+
return 'moderate';
|
|
64
|
+
}
|
|
65
|
+
return 'ok';
|
|
66
|
+
}
|
|
67
|
+
estimateContextPercent() {
|
|
68
|
+
// Tier 1: Token-based (most accurate)
|
|
69
|
+
if (this.latestInputTokens !== null) {
|
|
70
|
+
const window = this.contextWindowSize ?? DEFAULT_CONTEXT_WINDOW;
|
|
71
|
+
return Math.min(100, Math.round((this.latestInputTokens / window) * 100));
|
|
72
|
+
}
|
|
73
|
+
// Tier 2: Cost-based heuristic
|
|
74
|
+
if (this.totalCostUsd > 0) {
|
|
75
|
+
const estimatedTokens = this.totalCostUsd * 130_000;
|
|
76
|
+
const window = this.contextWindowSize ?? DEFAULT_CONTEXT_WINDOW;
|
|
77
|
+
return Math.min(100, Math.round((estimatedTokens / window) * 100));
|
|
78
|
+
}
|
|
79
|
+
// Tier 3: Turns-based fallback
|
|
80
|
+
if (this.totalTurns > 0) {
|
|
81
|
+
const estimatedTokens = this.totalTurns * 6_000;
|
|
82
|
+
const window = this.contextWindowSize ?? DEFAULT_CONTEXT_WINDOW;
|
|
83
|
+
return Math.min(100, Math.round((estimatedTokens / window) * 100));
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
isAboveTokenThreshold(thresholdTokens = 200_000) {
|
|
88
|
+
return (this.latestInputTokens !== null &&
|
|
89
|
+
this.latestInputTokens >= thresholdTokens);
|
|
90
|
+
}
|
|
91
|
+
reset() {
|
|
92
|
+
this.totalTurns = 0;
|
|
93
|
+
this.totalCostUsd = 0;
|
|
94
|
+
this.latestInputTokens = null;
|
|
95
|
+
this.latestOutputTokens = null;
|
|
96
|
+
this.contextWindowSize = null;
|
|
97
|
+
this.compactionCount = 0;
|
|
98
|
+
this.sessionId = null;
|
|
99
|
+
}
|
|
100
|
+
/** Restore from persisted active session state. */
|
|
101
|
+
restore(state) {
|
|
102
|
+
this.sessionId = state.sessionId;
|
|
103
|
+
this.totalTurns = state.totalTurns;
|
|
104
|
+
this.totalCostUsd = state.totalCostUsd;
|
|
105
|
+
this.contextWindowSize = state.contextWindowSize;
|
|
106
|
+
this.latestInputTokens = state.latestInputTokens;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GitDiffResult, GitOperationResult } from '../types/contracts.js';
|
|
2
|
+
export declare class GitOperations {
|
|
3
|
+
private readonly cwd;
|
|
4
|
+
constructor(cwd: string);
|
|
5
|
+
diff(): Promise<GitDiffResult>;
|
|
6
|
+
diffStat(): Promise<string>;
|
|
7
|
+
commit(message: string): Promise<GitOperationResult>;
|
|
8
|
+
resetHard(): Promise<GitOperationResult>;
|
|
9
|
+
currentBranch(): Promise<string>;
|
|
10
|
+
recentCommits(count?: number): Promise<string>;
|
|
11
|
+
private git;
|
|
12
|
+
}
|