claude-code-rust 0.9.0 → 0.10.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.
@@ -2,9 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import { spawn as spawnChild } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import { query, } from "@anthropic-ai/claude-agent-sdk";
5
- import { AsyncQueue, logPermissionDebug } from "./shared.js";
6
- import { formatPermissionUpdates, permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./permissions.js";
7
- import { writeEvent, failConnection, emitSessionUpdate, emitConnectEvent, } from "./events.js";
5
+ import { bridgeLogger, LOG_TARGETS, logSdkStderrLine } from "./logger.js";
6
+ import { AsyncQueue } from "./shared.js";
7
+ import { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./permissions.js";
8
+ import { failConnection, emitSessionUpdate, emitConnectEvent, emitPermissionRequestEvent, emitElicitationRequestEvent, } from "./events.js";
8
9
  import { ensureToolCallVisible, setToolCallStatus, } from "./tool_calls.js";
9
10
  import { requestExitPlanModeApproval, requestAskUserQuestionAnswers, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "./user_interaction.js";
10
11
  import { mapAvailableAgents, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
@@ -44,25 +45,50 @@ export async function closeSession(session) {
44
45
  }
45
46
  session.pendingElicitations.clear();
46
47
  }
47
- export async function closeAllSessions() {
48
+ export async function closeSessionWithLogging(session, options = {}) {
49
+ await closeSession(session);
50
+ bridgeLogger.info({
51
+ target: LOG_TARGETS.APP_SESSION,
52
+ eventName: "session_closed",
53
+ message: "session closed",
54
+ outcome: "success",
55
+ sessionId: session.sessionId,
56
+ ...(options.requestId ? { requestId: options.requestId } : {}),
57
+ fields: { reason: options.reason ?? "unspecified" },
58
+ });
59
+ }
60
+ export async function closeAllSessions(options = {}) {
48
61
  const active = Array.from(sessions.values());
49
62
  sessions.clear();
50
- await Promise.all(active.map((session) => closeSession(session)));
63
+ await Promise.all(active.map((session) => closeSessionWithLogging(session, {
64
+ reason: options.reason ?? "bulk_close",
65
+ requestId: options.requestId,
66
+ })));
67
+ bridgeLogger.info({
68
+ target: LOG_TARGETS.APP_SESSION,
69
+ eventName: "all_sessions_closed",
70
+ message: "all sessions closed",
71
+ outcome: "success",
72
+ ...(options.requestId ? { requestId: options.requestId } : {}),
73
+ count: active.length,
74
+ fields: { reason: options.reason ?? "bulk_close" },
75
+ });
51
76
  }
52
77
  export async function createSession(params) {
53
78
  const input = new AsyncQueue();
54
79
  const provisionalSessionId = params.resume ?? randomUUID();
55
80
  const initialModel = initialSessionModel(params.launchSettings);
56
81
  const initialMode = initialSessionMode(params.launchSettings);
82
+ const historyUpdateCount = params.resumeUpdates?.length ?? 0;
83
+ const staleSessionCount = params.sessionsToCloseAfterConnect?.length ?? 0;
57
84
  let session;
85
+ const sessionIdForLogs = () => session?.sessionId ?? provisionalSessionId;
58
86
  const canUseTool = async (toolName, inputData, options) => {
59
87
  const toolUseId = options.toolUseID;
60
88
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
61
89
  const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
62
90
  return await requestExitPlanModeApproval(session, toolUseId, inputData, existing);
63
91
  }
64
- logPermissionDebug(`request tool_use_id=${toolUseId} tool=${toolName} blocked_path=${options.blockedPath ?? "<none>"} ` +
65
- `decision_reason=${options.decisionReason ?? "<none>"} suggestions=${formatPermissionUpdates(options.suggestions)}`);
66
92
  const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
67
93
  if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
68
94
  return await requestAskUserQuestionAnswers(session, toolUseId, inputData, existing);
@@ -71,7 +97,21 @@ export async function createSession(params) {
71
97
  tool_call: existing,
72
98
  options: permissionOptionsFromSuggestions(options.suggestions),
73
99
  };
74
- writeEvent({ event: "permission_request", session_id: session.sessionId, request });
100
+ bridgeLogger.info({
101
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
102
+ eventName: "permission_request_created",
103
+ message: "permission request created",
104
+ outcome: "start",
105
+ sessionId: session.sessionId,
106
+ toolCallId: toolUseId,
107
+ count: request.options.length,
108
+ fields: {
109
+ tool_name: toolName,
110
+ blocked_path: options.blockedPath ?? "<none>",
111
+ decision_reason: options.decisionReason ?? "<none>",
112
+ },
113
+ });
114
+ emitPermissionRequestEvent(session.sessionId, request);
75
115
  return await new Promise((resolve) => {
76
116
  session.pendingPermissions.set(toolUseId, {
77
117
  resolve,
@@ -89,6 +129,21 @@ export async function createSession(params) {
89
129
  throw new Error(`CLAUDE_CODE_EXECUTABLE does not exist: ${claudeCodeExecutable}`);
90
130
  }
91
131
  let queryHandle;
132
+ bridgeLogger.info({
133
+ target: LOG_TARGETS.APP_SESSION,
134
+ eventName: "session_create_started",
135
+ message: "session creation started",
136
+ outcome: "start",
137
+ ...(params.requestId ? { requestId: params.requestId } : {}),
138
+ sessionId: provisionalSessionId,
139
+ fields: {
140
+ cwd: params.cwd,
141
+ connect_event: params.connectEvent,
142
+ resume_requested: params.resume !== undefined,
143
+ history_update_count: historyUpdateCount,
144
+ stale_session_count: staleSessionCount,
145
+ },
146
+ });
92
147
  try {
93
148
  queryHandle = query({
94
149
  prompt: input,
@@ -103,12 +158,25 @@ export async function createSession(params) {
103
158
  sdkDebugFile,
104
159
  enableSdkDebug,
105
160
  enableSpawnDebug,
106
- sessionIdForLogs: () => session.sessionId,
161
+ sessionIdForLogs,
107
162
  }),
108
163
  });
109
164
  }
110
165
  catch (error) {
111
166
  const message = error instanceof Error ? error.message : String(error);
167
+ bridgeLogger.error({
168
+ target: LOG_TARGETS.APP_SESSION,
169
+ eventName: "session_query_failed",
170
+ message: "session query creation failed",
171
+ outcome: "failure",
172
+ ...(params.requestId ? { requestId: params.requestId } : {}),
173
+ sessionId: provisionalSessionId,
174
+ fields: {
175
+ cwd: params.cwd,
176
+ resume_requested: params.resume !== undefined,
177
+ error_message: message,
178
+ },
179
+ });
112
180
  throw new Error(`query() failed: node_executable=${process.execPath}; cwd=${params.cwd}; ` +
113
181
  `resume=${params.resume ?? "<none>"}; ` +
114
182
  `CLAUDE_CODE_EXECUTABLE=${claudeCodeExecutable ?? "<unset>"}; error=${message}`);
@@ -140,12 +208,51 @@ export async function createSession(params) {
140
208
  : {}),
141
209
  };
142
210
  sessions.set(provisionalSessionId, session);
211
+ bridgeLogger.info({
212
+ target: LOG_TARGETS.APP_SESSION,
213
+ eventName: "session_query_started",
214
+ message: "session query started",
215
+ outcome: "success",
216
+ ...(params.requestId ? { requestId: params.requestId } : {}),
217
+ sessionId: session.sessionId,
218
+ fields: {
219
+ cwd: session.cwd,
220
+ connect_event: session.connectEvent,
221
+ resume_requested: params.resume !== undefined,
222
+ },
223
+ });
224
+ bridgeLogger.info({
225
+ target: LOG_TARGETS.APP_SESSION,
226
+ eventName: "session_create_registered",
227
+ message: "session registered in bridge state",
228
+ outcome: "success",
229
+ ...(params.requestId ? { requestId: params.requestId } : {}),
230
+ sessionId: session.sessionId,
231
+ count: sessions.size,
232
+ fields: {
233
+ active_session_count: sessions.size,
234
+ connect_event: session.connectEvent,
235
+ },
236
+ });
143
237
  // In stream-input mode the SDK may defer init until input arrives.
144
238
  // Trigger initialization explicitly so the Rust UI can receive `connected`
145
239
  // before the first user prompt.
146
240
  void session.query
147
241
  .initializationResult()
148
242
  .then((result) => {
243
+ bridgeLogger.info({
244
+ target: LOG_TARGETS.APP_SESSION,
245
+ eventName: "session_initialization_completed",
246
+ message: "session initialization completed",
247
+ outcome: "success",
248
+ ...(session.connectRequestId ? { requestId: session.connectRequestId } : {}),
249
+ sessionId: session.sessionId,
250
+ fields: {
251
+ available_model_count: Array.isArray(result.models) ? result.models.length : 0,
252
+ connect_event: session.connectEvent,
253
+ history_update_count: session.resumeUpdates?.length ?? 0,
254
+ },
255
+ });
149
256
  session.availableModels = mapAvailableModels(result.models);
150
257
  if (!session.connected) {
151
258
  emitConnectEvent(session);
@@ -177,6 +284,15 @@ export async function createSession(params) {
177
284
  return;
178
285
  }
179
286
  const message = error instanceof Error ? error.message : String(error);
287
+ bridgeLogger.error({
288
+ target: LOG_TARGETS.APP_SESSION,
289
+ eventName: "session_initialization_failed",
290
+ message: "session initialization failed before connect",
291
+ outcome: "failure",
292
+ ...(session.connectRequestId ? { requestId: session.connectRequestId } : {}),
293
+ sessionId: session.sessionId,
294
+ fields: { error_message: message },
295
+ });
180
296
  failConnection(`agent initialization failed: ${message}`, session.connectRequestId);
181
297
  session.connectRequestId = undefined;
182
298
  });
@@ -188,15 +304,74 @@ export async function createSession(params) {
188
304
  handleSdkMessage(session, message);
189
305
  }
190
306
  if (!session.connected) {
307
+ bridgeLogger.error({
308
+ target: LOG_TARGETS.APP_SESSION,
309
+ eventName: "session_stream_ended_before_connect",
310
+ message: "session stream ended before connect",
311
+ outcome: "failure",
312
+ ...(params.requestId ? { requestId: params.requestId } : {}),
313
+ sessionId: session.sessionId,
314
+ });
191
315
  failConnection("agent stream ended before session initialization", params.requestId);
192
316
  }
193
317
  }
194
318
  catch (error) {
195
319
  const message = error instanceof Error ? error.message : String(error);
320
+ bridgeLogger.error({
321
+ target: LOG_TARGETS.APP_SESSION,
322
+ eventName: "session_stream_failed_before_connect",
323
+ message: "session stream failed before connect",
324
+ outcome: "failure",
325
+ ...(params.requestId ? { requestId: params.requestId } : {}),
326
+ sessionId: session.sessionId,
327
+ fields: { error_message: message },
328
+ });
196
329
  failConnection(`agent stream failed: ${message}`, params.requestId);
197
330
  }
198
331
  })();
199
332
  }
333
+ function logSdkProcessSpawnStarted(options, includeArgsPreview) {
334
+ bridgeLogger.info({
335
+ target: LOG_TARGETS.BRIDGE_SDK,
336
+ eventName: "sdk_spawn_started",
337
+ message: "spawning Claude Code process",
338
+ outcome: "start",
339
+ fields: {
340
+ command: options.command,
341
+ cwd: options.cwd ?? "<none>",
342
+ arg_count: options.args.length,
343
+ ...(includeArgsPreview ? { args_preview: options.args.slice(0, 5) } : {}),
344
+ },
345
+ });
346
+ }
347
+ function logSdkProcessSpawned(sessionId, child, cwd) {
348
+ bridgeLogger.info({
349
+ target: LOG_TARGETS.BRIDGE_SDK,
350
+ eventName: "sdk_spawned",
351
+ message: "Claude Code process spawned",
352
+ outcome: "success",
353
+ ...(sessionId ? { sessionId } : {}),
354
+ fields: {
355
+ cwd: cwd ?? "<none>",
356
+ pid: child.pid ?? "<none>",
357
+ },
358
+ });
359
+ }
360
+ function logSdkProcessExit(sessionId, code, signal) {
361
+ const exitedCleanly = code === 0 && signal === null;
362
+ const logger = exitedCleanly ? bridgeLogger.info : bridgeLogger.warn;
363
+ logger({
364
+ target: LOG_TARGETS.BRIDGE_SDK,
365
+ eventName: "sdk_process_exited",
366
+ message: "Claude Code process exited",
367
+ outcome: exitedCleanly ? "success" : "failure",
368
+ ...(sessionId ? { sessionId } : {}),
369
+ fields: {
370
+ exit_code: code ?? "<none>",
371
+ exit_signal: signal ?? "<none>",
372
+ },
373
+ });
374
+ }
200
375
  function permissionModeFromSettingsValue(rawMode) {
201
376
  if (typeof rawMode !== "string") {
202
377
  return undefined;
@@ -281,28 +456,37 @@ export function buildQueryOptions(params) {
281
456
  ...(params.sdkDebugFile ? { debugFile: params.sdkDebugFile } : {}),
282
457
  stderr: (line) => {
283
458
  if (line.trim().length > 0) {
284
- console.error(`[sdk stderr] ${line}`);
459
+ logSdkStderrLine(line);
285
460
  }
286
461
  },
287
- ...(params.enableSpawnDebug
288
- ? {
289
- spawnClaudeCodeProcess: (options) => {
290
- console.error(`[sdk spawn] command=${options.command} args=${JSON.stringify(options.args)} cwd=${options.cwd ?? "<none>"}`);
291
- const child = spawnChild(options.command, options.args, {
292
- cwd: options.cwd,
293
- env: options.env,
294
- signal: options.signal,
295
- stdio: ["pipe", "pipe", "pipe"],
296
- windowsHide: true,
297
- });
298
- child.on("error", (error) => {
299
- console.error(`[sdk spawn error] code=${error.code ?? "<none>"} message=${error.message}`);
300
- });
301
- return child;
302
- },
303
- }
304
- : {}),
305
- // Match claude-agent-acp defaults to avoid emitting an empty
462
+ spawnClaudeCodeProcess: (options) => {
463
+ logSdkProcessSpawnStarted(options, params.enableSpawnDebug);
464
+ const child = spawnChild(options.command, options.args, {
465
+ cwd: options.cwd,
466
+ env: options.env,
467
+ signal: options.signal,
468
+ stdio: ["pipe", "pipe", "pipe"],
469
+ windowsHide: true,
470
+ });
471
+ logSdkProcessSpawned(params.sessionIdForLogs() || undefined, child, options.cwd);
472
+ child.on("error", (error) => {
473
+ const sessionId = params.sessionIdForLogs();
474
+ bridgeLogger.error({
475
+ target: LOG_TARGETS.BRIDGE_SDK,
476
+ eventName: "sdk_spawn_failed",
477
+ message: "Claude Code process spawn failed",
478
+ outcome: "failure",
479
+ ...(sessionId ? { sessionId } : {}),
480
+ errorCode: error.code ?? "<none>",
481
+ fields: { error_message: error.message },
482
+ });
483
+ });
484
+ child.on("exit", (code, signal) => {
485
+ logSdkProcessExit(params.sessionIdForLogs() || undefined, code, signal);
486
+ });
487
+ return child;
488
+ },
489
+ // Match the Claude Code CLI defaults to avoid emitting an empty
306
490
  // --setting-sources argument.
307
491
  settingSources: DEFAULT_SETTING_SOURCES,
308
492
  resume: params.resume,
@@ -333,14 +517,32 @@ export function buildQueryOptions(params) {
333
517
  ? { requested_schema: request.requestedSchema }
334
518
  : {}),
335
519
  };
336
- writeEvent({
337
- event: "elicitation_request",
338
- session_id: params.sessionIdForLogs(),
339
- request: normalized,
520
+ bridgeLogger.info({
521
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
522
+ eventName: "elicitation_request_created",
523
+ message: "elicitation request created",
524
+ outcome: "start",
525
+ sessionId: params.sessionIdForLogs(),
526
+ requestId,
527
+ fields: {
528
+ server_name: normalized.server_name,
529
+ mode: normalized.mode,
530
+ has_url: normalized.url !== undefined,
531
+ },
340
532
  });
533
+ emitElicitationRequestEvent(params.sessionIdForLogs(), normalized);
341
534
  return await new Promise((resolve) => {
342
535
  const currentSession = sessions.get(params.sessionIdForLogs());
343
536
  if (!currentSession) {
537
+ bridgeLogger.warn({
538
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
539
+ eventName: "elicitation_request_dropped",
540
+ message: "elicitation request dropped without an active session",
541
+ outcome: "dropped",
542
+ sessionId: params.sessionIdForLogs(),
543
+ requestId,
544
+ fields: { reason: "unknown_session" },
545
+ });
344
546
  resolve({ action: "cancel" });
345
547
  return;
346
548
  }
@@ -386,29 +588,76 @@ export function mapAvailableModels(models) {
386
588
  }));
387
589
  }
388
590
  export function handlePermissionResponse(command) {
591
+ bridgeLogger.info({
592
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
593
+ eventName: "permission_response_received",
594
+ message: "permission response received",
595
+ outcome: "success",
596
+ sessionId: command.session_id,
597
+ toolCallId: command.tool_call_id,
598
+ fields: {
599
+ response_kind: command.outcome.outcome,
600
+ selected_option: command.outcome.outcome === "selected" ? command.outcome.option_id : "cancelled",
601
+ },
602
+ });
389
603
  const session = sessionById(command.session_id);
390
604
  if (!session) {
391
- logPermissionDebug(`response dropped: unknown session session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
605
+ bridgeLogger.warn({
606
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
607
+ eventName: "permission_response_dropped",
608
+ message: "permission response dropped for unknown session",
609
+ outcome: "dropped",
610
+ sessionId: command.session_id,
611
+ toolCallId: command.tool_call_id,
612
+ fields: { reason: "unknown_session" },
613
+ });
392
614
  return;
393
615
  }
394
616
  const resolver = session.pendingPermissions.get(command.tool_call_id);
395
617
  if (!resolver) {
396
- logPermissionDebug(`response dropped: no pending resolver session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
618
+ bridgeLogger.warn({
619
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
620
+ eventName: "permission_response_dropped",
621
+ message: "permission response dropped without a pending resolver",
622
+ outcome: "dropped",
623
+ sessionId: command.session_id,
624
+ toolCallId: command.tool_call_id,
625
+ fields: { reason: "missing_pending_resolver" },
626
+ });
397
627
  return;
398
628
  }
399
629
  session.pendingPermissions.delete(command.tool_call_id);
400
630
  const outcome = command.outcome;
401
631
  if (resolver.onOutcome) {
632
+ bridgeLogger.info({
633
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
634
+ eventName: "permission_response_applied",
635
+ message: "permission response applied to outcome callback",
636
+ outcome: "success",
637
+ sessionId: command.session_id,
638
+ toolCallId: command.tool_call_id,
639
+ fields: {
640
+ tool_name: resolver.toolName,
641
+ response_kind: outcome.outcome,
642
+ selected_option: outcome.outcome === "selected" ? outcome.option_id : "cancelled",
643
+ },
644
+ });
402
645
  resolver.onOutcome(outcome);
403
646
  return;
404
647
  }
405
648
  if (!resolver.resolve) {
406
- logPermissionDebug(`response dropped: resolver missing callback session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
649
+ bridgeLogger.warn({
650
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
651
+ eventName: "permission_response_dropped",
652
+ message: "permission response dropped because resolver callback was missing",
653
+ outcome: "dropped",
654
+ sessionId: command.session_id,
655
+ toolCallId: command.tool_call_id,
656
+ fields: { reason: "missing_resolver_callback" },
657
+ });
407
658
  return;
408
659
  }
409
660
  const selectedOption = outcome.outcome === "selected" ? outcome.option_id : "cancelled";
410
- logPermissionDebug(`response session_id=${command.session_id} tool_call_id=${command.tool_call_id} tool=${resolver.toolName} ` +
411
- `selected=${selectedOption} suggestions=${formatPermissionUpdates(resolver.suggestions)}`);
412
661
  if (outcome.outcome === "selected" &&
413
662
  (outcome.option_id === "allow_once" ||
414
663
  outcome.option_id === "allow_session" ||
@@ -422,43 +671,128 @@ export function handlePermissionResponse(command) {
422
671
  setToolCallStatus(session, command.tool_call_id, "failed", "Permission cancelled");
423
672
  }
424
673
  const permissionResult = permissionResultFromOutcome(outcome, command.tool_call_id, resolver.inputData, resolver.suggestions, resolver.toolName);
425
- if (permissionResult.behavior === "allow") {
426
- logPermissionDebug(`result tool_call_id=${command.tool_call_id} behavior=allow updated_permissions=` +
427
- `${formatPermissionUpdates(permissionResult.updatedPermissions)}`);
428
- }
429
- else {
430
- logPermissionDebug(`result tool_call_id=${command.tool_call_id} behavior=deny message=${permissionResult.message}`);
431
- }
674
+ bridgeLogger.info({
675
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
676
+ eventName: "permission_response_applied",
677
+ message: "permission response applied",
678
+ outcome: "success",
679
+ sessionId: command.session_id,
680
+ toolCallId: command.tool_call_id,
681
+ fields: {
682
+ tool_name: resolver.toolName,
683
+ response_kind: outcome.outcome,
684
+ selected_option: selectedOption,
685
+ behavior: permissionResult.behavior,
686
+ },
687
+ });
432
688
  resolver.resolve(permissionResult);
433
689
  }
434
690
  export function handleQuestionResponse(command) {
691
+ bridgeLogger.info({
692
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
693
+ eventName: "question_response_received",
694
+ message: "question response received",
695
+ outcome: "success",
696
+ sessionId: command.session_id,
697
+ toolCallId: command.tool_call_id,
698
+ fields: { response_kind: command.outcome.outcome },
699
+ });
435
700
  const session = sessionById(command.session_id);
436
701
  if (!session) {
437
- logPermissionDebug(`question response dropped: unknown session session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
702
+ bridgeLogger.warn({
703
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
704
+ eventName: "question_response_dropped",
705
+ message: "question response dropped for unknown session",
706
+ outcome: "dropped",
707
+ sessionId: command.session_id,
708
+ toolCallId: command.tool_call_id,
709
+ fields: { reason: "unknown_session" },
710
+ });
438
711
  return;
439
712
  }
440
713
  const resolver = session.pendingQuestions.get(command.tool_call_id);
441
714
  if (!resolver) {
442
- logPermissionDebug(`question response dropped: no pending resolver session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
715
+ bridgeLogger.warn({
716
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
717
+ eventName: "question_response_dropped",
718
+ message: "question response dropped without a pending resolver",
719
+ outcome: "dropped",
720
+ sessionId: command.session_id,
721
+ toolCallId: command.tool_call_id,
722
+ fields: { reason: "missing_pending_resolver" },
723
+ });
443
724
  return;
444
725
  }
445
726
  session.pendingQuestions.delete(command.tool_call_id);
727
+ bridgeLogger.info({
728
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
729
+ eventName: "question_response_applied",
730
+ message: "question response applied",
731
+ outcome: "success",
732
+ sessionId: command.session_id,
733
+ toolCallId: command.tool_call_id,
734
+ fields: {
735
+ tool_name: resolver.toolName,
736
+ response_kind: command.outcome.outcome,
737
+ selected_option_count: command.outcome.outcome === "answered" ? command.outcome.selected_option_ids.length : 0,
738
+ has_annotation: command.outcome.outcome === "answered" && command.outcome.annotation !== undefined,
739
+ },
740
+ });
446
741
  resolver.onOutcome(command.outcome);
447
742
  }
448
743
  export function handleElicitationResponse(command) {
744
+ bridgeLogger.info({
745
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
746
+ eventName: "elicitation_response_received",
747
+ message: "elicitation response received",
748
+ outcome: "success",
749
+ sessionId: command.session_id,
750
+ requestId: command.elicitation_request_id,
751
+ fields: {
752
+ action: command.action,
753
+ has_content: command.content !== undefined,
754
+ },
755
+ });
449
756
  const session = sessionById(command.session_id);
450
757
  if (!session) {
451
- console.error(`[sdk warn] elicitation response dropped: unknown session ` +
452
- `session_id=${command.session_id} request_id=${command.elicitation_request_id}`);
758
+ bridgeLogger.warn({
759
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
760
+ eventName: "elicitation_response_dropped",
761
+ message: "elicitation response dropped for unknown session",
762
+ outcome: "dropped",
763
+ sessionId: command.session_id,
764
+ requestId: command.elicitation_request_id,
765
+ fields: { reason: "unknown_session" },
766
+ });
453
767
  return;
454
768
  }
455
769
  const pending = session.pendingElicitations.get(command.elicitation_request_id);
456
770
  if (!pending) {
457
- console.error(`[sdk warn] elicitation response dropped: no pending request ` +
458
- `session_id=${command.session_id} request_id=${command.elicitation_request_id}`);
771
+ bridgeLogger.warn({
772
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
773
+ eventName: "elicitation_response_dropped",
774
+ message: "elicitation response dropped without pending request",
775
+ outcome: "dropped",
776
+ sessionId: command.session_id,
777
+ requestId: command.elicitation_request_id,
778
+ fields: { reason: "missing_pending_request" },
779
+ });
459
780
  return;
460
781
  }
461
782
  session.pendingElicitations.delete(command.elicitation_request_id);
783
+ bridgeLogger.info({
784
+ target: LOG_TARGETS.BRIDGE_PERMISSION,
785
+ eventName: "elicitation_response_applied",
786
+ message: "elicitation response applied",
787
+ outcome: "success",
788
+ sessionId: command.session_id,
789
+ requestId: command.elicitation_request_id,
790
+ fields: {
791
+ action: command.action,
792
+ server_name: pending.serverName,
793
+ has_content: command.content !== undefined,
794
+ },
795
+ });
462
796
  pending.resolve({
463
797
  action: command.action,
464
798
  ...(command.content ? { content: command.content } : {}),
@@ -46,10 +46,3 @@ export class AsyncQueue {
46
46
  };
47
47
  }
48
48
  }
49
- const permissionDebugEnabled = process.env.CLAUDE_RS_SDK_PERMISSION_DEBUG === "1" || process.env.CLAUDE_RS_SDK_DEBUG === "1";
50
- export function logPermissionDebug(message) {
51
- if (!permissionDebugEnabled) {
52
- return;
53
- }
54
- console.error(`[perm debug] ${message}`);
55
- }