@doingdev/opencode-claude-manager-plugin 0.1.5 → 0.1.7

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
@@ -50,7 +50,7 @@ If you are testing locally, point OpenCode at the local package or plugin file u
50
50
 
51
51
  ## OpenCode tools
52
52
 
53
- - `claude_manager_run` - run a task through Claude with optional splitting and worktrees
53
+ - `claude_manager_run` - run a task through Claude with optional splitting and worktrees; returns a compact output summary and a `runId` for deeper inspection
54
54
  - `claude_manager_metadata` - inspect available Claude commands, skills, hooks, and settings
55
55
  - `claude_manager_sessions` - list Claude sessions or inspect a saved transcript
56
56
  - `claude_manager_runs` - inspect persisted manager run records
@@ -136,10 +136,14 @@ function normalizeSdkMessage(message) {
136
136
  };
137
137
  }
138
138
  if (message.type === 'stream_event') {
139
+ const partialText = extractPartialEventText(message.event);
140
+ if (!partialText) {
141
+ return null;
142
+ }
139
143
  return {
140
144
  type: 'partial',
141
145
  sessionId,
142
- text: extractPartialEventText(message.event),
146
+ text: partialText,
143
147
  rawType: message.type,
144
148
  };
145
149
  }
@@ -191,21 +195,21 @@ function normalizeSdkMessage(message) {
191
195
  }
192
196
  function extractPartialEventText(event) {
193
197
  if (!event || typeof event !== 'object') {
194
- return 'stream_event';
198
+ return null;
195
199
  }
196
200
  const eventRecord = event;
197
- const eventType = typeof eventRecord.type === 'string' ? eventRecord.type : 'stream_event';
198
201
  const delta = eventRecord.delta;
199
202
  if (delta && typeof delta === 'object') {
200
203
  const deltaRecord = delta;
201
- if (typeof deltaRecord.text === 'string') {
204
+ if (typeof deltaRecord.text === 'string' && deltaRecord.text.length > 0) {
202
205
  return deltaRecord.text;
203
206
  }
204
- if (typeof deltaRecord.partial_json === 'string') {
207
+ if (typeof deltaRecord.partial_json === 'string' &&
208
+ deltaRecord.partial_json.length > 0) {
205
209
  return deltaRecord.partial_json;
206
210
  }
207
211
  }
208
- return eventType;
212
+ return null;
209
213
  }
210
214
  function extractText(payload) {
211
215
  if (typeof payload === 'string') {
@@ -1,2 +1,4 @@
1
1
  import { type Plugin } from '@opencode-ai/plugin';
2
+ import type { ManagerRunRecord } from '../types/contracts.js';
2
3
  export declare const ClaudeManagerPlugin: Plugin;
4
+ export declare function formatManagerRunToolResult(run: ManagerRunRecord): string;
@@ -8,6 +8,7 @@ const MANAGER_TOOL_IDS = [
8
8
  'claude_manager_runs',
9
9
  'claude_manager_cleanup_run',
10
10
  ];
11
+ const RESTRICTED_AGENT_TOOLS = buildRestrictedToolMap(MANAGER_TOOL_IDS);
11
12
  export const ClaudeManagerPlugin = async ({ worktree }) => {
12
13
  const services = getOrCreatePluginServices(worktree);
13
14
  return {
@@ -37,6 +38,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
37
38
  description: 'Primary agent that manages Claude Code sessions through the bundled plugin tools.',
38
39
  mode: 'primary',
39
40
  color: 'accent',
41
+ tools: RESTRICTED_AGENT_TOOLS,
40
42
  permission: managerPermissions,
41
43
  prompt: [
42
44
  managerPromptRegistry.managerSystemPrompt,
@@ -49,6 +51,11 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
49
51
  description: 'Subagent that inspects Claude metadata, prior sessions, and manager runs without changing repository state.',
50
52
  mode: 'subagent',
51
53
  color: 'info',
54
+ tools: {
55
+ ...RESTRICTED_AGENT_TOOLS,
56
+ claude_manager_run: false,
57
+ claude_manager_cleanup_run: false,
58
+ },
52
59
  permission: researchPermissions,
53
60
  prompt: [
54
61
  managerPromptRegistry.subagentSystemPrompt,
@@ -127,7 +134,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
127
134
  lastProgressSignature = signature;
128
135
  context.metadata(progressView);
129
136
  });
130
- return JSON.stringify(result.run, null, 2);
137
+ return formatManagerRunToolResult(result.run);
131
138
  },
132
139
  }),
133
140
  claude_manager_metadata: tool({
@@ -243,6 +250,39 @@ function rewriteCommandParts(parts, text) {
243
250
  });
244
251
  return rewrittenParts;
245
252
  }
253
+ function buildRestrictedToolMap(toolIds) {
254
+ const tools = { '*': false };
255
+ for (const toolId of toolIds) {
256
+ tools[toolId] = true;
257
+ }
258
+ return tools;
259
+ }
260
+ export function formatManagerRunToolResult(run) {
261
+ const finalSummary = run.finalSummary ?? summarizeSessionOutputs(run.sessions);
262
+ const output = run.sessions.length === 1
263
+ ? resolveSessionOutput(run.sessions[0])
264
+ : finalSummary;
265
+ return JSON.stringify({
266
+ runId: run.id,
267
+ status: run.status,
268
+ output,
269
+ finalSummary,
270
+ sessions: run.sessions.map((session) => ({
271
+ title: session.title,
272
+ status: session.status,
273
+ output: resolveSessionOutput(session),
274
+ claudeSessionId: session.claudeSessionId,
275
+ worktreeMode: session.worktreeMode,
276
+ branchName: session.branchName,
277
+ turns: session.turns,
278
+ totalCostUsd: session.totalCostUsd,
279
+ })),
280
+ inspectRun: {
281
+ tool: 'claude_manager_runs',
282
+ runId: run.id,
283
+ },
284
+ }, null, 2);
285
+ }
246
286
  function buildRunProgressView(run) {
247
287
  const completed = run.sessions.filter((session) => session.status === 'completed').length;
248
288
  const failed = run.sessions.filter((session) => session.status === 'failed').length;
@@ -284,18 +324,26 @@ function formatSessionActivity(session) {
284
324
  parts.push(session.claudeSessionId);
285
325
  }
286
326
  const latestEvent = findLatestDisplayEvent(session.events);
287
- if (latestEvent) {
288
- parts.push(`${latestEvent.type}: ${truncateForDisplay(latestEvent.text, 120)}`);
289
- }
290
- else if (session.finalText) {
327
+ if (session.finalText) {
291
328
  parts.push(truncateForDisplay(session.finalText, 120));
292
329
  }
293
330
  else if (session.error) {
294
331
  parts.push(truncateForDisplay(session.error, 120));
295
332
  }
333
+ else if (latestEvent) {
334
+ parts.push(`${latestEvent.type}: ${truncateForDisplay(latestEvent.text, 120)}`);
335
+ }
296
336
  return parts.join(' | ');
297
337
  }
298
338
  function findLatestDisplayEvent(events) {
339
+ const reversedEvents = [...events].reverse();
340
+ const preferredEvent = reversedEvents.find((event) => (event.type === 'result' ||
341
+ event.type === 'error' ||
342
+ event.type === 'assistant') &&
343
+ Boolean(event.text.trim()));
344
+ if (preferredEvent) {
345
+ return preferredEvent;
346
+ }
299
347
  return [...events]
300
348
  .reverse()
301
349
  .find((event) => event.type !== 'partial' && Boolean(event.text.trim()));
@@ -307,3 +355,15 @@ function truncateForDisplay(text, maxLength) {
307
355
  }
308
356
  return `${normalized.slice(0, maxLength - 3)}...`;
309
357
  }
358
+ function summarizeSessionOutputs(sessions) {
359
+ return sessions
360
+ .map((session) => `${session.title}: ${resolveSessionOutput(session)}`)
361
+ .join('\n');
362
+ }
363
+ function resolveSessionOutput(session) {
364
+ const latestEvent = findLatestDisplayEvent(session.events);
365
+ return (session.finalText?.trim() ||
366
+ session.error?.trim() ||
367
+ latestEvent?.text.trim() ||
368
+ session.status);
369
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",