claude-code-rust 0.12.0 → 0.12.2

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.
@@ -2,13 +2,17 @@ import { asRecordOrNull } from "./shared.js";
2
2
  import { toPermissionMode, buildModeState, refreshSupportedModesForSession } from "./commands.js";
3
3
  import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
4
4
  import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, } from "./tooling.js";
5
- import { emitToolCall, emitToolCallUpdate, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
5
+ import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
6
+ import { applyTaskLifecycleState } from "./tasks.js";
7
+ import { linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
6
8
  import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
7
9
  import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
8
- import { buildApiRetryUpdate, buildRateLimitUpdate, normalizeSettingsParseErrors, numberField, parseRuntimeSessionState, } from "./state_parsing.js";
10
+ import { mapInitSlashCommands, mapSdkSlashCommands, updateAvailableCommands, } from "./available_commands.js";
11
+ import { buildApiRetryUpdate, buildRateLimitUpdate, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
9
12
  import { looksLikeAuthRequired } from "./auth.js";
10
13
  import { emitCurrentModelUpdate, refreshCurrentModel, updateSessionId } from "./session_lifecycle.js";
11
14
  import { bridgeLogger, LOG_TARGETS } from "./logger.js";
15
+ import { emitMcpSnapshotFromStatuses } from "./mcp.js";
12
16
  export function textFromPrompt(command) {
13
17
  const chunks = command.chunks ?? [];
14
18
  return chunks
@@ -30,6 +34,68 @@ const SUPPORTED_IMAGE_MIME_TYPES = new Set([
30
34
  "image/gif",
31
35
  "image/webp",
32
36
  ]);
37
+ function sdkCorrelationMetadata(msg) {
38
+ return {
39
+ requestId: typeof msg.request_id === "string" ? msg.request_id : undefined,
40
+ subagentType: typeof msg.subagent_type === "string" ? msg.subagent_type : undefined,
41
+ taskDescription: typeof msg.task_description === "string" ? msg.task_description : undefined,
42
+ };
43
+ }
44
+ function sdkTaskMetadata(msg) {
45
+ const metadata = sdkCorrelationMetadata(msg);
46
+ const taskType = typeof msg.task_type === "string" && msg.task_type.length > 0 ? msg.task_type : undefined;
47
+ const workflowName = typeof msg.workflow_name === "string" && msg.workflow_name.length > 0 ? msg.workflow_name : undefined;
48
+ const prompt = typeof msg.prompt === "string" && msg.prompt.length > 0 ? msg.prompt : undefined;
49
+ const outputFile = typeof msg.output_file === "string" && msg.output_file.length > 0 ? msg.output_file : undefined;
50
+ const status = typeof msg.status === "string" && msg.status.length > 0 ? msg.status : undefined;
51
+ const summary = status && typeof msg.summary === "string" && msg.summary.length > 0 ? msg.summary : undefined;
52
+ const taskMetadata = {
53
+ ...(metadata.requestId ? { request_id: metadata.requestId } : {}),
54
+ ...(metadata.subagentType ? { subagent_type: metadata.subagentType } : {}),
55
+ ...(metadata.taskDescription ? { task_description: metadata.taskDescription } : {}),
56
+ ...(taskType ? { task_type: taskType } : {}),
57
+ ...(workflowName ? { workflow_name: workflowName } : {}),
58
+ ...(prompt ? { prompt } : {}),
59
+ ...(outputFile ? { output_file: outputFile } : {}),
60
+ ...(summary ? { summary } : {}),
61
+ ...(status ? { terminal_status: status } : {}),
62
+ };
63
+ return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
64
+ }
65
+ function sdkMessageOriginKind(msg) {
66
+ const origin = msg.origin && typeof msg.origin === "object" ? msg.origin : null;
67
+ return typeof origin?.kind === "string" ? origin.kind : undefined;
68
+ }
69
+ function logSdkMessageOrigin(session, msg) {
70
+ const originKind = sdkMessageOriginKind(msg);
71
+ if (!originKind) {
72
+ return;
73
+ }
74
+ bridgeLogger.debug({
75
+ target: LOG_TARGETS.APP_SESSION,
76
+ eventName: "sdk_message_origin_observed",
77
+ message: "SDK message origin observed",
78
+ outcome: originKind === "auto-continuation" ? "accepted" : "observed",
79
+ sessionId: session.sessionId,
80
+ fields: {
81
+ message_type: typeof msg.type === "string" ? msg.type : undefined,
82
+ origin_kind: originKind,
83
+ },
84
+ });
85
+ }
86
+ function emitSystemNoticeUpdate(session, severity, message) {
87
+ const trimmed = message.trim();
88
+ if (!trimmed) {
89
+ return;
90
+ }
91
+ emitSessionUpdate(session.sessionId, { type: "system_notice_update", severity, message: trimmed });
92
+ }
93
+ function notificationSeverity(priority) {
94
+ return priority === "high" || priority === "immediate" ? "warning" : "info";
95
+ }
96
+ function isTerminalToolStatus(status) {
97
+ return status === "completed" || status === "failed" || status === "killed";
98
+ }
33
99
  /** Fast check that a string looks like valid base64 (non-empty, correct charset & padding). */
34
100
  function isValidBase64(data) {
35
101
  if (!data)
@@ -98,12 +164,13 @@ export function handleTaskSystemMessage(session, subtype, msg) {
98
164
  subtype !== "task_progress" &&
99
165
  subtype !== "task_updated" &&
100
166
  subtype !== "task_notification") {
101
- return;
167
+ return false;
102
168
  }
103
169
  const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
104
170
  const explicitToolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
171
+ const messageTaskMetadata = sdkTaskMetadata(msg);
105
172
  if (taskId && explicitToolUseId) {
106
- session.taskToolUseIds.set(taskId, explicitToolUseId);
173
+ linkTaskToolUse(session, taskId, explicitToolUseId);
107
174
  }
108
175
  const toolUseId = resolveTaskToolUseId(session, msg);
109
176
  bridgeLogger.debug({
@@ -142,6 +209,7 @@ export function handleTaskSystemMessage(session, subtype, msg) {
142
209
  });
143
210
  }
144
211
  if (!toolUseId) {
212
+ applyTaskLifecycleState(session, subtype, msg);
145
213
  if (subtype === "task_updated" && taskId) {
146
214
  bridgeLogger.debug({
147
215
  target: LOG_TARGETS.APP_TOOL,
@@ -152,46 +220,54 @@ export function handleTaskSystemMessage(session, subtype, msg) {
152
220
  fields: { task_id: taskId, subtype },
153
221
  });
154
222
  }
155
- return;
223
+ return true;
156
224
  }
157
225
  const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
158
226
  if (!toolAcceptsTaskLifecycle(toolCall)) {
159
227
  if (taskId) {
160
- session.taskToolUseIds.delete(taskId);
228
+ unlinkTaskToolUse(session, taskId);
161
229
  }
162
- return;
230
+ return true;
163
231
  }
232
+ applyTaskLifecycleState(session, subtype, msg);
164
233
  if (toolCall.status === "pending") {
165
234
  emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
166
235
  }
167
236
  if (subtype === "task_started") {
168
237
  const description = typeof msg.description === "string" ? msg.description : "";
169
238
  if (!description) {
170
- return;
239
+ return true;
171
240
  }
172
- emitToolCallUpdate(session, toolUseId, {
241
+ const fields = {
173
242
  status: "in_progress",
174
243
  raw_output: description,
175
244
  content: [{ type: "content", content: { type: "text", text: description } }],
176
- }, "task_started");
177
- return;
245
+ ...(messageTaskMetadata ? { task_metadata: messageTaskMetadata } : {}),
246
+ };
247
+ emitToolCallUpdate(session, toolUseId, fields, "task_started");
248
+ return true;
178
249
  }
179
250
  if (subtype === "task_progress") {
180
251
  const progress = taskProgressText(msg);
181
252
  if (!progress) {
182
- return;
253
+ return true;
183
254
  }
184
- emitToolCallUpdate(session, toolUseId, {
255
+ const fields = {
185
256
  status: "in_progress",
186
257
  raw_output: progress,
187
258
  content: [{ type: "content", content: { type: "text", text: progress } }],
188
- }, "task_progress");
189
- return;
259
+ ...(messageTaskMetadata ? { task_metadata: messageTaskMetadata } : {}),
260
+ };
261
+ emitToolCallUpdate(session, toolUseId, fields, "task_progress");
262
+ return true;
190
263
  }
191
264
  if (subtype === "task_updated") {
192
265
  const fields = taskUpdatedFields(msg);
266
+ if (messageTaskMetadata) {
267
+ fields.task_metadata = { ...(fields.task_metadata ?? {}), ...messageTaskMetadata };
268
+ }
193
269
  if (Object.keys(fields).length === 0) {
194
- return;
270
+ return true;
195
271
  }
196
272
  bridgeLogger.debug({
197
273
  target: LOG_TARGETS.APP_TOOL,
@@ -209,20 +285,117 @@ export function handleTaskSystemMessage(session, subtype, msg) {
209
285
  },
210
286
  });
211
287
  emitToolCallUpdate(session, toolUseId, fields, "task_updated");
212
- return;
288
+ if (taskId && isTerminalToolStatus(fields.status)) {
289
+ unlinkTaskToolUse(session, taskId);
290
+ }
291
+ return true;
213
292
  }
214
293
  const status = typeof msg.status === "string" ? msg.status : "";
215
294
  const summary = typeof msg.summary === "string" ? msg.summary : "";
216
295
  const finalStatus = status === "completed" ? "completed" : status === "stopped" ? "killed" : "failed";
217
- const fields = { status: finalStatus };
296
+ const deferCompletion = finalStatus === "completed" && defersTaskNotificationCompletion(toolCall);
297
+ const fields = deferCompletion ? {} : { status: finalStatus };
298
+ if (messageTaskMetadata) {
299
+ fields.task_metadata = messageTaskMetadata;
300
+ }
218
301
  if (summary) {
219
302
  fields.raw_output = summary;
220
303
  fields.content = [{ type: "content", content: { type: "text", text: summary } }];
221
304
  }
222
- emitToolCallUpdate(session, toolUseId, fields, "task_notification");
223
- if (taskId) {
224
- session.taskToolUseIds.delete(taskId);
305
+ if (Object.keys(fields).length > 0) {
306
+ emitToolCallUpdate(session, toolUseId, fields, "task_notification");
307
+ }
308
+ if (taskId && !deferCompletion) {
309
+ unlinkTaskToolUse(session, taskId);
310
+ }
311
+ return true;
312
+ }
313
+ function stringField(msg, field) {
314
+ const value = msg[field];
315
+ return typeof value === "string" && value.length > 0 ? value : undefined;
316
+ }
317
+ function nullableStringField(msg, field) {
318
+ const value = msg[field];
319
+ return typeof value === "string" && value.length > 0 ? value : undefined;
320
+ }
321
+ function sourceMessageUuid(msg) {
322
+ return stringField(msg, "uuid");
323
+ }
324
+ function dedupeMessageUuids(value) {
325
+ if (!Array.isArray(value)) {
326
+ return [];
327
+ }
328
+ const seen = new Set();
329
+ const uuids = [];
330
+ for (const entry of value) {
331
+ if (typeof entry !== "string" || !entry) {
332
+ continue;
333
+ }
334
+ if (seen.has(entry)) {
335
+ continue;
336
+ }
337
+ seen.add(entry);
338
+ uuids.push(entry);
339
+ }
340
+ return uuids;
341
+ }
342
+ function emitTranscriptRetraction(session, messageUuids, reason, metadata = {}) {
343
+ const deduped = dedupeMessageUuids(messageUuids);
344
+ if (deduped.length === 0) {
345
+ return false;
225
346
  }
347
+ emitSessionUpdate(session.sessionId, {
348
+ type: "transcript_retraction",
349
+ message_uuids: deduped,
350
+ reason,
351
+ ...(metadata.requestId ? { request_id: metadata.requestId } : {}),
352
+ ...(metadata.trigger ? { trigger: metadata.trigger } : {}),
353
+ ...(metadata.direction ? { direction: metadata.direction } : {}),
354
+ ...(metadata.originalModel ? { original_model: metadata.originalModel } : {}),
355
+ ...(metadata.fallbackModel ? { fallback_model: metadata.fallbackModel } : {}),
356
+ ...(metadata.apiRefusalCategory ? { api_refusal_category: metadata.apiRefusalCategory } : {}),
357
+ ...(metadata.apiRefusalExplanation ? { api_refusal_explanation: metadata.apiRefusalExplanation } : {}),
358
+ ...(metadata.content ? { content: metadata.content } : {}),
359
+ });
360
+ return true;
361
+ }
362
+ function handleFallbackRetractionMessage(session, subtype, msg) {
363
+ if (subtype !== "model_refusal_fallback" && subtype !== "model_fallback") {
364
+ return false;
365
+ }
366
+ const reason = subtype === "model_fallback" ? "model_fallback" : "model_refusal_fallback";
367
+ const messageUuids = dedupeMessageUuids(msg.retracted_message_uuids);
368
+ const metadata = {
369
+ requestId: nullableStringField(msg, "request_id"),
370
+ trigger: stringField(msg, "trigger"),
371
+ direction: stringField(msg, "direction"),
372
+ originalModel: stringField(msg, "original_model"),
373
+ fallbackModel: stringField(msg, "fallback_model"),
374
+ apiRefusalCategory: nullableStringField(msg, "api_refusal_category"),
375
+ apiRefusalExplanation: nullableStringField(msg, "api_refusal_explanation"),
376
+ content: stringField(msg, "content"),
377
+ };
378
+ const emitted = emitTranscriptRetraction(session, messageUuids, reason, metadata);
379
+ bridgeLogger.info({
380
+ target: LOG_TARGETS.APP_SESSION,
381
+ eventName: "sdk_model_fallback_received",
382
+ message: "SDK model fallback retraction received",
383
+ outcome: emitted ? "success" : "observed",
384
+ sessionId: session.sessionId,
385
+ requestId: metadata.requestId,
386
+ count: messageUuids.length,
387
+ fields: {
388
+ sdk_subtype: subtype,
389
+ trigger: metadata.trigger,
390
+ direction: metadata.direction,
391
+ original_model: metadata.originalModel,
392
+ fallback_model: metadata.fallbackModel,
393
+ api_refusal_category: metadata.apiRefusalCategory,
394
+ has_api_refusal_explanation: metadata.apiRefusalExplanation !== undefined,
395
+ has_content: metadata.content !== undefined,
396
+ },
397
+ });
398
+ return true;
226
399
  }
227
400
  function logContentBlockLinkage(session, blockType, toolUseId, toolName, linkage) {
228
401
  if (!toolUseId && !linkage?.parentToolUseId) {
@@ -274,14 +447,22 @@ export function handleContentBlock(session, block, linkage) {
274
447
  if (blockType === "text") {
275
448
  const text = typeof block.text === "string" ? block.text : "";
276
449
  if (text) {
277
- emitSessionUpdate(session.sessionId, { type: "agent_message_chunk", content: { type: "text", text } });
450
+ emitSessionUpdate(session.sessionId, {
451
+ type: "agent_message_chunk",
452
+ content: { type: "text", text },
453
+ ...(linkage?.sourceMessageUuid ? { source_message_uuid: linkage.sourceMessageUuid } : {}),
454
+ });
278
455
  }
279
456
  return;
280
457
  }
281
458
  if (blockType === "thinking") {
282
459
  const text = typeof block.thinking === "string" ? block.thinking : "";
283
460
  if (text) {
284
- emitSessionUpdate(session.sessionId, { type: "agent_thought_chunk", content: { type: "text", text } });
461
+ emitSessionUpdate(session.sessionId, {
462
+ type: "agent_thought_chunk",
463
+ content: { type: "text", text },
464
+ ...(linkage?.sourceMessageUuid ? { source_message_uuid: linkage.sourceMessageUuid } : {}),
465
+ });
285
466
  }
286
467
  return;
287
468
  }
@@ -296,8 +477,7 @@ export function handleContentBlock(session, block, linkage) {
296
477
  return;
297
478
  }
298
479
  logContentBlockLinkage(session, blockType, toolUseId, name, linkage);
299
- emitPlanIfTodoWrite(session, name, input);
300
- emitToolCall(session, toolUseId, name, input, linkage?.parentToolUseId ?? null);
480
+ emitToolCall(session, toolUseId, name, input, linkage?.parentToolUseId ?? null, linkage?.metadata, linkage?.sourceMessageUuid);
301
481
  return;
302
482
  }
303
483
  if (TOOL_RESULT_TYPES.has(blockType)) {
@@ -310,16 +490,17 @@ export function handleContentBlock(session, block, linkage) {
310
490
  }
311
491
  logContentBlockLinkage(session, blockType, toolUseId, undefined, linkage);
312
492
  const isError = Boolean(block.is_error);
313
- emitToolResultUpdate(session, toolUseId, isError, block.content, block);
493
+ emitToolResultUpdate(session, toolUseId, isError, block.content, block, linkage?.sourceMessageUuid);
314
494
  }
315
495
  }
316
- export function handleStreamEvent(session, event, parentToolUseId) {
496
+ export function handleStreamEvent(session, event, parentToolUseId, sourceMessageUuid) {
317
497
  const eventType = typeof event.type === "string" ? event.type : "";
318
498
  if (eventType === "content_block_start") {
319
499
  if (event.content_block && typeof event.content_block === "object") {
320
500
  handleContentBlock(session, event.content_block, {
321
501
  source: "stream_event",
322
502
  parentToolUseId,
503
+ sourceMessageUuid,
323
504
  });
324
505
  }
325
506
  return;
@@ -333,22 +514,33 @@ export function handleStreamEvent(session, event, parentToolUseId) {
333
514
  if (deltaType === "text_delta") {
334
515
  const text = typeof delta.text === "string" ? delta.text : "";
335
516
  if (text) {
336
- emitSessionUpdate(session.sessionId, { type: "agent_message_chunk", content: { type: "text", text } });
517
+ emitSessionUpdate(session.sessionId, {
518
+ type: "agent_message_chunk",
519
+ content: { type: "text", text },
520
+ ...(sourceMessageUuid ? { source_message_uuid: sourceMessageUuid } : {}),
521
+ });
337
522
  }
338
523
  }
339
524
  else if (deltaType === "thinking_delta") {
340
525
  const text = typeof delta.thinking === "string" ? delta.thinking : "";
341
526
  if (text) {
342
- emitSessionUpdate(session.sessionId, { type: "agent_thought_chunk", content: { type: "text", text } });
527
+ emitSessionUpdate(session.sessionId, {
528
+ type: "agent_thought_chunk",
529
+ content: { type: "text", text },
530
+ ...(sourceMessageUuid ? { source_message_uuid: sourceMessageUuid } : {}),
531
+ });
343
532
  }
344
533
  }
345
534
  }
346
535
  }
347
536
  export function handleAssistantMessage(session, message) {
537
+ const assistantMessageUuid = sourceMessageUuid(message);
538
+ emitTranscriptRetraction(session, dedupeMessageUuids(message.supersedes), "assistant_supersedes");
348
539
  const assistantError = typeof message.error === "string" ? message.error : "";
349
540
  if (assistantError.length > 0) {
350
- session.lastAssistantError = assistantError;
541
+ session.lastAssistantError = parseApiRetryError(assistantError);
351
542
  }
543
+ const metadata = sdkCorrelationMetadata(message);
352
544
  const messageObject = message.message && typeof message.message === "object"
353
545
  ? message.message
354
546
  : null;
@@ -367,18 +559,33 @@ export function handleAssistantMessage(session, message) {
367
559
  blockType === "mcp_tool_use" ||
368
560
  TOOL_RESULT_TYPES.has(blockType)) {
369
561
  const parentToolUseId = typeof message.parent_tool_use_id === "string" ? message.parent_tool_use_id : undefined;
370
- handleContentBlock(session, blockRecord, { source: "assistant", parentToolUseId });
562
+ handleContentBlock(session, blockRecord, {
563
+ source: "assistant",
564
+ parentToolUseId,
565
+ metadata,
566
+ sourceMessageUuid: assistantMessageUuid,
567
+ });
371
568
  }
372
569
  }
373
570
  }
571
+ function messageToolUseResult(message) {
572
+ if (Object.hasOwn(message, "toolUseResult")) {
573
+ return message.toolUseResult;
574
+ }
575
+ if (Object.hasOwn(message, "tool_use_result")) {
576
+ return message.tool_use_result;
577
+ }
578
+ return undefined;
579
+ }
374
580
  export function handleUserToolResultBlocks(session, message) {
375
581
  const messageObject = message.message && typeof message.message === "object"
376
582
  ? message.message
377
583
  : null;
378
584
  if (!messageObject) {
379
- return;
585
+ return false;
380
586
  }
381
587
  const content = Array.isArray(messageObject.content) ? messageObject.content : [];
588
+ let handled = false;
382
589
  for (const block of content) {
383
590
  if (!block || typeof block !== "object") {
384
591
  continue;
@@ -387,9 +594,23 @@ export function handleUserToolResultBlocks(session, message) {
387
594
  const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
388
595
  if (TOOL_RESULT_TYPES.has(blockType)) {
389
596
  const parentToolUseId = typeof message.parent_tool_use_id === "string" ? message.parent_tool_use_id : undefined;
390
- handleContentBlock(session, blockRecord, { source: "user", parentToolUseId });
597
+ const toolUseId = typeof blockRecord.tool_use_id === "string" ? blockRecord.tool_use_id : "";
598
+ if (!toolUseId) {
599
+ continue;
600
+ }
601
+ handled = true;
602
+ if (isHiddenToolResult(session, toolUseId, blockType)) {
603
+ continue;
604
+ }
605
+ logContentBlockLinkage(session, blockType, toolUseId, undefined, {
606
+ source: "user",
607
+ parentToolUseId,
608
+ sourceMessageUuid: sourceMessageUuid(message),
609
+ });
610
+ emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message));
391
611
  }
392
612
  }
613
+ return handled;
393
614
  }
394
615
  export function handleResultMessage(session, message) {
395
616
  emitFastModeUpdateIfChanged(session, message.fast_mode_state);
@@ -419,6 +640,7 @@ export function handleResultMessage(session, message) {
419
640
  finalizeOpenToolCalls(session, "failed");
420
641
  const errorKind = classifyTurnErrorKind(subtype, errors, assistantError);
421
642
  const fallback = subtype ? `turn failed: ${subtype}` : "turn failed";
643
+ const apiErrorStatus = numberField(message, "api_error_status", "apiErrorStatus");
422
644
  writeEvent({
423
645
  event: "turn_error",
424
646
  session_id: session.sessionId,
@@ -426,6 +648,7 @@ export function handleResultMessage(session, message) {
426
648
  error_kind: errorKind,
427
649
  ...(subtype ? { sdk_result_subtype: subtype } : {}),
428
650
  ...(assistantError ? { assistant_error: assistantError } : {}),
651
+ ...(apiErrorStatus !== undefined ? { api_error_status: apiErrorStatus } : {}),
429
652
  ...(terminalReason ? { terminal_reason: terminalReason } : {}),
430
653
  });
431
654
  session.lastAssistantError = undefined;
@@ -452,8 +675,96 @@ function terminalReasonFromValue(value) {
452
675
  export function handleSdkMessage(session, message) {
453
676
  const msg = message;
454
677
  const type = typeof msg.type === "string" ? msg.type : "";
678
+ logSdkMessageOrigin(session, msg);
455
679
  if (type === "system") {
456
680
  const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
681
+ if (handleFallbackRetractionMessage(session, subtype, msg)) {
682
+ return;
683
+ }
684
+ if (subtype === "commands_changed") {
685
+ updateAvailableCommands(session, "commands_changed", mapSdkSlashCommands(msg.commands));
686
+ return;
687
+ }
688
+ if (subtype === "notification") {
689
+ const text = typeof msg.text === "string" ? msg.text : "";
690
+ emitSystemNoticeUpdate(session, notificationSeverity(msg.priority), text);
691
+ return;
692
+ }
693
+ if (subtype === "mirror_error") {
694
+ const error = typeof msg.error === "string" ? msg.error : "";
695
+ const key = asRecordOrNull(msg.key);
696
+ bridgeLogger.warn({
697
+ target: LOG_TARGETS.APP_SESSION,
698
+ eventName: "sdk_mirror_error_received",
699
+ message: "SDK transcript mirror error received",
700
+ outcome: "failure",
701
+ sessionId: session.sessionId,
702
+ fields: {
703
+ error_message: error || undefined,
704
+ project_key: typeof key?.projectKey === "string" ? key.projectKey : undefined,
705
+ mirror_session_id: typeof key?.sessionId === "string" ? key.sessionId : undefined,
706
+ subpath: typeof key?.subpath === "string" ? key.subpath : undefined,
707
+ },
708
+ });
709
+ return;
710
+ }
711
+ if (subtype === "plugin_install") {
712
+ const status = typeof msg.status === "string" ? msg.status : "";
713
+ const name = typeof msg.name === "string" ? msg.name : "";
714
+ const error = typeof msg.error === "string" ? msg.error : "";
715
+ bridgeLogger.info({
716
+ target: LOG_TARGETS.BRIDGE_SDK,
717
+ eventName: "sdk_plugin_install_received",
718
+ message: "SDK plugin install event received",
719
+ outcome: status === "failed" ? "failure" : status || "observed",
720
+ sessionId: session.sessionId,
721
+ fields: {
722
+ plugin_status: status || undefined,
723
+ plugin_name: name || undefined,
724
+ error_message: error || undefined,
725
+ },
726
+ });
727
+ if (status === "failed") {
728
+ const subject = name ? ` ${name}` : "";
729
+ const suffix = error ? `: ${error}` : ".";
730
+ emitSystemNoticeUpdate(session, "warning", `Plugin install failed${subject}${suffix}`);
731
+ }
732
+ return;
733
+ }
734
+ if (subtype === "permission_denied") {
735
+ bridgeLogger.info({
736
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
737
+ eventName: "sdk_permission_denied_received",
738
+ message: "SDK permission denied event received",
739
+ outcome: "denied",
740
+ sessionId: session.sessionId,
741
+ toolCallId: typeof msg.tool_use_id === "string" ? msg.tool_use_id : undefined,
742
+ fields: {
743
+ tool_name: typeof msg.tool_name === "string" ? msg.tool_name : undefined,
744
+ agent_id: typeof msg.agent_id === "string" ? msg.agent_id : undefined,
745
+ decision_reason_type: typeof msg.decision_reason_type === "string" ? msg.decision_reason_type : undefined,
746
+ decision_reason: typeof msg.decision_reason === "string" ? msg.decision_reason : undefined,
747
+ denial_message: typeof msg.message === "string" ? msg.message : undefined,
748
+ },
749
+ });
750
+ return;
751
+ }
752
+ if (subtype === "memory_recall" || subtype === "thinking_tokens") {
753
+ bridgeLogger.debug({
754
+ target: LOG_TARGETS.BRIDGE_SDK,
755
+ eventName: "sdk_system_message_log_only",
756
+ message: "SDK system message handled with log-only policy",
757
+ outcome: "ignored",
758
+ sessionId: session.sessionId,
759
+ fields: {
760
+ sdk_subtype: subtype,
761
+ memory_count: Array.isArray(msg.memories) ? msg.memories.length : undefined,
762
+ estimated_tokens: typeof msg.estimated_tokens === "number" ? msg.estimated_tokens : undefined,
763
+ estimated_tokens_delta: typeof msg.estimated_tokens_delta === "number" ? msg.estimated_tokens_delta : undefined,
764
+ },
765
+ });
766
+ return;
767
+ }
457
768
  if (subtype === "api_retry") {
458
769
  const update = buildApiRetryUpdate(msg);
459
770
  if (update) {
@@ -502,12 +813,10 @@ export function handleSdkMessage(session, message) {
502
813
  }
503
814
  }
504
815
  if (Array.isArray(msg.slash_commands)) {
505
- const commands = msg.slash_commands
506
- .filter((entry) => typeof entry === "string")
507
- .map((name) => ({ name, description: "", input_hint: undefined }));
508
- if (commands.length > 0) {
509
- emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands });
510
- }
816
+ updateAvailableCommands(session, "init_slash_commands", mapInitSlashCommands(msg.slash_commands));
817
+ }
818
+ if (Array.isArray(msg.mcp_servers)) {
819
+ emitMcpSnapshotFromStatuses(session, msg.mcp_servers, "init");
511
820
  }
512
821
  if (session.lastAvailableAgentsSignature === undefined && Array.isArray(msg.agents)) {
513
822
  emitAvailableAgentsIfChanged(session, mapAvailableAgentsFromNames(msg.agents));
@@ -515,12 +824,8 @@ export function handleSdkMessage(session, message) {
515
824
  void session.query
516
825
  .supportedCommands()
517
826
  .then((commands) => {
518
- const mapped = commands.map((command) => ({
519
- name: command.name,
520
- description: command.description ?? "",
521
- input_hint: command.argumentHint ?? undefined,
522
- }));
523
- emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands: mapped });
827
+ const mapped = mapSdkSlashCommands(commands);
828
+ updateAvailableCommands(session, "supportedCommands", mapped);
524
829
  })
525
830
  .catch(() => {
526
831
  // Best-effort only; slash commands from init were already emitted.
@@ -544,6 +849,9 @@ export function handleSdkMessage(session, message) {
544
849
  if (msg.status === "compacting") {
545
850
  emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "compacting" });
546
851
  }
852
+ else if (msg.status === "requesting") {
853
+ emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "requesting" });
854
+ }
547
855
  else if (msg.status === null) {
548
856
  emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
549
857
  }
@@ -572,6 +880,7 @@ export function handleSdkMessage(session, message) {
572
880
  emitSessionUpdate(session.sessionId, {
573
881
  type: "agent_message_chunk",
574
882
  content: { type: "text", text: content },
883
+ ...(sourceMessageUuid(msg) ? { source_message_uuid: sourceMessageUuid(msg) } : {}),
575
884
  });
576
885
  }
577
886
  return;
@@ -591,7 +900,19 @@ export function handleSdkMessage(session, message) {
591
900
  });
592
901
  return;
593
902
  }
594
- handleTaskSystemMessage(session, subtype, msg);
903
+ if (handleTaskSystemMessage(session, subtype, msg)) {
904
+ return;
905
+ }
906
+ bridgeLogger.debug({
907
+ target: LOG_TARGETS.BRIDGE_SDK,
908
+ eventName: "sdk_system_message_unhandled",
909
+ message: "SDK system message ignored by explicit fallback policy",
910
+ outcome: "ignored",
911
+ sessionId: session.sessionId,
912
+ fields: {
913
+ sdk_subtype: subtype || undefined,
914
+ },
915
+ });
595
916
  return;
596
917
  }
597
918
  if (type === "prompt_suggestion") {
@@ -624,14 +945,17 @@ export function handleSdkMessage(session, message) {
624
945
  if (type === "stream_event") {
625
946
  if (msg.event && typeof msg.event === "object") {
626
947
  const parentToolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : undefined;
627
- handleStreamEvent(session, msg.event, parentToolUseId);
948
+ handleStreamEvent(session, msg.event, parentToolUseId, sourceMessageUuid(msg));
628
949
  }
629
950
  return;
630
951
  }
631
952
  if (type === "tool_progress") {
632
953
  const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
633
954
  const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
634
- if (isHiddenToolUse(session, toolUseId, toolName)) {
955
+ const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
956
+ const taskToolUseId = taskId ? session.taskToolUseIds.get(taskId) ?? "" : "";
957
+ const resolvedToolUseId = taskToolUseId || toolUseId;
958
+ if (isHiddenToolUse(session, resolvedToolUseId, toolName)) {
635
959
  return;
636
960
  }
637
961
  bridgeLogger.debug({
@@ -640,16 +964,17 @@ export function handleSdkMessage(session, message) {
640
964
  message: "SDK tool progress linkage observed",
641
965
  outcome: typeof msg.parent_tool_use_id === "string" ? "child" : "root_or_unknown",
642
966
  sessionId: session.sessionId,
643
- toolCallId: toolUseId || undefined,
967
+ toolCallId: resolvedToolUseId || undefined,
644
968
  fields: {
645
969
  tool_name: toolName,
646
970
  tool_use_id: toolUseId || undefined,
647
971
  parent_tool_use_id: typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : undefined,
648
- task_id: typeof msg.task_id === "string" ? msg.task_id : undefined,
972
+ task_id: taskId || undefined,
973
+ task_resolved_tool_use_id: taskToolUseId || undefined,
649
974
  },
650
975
  });
651
- if (toolUseId) {
652
- emitToolProgressUpdate(session, toolUseId, toolName);
976
+ if (resolvedToolUseId) {
977
+ emitToolProgressUpdate(session, resolvedToolUseId, toolName);
653
978
  }
654
979
  return;
655
980
  }
@@ -671,6 +996,23 @@ export function handleSdkMessage(session, message) {
671
996
  if (type === "rate_limit_event") {
672
997
  const rateLimitInfo = asRecordOrNull(msg.rate_limit_info);
673
998
  const update = buildRateLimitUpdate(msg.rate_limit_info);
999
+ const rawIsUsingOverage = typeof rateLimitInfo?.isUsingOverage === "boolean" ? rateLimitInfo.isUsingOverage : undefined;
1000
+ const rawOverageInUse = typeof rateLimitInfo?.overageInUse === "boolean" ? rateLimitInfo.overageInUse : undefined;
1001
+ if (rawIsUsingOverage !== undefined &&
1002
+ rawOverageInUse !== undefined &&
1003
+ rawIsUsingOverage !== rawOverageInUse) {
1004
+ bridgeLogger.warn({
1005
+ target: LOG_TARGETS.APP_SESSION,
1006
+ eventName: "sdk_rate_limit_overage_spelling_conflict",
1007
+ message: "SDK rate limit overage booleans conflict",
1008
+ outcome: "using_overageInUse",
1009
+ sessionId: session.sessionId,
1010
+ fields: {
1011
+ raw_is_using_overage: rawIsUsingOverage,
1012
+ raw_overage_in_use: rawOverageInUse,
1013
+ },
1014
+ });
1015
+ }
674
1016
  bridgeLogger.debug({
675
1017
  target: LOG_TARGETS.APP_SESSION,
676
1018
  eventName: "sdk_rate_limit_event_received",
@@ -684,7 +1026,8 @@ export function handleSdkMessage(session, message) {
684
1026
  raw_resets_at: numberField(rateLimitInfo ?? {}, "resetsAt"),
685
1027
  raw_overage_status: typeof rateLimitInfo?.overageStatus === "string" ? rateLimitInfo.overageStatus : undefined,
686
1028
  raw_overage_resets_at: numberField(rateLimitInfo ?? {}, "overageResetsAt"),
687
- raw_is_using_overage: typeof rateLimitInfo?.isUsingOverage === "boolean" ? rateLimitInfo.isUsingOverage : undefined,
1029
+ raw_is_using_overage: rawIsUsingOverage,
1030
+ raw_overage_in_use: rawOverageInUse,
688
1031
  raw_surpassed_threshold: numberField(rateLimitInfo ?? {}, "surpassedThreshold"),
689
1032
  parsed_status: update?.status,
690
1033
  parsed_rate_limit_type: update?.rate_limit_type,
@@ -712,14 +1055,15 @@ export function handleSdkMessage(session, message) {
712
1055
  return;
713
1056
  }
714
1057
  if (type === "user") {
715
- handleUserToolResultBlocks(session, msg);
1058
+ const handledBlocks = handleUserToolResultBlocks(session, msg);
716
1059
  const toolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : "";
717
- if (toolUseId && "tool_use_result" in msg) {
1060
+ const rawToolUseResult = messageToolUseResult(msg);
1061
+ if (!handledBlocks && toolUseId && rawToolUseResult !== undefined) {
718
1062
  if (session.hiddenToolUseIds.has(toolUseId)) {
719
1063
  return;
720
1064
  }
721
- const parsed = unwrapToolUseResult(msg.tool_use_result);
722
- emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, msg.tool_use_result);
1065
+ const parsed = unwrapToolUseResult(rawToolUseResult);
1066
+ emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg));
723
1067
  }
724
1068
  return;
725
1069
  }