@doingdev/opencode-claude-manager-plugin 0.1.64 → 0.1.66
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 +106 -120
- package/dist/claude/claude-agent-sdk-adapter.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/manager/team-orchestrator.js +1 -1
- package/dist/plugin/agents/common.d.ts +2 -2
- package/dist/plugin/agents/common.js +5 -0
- package/dist/plugin/claude-manager.plugin.js +104 -0
- package/dist/plugin/inbox-ops.d.ts +50 -0
- package/dist/plugin/inbox-ops.js +166 -0
- package/dist/types/contracts.d.ts +18 -0
- package/package.json +13 -13
- package/dist/claude/session-live-tailer.d.ts +0 -51
- package/dist/claude/session-live-tailer.js +0 -269
- package/dist/manager/session-controller.d.ts +0 -41
- package/dist/manager/session-controller.js +0 -97
- package/dist/metadata/claude-metadata.service.d.ts +0 -12
- package/dist/metadata/claude-metadata.service.js +0 -38
- package/dist/metadata/repo-claude-config-reader.d.ts +0 -7
- package/dist/metadata/repo-claude-config-reader.js +0 -154
- package/dist/plugin/orchestrator.plugin.d.ts +0 -2
- package/dist/plugin/orchestrator.plugin.js +0 -116
- package/dist/providers/claude-code-wrapper.d.ts +0 -13
- package/dist/providers/claude-code-wrapper.js +0 -13
- package/dist/safety/bash-safety.d.ts +0 -21
- package/dist/safety/bash-safety.js +0 -62
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +0 -28
- package/dist/src/claude/claude-agent-sdk-adapter.js +0 -559
- package/dist/src/claude/claude-session.service.d.ts +0 -9
- package/dist/src/claude/claude-session.service.js +0 -15
- package/dist/src/claude/session-live-tailer.d.ts +0 -51
- package/dist/src/claude/session-live-tailer.js +0 -269
- package/dist/src/claude/tool-approval-manager.d.ts +0 -30
- package/dist/src/claude/tool-approval-manager.js +0 -279
- package/dist/src/index.d.ts +0 -5
- package/dist/src/index.js +0 -3
- package/dist/src/manager/context-tracker.d.ts +0 -32
- package/dist/src/manager/context-tracker.js +0 -103
- package/dist/src/manager/git-operations.d.ts +0 -18
- package/dist/src/manager/git-operations.js +0 -86
- package/dist/src/manager/persistent-manager.d.ts +0 -39
- package/dist/src/manager/persistent-manager.js +0 -44
- package/dist/src/manager/session-controller.d.ts +0 -41
- package/dist/src/manager/session-controller.js +0 -97
- package/dist/src/manager/team-orchestrator.d.ts +0 -81
- package/dist/src/manager/team-orchestrator.js +0 -612
- package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
- package/dist/src/plugin/agent-hierarchy.js +0 -2
- package/dist/src/plugin/agents/browser-qa.d.ts +0 -14
- package/dist/src/plugin/agents/browser-qa.js +0 -31
- package/dist/src/plugin/agents/common.d.ts +0 -36
- package/dist/src/plugin/agents/common.js +0 -59
- package/dist/src/plugin/agents/cto.d.ts +0 -9
- package/dist/src/plugin/agents/cto.js +0 -39
- package/dist/src/plugin/agents/engineers.d.ts +0 -9
- package/dist/src/plugin/agents/engineers.js +0 -11
- package/dist/src/plugin/agents/index.d.ts +0 -5
- package/dist/src/plugin/agents/index.js +0 -5
- package/dist/src/plugin/agents/team-planner.d.ts +0 -10
- package/dist/src/plugin/agents/team-planner.js +0 -23
- package/dist/src/plugin/claude-manager.plugin.d.ts +0 -10
- package/dist/src/plugin/claude-manager.plugin.js +0 -950
- package/dist/src/plugin/service-factory.d.ts +0 -38
- package/dist/src/plugin/service-factory.js +0 -101
- package/dist/src/prompts/registry.d.ts +0 -2
- package/dist/src/prompts/registry.js +0 -210
- package/dist/src/state/file-run-state-store.d.ts +0 -14
- package/dist/src/state/file-run-state-store.js +0 -85
- package/dist/src/state/team-state-store.d.ts +0 -14
- package/dist/src/state/team-state-store.js +0 -88
- package/dist/src/state/transcript-store.d.ts +0 -15
- package/dist/src/state/transcript-store.js +0 -44
- package/dist/src/team/roster.d.ts +0 -5
- package/dist/src/team/roster.js +0 -40
- package/dist/src/types/contracts.d.ts +0 -261
- package/dist/src/types/contracts.js +0 -2
- package/dist/src/util/fs-helpers.d.ts +0 -8
- package/dist/src/util/fs-helpers.js +0 -21
- package/dist/src/util/project-context.d.ts +0 -10
- package/dist/src/util/project-context.js +0 -105
- package/dist/src/util/transcript-append.d.ts +0 -7
- package/dist/src/util/transcript-append.js +0 -29
- package/dist/state/file-run-state-store.d.ts +0 -14
- package/dist/state/file-run-state-store.js +0 -85
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +0 -1
- package/dist/test/claude-agent-sdk-adapter.test.js +0 -707
- package/dist/test/claude-manager.plugin.test.d.ts +0 -1
- package/dist/test/claude-manager.plugin.test.js +0 -316
- package/dist/test/context-tracker.test.d.ts +0 -1
- package/dist/test/context-tracker.test.js +0 -130
- package/dist/test/cto-active-team.test.d.ts +0 -1
- package/dist/test/cto-active-team.test.js +0 -199
- package/dist/test/file-run-state-store.test.d.ts +0 -1
- package/dist/test/file-run-state-store.test.js +0 -82
- package/dist/test/fs-helpers.test.d.ts +0 -1
- package/dist/test/fs-helpers.test.js +0 -56
- package/dist/test/git-operations.test.d.ts +0 -1
- package/dist/test/git-operations.test.js +0 -133
- package/dist/test/persistent-manager.test.d.ts +0 -1
- package/dist/test/persistent-manager.test.js +0 -48
- package/dist/test/project-context.test.d.ts +0 -1
- package/dist/test/project-context.test.js +0 -92
- package/dist/test/prompt-registry.test.d.ts +0 -1
- package/dist/test/prompt-registry.test.js +0 -117
- package/dist/test/report-claude-event.test.d.ts +0 -1
- package/dist/test/report-claude-event.test.js +0 -304
- package/dist/test/session-controller.test.d.ts +0 -1
- package/dist/test/session-controller.test.js +0 -149
- package/dist/test/session-live-tailer.test.d.ts +0 -1
- package/dist/test/session-live-tailer.test.js +0 -313
- package/dist/test/team-orchestrator.test.d.ts +0 -1
- package/dist/test/team-orchestrator.test.js +0 -583
- package/dist/test/team-state-store.test.d.ts +0 -1
- package/dist/test/team-state-store.test.js +0 -54
- package/dist/test/tool-approval-manager.test.d.ts +0 -1
- package/dist/test/tool-approval-manager.test.js +0 -260
- package/dist/test/transcript-append.test.d.ts +0 -1
- package/dist/test/transcript-append.test.js +0 -37
- package/dist/test/transcript-store.test.d.ts +0 -1
- package/dist/test/transcript-store.test.js +0 -50
- package/dist/test/undo-propagation.test.d.ts +0 -1
- package/dist/test/undo-propagation.test.js +0 -837
- package/dist/util/project-context.d.ts +0 -10
- package/dist/util/project-context.js +0 -105
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -11
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
export class SessionController {
|
|
2
|
-
sdkAdapter;
|
|
3
|
-
contextTracker;
|
|
4
|
-
sessionPrompt;
|
|
5
|
-
wrapperType;
|
|
6
|
-
worktree;
|
|
7
|
-
modePrefixes;
|
|
8
|
-
activeSessionId = null;
|
|
9
|
-
constructor(sdkAdapter, contextTracker, sessionPrompt, wrapperType, worktree, modePrefixes = {
|
|
10
|
-
plan: '',
|
|
11
|
-
free: '',
|
|
12
|
-
}) {
|
|
13
|
-
this.sdkAdapter = sdkAdapter;
|
|
14
|
-
this.contextTracker = contextTracker;
|
|
15
|
-
this.sessionPrompt = sessionPrompt;
|
|
16
|
-
this.wrapperType = wrapperType;
|
|
17
|
-
this.worktree = worktree;
|
|
18
|
-
this.modePrefixes = modePrefixes;
|
|
19
|
-
}
|
|
20
|
-
get isActive() {
|
|
21
|
-
return this.activeSessionId !== null;
|
|
22
|
-
}
|
|
23
|
-
get sessionId() {
|
|
24
|
-
return this.activeSessionId;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Send a message to the persistent session. Creates one if none exists.
|
|
28
|
-
* Returns the session result including usage data.
|
|
29
|
-
*/
|
|
30
|
-
async sendMessage(message, options, onEvent) {
|
|
31
|
-
const mode = options?.mode ?? 'free';
|
|
32
|
-
const prefix = this.modePrefixes[mode];
|
|
33
|
-
const prompt = prefix ? `${prefix}\n\n${message}` : message;
|
|
34
|
-
const input = {
|
|
35
|
-
cwd: this.worktree,
|
|
36
|
-
prompt,
|
|
37
|
-
persistSession: true,
|
|
38
|
-
permissionMode: mode === 'plan' ? 'plan' : 'acceptEdits',
|
|
39
|
-
includePartialMessages: true,
|
|
40
|
-
model: options?.model,
|
|
41
|
-
effort: options?.effort,
|
|
42
|
-
settingSources: ['user'],
|
|
43
|
-
abortSignal: options?.abortSignal,
|
|
44
|
-
};
|
|
45
|
-
if (this.activeSessionId) {
|
|
46
|
-
// Resume existing session
|
|
47
|
-
input.resumeSessionId = this.activeSessionId;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// New session — prefer dynamically constructed prompt from wrapper, fall back to static default
|
|
51
|
-
input.systemPrompt = options?.sessionSystemPrompt ?? this.sessionPrompt;
|
|
52
|
-
input.model ??= 'claude-opus-4-6';
|
|
53
|
-
input.effort ??= 'high';
|
|
54
|
-
}
|
|
55
|
-
const result = await this.sdkAdapter.runSession(input, onEvent);
|
|
56
|
-
// Track the session ID
|
|
57
|
-
if (result.sessionId) {
|
|
58
|
-
this.activeSessionId = result.sessionId;
|
|
59
|
-
}
|
|
60
|
-
// Update context tracking
|
|
61
|
-
this.contextTracker.recordResult({
|
|
62
|
-
sessionId: result.sessionId,
|
|
63
|
-
turns: result.turns,
|
|
64
|
-
totalCostUsd: result.totalCostUsd,
|
|
65
|
-
inputTokens: result.inputTokens,
|
|
66
|
-
outputTokens: result.outputTokens,
|
|
67
|
-
contextWindowSize: result.contextWindowSize,
|
|
68
|
-
});
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Send /compact to the current session to compress context.
|
|
73
|
-
*/
|
|
74
|
-
async compactSession(onEvent) {
|
|
75
|
-
if (!this.activeSessionId) {
|
|
76
|
-
throw new Error('No active session to compact');
|
|
77
|
-
}
|
|
78
|
-
const result = await this.sendMessage('/compact', undefined, onEvent);
|
|
79
|
-
this.contextTracker.recordCompaction();
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Clear the current session. The next sendMessage will create a fresh one.
|
|
84
|
-
*/
|
|
85
|
-
async clearSession() {
|
|
86
|
-
const clearedId = this.activeSessionId;
|
|
87
|
-
this.activeSessionId = null;
|
|
88
|
-
this.contextTracker.reset();
|
|
89
|
-
return clearedId;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Get current context tracking snapshot.
|
|
93
|
-
*/
|
|
94
|
-
getContextSnapshot() {
|
|
95
|
-
return this.contextTracker.snapshot();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ClaudeMetadataSnapshot, ClaudeSettingSource } from '../types/contracts.js';
|
|
2
|
-
import type { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
|
|
3
|
-
import type { RepoClaudeConfigReader } from './repo-claude-config-reader.js';
|
|
4
|
-
export declare class ClaudeMetadataService {
|
|
5
|
-
private readonly configReader;
|
|
6
|
-
private readonly sdkAdapter;
|
|
7
|
-
constructor(configReader: RepoClaudeConfigReader, sdkAdapter: ClaudeAgentSdkAdapter);
|
|
8
|
-
collect(cwd: string, options?: {
|
|
9
|
-
includeSdkProbe?: boolean;
|
|
10
|
-
settingSources?: ClaudeSettingSource[];
|
|
11
|
-
}): Promise<ClaudeMetadataSnapshot>;
|
|
12
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export class ClaudeMetadataService {
|
|
2
|
-
configReader;
|
|
3
|
-
sdkAdapter;
|
|
4
|
-
constructor(configReader, sdkAdapter) {
|
|
5
|
-
this.configReader = configReader;
|
|
6
|
-
this.sdkAdapter = sdkAdapter;
|
|
7
|
-
}
|
|
8
|
-
async collect(cwd, options = {}) {
|
|
9
|
-
const baseSnapshot = await this.configReader.read(cwd);
|
|
10
|
-
if (!options.includeSdkProbe) {
|
|
11
|
-
return dedupeSnapshot(baseSnapshot);
|
|
12
|
-
}
|
|
13
|
-
const capabilities = await this.sdkAdapter.probeCapabilities(cwd, options.settingSources);
|
|
14
|
-
return dedupeSnapshot({
|
|
15
|
-
...baseSnapshot,
|
|
16
|
-
commands: [...baseSnapshot.commands, ...capabilities.commands],
|
|
17
|
-
agents: capabilities.agents,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function dedupeSnapshot(snapshot) {
|
|
22
|
-
return {
|
|
23
|
-
...snapshot,
|
|
24
|
-
commands: dedupeByName(snapshot.commands),
|
|
25
|
-
skills: dedupeByName(snapshot.skills),
|
|
26
|
-
hooks: dedupeByName(snapshot.hooks),
|
|
27
|
-
agents: dedupeByName(snapshot.agents),
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function dedupeByName(items) {
|
|
31
|
-
const seen = new Map();
|
|
32
|
-
for (const item of items) {
|
|
33
|
-
if (!seen.has(item.name)) {
|
|
34
|
-
seen.set(item.name, item);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return [...seen.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
38
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import JSON5 from 'json5';
|
|
4
|
-
export class RepoClaudeConfigReader {
|
|
5
|
-
async read(cwd) {
|
|
6
|
-
const claudeDirectory = path.join(cwd, '.claude');
|
|
7
|
-
const skillsDirectory = path.join(claudeDirectory, 'skills');
|
|
8
|
-
const commandsDirectory = path.join(claudeDirectory, 'commands');
|
|
9
|
-
const claudeMdCandidates = [
|
|
10
|
-
path.join(cwd, 'CLAUDE.md'),
|
|
11
|
-
path.join(claudeDirectory, 'CLAUDE.md'),
|
|
12
|
-
];
|
|
13
|
-
const collectedAt = new Date().toISOString();
|
|
14
|
-
const [skills, commands, settingsResult, claudeMdPath] = await Promise.all([
|
|
15
|
-
this.readSkills(skillsDirectory),
|
|
16
|
-
this.readCommands(commandsDirectory),
|
|
17
|
-
this.readSettings(claudeDirectory),
|
|
18
|
-
findFirstExistingPath(claudeMdCandidates),
|
|
19
|
-
]);
|
|
20
|
-
return {
|
|
21
|
-
collectedAt,
|
|
22
|
-
cwd,
|
|
23
|
-
commands: [...skillsToCommands(skills), ...commands],
|
|
24
|
-
skills,
|
|
25
|
-
hooks: settingsResult.hooks,
|
|
26
|
-
agents: [],
|
|
27
|
-
claudeMdPath: claudeMdPath ?? undefined,
|
|
28
|
-
settingsPaths: settingsResult.settingsPaths,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async readSkills(directory) {
|
|
32
|
-
if (!(await pathExists(directory))) {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
36
|
-
const skills = await Promise.all(entries
|
|
37
|
-
.filter((entry) => entry.isDirectory())
|
|
38
|
-
.map(async (entry) => {
|
|
39
|
-
const skillPath = path.join(directory, entry.name, 'SKILL.md');
|
|
40
|
-
if (!(await pathExists(skillPath))) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
const content = await fs.readFile(skillPath, 'utf8');
|
|
44
|
-
return {
|
|
45
|
-
name: entry.name,
|
|
46
|
-
description: extractMarkdownDescription(content),
|
|
47
|
-
path: skillPath,
|
|
48
|
-
source: 'skill',
|
|
49
|
-
};
|
|
50
|
-
}));
|
|
51
|
-
return skills.filter((skill) => skill !== null);
|
|
52
|
-
}
|
|
53
|
-
async readCommands(directory) {
|
|
54
|
-
if (!(await pathExists(directory))) {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
const commandFiles = await collectMarkdownFiles(directory);
|
|
58
|
-
const commands = await Promise.all(commandFiles.map(async (commandPath) => {
|
|
59
|
-
const content = await fs.readFile(commandPath, 'utf8');
|
|
60
|
-
return {
|
|
61
|
-
name: path.basename(commandPath, path.extname(commandPath)),
|
|
62
|
-
description: extractMarkdownDescription(content),
|
|
63
|
-
source: 'command',
|
|
64
|
-
path: commandPath,
|
|
65
|
-
};
|
|
66
|
-
}));
|
|
67
|
-
return commands.sort((left, right) => left.name.localeCompare(right.name));
|
|
68
|
-
}
|
|
69
|
-
async readSettings(claudeDirectory) {
|
|
70
|
-
const candidatePaths = [
|
|
71
|
-
path.join(claudeDirectory, 'settings.json'),
|
|
72
|
-
path.join(claudeDirectory, 'settings.local.json'),
|
|
73
|
-
];
|
|
74
|
-
const settingsPaths = [];
|
|
75
|
-
const hooks = [];
|
|
76
|
-
for (const candidatePath of candidatePaths) {
|
|
77
|
-
if (!(await pathExists(candidatePath))) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
settingsPaths.push(candidatePath);
|
|
81
|
-
const content = await fs.readFile(candidatePath, 'utf8');
|
|
82
|
-
const parsed = JSON5.parse(content);
|
|
83
|
-
const hookEntries = Object.entries(parsed.hooks ?? {});
|
|
84
|
-
for (const [hookName, hookValue] of hookEntries) {
|
|
85
|
-
const hookMatchers = Array.isArray(hookValue) ? hookValue : [hookValue];
|
|
86
|
-
for (const hookMatcher of hookMatchers) {
|
|
87
|
-
if (!hookMatcher || typeof hookMatcher !== 'object') {
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
const matcher = typeof hookMatcher.matcher === 'string'
|
|
91
|
-
? hookMatcher.matcher
|
|
92
|
-
: undefined;
|
|
93
|
-
const commandCount = Array.isArray(hookMatcher.hooks)
|
|
94
|
-
? (hookMatcher.hooks?.length ?? 0)
|
|
95
|
-
: 0;
|
|
96
|
-
hooks.push({
|
|
97
|
-
name: hookName,
|
|
98
|
-
matcher,
|
|
99
|
-
sourcePath: candidatePath,
|
|
100
|
-
commandCount,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
settingsPaths,
|
|
107
|
-
hooks,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function extractMarkdownDescription(markdown) {
|
|
112
|
-
const lines = markdown
|
|
113
|
-
.split(/\r?\n/)
|
|
114
|
-
.map((line) => line.trim())
|
|
115
|
-
.filter(Boolean);
|
|
116
|
-
const descriptionLine = lines.find((line) => !line.startsWith('#') && !line.startsWith('---'));
|
|
117
|
-
return descriptionLine ?? 'No description provided.';
|
|
118
|
-
}
|
|
119
|
-
async function collectMarkdownFiles(directory) {
|
|
120
|
-
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
121
|
-
const files = await Promise.all(entries.map(async (entry) => {
|
|
122
|
-
const resolvedPath = path.join(directory, entry.name);
|
|
123
|
-
if (entry.isDirectory()) {
|
|
124
|
-
return collectMarkdownFiles(resolvedPath);
|
|
125
|
-
}
|
|
126
|
-
return entry.name.endsWith('.md') ? [resolvedPath] : [];
|
|
127
|
-
}));
|
|
128
|
-
return files.flat();
|
|
129
|
-
}
|
|
130
|
-
async function pathExists(candidatePath) {
|
|
131
|
-
try {
|
|
132
|
-
await fs.access(candidatePath);
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
async function findFirstExistingPath(candidatePaths) {
|
|
140
|
-
for (const candidatePath of candidatePaths) {
|
|
141
|
-
if (await pathExists(candidatePath)) {
|
|
142
|
-
return candidatePath;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
function skillsToCommands(skills) {
|
|
148
|
-
return skills.map((skill) => ({
|
|
149
|
-
name: skill.name,
|
|
150
|
-
description: skill.description,
|
|
151
|
-
source: 'skill',
|
|
152
|
-
path: skill.path,
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thin re-export shim for ai-sdk-provider-claude-code.
|
|
3
|
-
*
|
|
4
|
-
* OpenCode's provider loader finds the provider factory by scanning
|
|
5
|
-
* `Object.keys(module).find(key => key.startsWith("create"))`. The upstream
|
|
6
|
-
* package exports `createAPICallError` before `createClaudeCode`, so OpenCode
|
|
7
|
-
* picks the wrong function and `.languageModel` ends up undefined.
|
|
8
|
-
*
|
|
9
|
-
* This shim re-exports only `createClaudeCode`, making it the sole "create*"
|
|
10
|
-
* export. The plugin references this file via a `file://` URL so the upstream
|
|
11
|
-
* package is still the actual implementation.
|
|
12
|
-
*/
|
|
13
|
-
export { createClaudeCode } from 'ai-sdk-provider-claude-code';
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thin re-export shim for ai-sdk-provider-claude-code.
|
|
3
|
-
*
|
|
4
|
-
* OpenCode's provider loader finds the provider factory by scanning
|
|
5
|
-
* `Object.keys(module).find(key => key.startsWith("create"))`. The upstream
|
|
6
|
-
* package exports `createAPICallError` before `createClaudeCode`, so OpenCode
|
|
7
|
-
* picks the wrong function and `.languageModel` ends up undefined.
|
|
8
|
-
*
|
|
9
|
-
* This shim re-exports only `createClaudeCode`, making it the sole "create*"
|
|
10
|
-
* export. The plugin references this file via a `file://` URL so the upstream
|
|
11
|
-
* package is still the actual implementation.
|
|
12
|
-
*/
|
|
13
|
-
export { createClaudeCode } from 'ai-sdk-provider-claude-code';
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal bash command safety layer.
|
|
3
|
-
* Denies known-dangerous patterns; allows everything else.
|
|
4
|
-
*/
|
|
5
|
-
export type BashSafetyResult = {
|
|
6
|
-
allowed: true;
|
|
7
|
-
} | {
|
|
8
|
-
allowed: false;
|
|
9
|
-
reason: string;
|
|
10
|
-
};
|
|
11
|
-
export declare function evaluateBashCommand(command: string): BashSafetyResult;
|
|
12
|
-
/**
|
|
13
|
-
* Extract the bash command string from a permission hook input,
|
|
14
|
-
* handling both SDK payload shapes:
|
|
15
|
-
*
|
|
16
|
-
* v1 Permission: { type: string, pattern?: string | string[], metadata }
|
|
17
|
-
* v2 PermissionRequest: { permission: string, patterns: string[], metadata }
|
|
18
|
-
*
|
|
19
|
-
* Returns `null` when the input is not a bash permission request.
|
|
20
|
-
*/
|
|
21
|
-
export declare function extractBashCommand(input: Record<string, unknown>): string | null;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal bash command safety layer.
|
|
3
|
-
* Denies known-dangerous patterns; allows everything else.
|
|
4
|
-
*/
|
|
5
|
-
const DENY_PATTERNS = [
|
|
6
|
-
{ pattern: 'rm -rf /', reason: 'Destructive: rm -rf / is not allowed.' },
|
|
7
|
-
{
|
|
8
|
-
pattern: 'git push --force',
|
|
9
|
-
reason: 'Force push is not allowed.',
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
pattern: 'git reset --hard',
|
|
13
|
-
reason: 'git reset --hard is not allowed. Use a safer alternative.',
|
|
14
|
-
},
|
|
15
|
-
];
|
|
16
|
-
export function evaluateBashCommand(command) {
|
|
17
|
-
for (const { pattern, reason } of DENY_PATTERNS) {
|
|
18
|
-
if (command.includes(pattern)) {
|
|
19
|
-
return { allowed: false, reason };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return { allowed: true };
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Extract the bash command string from a permission hook input,
|
|
26
|
-
* handling both SDK payload shapes:
|
|
27
|
-
*
|
|
28
|
-
* v1 Permission: { type: string, pattern?: string | string[], metadata }
|
|
29
|
-
* v2 PermissionRequest: { permission: string, patterns: string[], metadata }
|
|
30
|
-
*
|
|
31
|
-
* Returns `null` when the input is not a bash permission request.
|
|
32
|
-
*/
|
|
33
|
-
export function extractBashCommand(input) {
|
|
34
|
-
// Determine the permission kind from whichever field is present.
|
|
35
|
-
const kind = typeof input['permission'] === 'string'
|
|
36
|
-
? input['permission']
|
|
37
|
-
: typeof input['type'] === 'string'
|
|
38
|
-
? input['type']
|
|
39
|
-
: null;
|
|
40
|
-
if (kind !== 'bash')
|
|
41
|
-
return null;
|
|
42
|
-
// Prefer an explicit command in metadata regardless of shape.
|
|
43
|
-
const meta = input['metadata'];
|
|
44
|
-
if (meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
45
|
-
const cmd = meta['command'];
|
|
46
|
-
if (typeof cmd === 'string' && cmd.length > 0)
|
|
47
|
-
return cmd;
|
|
48
|
-
}
|
|
49
|
-
// v2: patterns is always string[]
|
|
50
|
-
const patterns = input['patterns'];
|
|
51
|
-
if (Array.isArray(patterns) && patterns.length > 0) {
|
|
52
|
-
return patterns.join(' ');
|
|
53
|
-
}
|
|
54
|
-
// v1: pattern may be string or string[]
|
|
55
|
-
const pattern = input['pattern'];
|
|
56
|
-
if (typeof pattern === 'string' && pattern.length > 0)
|
|
57
|
-
return pattern;
|
|
58
|
-
if (Array.isArray(pattern) && pattern.length > 0) {
|
|
59
|
-
return pattern.join(' ');
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type Options, type Query, type SDKSessionInfo, type SessionMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
-
import type { ClaudeSessionEvent, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, RunClaudeSessionInput } from '../types/contracts.js';
|
|
3
|
-
import type { ToolApprovalManager } from './tool-approval-manager.js';
|
|
4
|
-
export type ClaudeSessionEventHandler = (event: ClaudeSessionEvent) => void | Promise<void>;
|
|
5
|
-
interface ClaudeAgentSdkFacade {
|
|
6
|
-
query(params: {
|
|
7
|
-
prompt: string;
|
|
8
|
-
options?: Options;
|
|
9
|
-
}): Query;
|
|
10
|
-
listSessions(options?: {
|
|
11
|
-
dir?: string;
|
|
12
|
-
}): Promise<SDKSessionInfo[]>;
|
|
13
|
-
getSessionMessages(sessionId: string, options?: {
|
|
14
|
-
dir?: string;
|
|
15
|
-
}): Promise<SessionMessage[]>;
|
|
16
|
-
}
|
|
17
|
-
export declare class ClaudeAgentSdkAdapter {
|
|
18
|
-
private readonly sdkFacade;
|
|
19
|
-
private readonly approvalManager?;
|
|
20
|
-
private readonly debugLogPath?;
|
|
21
|
-
constructor(sdkFacade?: ClaudeAgentSdkFacade, approvalManager?: ToolApprovalManager | undefined, debugLogPath?: string | undefined);
|
|
22
|
-
runSession(input: RunClaudeSessionInput, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
|
|
23
|
-
listSavedSessions(cwd?: string): Promise<ClaudeSessionSummary[]>;
|
|
24
|
-
getTranscript(sessionId: string, cwd?: string): Promise<ClaudeSessionTranscriptMessage[]>;
|
|
25
|
-
private buildOptions;
|
|
26
|
-
private logDeniedTool;
|
|
27
|
-
}
|
|
28
|
-
export {};
|