claude-code-rust 0.14.1 → 0.14.3

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.
@@ -1,8 +1,9 @@
1
1
  import { emitMcpOperationError, slashError, writeEvent } from "./events.js";
2
2
  import { bridgeLogger, LOG_TARGETS } from "./logger.js";
3
3
  import { bridgeMcpServersToSdk, mapMcpServerStatus, summarizeMcpServersForDiagnostics, } from "./mcp_metadata.js";
4
+ import { authenticateMcpServer, clearMcpServerAuth, detectMcpAuthCapabilities, submitMcpOAuthCallbackUrl, } from "./mcp_auth_adapter.js";
5
+ import { runMcpAuthMonitor, } from "./mcp_monitor.js";
4
6
  export const MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS = 30_000;
5
- const knownConnectedMcpServers = new Set();
6
7
  function logMcpSuccess(eventName, message, sessionId, requestId, fields) {
7
8
  bridgeLogger.info({
8
9
  target: LOG_TARGETS.BRIDGE_MCP,
@@ -28,9 +29,6 @@ function logMcpFailure(eventName, message, sessionId, errorMessage, requestId, f
28
29
  },
29
30
  });
30
31
  }
31
- function queryWithMcpAuth(session) {
32
- return session.query;
33
- }
34
32
  function mapMcpSetServersResult(result) {
35
33
  if (!result || typeof result !== "object" || Array.isArray(result)) {
36
34
  return { added: [], removed: [], errors: {} };
@@ -47,41 +45,6 @@ function mapMcpSetServersResult(result) {
47
45
  : {};
48
46
  return { added, removed, errors };
49
47
  }
50
- async function callMcpAuthMethod(session, methodName, args) {
51
- const query = queryWithMcpAuth(session);
52
- switch (methodName) {
53
- case "mcpAuthenticate":
54
- if (typeof query.mcpAuthenticate !== "function") {
55
- throw new Error("installed SDK does not support mcpAuthenticate");
56
- }
57
- return await query.mcpAuthenticate(args[0] ?? "");
58
- case "mcpClearAuth":
59
- if (typeof query.mcpClearAuth !== "function") {
60
- throw new Error("installed SDK does not support mcpClearAuth");
61
- }
62
- return await query.mcpClearAuth(args[0] ?? "");
63
- case "mcpSubmitOAuthCallbackUrl":
64
- if (typeof query.mcpSubmitOAuthCallbackUrl !== "function") {
65
- throw new Error("installed SDK does not support mcpSubmitOAuthCallbackUrl");
66
- }
67
- return await query.mcpSubmitOAuthCallbackUrl(args[0] ?? "", args[1] ?? "");
68
- }
69
- }
70
- function extractMcpAuthRedirect(serverName, value) {
71
- if (!value || typeof value !== "object") {
72
- return null;
73
- }
74
- const authUrl = Reflect.get(value, "authUrl");
75
- if (typeof authUrl !== "string" || authUrl.trim().length === 0) {
76
- return null;
77
- }
78
- const requiresUserAction = Reflect.get(value, "requiresUserAction");
79
- return {
80
- server_name: serverName,
81
- auth_url: authUrl,
82
- requires_user_action: requiresUserAction === true,
83
- };
84
- }
85
48
  function emitMcpCommandError(sessionId, operation, message, requestId, serverName) {
86
49
  emitMcpOperationError(sessionId, {
87
50
  ...(serverName ? { server_name: serverName } : {}),
@@ -90,9 +53,10 @@ function emitMcpCommandError(sessionId, operation, message, requestId, serverNam
90
53
  }, requestId);
91
54
  }
92
55
  export async function emitMcpSnapshotEvent(session, requestId, source = "mcp_status") {
93
- const servers = await session.query.mcpServerStatus();
94
- let mapped = servers.map(mapMcpServerStatus);
95
- mapped = await reconcileSuspiciousMcpStatuses(session, mapped);
56
+ const mapped = await loadReconciledMcpStatuses(session);
57
+ if (!mapped) {
58
+ return [];
59
+ }
96
60
  return emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId);
97
61
  }
98
62
  export function emitMcpSnapshotFromStatuses(session, servers, source, requestId) {
@@ -104,7 +68,7 @@ export async function emitReconciledMcpSnapshotFromStatuses(session, servers, so
104
68
  return emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId);
105
69
  }
106
70
  function emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId) {
107
- rememberKnownConnectedMcpServers(mapped);
71
+ rememberKnownConnectedMcpServers(session, mapped);
108
72
  logMcpSuccess("mcp_snapshot_emitted", "MCP snapshot emitted", session.sessionId, requestId, {
109
73
  source,
110
74
  server_count: mapped.length,
@@ -115,6 +79,7 @@ function emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId) {
115
79
  session_id: session.sessionId,
116
80
  source,
117
81
  servers: mapped,
82
+ auth_capabilities: detectMcpAuthCapabilities(session.query),
118
83
  }, requestId);
119
84
  return mapped;
120
85
  }
@@ -132,23 +97,33 @@ export function staleMcpAuthCandidates(servers, knownConnectedServerNames, lastR
132
97
  })
133
98
  .map((server) => server.name);
134
99
  }
135
- function rememberKnownConnectedMcpServers(servers) {
100
+ function rememberKnownConnectedMcpServers(session, servers) {
136
101
  for (const server of servers) {
137
102
  if (server.status === "connected") {
138
- knownConnectedMcpServers.add(server.name);
103
+ session.knownConnectedMcpServers.add(server.name);
139
104
  }
140
105
  }
141
106
  }
142
- function forgetKnownConnectedMcpServer(serverName) {
143
- knownConnectedMcpServers.delete(serverName);
107
+ function forgetKnownConnectedMcpServer(session, serverName) {
108
+ session.knownConnectedMcpServers.delete(serverName);
144
109
  }
145
- async function reconcileSuspiciousMcpStatuses(session, servers) {
146
- const candidates = staleMcpAuthCandidates(servers, knownConnectedMcpServers, session.mcpStatusRevalidatedAt);
110
+ async function loadReconciledMcpStatuses(session, isActive = () => true) {
111
+ const servers = await session.query.mcpServerStatus();
112
+ if (!isActive()) {
113
+ return undefined;
114
+ }
115
+ return await reconcileSuspiciousMcpStatuses(session, servers.map(mapMcpServerStatus), isActive);
116
+ }
117
+ async function reconcileSuspiciousMcpStatuses(session, servers, isActive = () => true) {
118
+ const candidates = staleMcpAuthCandidates(servers, session.knownConnectedMcpServers, session.mcpStatusRevalidatedAt);
147
119
  if (candidates.length === 0) {
148
120
  return servers;
149
121
  }
150
122
  const now = Date.now();
151
123
  for (const serverName of candidates) {
124
+ if (!isActive()) {
125
+ return undefined;
126
+ }
152
127
  session.mcpStatusRevalidatedAt.set(serverName, now);
153
128
  bridgeLogger.info({
154
129
  target: LOG_TARGETS.BRIDGE_MCP,
@@ -165,8 +140,14 @@ async function reconcileSuspiciousMcpStatuses(session, servers) {
165
140
  });
166
141
  try {
167
142
  await session.query.reconnectMcpServer(serverName);
143
+ if (!isActive()) {
144
+ return undefined;
145
+ }
168
146
  }
169
147
  catch (error) {
148
+ if (!isActive()) {
149
+ return undefined;
150
+ }
170
151
  const message = error instanceof Error ? error.message : String(error);
171
152
  bridgeLogger.warn({
172
153
  target: LOG_TARGETS.BRIDGE_MCP,
@@ -182,35 +163,67 @@ async function reconcileSuspiciousMcpStatuses(session, servers) {
182
163
  });
183
164
  }
184
165
  }
185
- return (await session.query.mcpServerStatus()).map(mapMcpServerStatus);
166
+ const refreshed = await session.query.mcpServerStatus();
167
+ return isActive() ? refreshed.map(mapMcpServerStatus) : undefined;
186
168
  }
187
169
  function shouldKeepMonitoringMcpAuth(server) {
188
170
  return server?.status === "needs-auth" || server?.status === "pending";
189
171
  }
190
- function scheduleMcpAuthSnapshotMonitor(session, serverName, attempt = 0) {
191
- const maxAttempts = 180;
192
- const delayMs = 1000;
193
- setTimeout(() => {
194
- void monitorMcpAuthSnapshot(session, serverName, attempt + 1, maxAttempts, delayMs);
195
- }, delayMs);
172
+ function logMcpAuthMonitorExhausted(session, serverName, result) {
173
+ bridgeLogger.warn({
174
+ target: LOG_TARGETS.BRIDGE_MCP,
175
+ eventName: "mcp_auth_monitor_exhausted",
176
+ message: "MCP authentication status monitor exhausted",
177
+ outcome: "failure",
178
+ sessionId: session.sessionId,
179
+ count: result.attempts,
180
+ fields: {
181
+ server_name: serverName,
182
+ attempts: result.attempts,
183
+ reason: result.reason,
184
+ ...(result.lastError ? { error_message: result.lastError } : {}),
185
+ },
186
+ });
196
187
  }
197
- async function monitorMcpAuthSnapshot(session, serverName, attempt, maxAttempts, delayMs) {
198
- try {
199
- const servers = await emitMcpSnapshotEvent(session);
200
- const server = servers.find((candidate) => candidate.name === serverName);
201
- if (attempt < maxAttempts && shouldKeepMonitoringMcpAuth(server)) {
202
- setTimeout(() => {
203
- void monitorMcpAuthSnapshot(session, serverName, attempt + 1, maxAttempts, delayMs);
204
- }, delayMs);
205
- }
188
+ export function startMcpAuthSnapshotMonitor(session, serverName, timing = {}) {
189
+ if (session.closing) {
190
+ return Promise.resolve({ outcome: "cancelled", attempts: 0 });
206
191
  }
207
- catch {
208
- if (attempt < maxAttempts) {
209
- setTimeout(() => {
210
- void monitorMcpAuthSnapshot(session, serverName, attempt + 1, maxAttempts, delayMs);
211
- }, delayMs);
212
- }
192
+ const existing = session.mcpAuthMonitors.get(serverName);
193
+ if (existing) {
194
+ return existing.task;
213
195
  }
196
+ const controller = new AbortController();
197
+ let monitor;
198
+ const isActive = () => !session.closing &&
199
+ !controller.signal.aborted &&
200
+ session.mcpAuthMonitors.get(serverName) === monitor;
201
+ const task = runMcpAuthMonitor({
202
+ ...timing,
203
+ signal: controller.signal,
204
+ poll: async () => {
205
+ const servers = await loadReconciledMcpStatuses(session, isActive);
206
+ if (!isActive() || !servers) {
207
+ return "complete";
208
+ }
209
+ emitMcpSnapshotFromMappedStatuses(session, servers, "mcp_status");
210
+ const server = servers.find((candidate) => candidate.name === serverName);
211
+ return shouldKeepMonitoringMcpAuth(server) ? "continue" : "complete";
212
+ },
213
+ }).then((result) => {
214
+ if (result.outcome === "exhausted") {
215
+ logMcpAuthMonitorExhausted(session, serverName, result);
216
+ }
217
+ return result;
218
+ });
219
+ monitor = { controller, task };
220
+ session.mcpAuthMonitors.set(serverName, monitor);
221
+ void task.then(() => {
222
+ if (session.mcpAuthMonitors.get(serverName) === monitor) {
223
+ session.mcpAuthMonitors.delete(serverName);
224
+ }
225
+ });
226
+ return task;
214
227
  }
215
228
  export async function handleMcpStatusCommand(session, requestId, source = "mcp_status") {
216
229
  try {
@@ -224,6 +237,7 @@ export async function handleMcpStatusCommand(session, requestId, source = "mcp_s
224
237
  session_id: session.sessionId,
225
238
  source,
226
239
  servers: [],
240
+ auth_capabilities: detectMcpAuthCapabilities(session.query),
227
241
  error: message,
228
242
  }, requestId);
229
243
  }
@@ -277,8 +291,7 @@ export async function handleMcpSetServersCommand(session, command, requestId) {
277
291
  }
278
292
  export async function handleMcpAuthenticateCommand(session, command, requestId) {
279
293
  try {
280
- const result = await callMcpAuthMethod(session, "mcpAuthenticate", [command.server_name]);
281
- const redirect = extractMcpAuthRedirect(command.server_name, result);
294
+ const redirect = await authenticateMcpServer(session.query, command.server_name);
282
295
  if (redirect) {
283
296
  logMcpSuccess("mcp_auth_redirect_emitted", "MCP auth redirect emitted", command.session_id, requestId, { server_name: command.server_name, requires_user_action: redirect.requires_user_action });
284
297
  writeEvent({
@@ -290,7 +303,7 @@ export async function handleMcpAuthenticateCommand(session, command, requestId)
290
303
  else {
291
304
  logMcpSuccess("mcp_authenticate_completed", "MCP authentication command completed", command.session_id, requestId, { server_name: command.server_name });
292
305
  }
293
- scheduleMcpAuthSnapshotMonitor(session, command.server_name);
306
+ startMcpAuthSnapshotMonitor(session, command.server_name);
294
307
  }
295
308
  catch (error) {
296
309
  const message = error instanceof Error ? error.message : String(error);
@@ -300,8 +313,8 @@ export async function handleMcpAuthenticateCommand(session, command, requestId)
300
313
  }
301
314
  export async function handleMcpClearAuthCommand(session, command, requestId) {
302
315
  try {
303
- await callMcpAuthMethod(session, "mcpClearAuth", [command.server_name]);
304
- forgetKnownConnectedMcpServer(command.server_name);
316
+ await clearMcpServerAuth(session.query, command.server_name);
317
+ forgetKnownConnectedMcpServer(session, command.server_name);
305
318
  session.mcpStatusRevalidatedAt.delete(command.server_name);
306
319
  logMcpSuccess("mcp_clear_auth_completed", "MCP auth cleared", command.session_id, requestId, {
307
320
  server_name: command.server_name,
@@ -315,10 +328,7 @@ export async function handleMcpClearAuthCommand(session, command, requestId) {
315
328
  }
316
329
  export async function handleMcpOauthCallbackUrlCommand(session, command, requestId) {
317
330
  try {
318
- await callMcpAuthMethod(session, "mcpSubmitOAuthCallbackUrl", [
319
- command.server_name,
320
- command.callback_url,
321
- ]);
331
+ await submitMcpOAuthCallbackUrl(session.query, command.server_name, command.callback_url);
322
332
  logMcpSuccess("mcp_oauth_callback_completed", "MCP OAuth callback URL submitted", command.session_id, requestId, {
323
333
  server_name: command.server_name,
324
334
  callback_url_chars: command.callback_url.length,
@@ -0,0 +1,67 @@
1
+ const MCP_AUTH_METHODS = {
2
+ authenticate: "mcpAuthenticate",
3
+ clear_auth: "mcpClearAuth",
4
+ submit_oauth_callback_url: "mcpSubmitOAuthCallbackUrl",
5
+ };
6
+ function runtimeMethod(query, methodName) {
7
+ if ((!query || typeof query !== "object") && typeof query !== "function") {
8
+ return undefined;
9
+ }
10
+ const method = Reflect.get(query, methodName);
11
+ return typeof method === "function" ? method : undefined;
12
+ }
13
+ function requiredRuntimeMethod(query, methodName) {
14
+ const method = runtimeMethod(query, methodName);
15
+ if (!method) {
16
+ throw new Error(`installed SDK does not support ${methodName}`);
17
+ }
18
+ return method;
19
+ }
20
+ async function invokeRuntimeMethod(query, methodName, args) {
21
+ const method = requiredRuntimeMethod(query, methodName);
22
+ return await Reflect.apply(method, query, args);
23
+ }
24
+ export function detectMcpAuthCapabilities(query) {
25
+ return {
26
+ authenticate: runtimeMethod(query, MCP_AUTH_METHODS.authenticate) !== undefined,
27
+ clear_auth: runtimeMethod(query, MCP_AUTH_METHODS.clear_auth) !== undefined,
28
+ submit_oauth_callback_url: runtimeMethod(query, MCP_AUTH_METHODS.submit_oauth_callback_url) !== undefined,
29
+ };
30
+ }
31
+ function parseMcpAuthRedirect(serverName, value) {
32
+ if (value === undefined || value === null) {
33
+ return null;
34
+ }
35
+ if (typeof value !== "object" || Array.isArray(value)) {
36
+ throw new Error("installed SDK returned an invalid mcpAuthenticate response");
37
+ }
38
+ const authUrl = Reflect.get(value, "authUrl");
39
+ if (authUrl === undefined || authUrl === null) {
40
+ return null;
41
+ }
42
+ if (typeof authUrl !== "string" || authUrl.trim().length === 0) {
43
+ throw new Error("installed SDK returned an invalid mcpAuthenticate authUrl");
44
+ }
45
+ const requiresUserAction = Reflect.get(value, "requiresUserAction");
46
+ if (requiresUserAction !== undefined && typeof requiresUserAction !== "boolean") {
47
+ throw new Error("installed SDK returned an invalid mcpAuthenticate requiresUserAction");
48
+ }
49
+ return {
50
+ server_name: serverName,
51
+ auth_url: authUrl,
52
+ requires_user_action: requiresUserAction === true,
53
+ };
54
+ }
55
+ export async function authenticateMcpServer(query, serverName) {
56
+ const result = await invokeRuntimeMethod(query, MCP_AUTH_METHODS.authenticate, [serverName]);
57
+ return parseMcpAuthRedirect(serverName, result);
58
+ }
59
+ export async function clearMcpServerAuth(query, serverName) {
60
+ await invokeRuntimeMethod(query, MCP_AUTH_METHODS.clear_auth, [serverName]);
61
+ }
62
+ export async function submitMcpOAuthCallbackUrl(query, serverName, callbackUrl) {
63
+ await invokeRuntimeMethod(query, MCP_AUTH_METHODS.submit_oauth_callback_url, [
64
+ serverName,
65
+ callbackUrl,
66
+ ]);
67
+ }
@@ -0,0 +1,59 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ const DEFAULT_MAX_ATTEMPTS = 24;
3
+ const DEFAULT_INITIAL_DELAY_MS = 1_000;
4
+ const DEFAULT_MAX_DELAY_MS = 10_000;
5
+ async function abortableDelay(delayMs, signal) {
6
+ await delay(delayMs, undefined, { signal, ref: false });
7
+ }
8
+ function errorMessage(error) {
9
+ return error instanceof Error ? error.message : String(error);
10
+ }
11
+ export async function runMcpAuthMonitor({ signal, poll, maxAttempts = DEFAULT_MAX_ATTEMPTS, initialDelayMs = DEFAULT_INITIAL_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, sleep = abortableDelay, }) {
12
+ let nextDelayMs = initialDelayMs;
13
+ let lastError;
14
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
15
+ try {
16
+ await sleep(nextDelayMs, signal);
17
+ }
18
+ catch (error) {
19
+ if (signal.aborted) {
20
+ return { outcome: "cancelled", attempts: attempt - 1 };
21
+ }
22
+ lastError = errorMessage(error);
23
+ if (attempt === maxAttempts) {
24
+ return { outcome: "exhausted", attempts: attempt, reason: "error", lastError };
25
+ }
26
+ nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
27
+ continue;
28
+ }
29
+ if (signal.aborted) {
30
+ return { outcome: "cancelled", attempts: attempt - 1 };
31
+ }
32
+ try {
33
+ const result = await poll();
34
+ if (signal.aborted) {
35
+ return { outcome: "cancelled", attempts: attempt };
36
+ }
37
+ if (result === "complete") {
38
+ return { outcome: "completed", attempts: attempt };
39
+ }
40
+ lastError = undefined;
41
+ }
42
+ catch (error) {
43
+ if (signal.aborted) {
44
+ return { outcome: "cancelled", attempts: attempt };
45
+ }
46
+ lastError = errorMessage(error);
47
+ }
48
+ if (attempt === maxAttempts) {
49
+ return {
50
+ outcome: "exhausted",
51
+ attempts: attempt,
52
+ reason: lastError ? "error" : "status",
53
+ ...(lastError ? { lastError } : {}),
54
+ };
55
+ }
56
+ nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
57
+ }
58
+ return { outcome: "exhausted", attempts: 0, reason: "status" };
59
+ }
@@ -1,14 +1,14 @@
1
1
  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
- import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, } from "./tooling.js";
4
+ import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, parseToolNonExecutionMetadata, } from "./tooling.js";
5
5
  import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
6
6
  import { applyBackgroundTasksChanged, applyTaskLifecycleState } from "./tasks.js";
7
7
  import { linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
8
- import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
8
+ import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdate, emitFastModeUpdateIfChanged, setFastModeSnapshotIfChanged, } from "./error_classification.js";
9
9
  import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
10
10
  import { mapInitSlashCommands, mapSdkSlashCommands, updateAvailableCommands, } from "./available_commands.js";
11
- import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
11
+ import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, nonNegativeIntegerField, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
12
12
  import { looksLikeAuthRequired } from "./auth.js";
13
13
  import { emitCurrentModelUpdate, refreshCurrentModel, updateSessionId } from "./session_lifecycle.js";
14
14
  import { bridgeLogger, LOG_TARGETS } from "./logger.js";
@@ -576,6 +576,18 @@ function logContentBlockLinkage(session, blockType, toolUseId, toolName, linkage
576
576
  },
577
577
  });
578
578
  }
579
+ function resolveToolProgressTarget(session, taskToolUseId, toolUseId, parentToolUseId) {
580
+ if (taskToolUseId && session.toolCalls.has(taskToolUseId)) {
581
+ return { toolUseId: taskToolUseId, source: "task" };
582
+ }
583
+ if (toolUseId && session.toolCalls.has(toolUseId)) {
584
+ return { toolUseId, source: "tool" };
585
+ }
586
+ if (parentToolUseId && session.toolCalls.has(parentToolUseId)) {
587
+ return { toolUseId: parentToolUseId, source: "parent" };
588
+ }
589
+ return null;
590
+ }
579
591
  function hideToolUse(session, toolUseId) {
580
592
  if (toolUseId) {
581
593
  session.hiddenToolUseIds.add(toolUseId);
@@ -744,6 +756,7 @@ export function handleUserToolResultBlocks(session, message) {
744
756
  return false;
745
757
  }
746
758
  const content = Array.isArray(messageObject.content) ? messageObject.content : [];
759
+ const nonExecutionByToolUseId = parseToolNonExecutionMetadata(message.tool_result_meta);
747
760
  let handled = false;
748
761
  for (const block of content) {
749
762
  if (!block || typeof block !== "object") {
@@ -766,13 +779,13 @@ export function handleUserToolResultBlocks(session, message) {
766
779
  parentToolUseId,
767
780
  sourceMessageUuid: sourceMessageUuid(message),
768
781
  });
769
- emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message));
782
+ emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message), nonExecutionByToolUseId.get(toolUseId));
770
783
  }
771
784
  }
772
785
  return handled;
773
786
  }
774
787
  export function handleResultMessage(session, message) {
775
- emitFastModeUpdateIfChanged(session, message.fast_mode_state);
788
+ emitFastModeUpdateIfChanged(session, message.fast_mode_state, message.fast_mode_disabled_reason);
776
789
  const terminalReason = terminalReasonFromValue(message.terminal_reason);
777
790
  const subtype = typeof message.subtype === "string" ? message.subtype : "";
778
791
  if (subtype === "success") {
@@ -979,7 +992,7 @@ export function handleSdkMessage(session, message) {
979
992
  session.mode = incomingMode;
980
993
  }
981
994
  refreshSupportedModesForSession(session);
982
- emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
995
+ const fastModeChanged = setFastModeSnapshotIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
983
996
  if (!session.connected) {
984
997
  emitConnectEvent(session);
985
998
  }
@@ -996,6 +1009,9 @@ export function handleSdkMessage(session, message) {
996
1009
  mode: buildModeState(session, incomingMode),
997
1010
  });
998
1011
  }
1012
+ if (fastModeChanged) {
1013
+ emitFastModeUpdate(session);
1014
+ }
999
1015
  }
1000
1016
  if (Array.isArray(msg.slash_commands)) {
1001
1017
  updateAvailableCommands(session, "init_slash_commands", mapInitSlashCommands(msg.slash_commands));
@@ -1032,15 +1048,34 @@ export function handleSdkMessage(session, message) {
1032
1048
  emitSessionUpdate(session.sessionId, { type: "current_mode_update", current_mode_id: mode });
1033
1049
  }
1034
1050
  if (msg.status === "compacting") {
1035
- emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "compacting" });
1051
+ emitSessionUpdate(session.sessionId, { type: "compaction_update", phase: "started" });
1036
1052
  }
1037
1053
  else if (msg.status === "requesting") {
1038
1054
  emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "requesting" });
1039
1055
  }
1040
1056
  else if (msg.status === null) {
1057
+ if (msg.compact_result === "success") {
1058
+ emitSessionUpdate(session.sessionId, {
1059
+ type: "compaction_update",
1060
+ phase: "finished",
1061
+ result: "success",
1062
+ });
1063
+ }
1064
+ else if (msg.compact_result === "failed") {
1065
+ const compactError = typeof msg.compact_error === "string" && msg.compact_error.trim().length > 0
1066
+ ? msg.compact_error.trim()
1067
+ : undefined;
1068
+ emitSessionUpdate(session.sessionId, {
1069
+ type: "compaction_update",
1070
+ phase: "finished",
1071
+ result: "failed",
1072
+ error_code: compactError === "too_few_groups" ? "too_few_groups" : "unknown",
1073
+ ...(compactError ? { error: compactError } : {}),
1074
+ });
1075
+ }
1041
1076
  emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
1042
1077
  }
1043
- emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
1078
+ emitFastModeUpdateIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
1044
1079
  return;
1045
1080
  }
1046
1081
  if (subtype === "compact_boundary") {
@@ -1049,12 +1084,17 @@ export function handleSdkMessage(session, message) {
1049
1084
  return;
1050
1085
  }
1051
1086
  const trigger = compactMetadata.trigger;
1052
- const preTokens = numberField(compactMetadata, "pre_tokens", "preTokens");
1087
+ const preTokens = nonNegativeIntegerField(compactMetadata, "pre_tokens", "preTokens");
1088
+ const postTokens = nonNegativeIntegerField(compactMetadata, "post_tokens", "postTokens");
1089
+ const durationMs = nonNegativeIntegerField(compactMetadata, "duration_ms", "durationMs");
1053
1090
  if ((trigger === "manual" || trigger === "auto") && preTokens !== undefined) {
1054
1091
  emitSessionUpdate(session.sessionId, {
1055
- type: "compaction_boundary",
1092
+ type: "compaction_update",
1093
+ phase: "boundary",
1056
1094
  trigger,
1057
1095
  pre_tokens: preTokens,
1096
+ ...(postTokens !== undefined ? { post_tokens: postTokens } : {}),
1097
+ ...(durationMs !== undefined ? { duration_ms: durationMs } : {}),
1058
1098
  });
1059
1099
  }
1060
1100
  return;
@@ -1137,25 +1177,29 @@ export function handleSdkMessage(session, message) {
1137
1177
  if (type === "tool_progress") {
1138
1178
  const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
1139
1179
  const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
1180
+ const parentToolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : "";
1140
1181
  const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
1141
1182
  const taskToolUseId = taskId ? session.taskToolUseIds.get(taskId) ?? "" : "";
1142
- const resolvedToolUseId = taskToolUseId || toolUseId;
1143
- if (isHiddenToolUse(session, resolvedToolUseId, toolName)) {
1183
+ if (isHiddenToolUse(session, toolUseId, toolName)) {
1144
1184
  return;
1145
1185
  }
1186
+ const progressTarget = resolveToolProgressTarget(session, taskToolUseId, toolUseId, parentToolUseId);
1187
+ const resolvedToolUseId = progressTarget?.toolUseId ?? "";
1146
1188
  bridgeLogger.debug({
1147
1189
  target: LOG_TARGETS.APP_TOOL,
1148
1190
  eventName: "sdk_tool_progress_linkage_observed",
1149
1191
  message: "SDK tool progress linkage observed",
1150
- outcome: typeof msg.parent_tool_use_id === "string" ? "child" : "root_or_unknown",
1192
+ outcome: progressTarget?.source ?? "orphaned",
1151
1193
  sessionId: session.sessionId,
1152
- toolCallId: resolvedToolUseId || undefined,
1194
+ toolCallId: resolvedToolUseId || toolUseId || undefined,
1153
1195
  fields: {
1154
1196
  tool_name: toolName,
1155
1197
  tool_use_id: toolUseId || undefined,
1156
- parent_tool_use_id: typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : undefined,
1198
+ parent_tool_use_id: parentToolUseId || undefined,
1157
1199
  task_id: taskId || undefined,
1158
1200
  task_resolved_tool_use_id: taskToolUseId || undefined,
1201
+ resolved_tool_use_id: resolvedToolUseId || undefined,
1202
+ correlation_source: progressTarget?.source,
1159
1203
  },
1160
1204
  });
1161
1205
  if (resolvedToolUseId) {
@@ -1174,7 +1218,7 @@ export function handleSdkMessage(session, message) {
1174
1218
  const subagentType = typeof msg.subagent_type === "string" && msg.subagent_type.trim()
1175
1219
  ? msg.subagent_type.trim()
1176
1220
  : undefined;
1177
- emitToolProgressUpdate(session, resolvedToolUseId, toolName, {
1221
+ emitToolProgressUpdate(session, resolvedToolUseId, {
1178
1222
  ...(subagentRetry
1179
1223
  ? { subagentRetry }
1180
1224
  : hasSubagentRetry
@@ -1270,7 +1314,7 @@ export function handleSdkMessage(session, message) {
1270
1314
  return;
1271
1315
  }
1272
1316
  const parsed = unwrapToolUseResult(rawToolUseResult);
1273
- emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg));
1317
+ emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg), parseToolNonExecutionMetadata(msg.tool_result_meta).get(toolUseId));
1274
1318
  }
1275
1319
  return;
1276
1320
  }