@schoolai/shipyard 3.24.0 → 3.25.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.
Files changed (29) hide show
  1. package/dist/capability-detector-worker.js +3 -3
  2. package/dist/{chunk-NG355H2L.js → chunk-BBKZIEKF.js} +68 -1
  3. package/dist/chunk-BBKZIEKF.js.map +1 -0
  4. package/dist/{chunk-BGJ6QJNS.js → chunk-FC3HWVYQ.js} +46 -1
  5. package/dist/{chunk-BGJ6QJNS.js.map → chunk-FC3HWVYQ.js.map} +1 -1
  6. package/dist/{chunk-JZTUQVHQ.js → chunk-IRS6HYMU.js} +2 -2
  7. package/dist/{chunk-UK3IMOSP.js → chunk-VQCBF7X5.js} +71 -7
  8. package/dist/chunk-VQCBF7X5.js.map +1 -0
  9. package/dist/{chunk-TU3MSY2I.js → chunk-X4IWMBWY.js} +2 -2
  10. package/dist/{chunk-DDC5OQOC.js → chunk-XNH6FVIX.js} +3 -3
  11. package/dist/{chunk-BN54GQR3.js → chunk-YTYPMVS4.js} +3 -53
  12. package/dist/chunk-YTYPMVS4.js.map +1 -0
  13. package/dist/cursor-runner.js +8 -4
  14. package/dist/cursor-runner.js.map +1 -1
  15. package/dist/electron-utility.js +2 -2
  16. package/dist/index.js +3 -3
  17. package/dist/{plan-backfill-XYOUK2DY.js → plan-backfill-SDYNTKUX.js} +3 -3
  18. package/dist/{serve-RWFBSQIE.js → serve-HTQMCHRP.js} +334 -188
  19. package/dist/{serve-RWFBSQIE.js.map → serve-HTQMCHRP.js.map} +1 -1
  20. package/dist/{start-ZV22UPMC.js → start-HOICUTYV.js} +4 -4
  21. package/package.json +1 -1
  22. package/dist/chunk-BN54GQR3.js.map +0 -1
  23. package/dist/chunk-NG355H2L.js.map +0 -1
  24. package/dist/chunk-UK3IMOSP.js.map +0 -1
  25. /package/dist/{chunk-JZTUQVHQ.js.map → chunk-IRS6HYMU.js.map} +0 -0
  26. /package/dist/{chunk-TU3MSY2I.js.map → chunk-X4IWMBWY.js.map} +0 -0
  27. /package/dist/{chunk-DDC5OQOC.js.map → chunk-XNH6FVIX.js.map} +0 -0
  28. /package/dist/{plan-backfill-XYOUK2DY.js.map → plan-backfill-SDYNTKUX.js.map} +0 -0
  29. /package/dist/{start-ZV22UPMC.js.map → start-HOICUTYV.js.map} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  guardedSubscribe,
13
13
  makeInitialAttemptState,
14
14
  shutdownFileWatcherGuard
15
- } from "./chunk-JZTUQVHQ.js";
15
+ } from "./chunk-IRS6HYMU.js";
16
16
  import {
17
17
  TRAILER_SCHEMA_VERSION,
18
18
  appendTrailerToMessage,
@@ -39,13 +39,13 @@ import {
39
39
  startPlanBackfillForDaemon,
40
40
  sync,
41
41
  validatePeerId
42
- } from "./chunk-UK3IMOSP.js";
42
+ } from "./chunk-VQCBF7X5.js";
43
43
  import {
44
44
  loadAuthToken
45
45
  } from "./chunk-2UE3TNY7.js";
46
46
  import {
47
47
  getDaemonVersion
48
- } from "./chunk-DDC5OQOC.js";
48
+ } from "./chunk-XNH6FVIX.js";
49
49
  import {
50
50
  collectPriorCrashDetail,
51
51
  getRecentWasmPanicMessages
@@ -178,7 +178,7 @@ import {
178
178
  unresolveReviewThread,
179
179
  withLabelAsync,
180
180
  writeCursorApiKeyToVault
181
- } from "./chunk-BN54GQR3.js";
181
+ } from "./chunk-YTYPMVS4.js";
182
182
  import {
183
183
  buildNodeSpawnEnv,
184
184
  nodeSpawnEnvOverlay,
@@ -233,15 +233,16 @@ import {
233
233
  createOneShotPool
234
234
  } from "./chunk-ZFKJAYAN.js";
235
235
  import {
236
+ buildCursorMcpConfigFromSet,
236
237
  parseRunnerToDaemon,
237
238
  shouldUseCursorFastModePricing
238
- } from "./chunk-NG355H2L.js";
239
+ } from "./chunk-BBKZIEKF.js";
239
240
  import {
240
241
  XML_TAGS,
241
242
  createAssetResolver,
242
243
  toSdkContent,
243
244
  toSdkPermissionMode
244
- } from "./chunk-TU3MSY2I.js";
245
+ } from "./chunk-X4IWMBWY.js";
245
246
  import {
246
247
  AGENT_IDS,
247
248
  AGENT_SYSTEM_CLAUDE_CODE,
@@ -446,7 +447,7 @@ import {
446
447
  unsafeAssertSourceId,
447
448
  vizFileExtension,
448
449
  withPreferredRuntimeAuthMethod
449
- } from "./chunk-BGJ6QJNS.js";
450
+ } from "./chunk-FC3HWVYQ.js";
450
451
  import {
451
452
  AuthGitHubCallbackRequestSchema,
452
453
  AuthGitHubCallbackResponseSchema,
@@ -6021,7 +6022,7 @@ function nanoid(size = 21) {
6021
6022
  }
6022
6023
 
6023
6024
  // src/services/bootstrap/signaling.ts
6024
- var DAEMON_NPM_VERSION = true ? "3.24.0" : "unknown";
6025
+ var DAEMON_NPM_VERSION = true ? "3.25.0" : "unknown";
6025
6026
  function createDaemonSignaling(config) {
6026
6027
  const agentId = config.agentId ?? nanoid();
6027
6028
  function send(msg) {
@@ -33770,8 +33771,8 @@ function getCursorHookShimPath() {
33770
33771
  function verifyCursorHookShimPresent(path5) {
33771
33772
  const resolved = path5 ?? getCursorHookShimPath();
33772
33773
  try {
33773
- const stat19 = statSync4(resolved);
33774
- if (!stat19.isFile()) return null;
33774
+ const stat20 = statSync4(resolved);
33775
+ if (!stat20.isFile()) return null;
33775
33776
  return { path: resolved };
33776
33777
  } catch {
33777
33778
  return null;
@@ -34362,7 +34363,8 @@ function parseQueueKey(queueKey) {
34362
34363
  // src/services/task/manager/task-manager-composer-settings.ts
34363
34364
  var DEPRECATED_COMPOSER_MODEL_FALLBACKS = {
34364
34365
  "gpt-5.3-codex": "gpt-5.6-sol",
34365
- "gpt-5.5": "gpt-5.6-sol"
34366
+ "gpt-5.5": "gpt-5.6-sol",
34367
+ "grok-4.5": "grok-4.6"
34366
34368
  };
34367
34369
  function normalizeComposerModel(model) {
34368
34370
  return DEPRECATED_COMPOSER_MODEL_FALLBACKS[model] ?? model;
@@ -52554,61 +52556,6 @@ function decideBridgeMaterialization(input) {
52554
52556
  }
52555
52557
  }
52556
52558
 
52557
- // src/services/mcp/cursor-mcp-config.ts
52558
- function isRecord8(value) {
52559
- return typeof value === "object" && value !== null && !Array.isArray(value);
52560
- }
52561
- function copyNonEmptyRecord(target, key, value) {
52562
- if (isRecord8(value) && Object.keys(value).length > 0) target[key] = value;
52563
- }
52564
- function buildCursorStdioEntry(value) {
52565
- if (typeof value.command !== "string" || value.command.length === 0) return null;
52566
- const entry = { type: "stdio", command: value.command };
52567
- if (Array.isArray(value.args) && value.args.length > 0) entry.args = value.args;
52568
- copyNonEmptyRecord(entry, "env", value.env);
52569
- if (typeof value.cwd === "string" && value.cwd.length > 0) entry.cwd = value.cwd;
52570
- return entry;
52571
- }
52572
- function buildCursorHttpEntry(value) {
52573
- if (typeof value.url !== "string" || value.url.length === 0) return null;
52574
- const transport = value.type === "sse" ? "sse" : value.type === "http" ? "http" : "http";
52575
- const entry = { type: transport, url: value.url };
52576
- const headers = value.headers ?? value.http_headers;
52577
- copyNonEmptyRecord(entry, "headers", headers);
52578
- return entry;
52579
- }
52580
- function getUnsupportedCursorMcpReason(value) {
52581
- if (value.type === "sdk" || "instance" in value) return "unsupported_sdk_instance";
52582
- return null;
52583
- }
52584
- function buildCursorMcpEntry(value) {
52585
- if (!isRecord8(value)) return { kind: "skip", reason: "not_object" };
52586
- const unsupportedReason = getUnsupportedCursorMcpReason(value);
52587
- if (unsupportedReason) return { kind: "skip", reason: unsupportedReason };
52588
- if (typeof value.command === "string") {
52589
- const stdio = buildCursorStdioEntry(value);
52590
- return stdio ? { kind: "ok", config: stdio } : { kind: "skip", reason: "missing_command" };
52591
- }
52592
- if (typeof value.url === "string") {
52593
- const http2 = buildCursorHttpEntry(value);
52594
- return http2 ? { kind: "ok", config: http2 } : { kind: "skip", reason: "missing_url" };
52595
- }
52596
- return { kind: "skip", reason: "missing_command_or_url" };
52597
- }
52598
- function buildCursorMcpConfigFromSet(servers) {
52599
- const result = {};
52600
- const skipped = [];
52601
- for (const [name, config] of Object.entries(servers)) {
52602
- const entry = buildCursorMcpEntry(config);
52603
- if (entry.kind === "ok") {
52604
- result[name] = entry.config;
52605
- } else {
52606
- skipped.push({ name, reason: entry.reason });
52607
- }
52608
- }
52609
- return { servers: result, skipped };
52610
- }
52611
-
52612
52559
  // src/services/mcp/oauth/auth-status.ts
52613
52560
  var UNSUPPORTED_DCR_PATTERNS = [
52614
52561
  /incompatible auth server/i,
@@ -55232,8 +55179,7 @@ function findAvailablePort() {
55232
55179
  }
55233
55180
 
55234
55181
  // src/shared/mcp/proxy-server.ts
55235
- import { randomUUID as randomUUID13, timingSafeEqual as timingSafeEqual2 } from "crypto";
55236
- import { createServer as createServer3 } from "http";
55182
+ import { randomUUID as randomUUID13 } from "crypto";
55237
55183
  import { auth as auth2, UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
55238
55184
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
55239
55185
  import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -55249,6 +55195,10 @@ function classifyConnectFailureLevel(errorKind) {
55249
55195
  return errorKind === "auth" ? "info" : "error";
55250
55196
  }
55251
55197
 
55198
+ // src/shared/mcp/proxy-loopback-listener.ts
55199
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
55200
+ import { createServer as createServer3 } from "http";
55201
+
55252
55202
  // src/shared/mcp/json-rpc-http-error.ts
55253
55203
  var JSON_CONTENT_TYPE = "application/json";
55254
55204
  var DEFAULT_MAX_ID_BODY_BYTES = 1024 * 1024;
@@ -55376,6 +55326,117 @@ function caseInsensitiveDelete(obj, key) {
55376
55326
  }
55377
55327
  }
55378
55328
 
55329
+ // src/shared/mcp/proxy-loopback-listener.ts
55330
+ async function startLoopbackHttpServer(opts) {
55331
+ const httpServer = createServer3((req, res) => {
55332
+ void handleProxyHttpRequest({
55333
+ req,
55334
+ res,
55335
+ bearerToken: opts.bearerToken,
55336
+ transport: opts.transport,
55337
+ log: opts.log,
55338
+ serverName: opts.serverName
55339
+ }).catch((err3) => {
55340
+ const message = err3 instanceof Error ? err3.message : String(err3);
55341
+ opts.log({
55342
+ event: "mcp_proxy_local_http_request_error",
55343
+ serverName: opts.serverName,
55344
+ error: message
55345
+ });
55346
+ if (!res.headersSent) {
55347
+ sendJsonRpcHttpError(
55348
+ res,
55349
+ 500,
55350
+ MCP_PROXY_JSON_RPC_INTERNAL,
55351
+ "internal_error",
55352
+ FALLBACK_REQUEST_ID
55353
+ );
55354
+ }
55355
+ });
55356
+ });
55357
+ await new Promise((resolve12, reject) => {
55358
+ httpServer.once("error", reject);
55359
+ httpServer.listen(0, "127.0.0.1", () => {
55360
+ httpServer.off("error", reject);
55361
+ resolve12();
55362
+ });
55363
+ });
55364
+ const addr = httpServer.address();
55365
+ if (addr === null || typeof addr === "string") {
55366
+ httpServer.close();
55367
+ throw new Error("Failed to bind MCP proxy HTTP server: missing AddressInfo");
55368
+ }
55369
+ return { httpServer, host: addr.address, port: addr.port };
55370
+ }
55371
+ async function handleProxyHttpRequest(opts) {
55372
+ installResponseIdSanitizer(opts.res, FALLBACK_REQUEST_ID);
55373
+ const url = opts.req.url ?? "/";
55374
+ if (url !== "/mcp") {
55375
+ opts.log({
55376
+ event: "mcp_proxy_local_http_rejected",
55377
+ serverName: opts.serverName,
55378
+ reason: "not_found",
55379
+ status: 404,
55380
+ method: opts.req.method ?? "unknown",
55381
+ path: url
55382
+ });
55383
+ await respondProxyError(opts.req, opts.res, 404, MCP_PROXY_JSON_RPC_NOT_FOUND, "not_found");
55384
+ return;
55385
+ }
55386
+ const authHeader = opts.req.headers.authorization;
55387
+ if (!authHeader?.toLowerCase().startsWith("bearer ")) {
55388
+ opts.log({
55389
+ event: "mcp_proxy_local_http_rejected",
55390
+ serverName: opts.serverName,
55391
+ reason: authHeader === void 0 ? "bearer_absent" : "bearer_malformed",
55392
+ status: 401,
55393
+ method: opts.req.method ?? "unknown"
55394
+ });
55395
+ await respondProxyError(
55396
+ opts.req,
55397
+ opts.res,
55398
+ 401,
55399
+ MCP_PROXY_JSON_RPC_UNAUTHORIZED,
55400
+ "unauthorized"
55401
+ );
55402
+ return;
55403
+ }
55404
+ const provided = authHeader.slice("bearer ".length).trim();
55405
+ if (!timingSafeStringEquals(provided, opts.bearerToken)) {
55406
+ opts.log({
55407
+ event: "mcp_proxy_local_http_rejected",
55408
+ serverName: opts.serverName,
55409
+ reason: "bearer_mismatch",
55410
+ status: 401,
55411
+ method: opts.req.method ?? "unknown",
55412
+ providedLength: provided.length,
55413
+ expectedLength: opts.bearerToken.length,
55414
+ sameShape: provided.length === opts.bearerToken.length
55415
+ });
55416
+ await respondProxyError(
55417
+ opts.req,
55418
+ opts.res,
55419
+ 401,
55420
+ MCP_PROXY_JSON_RPC_UNAUTHORIZED,
55421
+ "unauthorized"
55422
+ );
55423
+ return;
55424
+ }
55425
+ await opts.transport.handleRequest(opts.req, opts.res);
55426
+ }
55427
+ var MCP_PROXY_JSON_RPC_INTERNAL = -32603;
55428
+ var MCP_PROXY_JSON_RPC_UNAUTHORIZED = -32001;
55429
+ var MCP_PROXY_JSON_RPC_NOT_FOUND = -32004;
55430
+ async function respondProxyError(req, res, status, code, message) {
55431
+ sendJsonRpcHttpError(res, status, code, message, await readRequestId(req));
55432
+ }
55433
+ function timingSafeStringEquals(provided, expected) {
55434
+ const expectedBuf = Buffer.from(expected, "utf8");
55435
+ const providedBuf = Buffer.from(provided, "utf8");
55436
+ if (providedBuf.length !== expectedBuf.length) return false;
55437
+ return timingSafeEqual2(providedBuf, expectedBuf);
55438
+ }
55439
+
55379
55440
  // src/shared/mcp/proxy-server.ts
55380
55441
  var GATEWAY_BASE_URL = "https://mcp-proxy.anthropic.com/v1/mcp";
55381
55442
  var GATEWAY_BETA_HEADER = "mcp-servers-2025-12-04";
@@ -56291,88 +56352,6 @@ function buildLocalHttpProxyMcpServer(opts) {
56291
56352
  });
56292
56353
  return mcpServer;
56293
56354
  }
56294
- async function startLoopbackHttpServer(opts) {
56295
- const httpServer = createServer3((req, res) => {
56296
- void handleProxyHttpRequest({
56297
- req,
56298
- res,
56299
- bearerToken: opts.bearerToken,
56300
- transport: opts.transport
56301
- }).catch((err3) => {
56302
- const message = err3 instanceof Error ? err3.message : String(err3);
56303
- opts.log({
56304
- event: "mcp_proxy_local_http_request_error",
56305
- serverName: opts.serverName,
56306
- error: message
56307
- });
56308
- if (!res.headersSent) {
56309
- sendJsonRpcHttpError(
56310
- res,
56311
- 500,
56312
- MCP_PROXY_JSON_RPC_INTERNAL,
56313
- "internal_error",
56314
- FALLBACK_REQUEST_ID
56315
- );
56316
- }
56317
- });
56318
- });
56319
- await new Promise((resolve12, reject) => {
56320
- httpServer.once("error", reject);
56321
- httpServer.listen(0, "127.0.0.1", () => {
56322
- httpServer.off("error", reject);
56323
- resolve12();
56324
- });
56325
- });
56326
- const addr = httpServer.address();
56327
- if (addr === null || typeof addr === "string") {
56328
- httpServer.close();
56329
- throw new Error("Failed to bind MCP proxy HTTP server: missing AddressInfo");
56330
- }
56331
- return { httpServer, host: addr.address, port: addr.port };
56332
- }
56333
- async function handleProxyHttpRequest(opts) {
56334
- installResponseIdSanitizer(opts.res, FALLBACK_REQUEST_ID);
56335
- const url = opts.req.url ?? "/";
56336
- if (url !== "/mcp") {
56337
- await respondProxyError(opts.req, opts.res, 404, MCP_PROXY_JSON_RPC_NOT_FOUND, "not_found");
56338
- return;
56339
- }
56340
- const authHeader = opts.req.headers.authorization;
56341
- if (!authHeader?.toLowerCase().startsWith("bearer ")) {
56342
- await respondProxyError(
56343
- opts.req,
56344
- opts.res,
56345
- 401,
56346
- MCP_PROXY_JSON_RPC_UNAUTHORIZED,
56347
- "unauthorized"
56348
- );
56349
- return;
56350
- }
56351
- const provided = authHeader.slice("bearer ".length).trim();
56352
- if (!timingSafeStringEquals(provided, opts.bearerToken)) {
56353
- await respondProxyError(
56354
- opts.req,
56355
- opts.res,
56356
- 401,
56357
- MCP_PROXY_JSON_RPC_UNAUTHORIZED,
56358
- "unauthorized"
56359
- );
56360
- return;
56361
- }
56362
- await opts.transport.handleRequest(opts.req, opts.res);
56363
- }
56364
- var MCP_PROXY_JSON_RPC_INTERNAL = -32603;
56365
- var MCP_PROXY_JSON_RPC_UNAUTHORIZED = -32001;
56366
- var MCP_PROXY_JSON_RPC_NOT_FOUND = -32004;
56367
- async function respondProxyError(req, res, status, code, message) {
56368
- sendJsonRpcHttpError(res, status, code, message, await readRequestId(req));
56369
- }
56370
- function timingSafeStringEquals(provided, expected) {
56371
- const expectedBuf = Buffer.from(expected, "utf8");
56372
- const providedBuf = Buffer.from(provided, "utf8");
56373
- if (providedBuf.length !== expectedBuf.length) return false;
56374
- return timingSafeEqual2(providedBuf, expectedBuf);
56375
- }
56376
56355
  async function disposeLocalHttpBinding(binding, log) {
56377
56356
  try {
56378
56357
  await binding.transport.close();
@@ -64932,7 +64911,7 @@ function buildCodexMcpConfigFromSet(servers) {
64932
64911
  return { servers: result, skipped };
64933
64912
  }
64934
64913
  function buildCodexMcpEntry(value) {
64935
- if (!isRecord9(value)) return { kind: "skip", reason: "not_object" };
64914
+ if (!isRecord8(value)) return { kind: "skip", reason: "not_object" };
64936
64915
  const unsupportedReason = getUnsupportedCodexMcpReason(value);
64937
64916
  if (unsupportedReason) return { kind: "skip", reason: unsupportedReason };
64938
64917
  if (typeof value.command === "string") {
@@ -64951,7 +64930,7 @@ function getUnsupportedCodexMcpReason(value) {
64951
64930
  function buildCodexStdioMcpEntry(value) {
64952
64931
  const entry = { command: value.command };
64953
64932
  if (Array.isArray(value.args) && value.args.length > 0) entry.args = value.args;
64954
- copyNonEmptyRecord2(entry, "env", value.env);
64933
+ copyNonEmptyRecord(entry, "env", value.env);
64955
64934
  return entry;
64956
64935
  }
64957
64936
  function buildCodexHttpMcpEntry(value) {
@@ -64959,14 +64938,14 @@ function buildCodexHttpMcpEntry(value) {
64959
64938
  if (typeof value.bearer_token_env_var === "string") {
64960
64939
  entry.bearer_token_env_var = value.bearer_token_env_var;
64961
64940
  }
64962
- copyNonEmptyRecord2(entry, "http_headers", value.http_headers ?? value.headers);
64963
- copyNonEmptyRecord2(entry, "env_http_headers", value.env_http_headers);
64941
+ copyNonEmptyRecord(entry, "http_headers", value.http_headers ?? value.headers);
64942
+ copyNonEmptyRecord(entry, "env_http_headers", value.env_http_headers);
64964
64943
  return entry;
64965
64944
  }
64966
- function copyNonEmptyRecord2(target, key, value) {
64967
- if (isRecord9(value) && Object.keys(value).length > 0) target[key] = value;
64945
+ function copyNonEmptyRecord(target, key, value) {
64946
+ if (isRecord8(value) && Object.keys(value).length > 0) target[key] = value;
64968
64947
  }
64969
- function isRecord9(value) {
64948
+ function isRecord8(value) {
64970
64949
  return typeof value === "object" && value !== null && !Array.isArray(value);
64971
64950
  }
64972
64951
  function resolveCodexSpawnPolicy(opts) {
@@ -67437,7 +67416,7 @@ function buildCodexSpawnArgs(args) {
67437
67416
  stripEnv: args.stripEnv
67438
67417
  };
67439
67418
  }
67440
- function isRecord10(value) {
67419
+ function isRecord9(value) {
67441
67420
  return value !== null && typeof value === "object" && !Array.isArray(value);
67442
67421
  }
67443
67422
  function partitionAnnotationsByCacheBucket(records) {
@@ -69002,7 +68981,7 @@ function createVizPreviewRegistry(deps) {
69002
68981
  }
69003
68982
  const previewLifecycleSubs = /* @__PURE__ */ new Map();
69004
68983
  function isLocalPreviewElement(dataField, selfUserId) {
69005
- if (!isRecord10(dataField)) return false;
68984
+ if (!isRecord9(dataField)) return false;
69006
68985
  return dataField.ownerUserId === selfUserId;
69007
68986
  }
69008
68987
  function collectLocalPreviewElementIds(taskId) {
@@ -71126,10 +71105,19 @@ var CursorAgentSubprocess = class {
71126
71105
  if (added.length === 0 && removed.length === 0) {
71127
71106
  return { added: [], removed: [], errors: {} };
71128
71107
  }
71108
+ const converted = buildCursorMcpConfigFromSet(servers);
71109
+ if (converted.skipped.length > 0) {
71110
+ logger.warn({
71111
+ event: "cursor_set_mcp_servers_skipped_entries",
71112
+ taskId: this.#harnessTaskId,
71113
+ skipped: converted.skipped,
71114
+ keptCount: Object.keys(converted.servers).length
71115
+ });
71116
+ }
71129
71117
  this.#sendToChild({
71130
71118
  kind: "set_mcp_servers",
71131
71119
  generation: this.#generation,
71132
- servers: buildCursorMcpConfigFromSet(servers).servers
71120
+ servers: converted.servers
71133
71121
  });
71134
71122
  this.#mcpServerSet = incoming;
71135
71123
  return { added, removed, errors: {} };
@@ -76356,6 +76344,13 @@ function isKnownAgentProvider(runtimeId) {
76356
76344
  const id = AGENT_IDS.find((a) => a === runtimeId);
76357
76345
  return id !== void 0 && profileRegistry.has(id);
76358
76346
  }
76347
+ function deriveSubprocessKind(runtimeId) {
76348
+ if (!isKnownAgentProvider(runtimeId)) return "claude";
76349
+ const profileId = profileRegistry.get(runtimeId).id;
76350
+ if (profileId === "codex") return "codex";
76351
+ if (profileId === "cursor") return "cursor";
76352
+ return "claude";
76353
+ }
76359
76354
 
76360
76355
  // src/services/task/cross-runtime/bridge-orchestration.ts
76361
76356
  function maybeApplyCrossRuntimeSwitch(ctx, taskId, orchestrator, settings) {
@@ -82122,21 +82117,75 @@ import { dirname as dirname34, join as join73 } from "path";
82122
82117
 
82123
82118
  // src/services/plan-backfill/plan-lazy-rebuild.ts
82124
82119
  var SETTLE_BUDGET_MS = 2e3;
82120
+ function classifyPlanDocState(input) {
82121
+ if (input.hasContent) return { kind: "has-content" };
82122
+ const probe = input.probe;
82123
+ if (probe === null) return { kind: "rebuild", reason: "no-boundary", chunkCount: 0 };
82124
+ switch (probe.kind) {
82125
+ case "absent":
82126
+ return { kind: "rebuild", reason: "no-chunks", chunkCount: 0 };
82127
+ case "unreadable":
82128
+ return {
82129
+ kind: "storage-error",
82130
+ reason: "chunks-unreadable",
82131
+ chunkCount: 0,
82132
+ chunksPath: probe.path,
82133
+ code: probe.code
82134
+ };
82135
+ case "chunks": {
82136
+ if (input.docHasImportedOps)
82137
+ return { kind: "rebuild", reason: "durably-empty", chunkCount: probe.chunkCount };
82138
+ return input.retriesExhausted ? { kind: "rebuild", reason: "exhausted-retries", chunkCount: probe.chunkCount } : {
82139
+ kind: "storage-error",
82140
+ reason: "chunks-not-loaded",
82141
+ chunkCount: probe.chunkCount,
82142
+ chunksPath: probe.path,
82143
+ code: null
82144
+ };
82145
+ }
82146
+ default: {
82147
+ const _exhaustive = probe;
82148
+ void _exhaustive;
82149
+ return {
82150
+ kind: "storage-error",
82151
+ reason: "chunks-not-loaded",
82152
+ chunkCount: 0,
82153
+ chunksPath: "",
82154
+ code: null
82155
+ };
82156
+ }
82157
+ }
82158
+ }
82125
82159
  async function ensurePlanDocRebuilt(deps) {
82126
82160
  try {
82127
82161
  const verdict = await settleAndCheckContent(deps);
82128
- if (verdict === "has-content") return { kind: "has-content" };
82129
- if (verdict === "storage-error") {
82130
- deps.log({ event: "plan_lazy_rebuild_storage_error", taskId: deps.taskId });
82162
+ if (verdict.kind === "has-content") return { kind: "has-content" };
82163
+ if (verdict.kind === "storage-error") {
82164
+ deps.log({
82165
+ event: "plan_lazy_rebuild_storage_error",
82166
+ taskId: deps.taskId,
82167
+ reason: verdict.reason,
82168
+ chunkCount: verdict.chunkCount,
82169
+ chunksPath: verdict.chunksPath,
82170
+ code: verdict.code
82171
+ });
82131
82172
  return { kind: "storage-error" };
82132
82173
  }
82174
+ if (verdict.reason === "durably-empty" || verdict.reason === "exhausted-retries") {
82175
+ deps.log({
82176
+ event: "plan_lazy_rebuild_durably_empty",
82177
+ taskId: deps.taskId,
82178
+ reason: verdict.reason,
82179
+ chunkCount: verdict.chunkCount
82180
+ });
82181
+ }
82133
82182
  const sources = await gatherBackfillSources(
82134
82183
  deps.taskRecord,
82135
82184
  deps.taskId,
82136
82185
  deps.dataDir,
82137
82186
  deps.shipyardHome
82138
82187
  );
82139
- const fileFallback = await resolveFileFallback(deps.taskId, sources);
82188
+ const fileFallback = await resolveFileFallback(deps.taskId, sources, deps.planRepo);
82140
82189
  if (sources.planVersions.length === 0 && !fileFallback) return { kind: "no-source" };
82141
82190
  const action = planBackfill({
82142
82191
  taskId: deps.taskId,
@@ -82171,9 +82220,18 @@ async function settleAndCheckContent(deps) {
82171
82220
  await sync(doc).waitForSync({ kind: "storage", timeout: SETTLE_BUDGET_MS });
82172
82221
  } catch {
82173
82222
  }
82174
- if (deps.planRepo.getContent(deps.taskId).trim() !== "") return "has-content";
82175
- const onDisk = await (deps.hasOnDiskDoc?.() ?? deps.planRepo.hasOnDiskDoc(deps.taskId));
82176
- return onDisk === true ? "storage-error" : "absent";
82223
+ const retriesExhausted = deps.retriesExhausted ?? false;
82224
+ const hasContent = deps.planRepo.getContent(deps.taskId).trim() !== "";
82225
+ if (hasContent)
82226
+ return classifyPlanDocState({
82227
+ hasContent,
82228
+ docHasImportedOps: true,
82229
+ probe: null,
82230
+ retriesExhausted
82231
+ });
82232
+ const probe = await (deps.probeDocChunks?.() ?? deps.planRepo.probeDocChunks(deps.taskId));
82233
+ const docHasImportedOps = deps.docHasImportedOps?.() ?? deps.planRepo.docHasImportedOps(deps.taskId);
82234
+ return classifyPlanDocState({ hasContent, docHasImportedOps, probe, retriesExhausted });
82177
82235
  }
82178
82236
 
82179
82237
  // src/services/plan/plan-detection-policy.ts
@@ -82228,7 +82286,7 @@ function decideWatchResolved(trigger, snapshot) {
82228
82286
  // src/services/plan/plan-file-bridge.ts
82229
82287
  import { createHash as createHash12, randomUUID as randomUUID20 } from "crypto";
82230
82288
  import { existsSync as existsSync9 } from "fs";
82231
- import { readFile as readFile44, rename as rename18, writeFile as writeFile25 } from "fs/promises";
82289
+ import { readFile as readFile44, rename as rename18, stat as stat18, writeFile as writeFile25 } from "fs/promises";
82232
82290
  import { basename as basename13, dirname as dirname33 } from "path";
82233
82291
  var FILE_READ_MAX_RETRIES = 3;
82234
82292
  var FILE_READ_RETRY_BASE_MS = 200;
@@ -82616,6 +82674,15 @@ var PlanFileBridge = class _PlanFileBridge {
82616
82674
  }
82617
82675
  await this.#writeBackToFile();
82618
82676
  }
82677
+ /** Byte size of the plan file on disk, or 0 when it is missing or unreadable. */
82678
+ async #existingFileSize() {
82679
+ if (!this.#filePath) return 0;
82680
+ try {
82681
+ return (await stat18(this.#filePath)).size;
82682
+ } catch {
82683
+ return 0;
82684
+ }
82685
+ }
82619
82686
  async #readFileWithRetry(filePath) {
82620
82687
  let lastError;
82621
82688
  for (let attempt = 0; attempt < FILE_READ_MAX_RETRIES; attempt++) {
@@ -82659,6 +82726,18 @@ var PlanFileBridge = class _PlanFileBridge {
82659
82726
  const content = this.#config.planRepo.getContent(this.#config.taskId);
82660
82727
  const hash = contentHash(content);
82661
82728
  if (hash === this.#lastWrittenHash) return;
82729
+ if (content.trim() === "") {
82730
+ const existingBytes = await this.#existingFileSize();
82731
+ if (existingBytes > 0) {
82732
+ this.#config.log({
82733
+ event: "plan_write_back_refused_empty",
82734
+ taskId: this.#config.taskId,
82735
+ filePath: this.#filePath,
82736
+ existingBytes
82737
+ });
82738
+ return;
82739
+ }
82740
+ }
82662
82741
  const tmpPath = `${this.#filePath}.${randomUUID20()}.tmp`;
82663
82742
  await writeFile25(tmpPath, content, "utf-8");
82664
82743
  await rename18(tmpPath, this.#filePath);
@@ -83290,15 +83369,19 @@ var PlanStorageRecoveryController = class {
83290
83369
  this.#scheduleAttempt(detection, attempt + 1);
83291
83370
  return;
83292
83371
  case "exhausted":
83293
- case "give_up":
83294
83372
  this.#inFlight = false;
83295
83373
  this.#deps.log({
83296
83374
  event: "plan_storage_recovery_gave_up",
83297
83375
  taskId: this.#deps.taskId,
83298
83376
  attempt,
83299
- reason: action.kind
83377
+ reason: action.kind,
83378
+ outcome: outcome.kind
83300
83379
  });
83301
83380
  return;
83381
+ case "give_up":
83382
+ this.#inFlight = false;
83383
+ await this.#rebuildAsDurable(detection, attempt);
83384
+ return;
83302
83385
  default: {
83303
83386
  const _exhaustive = action;
83304
83387
  void _exhaustive;
@@ -83307,6 +83390,51 @@ var PlanStorageRecoveryController = class {
83307
83390
  }
83308
83391
  }
83309
83392
  }
83393
+ /**
83394
+ * Last pass, after the whole attempt budget is spent on a persistent
83395
+ * `storage-error`. `storage-error` means the doc's
83396
+ * chunks are on disk but nothing imported — which is a TRANSIENT load failure
83397
+ * only for as long as retrying can still change the answer. Once N evict-and-
83398
+ * reload attempts have each re-read disk and seen the same nothing, the reading
83399
+ * is durable, and the two states behind it are both better served by a rebuild:
83400
+ * a doc that was flushed while holding zero ops (real, and reachable — a
83401
+ * repo-WIDE `repo.flush()` persists a handle that was merely opened), and a
83402
+ * storage fault that is not going to clear on this cycle.
83403
+ *
83404
+ * Giving up here instead is what left #5266's symptom in place: a permanent
83405
+ * "Failed to load plan" over a durable source that was never consulted. Time is
83406
+ * the discriminator no single observation could supply, and the retries are how
83407
+ * we spend it — so the rebuild is gated behind them, never in place of them.
83408
+ */
83409
+ async #rebuildAsDurable(detection, attempt) {
83410
+ const outcome = await ensurePlanDocRebuilt({
83411
+ taskId: this.#deps.taskId,
83412
+ planRepo: this.#deps.planRepo,
83413
+ taskRecord: { lastPlanDetection: { filePath: detection.filePath } },
83414
+ dataDir: this.#deps.dataDir,
83415
+ shipyardHome: getShipyardHome(),
83416
+ retriesExhausted: true,
83417
+ log: this.#deps.log
83418
+ });
83419
+ if (this.#deps.isDisposed()) return;
83420
+ if (outcome.kind === "rebuilt") {
83421
+ this.#deps.log({
83422
+ event: "plan_storage_recovery_succeeded",
83423
+ taskId: this.#deps.taskId,
83424
+ attempt,
83425
+ via: "durable-rebuild"
83426
+ });
83427
+ this.#deps.reEmitPlanDetected(detection);
83428
+ return;
83429
+ }
83430
+ this.#deps.log({
83431
+ event: "plan_storage_recovery_gave_up",
83432
+ taskId: this.#deps.taskId,
83433
+ attempt,
83434
+ reason: "give_up",
83435
+ outcome: outcome.kind
83436
+ });
83437
+ }
83310
83438
  };
83311
83439
 
83312
83440
  // src/services/plan/plan-handler.ts
@@ -89265,6 +89393,14 @@ function clampReasoningEffortForModelSwitch(args) {
89265
89393
 
89266
89394
  // src/services/task/orchestrator/task-mcp-coordinator.ts
89267
89395
  function connectDynamicMcpServersForTask(params) {
89396
+ if (params.subprocessKind !== "claude") {
89397
+ params.log({
89398
+ event: "dynamic_mcp_push_skipped_non_claude",
89399
+ taskId: params.taskId,
89400
+ subprocessKind: params.subprocessKind
89401
+ });
89402
+ return;
89403
+ }
89268
89404
  const resolved = params.resolveMcpServers();
89269
89405
  if (!resolved || Object.keys(resolved).length === 0) return;
89270
89406
  const filtered = filterDisabledMcpServers(resolved, params.disabledMcpServers, {
@@ -89912,11 +90048,11 @@ var IDLE_SWEEP_INTERVAL_MS = 6e4;
89912
90048
  function sanitize(id) {
89913
90049
  return id.replace(/[^a-zA-Z0-9_-]/g, "-");
89914
90050
  }
89915
- function isRecord11(value) {
90051
+ function isRecord10(value) {
89916
90052
  return typeof value === "object" && value !== null && !Array.isArray(value);
89917
90053
  }
89918
90054
  function isCCTaskFile(value) {
89919
- if (!isRecord11(value)) return false;
90055
+ if (!isRecord10(value)) return false;
89920
90056
  return typeof value.id === "string" && typeof value.subject === "string" && typeof value.description === "string" && typeof value.status === "string" && VALID_STATUSES.has(value.status) && Array.isArray(value.blocks) && Array.isArray(value.blockedBy);
89921
90057
  }
89922
90058
  function createCCTaskFileWatcher(listId, log) {
@@ -90989,9 +91125,9 @@ var SubagentTranscriptWatcher = class {
90989
91125
  let fh = null;
90990
91126
  try {
90991
91127
  fh = await open2(this.#config.transcriptPath, "r");
90992
- const stat19 = await fh.stat();
90993
- if (stat19.size <= this.#bytesRead) return;
90994
- const bytesToRead = stat19.size - this.#bytesRead;
91128
+ const stat20 = await fh.stat();
91129
+ if (stat20.size <= this.#bytesRead) return;
91130
+ const bytesToRead = stat20.size - this.#bytesRead;
90995
91131
  const buffer = Buffer.alloc(bytesToRead);
90996
91132
  const { bytesRead } = await fh.read(buffer, 0, bytesToRead, this.#bytesRead);
90997
91133
  this.#bytesRead += bytesRead;
@@ -94309,6 +94445,12 @@ var Task = class {
94309
94445
  */
94310
94446
  #connectDynamicMcpServers() {
94311
94447
  connectDynamicMcpServersForTask({
94448
+ /**
94449
+ * Derived from the SHARED owner so this gate and the task-manager's
94450
+ * coordinator-adoption push cannot disagree about who owns the MCP push
94451
+ * (#5265). The push itself is Claude-only — see the callee.
94452
+ */
94453
+ subprocessKind: deriveSubprocessKind(this.#runtimeId),
94312
94454
  mainThread: this.#getPrimary(),
94313
94455
  log: this.#deps.log,
94314
94456
  resolveMcpServers: this.#deps.resolveMcpServers,
@@ -97266,14 +97408,7 @@ var TaskManager = class {
97266
97408
  onSubprocessReady: (tid) => {
97267
97409
  const task = this.#tasks.get(tid);
97268
97410
  if (!task) return;
97269
- const _rId = task.orchestrator.runtimeId;
97270
- const _adoptProfile = isKnownAgentProvider(_rId) ? profileRegistry.get(_rId) : null;
97271
- const subprocessKind = (() => {
97272
- const profileId = _adoptProfile?.id;
97273
- if (profileId === "codex") return "codex";
97274
- if (profileId === "cursor") return "cursor";
97275
- return "claude";
97276
- })();
97411
+ const subprocessKind = deriveSubprocessKind(task.orchestrator.runtimeId);
97277
97412
  void this.pushTaskSkillsSnapshot(tid);
97278
97413
  this.#registerMcpAdoption(
97279
97414
  tid,
@@ -99070,7 +99205,7 @@ async function createDaemon(deps) {
99070
99205
  }
99071
99206
  });
99072
99207
  function projectCanvasElementForResolver(taskId, el) {
99073
- const innerData = isRecord10(el.data.data) ? el.data.data : {};
99208
+ const innerData = isRecord9(el.data.data) ? el.data.data : {};
99074
99209
  const elementId = String(el.id);
99075
99210
  if (el.data.type !== "preview" || !deps.previewStateStore) {
99076
99211
  return { id: elementId, type: el.data.type, data: innerData };
@@ -99124,7 +99259,7 @@ async function createDaemon(deps) {
99124
99259
  const elements = canvasRepo.getElements(taskId);
99125
99260
  const target = elements.find((el) => String(el.id) === elementId);
99126
99261
  if (!target) return;
99127
- const currentData = isRecord10(target.data.data) ? target.data.data : {};
99262
+ const currentData = isRecord9(target.data.data) ? target.data.data : {};
99128
99263
  canvasRepo.updateElement(taskId, target.id, {
99129
99264
  data: { ...currentData, port: newPort }
99130
99265
  });
@@ -101061,6 +101196,17 @@ var WARN_EVENTS = /* @__PURE__ */ new Set([
101061
101196
  "subprocess_force_killed",
101062
101197
  "file_watcher_events_dropped",
101063
101198
  "mcp_server_toggle_failed",
101199
+ /**
101200
+ * Both #5266. `plan_storage_recovery_gave_up` means a core-loop surface stays
101201
+ * dark until something else re-triggers a load — it logged at INFO while the
101202
+ * user was staring at "Failed to load plan", which is how a 93k-line session
101203
+ * hid it. `plan_write_back_refused_empty` means the durable plan markdown was
101204
+ * about to be truncated to 0 bytes and we declined; if that guard ever starts
101205
+ * firing routinely, an empty replica is reaching the bridge and someone needs
101206
+ * to see it.
101207
+ */
101208
+ "plan_storage_recovery_gave_up",
101209
+ "plan_write_back_refused_empty",
101064
101210
  /**
101065
101211
  * Mermaid validation fails open when the mermaid module itself cannot load
101066
101212
  * (e.g. a packaging defect stripped a transitive dep): diagrams are created
@@ -101958,7 +102104,7 @@ async function saveCodexSkillsCache(path5, cache2) {
101958
102104
  }
101959
102105
 
101960
102106
  // src/services/skills-mirror/manager.ts
101961
- import { lstat, mkdir as mkdir40, readlink, stat as stat18, symlink, unlink as unlink18 } from "fs/promises";
102107
+ import { lstat, mkdir as mkdir40, readlink, stat as stat19, symlink, unlink as unlink18 } from "fs/promises";
101962
102108
  import { homedir as homedir14 } from "os";
101963
102109
  import { dirname as dirname40, join as join83 } from "path";
101964
102110
 
@@ -102170,7 +102316,7 @@ function planMirrorActions(skills, paths) {
102170
102316
  }
102171
102317
  async function classifyManifestEntry(entry) {
102172
102318
  try {
102173
- await stat18(entry.target);
102319
+ await stat19(entry.target);
102174
102320
  return "retain";
102175
102321
  } catch (err3) {
102176
102322
  if (isEnoent(err3)) return "sweep-dangling";
@@ -103480,4 +103626,4 @@ export {
103480
103626
  decideWorkspaceScope,
103481
103627
  serve
103482
103628
  };
103483
- //# sourceMappingURL=serve-RWFBSQIE.js.map
103629
+ //# sourceMappingURL=serve-HTQMCHRP.js.map