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.
- package/README.md +9 -3
- package/agent-sdk/dist/bridge/events.js +116 -8
- package/agent-sdk/dist/bridge/logger.js +245 -0
- package/agent-sdk/dist/bridge/mcp.js +81 -5
- package/agent-sdk/dist/bridge/message_handlers.js +87 -48
- package/agent-sdk/dist/bridge/session_lifecycle.js +385 -51
- package/agent-sdk/dist/bridge/shared.js +0 -7
- package/agent-sdk/dist/bridge/tool_calls.js +174 -59
- package/agent-sdk/dist/bridge/user_interaction.js +34 -23
- package/agent-sdk/dist/bridge.js +203 -21
- package/package.json +1 -1
|
@@ -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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
459
|
+
logSdkStderrLine(line);
|
|
285
460
|
}
|
|
286
461
|
},
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
452
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
}
|