@schoolai/shipyard 3.23.0 → 3.24.0-rc.20260813.1

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 (36) hide show
  1. package/dist/capability-detector-worker.js +4 -4
  2. package/dist/{chunk-QI5ERXFK.js → chunk-BGJ6QJNS.js} +27 -2
  3. package/dist/chunk-BGJ6QJNS.js.map +1 -0
  4. package/dist/{chunk-Y4RRW6MA.js → chunk-BLGHJ3OJ.js} +2 -2
  5. package/dist/{chunk-OCYT65KO.js → chunk-BN54GQR3.js} +139 -20
  6. package/dist/chunk-BN54GQR3.js.map +1 -0
  7. package/dist/{chunk-W3EMVBWP.js → chunk-DDC5OQOC.js} +3 -3
  8. package/dist/{chunk-7E6VXTEO.js → chunk-JZTUQVHQ.js} +2 -2
  9. package/dist/{chunk-CKQL7TWQ.js → chunk-NG355H2L.js} +39 -14
  10. package/dist/chunk-NG355H2L.js.map +1 -0
  11. package/dist/{chunk-UESTOND6.js → chunk-RL25HGER.js} +3 -3
  12. package/dist/chunk-RL25HGER.js.map +1 -0
  13. package/dist/{chunk-HQTCVJRK.js → chunk-TU3MSY2I.js} +2 -2
  14. package/dist/{chunk-LRBG3X5U.js → chunk-UK3IMOSP.js} +2 -2
  15. package/dist/cursor-runner.js +24 -17
  16. package/dist/cursor-runner.js.map +1 -1
  17. package/dist/electron-utility.js +2 -2
  18. package/dist/index.js +4 -4
  19. package/dist/{login-WL5WEEGJ.js → login-AX7SM2SJ.js} +3 -3
  20. package/dist/{plan-backfill-EF6KR6BE.js → plan-backfill-XYOUK2DY.js} +4 -4
  21. package/dist/{serve-J3MG3WHK.js → serve-RWFBSQIE.js} +670 -257
  22. package/dist/{serve-J3MG3WHK.js.map → serve-RWFBSQIE.js.map} +1 -1
  23. package/dist/{start-TCI35UML.js → start-ZV22UPMC.js} +6 -6
  24. package/package.json +1 -1
  25. package/dist/chunk-CKQL7TWQ.js.map +0 -1
  26. package/dist/chunk-OCYT65KO.js.map +0 -1
  27. package/dist/chunk-QI5ERXFK.js.map +0 -1
  28. package/dist/chunk-UESTOND6.js.map +0 -1
  29. /package/dist/{chunk-Y4RRW6MA.js.map → chunk-BLGHJ3OJ.js.map} +0 -0
  30. /package/dist/{chunk-W3EMVBWP.js.map → chunk-DDC5OQOC.js.map} +0 -0
  31. /package/dist/{chunk-7E6VXTEO.js.map → chunk-JZTUQVHQ.js.map} +0 -0
  32. /package/dist/{chunk-HQTCVJRK.js.map → chunk-TU3MSY2I.js.map} +0 -0
  33. /package/dist/{chunk-LRBG3X5U.js.map → chunk-UK3IMOSP.js.map} +0 -0
  34. /package/dist/{login-WL5WEEGJ.js.map → login-AX7SM2SJ.js.map} +0 -0
  35. /package/dist/{plan-backfill-EF6KR6BE.js.map → plan-backfill-XYOUK2DY.js.map} +0 -0
  36. /package/dist/{start-TCI35UML.js.map → start-ZV22UPMC.js.map} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  guardedSubscribe,
13
13
  makeInitialAttemptState,
14
14
  shutdownFileWatcherGuard
15
- } from "./chunk-7E6VXTEO.js";
15
+ } from "./chunk-JZTUQVHQ.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-LRBG3X5U.js";
42
+ } from "./chunk-UK3IMOSP.js";
43
43
  import {
44
44
  loadAuthToken
45
45
  } from "./chunk-2UE3TNY7.js";
46
46
  import {
47
47
  getDaemonVersion
48
- } from "./chunk-W3EMVBWP.js";
48
+ } from "./chunk-DDC5OQOC.js";
49
49
  import {
50
50
  collectPriorCrashDetail,
51
51
  getRecentWasmPanicMessages
@@ -56,6 +56,7 @@ import {
56
56
  CHARS_PER_TOKEN,
57
57
  CODEX_BUNDLED_VERSION,
58
58
  CURSOR_MODEL_CATALOG,
59
+ DAEMON_PR_COLD_DISCOVERY_INTERVAL_MS,
59
60
  DAEMON_PR_POLL_INTERVAL_MS,
60
61
  PROFILE_AUTH_ADAPTERS,
61
62
  REDACTED_THINKING_PLACEHOLDER,
@@ -87,6 +88,7 @@ import {
87
88
  cursorImportConversation,
88
89
  cursorPermissionModeMap,
89
90
  decideCursorAuthRefresh,
91
+ decidePrColdDiscoveryPlan,
90
92
  decidePrPollPlan,
91
93
  deduplicatePRs,
92
94
  detectAgentProviders,
@@ -135,6 +137,7 @@ import {
135
137
  notificationsSince,
136
138
  pickLastKnownCapabilities,
137
139
  profileRegistry,
140
+ reconcileCursorSupportsFastMode,
138
141
  recordGithubReadFailure,
139
142
  refreshCodexAuthSlice,
140
143
  refreshCommands,
@@ -154,6 +157,7 @@ import {
154
157
  searchPrs,
155
158
  setBootstrapPhaseBreadcrumbSink,
156
159
  setBootstrapPhaseMetricsSink,
160
+ setCursorFastModeSupportLookup,
157
161
  skipForMainChannel,
158
162
  snapshotProfileAuth,
159
163
  supportsAdaptiveThinking,
@@ -174,7 +178,7 @@ import {
174
178
  unresolveReviewThread,
175
179
  withLabelAsync,
176
180
  writeCursorApiKeyToVault
177
- } from "./chunk-OCYT65KO.js";
181
+ } from "./chunk-BN54GQR3.js";
178
182
  import {
179
183
  buildNodeSpawnEnv,
180
184
  nodeSpawnEnvOverlay,
@@ -229,14 +233,15 @@ import {
229
233
  createOneShotPool
230
234
  } from "./chunk-ZFKJAYAN.js";
231
235
  import {
232
- parseRunnerToDaemon
233
- } from "./chunk-CKQL7TWQ.js";
236
+ parseRunnerToDaemon,
237
+ shouldUseCursorFastModePricing
238
+ } from "./chunk-NG355H2L.js";
234
239
  import {
235
240
  XML_TAGS,
236
241
  createAssetResolver,
237
242
  toSdkContent,
238
243
  toSdkPermissionMode
239
- } from "./chunk-HQTCVJRK.js";
244
+ } from "./chunk-TU3MSY2I.js";
240
245
  import {
241
246
  AGENT_IDS,
242
247
  AGENT_SYSTEM_CLAUDE_CODE,
@@ -441,7 +446,7 @@ import {
441
446
  unsafeAssertSourceId,
442
447
  vizFileExtension,
443
448
  withPreferredRuntimeAuthMethod
444
- } from "./chunk-QI5ERXFK.js";
449
+ } from "./chunk-BGJ6QJNS.js";
445
450
  import {
446
451
  AuthGitHubCallbackRequestSchema,
447
452
  AuthGitHubCallbackResponseSchema,
@@ -489,7 +494,7 @@ import {
489
494
  VaultKeyPutResponseSchema,
490
495
  classifyClaudeCodeCompatibility,
491
496
  classifyCodexCompatibility
492
- } from "./chunk-UESTOND6.js";
497
+ } from "./chunk-RL25HGER.js";
493
498
  import {
494
499
  external_exports as external_exports2
495
500
  } from "./chunk-EHQITHQX.js";
@@ -6016,7 +6021,7 @@ function nanoid(size = 21) {
6016
6021
  }
6017
6022
 
6018
6023
  // src/services/bootstrap/signaling.ts
6019
- var DAEMON_NPM_VERSION = true ? "3.23.0" : "unknown";
6024
+ var DAEMON_NPM_VERSION = true ? "3.24.0" : "unknown";
6020
6025
  function createDaemonSignaling(config) {
6021
6026
  const agentId = config.agentId ?? nanoid();
6022
6027
  function send(msg) {
@@ -7350,6 +7355,9 @@ function computeTransportEnabled(server, live) {
7350
7355
  return server.enabled;
7351
7356
  }
7352
7357
  function resolveSnapshotAwareLive(s2, sessionEntry, workspaceLive, inSnapshot) {
7358
+ if (workspaceLive?.status === "needs-auth") {
7359
+ return { live: workspaceLive, transportEnabled: true };
7360
+ }
7353
7361
  if (sessionEntry) {
7354
7362
  return { live: sessionEntry, transportEnabled: sessionEntry.status !== "disabled" };
7355
7363
  }
@@ -7362,6 +7370,22 @@ function resolveSnapshotAwareLive(s2, sessionEntry, workspaceLive, inSnapshot) {
7362
7370
  transportEnabled: computeTransportEnabled(s2, workspaceLive)
7363
7371
  };
7364
7372
  }
7373
+ function collectMcpSessionCoordinatorDivergences(sessionServers, liveStatuses) {
7374
+ const out = [];
7375
+ for (const [name, session] of Object.entries(sessionServers)) {
7376
+ if (!session) continue;
7377
+ const coordinator = liveStatuses[name];
7378
+ if (!coordinator) continue;
7379
+ if (session.status === "connected" && coordinator.status === "needs-auth") {
7380
+ out.push({
7381
+ name,
7382
+ sessionStatus: session.status,
7383
+ coordinatorStatus: coordinator.status
7384
+ });
7385
+ }
7386
+ }
7387
+ return out;
7388
+ }
7365
7389
  function buildWorkspaceRow(s2, live, transportEnabled, userEnabled) {
7366
7390
  const accountIssue = s2.source === "claudeai" && s2.authStatus === "unauthenticated" && s2.url === void 0 ? {
7367
7391
  errorType: "agent_signin_required",
@@ -12063,32 +12087,69 @@ function createPublishTools(ctx) {
12063
12087
  true
12064
12088
  );
12065
12089
  }
12066
- try {
12067
- await deletePublish({
12068
- sessionServerUrl: ctx.sessionServerUrl,
12069
- authToken: token,
12070
- publishId
12071
- });
12072
- const store = ctx.publishedArtifactStore;
12073
- if (store) {
12074
- const taskId = resolveHarnessTaskId(ctx.taskId, ctx.getTaskIdForThread);
12075
- const allBindings = store.listByTask(taskId);
12076
- const match = allBindings.find((b2) => b2.publishId === publishId);
12077
- if (match) {
12078
- await store.deleteBinding(taskId, match.bindKind, match.bindKey);
12079
- }
12080
- }
12090
+ const taskId = resolveHarnessTaskId(ctx.taskId, ctx.getTaskIdForThread);
12091
+ const outcome = await runUnpublish({
12092
+ publishId,
12093
+ taskId,
12094
+ sessionServerUrl: ctx.sessionServerUrl,
12095
+ authToken: token,
12096
+ publishedArtifactStore: ctx.publishedArtifactStore
12097
+ });
12098
+ if (outcome.ok) {
12081
12099
  return textResult(JSON.stringify({ ok: true }));
12082
- } catch (err3) {
12083
- return textResult(
12084
- `Failed to unpublish: ${err3 instanceof Error ? err3.message : String(err3)}`,
12085
- true
12086
- );
12087
12100
  }
12101
+ return textResult(outcome.error, true);
12088
12102
  }
12089
12103
  }
12090
12104
  ];
12091
12105
  }
12106
+ async function runUnpublish(params) {
12107
+ try {
12108
+ await deletePublish({
12109
+ sessionServerUrl: params.sessionServerUrl,
12110
+ authToken: params.authToken,
12111
+ publishId: params.publishId
12112
+ });
12113
+ } catch (err3) {
12114
+ return {
12115
+ ok: false,
12116
+ error: `Failed to unpublish: ${err3 instanceof Error ? err3.message : String(err3)}`
12117
+ };
12118
+ }
12119
+ const store = params.publishedArtifactStore;
12120
+ if (store) {
12121
+ const match = store.listByTask(params.taskId).find((b2) => b2.publishId === params.publishId);
12122
+ if (match) {
12123
+ await store.deleteBinding(params.taskId, match.bindKind, match.bindKey);
12124
+ }
12125
+ }
12126
+ return { ok: true };
12127
+ }
12128
+ async function handleUnpublishRequest(params) {
12129
+ const { correlationId, sendControl } = params;
12130
+ const token = params.getAuthToken();
12131
+ if (!token) {
12132
+ sendControl({
12133
+ type: "unpublish_result",
12134
+ correlationId,
12135
+ ok: false,
12136
+ error: "Not authenticated. Please log in before unpublishing."
12137
+ });
12138
+ return;
12139
+ }
12140
+ const outcome = await runUnpublish({
12141
+ publishId: params.publishId,
12142
+ taskId: params.taskId,
12143
+ sessionServerUrl: params.sessionServerUrl,
12144
+ authToken: token,
12145
+ publishedArtifactStore: params.publishedArtifactStore
12146
+ });
12147
+ if (outcome.ok) {
12148
+ sendControl({ type: "unpublish_result", correlationId, ok: true });
12149
+ } else {
12150
+ sendControl({ type: "unpublish_result", correlationId, ok: false, error: outcome.error });
12151
+ }
12152
+ }
12092
12153
  async function handlePublishRequest(params) {
12093
12154
  const { correlationId, target, ttl, title, sendControl } = params;
12094
12155
  function emitProgress(phase, detail) {
@@ -14692,6 +14753,7 @@ function filterOutboundForCollab(msg, collabTaskId, getTaskSync) {
14692
14753
  */
14693
14754
  case "publish_progress":
14694
14755
  case "publish_result":
14756
+ case "unpublish_result":
14695
14757
  return null;
14696
14758
  /**
14697
14759
  * task_index_version is a heartbeat staleness probe response carrying the
@@ -15461,6 +15523,61 @@ async function routeRequestSkills(args) {
15461
15523
  args.sendControl(args.buildSnapshot());
15462
15524
  }
15463
15525
 
15526
+ // src/services/channels/control-channel/unpublish-request-wiring.ts
15527
+ function wireOnRequestUnpublish(deps, logAdapter) {
15528
+ return ({ correlationId, taskId, publishId, sendControl }) => {
15529
+ logAdapter({
15530
+ event: "unpublish_request_received",
15531
+ correlationId,
15532
+ taskId,
15533
+ publishId
15534
+ });
15535
+ const wrappedSendControl = (msg) => {
15536
+ if (msg.type === "unpublish_result") {
15537
+ logAdapter({
15538
+ event: "unpublish_result",
15539
+ correlationId,
15540
+ ok: msg.ok,
15541
+ error: msg.ok ? void 0 : msg.error
15542
+ });
15543
+ }
15544
+ sendControl(msg);
15545
+ };
15546
+ const sessionServerUrl = deps.sessionServerUrl;
15547
+ const getAuthToken = deps.getAuthToken;
15548
+ if (!sessionServerUrl || !getAuthToken) {
15549
+ wrappedSendControl({
15550
+ type: "unpublish_result",
15551
+ correlationId,
15552
+ ok: false,
15553
+ error: "Unpublish not available: session server not configured."
15554
+ });
15555
+ return;
15556
+ }
15557
+ handleUnpublishRequest({
15558
+ correlationId,
15559
+ taskId,
15560
+ publishId,
15561
+ sessionServerUrl,
15562
+ getAuthToken,
15563
+ publishedArtifactStore: deps.publishedArtifactStore ?? null,
15564
+ sendControl: wrappedSendControl
15565
+ }).catch((err3) => {
15566
+ logAdapter({
15567
+ event: "unpublish_request_unhandled_error",
15568
+ correlationId,
15569
+ error: err3 instanceof Error ? err3.message : String(err3)
15570
+ });
15571
+ wrappedSendControl({
15572
+ type: "unpublish_result",
15573
+ correlationId,
15574
+ ok: false,
15575
+ error: `Unexpected error: ${err3 instanceof Error ? err3.message : String(err3)}`
15576
+ });
15577
+ });
15578
+ };
15579
+ }
15580
+
15464
15581
  // src/services/channels/control-channel-handler.ts
15465
15582
  function handleControlChannel(send, callbacks, log) {
15466
15583
  function sendControl(msg) {
@@ -15957,6 +16074,14 @@ function routeMessage(msg, callbacks, sendControl, log) {
15957
16074
  sendControl
15958
16075
  });
15959
16076
  break;
16077
+ case "request_unpublish":
16078
+ callbacks.onRequestUnpublish({
16079
+ correlationId: msg.correlationId,
16080
+ taskId: msg.taskId,
16081
+ publishId: msg.publishId,
16082
+ sendControl
16083
+ });
16084
+ break;
15960
16085
  case "start_from_linear":
15961
16086
  callbacks.onStartFromLinear(msg);
15962
16087
  break;
@@ -21436,7 +21561,11 @@ function matchCollabQueueCorrelationIds(messages, queueEntries) {
21436
21561
  }
21437
21562
 
21438
21563
  // src/services/session/claude-tool-guard.ts
21439
- var SHIPYARD_DENIED_CLAUDE_TOOLS = ["Artifact"];
21564
+ var SHIPYARD_DENIED_CLAUDE_TOOLS = [
21565
+ "Artifact",
21566
+ "EnterWorktree",
21567
+ "ExitWorktree"
21568
+ ];
21440
21569
  function detectDeniedToolsPresent(initTools, denied = SHIPYARD_DENIED_CLAUDE_TOOLS) {
21441
21570
  const present = new Set(initTools);
21442
21571
  return denied.filter((name) => present.has(name));
@@ -27032,6 +27161,14 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
27032
27161
  });
27033
27162
  });
27034
27163
  },
27164
+ onRequestUnpublish: wireOnRequestUnpublish(
27165
+ {
27166
+ sessionServerUrl: deps?.sessionServerUrl,
27167
+ getAuthToken: deps?.getAuthToken,
27168
+ publishedArtifactStore: deps?.publishedArtifactStore
27169
+ },
27170
+ logAdapter
27171
+ ),
27035
27172
  onStartFromLinear: (payload) => linearStartHandler.handleStartFromLinear(payload),
27036
27173
  /**
27037
27174
  * Browser-initiated flush for a specific resource URI (PROTOCOL_VERSION 48).
@@ -33580,6 +33717,7 @@ function buildCollabRoomManager(deps) {
33580
33717
  import { randomUUID as randomUUID25 } from "crypto";
33581
33718
  import { mkdir as mkdir36 } from "fs/promises";
33582
33719
  import { join as join79 } from "path";
33720
+ import { getHeapStatistics } from "v8";
33583
33721
 
33584
33722
  // src/shared/capabilities/cursor-boot-auth.ts
33585
33723
  async function mergeCursorAuthFromVault(caps, detect, log) {
@@ -34291,6 +34429,24 @@ function mergeWithDefaultComposerSettings(caller, defaults) {
34291
34429
  return normalizeComposerSettings(merged);
34292
34430
  }
34293
34431
 
34432
+ // src/services/task/session-activity.ts
34433
+ function isActiveUserWork(state) {
34434
+ switch (state) {
34435
+ case "spawning":
34436
+ case "running":
34437
+ case "stopping":
34438
+ return true;
34439
+ case "warm_idle":
34440
+ case "resumable_idle":
34441
+ case "cold_idle":
34442
+ return false;
34443
+ default: {
34444
+ const _exhaustive = state;
34445
+ throw new Error(`Unhandled session state: ${JSON.stringify(_exhaustive)}`);
34446
+ }
34447
+ }
34448
+ }
34449
+
34294
34450
  // src/services/bootstrap/rehydrate.ts
34295
34451
  var ACTIVE_STATUSES = {
34296
34452
  open: true,
@@ -34313,9 +34469,11 @@ var ACTIVE_STATUSES = {
34313
34469
  */
34314
34470
  canceled: false
34315
34471
  };
34316
- function planStaleSweep(tasks, isRunning) {
34472
+ function planStaleSweep(tasks, getSessionState) {
34317
34473
  return Object.values(tasks).map((task) => {
34318
- if (ACTIVE_STATUSES[task.status] && !isRunning(task.taskId)) {
34474
+ const state = getSessionState(task.taskId);
34475
+ const isLive = state !== null && isActiveUserWork(state);
34476
+ if (ACTIVE_STATUSES[task.status] && !isLive) {
34319
34477
  return { kind: "mark_input_required", taskId: task.taskId, previousStatus: task.status };
34320
34478
  }
34321
34479
  return { kind: "keep", taskId: task.taskId };
@@ -34342,6 +34500,15 @@ function classifyPersistedSessions(sessions, knownTaskIds) {
34342
34500
  }
34343
34501
  var EAGER_RESTORE_STATUSES = {
34344
34502
  open: true,
34503
+ /**
34504
+ * Since the `planStaleSweep` fix for issue #5224, an eagerly-restored
34505
+ * `in_progress` task lands in `resumable_idle` and is immediately demoted
34506
+ * to `input_required` by the sweep three lines later in the boot sequence
34507
+ * — the eventual status is identical to `input_required` either way.
34508
+ * Eager restore is still worth it here purely as a warm-cache optimization:
34509
+ * the resumable SDK session is materialized immediately rather than on the
34510
+ * user's next message, so replying doesn't pay the lazy-restore cost.
34511
+ */
34345
34512
  in_progress: true,
34346
34513
  /**
34347
34514
  * Excluded for boot-cost reasons, not correctness: `planStaleSweep`
@@ -34696,7 +34863,7 @@ async function migrateLegacyPreviewData(canvasRepo, previewStateStore, taskState
34696
34863
  }
34697
34864
  async function sweepStaleTasks(taskStateStore, taskManager, log) {
34698
34865
  const tasks = await taskStateStore.listTasks();
34699
- const actions = planStaleSweep(tasks, (id) => taskManager.isRunning(id));
34866
+ const actions = planStaleSweep(tasks, (id) => taskManager.getTaskState(id)?.sessionState ?? null);
34700
34867
  const noBroadcast = { broadcast: false };
34701
34868
  for (const action of actions) {
34702
34869
  if (action.kind === "mark_input_required") {
@@ -37464,7 +37631,7 @@ function createDaemonPrPollingService(deps) {
37464
37631
  syncing = true;
37465
37632
  return true;
37466
37633
  }
37467
- async function loadPlanTasks() {
37634
+ async function loadPlanTasks2() {
37468
37635
  const records = await deps.taskStateStore.listTasks();
37469
37636
  return Object.values(records).map(
37470
37637
  (record) => toPrPollPlanTask(record, deps.getPrState(record.taskId))
@@ -37488,7 +37655,7 @@ function createDaemonPrPollingService(deps) {
37488
37655
  }
37489
37656
  }
37490
37657
  async function runSyncCore() {
37491
- const tasks = await loadPlanTasks();
37658
+ const tasks = await loadPlanTasks2();
37492
37659
  if (disposed) return;
37493
37660
  const tasksById = new Map(tasks.map((task) => [task.taskId, task]));
37494
37661
  stopIneligibleSubscriptions(tasksById);
@@ -37549,70 +37716,8 @@ function createDaemonPrPollingService(deps) {
37549
37716
  return { start, requestSync, dispose };
37550
37717
  }
37551
37718
 
37552
- // src/services/pr-first-discovery.ts
37553
- function applyPrFirstDiscovery(payload, attributedTaskIds, effects) {
37554
- const { taskId } = payload;
37555
- const pr = payload.currentBranchPR;
37556
- if (!pr || attributedTaskIds.has(taskId)) return;
37557
- attributedTaskIds.add(taskId);
37558
- effects.forceRefresh(taskId);
37559
- effects.attribute(taskId, pr);
37560
- }
37561
-
37562
- // src/services/pr-poll-subscriptions.ts
37563
- function createPRPollSubscriptionCoordinator(poller) {
37564
- const subscriptions = /* @__PURE__ */ new Map();
37565
- function startPolling(taskId, cwd, source = "browser") {
37566
- const existing = subscriptions.get(taskId);
37567
- if (existing) {
37568
- const wasEmpty = existing.sources.size === 0;
37569
- const cwdChanged = existing.cwd !== cwd;
37570
- existing.cwd = cwd;
37571
- existing.sources.add(source);
37572
- if (wasEmpty) {
37573
- poller.startPolling(taskId, cwd);
37574
- } else if (cwdChanged) {
37575
- void poller.updateCwd(taskId, cwd);
37576
- } else if (source === "browser") {
37577
- poller.refreshNow(taskId);
37578
- }
37579
- return;
37580
- }
37581
- subscriptions.set(taskId, { cwd, sources: /* @__PURE__ */ new Set([source]) });
37582
- poller.startPolling(taskId, cwd);
37583
- }
37584
- function stopPolling(taskId, source = "browser") {
37585
- const entry = subscriptions.get(taskId);
37586
- if (!entry) return;
37587
- entry.sources.delete(source);
37588
- if (entry.sources.size > 0) return;
37589
- subscriptions.delete(taskId);
37590
- poller.stopPolling(taskId);
37591
- }
37592
- function getCwd(taskId) {
37593
- return subscriptions.get(taskId)?.cwd ?? poller.getCwd(taskId);
37594
- }
37595
- async function updateCwd(taskId, newCwd) {
37596
- const entry = subscriptions.get(taskId);
37597
- if (!entry) return;
37598
- if (entry.cwd === newCwd) return;
37599
- entry.cwd = newCwd;
37600
- await poller.updateCwd(taskId, newCwd);
37601
- }
37602
- function dispose() {
37603
- subscriptions.clear();
37604
- poller.dispose();
37605
- }
37606
- return {
37607
- startPolling,
37608
- stopPolling,
37609
- forceRefresh: poller.forceRefresh,
37610
- refreshNow: poller.refreshNow,
37611
- getCwd,
37612
- updateCwd,
37613
- dispose
37614
- };
37615
- }
37719
+ // src/services/pr-cold-discovery.ts
37720
+ import { existsSync as existsSync7 } from "fs";
37616
37721
 
37617
37722
  // src/services/pr-stack/build-stack-graph.ts
37618
37723
  var DEFAULT_MAX_DEPTH = 20;
@@ -37938,47 +38043,47 @@ async function fetchCIDataWithRemote(pr, cwd, remote) {
37938
38043
  );
37939
38044
  return { requiredChecks };
37940
38045
  }
38046
+ async function fetchFastPRState(cwd, log) {
38047
+ const backoff = getGithubReadBackoffState();
38048
+ if (backoff.backoffUntil !== null) {
38049
+ log.warn(
38050
+ { backoffUntil: backoff.backoffUntil, reason: backoff.reason },
38051
+ "PR polling skipped during GitHub read backoff"
38052
+ );
38053
+ return null;
38054
+ }
38055
+ const available = await isGhAvailable();
38056
+ if (!available) {
38057
+ return {
38058
+ prAvailable: false,
38059
+ currentBranchPR: null,
38060
+ assignedReviews: [],
38061
+ userPRs: [],
38062
+ allPRs: [],
38063
+ remote: ""
38064
+ };
38065
+ }
38066
+ const [prResults, remote] = await Promise.all([
38067
+ Promise.allSettled([getPRForCurrentBranch(cwd), getAssignedReviews(cwd), getUserPRs(cwd)]),
38068
+ gitExecSafe(cwd, ["remote", "get-url", "origin"]).then((v2) => v2 ?? "")
38069
+ ]);
38070
+ const [currentPR, assigned, user] = prResults;
38071
+ const { allPRs, currentBranchPR: rawPR } = deduplicatePRs(currentPR, assigned, user);
38072
+ const currentBranchPR = rawPR ? await enrichClosedPR(rawPR, cwd) : null;
38073
+ return {
38074
+ prAvailable: true,
38075
+ currentBranchPR,
38076
+ assignedReviews: assigned.status === "fulfilled" ? assigned.value : [],
38077
+ userPRs: user.status === "fulfilled" ? user.value : [],
38078
+ allPRs,
38079
+ remote
38080
+ };
38081
+ }
37941
38082
  function createPRPoller(callbacks, log, resolveTopLevel = getGitTopLevel) {
37942
38083
  const repos = /* @__PURE__ */ new Map();
37943
38084
  const taskToRepo = /* @__PURE__ */ new Map();
37944
38085
  let disposed = false;
37945
38086
  let activePollCount = 0;
37946
- async function fetchFastPRState(cwd) {
37947
- const backoff = getGithubReadBackoffState();
37948
- if (backoff.backoffUntil !== null) {
37949
- log.warn(
37950
- { backoffUntil: backoff.backoffUntil, reason: backoff.reason },
37951
- "PR polling skipped during GitHub read backoff"
37952
- );
37953
- return null;
37954
- }
37955
- const available = await isGhAvailable();
37956
- if (!available) {
37957
- return {
37958
- prAvailable: false,
37959
- currentBranchPR: null,
37960
- assignedReviews: [],
37961
- userPRs: [],
37962
- allPRs: [],
37963
- remote: ""
37964
- };
37965
- }
37966
- const [prResults, remote] = await Promise.all([
37967
- Promise.allSettled([getPRForCurrentBranch(cwd), getAssignedReviews(cwd), getUserPRs(cwd)]),
37968
- gitExecSafe(cwd, ["remote", "get-url", "origin"]).then((v2) => v2 ?? "")
37969
- ]);
37970
- const [currentPR, assigned, user] = prResults;
37971
- const { allPRs, currentBranchPR: rawPR } = deduplicatePRs(currentPR, assigned, user);
37972
- const currentBranchPR = rawPR ? await enrichClosedPR(rawPR, cwd) : null;
37973
- return {
37974
- prAvailable: true,
37975
- currentBranchPR,
37976
- assignedReviews: assigned.status === "fulfilled" ? assigned.value : [],
37977
- userPRs: user.status === "fulfilled" ? user.value : [],
37978
- allPRs,
37979
- remote
37980
- };
37981
- }
37982
38087
  async function fetchEnrichment(fast, cwd) {
37983
38088
  const ciData = await fetchCIDataWithRemote(fast.currentBranchPR, cwd, fast.remote).catch(
37984
38089
  () => ({
@@ -38115,7 +38220,7 @@ function createPRPoller(callbacks, log, resolveTopLevel = getGitTopLevel) {
38115
38220
  const currentBranch = await gitExecSafe(entry.cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) ?? "";
38116
38221
  if (disposed) return;
38117
38222
  handleBranchChange(topLevel, entry, currentBranch);
38118
- const fast = await fetchFastPRState(entry.cwd);
38223
+ const fast = await fetchFastPRState(entry.cwd, log);
38119
38224
  if (!fast || disposed) return;
38120
38225
  entry.lastPolledAt = Date.now();
38121
38226
  pushPhase1IfChanged(topLevel, entry, fast);
@@ -38311,6 +38416,190 @@ function createPRPoller(callbacks, log, resolveTopLevel = getGitTopLevel) {
38311
38416
  return { startPolling, stopPolling, forceRefresh, refreshNow, getCwd, updateCwd, dispose };
38312
38417
  }
38313
38418
 
38419
+ // src/services/pr-cold-discovery.ts
38420
+ async function loadPlanTasks(taskStateStore, getPrState) {
38421
+ const records = await taskStateStore.listTasks();
38422
+ return Object.values(records).map(
38423
+ (record) => toPrPollPlanTask(record, getPrState(record.taskId))
38424
+ );
38425
+ }
38426
+ function createPrColdDiscoveryService(deps) {
38427
+ const intervalMs = deps.intervalMs ?? DAEMON_PR_COLD_DISCOVERY_INTERVAL_MS;
38428
+ const now = deps.now ?? Date.now;
38429
+ const checkPR = deps.checkPR ?? fetchFastPRState;
38430
+ const lastCheckedAt = /* @__PURE__ */ new Map();
38431
+ let timer = null;
38432
+ let disposed = false;
38433
+ let syncing = false;
38434
+ let syncQueued = false;
38435
+ let unsubscribeTaskStore = null;
38436
+ async function checkTask(task) {
38437
+ if (!task.cwd) return;
38438
+ if (!existsSync7(task.cwd)) {
38439
+ deps.log.debug({ taskId: task.taskId, cwd: task.cwd }, "Cold discovery skipped, cwd missing");
38440
+ return;
38441
+ }
38442
+ try {
38443
+ const fast = await checkPR(task.cwd, deps.log);
38444
+ if (fast?.currentBranchPR) {
38445
+ deps.log.debug(
38446
+ { taskId: task.taskId, prUrl: fast.currentBranchPR.url },
38447
+ "Cold discovery found a PR, handing off to daemon polling"
38448
+ );
38449
+ deps.subscriptions.startPolling(task.taskId, task.cwd, "daemon");
38450
+ }
38451
+ } catch (err3) {
38452
+ recordGithubReadFailure(err3);
38453
+ deps.log.warn({ err: err3, taskId: task.taskId }, "Cold discovery check failed");
38454
+ }
38455
+ }
38456
+ function tryEnterSync() {
38457
+ if (disposed) return false;
38458
+ if (syncing) {
38459
+ syncQueued = true;
38460
+ return false;
38461
+ }
38462
+ syncing = true;
38463
+ return true;
38464
+ }
38465
+ async function runSyncCore() {
38466
+ const tasks = await loadPlanTasks(deps.taskStateStore, deps.getPrState);
38467
+ if (disposed) return;
38468
+ const tasksById = new Map(tasks.map((task) => [task.taskId, task]));
38469
+ const currentTime = now();
38470
+ const toCheck = decidePrColdDiscoveryPlan(
38471
+ tasks,
38472
+ currentTime,
38473
+ lastCheckedAt,
38474
+ deps.getRateLimitState(),
38475
+ {
38476
+ intervalMs
38477
+ }
38478
+ );
38479
+ for (const taskId of toCheck) {
38480
+ lastCheckedAt.set(taskId, currentTime);
38481
+ }
38482
+ if (toCheck.length > 0) {
38483
+ deps.log.debug({ taskIds: toCheck }, "Cold PR discovery candidates selected");
38484
+ }
38485
+ await Promise.all(
38486
+ toCheck.map((taskId) => {
38487
+ const task = tasksById.get(taskId);
38488
+ return task ? checkTask(task) : Promise.resolve();
38489
+ })
38490
+ );
38491
+ }
38492
+ function finishSync() {
38493
+ syncing = false;
38494
+ if (!syncQueued || disposed) return;
38495
+ syncQueued = false;
38496
+ void sync2();
38497
+ }
38498
+ async function sync2() {
38499
+ if (!tryEnterSync()) return;
38500
+ try {
38501
+ await runSyncCore();
38502
+ } catch (err3) {
38503
+ deps.log.warn(
38504
+ { error: err3 instanceof Error ? err3.message : String(err3) },
38505
+ "Cold PR discovery sync failed"
38506
+ );
38507
+ } finally {
38508
+ finishSync();
38509
+ }
38510
+ }
38511
+ function requestSync() {
38512
+ void sync2();
38513
+ }
38514
+ function start() {
38515
+ if (timer !== null || disposed) return;
38516
+ unsubscribeTaskStore = deps.taskStateStore.subscribe((event) => {
38517
+ if (event.kind === "delete") {
38518
+ lastCheckedAt.delete(event.id);
38519
+ return;
38520
+ }
38521
+ requestSync();
38522
+ });
38523
+ requestSync();
38524
+ timer = setInterval(requestSync, intervalMs);
38525
+ timer.unref();
38526
+ }
38527
+ function dispose() {
38528
+ disposed = true;
38529
+ if (timer) clearInterval(timer);
38530
+ timer = null;
38531
+ unsubscribeTaskStore?.();
38532
+ unsubscribeTaskStore = null;
38533
+ lastCheckedAt.clear();
38534
+ }
38535
+ return { start, dispose };
38536
+ }
38537
+
38538
+ // src/services/pr-first-discovery.ts
38539
+ function applyPrFirstDiscovery(payload, attributedTaskIds, effects) {
38540
+ const { taskId } = payload;
38541
+ const pr = payload.currentBranchPR;
38542
+ if (!pr || attributedTaskIds.has(taskId)) return;
38543
+ attributedTaskIds.add(taskId);
38544
+ effects.forceRefresh(taskId);
38545
+ effects.attribute(taskId, pr);
38546
+ }
38547
+
38548
+ // src/services/pr-poll-subscriptions.ts
38549
+ function createPRPollSubscriptionCoordinator(poller) {
38550
+ const subscriptions = /* @__PURE__ */ new Map();
38551
+ function startPolling(taskId, cwd, source = "browser") {
38552
+ const existing = subscriptions.get(taskId);
38553
+ if (existing) {
38554
+ const wasEmpty = existing.sources.size === 0;
38555
+ const cwdChanged = existing.cwd !== cwd;
38556
+ existing.cwd = cwd;
38557
+ existing.sources.add(source);
38558
+ if (wasEmpty) {
38559
+ poller.startPolling(taskId, cwd);
38560
+ } else if (cwdChanged) {
38561
+ void poller.updateCwd(taskId, cwd);
38562
+ } else if (source === "browser") {
38563
+ poller.refreshNow(taskId);
38564
+ }
38565
+ return;
38566
+ }
38567
+ subscriptions.set(taskId, { cwd, sources: /* @__PURE__ */ new Set([source]) });
38568
+ poller.startPolling(taskId, cwd);
38569
+ }
38570
+ function stopPolling(taskId, source = "browser") {
38571
+ const entry = subscriptions.get(taskId);
38572
+ if (!entry) return;
38573
+ entry.sources.delete(source);
38574
+ if (entry.sources.size > 0) return;
38575
+ subscriptions.delete(taskId);
38576
+ poller.stopPolling(taskId);
38577
+ }
38578
+ function getCwd(taskId) {
38579
+ return subscriptions.get(taskId)?.cwd ?? poller.getCwd(taskId);
38580
+ }
38581
+ async function updateCwd(taskId, newCwd) {
38582
+ const entry = subscriptions.get(taskId);
38583
+ if (!entry) return;
38584
+ if (entry.cwd === newCwd) return;
38585
+ entry.cwd = newCwd;
38586
+ await poller.updateCwd(taskId, newCwd);
38587
+ }
38588
+ function dispose() {
38589
+ subscriptions.clear();
38590
+ poller.dispose();
38591
+ }
38592
+ return {
38593
+ startPolling,
38594
+ stopPolling,
38595
+ forceRefresh: poller.forceRefresh,
38596
+ refreshNow: poller.refreshNow,
38597
+ getCwd,
38598
+ updateCwd,
38599
+ dispose
38600
+ };
38601
+ }
38602
+
38314
38603
  // src/services/channels/control-channel/pr-ingest-dedup.ts
38315
38604
  var INGEST_DEDUPE_WINDOW_MS = 15e3;
38316
38605
  function createPrIngestDedup() {
@@ -38499,6 +38788,14 @@ function buildPRAttributionSubsystem(daemon, options) {
38499
38788
  log: prPollerLog
38500
38789
  });
38501
38790
  daemonPrPolling.start();
38791
+ const prColdDiscovery = createPrColdDiscoveryService({
38792
+ taskStateStore: daemon.taskStateStore,
38793
+ subscriptions: prPoller,
38794
+ getPrState,
38795
+ getRateLimitState: getGithubReadBackoffState,
38796
+ log: prPollerLog
38797
+ });
38798
+ prColdDiscovery.start();
38502
38799
  const unsubscribeCwdChanged = daemon.taskManager.addOnCwdChanged((taskId, newCwd) => {
38503
38800
  void prPoller.updateCwd(taskId, newCwd);
38504
38801
  });
@@ -38575,6 +38872,7 @@ function buildPRAttributionSubsystem(daemon, options) {
38575
38872
  seedReadyPromise,
38576
38873
  dispose: () => {
38577
38874
  daemonPrPolling?.dispose();
38875
+ prColdDiscovery.dispose();
38578
38876
  unsubscribeCwdChanged();
38579
38877
  prPoller.dispose();
38580
38878
  }
@@ -39897,6 +40195,11 @@ var EXTENSION_KIND = {
39897
40195
  ".jpeg": "screenshot",
39898
40196
  ".gif": "screenshot",
39899
40197
  ".webp": "screenshot",
40198
+ /**
40199
+ * #5231 — asset download already maps .svg → image/svg+xml; registration must
40200
+ * match so the viewer takes the visual path instead of monospace `other`.
40201
+ */
40202
+ ".svg": "screenshot",
39900
40203
  ".mp4": "video",
39901
40204
  ".webm": "video",
39902
40205
  ".mov": "video",
@@ -51381,7 +51684,7 @@ import { extname as extname3 } from "path";
51381
51684
  import { pathToFileURL as pathToFileURL3 } from "url";
51382
51685
 
51383
51686
  // src/services/lsp/language-server-registry.ts
51384
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
51687
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
51385
51688
  import { createRequire as createRequire3 } from "module";
51386
51689
  import { dirname as dirname22, isAbsolute as isAbsolute4, join as join53 } from "path";
51387
51690
  function decideTypescriptServer(d) {
@@ -51507,7 +51810,7 @@ function createRegistryDeps(requireFrom) {
51507
51810
  resolveBin: (pkg, binName) => resolveBinViaPackageJson(pkg, binName, req),
51508
51811
  resolveTsgoBinary: () => resolveTsgoBinaryPath(req),
51509
51812
  nodeExecPath: resolveNodeExecPath(),
51510
- fileExists: (path5) => existsSync7(path5),
51813
+ fileExists: (path5) => existsSync8(path5),
51511
51814
  readJsonFile: (path5) => {
51512
51815
  try {
51513
51816
  return parse2(readFileSync8(path5, "utf-8"));
@@ -51531,7 +51834,7 @@ function resolveTsgoBinaryPath(req) {
51531
51834
  }
51532
51835
  const exeDir = join53(dirname22(pkgJsonPath), "lib");
51533
51836
  const exe = process.platform === "win32" ? join53(exeDir, "tsgo.exe") : join53(exeDir, "tsgo");
51534
- if (!existsSync7(exe)) {
51837
+ if (!existsSync8(exe)) {
51535
51838
  throw new Error(`tsgo native binary not found: ${exe}`);
51536
51839
  }
51537
51840
  return exe;
@@ -70010,7 +70313,7 @@ function applyCursorUsage(result, usage, ctx) {
70010
70313
  };
70011
70314
  const catalog = CURSOR_MODEL_CATALOG.find((m2) => m2.id === ctx.modelId);
70012
70315
  if (catalog !== void 0) {
70013
- const fastPricing = ctx.fastMode && ctx.fastModeParam !== null ? catalog.fastModePricing : void 0;
70316
+ const fastPricing = shouldUseCursorFastModePricing(ctx.fastMode, ctx.fastModeParam) ? catalog.fastModePricing : void 0;
70014
70317
  const costIn = fastPricing?.costInputUsd ?? catalog.costInputUsd;
70015
70318
  const costOut = fastPricing?.costOutputUsd ?? catalog.costOutputUsd;
70016
70319
  if (costIn !== void 0 && costOut !== void 0) {
@@ -70314,18 +70617,16 @@ var CursorAgentSubprocess = class {
70314
70617
  #injectedAgentId = null;
70315
70618
  /**
70316
70619
  * Live Fast Mode flag. Mirrored in the runner via `init` (initial value)
70317
- * and `set_fast_mode` IPC (subsequent toggles). `pushMessage` does not
70318
- * read this directly — it's the runner that wraps `agent.send()` with
70319
- * `SendOptions.model.params` when both `#fastMode === true` AND
70320
- * `#fastModeParam !== null`.
70620
+ * and `set_fast_mode` IPC (subsequent toggles). The runner attaches
70621
+ * `SendOptions.model.params` with value `"true"`/`"false"` whenever
70622
+ * `#fastModeParam` is non-null; `#fastMode` selects which value.
70321
70623
  */
70322
70624
  #fastMode = false;
70323
70625
  /**
70324
- * Resolved Fast Mode binding for this task's `modelId`. Frozen at spawn
70325
- * time — the daemon never re-discovers per-task because Cursor model
70326
- * Fast-Mode bindings are stable for the lifetime of a session. A future
70327
- * `setModel` call could in principle invalidate this; today the runner
70328
- * just keeps using the binding it was given.
70626
+ * Resolved Fast Mode ON-binding for this task's `modelId`. Frozen at
70627
+ * spawn time — the daemon never re-discovers per-task because Cursor
70628
+ * Fast Mode bindings are stable for the lifetime of a session. Null
70629
+ * means the model exposes no Fast Mode knob (omit `model.params`).
70329
70630
  */
70330
70631
  #fastModeParam = null;
70331
70632
  /**
@@ -71440,12 +71741,7 @@ var CursorAgentSubprocess = class {
71440
71741
  status: normalizeCursorMcpStatus(s2.status)
71441
71742
  }));
71442
71743
  if (this.#mcpStatus.length === 0) {
71443
- const fallback = [HARNESS_SERVER_NAME, ...this.#mcpServerSet].map(
71444
- (name) => ({
71445
- name,
71446
- status: "connected"
71447
- })
71448
- );
71744
+ const fallback = [{ name: HARNESS_SERVER_NAME, status: "connected" }];
71449
71745
  this.#mcpStatus = fallback;
71450
71746
  logger.info({
71451
71747
  event: "cursor_subprocess_mcp_status_seeded_from_spawn",
@@ -71759,49 +72055,41 @@ function createCursorMeCache(deps) {
71759
72055
  import { createHash as createHash8 } from "crypto";
71760
72056
 
71761
72057
  // src/services/session/profiles/cursor-fast-mode-resolver.ts
71762
- var FAST_MODE_ID_PREFIXES = ["fast", "speed", "tier", "pricing"];
71763
- var FAST_MODE_VALUE_PREFERENCE = ["fast", "priority", "high"];
71764
- function idHasFastModePrefix(rawId) {
71765
- const lower = rawId.toLowerCase();
71766
- for (const prefix of FAST_MODE_ID_PREFIXES) {
71767
- if (lower.startsWith(prefix)) return true;
71768
- }
71769
- return false;
71770
- }
71771
- function pickFastModeValue(values) {
71772
- const lowerToOriginal = /* @__PURE__ */ new Map();
72058
+ function findTrueFalseValues(values) {
72059
+ let trueValue;
72060
+ let falseValue;
71773
72061
  for (const entry of values) {
71774
- lowerToOriginal.set(entry.value.toLowerCase(), entry.value);
71775
- }
71776
- for (const preferred of FAST_MODE_VALUE_PREFERENCE) {
71777
- const match = lowerToOriginal.get(preferred);
71778
- if (match !== void 0) return match;
72062
+ const lower = entry.value.toLowerCase();
72063
+ if (lower === "true") trueValue = entry.value;
72064
+ if (lower === "false") falseValue = entry.value;
71779
72065
  }
71780
- return null;
72066
+ if (trueValue === void 0 || falseValue === void 0) return null;
72067
+ return { trueValue, falseValue };
71781
72068
  }
71782
72069
  function resolveCursorFastModeParam(model) {
71783
72070
  const warnings = [];
71784
72071
  const parameters = model.parameters ?? [];
71785
- const matching = parameters.filter(
71786
- (param) => idHasFastModePrefix(param.id) && pickFastModeValue(param.values) !== null
71787
- );
71788
- if (matching.length === 0) {
72072
+ const fastParams = parameters.filter((param) => param.id.toLowerCase() === "fast");
72073
+ if (fastParams.length === 0) {
71789
72074
  return { param: null, warnings };
71790
72075
  }
71791
- if (matching.length > 1) {
72076
+ if (fastParams.length > 1) {
71792
72077
  warnings.push(
71793
- `cursor model ${model.id} exposes ${matching.length} fast-mode-shaped parameters; picking first (${matching[0]?.id ?? "unknown"})`
72078
+ `cursor model ${model.id} exposes ${fastParams.length} parameters named "fast"; picking first`
71794
72079
  );
71795
72080
  }
71796
- const chosen = matching[0];
72081
+ const chosen = fastParams[0];
71797
72082
  if (chosen === void 0) {
71798
72083
  return { param: null, warnings };
71799
72084
  }
71800
- const value = pickFastModeValue(chosen.values);
71801
- if (value === null) {
72085
+ const pair = findTrueFalseValues(chosen.values);
72086
+ if (pair === null) {
72087
+ warnings.push(
72088
+ `cursor model ${model.id} has parameter id="fast" but values are not {true,false}; refusing to bind (values=[${chosen.values.map((v2) => v2.value).join(",")}])`
72089
+ );
71802
72090
  return { param: null, warnings };
71803
72091
  }
71804
- return { param: { id: chosen.id, value }, warnings };
72092
+ return { param: { id: chosen.id, value: pair.trueValue }, warnings };
71805
72093
  }
71806
72094
 
71807
72095
  // src/services/session/profiles/cursor-models-cache.ts
@@ -71860,6 +72148,7 @@ function createCursorModelsCache(deps) {
71860
72148
  function recordModel(model) {
71861
72149
  const { param, warnings } = resolveCursorFastModeParam(model);
71862
72150
  cache2.set(model.id, { fastModeParam: param });
72151
+ const catalogClaimsFast = CURSOR_MODEL_CATALOG.find((entry) => entry.id === model.id)?.supportsFastMode === true;
71863
72152
  if (param !== null) {
71864
72153
  deps.log("info", {
71865
72154
  event: "cursor_fast_mode_resolved",
@@ -71869,7 +72158,16 @@ function createCursorModelsCache(deps) {
71869
72158
  });
71870
72159
  } else {
71871
72160
  const reason = (model.parameters?.length ?? 0) === 0 ? "no_parameters" : "no_matching_param";
71872
- deps.log("info", { event: "cursor_fast_mode_unresolved", modelId: model.id, reason });
72161
+ if (catalogClaimsFast) {
72162
+ deps.log("warn", {
72163
+ event: "cursor_fast_mode_catalog_drift",
72164
+ modelId: model.id,
72165
+ reason,
72166
+ catalogSupportsFastMode: true
72167
+ });
72168
+ } else {
72169
+ deps.log("info", { event: "cursor_fast_mode_unresolved", modelId: model.id, reason });
72170
+ }
71873
72171
  }
71874
72172
  for (const warning of warnings) {
71875
72173
  deps.log("warn", {
@@ -71925,10 +72223,40 @@ function createCursorModelsCache(deps) {
71925
72223
  lastKeyHash = keyHash;
71926
72224
  lastRefreshAt = now();
71927
72225
  ready = true;
72226
+ deps.onRefreshComplete?.();
71928
72227
  }
71929
72228
  return { getFastModeParam, isReady, refresh };
71930
72229
  }
71931
72230
 
72231
+ // src/services/session/profiles/cursor-models-cache-wiring.ts
72232
+ function createWiredCursorModelsCache(deps) {
72233
+ let modelsApplier = null;
72234
+ const cache2 = createCursorModelsCache({
72235
+ listModels: deps.listModels,
72236
+ log: deps.log,
72237
+ onRefreshComplete: () => {
72238
+ const current = deps.getCurrentModels();
72239
+ if (current === void 0 || modelsApplier === null) return;
72240
+ if (!cache2.isReady()) return;
72241
+ const next = reconcileCursorSupportsFastMode(current, {
72242
+ isReady: () => true,
72243
+ supportsFastMode: (modelId) => cache2.getFastModeParam(modelId) !== null
72244
+ });
72245
+ void modelsApplier(next);
72246
+ }
72247
+ });
72248
+ setCursorFastModeSupportLookup({
72249
+ isReady: () => cache2.isReady(),
72250
+ supportsFastMode: (modelId) => cache2.getFastModeParam(modelId) !== null
72251
+ });
72252
+ return {
72253
+ cache: cache2,
72254
+ setModelsApplier: (apply) => {
72255
+ modelsApplier = apply;
72256
+ }
72257
+ };
72258
+ }
72259
+
71932
72260
  // src/services/session/profiles/cursor-vault-key-cache.ts
71933
72261
  function createCursorVaultKeyCache() {
71934
72262
  let current = null;
@@ -74335,6 +74663,12 @@ function logPerf(entry) {
74335
74663
  }
74336
74664
  }
74337
74665
  var DEFAULT_READ_CACHE_BUDGET_BYTES = 384 * 1024 * 1024;
74666
+ var READ_CACHE_HEAP_FRACTION = 0.05;
74667
+ var READ_CACHE_BUDGET_CAP_BYTES = 2048 * 1024 * 1024;
74668
+ function computeReadCacheBudgetBytes(heapSizeLimitBytes) {
74669
+ const scaled = Math.round(heapSizeLimitBytes * READ_CACHE_HEAP_FRACTION);
74670
+ return Math.min(READ_CACHE_BUDGET_CAP_BYTES, Math.max(DEFAULT_READ_CACHE_BUDGET_BYTES, scaled));
74671
+ }
74338
74672
  function buildJsonlConversationStore(dataDir, opts = {}) {
74339
74673
  const channelsDir = join67(dataDir, "channels");
74340
74674
  const seqCounters = /* @__PURE__ */ new Map();
@@ -75982,7 +76316,7 @@ var LoroDocSweeper = class {
75982
76316
  };
75983
76317
 
75984
76318
  // src/services/task/manager/task-manager.ts
75985
- import { existsSync as existsSync12 } from "fs";
76319
+ import { existsSync as existsSync13 } from "fs";
75986
76320
  import { homedir as homedir13 } from "os";
75987
76321
  import { dirname as dirname37 } from "path";
75988
76322
 
@@ -78488,6 +78822,13 @@ function handleSpawning(snapshot, event) {
78488
78822
  }
78489
78823
  return noop(snapshot);
78490
78824
  }
78825
+ function buildStallGiveUpMessage(recoveryAttempts) {
78826
+ if (recoveryAttempts <= 0) {
78827
+ return "The agent stopped responding mid-run. Send a message to try again.";
78828
+ }
78829
+ const plural = recoveryAttempts === 1 ? "time" : "times";
78830
+ return `The agent stopped responding mid-run. Automatic recovery already retried ${recoveryAttempts} ${plural} without success \u2014 send a message to resume.`;
78831
+ }
78491
78832
  function runningStallTimeout(sessionId, event) {
78492
78833
  const b2 = createEffectBuilder();
78493
78834
  b2.log("running", "resumable_idle", event.type);
@@ -78495,11 +78836,7 @@ function runningStallTimeout(sessionId, event) {
78495
78836
  b2.effects.push({ type: "clear_queue" });
78496
78837
  if (!event.recoveryArmed) {
78497
78838
  b2.taskStatus("input_required");
78498
- b2.emitError(
78499
- "The agent stopped responding mid-run. Send a message to try again.",
78500
- "sdk_error",
78501
- "run_stall_timeout"
78502
- );
78839
+ b2.emitError(buildStallGiveUpMessage(event.recoveryAttempts), "sdk_error", "run_stall_timeout");
78503
78840
  }
78504
78841
  b2.effects.push({ type: "kill_runner" });
78505
78842
  return {
@@ -79320,10 +79657,13 @@ var AgentSessionManager = class {
79320
79657
  return;
79321
79658
  }
79322
79659
  let recoveryArmed = false;
79660
+ let recoveryAttempts = 0;
79323
79661
  try {
79324
- recoveryArmed = this.#config.onStallTimeout?.() ?? false;
79662
+ const outcome = this.#config.onStallTimeout?.() ?? { armed: false, recoveryAttempts: 0 };
79663
+ recoveryArmed = outcome.armed;
79664
+ recoveryAttempts = outcome.recoveryAttempts;
79325
79665
  } finally {
79326
- this.#dispatch({ type: "run_stall_timeout", recoveryArmed });
79666
+ this.#dispatch({ type: "run_stall_timeout", recoveryArmed, recoveryAttempts });
79327
79667
  }
79328
79668
  }, timeoutMs);
79329
79669
  }
@@ -79351,48 +79691,98 @@ var AgentSessionManager = class {
79351
79691
 
79352
79692
  // src/services/conversation/cursor-recovery-controller.ts
79353
79693
  var MAX_SIGNAL_DEATH_RETRIES = 1;
79354
- var MAX_STALL_RESUME_RETRIES = 1;
79694
+ var MAX_STALL_RESUME_RETRIES = 3;
79695
+ var STALL_RESUME_BASE_DELAY_MS = 3e3;
79696
+ var STALL_RESUME_BACKOFF_MULTIPLIER = 3;
79697
+ function stallResumeDelayMs(attempt) {
79698
+ return STALL_RESUME_BASE_DELAY_MS * STALL_RESUME_BACKOFF_MULTIPLIER ** (attempt - 1);
79699
+ }
79355
79700
  var MAX_CRASH_DEATH_RETRIES = 1;
79356
79701
  var CursorRecoveryController = class {
79357
79702
  #signalDeathRetries = 0;
79358
79703
  #stallResumeRetries = 0;
79359
79704
  #stallResumeArmed = false;
79360
79705
  #crashDeathRetries = 0;
79706
+ /**
79707
+ * Pending timer for the settle-delay before an armed stall resume fires.
79708
+ * Cleared at every call site that supersedes the in-flight resume (new
79709
+ * user turn, stop, rewind, dispose) plus its own callback on fire — the
79710
+ * same shape as `RateLimitFlapController#timer`. A single handle suffices;
79711
+ * `consumeStallResumeOnDeath` cannot run twice without an intervening
79712
+ * `onStallTimeout` re-arming it.
79713
+ */
79714
+ #stallResumeTimer = null;
79361
79715
  #deps;
79362
79716
  constructor(deps) {
79363
79717
  this.#deps = deps;
79364
79718
  }
79719
+ /**
79720
+ * Cancel a pending delayed stall resume. Idempotent. Call from any path
79721
+ * that supersedes the armed resume (new user turn, stop, rewind, dispose)
79722
+ * — without this, a fresh intent would race the deferred
79723
+ * `respawnForResume()` fired by the settle-delay timer.
79724
+ */
79725
+ cancelPendingResume() {
79726
+ if (this.#stallResumeTimer !== null) {
79727
+ clearTimeout(this.#stallResumeTimer);
79728
+ this.#stallResumeTimer = null;
79729
+ }
79730
+ }
79365
79731
  /** Reset every budget. Call on each new user turn and on turn_complete. */
79366
79732
  reset() {
79733
+ this.cancelPendingResume();
79367
79734
  this.#signalDeathRetries = 0;
79368
79735
  this.#stallResumeRetries = 0;
79369
79736
  this.#stallResumeArmed = false;
79370
79737
  this.#crashDeathRetries = 0;
79371
79738
  }
79739
+ /**
79740
+ * Call on `turn_complete` instead of `reset()`. A successful auto-recovery
79741
+ * otherwise leaves no trace that the daemon self-healed rather than the
79742
+ * run simply never stalling — log the recovery before clearing the budget.
79743
+ */
79744
+ noteTurnCompleteRecovery() {
79745
+ if (this.#stallResumeRetries > 0) {
79746
+ this.#deps.log({
79747
+ event: "thread_cursor_stall_auto_resume_recovered",
79748
+ threadId: this.#deps.threadId,
79749
+ attempts: this.#stallResumeRetries
79750
+ });
79751
+ }
79752
+ this.reset();
79753
+ }
79372
79754
  /**
79373
79755
  * Fired by the manager when the stall watchdog expires, BEFORE the
79374
79756
  * `run_stall_timeout` event transitions the FSM. A mid-run Cursor stall
79375
79757
  * otherwise lands in `resumable_idle` with a "send a message to try again"
79376
- * banner and strands the orphaned turn until the user returns. Arm a one-shot
79377
- * resume so the imminent `kill_runner` death is resumed instead of surfaced.
79378
- * On repeat (cap exceeded) stay armed-off and let the existing banner run.
79758
+ * banner and strands the orphaned turn until the user returns. Arm a
79759
+ * resume so the imminent `kill_runner` death is resumed instead of
79760
+ * surfaced. On repeat, up to `MAX_STALL_RESUME_RETRIES` times, stay
79761
+ * armed-off and let the existing banner run.
79379
79762
  *
79380
- * Returns true when a recovery was armed — the manager passes this as
79381
- * `recoveryArmed` on the `run_stall_timeout` FSM event so the FSM suppresses
79382
- * the user-facing error banner during the 5-second self-heal (Fix B2).
79763
+ * Returns `armed: true` when a recovery was armed — the manager passes
79764
+ * this as `recoveryArmed` on the `run_stall_timeout` FSM event so the FSM
79765
+ * suppresses the user-facing error banner during the self-heal.
79766
+ * `recoveryAttempts` is only ever positive on the cap-exceeded give-up
79767
+ * (see `StallTimeoutOutcome`), letting the FSM distinguish "never
79768
+ * attempted" from "tried N times".
79383
79769
  */
79384
79770
  onStallTimeout() {
79385
- if (this.#deps.getState() !== "running") return false;
79386
- if (this.#deps.getAgentSystem() !== AGENT_SYSTEM_CURSOR) return false;
79771
+ if (this.#deps.getState() !== "running") return { armed: false, recoveryAttempts: 0 };
79772
+ if (this.#deps.getAgentSystem() !== AGENT_SYSTEM_CURSOR) {
79773
+ return { armed: false, recoveryAttempts: 0 };
79774
+ }
79387
79775
  this.#stallResumeRetries++;
79388
79776
  if (this.#stallResumeRetries > MAX_STALL_RESUME_RETRIES) {
79777
+ const recoveryAttempts = this.#stallResumeRetries - 1;
79389
79778
  this.#deps.log({
79390
79779
  event: "thread_cursor_stall_resume_cap_exceeded",
79391
79780
  threadId: this.#deps.threadId,
79392
- retries: this.#stallResumeRetries
79781
+ retries: this.#stallResumeRetries,
79782
+ maxRetries: MAX_STALL_RESUME_RETRIES
79393
79783
  });
79394
79784
  this.#stallResumeRetries = 0;
79395
- return false;
79785
+ return { armed: false, recoveryAttempts };
79396
79786
  }
79397
79787
  this.#deps.log({
79398
79788
  event: "thread_cursor_stall_resume_armed",
@@ -79400,19 +79790,41 @@ var CursorRecoveryController = class {
79400
79790
  retries: this.#stallResumeRetries
79401
79791
  });
79402
79792
  this.#stallResumeArmed = true;
79403
- return true;
79793
+ return { armed: true, recoveryAttempts: 0 };
79404
79794
  }
79405
79795
  /**
79406
- * Consume an armed stall resume on the `kill_runner` death. By now the FSM is
79407
- * in `resumable_idle` and the subprocess was already cleared by Thread's
79408
- * force-kill, so this only re-spawns. Race-free: we resume ON the death, so
79409
- * the hung runner is confirmed gone. Returns true when it handled the death.
79796
+ * Consume an armed stall resume on the `kill_runner` death. By now the FSM
79797
+ * is in `resumable_idle` and the subprocess was already cleared by
79798
+ * Thread's force-kill. Schedule the actual resume after
79799
+ * `stallResumeDelayMs(attempt)` rather than firing immediately — the
79800
+ * settle window that prevents the busy collision (see
79801
+ * `MAX_STALL_RESUME_RETRIES`'s doc comment). Returns true as soon as it
79802
+ * commits to handling the death (the timer is in-flight); the caller must
79803
+ * not also surface it.
79410
79804
  */
79411
79805
  consumeStallResumeOnDeath() {
79412
79806
  if (!this.#stallResumeArmed) return false;
79413
79807
  this.#stallResumeArmed = false;
79414
- this.#deps.log({ event: "thread_cursor_stall_auto_resume", threadId: this.#deps.threadId });
79415
- this.#deps.respawnForResume();
79808
+ this.cancelPendingResume();
79809
+ const attempt = this.#stallResumeRetries;
79810
+ const delayMs = stallResumeDelayMs(attempt);
79811
+ this.#deps.log({
79812
+ event: "thread_cursor_stall_auto_resume_scheduled",
79813
+ threadId: this.#deps.threadId,
79814
+ attempt,
79815
+ delayMs
79816
+ });
79817
+ this.#stallResumeTimer = setTimeout(() => {
79818
+ this.#stallResumeTimer = null;
79819
+ if (this.#deps.isDisposed()) return;
79820
+ this.#deps.log({
79821
+ event: "thread_cursor_stall_auto_resume",
79822
+ threadId: this.#deps.threadId,
79823
+ attempt,
79824
+ delayMs
79825
+ });
79826
+ this.#deps.respawnForResume();
79827
+ }, delayMs);
79416
79828
  return true;
79417
79829
  }
79418
79830
  /**
@@ -80523,6 +80935,7 @@ var Thread = class {
80523
80935
  threadId: config.threadId,
80524
80936
  getState: () => this.#manager.state,
80525
80937
  getAgentSystem: () => this.#agentSystem,
80938
+ isDisposed: () => this.#disposed,
80526
80939
  log: config.log,
80527
80940
  clearSubprocess: () => {
80528
80941
  this.#subprocess?.setOnCwdChanged(null);
@@ -80683,8 +81096,16 @@ var Thread = class {
80683
81096
  * Unlike `handleUserMessage`, this does NOT call `#recovery.noteNewUserTurn()`
80684
81097
  * (which resets the request-aborted retry budget) because a synthetic wake is
80685
81098
  * not a real user message.
81099
+ *
81100
+ * DOES cancel a pending flap/stall-resume timer, same reasoning as `stop()`
81101
+ * and `rewind()`: `sendMessage` from `resumable_idle` walks the FSM to
81102
+ * `spawning` on its own, so a still-armed timer firing later would call
81103
+ * `respawnForResume()` against whatever run this wake started — a resume
81104
+ * with nothing to do with the death it was originally scheduled for.
80686
81105
  */
80687
81106
  pushSyntheticMessageAndWake(content) {
81107
+ this.#rateLimitFlap.cancelPendingRetry();
81108
+ this.#cursorRecovery.cancelPendingResume();
80688
81109
  this.#manager.sendMessage(content);
80689
81110
  }
80690
81111
  /**
@@ -80725,6 +81146,7 @@ var Thread = class {
80725
81146
  }
80726
81147
  stop() {
80727
81148
  this.#rateLimitFlap.cancelPendingRetry();
81149
+ this.#cursorRecovery.cancelPendingResume();
80728
81150
  this.#manager.stop();
80729
81151
  }
80730
81152
  /**
@@ -80734,6 +81156,7 @@ var Thread = class {
80734
81156
  */
80735
81157
  rewind(resumeAtMessageId) {
80736
81158
  this.#rateLimitFlap.cancelPendingRetry();
81159
+ this.#cursorRecovery.cancelPendingResume();
80737
81160
  this.#manager.rewind(resumeAtMessageId);
80738
81161
  }
80739
81162
  notifySetupReady() {
@@ -80887,6 +81310,7 @@ var Thread = class {
80887
81310
  await this.#asyncQueue;
80888
81311
  this.#disposed = true;
80889
81312
  this.#rateLimitFlap.cancelPendingRetry();
81313
+ this.#cursorRecovery.cancelPendingResume();
80890
81314
  this.#permissionHandler.denyAllPending("Thread disposed");
80891
81315
  this.#permissionQueue.length = 0;
80892
81316
  this.#streamDelta.dispose();
@@ -81527,7 +81951,7 @@ var Thread = class {
81527
81951
  });
81528
81952
  this.#streamDelta.resetForTurn();
81529
81953
  this.#recovery.resetRetriesOnTurnComplete();
81530
- this.#cursorRecovery.reset();
81954
+ this.#cursorRecovery.noteTurnCompleteRecovery();
81531
81955
  this.#rateLimitFlap.noteRecoveredOnTurnComplete((event.result.outputTokens ?? 0) > 0);
81532
81956
  this.#emitThreadTurnStats(event.result);
81533
81957
  this.#manager.notifyTurnComplete();
@@ -81691,7 +82115,7 @@ var Thread = class {
81691
82115
 
81692
82116
  // src/services/plan/plan-handler.ts
81693
82117
  import { randomUUID as randomUUID21 } from "crypto";
81694
- import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
82118
+ import { existsSync as existsSync11, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
81695
82119
  import { mkdir as mkdir33, readFile as readFile45, writeFile as writeFile26 } from "fs/promises";
81696
82120
  import { homedir as homedir10 } from "os";
81697
82121
  import { dirname as dirname34, join as join73 } from "path";
@@ -81803,7 +82227,7 @@ function decideWatchResolved(trigger, snapshot) {
81803
82227
 
81804
82228
  // src/services/plan/plan-file-bridge.ts
81805
82229
  import { createHash as createHash12, randomUUID as randomUUID20 } from "crypto";
81806
- import { existsSync as existsSync8 } from "fs";
82230
+ import { existsSync as existsSync9 } from "fs";
81807
82231
  import { readFile as readFile44, rename as rename18, writeFile as writeFile25 } from "fs/promises";
81808
82232
  import { basename as basename13, dirname as dirname33 } from "path";
81809
82233
  var FILE_READ_MAX_RETRIES = 3;
@@ -81998,7 +82422,7 @@ var PlanFileBridge = class _PlanFileBridge {
81998
82422
  */
81999
82423
  async #startDirectoryWatcher(filePath) {
82000
82424
  const dir = dirname33(filePath);
82001
- if (!existsSync8(dir)) return;
82425
+ if (!existsSync9(dir)) return;
82002
82426
  const targetFilename = basename13(filePath);
82003
82427
  let sub;
82004
82428
  try {
@@ -82231,7 +82655,7 @@ var PlanFileBridge = class _PlanFileBridge {
82231
82655
  }
82232
82656
  async #doWriteBackToFile() {
82233
82657
  if (this.#disposed || !this.#filePath) return;
82234
- if (!existsSync8(this.#filePath)) return;
82658
+ if (!existsSync9(this.#filePath)) return;
82235
82659
  const content = this.#config.planRepo.getContent(this.#config.taskId);
82236
82660
  const hash = contentHash(content);
82237
82661
  if (hash === this.#lastWrittenHash) return;
@@ -82462,7 +82886,7 @@ function greedyLCS(a, b2) {
82462
82886
  }
82463
82887
 
82464
82888
  // src/services/plan/plan-file-watcher.ts
82465
- import { existsSync as existsSync9 } from "fs";
82889
+ import { existsSync as existsSync10 } from "fs";
82466
82890
  import { homedir as homedir9 } from "os";
82467
82891
  import { join as join72 } from "path";
82468
82892
  var DEFAULT_PLANS_DIR = join72(homedir9(), ".claude", "plans");
@@ -82525,7 +82949,7 @@ function createPlanFileWatcher(deps) {
82525
82949
  deps.onTimeout();
82526
82950
  }, PLAN_WATCH_TIMEOUT_MS);
82527
82951
  const plansDir = deps.plansDir ?? DEFAULT_PLANS_DIR;
82528
- if (!existsSync9(plansDir)) {
82952
+ if (!existsSync10(plansDir)) {
82529
82953
  deps.log({
82530
82954
  event: "plan_watch_skip_missing_dir",
82531
82955
  taskId: deps.taskId,
@@ -82988,7 +83412,7 @@ var PlanHandler = class {
82988
83412
  }
82989
83413
  async #rehydratePlanFileBridge(filePath, toolUseId) {
82990
83414
  if (this.#deps.isDisposed()) return;
82991
- if (!existsSync10(filePath)) {
83415
+ if (!existsSync11(filePath)) {
82992
83416
  this.#deps.log({
82993
83417
  event: "plan_bridge_rehydrate_file_missing",
82994
83418
  taskId: this.#deps.taskId,
@@ -83687,7 +84111,7 @@ var PlanHandler = class {
83687
84111
  return newest?.path ?? null;
83688
84112
  }
83689
84113
  const plansDir = join73(homedir10(), ".claude", "plans");
83690
- if (!existsSync10(plansDir)) return null;
84114
+ if (!existsSync11(plansDir)) return null;
83691
84115
  try {
83692
84116
  const files = readdirSync5(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
83693
84117
  path: join73(plansDir, f2),
@@ -89478,7 +89902,7 @@ async function resolveTaskListPrepend(ctx) {
89478
89902
  }
89479
89903
 
89480
89904
  // src/services/task/cc-task-file-store.ts
89481
- import { existsSync as existsSync11 } from "fs";
89905
+ import { existsSync as existsSync12 } from "fs";
89482
89906
  import { readdir as readdir21, readFile as readFile47 } from "fs/promises";
89483
89907
  import { homedir as homedir11 } from "os";
89484
89908
  import { basename as basename14, dirname as dirname36, join as join75 } from "path";
@@ -89607,7 +90031,7 @@ function createCCTaskFileWatcher(listId, log) {
89607
90031
  }
89608
90032
  }
89609
90033
  async function startDirWatcher(reason = "initial") {
89610
- if (!existsSync11(dir)) return false;
90034
+ if (!existsSync12(dir)) return false;
89611
90035
  try {
89612
90036
  const sub = await guardedSubscribe(dir, onDirEvent, { ignore: ["**/*/"] }, "lazy", reason);
89613
90037
  if (watchState.stopped) {
@@ -94249,12 +94673,22 @@ function pushTaskMcpStateSnapshot(ctx, taskId) {
94249
94673
  for (const s2 of ctx.sessionMcpsByTask.get(taskId) ?? []) {
94250
94674
  sessionServers[s2.name] = { status: normalizeMcpStatus(s2.status) };
94251
94675
  }
94676
+ const liveStatuses = liveStatusesFromStatusEntries(ctx.getMcpCoordinator()?.getStatuses() ?? []);
94677
+ for (const divergence of collectMcpSessionCoordinatorDivergences(sessionServers, liveStatuses)) {
94678
+ logger.info({
94679
+ event: "mcp_session_coordinator_status_divergence",
94680
+ taskId,
94681
+ serverName: divergence.name,
94682
+ sessionStatus: divergence.sessionStatus,
94683
+ coordinatorStatus: divergence.coordinatorStatus
94684
+ });
94685
+ }
94252
94686
  const message = {
94253
94687
  type: "mcp_state_snapshot",
94254
94688
  taskId,
94255
94689
  servers: resolveMcpStateSnapshot({
94256
94690
  workspaceServers: workspaceServersFromCapabilities(ctx.getCapabilities()),
94257
- liveStatuses: liveStatusesFromStatusEntries(ctx.getMcpCoordinator()?.getStatuses() ?? []),
94691
+ liveStatuses,
94258
94692
  sessionServers,
94259
94693
  perTaskDisabled: settings.disabledMcpServers ?? null,
94260
94694
  perTaskSnapshot: settings.mcpServerSnapshot ?? null
@@ -96547,16 +96981,6 @@ var TaskManager = class {
96547
96981
  getOrchestrator(taskId) {
96548
96982
  return this.#tasks.get(taskId)?.orchestrator ?? null;
96549
96983
  }
96550
- /**
96551
- * True if the task has an orchestrator with a live or resumable session.
96552
- * cold_idle orchestrators are NOT considered running — the SDK session is
96553
- * expired and the task needs user input to restart.
96554
- */
96555
- isRunning(taskId) {
96556
- const task = this.#tasks.get(taskId);
96557
- if (!task) return false;
96558
- return task.orchestrator.state !== "cold_idle";
96559
- }
96560
96984
  get size() {
96561
96985
  return this.#tasks.size;
96562
96986
  }
@@ -96951,7 +97375,7 @@ var TaskManager = class {
96951
97375
  function pathExistsForLog2(path5) {
96952
97376
  if (!path5) return false;
96953
97377
  try {
96954
- return existsSync12(path5);
97378
+ return existsSync13(path5);
96955
97379
  } catch {
96956
97380
  return false;
96957
97381
  }
@@ -97594,7 +98018,9 @@ async function createDaemon(deps) {
97594
98018
  const compactor = startLoroCompactor(storageAdapter);
97595
98019
  const taskStateStore = buildTaskStateStore(deps.dataDir);
97596
98020
  const planRepo = new PlanRepository(repo, PLAN_EPOCH, createPlanContentBridge(), storageAdapter);
97597
- const jsonlStore = buildJsonlConversationStore(deps.dataDir);
98021
+ const jsonlStore = buildJsonlConversationStore(deps.dataDir, {
98022
+ readCacheBudgetBytes: computeReadCacheBudgetBytes(getHeapStatistics().heap_size_limit)
98023
+ });
97598
98024
  const store = buildObservableConversationStore(jsonlStore);
97599
98025
  const sessionPersistence = buildSessionPersistence(deps.dataDir);
97600
98026
  const collabQueuePersistence = buildCollabQueuePersistence(deps.dataDir);
@@ -97654,14 +98080,15 @@ async function createDaemon(deps) {
97654
98080
  const CURSOR_VAULT_WRITE_TIMEOUT_MS = 5e3;
97655
98081
  const cursorStoredByRef = { current: "" };
97656
98082
  const cursorVaultKeyCache = createCursorVaultKeyCache();
97657
- const cursorModelsCache = createCursorModelsCache({
98083
+ const { cache: cursorModelsCache, setModelsApplier: setCursorFastModeModelsApplierFn } = createWiredCursorModelsCache({
97658
98084
  listModels: async (options) => {
97659
98085
  const { Cursor } = await import("@cursor/sdk");
97660
98086
  return Cursor.models.list(options);
97661
98087
  },
97662
98088
  log: (level, entry) => {
97663
98089
  logger[level](entry);
97664
- }
98090
+ },
98091
+ getCurrentModels: () => capabilitiesRef.current?.models
97665
98092
  });
97666
98093
  cursorVaultKeyCache.subscribe((key) => {
97667
98094
  void cursorModelsCache.refresh(key);
@@ -99792,6 +100219,7 @@ async function createDaemon(deps) {
99792
100219
  clearCursorVaultCache: () => {
99793
100220
  cursorVaultKeyCache.set(null);
99794
100221
  },
100222
+ setCursorFastModeModelsApplier: setCursorFastModeModelsApplierFn,
99795
100223
  notifyCodexAccountId: (accountId) => {
99796
100224
  codexEngineRef.current?.notifyAccountId(accountId);
99797
100225
  },
@@ -99921,7 +100349,7 @@ async function createDaemon(deps) {
99921
100349
  }
99922
100350
 
99923
100351
  // src/services/serve-helpers.ts
99924
- import { existsSync as existsSync13 } from "fs";
100352
+ import { existsSync as existsSync14 } from "fs";
99925
100353
 
99926
100354
  // src/services/bootstrap/self-update.ts
99927
100355
  import { execFile as execFile11, spawn as spawn13 } from "child_process";
@@ -100589,7 +101017,7 @@ function resolveClaudeCodePath(log) {
100589
101017
  log({ event: "claude_code_path_resolved", source: "sdk_default" });
100590
101018
  return void 0;
100591
101019
  }
100592
- if (!existsSync13(envPath)) {
101020
+ if (!existsSync14(envPath)) {
100593
101021
  log({ event: "claude_code_path_skipped", source: "env", path: envPath, reason: "missing" });
100594
101022
  log({ event: "claude_code_path_resolved", source: "sdk_default" });
100595
101023
  return void 0;
@@ -101978,24 +102406,6 @@ function buildPluginConfigStore(pluginsDir) {
101978
102406
  };
101979
102407
  }
101980
102408
 
101981
- // src/services/task/session-activity.ts
101982
- function isActiveUserWork(state) {
101983
- switch (state) {
101984
- case "spawning":
101985
- case "running":
101986
- case "stopping":
101987
- return true;
101988
- case "warm_idle":
101989
- case "resumable_idle":
101990
- case "cold_idle":
101991
- return false;
101992
- default: {
101993
- const _exhaustive = state;
101994
- throw new Error(`Unhandled session state: ${JSON.stringify(_exhaustive)}`);
101995
- }
101996
- }
101997
- }
101998
-
101999
102409
  // src/services/task/electron-idle-rpc.ts
102000
102410
  function registerElectronIdleRpc(taskManager, parentPort2) {
102001
102411
  parentPort2.on("message", (event) => {
@@ -102613,6 +103023,9 @@ async function runServeBody(options, captureRefs) {
102613
103023
  log: logAdapter
102614
103024
  });
102615
103025
  codexSkillsApplyScopedPatchRef = applyScopedPatch;
103026
+ daemon.setCursorFastModeModelsApplier(async (models) => {
103027
+ await applyScopedPatch({ models });
103028
+ });
102616
103029
  void (async () => {
102617
103030
  try {
102618
103031
  const seed = await loadCodexSkillsCache(codexSkillsCachePath);
@@ -103067,4 +103480,4 @@ export {
103067
103480
  decideWorkspaceScope,
103068
103481
  serve
103069
103482
  };
103070
- //# sourceMappingURL=serve-J3MG3WHK.js.map
103483
+ //# sourceMappingURL=serve-RWFBSQIE.js.map