claude-code-rust 0.14.2 → 0.14.4
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 +2 -3
- 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 +5 -1
- package/agent-sdk/dist/bridge/events.js +45 -7
- package/agent-sdk/dist/bridge/history.js +33 -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 +336 -74
- 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 +224 -45
- package/agent-sdk/dist/bridge/state_parsing.js +23 -9
- package/agent-sdk/dist/bridge/tasks.js +62 -22
- package/agent-sdk/dist/bridge/tool_calls.js +89 -31
- package/agent-sdk/dist/bridge/tooling.js +430 -82
- package/agent-sdk/dist/bridge/user_interaction.js +45 -13
- package/agent-sdk/dist/bridge.js +200 -104
- package/package.json +8 -8
|
@@ -11,12 +11,20 @@ function isEffortLevel(value) {
|
|
|
11
11
|
function normalizeModelKey(id) {
|
|
12
12
|
const original = id.trim();
|
|
13
13
|
if (!original) {
|
|
14
|
-
return {
|
|
14
|
+
return {
|
|
15
|
+
original,
|
|
16
|
+
family: "unknown",
|
|
17
|
+
versionParts: [],
|
|
18
|
+
variantParts: [],
|
|
19
|
+
buildParts: [],
|
|
20
|
+
};
|
|
15
21
|
}
|
|
16
22
|
const lower = original.toLowerCase();
|
|
17
23
|
const contextMatch = lower.match(/\[([^[\]]+)\]$/);
|
|
18
24
|
const contextSuffix = contextMatch?.[1];
|
|
19
|
-
const withoutContext = contextMatch
|
|
25
|
+
const withoutContext = contextMatch
|
|
26
|
+
? lower.slice(0, contextMatch.index)
|
|
27
|
+
: lower;
|
|
20
28
|
const withoutPrefix = withoutContext.startsWith("claude-")
|
|
21
29
|
? withoutContext.slice("claude-".length)
|
|
22
30
|
: withoutContext;
|
|
@@ -82,7 +90,8 @@ function modelKeysAreCompatible(leftId, rightId) {
|
|
|
82
90
|
function sameContextSuffix(leftId, rightId) {
|
|
83
91
|
const left = normalizeModelKey(leftId);
|
|
84
92
|
const right = normalizeModelKey(rightId);
|
|
85
|
-
return (left.contextSuffix?.toLowerCase() ?? "") ===
|
|
93
|
+
return ((left.contextSuffix?.toLowerCase() ?? "") ===
|
|
94
|
+
(right.contextSuffix?.toLowerCase() ?? ""));
|
|
86
95
|
}
|
|
87
96
|
function sameFamilyAndVersion(leftId, rightId) {
|
|
88
97
|
const left = normalizeModelKey(leftId);
|
|
@@ -129,7 +138,9 @@ function humanizeModelId(id) {
|
|
|
129
138
|
: normalized.family === "sonnet"
|
|
130
139
|
? "Sonnet"
|
|
131
140
|
: "Haiku";
|
|
132
|
-
const versionLabel = normalized.versionParts.length > 0
|
|
141
|
+
const versionLabel = normalized.versionParts.length > 0
|
|
142
|
+
? ` ${normalized.versionParts.join(".")}`
|
|
143
|
+
: "";
|
|
133
144
|
const contextLabel = normalized.contextSuffix?.toLowerCase() === "1m"
|
|
134
145
|
? " [1M]"
|
|
135
146
|
: normalized.contextSuffix
|
|
@@ -181,7 +192,8 @@ export function mapAvailableModels(models) {
|
|
|
181
192
|
})
|
|
182
193
|
.map((entry) => ({
|
|
183
194
|
id: entry.value,
|
|
184
|
-
...(typeof entry.resolvedModel === "string" &&
|
|
195
|
+
...(typeof entry.resolvedModel === "string" &&
|
|
196
|
+
entry.resolvedModel.trim().length > 0
|
|
185
197
|
? { resolved_model: entry.resolvedModel.trim() }
|
|
186
198
|
: {}),
|
|
187
199
|
display_name: entry.displayName,
|
|
@@ -198,7 +210,8 @@ export function mapAvailableModels(models) {
|
|
|
198
210
|
...(typeof entry.supportsAutoMode === "boolean"
|
|
199
211
|
? { supports_auto_mode: entry.supportsAutoMode }
|
|
200
212
|
: {}),
|
|
201
|
-
...(typeof entry.description === "string" &&
|
|
213
|
+
...(typeof entry.description === "string" &&
|
|
214
|
+
entry.description.trim().length > 0
|
|
202
215
|
? { description: entry.description }
|
|
203
216
|
: {}),
|
|
204
217
|
}));
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
const SESSION_PERMISSION_DESTINATIONS = new Set(["session", "cliArg"]);
|
|
2
|
-
const PERSISTENT_PERMISSION_DESTINATIONS = new Set([
|
|
2
|
+
const PERSISTENT_PERMISSION_DESTINATIONS = new Set([
|
|
3
|
+
"userSettings",
|
|
4
|
+
"projectSettings",
|
|
5
|
+
"localSettings",
|
|
6
|
+
]);
|
|
3
7
|
function formatPermissionRule(rule) {
|
|
4
|
-
return rule.ruleContent === undefined
|
|
8
|
+
return rule.ruleContent === undefined
|
|
9
|
+
? rule.toolName
|
|
10
|
+
: `${rule.toolName}(${rule.ruleContent})`;
|
|
5
11
|
}
|
|
6
12
|
export function formatPermissionUpdates(updates) {
|
|
7
13
|
if (!updates || updates.length === 0) {
|
|
@@ -9,8 +15,12 @@ export function formatPermissionUpdates(updates) {
|
|
|
9
15
|
}
|
|
10
16
|
return updates
|
|
11
17
|
.map((update) => {
|
|
12
|
-
if (update.type === "addRules" ||
|
|
13
|
-
|
|
18
|
+
if (update.type === "addRules" ||
|
|
19
|
+
update.type === "replaceRules" ||
|
|
20
|
+
update.type === "removeRules") {
|
|
21
|
+
const rules = update.rules
|
|
22
|
+
.map((rule) => formatPermissionRule(rule))
|
|
23
|
+
.join(", ");
|
|
14
24
|
return `${update.type}:${update.behavior}:${update.destination}=[${rules}]`;
|
|
15
25
|
}
|
|
16
26
|
if (update.type === "setMode") {
|
|
@@ -44,7 +54,9 @@ export function permissionOptionsFromSuggestions(suggestions) {
|
|
|
44
54
|
const hasSessionScoped = scoped.session.length > 0;
|
|
45
55
|
const hasPersistentScoped = scoped.persistent.length > 0;
|
|
46
56
|
const sessionOnly = hasSessionScoped && !hasPersistentScoped;
|
|
47
|
-
const options = [
|
|
57
|
+
const options = [
|
|
58
|
+
{ option_id: "allow_once", name: "Allow once", kind: "allow_once" },
|
|
59
|
+
];
|
|
48
60
|
options.push({
|
|
49
61
|
option_id: sessionOnly ? "allow_session" : "allow_always",
|
|
50
62
|
name: sessionOnly ? "Allow for session" : "Always allow",
|
|
@@ -57,7 +69,11 @@ export function permissionResultFromOutcome(outcome, toolCallId, inputData, sugg
|
|
|
57
69
|
const scopedSuggestions = splitPermissionSuggestionsByScope(suggestions);
|
|
58
70
|
if (outcome.outcome === "selected") {
|
|
59
71
|
if (outcome.option_id === "allow_once") {
|
|
60
|
-
return {
|
|
72
|
+
return {
|
|
73
|
+
behavior: "allow",
|
|
74
|
+
updatedInput: inputData,
|
|
75
|
+
toolUseID: toolCallId,
|
|
76
|
+
};
|
|
61
77
|
}
|
|
62
78
|
if (outcome.option_id === "allow_session") {
|
|
63
79
|
const sessionSuggestions = scopedSuggestions.session;
|
|
@@ -105,7 +121,15 @@ export function permissionResultFromOutcome(outcome, toolCallId, inputData, sugg
|
|
|
105
121
|
toolUseID: toolCallId,
|
|
106
122
|
};
|
|
107
123
|
}
|
|
108
|
-
return {
|
|
124
|
+
return {
|
|
125
|
+
behavior: "deny",
|
|
126
|
+
message: "Permission denied",
|
|
127
|
+
toolUseID: toolCallId,
|
|
128
|
+
};
|
|
109
129
|
}
|
|
110
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
behavior: "deny",
|
|
132
|
+
message: "Permission cancelled",
|
|
133
|
+
toolUseID: toolCallId,
|
|
134
|
+
};
|
|
111
135
|
}
|
|
@@ -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 }
|
|
@@ -724,10 +833,18 @@ export function buildQueryOptions(params) {
|
|
|
724
833
|
// --setting-sources argument.
|
|
725
834
|
settingSources: DEFAULT_SETTING_SOURCES,
|
|
726
835
|
resume: params.resume,
|
|
727
|
-
...(params.resumeSessionAt
|
|
836
|
+
...(params.resumeSessionAt
|
|
837
|
+
? { resumeSessionAt: params.resumeSessionAt }
|
|
838
|
+
: {}),
|
|
839
|
+
...(params.resumeDropsTurn
|
|
840
|
+
? { resumeDropsTurn: params.resumeDropsTurn }
|
|
841
|
+
: {}),
|
|
842
|
+
...(params.forkSession
|
|
843
|
+
? { forkSession: true, sessionId: params.provisionalSessionId }
|
|
844
|
+
: {}),
|
|
728
845
|
...(shouldPassCanUseTool ? { canUseTool: params.canUseTool } : {}),
|
|
729
|
-
onElicitation: async (request) => {
|
|
730
|
-
const requestId =
|
|
846
|
+
onElicitation: async (request, options) => {
|
|
847
|
+
const requestId = options.requestId;
|
|
731
848
|
const mode = request.mode === "form" || request.mode === "url"
|
|
732
849
|
? request.mode
|
|
733
850
|
: typeof request.url === "string" && request.url.trim().length > 0
|
|
@@ -735,21 +852,26 @@ export function buildQueryOptions(params) {
|
|
|
735
852
|
: "form";
|
|
736
853
|
const normalized = {
|
|
737
854
|
request_id: requestId,
|
|
738
|
-
server_name: typeof request.serverName === "string" &&
|
|
855
|
+
server_name: typeof request.serverName === "string" &&
|
|
856
|
+
request.serverName.trim().length > 0
|
|
739
857
|
? request.serverName
|
|
740
858
|
: "unknown",
|
|
741
|
-
message: typeof request.message === "string" &&
|
|
859
|
+
message: typeof request.message === "string" &&
|
|
860
|
+
request.message.trim().length > 0
|
|
742
861
|
? request.message
|
|
743
862
|
: "<no message>",
|
|
744
863
|
mode,
|
|
745
864
|
...(typeof request.url === "string" && request.url.trim().length > 0
|
|
746
865
|
? { url: request.url }
|
|
747
866
|
: {}),
|
|
748
|
-
...(typeof request.elicitationId === "string" &&
|
|
867
|
+
...(typeof request.elicitationId === "string" &&
|
|
868
|
+
request.elicitationId.trim().length > 0
|
|
749
869
|
? { elicitation_id: request.elicitationId }
|
|
750
870
|
: {}),
|
|
751
871
|
...(request.requestedSchema
|
|
752
|
-
? {
|
|
872
|
+
? {
|
|
873
|
+
requested_schema: request.requestedSchema,
|
|
874
|
+
}
|
|
753
875
|
: {}),
|
|
754
876
|
};
|
|
755
877
|
bridgeLogger.info({
|
|
@@ -781,8 +903,37 @@ export function buildQueryOptions(params) {
|
|
|
781
903
|
resolve({ action: "cancel" });
|
|
782
904
|
return;
|
|
783
905
|
}
|
|
906
|
+
if (currentSession.pendingElicitations.has(requestId)) {
|
|
907
|
+
bridgeLogger.warn({
|
|
908
|
+
target: LOG_TARGETS.BRIDGE_PERMISSION,
|
|
909
|
+
eventName: "elicitation_request_dropped",
|
|
910
|
+
message: "duplicate elicitation request dropped",
|
|
911
|
+
outcome: "dropped",
|
|
912
|
+
sessionId: params.sessionIdForLogs(),
|
|
913
|
+
requestId,
|
|
914
|
+
fields: { reason: "duplicate_request_id" },
|
|
915
|
+
});
|
|
916
|
+
resolve({ action: "cancel" });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
let settled = false;
|
|
920
|
+
const settle = (result) => {
|
|
921
|
+
if (settled) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
settled = true;
|
|
925
|
+
currentSession.pendingElicitations.delete(requestId);
|
|
926
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
927
|
+
resolve(result);
|
|
928
|
+
};
|
|
929
|
+
const onAbort = () => settle({ action: "cancel" });
|
|
930
|
+
if (options.signal.aborted) {
|
|
931
|
+
settle({ action: "cancel" });
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
options.signal.addEventListener("abort", onAbort);
|
|
784
935
|
currentSession.pendingElicitations.set(requestId, {
|
|
785
|
-
resolve,
|
|
936
|
+
resolve: settle,
|
|
786
937
|
serverName: normalized.server_name,
|
|
787
938
|
elicitationId: normalized.elicitation_id,
|
|
788
939
|
});
|
|
@@ -807,13 +958,15 @@ export function buildQueryOptions(params) {
|
|
|
807
958
|
message: "request_user_dialog received for unknown kind; cancelled",
|
|
808
959
|
outcome: "cancelled",
|
|
809
960
|
sessionId: params.sessionIdForLogs(),
|
|
810
|
-
...(typeof request.toolUseID === "string"
|
|
961
|
+
...(typeof request.toolUseID === "string"
|
|
962
|
+
? { toolCallId: request.toolUseID }
|
|
963
|
+
: {}),
|
|
811
964
|
fields: { dialog_kind: request.dialogKind },
|
|
812
965
|
});
|
|
813
966
|
return { behavior: "cancelled" };
|
|
814
967
|
}
|
|
815
968
|
const payload = normalizeRefusalFallbackPayload(request.payload);
|
|
816
|
-
const requestId =
|
|
969
|
+
const requestId = options.requestId;
|
|
817
970
|
const dialogRequest = {
|
|
818
971
|
request_id: requestId,
|
|
819
972
|
dialog_kind: REFUSAL_FALLBACK_DIALOG_KIND,
|
|
@@ -827,7 +980,9 @@ export function buildQueryOptions(params) {
|
|
|
827
980
|
outcome: "start",
|
|
828
981
|
sessionId: params.sessionIdForLogs(),
|
|
829
982
|
requestId,
|
|
830
|
-
...(typeof request.toolUseID === "string"
|
|
983
|
+
...(typeof request.toolUseID === "string"
|
|
984
|
+
? { toolCallId: request.toolUseID }
|
|
985
|
+
: {}),
|
|
831
986
|
fields: {
|
|
832
987
|
dialog_kind: request.dialogKind,
|
|
833
988
|
original_model: payload.original_model,
|
|
@@ -849,6 +1004,19 @@ export function buildQueryOptions(params) {
|
|
|
849
1004
|
resolve("cancelled");
|
|
850
1005
|
return;
|
|
851
1006
|
}
|
|
1007
|
+
if (currentSession.pendingUserDialogs.has(requestId)) {
|
|
1008
|
+
bridgeLogger.warn({
|
|
1009
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
1010
|
+
eventName: "user_dialog_request_dropped",
|
|
1011
|
+
message: "duplicate user dialog request dropped",
|
|
1012
|
+
outcome: "dropped",
|
|
1013
|
+
sessionId: params.sessionIdForLogs(),
|
|
1014
|
+
requestId,
|
|
1015
|
+
fields: { reason: "duplicate_request_id" },
|
|
1016
|
+
});
|
|
1017
|
+
resolve("cancelled");
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
852
1020
|
let settled = false;
|
|
853
1021
|
const settle = (value) => {
|
|
854
1022
|
if (settled) {
|
|
@@ -884,7 +1052,9 @@ export function handlePermissionResponse(command) {
|
|
|
884
1052
|
toolCallId: command.tool_call_id,
|
|
885
1053
|
fields: {
|
|
886
1054
|
response_kind: command.outcome.outcome,
|
|
887
|
-
selected_option: command.outcome.outcome === "selected"
|
|
1055
|
+
selected_option: command.outcome.outcome === "selected"
|
|
1056
|
+
? command.outcome.option_id
|
|
1057
|
+
: "cancelled",
|
|
888
1058
|
},
|
|
889
1059
|
});
|
|
890
1060
|
const session = sessionById(command.session_id);
|
|
@@ -1021,8 +1191,11 @@ export function handleQuestionResponse(command) {
|
|
|
1021
1191
|
fields: {
|
|
1022
1192
|
tool_name: resolver.toolName,
|
|
1023
1193
|
response_kind: command.outcome.outcome,
|
|
1024
|
-
selected_option_count: command.outcome.outcome === "answered"
|
|
1025
|
-
|
|
1194
|
+
selected_option_count: command.outcome.outcome === "answered"
|
|
1195
|
+
? command.outcome.selected_option_ids.length
|
|
1196
|
+
: 0,
|
|
1197
|
+
has_annotation: command.outcome.outcome === "answered" &&
|
|
1198
|
+
command.outcome.annotation !== undefined,
|
|
1026
1199
|
},
|
|
1027
1200
|
});
|
|
1028
1201
|
resolver.onOutcome(command.outcome);
|
|
@@ -1037,7 +1210,9 @@ export function handleUserDialogResponse(command) {
|
|
|
1037
1210
|
requestId: command.request_id,
|
|
1038
1211
|
fields: {
|
|
1039
1212
|
response_kind: command.outcome.outcome,
|
|
1040
|
-
selected_option: command.outcome.outcome === "selected"
|
|
1213
|
+
selected_option: command.outcome.outcome === "selected"
|
|
1214
|
+
? command.outcome.option_id
|
|
1215
|
+
: "cancelled",
|
|
1041
1216
|
},
|
|
1042
1217
|
});
|
|
1043
1218
|
const session = sessionById(command.session_id);
|
|
@@ -1069,7 +1244,9 @@ export function handleUserDialogResponse(command) {
|
|
|
1069
1244
|
return;
|
|
1070
1245
|
}
|
|
1071
1246
|
session.pendingUserDialogs.delete(command.request_id);
|
|
1072
|
-
const choice = command.outcome.outcome === "selected"
|
|
1247
|
+
const choice = command.outcome.outcome === "selected"
|
|
1248
|
+
? command.outcome.option_id
|
|
1249
|
+
: "cancelled";
|
|
1073
1250
|
bridgeLogger.info({
|
|
1074
1251
|
target: LOG_TARGETS.BRIDGE_PERMISSION,
|
|
1075
1252
|
eventName: "user_dialog_response_applied",
|
|
@@ -1136,9 +1313,11 @@ export function handleElicitationResponse(command) {
|
|
|
1136
1313
|
});
|
|
1137
1314
|
pending.resolve({
|
|
1138
1315
|
action: command.action,
|
|
1139
|
-
...(normalizeSdkElicitationContent(command.content)
|
|
1140
|
-
|
|
1141
|
-
|
|
1316
|
+
...(normalizeSdkElicitationContent(command.content)
|
|
1317
|
+
? {
|
|
1318
|
+
content: normalizeSdkElicitationContent(command.content),
|
|
1319
|
+
}
|
|
1320
|
+
: {}),
|
|
1142
1321
|
});
|
|
1143
1322
|
}
|
|
1144
1323
|
export function shouldInvalidateResolvedRuntimeModel(previousRequestedId, previousSessionModel, nextRequestedId) {
|