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.
@@ -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
- export function emitToolCall(session, toolUseId, name, input) {
4
- const toolCall = createToolCall(toolUseId, name, input);
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.toolCalls.set(toolUseId, toolCall);
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
- emitSessionUpdate(session.sessionId, {
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.toolCalls.set(toolUseId, toolCall);
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 base = session.toolCalls.get(toolUseId);
72
- const fields = buildToolResultFields(isError, rawContent, base, rawResult);
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
- const fields = { status };
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
- const fields = { status: "in_progress" };
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" ? "failed" : "completed",
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
- emitSessionUpdate(session.sessionId, {
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
- emitSessionUpdate(session.sessionId, {
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
- pushRecord(record.result);
143
- pushRecord(record.data);
144
- pushRecord(record.content);
158
+ pushRecords(record.result);
159
+ pushRecords(record.data);
160
+ pushRecords(record.content);
145
161
  };
146
- pushRecord(rawResult);
162
+ pushRecords(rawResult);
147
163
  pushNestedRecords(rawResult);
148
- pushRecord(rawContent);
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
- const hasTokenSaverOutput = typeof candidate.tokenSaverOutput === "string" && candidate.tokenSaverOutput.length > 0;
234
- if (hasAssistantAutoBackgrounded || hasTokenSaverOutput) {
249
+ if (hasAssistantAutoBackgrounded) {
235
250
  const bashMetadata = {};
236
- if (hasAssistantAutoBackgrounded) {
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
  }