@doingdev/opencode-claude-manager-plugin 0.1.15 → 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.
- package/dist/claude/claude-agent-sdk-adapter.js +31 -0
- package/dist/claude/session-live-tailer.d.ts +51 -0
- package/dist/claude/session-live-tailer.js +269 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/manager/persistent-manager.d.ts +1 -0
- package/dist/manager/session-controller.d.ts +8 -2
- package/dist/manager/session-controller.js +15 -4
- package/dist/plugin/claude-manager.plugin.js +76 -2
- package/dist/plugin/service-factory.d.ts +2 -0
- package/dist/plugin/service-factory.js +4 -1
- package/dist/prompts/registry.js +122 -31
- package/dist/types/contracts.d.ts +20 -1
- package/package.json +1 -1
|
@@ -151,6 +151,7 @@ export class ClaudeAgentSdkAdapter {
|
|
|
151
151
|
settingSources: input.settingSources,
|
|
152
152
|
maxTurns: input.maxTurns,
|
|
153
153
|
model: input.model,
|
|
154
|
+
effort: input.effort,
|
|
154
155
|
permissionMode: input.permissionMode ?? 'acceptEdits',
|
|
155
156
|
systemPrompt: input.systemPrompt
|
|
156
157
|
? { type: 'preset', preset: 'claude_code', append: input.systemPrompt }
|
|
@@ -244,6 +245,36 @@ function normalizeSdkMessages(message, includePartials) {
|
|
|
244
245
|
if (message.type === 'user') {
|
|
245
246
|
return normalizeUserSdkMessage(message, sessionId);
|
|
246
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
|
+
}
|
|
247
278
|
return [
|
|
248
279
|
{
|
|
249
280
|
type: 'system',
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LiveTailEvent, ToolOutputPreview } from '../types/contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tails Claude Code session JSONL files for live tool output.
|
|
4
|
+
*
|
|
5
|
+
* The SDK streams high-level events (assistant text, tool_call summaries, results)
|
|
6
|
+
* but does not expose the raw tool output that Claude Code writes to the JSONL
|
|
7
|
+
* transcript on disk. This service fills that gap by polling the file for new
|
|
8
|
+
* lines, parsing each one, and forwarding parsed events to a caller-supplied
|
|
9
|
+
* callback.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SessionLiveTailer {
|
|
12
|
+
private activeTails;
|
|
13
|
+
/**
|
|
14
|
+
* Locate the JSONL file for a session.
|
|
15
|
+
*
|
|
16
|
+
* Claude Code stores transcripts at:
|
|
17
|
+
* ~/.claude/projects/<sanitized-cwd>/sessions/<session-id>.jsonl
|
|
18
|
+
*
|
|
19
|
+
* The `<sanitized-cwd>` folder name is an internal implementation detail that
|
|
20
|
+
* can change across Claude Code versions, so we search all project directories
|
|
21
|
+
* for the session file rather than attempting to replicate the sanitisation.
|
|
22
|
+
*/
|
|
23
|
+
findSessionFile(sessionId: string, cwd?: string): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Check whether we can locate a JSONL file for the given session.
|
|
26
|
+
*/
|
|
27
|
+
sessionFileExists(sessionId: string, cwd?: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Poll a session's JSONL file for new lines and emit parsed events.
|
|
30
|
+
*
|
|
31
|
+
* Returns a stop function. The caller **must** invoke it when tailing is no
|
|
32
|
+
* longer needed (e.g. when the session completes or the tool is interrupted).
|
|
33
|
+
*/
|
|
34
|
+
startTailing(sessionId: string, cwd: string | undefined, onEvent: (event: LiveTailEvent) => void, pollIntervalMs?: number): () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Stop tailing a specific session.
|
|
37
|
+
*/
|
|
38
|
+
stopTailing(sessionId: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Stop all active tails (cleanup on shutdown).
|
|
41
|
+
*/
|
|
42
|
+
stopAll(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Read the last N lines from a session JSONL file.
|
|
45
|
+
*/
|
|
46
|
+
getLastLines(sessionId: string, cwd: string | undefined, lineCount?: number): Promise<string[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Extract a preview of recent tool output from the tail of a session file.
|
|
49
|
+
*/
|
|
50
|
+
getToolOutputPreview(sessionId: string, cwd: string | undefined, maxEntries?: number): Promise<ToolOutputPreview[]>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { createReadStream, existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
/**
|
|
6
|
+
* Tails Claude Code session JSONL files for live tool output.
|
|
7
|
+
*
|
|
8
|
+
* The SDK streams high-level events (assistant text, tool_call summaries, results)
|
|
9
|
+
* but does not expose the raw tool output that Claude Code writes to the JSONL
|
|
10
|
+
* transcript on disk. This service fills that gap by polling the file for new
|
|
11
|
+
* lines, parsing each one, and forwarding parsed events to a caller-supplied
|
|
12
|
+
* callback.
|
|
13
|
+
*/
|
|
14
|
+
export class SessionLiveTailer {
|
|
15
|
+
activeTails = new Map();
|
|
16
|
+
// ── Path discovery ──────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Locate the JSONL file for a session.
|
|
19
|
+
*
|
|
20
|
+
* Claude Code stores transcripts at:
|
|
21
|
+
* ~/.claude/projects/<sanitized-cwd>/sessions/<session-id>.jsonl
|
|
22
|
+
*
|
|
23
|
+
* The `<sanitized-cwd>` folder name is an internal implementation detail that
|
|
24
|
+
* can change across Claude Code versions, so we search all project directories
|
|
25
|
+
* for the session file rather than attempting to replicate the sanitisation.
|
|
26
|
+
*/
|
|
27
|
+
findSessionFile(sessionId, cwd) {
|
|
28
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
29
|
+
if (!existsSync(projectsDir)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// If cwd is provided, try the sanitised form first (best-effort fast path).
|
|
33
|
+
if (cwd) {
|
|
34
|
+
const sanitised = cwd.replace(/\//g, '-');
|
|
35
|
+
const candidate = path.join(projectsDir, sanitised, 'sessions', `${sessionId}.jsonl`);
|
|
36
|
+
if (existsSync(candidate)) {
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Fall back to scanning all project directories.
|
|
41
|
+
try {
|
|
42
|
+
for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
43
|
+
if (!entry.isDirectory())
|
|
44
|
+
continue;
|
|
45
|
+
const candidate = path.join(projectsDir, entry.name, 'sessions', `${sessionId}.jsonl`);
|
|
46
|
+
if (existsSync(candidate)) {
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Permission denied or similar — nothing we can do.
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check whether we can locate a JSONL file for the given session.
|
|
58
|
+
*/
|
|
59
|
+
sessionFileExists(sessionId, cwd) {
|
|
60
|
+
return this.findSessionFile(sessionId, cwd) !== null;
|
|
61
|
+
}
|
|
62
|
+
// ── Live tailing ────────────────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* Poll a session's JSONL file for new lines and emit parsed events.
|
|
65
|
+
*
|
|
66
|
+
* Returns a stop function. The caller **must** invoke it when tailing is no
|
|
67
|
+
* longer needed (e.g. when the session completes or the tool is interrupted).
|
|
68
|
+
*/
|
|
69
|
+
startTailing(sessionId, cwd, onEvent, pollIntervalMs = 150) {
|
|
70
|
+
const filePath = this.findSessionFile(sessionId, cwd);
|
|
71
|
+
if (!filePath) {
|
|
72
|
+
onEvent({
|
|
73
|
+
type: 'error',
|
|
74
|
+
sessionId,
|
|
75
|
+
error: `Session JSONL not found for ${sessionId}`,
|
|
76
|
+
});
|
|
77
|
+
return () => { };
|
|
78
|
+
}
|
|
79
|
+
let offset = statSync(filePath).size;
|
|
80
|
+
let buffer = '';
|
|
81
|
+
let reading = false; // guard against overlapping reads
|
|
82
|
+
const interval = setInterval(() => {
|
|
83
|
+
if (reading)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const currentSize = statSync(filePath).size;
|
|
87
|
+
if (currentSize < offset) {
|
|
88
|
+
// File was truncated (rotation / compaction) — reset.
|
|
89
|
+
offset = 0;
|
|
90
|
+
buffer = '';
|
|
91
|
+
}
|
|
92
|
+
if (currentSize <= offset)
|
|
93
|
+
return;
|
|
94
|
+
reading = true;
|
|
95
|
+
const stream = createReadStream(filePath, {
|
|
96
|
+
start: offset,
|
|
97
|
+
end: currentSize - 1,
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
});
|
|
100
|
+
let chunk = '';
|
|
101
|
+
stream.on('data', (data) => {
|
|
102
|
+
chunk += data;
|
|
103
|
+
});
|
|
104
|
+
stream.on('end', () => {
|
|
105
|
+
reading = false;
|
|
106
|
+
offset = currentSize;
|
|
107
|
+
buffer += chunk;
|
|
108
|
+
const lines = buffer.split('\n');
|
|
109
|
+
// Keep the last (possibly incomplete) segment in the buffer.
|
|
110
|
+
buffer = lines.pop() ?? '';
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
if (!trimmed)
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(trimmed);
|
|
117
|
+
onEvent({
|
|
118
|
+
type: 'line',
|
|
119
|
+
sessionId,
|
|
120
|
+
data: parsed,
|
|
121
|
+
rawLine: trimmed,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
onEvent({
|
|
126
|
+
type: 'line',
|
|
127
|
+
sessionId,
|
|
128
|
+
data: null,
|
|
129
|
+
rawLine: trimmed,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
stream.on('error', (err) => {
|
|
135
|
+
reading = false;
|
|
136
|
+
onEvent({
|
|
137
|
+
type: 'error',
|
|
138
|
+
sessionId,
|
|
139
|
+
error: err.message,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
reading = false;
|
|
145
|
+
onEvent({
|
|
146
|
+
type: 'error',
|
|
147
|
+
sessionId,
|
|
148
|
+
error: err instanceof Error ? err.message : String(err),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}, pollIntervalMs);
|
|
152
|
+
this.activeTails.set(sessionId, interval);
|
|
153
|
+
return () => this.stopTailing(sessionId);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Stop tailing a specific session.
|
|
157
|
+
*/
|
|
158
|
+
stopTailing(sessionId) {
|
|
159
|
+
const interval = this.activeTails.get(sessionId);
|
|
160
|
+
if (interval) {
|
|
161
|
+
clearInterval(interval);
|
|
162
|
+
this.activeTails.delete(sessionId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Stop all active tails (cleanup on shutdown).
|
|
167
|
+
*/
|
|
168
|
+
stopAll() {
|
|
169
|
+
for (const [, interval] of this.activeTails) {
|
|
170
|
+
clearInterval(interval);
|
|
171
|
+
}
|
|
172
|
+
this.activeTails.clear();
|
|
173
|
+
}
|
|
174
|
+
// ── Snapshot helpers ────────────────────────────────────────────────
|
|
175
|
+
/**
|
|
176
|
+
* Read the last N lines from a session JSONL file.
|
|
177
|
+
*/
|
|
178
|
+
async getLastLines(sessionId, cwd, lineCount = 20) {
|
|
179
|
+
const filePath = this.findSessionFile(sessionId, cwd);
|
|
180
|
+
if (!filePath)
|
|
181
|
+
return [];
|
|
182
|
+
const lines = [];
|
|
183
|
+
const rl = createInterface({
|
|
184
|
+
input: createReadStream(filePath, { encoding: 'utf8' }),
|
|
185
|
+
});
|
|
186
|
+
for await (const line of rl) {
|
|
187
|
+
lines.push(line);
|
|
188
|
+
if (lines.length > lineCount) {
|
|
189
|
+
lines.shift();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return lines;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Extract a preview of recent tool output from the tail of a session file.
|
|
196
|
+
*/
|
|
197
|
+
async getToolOutputPreview(sessionId, cwd, maxEntries = 5) {
|
|
198
|
+
const lastLines = await this.getLastLines(sessionId, cwd, 100);
|
|
199
|
+
const previews = [];
|
|
200
|
+
for (const line of lastLines) {
|
|
201
|
+
const trimmed = line.trim();
|
|
202
|
+
if (!trimmed)
|
|
203
|
+
continue;
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(trimmed);
|
|
206
|
+
const preview = extractToolOutput(parsed);
|
|
207
|
+
if (preview) {
|
|
208
|
+
previews.push(preview);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// skip unparseable lines
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return previews.slice(-maxEntries);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ── Internal helpers ────────────────────────────────────────────────────
|
|
219
|
+
/**
|
|
220
|
+
* Attempt to extract tool output information from a JSONL record.
|
|
221
|
+
*
|
|
222
|
+
* Claude Code JSONL records with tool results can appear as:
|
|
223
|
+
* 1. A top-level `{ type: "user", message: { content: [{ type: "tool_result", ... }] } }`
|
|
224
|
+
* 2. Direct `tool_result` entries (less common).
|
|
225
|
+
*/
|
|
226
|
+
function extractToolOutput(record) {
|
|
227
|
+
// Pattern 1: user message wrapping tool_result content blocks
|
|
228
|
+
if (record.type === 'user') {
|
|
229
|
+
const message = record.message;
|
|
230
|
+
if (!message)
|
|
231
|
+
return null;
|
|
232
|
+
const content = message.content;
|
|
233
|
+
if (!Array.isArray(content))
|
|
234
|
+
return null;
|
|
235
|
+
for (const block of content) {
|
|
236
|
+
if (block &&
|
|
237
|
+
typeof block === 'object' &&
|
|
238
|
+
block.type === 'tool_result') {
|
|
239
|
+
const b = block;
|
|
240
|
+
return {
|
|
241
|
+
toolUseId: typeof b.tool_use_id === 'string' ? b.tool_use_id : '',
|
|
242
|
+
content: stringifyContent(b.content),
|
|
243
|
+
isError: b.is_error === true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Pattern 2: direct tool_result record (varies by Claude Code version)
|
|
249
|
+
if (record.type === 'tool_result') {
|
|
250
|
+
return {
|
|
251
|
+
toolUseId: typeof record.tool_use_id === 'string' ? record.tool_use_id : '',
|
|
252
|
+
content: stringifyContent(record.content),
|
|
253
|
+
isError: record.is_error === true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
function stringifyContent(value) {
|
|
259
|
+
if (typeof value === 'string')
|
|
260
|
+
return value;
|
|
261
|
+
if (value === undefined || value === null)
|
|
262
|
+
return '';
|
|
263
|
+
try {
|
|
264
|
+
return JSON.stringify(value);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return String(value);
|
|
268
|
+
}
|
|
269
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +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, 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
|
+
export { SessionLiveTailer } from './claude/session-live-tailer.js';
|
|
4
5
|
export { ClaudeManagerPlugin };
|
|
5
6
|
export declare const plugin: Plugin;
|
package/dist/index.js
CHANGED
|
@@ -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
|
/**
|
|
@@ -15,6 +19,8 @@ export declare class SessionController {
|
|
|
15
19
|
*/
|
|
16
20
|
sendMessage(cwd: string, message: string, options?: {
|
|
17
21
|
model?: string;
|
|
22
|
+
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
23
|
+
mode?: SessionMode;
|
|
18
24
|
settingSources?: Array<'user' | 'project' | 'local'>;
|
|
19
25
|
abortSignal?: AbortSignal;
|
|
20
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,13 +27,17 @@ 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
|
|
35
|
+
prompt,
|
|
28
36
|
persistSession: true,
|
|
29
|
-
permissionMode: 'acceptEdits',
|
|
37
|
+
permissionMode: mode === 'plan' ? 'plan' : 'acceptEdits',
|
|
30
38
|
includePartialMessages: true,
|
|
31
39
|
model: options?.model,
|
|
40
|
+
effort: options?.effort,
|
|
32
41
|
settingSources: options?.settingSources ?? ['user', 'project', 'local'],
|
|
33
42
|
abortSignal: options?.abortSignal,
|
|
34
43
|
};
|
|
@@ -37,8 +46,10 @@ export class SessionController {
|
|
|
37
46
|
input.resumeSessionId = this.activeSessionId;
|
|
38
47
|
}
|
|
39
48
|
else {
|
|
40
|
-
// New session — apply the expert operator system prompt
|
|
49
|
+
// New session — apply the expert operator system prompt and defaults
|
|
41
50
|
input.systemPrompt = this.sessionPrompt;
|
|
51
|
+
input.model ??= 'claude-opus-4-6';
|
|
52
|
+
input.effort ??= 'high';
|
|
42
53
|
}
|
|
43
54
|
const result = await this.sdkAdapter.runSession(input, onEvent);
|
|
44
55
|
// Track the session ID
|
|
@@ -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`,
|
|
@@ -214,6 +277,16 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
214
277
|
metadata: { sessionId: result.sessionId },
|
|
215
278
|
});
|
|
216
279
|
}
|
|
280
|
+
// Fetch recent tool output from the JSONL file for richer feedback.
|
|
281
|
+
let toolOutputs = [];
|
|
282
|
+
if (result.sessionId) {
|
|
283
|
+
try {
|
|
284
|
+
toolOutputs = await services.liveTailer.getToolOutputPreview(result.sessionId, cwd, 3);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// Non-critical — the JSONL file may not exist yet.
|
|
288
|
+
}
|
|
289
|
+
}
|
|
217
290
|
return JSON.stringify({
|
|
218
291
|
sessionId: result.sessionId,
|
|
219
292
|
finalText: result.finalText,
|
|
@@ -221,6 +294,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
221
294
|
totalCostUsd: result.totalCostUsd,
|
|
222
295
|
context: result.context,
|
|
223
296
|
contextWarning,
|
|
297
|
+
toolOutputs: toolOutputs.length > 0 ? toolOutputs : undefined,
|
|
224
298
|
}, null, 2);
|
|
225
299
|
},
|
|
226
300
|
}),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
2
|
+
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
2
3
|
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
3
4
|
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
4
5
|
interface ClaudeManagerPluginServices {
|
|
5
6
|
manager: PersistentManager;
|
|
6
7
|
sessions: ClaudeSessionService;
|
|
7
8
|
approvalManager: ToolApprovalManager;
|
|
9
|
+
liveTailer: SessionLiveTailer;
|
|
8
10
|
}
|
|
9
11
|
export declare function getOrCreatePluginServices(worktree: string): ClaudeManagerPluginServices;
|
|
10
12
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
|
|
2
2
|
import { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
|
+
import { SessionLiveTailer } from '../claude/session-live-tailer.js';
|
|
3
4
|
import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
|
|
4
5
|
import { ClaudeMetadataService } from '../metadata/claude-metadata.service.js';
|
|
5
6
|
import { RepoClaudeConfigReader } from '../metadata/repo-claude-config-reader.js';
|
|
@@ -21,17 +22,19 @@ export function getOrCreatePluginServices(worktree) {
|
|
|
21
22
|
const metadataService = new ClaudeMetadataService(new RepoClaudeConfigReader(), sdkAdapter);
|
|
22
23
|
const sessionService = new ClaudeSessionService(sdkAdapter, metadataService);
|
|
23
24
|
const contextTracker = new ContextTracker();
|
|
24
|
-
const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.claudeCodeSessionPrompt);
|
|
25
|
+
const sessionController = new SessionController(sdkAdapter, contextTracker, managerPromptRegistry.claudeCodeSessionPrompt, managerPromptRegistry.modePrefixes);
|
|
25
26
|
const gitOps = new GitOperations(worktree);
|
|
26
27
|
const stateStore = new FileRunStateStore();
|
|
27
28
|
const transcriptStore = new TranscriptStore();
|
|
28
29
|
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker, transcriptStore);
|
|
29
30
|
// Try to restore active session state (fire and forget)
|
|
30
31
|
manager.tryRestore(worktree).catch(() => { });
|
|
32
|
+
const liveTailer = new SessionLiveTailer();
|
|
31
33
|
const services = {
|
|
32
34
|
manager,
|
|
33
35
|
sessions: sessionService,
|
|
34
36
|
approvalManager,
|
|
37
|
+
liveTailer,
|
|
35
38
|
};
|
|
36
39
|
serviceCache.set(worktree, services);
|
|
37
40
|
return services;
|
package/dist/prompts/registry.js
CHANGED
|
@@ -1,42 +1,133 @@
|
|
|
1
1
|
export const managerPromptRegistry = {
|
|
2
2
|
managerSystemPrompt: [
|
|
3
|
-
'You
|
|
4
|
-
'
|
|
3
|
+
'You are a senior IC operating Claude Code through a persistent session.',
|
|
4
|
+
'Your job is to make Claude Code do the work — not to write code yourself.',
|
|
5
|
+
'Think like a staff engineer: correctness, maintainability, tests, rollback safety,',
|
|
6
|
+
'and clear communication to the user.',
|
|
5
7
|
'',
|
|
6
|
-
'##
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
8
|
+
'## Decision loop',
|
|
9
|
+
'On every turn, choose exactly one action:',
|
|
10
|
+
' investigate — read files, grep, search the codebase to build context',
|
|
11
|
+
' delegate — send a focused instruction to Claude Code via claude_manager_send',
|
|
12
|
+
' review — run claude_manager_git_diff to inspect what changed',
|
|
13
|
+
' validate — tell Claude Code to run tests, lint, or typecheck',
|
|
14
|
+
' commit — checkpoint good work with claude_manager_git_commit',
|
|
15
|
+
' correct — send a targeted fix instruction (never "try again")',
|
|
16
|
+
' reset — discard bad work with claude_manager_git_reset',
|
|
17
|
+
' ask — ask the user one narrow, high-value question',
|
|
18
|
+
'',
|
|
19
|
+
'Default order: investigate → delegate → review → validate → commit.',
|
|
20
|
+
'Skip steps only when you have strong evidence they are unnecessary.',
|
|
21
|
+
'',
|
|
22
|
+
'## Before you delegate',
|
|
23
|
+
'1. Read the relevant files yourself (you have read, grep, glob).',
|
|
24
|
+
' For broad investigations, scope them narrowly or use subagents to avoid',
|
|
25
|
+
' polluting your own context with excessive file contents.',
|
|
26
|
+
'2. Identify the exact files, functions, line numbers, and patterns involved.',
|
|
27
|
+
'3. Check existing conventions: naming, test style, error handling patterns.',
|
|
28
|
+
'4. Craft an instruction that a senior engineer would find unambiguous.',
|
|
29
|
+
' Bad: "Fix the auth bug"',
|
|
30
|
+
' Good: "In src/auth/session.ts, the `validateToken` function (line 42)',
|
|
31
|
+
' throws on expired tokens instead of returning null. Change it to',
|
|
32
|
+
' return null and update the caller in src/routes/login.ts:87."',
|
|
33
|
+
'',
|
|
34
|
+
'## After delegation — mandatory review',
|
|
35
|
+
'Never claim success without evidence:',
|
|
36
|
+
'1. claude_manager_git_diff — read the actual diff, not just the summary.',
|
|
37
|
+
'2. Verify the diff matches what you asked for. Check for:',
|
|
38
|
+
' - Unintended changes or regressions',
|
|
39
|
+
' - Missing test updates',
|
|
40
|
+
' - Style violations against repo conventions',
|
|
41
|
+
'3. If changes look correct, tell Claude Code to run tests/lint/typecheck.',
|
|
42
|
+
'4. Only commit after verification passes.',
|
|
43
|
+
'5. If the diff is wrong: send a specific correction or reset.',
|
|
44
|
+
'',
|
|
45
|
+
'## Handling ambiguity',
|
|
46
|
+
'When requirements are unclear:',
|
|
47
|
+
'1. First, try to resolve it yourself — read code, check tests, grep for usage.',
|
|
48
|
+
'2. If ambiguity remains, ask the user ONE specific question.',
|
|
49
|
+
' Bad: "What should I do?"',
|
|
50
|
+
' Good: "The `UserService` has both `deactivate()` and `softDelete()` —',
|
|
51
|
+
' should the new endpoint use deactivation (reversible) or',
|
|
52
|
+
' soft-delete (audit-logged)?"',
|
|
53
|
+
'3. Never block on multiple questions at once.',
|
|
54
|
+
'',
|
|
55
|
+
'## Correction and recovery',
|
|
56
|
+
'If Claude Code produces wrong output:',
|
|
57
|
+
'1. First correction: send a specific, targeted fix instruction.',
|
|
58
|
+
'2. Second correction on the same issue: reset, clear the session,',
|
|
59
|
+
' and rewrite the prompt incorporating lessons from both failures.',
|
|
60
|
+
'Never send three corrections for the same problem in one session.',
|
|
61
|
+
'',
|
|
62
|
+
'## Multi-step tasks',
|
|
63
|
+
'- Decompose large tasks into sequential focused instructions.',
|
|
64
|
+
'- Commit after each successful step (checkpoint for rollback).',
|
|
65
|
+
'- Tell Claude Code to use subagents for independent parallel work.',
|
|
66
|
+
'- For complex design decisions, tell Claude Code to "think hard".',
|
|
67
|
+
'- Prefer small diffs — they are easier to review and safer to ship.',
|
|
12
68
|
'',
|
|
13
69
|
'## Context management',
|
|
14
|
-
'Check the context snapshot
|
|
15
|
-
'- Under 50%: proceed freely',
|
|
16
|
-
'- 50
|
|
17
|
-
'- Over 70%: compact or clear before heavy
|
|
18
|
-
'- Over 85
|
|
19
|
-
'',
|
|
20
|
-
'##
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
70
|
+
'Check the context snapshot returned by each send:',
|
|
71
|
+
'- Under 50%: proceed freely.',
|
|
72
|
+
'- 50–70%: finish current step, then evaluate if a fresh session is needed.',
|
|
73
|
+
'- Over 70%: compact or clear before sending heavy instructions.',
|
|
74
|
+
'- Over 85%: clear the session immediately.',
|
|
75
|
+
'',
|
|
76
|
+
'## Tools reference',
|
|
77
|
+
'claude_manager_send — send instruction (creates or resumes session)',
|
|
78
|
+
'claude_manager_git_diff — review all uncommitted changes',
|
|
79
|
+
'claude_manager_git_commit — stage all + commit',
|
|
80
|
+
'claude_manager_git_reset — hard reset + clean (destructive)',
|
|
81
|
+
'claude_manager_clear — drop session, next send starts fresh',
|
|
82
|
+
'claude_manager_status — context health snapshot',
|
|
83
|
+
'claude_manager_metadata — inspect repo Claude config',
|
|
84
|
+
'claude_manager_sessions — list sessions or read transcripts',
|
|
85
|
+
'claude_manager_runs — list or inspect run records',
|
|
86
|
+
'',
|
|
87
|
+
'## Autonomy blockers — surface these to the user',
|
|
88
|
+
'Be candid about what you cannot do autonomously:',
|
|
89
|
+
'- Credentials, API keys, or secrets you do not have.',
|
|
90
|
+
'- Architectural decisions with trade-offs the user should weigh.',
|
|
91
|
+
'- Destructive actions on shared state (deploy, publish, force-push).',
|
|
92
|
+
'- Access to external services or environments you cannot reach.',
|
|
93
|
+
'State the blocker, what you need, and a concrete suggestion to unblock.',
|
|
26
94
|
].join('\n'),
|
|
27
95
|
claudeCodeSessionPrompt: [
|
|
28
|
-
'You are
|
|
29
|
-
'as a precise instruction from a
|
|
30
|
-
'',
|
|
31
|
-
'
|
|
32
|
-
'- Execute instructions directly
|
|
33
|
-
'-
|
|
34
|
-
'-
|
|
35
|
-
'-
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'-
|
|
96
|
+
'You are directed by an expert automated operator.',
|
|
97
|
+
'Treat each message as a precise instruction from a senior engineer.',
|
|
98
|
+
'',
|
|
99
|
+
'## Execution rules',
|
|
100
|
+
'- Execute instructions directly. Do not ask for clarification.',
|
|
101
|
+
'- Be concise — no preamble, no restating the task.',
|
|
102
|
+
'- Prefer targeted file reads over reading entire files.',
|
|
103
|
+
'- Use the Agent tool for independent parallel work.',
|
|
104
|
+
'',
|
|
105
|
+
'## Quality expectations',
|
|
106
|
+
'- Follow existing repo conventions (naming, style, patterns).',
|
|
107
|
+
'- When creating or modifying code, consider edge cases and error handling.',
|
|
108
|
+
'- When modifying existing code, preserve surrounding style and structure.',
|
|
109
|
+
'- If asked to implement a feature, include relevant tests unless told otherwise.',
|
|
110
|
+
'- Run tests/lint/typecheck when instructed; report exact output on failure.',
|
|
111
|
+
'',
|
|
112
|
+
'## Git boundary — do NOT run these commands:',
|
|
113
|
+
'git commit, git push, git reset, git checkout, git stash.',
|
|
114
|
+
'The operator manages all git operations externally.',
|
|
115
|
+
'',
|
|
116
|
+
'## Reporting',
|
|
117
|
+
'- End with a brief verification summary: what was done, what was verified.',
|
|
118
|
+
'- Report blockers immediately with specifics: file, line, error message.',
|
|
119
|
+
'- If a task is partially complete, state exactly what remains.',
|
|
39
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
|
+
},
|
|
40
131
|
contextWarnings: {
|
|
41
132
|
moderate: 'Session context is filling up ({percent}% estimated). Consider whether a fresh session would be more efficient.',
|
|
42
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;
|
|
@@ -57,6 +62,8 @@ export interface RunClaudeSessionInput {
|
|
|
57
62
|
prompt: string;
|
|
58
63
|
systemPrompt?: string;
|
|
59
64
|
model?: string;
|
|
65
|
+
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
66
|
+
mode?: SessionMode;
|
|
60
67
|
permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk';
|
|
61
68
|
allowedTools?: string[];
|
|
62
69
|
disallowedTools?: string[];
|
|
@@ -167,6 +174,18 @@ export interface PersistentRunRecord {
|
|
|
167
174
|
export interface PersistentRunResult {
|
|
168
175
|
run: PersistentRunRecord;
|
|
169
176
|
}
|
|
177
|
+
export interface LiveTailEvent {
|
|
178
|
+
type: 'line' | 'error' | 'end';
|
|
179
|
+
sessionId: string;
|
|
180
|
+
data?: unknown;
|
|
181
|
+
rawLine?: string;
|
|
182
|
+
error?: string;
|
|
183
|
+
}
|
|
184
|
+
export interface ToolOutputPreview {
|
|
185
|
+
toolUseId: string;
|
|
186
|
+
content: string;
|
|
187
|
+
isError: boolean;
|
|
188
|
+
}
|
|
170
189
|
export interface ToolApprovalRule {
|
|
171
190
|
id: string;
|
|
172
191
|
description?: string;
|