claude-code-rust 0.12.3 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,7 +19,7 @@ export { shouldEmitStartupAuthRequiredForAccount } from "./account_metadata.js";
19
19
  const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32" ? "claude-rs-bridge-node.exe" : "claude-rs-bridge-node";
20
20
  const BRIDGE_RUNTIME_GUARD_PROMPT = `Do not terminate the Claude Rust bridge runtime process \`${BRIDGE_RUNTIME_PROCESS_NAME}\`; ` +
21
21
  "when cleaning up development servers, only stop processes by explicit PIDs you started in this session.";
22
- const STARTUP_FALLBACK_MODEL_ALIAS = "opus";
22
+ const STARTUP_FALLBACK_MODEL_ALIAS = "fable";
23
23
  function permissionDisplayFromCanUseOptions(options) {
24
24
  const title = typeof options.title === "string" ? options.title.trim() : "";
25
25
  const displayName = typeof options.displayName === "string" ? options.displayName.trim() : "";
@@ -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({
@@ -565,11 +600,13 @@ export function buildQueryOptions(params) {
565
600
  const systemPrompt = systemPromptFromLaunchSettings(params.launchSettings);
566
601
  const modelOption = startupModelOption(params.launchSettings);
567
602
  const permissionModeOptions = startupPermissionModeOptions(params.launchSettings);
603
+ const shouldPassCanUseTool = permissionModeOptions.permissionMode !== "bypassPermissions";
568
604
  const settings = normalizedSettingsFromLaunchSettings(params.launchSettings);
569
605
  return {
570
606
  cwd: params.cwd,
571
607
  includePartialMessages: true,
572
608
  promptSuggestions: true,
609
+ enableFileCheckpointing: true,
573
610
  executable: "node",
574
611
  ...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
575
612
  ...(settings ? { settings } : {}),
@@ -621,7 +658,8 @@ export function buildQueryOptions(params) {
621
658
  // --setting-sources argument.
622
659
  settingSources: DEFAULT_SETTING_SOURCES,
623
660
  resume: params.resume,
624
- canUseTool: params.canUseTool,
661
+ ...(params.resumeSessionAt ? { resumeSessionAt: params.resumeSessionAt } : {}),
662
+ ...(shouldPassCanUseTool ? { canUseTool: params.canUseTool } : {}),
625
663
  onElicitation: async (request) => {
626
664
  const requestId = randomUUID();
627
665
  const mode = request.mode === "form" || request.mode === "url"
@@ -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;
@@ -142,6 +142,32 @@ function buildQuestionRequest(promptToolCall, prompt, index, total) {
142
142
  function askUserQuestionTranscript(answers) {
143
143
  return answers.map((entry) => `${entry.header}: ${entry.answer}\n ${entry.question}`).join("\n");
144
144
  }
145
+ function askUserQuestionCompletedRawInput(prompts, answers, annotations, questionResults) {
146
+ return {
147
+ questions: prompts.map((prompt) => ({
148
+ question: prompt.question,
149
+ header: prompt.header,
150
+ multiSelect: prompt.multiSelect,
151
+ options: prompt.options.map((option) => ({
152
+ label: option.label,
153
+ description: option.description,
154
+ ...(option.preview ? { preview: option.preview } : {}),
155
+ })),
156
+ })),
157
+ answers,
158
+ ...(Object.keys(annotations).length > 0 ? { annotations: questionAnnotationsJson(annotations) } : {}),
159
+ question_results: questionResults,
160
+ };
161
+ }
162
+ function questionAnnotationsJson(annotations) {
163
+ return Object.fromEntries(Object.entries(annotations).map(([question, annotation]) => [
164
+ question,
165
+ {
166
+ ...(annotation.preview ? { preview: annotation.preview } : {}),
167
+ ...(annotation.notes ? { notes: annotation.notes } : {}),
168
+ },
169
+ ]));
170
+ }
145
171
  function deriveAnnotation(selectedOptions, annotation) {
146
172
  const preview = annotation?.preview?.trim().length
147
173
  ? annotation.preview
@@ -166,6 +192,7 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
166
192
  const answers = {};
167
193
  const annotations = {};
168
194
  const transcript = [];
195
+ const questionResults = [];
169
196
  for (const [index, prompt] of prompts.entries()) {
170
197
  const promptToolCall = askUserQuestionPromptToolCall(baseToolCall, prompt, index, prompts.length);
171
198
  const fields = {
@@ -214,11 +241,35 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
214
241
  annotations[prompt.question] = annotation;
215
242
  }
216
243
  transcript.push({ header: prompt.header, question: prompt.question, answer });
244
+ questionResults.push({
245
+ question: prompt.question,
246
+ header: prompt.header,
247
+ question_index: index,
248
+ total_questions: prompts.length,
249
+ selected_options: selectedOptions.map((option) => ({
250
+ option_id: option.option_id,
251
+ label: option.label,
252
+ ...(option.description ? { description: option.description } : {}),
253
+ ...(option.preview ? { preview: option.preview } : {}),
254
+ })),
255
+ ...(annotation
256
+ ? {
257
+ annotation: {
258
+ ...(annotation.preview ? { preview: annotation.preview } : {}),
259
+ ...(annotation.notes ? { notes: annotation.notes } : {}),
260
+ },
261
+ }
262
+ : {}),
263
+ });
217
264
  const summary = askUserQuestionTranscript(transcript);
265
+ const completed = index + 1 >= prompts.length;
218
266
  const progressFields = {
219
- status: index + 1 >= prompts.length ? "completed" : "in_progress",
267
+ status: completed ? "completed" : "in_progress",
220
268
  raw_output: summary,
221
269
  content: [{ type: "content", content: { type: "text", text: summary } }],
270
+ ...(completed
271
+ ? { raw_input: askUserQuestionCompletedRawInput(prompts, answers, annotations, questionResults) }
272
+ : {}),
222
273
  };
223
274
  emitToolCallUpdate(session, toolUseId, progressFields, "summary");
224
275
  }