claude-code-rust 0.14.1 → 0.14.3
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/available_commands.js +1 -1
- package/agent-sdk/dist/bridge/command_interactions.js +16 -0
- package/agent-sdk/dist/bridge/command_lifecycle.js +184 -0
- package/agent-sdk/dist/bridge/command_mcp.js +32 -0
- package/agent-sdk/dist/bridge/command_scheduler.js +136 -0
- package/agent-sdk/dist/bridge/command_session_control.js +269 -0
- package/agent-sdk/dist/bridge/command_session_data.js +203 -0
- package/agent-sdk/dist/bridge/commands.js +6 -0
- package/agent-sdk/dist/bridge/error_classification.js +24 -7
- package/agent-sdk/dist/bridge/events.js +42 -9
- package/agent-sdk/dist/bridge/history.js +7 -3
- package/agent-sdk/dist/bridge/logger.js +2 -0
- package/agent-sdk/dist/bridge/mcp.js +90 -80
- package/agent-sdk/dist/bridge/mcp_auth_adapter.js +67 -0
- package/agent-sdk/dist/bridge/mcp_monitor.js +59 -0
- package/agent-sdk/dist/bridge/message_handlers.js +61 -17
- package/agent-sdk/dist/bridge/session_lifecycle.js +54 -4
- package/agent-sdk/dist/bridge/state_parsing.js +8 -1
- package/agent-sdk/dist/bridge/tool_calls.js +12 -10
- package/agent-sdk/dist/bridge/tooling.js +183 -1
- package/agent-sdk/dist/bridge.js +118 -617
- package/package.json +8 -8
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { emitMcpOperationError, slashError, writeEvent } from "./events.js";
|
|
2
2
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
3
3
|
import { bridgeMcpServersToSdk, mapMcpServerStatus, summarizeMcpServersForDiagnostics, } from "./mcp_metadata.js";
|
|
4
|
+
import { authenticateMcpServer, clearMcpServerAuth, detectMcpAuthCapabilities, submitMcpOAuthCallbackUrl, } from "./mcp_auth_adapter.js";
|
|
5
|
+
import { runMcpAuthMonitor, } from "./mcp_monitor.js";
|
|
4
6
|
export const MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS = 30_000;
|
|
5
|
-
const knownConnectedMcpServers = new Set();
|
|
6
7
|
function logMcpSuccess(eventName, message, sessionId, requestId, fields) {
|
|
7
8
|
bridgeLogger.info({
|
|
8
9
|
target: LOG_TARGETS.BRIDGE_MCP,
|
|
@@ -28,9 +29,6 @@ function logMcpFailure(eventName, message, sessionId, errorMessage, requestId, f
|
|
|
28
29
|
},
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
|
-
function queryWithMcpAuth(session) {
|
|
32
|
-
return session.query;
|
|
33
|
-
}
|
|
34
32
|
function mapMcpSetServersResult(result) {
|
|
35
33
|
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
36
34
|
return { added: [], removed: [], errors: {} };
|
|
@@ -47,41 +45,6 @@ function mapMcpSetServersResult(result) {
|
|
|
47
45
|
: {};
|
|
48
46
|
return { added, removed, errors };
|
|
49
47
|
}
|
|
50
|
-
async function callMcpAuthMethod(session, methodName, args) {
|
|
51
|
-
const query = queryWithMcpAuth(session);
|
|
52
|
-
switch (methodName) {
|
|
53
|
-
case "mcpAuthenticate":
|
|
54
|
-
if (typeof query.mcpAuthenticate !== "function") {
|
|
55
|
-
throw new Error("installed SDK does not support mcpAuthenticate");
|
|
56
|
-
}
|
|
57
|
-
return await query.mcpAuthenticate(args[0] ?? "");
|
|
58
|
-
case "mcpClearAuth":
|
|
59
|
-
if (typeof query.mcpClearAuth !== "function") {
|
|
60
|
-
throw new Error("installed SDK does not support mcpClearAuth");
|
|
61
|
-
}
|
|
62
|
-
return await query.mcpClearAuth(args[0] ?? "");
|
|
63
|
-
case "mcpSubmitOAuthCallbackUrl":
|
|
64
|
-
if (typeof query.mcpSubmitOAuthCallbackUrl !== "function") {
|
|
65
|
-
throw new Error("installed SDK does not support mcpSubmitOAuthCallbackUrl");
|
|
66
|
-
}
|
|
67
|
-
return await query.mcpSubmitOAuthCallbackUrl(args[0] ?? "", args[1] ?? "");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function extractMcpAuthRedirect(serverName, value) {
|
|
71
|
-
if (!value || typeof value !== "object") {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
const authUrl = Reflect.get(value, "authUrl");
|
|
75
|
-
if (typeof authUrl !== "string" || authUrl.trim().length === 0) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const requiresUserAction = Reflect.get(value, "requiresUserAction");
|
|
79
|
-
return {
|
|
80
|
-
server_name: serverName,
|
|
81
|
-
auth_url: authUrl,
|
|
82
|
-
requires_user_action: requiresUserAction === true,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
48
|
function emitMcpCommandError(sessionId, operation, message, requestId, serverName) {
|
|
86
49
|
emitMcpOperationError(sessionId, {
|
|
87
50
|
...(serverName ? { server_name: serverName } : {}),
|
|
@@ -90,9 +53,10 @@ function emitMcpCommandError(sessionId, operation, message, requestId, serverNam
|
|
|
90
53
|
}, requestId);
|
|
91
54
|
}
|
|
92
55
|
export async function emitMcpSnapshotEvent(session, requestId, source = "mcp_status") {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
56
|
+
const mapped = await loadReconciledMcpStatuses(session);
|
|
57
|
+
if (!mapped) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
96
60
|
return emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId);
|
|
97
61
|
}
|
|
98
62
|
export function emitMcpSnapshotFromStatuses(session, servers, source, requestId) {
|
|
@@ -104,7 +68,7 @@ export async function emitReconciledMcpSnapshotFromStatuses(session, servers, so
|
|
|
104
68
|
return emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId);
|
|
105
69
|
}
|
|
106
70
|
function emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId) {
|
|
107
|
-
rememberKnownConnectedMcpServers(mapped);
|
|
71
|
+
rememberKnownConnectedMcpServers(session, mapped);
|
|
108
72
|
logMcpSuccess("mcp_snapshot_emitted", "MCP snapshot emitted", session.sessionId, requestId, {
|
|
109
73
|
source,
|
|
110
74
|
server_count: mapped.length,
|
|
@@ -115,6 +79,7 @@ function emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId) {
|
|
|
115
79
|
session_id: session.sessionId,
|
|
116
80
|
source,
|
|
117
81
|
servers: mapped,
|
|
82
|
+
auth_capabilities: detectMcpAuthCapabilities(session.query),
|
|
118
83
|
}, requestId);
|
|
119
84
|
return mapped;
|
|
120
85
|
}
|
|
@@ -132,23 +97,33 @@ export function staleMcpAuthCandidates(servers, knownConnectedServerNames, lastR
|
|
|
132
97
|
})
|
|
133
98
|
.map((server) => server.name);
|
|
134
99
|
}
|
|
135
|
-
function rememberKnownConnectedMcpServers(servers) {
|
|
100
|
+
function rememberKnownConnectedMcpServers(session, servers) {
|
|
136
101
|
for (const server of servers) {
|
|
137
102
|
if (server.status === "connected") {
|
|
138
|
-
knownConnectedMcpServers.add(server.name);
|
|
103
|
+
session.knownConnectedMcpServers.add(server.name);
|
|
139
104
|
}
|
|
140
105
|
}
|
|
141
106
|
}
|
|
142
|
-
function forgetKnownConnectedMcpServer(serverName) {
|
|
143
|
-
knownConnectedMcpServers.delete(serverName);
|
|
107
|
+
function forgetKnownConnectedMcpServer(session, serverName) {
|
|
108
|
+
session.knownConnectedMcpServers.delete(serverName);
|
|
144
109
|
}
|
|
145
|
-
async function
|
|
146
|
-
const
|
|
110
|
+
async function loadReconciledMcpStatuses(session, isActive = () => true) {
|
|
111
|
+
const servers = await session.query.mcpServerStatus();
|
|
112
|
+
if (!isActive()) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return await reconcileSuspiciousMcpStatuses(session, servers.map(mapMcpServerStatus), isActive);
|
|
116
|
+
}
|
|
117
|
+
async function reconcileSuspiciousMcpStatuses(session, servers, isActive = () => true) {
|
|
118
|
+
const candidates = staleMcpAuthCandidates(servers, session.knownConnectedMcpServers, session.mcpStatusRevalidatedAt);
|
|
147
119
|
if (candidates.length === 0) {
|
|
148
120
|
return servers;
|
|
149
121
|
}
|
|
150
122
|
const now = Date.now();
|
|
151
123
|
for (const serverName of candidates) {
|
|
124
|
+
if (!isActive()) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
152
127
|
session.mcpStatusRevalidatedAt.set(serverName, now);
|
|
153
128
|
bridgeLogger.info({
|
|
154
129
|
target: LOG_TARGETS.BRIDGE_MCP,
|
|
@@ -165,8 +140,14 @@ async function reconcileSuspiciousMcpStatuses(session, servers) {
|
|
|
165
140
|
});
|
|
166
141
|
try {
|
|
167
142
|
await session.query.reconnectMcpServer(serverName);
|
|
143
|
+
if (!isActive()) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
168
146
|
}
|
|
169
147
|
catch (error) {
|
|
148
|
+
if (!isActive()) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
170
151
|
const message = error instanceof Error ? error.message : String(error);
|
|
171
152
|
bridgeLogger.warn({
|
|
172
153
|
target: LOG_TARGETS.BRIDGE_MCP,
|
|
@@ -182,35 +163,67 @@ async function reconcileSuspiciousMcpStatuses(session, servers) {
|
|
|
182
163
|
});
|
|
183
164
|
}
|
|
184
165
|
}
|
|
185
|
-
|
|
166
|
+
const refreshed = await session.query.mcpServerStatus();
|
|
167
|
+
return isActive() ? refreshed.map(mapMcpServerStatus) : undefined;
|
|
186
168
|
}
|
|
187
169
|
function shouldKeepMonitoringMcpAuth(server) {
|
|
188
170
|
return server?.status === "needs-auth" || server?.status === "pending";
|
|
189
171
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
172
|
+
function logMcpAuthMonitorExhausted(session, serverName, result) {
|
|
173
|
+
bridgeLogger.warn({
|
|
174
|
+
target: LOG_TARGETS.BRIDGE_MCP,
|
|
175
|
+
eventName: "mcp_auth_monitor_exhausted",
|
|
176
|
+
message: "MCP authentication status monitor exhausted",
|
|
177
|
+
outcome: "failure",
|
|
178
|
+
sessionId: session.sessionId,
|
|
179
|
+
count: result.attempts,
|
|
180
|
+
fields: {
|
|
181
|
+
server_name: serverName,
|
|
182
|
+
attempts: result.attempts,
|
|
183
|
+
reason: result.reason,
|
|
184
|
+
...(result.lastError ? { error_message: result.lastError } : {}),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
196
187
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const server = servers.find((candidate) => candidate.name === serverName);
|
|
201
|
-
if (attempt < maxAttempts && shouldKeepMonitoringMcpAuth(server)) {
|
|
202
|
-
setTimeout(() => {
|
|
203
|
-
void monitorMcpAuthSnapshot(session, serverName, attempt + 1, maxAttempts, delayMs);
|
|
204
|
-
}, delayMs);
|
|
205
|
-
}
|
|
188
|
+
export function startMcpAuthSnapshotMonitor(session, serverName, timing = {}) {
|
|
189
|
+
if (session.closing) {
|
|
190
|
+
return Promise.resolve({ outcome: "cancelled", attempts: 0 });
|
|
206
191
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
void monitorMcpAuthSnapshot(session, serverName, attempt + 1, maxAttempts, delayMs);
|
|
211
|
-
}, delayMs);
|
|
212
|
-
}
|
|
192
|
+
const existing = session.mcpAuthMonitors.get(serverName);
|
|
193
|
+
if (existing) {
|
|
194
|
+
return existing.task;
|
|
213
195
|
}
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
let monitor;
|
|
198
|
+
const isActive = () => !session.closing &&
|
|
199
|
+
!controller.signal.aborted &&
|
|
200
|
+
session.mcpAuthMonitors.get(serverName) === monitor;
|
|
201
|
+
const task = runMcpAuthMonitor({
|
|
202
|
+
...timing,
|
|
203
|
+
signal: controller.signal,
|
|
204
|
+
poll: async () => {
|
|
205
|
+
const servers = await loadReconciledMcpStatuses(session, isActive);
|
|
206
|
+
if (!isActive() || !servers) {
|
|
207
|
+
return "complete";
|
|
208
|
+
}
|
|
209
|
+
emitMcpSnapshotFromMappedStatuses(session, servers, "mcp_status");
|
|
210
|
+
const server = servers.find((candidate) => candidate.name === serverName);
|
|
211
|
+
return shouldKeepMonitoringMcpAuth(server) ? "continue" : "complete";
|
|
212
|
+
},
|
|
213
|
+
}).then((result) => {
|
|
214
|
+
if (result.outcome === "exhausted") {
|
|
215
|
+
logMcpAuthMonitorExhausted(session, serverName, result);
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
});
|
|
219
|
+
monitor = { controller, task };
|
|
220
|
+
session.mcpAuthMonitors.set(serverName, monitor);
|
|
221
|
+
void task.then(() => {
|
|
222
|
+
if (session.mcpAuthMonitors.get(serverName) === monitor) {
|
|
223
|
+
session.mcpAuthMonitors.delete(serverName);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return task;
|
|
214
227
|
}
|
|
215
228
|
export async function handleMcpStatusCommand(session, requestId, source = "mcp_status") {
|
|
216
229
|
try {
|
|
@@ -224,6 +237,7 @@ export async function handleMcpStatusCommand(session, requestId, source = "mcp_s
|
|
|
224
237
|
session_id: session.sessionId,
|
|
225
238
|
source,
|
|
226
239
|
servers: [],
|
|
240
|
+
auth_capabilities: detectMcpAuthCapabilities(session.query),
|
|
227
241
|
error: message,
|
|
228
242
|
}, requestId);
|
|
229
243
|
}
|
|
@@ -277,8 +291,7 @@ export async function handleMcpSetServersCommand(session, command, requestId) {
|
|
|
277
291
|
}
|
|
278
292
|
export async function handleMcpAuthenticateCommand(session, command, requestId) {
|
|
279
293
|
try {
|
|
280
|
-
const
|
|
281
|
-
const redirect = extractMcpAuthRedirect(command.server_name, result);
|
|
294
|
+
const redirect = await authenticateMcpServer(session.query, command.server_name);
|
|
282
295
|
if (redirect) {
|
|
283
296
|
logMcpSuccess("mcp_auth_redirect_emitted", "MCP auth redirect emitted", command.session_id, requestId, { server_name: command.server_name, requires_user_action: redirect.requires_user_action });
|
|
284
297
|
writeEvent({
|
|
@@ -290,7 +303,7 @@ export async function handleMcpAuthenticateCommand(session, command, requestId)
|
|
|
290
303
|
else {
|
|
291
304
|
logMcpSuccess("mcp_authenticate_completed", "MCP authentication command completed", command.session_id, requestId, { server_name: command.server_name });
|
|
292
305
|
}
|
|
293
|
-
|
|
306
|
+
startMcpAuthSnapshotMonitor(session, command.server_name);
|
|
294
307
|
}
|
|
295
308
|
catch (error) {
|
|
296
309
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -300,8 +313,8 @@ export async function handleMcpAuthenticateCommand(session, command, requestId)
|
|
|
300
313
|
}
|
|
301
314
|
export async function handleMcpClearAuthCommand(session, command, requestId) {
|
|
302
315
|
try {
|
|
303
|
-
await
|
|
304
|
-
forgetKnownConnectedMcpServer(command.server_name);
|
|
316
|
+
await clearMcpServerAuth(session.query, command.server_name);
|
|
317
|
+
forgetKnownConnectedMcpServer(session, command.server_name);
|
|
305
318
|
session.mcpStatusRevalidatedAt.delete(command.server_name);
|
|
306
319
|
logMcpSuccess("mcp_clear_auth_completed", "MCP auth cleared", command.session_id, requestId, {
|
|
307
320
|
server_name: command.server_name,
|
|
@@ -315,10 +328,7 @@ export async function handleMcpClearAuthCommand(session, command, requestId) {
|
|
|
315
328
|
}
|
|
316
329
|
export async function handleMcpOauthCallbackUrlCommand(session, command, requestId) {
|
|
317
330
|
try {
|
|
318
|
-
await
|
|
319
|
-
command.server_name,
|
|
320
|
-
command.callback_url,
|
|
321
|
-
]);
|
|
331
|
+
await submitMcpOAuthCallbackUrl(session.query, command.server_name, command.callback_url);
|
|
322
332
|
logMcpSuccess("mcp_oauth_callback_completed", "MCP OAuth callback URL submitted", command.session_id, requestId, {
|
|
323
333
|
server_name: command.server_name,
|
|
324
334
|
callback_url_chars: command.callback_url.length,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const MCP_AUTH_METHODS = {
|
|
2
|
+
authenticate: "mcpAuthenticate",
|
|
3
|
+
clear_auth: "mcpClearAuth",
|
|
4
|
+
submit_oauth_callback_url: "mcpSubmitOAuthCallbackUrl",
|
|
5
|
+
};
|
|
6
|
+
function runtimeMethod(query, methodName) {
|
|
7
|
+
if ((!query || typeof query !== "object") && typeof query !== "function") {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const method = Reflect.get(query, methodName);
|
|
11
|
+
return typeof method === "function" ? method : undefined;
|
|
12
|
+
}
|
|
13
|
+
function requiredRuntimeMethod(query, methodName) {
|
|
14
|
+
const method = runtimeMethod(query, methodName);
|
|
15
|
+
if (!method) {
|
|
16
|
+
throw new Error(`installed SDK does not support ${methodName}`);
|
|
17
|
+
}
|
|
18
|
+
return method;
|
|
19
|
+
}
|
|
20
|
+
async function invokeRuntimeMethod(query, methodName, args) {
|
|
21
|
+
const method = requiredRuntimeMethod(query, methodName);
|
|
22
|
+
return await Reflect.apply(method, query, args);
|
|
23
|
+
}
|
|
24
|
+
export function detectMcpAuthCapabilities(query) {
|
|
25
|
+
return {
|
|
26
|
+
authenticate: runtimeMethod(query, MCP_AUTH_METHODS.authenticate) !== undefined,
|
|
27
|
+
clear_auth: runtimeMethod(query, MCP_AUTH_METHODS.clear_auth) !== undefined,
|
|
28
|
+
submit_oauth_callback_url: runtimeMethod(query, MCP_AUTH_METHODS.submit_oauth_callback_url) !== undefined,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function parseMcpAuthRedirect(serverName, value) {
|
|
32
|
+
if (value === undefined || value === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
36
|
+
throw new Error("installed SDK returned an invalid mcpAuthenticate response");
|
|
37
|
+
}
|
|
38
|
+
const authUrl = Reflect.get(value, "authUrl");
|
|
39
|
+
if (authUrl === undefined || authUrl === null) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (typeof authUrl !== "string" || authUrl.trim().length === 0) {
|
|
43
|
+
throw new Error("installed SDK returned an invalid mcpAuthenticate authUrl");
|
|
44
|
+
}
|
|
45
|
+
const requiresUserAction = Reflect.get(value, "requiresUserAction");
|
|
46
|
+
if (requiresUserAction !== undefined && typeof requiresUserAction !== "boolean") {
|
|
47
|
+
throw new Error("installed SDK returned an invalid mcpAuthenticate requiresUserAction");
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
server_name: serverName,
|
|
51
|
+
auth_url: authUrl,
|
|
52
|
+
requires_user_action: requiresUserAction === true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export async function authenticateMcpServer(query, serverName) {
|
|
56
|
+
const result = await invokeRuntimeMethod(query, MCP_AUTH_METHODS.authenticate, [serverName]);
|
|
57
|
+
return parseMcpAuthRedirect(serverName, result);
|
|
58
|
+
}
|
|
59
|
+
export async function clearMcpServerAuth(query, serverName) {
|
|
60
|
+
await invokeRuntimeMethod(query, MCP_AUTH_METHODS.clear_auth, [serverName]);
|
|
61
|
+
}
|
|
62
|
+
export async function submitMcpOAuthCallbackUrl(query, serverName, callbackUrl) {
|
|
63
|
+
await invokeRuntimeMethod(query, MCP_AUTH_METHODS.submit_oauth_callback_url, [
|
|
64
|
+
serverName,
|
|
65
|
+
callbackUrl,
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
const DEFAULT_MAX_ATTEMPTS = 24;
|
|
3
|
+
const DEFAULT_INITIAL_DELAY_MS = 1_000;
|
|
4
|
+
const DEFAULT_MAX_DELAY_MS = 10_000;
|
|
5
|
+
async function abortableDelay(delayMs, signal) {
|
|
6
|
+
await delay(delayMs, undefined, { signal, ref: false });
|
|
7
|
+
}
|
|
8
|
+
function errorMessage(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
export async function runMcpAuthMonitor({ signal, poll, maxAttempts = DEFAULT_MAX_ATTEMPTS, initialDelayMs = DEFAULT_INITIAL_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, sleep = abortableDelay, }) {
|
|
12
|
+
let nextDelayMs = initialDelayMs;
|
|
13
|
+
let lastError;
|
|
14
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
15
|
+
try {
|
|
16
|
+
await sleep(nextDelayMs, signal);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (signal.aborted) {
|
|
20
|
+
return { outcome: "cancelled", attempts: attempt - 1 };
|
|
21
|
+
}
|
|
22
|
+
lastError = errorMessage(error);
|
|
23
|
+
if (attempt === maxAttempts) {
|
|
24
|
+
return { outcome: "exhausted", attempts: attempt, reason: "error", lastError };
|
|
25
|
+
}
|
|
26
|
+
nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (signal.aborted) {
|
|
30
|
+
return { outcome: "cancelled", attempts: attempt - 1 };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const result = await poll();
|
|
34
|
+
if (signal.aborted) {
|
|
35
|
+
return { outcome: "cancelled", attempts: attempt };
|
|
36
|
+
}
|
|
37
|
+
if (result === "complete") {
|
|
38
|
+
return { outcome: "completed", attempts: attempt };
|
|
39
|
+
}
|
|
40
|
+
lastError = undefined;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (signal.aborted) {
|
|
44
|
+
return { outcome: "cancelled", attempts: attempt };
|
|
45
|
+
}
|
|
46
|
+
lastError = errorMessage(error);
|
|
47
|
+
}
|
|
48
|
+
if (attempt === maxAttempts) {
|
|
49
|
+
return {
|
|
50
|
+
outcome: "exhausted",
|
|
51
|
+
attempts: attempt,
|
|
52
|
+
reason: lastError ? "error" : "status",
|
|
53
|
+
...(lastError ? { lastError } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
|
|
57
|
+
}
|
|
58
|
+
return { outcome: "exhausted", attempts: 0, reason: "status" };
|
|
59
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { asRecordOrNull } from "./shared.js";
|
|
2
2
|
import { toPermissionMode, buildModeState, refreshSupportedModesForSession } from "./commands.js";
|
|
3
3
|
import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
|
|
4
|
-
import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, } from "./tooling.js";
|
|
4
|
+
import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, parseToolNonExecutionMetadata, } from "./tooling.js";
|
|
5
5
|
import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
|
|
6
6
|
import { applyBackgroundTasksChanged, applyTaskLifecycleState } from "./tasks.js";
|
|
7
7
|
import { linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
|
|
8
|
-
import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
|
|
8
|
+
import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdate, emitFastModeUpdateIfChanged, setFastModeSnapshotIfChanged, } from "./error_classification.js";
|
|
9
9
|
import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
|
|
10
10
|
import { mapInitSlashCommands, mapSdkSlashCommands, updateAvailableCommands, } from "./available_commands.js";
|
|
11
|
-
import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
|
|
11
|
+
import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, nonNegativeIntegerField, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
|
|
12
12
|
import { looksLikeAuthRequired } from "./auth.js";
|
|
13
13
|
import { emitCurrentModelUpdate, refreshCurrentModel, updateSessionId } from "./session_lifecycle.js";
|
|
14
14
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
@@ -576,6 +576,18 @@ function logContentBlockLinkage(session, blockType, toolUseId, toolName, linkage
|
|
|
576
576
|
},
|
|
577
577
|
});
|
|
578
578
|
}
|
|
579
|
+
function resolveToolProgressTarget(session, taskToolUseId, toolUseId, parentToolUseId) {
|
|
580
|
+
if (taskToolUseId && session.toolCalls.has(taskToolUseId)) {
|
|
581
|
+
return { toolUseId: taskToolUseId, source: "task" };
|
|
582
|
+
}
|
|
583
|
+
if (toolUseId && session.toolCalls.has(toolUseId)) {
|
|
584
|
+
return { toolUseId, source: "tool" };
|
|
585
|
+
}
|
|
586
|
+
if (parentToolUseId && session.toolCalls.has(parentToolUseId)) {
|
|
587
|
+
return { toolUseId: parentToolUseId, source: "parent" };
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
579
591
|
function hideToolUse(session, toolUseId) {
|
|
580
592
|
if (toolUseId) {
|
|
581
593
|
session.hiddenToolUseIds.add(toolUseId);
|
|
@@ -744,6 +756,7 @@ export function handleUserToolResultBlocks(session, message) {
|
|
|
744
756
|
return false;
|
|
745
757
|
}
|
|
746
758
|
const content = Array.isArray(messageObject.content) ? messageObject.content : [];
|
|
759
|
+
const nonExecutionByToolUseId = parseToolNonExecutionMetadata(message.tool_result_meta);
|
|
747
760
|
let handled = false;
|
|
748
761
|
for (const block of content) {
|
|
749
762
|
if (!block || typeof block !== "object") {
|
|
@@ -766,13 +779,13 @@ export function handleUserToolResultBlocks(session, message) {
|
|
|
766
779
|
parentToolUseId,
|
|
767
780
|
sourceMessageUuid: sourceMessageUuid(message),
|
|
768
781
|
});
|
|
769
|
-
emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message));
|
|
782
|
+
emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message), nonExecutionByToolUseId.get(toolUseId));
|
|
770
783
|
}
|
|
771
784
|
}
|
|
772
785
|
return handled;
|
|
773
786
|
}
|
|
774
787
|
export function handleResultMessage(session, message) {
|
|
775
|
-
emitFastModeUpdateIfChanged(session, message.fast_mode_state);
|
|
788
|
+
emitFastModeUpdateIfChanged(session, message.fast_mode_state, message.fast_mode_disabled_reason);
|
|
776
789
|
const terminalReason = terminalReasonFromValue(message.terminal_reason);
|
|
777
790
|
const subtype = typeof message.subtype === "string" ? message.subtype : "";
|
|
778
791
|
if (subtype === "success") {
|
|
@@ -979,7 +992,7 @@ export function handleSdkMessage(session, message) {
|
|
|
979
992
|
session.mode = incomingMode;
|
|
980
993
|
}
|
|
981
994
|
refreshSupportedModesForSession(session);
|
|
982
|
-
|
|
995
|
+
const fastModeChanged = setFastModeSnapshotIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
|
|
983
996
|
if (!session.connected) {
|
|
984
997
|
emitConnectEvent(session);
|
|
985
998
|
}
|
|
@@ -996,6 +1009,9 @@ export function handleSdkMessage(session, message) {
|
|
|
996
1009
|
mode: buildModeState(session, incomingMode),
|
|
997
1010
|
});
|
|
998
1011
|
}
|
|
1012
|
+
if (fastModeChanged) {
|
|
1013
|
+
emitFastModeUpdate(session);
|
|
1014
|
+
}
|
|
999
1015
|
}
|
|
1000
1016
|
if (Array.isArray(msg.slash_commands)) {
|
|
1001
1017
|
updateAvailableCommands(session, "init_slash_commands", mapInitSlashCommands(msg.slash_commands));
|
|
@@ -1032,15 +1048,34 @@ export function handleSdkMessage(session, message) {
|
|
|
1032
1048
|
emitSessionUpdate(session.sessionId, { type: "current_mode_update", current_mode_id: mode });
|
|
1033
1049
|
}
|
|
1034
1050
|
if (msg.status === "compacting") {
|
|
1035
|
-
emitSessionUpdate(session.sessionId, { type: "
|
|
1051
|
+
emitSessionUpdate(session.sessionId, { type: "compaction_update", phase: "started" });
|
|
1036
1052
|
}
|
|
1037
1053
|
else if (msg.status === "requesting") {
|
|
1038
1054
|
emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "requesting" });
|
|
1039
1055
|
}
|
|
1040
1056
|
else if (msg.status === null) {
|
|
1057
|
+
if (msg.compact_result === "success") {
|
|
1058
|
+
emitSessionUpdate(session.sessionId, {
|
|
1059
|
+
type: "compaction_update",
|
|
1060
|
+
phase: "finished",
|
|
1061
|
+
result: "success",
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
else if (msg.compact_result === "failed") {
|
|
1065
|
+
const compactError = typeof msg.compact_error === "string" && msg.compact_error.trim().length > 0
|
|
1066
|
+
? msg.compact_error.trim()
|
|
1067
|
+
: undefined;
|
|
1068
|
+
emitSessionUpdate(session.sessionId, {
|
|
1069
|
+
type: "compaction_update",
|
|
1070
|
+
phase: "finished",
|
|
1071
|
+
result: "failed",
|
|
1072
|
+
error_code: compactError === "too_few_groups" ? "too_few_groups" : "unknown",
|
|
1073
|
+
...(compactError ? { error: compactError } : {}),
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1041
1076
|
emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
|
|
1042
1077
|
}
|
|
1043
|
-
emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
|
|
1078
|
+
emitFastModeUpdateIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
|
|
1044
1079
|
return;
|
|
1045
1080
|
}
|
|
1046
1081
|
if (subtype === "compact_boundary") {
|
|
@@ -1049,12 +1084,17 @@ export function handleSdkMessage(session, message) {
|
|
|
1049
1084
|
return;
|
|
1050
1085
|
}
|
|
1051
1086
|
const trigger = compactMetadata.trigger;
|
|
1052
|
-
const preTokens =
|
|
1087
|
+
const preTokens = nonNegativeIntegerField(compactMetadata, "pre_tokens", "preTokens");
|
|
1088
|
+
const postTokens = nonNegativeIntegerField(compactMetadata, "post_tokens", "postTokens");
|
|
1089
|
+
const durationMs = nonNegativeIntegerField(compactMetadata, "duration_ms", "durationMs");
|
|
1053
1090
|
if ((trigger === "manual" || trigger === "auto") && preTokens !== undefined) {
|
|
1054
1091
|
emitSessionUpdate(session.sessionId, {
|
|
1055
|
-
type: "
|
|
1092
|
+
type: "compaction_update",
|
|
1093
|
+
phase: "boundary",
|
|
1056
1094
|
trigger,
|
|
1057
1095
|
pre_tokens: preTokens,
|
|
1096
|
+
...(postTokens !== undefined ? { post_tokens: postTokens } : {}),
|
|
1097
|
+
...(durationMs !== undefined ? { duration_ms: durationMs } : {}),
|
|
1058
1098
|
});
|
|
1059
1099
|
}
|
|
1060
1100
|
return;
|
|
@@ -1137,25 +1177,29 @@ export function handleSdkMessage(session, message) {
|
|
|
1137
1177
|
if (type === "tool_progress") {
|
|
1138
1178
|
const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
|
|
1139
1179
|
const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
|
|
1180
|
+
const parentToolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : "";
|
|
1140
1181
|
const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
|
|
1141
1182
|
const taskToolUseId = taskId ? session.taskToolUseIds.get(taskId) ?? "" : "";
|
|
1142
|
-
|
|
1143
|
-
if (isHiddenToolUse(session, resolvedToolUseId, toolName)) {
|
|
1183
|
+
if (isHiddenToolUse(session, toolUseId, toolName)) {
|
|
1144
1184
|
return;
|
|
1145
1185
|
}
|
|
1186
|
+
const progressTarget = resolveToolProgressTarget(session, taskToolUseId, toolUseId, parentToolUseId);
|
|
1187
|
+
const resolvedToolUseId = progressTarget?.toolUseId ?? "";
|
|
1146
1188
|
bridgeLogger.debug({
|
|
1147
1189
|
target: LOG_TARGETS.APP_TOOL,
|
|
1148
1190
|
eventName: "sdk_tool_progress_linkage_observed",
|
|
1149
1191
|
message: "SDK tool progress linkage observed",
|
|
1150
|
-
outcome:
|
|
1192
|
+
outcome: progressTarget?.source ?? "orphaned",
|
|
1151
1193
|
sessionId: session.sessionId,
|
|
1152
|
-
toolCallId: resolvedToolUseId || undefined,
|
|
1194
|
+
toolCallId: resolvedToolUseId || toolUseId || undefined,
|
|
1153
1195
|
fields: {
|
|
1154
1196
|
tool_name: toolName,
|
|
1155
1197
|
tool_use_id: toolUseId || undefined,
|
|
1156
|
-
parent_tool_use_id:
|
|
1198
|
+
parent_tool_use_id: parentToolUseId || undefined,
|
|
1157
1199
|
task_id: taskId || undefined,
|
|
1158
1200
|
task_resolved_tool_use_id: taskToolUseId || undefined,
|
|
1201
|
+
resolved_tool_use_id: resolvedToolUseId || undefined,
|
|
1202
|
+
correlation_source: progressTarget?.source,
|
|
1159
1203
|
},
|
|
1160
1204
|
});
|
|
1161
1205
|
if (resolvedToolUseId) {
|
|
@@ -1174,7 +1218,7 @@ export function handleSdkMessage(session, message) {
|
|
|
1174
1218
|
const subagentType = typeof msg.subagent_type === "string" && msg.subagent_type.trim()
|
|
1175
1219
|
? msg.subagent_type.trim()
|
|
1176
1220
|
: undefined;
|
|
1177
|
-
emitToolProgressUpdate(session, resolvedToolUseId,
|
|
1221
|
+
emitToolProgressUpdate(session, resolvedToolUseId, {
|
|
1178
1222
|
...(subagentRetry
|
|
1179
1223
|
? { subagentRetry }
|
|
1180
1224
|
: hasSubagentRetry
|
|
@@ -1270,7 +1314,7 @@ export function handleSdkMessage(session, message) {
|
|
|
1270
1314
|
return;
|
|
1271
1315
|
}
|
|
1272
1316
|
const parsed = unwrapToolUseResult(rawToolUseResult);
|
|
1273
|
-
emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg));
|
|
1317
|
+
emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg), parseToolNonExecutionMetadata(msg.tool_result_meta).get(toolUseId));
|
|
1274
1318
|
}
|
|
1275
1319
|
return;
|
|
1276
1320
|
}
|