claude-code-rust 0.12.3 → 0.12.4

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.
@@ -95,6 +95,13 @@ function expectEffortLevel(record, key, context) {
95
95
  }
96
96
  return value;
97
97
  }
98
+ function expectRewindRestoreMode(record, key, context) {
99
+ const value = expectString(record, key, context);
100
+ if (value === "both" || value === "conversation" || value === "code") {
101
+ return value;
102
+ }
103
+ throw new Error(`${context}.${key} must be one of both, conversation, code`);
104
+ }
98
105
  function expectNonEmptyStringOrNull(record, key, context) {
99
106
  const value = record[key];
100
107
  if (value === null) {
@@ -281,6 +288,19 @@ export function parseCommandEnvelope(line) {
281
288
  command: "get_context_usage",
282
289
  session_id: expectString(raw, "session_id", "get_context_usage"),
283
290
  };
291
+ case "get_rewind_targets":
292
+ return {
293
+ command: "get_rewind_targets",
294
+ session_id: expectString(raw, "session_id", "get_rewind_targets"),
295
+ };
296
+ case "rewind":
297
+ return {
298
+ command: "rewind",
299
+ session_id: expectString(raw, "session_id", "rewind"),
300
+ target_user_message_id: expectString(raw, "target_user_message_id", "rewind"),
301
+ restore_mode: expectRewindRestoreMode(raw, "restore_mode", "rewind"),
302
+ launch_settings: optionalLaunchSettings(raw, "launch_settings", "rewind"),
303
+ };
284
304
  case "reload_plugins":
285
305
  return {
286
306
  command: "reload_plugins",
@@ -6,7 +6,9 @@ import { resolveCurrentModel } from "./session_lifecycle.js";
6
6
  const SESSION_LIST_LIMIT = 50;
7
7
  let sessionListingDir;
8
8
  export function buildSessionListOptions(dir, limit = SESSION_LIST_LIMIT) {
9
- return dir ? { dir, includeWorktrees: true, limit } : { limit };
9
+ return dir
10
+ ? { dir, includeProgrammatic: true, includeWorktrees: true, limit }
11
+ : { includeProgrammatic: true, limit };
10
12
  }
11
13
  export function setSessionListingDir(dir) {
12
14
  sessionListingDir = dir;
@@ -121,6 +123,7 @@ function buildConnectBridgeEvent(session, eventName) {
121
123
  available_models: session.availableModels,
122
124
  mode: session.mode ? buildModeState(session, session.mode) : null,
123
125
  ...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
126
+ ...(session.restoredInput !== undefined ? { restored_input: session.restoredInput } : {}),
124
127
  }
125
128
  : {
126
129
  event: "connected",
@@ -144,6 +147,7 @@ function logConnectEventEmission(session, eventName, requestId) {
144
147
  history_update_count: session.resumeUpdates?.length ?? 0,
145
148
  available_model_count: session.availableModels.length,
146
149
  stale_session_count: session.sessionsToCloseAfterConnect?.length ?? 0,
150
+ has_restored_input: session.restoredInput !== undefined,
147
151
  },
148
152
  });
149
153
  }
@@ -151,10 +155,15 @@ export function emitConnectEvent(session) {
151
155
  const bridgeEvent = buildConnectBridgeEvent(session, session.connectEvent);
152
156
  logConnectEventEmission(session, session.connectEvent, session.connectRequestId);
153
157
  writeEvent(bridgeEvent, session.connectRequestId);
158
+ if (session.pendingRewindResult) {
159
+ writeEvent({ ...session.pendingRewindResult, session_id: session.sessionId }, session.connectRequestId);
160
+ session.pendingRewindResult = undefined;
161
+ }
154
162
  session.connectRequestId = undefined;
155
163
  session.connected = true;
156
164
  session.authHintSent = false;
157
165
  session.resumeUpdates = undefined;
166
+ session.restoredInput = undefined;
158
167
  const staleSessions = session.sessionsToCloseAfterConnect;
159
168
  session.sessionsToCloseAfterConnect = undefined;
160
169
  if (!staleSessions || staleSessions.length === 0) {
@@ -180,7 +189,12 @@ export function emitSessionReplacedEvent(session, requestId) {
180
189
  const bridgeEvent = buildConnectBridgeEvent(session, "session_replaced");
181
190
  logConnectEventEmission(session, "session_replaced", requestId);
182
191
  writeEvent(bridgeEvent, requestId);
192
+ if (session.pendingRewindResult) {
193
+ writeEvent({ ...session.pendingRewindResult, session_id: session.sessionId }, requestId);
194
+ session.pendingRewindResult = undefined;
195
+ }
183
196
  session.resumeUpdates = undefined;
197
+ session.restoredInput = undefined;
184
198
  refreshSessionsList();
185
199
  }
186
200
  export async function emitSessionsList(requestId) {
@@ -71,6 +71,10 @@ function commandSessionId(command) {
71
71
  case "question_response":
72
72
  case "elicitation_response":
73
73
  case "get_status_snapshot":
74
+ case "get_context_usage":
75
+ case "get_rewind_targets":
76
+ case "rewind":
77
+ case "reload_plugins":
74
78
  case "mcp_status":
75
79
  case "mcp_reconnect":
76
80
  case "mcp_toggle":
@@ -105,6 +109,8 @@ function commandToolCallId(command) {
105
109
  case "elicitation_response":
106
110
  case "get_status_snapshot":
107
111
  case "get_context_usage":
112
+ case "get_rewind_targets":
113
+ case "rewind":
108
114
  case "reload_plugins":
109
115
  case "mcp_status":
110
116
  case "mcp_reconnect":
@@ -141,6 +147,8 @@ function eventToolCallId(event) {
141
147
  case "sessions_listed":
142
148
  case "status_snapshot":
143
149
  case "context_usage":
150
+ case "rewind_targets":
151
+ case "rewind_result":
144
152
  case "mcp_snapshot":
145
153
  return undefined;
146
154
  }
@@ -183,6 +191,8 @@ function protocolEventLevel(event) {
183
191
  case "sessions_listed":
184
192
  case "status_snapshot":
185
193
  case "context_usage":
194
+ case "rewind_targets":
195
+ case "rewind_result":
186
196
  case "runtime_reload_completed":
187
197
  case "mcp_set_servers_result":
188
198
  case "mcp_snapshot":
@@ -90,6 +90,162 @@ function emitSystemNoticeUpdate(session, severity, message) {
90
90
  }
91
91
  emitSessionUpdate(session.sessionId, { type: "system_notice_update", severity, message: trimmed });
92
92
  }
93
+ const MAX_INFORMATIONAL_DEDUP_KEYS = 256;
94
+ function shouldEmitInformationalMessage(session, level, content, toolUseId) {
95
+ if (!toolUseId) {
96
+ return true;
97
+ }
98
+ const key = `${toolUseId}\u0000${level}\u0000${content}`;
99
+ if (session.informationalDedupKeys.has(key)) {
100
+ return false;
101
+ }
102
+ session.informationalDedupKeys.add(key);
103
+ while (session.informationalDedupKeys.size > MAX_INFORMATIONAL_DEDUP_KEYS) {
104
+ const first = session.informationalDedupKeys.values().next().value;
105
+ if (typeof first !== "string") {
106
+ break;
107
+ }
108
+ session.informationalDedupKeys.delete(first);
109
+ }
110
+ return true;
111
+ }
112
+ function handleInformationalSystemMessage(session, msg) {
113
+ const content = typeof msg.content === "string" ? msg.content.trim() : "";
114
+ if (!content) {
115
+ return;
116
+ }
117
+ const level = typeof msg.level === "string" ? msg.level : "info";
118
+ const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
119
+ if (!shouldEmitInformationalMessage(session, level, content, toolUseId)) {
120
+ return;
121
+ }
122
+ switch (level) {
123
+ case "notice":
124
+ emitSystemNoticeUpdate(session, "info", content);
125
+ return;
126
+ case "suggestion":
127
+ emitSystemNoticeUpdate(session, "info", `Suggestion: ${content}`);
128
+ return;
129
+ case "warning":
130
+ emitSystemNoticeUpdate(session, "warning", content);
131
+ return;
132
+ case "info":
133
+ if (msg.prevent_continuation === true) {
134
+ emitSystemNoticeUpdate(session, "warning", content);
135
+ }
136
+ return;
137
+ default:
138
+ bridgeLogger.debug({
139
+ target: LOG_TARGETS.BRIDGE_SDK,
140
+ eventName: "sdk_informational_level_unhandled",
141
+ message: "SDK informational message ignored for unknown level",
142
+ outcome: "ignored",
143
+ sessionId: session.sessionId,
144
+ toolCallId: toolUseId || undefined,
145
+ fields: {
146
+ informational_level: level,
147
+ },
148
+ });
149
+ }
150
+ }
151
+ function trimmedStringField(msg, field) {
152
+ const value = msg[field];
153
+ if (typeof value !== "string") {
154
+ return undefined;
155
+ }
156
+ const trimmed = value.trim();
157
+ return trimmed ? trimmed : undefined;
158
+ }
159
+ function ensureSentencePunctuation(value) {
160
+ const trimmed = value.trim();
161
+ if (!trimmed) {
162
+ return "";
163
+ }
164
+ return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
165
+ }
166
+ function modelRefusalNoFallbackMessage(msg) {
167
+ const model = trimmedStringField(msg, "original_model") ?? "the selected model";
168
+ const base = `Could not continue with ${model}: model refused the request and no fallback model is configured.`;
169
+ const explanation = trimmedStringField(msg, "api_refusal_explanation");
170
+ const category = trimmedStringField(msg, "api_refusal_category");
171
+ const content = trimmedStringField(msg, "content");
172
+ const detail = explanation
173
+ ? `Reason: ${explanation}`
174
+ : category
175
+ ? `Refusal category: ${category}`
176
+ : content;
177
+ const detailSentence = detail ? ensureSentencePunctuation(detail) : "";
178
+ return detailSentence ? `${base} ${detailSentence}` : base;
179
+ }
180
+ function handleModelRefusalNoFallbackMessage(session, msg) {
181
+ const message = modelRefusalNoFallbackMessage(msg);
182
+ emitSystemNoticeUpdate(session, "warning", message);
183
+ bridgeLogger.info({
184
+ target: LOG_TARGETS.APP_SESSION,
185
+ eventName: "sdk_model_refusal_no_fallback_received",
186
+ message: "SDK model refusal without fallback received",
187
+ outcome: "success",
188
+ sessionId: session.sessionId,
189
+ requestId: trimmedStringField(msg, "request_id"),
190
+ fields: {
191
+ original_model: trimmedStringField(msg, "original_model"),
192
+ api_refusal_category: trimmedStringField(msg, "api_refusal_category"),
193
+ refused_user_message_uuid: trimmedStringField(msg, "refused_user_message_uuid"),
194
+ sdk_message_uuid: trimmedStringField(msg, "uuid"),
195
+ sdk_message_session_id: trimmedStringField(msg, "session_id"),
196
+ has_api_refusal_explanation: trimmedStringField(msg, "api_refusal_explanation") !== undefined,
197
+ has_content: trimmedStringField(msg, "content") !== undefined,
198
+ },
199
+ });
200
+ }
201
+ function workerShutdownMessage(reason) {
202
+ const trimmed = reason.trim();
203
+ return trimmed ? `Claude worker is shutting down: ${trimmed}` : "Claude worker is shutting down.";
204
+ }
205
+ function handleWorkerShuttingDownSystemMessage(session, msg) {
206
+ const reason = typeof msg.reason === "string" ? msg.reason.trim() : "";
207
+ if (!session.connected) {
208
+ bridgeLogger.debug({
209
+ target: LOG_TARGETS.BRIDGE_SDK,
210
+ eventName: "sdk_worker_shutdown_preconnect_ignored",
211
+ message: "SDK worker shutdown ignored before session connect",
212
+ outcome: "ignored",
213
+ sessionId: session.sessionId,
214
+ fields: {
215
+ reason: reason || undefined,
216
+ },
217
+ });
218
+ return;
219
+ }
220
+ session.pendingWorkerShutdown = { reason };
221
+ }
222
+ function cancelPendingWorkerShutdown(session) {
223
+ if (!session.pendingWorkerShutdown) {
224
+ return;
225
+ }
226
+ bridgeLogger.debug({
227
+ target: LOG_TARGETS.BRIDGE_SDK,
228
+ eventName: "sdk_worker_shutdown_cancelled",
229
+ message: "SDK worker shutdown ignored after later stream activity",
230
+ outcome: "ignored",
231
+ sessionId: session.sessionId,
232
+ fields: {
233
+ reason: session.pendingWorkerShutdown.reason || undefined,
234
+ },
235
+ });
236
+ session.pendingWorkerShutdown = undefined;
237
+ }
238
+ export function flushPendingWorkerShutdown(session) {
239
+ const pending = session.pendingWorkerShutdown;
240
+ if (!pending) {
241
+ return;
242
+ }
243
+ session.pendingWorkerShutdown = undefined;
244
+ if (!session.connected) {
245
+ return;
246
+ }
247
+ emitSystemNoticeUpdate(session, "warning", workerShutdownMessage(pending.reason));
248
+ }
93
249
  function notificationSeverity(priority) {
94
250
  return priority === "high" || priority === "immediate" ? "warning" : "info";
95
251
  }
@@ -675,12 +831,19 @@ function terminalReasonFromValue(value) {
675
831
  export function handleSdkMessage(session, message) {
676
832
  const msg = message;
677
833
  const type = typeof msg.type === "string" ? msg.type : "";
834
+ const subtype = type === "system" && typeof msg.subtype === "string" ? msg.subtype : "";
835
+ if (subtype !== "worker_shutting_down") {
836
+ cancelPendingWorkerShutdown(session);
837
+ }
678
838
  logSdkMessageOrigin(session, msg);
679
839
  if (type === "system") {
680
- const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
681
840
  if (handleFallbackRetractionMessage(session, subtype, msg)) {
682
841
  return;
683
842
  }
843
+ if (subtype === "model_refusal_no_fallback") {
844
+ handleModelRefusalNoFallbackMessage(session, msg);
845
+ return;
846
+ }
684
847
  if (subtype === "commands_changed") {
685
848
  updateAvailableCommands(session, "commands_changed", mapSdkSlashCommands(msg.commands));
686
849
  return;
@@ -690,6 +853,14 @@ export function handleSdkMessage(session, message) {
690
853
  emitSystemNoticeUpdate(session, notificationSeverity(msg.priority), text);
691
854
  return;
692
855
  }
856
+ if (subtype === "informational") {
857
+ handleInformationalSystemMessage(session, msg);
858
+ return;
859
+ }
860
+ if (subtype === "worker_shutting_down") {
861
+ handleWorkerShuttingDownSystemMessage(session, msg);
862
+ return;
863
+ }
693
864
  if (subtype === "mirror_error") {
694
865
  const error = typeof msg.error === "string" ? msg.error : "";
695
866
  const key = asRecordOrNull(msg.key);
@@ -162,6 +162,23 @@ export async function closeSessionWithLogging(session, options = {}) {
162
162
  fields: { reason: options.reason ?? "unspecified" },
163
163
  });
164
164
  }
165
+ export async function closeSessionsBeforeRegister(replacementSession, staleSessions, requestId) {
166
+ if (!staleSessions || staleSessions.length === 0) {
167
+ return;
168
+ }
169
+ for (const stale of staleSessions) {
170
+ if (stale === replacementSession) {
171
+ continue;
172
+ }
173
+ if (sessions.get(stale.sessionId) === stale) {
174
+ sessions.delete(stale.sessionId);
175
+ }
176
+ await closeSessionWithLogging(stale, {
177
+ reason: "stale_before_register",
178
+ requestId,
179
+ });
180
+ }
181
+ }
165
182
  export async function closeAllSessions(options = {}) {
166
183
  const active = Array.from(sessions.values());
167
184
  sessions.clear();
@@ -187,6 +204,7 @@ export async function createSession(params) {
187
204
  const supportsBypassPermissionsMode = startupPermissionModeOptions(params.launchSettings).allowDangerouslySkipPermissions === true;
188
205
  const historyUpdateCount = params.resumeUpdates?.length ?? 0;
189
206
  const staleSessionCount = params.sessionsToCloseAfterConnect?.length ?? 0;
207
+ const staleSessionBeforeRegisterCount = params.sessionsToCloseBeforeRegister?.length ?? 0;
190
208
  let session;
191
209
  const sessionIdForLogs = () => session?.sessionId ?? provisionalSessionId;
192
210
  const canUseTool = async (toolName, inputData, options) => {
@@ -253,8 +271,10 @@ export async function createSession(params) {
253
271
  cwd: params.cwd,
254
272
  connect_event: params.connectEvent,
255
273
  resume_requested: params.resume !== undefined,
274
+ resume_session_at: params.resumeSessionAt ?? "<none>",
256
275
  history_update_count: historyUpdateCount,
257
276
  stale_session_count: staleSessionCount,
277
+ stale_session_before_register_count: staleSessionBeforeRegisterCount,
258
278
  },
259
279
  });
260
280
  try {
@@ -263,6 +283,7 @@ export async function createSession(params) {
263
283
  options: buildQueryOptions({
264
284
  cwd: params.cwd,
265
285
  resume: params.resume,
286
+ resumeSessionAt: params.resumeSessionAt,
266
287
  launchSettings: params.launchSettings,
267
288
  provisionalSessionId,
268
289
  input,
@@ -287,6 +308,7 @@ export async function createSession(params) {
287
308
  fields: {
288
309
  cwd: params.cwd,
289
310
  resume_requested: params.resume !== undefined,
311
+ resume_session_at: params.resumeSessionAt ?? "<none>",
290
312
  error_message: message,
291
313
  },
292
314
  });
@@ -319,12 +341,17 @@ export async function createSession(params) {
319
341
  pendingQuestions: new Map(),
320
342
  pendingUserDialogs: new Map(),
321
343
  pendingElicitations: new Map(),
344
+ informationalDedupKeys: new Set(),
322
345
  mcpStatusRevalidatedAt: new Map(),
323
346
  hiddenToolUseIds: new Set(),
324
347
  authHintSent: false,
325
348
  ...(params.resumeUpdates && params.resumeUpdates.length > 0
326
349
  ? { resumeUpdates: params.resumeUpdates }
327
350
  : {}),
351
+ ...(params.restoredInput !== undefined ? { restoredInput: params.restoredInput } : {}),
352
+ ...(params.pendingRewindResult !== undefined
353
+ ? { pendingRewindResult: params.pendingRewindResult }
354
+ : {}),
328
355
  ...(params.sessionsToCloseAfterConnect
329
356
  ? { sessionsToCloseAfterConnect: params.sessionsToCloseAfterConnect }
330
357
  : {}),
@@ -332,6 +359,7 @@ export async function createSession(params) {
332
359
  refreshCurrentModel(session);
333
360
  const { refreshSupportedModesForSession } = await import("./commands.js");
334
361
  refreshSupportedModesForSession(session);
362
+ await closeSessionsBeforeRegister(session, params.sessionsToCloseBeforeRegister, params.requestId);
335
363
  sessions.set(provisionalSessionId, session);
336
364
  bridgeLogger.info({
337
365
  target: LOG_TARGETS.APP_SESSION,
@@ -344,6 +372,7 @@ export async function createSession(params) {
344
372
  cwd: session.cwd,
345
373
  connect_event: session.connectEvent,
346
374
  resume_requested: params.resume !== undefined,
375
+ resume_session_at: params.resumeSessionAt ?? "<none>",
347
376
  },
348
377
  });
349
378
  bridgeLogger.info({
@@ -430,6 +459,11 @@ export async function createSession(params) {
430
459
  const { handleSdkMessage } = await import("./message_handlers.js");
431
460
  handleSdkMessage(session, message);
432
461
  }
462
+ {
463
+ // Lazy import to break circular dependency at module-evaluation time.
464
+ const { flushPendingWorkerShutdown } = await import("./message_handlers.js");
465
+ flushPendingWorkerShutdown(session);
466
+ }
433
467
  if (!session.connected) {
434
468
  bridgeLogger.error({
435
469
  target: LOG_TARGETS.APP_SESSION,
@@ -456,6 +490,7 @@ export async function createSession(params) {
456
490
  failConnection(`agent stream failed: ${message}`, params.requestId);
457
491
  }
458
492
  })();
493
+ return session;
459
494
  }
460
495
  function logSdkProcessSpawnStarted(options, includeArgsPreview) {
461
496
  bridgeLogger.info({
@@ -570,6 +605,7 @@ export function buildQueryOptions(params) {
570
605
  cwd: params.cwd,
571
606
  includePartialMessages: true,
572
607
  promptSuggestions: true,
608
+ enableFileCheckpointing: true,
573
609
  executable: "node",
574
610
  ...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
575
611
  ...(settings ? { settings } : {}),
@@ -621,6 +657,7 @@ export function buildQueryOptions(params) {
621
657
  // --setting-sources argument.
622
658
  settingSources: DEFAULT_SETTING_SOURCES,
623
659
  resume: params.resume,
660
+ ...(params.resumeSessionAt ? { resumeSessionAt: params.resumeSessionAt } : {}),
624
661
  canUseTool: params.canUseTool,
625
662
  onElicitation: async (request) => {
626
663
  const requestId = randomUUID();
@@ -62,6 +62,9 @@ export function buildRateLimitUpdate(rateLimitInfo) {
62
62
  type: "rate_limit_update",
63
63
  status,
64
64
  };
65
+ if (info.errorCode === "credits_required") {
66
+ update.error_code = "credits_required";
67
+ }
65
68
  const resetsAt = numberField(info, "resetsAt");
66
69
  if (resetsAt !== undefined) {
67
70
  update.resets_at = resetsAt;
@@ -94,6 +97,12 @@ export function buildRateLimitUpdate(rateLimitInfo) {
94
97
  if (surpassedThreshold !== undefined) {
95
98
  update.surpassed_threshold = surpassedThreshold;
96
99
  }
100
+ if (typeof info.canUserPurchaseCredits === "boolean") {
101
+ update.can_user_purchase_credits = info.canUserPurchaseCredits;
102
+ }
103
+ if (typeof info.hasChargeableSavedPaymentMethod === "boolean") {
104
+ update.has_chargeable_saved_payment_method = info.hasChargeableSavedPaymentMethod;
105
+ }
97
106
  return update;
98
107
  }
99
108
  export function buildApiRetryUpdate(message) {
@@ -23,6 +23,8 @@ const WORKFLOW_TOOL_NAME = "Workflow";
23
23
  const PROJECTS_TOOL_NAME = "Projects";
24
24
  const ARTIFACT_TOOL_NAME = "Artifact";
25
25
  const SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME = "ShowOnboardingRolePicker";
26
+ const READ_MCP_RESOURCE_TOOL_NAME = "ReadMcpResource";
27
+ const READ_MCP_RESOURCE_DIR_TOOL_NAME = "ReadMcpResourceDir";
26
28
  const SEARCH_OUTPUT_MODES = new Set(["content", "files_with_matches", "count"]);
27
29
  function isCronToolName(name) {
28
30
  return CRON_TOOL_NAMES.has(name);
@@ -53,6 +55,9 @@ function isAgentLikeToolName(name) {
53
55
  export function isShellToolName(name) {
54
56
  return name === "Bash" || name === "PowerShell";
55
57
  }
58
+ function isMcpResourceReadToolName(name) {
59
+ return name === READ_MCP_RESOURCE_TOOL_NAME || name === READ_MCP_RESOURCE_DIR_TOOL_NAME;
60
+ }
56
61
  function agentInputTitle(name, input) {
57
62
  if (!isAgentLikeToolName(name)) {
58
63
  return undefined;
@@ -143,7 +148,8 @@ export function normalizeToolKind(name) {
143
148
  }
144
149
  switch (name) {
145
150
  case "Read":
146
- case "ReadMcpResource":
151
+ case READ_MCP_RESOURCE_TOOL_NAME:
152
+ case READ_MCP_RESOURCE_DIR_TOOL_NAME:
147
153
  return "read";
148
154
  case "Write":
149
155
  case "Edit":
@@ -268,14 +274,14 @@ export function toolTitle(name, input, context = {}) {
268
274
  if ((name === "Read" || name === "Write" || name === "Edit") && typeof input.file_path === "string") {
269
275
  return `${name} ${input.file_path}`;
270
276
  }
271
- if (name === "ReadMcpResource") {
277
+ if (isMcpResourceReadToolName(name)) {
272
278
  const uri = typeof input.uri === "string" ? input.uri : "";
273
279
  const server = typeof input.server === "string" ? input.server : "";
274
280
  if (server && uri) {
275
- return `ReadMcpResource ${server} ${uri}`;
281
+ return `${name} ${server} ${uri}`;
276
282
  }
277
283
  if (uri) {
278
- return `ReadMcpResource ${uri}`;
284
+ return `${name} ${uri}`;
279
285
  }
280
286
  }
281
287
  return name;
@@ -440,6 +446,37 @@ function mcpResourceContentFromResult(rawResult, rawContent) {
440
446
  }
441
447
  return [];
442
448
  }
449
+ function mcpResourceDirTextFromResult(toolName, rawResult, rawContent) {
450
+ if (toolName !== READ_MCP_RESOURCE_DIR_TOOL_NAME) {
451
+ return undefined;
452
+ }
453
+ for (const candidate of collectResultCandidates(rawResult, rawContent)) {
454
+ if (!Array.isArray(candidate.resources)) {
455
+ continue;
456
+ }
457
+ const lines = [];
458
+ for (const entry of candidate.resources) {
459
+ const record = asRecordOrNull(entry);
460
+ if (!record) {
461
+ continue;
462
+ }
463
+ const name = nonEmptyString(record.name);
464
+ const uri = nonEmptyString(record.uri);
465
+ if (!name || !uri) {
466
+ continue;
467
+ }
468
+ const mimeType = nonEmptyString(record.mimeType);
469
+ const suffix = mimeType
470
+ ? mimeType === "inode/directory"
471
+ ? " (directory)"
472
+ : ` (${mimeType})`
473
+ : "";
474
+ lines.push(`${name} - ${uri}${suffix}`);
475
+ }
476
+ return lines.length > 0 ? lines.join("\n") : "No resources found.";
477
+ }
478
+ return undefined;
479
+ }
443
480
  function extractToolOutputMetadata(toolName, rawResult, rawContent) {
444
481
  const candidates = collectResultCandidates(rawResult, rawContent);
445
482
  const metadata = {};
@@ -1604,8 +1641,8 @@ function enterPlanModeStructuredOutputHandled(toolName, rawResult, rawContent) {
1604
1641
  }
1605
1642
  return false;
1606
1643
  }
1607
- function readMcpResourceErrorText(toolName, rawResult, rawContent) {
1608
- if (toolName !== "ReadMcpResource") {
1644
+ function mcpResourceReadErrorText(toolName, rawResult, rawContent) {
1645
+ if (!isMcpResourceReadToolName(toolName)) {
1609
1646
  return undefined;
1610
1647
  }
1611
1648
  for (const candidate of collectResultCandidates(rawResult, rawContent)) {
@@ -1675,13 +1712,23 @@ export function buildToolResultFields(isError, rawContent, base, rawResult, _con
1675
1712
  if (agentTitle) {
1676
1713
  fields.title = agentTitle;
1677
1714
  }
1678
- const readMcpResourceError = readMcpResourceErrorText(toolName, rawResult, rawContent);
1715
+ const readMcpResourceError = mcpResourceReadErrorText(toolName, rawResult, rawContent);
1679
1716
  if (readMcpResourceError) {
1680
1717
  fields.status = "failed";
1681
1718
  fields.raw_output = readMcpResourceError;
1682
1719
  fields.content = [{ type: "content", content: { type: "text", text: readMcpResourceError } }];
1683
1720
  return fields;
1684
1721
  }
1722
+ const readMcpResourceDirOutput = !isError
1723
+ ? mcpResourceDirTextFromResult(toolName, rawResult, rawContent)
1724
+ : undefined;
1725
+ if (readMcpResourceDirOutput !== undefined) {
1726
+ fields.raw_output = readMcpResourceDirOutput;
1727
+ fields.content = [
1728
+ { type: "content", content: { type: "text", text: readMcpResourceDirOutput } },
1729
+ ];
1730
+ return fields;
1731
+ }
1685
1732
  const searchOutput = !isError ? searchResultText(toolName, rawResult, rawContent) : undefined;
1686
1733
  if (searchOutput !== undefined) {
1687
1734
  fields.raw_output = searchOutput;
@@ -1826,7 +1873,7 @@ export function buildToolResultFields(isError, rawContent, base, rawResult, _con
1826
1873
  return fields;
1827
1874
  }
1828
1875
  }
1829
- if (!isError && toolName === "ReadMcpResource") {
1876
+ if (!isError && toolName === READ_MCP_RESOURCE_TOOL_NAME) {
1830
1877
  const structuredResourceContent = mcpResourceContentFromResult(rawResult, rawContent);
1831
1878
  if (structuredResourceContent.length > 0) {
1832
1879
  fields.content = structuredResourceContent;