@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.
@@ -12,7 +12,7 @@ import {
12
12
  guardedSubscribe,
13
13
  makeInitialAttemptState,
14
14
  shutdownFileWatcherGuard
15
- } from "./chunk-IRS6HYMU.js";
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-XNH6FVIX.js";
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.25.0" : "unknown";
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 fromPrefs = prefsToFraction(taskPrefs, maxTokens);
21285
- if (fromPrefs !== void 0) return fromPrefs;
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
- transport: opts.transport,
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 opts.transport.handleRequest(opts.req, opts.res);
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 toExposure(cached);
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, toExposure(v2)]));
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 mcpServer = buildLocalHttpProxyMcpServer({
56118
- serverName,
56119
- lookupClient: () => this.#servers.get(serverName)?.client ?? null,
56120
- lookupTools: () => this.#servers.get(serverName)?.tools ?? [],
56121
- log: this.#deps.log,
56122
- onAuthError: (sn) => {
56123
- this.#connectErrors.set(sn, "auth");
56124
- this.#fireOnAuthRequired(sn);
56125
- }
56126
- });
56127
- const transport = new StreamableHTTPServerTransport({
56128
- sessionIdGenerator: () => randomUUID13()
56129
- });
56130
- await mcpServer.connect(transport);
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
- transport,
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
- transport,
56146
- mcpServer
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 toExposure(binding);
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 McpServer(
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(ListToolsRequestSchema, async () => ({
56556
+ lowLevelServer.setRequestHandler(ListToolsRequestSchema2, async () => ({
56266
56557
  tools: [...tools]
56267
56558
  }));
56268
- lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
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
- status: McpServerStartupStatusSchema.catch("ready")
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 ListMcpServerStatusParamsSchema = external_exports2.object({ cursor: external_exports2.string().nullish(), limit: external_exports2.number().int().optional() }).passthrough();
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.#client.listMcpServerStatus({ limit: 100 }),
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.data.filter((s2) => s2.name !== HARNESS_SERVER_NAME).map((s2) => ({ name: s2.name, status: s2.status })) : [];
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 McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
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 McpServer2(
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(buildStallGiveUpMessage(event.recoveryAttempts), "sdk_error", "run_stall_timeout");
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?.() ?? { armed: false, recoveryAttempts: 0 };
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({ type: "run_stall_timeout", recoveryArmed, recoveryAttempts });
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
- /** Reset every budget. Call on each new user turn and on turn_complete. */
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
- if (this.#deps.getState() !== "running") return { armed: false, recoveryAttempts: 0 };
79767
- if (this.#deps.getAgentSystem() !== AGENT_SYSTEM_CURSOR) {
79768
- return { armed: false, recoveryAttempts: 0 };
79769
- }
79770
- this.#stallResumeRetries++;
79771
- if (this.#stallResumeRetries > MAX_STALL_RESUME_RETRIES) {
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
- this.#stallResumeArmed = true;
79788
- return { armed: true, recoveryAttempts: 0 };
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 { kind: "schedule", delayMs: AUTO_COMPACTION_CANCEL_DELAY_MS };
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: resolveEffectiveThresholdFraction(ctx, contextWindow),
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
- threshold: this.#compactionThreshold,
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(delayMs, liveContextTokens, contextWindow),
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-HTQMCHRP.js.map
103952
+ //# sourceMappingURL=serve-JSMTUTQZ.js.map