claude-code-rust 0.14.3 → 0.14.5
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 +3 -4
- package/agent-sdk/dist/bridge/account_metadata.js +6 -2
- package/agent-sdk/dist/bridge/agents.js +9 -3
- package/agent-sdk/dist/bridge/available_commands.js +13 -3
- package/agent-sdk/dist/bridge/command_lifecycle.js +79 -4
- package/agent-sdk/dist/bridge/command_scheduler.js +7 -2
- package/agent-sdk/dist/bridge/command_session_control.js +5 -1
- package/agent-sdk/dist/bridge/command_session_data.js +225 -10
- package/agent-sdk/dist/bridge/commands.js +48 -11
- package/agent-sdk/dist/bridge/error_classification.js +6 -1
- package/agent-sdk/dist/bridge/events.js +45 -7
- package/agent-sdk/dist/bridge/history.js +34 -10
- package/agent-sdk/dist/bridge/logger.js +19 -3
- package/agent-sdk/dist/bridge/mcp.js +7 -2
- package/agent-sdk/dist/bridge/mcp_auth_adapter.js +4 -2
- package/agent-sdk/dist/bridge/mcp_metadata.js +113 -39
- package/agent-sdk/dist/bridge/mcp_monitor.js +6 -1
- package/agent-sdk/dist/bridge/message_handlers.js +324 -66
- package/agent-sdk/dist/bridge/model_metadata.js +19 -6
- package/agent-sdk/dist/bridge/permissions.js +32 -8
- package/agent-sdk/dist/bridge/session_lifecycle.js +234 -52
- package/agent-sdk/dist/bridge/state_parsing.js +23 -8
- package/agent-sdk/dist/bridge/tasks.js +63 -22
- package/agent-sdk/dist/bridge/tool_calls.js +82 -25
- package/agent-sdk/dist/bridge/tooling.js +311 -88
- package/agent-sdk/dist/bridge/user_interaction.js +45 -13
- package/agent-sdk/dist/bridge.js +200 -104
- package/package.json +8 -8
|
@@ -6,17 +6,19 @@ import { bridgeLogger, LOG_TARGETS, logSdkStderrLine } from "./logger.js";
|
|
|
6
6
|
import { AsyncQueue } from "./shared.js";
|
|
7
7
|
import { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./permissions.js";
|
|
8
8
|
import { failConnection, emitSessionUpdate, emitConnectEvent, emitPermissionRequestEvent, emitElicitationRequestEvent, emitUserDialogRequestEvent, } from "./events.js";
|
|
9
|
-
import { ensureToolCallVisible, setToolCallStatus
|
|
9
|
+
import { ensureToolCallVisible, setToolCallStatus } from "./tool_calls.js";
|
|
10
10
|
import { isToolSearchToolName } from "./tooling.js";
|
|
11
11
|
import { requestExitPlanModeApproval, requestAskUserQuestionAnswers, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "./user_interaction.js";
|
|
12
|
-
import { mapAvailableAgents, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
|
|
12
|
+
import { mapAvailableAgents, emitAvailableAgentsIfChanged, refreshAvailableAgents, } from "./agents.js";
|
|
13
13
|
import { mapSdkSlashCommands, updateAvailableCommands, } from "./available_commands.js";
|
|
14
14
|
import { emitAuthRequired, emitFastModeUpdate, setFastModeSnapshotIfChanged, } from "./error_classification.js";
|
|
15
15
|
import { mapAvailableModels, resolveCurrentModel, currentModelsEqual, } from "./model_metadata.js";
|
|
16
16
|
import { shouldEmitStartupAuthRequiredForAccount } from "./account_metadata.js";
|
|
17
17
|
export { mapAvailableModels, resolveCurrentModel } from "./model_metadata.js";
|
|
18
18
|
export { shouldEmitStartupAuthRequiredForAccount } from "./account_metadata.js";
|
|
19
|
-
const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32"
|
|
19
|
+
const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32"
|
|
20
|
+
? "claude-rs-bridge-bun.exe"
|
|
21
|
+
: "claude-rs-bridge-bun";
|
|
20
22
|
const BRIDGE_RUNTIME_GUARD_PROMPT = `Do not terminate the Claude Rust bridge runtime process \`${BRIDGE_RUNTIME_PROCESS_NAME}\`; ` +
|
|
21
23
|
"when cleaning up development servers, only stop processes by explicit PIDs you started in this session.";
|
|
22
24
|
const STARTUP_FALLBACK_MODEL_ALIAS = "fable";
|
|
@@ -82,7 +84,9 @@ function normalizeRefusalFallbackPayload(payload) {
|
|
|
82
84
|
? { guidance_text: optionalString(payload.guidanceText) }
|
|
83
85
|
: {}),
|
|
84
86
|
...(optionalStringArray(payload.retractedMessageUuids) !== undefined
|
|
85
|
-
? {
|
|
87
|
+
? {
|
|
88
|
+
retracted_message_uuids: optionalStringArray(payload.retractedMessageUuids),
|
|
89
|
+
}
|
|
86
90
|
: {}),
|
|
87
91
|
};
|
|
88
92
|
}
|
|
@@ -93,8 +97,14 @@ function normalizeRefusalFallbackPayload(payload) {
|
|
|
93
97
|
*/
|
|
94
98
|
function buildRefusalFallbackOptions(payload) {
|
|
95
99
|
return [
|
|
96
|
-
{
|
|
97
|
-
|
|
100
|
+
{
|
|
101
|
+
option_id: "retry_fallback",
|
|
102
|
+
label: `Switch to ${payload.fallback_model}`,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
option_id: "edit_prompt",
|
|
106
|
+
label: `Edit prompt and retry with ${payload.original_model}`,
|
|
107
|
+
},
|
|
98
108
|
];
|
|
99
109
|
}
|
|
100
110
|
function settingsObjectFromLaunchSettings(launchSettings) {
|
|
@@ -110,7 +120,9 @@ function normalizedSettingsFromLaunchSettings(launchSettings) {
|
|
|
110
120
|
...settings,
|
|
111
121
|
feedbackDrafts: "off",
|
|
112
122
|
};
|
|
113
|
-
const sandbox = settings.sandbox &&
|
|
123
|
+
const sandbox = settings.sandbox &&
|
|
124
|
+
typeof settings.sandbox === "object" &&
|
|
125
|
+
!Array.isArray(settings.sandbox)
|
|
114
126
|
? settings.sandbox
|
|
115
127
|
: undefined;
|
|
116
128
|
if (sandbox?.enabled === true && sandbox.failIfUnavailable === undefined) {
|
|
@@ -247,10 +259,12 @@ export async function closeAllSessions(options = {}) {
|
|
|
247
259
|
}
|
|
248
260
|
export async function createSession(params) {
|
|
249
261
|
const input = new AsyncQueue();
|
|
250
|
-
const provisionalSessionId = params.
|
|
262
|
+
const provisionalSessionId = params.sessionId ??
|
|
263
|
+
(params.resume && !params.forkSession ? params.resume : randomUUID());
|
|
251
264
|
const initialModel = initialSessionModel(params.launchSettings);
|
|
252
265
|
const initialMode = initialSessionMode(params.launchSettings);
|
|
253
|
-
const supportsBypassPermissionsMode = startupPermissionModeOptions(params.launchSettings)
|
|
266
|
+
const supportsBypassPermissionsMode = startupPermissionModeOptions(params.launchSettings)
|
|
267
|
+
.allowDangerouslySkipPermissions === true;
|
|
254
268
|
const historyUpdateCount = params.resumeUpdates?.length ?? 0;
|
|
255
269
|
const staleSessionCount = params.sessionsToCloseAfterConnect?.length ?? 0;
|
|
256
270
|
const staleSessionBeforeRegisterCount = params.sessionsToCloseBeforeRegister?.length ?? 0;
|
|
@@ -260,7 +274,11 @@ export async function createSession(params) {
|
|
|
260
274
|
const toolUseId = options.toolUseID;
|
|
261
275
|
if (isToolSearchToolName(toolName)) {
|
|
262
276
|
session?.hiddenToolUseIds.add(toolUseId);
|
|
263
|
-
return {
|
|
277
|
+
return {
|
|
278
|
+
behavior: "allow",
|
|
279
|
+
updatedInput: inputData,
|
|
280
|
+
toolUseID: toolUseId,
|
|
281
|
+
};
|
|
264
282
|
}
|
|
265
283
|
if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
|
|
266
284
|
const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
|
|
@@ -321,6 +339,8 @@ export async function createSession(params) {
|
|
|
321
339
|
connect_event: params.connectEvent,
|
|
322
340
|
resume_requested: params.resume !== undefined,
|
|
323
341
|
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
342
|
+
resume_drops_turn: params.resumeDropsTurn ?? "<none>",
|
|
343
|
+
fork_session: params.forkSession === true,
|
|
324
344
|
history_update_count: historyUpdateCount,
|
|
325
345
|
stale_session_count: staleSessionCount,
|
|
326
346
|
stale_session_before_register_count: staleSessionBeforeRegisterCount,
|
|
@@ -333,6 +353,8 @@ export async function createSession(params) {
|
|
|
333
353
|
cwd: params.cwd,
|
|
334
354
|
resume: params.resume,
|
|
335
355
|
resumeSessionAt: params.resumeSessionAt,
|
|
356
|
+
resumeDropsTurn: params.resumeDropsTurn,
|
|
357
|
+
forkSession: params.forkSession,
|
|
336
358
|
launchSettings: params.launchSettings,
|
|
337
359
|
provisionalSessionId,
|
|
338
360
|
input,
|
|
@@ -379,6 +401,10 @@ export async function createSession(params) {
|
|
|
379
401
|
query: queryHandle,
|
|
380
402
|
input,
|
|
381
403
|
connected: false,
|
|
404
|
+
...(params.deferConnect ? { deferConnect: true } : {}),
|
|
405
|
+
...(params.resumeDropsTurn
|
|
406
|
+
? { resumeDropsTurn: params.resumeDropsTurn }
|
|
407
|
+
: {}),
|
|
382
408
|
closing: false,
|
|
383
409
|
connectEvent: params.connectEvent,
|
|
384
410
|
connectRequestId: params.requestId,
|
|
@@ -400,7 +426,9 @@ export async function createSession(params) {
|
|
|
400
426
|
...(params.resumeUpdates && params.resumeUpdates.length > 0
|
|
401
427
|
? { resumeUpdates: params.resumeUpdates }
|
|
402
428
|
: {}),
|
|
403
|
-
...(params.restoredInput !== undefined
|
|
429
|
+
...(params.restoredInput !== undefined
|
|
430
|
+
? { restoredInput: params.restoredInput }
|
|
431
|
+
: {}),
|
|
404
432
|
...(params.pendingRewindResult !== undefined
|
|
405
433
|
? { pendingRewindResult: params.pendingRewindResult }
|
|
406
434
|
: {}),
|
|
@@ -425,6 +453,8 @@ export async function createSession(params) {
|
|
|
425
453
|
connect_event: session.connectEvent,
|
|
426
454
|
resume_requested: params.resume !== undefined,
|
|
427
455
|
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
456
|
+
resume_drops_turn: params.resumeDropsTurn ?? "<none>",
|
|
457
|
+
fork_session: params.forkSession === true,
|
|
428
458
|
},
|
|
429
459
|
});
|
|
430
460
|
bridgeLogger.info({
|
|
@@ -451,10 +481,14 @@ export async function createSession(params) {
|
|
|
451
481
|
eventName: "session_initialization_completed",
|
|
452
482
|
message: "session initialization completed",
|
|
453
483
|
outcome: "success",
|
|
454
|
-
...(session.connectRequestId
|
|
484
|
+
...(session.connectRequestId
|
|
485
|
+
? { requestId: session.connectRequestId }
|
|
486
|
+
: {}),
|
|
455
487
|
sessionId: session.sessionId,
|
|
456
488
|
fields: {
|
|
457
|
-
available_model_count: Array.isArray(result.models)
|
|
489
|
+
available_model_count: Array.isArray(result.models)
|
|
490
|
+
? result.models.length
|
|
491
|
+
: 0,
|
|
458
492
|
connect_event: session.connectEvent,
|
|
459
493
|
history_update_count: session.resumeUpdates?.length ?? 0,
|
|
460
494
|
},
|
|
@@ -464,7 +498,12 @@ export async function createSession(params) {
|
|
|
464
498
|
const { buildModeState, refreshSupportedModesForSession } = await import("./commands.js");
|
|
465
499
|
refreshSupportedModesForSession(session);
|
|
466
500
|
const fastModeChanged = setFastModeSnapshotIfChanged(session, result.fast_mode_state, result.fast_mode_disabled_reason);
|
|
467
|
-
if (!session.connected
|
|
501
|
+
if (!session.connected &&
|
|
502
|
+
session.deferConnect &&
|
|
503
|
+
!session.initializationError) {
|
|
504
|
+
session.initializationReady = true;
|
|
505
|
+
}
|
|
506
|
+
else if (!session.connected) {
|
|
468
507
|
emitConnectEvent(session);
|
|
469
508
|
}
|
|
470
509
|
else {
|
|
@@ -495,16 +534,21 @@ export async function createSession(params) {
|
|
|
495
534
|
return;
|
|
496
535
|
}
|
|
497
536
|
const message = error instanceof Error ? error.message : String(error);
|
|
537
|
+
session.initializationError = message;
|
|
498
538
|
bridgeLogger.error({
|
|
499
539
|
target: LOG_TARGETS.APP_SESSION,
|
|
500
540
|
eventName: "session_initialization_failed",
|
|
501
541
|
message: "session initialization failed before connect",
|
|
502
542
|
outcome: "failure",
|
|
503
|
-
...(session.connectRequestId
|
|
543
|
+
...(session.connectRequestId
|
|
544
|
+
? { requestId: session.connectRequestId }
|
|
545
|
+
: {}),
|
|
504
546
|
sessionId: session.sessionId,
|
|
505
547
|
fields: { error_message: message },
|
|
506
548
|
});
|
|
507
|
-
|
|
549
|
+
if (!session.deferConnect) {
|
|
550
|
+
failConnection(`agent initialization failed: ${message}`, session.connectRequestId);
|
|
551
|
+
}
|
|
508
552
|
session.connectRequestId = undefined;
|
|
509
553
|
});
|
|
510
554
|
session.queryConsumerTask = (async () => {
|
|
@@ -528,11 +572,16 @@ export async function createSession(params) {
|
|
|
528
572
|
...(params.requestId ? { requestId: params.requestId } : {}),
|
|
529
573
|
sessionId: session.sessionId,
|
|
530
574
|
});
|
|
531
|
-
|
|
575
|
+
session.initializationError =
|
|
576
|
+
"agent stream ended before session initialization";
|
|
577
|
+
if (!session.deferConnect) {
|
|
578
|
+
failConnection("agent stream ended before session initialization", params.requestId);
|
|
579
|
+
}
|
|
532
580
|
}
|
|
533
581
|
}
|
|
534
582
|
catch (error) {
|
|
535
583
|
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
+
session.initializationError = message;
|
|
536
585
|
bridgeLogger.error({
|
|
537
586
|
target: LOG_TARGETS.APP_SESSION,
|
|
538
587
|
eventName: "session_stream_failed_before_connect",
|
|
@@ -542,11 +591,65 @@ export async function createSession(params) {
|
|
|
542
591
|
sessionId: session.sessionId,
|
|
543
592
|
fields: { error_message: message },
|
|
544
593
|
});
|
|
545
|
-
|
|
594
|
+
if (!session.deferConnect) {
|
|
595
|
+
failConnection(`agent stream failed: ${message}`, params.requestId);
|
|
596
|
+
}
|
|
546
597
|
}
|
|
547
598
|
})();
|
|
548
599
|
return session;
|
|
549
600
|
}
|
|
601
|
+
export async function awaitSessionInitialization(session) {
|
|
602
|
+
await session.initializationTask;
|
|
603
|
+
if (session.deferConnect &&
|
|
604
|
+
session.resumeDropsTurn &&
|
|
605
|
+
!session.initializationError) {
|
|
606
|
+
// The guard is evaluated at fork time and a refusal is delivered as a result event,
|
|
607
|
+
// not as a rejected initialization request. A post-initialize control response is
|
|
608
|
+
// the protocol barrier proving that startup messages have reached the SDK; yield
|
|
609
|
+
// once more so the bridge's query consumer can process the already-enqueued result.
|
|
610
|
+
try {
|
|
611
|
+
await session.query.getContextUsage();
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
615
|
+
bridgeLogger.warn({
|
|
616
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
617
|
+
eventName: "session_resume_guard_barrier_failed",
|
|
618
|
+
message: "guarded resume control fence failed",
|
|
619
|
+
outcome: "failure",
|
|
620
|
+
sessionId: session.sessionId,
|
|
621
|
+
fields: { error_message: message },
|
|
622
|
+
});
|
|
623
|
+
throw new Error(`guarded resume validation fence failed: ${message}`);
|
|
624
|
+
}
|
|
625
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
626
|
+
session.resumeGuardFenceComplete = true;
|
|
627
|
+
}
|
|
628
|
+
if (session.initializationError) {
|
|
629
|
+
throw new Error(session.initializationError);
|
|
630
|
+
}
|
|
631
|
+
if (session.deferConnect && !session.initializationReady) {
|
|
632
|
+
throw new Error("candidate session did not reach initialization readiness");
|
|
633
|
+
}
|
|
634
|
+
if (!session.deferConnect && !session.connected) {
|
|
635
|
+
throw new Error("session did not connect after initialization");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
export function commitDeferredSession(session) {
|
|
639
|
+
if (!session.deferConnect ||
|
|
640
|
+
!session.initializationReady ||
|
|
641
|
+
session.initializationError ||
|
|
642
|
+
(session.resumeDropsTurn !== undefined && !session.resumeGuardFenceComplete)) {
|
|
643
|
+
throw new Error("candidate session is not ready to commit");
|
|
644
|
+
}
|
|
645
|
+
const deferredAuthRequired = session.deferredAuthRequired;
|
|
646
|
+
session.deferredAuthRequired = undefined;
|
|
647
|
+
session.deferConnect = false;
|
|
648
|
+
emitConnectEvent(session);
|
|
649
|
+
if (deferredAuthRequired) {
|
|
650
|
+
emitAuthRequired(session, deferredAuthRequired.detail);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
550
653
|
function logSdkProcessSpawnStarted(options, includeArgsPreview) {
|
|
551
654
|
bridgeLogger.info({
|
|
552
655
|
target: LOG_TARGETS.BRIDGE_SDK,
|
|
@@ -622,14 +725,19 @@ function startupModelOption(launchSettings) {
|
|
|
622
725
|
}
|
|
623
726
|
function initialSessionMode(launchSettings) {
|
|
624
727
|
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
625
|
-
const permissions = settings?.permissions &&
|
|
728
|
+
const permissions = settings?.permissions &&
|
|
729
|
+
typeof settings.permissions === "object" &&
|
|
730
|
+
!Array.isArray(settings.permissions)
|
|
626
731
|
? settings.permissions
|
|
627
732
|
: undefined;
|
|
628
|
-
return permissionModeFromSettingsValue(permissions?.defaultMode) ??
|
|
733
|
+
return (permissionModeFromSettingsValue(permissions?.defaultMode) ??
|
|
734
|
+
DEFAULT_PERMISSION_MODE);
|
|
629
735
|
}
|
|
630
736
|
function startupPermissionModeOptions(launchSettings) {
|
|
631
737
|
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
632
|
-
const permissions = settings?.permissions &&
|
|
738
|
+
const permissions = settings?.permissions &&
|
|
739
|
+
typeof settings.permissions === "object" &&
|
|
740
|
+
!Array.isArray(settings.permissions)
|
|
633
741
|
? settings.permissions
|
|
634
742
|
: undefined;
|
|
635
743
|
const permissionMode = permissionModeFromSettingsValue(permissions?.defaultMode);
|
|
@@ -667,10 +775,9 @@ export function buildQueryOptions(params) {
|
|
|
667
775
|
includePartialMessages: true,
|
|
668
776
|
promptSuggestions: true,
|
|
669
777
|
enableFileCheckpointing: true,
|
|
670
|
-
//
|
|
671
|
-
//
|
|
672
|
-
|
|
673
|
-
disallowedTools: ["ProposeSkills"],
|
|
778
|
+
// Proposal tools require native review and lifecycle surfaces that the
|
|
779
|
+
// public headless SDK does not currently expose to this host.
|
|
780
|
+
disallowedTools: ["ProposeSkills", "ProposeGoal"],
|
|
674
781
|
executable: "bun",
|
|
675
782
|
...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
|
|
676
783
|
settings,
|
|
@@ -679,7 +786,9 @@ export function buildQueryOptions(params) {
|
|
|
679
786
|
toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
|
|
680
787
|
systemPrompt,
|
|
681
788
|
...(params.launchSettings.agent_progress_summaries !== undefined
|
|
682
|
-
? {
|
|
789
|
+
? {
|
|
790
|
+
agentProgressSummaries: params.launchSettings.agent_progress_summaries,
|
|
791
|
+
}
|
|
683
792
|
: {}),
|
|
684
793
|
...(params.claudeCodeExecutable
|
|
685
794
|
? { pathToClaudeCodeExecutable: params.claudeCodeExecutable }
|
|
@@ -693,11 +802,15 @@ export function buildQueryOptions(params) {
|
|
|
693
802
|
},
|
|
694
803
|
spawnClaudeCodeProcess: (options) => {
|
|
695
804
|
const command = resolveClaudeCodeSpawnCommand(options.command);
|
|
696
|
-
const
|
|
805
|
+
const env = { ...options.env };
|
|
806
|
+
if (env.CLAUDE_CODE_ENABLE_TODO_TOOLS === undefined) {
|
|
807
|
+
env.CLAUDE_CODE_ENABLE_TODO_TOOLS = "1";
|
|
808
|
+
}
|
|
809
|
+
const spawnOptions = { ...options, command, env };
|
|
697
810
|
logSdkProcessSpawnStarted(spawnOptions, params.enableSpawnDebug);
|
|
698
811
|
const child = spawnChild(command, options.args, {
|
|
699
812
|
cwd: options.cwd,
|
|
700
|
-
env
|
|
813
|
+
env,
|
|
701
814
|
signal: options.signal,
|
|
702
815
|
stdio: ["pipe", "pipe", "pipe"],
|
|
703
816
|
windowsHide: true,
|
|
@@ -724,10 +837,18 @@ export function buildQueryOptions(params) {
|
|
|
724
837
|
// --setting-sources argument.
|
|
725
838
|
settingSources: DEFAULT_SETTING_SOURCES,
|
|
726
839
|
resume: params.resume,
|
|
727
|
-
...(params.resumeSessionAt
|
|
840
|
+
...(params.resumeSessionAt
|
|
841
|
+
? { resumeSessionAt: params.resumeSessionAt }
|
|
842
|
+
: {}),
|
|
843
|
+
...(params.resumeDropsTurn
|
|
844
|
+
? { resumeDropsTurn: params.resumeDropsTurn }
|
|
845
|
+
: {}),
|
|
846
|
+
...(params.forkSession
|
|
847
|
+
? { forkSession: true, sessionId: params.provisionalSessionId }
|
|
848
|
+
: {}),
|
|
728
849
|
...(shouldPassCanUseTool ? { canUseTool: params.canUseTool } : {}),
|
|
729
|
-
onElicitation: async (request) => {
|
|
730
|
-
const requestId =
|
|
850
|
+
onElicitation: async (request, options) => {
|
|
851
|
+
const requestId = options.requestId;
|
|
731
852
|
const mode = request.mode === "form" || request.mode === "url"
|
|
732
853
|
? request.mode
|
|
733
854
|
: typeof request.url === "string" && request.url.trim().length > 0
|
|
@@ -735,21 +856,26 @@ export function buildQueryOptions(params) {
|
|
|
735
856
|
: "form";
|
|
736
857
|
const normalized = {
|
|
737
858
|
request_id: requestId,
|
|
738
|
-
server_name: typeof request.serverName === "string" &&
|
|
859
|
+
server_name: typeof request.serverName === "string" &&
|
|
860
|
+
request.serverName.trim().length > 0
|
|
739
861
|
? request.serverName
|
|
740
862
|
: "unknown",
|
|
741
|
-
message: typeof request.message === "string" &&
|
|
863
|
+
message: typeof request.message === "string" &&
|
|
864
|
+
request.message.trim().length > 0
|
|
742
865
|
? request.message
|
|
743
866
|
: "<no message>",
|
|
744
867
|
mode,
|
|
745
868
|
...(typeof request.url === "string" && request.url.trim().length > 0
|
|
746
869
|
? { url: request.url }
|
|
747
870
|
: {}),
|
|
748
|
-
...(typeof request.elicitationId === "string" &&
|
|
871
|
+
...(typeof request.elicitationId === "string" &&
|
|
872
|
+
request.elicitationId.trim().length > 0
|
|
749
873
|
? { elicitation_id: request.elicitationId }
|
|
750
874
|
: {}),
|
|
751
875
|
...(request.requestedSchema
|
|
752
|
-
? {
|
|
876
|
+
? {
|
|
877
|
+
requested_schema: request.requestedSchema,
|
|
878
|
+
}
|
|
753
879
|
: {}),
|
|
754
880
|
};
|
|
755
881
|
bridgeLogger.info({
|
|
@@ -781,8 +907,37 @@ export function buildQueryOptions(params) {
|
|
|
781
907
|
resolve({ action: "cancel" });
|
|
782
908
|
return;
|
|
783
909
|
}
|
|
910
|
+
if (currentSession.pendingElicitations.has(requestId)) {
|
|
911
|
+
bridgeLogger.warn({
|
|
912
|
+
target: LOG_TARGETS.BRIDGE_PERMISSION,
|
|
913
|
+
eventName: "elicitation_request_dropped",
|
|
914
|
+
message: "duplicate elicitation request dropped",
|
|
915
|
+
outcome: "dropped",
|
|
916
|
+
sessionId: params.sessionIdForLogs(),
|
|
917
|
+
requestId,
|
|
918
|
+
fields: { reason: "duplicate_request_id" },
|
|
919
|
+
});
|
|
920
|
+
resolve({ action: "cancel" });
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
let settled = false;
|
|
924
|
+
const settle = (result) => {
|
|
925
|
+
if (settled) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
settled = true;
|
|
929
|
+
currentSession.pendingElicitations.delete(requestId);
|
|
930
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
931
|
+
resolve(result);
|
|
932
|
+
};
|
|
933
|
+
const onAbort = () => settle({ action: "cancel" });
|
|
934
|
+
if (options.signal.aborted) {
|
|
935
|
+
settle({ action: "cancel" });
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
options.signal.addEventListener("abort", onAbort);
|
|
784
939
|
currentSession.pendingElicitations.set(requestId, {
|
|
785
|
-
resolve,
|
|
940
|
+
resolve: settle,
|
|
786
941
|
serverName: normalized.server_name,
|
|
787
942
|
elicitationId: normalized.elicitation_id,
|
|
788
943
|
});
|
|
@@ -794,8 +949,7 @@ export function buildQueryOptions(params) {
|
|
|
794
949
|
// the CLI emits `request_user_dialog` and the host renders the chooser below.
|
|
795
950
|
supportedDialogKinds: [REFUSAL_FALLBACK_DIALOG_KIND],
|
|
796
951
|
// Host policy for `request_user_dialog` control requests. Unknown kinds are
|
|
797
|
-
// logged and
|
|
798
|
-
// for unrecognized kinds). For `refusal_fallback_prompt`, we surface an
|
|
952
|
+
// logged and left unsettled by returning `null`. For `refusal_fallback_prompt`, we surface an
|
|
799
953
|
// interactive chooser in the TUI and route the user's decision back to the
|
|
800
954
|
// CLI as `{ behavior: "completed", result: <choice> }` (or cancelled on
|
|
801
955
|
// decline/abort/teardown).
|
|
@@ -804,16 +958,18 @@ export function buildQueryOptions(params) {
|
|
|
804
958
|
bridgeLogger.warn({
|
|
805
959
|
target: LOG_TARGETS.APP_SESSION,
|
|
806
960
|
eventName: "user_dialog_received",
|
|
807
|
-
message: "request_user_dialog received for unknown kind;
|
|
808
|
-
outcome: "
|
|
961
|
+
message: "request_user_dialog received for unknown kind; unsupported",
|
|
962
|
+
outcome: "unsupported",
|
|
809
963
|
sessionId: params.sessionIdForLogs(),
|
|
810
|
-
...(typeof request.toolUseID === "string"
|
|
964
|
+
...(typeof request.toolUseID === "string"
|
|
965
|
+
? { toolCallId: request.toolUseID }
|
|
966
|
+
: {}),
|
|
811
967
|
fields: { dialog_kind: request.dialogKind },
|
|
812
968
|
});
|
|
813
|
-
return
|
|
969
|
+
return null;
|
|
814
970
|
}
|
|
815
971
|
const payload = normalizeRefusalFallbackPayload(request.payload);
|
|
816
|
-
const requestId =
|
|
972
|
+
const requestId = options.requestId;
|
|
817
973
|
const dialogRequest = {
|
|
818
974
|
request_id: requestId,
|
|
819
975
|
dialog_kind: REFUSAL_FALLBACK_DIALOG_KIND,
|
|
@@ -827,7 +983,9 @@ export function buildQueryOptions(params) {
|
|
|
827
983
|
outcome: "start",
|
|
828
984
|
sessionId: params.sessionIdForLogs(),
|
|
829
985
|
requestId,
|
|
830
|
-
...(typeof request.toolUseID === "string"
|
|
986
|
+
...(typeof request.toolUseID === "string"
|
|
987
|
+
? { toolCallId: request.toolUseID }
|
|
988
|
+
: {}),
|
|
831
989
|
fields: {
|
|
832
990
|
dialog_kind: request.dialogKind,
|
|
833
991
|
original_model: payload.original_model,
|
|
@@ -849,6 +1007,19 @@ export function buildQueryOptions(params) {
|
|
|
849
1007
|
resolve("cancelled");
|
|
850
1008
|
return;
|
|
851
1009
|
}
|
|
1010
|
+
if (currentSession.pendingUserDialogs.has(requestId)) {
|
|
1011
|
+
bridgeLogger.warn({
|
|
1012
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
1013
|
+
eventName: "user_dialog_request_dropped",
|
|
1014
|
+
message: "duplicate user dialog request dropped",
|
|
1015
|
+
outcome: "dropped",
|
|
1016
|
+
sessionId: params.sessionIdForLogs(),
|
|
1017
|
+
requestId,
|
|
1018
|
+
fields: { reason: "duplicate_request_id" },
|
|
1019
|
+
});
|
|
1020
|
+
resolve("cancelled");
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
852
1023
|
let settled = false;
|
|
853
1024
|
const settle = (value) => {
|
|
854
1025
|
if (settled) {
|
|
@@ -884,7 +1055,9 @@ export function handlePermissionResponse(command) {
|
|
|
884
1055
|
toolCallId: command.tool_call_id,
|
|
885
1056
|
fields: {
|
|
886
1057
|
response_kind: command.outcome.outcome,
|
|
887
|
-
selected_option: command.outcome.outcome === "selected"
|
|
1058
|
+
selected_option: command.outcome.outcome === "selected"
|
|
1059
|
+
? command.outcome.option_id
|
|
1060
|
+
: "cancelled",
|
|
888
1061
|
},
|
|
889
1062
|
});
|
|
890
1063
|
const session = sessionById(command.session_id);
|
|
@@ -1021,8 +1194,11 @@ export function handleQuestionResponse(command) {
|
|
|
1021
1194
|
fields: {
|
|
1022
1195
|
tool_name: resolver.toolName,
|
|
1023
1196
|
response_kind: command.outcome.outcome,
|
|
1024
|
-
selected_option_count: command.outcome.outcome === "answered"
|
|
1025
|
-
|
|
1197
|
+
selected_option_count: command.outcome.outcome === "answered"
|
|
1198
|
+
? command.outcome.selected_option_ids.length
|
|
1199
|
+
: 0,
|
|
1200
|
+
has_annotation: command.outcome.outcome === "answered" &&
|
|
1201
|
+
command.outcome.annotation !== undefined,
|
|
1026
1202
|
},
|
|
1027
1203
|
});
|
|
1028
1204
|
resolver.onOutcome(command.outcome);
|
|
@@ -1037,7 +1213,9 @@ export function handleUserDialogResponse(command) {
|
|
|
1037
1213
|
requestId: command.request_id,
|
|
1038
1214
|
fields: {
|
|
1039
1215
|
response_kind: command.outcome.outcome,
|
|
1040
|
-
selected_option: command.outcome.outcome === "selected"
|
|
1216
|
+
selected_option: command.outcome.outcome === "selected"
|
|
1217
|
+
? command.outcome.option_id
|
|
1218
|
+
: "cancelled",
|
|
1041
1219
|
},
|
|
1042
1220
|
});
|
|
1043
1221
|
const session = sessionById(command.session_id);
|
|
@@ -1069,7 +1247,9 @@ export function handleUserDialogResponse(command) {
|
|
|
1069
1247
|
return;
|
|
1070
1248
|
}
|
|
1071
1249
|
session.pendingUserDialogs.delete(command.request_id);
|
|
1072
|
-
const choice = command.outcome.outcome === "selected"
|
|
1250
|
+
const choice = command.outcome.outcome === "selected"
|
|
1251
|
+
? command.outcome.option_id
|
|
1252
|
+
: "cancelled";
|
|
1073
1253
|
bridgeLogger.info({
|
|
1074
1254
|
target: LOG_TARGETS.BRIDGE_PERMISSION,
|
|
1075
1255
|
eventName: "user_dialog_response_applied",
|
|
@@ -1136,9 +1316,11 @@ export function handleElicitationResponse(command) {
|
|
|
1136
1316
|
});
|
|
1137
1317
|
pending.resolve({
|
|
1138
1318
|
action: command.action,
|
|
1139
|
-
...(normalizeSdkElicitationContent(command.content)
|
|
1140
|
-
|
|
1141
|
-
|
|
1319
|
+
...(normalizeSdkElicitationContent(command.content)
|
|
1320
|
+
? {
|
|
1321
|
+
content: normalizeSdkElicitationContent(command.content),
|
|
1322
|
+
}
|
|
1323
|
+
: {}),
|
|
1142
1324
|
});
|
|
1143
1325
|
}
|
|
1144
1326
|
export function shouldInvalidateResolvedRuntimeModel(previousRequestedId, previousSessionModel, nextRequestedId) {
|
|
@@ -17,7 +17,9 @@ function nonNegativeNumberField(record, ...keys) {
|
|
|
17
17
|
}
|
|
18
18
|
export function nonNegativeIntegerField(record, ...keys) {
|
|
19
19
|
const value = numberField(record, ...keys);
|
|
20
|
-
return value !== undefined && value >= 0 && Number.isInteger(value)
|
|
20
|
+
return value !== undefined && value >= 0 && Number.isInteger(value)
|
|
21
|
+
? value
|
|
22
|
+
: undefined;
|
|
21
23
|
}
|
|
22
24
|
export function buildSubagentRetryUpdate(message) {
|
|
23
25
|
const retry = asRecordOrNull(message.subagent_retry);
|
|
@@ -27,7 +29,9 @@ export function buildSubagentRetryUpdate(message) {
|
|
|
27
29
|
const attempt = nonNegativeIntegerField(retry, "attempt");
|
|
28
30
|
const maxRetries = nonNegativeIntegerField(retry, "max_retries", "maxRetries");
|
|
29
31
|
const retryDelayMs = nonNegativeIntegerField(retry, "retry_delay_ms", "retryDelayMs");
|
|
30
|
-
if (attempt === undefined ||
|
|
32
|
+
if (attempt === undefined ||
|
|
33
|
+
maxRetries === undefined ||
|
|
34
|
+
retryDelayMs === undefined) {
|
|
31
35
|
return null;
|
|
32
36
|
}
|
|
33
37
|
const agentId = typeof retry.agent_id === "string" ? retry.agent_id.trim() : "";
|
|
@@ -57,7 +61,9 @@ export function parseFastModeDisabledReason(value) {
|
|
|
57
61
|
return reason.length > 0 ? reason : undefined;
|
|
58
62
|
}
|
|
59
63
|
export function parseRateLimitStatus(value) {
|
|
60
|
-
if (value === "allowed" ||
|
|
64
|
+
if (value === "allowed" ||
|
|
65
|
+
value === "allowed_warning" ||
|
|
66
|
+
value === "rejected") {
|
|
61
67
|
return value;
|
|
62
68
|
}
|
|
63
69
|
return null;
|
|
@@ -71,6 +77,7 @@ export function parseRuntimeSessionState(value) {
|
|
|
71
77
|
export function parseApiRetryError(value) {
|
|
72
78
|
switch (value) {
|
|
73
79
|
case "authentication_failed":
|
|
80
|
+
case "account_on_hold":
|
|
74
81
|
case "oauth_org_not_allowed":
|
|
75
82
|
case "billing_error":
|
|
76
83
|
case "rate_limit":
|
|
@@ -119,7 +126,8 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
119
126
|
if (overageResetsAt !== undefined) {
|
|
120
127
|
update.overage_resets_at = overageResetsAt;
|
|
121
128
|
}
|
|
122
|
-
if (typeof info.overageDisabledReason === "string" &&
|
|
129
|
+
if (typeof info.overageDisabledReason === "string" &&
|
|
130
|
+
info.overageDisabledReason.length > 0) {
|
|
123
131
|
update.overage_disabled_reason = info.overageDisabledReason;
|
|
124
132
|
}
|
|
125
133
|
if (typeof info.overageInUse === "boolean") {
|
|
@@ -136,7 +144,8 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
136
144
|
update.can_user_purchase_credits = info.canUserPurchaseCredits;
|
|
137
145
|
}
|
|
138
146
|
if (typeof info.hasChargeableSavedPaymentMethod === "boolean") {
|
|
139
|
-
update.has_chargeable_saved_payment_method =
|
|
147
|
+
update.has_chargeable_saved_payment_method =
|
|
148
|
+
info.hasChargeableSavedPaymentMethod;
|
|
140
149
|
}
|
|
141
150
|
return update;
|
|
142
151
|
}
|
|
@@ -144,11 +153,15 @@ export function buildApiRetryUpdate(message) {
|
|
|
144
153
|
const attempt = numberField(message, "attempt");
|
|
145
154
|
const maxRetries = numberField(message, "max_retries", "maxRetries");
|
|
146
155
|
const retryDelayMs = nonNegativeNumberField(message, "retry_delay_ms", "retryDelayMs");
|
|
147
|
-
if (attempt === undefined ||
|
|
156
|
+
if (attempt === undefined ||
|
|
157
|
+
maxRetries === undefined ||
|
|
158
|
+
retryDelayMs === undefined) {
|
|
148
159
|
return null;
|
|
149
160
|
}
|
|
150
161
|
const rawStatus = message.error_status ?? message.errorStatus;
|
|
151
|
-
const errorStatus = typeof rawStatus === "number" && Number.isFinite(rawStatus)
|
|
162
|
+
const errorStatus = typeof rawStatus === "number" && Number.isFinite(rawStatus)
|
|
163
|
+
? rawStatus
|
|
164
|
+
: null;
|
|
152
165
|
return {
|
|
153
166
|
type: "api_retry_update",
|
|
154
167
|
attempt,
|
|
@@ -168,7 +181,9 @@ export function normalizeSettingsParseError(value) {
|
|
|
168
181
|
return null;
|
|
169
182
|
}
|
|
170
183
|
const path = typeof record.path === "string" ? record.path : "";
|
|
171
|
-
const file = typeof record.file === "string" && record.file.trim()
|
|
184
|
+
const file = typeof record.file === "string" && record.file.trim()
|
|
185
|
+
? record.file
|
|
186
|
+
: undefined;
|
|
172
187
|
return {
|
|
173
188
|
...(file ? { file } : {}),
|
|
174
189
|
path,
|