@doingdev/opencode-claude-manager-plugin 0.1.0

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 ADDED
@@ -0,0 +1,119 @@
1
+ # OpenCode Claude Manager Plugin
2
+
3
+ This package provides an OpenCode plugin that lets an OpenCode-side manager agent orchestrate Claude Code sessions through a stable local bridge.
4
+
5
+ ## Overview
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.
8
+
9
+ ## Features
10
+
11
+ - Runs Claude Code tasks from OpenCode through `@anthropic-ai/claude-agent-sdk`.
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.
14
+ - Persists manager runs under `.claude-manager/runs` so sessions can be inspected later.
15
+ - Exposes manager-facing tools instead of relying on undocumented plugin-defined slash commands.
16
+
17
+ ## Requirements
18
+
19
+ - Node `22+`
20
+ - OpenCode with plugin loading enabled
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
+
24
+ ## Installation
25
+
26
+ Install from npm:
27
+
28
+ ```bash
29
+ npm install @doingdev/opencode-claude-manager-plugin
30
+ ```
31
+
32
+ Or for local development in this repo:
33
+
34
+ ```bash
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ ## OpenCode Config
40
+
41
+ Add the plugin to your OpenCode config:
42
+
43
+ ```json
44
+ {
45
+ "plugin": ["@doingdev/opencode-claude-manager-plugin"]
46
+ }
47
+ ```
48
+
49
+ If you are testing locally, point OpenCode at the local package or plugin file using your normal local plugin workflow.
50
+
51
+ ## OpenCode tools
52
+
53
+ - `claude_manager_run` - run a task through Claude with optional splitting and worktrees
54
+ - `claude_manager_metadata` - inspect available Claude commands, skills, hooks, and settings
55
+ - `claude_manager_sessions` - list Claude sessions or inspect a saved transcript
56
+ - `claude_manager_runs` - inspect persisted manager run records
57
+ - `claude_manager_cleanup_run` - explicitly remove worktrees created for a prior manager run
58
+
59
+ ## Quick Start
60
+
61
+ Typical flow inside OpenCode:
62
+
63
+ 1. Inspect Claude capabilities with `claude_manager_metadata`.
64
+ 2. Delegate work with `claude_manager_run`.
65
+ 3. Inspect saved Claude history with `claude_manager_sessions` or prior orchestration records with `claude_manager_runs`.
66
+
67
+ Example task:
68
+
69
+ ```text
70
+ Use claude_manager_run to split this implementation into subagents, use worktrees, and summarize the final result.
71
+ ```
72
+
73
+ ## Local Development
74
+
75
+ Clone the repo and run:
76
+
77
+ ```bash
78
+ npm install
79
+ npm run lint
80
+ npm run typecheck
81
+ npm run test
82
+ npm run build
83
+ ```
84
+
85
+ The compiled plugin output is written to `dist/`.
86
+
87
+ ## Publishing
88
+
89
+ This package is configured for the npm scope `@doingdev`.
90
+
91
+ Release flow:
92
+
93
+ ```bash
94
+ npm login
95
+ npm whoami
96
+ npm version patch
97
+ npm run lint
98
+ npm run typecheck
99
+ npm run test
100
+ npm run build
101
+ npm publish --access public
102
+ ```
103
+
104
+ You can also publish through the GitHub Actions workflow after creating a GitHub release and configuring the `NPM_TOKEN` secret.
105
+
106
+ ## Limitations
107
+
108
+ - Claude slash commands and skills come primarily from filesystem discovery; SDK probing is available but optional.
109
+ - Worktree creation only activates when the current directory is a git repo and multiple subtasks are planned.
110
+ - Worktrees are preserved by default so you can inspect changes; clean them up explicitly with `claude_manager_cleanup_run`.
111
+ - Run state is local to the repo under `.claude-manager/` and is ignored by git.
112
+
113
+ ## Scripts
114
+
115
+ - `npm run build`
116
+ - `npm run typecheck`
117
+ - `npm run lint`
118
+ - `npm run format`
119
+ - `npm run test`
@@ -0,0 +1,24 @@
1
+ import { type Options, type Query, type SDKSessionInfo, type SessionMessage, type SettingSource } from '@anthropic-ai/claude-agent-sdk';
2
+ import type { ClaudeCapabilitySnapshot, ClaudeSessionEvent, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, RunClaudeSessionInput } from '../types/contracts.js';
3
+ export type ClaudeSessionEventHandler = (event: ClaudeSessionEvent) => void | Promise<void>;
4
+ export interface ClaudeAgentSdkFacade {
5
+ query(params: {
6
+ prompt: string;
7
+ options?: Options;
8
+ }): Query;
9
+ listSessions(options?: {
10
+ dir?: string;
11
+ }): Promise<SDKSessionInfo[]>;
12
+ getSessionMessages(sessionId: string, options?: {
13
+ dir?: string;
14
+ }): Promise<SessionMessage[]>;
15
+ }
16
+ export declare class ClaudeAgentSdkAdapter {
17
+ private readonly sdkFacade;
18
+ constructor(sdkFacade?: ClaudeAgentSdkFacade);
19
+ runSession(input: RunClaudeSessionInput, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
20
+ listSavedSessions(cwd?: string): Promise<ClaudeSessionSummary[]>;
21
+ getTranscript(sessionId: string, cwd?: string): Promise<ClaudeSessionTranscriptMessage[]>;
22
+ probeCapabilities(cwd: string, settingSources?: SettingSource[]): Promise<ClaudeCapabilitySnapshot>;
23
+ private buildOptions;
24
+ }
@@ -0,0 +1,256 @@
1
+ import { getSessionMessages, listSessions, query, } from '@anthropic-ai/claude-agent-sdk';
2
+ const defaultFacade = {
3
+ query,
4
+ listSessions,
5
+ getSessionMessages,
6
+ };
7
+ export class ClaudeAgentSdkAdapter {
8
+ sdkFacade;
9
+ constructor(sdkFacade = defaultFacade) {
10
+ this.sdkFacade = sdkFacade;
11
+ }
12
+ async runSession(input, onEvent) {
13
+ const sessionQuery = this.sdkFacade.query({
14
+ prompt: input.prompt,
15
+ options: this.buildOptions(input),
16
+ });
17
+ const events = [];
18
+ let finalText = '';
19
+ let sessionId;
20
+ let turns;
21
+ let totalCostUsd;
22
+ try {
23
+ for await (const message of sessionQuery) {
24
+ const event = normalizeSdkMessage(message);
25
+ if (!event) {
26
+ continue;
27
+ }
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);
37
+ }
38
+ }
39
+ }
40
+ finally {
41
+ sessionQuery.close();
42
+ }
43
+ return {
44
+ sessionId,
45
+ events,
46
+ finalText,
47
+ turns,
48
+ totalCostUsd,
49
+ };
50
+ }
51
+ async listSavedSessions(cwd) {
52
+ const sessions = await this.sdkFacade.listSessions(cwd ? { dir: cwd } : undefined);
53
+ return sessions.map((session) => ({
54
+ sessionId: session.sessionId,
55
+ summary: session.summary,
56
+ cwd: session.cwd,
57
+ gitBranch: session.gitBranch,
58
+ createdAt: session.createdAt,
59
+ lastModified: session.lastModified,
60
+ }));
61
+ }
62
+ async getTranscript(sessionId, cwd) {
63
+ const messages = await this.sdkFacade.getSessionMessages(sessionId, cwd ? { dir: cwd } : undefined);
64
+ return messages.map((message) => ({
65
+ role: message.type,
66
+ sessionId: message.session_id,
67
+ messageId: message.uuid,
68
+ text: extractText(message.message),
69
+ }));
70
+ }
71
+ async probeCapabilities(cwd, settingSources = ['project']) {
72
+ const sessionQuery = this.sdkFacade.query({
73
+ prompt: 'Reply with OK.',
74
+ options: {
75
+ cwd,
76
+ maxTurns: 1,
77
+ permissionMode: 'plan',
78
+ persistSession: false,
79
+ tools: [],
80
+ settingSources,
81
+ },
82
+ });
83
+ try {
84
+ const [commands, agents, models] = await Promise.all([
85
+ sessionQuery.supportedCommands(),
86
+ sessionQuery.supportedAgents(),
87
+ sessionQuery.supportedModels(),
88
+ ]);
89
+ return {
90
+ commands: commands.map(mapSlashCommand),
91
+ agents: agents.map(mapAgent),
92
+ models: models.map((model) => model.value),
93
+ };
94
+ }
95
+ finally {
96
+ sessionQuery.close();
97
+ }
98
+ }
99
+ buildOptions(input) {
100
+ const options = {
101
+ cwd: input.cwd,
102
+ tools: { type: 'preset', preset: 'claude_code' },
103
+ allowedTools: input.allowedTools,
104
+ disallowedTools: input.disallowedTools,
105
+ continue: input.continueSession,
106
+ resume: input.resumeSessionId,
107
+ forkSession: input.forkSession,
108
+ persistSession: input.persistSession,
109
+ includePartialMessages: input.includePartialMessages,
110
+ settingSources: input.settingSources,
111
+ maxTurns: input.maxTurns,
112
+ model: input.model,
113
+ permissionMode: input.permissionMode,
114
+ systemPrompt: input.systemPrompt
115
+ ? { type: 'preset', preset: 'claude_code', append: input.systemPrompt }
116
+ : { type: 'preset', preset: 'claude_code' },
117
+ env: {
118
+ ...process.env,
119
+ CLAUDE_AGENT_SDK_CLIENT_APP: 'opencode-claude-manager-plugin/0.1.0',
120
+ },
121
+ };
122
+ if (!input.resumeSessionId) {
123
+ delete options.resume;
124
+ }
125
+ return options;
126
+ }
127
+ }
128
+ function normalizeSdkMessage(message) {
129
+ const sessionId = 'session_id' in message ? message.session_id : undefined;
130
+ if (message.type === 'assistant') {
131
+ return {
132
+ type: 'assistant',
133
+ sessionId,
134
+ text: extractText(message.message),
135
+ rawType: message.type,
136
+ };
137
+ }
138
+ if (message.type === 'stream_event') {
139
+ return {
140
+ type: 'partial',
141
+ sessionId,
142
+ text: extractPartialEventText(message.event),
143
+ rawType: message.type,
144
+ };
145
+ }
146
+ if (message.type === 'result') {
147
+ return {
148
+ type: message.is_error ? 'error' : 'result',
149
+ sessionId,
150
+ text: message.subtype === 'success'
151
+ ? message.result
152
+ : message.errors.join('\n') || message.subtype,
153
+ turns: message.num_turns,
154
+ totalCostUsd: message.total_cost_usd,
155
+ rawType: `${message.type}:${message.subtype}`,
156
+ };
157
+ }
158
+ if (message.type === 'system') {
159
+ return {
160
+ type: message.subtype === 'init' ? 'init' : 'system',
161
+ sessionId,
162
+ text: message.subtype,
163
+ rawType: `${message.type}:${message.subtype}`,
164
+ };
165
+ }
166
+ if (message.type === 'auth_status') {
167
+ return {
168
+ type: 'system',
169
+ sessionId,
170
+ text: message.output.join('\n') || 'auth_status',
171
+ rawType: message.type,
172
+ };
173
+ }
174
+ if (message.type === 'prompt_suggestion') {
175
+ return {
176
+ type: 'system',
177
+ sessionId,
178
+ text: message.suggestion,
179
+ rawType: message.type,
180
+ };
181
+ }
182
+ if (message.type === 'user') {
183
+ return null;
184
+ }
185
+ return {
186
+ type: 'system',
187
+ sessionId,
188
+ text: message.type,
189
+ rawType: message.type,
190
+ };
191
+ }
192
+ function extractPartialEventText(event) {
193
+ if (!event || typeof event !== 'object') {
194
+ return 'stream_event';
195
+ }
196
+ const eventRecord = event;
197
+ const eventType = typeof eventRecord.type === 'string' ? eventRecord.type : 'stream_event';
198
+ const delta = eventRecord.delta;
199
+ if (delta && typeof delta === 'object') {
200
+ const deltaRecord = delta;
201
+ if (typeof deltaRecord.text === 'string') {
202
+ return deltaRecord.text;
203
+ }
204
+ if (typeof deltaRecord.partial_json === 'string') {
205
+ return deltaRecord.partial_json;
206
+ }
207
+ }
208
+ return eventType;
209
+ }
210
+ function extractText(payload) {
211
+ if (typeof payload === 'string') {
212
+ return payload;
213
+ }
214
+ if (!payload || typeof payload !== 'object') {
215
+ return '';
216
+ }
217
+ const payloadRecord = payload;
218
+ if (Array.isArray(payloadRecord.content)) {
219
+ const parts = payloadRecord.content
220
+ .map((contentPart) => {
221
+ if (!contentPart || typeof contentPart !== 'object') {
222
+ return '';
223
+ }
224
+ const partRecord = contentPart;
225
+ if (typeof partRecord.text === 'string') {
226
+ return partRecord.text;
227
+ }
228
+ if (partRecord.type === 'tool_use' &&
229
+ typeof partRecord.name === 'string') {
230
+ return `[tool:${partRecord.name}]`;
231
+ }
232
+ return '';
233
+ })
234
+ .filter(Boolean);
235
+ if (parts.length > 0) {
236
+ return parts.join('\n');
237
+ }
238
+ }
239
+ return JSON.stringify(payload);
240
+ }
241
+ function mapSlashCommand(command) {
242
+ return {
243
+ name: command.name,
244
+ description: command.description,
245
+ argumentHint: command.argumentHint,
246
+ source: 'sdk',
247
+ };
248
+ }
249
+ function mapAgent(agent) {
250
+ return {
251
+ name: agent.name,
252
+ description: agent.description,
253
+ model: agent.model,
254
+ source: 'sdk',
255
+ };
256
+ }
@@ -0,0 +1,15 @@
1
+ import type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, RunClaudeSessionInput } from '../types/contracts.js';
2
+ import type { ClaudeMetadataService } from '../metadata/claude-metadata.service.js';
3
+ import type { ClaudeAgentSdkAdapter, ClaudeSessionEventHandler } from './claude-agent-sdk-adapter.js';
4
+ export declare class ClaudeSessionService {
5
+ private readonly sdkAdapter;
6
+ private readonly metadataService;
7
+ constructor(sdkAdapter: ClaudeAgentSdkAdapter, metadataService: ClaudeMetadataService);
8
+ runTask(input: RunClaudeSessionInput, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
9
+ listSessions(cwd?: string): Promise<ClaudeSessionSummary[]>;
10
+ getTranscript(sessionId: string, cwd?: string): Promise<ClaudeSessionTranscriptMessage[]>;
11
+ inspectRepository(cwd: string, options?: {
12
+ includeSdkProbe?: boolean;
13
+ }): Promise<ClaudeMetadataSnapshot>;
14
+ probeCapabilities(cwd: string): Promise<ClaudeCapabilitySnapshot>;
15
+ }
@@ -0,0 +1,23 @@
1
+ export class ClaudeSessionService {
2
+ sdkAdapter;
3
+ metadataService;
4
+ constructor(sdkAdapter, metadataService) {
5
+ this.sdkAdapter = sdkAdapter;
6
+ this.metadataService = metadataService;
7
+ }
8
+ runTask(input, onEvent) {
9
+ return this.sdkAdapter.runSession(input, onEvent);
10
+ }
11
+ listSessions(cwd) {
12
+ return this.sdkAdapter.listSavedSessions(cwd);
13
+ }
14
+ getTranscript(sessionId, cwd) {
15
+ return this.sdkAdapter.getTranscript(sessionId, cwd);
16
+ }
17
+ inspectRepository(cwd, options) {
18
+ return this.metadataService.collect(cwd, options);
19
+ }
20
+ probeCapabilities(cwd) {
21
+ return this.sdkAdapter.probeCapabilities(cwd);
22
+ }
23
+ }
@@ -0,0 +1,14 @@
1
+ import type { Plugin } from '@opencode-ai/plugin';
2
+ export { ClaudeAgentSdkAdapter } from './claude/claude-agent-sdk-adapter.js';
3
+ export { ClaudeSessionService } from './claude/claude-session.service.js';
4
+ export { ManagerOrchestrator } from './manager/manager-orchestrator.js';
5
+ export { TaskPlanner } from './manager/task-planner.js';
6
+ export { ClaudeMetadataService } from './metadata/claude-metadata.service.js';
7
+ export { RepoClaudeConfigReader } from './metadata/repo-claude-config-reader.js';
8
+ export { getOrCreatePluginServices } from './plugin/service-factory.js';
9
+ export { FileRunStateStore } from './state/file-run-state-store.js';
10
+ export { WorktreeCoordinator } from './worktree/worktree-coordinator.js';
11
+ import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
12
+ export type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerRunRecord, ManagerRunResult, ManagerTaskRequest, ManagerPromptRegistry, RunClaudeSessionInput, } from './types/contracts.js';
13
+ export { ClaudeManagerPlugin };
14
+ export declare const plugin: Plugin;
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export { ClaudeAgentSdkAdapter } from './claude/claude-agent-sdk-adapter.js';
2
+ export { ClaudeSessionService } from './claude/claude-session.service.js';
3
+ export { ManagerOrchestrator } from './manager/manager-orchestrator.js';
4
+ export { TaskPlanner } from './manager/task-planner.js';
5
+ export { ClaudeMetadataService } from './metadata/claude-metadata.service.js';
6
+ export { RepoClaudeConfigReader } from './metadata/repo-claude-config-reader.js';
7
+ export { getOrCreatePluginServices } from './plugin/service-factory.js';
8
+ export { FileRunStateStore } from './state/file-run-state-store.js';
9
+ export { WorktreeCoordinator } from './worktree/worktree-coordinator.js';
10
+ import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
11
+ export { ClaudeManagerPlugin };
12
+ export const plugin = ClaudeManagerPlugin;
@@ -0,0 +1,18 @@
1
+ import type { ClaudeSessionService } from '../claude/claude-session.service.js';
2
+ import type { FileRunStateStore } from '../state/file-run-state-store.js';
3
+ import type { ManagerRunRecord, ManagerRunResult, ManagerTaskRequest } from '../types/contracts.js';
4
+ import type { WorktreeCoordinator } from '../worktree/worktree-coordinator.js';
5
+ import type { TaskPlanner } from './task-planner.js';
6
+ export declare class ManagerOrchestrator {
7
+ private readonly sessionService;
8
+ private readonly stateStore;
9
+ private readonly worktreeCoordinator;
10
+ private readonly taskPlanner;
11
+ constructor(sessionService: ClaudeSessionService, stateStore: FileRunStateStore, worktreeCoordinator: WorktreeCoordinator, taskPlanner: TaskPlanner);
12
+ run(request: ManagerTaskRequest): Promise<ManagerRunResult>;
13
+ listRuns(cwd: string): Promise<ManagerRunRecord[]>;
14
+ getRun(cwd: string, runId: string): Promise<ManagerRunRecord | null>;
15
+ cleanupRunWorktrees(cwd: string, runId: string): Promise<ManagerRunRecord | null>;
16
+ private executePlan;
17
+ private patchSession;
18
+ }
@@ -0,0 +1,186 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { managerPromptRegistry } from '../prompts/registry.js';
3
+ export class ManagerOrchestrator {
4
+ sessionService;
5
+ stateStore;
6
+ worktreeCoordinator;
7
+ taskPlanner;
8
+ constructor(sessionService, stateStore, worktreeCoordinator, taskPlanner) {
9
+ this.sessionService = sessionService;
10
+ this.stateStore = stateStore;
11
+ this.worktreeCoordinator = worktreeCoordinator;
12
+ this.taskPlanner = taskPlanner;
13
+ }
14
+ async run(request) {
15
+ const mode = request.mode ?? 'auto';
16
+ const maxSubagents = Math.max(1, request.maxSubagents ?? 3);
17
+ const useWorktrees = request.useWorktrees ?? maxSubagents > 1;
18
+ const includeProjectSettings = request.includeProjectSettings ?? true;
19
+ const metadata = await this.sessionService.inspectRepository(request.cwd);
20
+ const plans = this.taskPlanner.plan(request.task, mode, maxSubagents);
21
+ const runId = randomUUID();
22
+ const createdAt = new Date().toISOString();
23
+ const assignments = await Promise.all(plans.map((plan) => this.worktreeCoordinator.prepareAssignment({
24
+ cwd: request.cwd,
25
+ runId,
26
+ title: plan.title,
27
+ useWorktree: useWorktrees && plans.length > 1,
28
+ })));
29
+ const plannedAssignments = plans.map((plan, index) => ({
30
+ plan,
31
+ assignment: assignments[index],
32
+ }));
33
+ const runRecord = {
34
+ id: runId,
35
+ cwd: request.cwd,
36
+ task: request.task,
37
+ mode,
38
+ useWorktrees,
39
+ includeProjectSettings,
40
+ status: 'running',
41
+ createdAt,
42
+ updatedAt: createdAt,
43
+ metadata,
44
+ sessions: plannedAssignments.map(({ plan, assignment }) => createManagedSessionRecord(plan, assignment.cwd, assignment)),
45
+ };
46
+ await this.stateStore.saveRun(runRecord);
47
+ const canRunInParallel = plannedAssignments.every(({ assignment }) => assignment.mode === 'git-worktree');
48
+ const settledResults = canRunInParallel
49
+ ? await Promise.allSettled(plannedAssignments.map(({ plan, assignment }) => this.executePlan({
50
+ request,
51
+ runId,
52
+ plan,
53
+ assignment,
54
+ includeProjectSettings,
55
+ })))
56
+ : await runSequentially(plannedAssignments.map(({ plan, assignment }) => () => this.executePlan({
57
+ request,
58
+ runId,
59
+ plan,
60
+ assignment,
61
+ includeProjectSettings,
62
+ })));
63
+ const failedResult = settledResults.find((result) => result.status === 'rejected');
64
+ const finalRun = await this.stateStore.updateRun(request.cwd, runId, (currentRun) => ({
65
+ ...currentRun,
66
+ status: failedResult ? 'failed' : 'completed',
67
+ updatedAt: new Date().toISOString(),
68
+ finalSummary: summarizeRun(currentRun.sessions),
69
+ }));
70
+ return { run: finalRun };
71
+ }
72
+ listRuns(cwd) {
73
+ return this.stateStore.listRuns(cwd);
74
+ }
75
+ getRun(cwd, runId) {
76
+ return this.stateStore.getRun(cwd, runId);
77
+ }
78
+ async cleanupRunWorktrees(cwd, runId) {
79
+ const run = await this.stateStore.getRun(cwd, runId);
80
+ if (!run) {
81
+ return null;
82
+ }
83
+ for (const session of run.sessions) {
84
+ if (session.worktreeMode !== 'git-worktree') {
85
+ continue;
86
+ }
87
+ await this.worktreeCoordinator.cleanupAssignment({
88
+ mode: 'git-worktree',
89
+ cwd: session.cwd,
90
+ rootCwd: run.cwd,
91
+ branchName: session.branchName,
92
+ });
93
+ }
94
+ return run;
95
+ }
96
+ async executePlan(input) {
97
+ const { request, runId, plan, assignment, includeProjectSettings } = input;
98
+ await this.patchSession(request.cwd, runId, plan.id, (session) => ({
99
+ ...session,
100
+ status: 'running',
101
+ }));
102
+ try {
103
+ const sessionResult = await this.sessionService.runTask({
104
+ cwd: assignment.cwd,
105
+ prompt: buildWorkerPrompt(plan.prompt, request.task),
106
+ systemPrompt: managerPromptRegistry.subagentSystemPrompt,
107
+ model: request.model,
108
+ includePartialMessages: true,
109
+ settingSources: includeProjectSettings ? ['project', 'local'] : [],
110
+ }, async (event) => {
111
+ await this.patchSession(request.cwd, runId, plan.id, (session) => ({
112
+ ...session,
113
+ claudeSessionId: event.sessionId ?? session.claudeSessionId,
114
+ events: [...session.events, compactEvent(event)],
115
+ }));
116
+ });
117
+ await this.patchSession(request.cwd, runId, plan.id, (session) => ({
118
+ ...session,
119
+ status: 'completed',
120
+ claudeSessionId: sessionResult.sessionId,
121
+ finalText: sessionResult.finalText,
122
+ turns: sessionResult.turns,
123
+ totalCostUsd: sessionResult.totalCostUsd,
124
+ }));
125
+ }
126
+ catch (error) {
127
+ await this.patchSession(request.cwd, runId, plan.id, (session) => ({
128
+ ...session,
129
+ status: 'failed',
130
+ error: error instanceof Error ? error.message : String(error),
131
+ }));
132
+ throw error;
133
+ }
134
+ }
135
+ async patchSession(cwd, runId, sessionId, update) {
136
+ await this.stateStore.updateRun(cwd, runId, (run) => ({
137
+ ...run,
138
+ updatedAt: new Date().toISOString(),
139
+ sessions: run.sessions.map((session) => session.id === sessionId ? update(session) : session),
140
+ }));
141
+ }
142
+ }
143
+ function createManagedSessionRecord(plan, cwd, assignment) {
144
+ return {
145
+ id: plan.id,
146
+ title: plan.title,
147
+ prompt: plan.prompt,
148
+ status: 'pending',
149
+ cwd,
150
+ worktreeMode: assignment.mode,
151
+ branchName: assignment.branchName,
152
+ events: [],
153
+ };
154
+ }
155
+ function buildWorkerPrompt(subtaskPrompt, parentTask) {
156
+ return [
157
+ 'You are executing a delegated Claude Code subtask.',
158
+ `Parent task: ${parentTask}`,
159
+ `Assigned subtask: ${subtaskPrompt}`,
160
+ 'Stay within scope, finish the requested work, and end with a concise verification summary.',
161
+ ].join('\n\n');
162
+ }
163
+ function summarizeRun(sessions) {
164
+ return sessions
165
+ .map((session) => `${session.title}: ${session.finalText ?? session.error ?? session.status}`)
166
+ .join('\n');
167
+ }
168
+ function compactEvent(event) {
169
+ return {
170
+ ...event,
171
+ text: event.text.slice(0, 4000),
172
+ };
173
+ }
174
+ async function runSequentially(tasks) {
175
+ const results = [];
176
+ for (const task of tasks) {
177
+ try {
178
+ await task();
179
+ results.push({ status: 'fulfilled', value: undefined });
180
+ }
181
+ catch (error) {
182
+ results.push({ status: 'rejected', reason: error });
183
+ }
184
+ }
185
+ return results;
186
+ }
@@ -0,0 +1,5 @@
1
+ import type { ManagedSubtaskPlan, ManagerRunMode } from '../types/contracts.js';
2
+ export declare class TaskPlanner {
3
+ plan(task: string, mode: ManagerRunMode, maxSubagents: number): ManagedSubtaskPlan[];
4
+ private createPlan;
5
+ }