@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.
@@ -0,0 +1,15 @@
1
+ import type { ClaudeSessionEvent } from '../types/contracts.js';
2
+ export declare class TranscriptStore {
3
+ private readonly baseDirectoryName;
4
+ constructor(baseDirectoryName?: string);
5
+ /**
6
+ * Append new events to the transcript file for the given session.
7
+ * Creates the file if it does not exist. Strips trailing partials before persisting.
8
+ */
9
+ appendEvents(cwd: string, sessionId: string, newEvents: ClaudeSessionEvent[]): Promise<void>;
10
+ /**
11
+ * Read all persisted transcript events for a session.
12
+ */
13
+ readEvents(cwd: string, sessionId: string): Promise<ClaudeSessionEvent[]>;
14
+ private getTranscriptPath;
15
+ }
@@ -0,0 +1,44 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { appendTranscriptEvents, stripTrailingPartials, } from '../util/transcript-append.js';
4
+ import { isFileNotFoundError, writeJsonAtomically, } from '../util/fs-helpers.js';
5
+ export class TranscriptStore {
6
+ baseDirectoryName;
7
+ constructor(baseDirectoryName = '.claude-manager') {
8
+ this.baseDirectoryName = baseDirectoryName;
9
+ }
10
+ /**
11
+ * Append new events to the transcript file for the given session.
12
+ * Creates the file if it does not exist. Strips trailing partials before persisting.
13
+ */
14
+ async appendEvents(cwd, sessionId, newEvents) {
15
+ if (newEvents.length === 0) {
16
+ return;
17
+ }
18
+ const filePath = this.getTranscriptPath(cwd, sessionId);
19
+ const existing = await this.readEvents(cwd, sessionId);
20
+ const merged = appendTranscriptEvents(existing, newEvents);
21
+ const cleaned = stripTrailingPartials(merged);
22
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
23
+ await writeJsonAtomically(filePath, cleaned);
24
+ }
25
+ /**
26
+ * Read all persisted transcript events for a session.
27
+ */
28
+ async readEvents(cwd, sessionId) {
29
+ const filePath = this.getTranscriptPath(cwd, sessionId);
30
+ try {
31
+ const content = await fs.readFile(filePath, 'utf8');
32
+ return JSON.parse(content);
33
+ }
34
+ catch (error) {
35
+ if (isFileNotFoundError(error)) {
36
+ return [];
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ getTranscriptPath(cwd, sessionId) {
42
+ return path.join(cwd, this.baseDirectoryName, 'transcripts', `${sessionId}.json`);
43
+ }
44
+ }
@@ -1,6 +1,11 @@
1
1
  export interface ManagerPromptRegistry {
2
2
  managerSystemPrompt: string;
3
- subagentSystemPrompt: string;
3
+ claudeCodeSessionPrompt: string;
4
+ contextWarnings: {
5
+ moderate: string;
6
+ high: string;
7
+ critical: string;
8
+ };
4
9
  }
5
10
  export type ClaudeSettingSource = 'user' | 'project' | 'local';
6
11
  export interface ClaudeCommandMetadata {
@@ -39,12 +44,13 @@ export interface ClaudeMetadataSnapshot {
39
44
  settingsPaths: string[];
40
45
  }
41
46
  export interface ClaudeSessionEvent {
42
- type: 'init' | 'assistant' | 'partial' | 'status' | 'system' | 'result' | 'error';
47
+ type: 'init' | 'assistant' | 'partial' | 'user' | 'tool_call' | 'status' | 'system' | 'result' | 'error';
43
48
  sessionId?: string;
44
49
  text: string;
45
50
  turns?: number;
46
51
  totalCostUsd?: number;
47
- rawType: string;
52
+ /** Present on live SDK-normalized events; omitted when compact-persisted to save space. */
53
+ rawType?: string;
48
54
  }
49
55
  export interface RunClaudeSessionInput {
50
56
  cwd: string;
@@ -68,6 +74,9 @@ export interface ClaudeSessionRunResult {
68
74
  finalText: string;
69
75
  turns?: number;
70
76
  totalCostUsd?: number;
77
+ inputTokens?: number;
78
+ outputTokens?: number;
79
+ contextWindowSize?: number;
71
80
  }
72
81
  export interface ClaudeSessionSummary {
73
82
  sessionId: string;
@@ -88,58 +97,72 @@ export interface ClaudeCapabilitySnapshot {
88
97
  agents: ClaudeAgentMetadata[];
89
98
  models: string[];
90
99
  }
91
- export type ManagerRunMode = 'auto' | 'single' | 'split';
92
- export type ManagerRunStatus = 'pending' | 'running' | 'completed' | 'failed';
93
- export type WorktreeMode = 'shared-root' | 'git-worktree';
94
- export interface ManagedSubtaskPlan {
95
- id: string;
96
- title: string;
97
- prompt: string;
98
- }
99
- export interface WorktreeAssignment {
100
- mode: WorktreeMode;
101
- cwd: string;
102
- rootCwd: string;
103
- branchName?: string;
100
+ export type ContextWarningLevel = 'ok' | 'moderate' | 'high' | 'critical';
101
+ export interface SessionContextSnapshot {
102
+ sessionId: string | null;
103
+ totalTurns: number;
104
+ totalCostUsd: number;
105
+ latestInputTokens: number | null;
106
+ latestOutputTokens: number | null;
107
+ contextWindowSize: number | null;
108
+ estimatedContextPercent: number | null;
109
+ warningLevel: ContextWarningLevel;
110
+ compactionCount: number;
111
+ }
112
+ export interface GitDiffResult {
113
+ hasDiff: boolean;
114
+ diffText: string;
115
+ stats: {
116
+ filesChanged: number;
117
+ insertions: number;
118
+ deletions: number;
119
+ };
120
+ }
121
+ export interface GitOperationResult {
122
+ success: boolean;
123
+ output: string;
124
+ error?: string;
104
125
  }
105
- export interface ManagedSessionRecord {
106
- id: string;
107
- title: string;
108
- prompt: string;
109
- status: ManagerRunStatus;
126
+ export interface ActiveSessionState {
127
+ sessionId: string;
110
128
  cwd: string;
111
- worktreeMode: WorktreeMode;
112
- branchName?: string;
113
- claudeSessionId?: string;
114
- finalText?: string;
129
+ startedAt: string;
130
+ totalTurns: number;
131
+ totalCostUsd: number;
132
+ estimatedContextPercent: number | null;
133
+ contextWindowSize: number | null;
134
+ latestInputTokens: number | null;
135
+ }
136
+ export type PersistentRunStatus = 'running' | 'completed' | 'failed';
137
+ export interface PersistentRunMessageRecord {
138
+ timestamp: string;
139
+ direction: 'sent' | 'received';
140
+ text: string;
115
141
  turns?: number;
116
142
  totalCostUsd?: number;
117
- error?: string;
118
- events: ClaudeSessionEvent[];
143
+ inputTokens?: number;
144
+ outputTokens?: number;
145
+ }
146
+ export interface PersistentRunActionRecord {
147
+ timestamp: string;
148
+ type: 'git_diff' | 'git_commit' | 'git_reset' | 'compact' | 'clear';
149
+ result: string;
119
150
  }
120
- export interface ManagerRunRecord {
151
+ export interface PersistentRunRecord {
121
152
  id: string;
122
153
  cwd: string;
123
154
  task: string;
124
- mode: ManagerRunMode;
125
- useWorktrees: boolean;
126
- includeProjectSettings: boolean;
127
- status: ManagerRunStatus;
155
+ status: PersistentRunStatus;
128
156
  createdAt: string;
129
157
  updatedAt: string;
130
- metadata: ClaudeMetadataSnapshot;
131
- sessions: ManagedSessionRecord[];
158
+ sessionId: string | null;
159
+ sessionHistory: string[];
160
+ messages: PersistentRunMessageRecord[];
161
+ actions: PersistentRunActionRecord[];
162
+ commits: string[];
163
+ context: SessionContextSnapshot;
132
164
  finalSummary?: string;
133
165
  }
134
- export interface ManagerTaskRequest {
135
- cwd: string;
136
- task: string;
137
- mode?: ManagerRunMode;
138
- maxSubagents?: number;
139
- useWorktrees?: boolean;
140
- includeProjectSettings?: boolean;
141
- model?: string;
142
- }
143
- export interface ManagerRunResult {
144
- run: ManagerRunRecord;
166
+ export interface PersistentRunResult {
167
+ run: PersistentRunRecord;
145
168
  }
@@ -0,0 +1,2 @@
1
+ export declare function writeJsonAtomically(filePath: string, data: unknown): Promise<void>;
2
+ export declare function isFileNotFoundError(error: unknown): error is NodeJS.ErrnoException;
@@ -0,0 +1,12 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ export async function writeJsonAtomically(filePath, data) {
4
+ const tempPath = `${filePath}.${randomUUID()}.tmp`;
5
+ await fs.writeFile(tempPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
6
+ await fs.rename(tempPath, filePath);
7
+ }
8
+ export function isFileNotFoundError(error) {
9
+ return (error instanceof Error &&
10
+ 'code' in error &&
11
+ error.code === 'ENOENT');
12
+ }
@@ -0,0 +1,7 @@
1
+ import type { ClaudeSessionEvent } from '../types/contracts.js';
2
+ export declare function stripTrailingPartials(events: ClaudeSessionEvent[]): ClaudeSessionEvent[];
3
+ /**
4
+ * Append transcript events for run-state persistence: at most one trailing
5
+ * `partial`, dropped whenever a non-partial event is appended.
6
+ */
7
+ export declare function appendTranscriptEvents(events: ClaudeSessionEvent[], incoming: ClaudeSessionEvent[]): ClaudeSessionEvent[];
@@ -0,0 +1,29 @@
1
+ export function stripTrailingPartials(events) {
2
+ let end = events.length;
3
+ while (end > 0 && events[end - 1].type === 'partial') {
4
+ end -= 1;
5
+ }
6
+ return end === events.length ? events : events.slice(0, end);
7
+ }
8
+ /**
9
+ * Append transcript events for run-state persistence: at most one trailing
10
+ * `partial`, dropped whenever a non-partial event is appended.
11
+ */
12
+ export function appendTranscriptEvents(events, incoming) {
13
+ let next = events;
14
+ for (const event of incoming) {
15
+ if (event.type === 'partial') {
16
+ if (next.length > 0 && next[next.length - 1].type === 'partial') {
17
+ next = [...next.slice(0, -1), event];
18
+ }
19
+ else {
20
+ next = [...next, event];
21
+ }
22
+ }
23
+ else {
24
+ next = stripTrailingPartials(next);
25
+ next = [...next, event];
26
+ }
27
+ }
28
+ return next;
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -25,14 +25,6 @@
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
- "scripts": {
29
- "build": "tsc -p tsconfig.build.json",
30
- "typecheck": "tsc -p tsconfig.json --noEmit",
31
- "lint": "eslint .",
32
- "format": "prettier --write .",
33
- "test": "vitest run",
34
- "release": "npm run build && npm version patch && npm publish"
35
- },
36
28
  "engines": {
37
29
  "node": ">=22.0.0"
38
30
  },
@@ -48,9 +40,19 @@
48
40
  "@vitest/coverage-v8": "^4.1.0",
49
41
  "eslint": "^9.22.0",
50
42
  "globals": "^15.15.0",
43
+ "knip": "^6.0.2",
51
44
  "prettier": "^3.8.1",
52
45
  "typescript": "^5.9.3",
53
46
  "typescript-eslint": "^8.57.1",
54
47
  "vitest": "^4.1.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit",
52
+ "lint": "eslint .",
53
+ "format": "prettier --write .",
54
+ "test": "vitest run",
55
+ "knip": "knip",
56
+ "release": "pnpm run build && pnpm version patch && pnpm publish"
55
57
  }
56
- }
58
+ }