@doingdev/opencode-claude-manager-plugin 0.1.35 → 0.1.43
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/dist/claude/claude-agent-sdk-adapter.js +1 -0
- package/dist/manager/git-operations.d.ts +10 -1
- package/dist/manager/git-operations.js +18 -3
- package/dist/manager/persistent-manager.d.ts +19 -3
- package/dist/manager/persistent-manager.js +21 -9
- package/dist/manager/session-controller.d.ts +8 -5
- package/dist/manager/session-controller.js +25 -20
- 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/agent-hierarchy.d.ts +9 -9
- package/dist/plugin/agent-hierarchy.js +25 -25
- package/dist/plugin/claude-manager.plugin.js +83 -46
- package/dist/plugin/orchestrator.plugin.d.ts +2 -0
- package/dist/plugin/orchestrator.plugin.js +116 -0
- package/dist/plugin/service-factory.js +3 -8
- package/dist/prompts/registry.js +100 -103
- package/dist/providers/claude-code-wrapper.d.ts +13 -0
- package/dist/providers/claude-code-wrapper.js +13 -0
- package/dist/safety/bash-safety.d.ts +21 -0
- package/dist/safety/bash-safety.js +62 -0
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +27 -0
- package/dist/src/claude/claude-agent-sdk-adapter.js +517 -0
- package/dist/src/claude/claude-session.service.d.ts +10 -0
- package/dist/src/claude/claude-session.service.js +18 -0
- package/dist/src/claude/session-live-tailer.d.ts +51 -0
- package/dist/src/claude/session-live-tailer.js +269 -0
- package/dist/src/claude/tool-approval-manager.d.ts +27 -0
- package/dist/src/claude/tool-approval-manager.js +232 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -0
- package/dist/src/manager/context-tracker.d.ts +33 -0
- package/dist/src/manager/context-tracker.js +106 -0
- package/dist/src/manager/git-operations.d.ts +12 -0
- package/dist/src/manager/git-operations.js +76 -0
- package/dist/src/manager/persistent-manager.d.ts +77 -0
- package/dist/src/manager/persistent-manager.js +170 -0
- package/dist/src/manager/session-controller.d.ts +44 -0
- package/dist/src/manager/session-controller.js +147 -0
- package/dist/src/plugin/agent-hierarchy.d.ts +60 -0
- package/dist/src/plugin/agent-hierarchy.js +157 -0
- package/dist/src/plugin/claude-manager.plugin.d.ts +2 -0
- package/dist/src/plugin/claude-manager.plugin.js +563 -0
- package/dist/src/plugin/service-factory.d.ts +12 -0
- package/dist/src/plugin/service-factory.js +38 -0
- package/dist/src/prompts/registry.d.ts +11 -0
- package/dist/src/prompts/registry.js +260 -0
- package/dist/src/state/file-run-state-store.d.ts +14 -0
- package/dist/src/state/file-run-state-store.js +85 -0
- package/dist/src/state/transcript-store.d.ts +15 -0
- package/dist/src/state/transcript-store.js +44 -0
- package/dist/src/types/contracts.d.ts +200 -0
- package/dist/src/types/contracts.js +1 -0
- package/dist/src/util/fs-helpers.d.ts +2 -0
- package/dist/src/util/fs-helpers.js +10 -0
- package/dist/src/util/project-context.d.ts +10 -0
- package/dist/src/util/project-context.js +105 -0
- package/dist/src/util/transcript-append.d.ts +7 -0
- package/dist/src/util/transcript-append.js +29 -0
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +1 -0
- package/dist/test/claude-agent-sdk-adapter.test.js +459 -0
- package/dist/test/claude-manager.plugin.test.d.ts +1 -0
- package/dist/test/claude-manager.plugin.test.js +331 -0
- package/dist/test/context-tracker.test.d.ts +1 -0
- package/dist/test/context-tracker.test.js +138 -0
- package/dist/test/file-run-state-store.test.d.ts +1 -0
- package/dist/test/file-run-state-store.test.js +82 -0
- package/dist/test/git-operations.test.d.ts +1 -0
- package/dist/test/git-operations.test.js +90 -0
- package/dist/test/persistent-manager.test.d.ts +1 -0
- package/dist/test/persistent-manager.test.js +208 -0
- package/dist/test/project-context.test.d.ts +1 -0
- package/dist/test/project-context.test.js +92 -0
- package/dist/test/prompt-registry.test.d.ts +1 -0
- package/dist/test/prompt-registry.test.js +256 -0
- package/dist/test/session-controller.test.d.ts +1 -0
- package/dist/test/session-controller.test.js +149 -0
- package/dist/test/session-live-tailer.test.d.ts +1 -0
- package/dist/test/session-live-tailer.test.js +313 -0
- package/dist/test/tool-approval-manager.test.d.ts +1 -0
- package/dist/test/tool-approval-manager.test.js +264 -0
- package/dist/test/transcript-append.test.d.ts +1 -0
- package/dist/test/transcript-append.test.js +37 -0
- package/dist/test/transcript-store.test.d.ts +1 -0
- package/dist/test/transcript-store.test.js +50 -0
- package/dist/types/contracts.d.ts +3 -4
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +11 -0
- package/package.json +2 -2
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent hierarchy configuration for the CTO + Engineer Wrapper architecture.
|
|
3
3
|
*
|
|
4
|
-
* CTO (cto)
|
|
5
|
-
* Engineer
|
|
6
|
-
* Engineer
|
|
7
|
-
* Claude Code session
|
|
4
|
+
* CTO (cto) — pure orchestrator, spawns engineers, reviews diffs, commits
|
|
5
|
+
* Engineer Explore (engineer_explore) — manages a Claude Code session for read-only investigation
|
|
6
|
+
* Engineer Implement (engineer_implement) — manages a Claude Code session for implementation
|
|
7
|
+
* Claude Code session — the underlying AI session (prompt only, no OpenCode agent)
|
|
8
8
|
*/
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Agent names
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
export const AGENT_CTO = 'cto';
|
|
13
|
-
export const
|
|
14
|
-
export const
|
|
13
|
+
export const AGENT_ENGINEER_EXPLORE = 'engineer_explore';
|
|
14
|
+
export const AGENT_ENGINEER_IMPLEMENT = 'engineer_implement';
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Tool IDs — grouped by domain
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
@@ -25,12 +25,12 @@ const ENGINEER_SHARED_TOOL_IDS = [
|
|
|
25
25
|
];
|
|
26
26
|
/** All engineer tools — mode-locked sends + shared session tools */
|
|
27
27
|
const ENGINEER_TOOL_IDS = ['explore', 'implement', ...ENGINEER_SHARED_TOOL_IDS];
|
|
28
|
-
/** Tools for the
|
|
29
|
-
const
|
|
30
|
-
/** Tools for the
|
|
31
|
-
const
|
|
28
|
+
/** Tools for the engineer_explore wrapper (explore-mode send + shared) */
|
|
29
|
+
const ENGINEER_EXPLORE_TOOL_IDS = ['explore', ...ENGINEER_SHARED_TOOL_IDS];
|
|
30
|
+
/** Tools for the engineer_implement wrapper (implement-mode send + shared) */
|
|
31
|
+
const ENGINEER_IMPLEMENT_TOOL_IDS = ['implement', ...ENGINEER_SHARED_TOOL_IDS];
|
|
32
32
|
/** Git tools — owned by CTO */
|
|
33
|
-
const GIT_TOOL_IDS = ['git_diff', 'git_commit', 'git_reset'];
|
|
33
|
+
const GIT_TOOL_IDS = ['git_diff', 'git_commit', 'git_reset', 'git_status', 'git_log'];
|
|
34
34
|
/** Approval tools — owned by CTO */
|
|
35
35
|
const APPROVAL_TOOL_IDS = ['approval_policy', 'approval_decisions', 'approval_update'];
|
|
36
36
|
/** All restricted tool IDs (union of all domain groups) */
|
|
@@ -77,19 +77,19 @@ function buildCtoPermissions() {
|
|
|
77
77
|
...allowed,
|
|
78
78
|
task: {
|
|
79
79
|
'*': 'deny',
|
|
80
|
-
[
|
|
81
|
-
[
|
|
80
|
+
[AGENT_ENGINEER_EXPLORE]: 'allow',
|
|
81
|
+
[AGENT_ENGINEER_IMPLEMENT]: 'allow',
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
/** Engineer
|
|
86
|
-
function
|
|
85
|
+
/** Engineer explore wrapper: read-only investigation + explore + shared session tools. */
|
|
86
|
+
function buildEngineerExplorePermissions() {
|
|
87
87
|
const denied = {};
|
|
88
88
|
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
89
89
|
denied[toolId] = 'deny';
|
|
90
90
|
}
|
|
91
91
|
const allowed = {};
|
|
92
|
-
for (const toolId of
|
|
92
|
+
for (const toolId of ENGINEER_EXPLORE_TOOL_IDS) {
|
|
93
93
|
allowed[toolId] = 'allow';
|
|
94
94
|
}
|
|
95
95
|
return {
|
|
@@ -99,14 +99,14 @@ function buildEngineerPlanPermissions() {
|
|
|
99
99
|
...allowed,
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
-
/** Engineer
|
|
103
|
-
function
|
|
102
|
+
/** Engineer implement wrapper: read-only investigation + implement + shared session tools. */
|
|
103
|
+
function buildEngineerImplementPermissions() {
|
|
104
104
|
const denied = {};
|
|
105
105
|
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
106
106
|
denied[toolId] = 'deny';
|
|
107
107
|
}
|
|
108
108
|
const allowed = {};
|
|
109
|
-
for (const toolId of
|
|
109
|
+
for (const toolId of ENGINEER_IMPLEMENT_TOOL_IDS) {
|
|
110
110
|
allowed[toolId] = 'allow';
|
|
111
111
|
}
|
|
112
112
|
return {
|
|
@@ -128,22 +128,22 @@ export function buildCtoAgentConfig(prompts) {
|
|
|
128
128
|
prompt: prompts.ctoSystemPrompt,
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
-
export function
|
|
131
|
+
export function buildEngineerExploreAgentConfig(prompts) {
|
|
132
132
|
return {
|
|
133
133
|
description: 'Thin high-judgment wrapper that frames work quickly and dispatches to Claude Code in plan mode for read-only investigation.',
|
|
134
134
|
mode: 'subagent',
|
|
135
135
|
color: '#D97757',
|
|
136
|
-
permission:
|
|
137
|
-
prompt: prompts.
|
|
136
|
+
permission: buildEngineerExplorePermissions(),
|
|
137
|
+
prompt: prompts.engineerExplorePrompt,
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
|
-
export function
|
|
140
|
+
export function buildEngineerImplementAgentConfig(prompts) {
|
|
141
141
|
return {
|
|
142
142
|
description: 'Thin high-judgment wrapper that frames work quickly and dispatches to Claude Code in free mode for implementation.',
|
|
143
143
|
mode: 'subagent',
|
|
144
144
|
color: '#D97757',
|
|
145
|
-
permission:
|
|
146
|
-
prompt: prompts.
|
|
145
|
+
permission: buildEngineerImplementPermissions(),
|
|
146
|
+
prompt: prompts.engineerImplementPrompt,
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
// ---------------------------------------------------------------------------
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { tool } from '@opencode-ai/plugin';
|
|
2
2
|
import { composeWrapperPrompt, managerPromptRegistry } from '../prompts/registry.js';
|
|
3
3
|
import { discoverProjectClaudeFiles } from '../util/project-context.js';
|
|
4
|
-
import { AGENT_CTO,
|
|
4
|
+
import { AGENT_CTO, AGENT_ENGINEER_EXPLORE, AGENT_ENGINEER_IMPLEMENT, buildCtoAgentConfig, buildEngineerExploreAgentConfig, buildEngineerImplementAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
|
|
5
5
|
import { getOrCreatePluginServices } from './service-factory.js';
|
|
6
6
|
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
7
7
|
const services = getOrCreatePluginServices(worktree);
|
|
8
8
|
async function executeDelegate(args, context) {
|
|
9
|
-
const
|
|
9
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
10
10
|
if (args.freshSession) {
|
|
11
|
-
await
|
|
11
|
+
await wrapperServices.manager.clearSession();
|
|
12
12
|
}
|
|
13
|
-
const hasActiveSession =
|
|
13
|
+
const hasActiveSession = wrapperServices.manager.getStatus().sessionId !== null;
|
|
14
14
|
const promptPreview = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
15
15
|
context.metadata({
|
|
16
16
|
title: hasActiveSession
|
|
@@ -18,16 +18,17 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
18
18
|
: '⚡ Claude Code: Initializing...',
|
|
19
19
|
metadata: {
|
|
20
20
|
status: 'running',
|
|
21
|
-
sessionId:
|
|
21
|
+
sessionId: wrapperServices.manager.getStatus().sessionId,
|
|
22
22
|
prompt: promptPreview,
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
25
|
let turnsSoFar;
|
|
26
26
|
let costSoFar;
|
|
27
|
-
const result = await
|
|
27
|
+
const result = await wrapperServices.manager.sendMessage(context.worktree, args.message, {
|
|
28
28
|
model: args.model,
|
|
29
29
|
effort: args.effort,
|
|
30
30
|
mode: args.mode,
|
|
31
|
+
sessionSystemPrompt: args.sessionSystemPrompt,
|
|
31
32
|
abortSignal: context.abort,
|
|
32
33
|
}, (event) => {
|
|
33
34
|
if (event.turns !== undefined) {
|
|
@@ -182,7 +183,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
182
183
|
let toolOutputs = [];
|
|
183
184
|
if (result.sessionId) {
|
|
184
185
|
try {
|
|
185
|
-
toolOutputs = await
|
|
186
|
+
toolOutputs = await wrapperServices.liveTailer.getToolOutputPreview(result.sessionId, context.worktree, 3);
|
|
186
187
|
}
|
|
187
188
|
catch {
|
|
188
189
|
// Non-critical — the JSONL file may not exist yet.
|
|
@@ -210,12 +211,12 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
210
211
|
const claudeFiles = await discoverProjectClaudeFiles(worktree);
|
|
211
212
|
const derivedPrompts = {
|
|
212
213
|
...managerPromptRegistry,
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
engineerExplorePrompt: composeWrapperPrompt(managerPromptRegistry.engineerExplorePrompt, claudeFiles),
|
|
215
|
+
engineerImplementPrompt: composeWrapperPrompt(managerPromptRegistry.engineerImplementPrompt, claudeFiles),
|
|
215
216
|
};
|
|
216
217
|
config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
|
|
217
|
-
config.agent[
|
|
218
|
-
config.agent[
|
|
218
|
+
config.agent[AGENT_ENGINEER_EXPLORE] ??= buildEngineerExploreAgentConfig(derivedPrompts);
|
|
219
|
+
config.agent[AGENT_ENGINEER_IMPLEMENT] ??= buildEngineerImplementAgentConfig(derivedPrompts);
|
|
219
220
|
},
|
|
220
221
|
tool: {
|
|
221
222
|
explore: tool({
|
|
@@ -225,14 +226,14 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
225
226
|
args: {
|
|
226
227
|
message: tool.schema.string().min(1),
|
|
227
228
|
model: tool.schema
|
|
228
|
-
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'
|
|
229
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
229
230
|
.optional(),
|
|
230
|
-
effort: tool.schema.enum(['
|
|
231
|
+
effort: tool.schema.enum(['medium', 'high', 'max']).default('high'),
|
|
231
232
|
freshSession: tool.schema.boolean().default(false),
|
|
232
|
-
|
|
233
|
+
sessionSystemPrompt: tool.schema.string().optional(),
|
|
233
234
|
},
|
|
234
235
|
async execute(args, context) {
|
|
235
|
-
return executeDelegate({ ...args, mode: 'plan' }, context);
|
|
236
|
+
return executeDelegate({ ...args, mode: 'plan', wrapperType: 'explore' }, context);
|
|
236
237
|
},
|
|
237
238
|
}),
|
|
238
239
|
implement: tool({
|
|
@@ -241,27 +242,27 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
241
242
|
args: {
|
|
242
243
|
message: tool.schema.string().min(1),
|
|
243
244
|
model: tool.schema
|
|
244
|
-
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'
|
|
245
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
245
246
|
.optional(),
|
|
246
|
-
effort: tool.schema.enum(['
|
|
247
|
+
effort: tool.schema.enum(['medium', 'high', 'max']).default('high'),
|
|
247
248
|
freshSession: tool.schema.boolean().default(false),
|
|
248
|
-
|
|
249
|
+
sessionSystemPrompt: tool.schema.string().optional(),
|
|
249
250
|
},
|
|
250
251
|
async execute(args, context) {
|
|
251
|
-
return executeDelegate({ ...args, mode: 'free' }, context);
|
|
252
|
+
return executeDelegate({ ...args, mode: 'free', wrapperType: 'implement' }, context);
|
|
252
253
|
},
|
|
253
254
|
}),
|
|
254
255
|
compact_context: tool({
|
|
255
256
|
description: 'Compress session history to reclaim context window space. ' +
|
|
256
257
|
'Preserves state while reducing token usage.',
|
|
257
258
|
args: {
|
|
258
|
-
|
|
259
|
+
wrapperType: tool.schema.string().optional(),
|
|
259
260
|
},
|
|
260
261
|
async execute(args, context) {
|
|
261
|
-
const
|
|
262
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
262
263
|
annotateToolRun(context, 'Compacting session', {});
|
|
263
|
-
const result = await
|
|
264
|
-
const snap =
|
|
264
|
+
const result = await wrapperServices.manager.compactSession(context.worktree);
|
|
265
|
+
const snap = wrapperServices.manager.getStatus();
|
|
265
266
|
const contextWarning = formatContextWarning(snap);
|
|
266
267
|
context.metadata({
|
|
267
268
|
title: contextWarning
|
|
@@ -283,13 +284,27 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
283
284
|
},
|
|
284
285
|
}),
|
|
285
286
|
git_diff: tool({
|
|
286
|
-
description: '
|
|
287
|
+
description: 'Show diff of uncommitted changes. ' +
|
|
288
|
+
'Use paths to filter to specific files/dirs. ' +
|
|
289
|
+
'Use staged=true to see staged changes. ' +
|
|
290
|
+
'Use ref to compare against a branch/tag/commit (e.g., ref="main").',
|
|
287
291
|
args: {
|
|
288
|
-
|
|
292
|
+
paths: tool.schema.string().array().optional(),
|
|
293
|
+
staged: tool.schema.boolean().optional(),
|
|
294
|
+
ref: tool.schema.string().optional(),
|
|
289
295
|
},
|
|
290
|
-
async execute(
|
|
291
|
-
annotateToolRun(context, 'Running git diff', {
|
|
292
|
-
|
|
296
|
+
async execute(args, context) {
|
|
297
|
+
annotateToolRun(context, 'Running git diff', {
|
|
298
|
+
paths: args.paths,
|
|
299
|
+
staged: args.staged,
|
|
300
|
+
ref: args.ref,
|
|
301
|
+
});
|
|
302
|
+
const paths = args.paths?.filter((p) => p !== undefined);
|
|
303
|
+
const result = await services.manager.gitDiff({
|
|
304
|
+
paths,
|
|
305
|
+
staged: args.staged,
|
|
306
|
+
ref: args.ref,
|
|
307
|
+
});
|
|
293
308
|
return JSON.stringify(result, null, 2);
|
|
294
309
|
},
|
|
295
310
|
}),
|
|
@@ -297,7 +312,6 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
297
312
|
description: 'Stage all changes and commit with the given message.',
|
|
298
313
|
args: {
|
|
299
314
|
message: tool.schema.string().min(1),
|
|
300
|
-
cwd: tool.schema.string().optional(),
|
|
301
315
|
},
|
|
302
316
|
async execute(args, context) {
|
|
303
317
|
annotateToolRun(context, 'Committing changes', {
|
|
@@ -308,39 +322,61 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
308
322
|
},
|
|
309
323
|
}),
|
|
310
324
|
git_reset: tool({
|
|
311
|
-
description: '
|
|
312
|
-
args: {
|
|
313
|
-
cwd: tool.schema.string().optional(),
|
|
314
|
-
},
|
|
325
|
+
description: 'Discard all uncommitted changes: runs git reset --hard HEAD and git clean -fd.',
|
|
326
|
+
args: {},
|
|
315
327
|
async execute(_args, context) {
|
|
316
328
|
annotateToolRun(context, 'Resetting working directory', {});
|
|
317
329
|
const result = await services.manager.gitReset();
|
|
318
330
|
return JSON.stringify(result, null, 2);
|
|
319
331
|
},
|
|
320
332
|
}),
|
|
333
|
+
git_status: tool({
|
|
334
|
+
description: 'Show working tree status — lists changed files in short format. ' +
|
|
335
|
+
'Returns isClean=true if nothing changed.',
|
|
336
|
+
args: {},
|
|
337
|
+
async execute(_args, context) {
|
|
338
|
+
annotateToolRun(context, 'Checking git status', {});
|
|
339
|
+
const result = await services.manager.gitStatus();
|
|
340
|
+
return JSON.stringify(result, null, 2);
|
|
341
|
+
},
|
|
342
|
+
}),
|
|
343
|
+
git_log: tool({
|
|
344
|
+
description: 'Show recent commits in short format. ' +
|
|
345
|
+
'Default shows last 5 commits. Use count to change.',
|
|
346
|
+
args: {
|
|
347
|
+
count: tool.schema.number().optional(),
|
|
348
|
+
},
|
|
349
|
+
async execute(args, context) {
|
|
350
|
+
annotateToolRun(context, 'Fetching git log', { count: args.count });
|
|
351
|
+
const result = await services.manager.gitLog(args.count ?? 5);
|
|
352
|
+
return result;
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
321
355
|
clear_session: tool({
|
|
322
356
|
description: 'Clear the active session to start fresh. ' +
|
|
323
357
|
'Use when context is full or starting a new task.',
|
|
324
358
|
args: {
|
|
325
|
-
|
|
359
|
+
wrapperType: tool.schema.string().optional(),
|
|
326
360
|
reason: tool.schema.string().optional(),
|
|
327
361
|
},
|
|
328
362
|
async execute(args, context) {
|
|
363
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
329
364
|
annotateToolRun(context, 'Clearing session', {
|
|
330
365
|
reason: args.reason,
|
|
331
366
|
});
|
|
332
|
-
const clearedId = await
|
|
367
|
+
const clearedId = await wrapperServices.manager.clearSession();
|
|
333
368
|
return JSON.stringify({ clearedSessionId: clearedId });
|
|
334
369
|
},
|
|
335
370
|
}),
|
|
336
371
|
session_health: tool({
|
|
337
372
|
description: 'Check session health metrics: context usage %, turn count, cost, and session ID.',
|
|
338
373
|
args: {
|
|
339
|
-
|
|
374
|
+
wrapperType: tool.schema.string().optional(),
|
|
340
375
|
},
|
|
341
|
-
async execute(
|
|
376
|
+
async execute(args, context) {
|
|
377
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
342
378
|
annotateToolRun(context, 'Checking session status', {});
|
|
343
|
-
const status =
|
|
379
|
+
const status = wrapperServices.manager.getStatus();
|
|
344
380
|
return JSON.stringify({
|
|
345
381
|
...status,
|
|
346
382
|
transcriptFile: status.sessionId
|
|
@@ -353,39 +389,40 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
353
389
|
list_transcripts: tool({
|
|
354
390
|
description: 'List available session transcripts or inspect a specific transcript by ID.',
|
|
355
391
|
args: {
|
|
356
|
-
|
|
392
|
+
wrapperType: tool.schema.string().optional(),
|
|
357
393
|
sessionId: tool.schema.string().optional(),
|
|
358
394
|
},
|
|
359
395
|
async execute(args, context) {
|
|
396
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
360
397
|
annotateToolRun(context, 'Inspecting Claude session history', {});
|
|
361
|
-
const cwd = args.cwd ?? context.worktree;
|
|
362
398
|
if (args.sessionId) {
|
|
363
399
|
const [sdkTranscript, localEvents] = await Promise.all([
|
|
364
|
-
|
|
365
|
-
|
|
400
|
+
wrapperServices.sessions.getTranscript(args.sessionId, context.worktree),
|
|
401
|
+
wrapperServices.manager.getTranscriptEvents(context.worktree, args.sessionId),
|
|
366
402
|
]);
|
|
367
403
|
return JSON.stringify({
|
|
368
404
|
sdkTranscript,
|
|
369
405
|
localEvents: localEvents.length > 0 ? localEvents : undefined,
|
|
370
406
|
}, null, 2);
|
|
371
407
|
}
|
|
372
|
-
const sessions = await
|
|
408
|
+
const sessions = await wrapperServices.sessions.listSessions(context.worktree);
|
|
373
409
|
return JSON.stringify(sessions, null, 2);
|
|
374
410
|
},
|
|
375
411
|
}),
|
|
376
412
|
list_history: tool({
|
|
377
413
|
description: 'List persistent run records from the manager or inspect a specific run.',
|
|
378
414
|
args: {
|
|
379
|
-
|
|
415
|
+
wrapperType: tool.schema.string().optional(),
|
|
380
416
|
runId: tool.schema.string().optional(),
|
|
381
417
|
},
|
|
382
418
|
async execute(args, context) {
|
|
419
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
383
420
|
annotateToolRun(context, 'Reading manager run state', {});
|
|
384
421
|
if (args.runId) {
|
|
385
|
-
const run = await
|
|
422
|
+
const run = await wrapperServices.manager.getRun(context.worktree, args.runId);
|
|
386
423
|
return JSON.stringify(run, null, 2);
|
|
387
424
|
}
|
|
388
|
-
const runs = await
|
|
425
|
+
const runs = await wrapperServices.manager.listRuns(context.worktree);
|
|
389
426
|
return JSON.stringify(runs, null, 2);
|
|
390
427
|
},
|
|
391
428
|
}),
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { prompts } from '../prompts/registry.js';
|
|
2
|
+
import { evaluateBashCommand, extractBashCommand, } from '../safety/bash-safety.js';
|
|
3
|
+
/**
|
|
4
|
+
* Thin OpenCode orchestrator plugin with Claude Code specialist subagents.
|
|
5
|
+
*
|
|
6
|
+
* - Registers `claude-code` provider via a local shim over ai-sdk-provider-claude-code.
|
|
7
|
+
* - Creates one orchestrator agent (uses the user's default OpenCode model).
|
|
8
|
+
* - Creates 4 Claude Code subagents: planning + build × opus + sonnet.
|
|
9
|
+
* - Enforces bash safety via the permission.ask hook.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Claude Code `effort` is not configurable through OpenCode provider/model
|
|
12
|
+
* options at this time. The subagent prompts compensate by setting high-quality
|
|
13
|
+
* expectations directly.
|
|
14
|
+
*/
|
|
15
|
+
// Resolve the shim path at module load time so it is stable for the lifetime
|
|
16
|
+
// of the process. The compiled output for this file sits at dist/plugin/ and
|
|
17
|
+
// the shim at dist/providers/, so we walk up one level.
|
|
18
|
+
const claudeCodeShimUrl = new URL('../providers/claude-code-wrapper.js', import.meta.url).href;
|
|
19
|
+
export const OrchestratorPlugin = async () => {
|
|
20
|
+
return {
|
|
21
|
+
config: async (config) => {
|
|
22
|
+
config.provider ??= {};
|
|
23
|
+
config.agent ??= {};
|
|
24
|
+
// ── Provider ──────────────────────────────────────────────────────
|
|
25
|
+
// Uses a file:// shim so OpenCode's factory-finder heuristic sees only
|
|
26
|
+
// createClaudeCode and not createAPICallError from the upstream package.
|
|
27
|
+
config.provider['claude-code'] ??= {
|
|
28
|
+
npm: claudeCodeShimUrl,
|
|
29
|
+
models: {
|
|
30
|
+
opus: {
|
|
31
|
+
id: 'opus',
|
|
32
|
+
name: 'Claude Code Opus 4.6',
|
|
33
|
+
},
|
|
34
|
+
sonnet: {
|
|
35
|
+
id: 'sonnet',
|
|
36
|
+
name: 'Claude Code Sonnet 4.6',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// ── Orchestrator (uses user's default model — no model set) ───────
|
|
41
|
+
config.agent['opencode-orchestrator'] ??= {
|
|
42
|
+
description: 'CTO-level orchestrator that gathers context and delegates coding to Claude Code specialists.',
|
|
43
|
+
mode: 'primary',
|
|
44
|
+
color: '#D97757',
|
|
45
|
+
prompt: prompts.orchestrator,
|
|
46
|
+
permission: {
|
|
47
|
+
'*': 'deny',
|
|
48
|
+
read: 'allow',
|
|
49
|
+
grep: 'allow',
|
|
50
|
+
glob: 'allow',
|
|
51
|
+
list: 'allow',
|
|
52
|
+
webfetch: 'allow',
|
|
53
|
+
question: 'allow',
|
|
54
|
+
todowrite: 'allow',
|
|
55
|
+
todoread: 'allow',
|
|
56
|
+
task: 'allow',
|
|
57
|
+
bash: 'deny',
|
|
58
|
+
edit: 'deny',
|
|
59
|
+
skill: 'deny',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
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.
|
|
66
|
+
const claudeCodePermissions = {
|
|
67
|
+
'*': 'deny',
|
|
68
|
+
};
|
|
69
|
+
config.agent['claude-code-planning-opus'] ??= {
|
|
70
|
+
description: 'Claude Code Opus specialist for investigation, architecture, and planning.',
|
|
71
|
+
model: 'claude-code/opus',
|
|
72
|
+
mode: 'subagent',
|
|
73
|
+
color: 'info',
|
|
74
|
+
prompt: prompts.planningAgent,
|
|
75
|
+
permission: { ...claudeCodePermissions },
|
|
76
|
+
};
|
|
77
|
+
config.agent['claude-code-planning-sonnet'] ??= {
|
|
78
|
+
description: 'Claude Code Sonnet specialist for lighter investigation and planning.',
|
|
79
|
+
model: 'claude-code/sonnet',
|
|
80
|
+
mode: 'subagent',
|
|
81
|
+
color: 'info',
|
|
82
|
+
prompt: prompts.planningAgent,
|
|
83
|
+
permission: { ...claudeCodePermissions },
|
|
84
|
+
};
|
|
85
|
+
// ── Build subagents ───────────────────────────────────────────────
|
|
86
|
+
config.agent['claude-code-build-opus'] ??= {
|
|
87
|
+
description: 'Claude Code Opus specialist for implementation and validation.',
|
|
88
|
+
model: 'claude-code/opus',
|
|
89
|
+
mode: 'subagent',
|
|
90
|
+
color: 'success',
|
|
91
|
+
prompt: prompts.buildAgent,
|
|
92
|
+
permission: { ...claudeCodePermissions },
|
|
93
|
+
};
|
|
94
|
+
config.agent['claude-code-build-sonnet'] ??= {
|
|
95
|
+
description: 'Claude Code Sonnet specialist for lighter implementation tasks.',
|
|
96
|
+
model: 'claude-code/sonnet',
|
|
97
|
+
mode: 'subagent',
|
|
98
|
+
color: 'success',
|
|
99
|
+
prompt: prompts.buildAgent,
|
|
100
|
+
permission: { ...claudeCodePermissions },
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
// ── Bash safety via permission.ask hook ────────────────────────────
|
|
104
|
+
// Handles both v1 Permission ({ type, pattern }) and v2 PermissionRequest
|
|
105
|
+
// ({ permission, patterns }) via runtime narrowing in extractBashCommand.
|
|
106
|
+
'permission.ask': async (input, output) => {
|
|
107
|
+
const command = extractBashCommand(input);
|
|
108
|
+
if (command === null)
|
|
109
|
+
return;
|
|
110
|
+
const result = evaluateBashCommand(command);
|
|
111
|
+
if (!result.allowed) {
|
|
112
|
+
output.status = 'deny';
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -9,23 +9,19 @@ import { GitOperations } from '../manager/git-operations.js';
|
|
|
9
9
|
import { SessionController } from '../manager/session-controller.js';
|
|
10
10
|
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
11
11
|
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
12
|
-
const serviceCache = new Map();
|
|
13
12
|
export function getOrCreatePluginServices(worktree) {
|
|
14
|
-
const cachedServices = serviceCache.get(worktree);
|
|
15
|
-
if (cachedServices) {
|
|
16
|
-
return cachedServices;
|
|
17
|
-
}
|
|
18
13
|
const approvalManager = new ToolApprovalManager();
|
|
19
14
|
const sdkAdapter = new ClaudeAgentSdkAdapter(undefined, approvalManager);
|
|
20
15
|
const sessionService = new ClaudeSessionService(sdkAdapter);
|
|
21
16
|
const contextTracker = new ContextTracker();
|
|
22
|
-
const sessionController = new SessionController(sdkAdapter, contextTracker,
|
|
17
|
+
const sessionController = new SessionController(sdkAdapter, contextTracker, undefined, // session prompt is now constructed dynamically by the wrapper
|
|
18
|
+
'default', worktree, managerPromptRegistry.modePrefixes);
|
|
23
19
|
const gitOps = new GitOperations(worktree);
|
|
24
20
|
const stateStore = new FileRunStateStore();
|
|
25
21
|
const transcriptStore = new TranscriptStore();
|
|
26
22
|
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker, transcriptStore);
|
|
27
23
|
// Try to restore active session state (fire and forget)
|
|
28
|
-
manager.tryRestore(
|
|
24
|
+
manager.tryRestore().catch(() => { });
|
|
29
25
|
const liveTailer = new SessionLiveTailer();
|
|
30
26
|
const services = {
|
|
31
27
|
manager,
|
|
@@ -33,6 +29,5 @@ export function getOrCreatePluginServices(worktree) {
|
|
|
33
29
|
approvalManager,
|
|
34
30
|
liveTailer,
|
|
35
31
|
};
|
|
36
|
-
serviceCache.set(worktree, services);
|
|
37
32
|
return services;
|
|
38
33
|
}
|