@doingdev/opencode-claude-manager-plugin 0.1.22 → 0.1.25
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 +129 -40
- package/dist/claude/claude-agent-sdk-adapter.d.ts +27 -0
- package/dist/claude/claude-agent-sdk-adapter.js +520 -0
- package/dist/claude/claude-session.service.d.ts +15 -0
- package/dist/claude/claude-session.service.js +23 -0
- package/dist/claude/session-live-tailer.d.ts +51 -0
- package/dist/claude/session-live-tailer.js +269 -0
- package/dist/claude/tool-approval-manager.d.ts +27 -0
- package/dist/claude/tool-approval-manager.js +238 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- 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/persistent-manager.d.ts +74 -0
- package/dist/manager/persistent-manager.js +167 -0
- package/dist/manager/session-controller.d.ts +45 -0
- package/dist/manager/session-controller.js +147 -0
- 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/claude-manager.plugin.d.ts +2 -0
- package/dist/plugin/claude-manager.plugin.js +627 -0
- package/dist/plugin/orchestrator.plugin.js +4 -2
- package/dist/plugin/service-factory.d.ts +12 -0
- package/dist/plugin/service-factory.js +41 -0
- package/dist/prompts/registry.d.ts +2 -8
- package/dist/prompts/registry.js +159 -30
- package/dist/state/file-run-state-store.d.ts +14 -0
- package/dist/state/file-run-state-store.js +87 -0
- package/dist/state/transcript-store.d.ts +15 -0
- package/dist/state/transcript-store.js +44 -0
- package/dist/types/contracts.d.ts +215 -0
- package/dist/types/contracts.js +1 -0
- package/dist/util/fs-helpers.d.ts +2 -0
- package/dist/util/fs-helpers.js +12 -0
- package/dist/util/transcript-append.d.ts +7 -0
- package/dist/util/transcript-append.js +29 -0
- package/package.json +5 -3
|
@@ -0,0 +1,41 @@
|
|
|
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 { ClaudeMetadataService } from '../metadata/claude-metadata.service.js';
|
|
6
|
+
import { RepoClaudeConfigReader } from '../metadata/repo-claude-config-reader.js';
|
|
7
|
+
import { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
8
|
+
import { TranscriptStore } from '../state/transcript-store.js';
|
|
9
|
+
import { ContextTracker } from '../manager/context-tracker.js';
|
|
10
|
+
import { GitOperations } from '../manager/git-operations.js';
|
|
11
|
+
import { SessionController } from '../manager/session-controller.js';
|
|
12
|
+
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
13
|
+
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
14
|
+
const serviceCache = new Map();
|
|
15
|
+
export function getOrCreatePluginServices(worktree) {
|
|
16
|
+
const cachedServices = serviceCache.get(worktree);
|
|
17
|
+
if (cachedServices) {
|
|
18
|
+
return cachedServices;
|
|
19
|
+
}
|
|
20
|
+
const approvalManager = new ToolApprovalManager();
|
|
21
|
+
const sdkAdapter = new ClaudeAgentSdkAdapter(undefined, approvalManager);
|
|
22
|
+
const metadataService = new ClaudeMetadataService(new RepoClaudeConfigReader(), sdkAdapter);
|
|
23
|
+
const sessionService = new ClaudeSessionService(sdkAdapter, metadataService);
|
|
24
|
+
const contextTracker = new ContextTracker();
|
|
25
|
+
const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.claudeCodeSessionPrompt, managerPromptRegistry.modePrefixes);
|
|
26
|
+
const gitOps = new GitOperations(worktree);
|
|
27
|
+
const stateStore = new FileRunStateStore();
|
|
28
|
+
const transcriptStore = new TranscriptStore();
|
|
29
|
+
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker, transcriptStore);
|
|
30
|
+
// Try to restore active session state (fire and forget)
|
|
31
|
+
manager.tryRestore(worktree).catch(() => { });
|
|
32
|
+
const liveTailer = new SessionLiveTailer();
|
|
33
|
+
const services = {
|
|
34
|
+
manager,
|
|
35
|
+
sessions: sessionService,
|
|
36
|
+
approvalManager,
|
|
37
|
+
liveTailer,
|
|
38
|
+
};
|
|
39
|
+
serviceCache.set(worktree, services);
|
|
40
|
+
return services;
|
|
41
|
+
}
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
export declare const prompts: {
|
|
5
|
-
orchestrator: string;
|
|
6
|
-
planningAgent: string;
|
|
7
|
-
buildAgent: string;
|
|
8
|
-
};
|
|
1
|
+
import type { ManagerPromptRegistry } from '../types/contracts.js';
|
|
2
|
+
export declare const managerPromptRegistry: ManagerPromptRegistry;
|
package/dist/prompts/registry.js
CHANGED
|
@@ -1,37 +1,166 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'',
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'
|
|
1
|
+
export const managerPromptRegistry = {
|
|
2
|
+
managerSystemPrompt: [
|
|
3
|
+
'You are a senior IC operating Claude Code through a persistent session.',
|
|
4
|
+
'Your job is to make Claude Code do the work — not to write code yourself.',
|
|
5
|
+
'Think like a staff engineer: correctness, maintainability, tests, rollback safety,',
|
|
6
|
+
'and clear communication to the user.',
|
|
7
|
+
'',
|
|
8
|
+
'## Decision loop',
|
|
9
|
+
'On every turn, choose exactly one action:',
|
|
10
|
+
' investigate — read files, grep, search the codebase to build context',
|
|
11
|
+
' delegate — send a focused instruction to Claude Code via claude_manager_send',
|
|
12
|
+
' review — run claude_manager_git_diff to inspect what changed',
|
|
13
|
+
' validate — tell Claude Code to run tests, lint, or typecheck',
|
|
14
|
+
' commit — checkpoint good work with claude_manager_git_commit',
|
|
15
|
+
' correct — send a targeted fix instruction (never "try again")',
|
|
16
|
+
' reset — discard bad work with claude_manager_git_reset',
|
|
17
|
+
' ask — use the question tool for structured choices, or one narrow text question',
|
|
18
|
+
'',
|
|
19
|
+
'Default order: investigate → delegate → review → validate → commit.',
|
|
20
|
+
'Skip steps only when you have strong evidence they are unnecessary.',
|
|
21
|
+
'',
|
|
22
|
+
'## Before you delegate',
|
|
23
|
+
'1. Read the relevant files yourself (you have read, grep, glob).',
|
|
24
|
+
' For broad investigations, scope them narrowly or use subagents to avoid',
|
|
25
|
+
' polluting your own context with excessive file contents.',
|
|
26
|
+
'2. Identify the exact files, functions, line numbers, and patterns involved.',
|
|
27
|
+
'3. Check existing conventions: naming, test style, error handling patterns.',
|
|
28
|
+
'4. Craft an instruction that a senior engineer would find unambiguous.',
|
|
29
|
+
' Bad: "Fix the auth bug"',
|
|
30
|
+
' Good: "In src/auth/session.ts, the `validateToken` function (line 42)',
|
|
31
|
+
' throws on expired tokens instead of returning null. Change it to',
|
|
32
|
+
' return null and update the caller in src/routes/login.ts:87."',
|
|
33
|
+
'',
|
|
34
|
+
'## After delegation — mandatory review',
|
|
35
|
+
'Never claim success without evidence:',
|
|
36
|
+
'1. claude_manager_git_diff — read the actual diff, not just the summary.',
|
|
37
|
+
'2. Verify the diff matches what you asked for. Check for:',
|
|
38
|
+
' - Unintended changes or regressions',
|
|
39
|
+
' - Missing test updates',
|
|
40
|
+
' - Style violations against repo conventions',
|
|
41
|
+
'3. If changes look correct, tell Claude Code to run tests/lint/typecheck.',
|
|
42
|
+
'4. Only commit after verification passes.',
|
|
43
|
+
'5. If the diff is wrong: send a specific correction or reset.',
|
|
44
|
+
'',
|
|
45
|
+
'## Handling ambiguity',
|
|
46
|
+
'When requirements are unclear:',
|
|
47
|
+
'1. First, try to resolve it yourself — read code, check tests, grep for usage.',
|
|
48
|
+
'2. If ambiguity remains, ask the user ONE specific question.',
|
|
49
|
+
' Prefer the question tool when discrete options exist (OpenCode shows choices in the UI).',
|
|
50
|
+
' Bad: "What should I do?"',
|
|
51
|
+
' Good: "The `UserService` has both `deactivate()` and `softDelete()` —',
|
|
52
|
+
' should the new endpoint use deactivation (reversible) or',
|
|
53
|
+
' soft-delete (audit-logged)?"',
|
|
54
|
+
'3. Never block on multiple questions at once.',
|
|
55
|
+
'',
|
|
56
|
+
'## Correction and recovery',
|
|
57
|
+
'If Claude Code produces wrong output:',
|
|
58
|
+
'1. First correction: send a specific, targeted fix instruction.',
|
|
59
|
+
'2. Second correction on the same issue: reset, clear the session,',
|
|
60
|
+
' and rewrite the prompt incorporating lessons from both failures.',
|
|
61
|
+
'Never send three corrections for the same problem in one session.',
|
|
62
|
+
'',
|
|
63
|
+
'## Multi-step tasks',
|
|
64
|
+
'- Use todowrite / todoread to track steps in OpenCode; keep items concrete and few.',
|
|
65
|
+
'- Decompose large tasks into sequential focused instructions.',
|
|
66
|
+
'- Commit after each successful step (checkpoint for rollback).',
|
|
67
|
+
'- Tell Claude Code to use subagents for independent parallel work.',
|
|
68
|
+
'- For complex design decisions, tell Claude Code to "think hard".',
|
|
69
|
+
'- Prefer small diffs — they are easier to review and safer to ship.',
|
|
22
70
|
'',
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'-
|
|
26
|
-
'-
|
|
71
|
+
'## Context management',
|
|
72
|
+
'Check the context snapshot returned by each send:',
|
|
73
|
+
'- Under 50%: proceed freely.',
|
|
74
|
+
'- 50–70%: finish current step, then evaluate if a fresh session is needed.',
|
|
75
|
+
'- Over 70%: use claude_manager_compact to reclaim context if the session',
|
|
76
|
+
' still has useful state. Only clear if compaction is insufficient.',
|
|
77
|
+
'- Over 85%: clear the session immediately.',
|
|
78
|
+
'Use freshSession:true on claude_manager_send when switching to an unrelated',
|
|
79
|
+
'task or when the session context is contaminated. Prefer this over a manual',
|
|
80
|
+
'clear+send sequence — it is atomic and self-documenting.',
|
|
81
|
+
'',
|
|
82
|
+
'## Model and effort selection',
|
|
83
|
+
'Choose model and effort deliberately before each delegation:',
|
|
84
|
+
'- claude-opus-4-6 + high effort: default for most coding tasks.',
|
|
85
|
+
'- claude-sonnet-4-6 or claude-sonnet-4-5: faster/lighter work (simple renames,',
|
|
86
|
+
' formatting, test scaffolding, quick investigations).',
|
|
87
|
+
'- effort "medium": acceptable for lighter tasks that do not require deep reasoning.',
|
|
88
|
+
'- effort "max": reserve for unusually hard problems (complex refactors,',
|
|
89
|
+
' subtle concurrency bugs, large cross-cutting changes).',
|
|
90
|
+
"- Do not use Haiku for this plugin's coding-agent role.",
|
|
91
|
+
'',
|
|
92
|
+
'## Plan mode',
|
|
93
|
+
'When delegating with mode:"plan", Claude Code returns a read-only',
|
|
94
|
+
'implementation plan. The plan MUST be returned inline in the assistant',
|
|
95
|
+
'response — do NOT write plan artifacts to disk, create files, or rely on',
|
|
96
|
+
'ExitPlanMode. Treat the returned finalText as the plan. If the plan is',
|
|
97
|
+
'acceptable, switch to mode:"free" and delegate the implementation steps.',
|
|
98
|
+
'',
|
|
99
|
+
'## Tools reference',
|
|
100
|
+
'todowrite / todoread — OpenCode session todo list (track multi-step work)',
|
|
101
|
+
'question — OpenCode user prompt with options (clarify trade-offs)',
|
|
102
|
+
'claude_manager_send — send instruction (creates or resumes session)',
|
|
103
|
+
' freshSession:true — clear session first (use for unrelated tasks)',
|
|
104
|
+
' model / effort — choose deliberately (see "Model and effort selection")',
|
|
105
|
+
'claude_manager_compact — compress session context (preserves session state)',
|
|
106
|
+
'claude_manager_git_diff — review all uncommitted changes',
|
|
107
|
+
'claude_manager_git_commit — stage all + commit',
|
|
108
|
+
'claude_manager_git_reset — hard reset + clean (destructive)',
|
|
109
|
+
'claude_manager_clear — drop session, next send starts fresh',
|
|
110
|
+
'claude_manager_status — context health snapshot',
|
|
111
|
+
'claude_manager_metadata — inspect repo Claude config',
|
|
112
|
+
'claude_manager_sessions — list sessions or read transcripts',
|
|
113
|
+
'claude_manager_runs — list or inspect run records',
|
|
114
|
+
'',
|
|
115
|
+
'## Autonomy blockers — surface these to the user',
|
|
116
|
+
'Be candid about what you cannot do autonomously:',
|
|
117
|
+
'- Credentials, API keys, or secrets you do not have.',
|
|
118
|
+
'- Architectural decisions with trade-offs the user should weigh.',
|
|
119
|
+
'- Destructive actions on shared state (deploy, publish, force-push).',
|
|
120
|
+
'- Access to external services or environments you cannot reach.',
|
|
121
|
+
'State the blocker, what you need, and a concrete suggestion to unblock.',
|
|
27
122
|
].join('\n'),
|
|
28
|
-
|
|
29
|
-
'You are
|
|
123
|
+
claudeCodeSessionPrompt: [
|
|
124
|
+
'You are directed by an expert automated operator.',
|
|
125
|
+
'Treat each message as a precise instruction from a senior engineer.',
|
|
30
126
|
'',
|
|
31
|
-
'
|
|
32
|
-
'-
|
|
127
|
+
'## Execution rules',
|
|
128
|
+
'- Execute instructions directly. Do not ask for clarification.',
|
|
129
|
+
'- Be concise — no preamble, no restating the task.',
|
|
130
|
+
'- Prefer targeted file reads over reading entire files.',
|
|
131
|
+
'- Use the Agent tool for independent parallel work.',
|
|
132
|
+
'',
|
|
133
|
+
'## Quality expectations',
|
|
134
|
+
'- Follow existing repo conventions (naming, style, patterns).',
|
|
135
|
+
'- When creating or modifying code, consider edge cases and error handling.',
|
|
136
|
+
'- When modifying existing code, preserve surrounding style and structure.',
|
|
137
|
+
'- If asked to implement a feature, include relevant tests unless told otherwise.',
|
|
33
138
|
'- Run tests/lint/typecheck when instructed; report exact output on failure.',
|
|
34
|
-
'
|
|
139
|
+
'',
|
|
140
|
+
'## Git boundary — do NOT run these commands:',
|
|
141
|
+
'git commit, git push, git reset, git checkout, git stash.',
|
|
142
|
+
'The operator manages all git operations externally.',
|
|
143
|
+
'',
|
|
144
|
+
'## Reporting',
|
|
35
145
|
'- End with a brief verification summary: what was done, what was verified.',
|
|
146
|
+
'- Report blockers immediately with specifics: file, line, error message.',
|
|
147
|
+
'- If a task is partially complete, state exactly what remains.',
|
|
36
148
|
].join('\n'),
|
|
149
|
+
modePrefixes: {
|
|
150
|
+
plan: [
|
|
151
|
+
'[PLAN MODE] You are in read-only planning mode. Do NOT create or edit any files.',
|
|
152
|
+
'Do NOT use ExitPlanMode or write plan artifacts to disk.',
|
|
153
|
+
'Use read, grep, glob, and search tools only.',
|
|
154
|
+
'Analyze the codebase and produce a detailed implementation plan:',
|
|
155
|
+
'files to change, functions to modify, new files to create, test strategy,',
|
|
156
|
+
'and potential risks. End with a numbered step-by-step plan.',
|
|
157
|
+
'Return the entire plan inline in your response text.',
|
|
158
|
+
].join(' '),
|
|
159
|
+
free: '',
|
|
160
|
+
},
|
|
161
|
+
contextWarnings: {
|
|
162
|
+
moderate: 'Session context is filling up ({percent}% estimated). Consider whether a fresh session would be more efficient.',
|
|
163
|
+
high: 'Session context is heavy ({percent}% estimated, {turns} turns, ${cost}). Start a new session or compact first.',
|
|
164
|
+
critical: 'Session context is near capacity ({percent}% estimated). Clear the session immediately before continuing.',
|
|
165
|
+
},
|
|
37
166
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PersistentRunRecord } from '../types/contracts.js';
|
|
2
|
+
export declare class FileRunStateStore {
|
|
3
|
+
private readonly baseDirectoryName;
|
|
4
|
+
private readonly writeQueues;
|
|
5
|
+
constructor(baseDirectoryName?: string);
|
|
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
|
+
private getRunKey;
|
|
11
|
+
private getRunsDirectory;
|
|
12
|
+
private getRunPath;
|
|
13
|
+
private enqueueWrite;
|
|
14
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isFileNotFoundError, writeJsonAtomically, } from '../util/fs-helpers.js';
|
|
4
|
+
export class FileRunStateStore {
|
|
5
|
+
baseDirectoryName;
|
|
6
|
+
writeQueues = new Map();
|
|
7
|
+
constructor(baseDirectoryName = '.claude-manager') {
|
|
8
|
+
this.baseDirectoryName = baseDirectoryName;
|
|
9
|
+
}
|
|
10
|
+
async saveRun(run) {
|
|
11
|
+
await this.enqueueWrite(this.getRunKey(run.cwd, run.id), async () => {
|
|
12
|
+
const runPath = this.getRunPath(run.cwd, run.id);
|
|
13
|
+
await fs.mkdir(path.dirname(runPath), { recursive: true });
|
|
14
|
+
await writeJsonAtomically(runPath, run);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async getRun(cwd, runId) {
|
|
18
|
+
const runPath = this.getRunPath(cwd, runId);
|
|
19
|
+
try {
|
|
20
|
+
const content = await fs.readFile(runPath, 'utf8');
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (isFileNotFoundError(error)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async listRuns(cwd) {
|
|
31
|
+
const runsDirectory = this.getRunsDirectory(cwd);
|
|
32
|
+
try {
|
|
33
|
+
const entries = await fs.readdir(runsDirectory);
|
|
34
|
+
const runs = await Promise.all(entries
|
|
35
|
+
.filter((entry) => entry.endsWith('.json'))
|
|
36
|
+
.map(async (entry) => {
|
|
37
|
+
const content = await fs.readFile(path.join(runsDirectory, entry), 'utf8');
|
|
38
|
+
return JSON.parse(content);
|
|
39
|
+
}));
|
|
40
|
+
return runs.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (isFileNotFoundError(error)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async updateRun(cwd, runId, update) {
|
|
50
|
+
return this.enqueueWrite(this.getRunKey(cwd, runId), async () => {
|
|
51
|
+
const existingRun = await this.getRun(cwd, runId);
|
|
52
|
+
if (!existingRun) {
|
|
53
|
+
throw new Error(`Run ${runId} does not exist.`);
|
|
54
|
+
}
|
|
55
|
+
const updatedRun = update(existingRun);
|
|
56
|
+
const runPath = this.getRunPath(cwd, runId);
|
|
57
|
+
await fs.mkdir(path.dirname(runPath), { recursive: true });
|
|
58
|
+
await writeJsonAtomically(runPath, updatedRun);
|
|
59
|
+
return updatedRun;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
getRunKey(cwd, runId) {
|
|
63
|
+
return `${cwd}:${runId}`;
|
|
64
|
+
}
|
|
65
|
+
getRunsDirectory(cwd) {
|
|
66
|
+
return path.join(cwd, this.baseDirectoryName, 'runs');
|
|
67
|
+
}
|
|
68
|
+
getRunPath(cwd, runId) {
|
|
69
|
+
return path.join(this.getRunsDirectory(cwd), `${runId}.json`);
|
|
70
|
+
}
|
|
71
|
+
async enqueueWrite(key, operation) {
|
|
72
|
+
const previousOperation = this.writeQueues.get(key) ?? Promise.resolve();
|
|
73
|
+
const resultPromise = previousOperation
|
|
74
|
+
.catch(() => undefined)
|
|
75
|
+
.then(operation);
|
|
76
|
+
const settledPromise = resultPromise.then(() => undefined, () => undefined);
|
|
77
|
+
this.writeQueues.set(key, settledPromise);
|
|
78
|
+
try {
|
|
79
|
+
return await resultPromise;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (this.writeQueues.get(key) === settledPromise) {
|
|
83
|
+
this.writeQueues.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export interface ManagerPromptRegistry {
|
|
2
|
+
managerSystemPrompt: string;
|
|
3
|
+
claudeCodeSessionPrompt: string;
|
|
4
|
+
modePrefixes: {
|
|
5
|
+
plan: string;
|
|
6
|
+
free: string;
|
|
7
|
+
};
|
|
8
|
+
contextWarnings: {
|
|
9
|
+
moderate: string;
|
|
10
|
+
high: string;
|
|
11
|
+
critical: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export type ClaudeSettingSource = 'user' | 'project' | 'local';
|
|
15
|
+
export type SessionMode = 'plan' | 'free';
|
|
16
|
+
export interface ClaudeCommandMetadata {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
argumentHint?: string;
|
|
20
|
+
source: 'sdk' | 'skill' | 'command';
|
|
21
|
+
path?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ClaudeSkillMetadata {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
path: string;
|
|
27
|
+
source: 'skill' | 'command';
|
|
28
|
+
}
|
|
29
|
+
export interface ClaudeHookMetadata {
|
|
30
|
+
name: string;
|
|
31
|
+
matcher?: string;
|
|
32
|
+
sourcePath: string;
|
|
33
|
+
commandCount: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ClaudeAgentMetadata {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
source: 'sdk' | 'filesystem';
|
|
40
|
+
}
|
|
41
|
+
export interface ClaudeMetadataSnapshot {
|
|
42
|
+
collectedAt: string;
|
|
43
|
+
cwd: string;
|
|
44
|
+
commands: ClaudeCommandMetadata[];
|
|
45
|
+
skills: ClaudeSkillMetadata[];
|
|
46
|
+
hooks: ClaudeHookMetadata[];
|
|
47
|
+
agents: ClaudeAgentMetadata[];
|
|
48
|
+
claudeMdPath?: string;
|
|
49
|
+
settingsPaths: string[];
|
|
50
|
+
}
|
|
51
|
+
export interface ClaudeSessionEvent {
|
|
52
|
+
type: 'init' | 'assistant' | 'partial' | 'user' | 'tool_call' | 'tool_progress' | 'tool_summary' | 'status' | 'system' | 'result' | 'error';
|
|
53
|
+
sessionId?: string;
|
|
54
|
+
text: string;
|
|
55
|
+
turns?: number;
|
|
56
|
+
totalCostUsd?: number;
|
|
57
|
+
/** Present on live SDK-normalized events; omitted when compact-persisted to save space. */
|
|
58
|
+
rawType?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface RunClaudeSessionInput {
|
|
61
|
+
cwd: string;
|
|
62
|
+
prompt: string;
|
|
63
|
+
systemPrompt?: string;
|
|
64
|
+
model?: string;
|
|
65
|
+
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
66
|
+
mode?: SessionMode;
|
|
67
|
+
permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk';
|
|
68
|
+
/** Merged with `Skill` by the SDK adapter unless `Skill` appears in `disallowedTools`. */
|
|
69
|
+
allowedTools?: string[];
|
|
70
|
+
disallowedTools?: string[];
|
|
71
|
+
continueSession?: boolean;
|
|
72
|
+
resumeSessionId?: string;
|
|
73
|
+
forkSession?: boolean;
|
|
74
|
+
persistSession?: boolean;
|
|
75
|
+
includePartialMessages?: boolean;
|
|
76
|
+
settingSources?: ClaudeSettingSource[];
|
|
77
|
+
maxTurns?: number;
|
|
78
|
+
abortSignal?: AbortSignal;
|
|
79
|
+
}
|
|
80
|
+
export interface ClaudeSessionRunResult {
|
|
81
|
+
sessionId?: string;
|
|
82
|
+
events: ClaudeSessionEvent[];
|
|
83
|
+
finalText: string;
|
|
84
|
+
turns?: number;
|
|
85
|
+
totalCostUsd?: number;
|
|
86
|
+
inputTokens?: number;
|
|
87
|
+
outputTokens?: number;
|
|
88
|
+
contextWindowSize?: number;
|
|
89
|
+
}
|
|
90
|
+
export interface ClaudeSessionSummary {
|
|
91
|
+
sessionId: string;
|
|
92
|
+
summary: string;
|
|
93
|
+
cwd?: string;
|
|
94
|
+
gitBranch?: string;
|
|
95
|
+
createdAt?: number;
|
|
96
|
+
lastModified: number;
|
|
97
|
+
}
|
|
98
|
+
export interface ClaudeSessionTranscriptMessage {
|
|
99
|
+
role: 'user' | 'assistant';
|
|
100
|
+
sessionId: string;
|
|
101
|
+
messageId: string;
|
|
102
|
+
text: string;
|
|
103
|
+
}
|
|
104
|
+
export interface ClaudeCapabilitySnapshot {
|
|
105
|
+
commands: ClaudeCommandMetadata[];
|
|
106
|
+
agents: ClaudeAgentMetadata[];
|
|
107
|
+
models: string[];
|
|
108
|
+
}
|
|
109
|
+
export type ContextWarningLevel = 'ok' | 'moderate' | 'high' | 'critical';
|
|
110
|
+
export interface SessionContextSnapshot {
|
|
111
|
+
sessionId: string | null;
|
|
112
|
+
totalTurns: number;
|
|
113
|
+
totalCostUsd: number;
|
|
114
|
+
latestInputTokens: number | null;
|
|
115
|
+
latestOutputTokens: number | null;
|
|
116
|
+
contextWindowSize: number | null;
|
|
117
|
+
estimatedContextPercent: number | null;
|
|
118
|
+
warningLevel: ContextWarningLevel;
|
|
119
|
+
compactionCount: number;
|
|
120
|
+
}
|
|
121
|
+
export interface GitDiffResult {
|
|
122
|
+
hasDiff: boolean;
|
|
123
|
+
diffText: string;
|
|
124
|
+
stats: {
|
|
125
|
+
filesChanged: number;
|
|
126
|
+
insertions: number;
|
|
127
|
+
deletions: number;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export interface GitOperationResult {
|
|
131
|
+
success: boolean;
|
|
132
|
+
output: string;
|
|
133
|
+
error?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface ActiveSessionState {
|
|
136
|
+
sessionId: string;
|
|
137
|
+
cwd: string;
|
|
138
|
+
startedAt: string;
|
|
139
|
+
totalTurns: number;
|
|
140
|
+
totalCostUsd: number;
|
|
141
|
+
estimatedContextPercent: number | null;
|
|
142
|
+
contextWindowSize: number | null;
|
|
143
|
+
latestInputTokens: number | null;
|
|
144
|
+
}
|
|
145
|
+
export type PersistentRunStatus = 'running' | 'completed' | 'failed';
|
|
146
|
+
export interface PersistentRunMessageRecord {
|
|
147
|
+
timestamp: string;
|
|
148
|
+
direction: 'sent' | 'received';
|
|
149
|
+
text: string;
|
|
150
|
+
turns?: number;
|
|
151
|
+
totalCostUsd?: number;
|
|
152
|
+
inputTokens?: number;
|
|
153
|
+
outputTokens?: number;
|
|
154
|
+
}
|
|
155
|
+
export interface PersistentRunActionRecord {
|
|
156
|
+
timestamp: string;
|
|
157
|
+
type: 'git_diff' | 'git_commit' | 'git_reset' | 'compact' | 'clear';
|
|
158
|
+
result: string;
|
|
159
|
+
}
|
|
160
|
+
export interface PersistentRunRecord {
|
|
161
|
+
id: string;
|
|
162
|
+
cwd: string;
|
|
163
|
+
task: string;
|
|
164
|
+
status: PersistentRunStatus;
|
|
165
|
+
createdAt: string;
|
|
166
|
+
updatedAt: string;
|
|
167
|
+
sessionId: string | null;
|
|
168
|
+
sessionHistory: string[];
|
|
169
|
+
messages: PersistentRunMessageRecord[];
|
|
170
|
+
actions: PersistentRunActionRecord[];
|
|
171
|
+
commits: string[];
|
|
172
|
+
context: SessionContextSnapshot;
|
|
173
|
+
finalSummary?: string;
|
|
174
|
+
}
|
|
175
|
+
export interface PersistentRunResult {
|
|
176
|
+
run: PersistentRunRecord;
|
|
177
|
+
}
|
|
178
|
+
export interface LiveTailEvent {
|
|
179
|
+
type: 'line' | 'error' | 'end';
|
|
180
|
+
sessionId: string;
|
|
181
|
+
data?: unknown;
|
|
182
|
+
rawLine?: string;
|
|
183
|
+
error?: string;
|
|
184
|
+
}
|
|
185
|
+
export interface ToolOutputPreview {
|
|
186
|
+
toolUseId: string;
|
|
187
|
+
content: string;
|
|
188
|
+
isError: boolean;
|
|
189
|
+
}
|
|
190
|
+
export interface ToolApprovalRule {
|
|
191
|
+
id: string;
|
|
192
|
+
description?: string;
|
|
193
|
+
/** Tool name — exact match or glob with * wildcard */
|
|
194
|
+
toolPattern: string;
|
|
195
|
+
/** Optional substring match against JSON-serialized tool input */
|
|
196
|
+
inputPattern?: string;
|
|
197
|
+
action: 'allow' | 'deny';
|
|
198
|
+
denyMessage?: string;
|
|
199
|
+
}
|
|
200
|
+
export interface ToolApprovalPolicy {
|
|
201
|
+
rules: ToolApprovalRule[];
|
|
202
|
+
defaultAction: 'allow' | 'deny';
|
|
203
|
+
defaultDenyMessage?: string;
|
|
204
|
+
enabled: boolean;
|
|
205
|
+
}
|
|
206
|
+
export interface ToolApprovalDecision {
|
|
207
|
+
timestamp: string;
|
|
208
|
+
toolName: string;
|
|
209
|
+
inputPreview: string;
|
|
210
|
+
title?: string;
|
|
211
|
+
matchedRuleId: string;
|
|
212
|
+
action: 'allow' | 'deny';
|
|
213
|
+
denyMessage?: string;
|
|
214
|
+
agentId?: string;
|
|
215
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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[];
|