@doingdev/opencode-claude-manager-plugin 0.1.0 → 0.1.5
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
CHANGED
|
@@ -56,6 +56,16 @@ If you are testing locally, point OpenCode at the local package or plugin file u
|
|
|
56
56
|
- `claude_manager_runs` - inspect persisted manager run records
|
|
57
57
|
- `claude_manager_cleanup_run` - explicitly remove worktrees created for a prior manager run
|
|
58
58
|
|
|
59
|
+
## Plugin-provided agents and commands
|
|
60
|
+
|
|
61
|
+
When the plugin loads successfully, it also injects config entries through the OpenCode plugin `config` hook.
|
|
62
|
+
|
|
63
|
+
- Primary agent: `claude-manager`
|
|
64
|
+
- Subagent: `claude-manager-research`
|
|
65
|
+
- Commands: `/claude-metadata`, `/claude-run`, `/claude-sessions`
|
|
66
|
+
|
|
67
|
+
These are added to OpenCode config at runtime by the plugin, so they do not require separate manual `opencode.json` entries.
|
|
68
|
+
|
|
59
69
|
## Quick Start
|
|
60
70
|
|
|
61
71
|
Typical flow inside OpenCode:
|
|
@@ -88,6 +98,24 @@ The compiled plugin output is written to `dist/`.
|
|
|
88
98
|
|
|
89
99
|
This package is configured for the npm scope `@doingdev`.
|
|
90
100
|
|
|
101
|
+
This repository uses npm trusted publishing with GitHub Actions OIDC, so you do not need an `NPM_TOKEN` secret once npm is configured correctly.
|
|
102
|
+
|
|
103
|
+
Before the first automated publish, configure npm trusted publishing for `@doingdev/opencode-claude-manager-plugin` on npmjs.com:
|
|
104
|
+
|
|
105
|
+
1. Open the package settings on npmjs.com.
|
|
106
|
+
2. Go to the `Trusted Publisher` section.
|
|
107
|
+
3. Choose `GitHub Actions`.
|
|
108
|
+
4. Set the GitHub owner/user to your account or org.
|
|
109
|
+
5. Set the repository name.
|
|
110
|
+
6. Set the workflow filename to `publish.yml`.
|
|
111
|
+
7. Leave the environment name empty unless you later add a GitHub Actions environment back to the workflow.
|
|
112
|
+
|
|
113
|
+
Notes for trusted publishing:
|
|
114
|
+
|
|
115
|
+
- npm trusted publishing requires GitHub-hosted runners.
|
|
116
|
+
- npm recommends Node `22.14.0+` with npm CLI `11.5.1+`; the workflows use Node `24`.
|
|
117
|
+
- Provenance is generated automatically by npm for trusted publishes from public GitHub repositories.
|
|
118
|
+
|
|
91
119
|
Release flow:
|
|
92
120
|
|
|
93
121
|
```bash
|
|
@@ -98,10 +126,14 @@ npm run lint
|
|
|
98
126
|
npm run typecheck
|
|
99
127
|
npm run test
|
|
100
128
|
npm run build
|
|
101
|
-
npm publish --access public
|
|
102
129
|
```
|
|
103
130
|
|
|
104
|
-
|
|
131
|
+
Then publish from GitHub by either:
|
|
132
|
+
|
|
133
|
+
- creating a GitHub Release, or
|
|
134
|
+
- running the `Publish` workflow manually from the Actions tab
|
|
135
|
+
|
|
136
|
+
After trusted publishing is working, you can tighten npm package security by disabling token-based publishing for the package in npm settings.
|
|
105
137
|
|
|
106
138
|
## Limitations
|
|
107
139
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
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
2
|
import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
|
|
12
3
|
export type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerRunRecord, ManagerRunResult, ManagerTaskRequest, ManagerPromptRegistry, RunClaudeSessionInput, } from './types/contracts.js';
|
|
13
4
|
export { ClaudeManagerPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
|
|
11
2
|
export { ClaudeManagerPlugin };
|
|
12
3
|
export const plugin = ClaudeManagerPlugin;
|
|
@@ -3,16 +3,18 @@ import type { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
|
3
3
|
import type { ManagerRunRecord, ManagerRunResult, ManagerTaskRequest } from '../types/contracts.js';
|
|
4
4
|
import type { WorktreeCoordinator } from '../worktree/worktree-coordinator.js';
|
|
5
5
|
import type { TaskPlanner } from './task-planner.js';
|
|
6
|
+
export type ManagerRunProgressHandler = (run: ManagerRunRecord) => void | Promise<void>;
|
|
6
7
|
export declare class ManagerOrchestrator {
|
|
7
8
|
private readonly sessionService;
|
|
8
9
|
private readonly stateStore;
|
|
9
10
|
private readonly worktreeCoordinator;
|
|
10
11
|
private readonly taskPlanner;
|
|
11
12
|
constructor(sessionService: ClaudeSessionService, stateStore: FileRunStateStore, worktreeCoordinator: WorktreeCoordinator, taskPlanner: TaskPlanner);
|
|
12
|
-
run(request: ManagerTaskRequest): Promise<ManagerRunResult>;
|
|
13
|
+
run(request: ManagerTaskRequest, onProgress?: ManagerRunProgressHandler): Promise<ManagerRunResult>;
|
|
13
14
|
listRuns(cwd: string): Promise<ManagerRunRecord[]>;
|
|
14
15
|
getRun(cwd: string, runId: string): Promise<ManagerRunRecord | null>;
|
|
15
16
|
cleanupRunWorktrees(cwd: string, runId: string): Promise<ManagerRunRecord | null>;
|
|
16
17
|
private executePlan;
|
|
17
18
|
private patchSession;
|
|
19
|
+
private updateRunAndNotify;
|
|
18
20
|
}
|
|
@@ -11,7 +11,7 @@ export class ManagerOrchestrator {
|
|
|
11
11
|
this.worktreeCoordinator = worktreeCoordinator;
|
|
12
12
|
this.taskPlanner = taskPlanner;
|
|
13
13
|
}
|
|
14
|
-
async run(request) {
|
|
14
|
+
async run(request, onProgress) {
|
|
15
15
|
const mode = request.mode ?? 'auto';
|
|
16
16
|
const maxSubagents = Math.max(1, request.maxSubagents ?? 3);
|
|
17
17
|
const useWorktrees = request.useWorktrees ?? maxSubagents > 1;
|
|
@@ -44,6 +44,7 @@ export class ManagerOrchestrator {
|
|
|
44
44
|
sessions: plannedAssignments.map(({ plan, assignment }) => createManagedSessionRecord(plan, assignment.cwd, assignment)),
|
|
45
45
|
};
|
|
46
46
|
await this.stateStore.saveRun(runRecord);
|
|
47
|
+
await onProgress?.(runRecord);
|
|
47
48
|
const canRunInParallel = plannedAssignments.every(({ assignment }) => assignment.mode === 'git-worktree');
|
|
48
49
|
const settledResults = canRunInParallel
|
|
49
50
|
? await Promise.allSettled(plannedAssignments.map(({ plan, assignment }) => this.executePlan({
|
|
@@ -52,6 +53,7 @@ export class ManagerOrchestrator {
|
|
|
52
53
|
plan,
|
|
53
54
|
assignment,
|
|
54
55
|
includeProjectSettings,
|
|
56
|
+
onProgress,
|
|
55
57
|
})))
|
|
56
58
|
: await runSequentially(plannedAssignments.map(({ plan, assignment }) => () => this.executePlan({
|
|
57
59
|
request,
|
|
@@ -59,14 +61,15 @@ export class ManagerOrchestrator {
|
|
|
59
61
|
plan,
|
|
60
62
|
assignment,
|
|
61
63
|
includeProjectSettings,
|
|
64
|
+
onProgress,
|
|
62
65
|
})));
|
|
63
66
|
const failedResult = settledResults.find((result) => result.status === 'rejected');
|
|
64
|
-
const finalRun = await this.
|
|
67
|
+
const finalRun = await this.updateRunAndNotify(request.cwd, runId, (currentRun) => ({
|
|
65
68
|
...currentRun,
|
|
66
69
|
status: failedResult ? 'failed' : 'completed',
|
|
67
70
|
updatedAt: new Date().toISOString(),
|
|
68
71
|
finalSummary: summarizeRun(currentRun.sessions),
|
|
69
|
-
}));
|
|
72
|
+
}), onProgress);
|
|
70
73
|
return { run: finalRun };
|
|
71
74
|
}
|
|
72
75
|
listRuns(cwd) {
|
|
@@ -94,11 +97,11 @@ export class ManagerOrchestrator {
|
|
|
94
97
|
return run;
|
|
95
98
|
}
|
|
96
99
|
async executePlan(input) {
|
|
97
|
-
const { request, runId, plan, assignment, includeProjectSettings } = input;
|
|
100
|
+
const { request, runId, plan, assignment, includeProjectSettings, onProgress, } = input;
|
|
98
101
|
await this.patchSession(request.cwd, runId, plan.id, (session) => ({
|
|
99
102
|
...session,
|
|
100
103
|
status: 'running',
|
|
101
|
-
}));
|
|
104
|
+
}), onProgress);
|
|
102
105
|
try {
|
|
103
106
|
const sessionResult = await this.sessionService.runTask({
|
|
104
107
|
cwd: assignment.cwd,
|
|
@@ -112,7 +115,7 @@ export class ManagerOrchestrator {
|
|
|
112
115
|
...session,
|
|
113
116
|
claudeSessionId: event.sessionId ?? session.claudeSessionId,
|
|
114
117
|
events: [...session.events, compactEvent(event)],
|
|
115
|
-
}));
|
|
118
|
+
}), onProgress);
|
|
116
119
|
});
|
|
117
120
|
await this.patchSession(request.cwd, runId, plan.id, (session) => ({
|
|
118
121
|
...session,
|
|
@@ -121,23 +124,28 @@ export class ManagerOrchestrator {
|
|
|
121
124
|
finalText: sessionResult.finalText,
|
|
122
125
|
turns: sessionResult.turns,
|
|
123
126
|
totalCostUsd: sessionResult.totalCostUsd,
|
|
124
|
-
}));
|
|
127
|
+
}), onProgress);
|
|
125
128
|
}
|
|
126
129
|
catch (error) {
|
|
127
130
|
await this.patchSession(request.cwd, runId, plan.id, (session) => ({
|
|
128
131
|
...session,
|
|
129
132
|
status: 'failed',
|
|
130
133
|
error: error instanceof Error ? error.message : String(error),
|
|
131
|
-
}));
|
|
134
|
+
}), onProgress);
|
|
132
135
|
throw error;
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
|
-
async patchSession(cwd, runId, sessionId, update) {
|
|
136
|
-
await this.
|
|
138
|
+
async patchSession(cwd, runId, sessionId, update, onProgress) {
|
|
139
|
+
await this.updateRunAndNotify(cwd, runId, (run) => ({
|
|
137
140
|
...run,
|
|
138
141
|
updatedAt: new Date().toISOString(),
|
|
139
142
|
sessions: run.sessions.map((session) => session.id === sessionId ? update(session) : session),
|
|
140
|
-
}));
|
|
143
|
+
}), onProgress);
|
|
144
|
+
}
|
|
145
|
+
async updateRunAndNotify(cwd, runId, update, onProgress) {
|
|
146
|
+
const updatedRun = await this.stateStore.updateRun(cwd, runId, update);
|
|
147
|
+
await onProgress?.(updatedRun);
|
|
148
|
+
return updatedRun;
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
function createManagedSessionRecord(plan, cwd, assignment) {
|
|
@@ -1,8 +1,97 @@
|
|
|
1
1
|
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
2
3
|
import { getOrCreatePluginServices } from './service-factory.js';
|
|
4
|
+
const MANAGER_TOOL_IDS = [
|
|
5
|
+
'claude_manager_run',
|
|
6
|
+
'claude_manager_metadata',
|
|
7
|
+
'claude_manager_sessions',
|
|
8
|
+
'claude_manager_runs',
|
|
9
|
+
'claude_manager_cleanup_run',
|
|
10
|
+
];
|
|
3
11
|
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
4
12
|
const services = getOrCreatePluginServices(worktree);
|
|
5
13
|
return {
|
|
14
|
+
config: async (config) => {
|
|
15
|
+
config.agent ??= {};
|
|
16
|
+
config.command ??= {};
|
|
17
|
+
config.permission ??= {};
|
|
18
|
+
const globalPermissions = config.permission;
|
|
19
|
+
const managerPermissions = {
|
|
20
|
+
claude_manager_run: 'allow',
|
|
21
|
+
claude_manager_metadata: 'allow',
|
|
22
|
+
claude_manager_sessions: 'allow',
|
|
23
|
+
claude_manager_runs: 'allow',
|
|
24
|
+
claude_manager_cleanup_run: 'allow',
|
|
25
|
+
};
|
|
26
|
+
const researchPermissions = {
|
|
27
|
+
claude_manager_run: 'deny',
|
|
28
|
+
claude_manager_metadata: 'allow',
|
|
29
|
+
claude_manager_sessions: 'allow',
|
|
30
|
+
claude_manager_runs: 'allow',
|
|
31
|
+
claude_manager_cleanup_run: 'deny',
|
|
32
|
+
};
|
|
33
|
+
for (const toolId of MANAGER_TOOL_IDS) {
|
|
34
|
+
globalPermissions[toolId] ??= 'deny';
|
|
35
|
+
}
|
|
36
|
+
config.agent['claude-manager'] ??= {
|
|
37
|
+
description: 'Primary agent that manages Claude Code sessions through the bundled plugin tools.',
|
|
38
|
+
mode: 'primary',
|
|
39
|
+
color: 'accent',
|
|
40
|
+
permission: managerPermissions,
|
|
41
|
+
prompt: [
|
|
42
|
+
managerPromptRegistry.managerSystemPrompt,
|
|
43
|
+
'When Claude Code delegation is useful, prefer the claude_manager_run tool instead of simulating the work yourself.',
|
|
44
|
+
'Use claude_manager_metadata to inspect available Claude commands, skills, and hooks before making assumptions.',
|
|
45
|
+
'Use claude_manager_sessions and claude_manager_runs to inspect prior work before starting a new Claude session.',
|
|
46
|
+
].join(' '),
|
|
47
|
+
};
|
|
48
|
+
config.agent['claude-manager-research'] ??= {
|
|
49
|
+
description: 'Subagent that inspects Claude metadata, prior sessions, and manager runs without changing repository state.',
|
|
50
|
+
mode: 'subagent',
|
|
51
|
+
color: 'info',
|
|
52
|
+
permission: researchPermissions,
|
|
53
|
+
prompt: [
|
|
54
|
+
managerPromptRegistry.subagentSystemPrompt,
|
|
55
|
+
'Focus on inspection and summarization.',
|
|
56
|
+
'Prefer claude_manager_metadata, claude_manager_sessions, and claude_manager_runs over guesswork.',
|
|
57
|
+
].join(' '),
|
|
58
|
+
};
|
|
59
|
+
config.command['claude-metadata'] ??= {
|
|
60
|
+
description: 'Inspect bundled Claude commands, skills, hooks, and agents.',
|
|
61
|
+
agent: 'claude-manager-research',
|
|
62
|
+
subtask: true,
|
|
63
|
+
template: [
|
|
64
|
+
'Use claude_manager_metadata to inspect the current repository.',
|
|
65
|
+
'Summarize Claude commands, skills, hooks, discovered agents, and important config files.',
|
|
66
|
+
].join(' '),
|
|
67
|
+
};
|
|
68
|
+
config.command['claude-run'] ??= {
|
|
69
|
+
description: 'Delegate a task to Claude Code through the manager plugin.',
|
|
70
|
+
agent: 'claude-manager',
|
|
71
|
+
subtask: true,
|
|
72
|
+
template: [
|
|
73
|
+
'Call claude_manager_run immediately for the following task:',
|
|
74
|
+
'$ARGUMENTS',
|
|
75
|
+
'Avoid planning narration before the tool call. After it completes, return a concise result summary.',
|
|
76
|
+
].join('\n\n'),
|
|
77
|
+
};
|
|
78
|
+
config.command['claude-sessions'] ??= {
|
|
79
|
+
description: 'Inspect Claude session history and manager run records.',
|
|
80
|
+
agent: 'claude-manager-research',
|
|
81
|
+
subtask: true,
|
|
82
|
+
template: [
|
|
83
|
+
'Use claude_manager_sessions and claude_manager_runs to inspect recent Claude activity for this repository.',
|
|
84
|
+
'If the user provided extra arguments, use them to focus the inspection:',
|
|
85
|
+
'$ARGUMENTS',
|
|
86
|
+
].join('\n\n'),
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
'command.execute.before': async (input, output) => {
|
|
90
|
+
const commandText = buildCommandText(input.command, input.arguments);
|
|
91
|
+
if (commandText) {
|
|
92
|
+
output.parts = rewriteCommandParts(output.parts, commandText);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
6
95
|
tool: {
|
|
7
96
|
claude_manager_run: tool({
|
|
8
97
|
description: 'Delegate a task to Claude Code with optional subagents and worktrees.',
|
|
@@ -20,6 +109,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
20
109
|
task: args.task,
|
|
21
110
|
mode: args.mode,
|
|
22
111
|
});
|
|
112
|
+
let lastProgressSignature = '';
|
|
23
113
|
const result = await services.manager.run({
|
|
24
114
|
cwd: args.cwd ?? context.worktree,
|
|
25
115
|
task: args.task,
|
|
@@ -28,6 +118,14 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
28
118
|
useWorktrees: args.useWorktrees,
|
|
29
119
|
includeProjectSettings: args.includeProjectSettings,
|
|
30
120
|
model: args.model,
|
|
121
|
+
}, async (run) => {
|
|
122
|
+
const progressView = buildRunProgressView(run);
|
|
123
|
+
const signature = JSON.stringify(progressView);
|
|
124
|
+
if (signature === lastProgressSignature) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
lastProgressSignature = signature;
|
|
128
|
+
context.metadata(progressView);
|
|
31
129
|
});
|
|
32
130
|
return JSON.stringify(result.run, null, 2);
|
|
33
131
|
},
|
|
@@ -100,3 +198,112 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
100
198
|
function annotateToolRun(context, title, metadata) {
|
|
101
199
|
context.metadata({ title, metadata });
|
|
102
200
|
}
|
|
201
|
+
function buildCommandText(command, rawArguments) {
|
|
202
|
+
const argumentsText = rawArguments.trim();
|
|
203
|
+
if (command === 'claude-run') {
|
|
204
|
+
return [
|
|
205
|
+
'Call `claude_manager_run` immediately.',
|
|
206
|
+
argumentsText
|
|
207
|
+
? `Task: ${argumentsText}`
|
|
208
|
+
: 'Task: Inspect the current repository and wait for follow-up instructions.',
|
|
209
|
+
'Do not add planning text before the tool call. After it completes, give a short result summary.',
|
|
210
|
+
].join('\n\n');
|
|
211
|
+
}
|
|
212
|
+
if (command === 'claude-metadata') {
|
|
213
|
+
return [
|
|
214
|
+
'Call `claude_manager_metadata` immediately for the current repository.',
|
|
215
|
+
argumentsText ? `Focus: ${argumentsText}` : '',
|
|
216
|
+
'Then summarize the discovered Claude commands, skills, hooks, agents, and config files briefly.',
|
|
217
|
+
]
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
.join('\n\n');
|
|
220
|
+
}
|
|
221
|
+
if (command === 'claude-sessions') {
|
|
222
|
+
return [
|
|
223
|
+
'Call `claude_manager_sessions` and `claude_manager_runs` immediately for the current repository.',
|
|
224
|
+
argumentsText ? `Focus: ${argumentsText}` : '',
|
|
225
|
+
'Then summarize the most relevant recent Claude activity briefly.',
|
|
226
|
+
]
|
|
227
|
+
.filter(Boolean)
|
|
228
|
+
.join('\n\n');
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function rewriteCommandParts(parts, text) {
|
|
233
|
+
let hasRewrittenText = false;
|
|
234
|
+
const rewrittenParts = parts.map((part) => {
|
|
235
|
+
if (part.type !== 'text' || hasRewrittenText) {
|
|
236
|
+
return part;
|
|
237
|
+
}
|
|
238
|
+
hasRewrittenText = true;
|
|
239
|
+
return {
|
|
240
|
+
...part,
|
|
241
|
+
text,
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
return rewrittenParts;
|
|
245
|
+
}
|
|
246
|
+
function buildRunProgressView(run) {
|
|
247
|
+
const completed = run.sessions.filter((session) => session.status === 'completed').length;
|
|
248
|
+
const failed = run.sessions.filter((session) => session.status === 'failed').length;
|
|
249
|
+
const running = run.sessions.filter((session) => session.status === 'running').length;
|
|
250
|
+
const pending = run.sessions.filter((session) => session.status === 'pending').length;
|
|
251
|
+
const total = run.sessions.length;
|
|
252
|
+
return {
|
|
253
|
+
title: buildRunProgressTitle(run, { completed, failed, running, total }),
|
|
254
|
+
metadata: {
|
|
255
|
+
runId: run.id,
|
|
256
|
+
status: run.status,
|
|
257
|
+
progress: `${completed}/${total} completed`,
|
|
258
|
+
active: running,
|
|
259
|
+
pending,
|
|
260
|
+
failed,
|
|
261
|
+
sessions: run.sessions.map(formatSessionActivity),
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function buildRunProgressTitle(run, counts) {
|
|
266
|
+
const suffix = `(${counts.completed}/${counts.total} complete` +
|
|
267
|
+
(counts.running > 0 ? `, ${counts.running} active` : '') +
|
|
268
|
+
(counts.failed > 0 ? `, ${counts.failed} failed` : '') +
|
|
269
|
+
')';
|
|
270
|
+
if (run.status === 'completed') {
|
|
271
|
+
return `Claude manager completed ${suffix}`;
|
|
272
|
+
}
|
|
273
|
+
if (run.status === 'failed') {
|
|
274
|
+
return `Claude manager failed ${suffix}`;
|
|
275
|
+
}
|
|
276
|
+
if (counts.running > 0) {
|
|
277
|
+
return `Claude manager running ${suffix}`;
|
|
278
|
+
}
|
|
279
|
+
return `Claude manager queued ${suffix}`;
|
|
280
|
+
}
|
|
281
|
+
function formatSessionActivity(session) {
|
|
282
|
+
const parts = [session.title, session.status];
|
|
283
|
+
if (session.claudeSessionId) {
|
|
284
|
+
parts.push(session.claudeSessionId);
|
|
285
|
+
}
|
|
286
|
+
const latestEvent = findLatestDisplayEvent(session.events);
|
|
287
|
+
if (latestEvent) {
|
|
288
|
+
parts.push(`${latestEvent.type}: ${truncateForDisplay(latestEvent.text, 120)}`);
|
|
289
|
+
}
|
|
290
|
+
else if (session.finalText) {
|
|
291
|
+
parts.push(truncateForDisplay(session.finalText, 120));
|
|
292
|
+
}
|
|
293
|
+
else if (session.error) {
|
|
294
|
+
parts.push(truncateForDisplay(session.error, 120));
|
|
295
|
+
}
|
|
296
|
+
return parts.join(' | ');
|
|
297
|
+
}
|
|
298
|
+
function findLatestDisplayEvent(events) {
|
|
299
|
+
return [...events]
|
|
300
|
+
.reverse()
|
|
301
|
+
.find((event) => event.type !== 'partial' && Boolean(event.text.trim()));
|
|
302
|
+
}
|
|
303
|
+
function truncateForDisplay(text, maxLength) {
|
|
304
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
305
|
+
if (normalized.length <= maxLength) {
|
|
306
|
+
return normalized;
|
|
307
|
+
}
|
|
308
|
+
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
309
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doingdev/opencode-claude-manager-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "OpenCode plugin that orchestrates Claude Code sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
31
31
|
"lint": "eslint .",
|
|
32
32
|
"format": "prettier --write .",
|
|
33
|
-
"test": "vitest run"
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"release": "npm run build && npm version patch && npm publish"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=22.0.0"
|