claude-code-rust 0.14.0 → 0.14.2
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 +5 -9
- package/agent-sdk/dist/bridge/account_metadata.js +1 -0
- 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 +35 -9
- package/agent-sdk/dist/bridge/session_lifecycle.js +70 -11
- package/agent-sdk/dist/bridge/state_parsing.js +35 -0
- package/agent-sdk/dist/bridge/tool_calls.js +45 -10
- package/agent-sdk/dist/bridge/tooling.js +106 -8
- package/agent-sdk/dist/bridge.js +135 -629
- package/package.json +8 -8
|
@@ -65,6 +65,7 @@ function commandSessionId(command) {
|
|
|
65
65
|
case "cancel_turn":
|
|
66
66
|
case "set_model":
|
|
67
67
|
case "set_mode":
|
|
68
|
+
case "set_fast_mode":
|
|
68
69
|
case "generate_session_title":
|
|
69
70
|
case "rename_session":
|
|
70
71
|
case "permission_response":
|
|
@@ -103,6 +104,7 @@ function commandToolCallId(command) {
|
|
|
103
104
|
case "cancel_turn":
|
|
104
105
|
case "set_model":
|
|
105
106
|
case "set_mode":
|
|
107
|
+
case "set_fast_mode":
|
|
106
108
|
case "generate_session_title":
|
|
107
109
|
case "rename_session":
|
|
108
110
|
case "new_session":
|
|
@@ -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, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
|
|
11
|
+
import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, 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";
|
|
@@ -744,6 +744,7 @@ export function handleUserToolResultBlocks(session, message) {
|
|
|
744
744
|
return false;
|
|
745
745
|
}
|
|
746
746
|
const content = Array.isArray(messageObject.content) ? messageObject.content : [];
|
|
747
|
+
const nonExecutionByToolUseId = parseToolNonExecutionMetadata(message.tool_result_meta);
|
|
747
748
|
let handled = false;
|
|
748
749
|
for (const block of content) {
|
|
749
750
|
if (!block || typeof block !== "object") {
|
|
@@ -766,13 +767,13 @@ export function handleUserToolResultBlocks(session, message) {
|
|
|
766
767
|
parentToolUseId,
|
|
767
768
|
sourceMessageUuid: sourceMessageUuid(message),
|
|
768
769
|
});
|
|
769
|
-
emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message));
|
|
770
|
+
emitToolResultUpdate(session, toolUseId, Boolean(blockRecord.is_error), blockRecord.content, messageToolUseResult(message) ?? blockRecord, sourceMessageUuid(message), nonExecutionByToolUseId.get(toolUseId));
|
|
770
771
|
}
|
|
771
772
|
}
|
|
772
773
|
return handled;
|
|
773
774
|
}
|
|
774
775
|
export function handleResultMessage(session, message) {
|
|
775
|
-
emitFastModeUpdateIfChanged(session, message.fast_mode_state);
|
|
776
|
+
emitFastModeUpdateIfChanged(session, message.fast_mode_state, message.fast_mode_disabled_reason);
|
|
776
777
|
const terminalReason = terminalReasonFromValue(message.terminal_reason);
|
|
777
778
|
const subtype = typeof message.subtype === "string" ? message.subtype : "";
|
|
778
779
|
if (subtype === "success") {
|
|
@@ -979,7 +980,7 @@ export function handleSdkMessage(session, message) {
|
|
|
979
980
|
session.mode = incomingMode;
|
|
980
981
|
}
|
|
981
982
|
refreshSupportedModesForSession(session);
|
|
982
|
-
|
|
983
|
+
const fastModeChanged = setFastModeSnapshotIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
|
|
983
984
|
if (!session.connected) {
|
|
984
985
|
emitConnectEvent(session);
|
|
985
986
|
}
|
|
@@ -996,6 +997,9 @@ export function handleSdkMessage(session, message) {
|
|
|
996
997
|
mode: buildModeState(session, incomingMode),
|
|
997
998
|
});
|
|
998
999
|
}
|
|
1000
|
+
if (fastModeChanged) {
|
|
1001
|
+
emitFastModeUpdate(session);
|
|
1002
|
+
}
|
|
999
1003
|
}
|
|
1000
1004
|
if (Array.isArray(msg.slash_commands)) {
|
|
1001
1005
|
updateAvailableCommands(session, "init_slash_commands", mapInitSlashCommands(msg.slash_commands));
|
|
@@ -1040,7 +1044,7 @@ export function handleSdkMessage(session, message) {
|
|
|
1040
1044
|
else if (msg.status === null) {
|
|
1041
1045
|
emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
|
|
1042
1046
|
}
|
|
1043
|
-
emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
|
|
1047
|
+
emitFastModeUpdateIfChanged(session, msg.fast_mode_state, msg.fast_mode_disabled_reason);
|
|
1044
1048
|
return;
|
|
1045
1049
|
}
|
|
1046
1050
|
if (subtype === "compact_boundary") {
|
|
@@ -1159,7 +1163,29 @@ export function handleSdkMessage(session, message) {
|
|
|
1159
1163
|
},
|
|
1160
1164
|
});
|
|
1161
1165
|
if (resolvedToolUseId) {
|
|
1162
|
-
|
|
1166
|
+
const hasSubagentRetry = Object.hasOwn(msg, "subagent_retry");
|
|
1167
|
+
const subagentRetry = hasSubagentRetry ? buildSubagentRetryUpdate(msg) : null;
|
|
1168
|
+
if (hasSubagentRetry && !subagentRetry) {
|
|
1169
|
+
bridgeLogger.warn({
|
|
1170
|
+
target: LOG_TARGETS.APP_TOOL,
|
|
1171
|
+
eventName: "sdk_subagent_retry_rejected",
|
|
1172
|
+
message: "ignored malformed SDK subagent retry progress",
|
|
1173
|
+
outcome: "invalid_payload",
|
|
1174
|
+
sessionId: session.sessionId,
|
|
1175
|
+
toolCallId: resolvedToolUseId,
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
const subagentType = typeof msg.subagent_type === "string" && msg.subagent_type.trim()
|
|
1179
|
+
? msg.subagent_type.trim()
|
|
1180
|
+
: undefined;
|
|
1181
|
+
emitToolProgressUpdate(session, resolvedToolUseId, toolName, {
|
|
1182
|
+
...(subagentRetry
|
|
1183
|
+
? { subagentRetry }
|
|
1184
|
+
: hasSubagentRetry
|
|
1185
|
+
? {}
|
|
1186
|
+
: { subagentRetry: { state: "clear" } }),
|
|
1187
|
+
...(subagentType ? { subagentType } : {}),
|
|
1188
|
+
});
|
|
1163
1189
|
}
|
|
1164
1190
|
return;
|
|
1165
1191
|
}
|
|
@@ -1248,7 +1274,7 @@ export function handleSdkMessage(session, message) {
|
|
|
1248
1274
|
return;
|
|
1249
1275
|
}
|
|
1250
1276
|
const parsed = unwrapToolUseResult(rawToolUseResult);
|
|
1251
|
-
emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg));
|
|
1277
|
+
emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, rawToolUseResult, sourceMessageUuid(msg), parseToolNonExecutionMetadata(msg.tool_result_meta).get(toolUseId));
|
|
1252
1278
|
}
|
|
1253
1279
|
return;
|
|
1254
1280
|
}
|