claude-code-rust 0.12.3 → 0.12.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/agent-sdk/dist/bridge/commands.js +20 -0
- package/agent-sdk/dist/bridge/events.js +15 -1
- package/agent-sdk/dist/bridge/logger.js +10 -0
- package/agent-sdk/dist/bridge/message_handlers.js +172 -1
- package/agent-sdk/dist/bridge/session_lifecycle.js +37 -0
- package/agent-sdk/dist/bridge/state_parsing.js +9 -0
- package/agent-sdk/dist/bridge/tooling.js +55 -8
- package/agent-sdk/dist/bridge.js +409 -1
- package/agent-sdk/dist/bridge.test.js +622 -5
- package/package.json +4 -4
|
@@ -95,6 +95,13 @@ function expectEffortLevel(record, key, context) {
|
|
|
95
95
|
}
|
|
96
96
|
return value;
|
|
97
97
|
}
|
|
98
|
+
function expectRewindRestoreMode(record, key, context) {
|
|
99
|
+
const value = expectString(record, key, context);
|
|
100
|
+
if (value === "both" || value === "conversation" || value === "code") {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`${context}.${key} must be one of both, conversation, code`);
|
|
104
|
+
}
|
|
98
105
|
function expectNonEmptyStringOrNull(record, key, context) {
|
|
99
106
|
const value = record[key];
|
|
100
107
|
if (value === null) {
|
|
@@ -281,6 +288,19 @@ export function parseCommandEnvelope(line) {
|
|
|
281
288
|
command: "get_context_usage",
|
|
282
289
|
session_id: expectString(raw, "session_id", "get_context_usage"),
|
|
283
290
|
};
|
|
291
|
+
case "get_rewind_targets":
|
|
292
|
+
return {
|
|
293
|
+
command: "get_rewind_targets",
|
|
294
|
+
session_id: expectString(raw, "session_id", "get_rewind_targets"),
|
|
295
|
+
};
|
|
296
|
+
case "rewind":
|
|
297
|
+
return {
|
|
298
|
+
command: "rewind",
|
|
299
|
+
session_id: expectString(raw, "session_id", "rewind"),
|
|
300
|
+
target_user_message_id: expectString(raw, "target_user_message_id", "rewind"),
|
|
301
|
+
restore_mode: expectRewindRestoreMode(raw, "restore_mode", "rewind"),
|
|
302
|
+
launch_settings: optionalLaunchSettings(raw, "launch_settings", "rewind"),
|
|
303
|
+
};
|
|
284
304
|
case "reload_plugins":
|
|
285
305
|
return {
|
|
286
306
|
command: "reload_plugins",
|
|
@@ -6,7 +6,9 @@ import { resolveCurrentModel } from "./session_lifecycle.js";
|
|
|
6
6
|
const SESSION_LIST_LIMIT = 50;
|
|
7
7
|
let sessionListingDir;
|
|
8
8
|
export function buildSessionListOptions(dir, limit = SESSION_LIST_LIMIT) {
|
|
9
|
-
return dir
|
|
9
|
+
return dir
|
|
10
|
+
? { dir, includeProgrammatic: true, includeWorktrees: true, limit }
|
|
11
|
+
: { includeProgrammatic: true, limit };
|
|
10
12
|
}
|
|
11
13
|
export function setSessionListingDir(dir) {
|
|
12
14
|
sessionListingDir = dir;
|
|
@@ -121,6 +123,7 @@ function buildConnectBridgeEvent(session, eventName) {
|
|
|
121
123
|
available_models: session.availableModels,
|
|
122
124
|
mode: session.mode ? buildModeState(session, session.mode) : null,
|
|
123
125
|
...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
|
|
126
|
+
...(session.restoredInput !== undefined ? { restored_input: session.restoredInput } : {}),
|
|
124
127
|
}
|
|
125
128
|
: {
|
|
126
129
|
event: "connected",
|
|
@@ -144,6 +147,7 @@ function logConnectEventEmission(session, eventName, requestId) {
|
|
|
144
147
|
history_update_count: session.resumeUpdates?.length ?? 0,
|
|
145
148
|
available_model_count: session.availableModels.length,
|
|
146
149
|
stale_session_count: session.sessionsToCloseAfterConnect?.length ?? 0,
|
|
150
|
+
has_restored_input: session.restoredInput !== undefined,
|
|
147
151
|
},
|
|
148
152
|
});
|
|
149
153
|
}
|
|
@@ -151,10 +155,15 @@ export function emitConnectEvent(session) {
|
|
|
151
155
|
const bridgeEvent = buildConnectBridgeEvent(session, session.connectEvent);
|
|
152
156
|
logConnectEventEmission(session, session.connectEvent, session.connectRequestId);
|
|
153
157
|
writeEvent(bridgeEvent, session.connectRequestId);
|
|
158
|
+
if (session.pendingRewindResult) {
|
|
159
|
+
writeEvent({ ...session.pendingRewindResult, session_id: session.sessionId }, session.connectRequestId);
|
|
160
|
+
session.pendingRewindResult = undefined;
|
|
161
|
+
}
|
|
154
162
|
session.connectRequestId = undefined;
|
|
155
163
|
session.connected = true;
|
|
156
164
|
session.authHintSent = false;
|
|
157
165
|
session.resumeUpdates = undefined;
|
|
166
|
+
session.restoredInput = undefined;
|
|
158
167
|
const staleSessions = session.sessionsToCloseAfterConnect;
|
|
159
168
|
session.sessionsToCloseAfterConnect = undefined;
|
|
160
169
|
if (!staleSessions || staleSessions.length === 0) {
|
|
@@ -180,7 +189,12 @@ export function emitSessionReplacedEvent(session, requestId) {
|
|
|
180
189
|
const bridgeEvent = buildConnectBridgeEvent(session, "session_replaced");
|
|
181
190
|
logConnectEventEmission(session, "session_replaced", requestId);
|
|
182
191
|
writeEvent(bridgeEvent, requestId);
|
|
192
|
+
if (session.pendingRewindResult) {
|
|
193
|
+
writeEvent({ ...session.pendingRewindResult, session_id: session.sessionId }, requestId);
|
|
194
|
+
session.pendingRewindResult = undefined;
|
|
195
|
+
}
|
|
183
196
|
session.resumeUpdates = undefined;
|
|
197
|
+
session.restoredInput = undefined;
|
|
184
198
|
refreshSessionsList();
|
|
185
199
|
}
|
|
186
200
|
export async function emitSessionsList(requestId) {
|
|
@@ -71,6 +71,10 @@ function commandSessionId(command) {
|
|
|
71
71
|
case "question_response":
|
|
72
72
|
case "elicitation_response":
|
|
73
73
|
case "get_status_snapshot":
|
|
74
|
+
case "get_context_usage":
|
|
75
|
+
case "get_rewind_targets":
|
|
76
|
+
case "rewind":
|
|
77
|
+
case "reload_plugins":
|
|
74
78
|
case "mcp_status":
|
|
75
79
|
case "mcp_reconnect":
|
|
76
80
|
case "mcp_toggle":
|
|
@@ -105,6 +109,8 @@ function commandToolCallId(command) {
|
|
|
105
109
|
case "elicitation_response":
|
|
106
110
|
case "get_status_snapshot":
|
|
107
111
|
case "get_context_usage":
|
|
112
|
+
case "get_rewind_targets":
|
|
113
|
+
case "rewind":
|
|
108
114
|
case "reload_plugins":
|
|
109
115
|
case "mcp_status":
|
|
110
116
|
case "mcp_reconnect":
|
|
@@ -141,6 +147,8 @@ function eventToolCallId(event) {
|
|
|
141
147
|
case "sessions_listed":
|
|
142
148
|
case "status_snapshot":
|
|
143
149
|
case "context_usage":
|
|
150
|
+
case "rewind_targets":
|
|
151
|
+
case "rewind_result":
|
|
144
152
|
case "mcp_snapshot":
|
|
145
153
|
return undefined;
|
|
146
154
|
}
|
|
@@ -183,6 +191,8 @@ function protocolEventLevel(event) {
|
|
|
183
191
|
case "sessions_listed":
|
|
184
192
|
case "status_snapshot":
|
|
185
193
|
case "context_usage":
|
|
194
|
+
case "rewind_targets":
|
|
195
|
+
case "rewind_result":
|
|
186
196
|
case "runtime_reload_completed":
|
|
187
197
|
case "mcp_set_servers_result":
|
|
188
198
|
case "mcp_snapshot":
|
|
@@ -90,6 +90,162 @@ function emitSystemNoticeUpdate(session, severity, message) {
|
|
|
90
90
|
}
|
|
91
91
|
emitSessionUpdate(session.sessionId, { type: "system_notice_update", severity, message: trimmed });
|
|
92
92
|
}
|
|
93
|
+
const MAX_INFORMATIONAL_DEDUP_KEYS = 256;
|
|
94
|
+
function shouldEmitInformationalMessage(session, level, content, toolUseId) {
|
|
95
|
+
if (!toolUseId) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
const key = `${toolUseId}\u0000${level}\u0000${content}`;
|
|
99
|
+
if (session.informationalDedupKeys.has(key)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
session.informationalDedupKeys.add(key);
|
|
103
|
+
while (session.informationalDedupKeys.size > MAX_INFORMATIONAL_DEDUP_KEYS) {
|
|
104
|
+
const first = session.informationalDedupKeys.values().next().value;
|
|
105
|
+
if (typeof first !== "string") {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
session.informationalDedupKeys.delete(first);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function handleInformationalSystemMessage(session, msg) {
|
|
113
|
+
const content = typeof msg.content === "string" ? msg.content.trim() : "";
|
|
114
|
+
if (!content) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const level = typeof msg.level === "string" ? msg.level : "info";
|
|
118
|
+
const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
|
|
119
|
+
if (!shouldEmitInformationalMessage(session, level, content, toolUseId)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
switch (level) {
|
|
123
|
+
case "notice":
|
|
124
|
+
emitSystemNoticeUpdate(session, "info", content);
|
|
125
|
+
return;
|
|
126
|
+
case "suggestion":
|
|
127
|
+
emitSystemNoticeUpdate(session, "info", `Suggestion: ${content}`);
|
|
128
|
+
return;
|
|
129
|
+
case "warning":
|
|
130
|
+
emitSystemNoticeUpdate(session, "warning", content);
|
|
131
|
+
return;
|
|
132
|
+
case "info":
|
|
133
|
+
if (msg.prevent_continuation === true) {
|
|
134
|
+
emitSystemNoticeUpdate(session, "warning", content);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
default:
|
|
138
|
+
bridgeLogger.debug({
|
|
139
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
140
|
+
eventName: "sdk_informational_level_unhandled",
|
|
141
|
+
message: "SDK informational message ignored for unknown level",
|
|
142
|
+
outcome: "ignored",
|
|
143
|
+
sessionId: session.sessionId,
|
|
144
|
+
toolCallId: toolUseId || undefined,
|
|
145
|
+
fields: {
|
|
146
|
+
informational_level: level,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function trimmedStringField(msg, field) {
|
|
152
|
+
const value = msg[field];
|
|
153
|
+
if (typeof value !== "string") {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const trimmed = value.trim();
|
|
157
|
+
return trimmed ? trimmed : undefined;
|
|
158
|
+
}
|
|
159
|
+
function ensureSentencePunctuation(value) {
|
|
160
|
+
const trimmed = value.trim();
|
|
161
|
+
if (!trimmed) {
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
165
|
+
}
|
|
166
|
+
function modelRefusalNoFallbackMessage(msg) {
|
|
167
|
+
const model = trimmedStringField(msg, "original_model") ?? "the selected model";
|
|
168
|
+
const base = `Could not continue with ${model}: model refused the request and no fallback model is configured.`;
|
|
169
|
+
const explanation = trimmedStringField(msg, "api_refusal_explanation");
|
|
170
|
+
const category = trimmedStringField(msg, "api_refusal_category");
|
|
171
|
+
const content = trimmedStringField(msg, "content");
|
|
172
|
+
const detail = explanation
|
|
173
|
+
? `Reason: ${explanation}`
|
|
174
|
+
: category
|
|
175
|
+
? `Refusal category: ${category}`
|
|
176
|
+
: content;
|
|
177
|
+
const detailSentence = detail ? ensureSentencePunctuation(detail) : "";
|
|
178
|
+
return detailSentence ? `${base} ${detailSentence}` : base;
|
|
179
|
+
}
|
|
180
|
+
function handleModelRefusalNoFallbackMessage(session, msg) {
|
|
181
|
+
const message = modelRefusalNoFallbackMessage(msg);
|
|
182
|
+
emitSystemNoticeUpdate(session, "warning", message);
|
|
183
|
+
bridgeLogger.info({
|
|
184
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
185
|
+
eventName: "sdk_model_refusal_no_fallback_received",
|
|
186
|
+
message: "SDK model refusal without fallback received",
|
|
187
|
+
outcome: "success",
|
|
188
|
+
sessionId: session.sessionId,
|
|
189
|
+
requestId: trimmedStringField(msg, "request_id"),
|
|
190
|
+
fields: {
|
|
191
|
+
original_model: trimmedStringField(msg, "original_model"),
|
|
192
|
+
api_refusal_category: trimmedStringField(msg, "api_refusal_category"),
|
|
193
|
+
refused_user_message_uuid: trimmedStringField(msg, "refused_user_message_uuid"),
|
|
194
|
+
sdk_message_uuid: trimmedStringField(msg, "uuid"),
|
|
195
|
+
sdk_message_session_id: trimmedStringField(msg, "session_id"),
|
|
196
|
+
has_api_refusal_explanation: trimmedStringField(msg, "api_refusal_explanation") !== undefined,
|
|
197
|
+
has_content: trimmedStringField(msg, "content") !== undefined,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function workerShutdownMessage(reason) {
|
|
202
|
+
const trimmed = reason.trim();
|
|
203
|
+
return trimmed ? `Claude worker is shutting down: ${trimmed}` : "Claude worker is shutting down.";
|
|
204
|
+
}
|
|
205
|
+
function handleWorkerShuttingDownSystemMessage(session, msg) {
|
|
206
|
+
const reason = typeof msg.reason === "string" ? msg.reason.trim() : "";
|
|
207
|
+
if (!session.connected) {
|
|
208
|
+
bridgeLogger.debug({
|
|
209
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
210
|
+
eventName: "sdk_worker_shutdown_preconnect_ignored",
|
|
211
|
+
message: "SDK worker shutdown ignored before session connect",
|
|
212
|
+
outcome: "ignored",
|
|
213
|
+
sessionId: session.sessionId,
|
|
214
|
+
fields: {
|
|
215
|
+
reason: reason || undefined,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
session.pendingWorkerShutdown = { reason };
|
|
221
|
+
}
|
|
222
|
+
function cancelPendingWorkerShutdown(session) {
|
|
223
|
+
if (!session.pendingWorkerShutdown) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
bridgeLogger.debug({
|
|
227
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
228
|
+
eventName: "sdk_worker_shutdown_cancelled",
|
|
229
|
+
message: "SDK worker shutdown ignored after later stream activity",
|
|
230
|
+
outcome: "ignored",
|
|
231
|
+
sessionId: session.sessionId,
|
|
232
|
+
fields: {
|
|
233
|
+
reason: session.pendingWorkerShutdown.reason || undefined,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
session.pendingWorkerShutdown = undefined;
|
|
237
|
+
}
|
|
238
|
+
export function flushPendingWorkerShutdown(session) {
|
|
239
|
+
const pending = session.pendingWorkerShutdown;
|
|
240
|
+
if (!pending) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
session.pendingWorkerShutdown = undefined;
|
|
244
|
+
if (!session.connected) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
emitSystemNoticeUpdate(session, "warning", workerShutdownMessage(pending.reason));
|
|
248
|
+
}
|
|
93
249
|
function notificationSeverity(priority) {
|
|
94
250
|
return priority === "high" || priority === "immediate" ? "warning" : "info";
|
|
95
251
|
}
|
|
@@ -675,12 +831,19 @@ function terminalReasonFromValue(value) {
|
|
|
675
831
|
export function handleSdkMessage(session, message) {
|
|
676
832
|
const msg = message;
|
|
677
833
|
const type = typeof msg.type === "string" ? msg.type : "";
|
|
834
|
+
const subtype = type === "system" && typeof msg.subtype === "string" ? msg.subtype : "";
|
|
835
|
+
if (subtype !== "worker_shutting_down") {
|
|
836
|
+
cancelPendingWorkerShutdown(session);
|
|
837
|
+
}
|
|
678
838
|
logSdkMessageOrigin(session, msg);
|
|
679
839
|
if (type === "system") {
|
|
680
|
-
const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
|
|
681
840
|
if (handleFallbackRetractionMessage(session, subtype, msg)) {
|
|
682
841
|
return;
|
|
683
842
|
}
|
|
843
|
+
if (subtype === "model_refusal_no_fallback") {
|
|
844
|
+
handleModelRefusalNoFallbackMessage(session, msg);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
684
847
|
if (subtype === "commands_changed") {
|
|
685
848
|
updateAvailableCommands(session, "commands_changed", mapSdkSlashCommands(msg.commands));
|
|
686
849
|
return;
|
|
@@ -690,6 +853,14 @@ export function handleSdkMessage(session, message) {
|
|
|
690
853
|
emitSystemNoticeUpdate(session, notificationSeverity(msg.priority), text);
|
|
691
854
|
return;
|
|
692
855
|
}
|
|
856
|
+
if (subtype === "informational") {
|
|
857
|
+
handleInformationalSystemMessage(session, msg);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (subtype === "worker_shutting_down") {
|
|
861
|
+
handleWorkerShuttingDownSystemMessage(session, msg);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
693
864
|
if (subtype === "mirror_error") {
|
|
694
865
|
const error = typeof msg.error === "string" ? msg.error : "";
|
|
695
866
|
const key = asRecordOrNull(msg.key);
|
|
@@ -162,6 +162,23 @@ export async function closeSessionWithLogging(session, options = {}) {
|
|
|
162
162
|
fields: { reason: options.reason ?? "unspecified" },
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
+
export async function closeSessionsBeforeRegister(replacementSession, staleSessions, requestId) {
|
|
166
|
+
if (!staleSessions || staleSessions.length === 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const stale of staleSessions) {
|
|
170
|
+
if (stale === replacementSession) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (sessions.get(stale.sessionId) === stale) {
|
|
174
|
+
sessions.delete(stale.sessionId);
|
|
175
|
+
}
|
|
176
|
+
await closeSessionWithLogging(stale, {
|
|
177
|
+
reason: "stale_before_register",
|
|
178
|
+
requestId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
165
182
|
export async function closeAllSessions(options = {}) {
|
|
166
183
|
const active = Array.from(sessions.values());
|
|
167
184
|
sessions.clear();
|
|
@@ -187,6 +204,7 @@ export async function createSession(params) {
|
|
|
187
204
|
const supportsBypassPermissionsMode = startupPermissionModeOptions(params.launchSettings).allowDangerouslySkipPermissions === true;
|
|
188
205
|
const historyUpdateCount = params.resumeUpdates?.length ?? 0;
|
|
189
206
|
const staleSessionCount = params.sessionsToCloseAfterConnect?.length ?? 0;
|
|
207
|
+
const staleSessionBeforeRegisterCount = params.sessionsToCloseBeforeRegister?.length ?? 0;
|
|
190
208
|
let session;
|
|
191
209
|
const sessionIdForLogs = () => session?.sessionId ?? provisionalSessionId;
|
|
192
210
|
const canUseTool = async (toolName, inputData, options) => {
|
|
@@ -253,8 +271,10 @@ export async function createSession(params) {
|
|
|
253
271
|
cwd: params.cwd,
|
|
254
272
|
connect_event: params.connectEvent,
|
|
255
273
|
resume_requested: params.resume !== undefined,
|
|
274
|
+
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
256
275
|
history_update_count: historyUpdateCount,
|
|
257
276
|
stale_session_count: staleSessionCount,
|
|
277
|
+
stale_session_before_register_count: staleSessionBeforeRegisterCount,
|
|
258
278
|
},
|
|
259
279
|
});
|
|
260
280
|
try {
|
|
@@ -263,6 +283,7 @@ export async function createSession(params) {
|
|
|
263
283
|
options: buildQueryOptions({
|
|
264
284
|
cwd: params.cwd,
|
|
265
285
|
resume: params.resume,
|
|
286
|
+
resumeSessionAt: params.resumeSessionAt,
|
|
266
287
|
launchSettings: params.launchSettings,
|
|
267
288
|
provisionalSessionId,
|
|
268
289
|
input,
|
|
@@ -287,6 +308,7 @@ export async function createSession(params) {
|
|
|
287
308
|
fields: {
|
|
288
309
|
cwd: params.cwd,
|
|
289
310
|
resume_requested: params.resume !== undefined,
|
|
311
|
+
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
290
312
|
error_message: message,
|
|
291
313
|
},
|
|
292
314
|
});
|
|
@@ -319,12 +341,17 @@ export async function createSession(params) {
|
|
|
319
341
|
pendingQuestions: new Map(),
|
|
320
342
|
pendingUserDialogs: new Map(),
|
|
321
343
|
pendingElicitations: new Map(),
|
|
344
|
+
informationalDedupKeys: new Set(),
|
|
322
345
|
mcpStatusRevalidatedAt: new Map(),
|
|
323
346
|
hiddenToolUseIds: new Set(),
|
|
324
347
|
authHintSent: false,
|
|
325
348
|
...(params.resumeUpdates && params.resumeUpdates.length > 0
|
|
326
349
|
? { resumeUpdates: params.resumeUpdates }
|
|
327
350
|
: {}),
|
|
351
|
+
...(params.restoredInput !== undefined ? { restoredInput: params.restoredInput } : {}),
|
|
352
|
+
...(params.pendingRewindResult !== undefined
|
|
353
|
+
? { pendingRewindResult: params.pendingRewindResult }
|
|
354
|
+
: {}),
|
|
328
355
|
...(params.sessionsToCloseAfterConnect
|
|
329
356
|
? { sessionsToCloseAfterConnect: params.sessionsToCloseAfterConnect }
|
|
330
357
|
: {}),
|
|
@@ -332,6 +359,7 @@ export async function createSession(params) {
|
|
|
332
359
|
refreshCurrentModel(session);
|
|
333
360
|
const { refreshSupportedModesForSession } = await import("./commands.js");
|
|
334
361
|
refreshSupportedModesForSession(session);
|
|
362
|
+
await closeSessionsBeforeRegister(session, params.sessionsToCloseBeforeRegister, params.requestId);
|
|
335
363
|
sessions.set(provisionalSessionId, session);
|
|
336
364
|
bridgeLogger.info({
|
|
337
365
|
target: LOG_TARGETS.APP_SESSION,
|
|
@@ -344,6 +372,7 @@ export async function createSession(params) {
|
|
|
344
372
|
cwd: session.cwd,
|
|
345
373
|
connect_event: session.connectEvent,
|
|
346
374
|
resume_requested: params.resume !== undefined,
|
|
375
|
+
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
347
376
|
},
|
|
348
377
|
});
|
|
349
378
|
bridgeLogger.info({
|
|
@@ -430,6 +459,11 @@ export async function createSession(params) {
|
|
|
430
459
|
const { handleSdkMessage } = await import("./message_handlers.js");
|
|
431
460
|
handleSdkMessage(session, message);
|
|
432
461
|
}
|
|
462
|
+
{
|
|
463
|
+
// Lazy import to break circular dependency at module-evaluation time.
|
|
464
|
+
const { flushPendingWorkerShutdown } = await import("./message_handlers.js");
|
|
465
|
+
flushPendingWorkerShutdown(session);
|
|
466
|
+
}
|
|
433
467
|
if (!session.connected) {
|
|
434
468
|
bridgeLogger.error({
|
|
435
469
|
target: LOG_TARGETS.APP_SESSION,
|
|
@@ -456,6 +490,7 @@ export async function createSession(params) {
|
|
|
456
490
|
failConnection(`agent stream failed: ${message}`, params.requestId);
|
|
457
491
|
}
|
|
458
492
|
})();
|
|
493
|
+
return session;
|
|
459
494
|
}
|
|
460
495
|
function logSdkProcessSpawnStarted(options, includeArgsPreview) {
|
|
461
496
|
bridgeLogger.info({
|
|
@@ -570,6 +605,7 @@ export function buildQueryOptions(params) {
|
|
|
570
605
|
cwd: params.cwd,
|
|
571
606
|
includePartialMessages: true,
|
|
572
607
|
promptSuggestions: true,
|
|
608
|
+
enableFileCheckpointing: true,
|
|
573
609
|
executable: "node",
|
|
574
610
|
...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
|
|
575
611
|
...(settings ? { settings } : {}),
|
|
@@ -621,6 +657,7 @@ export function buildQueryOptions(params) {
|
|
|
621
657
|
// --setting-sources argument.
|
|
622
658
|
settingSources: DEFAULT_SETTING_SOURCES,
|
|
623
659
|
resume: params.resume,
|
|
660
|
+
...(params.resumeSessionAt ? { resumeSessionAt: params.resumeSessionAt } : {}),
|
|
624
661
|
canUseTool: params.canUseTool,
|
|
625
662
|
onElicitation: async (request) => {
|
|
626
663
|
const requestId = randomUUID();
|
|
@@ -62,6 +62,9 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
62
62
|
type: "rate_limit_update",
|
|
63
63
|
status,
|
|
64
64
|
};
|
|
65
|
+
if (info.errorCode === "credits_required") {
|
|
66
|
+
update.error_code = "credits_required";
|
|
67
|
+
}
|
|
65
68
|
const resetsAt = numberField(info, "resetsAt");
|
|
66
69
|
if (resetsAt !== undefined) {
|
|
67
70
|
update.resets_at = resetsAt;
|
|
@@ -94,6 +97,12 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
94
97
|
if (surpassedThreshold !== undefined) {
|
|
95
98
|
update.surpassed_threshold = surpassedThreshold;
|
|
96
99
|
}
|
|
100
|
+
if (typeof info.canUserPurchaseCredits === "boolean") {
|
|
101
|
+
update.can_user_purchase_credits = info.canUserPurchaseCredits;
|
|
102
|
+
}
|
|
103
|
+
if (typeof info.hasChargeableSavedPaymentMethod === "boolean") {
|
|
104
|
+
update.has_chargeable_saved_payment_method = info.hasChargeableSavedPaymentMethod;
|
|
105
|
+
}
|
|
97
106
|
return update;
|
|
98
107
|
}
|
|
99
108
|
export function buildApiRetryUpdate(message) {
|
|
@@ -23,6 +23,8 @@ const WORKFLOW_TOOL_NAME = "Workflow";
|
|
|
23
23
|
const PROJECTS_TOOL_NAME = "Projects";
|
|
24
24
|
const ARTIFACT_TOOL_NAME = "Artifact";
|
|
25
25
|
const SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME = "ShowOnboardingRolePicker";
|
|
26
|
+
const READ_MCP_RESOURCE_TOOL_NAME = "ReadMcpResource";
|
|
27
|
+
const READ_MCP_RESOURCE_DIR_TOOL_NAME = "ReadMcpResourceDir";
|
|
26
28
|
const SEARCH_OUTPUT_MODES = new Set(["content", "files_with_matches", "count"]);
|
|
27
29
|
function isCronToolName(name) {
|
|
28
30
|
return CRON_TOOL_NAMES.has(name);
|
|
@@ -53,6 +55,9 @@ function isAgentLikeToolName(name) {
|
|
|
53
55
|
export function isShellToolName(name) {
|
|
54
56
|
return name === "Bash" || name === "PowerShell";
|
|
55
57
|
}
|
|
58
|
+
function isMcpResourceReadToolName(name) {
|
|
59
|
+
return name === READ_MCP_RESOURCE_TOOL_NAME || name === READ_MCP_RESOURCE_DIR_TOOL_NAME;
|
|
60
|
+
}
|
|
56
61
|
function agentInputTitle(name, input) {
|
|
57
62
|
if (!isAgentLikeToolName(name)) {
|
|
58
63
|
return undefined;
|
|
@@ -143,7 +148,8 @@ export function normalizeToolKind(name) {
|
|
|
143
148
|
}
|
|
144
149
|
switch (name) {
|
|
145
150
|
case "Read":
|
|
146
|
-
case
|
|
151
|
+
case READ_MCP_RESOURCE_TOOL_NAME:
|
|
152
|
+
case READ_MCP_RESOURCE_DIR_TOOL_NAME:
|
|
147
153
|
return "read";
|
|
148
154
|
case "Write":
|
|
149
155
|
case "Edit":
|
|
@@ -268,14 +274,14 @@ export function toolTitle(name, input, context = {}) {
|
|
|
268
274
|
if ((name === "Read" || name === "Write" || name === "Edit") && typeof input.file_path === "string") {
|
|
269
275
|
return `${name} ${input.file_path}`;
|
|
270
276
|
}
|
|
271
|
-
if (name
|
|
277
|
+
if (isMcpResourceReadToolName(name)) {
|
|
272
278
|
const uri = typeof input.uri === "string" ? input.uri : "";
|
|
273
279
|
const server = typeof input.server === "string" ? input.server : "";
|
|
274
280
|
if (server && uri) {
|
|
275
|
-
return
|
|
281
|
+
return `${name} ${server} ${uri}`;
|
|
276
282
|
}
|
|
277
283
|
if (uri) {
|
|
278
|
-
return
|
|
284
|
+
return `${name} ${uri}`;
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
return name;
|
|
@@ -440,6 +446,37 @@ function mcpResourceContentFromResult(rawResult, rawContent) {
|
|
|
440
446
|
}
|
|
441
447
|
return [];
|
|
442
448
|
}
|
|
449
|
+
function mcpResourceDirTextFromResult(toolName, rawResult, rawContent) {
|
|
450
|
+
if (toolName !== READ_MCP_RESOURCE_DIR_TOOL_NAME) {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
for (const candidate of collectResultCandidates(rawResult, rawContent)) {
|
|
454
|
+
if (!Array.isArray(candidate.resources)) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const lines = [];
|
|
458
|
+
for (const entry of candidate.resources) {
|
|
459
|
+
const record = asRecordOrNull(entry);
|
|
460
|
+
if (!record) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const name = nonEmptyString(record.name);
|
|
464
|
+
const uri = nonEmptyString(record.uri);
|
|
465
|
+
if (!name || !uri) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const mimeType = nonEmptyString(record.mimeType);
|
|
469
|
+
const suffix = mimeType
|
|
470
|
+
? mimeType === "inode/directory"
|
|
471
|
+
? " (directory)"
|
|
472
|
+
: ` (${mimeType})`
|
|
473
|
+
: "";
|
|
474
|
+
lines.push(`${name} - ${uri}${suffix}`);
|
|
475
|
+
}
|
|
476
|
+
return lines.length > 0 ? lines.join("\n") : "No resources found.";
|
|
477
|
+
}
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
443
480
|
function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
444
481
|
const candidates = collectResultCandidates(rawResult, rawContent);
|
|
445
482
|
const metadata = {};
|
|
@@ -1604,8 +1641,8 @@ function enterPlanModeStructuredOutputHandled(toolName, rawResult, rawContent) {
|
|
|
1604
1641
|
}
|
|
1605
1642
|
return false;
|
|
1606
1643
|
}
|
|
1607
|
-
function
|
|
1608
|
-
if (toolName
|
|
1644
|
+
function mcpResourceReadErrorText(toolName, rawResult, rawContent) {
|
|
1645
|
+
if (!isMcpResourceReadToolName(toolName)) {
|
|
1609
1646
|
return undefined;
|
|
1610
1647
|
}
|
|
1611
1648
|
for (const candidate of collectResultCandidates(rawResult, rawContent)) {
|
|
@@ -1675,13 +1712,23 @@ export function buildToolResultFields(isError, rawContent, base, rawResult, _con
|
|
|
1675
1712
|
if (agentTitle) {
|
|
1676
1713
|
fields.title = agentTitle;
|
|
1677
1714
|
}
|
|
1678
|
-
const readMcpResourceError =
|
|
1715
|
+
const readMcpResourceError = mcpResourceReadErrorText(toolName, rawResult, rawContent);
|
|
1679
1716
|
if (readMcpResourceError) {
|
|
1680
1717
|
fields.status = "failed";
|
|
1681
1718
|
fields.raw_output = readMcpResourceError;
|
|
1682
1719
|
fields.content = [{ type: "content", content: { type: "text", text: readMcpResourceError } }];
|
|
1683
1720
|
return fields;
|
|
1684
1721
|
}
|
|
1722
|
+
const readMcpResourceDirOutput = !isError
|
|
1723
|
+
? mcpResourceDirTextFromResult(toolName, rawResult, rawContent)
|
|
1724
|
+
: undefined;
|
|
1725
|
+
if (readMcpResourceDirOutput !== undefined) {
|
|
1726
|
+
fields.raw_output = readMcpResourceDirOutput;
|
|
1727
|
+
fields.content = [
|
|
1728
|
+
{ type: "content", content: { type: "text", text: readMcpResourceDirOutput } },
|
|
1729
|
+
];
|
|
1730
|
+
return fields;
|
|
1731
|
+
}
|
|
1685
1732
|
const searchOutput = !isError ? searchResultText(toolName, rawResult, rawContent) : undefined;
|
|
1686
1733
|
if (searchOutput !== undefined) {
|
|
1687
1734
|
fields.raw_output = searchOutput;
|
|
@@ -1826,7 +1873,7 @@ export function buildToolResultFields(isError, rawContent, base, rawResult, _con
|
|
|
1826
1873
|
return fields;
|
|
1827
1874
|
}
|
|
1828
1875
|
}
|
|
1829
|
-
if (!isError && toolName ===
|
|
1876
|
+
if (!isError && toolName === READ_MCP_RESOURCE_TOOL_NAME) {
|
|
1830
1877
|
const structuredResourceContent = mcpResourceContentFromResult(rawResult, rawContent);
|
|
1831
1878
|
if (structuredResourceContent.length > 0) {
|
|
1832
1879
|
fields.content = structuredResourceContent;
|