claude-code-rust 0.9.0 → 0.11.0
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 +16 -9
- package/agent-sdk/README.md +1 -1
- package/agent-sdk/dist/bridge/commands.js +71 -2
- package/agent-sdk/dist/bridge/events.js +127 -12
- package/agent-sdk/dist/bridge/history.js +9 -4
- package/agent-sdk/dist/bridge/logger.js +253 -0
- package/agent-sdk/dist/bridge/mcp.js +81 -5
- package/agent-sdk/dist/bridge/message_handlers.js +318 -67
- package/agent-sdk/dist/bridge/session_lifecycle.js +774 -58
- package/agent-sdk/dist/bridge/shared.js +0 -7
- package/agent-sdk/dist/bridge/state_parsing.js +61 -0
- package/agent-sdk/dist/bridge/tool_calls.js +269 -66
- package/agent-sdk/dist/bridge/tooling.js +60 -27
- package/agent-sdk/dist/bridge/user_interaction.js +34 -23
- package/agent-sdk/dist/bridge.js +406 -42
- package/agent-sdk/dist/bridge.test.js +765 -23
- package/package.json +2 -2
|
@@ -46,10 +46,3 @@ export class AsyncQueue {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
const permissionDebugEnabled = process.env.CLAUDE_RS_SDK_PERMISSION_DEBUG === "1" || process.env.CLAUDE_RS_SDK_DEBUG === "1";
|
|
50
|
-
export function logPermissionDebug(message) {
|
|
51
|
-
if (!permissionDebugEnabled) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
console.error(`[perm debug] ${message}`);
|
|
55
|
-
}
|
|
@@ -20,6 +20,25 @@ export function parseRateLimitStatus(value) {
|
|
|
20
20
|
}
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
|
+
export function parseRuntimeSessionState(value) {
|
|
24
|
+
if (value === "idle" || value === "running" || value === "requires_action") {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
export function parseApiRetryError(value) {
|
|
30
|
+
switch (value) {
|
|
31
|
+
case "authentication_failed":
|
|
32
|
+
case "billing_error":
|
|
33
|
+
case "rate_limit":
|
|
34
|
+
case "invalid_request":
|
|
35
|
+
case "server_error":
|
|
36
|
+
case "max_output_tokens":
|
|
37
|
+
return value;
|
|
38
|
+
default:
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
23
42
|
export function buildRateLimitUpdate(rateLimitInfo) {
|
|
24
43
|
const info = asRecordOrNull(rateLimitInfo);
|
|
25
44
|
if (!info) {
|
|
@@ -64,3 +83,45 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
64
83
|
}
|
|
65
84
|
return update;
|
|
66
85
|
}
|
|
86
|
+
export function buildApiRetryUpdate(message) {
|
|
87
|
+
const attempt = numberField(message, "attempt");
|
|
88
|
+
const maxRetries = numberField(message, "max_retries", "maxRetries");
|
|
89
|
+
const retryDelayMs = numberField(message, "retry_delay_ms", "retryDelayMs");
|
|
90
|
+
if (attempt === undefined || maxRetries === undefined || retryDelayMs === undefined) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const rawStatus = message.error_status ?? message.errorStatus;
|
|
94
|
+
const errorStatus = typeof rawStatus === "number" && Number.isFinite(rawStatus) ? rawStatus : null;
|
|
95
|
+
return {
|
|
96
|
+
type: "api_retry_update",
|
|
97
|
+
attempt,
|
|
98
|
+
max_retries: maxRetries,
|
|
99
|
+
retry_delay_ms: retryDelayMs,
|
|
100
|
+
error_status: errorStatus,
|
|
101
|
+
error: parseApiRetryError(message.error),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function normalizeSettingsParseError(value) {
|
|
105
|
+
const record = asRecordOrNull(value);
|
|
106
|
+
if (!record) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const message = typeof record.message === "string" ? record.message.trim() : "";
|
|
110
|
+
if (!message) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const path = typeof record.path === "string" ? record.path : "";
|
|
114
|
+
const file = typeof record.file === "string" && record.file.trim() ? record.file : undefined;
|
|
115
|
+
return {
|
|
116
|
+
...(file ? { file } : {}),
|
|
117
|
+
path,
|
|
118
|
+
message,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function normalizeSettingsParseErrors(value) {
|
|
122
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
123
|
+
return entries.flatMap((entry) => {
|
|
124
|
+
const normalized = normalizeSettingsParseError(entry);
|
|
125
|
+
return normalized ? [normalized] : [];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -1,13 +1,202 @@
|
|
|
1
1
|
import { emitSessionUpdate } from "./events.js";
|
|
2
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
2
3
|
import { buildToolResultFields, createToolCall } from "./tooling.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
function jsonSize(value) {
|
|
5
|
+
if (value === undefined) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return Buffer.byteLength(JSON.stringify(value));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function toolNameFromMeta(meta) {
|
|
16
|
+
if (!meta || typeof meta !== "object") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const claudeCode = "claudeCode" in meta && meta.claudeCode && typeof meta.claudeCode === "object" ? meta.claudeCode : undefined;
|
|
20
|
+
const toolName = claudeCode && "toolName" in claudeCode && typeof claudeCode.toolName === "string" ? claudeCode.toolName : "";
|
|
21
|
+
return toolName || undefined;
|
|
22
|
+
}
|
|
23
|
+
function toolName(base, fields) {
|
|
24
|
+
const fieldMeta = fields?.meta;
|
|
25
|
+
if (fieldMeta && typeof fieldMeta === "object") {
|
|
26
|
+
const fromFields = toolNameFromMeta(fieldMeta);
|
|
27
|
+
if (fromFields) {
|
|
28
|
+
return fromFields;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return toolNameFromMeta(base?.meta);
|
|
32
|
+
}
|
|
33
|
+
function classifyFailureKind(rawOutput) {
|
|
34
|
+
if (!rawOutput) {
|
|
35
|
+
return "failed";
|
|
36
|
+
}
|
|
37
|
+
const normalized = rawOutput.trim().toLowerCase();
|
|
38
|
+
if (normalized.includes("permission denied") ||
|
|
39
|
+
normalized.includes("cancelled by user") ||
|
|
40
|
+
normalized.includes("plan rejected") ||
|
|
41
|
+
normalized.includes("question cancelled")) {
|
|
42
|
+
return "refused";
|
|
43
|
+
}
|
|
44
|
+
if (normalized.includes("timed out") || normalized.includes("timeout")) {
|
|
45
|
+
return "timeout";
|
|
46
|
+
}
|
|
47
|
+
return "failed";
|
|
48
|
+
}
|
|
49
|
+
function updateOutcome(status) {
|
|
50
|
+
switch (status) {
|
|
51
|
+
case "completed":
|
|
52
|
+
return "success";
|
|
53
|
+
case "failed":
|
|
54
|
+
case "killed":
|
|
55
|
+
return "failure";
|
|
56
|
+
case "in_progress":
|
|
57
|
+
return "partial";
|
|
58
|
+
case "pending":
|
|
59
|
+
return "start";
|
|
60
|
+
default:
|
|
61
|
+
return "partial";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function parentToolUseIdFromMeta(meta) {
|
|
65
|
+
if (!meta || typeof meta !== "object") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const claudeCode = "claudeCode" in meta && meta.claudeCode && typeof meta.claudeCode === "object" ? meta.claudeCode : undefined;
|
|
69
|
+
const parentToolUseId = claudeCode && "parentToolUseId" in claudeCode && typeof claudeCode.parentToolUseId === "string"
|
|
70
|
+
? claudeCode.parentToolUseId
|
|
71
|
+
: null;
|
|
72
|
+
return parentToolUseId;
|
|
73
|
+
}
|
|
74
|
+
function mergeTaskMetadata(current, update) {
|
|
75
|
+
if (update === undefined) {
|
|
76
|
+
return current;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...(current ?? {}),
|
|
80
|
+
...update,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function applyFieldsToBase(base, fields) {
|
|
84
|
+
if (fields.title !== undefined) {
|
|
85
|
+
base.title = fields.title;
|
|
86
|
+
}
|
|
87
|
+
if (fields.kind !== undefined) {
|
|
88
|
+
base.kind = fields.kind;
|
|
89
|
+
}
|
|
90
|
+
if (fields.status !== undefined) {
|
|
91
|
+
base.status = fields.status;
|
|
92
|
+
}
|
|
93
|
+
if (fields.raw_input !== undefined) {
|
|
94
|
+
base.raw_input = fields.raw_input;
|
|
95
|
+
}
|
|
96
|
+
if (fields.raw_output !== undefined) {
|
|
97
|
+
base.raw_output = fields.raw_output;
|
|
98
|
+
}
|
|
99
|
+
if (fields.locations !== undefined) {
|
|
100
|
+
base.locations = fields.locations;
|
|
101
|
+
}
|
|
102
|
+
if (fields.output_metadata !== undefined) {
|
|
103
|
+
base.output_metadata = fields.output_metadata;
|
|
104
|
+
}
|
|
105
|
+
if (fields.task_metadata !== undefined) {
|
|
106
|
+
base.task_metadata = mergeTaskMetadata(base.task_metadata, fields.task_metadata);
|
|
107
|
+
}
|
|
108
|
+
if (fields.meta !== undefined) {
|
|
109
|
+
base.meta = fields.meta;
|
|
110
|
+
}
|
|
111
|
+
if (fields.content !== undefined) {
|
|
112
|
+
base.content = fields.content;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function logToolCallSubmitted(sessionId, toolCall, updateKind) {
|
|
116
|
+
bridgeLogger.info({
|
|
117
|
+
target: LOG_TARGETS.APP_TOOL,
|
|
118
|
+
eventName: "tool_call_submitted",
|
|
119
|
+
message: "tool call submitted",
|
|
120
|
+
outcome: "success",
|
|
121
|
+
sessionId,
|
|
122
|
+
toolCallId: toolCall.tool_call_id,
|
|
123
|
+
sizeBytes: jsonSize(toolCall.raw_input),
|
|
124
|
+
fields: {
|
|
125
|
+
submission_kind: updateKind,
|
|
126
|
+
tool_name: toolNameFromMeta(toolCall.meta),
|
|
127
|
+
tool_title: toolCall.title,
|
|
128
|
+
tool_kind: toolCall.kind,
|
|
129
|
+
status: toolCall.status,
|
|
130
|
+
content_block_count: toolCall.content.length,
|
|
131
|
+
location_count: toolCall.locations.length,
|
|
132
|
+
has_output_metadata: toolCall.output_metadata !== undefined,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function logToolCallUpdateEmitted(sessionId, toolUseId, fields, base, updateKind) {
|
|
137
|
+
const nextStatus = fields.status ?? base?.status;
|
|
138
|
+
const rawOutput = typeof fields.raw_output === "string" ? fields.raw_output : base?.raw_output;
|
|
139
|
+
const failureKind = nextStatus === "failed" ? classifyFailureKind(rawOutput) : undefined;
|
|
140
|
+
const commonEvent = {
|
|
141
|
+
target: LOG_TARGETS.APP_TOOL,
|
|
142
|
+
eventName: "tool_call_update_emitted",
|
|
143
|
+
message: "tool call update emitted",
|
|
144
|
+
outcome: updateOutcome(nextStatus),
|
|
145
|
+
sessionId,
|
|
146
|
+
toolCallId: toolUseId,
|
|
147
|
+
sizeBytes: jsonSize(fields.raw_input),
|
|
148
|
+
fields: {
|
|
149
|
+
update_kind: updateKind,
|
|
150
|
+
tool_name: toolName(base, fields),
|
|
151
|
+
previous_status: base?.status,
|
|
152
|
+
next_status: nextStatus,
|
|
153
|
+
title_changed: fields.title !== undefined && fields.title !== base?.title,
|
|
154
|
+
content_block_count: fields.content?.length,
|
|
155
|
+
location_count: fields.locations?.length,
|
|
156
|
+
raw_output_chars: rawOutput?.length,
|
|
157
|
+
has_output_metadata: fields.output_metadata !== undefined || base?.output_metadata !== undefined,
|
|
158
|
+
has_task_metadata: fields.task_metadata !== undefined || base?.task_metadata !== undefined,
|
|
159
|
+
failure_kind: failureKind,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
if (nextStatus === "failed" || nextStatus === "killed") {
|
|
163
|
+
bridgeLogger.warn(commonEvent);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (nextStatus === "completed") {
|
|
167
|
+
bridgeLogger.info(commonEvent);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (nextStatus === "in_progress" || nextStatus === "pending") {
|
|
171
|
+
bridgeLogger.debug(commonEvent);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
bridgeLogger.debug(commonEvent);
|
|
175
|
+
}
|
|
176
|
+
function emitInitialToolCall(session, toolCall, updateKind = "initial") {
|
|
177
|
+
session.toolCalls.set(toolCall.tool_call_id, toolCall);
|
|
178
|
+
logToolCallSubmitted(session.sessionId, toolCall, updateKind);
|
|
179
|
+
emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
|
|
180
|
+
}
|
|
181
|
+
export function emitToolCallUpdate(session, toolUseId, fields, updateKind) {
|
|
182
|
+
const base = session.toolCalls.get(toolUseId);
|
|
183
|
+
logToolCallUpdateEmitted(session.sessionId, toolUseId, fields, base, updateKind);
|
|
184
|
+
emitSessionUpdate(session.sessionId, {
|
|
185
|
+
type: "tool_call_update",
|
|
186
|
+
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
187
|
+
});
|
|
188
|
+
if (base) {
|
|
189
|
+
applyFieldsToBase(base, fields);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export function emitToolCall(session, toolUseId, name, input, parentToolUseId = null) {
|
|
193
|
+
const existing = session.toolCalls.get(toolUseId);
|
|
194
|
+
const resolvedParentToolUseId = parentToolUseId ?? parentToolUseIdFromMeta(existing?.meta);
|
|
195
|
+
const toolCall = createToolCall(toolUseId, name, input, resolvedParentToolUseId);
|
|
5
196
|
const status = "in_progress";
|
|
6
197
|
toolCall.status = status;
|
|
7
|
-
const existing = session.toolCalls.get(toolUseId);
|
|
8
198
|
if (!existing) {
|
|
9
|
-
session
|
|
10
|
-
emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
|
|
199
|
+
emitInitialToolCall(session, toolCall);
|
|
11
200
|
return;
|
|
12
201
|
}
|
|
13
202
|
const fields = {
|
|
@@ -21,28 +210,20 @@ export function emitToolCall(session, toolUseId, name, input) {
|
|
|
21
210
|
if (toolCall.content.length > 0) {
|
|
22
211
|
fields.content = toolCall.content;
|
|
23
212
|
}
|
|
24
|
-
|
|
25
|
-
type: "tool_call_update",
|
|
26
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
27
|
-
});
|
|
28
|
-
existing.title = toolCall.title;
|
|
29
|
-
existing.kind = toolCall.kind;
|
|
30
|
-
existing.status = status;
|
|
31
|
-
existing.raw_input = toolCall.raw_input;
|
|
32
|
-
existing.locations = toolCall.locations;
|
|
33
|
-
existing.meta = toolCall.meta;
|
|
34
|
-
if (toolCall.content.length > 0) {
|
|
35
|
-
existing.content = toolCall.content;
|
|
36
|
-
}
|
|
213
|
+
emitToolCallUpdate(session, toolUseId, fields, "refresh");
|
|
37
214
|
}
|
|
38
|
-
export function ensureToolCallVisible(session, toolUseId, toolName, input) {
|
|
215
|
+
export function ensureToolCallVisible(session, toolUseId, toolName, input, parentToolUseId = null) {
|
|
39
216
|
const existing = session.toolCalls.get(toolUseId);
|
|
40
217
|
if (existing) {
|
|
218
|
+
const existingParentToolUseId = parentToolUseIdFromMeta(existing.meta);
|
|
219
|
+
if (parentToolUseId && existingParentToolUseId !== parentToolUseId) {
|
|
220
|
+
const refreshed = createToolCall(toolUseId, toolName, input, parentToolUseId);
|
|
221
|
+
emitToolCallUpdate(session, toolUseId, { meta: refreshed.meta }, "refresh");
|
|
222
|
+
}
|
|
41
223
|
return existing;
|
|
42
224
|
}
|
|
43
|
-
const toolCall = createToolCall(toolUseId, toolName, input);
|
|
44
|
-
session
|
|
45
|
-
emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
|
|
225
|
+
const toolCall = createToolCall(toolUseId, toolName, input, parentToolUseId);
|
|
226
|
+
emitInitialToolCall(session, toolCall);
|
|
46
227
|
return toolCall;
|
|
47
228
|
}
|
|
48
229
|
export function emitPlanIfTodoWrite(session, name, input) {
|
|
@@ -68,34 +249,15 @@ export function emitPlanIfTodoWrite(session, name, input) {
|
|
|
68
249
|
}
|
|
69
250
|
}
|
|
70
251
|
export function emitToolResultUpdate(session, toolUseId, isError, rawContent, rawResult = rawContent) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const update = { tool_call_id: toolUseId, fields };
|
|
74
|
-
emitSessionUpdate(session.sessionId, { type: "tool_call_update", tool_call_update: update });
|
|
75
|
-
if (base) {
|
|
76
|
-
base.status = fields.status ?? base.status;
|
|
77
|
-
if (fields.raw_output) {
|
|
78
|
-
base.raw_output = fields.raw_output;
|
|
79
|
-
}
|
|
80
|
-
if (fields.content) {
|
|
81
|
-
base.content = fields.content;
|
|
82
|
-
}
|
|
83
|
-
if (fields.output_metadata) {
|
|
84
|
-
base.output_metadata = fields.output_metadata;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
252
|
+
const fields = buildToolResultFields(isError, rawContent, session.toolCalls.get(toolUseId), rawResult);
|
|
253
|
+
emitToolCallUpdate(session, toolUseId, fields, "result");
|
|
87
254
|
}
|
|
88
255
|
export function finalizeOpenToolCalls(session, status) {
|
|
89
256
|
for (const [toolUseId, toolCall] of session.toolCalls) {
|
|
90
257
|
if (toolCall.status !== "pending" && toolCall.status !== "in_progress") {
|
|
91
258
|
continue;
|
|
92
259
|
}
|
|
93
|
-
|
|
94
|
-
emitSessionUpdate(session.sessionId, {
|
|
95
|
-
type: "tool_call_update",
|
|
96
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
97
|
-
});
|
|
98
|
-
toolCall.status = status;
|
|
260
|
+
emitToolCallUpdate(session, toolUseId, { status }, "finalize");
|
|
99
261
|
}
|
|
100
262
|
}
|
|
101
263
|
export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
@@ -106,15 +268,11 @@ export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
|
106
268
|
}
|
|
107
269
|
if (existing.status === "in_progress" ||
|
|
108
270
|
existing.status === "completed" ||
|
|
109
|
-
existing.status === "failed"
|
|
271
|
+
existing.status === "failed" ||
|
|
272
|
+
existing.status === "killed") {
|
|
110
273
|
return;
|
|
111
274
|
}
|
|
112
|
-
|
|
113
|
-
emitSessionUpdate(session.sessionId, {
|
|
114
|
-
type: "tool_call_update",
|
|
115
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
116
|
-
});
|
|
117
|
-
existing.status = "in_progress";
|
|
275
|
+
emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
|
|
118
276
|
}
|
|
119
277
|
export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
120
278
|
const base = session.toolCalls.get(toolUseId);
|
|
@@ -122,16 +280,11 @@ export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
|
122
280
|
return;
|
|
123
281
|
}
|
|
124
282
|
const fields = {
|
|
125
|
-
status: base.status === "failed"
|
|
283
|
+
status: base.status === "failed" || base.status === "killed" ? base.status : "completed",
|
|
126
284
|
raw_output: summary,
|
|
127
285
|
content: [{ type: "content", content: { type: "text", text: summary } }],
|
|
128
286
|
};
|
|
129
|
-
|
|
130
|
-
type: "tool_call_update",
|
|
131
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
132
|
-
});
|
|
133
|
-
base.status = fields.status ?? base.status;
|
|
134
|
-
base.raw_output = summary;
|
|
287
|
+
emitToolCallUpdate(session, toolUseId, fields, "summary");
|
|
135
288
|
}
|
|
136
289
|
export function setToolCallStatus(session, toolUseId, status, message) {
|
|
137
290
|
const base = session.toolCalls.get(toolUseId);
|
|
@@ -143,14 +296,7 @@ export function setToolCallStatus(session, toolUseId, status, message) {
|
|
|
143
296
|
fields.raw_output = message;
|
|
144
297
|
fields.content = [{ type: "content", content: { type: "text", text: message } }];
|
|
145
298
|
}
|
|
146
|
-
|
|
147
|
-
type: "tool_call_update",
|
|
148
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
149
|
-
});
|
|
150
|
-
base.status = status;
|
|
151
|
-
if (fields.raw_output) {
|
|
152
|
-
base.raw_output = fields.raw_output;
|
|
153
|
-
}
|
|
299
|
+
emitToolCallUpdate(session, toolUseId, fields, "status");
|
|
154
300
|
}
|
|
155
301
|
export function resolveTaskToolUseId(session, msg) {
|
|
156
302
|
const direct = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
|
|
@@ -175,3 +321,60 @@ export function taskProgressText(msg) {
|
|
|
175
321
|
}
|
|
176
322
|
return description || lastTool;
|
|
177
323
|
}
|
|
324
|
+
function taskPatchStatus(value) {
|
|
325
|
+
switch (value) {
|
|
326
|
+
case "pending":
|
|
327
|
+
return "pending";
|
|
328
|
+
case "running":
|
|
329
|
+
return "in_progress";
|
|
330
|
+
case "completed":
|
|
331
|
+
return "completed";
|
|
332
|
+
case "failed":
|
|
333
|
+
return "failed";
|
|
334
|
+
case "killed":
|
|
335
|
+
return "killed";
|
|
336
|
+
default:
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function buildTaskMetadata(patch) {
|
|
341
|
+
const taskMetadata = {};
|
|
342
|
+
if (typeof patch.error === "string" && patch.error.length > 0) {
|
|
343
|
+
taskMetadata.error = patch.error;
|
|
344
|
+
}
|
|
345
|
+
if (typeof patch.is_backgrounded === "boolean") {
|
|
346
|
+
taskMetadata.is_backgrounded = patch.is_backgrounded;
|
|
347
|
+
}
|
|
348
|
+
if (typeof patch.end_time === "number" && Number.isFinite(patch.end_time) && patch.end_time >= 0) {
|
|
349
|
+
taskMetadata.end_time = Math.trunc(patch.end_time);
|
|
350
|
+
}
|
|
351
|
+
if (typeof patch.total_paused_ms === "number" &&
|
|
352
|
+
Number.isFinite(patch.total_paused_ms) &&
|
|
353
|
+
patch.total_paused_ms >= 0) {
|
|
354
|
+
taskMetadata.total_paused_ms = Math.trunc(patch.total_paused_ms);
|
|
355
|
+
}
|
|
356
|
+
return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
|
|
357
|
+
}
|
|
358
|
+
export function taskUpdatedFields(msg) {
|
|
359
|
+
const patch = msg.patch && typeof msg.patch === "object" ? msg.patch : {};
|
|
360
|
+
const fields = {};
|
|
361
|
+
const status = taskPatchStatus(patch.status);
|
|
362
|
+
const description = typeof patch.description === "string" ? patch.description : "";
|
|
363
|
+
const error = typeof patch.error === "string" ? patch.error : "";
|
|
364
|
+
if (status) {
|
|
365
|
+
fields.status = status;
|
|
366
|
+
}
|
|
367
|
+
if (description) {
|
|
368
|
+
fields.raw_output = description;
|
|
369
|
+
fields.content = [{ type: "content", content: { type: "text", text: description } }];
|
|
370
|
+
}
|
|
371
|
+
else if ((status === "failed" || status === "killed") && error) {
|
|
372
|
+
fields.raw_output = error;
|
|
373
|
+
fields.content = [{ type: "content", content: { type: "text", text: error } }];
|
|
374
|
+
}
|
|
375
|
+
const taskMetadata = buildTaskMetadata(patch);
|
|
376
|
+
if (taskMetadata) {
|
|
377
|
+
fields.task_metadata = taskMetadata;
|
|
378
|
+
}
|
|
379
|
+
return fields;
|
|
380
|
+
}
|
|
@@ -110,7 +110,7 @@ function editDiffContent(name, input) {
|
|
|
110
110
|
}
|
|
111
111
|
return [];
|
|
112
112
|
}
|
|
113
|
-
export function createToolCall(toolUseId, name, input) {
|
|
113
|
+
export function createToolCall(toolUseId, name, input, parentToolUseId = null) {
|
|
114
114
|
return {
|
|
115
115
|
tool_call_id: toolUseId,
|
|
116
116
|
title: toolTitle(name, input),
|
|
@@ -122,6 +122,7 @@ export function createToolCall(toolUseId, name, input) {
|
|
|
122
122
|
meta: {
|
|
123
123
|
claudeCode: {
|
|
124
124
|
toolName: name,
|
|
125
|
+
parentToolUseId,
|
|
125
126
|
},
|
|
126
127
|
},
|
|
127
128
|
};
|
|
@@ -134,18 +135,33 @@ function resultRecordCandidates(rawResult, rawContent) {
|
|
|
134
135
|
candidates.push(record);
|
|
135
136
|
}
|
|
136
137
|
};
|
|
138
|
+
const pushRecords = (value) => {
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
for (const entry of value) {
|
|
141
|
+
pushRecord(entry);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
pushRecord(value);
|
|
146
|
+
};
|
|
137
147
|
const pushNestedRecords = (value) => {
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
for (const entry of value) {
|
|
150
|
+
pushNestedRecords(entry);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
138
154
|
const record = asRecordOrNull(value);
|
|
139
155
|
if (!record) {
|
|
140
156
|
return;
|
|
141
157
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
pushRecords(record.result);
|
|
159
|
+
pushRecords(record.data);
|
|
160
|
+
pushRecords(record.content);
|
|
145
161
|
};
|
|
146
|
-
|
|
162
|
+
pushRecords(rawResult);
|
|
147
163
|
pushNestedRecords(rawResult);
|
|
148
|
-
|
|
164
|
+
pushRecords(rawContent);
|
|
149
165
|
pushNestedRecords(rawContent);
|
|
150
166
|
return candidates;
|
|
151
167
|
}
|
|
@@ -230,15 +246,9 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
230
246
|
if (toolName === "Bash") {
|
|
231
247
|
for (const candidate of candidates) {
|
|
232
248
|
const hasAssistantAutoBackgrounded = typeof candidate.assistantAutoBackgrounded === "boolean";
|
|
233
|
-
|
|
234
|
-
if (hasAssistantAutoBackgrounded || hasTokenSaverOutput) {
|
|
249
|
+
if (hasAssistantAutoBackgrounded) {
|
|
235
250
|
const bashMetadata = {};
|
|
236
|
-
|
|
237
|
-
bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
|
|
238
|
-
}
|
|
239
|
-
if (hasTokenSaverOutput) {
|
|
240
|
-
bashMetadata.token_saver_active = true;
|
|
241
|
-
}
|
|
251
|
+
bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
|
|
242
252
|
return {
|
|
243
253
|
bash: bashMetadata,
|
|
244
254
|
};
|
|
@@ -246,14 +256,6 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
246
256
|
}
|
|
247
257
|
return undefined;
|
|
248
258
|
}
|
|
249
|
-
if (toolName === "ExitPlanMode") {
|
|
250
|
-
for (const candidate of candidates) {
|
|
251
|
-
if (typeof candidate.isUltraplan === "boolean") {
|
|
252
|
-
return { exit_plan_mode: { is_ultraplan: candidate.isUltraplan } };
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return undefined;
|
|
256
|
-
}
|
|
257
259
|
if (toolName === "TodoWrite") {
|
|
258
260
|
for (const candidate of candidates) {
|
|
259
261
|
if (typeof candidate.verificationNudgeNeeded === "boolean") {
|
|
@@ -476,8 +478,7 @@ function findBashResultRecord(rawResult, rawContent) {
|
|
|
476
478
|
"stderr" in candidate ||
|
|
477
479
|
"backgroundTaskId" in candidate ||
|
|
478
480
|
"backgroundedByUser" in candidate ||
|
|
479
|
-
"assistantAutoBackgrounded" in candidate
|
|
480
|
-
"tokenSaverOutput" in candidate);
|
|
481
|
+
"assistantAutoBackgrounded" in candidate);
|
|
481
482
|
}
|
|
482
483
|
function bashBackgroundMessage(record) {
|
|
483
484
|
const backgroundTaskId = typeof record.backgroundTaskId === "string" ? record.backgroundTaskId : "";
|
|
@@ -511,16 +512,48 @@ function buildBashDisplayOutput(record) {
|
|
|
511
512
|
}
|
|
512
513
|
return segments.join("\n");
|
|
513
514
|
}
|
|
515
|
+
function fileUnchangedResultText(rawResult, rawContent) {
|
|
516
|
+
for (const candidate of resultRecordCandidates(rawResult, rawContent)) {
|
|
517
|
+
if (candidate.type !== "file_unchanged") {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const file = asRecordOrNull(candidate.file);
|
|
521
|
+
const filePath = typeof file?.filePath === "string" ? file.filePath.trim() : "";
|
|
522
|
+
if (filePath) {
|
|
523
|
+
return `File unchanged: ${filePath}`;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return "";
|
|
527
|
+
}
|
|
528
|
+
function agentTitleFromAgentOutput(rawResult, rawContent) {
|
|
529
|
+
for (const candidate of resultRecordCandidates(rawResult, rawContent)) {
|
|
530
|
+
const agentType = typeof candidate.agentType === "string" ? candidate.agentType.trim() : "";
|
|
531
|
+
if (agentType) {
|
|
532
|
+
return agentType;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return "";
|
|
536
|
+
}
|
|
514
537
|
export function buildToolResultFields(isError, rawContent, base, rawResult) {
|
|
515
538
|
const toolName = resolveToolName(base);
|
|
539
|
+
const fields = {
|
|
540
|
+
status: isError ? "failed" : "completed",
|
|
541
|
+
};
|
|
542
|
+
const fileUnchangedText = !isError && toolName === "Read" ? fileUnchangedResultText(rawResult, rawContent) : "";
|
|
543
|
+
if (fileUnchangedText) {
|
|
544
|
+
fields.raw_output = fileUnchangedText;
|
|
545
|
+
fields.content = [{ type: "content", content: { type: "text", text: fileUnchangedText } }];
|
|
546
|
+
return fields;
|
|
547
|
+
}
|
|
548
|
+
const agentTitle = !isError && toolName === "Agent" ? agentTitleFromAgentOutput(rawResult, rawContent) : "";
|
|
549
|
+
if (agentTitle) {
|
|
550
|
+
fields.title = agentTitle;
|
|
551
|
+
}
|
|
516
552
|
const bashResultRecord = toolName === "Bash" ? findBashResultRecord(rawResult, rawContent) : undefined;
|
|
517
553
|
const normalizedRawOutput = normalizeToolResultText(rawContent, isError);
|
|
518
554
|
const rawOutput = bashResultRecord
|
|
519
555
|
? buildBashDisplayOutput(bashResultRecord)
|
|
520
556
|
: normalizedRawOutput || JSON.stringify(rawContent);
|
|
521
|
-
const fields = {
|
|
522
|
-
status: isError ? "failed" : "completed",
|
|
523
|
-
};
|
|
524
557
|
if (rawOutput) {
|
|
525
558
|
fields.raw_output = rawOutput;
|
|
526
559
|
}
|