@schoolai/shipyard 3.25.0 → 3.26.0-rc.20260819.0
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/dist/{chunk-IRS6HYMU.js → chunk-ALNGADQY.js} +2 -2
- package/dist/{chunk-XNH6FVIX.js → chunk-FU6IF6C2.js} +3 -3
- package/dist/electron-utility.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{serve-HTQMCHRP.js → serve-JSMTUTQZ.js} +514 -191
- package/dist/{serve-HTQMCHRP.js.map → serve-JSMTUTQZ.js.map} +1 -1
- package/dist/{start-HOICUTYV.js → start-BS526GUR.js} +4 -4
- package/package.json +1 -1
- /package/dist/{chunk-IRS6HYMU.js.map → chunk-ALNGADQY.js.map} +0 -0
- /package/dist/{chunk-XNH6FVIX.js.map → chunk-FU6IF6C2.js.map} +0 -0
- /package/dist/{start-HOICUTYV.js.map → start-BS526GUR.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
guardedSubscribe,
|
|
13
13
|
makeInitialAttemptState,
|
|
14
14
|
shutdownFileWatcherGuard
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ALNGADQY.js";
|
|
16
16
|
import {
|
|
17
17
|
TRAILER_SCHEMA_VERSION,
|
|
18
18
|
appendTrailerToMessage,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
} from "./chunk-2UE3TNY7.js";
|
|
46
46
|
import {
|
|
47
47
|
getDaemonVersion
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-FU6IF6C2.js";
|
|
49
49
|
import {
|
|
50
50
|
collectPriorCrashDetail,
|
|
51
51
|
getRecentWasmPanicMessages
|
|
@@ -6022,7 +6022,7 @@ function nanoid(size = 21) {
|
|
|
6022
6022
|
}
|
|
6023
6023
|
|
|
6024
6024
|
// src/services/bootstrap/signaling.ts
|
|
6025
|
-
var DAEMON_NPM_VERSION = true ? "3.
|
|
6025
|
+
var DAEMON_NPM_VERSION = true ? "3.26.0" : "unknown";
|
|
6026
6026
|
function createDaemonSignaling(config) {
|
|
6027
6027
|
const agentId = config.agentId ?? nanoid();
|
|
6028
6028
|
function send(msg) {
|
|
@@ -21281,11 +21281,11 @@ function freshOccupancyOrNull(raw) {
|
|
|
21281
21281
|
}
|
|
21282
21282
|
function resolveCompactionThreshold(params) {
|
|
21283
21283
|
const { taskPrefs, runtimePrefs, runtimeDefaultFraction, maxTokens } = params;
|
|
21284
|
-
const
|
|
21285
|
-
if (
|
|
21284
|
+
const fromTask = prefsToFraction(taskPrefs, maxTokens);
|
|
21285
|
+
if (fromTask !== void 0) return { tier: "task", fraction: fromTask };
|
|
21286
21286
|
const fromRuntime = prefsToFraction(runtimePrefs, maxTokens);
|
|
21287
|
-
if (fromRuntime !== void 0) return fromRuntime;
|
|
21288
|
-
return runtimeDefaultFraction;
|
|
21287
|
+
if (fromRuntime !== void 0) return { tier: "runtime", fraction: fromRuntime };
|
|
21288
|
+
return runtimeDefaultFraction !== null ? { tier: "default", fraction: runtimeDefaultFraction } : { tier: "none", fraction: null };
|
|
21289
21289
|
}
|
|
21290
21290
|
function resolveActiveCompactionPrefs(params) {
|
|
21291
21291
|
return params.taskPrefs ?? params.runtimePrefs ?? null;
|
|
@@ -34388,6 +34388,10 @@ function applyPersistedComposerSettings(orchestrator, settings) {
|
|
|
34388
34388
|
patch.disabledMcpServers = normalizedSettings.disabledMcpServers;
|
|
34389
34389
|
if (normalizedSettings.mcpServerSnapshot != null)
|
|
34390
34390
|
patch.mcpServerSnapshot = normalizedSettings.mcpServerSnapshot;
|
|
34391
|
+
if (normalizedSettings.compactionPrefs != null)
|
|
34392
|
+
patch.compactionPrefs = normalizedSettings.compactionPrefs;
|
|
34393
|
+
if (normalizedSettings.mutedResources != null)
|
|
34394
|
+
patch.mutedResources = normalizedSettings.mutedResources;
|
|
34391
34395
|
if (Object.keys(patch).length > 0) orchestrator.applySettings(patch);
|
|
34392
34396
|
}
|
|
34393
34397
|
function persistMessageBorneComposerSettings(deps, settings) {
|
|
@@ -55183,11 +55187,11 @@ import { randomUUID as randomUUID13 } from "crypto";
|
|
|
55183
55187
|
import { auth as auth2, UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
55184
55188
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
55185
55189
|
import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
55186
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
55190
|
+
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
55187
55191
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
55188
55192
|
import {
|
|
55189
|
-
CallToolRequestSchema,
|
|
55190
|
-
ListToolsRequestSchema
|
|
55193
|
+
CallToolRequestSchema as CallToolRequestSchema2,
|
|
55194
|
+
ListToolsRequestSchema as ListToolsRequestSchema2
|
|
55191
55195
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
55192
55196
|
|
|
55193
55197
|
// src/shared/mcp/connect-failure-log.ts
|
|
@@ -55195,6 +55199,126 @@ function classifyConnectFailureLevel(errorKind) {
|
|
|
55195
55199
|
return errorKind === "auth" ? "info" : "error";
|
|
55196
55200
|
}
|
|
55197
55201
|
|
|
55202
|
+
// src/shared/mcp/proxy-loopback-binding.ts
|
|
55203
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
55204
|
+
import {
|
|
55205
|
+
CallToolRequestSchema,
|
|
55206
|
+
ListToolsRequestSchema
|
|
55207
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
55208
|
+
var LOCAL_HTTP_SESSION_IDLE_MS = 5 * 60 * 1e3;
|
|
55209
|
+
function buildLocalHttpProxyMcpServer(opts) {
|
|
55210
|
+
const mcpServer = new McpServer(
|
|
55211
|
+
{ name: `shipyard-proxy-http:${opts.serverName}`, version: "1.0.0" },
|
|
55212
|
+
{ capabilities: { tools: { listChanged: true } } }
|
|
55213
|
+
);
|
|
55214
|
+
const lowLevelServer = mcpServer.server;
|
|
55215
|
+
lowLevelServer.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
55216
|
+
tools: [...opts.lookupTools()]
|
|
55217
|
+
}));
|
|
55218
|
+
lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
55219
|
+
const { name, arguments: args } = request.params;
|
|
55220
|
+
opts.log({
|
|
55221
|
+
event: "proxy_http_call_tool_handler_fired",
|
|
55222
|
+
serverName: opts.serverName,
|
|
55223
|
+
toolName: name
|
|
55224
|
+
});
|
|
55225
|
+
const knownTool = opts.lookupTools().find((tool) => tool.name === name);
|
|
55226
|
+
if (!knownTool) {
|
|
55227
|
+
return {
|
|
55228
|
+
content: [
|
|
55229
|
+
{
|
|
55230
|
+
type: "text",
|
|
55231
|
+
text: `Unknown tool "${name}" on server "${opts.serverName}"`
|
|
55232
|
+
}
|
|
55233
|
+
],
|
|
55234
|
+
isError: true
|
|
55235
|
+
};
|
|
55236
|
+
}
|
|
55237
|
+
const client = opts.lookupClient();
|
|
55238
|
+
if (!client) {
|
|
55239
|
+
return {
|
|
55240
|
+
content: [
|
|
55241
|
+
{
|
|
55242
|
+
type: "text",
|
|
55243
|
+
text: `MCP server "${opts.serverName}" is not connected`
|
|
55244
|
+
}
|
|
55245
|
+
],
|
|
55246
|
+
isError: true
|
|
55247
|
+
};
|
|
55248
|
+
}
|
|
55249
|
+
try {
|
|
55250
|
+
return await client.callTool({ name, arguments: args });
|
|
55251
|
+
} catch (err3) {
|
|
55252
|
+
return opts.onToolCallError(err3, name);
|
|
55253
|
+
}
|
|
55254
|
+
});
|
|
55255
|
+
return mcpServer;
|
|
55256
|
+
}
|
|
55257
|
+
function toLocalHttpExposure(binding) {
|
|
55258
|
+
return {
|
|
55259
|
+
serverName: binding.serverName,
|
|
55260
|
+
url: binding.url,
|
|
55261
|
+
host: binding.host,
|
|
55262
|
+
port: binding.port,
|
|
55263
|
+
bearerToken: binding.bearerToken,
|
|
55264
|
+
bearerTokenEnvVar: binding.bearerTokenEnvVar
|
|
55265
|
+
};
|
|
55266
|
+
}
|
|
55267
|
+
function makeBearerTokenEnvVar(serverName) {
|
|
55268
|
+
const sanitized = serverName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
55269
|
+
return `SHIPYARD_MCP_PROXY_TOKEN_${sanitized}`;
|
|
55270
|
+
}
|
|
55271
|
+
async function disposeLocalHttpBinding(binding, log) {
|
|
55272
|
+
const sessions = [...binding.sessions.values(), ...binding.pendingSessions];
|
|
55273
|
+
binding.sessions.clear();
|
|
55274
|
+
binding.pendingSessions.clear();
|
|
55275
|
+
await Promise.all(
|
|
55276
|
+
sessions.map((session) => disposeLocalHttpSession(session, binding.serverName, log))
|
|
55277
|
+
);
|
|
55278
|
+
await new Promise((resolve12) => {
|
|
55279
|
+
binding.httpServer.closeAllConnections?.();
|
|
55280
|
+
binding.httpServer.close(() => resolve12());
|
|
55281
|
+
});
|
|
55282
|
+
log({
|
|
55283
|
+
event: "mcp_proxy_local_http_unbound",
|
|
55284
|
+
serverName: binding.serverName,
|
|
55285
|
+
port: binding.port
|
|
55286
|
+
});
|
|
55287
|
+
}
|
|
55288
|
+
async function disposeLocalHttpSession(session, serverName, log) {
|
|
55289
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
55290
|
+
try {
|
|
55291
|
+
await session.mcpServer.close();
|
|
55292
|
+
} catch (err3) {
|
|
55293
|
+
log({
|
|
55294
|
+
event: "mcp_proxy_local_http_mcp_server_close_failed",
|
|
55295
|
+
serverName,
|
|
55296
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
55297
|
+
});
|
|
55298
|
+
}
|
|
55299
|
+
}
|
|
55300
|
+
function armLocalHttpSessionExpiry(session, sessionId, sessions, serverName, log) {
|
|
55301
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
55302
|
+
session.idleTimer = null;
|
|
55303
|
+
if (sessions.get(sessionId) !== session) return;
|
|
55304
|
+
if (session.activeRequestCount > 0 || session.notificationStreamReady) return;
|
|
55305
|
+
session.idleTimer = setTimeout(() => {
|
|
55306
|
+
if (sessions.get(sessionId) !== session || session.notificationStreamReady) return;
|
|
55307
|
+
sessions.delete(sessionId);
|
|
55308
|
+
void disposeLocalHttpSession(session, serverName, log);
|
|
55309
|
+
}, LOCAL_HTTP_SESSION_IDLE_MS);
|
|
55310
|
+
session.idleTimer.unref();
|
|
55311
|
+
}
|
|
55312
|
+
function startLocalHttpSessionRequest(session) {
|
|
55313
|
+
session.activeRequestCount += 1;
|
|
55314
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
55315
|
+
session.idleTimer = null;
|
|
55316
|
+
}
|
|
55317
|
+
function finishLocalHttpSessionRequest(session, sessionId, sessions, serverName, log) {
|
|
55318
|
+
session.activeRequestCount = Math.max(0, session.activeRequestCount - 1);
|
|
55319
|
+
armLocalHttpSessionExpiry(session, sessionId, sessions, serverName, log);
|
|
55320
|
+
}
|
|
55321
|
+
|
|
55198
55322
|
// src/shared/mcp/proxy-loopback-listener.ts
|
|
55199
55323
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
55200
55324
|
import { createServer as createServer3 } from "http";
|
|
@@ -55333,7 +55457,8 @@ async function startLoopbackHttpServer(opts) {
|
|
|
55333
55457
|
req,
|
|
55334
55458
|
res,
|
|
55335
55459
|
bearerToken: opts.bearerToken,
|
|
55336
|
-
|
|
55460
|
+
createTransport: opts.createTransport,
|
|
55461
|
+
lookupTransport: opts.lookupTransport,
|
|
55337
55462
|
log: opts.log,
|
|
55338
55463
|
serverName: opts.serverName
|
|
55339
55464
|
}).catch((err3) => {
|
|
@@ -55422,11 +55547,64 @@ async function handleProxyHttpRequest(opts) {
|
|
|
55422
55547
|
);
|
|
55423
55548
|
return;
|
|
55424
55549
|
}
|
|
55425
|
-
await
|
|
55550
|
+
await forwardAuthenticatedRequest(opts);
|
|
55551
|
+
}
|
|
55552
|
+
async function forwardAuthenticatedRequest(opts) {
|
|
55553
|
+
const sessionHeader = opts.req.headers["mcp-session-id"];
|
|
55554
|
+
const sessionId = Array.isArray(sessionHeader) ? sessionHeader[0] : sessionHeader;
|
|
55555
|
+
const created = sessionId === void 0;
|
|
55556
|
+
const session = created ? await opts.createTransport() : opts.lookupTransport(sessionId);
|
|
55557
|
+
if (!session) {
|
|
55558
|
+
await respondProxyError(
|
|
55559
|
+
opts.req,
|
|
55560
|
+
opts.res,
|
|
55561
|
+
404,
|
|
55562
|
+
MCP_PROXY_JSON_RPC_SESSION_NOT_FOUND,
|
|
55563
|
+
"session_not_found"
|
|
55564
|
+
);
|
|
55565
|
+
return;
|
|
55566
|
+
}
|
|
55567
|
+
const isNotificationStream = opts.req.method === "GET";
|
|
55568
|
+
if (isNotificationStream) observeNotificationStream(opts.req, opts.res, session);
|
|
55569
|
+
session.onRequestStart();
|
|
55570
|
+
try {
|
|
55571
|
+
await session.transport.handleRequest(opts.req, opts.res);
|
|
55572
|
+
} finally {
|
|
55573
|
+
session.onRequestEnd();
|
|
55574
|
+
if (created && session.transport.sessionId === void 0) await session.dispose();
|
|
55575
|
+
}
|
|
55576
|
+
}
|
|
55577
|
+
function observeNotificationStream(req, res, session) {
|
|
55578
|
+
let readinessTimer = null;
|
|
55579
|
+
let becameReady = false;
|
|
55580
|
+
res.once("close", () => {
|
|
55581
|
+
if (readinessTimer) clearTimeout(readinessTimer);
|
|
55582
|
+
if (becameReady) session.onStreamClosed();
|
|
55583
|
+
});
|
|
55584
|
+
let retryMs = 1;
|
|
55585
|
+
const markReadyAfterHeaders = () => {
|
|
55586
|
+
if (res.closed) return;
|
|
55587
|
+
if (res.headersSent) {
|
|
55588
|
+
if (isSuccessfulNotificationStream(req, res)) {
|
|
55589
|
+
becameReady = true;
|
|
55590
|
+
session.onStreamReady();
|
|
55591
|
+
}
|
|
55592
|
+
return;
|
|
55593
|
+
}
|
|
55594
|
+
readinessTimer = setTimeout(markReadyAfterHeaders, retryMs);
|
|
55595
|
+
readinessTimer.unref();
|
|
55596
|
+
retryMs = Math.min(retryMs * 2, 250);
|
|
55597
|
+
};
|
|
55598
|
+
markReadyAfterHeaders();
|
|
55599
|
+
}
|
|
55600
|
+
function isSuccessfulNotificationStream(req, res) {
|
|
55601
|
+
const accept = req.headers.accept ?? "";
|
|
55602
|
+
return res.statusCode === 200 && accept.toLowerCase().includes("text/event-stream");
|
|
55426
55603
|
}
|
|
55427
55604
|
var MCP_PROXY_JSON_RPC_INTERNAL = -32603;
|
|
55428
55605
|
var MCP_PROXY_JSON_RPC_UNAUTHORIZED = -32001;
|
|
55429
55606
|
var MCP_PROXY_JSON_RPC_NOT_FOUND = -32004;
|
|
55607
|
+
var MCP_PROXY_JSON_RPC_SESSION_NOT_FOUND = -32005;
|
|
55430
55608
|
async function respondProxyError(req, res, status, code, message) {
|
|
55431
55609
|
sendJsonRpcHttpError(res, status, code, message, await readRequestId(req));
|
|
55432
55610
|
}
|
|
@@ -55809,7 +55987,9 @@ var McpProxyServer = class {
|
|
|
55809
55987
|
});
|
|
55810
55988
|
return;
|
|
55811
55989
|
}
|
|
55990
|
+
const previousTools = this.#servers.get(name)?.tools ?? [];
|
|
55812
55991
|
this.#servers.set(name, { client, transport, config, tools, provider });
|
|
55992
|
+
this.#notifyLocalHttpToolListChanged(name, previousTools, tools);
|
|
55813
55993
|
this.#connectErrors.delete(name);
|
|
55814
55994
|
this.#connectErrorMessages.delete(name);
|
|
55815
55995
|
this.#deps.log({
|
|
@@ -55820,6 +56000,30 @@ var McpProxyServer = class {
|
|
|
55820
56000
|
toolNames: tools.map((t) => t.name)
|
|
55821
56001
|
});
|
|
55822
56002
|
}
|
|
56003
|
+
#notifyLocalHttpToolListChanged(name, before, after) {
|
|
56004
|
+
if (JSON.stringify(before) === JSON.stringify(after)) return;
|
|
56005
|
+
const binding = this.#localHttpBindings.get(name);
|
|
56006
|
+
if (!binding) return;
|
|
56007
|
+
for (const session of binding.sessions.values()) {
|
|
56008
|
+
if (!session.notificationStreamReady) {
|
|
56009
|
+
session.pendingToolListChanged = true;
|
|
56010
|
+
continue;
|
|
56011
|
+
}
|
|
56012
|
+
this.#sendLocalHttpToolListChanged(name, session);
|
|
56013
|
+
}
|
|
56014
|
+
}
|
|
56015
|
+
#sendLocalHttpToolListChanged(name, session) {
|
|
56016
|
+
session.pendingToolListChanged = false;
|
|
56017
|
+
session.sentToolListChangedOnCurrentStream = true;
|
|
56018
|
+
void session.mcpServer.server.sendToolListChanged().catch((err3) => {
|
|
56019
|
+
session.pendingToolListChanged = true;
|
|
56020
|
+
this.#deps.log({
|
|
56021
|
+
event: "mcp_proxy_local_http_tool_list_changed_failed",
|
|
56022
|
+
serverName: name,
|
|
56023
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
56024
|
+
});
|
|
56025
|
+
});
|
|
56026
|
+
}
|
|
55823
56027
|
async #shouldPreflightRefresh(name) {
|
|
55824
56028
|
const token = await this.#deps.tokenStore.getToken(name);
|
|
55825
56029
|
return token ? this.#deps.tokenStore.needsRefresh(token) : false;
|
|
@@ -56061,7 +56265,7 @@ var McpProxyServer = class {
|
|
|
56061
56265
|
async exposeAsLocalHttp(serverName) {
|
|
56062
56266
|
const cached = this.#localHttpBindings.get(serverName);
|
|
56063
56267
|
if (cached) {
|
|
56064
|
-
return
|
|
56268
|
+
return toLocalHttpExposure(cached);
|
|
56065
56269
|
}
|
|
56066
56270
|
const inFlight = this.#localHttpPending.get(serverName);
|
|
56067
56271
|
if (inFlight) return inFlight;
|
|
@@ -56109,28 +56313,114 @@ var McpProxyServer = class {
|
|
|
56109
56313
|
* @internal
|
|
56110
56314
|
*/
|
|
56111
56315
|
getLocalHttpBindingsForTest() {
|
|
56112
|
-
return new Map([...this.#localHttpBindings].map(([k2, v2]) => [k2,
|
|
56316
|
+
return new Map([...this.#localHttpBindings].map(([k2, v2]) => [k2, toLocalHttpExposure(v2)]));
|
|
56113
56317
|
}
|
|
56114
56318
|
async #buildLocalHttpBinding(serverName) {
|
|
56115
56319
|
const bearerToken = randomUUID13();
|
|
56116
56320
|
const bearerTokenEnvVar = makeBearerTokenEnvVar(serverName);
|
|
56117
|
-
const
|
|
56118
|
-
|
|
56119
|
-
|
|
56120
|
-
|
|
56121
|
-
|
|
56122
|
-
|
|
56123
|
-
this.#
|
|
56124
|
-
this.#
|
|
56125
|
-
|
|
56126
|
-
|
|
56127
|
-
|
|
56128
|
-
|
|
56129
|
-
|
|
56130
|
-
|
|
56321
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
56322
|
+
const pendingSessions = /* @__PURE__ */ new Set();
|
|
56323
|
+
const createSession = async () => {
|
|
56324
|
+
const mcpServer = buildLocalHttpProxyMcpServer({
|
|
56325
|
+
serverName,
|
|
56326
|
+
lookupClient: () => this.#servers.get(serverName)?.client ?? null,
|
|
56327
|
+
lookupTools: () => this.#servers.get(serverName)?.tools ?? [],
|
|
56328
|
+
log: this.#deps.log,
|
|
56329
|
+
onToolCallError: (err3, toolName) => handleToolCallError(err3, toolName, serverName, this.#deps.log, (sn) => {
|
|
56330
|
+
this.#connectErrors.set(sn, "auth");
|
|
56331
|
+
this.#fireOnAuthRequired(sn);
|
|
56332
|
+
})
|
|
56333
|
+
});
|
|
56334
|
+
let session;
|
|
56335
|
+
const transport = new StreamableHTTPServerTransport({
|
|
56336
|
+
sessionIdGenerator: () => randomUUID13(),
|
|
56337
|
+
onsessioninitialized: (sessionId) => {
|
|
56338
|
+
pendingSessions.delete(session);
|
|
56339
|
+
sessions.set(sessionId, session);
|
|
56340
|
+
armLocalHttpSessionExpiry(session, sessionId, sessions, serverName, this.#deps.log);
|
|
56341
|
+
},
|
|
56342
|
+
onsessionclosed: (sessionId) => {
|
|
56343
|
+
const closed = sessions.get(sessionId);
|
|
56344
|
+
sessions.delete(sessionId);
|
|
56345
|
+
if (closed?.idleTimer) clearTimeout(closed.idleTimer);
|
|
56346
|
+
}
|
|
56347
|
+
});
|
|
56348
|
+
session = {
|
|
56349
|
+
transport,
|
|
56350
|
+
mcpServer,
|
|
56351
|
+
activeRequestCount: 0,
|
|
56352
|
+
notificationStreamReady: false,
|
|
56353
|
+
pendingToolListChanged: false,
|
|
56354
|
+
sentToolListChangedOnCurrentStream: false,
|
|
56355
|
+
idleTimer: null
|
|
56356
|
+
};
|
|
56357
|
+
pendingSessions.add(session);
|
|
56358
|
+
await mcpServer.connect(transport);
|
|
56359
|
+
return session;
|
|
56360
|
+
};
|
|
56131
56361
|
const { httpServer, host, port } = await startLoopbackHttpServer({
|
|
56132
56362
|
bearerToken,
|
|
56133
|
-
|
|
56363
|
+
createTransport: async () => {
|
|
56364
|
+
const session = await createSession();
|
|
56365
|
+
return {
|
|
56366
|
+
transport: session.transport,
|
|
56367
|
+
dispose: async () => {
|
|
56368
|
+
pendingSessions.delete(session);
|
|
56369
|
+
await disposeLocalHttpSession(session, serverName, this.#deps.log);
|
|
56370
|
+
},
|
|
56371
|
+
onRequestStart: () => startLocalHttpSessionRequest(session),
|
|
56372
|
+
onRequestEnd: () => {
|
|
56373
|
+
const sessionId = session.transport.sessionId;
|
|
56374
|
+
if (sessionId) {
|
|
56375
|
+
finishLocalHttpSessionRequest(
|
|
56376
|
+
session,
|
|
56377
|
+
sessionId,
|
|
56378
|
+
sessions,
|
|
56379
|
+
serverName,
|
|
56380
|
+
this.#deps.log
|
|
56381
|
+
);
|
|
56382
|
+
}
|
|
56383
|
+
},
|
|
56384
|
+
onStreamReady: () => {
|
|
56385
|
+
},
|
|
56386
|
+
onStreamClosed: () => {
|
|
56387
|
+
}
|
|
56388
|
+
};
|
|
56389
|
+
},
|
|
56390
|
+
lookupTransport: (sessionId) => {
|
|
56391
|
+
const session = sessions.get(sessionId);
|
|
56392
|
+
return session ? {
|
|
56393
|
+
transport: session.transport,
|
|
56394
|
+
dispose: () => disposeLocalHttpSession(session, serverName, this.#deps.log),
|
|
56395
|
+
onRequestStart: () => startLocalHttpSessionRequest(session),
|
|
56396
|
+
onRequestEnd: () => finishLocalHttpSessionRequest(
|
|
56397
|
+
session,
|
|
56398
|
+
sessionId,
|
|
56399
|
+
sessions,
|
|
56400
|
+
serverName,
|
|
56401
|
+
this.#deps.log
|
|
56402
|
+
),
|
|
56403
|
+
onStreamReady: () => {
|
|
56404
|
+
session.notificationStreamReady = true;
|
|
56405
|
+
session.sentToolListChangedOnCurrentStream = false;
|
|
56406
|
+
if (session.idleTimer) {
|
|
56407
|
+
clearTimeout(session.idleTimer);
|
|
56408
|
+
session.idleTimer = null;
|
|
56409
|
+
}
|
|
56410
|
+
if (session.pendingToolListChanged) {
|
|
56411
|
+
this.#sendLocalHttpToolListChanged(serverName, session);
|
|
56412
|
+
}
|
|
56413
|
+
},
|
|
56414
|
+
onStreamClosed: () => {
|
|
56415
|
+
session.notificationStreamReady = false;
|
|
56416
|
+
if (session.sentToolListChangedOnCurrentStream) {
|
|
56417
|
+
session.pendingToolListChanged = true;
|
|
56418
|
+
}
|
|
56419
|
+
session.sentToolListChangedOnCurrentStream = false;
|
|
56420
|
+
armLocalHttpSessionExpiry(session, sessionId, sessions, serverName, this.#deps.log);
|
|
56421
|
+
}
|
|
56422
|
+
} : void 0;
|
|
56423
|
+
},
|
|
56134
56424
|
log: this.#deps.log,
|
|
56135
56425
|
serverName
|
|
56136
56426
|
});
|
|
@@ -56142,8 +56432,8 @@ var McpProxyServer = class {
|
|
|
56142
56432
|
bearerToken,
|
|
56143
56433
|
bearerTokenEnvVar,
|
|
56144
56434
|
httpServer,
|
|
56145
|
-
|
|
56146
|
-
|
|
56435
|
+
sessions,
|
|
56436
|
+
pendingSessions
|
|
56147
56437
|
};
|
|
56148
56438
|
this.#localHttpBindings.set(serverName, binding);
|
|
56149
56439
|
this.#deps.log({
|
|
@@ -56153,7 +56443,7 @@ var McpProxyServer = class {
|
|
|
56153
56443
|
host,
|
|
56154
56444
|
bearerTokenEnvVar
|
|
56155
56445
|
});
|
|
56156
|
-
return
|
|
56446
|
+
return toLocalHttpExposure(binding);
|
|
56157
56447
|
}
|
|
56158
56448
|
/**
|
|
56159
56449
|
* Return connection statuses for all proxy-managed servers.
|
|
@@ -56222,6 +56512,7 @@ var McpProxyServer = class {
|
|
|
56222
56512
|
} catch {
|
|
56223
56513
|
}
|
|
56224
56514
|
this.#servers.delete(name);
|
|
56515
|
+
this.#notifyLocalHttpToolListChanged(name, server.tools, []);
|
|
56225
56516
|
}
|
|
56226
56517
|
/** Disconnect all servers and clean up resources. */
|
|
56227
56518
|
async dispose() {
|
|
@@ -56257,15 +56548,15 @@ function handleToolCallError(err3, toolName, serverName, log, onAuthError) {
|
|
|
56257
56548
|
}
|
|
56258
56549
|
function buildPerServerProxy(serverName, client, tools, log, onAuthError) {
|
|
56259
56550
|
const proxyName = `shipyard-proxy:${serverName}`;
|
|
56260
|
-
const mcpServer = new
|
|
56551
|
+
const mcpServer = new McpServer2(
|
|
56261
56552
|
{ name: proxyName, version: "1.0.0" },
|
|
56262
56553
|
{ capabilities: { tools: {} } }
|
|
56263
56554
|
);
|
|
56264
56555
|
const lowLevelServer = mcpServer.server;
|
|
56265
|
-
lowLevelServer.setRequestHandler(
|
|
56556
|
+
lowLevelServer.setRequestHandler(ListToolsRequestSchema2, async () => ({
|
|
56266
56557
|
tools: [...tools]
|
|
56267
56558
|
}));
|
|
56268
|
-
lowLevelServer.setRequestHandler(
|
|
56559
|
+
lowLevelServer.setRequestHandler(CallToolRequestSchema2, async (request) => {
|
|
56269
56560
|
const { name, arguments: args } = request.params;
|
|
56270
56561
|
log({ event: "proxy_call_tool_handler_fired", serverName, toolName: name });
|
|
56271
56562
|
const knownTool = tools.find((t) => t.name === name);
|
|
@@ -56289,98 +56580,6 @@ function buildPerServerProxy(serverName, client, tools, log, onAuthError) {
|
|
|
56289
56580
|
instance: mcpServer
|
|
56290
56581
|
};
|
|
56291
56582
|
}
|
|
56292
|
-
function toExposure(binding) {
|
|
56293
|
-
return {
|
|
56294
|
-
serverName: binding.serverName,
|
|
56295
|
-
url: binding.url,
|
|
56296
|
-
host: binding.host,
|
|
56297
|
-
port: binding.port,
|
|
56298
|
-
bearerToken: binding.bearerToken,
|
|
56299
|
-
bearerTokenEnvVar: binding.bearerTokenEnvVar
|
|
56300
|
-
};
|
|
56301
|
-
}
|
|
56302
|
-
function makeBearerTokenEnvVar(serverName) {
|
|
56303
|
-
const sanitized = serverName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
56304
|
-
return `SHIPYARD_MCP_PROXY_TOKEN_${sanitized}`;
|
|
56305
|
-
}
|
|
56306
|
-
function buildLocalHttpProxyMcpServer(opts) {
|
|
56307
|
-
const mcpServer = new McpServer(
|
|
56308
|
-
{ name: `shipyard-proxy-http:${opts.serverName}`, version: "1.0.0" },
|
|
56309
|
-
{ capabilities: { tools: {} } }
|
|
56310
|
-
);
|
|
56311
|
-
const lowLevelServer = mcpServer.server;
|
|
56312
|
-
lowLevelServer.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
56313
|
-
tools: [...opts.lookupTools()]
|
|
56314
|
-
}));
|
|
56315
|
-
lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
56316
|
-
const { name, arguments: args } = request.params;
|
|
56317
|
-
opts.log({
|
|
56318
|
-
event: "proxy_http_call_tool_handler_fired",
|
|
56319
|
-
serverName: opts.serverName,
|
|
56320
|
-
toolName: name
|
|
56321
|
-
});
|
|
56322
|
-
const tools = opts.lookupTools();
|
|
56323
|
-
const knownTool = tools.find((t) => t.name === name);
|
|
56324
|
-
if (!knownTool) {
|
|
56325
|
-
return {
|
|
56326
|
-
content: [
|
|
56327
|
-
{
|
|
56328
|
-
type: "text",
|
|
56329
|
-
text: `Unknown tool "${name}" on server "${opts.serverName}"`
|
|
56330
|
-
}
|
|
56331
|
-
],
|
|
56332
|
-
isError: true
|
|
56333
|
-
};
|
|
56334
|
-
}
|
|
56335
|
-
const client = opts.lookupClient();
|
|
56336
|
-
if (!client) {
|
|
56337
|
-
return {
|
|
56338
|
-
content: [
|
|
56339
|
-
{
|
|
56340
|
-
type: "text",
|
|
56341
|
-
text: `MCP server "${opts.serverName}" is not connected`
|
|
56342
|
-
}
|
|
56343
|
-
],
|
|
56344
|
-
isError: true
|
|
56345
|
-
};
|
|
56346
|
-
}
|
|
56347
|
-
try {
|
|
56348
|
-
return await client.callTool({ name, arguments: args });
|
|
56349
|
-
} catch (err3) {
|
|
56350
|
-
return handleToolCallError(err3, name, opts.serverName, opts.log, opts.onAuthError);
|
|
56351
|
-
}
|
|
56352
|
-
});
|
|
56353
|
-
return mcpServer;
|
|
56354
|
-
}
|
|
56355
|
-
async function disposeLocalHttpBinding(binding, log) {
|
|
56356
|
-
try {
|
|
56357
|
-
await binding.transport.close();
|
|
56358
|
-
} catch (err3) {
|
|
56359
|
-
log({
|
|
56360
|
-
event: "mcp_proxy_local_http_transport_close_failed",
|
|
56361
|
-
serverName: binding.serverName,
|
|
56362
|
-
error: err3 instanceof Error ? err3.message : String(err3)
|
|
56363
|
-
});
|
|
56364
|
-
}
|
|
56365
|
-
try {
|
|
56366
|
-
await binding.mcpServer.close();
|
|
56367
|
-
} catch (err3) {
|
|
56368
|
-
log({
|
|
56369
|
-
event: "mcp_proxy_local_http_mcp_server_close_failed",
|
|
56370
|
-
serverName: binding.serverName,
|
|
56371
|
-
error: err3 instanceof Error ? err3.message : String(err3)
|
|
56372
|
-
});
|
|
56373
|
-
}
|
|
56374
|
-
await new Promise((resolve12) => {
|
|
56375
|
-
binding.httpServer.closeAllConnections?.();
|
|
56376
|
-
binding.httpServer.close(() => resolve12());
|
|
56377
|
-
});
|
|
56378
|
-
log({
|
|
56379
|
-
event: "mcp_proxy_local_http_unbound",
|
|
56380
|
-
serverName: binding.serverName,
|
|
56381
|
-
port: binding.port
|
|
56382
|
-
});
|
|
56383
|
-
}
|
|
56384
56583
|
|
|
56385
56584
|
// src/services/mcp/oauth/reauth-orchestrator.ts
|
|
56386
56585
|
var ReauthOrchestrator = class {
|
|
@@ -61369,12 +61568,21 @@ var McpServerStartupStatusUpdatedNotificationSchema = external_exports2.object({
|
|
|
61369
61568
|
name: external_exports2.string(),
|
|
61370
61569
|
status: external_exports2.string()
|
|
61371
61570
|
}).passthrough();
|
|
61571
|
+
var McpAuthStatusSchema = external_exports2.enum(["unsupported", "notLoggedIn", "bearerToken", "oAuth"]);
|
|
61372
61572
|
var McpServerStatusEntrySchema = external_exports2.object({
|
|
61373
61573
|
name: external_exports2.string(),
|
|
61374
|
-
|
|
61574
|
+
tools: external_exports2.record(external_exports2.string(), external_exports2.unknown()),
|
|
61575
|
+
resources: external_exports2.array(external_exports2.unknown()),
|
|
61576
|
+
resourceTemplates: external_exports2.array(external_exports2.unknown()),
|
|
61577
|
+
authStatus: McpAuthStatusSchema
|
|
61578
|
+
}).passthrough();
|
|
61579
|
+
var ListMcpServerStatusParamsSchema = external_exports2.object({
|
|
61580
|
+
cursor: external_exports2.string().nullish(),
|
|
61581
|
+
limit: external_exports2.number().int().nullish(),
|
|
61582
|
+
detail: external_exports2.enum(["full", "toolsAndAuthOnly"]).nullish(),
|
|
61583
|
+
threadId: external_exports2.string().nullish()
|
|
61375
61584
|
}).passthrough();
|
|
61376
|
-
var
|
|
61377
|
-
var ListMcpServerStatusResponseSchema = external_exports2.object({ data: external_exports2.array(McpServerStatusEntrySchema) }).passthrough();
|
|
61585
|
+
var ListMcpServerStatusResponseSchema = external_exports2.object({ data: external_exports2.array(McpServerStatusEntrySchema), nextCursor: external_exports2.string().nullable() }).passthrough();
|
|
61378
61586
|
var SkillsListParamsSchema = external_exports2.object({ cwds: external_exports2.array(external_exports2.string()).optional(), force_reload: external_exports2.boolean().optional() }).passthrough();
|
|
61379
61587
|
var SkillMetadataSchema = external_exports2.object({
|
|
61380
61588
|
name: external_exports2.string(),
|
|
@@ -65781,7 +65989,7 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
65781
65989
|
async #emitInitFromThreadStart(response) {
|
|
65782
65990
|
const harnessMcp = this.#harnessHttp !== null ? [{ name: HARNESS_SERVER_NAME, status: "connected" }] : [];
|
|
65783
65991
|
const [mcpResult, skillsResult] = await Promise.allSettled([
|
|
65784
|
-
this.#
|
|
65992
|
+
this.#listThreadMcpInventory(response.thread.id),
|
|
65785
65993
|
this.#client.listSkills({ cwds: [this.#cwd], force_reload: false })
|
|
65786
65994
|
]);
|
|
65787
65995
|
if (mcpResult.status === "rejected") {
|
|
@@ -65790,7 +65998,10 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
65790
65998
|
if (skillsResult.status === "rejected") {
|
|
65791
65999
|
this.#log({ event: "codex_discover_skills_failed", error: String(skillsResult.reason) });
|
|
65792
66000
|
}
|
|
65793
|
-
const discoveredMcps = mcpResult.status === "fulfilled" ? mcpResult.value.
|
|
66001
|
+
const discoveredMcps = mcpResult.status === "fulfilled" ? mcpResult.value.filter((s2) => s2.name !== HARNESS_SERVER_NAME).map(toCodexMcpConnectionStatus) : [];
|
|
66002
|
+
const discoveredMcpTools = mcpResult.status === "fulfilled" ? mcpResult.value.flatMap(
|
|
66003
|
+
(server) => server.authStatus === "notLoggedIn" ? [] : Object.keys(server.tools).map((tool) => `mcp__${server.name}__${tool}`)
|
|
66004
|
+
) : [];
|
|
65794
66005
|
const discoveredSkillInfos = skillsResult.status === "fulfilled" ? coerceCodexSkillsListToSkillInfo(skillsResult.value.data) : [];
|
|
65795
66006
|
const discoveredSkills = discoveredSkillInfos.map((s2) => s2.name);
|
|
65796
66007
|
this.#log({
|
|
@@ -65798,16 +66009,8 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
65798
66009
|
mcpCount: discoveredMcps.length,
|
|
65799
66010
|
skillsCount: discoveredSkills.length
|
|
65800
66011
|
});
|
|
65801
|
-
const startingServers = new Set(
|
|
65802
|
-
discoveredMcps.filter((s2) => s2.status === "starting").map((s2) => s2.name)
|
|
65803
|
-
);
|
|
65804
|
-
await waitForMcpServersToSettle({
|
|
65805
|
-
startingServers,
|
|
65806
|
-
getMcpStatus: () => this.#client.listMcpServerStatus({ limit: 100 }).then((r) => r.data.map((s2) => ({ name: s2.name, status: s2.status }))),
|
|
65807
|
-
log: this.#log
|
|
65808
|
-
});
|
|
65809
66012
|
const metadata = {
|
|
65810
|
-
tools:
|
|
66013
|
+
tools: discoveredMcpTools,
|
|
65811
66014
|
skills: discoveredSkills,
|
|
65812
66015
|
agents: [],
|
|
65813
66016
|
mcpServers: [...harnessMcp, ...discoveredMcps],
|
|
@@ -65831,6 +66034,27 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
65831
66034
|
}
|
|
65832
66035
|
}
|
|
65833
66036
|
}
|
|
66037
|
+
async #listThreadMcpInventory(threadId) {
|
|
66038
|
+
const inventory = [];
|
|
66039
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
66040
|
+
let cursor = null;
|
|
66041
|
+
do {
|
|
66042
|
+
const page = await this.#client.listMcpServerStatus({
|
|
66043
|
+
threadId,
|
|
66044
|
+
detail: "toolsAndAuthOnly",
|
|
66045
|
+
limit: 100,
|
|
66046
|
+
...cursor === null ? {} : { cursor }
|
|
66047
|
+
});
|
|
66048
|
+
inventory.push(...page.data);
|
|
66049
|
+
cursor = page.nextCursor;
|
|
66050
|
+
if (cursor !== null && seenCursors.has(cursor)) {
|
|
66051
|
+
this.#log({ event: "codex_mcp_inventory_cursor_repeated", threadId });
|
|
66052
|
+
break;
|
|
66053
|
+
}
|
|
66054
|
+
if (cursor !== null) seenCursors.add(cursor);
|
|
66055
|
+
} while (cursor !== null);
|
|
66056
|
+
return inventory;
|
|
66057
|
+
}
|
|
65834
66058
|
pushMessage(content) {
|
|
65835
66059
|
if (this.#closed) return;
|
|
65836
66060
|
this.#lastUserContent = content;
|
|
@@ -66208,7 +66432,9 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
66208
66432
|
return { added: [], removed: [], errors: {} };
|
|
66209
66433
|
}
|
|
66210
66434
|
async mcpServerStatus() {
|
|
66211
|
-
return [];
|
|
66435
|
+
if (this.#threadId === null) return [];
|
|
66436
|
+
const inventory = await this.#listThreadMcpInventory(this.#threadId);
|
|
66437
|
+
return inventory.map(toCodexMcpConnectionStatus);
|
|
66212
66438
|
}
|
|
66213
66439
|
async mcpAuthenticate(_serverName) {
|
|
66214
66440
|
return null;
|
|
@@ -66547,6 +66773,13 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
66547
66773
|
this.#onEvent({ type: "sdk_error", error: `Codex engine unavailable: ${reason}` });
|
|
66548
66774
|
}
|
|
66549
66775
|
};
|
|
66776
|
+
function toCodexMcpConnectionStatus(server) {
|
|
66777
|
+
const toolCount = Object.keys(server.tools).length;
|
|
66778
|
+
if (server.authStatus === "notLoggedIn") {
|
|
66779
|
+
return { name: server.name, status: "needs-auth", toolCount: 0 };
|
|
66780
|
+
}
|
|
66781
|
+
return { name: server.name, status: "connected", toolCount };
|
|
66782
|
+
}
|
|
66550
66783
|
function toCodexWireEffort(effort) {
|
|
66551
66784
|
return effort;
|
|
66552
66785
|
}
|
|
@@ -67073,7 +67306,7 @@ function createCodexFacadeApiBuilder(d) {
|
|
|
67073
67306
|
// src/services/harness/harness-http-server.ts
|
|
67074
67307
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
67075
67308
|
import { createServer as createServer4 } from "http";
|
|
67076
|
-
import { McpServer as
|
|
67309
|
+
import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
67077
67310
|
import { StreamableHTTPServerTransport as StreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
67078
67311
|
var SHIPYARD_GENERATION_HEADER = "x-shipyard-generation";
|
|
67079
67312
|
async function createHarnessHttpServer(opts = {}) {
|
|
@@ -67256,7 +67489,7 @@ async function createHarnessHttpServer(opts = {}) {
|
|
|
67256
67489
|
}
|
|
67257
67490
|
async function buildRequestServer(ctx, scope) {
|
|
67258
67491
|
const { tools } = buildHarnessTools(ctx);
|
|
67259
|
-
const mcpServer = new
|
|
67492
|
+
const mcpServer = new McpServer3(
|
|
67260
67493
|
{ name: HARNESS_SERVER_NAME, version: "1.0.0" },
|
|
67261
67494
|
ctx.instructions !== void 0 ? { instructions: ctx.instructions } : void 0
|
|
67262
67495
|
);
|
|
@@ -78817,7 +79050,13 @@ function handleSpawning(snapshot, event) {
|
|
|
78817
79050
|
}
|
|
78818
79051
|
return noop(snapshot);
|
|
78819
79052
|
}
|
|
78820
|
-
function buildStallGiveUpMessage(recoveryAttempts) {
|
|
79053
|
+
function buildStallGiveUpMessage(recoveryAttempts, stallPhase) {
|
|
79054
|
+
if (stallPhase === "first_step") {
|
|
79055
|
+
if (recoveryAttempts <= 0) {
|
|
79056
|
+
return "The agent never started running. Send a message to try again.";
|
|
79057
|
+
}
|
|
79058
|
+
return "The agent never started running \u2014 no progress at all, across every automatic retry. Resuming this session will keep failing the same way; start a fresh task instead.";
|
|
79059
|
+
}
|
|
78821
79060
|
if (recoveryAttempts <= 0) {
|
|
78822
79061
|
return "The agent stopped responding mid-run. Send a message to try again.";
|
|
78823
79062
|
}
|
|
@@ -78831,7 +79070,11 @@ function runningStallTimeout(sessionId, event) {
|
|
|
78831
79070
|
b2.effects.push({ type: "clear_queue" });
|
|
78832
79071
|
if (!event.recoveryArmed) {
|
|
78833
79072
|
b2.taskStatus("input_required");
|
|
78834
|
-
b2.emitError(
|
|
79073
|
+
b2.emitError(
|
|
79074
|
+
buildStallGiveUpMessage(event.recoveryAttempts, event.stallPhase),
|
|
79075
|
+
"sdk_error",
|
|
79076
|
+
"run_stall_timeout"
|
|
79077
|
+
);
|
|
78835
79078
|
}
|
|
78836
79079
|
b2.effects.push({ type: "kill_runner" });
|
|
78837
79080
|
return {
|
|
@@ -79654,11 +79897,19 @@ var AgentSessionManager = class {
|
|
|
79654
79897
|
let recoveryArmed = false;
|
|
79655
79898
|
let recoveryAttempts = 0;
|
|
79656
79899
|
try {
|
|
79657
|
-
const outcome = this.#config.onStallTimeout?.() ?? {
|
|
79900
|
+
const outcome = this.#config.onStallTimeout?.(decision.phase) ?? {
|
|
79901
|
+
armed: false,
|
|
79902
|
+
recoveryAttempts: 0
|
|
79903
|
+
};
|
|
79658
79904
|
recoveryArmed = outcome.armed;
|
|
79659
79905
|
recoveryAttempts = outcome.recoveryAttempts;
|
|
79660
79906
|
} finally {
|
|
79661
|
-
this.#dispatch({
|
|
79907
|
+
this.#dispatch({
|
|
79908
|
+
type: "run_stall_timeout",
|
|
79909
|
+
recoveryArmed,
|
|
79910
|
+
recoveryAttempts,
|
|
79911
|
+
stallPhase: decision.phase
|
|
79912
|
+
});
|
|
79662
79913
|
}
|
|
79663
79914
|
}, timeoutMs);
|
|
79664
79915
|
}
|
|
@@ -79687,17 +79938,34 @@ var AgentSessionManager = class {
|
|
|
79687
79938
|
// src/services/conversation/cursor-recovery-controller.ts
|
|
79688
79939
|
var MAX_SIGNAL_DEATH_RETRIES = 1;
|
|
79689
79940
|
var MAX_STALL_RESUME_RETRIES = 3;
|
|
79941
|
+
var MAX_COLD_START_RESUME_RETRIES = 1;
|
|
79690
79942
|
var STALL_RESUME_BASE_DELAY_MS = 3e3;
|
|
79691
79943
|
var STALL_RESUME_BACKOFF_MULTIPLIER = 3;
|
|
79692
79944
|
function stallResumeDelayMs(attempt) {
|
|
79693
79945
|
return STALL_RESUME_BASE_DELAY_MS * STALL_RESUME_BACKOFF_MULTIPLIER ** (attempt - 1);
|
|
79694
79946
|
}
|
|
79695
79947
|
var MAX_CRASH_DEATH_RETRIES = 1;
|
|
79948
|
+
function decideStallResume(input) {
|
|
79949
|
+
if (input.state !== "running") return { kind: "decline" };
|
|
79950
|
+
if (input.agentSystem !== AGENT_SYSTEM_CURSOR) return { kind: "decline" };
|
|
79951
|
+
const isColdStart = input.phase === "first_step";
|
|
79952
|
+
const next = (isColdStart ? input.coldStartRetries : input.midRunRetries) + 1;
|
|
79953
|
+
const max = isColdStart ? MAX_COLD_START_RESUME_RETRIES : MAX_STALL_RESUME_RETRIES;
|
|
79954
|
+
return next > max ? { kind: "give_up", recoveryAttempts: next - 1 } : { kind: "arm", retries: next };
|
|
79955
|
+
}
|
|
79696
79956
|
var CursorRecoveryController = class {
|
|
79697
79957
|
#signalDeathRetries = 0;
|
|
79698
79958
|
#stallResumeRetries = 0;
|
|
79699
79959
|
#stallResumeArmed = false;
|
|
79700
79960
|
#crashDeathRetries = 0;
|
|
79961
|
+
/**
|
|
79962
|
+
* Consecutive silent resumes attempted for stalls in which the run never
|
|
79963
|
+
* produced a first step. Deliberately NOT cleared by `reset()` — see
|
|
79964
|
+
* `decideStallResume`. `noteFirstStep()` is its only reset.
|
|
79965
|
+
*/
|
|
79966
|
+
#coldStartResumeRetries = 0;
|
|
79967
|
+
/** Which budget armed the pending resume, so the backoff reads the right counter. */
|
|
79968
|
+
#armedPhase = null;
|
|
79701
79969
|
/**
|
|
79702
79970
|
* Pending timer for the settle-delay before an armed stall resume fires.
|
|
79703
79971
|
* Cleared at every call site that supersedes the in-flight resume (new
|
|
@@ -79723,14 +79991,37 @@ var CursorRecoveryController = class {
|
|
|
79723
79991
|
this.#stallResumeTimer = null;
|
|
79724
79992
|
}
|
|
79725
79993
|
}
|
|
79726
|
-
/**
|
|
79994
|
+
/**
|
|
79995
|
+
* Reset every turn-scoped budget. Call on each new user turn and on
|
|
79996
|
+
* turn_complete.
|
|
79997
|
+
*
|
|
79998
|
+
* `#coldStartResumeRetries` is deliberately excluded: it is scoped to "has
|
|
79999
|
+
* this session ever started a run", not to the current turn, and only
|
|
80000
|
+
* `noteFirstStep()` clears it. Adding it here would restore the #5298
|
|
80001
|
+
* livelock, where each human nudge bought another full futile round.
|
|
80002
|
+
*/
|
|
79727
80003
|
reset() {
|
|
79728
80004
|
this.cancelPendingResume();
|
|
79729
80005
|
this.#signalDeathRetries = 0;
|
|
79730
80006
|
this.#stallResumeRetries = 0;
|
|
79731
80007
|
this.#stallResumeArmed = false;
|
|
80008
|
+
this.#armedPhase = null;
|
|
79732
80009
|
this.#crashDeathRetries = 0;
|
|
79733
80010
|
}
|
|
80011
|
+
/**
|
|
80012
|
+
* Call on every `step_progress`. A completed step is the only evidence that
|
|
80013
|
+
* this session can actually run, so it is the only thing that may forgive
|
|
80014
|
+
* the cold-start budget. Cheap and idempotent — fires on every step.
|
|
80015
|
+
*/
|
|
80016
|
+
noteFirstStep() {
|
|
80017
|
+
if (this.#coldStartResumeRetries === 0) return;
|
|
80018
|
+
this.#deps.log({
|
|
80019
|
+
event: "thread_cursor_cold_start_budget_cleared",
|
|
80020
|
+
threadId: this.#deps.threadId,
|
|
80021
|
+
clearedRetries: this.#coldStartResumeRetries
|
|
80022
|
+
});
|
|
80023
|
+
this.#coldStartResumeRetries = 0;
|
|
80024
|
+
}
|
|
79734
80025
|
/**
|
|
79735
80026
|
* Call on `turn_complete` instead of `reset()`. A successful auto-recovery
|
|
79736
80027
|
* otherwise leaves no trace that the daemon self-healed rather than the
|
|
@@ -79762,30 +80053,43 @@ var CursorRecoveryController = class {
|
|
|
79762
80053
|
* (see `StallTimeoutOutcome`), letting the FSM distinguish "never
|
|
79763
80054
|
* attempted" from "tried N times".
|
|
79764
80055
|
*/
|
|
79765
|
-
onStallTimeout() {
|
|
79766
|
-
|
|
79767
|
-
|
|
79768
|
-
|
|
79769
|
-
|
|
79770
|
-
|
|
79771
|
-
|
|
79772
|
-
const recoveryAttempts = this.#stallResumeRetries - 1;
|
|
79773
|
-
this.#deps.log({
|
|
79774
|
-
event: "thread_cursor_stall_resume_cap_exceeded",
|
|
79775
|
-
threadId: this.#deps.threadId,
|
|
79776
|
-
retries: this.#stallResumeRetries,
|
|
79777
|
-
maxRetries: MAX_STALL_RESUME_RETRIES
|
|
79778
|
-
});
|
|
79779
|
-
this.#stallResumeRetries = 0;
|
|
79780
|
-
return { armed: false, recoveryAttempts };
|
|
79781
|
-
}
|
|
79782
|
-
this.#deps.log({
|
|
79783
|
-
event: "thread_cursor_stall_resume_armed",
|
|
79784
|
-
threadId: this.#deps.threadId,
|
|
79785
|
-
retries: this.#stallResumeRetries
|
|
80056
|
+
onStallTimeout(phase) {
|
|
80057
|
+
const decision = decideStallResume({
|
|
80058
|
+
state: this.#deps.getState(),
|
|
80059
|
+
agentSystem: this.#deps.getAgentSystem(),
|
|
80060
|
+
phase,
|
|
80061
|
+
midRunRetries: this.#stallResumeRetries,
|
|
80062
|
+
coldStartRetries: this.#coldStartResumeRetries
|
|
79786
80063
|
});
|
|
79787
|
-
|
|
79788
|
-
|
|
80064
|
+
const isColdStart = phase === "first_step";
|
|
80065
|
+
switch (decision.kind) {
|
|
80066
|
+
case "decline":
|
|
80067
|
+
return { armed: false, recoveryAttempts: 0, phase };
|
|
80068
|
+
case "give_up":
|
|
80069
|
+
this.#deps.log({
|
|
80070
|
+
event: "thread_cursor_stall_resume_cap_exceeded",
|
|
80071
|
+
threadId: this.#deps.threadId,
|
|
80072
|
+
phase,
|
|
80073
|
+
retries: decision.recoveryAttempts + 1,
|
|
80074
|
+
maxRetries: isColdStart ? MAX_COLD_START_RESUME_RETRIES : MAX_STALL_RESUME_RETRIES
|
|
80075
|
+
});
|
|
80076
|
+
this.#stallResumeRetries = 0;
|
|
80077
|
+
return { armed: false, recoveryAttempts: decision.recoveryAttempts, phase };
|
|
80078
|
+
case "arm":
|
|
80079
|
+
if (isColdStart) this.#coldStartResumeRetries = decision.retries;
|
|
80080
|
+
else this.#stallResumeRetries = decision.retries;
|
|
80081
|
+
this.#deps.log({
|
|
80082
|
+
event: "thread_cursor_stall_resume_armed",
|
|
80083
|
+
threadId: this.#deps.threadId,
|
|
80084
|
+
phase,
|
|
80085
|
+
retries: decision.retries
|
|
80086
|
+
});
|
|
80087
|
+
this.#stallResumeArmed = true;
|
|
80088
|
+
this.#armedPhase = phase;
|
|
80089
|
+
return { armed: true, recoveryAttempts: 0, phase };
|
|
80090
|
+
default:
|
|
80091
|
+
return assertNever(decision);
|
|
80092
|
+
}
|
|
79789
80093
|
}
|
|
79790
80094
|
/**
|
|
79791
80095
|
* Consume an armed stall resume on the `kill_runner` death. By now the FSM
|
|
@@ -79801,7 +80105,8 @@ var CursorRecoveryController = class {
|
|
|
79801
80105
|
if (!this.#stallResumeArmed) return false;
|
|
79802
80106
|
this.#stallResumeArmed = false;
|
|
79803
80107
|
this.cancelPendingResume();
|
|
79804
|
-
const attempt = this.#stallResumeRetries;
|
|
80108
|
+
const attempt = this.#armedPhase === "first_step" ? this.#coldStartResumeRetries : this.#stallResumeRetries;
|
|
80109
|
+
this.#armedPhase = null;
|
|
79805
80110
|
const delayMs = stallResumeDelayMs(attempt);
|
|
79806
80111
|
this.#deps.log({
|
|
79807
80112
|
event: "thread_cursor_stall_auto_resume_scheduled",
|
|
@@ -80917,7 +81222,7 @@ var Thread = class {
|
|
|
80917
81222
|
onClose: () => this.#handleClose(),
|
|
80918
81223
|
onForceKill: () => this.#handleForceKill(),
|
|
80919
81224
|
onKillRunner: () => this.#handleForceKill(),
|
|
80920
|
-
onStallTimeout: () => this.#cursorRecovery.onStallTimeout(),
|
|
81225
|
+
onStallTimeout: (phase) => this.#cursorRecovery.onStallTimeout(phase),
|
|
80921
81226
|
log: config.log,
|
|
80922
81227
|
onSessionStateChange: (newState, prevState) => this.#notifyStateChange(newState, prevState),
|
|
80923
81228
|
stallTimeoutMs: config.stallTimeoutMs ?? null,
|
|
@@ -82068,6 +82373,7 @@ var Thread = class {
|
|
|
82068
82373
|
break;
|
|
82069
82374
|
case "step_progress":
|
|
82070
82375
|
this.#manager.notifyStepProgress();
|
|
82376
|
+
this.#cursorRecovery.noteFirstStep();
|
|
82071
82377
|
break;
|
|
82072
82378
|
/** Handled in #enqueueSubprocessEvent (off-queue heartbeat); unreachable here. */
|
|
82073
82379
|
case "stream_activity":
|
|
@@ -87723,7 +88029,7 @@ function computeTelemetryTokenContext(params) {
|
|
|
87723
88029
|
const usedTokens = params.lastWindowOccupancyEvent ? resolveLiveContextTokens(params.lastWindowOccupancyEvent).liveContextTokens : void 0;
|
|
87724
88030
|
const window = resolveContextWindow(params.currentModel, 0);
|
|
87725
88031
|
const windowTokens = window > 0 ? window : void 0;
|
|
87726
|
-
const fraction = resolveCompactionThreshold({
|
|
88032
|
+
const { fraction } = resolveCompactionThreshold({
|
|
87727
88033
|
taskPrefs: params.taskPrefs,
|
|
87728
88034
|
runtimePrefs: params.runtimePrefs,
|
|
87729
88035
|
runtimeDefaultFraction: params.compactionThreshold,
|
|
@@ -87861,7 +88167,7 @@ function decideByRatio(params) {
|
|
|
87861
88167
|
}
|
|
87862
88168
|
if (params.isCompacting || params.hasPendingTimer) return { kind: "noop" };
|
|
87863
88169
|
if (params.threshold !== null && usageRatio >= params.threshold) {
|
|
87864
|
-
return { kind: "schedule", delayMs: AUTO_COMPACTION_CANCEL_DELAY_MS };
|
|
88170
|
+
return { kind: "schedule", delayMs: AUTO_COMPACTION_CANCEL_DELAY_MS, decisionPath: "ratio" };
|
|
87865
88171
|
}
|
|
87866
88172
|
return { kind: "noop" };
|
|
87867
88173
|
}
|
|
@@ -87871,7 +88177,11 @@ function decideByMessageCount(params) {
|
|
|
87871
88177
|
if (params.liveMessageCount === void 0) return { kind: "noop" };
|
|
87872
88178
|
const baseline = params.backstopBaselineSeq ?? 0;
|
|
87873
88179
|
if (params.liveMessageCount - baseline >= AUTO_COMPACTION_MESSAGE_CEILING) {
|
|
87874
|
-
return {
|
|
88180
|
+
return {
|
|
88181
|
+
kind: "schedule",
|
|
88182
|
+
delayMs: AUTO_COMPACTION_CANCEL_DELAY_MS,
|
|
88183
|
+
decisionPath: "message_count"
|
|
88184
|
+
};
|
|
87875
88185
|
}
|
|
87876
88186
|
return { kind: "noop" };
|
|
87877
88187
|
}
|
|
@@ -88435,8 +88745,14 @@ function priorOccupancyFromTurnStats(prior) {
|
|
|
88435
88745
|
}
|
|
88436
88746
|
function handleAutoCompactionSnapshot(ctx, event, contextWindow, occupancyStale = false) {
|
|
88437
88747
|
const { liveContextTokens } = resolveLiveContextTokens(event);
|
|
88748
|
+
const resolution = resolveCompactionThreshold({
|
|
88749
|
+
taskPrefs: ctx.getTaskPrefs(),
|
|
88750
|
+
runtimePrefs: ctx.getCompactionByRuntimePrefs(),
|
|
88751
|
+
runtimeDefaultFraction: ctx.getCompactionThreshold(),
|
|
88752
|
+
maxTokens: contextWindow
|
|
88753
|
+
});
|
|
88438
88754
|
const decision = decideAutoCompaction({
|
|
88439
|
-
threshold:
|
|
88755
|
+
threshold: resolution.fraction,
|
|
88440
88756
|
liveContextTokens,
|
|
88441
88757
|
contextWindow,
|
|
88442
88758
|
isCompacting: ctx.isCompactionActive(),
|
|
@@ -88461,7 +88777,13 @@ function handleAutoCompactionSnapshot(ctx, event, contextWindow, occupancyStale
|
|
|
88461
88777
|
case "schedule":
|
|
88462
88778
|
ctx.setBackstopBaselineSeq(ctx.getLiveMessageCount());
|
|
88463
88779
|
ctx.dispatchCompactionEvent({ kind: "threshold_reached", now: Date.now() });
|
|
88464
|
-
ctx.scheduleAutoCompaction(decision.delayMs, liveContextTokens, contextWindow
|
|
88780
|
+
ctx.scheduleAutoCompaction(decision.delayMs, liveContextTokens, contextWindow, {
|
|
88781
|
+
effectiveThreshold: resolution.fraction,
|
|
88782
|
+
thresholdTier: resolution.tier,
|
|
88783
|
+
runtimeDefaultFraction: ctx.getCompactionThreshold(),
|
|
88784
|
+
decisionPath: decision.decisionPath,
|
|
88785
|
+
occupancyStale
|
|
88786
|
+
});
|
|
88465
88787
|
return;
|
|
88466
88788
|
default:
|
|
88467
88789
|
assertNever(decision);
|
|
@@ -88491,14 +88813,6 @@ function logCumulativeFallback(ctx, liveContextTokens) {
|
|
|
88491
88813
|
});
|
|
88492
88814
|
}
|
|
88493
88815
|
}
|
|
88494
|
-
function resolveEffectiveThresholdFraction(ctx, contextWindow) {
|
|
88495
|
-
return resolveCompactionThreshold({
|
|
88496
|
-
taskPrefs: ctx.getTaskPrefs(),
|
|
88497
|
-
runtimePrefs: ctx.getCompactionByRuntimePrefs(),
|
|
88498
|
-
runtimeDefaultFraction: ctx.getCompactionThreshold(),
|
|
88499
|
-
maxTokens: contextWindow
|
|
88500
|
-
});
|
|
88501
|
-
}
|
|
88502
88816
|
|
|
88503
88817
|
// src/services/task/orchestrator/compaction-controller.ts
|
|
88504
88818
|
var CompactionController = class {
|
|
@@ -88963,7 +89277,7 @@ var CompactionController = class {
|
|
|
88963
89277
|
* Schedule the auto-compaction timer (called by the auto-compaction
|
|
88964
89278
|
* decision from task-event-dispatch via the eventCtx).
|
|
88965
89279
|
*/
|
|
88966
|
-
scheduleAutoCompaction(delayMs, liveContextTokens, contextWindow) {
|
|
89280
|
+
scheduleAutoCompaction(delayMs, liveContextTokens, contextWindow, decisionContext) {
|
|
88967
89281
|
if (this.#pendingAutoCompactionTimer !== null) return;
|
|
88968
89282
|
const cancelDeadline = Date.now() + delayMs;
|
|
88969
89283
|
this.#pendingAutoCompactionTimer = setTimeout(() => {
|
|
@@ -88980,7 +89294,11 @@ var CompactionController = class {
|
|
|
88980
89294
|
this.#deps.log({
|
|
88981
89295
|
event: "auto_compaction_pending",
|
|
88982
89296
|
taskId: this.#deps.taskId,
|
|
88983
|
-
|
|
89297
|
+
effectiveThreshold: decisionContext.effectiveThreshold,
|
|
89298
|
+
thresholdTier: decisionContext.thresholdTier,
|
|
89299
|
+
runtimeDefaultFraction: decisionContext.runtimeDefaultFraction,
|
|
89300
|
+
decisionPath: decisionContext.decisionPath,
|
|
89301
|
+
occupancyStale: decisionContext.occupancyStale,
|
|
88984
89302
|
liveContextTokens,
|
|
88985
89303
|
contextWindow,
|
|
88986
89304
|
cancelDeadline
|
|
@@ -94523,7 +94841,12 @@ var Task = class {
|
|
|
94523
94841
|
setAutoCompactionSuppressedUntilBelow: (value) => {
|
|
94524
94842
|
this.#compaction.setAutoCompactionSuppressedUntilBelow(value);
|
|
94525
94843
|
},
|
|
94526
|
-
scheduleAutoCompaction: (delayMs, liveContextTokens, contextWindow) => this.#compaction.scheduleAutoCompaction(
|
|
94844
|
+
scheduleAutoCompaction: (delayMs, liveContextTokens, contextWindow, decisionContext) => this.#compaction.scheduleAutoCompaction(
|
|
94845
|
+
delayMs,
|
|
94846
|
+
liveContextTokens,
|
|
94847
|
+
contextWindow,
|
|
94848
|
+
decisionContext
|
|
94849
|
+
),
|
|
94527
94850
|
getCompactionThreshold: () => this.#compaction.getCompactionThreshold(),
|
|
94528
94851
|
getCompactionByRuntimePrefs: () => this.#compaction.getCompactionByRuntimePrefs(),
|
|
94529
94852
|
getTaskPrefs: () => this.#deps.taskStateStore.getTaskSync(this.#taskIdRef.current)?.composerSettings?.compactionPrefs ?? null,
|
|
@@ -103626,4 +103949,4 @@ export {
|
|
|
103626
103949
|
decideWorkspaceScope,
|
|
103627
103950
|
serve
|
|
103628
103951
|
};
|
|
103629
|
-
//# sourceMappingURL=serve-
|
|
103952
|
+
//# sourceMappingURL=serve-JSMTUTQZ.js.map
|