@doingdev/opencode-claude-manager-plugin 0.1.10 → 0.1.12

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 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, and coordinating optional git worktrees.
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, optionally assigning dedicated git worktrees.
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
- npm install @doingdev/opencode-claude-manager-plugin
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
- npm install
36
- npm run build
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 and worktrees; returns a compact output summary and a `runId` for deeper inspection
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, use worktrees, and summarize the final result.
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
- npm install
89
- npm run lint
90
- npm run typecheck
91
- npm run test
92
- npm run build
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
- npm login
123
- npm whoami
124
- npm version patch
125
- npm run lint
126
- npm run typecheck
127
- npm run test
128
- npm run build
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
- - Worktree creation only activates when the current directory is a git repo and multiple subtasks are planned.
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
- - `npm run build`
148
- - `npm run typecheck`
149
- - `npm run lint`
150
- - `npm run format`
151
- - `npm run test`
144
+ - `pnpm run build`
145
+ - `pnpm run typecheck`
146
+ - `pnpm run lint`
147
+ - `pnpm run format`
148
+ - `pnpm run test`
@@ -1,7 +1,7 @@
1
1
  import { type Options, type Query, type SDKSessionInfo, type SessionMessage, type SettingSource } from '@anthropic-ai/claude-agent-sdk';
2
2
  import type { ClaudeCapabilitySnapshot, ClaudeSessionEvent, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, RunClaudeSessionInput } from '../types/contracts.js';
3
3
  export type ClaudeSessionEventHandler = (event: ClaudeSessionEvent) => void | Promise<void>;
4
- export interface ClaudeAgentSdkFacade {
4
+ interface ClaudeAgentSdkFacade {
5
5
  query(params: {
6
6
  prompt: string;
7
7
  options?: Options;
@@ -22,3 +22,4 @@ export declare class ClaudeAgentSdkAdapter {
22
22
  probeCapabilities(cwd: string, settingSources?: SettingSource[]): Promise<ClaudeCapabilitySnapshot>;
23
23
  private buildOptions;
24
24
  }
25
+ export {};
@@ -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: this.buildOptions(input),
20
+ options,
16
21
  });
17
- const events = [];
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 event = normalizeSdkMessage(message);
25
- if (!event) {
26
- continue;
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
- sessionId ??= event.sessionId;
29
- if (event.type === 'result') {
30
- finalText = event.text;
31
- turns = event.turns;
32
- totalCostUsd = event.totalCostUsd;
33
- }
34
- events.push(event);
35
- if (onEvent) {
36
- await onEvent(event);
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 normalizeSdkMessage(message) {
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 null;
162
+ return [];
142
163
  }
143
- return {
144
- type: 'partial',
145
- sessionId,
146
- text: partialText,
147
- rawType: message.type,
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
- type: message.is_error ? 'error' : 'result',
153
- sessionId,
154
- text: message.subtype === 'success'
155
- ? message.result
156
- : message.errors.join('\n') || message.subtype,
157
- turns: message.num_turns,
158
- totalCostUsd: message.total_cost_usd,
159
- rawType: `${message.type}:${message.subtype}`,
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
- type: message.subtype === 'init' ? 'init' : 'system',
165
- sessionId,
166
- text: message.subtype,
167
- rawType: `${message.type}:${message.subtype}`,
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
- type: 'system',
173
- sessionId,
174
- text: message.output.join('\n') || 'auth_status',
175
- rawType: message.type,
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.suggestion,
224
+ text: message.type,
183
225
  rawType: message.type,
184
- };
185
- }
186
- if (message.type === 'user') {
187
- return null;
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
- return {
190
- type: 'system',
191
- sessionId,
192
- text: message.type,
193
- rawType: message.type,
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, ManagerRunRecord, ManagerRunResult, ManagerTaskRequest, ManagerPromptRegistry, RunClaudeSessionInput, } from './types/contracts.js';
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
+ }