@doingdev/opencode-claude-manager-plugin 0.1.16 → 0.1.17

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.
@@ -245,6 +245,36 @@ function normalizeSdkMessages(message, includePartials) {
245
245
  if (message.type === 'user') {
246
246
  return normalizeUserSdkMessage(message, sessionId);
247
247
  }
248
+ if (message.type === 'tool_progress') {
249
+ const toolName = 'tool_name' in message && typeof message.tool_name === 'string'
250
+ ? message.tool_name
251
+ : 'tool';
252
+ const elapsed = 'elapsed_time_seconds' in message &&
253
+ typeof message.elapsed_time_seconds === 'number'
254
+ ? message.elapsed_time_seconds
255
+ : 0;
256
+ return [
257
+ {
258
+ type: 'tool_progress',
259
+ sessionId,
260
+ text: JSON.stringify({ name: toolName, elapsed }),
261
+ rawType: message.type,
262
+ },
263
+ ];
264
+ }
265
+ if (message.type === 'tool_use_summary') {
266
+ const summary = 'summary' in message && typeof message.summary === 'string'
267
+ ? message.summary
268
+ : '';
269
+ return [
270
+ {
271
+ type: 'tool_summary',
272
+ sessionId,
273
+ text: summary,
274
+ rawType: message.type,
275
+ },
276
+ ];
277
+ }
248
278
  return [
249
279
  {
250
280
  type: 'system',
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Plugin } from '@opencode-ai/plugin';
2
2
  import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
3
- export type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, ActiveSessionState, ContextWarningLevel, LiveTailEvent, ToolOutputPreview, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
3
+ export type { ClaudeCapabilitySnapshot, ClaudeMetadataSnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, ActiveSessionState, ContextWarningLevel, SessionMode, LiveTailEvent, ToolOutputPreview, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
4
4
  export { SessionLiveTailer } from './claude/session-live-tailer.js';
5
5
  export { ClaudeManagerPlugin };
6
6
  export declare const plugin: Plugin;
@@ -19,6 +19,7 @@ export declare class PersistentManager {
19
19
  */
20
20
  sendMessage(cwd: string, message: string, options?: {
21
21
  model?: string;
22
+ mode?: 'plan' | 'free';
22
23
  abortSignal?: AbortSignal;
23
24
  }, onEvent?: ClaudeSessionEventHandler): Promise<{
24
25
  sessionId: string | undefined;
@@ -1,12 +1,16 @@
1
1
  import type { ClaudeAgentSdkAdapter, ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapter.js';
2
- import type { ClaudeSessionRunResult, SessionContextSnapshot } from '../types/contracts.js';
2
+ import type { ClaudeSessionRunResult, SessionContextSnapshot, SessionMode } from '../types/contracts.js';
3
3
  import type { ContextTracker } from './context-tracker.js';
4
4
  export declare class SessionController {
5
5
  private readonly sdkAdapter;
6
6
  private readonly contextTracker;
7
7
  private readonly sessionPrompt;
8
+ private readonly modePrefixes;
8
9
  private activeSessionId;
9
- constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string);
10
+ constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string, modePrefixes?: {
11
+ plan: string;
12
+ free: string;
13
+ });
10
14
  get isActive(): boolean;
11
15
  get sessionId(): string | null;
12
16
  /**
@@ -16,6 +20,7 @@ export declare class SessionController {
16
20
  sendMessage(cwd: string, message: string, options?: {
17
21
  model?: string;
18
22
  effort?: 'low' | 'medium' | 'high' | 'max';
23
+ mode?: SessionMode;
19
24
  settingSources?: Array<'user' | 'project' | 'local'>;
20
25
  abortSignal?: AbortSignal;
21
26
  }, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
@@ -5,11 +5,16 @@ export class SessionController {
5
5
  sdkAdapter;
6
6
  contextTracker;
7
7
  sessionPrompt;
8
+ modePrefixes;
8
9
  activeSessionId = null;
9
- constructor(sdkAdapter, contextTracker, sessionPrompt) {
10
+ constructor(sdkAdapter, contextTracker, sessionPrompt, modePrefixes = {
11
+ plan: '',
12
+ free: '',
13
+ }) {
10
14
  this.sdkAdapter = sdkAdapter;
11
15
  this.contextTracker = contextTracker;
12
16
  this.sessionPrompt = sessionPrompt;
17
+ this.modePrefixes = modePrefixes;
13
18
  }
14
19
  get isActive() {
15
20
  return this.activeSessionId !== null;
@@ -22,11 +27,14 @@ export class SessionController {
22
27
  * Returns the session result including usage data.
23
28
  */
24
29
  async sendMessage(cwd, message, options, onEvent) {
30
+ const mode = options?.mode ?? 'free';
31
+ const prefix = this.modePrefixes[mode];
32
+ const prompt = prefix ? `${prefix}\n\n${message}` : message;
25
33
  const input = {
26
34
  cwd,
27
- prompt: message,
35
+ prompt,
28
36
  persistSession: true,
29
- permissionMode: 'acceptEdits',
37
+ permissionMode: mode === 'plan' ? 'plan' : 'acceptEdits',
30
38
  includePartialMessages: true,
31
39
  model: options?.model,
32
40
  effort: options?.effort,
@@ -107,10 +107,13 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
107
107
  claude_manager_send: tool({
108
108
  description: 'Send a message to the persistent Claude Code session. ' +
109
109
  'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
110
- 'Returns the assistant response and current context health snapshot.',
110
+ 'Returns the assistant response and current context health snapshot. ' +
111
+ 'Use mode "plan" for read-only investigation and planning (no edits), ' +
112
+ 'or "free" (default) for normal execution with edit permissions.',
111
113
  args: {
112
114
  message: tool.schema.string().min(1),
113
115
  model: tool.schema.string().optional(),
116
+ mode: tool.schema.enum(['plan', 'free']).default('free'),
114
117
  cwd: tool.schema.string().optional(),
115
118
  },
116
119
  async execute(args, context) {
@@ -130,7 +133,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
130
133
  });
131
134
  let turnsSoFar = 0;
132
135
  let costSoFar = 0;
133
- const result = await services.manager.sendMessage(cwd, args.message, { model: args.model, abortSignal: context.abort }, (event) => {
136
+ const result = await services.manager.sendMessage(cwd, args.message, { model: args.model, mode: args.mode, abortSignal: context.abort }, (event) => {
134
137
  if (event.turns !== undefined) {
135
138
  turnsSoFar = event.turns;
136
139
  }
@@ -189,6 +192,66 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
189
192
  },
190
193
  });
191
194
  }
195
+ else if (event.type === 'user') {
196
+ const preview = event.text.length > 200
197
+ ? event.text.slice(0, 200) + '...'
198
+ : event.text;
199
+ context.metadata({
200
+ title: `Claude Code: Tool result (${turnsSoFar} turns, ${costLabel})`,
201
+ metadata: {
202
+ sessionId: event.sessionId,
203
+ type: event.type,
204
+ output: preview,
205
+ },
206
+ });
207
+ }
208
+ else if (event.type === 'tool_progress') {
209
+ let toolName = 'tool';
210
+ let elapsed = 0;
211
+ try {
212
+ const parsed = JSON.parse(event.text);
213
+ toolName = parsed.name ?? 'tool';
214
+ elapsed = parsed.elapsed ?? 0;
215
+ }
216
+ catch {
217
+ // ignore
218
+ }
219
+ context.metadata({
220
+ title: `Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}... (${turnsSoFar} turns, ${costLabel})`,
221
+ metadata: {
222
+ sessionId: event.sessionId,
223
+ type: event.type,
224
+ tool: toolName,
225
+ elapsed,
226
+ },
227
+ });
228
+ }
229
+ else if (event.type === 'tool_summary') {
230
+ const summary = event.text.length > 200
231
+ ? event.text.slice(0, 200) + '...'
232
+ : event.text;
233
+ context.metadata({
234
+ title: `Claude Code: Tool done (${turnsSoFar} turns, ${costLabel})`,
235
+ metadata: {
236
+ sessionId: event.sessionId,
237
+ type: event.type,
238
+ summary,
239
+ },
240
+ });
241
+ }
242
+ else if (event.type === 'partial') {
243
+ const delta = event.text.length > 200
244
+ ? event.text.slice(0, 200) + '...'
245
+ : event.text;
246
+ context.metadata({
247
+ title: `Claude Code: Writing... (${turnsSoFar} turns, ${costLabel})`,
248
+ metadata: {
249
+ sessionId: event.sessionId,
250
+ type: event.type,
251
+ delta,
252
+ },
253
+ });
254
+ }
192
255
  else if (event.type === 'error') {
193
256
  context.metadata({
194
257
  title: `Claude Code: Error`,
@@ -22,7 +22,7 @@ export function getOrCreatePluginServices(worktree) {
22
22
  const metadataService = new ClaudeMetadataService(new RepoClaudeConfigReader(), sdkAdapter);
23
23
  const sessionService = new ClaudeSessionService(sdkAdapter, metadataService);
24
24
  const contextTracker = new ContextTracker();
25
- const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.claudeCodeSessionPrompt);
25
+ const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.claudeCodeSessionPrompt, managerPromptRegistry.modePrefixes);
26
26
  const gitOps = new GitOperations(worktree);
27
27
  const stateStore = new FileRunStateStore();
28
28
  const transcriptStore = new TranscriptStore();
@@ -118,6 +118,16 @@ export const managerPromptRegistry = {
118
118
  '- Report blockers immediately with specifics: file, line, error message.',
119
119
  '- If a task is partially complete, state exactly what remains.',
120
120
  ].join('\n'),
121
+ modePrefixes: {
122
+ plan: [
123
+ '[PLAN MODE] You are in read-only planning mode. Do NOT create or edit any files.',
124
+ 'Use read, grep, glob, and search tools only.',
125
+ 'Analyze the codebase and produce a detailed implementation plan:',
126
+ 'files to change, functions to modify, new files to create, test strategy,',
127
+ 'and potential risks. End with a numbered step-by-step plan.',
128
+ ].join(' '),
129
+ free: '',
130
+ },
121
131
  contextWarnings: {
122
132
  moderate: 'Session context is filling up ({percent}% estimated). Consider whether a fresh session would be more efficient.',
123
133
  high: 'Session context is heavy ({percent}% estimated, {turns} turns, ${cost}). Start a new session or compact first.',
@@ -1,6 +1,10 @@
1
1
  export interface ManagerPromptRegistry {
2
2
  managerSystemPrompt: string;
3
3
  claudeCodeSessionPrompt: string;
4
+ modePrefixes: {
5
+ plan: string;
6
+ free: string;
7
+ };
4
8
  contextWarnings: {
5
9
  moderate: string;
6
10
  high: string;
@@ -8,6 +12,7 @@ export interface ManagerPromptRegistry {
8
12
  };
9
13
  }
10
14
  export type ClaudeSettingSource = 'user' | 'project' | 'local';
15
+ export type SessionMode = 'plan' | 'free';
11
16
  export interface ClaudeCommandMetadata {
12
17
  name: string;
13
18
  description: string;
@@ -44,7 +49,7 @@ export interface ClaudeMetadataSnapshot {
44
49
  settingsPaths: string[];
45
50
  }
46
51
  export interface ClaudeSessionEvent {
47
- type: 'init' | 'assistant' | 'partial' | 'user' | 'tool_call' | 'status' | 'system' | 'result' | 'error';
52
+ type: 'init' | 'assistant' | 'partial' | 'user' | 'tool_call' | 'tool_progress' | 'tool_summary' | 'status' | 'system' | 'result' | 'error';
48
53
  sessionId?: string;
49
54
  text: string;
50
55
  turns?: number;
@@ -58,6 +63,7 @@ export interface RunClaudeSessionInput {
58
63
  systemPrompt?: string;
59
64
  model?: string;
60
65
  effort?: 'low' | 'medium' | 'high' | 'max';
66
+ mode?: SessionMode;
61
67
  permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk';
62
68
  allowedTools?: string[];
63
69
  disallowedTools?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",