claude-code-rust 0.9.0 → 0.11.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.
@@ -4,14 +4,16 @@ import { dirname, join } from "node:path";
4
4
  import readline from "node:readline";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { getSessionMessages, listSessions, renameSession, } from "@anthropic-ai/claude-agent-sdk";
7
- import { parseCommandEnvelope, toPermissionMode } from "./bridge/commands.js";
8
- import { writeEvent, failConnection, slashError, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
9
- import { textFromPrompt } from "./bridge/message_handlers.js";
10
- import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, } from "./bridge/session_lifecycle.js";
7
+ import { buildModeState, markModeUnavailableForSession, parseCommandEnvelope, permissionModeFailureLooksUnsupported, refreshSupportedModesForSession, toPermissionMode, } from "./bridge/commands.js";
8
+ import { writeEvent, failConnection, slashError, emitRuntimeReloadCompleted, emitRuntimeReloadFailed, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
9
+ import { contentFromPrompt } from "./bridge/message_handlers.js";
10
+ import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, emitCurrentModelUpdate, refreshCurrentModel, shouldInvalidateResolvedRuntimeModel, } from "./bridge/session_lifecycle.js";
11
11
  import { mapSessionMessagesToUpdates } from "./bridge/history.js";
12
+ import { emitAvailableAgentsIfChanged, mapAvailableAgents } from "./bridge/agents.js";
12
13
  import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, staleMcpAuthCandidates, } from "./bridge/mcp.js";
14
+ import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived } from "./bridge/logger.js";
13
15
  // Re-exports: all symbols that tests and external consumers import from bridge.js.
14
- export { AsyncQueue, logPermissionDebug } from "./bridge/shared.js";
16
+ export { AsyncQueue } from "./bridge/shared.js";
15
17
  export { asRecordOrNull } from "./bridge/shared.js";
16
18
  export { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./bridge/cache_policy.js";
17
19
  export { buildToolResultFields, createToolCall, normalizeToolKind, normalizeToolResultText, unwrapToolUseResult, } from "./bridge/tooling.js";
@@ -20,10 +22,10 @@ export { parseCommandEnvelope } from "./bridge/commands.js";
20
22
  export { buildSessionListOptions } from "./bridge/events.js";
21
23
  export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
22
24
  export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
23
- export { handleTaskSystemMessage } from "./bridge/message_handlers.js";
25
+ export { handleSdkMessage, handleTaskSystemMessage } from "./bridge/message_handlers.js";
24
26
  export { mapAvailableAgents } from "./bridge/agents.js";
25
- export { buildQueryOptions, mapAvailableModels } from "./bridge/session_lifecycle.js";
26
- export { parseFastModeState, parseRateLimitStatus, buildRateLimitUpdate, } from "./bridge/state_parsing.js";
27
+ export { attachRequestUserDialogInterceptor, buildQueryOptions, mapAvailableModels, } from "./bridge/session_lifecycle.js";
28
+ export { parseFastModeState, parseRateLimitStatus, parseRuntimeSessionState, parseApiRetryError, buildRateLimitUpdate, buildApiRetryUpdate, normalizeSettingsParseError, normalizeSettingsParseErrors, } from "./bridge/state_parsing.js";
27
29
  export { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, staleMcpAuthCandidates };
28
30
  export function buildSessionMutationOptions(cwd) {
29
31
  return cwd ? { dir: cwd } : undefined;
@@ -41,7 +43,7 @@ export async function generatePersistedSessionTitle(query, description) {
41
43
  }
42
44
  return title;
43
45
  }
44
- const EXPECTED_AGENT_SDK_VERSION = "0.2.74";
46
+ const EXPECTED_AGENT_SDK_VERSION = "0.2.104";
45
47
  const require = createRequire(import.meta.url);
46
48
  export function resolveInstalledAgentSdkVersion() {
47
49
  try {
@@ -67,14 +69,34 @@ export function agentSdkVersionCompatibilityError() {
67
69
  `found ${installed}.`);
68
70
  }
69
71
  async function handleCommand(command, requestId) {
72
+ logBridgeCommandReceived(command, requestId);
70
73
  const sdkVersionError = agentSdkVersionCompatibilityError();
71
74
  if (sdkVersionError && command.command !== "initialize" && command.command !== "shutdown") {
75
+ bridgeLogger.error({
76
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
77
+ eventName: "bridge_command_rejected",
78
+ message: "bridge command rejected due to unsupported SDK version",
79
+ outcome: "failure",
80
+ ...(requestId ? { requestId } : {}),
81
+ fields: {
82
+ bridge_command: command.command,
83
+ error_message: sdkVersionError,
84
+ },
85
+ });
72
86
  failConnection(sdkVersionError, requestId);
73
87
  return;
74
88
  }
75
89
  switch (command.command) {
76
90
  case "initialize":
77
91
  if (sdkVersionError) {
92
+ bridgeLogger.error({
93
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
94
+ eventName: "bridge_initialize_failed",
95
+ message: "bridge initialization failed due to unsupported SDK version",
96
+ outcome: "failure",
97
+ ...(requestId ? { requestId } : {}),
98
+ fields: { error_message: sdkVersionError },
99
+ });
78
100
  failConnection(sdkVersionError, requestId);
79
101
  return;
80
102
  }
@@ -92,7 +114,7 @@ async function handleCommand(command, requestId) {
92
114
  },
93
115
  ],
94
116
  capabilities: {
95
- prompt_image: false,
117
+ prompt_image: true,
96
118
  prompt_embedded_context: true,
97
119
  supports_session_listing: true,
98
120
  supports_resume_session: true,
@@ -102,6 +124,17 @@ async function handleCommand(command, requestId) {
102
124
  await emitSessionsList(requestId);
103
125
  return;
104
126
  case "create_session":
127
+ bridgeLogger.info({
128
+ target: LOG_TARGETS.APP_SESSION,
129
+ eventName: "session_create_requested",
130
+ message: "session creation requested",
131
+ outcome: "start",
132
+ ...(requestId ? { requestId } : {}),
133
+ fields: {
134
+ cwd: command.cwd,
135
+ resume_requested: command.resume !== undefined,
136
+ },
137
+ });
105
138
  setSessionListingDir(command.cwd);
106
139
  await createSession({
107
140
  cwd: command.cwd,
@@ -112,10 +145,27 @@ async function handleCommand(command, requestId) {
112
145
  });
113
146
  return;
114
147
  case "resume_session": {
148
+ bridgeLogger.info({
149
+ target: LOG_TARGETS.APP_SESSION,
150
+ eventName: "session_resume_requested",
151
+ message: "session resume requested",
152
+ outcome: "start",
153
+ ...(requestId ? { requestId } : {}),
154
+ sessionId: command.session_id,
155
+ });
115
156
  try {
116
157
  const sdkSessions = await listSessions(currentSessionListOptions());
117
158
  const matched = sdkSessions.find((entry) => entry.sessionId === command.session_id);
118
159
  if (!matched) {
160
+ bridgeLogger.warn({
161
+ target: LOG_TARGETS.APP_SESSION,
162
+ eventName: "session_resume_lookup_failed",
163
+ message: "session resume requested for an unknown session",
164
+ outcome: "failure",
165
+ ...(requestId ? { requestId } : {}),
166
+ sessionId: command.session_id,
167
+ fields: { reason: "unknown_session" },
168
+ });
119
169
  slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
120
170
  return;
121
171
  }
@@ -124,6 +174,18 @@ async function handleCommand(command, requestId) {
124
174
  const resumeUpdates = mapSessionMessagesToUpdates(historyMessages);
125
175
  const staleSessions = Array.from(sessions.values());
126
176
  const hadActiveSession = staleSessions.length > 0;
177
+ bridgeLogger.info({
178
+ target: LOG_TARGETS.APP_SESSION,
179
+ eventName: "session_resume_history_loaded",
180
+ message: "session resume history loaded",
181
+ outcome: "success",
182
+ ...(requestId ? { requestId } : {}),
183
+ sessionId: command.session_id,
184
+ fields: {
185
+ history_update_count: resumeUpdates.length,
186
+ stale_session_count: staleSessions.length,
187
+ },
188
+ });
127
189
  await createSession({
128
190
  cwd: matched.cwd ?? process.cwd(),
129
191
  resume: command.session_id,
@@ -136,12 +198,29 @@ async function handleCommand(command, requestId) {
136
198
  }
137
199
  catch (error) {
138
200
  const message = error instanceof Error ? error.message : String(error);
201
+ bridgeLogger.error({
202
+ target: LOG_TARGETS.APP_SESSION,
203
+ eventName: "session_resume_failed",
204
+ message: "session resume failed",
205
+ outcome: "failure",
206
+ ...(requestId ? { requestId } : {}),
207
+ sessionId: command.session_id,
208
+ fields: { error_message: message },
209
+ });
139
210
  slashError(command.session_id, `failed to resume session: ${message}`, requestId);
140
211
  }
141
212
  return;
142
213
  }
143
214
  case "new_session":
144
- await closeAllSessions();
215
+ bridgeLogger.info({
216
+ target: LOG_TARGETS.APP_SESSION,
217
+ eventName: "session_new_requested",
218
+ message: "replacement session requested",
219
+ outcome: "start",
220
+ ...(requestId ? { requestId } : {}),
221
+ fields: { cwd: command.cwd },
222
+ });
223
+ await closeAllSessions({ reason: "new_session_requested", requestId });
145
224
  setSessionListingDir(command.cwd);
146
225
  await createSession({
147
226
  cwd: command.cwd,
@@ -156,19 +235,20 @@ async function handleCommand(command, requestId) {
156
235
  slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
157
236
  return;
158
237
  }
159
- const text = textFromPrompt(command);
160
- if (!text.trim()) {
238
+ const content = contentFromPrompt(command);
239
+ if (content.length === 0) {
161
240
  return;
162
241
  }
163
- session.input.enqueue({
242
+ const message = {
164
243
  type: "user",
165
244
  session_id: session.sessionId,
166
245
  parent_tool_use_id: null,
167
246
  message: {
168
247
  role: "user",
169
- content: [{ type: "text", text }],
248
+ content,
170
249
  },
171
- });
250
+ };
251
+ session.input.enqueue(message);
172
252
  return;
173
253
  }
174
254
  case "cancel_turn": {
@@ -186,13 +266,80 @@ async function handleCommand(command, requestId) {
186
266
  slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
187
267
  return;
188
268
  }
189
- await session.query.setModel(command.model);
190
- session.model = command.model;
191
- emitSessionUpdate(session.sessionId, {
192
- type: "config_option_update",
193
- option_id: "model",
194
- value: command.model,
269
+ bridgeLogger.info({
270
+ target: LOG_TARGETS.APP_SESSION,
271
+ eventName: "set_model_started",
272
+ message: "set model started",
273
+ outcome: "start",
274
+ sessionId: session.sessionId,
275
+ requestId,
276
+ fields: {
277
+ requested_model: command.model,
278
+ previous_requested_model: session.requestedModelId,
279
+ previous_session_model: session.model,
280
+ previous_resolved_runtime_model: session.resolvedRuntimeModelId,
281
+ previous_current_model: session.currentModel?.resolved_id,
282
+ },
195
283
  });
284
+ try {
285
+ const previousRequestedModel = session.requestedModelId;
286
+ const previousSessionModel = session.model;
287
+ await session.query.setModel(command.model);
288
+ session.requestedModelId = command.model;
289
+ session.model = command.model;
290
+ const invalidatedResolvedRuntimeModel = shouldInvalidateResolvedRuntimeModel(previousRequestedModel, previousSessionModel, command.model);
291
+ if (invalidatedResolvedRuntimeModel) {
292
+ session.resolvedRuntimeModelId = undefined;
293
+ }
294
+ const changed = refreshCurrentModel(session, true);
295
+ const forcedCurrentModelUpdate = !changed && emitCurrentModelUpdate(session);
296
+ bridgeLogger.info({
297
+ target: LOG_TARGETS.APP_SESSION,
298
+ eventName: "set_model_succeeded",
299
+ message: "set model completed",
300
+ outcome: "success",
301
+ sessionId: session.sessionId,
302
+ requestId,
303
+ fields: {
304
+ requested_model: command.model,
305
+ session_model_after: session.model,
306
+ resolved_runtime_model_after: session.resolvedRuntimeModelId,
307
+ current_model_after: session.currentModel?.resolved_id,
308
+ current_model_display_short: session.currentModel?.display_name_short,
309
+ current_model_display_long: session.currentModel?.display_name_long,
310
+ current_model_update_emitted: changed || forcedCurrentModelUpdate,
311
+ current_model_update_forced: forcedCurrentModelUpdate,
312
+ resolved_runtime_model_invalidated: invalidatedResolvedRuntimeModel,
313
+ },
314
+ });
315
+ refreshSupportedModesForSession(session);
316
+ if (session.mode) {
317
+ emitSessionUpdate(session.sessionId, {
318
+ type: "mode_state_update",
319
+ mode: buildModeState(session, session.mode),
320
+ });
321
+ }
322
+ }
323
+ catch (error) {
324
+ const message = error instanceof Error ? error.message : String(error);
325
+ bridgeLogger.warn({
326
+ target: LOG_TARGETS.APP_SESSION,
327
+ eventName: "set_model_failed",
328
+ message: "set model failed",
329
+ outcome: "failure",
330
+ sessionId: session.sessionId,
331
+ requestId,
332
+ fields: {
333
+ requested_model: command.model,
334
+ error_message: message,
335
+ previous_requested_model: session.requestedModelId,
336
+ previous_session_model: session.model,
337
+ previous_resolved_runtime_model: session.resolvedRuntimeModelId,
338
+ previous_current_model: session.currentModel?.resolved_id,
339
+ },
340
+ });
341
+ slashError(command.session_id, `failed to set model: ${message}`, requestId);
342
+ }
196
343
  return;
197
344
  }
198
345
  case "set_mode": {
@@ -206,12 +353,28 @@ async function handleCommand(command, requestId) {
206
353
  slashError(command.session_id, `unsupported mode: ${command.mode}`, requestId);
207
354
  return;
208
355
  }
209
- await session.query.setPermissionMode(mode);
210
- session.mode = mode;
211
- emitSessionUpdate(session.sessionId, {
212
- type: "current_mode_update",
213
- current_mode_id: mode,
214
- });
356
+ try {
357
+ await session.query.setPermissionMode(mode);
358
+ session.mode = mode;
359
+ refreshSupportedModesForSession(session);
360
+ emitSessionUpdate(session.sessionId, {
361
+ type: "current_mode_update",
362
+ current_mode_id: mode,
363
+ });
364
+ }
365
+ catch (error) {
366
+ const message = error instanceof Error ? error.message : String(error);
367
+ if (permissionModeFailureLooksUnsupported(mode, message)) {
368
+ const changed = markModeUnavailableForSession(session, mode);
369
+ if (changed && session.mode) {
370
+ emitSessionUpdate(session.sessionId, {
371
+ type: "mode_state_update",
372
+ mode: buildModeState(session, session.mode),
373
+ });
374
+ }
375
+ }
376
+ slashError(command.session_id, `failed to set mode to ${mode}: ${message}`, requestId);
377
+ }
215
378
  return;
216
379
  }
217
380
  case "generate_session_title": {
@@ -254,18 +417,144 @@ async function handleCommand(command, requestId) {
254
417
  slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
255
418
  return;
256
419
  }
257
- const account = await session.query.accountInfo();
258
- writeEvent({
259
- event: "status_snapshot",
260
- session_id: session.sessionId,
261
- account: {
262
- email: account.email,
263
- organization: account.organization,
264
- subscription_type: account.subscriptionType,
265
- token_source: account.tokenSource,
266
- api_key_source: account.apiKeySource,
267
- },
268
- }, requestId);
420
+ try {
421
+ const account = await session.query.accountInfo();
422
+ bridgeLogger.info({
423
+ target: LOG_TARGETS.APP_AUTH,
424
+ eventName: "status_snapshot_emitted",
425
+ message: "status snapshot emitted",
426
+ outcome: "success",
427
+ ...(requestId ? { requestId } : {}),
428
+ sessionId: session.sessionId,
429
+ fields: {
430
+ has_email: typeof account.email === "string" && account.email.trim().length > 0,
431
+ has_organization: account.organization !== undefined,
432
+ subscription_type: account.subscriptionType,
433
+ token_source: account.tokenSource,
434
+ api_key_source: account.apiKeySource,
435
+ api_provider: account.apiProvider,
436
+ },
437
+ });
438
+ writeEvent({
439
+ event: "status_snapshot",
440
+ session_id: session.sessionId,
441
+ account: {
442
+ email: account.email,
443
+ organization: account.organization,
444
+ subscription_type: account.subscriptionType,
445
+ token_source: account.tokenSource,
446
+ api_key_source: account.apiKeySource,
447
+ api_provider: account.apiProvider,
448
+ },
449
+ }, requestId);
450
+ }
451
+ catch (error) {
452
+ const message = error instanceof Error ? error.message : String(error);
453
+ bridgeLogger.warn({
454
+ target: LOG_TARGETS.APP_AUTH,
455
+ eventName: "status_snapshot_failed",
456
+ message: "failed to build status snapshot",
457
+ outcome: "failure",
458
+ ...(requestId ? { requestId } : {}),
459
+ sessionId: session.sessionId,
460
+ fields: { error_message: message },
461
+ });
462
+ throw error;
463
+ }
464
+ return;
465
+ }
466
+ case "get_context_usage": {
467
+ const session = sessionById(command.session_id);
468
+ if (!session) {
469
+ slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
470
+ return;
471
+ }
472
+ try {
473
+ const usage = await session.query.getContextUsage();
474
+ if (typeof usage.model === "string" && usage.model.trim().length > 0) {
475
+ session.resolvedRuntimeModelId = usage.model.trim();
476
+ refreshCurrentModel(session, true);
477
+ }
478
+ const rawPercentage = typeof usage.percentage === "number" ? usage.percentage : undefined;
479
+ const normalizedPercentage = rawPercentage === undefined || !Number.isFinite(rawPercentage)
480
+ ? undefined
481
+ : Math.max(0, Math.min(100, Math.round(rawPercentage)));
482
+ bridgeLogger.debug({
483
+ target: LOG_TARGETS.APP_SESSION,
484
+ eventName: "context_usage_succeeded",
485
+ message: "session context usage received from SDK",
486
+ outcome: "success",
487
+ ...(requestId ? { requestId } : {}),
488
+ sessionId: session.sessionId,
489
+ fields: {
490
+ raw_percentage: rawPercentage,
491
+ normalized_percentage: normalizedPercentage,
492
+ total_tokens: typeof usage.totalTokens === "number" ? usage.totalTokens : undefined,
493
+ max_tokens: typeof usage.maxTokens === "number" ? usage.maxTokens : undefined,
494
+ raw_max_tokens: typeof usage.rawMaxTokens === "number" ? usage.rawMaxTokens : undefined,
495
+ model: typeof usage.model === "string" ? usage.model : undefined,
496
+ },
497
+ });
498
+ writeEvent({
499
+ event: "context_usage",
500
+ session_id: session.sessionId,
501
+ ...(normalizedPercentage !== undefined ? { percentage: normalizedPercentage } : {}),
502
+ }, requestId);
503
+ }
504
+ catch (error) {
505
+ const message = error instanceof Error ? error.message : String(error);
506
+ bridgeLogger.warn({
507
+ target: LOG_TARGETS.APP_SESSION,
508
+ eventName: "context_usage_failed",
509
+ message: "failed to get session context usage",
510
+ outcome: "failure",
511
+ ...(requestId ? { requestId } : {}),
512
+ sessionId: session.sessionId,
513
+ fields: { error_message: message },
514
+ });
515
+ writeEvent({
516
+ event: "context_usage",
517
+ session_id: session.sessionId,
518
+ }, requestId);
519
+ }
520
+ return;
521
+ }
522
+ case "reload_plugins": {
523
+ const session = sessionById(command.session_id);
524
+ if (!session) {
525
+ slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
526
+ return;
527
+ }
528
+ try {
529
+ const result = await session.query.reloadPlugins();
530
+ const commands = Array.isArray(result.commands)
531
+ ? result.commands.map((entry) => ({
532
+ name: entry.name,
533
+ description: entry.description ?? "",
534
+ input_hint: entry.argumentHint ?? undefined,
535
+ }))
536
+ : [];
537
+ emitSessionUpdate(session.sessionId, {
538
+ type: "available_commands_update",
539
+ commands,
540
+ });
541
+ emitAvailableAgentsIfChanged(session, mapAvailableAgents(result.agents));
542
+ await handleMcpStatusCommand(session, requestId);
543
+ emitRuntimeReloadCompleted(session.sessionId, requestId);
544
+ }
545
+ catch (error) {
546
+ const message = error instanceof Error ? error.message : String(error);
547
+ bridgeLogger.warn({
548
+ target: LOG_TARGETS.APP_SESSION,
549
+ eventName: "reload_plugins_failed",
550
+ message: "failed to reload session plugins",
551
+ outcome: "failure",
552
+ ...(requestId ? { requestId } : {}),
553
+ sessionId: session.sessionId,
554
+ fields: { error_message: message },
555
+ });
556
+ emitRuntimeReloadFailed(session.sessionId, message, requestId);
557
+ }
269
558
  return;
270
559
  }
271
560
  case "mcp_status": {
@@ -341,13 +630,46 @@ async function handleCommand(command, requestId) {
341
630
  handleElicitationResponse(command);
342
631
  return;
343
632
  case "shutdown":
344
- await closeAllSessions();
633
+ bridgeLogger.info({
634
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
635
+ eventName: "bridge_shutdown_requested",
636
+ message: "bridge shutdown requested",
637
+ outcome: "start",
638
+ ...(requestId ? { requestId } : {}),
639
+ });
640
+ await closeAllSessions({ reason: "bridge_shutdown_requested", requestId });
641
+ bridgeLogger.info({
642
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
643
+ eventName: "bridge_shutdown_completed",
644
+ message: "bridge shutdown completed",
645
+ outcome: "success",
646
+ ...(requestId ? { requestId } : {}),
647
+ });
345
648
  process.exit(0);
649
+ return;
346
650
  default:
651
+ bridgeLogger.error({
652
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
653
+ eventName: "bridge_command_rejected",
654
+ message: "received unsupported bridge command",
655
+ outcome: "failure",
656
+ ...(requestId ? { requestId } : {}),
657
+ fields: {
658
+ bridge_command: command.command ?? "unknown",
659
+ reason: "unsupported_command",
660
+ },
661
+ });
347
662
  failConnection(`unhandled command: ${command.command ?? "unknown"}`, requestId);
348
663
  }
349
664
  }
350
665
  function main() {
666
+ bridgeLogger.info({
667
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
668
+ eventName: "bridge_process_started",
669
+ message: "bridge process started",
670
+ outcome: "start",
671
+ fields: { pid: process.pid },
672
+ });
351
673
  const rl = readline.createInterface({
352
674
  input: process.stdin,
353
675
  crlfDelay: Number.POSITIVE_INFINITY,
@@ -363,6 +685,18 @@ function main() {
363
685
  }
364
686
  catch (error) {
365
687
  const message = error instanceof Error ? error.message : String(error);
688
+ bridgeLogger.error({
689
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
690
+ eventName: "bridge_command_decode_failed",
691
+ message: "failed to decode bridge command envelope",
692
+ outcome: "failure",
693
+ sizeBytes: Buffer.byteLength(line),
694
+ fields: {
695
+ preview: line.slice(0, 240),
696
+ preview_chars: Math.min(line.length, 240),
697
+ error_message: message,
698
+ },
699
+ });
366
700
  failConnection(`invalid command envelope: ${message}`);
367
701
  return;
368
702
  }
@@ -371,12 +705,42 @@ function main() {
371
705
  }
372
706
  catch (error) {
373
707
  const message = error instanceof Error ? error.message : String(error);
708
+ bridgeLogger.error({
709
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
710
+ eventName: "bridge_command_failed",
711
+ message: "bridge command handler failed",
712
+ outcome: "failure",
713
+ ...(parsed.requestId ? { requestId: parsed.requestId } : {}),
714
+ ...(parsed.command.command === "create_session" || parsed.command.command === "new_session"
715
+ ? {}
716
+ : "session_id" in parsed.command
717
+ ? { sessionId: parsed.command.session_id }
718
+ : {}),
719
+ fields: {
720
+ bridge_command: parsed.command.command,
721
+ error_message: message,
722
+ },
723
+ });
374
724
  failConnection(`bridge command failed (${parsed.command.command}): ${message}`, parsed.requestId);
375
725
  }
376
726
  })();
377
727
  });
378
728
  rl.on("close", () => {
379
- void closeAllSessions().finally(() => process.exit(0));
729
+ bridgeLogger.info({
730
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
731
+ eventName: "bridge_input_closed",
732
+ message: "bridge stdin closed",
733
+ outcome: "success",
734
+ });
735
+ void closeAllSessions({ reason: "bridge_stdin_closed" }).finally(() => {
736
+ bridgeLogger.info({
737
+ target: LOG_TARGETS.BRIDGE_LIFECYCLE,
738
+ eventName: "bridge_shutdown_completed",
739
+ message: "bridge shutdown completed after stdin close",
740
+ outcome: "success",
741
+ });
742
+ process.exit(0);
743
+ });
380
744
  });
381
745
  }
382
746
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {