@doingdev/opencode-claude-manager-plugin 0.1.22 → 0.1.26

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.
Files changed (43) hide show
  1. package/README.md +129 -40
  2. package/dist/claude/claude-agent-sdk-adapter.d.ts +27 -0
  3. package/dist/claude/claude-agent-sdk-adapter.js +520 -0
  4. package/dist/claude/claude-session.service.d.ts +15 -0
  5. package/dist/claude/claude-session.service.js +23 -0
  6. package/dist/claude/session-live-tailer.d.ts +51 -0
  7. package/dist/claude/session-live-tailer.js +269 -0
  8. package/dist/claude/tool-approval-manager.d.ts +27 -0
  9. package/dist/claude/tool-approval-manager.js +238 -0
  10. package/dist/index.d.ts +4 -5
  11. package/dist/index.js +4 -5
  12. package/dist/manager/context-tracker.d.ts +33 -0
  13. package/dist/manager/context-tracker.js +108 -0
  14. package/dist/manager/git-operations.d.ts +12 -0
  15. package/dist/manager/git-operations.js +76 -0
  16. package/dist/manager/persistent-manager.d.ts +74 -0
  17. package/dist/manager/persistent-manager.js +167 -0
  18. package/dist/manager/session-controller.d.ts +45 -0
  19. package/dist/manager/session-controller.js +147 -0
  20. package/dist/metadata/claude-metadata.service.d.ts +12 -0
  21. package/dist/metadata/claude-metadata.service.js +38 -0
  22. package/dist/metadata/repo-claude-config-reader.d.ts +7 -0
  23. package/dist/metadata/repo-claude-config-reader.js +154 -0
  24. package/dist/plugin/agent-hierarchy.d.ts +47 -0
  25. package/dist/plugin/agent-hierarchy.js +110 -0
  26. package/dist/plugin/claude-manager.plugin.d.ts +2 -0
  27. package/dist/plugin/claude-manager.plugin.js +490 -0
  28. package/dist/plugin/orchestrator.plugin.js +4 -2
  29. package/dist/plugin/service-factory.d.ts +12 -0
  30. package/dist/plugin/service-factory.js +41 -0
  31. package/dist/prompts/registry.d.ts +2 -8
  32. package/dist/prompts/registry.js +203 -29
  33. package/dist/state/file-run-state-store.d.ts +14 -0
  34. package/dist/state/file-run-state-store.js +87 -0
  35. package/dist/state/transcript-store.d.ts +15 -0
  36. package/dist/state/transcript-store.js +44 -0
  37. package/dist/types/contracts.d.ts +216 -0
  38. package/dist/types/contracts.js +1 -0
  39. package/dist/util/fs-helpers.d.ts +2 -0
  40. package/dist/util/fs-helpers.js +12 -0
  41. package/dist/util/transcript-append.d.ts +7 -0
  42. package/dist/util/transcript-append.js +29 -0
  43. package/package.json +5 -3
@@ -0,0 +1,490 @@
1
+ import { tool } from '@opencode-ai/plugin';
2
+ import { managerPromptRegistry } from '../prompts/registry.js';
3
+ import { AGENT_CTO, AGENT_MANAGER, buildCtoAgentConfig, buildManagerAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
4
+ import { getOrCreatePluginServices } from './service-factory.js';
5
+ export const ClaudeManagerPlugin = async ({ worktree }) => {
6
+ const services = getOrCreatePluginServices(worktree);
7
+ return {
8
+ config: async (config) => {
9
+ config.agent ??= {};
10
+ config.permission ??= {};
11
+ denyRestrictedToolsGlobally(config.permission);
12
+ config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
13
+ config.agent[AGENT_MANAGER] ??= buildManagerAgentConfig(managerPromptRegistry);
14
+ },
15
+ tool: {
16
+ engineer_send: tool({
17
+ description: 'Send a message to the persistent Claude Code session. ' +
18
+ 'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
19
+ 'Returns the assistant response and current context health snapshot. ' +
20
+ 'Use mode "plan" for read-only investigation and planning (no edits), ' +
21
+ 'or "free" (default) for normal execution with edit permissions. ' +
22
+ 'Set freshSession to clear the active session before sending (use for unrelated tasks). ' +
23
+ 'Prefer claude-opus-4-6 (default) for most coding work; use a Sonnet model for faster/lighter tasks. ' +
24
+ 'Prefer effort "high" (default) for most work; use "medium" for lighter tasks and "max" for especially hard problems.',
25
+ args: {
26
+ message: tool.schema.string().min(1),
27
+ model: tool.schema
28
+ .enum(['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5'])
29
+ .optional(),
30
+ effort: tool.schema
31
+ .enum(['low', 'medium', 'high', 'max'])
32
+ .default('high'),
33
+ mode: tool.schema.enum(['plan', 'free']).default('free'),
34
+ freshSession: tool.schema.boolean().default(false),
35
+ cwd: tool.schema.string().optional(),
36
+ },
37
+ async execute(args, context) {
38
+ const cwd = args.cwd ?? context.worktree;
39
+ if (args.freshSession) {
40
+ await services.manager.clearSession(cwd);
41
+ }
42
+ const hasActiveSession = services.manager.getStatus().sessionId !== null;
43
+ const promptPreview = args.message.length > 100
44
+ ? args.message.slice(0, 100) + '...'
45
+ : args.message;
46
+ context.metadata({
47
+ title: hasActiveSession
48
+ ? 'Claude Code: Resuming session...'
49
+ : 'Claude Code: Initializing...',
50
+ metadata: {
51
+ sessionId: services.manager.getStatus().sessionId,
52
+ prompt: promptPreview,
53
+ },
54
+ });
55
+ let turnsSoFar = 0;
56
+ let costSoFar = 0;
57
+ const result = await services.manager.sendMessage(cwd, args.message, {
58
+ model: args.model,
59
+ effort: args.effort,
60
+ mode: args.mode,
61
+ abortSignal: context.abort,
62
+ }, (event) => {
63
+ if (event.turns !== undefined) {
64
+ turnsSoFar = event.turns;
65
+ }
66
+ if (event.totalCostUsd !== undefined) {
67
+ costSoFar = event.totalCostUsd;
68
+ }
69
+ const costLabel = `$${costSoFar.toFixed(4)}`;
70
+ if (event.type === 'tool_call') {
71
+ let toolName = 'tool';
72
+ let inputPreview = '';
73
+ try {
74
+ const parsed = JSON.parse(event.text);
75
+ toolName = parsed.name ?? 'tool';
76
+ if (parsed.input) {
77
+ const inputStr = typeof parsed.input === 'string'
78
+ ? parsed.input
79
+ : JSON.stringify(parsed.input);
80
+ inputPreview =
81
+ inputStr.length > 150
82
+ ? inputStr.slice(0, 150) + '...'
83
+ : inputStr;
84
+ }
85
+ }
86
+ catch {
87
+ // ignore parse errors
88
+ }
89
+ context.metadata({
90
+ title: `Claude Code: Running ${toolName}... (${turnsSoFar} turns, ${costLabel})`,
91
+ metadata: {
92
+ sessionId: event.sessionId,
93
+ type: event.type,
94
+ tool: toolName,
95
+ input: inputPreview,
96
+ },
97
+ });
98
+ }
99
+ else if (event.type === 'assistant') {
100
+ const thinkingPreview = event.text.length > 150
101
+ ? event.text.slice(0, 150) + '...'
102
+ : event.text;
103
+ context.metadata({
104
+ title: `Claude Code: Thinking... (${turnsSoFar} turns, ${costLabel})`,
105
+ metadata: {
106
+ sessionId: event.sessionId,
107
+ type: event.type,
108
+ thinking: thinkingPreview,
109
+ },
110
+ });
111
+ }
112
+ else if (event.type === 'init') {
113
+ context.metadata({
114
+ title: `Claude Code: Session started`,
115
+ metadata: {
116
+ sessionId: event.sessionId,
117
+ prompt: promptPreview,
118
+ },
119
+ });
120
+ }
121
+ else if (event.type === 'user') {
122
+ const preview = event.text.length > 200
123
+ ? event.text.slice(0, 200) + '...'
124
+ : event.text;
125
+ context.metadata({
126
+ title: `Claude Code: Tool result (${turnsSoFar} turns, ${costLabel})`,
127
+ metadata: {
128
+ sessionId: event.sessionId,
129
+ type: event.type,
130
+ output: preview,
131
+ },
132
+ });
133
+ }
134
+ else if (event.type === 'tool_progress') {
135
+ let toolName = 'tool';
136
+ let elapsed = 0;
137
+ try {
138
+ const parsed = JSON.parse(event.text);
139
+ toolName = parsed.name ?? 'tool';
140
+ elapsed = parsed.elapsed ?? 0;
141
+ }
142
+ catch {
143
+ // ignore
144
+ }
145
+ context.metadata({
146
+ title: `Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}... (${turnsSoFar} turns, ${costLabel})`,
147
+ metadata: {
148
+ sessionId: event.sessionId,
149
+ type: event.type,
150
+ tool: toolName,
151
+ elapsed,
152
+ },
153
+ });
154
+ }
155
+ else if (event.type === 'tool_summary') {
156
+ const summary = event.text.length > 200
157
+ ? event.text.slice(0, 200) + '...'
158
+ : event.text;
159
+ context.metadata({
160
+ title: `Claude Code: Tool done (${turnsSoFar} turns, ${costLabel})`,
161
+ metadata: {
162
+ sessionId: event.sessionId,
163
+ type: event.type,
164
+ summary,
165
+ },
166
+ });
167
+ }
168
+ else if (event.type === 'partial') {
169
+ const delta = event.text.length > 200
170
+ ? event.text.slice(0, 200) + '...'
171
+ : event.text;
172
+ context.metadata({
173
+ title: `Claude Code: Writing... (${turnsSoFar} turns, ${costLabel})`,
174
+ metadata: {
175
+ sessionId: event.sessionId,
176
+ type: event.type,
177
+ delta,
178
+ },
179
+ });
180
+ }
181
+ else if (event.type === 'error') {
182
+ context.metadata({
183
+ title: `Claude Code: Error`,
184
+ metadata: {
185
+ sessionId: event.sessionId,
186
+ error: event.text.slice(0, 200),
187
+ },
188
+ });
189
+ }
190
+ });
191
+ const costLabel = `$${(result.totalCostUsd ?? 0).toFixed(4)}`;
192
+ const turns = result.turns ?? 0;
193
+ const contextWarning = formatContextWarning(result.context);
194
+ if (contextWarning) {
195
+ context.metadata({
196
+ title: `Claude Code: Context at ${result.context.estimatedContextPercent}% (${turns} turns)`,
197
+ metadata: { sessionId: result.sessionId, contextWarning },
198
+ });
199
+ }
200
+ else {
201
+ context.metadata({
202
+ title: `Claude Code: Complete (${turns} turns, ${costLabel})`,
203
+ metadata: { sessionId: result.sessionId },
204
+ });
205
+ }
206
+ // Fetch recent tool output from the JSONL file for richer feedback.
207
+ let toolOutputs = [];
208
+ if (result.sessionId) {
209
+ try {
210
+ toolOutputs = await services.liveTailer.getToolOutputPreview(result.sessionId, cwd, 3);
211
+ }
212
+ catch {
213
+ // Non-critical — the JSONL file may not exist yet.
214
+ }
215
+ }
216
+ return JSON.stringify({
217
+ sessionId: result.sessionId,
218
+ finalText: result.finalText,
219
+ turns: result.turns,
220
+ totalCostUsd: result.totalCostUsd,
221
+ context: result.context,
222
+ contextWarning,
223
+ toolOutputs: toolOutputs.length > 0 ? toolOutputs : undefined,
224
+ }, null, 2);
225
+ },
226
+ }),
227
+ engineer_compact: tool({
228
+ description: 'Compact the active Claude Code session to reclaim context space. ' +
229
+ 'Sends /compact to the session, which compresses prior conversation while preserving state. ' +
230
+ 'Use before clearing when context is high but the session still has useful state. ' +
231
+ 'Fails if there is no active session.',
232
+ args: {
233
+ cwd: tool.schema.string().optional(),
234
+ },
235
+ async execute(args, context) {
236
+ const cwd = args.cwd ?? context.worktree;
237
+ annotateToolRun(context, 'Compacting session', {});
238
+ const result = await services.manager.compactSession(cwd);
239
+ const snap = services.manager.getStatus();
240
+ const contextWarning = formatContextWarning(snap);
241
+ context.metadata({
242
+ title: contextWarning
243
+ ? `Claude Code: Compacted — context at ${snap.estimatedContextPercent}%`
244
+ : `Claude Code: Compacted (${snap.totalTurns} turns, $${(snap.totalCostUsd ?? 0).toFixed(4)})`,
245
+ metadata: { sessionId: result.sessionId },
246
+ });
247
+ return JSON.stringify({
248
+ sessionId: result.sessionId,
249
+ finalText: result.finalText,
250
+ turns: result.turns,
251
+ totalCostUsd: result.totalCostUsd,
252
+ context: snap,
253
+ contextWarning,
254
+ }, null, 2);
255
+ },
256
+ }),
257
+ git_diff: tool({
258
+ description: 'Run git diff to see all current changes (staged + unstaged) relative to HEAD.',
259
+ args: {
260
+ cwd: tool.schema.string().optional(),
261
+ },
262
+ async execute(_args, context) {
263
+ annotateToolRun(context, 'Running git diff', {});
264
+ const result = await services.manager.gitDiff();
265
+ return JSON.stringify(result, null, 2);
266
+ },
267
+ }),
268
+ git_commit: tool({
269
+ description: 'Stage all changes and commit with the given message.',
270
+ args: {
271
+ message: tool.schema.string().min(1),
272
+ cwd: tool.schema.string().optional(),
273
+ },
274
+ async execute(args, context) {
275
+ annotateToolRun(context, 'Committing changes', {
276
+ message: args.message,
277
+ });
278
+ const result = await services.manager.gitCommit(args.message);
279
+ return JSON.stringify(result, null, 2);
280
+ },
281
+ }),
282
+ git_reset: tool({
283
+ description: 'Run git reset --hard HEAD and git clean -fd to discard ALL uncommitted changes and untracked files.',
284
+ args: {
285
+ cwd: tool.schema.string().optional(),
286
+ },
287
+ async execute(_args, context) {
288
+ annotateToolRun(context, 'Resetting working directory', {});
289
+ const result = await services.manager.gitReset();
290
+ return JSON.stringify(result, null, 2);
291
+ },
292
+ }),
293
+ engineer_clear: tool({
294
+ description: 'Clear the active Claude Code session. The next send will start a fresh session. ' +
295
+ 'Use when context is full, the session is confused, or starting a different task.',
296
+ args: {
297
+ cwd: tool.schema.string().optional(),
298
+ reason: tool.schema.string().optional(),
299
+ },
300
+ async execute(args, context) {
301
+ annotateToolRun(context, 'Clearing session', {
302
+ reason: args.reason,
303
+ });
304
+ const clearedId = await services.manager.clearSession(args.cwd ?? context.worktree);
305
+ return JSON.stringify({ clearedSessionId: clearedId });
306
+ },
307
+ }),
308
+ engineer_status: tool({
309
+ description: 'Get the current persistent session status: context usage %, turns, cost, active session ID.',
310
+ args: {
311
+ cwd: tool.schema.string().optional(),
312
+ },
313
+ async execute(_args, context) {
314
+ annotateToolRun(context, 'Checking session status', {});
315
+ const status = services.manager.getStatus();
316
+ return JSON.stringify({
317
+ ...status,
318
+ transcriptFile: status.sessionId
319
+ ? `.claude-manager/transcripts/${status.sessionId}.json`
320
+ : null,
321
+ contextWarning: formatContextWarning(status),
322
+ }, null, 2);
323
+ },
324
+ }),
325
+ engineer_metadata: tool({
326
+ description: 'Inspect Claude slash commands, skills, hooks, and repo settings.',
327
+ args: {
328
+ cwd: tool.schema.string().optional(),
329
+ includeSdkProbe: tool.schema.boolean().default(false),
330
+ },
331
+ async execute(args, context) {
332
+ annotateToolRun(context, 'Collecting Claude metadata', {
333
+ includeSdkProbe: args.includeSdkProbe,
334
+ });
335
+ const metadata = await services.sessions.inspectRepository(args.cwd ?? context.worktree, {
336
+ includeSdkProbe: args.includeSdkProbe,
337
+ });
338
+ return JSON.stringify(metadata, null, 2);
339
+ },
340
+ }),
341
+ engineer_sessions: tool({
342
+ description: 'List Claude sessions or inspect a saved transcript. ' +
343
+ 'When sessionId is provided, returns both SDK transcript and local events.',
344
+ args: {
345
+ cwd: tool.schema.string().optional(),
346
+ sessionId: tool.schema.string().optional(),
347
+ },
348
+ async execute(args, context) {
349
+ annotateToolRun(context, 'Inspecting Claude session history', {});
350
+ const cwd = args.cwd ?? context.worktree;
351
+ if (args.sessionId) {
352
+ const [sdkTranscript, localEvents] = await Promise.all([
353
+ services.sessions.getTranscript(args.sessionId, cwd),
354
+ services.manager.getTranscriptEvents(cwd, args.sessionId),
355
+ ]);
356
+ return JSON.stringify({
357
+ sdkTranscript,
358
+ localEvents: localEvents.length > 0 ? localEvents : undefined,
359
+ }, null, 2);
360
+ }
361
+ const sessions = await services.sessions.listSessions(cwd);
362
+ return JSON.stringify(sessions, null, 2);
363
+ },
364
+ }),
365
+ engineer_runs: tool({
366
+ description: 'List persistent manager run records.',
367
+ args: {
368
+ cwd: tool.schema.string().optional(),
369
+ runId: tool.schema.string().optional(),
370
+ },
371
+ async execute(args, context) {
372
+ annotateToolRun(context, 'Reading manager run state', {});
373
+ if (args.runId) {
374
+ const run = await services.manager.getRun(args.cwd ?? context.worktree, args.runId);
375
+ return JSON.stringify(run, null, 2);
376
+ }
377
+ const runs = await services.manager.listRuns(args.cwd ?? context.worktree);
378
+ return JSON.stringify(runs, null, 2);
379
+ },
380
+ }),
381
+ approval_policy: tool({
382
+ description: 'View the current tool approval policy: rules, default action, and enabled status.',
383
+ args: {},
384
+ async execute(_args, context) {
385
+ annotateToolRun(context, 'Reading approval policy', {});
386
+ return JSON.stringify(services.approvalManager.getPolicy(), null, 2);
387
+ },
388
+ }),
389
+ approval_decisions: tool({
390
+ description: 'View recent tool approval decisions. Shows what tools were allowed or denied. ' +
391
+ 'Use deniedOnly to see only denied calls.',
392
+ args: {
393
+ limit: tool.schema.number().optional(),
394
+ deniedOnly: tool.schema.boolean().optional(),
395
+ },
396
+ async execute(args, context) {
397
+ annotateToolRun(context, 'Reading approval decisions', {});
398
+ const decisions = args.deniedOnly
399
+ ? services.approvalManager.getDeniedDecisions(args.limit)
400
+ : services.approvalManager.getDecisions(args.limit);
401
+ return JSON.stringify({ total: decisions.length, decisions }, null, 2);
402
+ },
403
+ }),
404
+ approval_update: tool({
405
+ description: 'Update the tool approval policy. Add/remove rules, change default action, or enable/disable. ' +
406
+ 'Rules are evaluated top-to-bottom; first match wins.',
407
+ args: {
408
+ action: tool.schema.enum([
409
+ 'addRule',
410
+ 'removeRule',
411
+ 'setDefault',
412
+ 'setEnabled',
413
+ 'clearDecisions',
414
+ ]),
415
+ ruleId: tool.schema.string().optional(),
416
+ toolPattern: tool.schema.string().optional(),
417
+ inputPattern: tool.schema.string().optional(),
418
+ ruleAction: tool.schema.enum(['allow', 'deny']).optional(),
419
+ denyMessage: tool.schema.string().optional(),
420
+ description: tool.schema.string().optional(),
421
+ position: tool.schema.number().optional(),
422
+ defaultAction: tool.schema.enum(['allow', 'deny']).optional(),
423
+ enabled: tool.schema.boolean().optional(),
424
+ },
425
+ async execute(args, context) {
426
+ annotateToolRun(context, `Updating approval: ${args.action}`, {});
427
+ if (args.action === 'addRule') {
428
+ if (!args.ruleId || !args.toolPattern || !args.ruleAction) {
429
+ return JSON.stringify({
430
+ error: 'addRule requires ruleId, toolPattern, and ruleAction',
431
+ });
432
+ }
433
+ services.approvalManager.addRule({
434
+ id: args.ruleId,
435
+ toolPattern: args.toolPattern,
436
+ inputPattern: args.inputPattern,
437
+ action: args.ruleAction,
438
+ denyMessage: args.denyMessage,
439
+ description: args.description,
440
+ }, args.position);
441
+ }
442
+ else if (args.action === 'removeRule') {
443
+ if (!args.ruleId) {
444
+ return JSON.stringify({ error: 'removeRule requires ruleId' });
445
+ }
446
+ const removed = services.approvalManager.removeRule(args.ruleId);
447
+ return JSON.stringify({ removed });
448
+ }
449
+ else if (args.action === 'setDefault') {
450
+ if (!args.defaultAction) {
451
+ return JSON.stringify({
452
+ error: 'setDefault requires defaultAction',
453
+ });
454
+ }
455
+ services.approvalManager.setDefaultAction(args.defaultAction);
456
+ }
457
+ else if (args.action === 'setEnabled') {
458
+ if (args.enabled === undefined) {
459
+ return JSON.stringify({ error: 'setEnabled requires enabled' });
460
+ }
461
+ services.approvalManager.setEnabled(args.enabled);
462
+ }
463
+ else if (args.action === 'clearDecisions') {
464
+ services.approvalManager.clearDecisions();
465
+ }
466
+ return JSON.stringify(services.approvalManager.getPolicy(), null, 2);
467
+ },
468
+ }),
469
+ },
470
+ };
471
+ };
472
+ function annotateToolRun(context, title, metadata) {
473
+ context.metadata({ title, metadata });
474
+ }
475
+ function formatContextWarning(context) {
476
+ const { warningLevel, estimatedContextPercent, totalTurns, totalCostUsd } = context;
477
+ if (warningLevel === 'ok' || estimatedContextPercent === null) {
478
+ return null;
479
+ }
480
+ const templates = managerPromptRegistry.contextWarnings;
481
+ const template = warningLevel === 'critical'
482
+ ? templates.critical
483
+ : warningLevel === 'high'
484
+ ? templates.high
485
+ : templates.moderate;
486
+ return template
487
+ .replace('{percent}', String(estimatedContextPercent))
488
+ .replace('{turns}', String(totalTurns))
489
+ .replace('{cost}', totalCostUsd.toFixed(2));
490
+ }
@@ -60,9 +60,11 @@ export const OrchestratorPlugin = async () => {
60
60
  },
61
61
  };
62
62
  // ── Planning subagents ────────────────────────────────────────────
63
+ // Claude Code tools (Bash, Read, Write, Edit, …) are executed internally
64
+ // by the claude CLI subprocess and streamed back with providerExecuted:true.
65
+ // OpenCode's own tools must not be advertised to these agents.
63
66
  const claudeCodePermissions = {
64
- '*': 'allow',
65
- bash: 'allow',
67
+ '*': 'deny',
66
68
  };
67
69
  config.agent['claude-code-planning-opus'] ??= {
68
70
  description: 'Claude Code Opus specialist for investigation, architecture, and planning.',
@@ -0,0 +1,12 @@
1
+ import { ClaudeSessionService } from '../claude/claude-session.service.js';
2
+ import { SessionLiveTailer } from '../claude/session-live-tailer.js';
3
+ import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
4
+ import { PersistentManager } from '../manager/persistent-manager.js';
5
+ interface ClaudeManagerPluginServices {
6
+ manager: PersistentManager;
7
+ sessions: ClaudeSessionService;
8
+ approvalManager: ToolApprovalManager;
9
+ liveTailer: SessionLiveTailer;
10
+ }
11
+ export declare function getOrCreatePluginServices(worktree: string): ClaudeManagerPluginServices;
12
+ export {};
@@ -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.engineerSessionPrompt, 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
- * Agent prompt registry for the orchestrator + Claude Code subagent architecture.
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;