claude-code-rust 0.14.0 → 0.14.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.
@@ -65,6 +65,7 @@ function commandSessionId(command) {
65
65
  case "cancel_turn":
66
66
  case "set_model":
67
67
  case "set_mode":
68
+ case "set_fast_mode":
68
69
  case "generate_session_title":
69
70
  case "rename_session":
70
71
  case "permission_response":
@@ -103,6 +104,7 @@ function commandToolCallId(command) {
103
104
  case "cancel_turn":
104
105
  case "set_model":
105
106
  case "set_mode":
107
+ case "set_fast_mode":
106
108
  case "generate_session_title":
107
109
  case "rename_session":
108
110
  case "new_session":
@@ -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, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
11
+ import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, 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";
@@ -744,6 +744,7 @@ export function handleUserToolResultBlocks(session, message) {
744
744
  return false;
745
745
  }
746
746
  const content = Array.isArray(messageObject.content) ? messageObject.content : [];
747
+ const nonExecutionByToolUseId = parseToolNonExecutionMetadata(message.tool_result_meta);
747
748
  let handled = false;
748
749
  for (const block of content) {
749
750
  if (!block || typeof block !== "object") {
@@ -766,13 +767,13 @@ export function handleUserToolResultBlocks(session, message) {
766
767
  parentToolUseId,
767
768
  sourceMessageUuid: sourceMessageUuid(message),
768
769
  });
769
- emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message));
770
+ emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message), nonExecutionByToolUseId.get(toolUseId));
770
771
  }
771
772
  }
772
773
  return handled;
773
774
  }
774
775
  export function handleResultMessage(session, message) {
775
- emitFastModeUpdateIfChanged(session, message.fast_mode_state);
776
+ emitFastModeUpdateIfChanged(session, message.fast_mode_state, message.fast_mode_disabled_reason);
776
777
  const terminalReason = terminalReasonFromValue(message.terminal_reason);
777
778
  const subtype = typeof message.subtype === "string" ? message.subtype : "";
778
779
  if (subtype === "success") {
@@ -979,7 +980,7 @@ export function handleSdkMessage(session, message) {
979
980
  session.mode = incomingMode;
980
981
  }
981
982
  refreshSupportedModesForSession(session);
982
- emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
983
+ const fastModeChanged = setFastModeSnapshotIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
983
984
  if (!session.connected) {
984
985
  emitConnectEvent(session);
985
986
  }
@@ -996,6 +997,9 @@ export function handleSdkMessage(session, message) {
996
997
  mode: buildModeState(session, incomingMode),
997
998
  });
998
999
  }
1000
+ if (fastModeChanged) {
1001
+ emitFastModeUpdate(session);
1002
+ }
999
1003
  }
1000
1004
  if (Array.isArray(msg.slash_commands)) {
1001
1005
  updateAvailableCommands(session, "init_slash_commands", mapInitSlashCommands(msg.slash_commands));
@@ -1040,7 +1044,7 @@ export function handleSdkMessage(session, message) {
1040
1044
  else if (msg.status === null) {
1041
1045
  emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
1042
1046
  }
1043
- emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
1047
+ emitFastModeUpdateIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
1044
1048
  return;
1045
1049
  }
1046
1050
  if (subtype === "compact_boundary") {
@@ -1159,7 +1163,29 @@ export function handleSdkMessage(session, message) {
1159
1163
  },
1160
1164
  });
1161
1165
  if (resolvedToolUseId) {
1162
- emitToolProgressUpdate(session, resolvedToolUseId, toolName);
1166
+ const hasSubagentRetry = Object.hasOwn(msg, "subagent_retry");
1167
+ const subagentRetry = hasSubagentRetry ? buildSubagentRetryUpdate(msg) : null;
1168
+ if (hasSubagentRetry && !subagentRetry) {
1169
+ bridgeLogger.warn({
1170
+ target: LOG_TARGETS.APP_TOOL,
1171
+ eventName: "sdk_subagent_retry_rejected",
1172
+ message: "ignored malformed SDK subagent retry progress",
1173
+ outcome: "invalid_payload",
1174
+ sessionId: session.sessionId,
1175
+ toolCallId: resolvedToolUseId,
1176
+ });
1177
+ }
1178
+ const subagentType = typeof msg.subagent_type === "string" && msg.subagent_type.trim()
1179
+ ? msg.subagent_type.trim()
1180
+ : undefined;
1181
+ emitToolProgressUpdate(session, resolvedToolUseId, toolName, {
1182
+ ...(subagentRetry
1183
+ ? { subagentRetry }
1184
+ : hasSubagentRetry
1185
+ ? {}
1186
+ : { subagentRetry: { state: "clear" } }),
1187
+ ...(subagentType ? { subagentType } : {}),
1188
+ });
1163
1189
  }
1164
1190
  return;
1165
1191
  }
@@ -1248,7 +1274,7 @@ export function handleSdkMessage(session, message) {
1248
1274
  return;
1249
1275
  }
1250
1276
  const parsed = unwrapToolUseResult(rawToolUseResult);
1251
- emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg));
1277
+ emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg), parseToolNonExecutionMetadata(msg.tool_result_meta).get(toolUseId));
1252
1278
  }
1253
1279
  return;
1254
1280
  }