@schoolai/shipyard 3.10.0 → 3.11.0-rc.20260610.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@ import {
12
12
  guardedSubscribe,
13
13
  makeInitialAttemptState,
14
14
  shutdownFileWatcherGuard
15
- } from "./chunk-AEUTFH76.js";
15
+ } from "./chunk-L7ELOV3S.js";
16
16
  import {
17
17
  TRAILER_SCHEMA_VERSION,
18
18
  appendTrailerToMessage,
@@ -28,7 +28,7 @@ import {
28
28
  } from "./chunk-3WEEGJJN.js";
29
29
  import {
30
30
  getDaemonVersion
31
- } from "./chunk-4THTCNVI.js";
31
+ } from "./chunk-ZRJTZLRF.js";
32
32
  import {
33
33
  getRecentWasmPanicMessages
34
34
  } from "./chunk-7H34LI75.js";
@@ -146,7 +146,7 @@ import {
146
146
  translateWarningNotification,
147
147
  unresolveReviewThread,
148
148
  writeCursorApiKeyToVault
149
- } from "./chunk-EI4HMJ54.js";
149
+ } from "./chunk-C6QOTETH.js";
150
150
  import {
151
151
  buildNodeSpawnEnv,
152
152
  nodeSpawnEnvOverlay,
@@ -399,7 +399,7 @@ import {
399
399
  unsafeAssertSourceId,
400
400
  vizFileExtension,
401
401
  withPreferredRuntimeAuthMethod
402
- } from "./chunk-VKCGK333.js";
402
+ } from "./chunk-YXPPZQBJ.js";
403
403
  import {
404
404
  AuthGitHubCallbackRequestSchema,
405
405
  AuthGitHubCallbackResponseSchema,
@@ -444,7 +444,7 @@ import {
444
444
  VaultKeyPutRequestSchema,
445
445
  VaultKeyPutResponseSchema,
446
446
  classifyClaudeCodeCompatibility
447
- } from "./chunk-4PBXNWJV.js";
447
+ } from "./chunk-RMLQ5DRP.js";
448
448
  import {
449
449
  external_exports as external_exports2
450
450
  } from "./chunk-EHQITHQX.js";
@@ -472,7 +472,7 @@ import "./chunk-2H7UOFLK.js";
472
472
  // src/services/serve.ts
473
473
  import { mkdir as mkdir44, realpath as realpath3 } from "fs/promises";
474
474
  import { homedir as homedir14 } from "os";
475
- import { join as join86 } from "path";
475
+ import { join as join87 } from "path";
476
476
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
477
477
 
478
478
  // ../../node_modules/.pnpm/@logtape+logtape@1.3.7/node_modules/@logtape/logtape/dist/level.js
@@ -12998,6 +12998,7 @@ async function writePeerIdAtomic(target, peerId) {
12998
12998
  import { readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
12999
12999
  import { join as join12 } from "path";
13000
13000
  var PID_FILE = "children.pid";
13001
+ var GRACE_TERM_TO_KILL_MS = 2e3;
13001
13002
  function buildPidTracker(shipyardHome) {
13002
13003
  const filePath = join12(shipyardHome, PID_FILE);
13003
13004
  let queue = Promise.resolve();
@@ -13043,14 +13044,10 @@ function buildPidTracker(shipyardHome) {
13043
13044
  return enqueue(async () => {
13044
13045
  const pids = await readPids();
13045
13046
  if (pids.length === 0) return 0;
13046
- let killed = 0;
13047
- for (const pid of pids) {
13048
- try {
13049
- process.kill(pid, "SIGTERM");
13050
- killed++;
13051
- log({ event: "orphan_killed", pid });
13052
- } catch {
13053
- }
13047
+ const killed = sendSigtermToEach(pids, log);
13048
+ if (killed > 0) {
13049
+ await new Promise((resolve10) => setTimeout(resolve10, GRACE_TERM_TO_KILL_MS));
13050
+ escalateSigkillToSurvivors(pids, log);
13054
13051
  }
13055
13052
  await writePids([]);
13056
13053
  log({ event: "orphan_sweep_complete", found: pids.length, killed });
@@ -13059,6 +13056,28 @@ function buildPidTracker(shipyardHome) {
13059
13056
  }
13060
13057
  };
13061
13058
  }
13059
+ function sendSigtermToEach(pids, log) {
13060
+ let killed = 0;
13061
+ for (const pid of pids) {
13062
+ try {
13063
+ process.kill(pid, "SIGTERM");
13064
+ killed++;
13065
+ log({ event: "orphan_killed", pid, signal: "SIGTERM" });
13066
+ } catch {
13067
+ }
13068
+ }
13069
+ return killed;
13070
+ }
13071
+ function escalateSigkillToSurvivors(pids, log) {
13072
+ for (const pid of pids) {
13073
+ try {
13074
+ process.kill(pid, 0);
13075
+ process.kill(pid, "SIGKILL");
13076
+ log({ event: "orphan_killed", pid, signal: "SIGKILL" });
13077
+ } catch {
13078
+ }
13079
+ }
13080
+ }
13062
13081
 
13063
13082
  // src/services/bootstrap/repo-reload.ts
13064
13083
  function buildGetDocDiagnostics(repoRef) {
@@ -13115,7 +13134,7 @@ function nanoid(size2 = 21) {
13115
13134
  }
13116
13135
 
13117
13136
  // src/services/bootstrap/signaling.ts
13118
- var DAEMON_NPM_VERSION = true ? "3.10.0" : "unknown";
13137
+ var DAEMON_NPM_VERSION = true ? "3.11.0" : "unknown";
13119
13138
  function createDaemonSignaling(config) {
13120
13139
  const agentId = config.agentId ?? nanoid();
13121
13140
  function send(msg) {
@@ -14288,7 +14307,7 @@ function runTaskSearch(args) {
14288
14307
  }
14289
14308
 
14290
14309
  // src/services/channels/control-channel-wiring.ts
14291
- import { join as join34 } from "path";
14310
+ import { join as join35 } from "path";
14292
14311
 
14293
14312
  // src/shared/type-utils.ts
14294
14313
  function narrow(value) {
@@ -25647,7 +25666,7 @@ function spawnCodexLogout(callbacks) {
25647
25666
 
25648
25667
  // src/services/serve-factory-helpers.ts
25649
25668
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
25650
- import { join as join28 } from "path";
25669
+ import { join as join29 } from "path";
25651
25670
 
25652
25671
  // src/shared/capabilities/runtime/rate-limit-account-key.ts
25653
25672
  function providerForProfile(profile) {
@@ -26940,6 +26959,7 @@ async function healGlobalDefault(settingsStore, workspaceRoot, detectedCount, lo
26940
26959
 
26941
26960
  // src/services/session/anthropic-adapter.ts
26942
26961
  import { spawn as spawn9 } from "child_process";
26962
+ import { join as join27 } from "path";
26943
26963
 
26944
26964
  // src/shared/strip-ansi.ts
26945
26965
  function stripAnsi(text) {
@@ -26981,6 +27001,7 @@ var PREFIX_FALLBACK_CONTEXT_WINDOWS = [
26981
27001
  { prefix: "claude-opus-4-8", contextWindow: 1e6 },
26982
27002
  { prefix: "claude-opus-4-7", contextWindow: 1e6 },
26983
27003
  { prefix: "claude-opus-4-6", contextWindow: 1e6 },
27004
+ { prefix: "claude-fable-5", contextWindow: 1e6 },
26984
27005
  { prefix: "claude-opus-4", contextWindow: 2e5 },
26985
27006
  { prefix: "claude-sonnet-4", contextWindow: 2e5 },
26986
27007
  { prefix: "claude-haiku-4", contextWindow: 2e5 },
@@ -27055,7 +27076,12 @@ function prefsToFraction(prefs, maxTokens) {
27055
27076
  if (maxTokens <= 0) return void 0;
27056
27077
  return prefs.thresholdTokens / maxTokens;
27057
27078
  }
27058
- var ONE_M_SPAWN_PREFIXES = ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"];
27079
+ var ONE_M_SPAWN_PREFIXES = [
27080
+ "claude-opus-4-8",
27081
+ "claude-opus-4-7",
27082
+ "claude-opus-4-6",
27083
+ "claude-fable-5"
27084
+ ];
27059
27085
  function resolveSpawnModel(model) {
27060
27086
  if (!model) return model;
27061
27087
  if (model.includes("[1m]")) return model;
@@ -27278,6 +27304,12 @@ function hasActiveCollaborators(participants) {
27278
27304
  function shouldClearPersistence(status) {
27279
27305
  return status === "completed" || status === "canceled";
27280
27306
  }
27307
+ function isRefusalStopReason(stopReason) {
27308
+ return stopReason === "refusal";
27309
+ }
27310
+ function refusalBannerMessage() {
27311
+ return "The model declined this request (safety classifier).";
27312
+ }
27281
27313
  function classifyRoiTransition(nextStatus, roiEverRan) {
27282
27314
  if (nextStatus === "completed" || nextStatus === "canceled") return "emit-ended";
27283
27315
  if (nextStatus === "input_required" && roiEverRan) return "emit-ended";
@@ -27572,6 +27604,11 @@ function hasErrorCode(err3, code) {
27572
27604
  const record = err3;
27573
27605
  return record.code === code;
27574
27606
  }
27607
+ function errnoCode(err3) {
27608
+ if (typeof err3 !== "object" || err3 === null || !("code" in err3)) return void 0;
27609
+ const record = err3;
27610
+ return typeof record.code === "string" ? record.code : void 0;
27611
+ }
27575
27612
  function isCliFlagEffort(effort) {
27576
27613
  switch (effort) {
27577
27614
  case "low":
@@ -27651,6 +27688,13 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
27651
27688
  #onEvent;
27652
27689
  #spawnMcpServers;
27653
27690
  #harnessTaskIdSetter = null;
27691
+ /**
27692
+ * The epoch bound to this subprocess's in-process harness server at spawn
27693
+ * time (installed by serve-factory immediately after `setHarnessEpoch`).
27694
+ * Surfaced via the `harnessEpoch` getter so the pre-warm adoption handshake
27695
+ * writes the SAME epoch to the registry that the harness fence checks.
27696
+ */
27697
+ #harnessEpoch = null;
27654
27698
  /**
27655
27699
  * Mutable slot for the CwdChanged handler. The SDK hook is registered
27656
27700
  * unconditionally at spawn time and reads through this slot on each
@@ -27709,6 +27753,18 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
27709
27753
  installHarnessTaskIdSetter(setter) {
27710
27754
  this.#harnessTaskIdSetter = setter;
27711
27755
  }
27756
+ /**
27757
+ * Record the epoch serve-factory minted and bound to this subprocess's
27758
+ * in-process harness server (called right after `setHarnessEpoch`). The
27759
+ * pre-warm manager reads it back via `harnessEpoch` so adoption registers
27760
+ * the same epoch the fence will check — never a second, divergent UUID.
27761
+ */
27762
+ installHarnessEpoch(epoch) {
27763
+ this.#harnessEpoch = epoch;
27764
+ }
27765
+ get harnessEpoch() {
27766
+ return this.#harnessEpoch;
27767
+ }
27712
27768
  setHarnessTaskId(taskId) {
27713
27769
  if (!this.#harnessTaskIdSetter) {
27714
27770
  throw new Error("setHarnessTaskId called before installHarnessTaskIdSetter");
@@ -27867,7 +27923,24 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
27867
27923
  ...process.env,
27868
27924
  ...options.env,
27869
27925
  DISABLE_AUTO_COMPACT: "1",
27870
- CLAUDE_CODE_ENTRYPOINT: "shipyard"
27926
+ CLAUDE_CODE_ENTRYPOINT: "shipyard",
27927
+ /**
27928
+ * Isolate every `codex` binary the agent invokes (via Bash/shell
27929
+ * tool) from the user's real `~/.codex/auth.json`. The primary
27930
+ * codex engine (daemon-internal, spawned by `codex-facade.ts`'s
27931
+ * `getOrCreateCodexEngine`) reads `~/.codex` directly through its
27932
+ * own `resolveSpawnConfig` path and is NOT affected by this env.
27933
+ *
27934
+ * Value is keyed off `SHIPYARD_HOME` so in E2E tests it resolves
27935
+ * to the per-test isolated home (`${shipyardHome}/codex-isolated`),
27936
+ * and in the primary daemon it resolves to
27937
+ * `~/.shipyard/codex-isolated` — distinct from `~/.codex`.
27938
+ *
27939
+ * Must come AFTER the spread of `options.env` and `process.env`
27940
+ * so it always wins, preventing an ambient `CODEX_HOME` from the
27941
+ * developer's shell from leaking through to the agent subprocess.
27942
+ */
27943
+ CODEX_HOME: join27(getShipyardHome(), "codex-isolated")
27871
27944
  },
27872
27945
  disallowedTools: options.disallowedTools,
27873
27946
  skills: options.skills,
@@ -27944,9 +28017,17 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
27944
28017
  forceKill() {
27945
28018
  this.#forceKilled = true;
27946
28019
  if (this.#pidRef.current != null) {
28020
+ const pid = this.#pidRef.current;
27947
28021
  try {
27948
- process.kill(this.#pidRef.current, "SIGKILL");
27949
- } catch {
28022
+ process.kill(-pid, "SIGKILL");
28023
+ } catch (err3) {
28024
+ const code = errnoCode(err3);
28025
+ if (code === "EPERM" || code === "EINVAL") {
28026
+ try {
28027
+ process.kill(pid, "SIGKILL");
28028
+ } catch {
28029
+ }
28030
+ }
27950
28031
  }
27951
28032
  this.#pidRef.current = null;
27952
28033
  }
@@ -28261,7 +28342,15 @@ function createChildSpawner(stderrCallback, pidRef, onChildSpawned, onChildExite
28261
28342
  stdio: ["pipe", "pipe", stderrCallback ? "pipe" : "ignore"],
28262
28343
  signal: spawnOpts.signal,
28263
28344
  env: { ...spawnOpts.env, CLAUDECODE: void 0, NODE_OPTIONS: nodeOptions },
28264
- windowsHide: true
28345
+ windowsHide: true,
28346
+ /**
28347
+ * detached: true makes the child the leader of its own process group.
28348
+ * Combined with the process-group kill in forceKill() (`kill(-pid)`)
28349
+ * this ensures the agent's subprocesses (bash, gh, git, npx) are
28350
+ * reaped when we abort — they would otherwise be orphaned to launchd.
28351
+ * POSIX only (macOS + Linux); Windows is not shipped.
28352
+ */
28353
+ detached: true
28265
28354
  });
28266
28355
  if (!child.stdin || !child.stdout) {
28267
28356
  throw new Error("Failed to create stdio pipes for Claude subprocess");
@@ -28337,7 +28426,7 @@ import { homedir as homedir4 } from "os";
28337
28426
 
28338
28427
  // src/shared/capabilities/capabilities-cache.ts
28339
28428
  import { mkdir as mkdir13, readFile as readFile18, rename as rename11, unlink as unlink8, writeFile as writeFile14 } from "fs/promises";
28340
- import { dirname as dirname15, join as join27 } from "path";
28429
+ import { dirname as dirname15, join as join28 } from "path";
28341
28430
  var CAPABILITIES_CACHE_VERSION = 1;
28342
28431
  var GitRepoInfoCacheSchema = external_exports.object({
28343
28432
  path: external_exports.string(),
@@ -28371,7 +28460,7 @@ var CacheFileSchema = external_exports.object({
28371
28460
  mcpServers: external_exports.array(MCPServerInfoCacheSchema)
28372
28461
  });
28373
28462
  function cacheFilePath() {
28374
- return join27(getShipyardHome(), "data", "capabilities-cache.json");
28463
+ return join28(getShipyardHome(), "data", "capabilities-cache.json");
28375
28464
  }
28376
28465
  var EMPTY_ENVIRONMENTS = [];
28377
28466
  var EMPTY_MCP_SERVERS = [];
@@ -28637,7 +28726,7 @@ function appendDynamicPrompt(base2, appendix) {
28637
28726
  ${appendix}` : base2;
28638
28727
  }
28639
28728
  function rehydrateVizRegistry(registry, vizWatcher, resolvedTaskId, vizDir, log) {
28640
- const registryPath = join28(vizDir, resolvedTaskId, "registry.json");
28729
+ const registryPath = join29(vizDir, resolvedTaskId, "registry.json");
28641
28730
  if (!existsSync3(registryPath)) return Promise.resolve();
28642
28731
  try {
28643
28732
  const raw = readFileSync4(registryPath, "utf-8");
@@ -28874,7 +28963,7 @@ async function initializeVaultSync(vaultKey, deps, credentialsVaultStore, oauthS
28874
28963
  });
28875
28964
  }
28876
28965
  function buildDefaultRepo(dataDir, identity) {
28877
- const storage = new FileStorageAdapter(join28(dataDir, "loro"));
28966
+ const storage = new FileStorageAdapter(join29(dataDir, "loro"));
28878
28967
  return {
28879
28968
  repo: new Repo({
28880
28969
  identity,
@@ -29116,6 +29205,9 @@ function buildRejectionStubSubprocess(input) {
29116
29205
  stopBackgroundTask: noop3,
29117
29206
  setHarnessTaskId() {
29118
29207
  },
29208
+ get harnessEpoch() {
29209
+ return null;
29210
+ },
29119
29211
  setOnCwdChanged() {
29120
29212
  },
29121
29213
  async rollback(_numTurns) {
@@ -29660,12 +29752,12 @@ function createBestEffortSender(deps) {
29660
29752
 
29661
29753
  // src/services/comments/github-ingest-pipeline.ts
29662
29754
  import { readFile as readFile20 } from "fs/promises";
29663
- import { join as join30 } from "path";
29755
+ import { join as join31 } from "path";
29664
29756
 
29665
29757
  // src/services/harness/comment-server.ts
29666
29758
  import { randomUUID as randomUUID9 } from "crypto";
29667
29759
  import { access as access4, readFile as readFile19 } from "fs/promises";
29668
- import { join as join29, relative as relative5 } from "path";
29760
+ import { join as join30, relative as relative5 } from "path";
29669
29761
 
29670
29762
  // src/shared/xml-utils.ts
29671
29763
  function escapeXmlAttr(text) {
@@ -30554,7 +30646,7 @@ async function resolveDiffScopeForFile(cwd, normalizedPath) {
30554
30646
  }
30555
30647
  async function handleDiffComment(ctx, filePath, lineNumber, comment) {
30556
30648
  const normalizedPath = normalizeToWorkspaceRelative(filePath, ctx.environmentKey);
30557
- const absolutePath = join29(ctx.environmentKey, normalizedPath);
30649
+ const absolutePath = join30(ctx.environmentKey, normalizedPath);
30558
30650
  const fileExists2 = await access4(absolutePath).then(
30559
30651
  () => true,
30560
30652
  () => false
@@ -30841,7 +30933,7 @@ function prCommentToAnnotation(prComment, ctx) {
30841
30933
  // src/services/comments/github-ingest-pipeline.ts
30842
30934
  async function readLocalFileContent(cwd, path5) {
30843
30935
  try {
30844
- return await readFile20(join30(cwd, path5), "utf-8");
30936
+ return await readFile20(join31(cwd, path5), "utf-8");
30845
30937
  } catch {
30846
30938
  return getFileAtRef(cwd, path5, "HEAD");
30847
30939
  }
@@ -31649,12 +31741,12 @@ function createPRPoller(callbacks, log, resolveTopLevel = getGitTopLevel) {
31649
31741
 
31650
31742
  // src/services/roi/commit-sweep.ts
31651
31743
  import { stat as stat11 } from "fs/promises";
31652
- import { join as join32 } from "path";
31744
+ import { join as join33 } from "path";
31653
31745
 
31654
31746
  // src/services/git-checkpoint.ts
31655
31747
  import { execFile as execFileCb2 } from "child_process";
31656
31748
  import { readFile as readFile21, stat as stat10, unlink as unlink9, writeFile as writeFile15 } from "fs/promises";
31657
- import { join as join31 } from "path";
31749
+ import { join as join32 } from "path";
31658
31750
  import { promisify as promisify7 } from "util";
31659
31751
  var execFile9 = promisify7(execFileCb2);
31660
31752
  var NOT_A_GIT_REPO = "Not a git repository";
@@ -31666,10 +31758,10 @@ async function resolveGitPaths(repoDir, taskId) {
31666
31758
  try {
31667
31759
  const { stdout } = await execFile9("git", ["rev-parse", "--git-dir"], { cwd: repoDir });
31668
31760
  const gitDir = stdout.trim();
31669
- const absoluteGitDir = gitDir.startsWith("/") ? gitDir : join31(repoDir, gitDir);
31761
+ const absoluteGitDir = gitDir.startsWith("/") ? gitDir : join32(repoDir, gitDir);
31670
31762
  return {
31671
31763
  gitDir: absoluteGitDir,
31672
- shadowIndex: join31(absoluteGitDir, `shipyard-index-${taskId}`)
31764
+ shadowIndex: join32(absoluteGitDir, `shipyard-index-${taskId}`)
31673
31765
  };
31674
31766
  } catch {
31675
31767
  return null;
@@ -31764,7 +31856,7 @@ async function rewindToCheckpointImpl(repoDir, taskId, turnNo) {
31764
31856
  const untrackedFiles = untrackedRaw.trim().split("\n").filter((f2) => f2.length > 0);
31765
31857
  for (const file of untrackedFiles) {
31766
31858
  try {
31767
- const fullPath = join31(repoDir, file);
31859
+ const fullPath = join32(repoDir, file);
31768
31860
  const s2 = await stat10(fullPath);
31769
31861
  if (s2.isFile()) {
31770
31862
  await unlink9(fullPath);
@@ -32031,7 +32123,7 @@ async function revertSingleFile(repoDir, file, restoreCommit, mode) {
32031
32123
  const isDeletedByAgent = file.status === "deleted";
32032
32124
  const shouldDelete = mode === "revert" && isAddedByAgent || mode === "unrevert" && isDeletedByAgent;
32033
32125
  if (shouldDelete) {
32034
- await unlink9(join31(repoDir, file.path)).catch((err3) => {
32126
+ await unlink9(join32(repoDir, file.path)).catch((err3) => {
32035
32127
  if (!isEnoent(err3)) throw err3;
32036
32128
  });
32037
32129
  await execFile9("git", ["reset", "HEAD", "--", file.path], { cwd: repoDir }).catch(() => {
@@ -32043,7 +32135,7 @@ async function revertSingleFile(repoDir, file, restoreCommit, mode) {
32043
32135
  maxBuffer: 10 * 1024 * 1024,
32044
32136
  encoding: "buffer"
32045
32137
  });
32046
- await writeFile15(join31(repoDir, file.path), stdout);
32138
+ await writeFile15(join32(repoDir, file.path), stdout);
32047
32139
  await execFile9("git", ["reset", "HEAD", "--", file.path], { cwd: repoDir }).catch(() => {
32048
32140
  });
32049
32141
  return true;
@@ -32136,7 +32228,7 @@ async function diffCheckpointToWorkingTreeImpl(repoDir, taskId, fromTurnNo, scop
32136
32228
  if (!line || statusMap.has(line)) continue;
32137
32229
  let ins = 0;
32138
32230
  try {
32139
- ins = countLines(await readFile21(join31(repoDir, line), "utf-8"));
32231
+ ins = countLines(await readFile21(join32(repoDir, line), "utf-8"));
32140
32232
  } catch {
32141
32233
  }
32142
32234
  entries.push({ path: line, status: "added", insertions: ins, deletions: 0 });
@@ -32156,7 +32248,7 @@ async function getFileVsWorkingTreeImpl(repoDir, taskId, fromTurnNo, filePath) {
32156
32248
  if (from2 === "error") return null;
32157
32249
  let modifiedContent;
32158
32250
  try {
32159
- modifiedContent = await readFile21(join31(repoDir, filePath), "utf-8");
32251
+ modifiedContent = await readFile21(join32(repoDir, filePath), "utf-8");
32160
32252
  } catch {
32161
32253
  modifiedContent = "";
32162
32254
  }
@@ -32437,7 +32529,7 @@ async function isGitBinaryMissing() {
32437
32529
  }
32438
32530
  async function isGitWorkingTree(cwd) {
32439
32531
  try {
32440
- await stat11(join32(cwd, ".git"));
32532
+ await stat11(join33(cwd, ".git"));
32441
32533
  return true;
32442
32534
  } catch {
32443
32535
  return false;
@@ -33401,7 +33493,7 @@ async function sweepOrphanedNotifications(taskStateStore, log) {
33401
33493
 
33402
33494
  // src/services/loro-recovery.ts
33403
33495
  import { access as access5, mkdir as mkdir14, rename as rename12, rm as rm6 } from "fs/promises";
33404
- import { join as join33 } from "path";
33496
+ import { join as join34 } from "path";
33405
33497
  var DEFAULT_RECOVERY_TTL_MS = 6e4;
33406
33498
  function createLoroRecoveryService(deps) {
33407
33499
  const disabled = validateEnv().SHIPYARD_DISABLE_LORO_RECOVERY;
@@ -33452,9 +33544,9 @@ function createLoroRecoveryService(deps) {
33452
33544
  }
33453
33545
  async function quarantineAndSweep(docId, isoTs) {
33454
33546
  const encDocId = encodeURIComponent(docId);
33455
- const sourcePath = join33(deps.dataDir, "loro", encDocId);
33456
- const quarantineRoot = join33(deps.dataDir, "loro-quarantine", isoTs);
33457
- const quarantinePath = join33(quarantineRoot, encDocId);
33547
+ const sourcePath = join34(deps.dataDir, "loro", encDocId);
33548
+ const quarantineRoot = join34(deps.dataDir, "loro-quarantine", isoTs);
33549
+ const quarantinePath = join34(quarantineRoot, encDocId);
33458
33550
  const sourceExists = await pathExists3(sourcePath);
33459
33551
  if (!sourceExists) return "";
33460
33552
  await mkdir14(quarantineRoot, { recursive: true, mode: 448 });
@@ -33463,7 +33555,7 @@ function createLoroRecoveryService(deps) {
33463
33555
  }
33464
33556
  async function postDeleteSweep(docId) {
33465
33557
  const encDocId = encodeURIComponent(docId);
33466
- const sourcePath = join33(deps.dataDir, "loro", encDocId);
33558
+ const sourcePath = join34(deps.dataDir, "loro", encDocId);
33467
33559
  if (await pathExists3(sourcePath)) {
33468
33560
  try {
33469
33561
  await rm6(sourcePath, { recursive: true, force: true });
@@ -35290,7 +35382,7 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
35290
35382
  const aloStreamId = controlChannelStreamId(deps.peerSourceId);
35291
35383
  const aloOutboxChannelName = controlChannelOutboxChannel("daemon", deps.peerSourceId);
35292
35384
  const channelEpoch = registerControlChannelEpoch(controlPeerId);
35293
- const dataDir = join34(deps?.shipyardHome ?? getShipyardHome(), "data");
35385
+ const dataDir = join35(deps?.shipyardHome ?? getShipyardHome(), "data");
35294
35386
  const aloOutbox = new JsonDocOutbox({
35295
35387
  dataDir,
35296
35388
  channel: aloOutboxChannelName,
@@ -35714,7 +35806,7 @@ function createPeerRoleRegistry() {
35714
35806
 
35715
35807
  // src/services/epoch-pruning.ts
35716
35808
  import { readdir as readdir14, rm as rm7 } from "fs/promises";
35717
- import { join as join35 } from "path";
35809
+ import { join as join36 } from "path";
35718
35810
  var LEGACY_PREFIXES = [
35719
35811
  "task-meta",
35720
35812
  "task-conv",
@@ -35754,7 +35846,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
35754
35846
  }
35755
35847
  if (!shouldPrune(decoded, currentEpoch)) continue;
35756
35848
  removals.push(
35757
- rm7(join35(dataDir, entry.name), { recursive: true }).then(() => {
35849
+ rm7(join36(dataDir, entry.name), { recursive: true }).then(() => {
35758
35850
  pruned++;
35759
35851
  }).catch((err3) => {
35760
35852
  log({
@@ -37979,7 +38071,7 @@ function onConnection(ws, deps, peers) {
37979
38071
 
37980
38072
  // src/services/local-direct/local-direct-token.ts
37981
38073
  import { chmod as chmod2, mkdir as mkdir15, rename as rename13, rm as rm8, writeFile as writeFile16 } from "fs/promises";
37982
- import { dirname as dirname17, join as join36 } from "path";
38074
+ import { dirname as dirname17, join as join37 } from "path";
37983
38075
  var ADVERTISEMENT_FILE = "local-direct.json";
37984
38076
  var ADVERTISEMENT_MODE = 384;
37985
38077
  var ADVERTISEMENT_DIR_MODE = 448;
@@ -37988,7 +38080,7 @@ function generateLocalDirectToken() {
37988
38080
  return nanoid(TOKEN_LENGTH);
37989
38081
  }
37990
38082
  function advertisementPath(shipyardHome) {
37991
- return join36(shipyardHome, "data", ADVERTISEMENT_FILE);
38083
+ return join37(shipyardHome, "data", ADVERTISEMENT_FILE);
37992
38084
  }
37993
38085
  async function writeAdvertisement(shipyardHome, ad) {
37994
38086
  const target = advertisementPath(shipyardHome);
@@ -38255,7 +38347,7 @@ async function setupPluginEventWiring(deps) {
38255
38347
  import { exec as execCb2 } from "child_process";
38256
38348
  import { existsSync as existsSync5 } from "fs";
38257
38349
  import { readdir as readdir15, readFile as readFile22, stat as stat12 } from "fs/promises";
38258
- import { basename as basename7, dirname as dirname18, join as join37 } from "path";
38350
+ import { basename as basename7, dirname as dirname18, join as join38 } from "path";
38259
38351
  import { pathToFileURL as pathToFileURL2 } from "url";
38260
38352
  import { promisify as promisify8 } from "util";
38261
38353
 
@@ -38390,7 +38482,7 @@ var PluginFileWatcher = class {
38390
38482
  }
38391
38483
  const loaded = [];
38392
38484
  for (const entry of entries) {
38393
- const pluginDir = join37(dir, entry);
38485
+ const pluginDir = join38(dir, entry);
38394
38486
  let stats;
38395
38487
  try {
38396
38488
  stats = await stat12(pluginDir);
@@ -38405,7 +38497,7 @@ var PluginFileWatcher = class {
38405
38497
  this.#reconcile(loaded);
38406
38498
  }
38407
38499
  async #loadPlugin(id, pluginDir) {
38408
- const manifestPath = join37(pluginDir, "plugin.json");
38500
+ const manifestPath = join38(pluginDir, "plugin.json");
38409
38501
  let manifestRaw;
38410
38502
  try {
38411
38503
  manifestRaw = await readFile22(manifestPath, "utf-8");
@@ -38442,7 +38534,7 @@ var PluginFileWatcher = class {
38442
38534
  return null;
38443
38535
  }
38444
38536
  let template = "";
38445
- const templatePath = join37(pluginDir, "template.html");
38537
+ const templatePath = join38(pluginDir, "template.html");
38446
38538
  try {
38447
38539
  template = await readFile22(templatePath, "utf-8");
38448
38540
  } catch {
@@ -38457,7 +38549,7 @@ var PluginFileWatcher = class {
38457
38549
  };
38458
38550
  }
38459
38551
  async #loadAndRegisterHandler(id, pluginDir, title, manifest) {
38460
- const handlerPath = join37(pluginDir, "handler.mjs");
38552
+ const handlerPath = join38(pluginDir, "handler.mjs");
38461
38553
  const loaded = await this.#resolveHandler(id, handlerPath);
38462
38554
  const { handlerFn, provideResourcesFn } = loaded;
38463
38555
  if (manifest.provideResources && provideResourcesFn && this.#config.resourceResolver) {
@@ -38615,7 +38707,7 @@ async function findNativeDependencyMarker(pluginDir) {
38615
38707
  }
38616
38708
  async function scanNativeDependencyDir(dir, stack) {
38617
38709
  for (const entry of await readNativeScanEntries(dir)) {
38618
- const fullPath = join37(dir, entry.name);
38710
+ const fullPath = join38(dir, entry.name);
38619
38711
  if (isNativeDependencyMarker(entry, dir)) return fullPath;
38620
38712
  if (shouldDescendForNativeScan(entry)) stack.push(fullPath);
38621
38713
  }
@@ -38640,7 +38732,7 @@ function isNodeBuildReleaseDir(dir) {
38640
38732
 
38641
38733
  // src/services/port-detection.ts
38642
38734
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
38643
- import { dirname as dirname19, join as join38 } from "path";
38735
+ import { dirname as dirname19, join as join39 } from "path";
38644
38736
  var LSOF_TIMEOUT_MS = 8e3;
38645
38737
  var LSOF_TTL_MS = 5e3;
38646
38738
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
@@ -38823,7 +38915,7 @@ function hasStringName(val) {
38823
38915
  function resolveProjectRoot(cwd) {
38824
38916
  let dir = cwd;
38825
38917
  while (true) {
38826
- const candidate = join38(dir, "package.json");
38918
+ const candidate = join39(dir, "package.json");
38827
38919
  if (existsSync6(candidate)) {
38828
38920
  let packageName;
38829
38921
  try {
@@ -38947,7 +39039,7 @@ import {
38947
39039
  unlinkSync as unlinkSync2,
38948
39040
  writeFileSync as writeFileSync2
38949
39041
  } from "fs";
38950
- import { join as join39 } from "path";
39042
+ import { join as join40 } from "path";
38951
39043
  function isEnoent6(err3) {
38952
39044
  return err3 instanceof Error && Reflect.get(err3, "code") === "ENOENT";
38953
39045
  }
@@ -38957,7 +39049,7 @@ function atomicWriteSync(filePath, content) {
38957
39049
  renameSync(tmpPath, filePath);
38958
39050
  }
38959
39051
  function recordFilePath2(rootDir, taskId, elementId) {
38960
- return join39(rootDir, taskId, `${elementId}.json`);
39052
+ return join40(rootDir, taskId, `${elementId}.json`);
38961
39053
  }
38962
39054
  function parseRecord2(filePath, logger2) {
38963
39055
  let raw;
@@ -38997,7 +39089,7 @@ function createPreviewStateStore(opts) {
38997
39089
  return taskMap;
38998
39090
  }
38999
39091
  function scanTaskDir(taskId) {
39000
- const taskPath = join39(rootDir, taskId);
39092
+ const taskPath = join40(rootDir, taskId);
39001
39093
  let files;
39002
39094
  try {
39003
39095
  files = readdirSync4(taskPath);
@@ -39006,7 +39098,7 @@ function createPreviewStateStore(opts) {
39006
39098
  }
39007
39099
  for (const file of files) {
39008
39100
  if (!file.endsWith(".json")) continue;
39009
- const record = parseRecord2(join39(taskPath, file), logger2);
39101
+ const record = parseRecord2(join40(taskPath, file), logger2);
39010
39102
  if (record) getTaskMap(taskId).set(record.elementId, record);
39011
39103
  }
39012
39104
  }
@@ -39032,7 +39124,7 @@ function createPreviewStateStore(opts) {
39032
39124
  function writeToDisk(taskId, state) {
39033
39125
  const filePath = recordFilePath2(rootDir, taskId, state.elementId);
39034
39126
  try {
39035
- mkdirSync3(join39(rootDir, taskId), { recursive: true });
39127
+ mkdirSync3(join40(rootDir, taskId), { recursive: true });
39036
39128
  atomicWriteSync(filePath, JSON.stringify(state));
39037
39129
  } catch (err3) {
39038
39130
  logger2.warn(
@@ -41373,7 +41465,7 @@ function wireThreadErrorFallback(daemon, dc, taskId, threadId, channelId, log) {
41373
41465
  }
41374
41466
 
41375
41467
  // src/services/terminal-handler.ts
41376
- import { join as join40 } from "path";
41468
+ import { join as join41 } from "path";
41377
41469
 
41378
41470
  // src/shared/pty-manager.ts
41379
41471
  import { accessSync, chmodSync, constants as constants2, createWriteStream } from "fs";
@@ -41654,7 +41746,7 @@ function createPtyManager() {
41654
41746
  // src/services/terminal-handler.ts
41655
41747
  var MAX_PTYS_DEFAULT = 20;
41656
41748
  function terminalLogPathFor(dir, terminalId) {
41657
- return join40(dir, `${terminalId}.log`);
41749
+ return join41(dir, `${terminalId}.log`);
41658
41750
  }
41659
41751
  function handleTerminalChannel(taskId, terminalId, cwd, send, log, deps) {
41660
41752
  const maxPtys = deps.maxPtys ?? MAX_PTYS_DEFAULT;
@@ -42264,7 +42356,7 @@ function buildCollabRoomManager(deps) {
42264
42356
  // src/services/serve-factory.ts
42265
42357
  import { randomUUID as randomUUID25 } from "crypto";
42266
42358
  import { mkdir as mkdir36 } from "fs/promises";
42267
- import { join as join78 } from "path";
42359
+ import { join as join79 } from "path";
42268
42360
 
42269
42361
  // src/shared/capabilities/cursor-boot-auth.ts
42270
42362
  async function mergeCursorAuthFromVault(caps, detect, log) {
@@ -42305,12 +42397,12 @@ async function mergeCursorAuthFromVault(caps, detect, log) {
42305
42397
 
42306
42398
  // src/shared/capabilities/cursor-hook-shim-path.ts
42307
42399
  import { statSync as statSync3 } from "fs";
42308
- import { join as join41 } from "path";
42400
+ import { join as join42 } from "path";
42309
42401
  import { fileURLToPath as fileURLToPath2 } from "url";
42310
42402
  function getCursorHookShimPath() {
42311
42403
  const resourcesPath = Reflect.get(process, "resourcesPath");
42312
42404
  if (typeof resourcesPath === "string" && resourcesPath.length > 0) {
42313
- return join41(resourcesPath, "daemon", "cursor-hook-shim.js");
42405
+ return join42(resourcesPath, "daemon", "cursor-hook-shim.js");
42314
42406
  }
42315
42407
  return fileURLToPath2(new URL("./cursor-hook-shim.js", import.meta.url));
42316
42408
  }
@@ -42545,7 +42637,7 @@ function resolveRuntimeAuthIdentity(runtimeAuth, runtimeId) {
42545
42637
  // src/shared/capabilities/spinner-verbs-reader.ts
42546
42638
  import { readFile as readFile23 } from "fs/promises";
42547
42639
  import { homedir as homedir5 } from "os";
42548
- import { join as join42 } from "path";
42640
+ import { join as join43 } from "path";
42549
42641
  var ClaudeSpinnerVerbsSchema = external_exports.object({
42550
42642
  mode: external_exports.enum(["append", "replace"]),
42551
42643
  verbs: external_exports.array(external_exports.string())
@@ -42570,8 +42662,8 @@ function toPersistedVerbs(resolved, defaults2) {
42570
42662
  return resolved.every((v2, i) => v2 === defaults2[i]) ? [] : [...resolved];
42571
42663
  }
42572
42664
  async function readSpinnerVerbs() {
42573
- const claudeDir = join42(homedir5(), ".claude");
42574
- const paths = [join42(claudeDir, "settings.json"), join42(claudeDir, "settings.local.json")];
42665
+ const claudeDir = join43(homedir5(), ".claude");
42666
+ const paths = [join43(claudeDir, "settings.json"), join43(claudeDir, "settings.local.json")];
42575
42667
  const parsed = await Promise.all(
42576
42668
  paths.map(async (p2) => {
42577
42669
  try {
@@ -42613,7 +42705,7 @@ async function reimportSpinnerVerbs(store, log) {
42613
42705
 
42614
42706
  // src/shared/mcp/oauth-state-store.ts
42615
42707
  import { readFile as readFile24 } from "fs/promises";
42616
- import { join as join43 } from "path";
42708
+ import { join as join44 } from "path";
42617
42709
  import {
42618
42710
  OAuthClientInformationFullSchema as OAuthClientInformationFullSchema2,
42619
42711
  OAuthClientInformationSchema as OAuthClientInformationSchema2,
@@ -42659,7 +42751,7 @@ var MCPOAuthStateStore = class {
42659
42751
  this.#shipyardHome = shipyardHome;
42660
42752
  }
42661
42753
  #filePath() {
42662
- return join43(this.#shipyardHome, STATE_FILE);
42754
+ return join44(this.#shipyardHome, STATE_FILE);
42663
42755
  }
42664
42756
  #lockPath() {
42665
42757
  return `${this.#filePath()}.lock`;
@@ -42758,11 +42850,11 @@ function getShipyardSubagentCatalog() {
42758
42850
 
42759
42851
  // src/services/bootstrap/rehydrate.ts
42760
42852
  import { readFile as readFile26, rename as rename16, writeFile as writeFile18 } from "fs/promises";
42761
- import { join as join45 } from "path";
42853
+ import { join as join46 } from "path";
42762
42854
 
42763
42855
  // src/services/collab/collab-queue-persistence.ts
42764
42856
  import { appendFile as appendFile2, mkdir as mkdir16, readdir as readdir16, readFile as readFile25, rename as rename15, rm as rm9, writeFile as writeFile17 } from "fs/promises";
42765
- import { join as join44 } from "path";
42857
+ import { join as join45 } from "path";
42766
42858
  var PersistedQueueEntrySchema = external_exports.object({
42767
42859
  content: external_exports.array(ContentBlockSchema),
42768
42860
  settings: DaemonSettingsSchema.optional(),
@@ -42786,9 +42878,9 @@ function parsePersistedLine(line) {
42786
42878
  };
42787
42879
  }
42788
42880
  function buildCollabQueuePersistence(dataDir) {
42789
- const queuesDir = join44(dataDir, "collab-queues");
42881
+ const queuesDir = join45(dataDir, "collab-queues");
42790
42882
  function queuePath(queueKey) {
42791
- return join44(queuesDir, `${queueKey}.jsonl`);
42883
+ return join45(queuesDir, `${queueKey}.jsonl`);
42792
42884
  }
42793
42885
  async function ensureDir() {
42794
42886
  await mkdir16(queuesDir, { recursive: true });
@@ -42881,7 +42973,7 @@ function buildCollabQueuePersistence(dataDir) {
42881
42973
  }
42882
42974
  for (const name of names) {
42883
42975
  if (!name.includes(".tmp-")) continue;
42884
- await rm9(join44(queuesDir, name), { force: true }).catch(() => {
42976
+ await rm9(join45(queuesDir, name), { force: true }).catch(() => {
42885
42977
  });
42886
42978
  }
42887
42979
  }
@@ -43200,7 +43292,7 @@ async function stripPinnedFieldFromTasksFile(tasksPath, envelope, records) {
43200
43292
  }
43201
43293
  }
43202
43294
  async function migratePinnedToFavoriteTasks(dataDir, userSettingsStore, log) {
43203
- const tasksPath = join45(dataDir, "tasks.json");
43295
+ const tasksPath = join46(dataDir, "tasks.json");
43204
43296
  const parsed = await readRawTasksEnvelope(tasksPath);
43205
43297
  if (!parsed) return;
43206
43298
  const { records, envelope, fromMigrated } = parsed;
@@ -43316,7 +43408,7 @@ async function sweepStaleTasks(taskStateStore, taskManager, log) {
43316
43408
 
43317
43409
  // src/services/bootstrap/update-status.ts
43318
43410
  import { readFile as readFile27, unlink as unlink11 } from "fs/promises";
43319
- import { join as join46 } from "path";
43411
+ import { join as join47 } from "path";
43320
43412
  var UPDATE_STATUS_FILENAME = "update-status.json";
43321
43413
  var RAW_LOG_MAX_CHARS = 500;
43322
43414
  var UpdateStatusSchema = external_exports.object({
@@ -43329,7 +43421,7 @@ var UpdateStatusSchema = external_exports.object({
43329
43421
  rolledBack: external_exports.boolean()
43330
43422
  });
43331
43423
  async function readAndClearUpdateStatus(shipyardHome, log) {
43332
- const path5 = join46(shipyardHome, UPDATE_STATUS_FILENAME);
43424
+ const path5 = join47(shipyardHome, UPDATE_STATUS_FILENAME);
43333
43425
  let raw;
43334
43426
  try {
43335
43427
  raw = await readFile27(path5, "utf-8");
@@ -43416,11 +43508,11 @@ function wireUpdateStatusForwarding(report, sink2, onMessage, log) {
43416
43508
 
43417
43509
  // src/services/bootstrap/updates-cleanup.ts
43418
43510
  import { readdir as readdir17, rm as rm10, stat as stat14, unlink as unlink13 } from "fs/promises";
43419
- import { join as join48 } from "path";
43511
+ import { join as join49 } from "path";
43420
43512
 
43421
43513
  // src/services/bootstrap/self-update-lock.ts
43422
43514
  import { mkdir as mkdir17, readFile as readFile28, stat as stat13, unlink as unlink12, writeFile as writeFile19 } from "fs/promises";
43423
- import { dirname as dirname21, join as join47 } from "path";
43515
+ import { dirname as dirname21, join as join48 } from "path";
43424
43516
  var LOCK_FILENAME = ".lock";
43425
43517
  var STALE_LOCK_MS = 10 * 60 * 1e3;
43426
43518
  var LockFileSchema = external_exports.object({
@@ -43428,7 +43520,7 @@ var LockFileSchema = external_exports.object({
43428
43520
  startedAt: external_exports.number()
43429
43521
  });
43430
43522
  function lockPath(shipyardHome) {
43431
- return join47(shipyardHome, "updates", LOCK_FILENAME);
43523
+ return join48(shipyardHome, "updates", LOCK_FILENAME);
43432
43524
  }
43433
43525
  function isStaleLock(lock, now, isProcessAlive2) {
43434
43526
  const age = now - lock.startedAt;
@@ -43551,7 +43643,7 @@ async function cleanupUpdatesDir(shipyardHome, deps = {}) {
43551
43643
  const now = deps.now ?? Date.now;
43552
43644
  const isProcessAlive2 = deps.isProcessAlive ?? defaultIsProcessAlive;
43553
43645
  const log = deps.log;
43554
- const updatesDir = join48(shipyardHome, "updates");
43646
+ const updatesDir = join49(shipyardHome, "updates");
43555
43647
  const empty2 = {
43556
43648
  tarballsKept: [],
43557
43649
  tarballsDeleted: [],
@@ -43675,7 +43767,7 @@ async function sortByMtimeDesc(baseDir, names, log) {
43675
43767
  const rows = [];
43676
43768
  for (const name of names) {
43677
43769
  try {
43678
- const s2 = await stat14(join48(baseDir, name));
43770
+ const s2 = await stat14(join49(baseDir, name));
43679
43771
  rows.push({ name, mtimeMs: s2.mtimeMs });
43680
43772
  } catch (err3) {
43681
43773
  log?.info({ err: err3, entry: name }, "updates cleanup: stat failed, skipping entry");
@@ -43688,7 +43780,7 @@ async function filterOlderThan(baseDir, names, nowMs, thresholdMs, log) {
43688
43780
  const out = [];
43689
43781
  for (const name of names) {
43690
43782
  try {
43691
- const s2 = await stat14(join48(baseDir, name));
43783
+ const s2 = await stat14(join49(baseDir, name));
43692
43784
  if (nowMs - s2.mtimeMs > thresholdMs) {
43693
43785
  out.push(name);
43694
43786
  }
@@ -43702,7 +43794,7 @@ async function deleteFiles(baseDir, names, log) {
43702
43794
  const deleted = [];
43703
43795
  for (const name of names) {
43704
43796
  try {
43705
- await unlink13(join48(baseDir, name));
43797
+ await unlink13(join49(baseDir, name));
43706
43798
  deleted.push(name);
43707
43799
  } catch (err3) {
43708
43800
  if (isEnoent(err3)) {
@@ -43718,7 +43810,7 @@ async function deleteDirs(baseDir, names, log) {
43718
43810
  const deleted = [];
43719
43811
  for (const name of names) {
43720
43812
  try {
43721
- await rm10(join48(baseDir, name), { recursive: true, force: true });
43813
+ await rm10(join49(baseDir, name), { recursive: true, force: true });
43722
43814
  deleted.push(name);
43723
43815
  } catch (err3) {
43724
43816
  log?.info({ err: err3, entry: name }, "updates cleanup: rm -rf failed");
@@ -43727,7 +43819,7 @@ async function deleteDirs(baseDir, names, log) {
43727
43819
  return deleted;
43728
43820
  }
43729
43821
  async function maybeClearLock(shipyardHome, nowMs, isProcessAlive2, log) {
43730
- const lockFilePath = join48(shipyardHome, "updates", LOCK_FILENAME);
43822
+ const lockFilePath = join49(shipyardHome, "updates", LOCK_FILENAME);
43731
43823
  const outcome = await readLockFileWithOutcome(shipyardHome);
43732
43824
  switch (outcome.kind) {
43733
43825
  case "absent":
@@ -45028,7 +45120,7 @@ function wireDevServersAndTerminalsResolvers(deps) {
45028
45120
 
45029
45121
  // src/services/file-resource-resolver.ts
45030
45122
  import { readFile as readFile29, stat as stat15 } from "fs/promises";
45031
- import { basename as basename8, join as join49, normalize as normalize4, sep as sep3 } from "path";
45123
+ import { basename as basename8, join as join50, normalize as normalize4, sep as sep3 } from "path";
45032
45124
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
45033
45125
  var MIME_BY_EXT2 = {
45034
45126
  ".ts": "text/typescript",
@@ -45097,7 +45189,7 @@ function createFileResourceResolver(deps) {
45097
45189
  const { taskId, relativePath } = parseFileUri2(uri);
45098
45190
  const cwd = deps.getTaskCwd(taskId) ?? deps.workspaceRoot;
45099
45191
  const normalizedCwd = normalize4(cwd);
45100
- const absolutePath = normalize4(join49(cwd, relativePath));
45192
+ const absolutePath = normalize4(join50(cwd, relativePath));
45101
45193
  const cwdPrefix = normalizedCwd.endsWith(sep3) ? normalizedCwd : `${normalizedCwd}${sep3}`;
45102
45194
  if (!absolutePath.startsWith(cwdPrefix) && absolutePath !== normalizedCwd) {
45103
45195
  throw new Error(`Path traversal rejected: ${relativePath}`);
@@ -45142,10 +45234,30 @@ function createFileResourceResolver(deps) {
45142
45234
  // src/services/harness/harness-server.ts
45143
45235
  import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
45144
45236
 
45237
+ // src/services/session/subprocess-epoch-registry.ts
45238
+ var subprocessEpochs = /* @__PURE__ */ new Map();
45239
+ function registerSubprocessEpoch(taskId, registry = subprocessEpochs) {
45240
+ const epoch = crypto.randomUUID();
45241
+ registry.set(taskId, epoch);
45242
+ return epoch;
45243
+ }
45244
+ function setSubprocessEpoch(taskId, epoch, registry = subprocessEpochs) {
45245
+ registry.set(taskId, epoch);
45246
+ }
45247
+ function currentSubprocessEpoch(taskId, registry = subprocessEpochs) {
45248
+ return registry.get(taskId);
45249
+ }
45250
+ function isLiveSubprocessEpoch(taskId, epoch, registry = subprocessEpochs) {
45251
+ return registry.get(taskId) === epoch;
45252
+ }
45253
+ function unregisterSubprocessEpoch(taskId, registry = subprocessEpochs) {
45254
+ registry.delete(taskId);
45255
+ }
45256
+
45145
45257
  // src/services/harness/deliverable-server.ts
45146
45258
  import { randomUUID as randomUUID12 } from "crypto";
45147
45259
  import { copyFile, rm as fsRm, stat as fsStat3, mkdir as mkdir18 } from "fs/promises";
45148
- import { basename as basename9, extname as extname2, join as join50, resolve as resolve8, sep as sep4 } from "path";
45260
+ import { basename as basename9, extname as extname2, join as join51, resolve as resolve8, sep as sep4 } from "path";
45149
45261
  var TOOL_DESCRIPTION2 = [
45150
45262
  "Register a proof-of-work deliverable for this task. Call this after producing an artifact (screenshot, video, log, markdown document, AX tree, DOM snapshot) so reviewers can see verifiable evidence of what the agent produced.",
45151
45263
  "",
@@ -45205,7 +45317,7 @@ var RegisterDeliverableInput = {
45205
45317
  };
45206
45318
  var COPY_MAX_BYTES = 50 * 1024 * 1024;
45207
45319
  function isInternedFile(dataDir, filePath) {
45208
- const internBase = resolve8(join50(dataDir, "deliverable-files")) + sep4;
45320
+ const internBase = resolve8(join51(dataDir, "deliverable-files")) + sep4;
45209
45321
  return resolve8(filePath).startsWith(internBase);
45210
45322
  }
45211
45323
  async function internDeliverableFile(deliverableId, taskId, filePath, storedSizeBytes, dataDir, prevInternedPath) {
@@ -45229,8 +45341,8 @@ async function internDeliverableFile(deliverableId, taskId, filePath, storedSize
45229
45341
  };
45230
45342
  }
45231
45343
  const ext = extname2(filePath);
45232
- const destDir = join50(dataDir, "deliverable-files", taskId);
45233
- const destPath = join50(destDir, `${deliverableId}${ext}`);
45344
+ const destDir = join51(dataDir, "deliverable-files", taskId);
45345
+ const destPath = join51(destDir, `${deliverableId}${ext}`);
45234
45346
  try {
45235
45347
  await mkdir18(destDir, { recursive: true });
45236
45348
  await copyFile(filePath, destPath);
@@ -45656,7 +45768,7 @@ function createDeliverableTools(ctx) {
45656
45768
 
45657
45769
  // src/services/harness/plugin-server.ts
45658
45770
  import { mkdir as mkdir19, writeFile as writeFile20 } from "fs/promises";
45659
- import { join as join51 } from "path";
45771
+ import { join as join52 } from "path";
45660
45772
 
45661
45773
  // src/services/harness/sandbox-docs.ts
45662
45774
  var SANDBOX_HTML_RULES = `## HTML Rules
@@ -46274,14 +46386,14 @@ ${addendum}`;
46274
46386
  events: input.events,
46275
46387
  provideResources: input.provideResources
46276
46388
  });
46277
- const pluginDir = join51(ctx.pluginsDir, input.pluginId);
46389
+ const pluginDir = join52(ctx.pluginsDir, input.pluginId);
46278
46390
  await mkdir19(pluginDir, { recursive: true });
46279
- await writeFile20(join51(pluginDir, "template.html"), input.template, "utf-8");
46391
+ await writeFile20(join52(pluginDir, "template.html"), input.template, "utf-8");
46280
46392
  if (input.handler) {
46281
- await writeFile20(join51(pluginDir, "handler.mjs"), input.handler, "utf-8");
46393
+ await writeFile20(join52(pluginDir, "handler.mjs"), input.handler, "utf-8");
46282
46394
  }
46283
46395
  await writeFile20(
46284
- join51(pluginDir, "plugin.json"),
46396
+ join52(pluginDir, "plugin.json"),
46285
46397
  JSON.stringify(manifest, null, 2),
46286
46398
  "utf-8"
46287
46399
  );
@@ -56329,6 +56441,7 @@ var LSP_TOOL_NAMES = [
56329
56441
  ];
56330
56442
  function buildHarnessTools(ctx) {
56331
56443
  const taskIdRef = { current: ctx.taskId };
56444
+ const epochRef = { current: null };
56332
56445
  const deliverableCtx = {
56333
56446
  deliverableStore: ctx.deliverableStore,
56334
56447
  taskId: taskIdRef,
@@ -56407,21 +56520,59 @@ function buildHarnessTools(ctx) {
56407
56520
  const tool = builtByName.get(spec.name);
56408
56521
  if (tool) tools.push(tool);
56409
56522
  }
56523
+ function wrapWithEpochGate(tool) {
56524
+ const originalHandler = tool.handler;
56525
+ return {
56526
+ ...tool,
56527
+ // biome-ignore lint/suspicious/noExplicitAny: args is typed per-tool by the generic; widening to any here matches the per-request HTTP path which also widened at the binding seam.
56528
+ handler: async (args, extra) => {
56529
+ const epoch = epochRef.current;
56530
+ if (epoch === null) {
56531
+ return originalHandler(args, extra);
56532
+ }
56533
+ if (!isLiveSubprocessEpoch(taskIdRef.current, epoch)) {
56534
+ ctx.log({
56535
+ event: "subprocess_epoch_mismatch",
56536
+ taskId: taskIdRef.current,
56537
+ bound: epoch,
56538
+ current: currentSubprocessEpoch(taskIdRef.current),
56539
+ runtime: "in-process",
56540
+ kind: "task",
56541
+ tool: tool.name
56542
+ });
56543
+ return {
56544
+ content: [
56545
+ {
56546
+ type: "text",
56547
+ text: "subprocess epoch mismatch: this runtime has been superseded"
56548
+ }
56549
+ ],
56550
+ isError: true
56551
+ };
56552
+ }
56553
+ return originalHandler(args, extra);
56554
+ }
56555
+ };
56556
+ }
56557
+ const wrappedTools = tools.map(wrapWithEpochGate);
56410
56558
  return {
56411
- tools,
56559
+ tools: wrappedTools,
56412
56560
  setTaskId: (taskId) => {
56413
56561
  taskIdRef.current = taskId;
56562
+ },
56563
+ setEpoch: (epoch) => {
56564
+ epochRef.current = epoch;
56414
56565
  }
56415
56566
  };
56416
56567
  }
56417
56568
  function createHarnessMcpServer(ctx) {
56418
- const { tools, setTaskId } = buildHarnessTools(ctx);
56569
+ const { tools, setTaskId, setEpoch } = buildHarnessTools(ctx);
56419
56570
  const server = createSdkMcpServer({
56420
56571
  name: HARNESS_SERVER_NAME,
56421
56572
  version: "1.0.0",
56422
56573
  tools
56423
56574
  });
56424
- return { server, setTaskId };
56575
+ return { server, setTaskId, setEpoch };
56425
56576
  }
56426
56577
 
56427
56578
  // src/services/harness/lsp-agent-client.ts
@@ -56433,7 +56584,7 @@ import { pathToFileURL as pathToFileURL3 } from "url";
56433
56584
  // src/services/lsp/language-server-registry.ts
56434
56585
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
56435
56586
  import { createRequire as createRequire3 } from "module";
56436
- import { dirname as dirname22, isAbsolute as isAbsolute4, join as join52 } from "path";
56587
+ import { dirname as dirname22, isAbsolute as isAbsolute4, join as join53 } from "path";
56437
56588
  function decideTypescriptServer(d) {
56438
56589
  if (d.hasAngular || d.hasNest || d.hasTsserverPlugins) return "vtsls";
56439
56590
  return "tsgo";
@@ -56523,11 +56674,11 @@ function isRecord7(value) {
56523
56674
  return typeof value === "object" && value !== null;
56524
56675
  }
56525
56676
  function gatherTsDetection(cwd, deps) {
56526
- const pkg = deps.readJsonFile(join52(cwd, "package.json"));
56677
+ const pkg = deps.readJsonFile(join53(cwd, "package.json"));
56527
56678
  const depNames = isRecord7(pkg) ? [...keysOf(pkg.dependencies), ...keysOf(pkg.devDependencies)] : [];
56528
- const hasAngular = depNames.includes("@angular/core") || deps.fileExists(join52(cwd, "angular.json"));
56529
- const hasNest = depNames.includes("@nestjs/core") || deps.fileExists(join52(cwd, "nest-cli.json"));
56530
- const tsconfig = deps.readJsonFile(join52(cwd, "tsconfig.json"));
56679
+ const hasAngular = depNames.includes("@angular/core") || deps.fileExists(join53(cwd, "angular.json"));
56680
+ const hasNest = depNames.includes("@nestjs/core") || deps.fileExists(join53(cwd, "nest-cli.json"));
56681
+ const tsconfig = deps.readJsonFile(join53(cwd, "tsconfig.json"));
56531
56682
  const compilerOptions = isRecord7(tsconfig) ? tsconfig.compilerOptions : void 0;
56532
56683
  const plugins2 = isRecord7(compilerOptions) ? compilerOptions.plugins : void 0;
56533
56684
  const hasTsserverPlugins = Array.isArray(plugins2) && plugins2.length > 0;
@@ -56542,11 +56693,11 @@ function detectPythonVenv(cwd, deps) {
56542
56693
  if (envVenv && isAbsolute4(envVenv) && deps.fileExists(envVenv)) {
56543
56694
  candidates.push(envVenv);
56544
56695
  }
56545
- candidates.push(join52(cwd, ".venv"), join52(cwd, "venv"));
56696
+ candidates.push(join53(cwd, ".venv"), join53(cwd, "venv"));
56546
56697
  const isWin = deps.platform === "win32";
56547
56698
  for (const root of candidates) {
56548
- const python = isWin ? join52(root, "Scripts", "python.exe") : join52(root, "bin", "python");
56549
- const python3 = isWin ? python : join52(root, "bin", "python3");
56699
+ const python = isWin ? join53(root, "Scripts", "python.exe") : join53(root, "bin", "python");
56700
+ const python3 = isWin ? python : join53(root, "bin", "python3");
56550
56701
  if (deps.fileExists(python) || deps.fileExists(python3)) return root;
56551
56702
  }
56552
56703
  return null;
@@ -56579,8 +56730,8 @@ function resolveTsgoBinaryPath(req) {
56579
56730
  `Unable to resolve ${platformPkg}: ${err3 instanceof Error ? err3.message : String(err3)}`
56580
56731
  );
56581
56732
  }
56582
- const exeDir = join52(dirname22(pkgJsonPath), "lib");
56583
- const exe = process.platform === "win32" ? join52(exeDir, "tsgo.exe") : join52(exeDir, "tsgo");
56733
+ const exeDir = join53(dirname22(pkgJsonPath), "lib");
56734
+ const exe = process.platform === "win32" ? join53(exeDir, "tsgo.exe") : join53(exeDir, "tsgo");
56584
56735
  if (!existsSync7(exe)) {
56585
56736
  throw new Error(`tsgo native binary not found: ${exe}`);
56586
56737
  }
@@ -56593,7 +56744,7 @@ function resolveBinViaPackageJson(pkg, binName, req) {
56593
56744
  const named = isRecord7(binField) ? binField[binName] : void 0;
56594
56745
  const bin = typeof binField === "string" ? binField : typeof named === "string" ? named : void 0;
56595
56746
  if (!bin) throw new Error(`package '${pkg}' has no bin entry '${binName}'`);
56596
- return join52(dirname22(pkgJsonPath), bin);
56747
+ return join53(dirname22(pkgJsonPath), bin);
56597
56748
  }
56598
56749
  function errorMessage3(err3) {
56599
56750
  return err3 instanceof Error ? err3.message : String(err3);
@@ -59456,7 +59607,7 @@ import { execFile as execFileCb3 } from "child_process";
59456
59607
  import { createHash as createHash3 } from "crypto";
59457
59608
  import { readFile as readFile31 } from "fs/promises";
59458
59609
  import { homedir as homedir6 } from "os";
59459
- import { join as join53 } from "path";
59610
+ import { join as join54 } from "path";
59460
59611
  import { promisify as promisify9 } from "util";
59461
59612
  var execFile10 = promisify9(execFileCb3);
59462
59613
  var CodexCredentialsEntrySchema = external_exports.object({
@@ -59517,10 +59668,10 @@ async function readKeychainStore() {
59517
59668
  return null;
59518
59669
  }
59519
59670
  }
59520
- async function readCodexMcpCredentials(serverName, serverUrl, codexHome = join53(homedir6(), ".codex")) {
59671
+ async function readCodexMcpCredentials(serverName, serverUrl, codexHome = join54(homedir6(), ".codex")) {
59521
59672
  if (!serverUrl) return null;
59522
59673
  try {
59523
- const raw = await readFile31(join53(codexHome, FALLBACK_FILENAME), "utf-8");
59674
+ const raw = await readFile31(join54(codexHome, FALLBACK_FILENAME), "utf-8");
59524
59675
  const fileResult = CodexCredentialsFileSchema.safeParse(JSON.parse(raw));
59525
59676
  if (fileResult.success) {
59526
59677
  const entry = findEntry2(fileResult.data, serverName, serverUrl);
@@ -61717,7 +61868,7 @@ var HealthMetrics = class {
61717
61868
  // src/services/metrics/stall-profiler.ts
61718
61869
  import { mkdir as mkdir20, readdir as readdir18, unlink as unlink14, writeFile as writeFile21 } from "fs/promises";
61719
61870
  import { Session as Session2 } from "inspector/promises";
61720
- import { join as join54 } from "path";
61871
+ import { join as join55 } from "path";
61721
61872
  import { monitorEventLoopDelay as monitorEventLoopDelay3 } from "perf_hooks";
61722
61873
  function hasProfile2(value) {
61723
61874
  return typeof value === "object" && value !== null && "profile" in value;
@@ -61827,7 +61978,7 @@ var StallProfiler = class {
61827
61978
  const isoTs = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
61828
61979
  const stallRounded = Math.round(stallMs);
61829
61980
  const filename = `stall-${isoTs}-${stallRounded}ms.cpuprofile`;
61830
- const profilePath = join54(this.#outDir, filename);
61981
+ const profilePath = join55(this.#outDir, filename);
61831
61982
  await mkdir20(this.#outDir, { recursive: true });
61832
61983
  await writeFile21(profilePath, JSON.stringify(stopResult.profile));
61833
61984
  this.#log({
@@ -61861,7 +62012,7 @@ var StallProfiler = class {
61861
62012
  const entries = await readdir18(this.#outDir);
61862
62013
  const toDelete = planProfileEviction(entries, this.#maxProfiles, STALL_PROFILE_PREFIX);
61863
62014
  if (toDelete.length === 0) return;
61864
- const results = await Promise.allSettled(toDelete.map((f2) => unlink14(join54(this.#outDir, f2))));
62015
+ const results = await Promise.allSettled(toDelete.map((f2) => unlink14(join55(this.#outDir, f2))));
61865
62016
  const failures = results.filter((r) => r.status === "rejected").length;
61866
62017
  if (failures > 0) {
61867
62018
  this.#log({
@@ -62920,7 +63071,7 @@ function joinPoint(doc2, pos, dir = -1) {
62920
63071
  pos = dir < 0 ? $pos.before(d) : $pos.after(d);
62921
63072
  }
62922
63073
  }
62923
- function join55(tr2, pos, depth) {
63074
+ function join56(tr2, pos, depth) {
62924
63075
  let convertNewlines = null;
62925
63076
  let { linebreakReplacement } = tr2.doc.type.schema;
62926
63077
  let $before = tr2.doc.resolve(pos - depth), beforeType = $before.node().type;
@@ -63606,7 +63757,7 @@ var Transform = class {
63606
63757
  last and first siblings are also joined, and so on.
63607
63758
  */
63608
63759
  join(pos, depth = 1) {
63609
- join55(this, pos, depth);
63760
+ join56(this, pos, depth);
63610
63761
  return this;
63611
63762
  }
63612
63763
  /**
@@ -84019,6 +84170,14 @@ var PreWarmManager = class {
84019
84170
  #subprocess = null;
84020
84171
  #cwd = null;
84021
84172
  #generation = 0;
84173
+ /**
84174
+ * The harness-bound epoch captured from `subprocess.harnessEpoch` after the
84175
+ * spawn dispatcher minted+bound it (NOT minted here). Surfaced on
84176
+ * `PreWarmClaim` so the adopting Thread registers the SAME epoch the harness
84177
+ * fence checks (AGENTS.md Invariant #2b). Reset to `null` on
84178
+ * `#teardown()` / `dispose()`.
84179
+ */
84180
+ #epoch = null;
84022
84181
  #disposed = false;
84023
84182
  /** Per-spawn event slot — each spawn gets its own, never shared. */
84024
84183
  #eventSlot = null;
@@ -84238,6 +84397,13 @@ var PreWarmManager = class {
84238
84397
  this.#subprocess = null;
84239
84398
  this.#eventSlot = null;
84240
84399
  this.#canUseToolSlot = null;
84400
+ if (this.#epoch === null) {
84401
+ throw new Error(
84402
+ `PreWarmManager.claim() invariant violated: warm subprocess has no harness epoch (status=${this.#status}, cwd=${cwd})`
84403
+ );
84404
+ }
84405
+ const epoch = this.#epoch;
84406
+ this.#epoch = null;
84241
84407
  this.#deps.log({ event: "prewarm_claimed", cwd });
84242
84408
  return {
84243
84409
  subprocess,
@@ -84246,7 +84412,8 @@ var PreWarmManager = class {
84246
84412
  },
84247
84413
  redirectCanUseTool: (handler) => {
84248
84414
  canUseToolSlot.current = handler;
84249
- }
84415
+ },
84416
+ epoch
84250
84417
  };
84251
84418
  }
84252
84419
  kill() {
@@ -84276,6 +84443,7 @@ var PreWarmManager = class {
84276
84443
  return;
84277
84444
  }
84278
84445
  const generation = ++this.#generation;
84446
+ this.#epoch = null;
84279
84447
  this.#cwd = cwd;
84280
84448
  this.#status = "spawning";
84281
84449
  const slot = createEventSlot();
@@ -84313,6 +84481,7 @@ var PreWarmManager = class {
84313
84481
  settings,
84314
84482
  cwd
84315
84483
  );
84484
+ this.#epoch = this.#subprocess.harnessEpoch;
84316
84485
  emitStep("spawn_call", performance.now() - tSpawnCall);
84317
84486
  this.#startWarmTimer(generation);
84318
84487
  }
@@ -84342,6 +84511,7 @@ var PreWarmManager = class {
84342
84511
  }
84343
84512
  this.#status = "idle";
84344
84513
  this.#cwd = null;
84514
+ this.#epoch = null;
84345
84515
  }
84346
84516
  /** ---------------------------------------------------------------- */
84347
84517
  /** Private: own event handler (during pre-warm phase) */
@@ -91093,6 +91263,16 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
91093
91263
  ...opts.spawnArgs.stripEnv !== void 0 ? { stripEnv: opts.spawnArgs.stripEnv } : {}
91094
91264
  };
91095
91265
  const initializeResponse = await client.connect(connectArgs);
91266
+ const tracker = opts.pidTracker;
91267
+ if (tracker !== void 0) {
91268
+ const pid = client.transport.pid;
91269
+ if (pid !== void 0) {
91270
+ void tracker.add(pid);
91271
+ client.transport.once("close", () => {
91272
+ void tracker.remove(pid);
91273
+ });
91274
+ }
91275
+ }
91096
91276
  const compat = classifyCodexAppServerCompatibility(initializeResponse.userAgent);
91097
91277
  opts.log({
91098
91278
  event: "codex_app_server_version",
@@ -91769,6 +91949,10 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
91769
91949
  reason: "Codex prewarm not supported; taskId is set at thread/start only."
91770
91950
  });
91771
91951
  }
91952
+ /** Codex fences its harness MCP via the HTTP `x-shipyard-generation` header / per-runner bearer, not an in-process epoch. */
91953
+ get harnessEpoch() {
91954
+ return null;
91955
+ }
91772
91956
  setOnCwdChanged(handler) {
91773
91957
  this.#cwdHandler = handler;
91774
91958
  }
@@ -92286,6 +92470,10 @@ function createCodexSubprocessFacade(options, log) {
92286
92470
  }
92287
92471
  pending.push({ kind: "setHarnessTaskId", taskId });
92288
92472
  },
92473
+ /** Codex fences via HTTP header / per-runner bearer — always null (delegated for forward-compat). */
92474
+ get harnessEpoch() {
92475
+ return inner ? inner.harnessEpoch : null;
92476
+ },
92289
92477
  setOnCwdChanged(handler) {
92290
92478
  cwdHandler = handler;
92291
92479
  if (inner) {
@@ -92364,6 +92552,10 @@ function createCodexFacadeApiBuilder(d) {
92364
92552
  reconnectMcpServer: (serverName) => codexFacadeReconnectMcpServer(state, serverName),
92365
92553
  stopBackgroundTask: (taskId) => codexFacadeStopBackgroundTask(state, taskId),
92366
92554
  setHarnessTaskId: (taskId) => codexFacadeSetHarnessTaskId(state, taskId),
92555
+ /** Codex fences its harness via HTTP header / per-runner bearer — delegate (always null today). */
92556
+ get harnessEpoch() {
92557
+ return state.inner?.harnessEpoch ?? null;
92558
+ },
92367
92559
  setOnCwdChanged: (handler) => codexFacadeSetOnCwdChanged(state, handler),
92368
92560
  getContextUsage: () => state.inner?.getContextUsage() ?? Promise.resolve(null),
92369
92561
  restartWithExtendedSandbox: (grant) => codexFacadeRestartWithExtendedSandbox(state, grant),
@@ -92496,6 +92688,7 @@ import { randomUUID as randomUUID15 } from "crypto";
92496
92688
  import { createServer as createServer4 } from "http";
92497
92689
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
92498
92690
  import { StreamableHTTPServerTransport as StreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
92691
+ var SHIPYARD_GENERATION_HEADER = "x-shipyard-generation";
92499
92692
  async function createHarnessHttpServer(opts = {}) {
92500
92693
  const log = opts.log ?? (() => {
92501
92694
  });
@@ -92576,6 +92769,29 @@ async function createHarnessHttpServer(opts = {}) {
92576
92769
  await rejectAuth(req, res, 401, JSON_RPC_ERROR_UNAUTHORIZED, "unauthorized");
92577
92770
  return null;
92578
92771
  }
92772
+ const headerEpochRunner = req.headers[SHIPYARD_GENERATION_HEADER];
92773
+ if (typeof headerEpochRunner === "string" && headerEpochRunner.length > 0) {
92774
+ if (!isLiveSubprocessEpoch(runnerEntry.currentTaskId, headerEpochRunner)) {
92775
+ log({
92776
+ event: "subprocess_epoch_mismatch",
92777
+ taskId: runnerEntry.currentTaskId,
92778
+ /*
92779
+ * `bound` = the epoch the subprocess was given at spawn time
92780
+ * (here it arrived via the `x-shipyard-generation` header).
92781
+ * `current` = the live value the registry holds (the most recent
92782
+ * spawn's epoch). Same fields on the in-process path
92783
+ * (harness-server.ts) so log triage can query both
92784
+ * transports with one schema.
92785
+ */
92786
+ bound: headerEpochRunner,
92787
+ current: currentSubprocessEpoch(runnerEntry.currentTaskId),
92788
+ runtime: "http",
92789
+ kind: "runner"
92790
+ });
92791
+ await respondError(req, res, 410, JSON_RPC_ERROR_GONE, "task_epoch_stale");
92792
+ return null;
92793
+ }
92794
+ }
92579
92795
  return { entry: runnerEntry, scope: { kind: "runner", runnerId } };
92580
92796
  }
92581
92797
  async function resolveTaskEntry(token, req, res) {
@@ -92598,6 +92814,22 @@ async function createHarnessHttpServer(opts = {}) {
92598
92814
  await rejectAuth(req, res, 401, JSON_RPC_ERROR_UNAUTHORIZED, "unauthorized");
92599
92815
  return null;
92600
92816
  }
92817
+ const headerEpoch = req.headers[SHIPYARD_GENERATION_HEADER];
92818
+ if (typeof headerEpoch === "string" && headerEpoch.length > 0) {
92819
+ if (!isLiveSubprocessEpoch(tokenTaskId, headerEpoch)) {
92820
+ log({
92821
+ event: "subprocess_epoch_mismatch",
92822
+ taskId: tokenTaskId,
92823
+ /** See `kind: 'runner'` block above for the bound vs current field semantics. */
92824
+ bound: headerEpoch,
92825
+ current: currentSubprocessEpoch(tokenTaskId),
92826
+ runtime: "http",
92827
+ kind: "task"
92828
+ });
92829
+ await respondError(req, res, 410, JSON_RPC_ERROR_GONE, "task_epoch_stale");
92830
+ return null;
92831
+ }
92832
+ }
92601
92833
  return { entry, scope: { kind: "task", taskId: tokenTaskId } };
92602
92834
  }
92603
92835
  function logRequestEntry(req) {
@@ -92758,6 +92990,7 @@ var JSON_RPC_ERROR_INTERNAL = -32603;
92758
92990
  var JSON_RPC_ERROR_UNAUTHORIZED = -32001;
92759
92991
  var JSON_RPC_ERROR_FORBIDDEN = -32002;
92760
92992
  var JSON_RPC_ERROR_NOT_FOUND = -32004;
92993
+ var JSON_RPC_ERROR_GONE = -32005;
92761
92994
  async function respondError(req, res, status, code, message) {
92762
92995
  sendJsonRpcHttpError(res, status, code, message, await readRequestId(req));
92763
92996
  }
@@ -92855,6 +93088,16 @@ function compactionPrefsChanged(a, b2) {
92855
93088
  }
92856
93089
 
92857
93090
  // src/services/serve-factory/codex-facade.ts
93091
+ async function bailoutIfOuterClosed(state, input) {
93092
+ if (!state.outerClosed) return false;
93093
+ await input.harnessHttp.unregisterTask(input.args.taskId).catch(() => {
93094
+ });
93095
+ return true;
93096
+ }
93097
+ function pickPidTrackerForFacade(useSharedEngine, daemonPidTracker) {
93098
+ if (useSharedEngine) return void 0;
93099
+ return daemonPidTracker;
93100
+ }
92858
93101
  function createCodexFacadeSubsystem(d) {
92859
93102
  const { deps, taskManager, taskStateStore, store, userSettingsStore, proxyRef } = d;
92860
93103
  const harnessHttpServer = d.harnessHttpServer;
@@ -92880,6 +93123,7 @@ function createCodexFacadeSubsystem(d) {
92880
93123
  reasonKind: args.reason.kind,
92881
93124
  resumeSessionId: args.reason.kind === "resume" ? args.reason.sessionId : null
92882
93125
  });
93126
+ registerSubprocessEpoch(args.taskId);
92883
93127
  return buildCodexOuterFacade({ args, harnessHttp: localHarnessHttp });
92884
93128
  }
92885
93129
  function buildCodexOuterFacade(input) {
@@ -93007,11 +93251,7 @@ function createCodexFacadeSubsystem(d) {
93007
93251
  agentSystem: AGENT_SYSTEM_CODEX
93008
93252
  })
93009
93253
  );
93010
- if (state.outerClosed) {
93011
- await input.harnessHttp.unregisterTask(input.args.taskId).catch(() => {
93012
- });
93013
- return;
93014
- }
93254
+ if (await bailoutIfOuterClosed(state, input)) return;
93015
93255
  await verifyCodexHarnessRegistration({
93016
93256
  taskId: input.args.taskId,
93017
93257
  harnessUrl: `http://127.0.0.1:${input.harnessHttp.port}/mcp`,
@@ -93019,11 +93259,7 @@ function createCodexFacadeSubsystem(d) {
93019
93259
  unregisterTask: (taskId) => input.harnessHttp.unregisterTask(taskId),
93020
93260
  log: deps.log
93021
93261
  });
93022
- if (state.outerClosed) {
93023
- await input.harnessHttp.unregisterTask(input.args.taskId).catch(() => {
93024
- });
93025
- return;
93026
- }
93262
+ if (await bailoutIfOuterClosed(state, input)) return;
93027
93263
  const stashRequestedPaths = (taskId, itemId, raw) => {
93028
93264
  const grant = codexProfile.sandboxExtensionFlow?.parseRequestedPaths(raw) ?? null;
93029
93265
  if (grant !== null) {
@@ -93250,6 +93486,7 @@ function createCodexFacadeSubsystem(d) {
93250
93486
  });
93251
93487
  const codexPendingNeutral = taskManager.getOrchestrator(input.args.taskId)?.consumePendingNeutralHandoff("codex") ?? void 0;
93252
93488
  const useSharedEngine = validateEnv().SHIPYARD_CODEX_SHARED_ENGINE;
93489
+ const pidTrackerForFacade = pickPidTrackerForFacade(useSharedEngine, deps.pidTracker);
93253
93490
  const facade = createCodexSubprocessFacade(
93254
93491
  {
93255
93492
  spawnArgs,
@@ -93354,7 +93591,8 @@ function createCodexFacadeSubsystem(d) {
93354
93591
  workspaceMcpServers: input.args.workspaceMcpServers,
93355
93592
  workspaceMcpProxyBridges
93356
93593
  }),
93357
- ...deps.onCodexCapabilitiesDiscovered !== void 0 ? { onCodexCapabilitiesDiscovered: deps.onCodexCapabilitiesDiscovered } : {}
93594
+ ...deps.onCodexCapabilitiesDiscovered !== void 0 ? { onCodexCapabilitiesDiscovered: deps.onCodexCapabilitiesDiscovered } : {},
93595
+ pidTracker: pidTrackerForFacade
93358
93596
  },
93359
93597
  deps.log
93360
93598
  );
@@ -93432,12 +93670,12 @@ function handleTaskStoreBroadcast(deps, event) {
93432
93670
  }
93433
93671
 
93434
93672
  // src/services/serve-factory/viz-preview.ts
93435
- import { join as join58 } from "path";
93673
+ import { join as join59 } from "path";
93436
93674
 
93437
93675
  // src/services/harness/visualization-file-watcher.ts
93438
93676
  import { randomUUID as randomUUID16 } from "crypto";
93439
93677
  import { mkdir as mkdir21, readFile as readFile32, rename as rename17, writeFile as writeFile22 } from "fs/promises";
93440
- import { basename as basename10, dirname as dirname23, join as join56 } from "path";
93678
+ import { basename as basename10, dirname as dirname23, join as join57 } from "path";
93441
93679
  var PREVIEW_DEFAULT_W = 1200;
93442
93680
  var PREVIEW_DEFAULT_H = 800;
93443
93681
  function previewDataToLoroValue(data) {
@@ -93648,7 +93886,7 @@ var VisualizationFileWatcher = class {
93648
93886
  }
93649
93887
  case "persist_registry":
93650
93888
  await atomicWrite2(
93651
- join56(this.#deps.vizDir, effect.taskId, "registry.json"),
93889
+ join57(this.#deps.vizDir, effect.taskId, "registry.json"),
93652
93890
  JSON.stringify(effect.data, null, 2)
93653
93891
  );
93654
93892
  break;
@@ -93916,7 +94154,7 @@ var VisualizationFileWatcher = class {
93916
94154
 
93917
94155
  // src/services/harness/visualization-registry.ts
93918
94156
  import { createHash as createHash5 } from "crypto";
93919
- import { join as join57 } from "path";
94157
+ import { join as join58 } from "path";
93920
94158
  function hashContent(content) {
93921
94159
  return createHash5("sha256").update(content).digest("hex");
93922
94160
  }
@@ -93930,7 +94168,7 @@ var VisualizationRegistry = class {
93930
94168
  return null;
93931
94169
  }
93932
94170
  const ext = vizFileExtension(vizType);
93933
- const filePath = join57(vizDir, taskId, `${slug}${ext}`);
94171
+ const filePath = join58(vizDir, taskId, `${slug}${ext}`);
93934
94172
  const contentHash3 = hashContent(content);
93935
94173
  const viz = {
93936
94174
  slug,
@@ -94081,7 +94319,7 @@ var VisualizationRegistry = class {
94081
94319
  // src/services/serve-factory/viz-preview.ts
94082
94320
  function createVizPreviewRegistry(deps) {
94083
94321
  const { canvasRepo } = deps;
94084
- const vizDir = join58(deps.dataDir, "visualizations");
94322
+ const vizDir = join59(deps.dataDir, "visualizations");
94085
94323
  const vizWatchers = /* @__PURE__ */ new Map();
94086
94324
  function getOrCreateVizWatcher(taskId) {
94087
94325
  const existing = vizWatchers.get(taskId);
@@ -95837,6 +96075,11 @@ var CursorAgentSubprocess = class {
95837
96075
  * unhealthy way (kill, do not pool). Null when no pooled close is in flight.
95838
96076
  */
95839
96077
  #pooledIdleState = null;
96078
+ /**
96079
+ * Optional PID tracker — registers the forked runner PID on spawn and
96080
+ * removes it on exit so sweepOrphans can reap it if the daemon crashes.
96081
+ */
96082
+ #pidTracker;
95840
96083
  constructor(opts) {
95841
96084
  this.#harnessTaskId = opts.taskId;
95842
96085
  this.#cwd = opts.cwd;
@@ -95855,6 +96098,7 @@ var CursorAgentSubprocess = class {
95855
96098
  this.#harnessRunnerId = opts.adoptChild?.runnerId ?? opts.harnessRunnerId ?? "";
95856
96099
  this.#onReleaseToPool = opts.onReleaseToPool ?? null;
95857
96100
  this.#onRunnerExited = opts.onRunnerExited;
96101
+ this.#pidTracker = opts.pidTracker ?? null;
95858
96102
  this.#translatorCtx = createCursorTranslatorCtx(0);
95859
96103
  this.#translatorCtx.collaborationMode = this.#collaborationMode;
95860
96104
  this.#maybeLogFastModeApplied();
@@ -96285,6 +96529,10 @@ var CursorAgentSubprocess = class {
96285
96529
  token: this.#harnessToken
96286
96530
  });
96287
96531
  }
96532
+ /** Cursor fences its harness MCP via the HTTP `x-shipyard-generation` header / per-runner bearer, not an in-process epoch. */
96533
+ get harnessEpoch() {
96534
+ return null;
96535
+ }
96288
96536
  setOnCwdChanged(_handler) {
96289
96537
  }
96290
96538
  async rollback(_numTurns) {
@@ -96341,6 +96589,13 @@ var CursorAgentSubprocess = class {
96341
96589
  env: buildNodeSpawnEnv(process.env)
96342
96590
  });
96343
96591
  this.#child = child;
96592
+ const tracker = this.#pidTracker;
96593
+ if (child.pid !== void 0 && tracker !== null) {
96594
+ void tracker.add(child.pid);
96595
+ child.once("exit", () => {
96596
+ if (child.pid !== void 0) void tracker.remove(child.pid);
96597
+ });
96598
+ }
96344
96599
  attachCursorRunnerStdoutCapture(child, this.#harnessRunnerId);
96345
96600
  attachCursorRunnerStderrCapture(child, this.#harnessRunnerId);
96346
96601
  this.#attachChildListeners(child);
@@ -97545,7 +97800,7 @@ function createShipyardResolver() {
97545
97800
 
97546
97801
  // src/services/stack-detection.ts
97547
97802
  import { access as access6 } from "fs/promises";
97548
- import { join as join60 } from "path";
97803
+ import { join as join61 } from "path";
97549
97804
  var STACK_TIMEOUT_MS = TIMEOUT_MS;
97550
97805
  async function defaultIsCommandAvailable(command2) {
97551
97806
  try {
@@ -97587,7 +97842,7 @@ async function detectStack(cwd, currentBranch, deps = {}) {
97587
97842
  async function detectGraphite(cwd, currentBranch, isCommandAvailable, fileExists2, exec, getDefaultBranch2) {
97588
97843
  const gtAvailable = await isCommandAvailable("gt");
97589
97844
  if (!gtAvailable) return null;
97590
- const markerPresent = await fileExists2(join60(cwd, ".git", ".graphite_repo_config"));
97845
+ const markerPresent = await fileExists2(join61(cwd, ".git", ".graphite_repo_config"));
97591
97846
  if (!markerPresent) return null;
97592
97847
  const ancestor = await exec("gt", ["parent"], cwd).then((s2) => normalizeBranchName(s2)).catch(() => null);
97593
97848
  const childrenRaw = await exec("gt", ["children"], cwd).then((s2) => s2).catch(() => "");
@@ -97600,7 +97855,7 @@ async function detectGraphite(cwd, currentBranch, isCommandAvailable, fileExists
97600
97855
  return { ancestor: resolvedAncestor, descendants };
97601
97856
  }
97602
97857
  async function detectJujutsu(cwd, fileExists2, exec) {
97603
- const jjPresent = await fileExists2(join60(cwd, ".jj"));
97858
+ const jjPresent = await fileExists2(join61(cwd, ".jj"));
97604
97859
  if (!jjPresent) return null;
97605
97860
  const ancestorRaw = await exec(
97606
97861
  "jj",
@@ -97649,7 +97904,7 @@ function parseJjLines(raw) {
97649
97904
 
97650
97905
  // src/services/storage/annotation-store.ts
97651
97906
  import { mkdir as mkdir22, readFile as readFile34 } from "fs/promises";
97652
- import { dirname as dirname25, join as join61 } from "path";
97907
+ import { dirname as dirname25, join as join62 } from "path";
97653
97908
  var LegacyBaseFields = external_exports.object({
97654
97909
  commentId: external_exports.string(),
97655
97910
  body: external_exports.string(),
@@ -97676,7 +97931,7 @@ var LegacyPlanStoreSchema = external_exports.object({
97676
97931
  versions: external_exports.array(PlanVersionZodSchema)
97677
97932
  });
97678
97933
  function buildAnnotationStore(dataDir) {
97679
- const baseDir = join61(dataDir, "annotations");
97934
+ const baseDir = join62(dataDir, "annotations");
97680
97935
  const cache2 = /* @__PURE__ */ new Map();
97681
97936
  const listeners = /* @__PURE__ */ new Set();
97682
97937
  const writeQueues = /* @__PURE__ */ new Map();
@@ -97689,7 +97944,7 @@ function buildAnnotationStore(dataDir) {
97689
97944
  }
97690
97945
  }
97691
97946
  function filePath(taskId) {
97692
- return join61(baseDir, `${taskId}.json`);
97947
+ return join62(baseDir, `${taskId}.json`);
97693
97948
  }
97694
97949
  function emptyData() {
97695
97950
  return {
@@ -97754,9 +98009,9 @@ function buildAnnotationStore(dataDir) {
97754
98009
  }
97755
98010
  async function migrateLegacyFiles(taskId) {
97756
98011
  const [diffRaw, previewRaw, planRaw] = await Promise.all([
97757
- readLegacyFile(join61(dataDir, "diff-review", `${taskId}.json`)),
97758
- readLegacyFile(join61(dataDir, "preview-annotations", `${taskId}.json`)),
97759
- readLegacyFile(join61(dataDir, "plan-comments", `${taskId}.json`))
98012
+ readLegacyFile(join62(dataDir, "diff-review", `${taskId}.json`)),
98013
+ readLegacyFile(join62(dataDir, "preview-annotations", `${taskId}.json`)),
98014
+ readLegacyFile(join62(dataDir, "plan-comments", `${taskId}.json`))
97760
98015
  ]);
97761
98016
  if (diffRaw === null && previewRaw === null && planRaw === null) {
97762
98017
  return null;
@@ -97959,7 +98214,7 @@ function buildAnnotationStore(dataDir) {
97959
98214
 
97960
98215
  // src/services/storage/asset-store.ts
97961
98216
  import { mkdir as mkdir23, readFile as readFile35, rename as rename18, writeFile as writeFile23 } from "fs/promises";
97962
- import { join as join62 } from "path";
98217
+ import { join as join63 } from "path";
97963
98218
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
97964
98219
  function isValidAssetId(id) {
97965
98220
  return UUID_RE.test(id);
@@ -97985,10 +98240,10 @@ function buildAssetStore(assetsDir) {
97985
98240
  }
97986
98241
  }
97987
98242
  function binaryPath(assetId) {
97988
- return join62(assetsDir, `${assetId}.bin`);
98243
+ return join63(assetsDir, `${assetId}.bin`);
97989
98244
  }
97990
98245
  function metaPath(assetId) {
97991
- return join62(assetsDir, `${assetId}.meta.json`);
98246
+ return join63(assetsDir, `${assetId}.meta.json`);
97992
98247
  }
97993
98248
  function parseMeta(raw) {
97994
98249
  return AssetMetadataSchema.parse(JSON.parse(raw));
@@ -98012,8 +98267,8 @@ function buildAssetStore(assetsDir) {
98012
98267
  size: data.byteLength,
98013
98268
  createdAt: Date.now()
98014
98269
  };
98015
- const tmpBin = join62(assetsDir, `${assetId}.bin.tmp`);
98016
- const tmpMeta = join62(assetsDir, `${assetId}.meta.tmp`);
98270
+ const tmpBin = join63(assetsDir, `${assetId}.bin.tmp`);
98271
+ const tmpMeta = join63(assetsDir, `${assetId}.meta.tmp`);
98017
98272
  await writeFile23(tmpBin, data);
98018
98273
  await writeFile23(tmpMeta, JSON.stringify(metadata), "utf-8");
98019
98274
  await rename18(tmpBin, binaryPath(assetId));
@@ -98238,7 +98493,7 @@ function buildCredentialsVaultStore(filePath, corruptionLogger) {
98238
98493
 
98239
98494
  // src/services/storage/deliverable-store.ts
98240
98495
  import { mkdir as mkdir25, readFile as readFile37 } from "fs/promises";
98241
- import { dirname as dirname27, join as join63 } from "path";
98496
+ import { dirname as dirname27, join as join64 } from "path";
98242
98497
  var DELIVERABLE_STORE_VERSION = 1;
98243
98498
  var DeliverableStoreSchema = external_exports.object({
98244
98499
  schemaVersion: external_exports.number(),
@@ -98252,7 +98507,7 @@ function migrateDeliverableStore(raw) {
98252
98507
  return { schemaVersion: DELIVERABLE_STORE_VERSION, records: [] };
98253
98508
  }
98254
98509
  function buildDeliverableStore(dataDir) {
98255
- const baseDir = join63(dataDir, "deliverables");
98510
+ const baseDir = join64(dataDir, "deliverables");
98256
98511
  const cache2 = /* @__PURE__ */ new Map();
98257
98512
  const listeners = /* @__PURE__ */ new Set();
98258
98513
  const writeQueues = /* @__PURE__ */ new Map();
@@ -98265,7 +98520,7 @@ function buildDeliverableStore(dataDir) {
98265
98520
  }
98266
98521
  }
98267
98522
  function filePath(taskId) {
98268
- return join63(baseDir, `${taskId}.json`);
98523
+ return join64(baseDir, `${taskId}.json`);
98269
98524
  }
98270
98525
  function emptyData() {
98271
98526
  return { schemaVersion: DELIVERABLE_STORE_VERSION, records: [] };
@@ -98405,7 +98660,7 @@ function buildDeliverableStore(dataDir) {
98405
98660
 
98406
98661
  // src/services/storage/jsonl-conversation-store.ts
98407
98662
  import { appendFile as appendFile3, mkdir as mkdir26, open, readFile as readFile38, stat as stat16 } from "fs/promises";
98408
- import { join as join64 } from "path";
98663
+ import { join as join65 } from "path";
98409
98664
  var StoredMessageSchema = MessageSchema.omit({ seqNo: true, channelId: true });
98410
98665
  function logPerf(entry) {
98411
98666
  try {
@@ -98415,12 +98670,12 @@ function logPerf(entry) {
98415
98670
  }
98416
98671
  }
98417
98672
  function buildJsonlConversationStore(dataDir, opts = {}) {
98418
- const channelsDir = join64(dataDir, "channels");
98673
+ const channelsDir = join65(dataDir, "channels");
98419
98674
  const seqCounters = /* @__PURE__ */ new Map();
98420
98675
  const channelQueues = /* @__PURE__ */ new Map();
98421
98676
  const readCache = /* @__PURE__ */ new Map();
98422
98677
  function channelPath(channelId) {
98423
- return join64(channelsDir, `${channelId}.jsonl`);
98678
+ return join65(channelsDir, `${channelId}.jsonl`);
98424
98679
  }
98425
98680
  async function ensureDir() {
98426
98681
  await mkdir26(channelsDir, { recursive: true });
@@ -99192,7 +99447,7 @@ function buildProjectsStore(filePath) {
99192
99447
 
99193
99448
  // src/services/storage/rate-limit-store.ts
99194
99449
  import { mkdir as mkdir28, readFile as readFile40 } from "fs/promises";
99195
- import { dirname as dirname29, join as join65 } from "path";
99450
+ import { dirname as dirname29, join as join66 } from "path";
99196
99451
  var RATE_LIMIT_STORE_VERSION = 4;
99197
99452
  var RateLimitRecordSchema = external_exports.object({
99198
99453
  info: RateLimitInfoSchema,
@@ -99231,7 +99486,7 @@ function isWindowKey(x2) {
99231
99486
  return x2 !== void 0 && WINDOW_KEYS.has(x2);
99232
99487
  }
99233
99488
  async function buildRateLimitStore(dataDir, opts) {
99234
- const filePath = join65(dataDir, "rate-limits.json");
99489
+ const filePath = join66(dataDir, "rate-limits.json");
99235
99490
  const lockPath2 = `${filePath}.lock`;
99236
99491
  const initial = await loadStoreFile(filePath, opts.logger);
99237
99492
  const records = { ...initial.records };
@@ -99484,7 +99739,7 @@ async function atomicWrite4(filePath, data) {
99484
99739
  }
99485
99740
 
99486
99741
  // src/services/storage/schedule-store.ts
99487
- import { join as join66 } from "path";
99742
+ import { join as join67 } from "path";
99488
99743
 
99489
99744
  // src/services/storage/json-document-store.ts
99490
99745
  import { mkdir as mkdir29, readFile as readFile41 } from "fs/promises";
@@ -99675,7 +99930,7 @@ function buildScheduleStore(dataDir) {
99675
99930
  const store = buildJsonDocumentStore({
99676
99931
  storeName: "schedules",
99677
99932
  docType: "schedule",
99678
- filePath: join66(dataDir, "schedules.json"),
99933
+ filePath: join67(dataDir, "schedules.json"),
99679
99934
  recordSchema: ScheduleRecordSchema,
99680
99935
  currentVersion: SCHEDULE_STORE_VERSION,
99681
99936
  migrate(raw) {
@@ -99714,7 +99969,7 @@ function buildScheduleStore(dataDir) {
99714
99969
 
99715
99970
  // src/services/storage/session-persistence.ts
99716
99971
  import { mkdir as mkdir30, readdir as readdir19, readFile as readFile42, rm as rm12 } from "fs/promises";
99717
- import { join as join67 } from "path";
99972
+ import { join as join68 } from "path";
99718
99973
  var PersistedSessionSchema = external_exports.object({
99719
99974
  sessionId: external_exports.string(),
99720
99975
  channelId: external_exports.string(),
@@ -99771,9 +100026,9 @@ async function loadOneSessionFile(originalPath, corruptionLogger) {
99771
100026
  }
99772
100027
  }
99773
100028
  function buildSessionPersistence(dataDir, corruptionLogger) {
99774
- const channelsDir = join67(dataDir, "channels");
100029
+ const channelsDir = join68(dataDir, "channels");
99775
100030
  function sessionPath(channelId) {
99776
- return join67(channelsDir, `${channelId}.session.json`);
100031
+ return join68(channelsDir, `${channelId}.session.json`);
99777
100032
  }
99778
100033
  async function ensureDir() {
99779
100034
  await mkdir30(channelsDir, { recursive: true });
@@ -99831,7 +100086,7 @@ function buildSessionPersistence(dataDir, corruptionLogger) {
99831
100086
  if (i >= sessionEntries.length) return;
99832
100087
  const entry = sessionEntries[i];
99833
100088
  if (!entry) return;
99834
- results[i] = await loadOneSessionFile(join67(channelsDir, entry), corruptionLogger);
100089
+ results[i] = await loadOneSessionFile(join68(channelsDir, entry), corruptionLogger);
99835
100090
  }
99836
100091
  }
99837
100092
  const workers = Array.from(
@@ -99845,12 +100100,12 @@ function buildSessionPersistence(dataDir, corruptionLogger) {
99845
100100
  }
99846
100101
 
99847
100102
  // src/services/storage/template-store.ts
99848
- import { join as join68 } from "path";
100103
+ import { join as join69 } from "path";
99849
100104
  function buildTemplateStore(dataDir) {
99850
100105
  const store = buildJsonDocumentStore({
99851
100106
  storeName: "templates",
99852
100107
  docType: "template",
99853
- filePath: join68(dataDir, "templates.json"),
100108
+ filePath: join69(dataDir, "templates.json"),
99854
100109
  recordSchema: TaskTemplateRecordSchema,
99855
100110
  currentVersion: TEMPLATE_STORE_VERSION,
99856
100111
  migrate(raw) {
@@ -102735,7 +102990,7 @@ function handleRunning(snapshot, event) {
102735
102990
  effects: b2.effects
102736
102991
  };
102737
102992
  }
102738
- if (event.type === "sdk_error" || event.type === "subprocess_died") {
102993
+ if (event.type === "subprocess_died") {
102739
102994
  b2.log("running", "resumable_idle", event.type);
102740
102995
  b2.effects.push({ type: "clear_pending_inputs" });
102741
102996
  b2.effects.push({ type: "clear_queue" });
@@ -102749,10 +103004,26 @@ function handleRunning(snapshot, event) {
102749
103004
  effects: b2.effects
102750
103005
  };
102751
103006
  }
103007
+ if (event.type === "sdk_error") {
103008
+ b2.log("running", "resumable_idle", event.type);
103009
+ b2.effects.push({ type: "clear_pending_inputs" });
103010
+ b2.effects.push({ type: "clear_queue" });
103011
+ b2.taskStatus("input_required");
103012
+ emitSessionDeath(b2, event, "running");
103013
+ b2.effects.push({ type: "force_kill" });
103014
+ return {
103015
+ state: "resumable_idle",
103016
+ sessionId,
103017
+ rewindAtMessageId: null,
103018
+ awaitingSetup: false,
103019
+ effects: b2.effects
103020
+ };
103021
+ }
102752
103022
  if (event.type === "abort_retry") {
102753
103023
  b2.log("running", "resumable_idle", event.type);
102754
103024
  b2.effects.push({ type: "clear_pending_inputs" });
102755
103025
  b2.effects.push({ type: "clear_queue" });
103026
+ b2.effects.push({ type: "force_kill" });
102756
103027
  return {
102757
103028
  state: "resumable_idle",
102758
103029
  sessionId,
@@ -104354,6 +104625,7 @@ var Thread = class {
104354
104625
  config.adoptedSubprocess.redirectEvents(
104355
104626
  (event) => this.#enqueueSubprocessEvent(event, generation)
104356
104627
  );
104628
+ setSubprocessEpoch(this.#config.threadId, config.adoptedSubprocess.epoch);
104357
104629
  if (config.onCwdChanged) {
104358
104630
  this.#subprocess.setOnCwdChanged(config.onCwdChanged);
104359
104631
  }
@@ -105019,11 +105291,13 @@ var Thread = class {
105019
105291
  this.#subprocess?.close();
105020
105292
  this.#subprocess = null;
105021
105293
  this.#appliedToSubprocess = {};
105294
+ unregisterSubprocessEpoch(this.#config.threadId);
105022
105295
  }
105023
105296
  #handleForceKill() {
105024
105297
  this.#subprocess?.forceKill();
105025
105298
  this.#subprocess = null;
105026
105299
  this.#appliedToSubprocess = {};
105300
+ unregisterSubprocessEpoch(this.#config.threadId);
105027
105301
  }
105028
105302
  /**
105029
105303
  * Persist a FINAL SDK error to the chat JSONL as an inline assistant
@@ -105366,7 +105640,7 @@ import { randomUUID as randomUUID21 } from "crypto";
105366
105640
  import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
105367
105641
  import { mkdir as mkdir32, readFile as readFile45, writeFile as writeFile25 } from "fs/promises";
105368
105642
  import { homedir as homedir9 } from "os";
105369
- import { dirname as dirname33, join as join70 } from "path";
105643
+ import { dirname as dirname33, join as join71 } from "path";
105370
105644
 
105371
105645
  // src/services/plan/plan-detection-policy.ts
105372
105646
  function decidePlanDetection(trigger, snapshot) {
@@ -105993,8 +106267,8 @@ function greedyLCS(a, b2) {
105993
106267
  // src/services/plan/plan-file-watcher.ts
105994
106268
  import { existsSync as existsSync9 } from "fs";
105995
106269
  import { homedir as homedir8 } from "os";
105996
- import { join as join69 } from "path";
105997
- var DEFAULT_PLANS_DIR = join69(homedir8(), ".claude", "plans");
106270
+ import { join as join70 } from "path";
106271
+ var DEFAULT_PLANS_DIR = join70(homedir8(), ".claude", "plans");
105998
106272
  var PLAN_WATCH_TIMEOUT_MS = 1e4;
105999
106273
  var PLAN_WATCH_DEBOUNCE_MS = 250;
106000
106274
  function createPlanFileWatcher(deps) {
@@ -106443,7 +106717,7 @@ var PlanHandler = class {
106443
106717
  * Called when a Write tool result mentions a file in ~/.claude/plans/.
106444
106718
  */
106445
106719
  trackCreatedFile(filePath) {
106446
- const plansDir = join70(homedir9(), ".claude", "plans");
106720
+ const plansDir = join71(homedir9(), ".claude", "plans");
106447
106721
  if (filePath.startsWith(plansDir) && filePath.endsWith(".md")) {
106448
106722
  this.#createdPlanFiles.add(filePath);
106449
106723
  this.#deps.log({
@@ -106475,7 +106749,7 @@ var PlanHandler = class {
106475
106749
  }
106476
106750
  async #runHandleCodexPlanReady(content) {
106477
106751
  if (this.#deps.isDisposed()) return;
106478
- const filePath = join70(getShipyardHome(), "plans", `${this.#deps.taskId}.md`);
106752
+ const filePath = join71(getShipyardHome(), "plans", `${this.#deps.taskId}.md`);
106479
106753
  try {
106480
106754
  await mkdir32(dirname33(filePath), { recursive: true });
106481
106755
  await writeFile25(filePath, content, "utf-8");
@@ -106855,12 +107129,12 @@ var PlanHandler = class {
106855
107129
  }).filter((f2) => f2 !== null).sort((a, b2) => b2.mtime - a.mtime)[0];
106856
107130
  return newest?.path ?? null;
106857
107131
  }
106858
- const plansDir = join70(homedir9(), ".claude", "plans");
107132
+ const plansDir = join71(homedir9(), ".claude", "plans");
106859
107133
  if (!existsSync10(plansDir)) return null;
106860
107134
  try {
106861
107135
  const files = readdirSync5(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
106862
- path: join70(plansDir, f2),
106863
- mtime: statSync4(join70(plansDir, f2)).mtimeMs
107136
+ path: join71(plansDir, f2),
107137
+ mtime: statSync4(join71(plansDir, f2)).mtimeMs
106864
107138
  })).sort((a, b2) => b2.mtime - a.mtime);
106865
107139
  const recent = files[0];
106866
107140
  if (recent && Date.now() - recent.mtime < 3e4) return recent.path;
@@ -109201,7 +109475,7 @@ var RewindCheckpointHandler = class {
109201
109475
 
109202
109476
  // src/services/task/side-thread-registry.ts
109203
109477
  import { mkdir as mkdir33, readFile as readFile46, rename as rename20, writeFile as writeFile26 } from "fs/promises";
109204
- import { dirname as dirname34, join as join71 } from "path";
109478
+ import { dirname as dirname34, join as join72 } from "path";
109205
109479
  var ThreadFileSchema = external_exports.object({
109206
109480
  threads: external_exports.record(external_exports.string(), ThreadMetadataSchema)
109207
109481
  });
@@ -109577,7 +109851,7 @@ var SideThreadRegistry = class {
109577
109851
  }
109578
109852
  /** Persistence */
109579
109853
  #filePath() {
109580
- return join71(this.#deps.dataDir, "threads", `${this.#deps.taskId}.json`);
109854
+ return join72(this.#deps.dataDir, "threads", `${this.#deps.taskId}.json`);
109581
109855
  }
109582
109856
  async #ensureLoaded() {
109583
109857
  if (this.#loadedFromDisk.has(this.#deps.taskId)) return;
@@ -110579,6 +110853,14 @@ function handleTurnComplete(ctx, event) {
110579
110853
  ctx.telemetryCaptureTurnComplete(event.result);
110580
110854
  ctx.telemetryResetTurn();
110581
110855
  ctx.enqueueCheckpoint();
110856
+ if (isRefusalStopReason(event.result.stopReason)) {
110857
+ ctx.broadcast({
110858
+ type: "task_error",
110859
+ taskId: ctx.taskId,
110860
+ error: refusalBannerMessage(),
110861
+ errorKind: "sdk_error"
110862
+ });
110863
+ }
110582
110864
  }
110583
110865
  function handleSdkError(ctx, event) {
110584
110866
  ctx.collabQueueTurnComplete();
@@ -112404,7 +112686,7 @@ async function resolveTaskListPrepend(ctx) {
112404
112686
  // src/services/task/cc-task-file-store.ts
112405
112687
  import { readdir as readdir20, readFile as readFile47 } from "fs/promises";
112406
112688
  import { homedir as homedir10 } from "os";
112407
- import { basename as basename12, dirname as dirname35, join as join72 } from "path";
112689
+ import { basename as basename12, dirname as dirname35, join as join73 } from "path";
112408
112690
  var VALID_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
112409
112691
  var IDLE_DETACH_MS = 5 * 60 * 1e3;
112410
112692
  var IDLE_SWEEP_INTERVAL_MS = 6e4;
@@ -112420,7 +112702,7 @@ function isCCTaskFile(value) {
112420
112702
  }
112421
112703
  function createCCTaskFileWatcher(listId, log) {
112422
112704
  const sanitizedId = sanitize(listId);
112423
- const dir = join72(homedir10(), ".claude", "tasks", sanitizedId);
112705
+ const dir = join73(homedir10(), ".claude", "tasks", sanitizedId);
112424
112706
  const targetDirName = basename12(dir);
112425
112707
  const parentDir = dirname35(dir);
112426
112708
  const watchState = {
@@ -112456,7 +112738,7 @@ function createCCTaskFileWatcher(listId, log) {
112456
112738
  return tasks;
112457
112739
  }
112458
112740
  async function readTask(taskId) {
112459
- const filePath = join72(dir, `${taskId}.json`);
112741
+ const filePath = join73(dir, `${taskId}.json`);
112460
112742
  try {
112461
112743
  const raw = await readFile47(filePath, "utf-8");
112462
112744
  const parsed = JSON.parse(raw);
@@ -112523,7 +112805,7 @@ function createCCTaskFileWatcher(listId, log) {
112523
112805
  watchState.parentSub = null;
112524
112806
  return;
112525
112807
  }
112526
- if (ev.path.endsWith(targetDirName) || ev.path === join72(parentDir, targetDirName)) {
112808
+ if (ev.path.endsWith(targetDirName) || ev.path === join73(parentDir, targetDirName)) {
112527
112809
  void ensureDirWatcher();
112528
112810
  break;
112529
112811
  }
@@ -112639,7 +112921,7 @@ function createCCTaskFileWatcher(listId, log) {
112639
112921
  // src/services/task/cc-task-file-writer.ts
112640
112922
  import { createHash as createHash10 } from "crypto";
112641
112923
  import { mkdir as mkdir34, readdir as readdir21, rename as rename21, unlink as unlink15, writeFile as writeFile27 } from "fs/promises";
112642
- import { join as join73 } from "path";
112924
+ import { join as join74 } from "path";
112643
112925
  function contentHash2(data) {
112644
112926
  return createHash10("sha256").update(data).digest("hex").slice(0, 16);
112645
112927
  }
@@ -112655,7 +112937,7 @@ async function writeTasks(dir, tasks, hashes) {
112655
112937
  const json = JSON.stringify(structuredTaskToCCFile(task), null, 2);
112656
112938
  const hash = contentHash2(json);
112657
112939
  if (hashes.get(id) === hash) continue;
112658
- await atomicWriteFile2(join73(dir, `${id}.json`), json);
112940
+ await atomicWriteFile2(join74(dir, `${id}.json`), json);
112659
112941
  hashes.set(id, hash);
112660
112942
  }
112661
112943
  return targetIds;
@@ -112671,7 +112953,7 @@ async function removeStaleFiles(dir, targetIds, hashes) {
112671
112953
  if (!entry.endsWith(".json")) continue;
112672
112954
  const id = entry.slice(0, -".json".length);
112673
112955
  if (!targetIds.has(id) && hashes.has(id)) {
112674
- await unlink15(join73(dir, entry)).catch(() => {
112956
+ await unlink15(join74(dir, entry)).catch(() => {
112675
112957
  });
112676
112958
  hashes.delete(id);
112677
112959
  }
@@ -113273,10 +113555,10 @@ var StructuredTaskTracker = class {
113273
113555
  import { unwatchFile, watchFile } from "fs";
113274
113556
  import { open as open2 } from "fs/promises";
113275
113557
  import { homedir as homedir11 } from "os";
113276
- import { join as join74 } from "path";
113558
+ import { join as join75 } from "path";
113277
113559
  function computeTranscriptPath(cwd, sessionId, taskId) {
113278
113560
  const cwdSlug = cwd.replace(/[^a-zA-Z0-9]/g, "-").slice(0, 200);
113279
- return join74(
113561
+ return join75(
113280
113562
  homedir11(),
113281
113563
  ".claude",
113282
113564
  "projects",
@@ -113991,7 +114273,7 @@ import { createHash as createHash11 } from "crypto";
113991
114273
  import { unlink as unlink16, writeFile as writeFile28 } from "fs/promises";
113992
114274
  import { createRequire as createRequire4 } from "module";
113993
114275
  import { tmpdir } from "os";
113994
- import { join as join75 } from "path";
114276
+ import { join as join76 } from "path";
113995
114277
  import { promisify as promisify10 } from "util";
113996
114278
  var execFileAsync4 = promisify10(execFile11);
113997
114279
  function resolveDaemonNodeModules(log) {
@@ -114004,7 +114286,7 @@ function resolveDaemonNodeModules(log) {
114004
114286
  } catch {
114005
114287
  }
114006
114288
  try {
114007
- const cwdRequire = createRequire4(join75(process.cwd(), "package.json"));
114289
+ const cwdRequire = createRequire4(join76(process.cwd(), "package.json"));
114008
114290
  const sdkPath = cwdRequire.resolve("@modelcontextprotocol/sdk/server/mcp.js");
114009
114291
  const idx = sdkPath.lastIndexOf("node_modules");
114010
114292
  if (idx >= 0) return sdkPath.slice(0, idx + "node_modules".length);
@@ -114014,7 +114296,7 @@ function resolveDaemonNodeModules(log) {
114014
114296
  error: err3 instanceof Error ? err3.message : String(err3)
114015
114297
  });
114016
114298
  }
114017
- return join75(process.cwd(), "node_modules");
114299
+ return join76(process.cwd(), "node_modules");
114018
114300
  }
114019
114301
  function planTokenCount(systemPrompt, metadata, cwd, model, mcpServers) {
114020
114302
  return {
@@ -114211,7 +114493,7 @@ async function serializeMcpConfig(servers, allToolNames, daemonNodeModules, rese
114211
114493
  if (isReservedMcpServerName(serverName, reservedServerNames)) continue;
114212
114494
  const script = buildStubServerScript(serverName, tools);
114213
114495
  if (!script) continue;
114214
- const stubPath = join75(tmpdir(), `shipyard-mcp-stub-${serverName}-${Date.now()}.cjs`);
114496
+ const stubPath = join76(tmpdir(), `shipyard-mcp-stub-${serverName}-${Date.now()}.cjs`);
114215
114497
  await writeFile28(stubPath, script, "utf-8");
114216
114498
  stubPaths.push(stubPath);
114217
114499
  mcpServers[serverName] = {
@@ -114237,7 +114519,7 @@ async function prepareTokenCountArgs(plan, log, reservedServerNames = SDK_RESERV
114237
114519
  reservedServerNames
114238
114520
  );
114239
114521
  if (!serialized.config) return { args, mcpConfigPath: null, stubPaths: serialized.stubPaths };
114240
- const mcpConfigPath = join75(tmpdir(), `shipyard-mcp-${Date.now()}.json`);
114522
+ const mcpConfigPath = join76(tmpdir(), `shipyard-mcp-${Date.now()}.json`);
114241
114523
  await writeFile28(mcpConfigPath, JSON.stringify(serialized.config), "utf-8");
114242
114524
  args.push("--mcp-config", mcpConfigPath);
114243
114525
  return { args, mcpConfigPath, stubPaths: serialized.stubPaths };
@@ -115338,6 +115620,7 @@ var Task = class {
115338
115620
  * compaction occurred on those.
115339
115621
  */
115340
115622
  applyCrossRuntimeSwitch(newRuntimeId, pendingHandoff, mode) {
115623
+ this.#deps.onCrossRuntimeSwitch?.(this.#deps.taskId, this.#runtimeId, newRuntimeId);
115341
115624
  applyCrossRuntimeSwitch(this.#crossRuntimeCtx(), newRuntimeId, pendingHandoff, mode);
115342
115625
  }
115343
115626
  /**
@@ -119390,6 +119673,9 @@ var TaskManager = class {
119390
119673
  resolveMcpServers: this.#deps.resolveMcpServers,
119391
119674
  notifyTaskMetadataChange: this.#deps.notifyTaskMetadataChange,
119392
119675
  notifyPlanResourceChange: this.#deps.notifyPlanResourceChange,
119676
+ ...this.#deps.onCrossRuntimeSwitch && {
119677
+ onCrossRuntimeSwitch: this.#deps.onCrossRuntimeSwitch
119678
+ },
119393
119679
  onSubprocessReady: (tid) => {
119394
119680
  const task = this.#tasks.get(tid);
119395
119681
  if (!task) return;
@@ -119502,11 +119788,11 @@ function pathExistsForLog2(path5) {
119502
119788
  }
119503
119789
 
119504
119790
  // src/services/task/manager/task-state-store.ts
119505
- import { join as join77 } from "path";
119791
+ import { join as join78 } from "path";
119506
119792
 
119507
119793
  // src/services/storage/directory-record-store.ts
119508
119794
  import { mkdir as mkdir35, readdir as readdir22, readFile as readFile48, rename as rename22, stat as stat17, unlink as unlink17 } from "fs/promises";
119509
- import { join as join76 } from "path";
119795
+ import { join as join77 } from "path";
119510
119796
  var SCHEMA_VERSION_FILENAME = "__schema-version";
119511
119797
  var RECORD_SUFFIX = ".json";
119512
119798
  var SAFE_ID_RE = /^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/;
@@ -119526,7 +119812,7 @@ function buildDirectoryRecordStore(opts) {
119526
119812
  let writeQueue = Promise.resolve();
119527
119813
  function recordPath(id) {
119528
119814
  assertSafeRecordId(id);
119529
- return join76(dataDir, `${id}${RECORD_SUFFIX}`);
119815
+ return join77(dataDir, `${id}${RECORD_SUFFIX}`);
119530
119816
  }
119531
119817
  function notify(event) {
119532
119818
  for (const listener of listeners) {
@@ -119541,7 +119827,7 @@ function buildDirectoryRecordStore(opts) {
119541
119827
  }
119542
119828
  async function readSchemaVersion() {
119543
119829
  try {
119544
- const raw = await readFile48(join76(dataDir, SCHEMA_VERSION_FILENAME), "utf-8");
119830
+ const raw = await readFile48(join77(dataDir, SCHEMA_VERSION_FILENAME), "utf-8");
119545
119831
  const parsed = Number.parseInt(raw.trim(), 10);
119546
119832
  return Number.isFinite(parsed) ? parsed : null;
119547
119833
  } catch (err3) {
@@ -119550,7 +119836,7 @@ function buildDirectoryRecordStore(opts) {
119550
119836
  }
119551
119837
  }
119552
119838
  async function writeSchemaVersion(version) {
119553
- const path5 = join76(dataDir, SCHEMA_VERSION_FILENAME);
119839
+ const path5 = join77(dataDir, SCHEMA_VERSION_FILENAME);
119554
119840
  await atomicWriteFile(path5, String(version));
119555
119841
  }
119556
119842
  async function legacyFileExists() {
@@ -119655,7 +119941,7 @@ function buildDirectoryRecordStore(opts) {
119655
119941
  for (const entry of entries) {
119656
119942
  if (!entry.endsWith(RECORD_SUFFIX)) continue;
119657
119943
  const id = entry.slice(0, -RECORD_SUFFIX.length);
119658
- const record = await loadOneRecord(join76(dataDir, entry), entry);
119944
+ const record = await loadOneRecord(join77(dataDir, entry), entry);
119659
119945
  if (record !== null) records.set(id, record);
119660
119946
  }
119661
119947
  return records;
@@ -119956,8 +120242,8 @@ function computePaginatedPage(all, capturedVersion, opts) {
119956
120242
  }
119957
120243
  function buildTaskStateStore(dataDir) {
119958
120244
  const store = buildDirectoryRecordStore({
119959
- dataDir: join77(dataDir, "tasks"),
119960
- legacyFilePath: join77(dataDir, "tasks.json"),
120245
+ dataDir: join78(dataDir, "tasks"),
120246
+ legacyFilePath: join78(dataDir, "tasks.json"),
119961
120247
  recordSchema: TaskRecordSchema,
119962
120248
  currentVersion: TASK_STORE_VERSION,
119963
120249
  migrate(raw) {
@@ -121041,7 +121327,7 @@ async function createDaemon(deps) {
121041
121327
  const annotationStore = buildAnnotationStore(deps.dataDir);
121042
121328
  const deliverableStore = buildDeliverableStore(deps.dataDir);
121043
121329
  const resourceRegistry = createResourceRegistry();
121044
- const assetStore = buildAssetStore(join78(deps.dataDir, "assets"));
121330
+ const assetStore = buildAssetStore(join79(deps.dataDir, "assets"));
121045
121331
  const shipyardResolver = createShipyardResolver();
121046
121332
  shipyardResolver.addSubResolver("asset", createAssetResolver(assetStore));
121047
121333
  const pluginResourceResolver = new PluginResourceResolver(deps.log);
@@ -121050,10 +121336,10 @@ async function createDaemon(deps) {
121050
121336
  const capabilitiesRef = { current: null };
121051
121337
  const rateLimitStoreRef = { current: null };
121052
121338
  const gitCheckpoint = createGitCheckpointService();
121053
- const userSettingsStore = buildUserSettingsStore(join78(deps.dataDir, "user-settings.json"));
121054
- const projectsStore = buildProjectsStore(join78(deps.dataDir, "projects.json"));
121339
+ const userSettingsStore = buildUserSettingsStore(join79(deps.dataDir, "user-settings.json"));
121340
+ const projectsStore = buildProjectsStore(join79(deps.dataDir, "projects.json"));
121055
121341
  const credentialsVaultStore = buildCredentialsVaultStore(
121056
- join78(deps.dataDir, "credentials-vault.json")
121342
+ join79(deps.dataDir, "credentials-vault.json")
121057
121343
  );
121058
121344
  const cursorVaultKeyRef = { current: null };
121059
121345
  let resolveCursorVaultKey;
@@ -121254,6 +121540,7 @@ async function createDaemon(deps) {
121254
121540
  });
121255
121541
  return null;
121256
121542
  }
121543
+ registerSubprocessEpoch(args.taskId);
121257
121544
  deps.log({
121258
121545
  event: "cursor_spawn_dispatch",
121259
121546
  taskId: args.taskId,
@@ -121443,7 +121730,8 @@ async function createDaemon(deps) {
121443
121730
  content,
121444
121731
  capabilitiesRef.current?.skills ?? [],
121445
121732
  deps.log
121446
- )
121733
+ ),
121734
+ ...deps.pidTracker !== void 0 && { pidTracker: deps.pidTracker }
121447
121735
  });
121448
121736
  void initAsync.then(async (resolved) => {
121449
121737
  try {
@@ -121497,7 +121785,7 @@ async function createDaemon(deps) {
121497
121785
  taskId: args.taskId,
121498
121786
  agentSystem: args.agentSystem,
121499
121787
  environmentKey: args.cwd,
121500
- pluginsDir: join78(deps.shipyardHome, "plugins"),
121788
+ pluginsDir: join79(deps.shipyardHome, "plugins"),
121501
121789
  vizWatcher: getOrCreateVizWatcher(args.taskId).watcher,
121502
121790
  mode: "task",
121503
121791
  previewProxy: deps.previewProxy,
@@ -121577,7 +121865,11 @@ async function createDaemon(deps) {
121577
121865
  }
121578
121866
  }
121579
121867
  function spawnClaudeTaskSubprocess(args) {
121580
- const { server: harnessServer, setTaskId: setHarnessTaskId } = createHarnessMcpServer({
121868
+ const {
121869
+ server: harnessServer,
121870
+ setTaskId: setHarnessTaskId,
121871
+ setEpoch: setHarnessEpoch
121872
+ } = createHarnessMcpServer({
121581
121873
  annotationStore,
121582
121874
  deliverableStore,
121583
121875
  dataDir: deps.dataDir,
@@ -121586,7 +121878,7 @@ async function createDaemon(deps) {
121586
121878
  /** Claude in-process harness — Codex uses the HTTP path with 'codex' above. */
121587
121879
  agentSystem: AGENT_SYSTEM_CLAUDE_CODE,
121588
121880
  environmentKey: args.cwd,
121589
- pluginsDir: join78(deps.shipyardHome, "plugins"),
121881
+ pluginsDir: join79(deps.shipyardHome, "plugins"),
121590
121882
  vizWatcher: args.vizWatcher,
121591
121883
  mode: args.mode ?? "task",
121592
121884
  previewProxy: deps.previewProxy,
@@ -121663,6 +121955,8 @@ async function createDaemon(deps) {
121663
121955
  if (pendingNeutral !== null) {
121664
121956
  applyClaudePendingNeutralHandoff(pendingNeutral, options, args.taskId);
121665
121957
  }
121958
+ const epoch = registerSubprocessEpoch(args.taskId);
121959
+ setHarnessEpoch(epoch);
121666
121960
  const subprocess = AnthropicAgentSubprocess.spawn(
121667
121961
  deps.queryFn,
121668
121962
  args.initialContent,
@@ -121671,6 +121965,7 @@ async function createDaemon(deps) {
121671
121965
  deps.log
121672
121966
  );
121673
121967
  subprocess.installHarnessTaskIdSetter(setHarnessTaskId);
121968
+ subprocess.installHarnessEpoch(epoch);
121674
121969
  return subprocess;
121675
121970
  }
121676
121971
  const lastUsageLimitBroadcastByTask = /* @__PURE__ */ new Map();
@@ -122115,7 +122410,28 @@ async function createDaemon(deps) {
122115
122410
  templateStore: templateStoreEarly,
122116
122411
  getDefaultComposerSettings,
122117
122412
  getCompactionPrefs: (runtime) => cachedCompactionByRuntime.get(runtime) ?? null,
122118
- resolveProfileForModel: (modelId) => profileRegistry.findByModelId(modelId)?.id ?? null
122413
+ resolveProfileForModel: (modelId) => profileRegistry.findByModelId(modelId)?.id ?? null,
122414
+ /**
122415
+ * ST9: Tear down HTTP MCP bearers from the source runtime when a
122416
+ * cross-runtime switch fires. Called before `stopPrimary()` so the
122417
+ * source identity is still unambiguous. Codex uses per-task bearer
122418
+ * (`unregisterTask`); Cursor uses per-runner bearer (`unregisterRunner`
122419
+ * is handled by its own `onRunnerExited` path — Cursor teardown is
122420
+ * runner-scoped, not task-scoped, so `unregisterTask` is a no-op for
122421
+ * Cursor but safe to call). Only fires when `harnessHttpServer` is
122422
+ * active (Codex / Cursor require it).
122423
+ */
122424
+ onCrossRuntimeSwitch: (taskId, sourceRuntimeId, _destRuntimeId) => {
122425
+ if (sourceRuntimeId !== "codex") return;
122426
+ if (!harnessHttpServer) return;
122427
+ void harnessHttpServer.unregisterTask(taskId).catch(() => {
122428
+ });
122429
+ deps.log({
122430
+ event: "cross_runtime_http_bearer_unregistered",
122431
+ taskId,
122432
+ sourceRuntimeId
122433
+ });
122434
+ }
122119
122435
  });
122120
122436
  const vizPreview = createVizPreviewRegistry({
122121
122437
  dataDir: deps.dataDir,
@@ -122577,7 +122893,7 @@ async function createDaemon(deps) {
122577
122893
  const stallProfilerConfig = getStallProfilerConfig();
122578
122894
  const stallProfiler = new StallProfiler({
122579
122895
  log: deps.log,
122580
- outDir: join78(deps.dataDir, "stall-profiles"),
122896
+ outDir: join79(deps.dataDir, "stall-profiles"),
122581
122897
  thresholdMs: stallProfilerConfig.thresholdMs,
122582
122898
  captureMs: stallProfilerConfig.captureMs,
122583
122899
  rateLimitMs: stallProfilerConfig.rateLimitMs
@@ -123180,7 +123496,7 @@ import { existsSync as existsSync12 } from "fs";
123180
123496
  import { execFile as execFile12, spawn as spawn13 } from "child_process";
123181
123497
  import { createHash as createHash12 } from "crypto";
123182
123498
  import { chmod as chmod3, mkdir as mkdir37, readFile as readFile49, rename as rename23, unlink as unlink18, writeFile as writeFile29 } from "fs/promises";
123183
- import { join as join79 } from "path";
123499
+ import { join as join80 } from "path";
123184
123500
 
123185
123501
  // src/services/bootstrap/self-update-installer-scripts.ts
123186
123502
  function buildPosixInstallerScript(params) {
@@ -123582,7 +123898,7 @@ async function downloadTarball(url, destPath, fetchFn) {
123582
123898
  throw new Error(`download failed: HTTP ${response.status} ${response.statusText}`);
123583
123899
  }
123584
123900
  const bytes = new Uint8Array(await response.arrayBuffer());
123585
- await mkdir37(join79(destPath, ".."), { recursive: true });
123901
+ await mkdir37(join80(destPath, ".."), { recursive: true });
123586
123902
  try {
123587
123903
  await writeFile29(tmpPath, bytes);
123588
123904
  await rename23(tmpPath, destPath);
@@ -123612,7 +123928,7 @@ async function verifyChecksum(path5, expectedHash) {
123612
123928
  }
123613
123929
  }
123614
123930
  async function stageInstallerScript(scriptPath, params) {
123615
- await mkdir37(join79(scriptPath, ".."), { recursive: true });
123931
+ await mkdir37(join80(scriptPath, ".."), { recursive: true });
123616
123932
  const body = process.platform === "win32" ? buildWindowsInstallerScript(params) : buildPosixInstallerScript(params);
123617
123933
  await writeFile29(scriptPath, body);
123618
123934
  await chmod3(scriptPath, 493);
@@ -123662,16 +123978,16 @@ function buildInstallerParams(state, deps) {
123662
123978
  targetVersion: state.resolved.version,
123663
123979
  previousVersion: deps.currentVersion,
123664
123980
  parentPid: deps.pid,
123665
- statusFilePath: join79(deps.shipyardHome, "update-status.json"),
123666
- pidFilePath: join79(deps.shipyardHome, "daemon.pid"),
123667
- logPath: join79(deps.shipyardHome, "updates", `install-${deps.pid}.log`),
123668
- snapshotPath: join79(deps.shipyardHome, "updates", `rollback-${deps.currentVersion}`),
123981
+ statusFilePath: join80(deps.shipyardHome, "update-status.json"),
123982
+ pidFilePath: join80(deps.shipyardHome, "daemon.pid"),
123983
+ logPath: join80(deps.shipyardHome, "updates", `install-${deps.pid}.log`),
123984
+ snapshotPath: join80(deps.shipyardHome, "updates", `rollback-${deps.currentVersion}`),
123669
123985
  npmBin: "npm"
123670
123986
  };
123671
123987
  }
123672
123988
  function tarballPathFor(shipyardHome, version, shasum) {
123673
123989
  const shaPrefix = shasum.slice(0, 12);
123674
- return join79(shipyardHome, "updates", `${version}-${shaPrefix}.tgz`);
123990
+ return join80(shipyardHome, "updates", `${version}-${shaPrefix}.tgz`);
123675
123991
  }
123676
123992
  async function runResolveStep(step, state, deps) {
123677
123993
  const resolver = deps.resolveVersion ?? defaultResolveVersion;
@@ -124746,7 +125062,7 @@ function buildLocalDirectChannelCallbacks(deps) {
124746
125062
 
124747
125063
  // src/services/skills-cache/codex-skills-cache.ts
124748
125064
  import { mkdir as mkdir38, readFile as readFile50, rename as rename24, writeFile as writeFile30 } from "fs/promises";
124749
- import { dirname as dirname37, join as join80 } from "path";
125065
+ import { dirname as dirname37, join as join81 } from "path";
124750
125066
  var CACHE_FILENAME = "codex-skills-cache.json";
124751
125067
  var SkillInfoCacheEntrySchema = external_exports.object({
124752
125068
  name: external_exports.string(),
@@ -124763,7 +125079,7 @@ var CodexSkillsCacheFileSchema = external_exports.object({
124763
125079
  skills: external_exports.array(SkillInfoCacheEntrySchema)
124764
125080
  });
124765
125081
  function defaultCodexSkillsCachePath(shipyardHome) {
124766
- return join80(shipyardHome, "state", CACHE_FILENAME);
125082
+ return join81(shipyardHome, "state", CACHE_FILENAME);
124767
125083
  }
124768
125084
  async function loadCodexSkillsCache(path5) {
124769
125085
  let raw;
@@ -124827,11 +125143,11 @@ async function saveCodexSkillsCache(path5, cache2) {
124827
125143
  // src/services/skills-mirror/manager.ts
124828
125144
  import { lstat, mkdir as mkdir40, readlink, stat as stat18, symlink, unlink as unlink20 } from "fs/promises";
124829
125145
  import { homedir as homedir13 } from "os";
124830
- import { dirname as dirname39, join as join82 } from "path";
125146
+ import { dirname as dirname39, join as join83 } from "path";
124831
125147
 
124832
125148
  // src/services/skills-mirror/manifest.ts
124833
125149
  import { mkdir as mkdir39, readFile as readFile51, rename as rename25, unlink as unlink19, writeFile as writeFile31 } from "fs/promises";
124834
- import { dirname as dirname38, join as join81 } from "path";
125150
+ import { dirname as dirname38, join as join82 } from "path";
124835
125151
  var MANIFEST_VERSION = 1;
124836
125152
  var ManifestEntrySchema = external_exports.object({
124837
125153
  /** Display name of the skill (e.g. `qa`, or `plugin:name` when namespaced). */
@@ -124851,7 +125167,7 @@ function emptyManifest() {
124851
125167
  return { version: MANIFEST_VERSION, entries: [] };
124852
125168
  }
124853
125169
  function defaultManifestPath(shipyardHome) {
124854
- return join81(shipyardHome, "state", "skill-mirror-manifest.json");
125170
+ return join82(shipyardHome, "state", "skill-mirror-manifest.json");
124855
125171
  }
124856
125172
  async function loadManifest(path5) {
124857
125173
  try {
@@ -124882,15 +125198,15 @@ async function deleteManifest(path5) {
124882
125198
  function defaultPaths() {
124883
125199
  const home = homedir13();
124884
125200
  return {
124885
- claudeSkillsDir: join82(home, ".claude", "skills"),
124886
- codexSkillsDir: join82(home, ".agents", "skills"),
125201
+ claudeSkillsDir: join83(home, ".claude", "skills"),
125202
+ codexSkillsDir: join83(home, ".agents", "skills"),
124887
125203
  manifestPath: defaultManifestPath(getShipyardHome())
124888
125204
  };
124889
125205
  }
124890
125206
  function plannedSymlinkPath(skill, paths) {
124891
125207
  const linkName = skill.namespace ? `${skill.namespace}:${skill.name}` : skill.name;
124892
- if (skill.sourceAgent === "claude-code") return join82(paths.codexSkillsDir, linkName);
124893
- if (skill.sourceAgent === "codex") return join82(paths.claudeSkillsDir, linkName);
125208
+ if (skill.sourceAgent === "claude-code") return join83(paths.codexSkillsDir, linkName);
125209
+ if (skill.sourceAgent === "codex") return join83(paths.claudeSkillsDir, linkName);
124894
125210
  return null;
124895
125211
  }
124896
125212
  function isPermissionError(err3) {
@@ -125103,7 +125419,7 @@ async function ensureMirror(skills, config, paths = defaultPaths()) {
125103
125419
 
125104
125420
  // src/services/skills-mirror/prefs.ts
125105
125421
  import { mkdir as mkdir41, readFile as readFile52, rename as rename26, writeFile as writeFile32 } from "fs/promises";
125106
- import { dirname as dirname40, join as join83 } from "path";
125422
+ import { dirname as dirname40, join as join84 } from "path";
125107
125423
  var SkillsMirrorPrefsSchema = external_exports.object({
125108
125424
  enabled: external_exports.boolean().default(true)
125109
125425
  });
@@ -125112,7 +125428,7 @@ function defaultSkillsMirrorPrefs() {
125112
125428
  return { enabled: true };
125113
125429
  }
125114
125430
  function prefsPath(dataDir) {
125115
- return join83(dataDir, FILENAME);
125431
+ return join84(dataDir, FILENAME);
125116
125432
  }
125117
125433
  async function loadSkillsMirrorPrefs(dataDir) {
125118
125434
  try {
@@ -125161,7 +125477,7 @@ async function reconcileSkillsMirror(args) {
125161
125477
  // src/services/storage/daemon-settings-store.ts
125162
125478
  import { createHash as createHash13 } from "crypto";
125163
125479
  import { mkdir as mkdir42, readFile as readFile53 } from "fs/promises";
125164
- import { join as join84 } from "path";
125480
+ import { join as join85 } from "path";
125165
125481
  var ProjectSettingsSchema = external_exports.object({
125166
125482
  disabledMcpServers: external_exports.array(external_exports.string()).optional(),
125167
125483
  previewMigrationV36Done: external_exports.boolean().default(false)
@@ -125170,9 +125486,9 @@ function hashProjectPath(projectPath) {
125170
125486
  return createHash13("sha256").update(projectPath).digest("hex").slice(0, 16);
125171
125487
  }
125172
125488
  function buildDaemonSettingsStore(dataDir) {
125173
- const settingsDir = join84(dataDir, "settings");
125489
+ const settingsDir = join85(dataDir, "settings");
125174
125490
  function settingsPath(projectPath) {
125175
- return join84(settingsDir, `${hashProjectPath(projectPath)}.json`);
125491
+ return join85(settingsDir, `${hashProjectPath(projectPath)}.json`);
125176
125492
  }
125177
125493
  async function ensureDir() {
125178
125494
  await mkdir42(settingsDir, { recursive: true });
@@ -125197,12 +125513,12 @@ function buildDaemonSettingsStore(dataDir) {
125197
125513
 
125198
125514
  // src/services/storage/plugin-config-store.ts
125199
125515
  import { mkdir as mkdir43, readFile as readFile54 } from "fs/promises";
125200
- import { join as join85 } from "path";
125516
+ import { join as join86 } from "path";
125201
125517
  function buildPluginConfigStore(pluginsDir) {
125202
125518
  const cache2 = /* @__PURE__ */ new Map();
125203
125519
  const writeQueues = /* @__PURE__ */ new Map();
125204
125520
  function configPath(pluginId) {
125205
- return join85(pluginsDir, pluginId, "config.json");
125521
+ return join86(pluginsDir, pluginId, "config.json");
125206
125522
  }
125207
125523
  async function readConfigFromDisk(pluginId) {
125208
125524
  const fp = configPath(pluginId);
@@ -125256,7 +125572,7 @@ function buildPluginConfigStore(pluginsDir) {
125256
125572
  const fresh = await readConfigFromDisk(pluginId);
125257
125573
  fresh[key] = value;
125258
125574
  cache2.set(pluginId, fresh);
125259
- await mkdir43(join85(pluginsDir, pluginId), { recursive: true });
125575
+ await mkdir43(join86(pluginsDir, pluginId), { recursive: true });
125260
125576
  await atomicWriteFile(configPath(pluginId), JSON.stringify(fresh, null, 2));
125261
125577
  })
125262
125578
  );
@@ -125502,7 +125818,7 @@ async function serve(options = {}) {
125502
125818
  }
125503
125819
  async function runServeBody(options, captureRefs) {
125504
125820
  const shipyardHome = options.shipyardHome ?? getShipyardHome();
125505
- const dataDir = join86(shipyardHome, options.isDev ? "data-dev" : "data");
125821
+ const dataDir = join87(shipyardHome, options.isDev ? "data-dev" : "data");
125506
125822
  const log = createChildLogger({ mode: "serve" });
125507
125823
  log.info(
125508
125824
  { event: "daemon_start", version: getDaemonVersion(), pid: process.pid },
@@ -125510,7 +125826,7 @@ async function runServeBody(options, captureRefs) {
125510
125826
  );
125511
125827
  const { workspaceRoot, unscopedWorkspace, unscopedReason } = await resolveWorkspaceScope();
125512
125828
  registerBuiltinPlugins();
125513
- const pluginConfigStore = buildPluginConfigStore(join86(dataDir, "plugins"));
125829
+ const pluginConfigStore = buildPluginConfigStore(join87(dataDir, "plugins"));
125514
125830
  await mkdir44(dataDir, { recursive: true });
125515
125831
  log.info(
125516
125832
  {
@@ -125603,9 +125919,9 @@ async function runServeBody(options, captureRefs) {
125603
125919
  await bootstrapPhase("pid_sweep_orphans", () => pidTracker.sweepOrphans(logAdapter));
125604
125920
  await bootstrapPhase(
125605
125921
  "legacy_epoch_prune",
125606
- () => pruneOldEpochData(join86(dataDir, "loro"), LEGACY_EPOCH, logAdapter)
125922
+ () => pruneOldEpochData(join87(dataDir, "loro"), LEGACY_EPOCH, logAdapter)
125607
125923
  );
125608
- const storage = new FileStorageAdapter(join86(dataDir, "loro"));
125924
+ const storage = new FileStorageAdapter(join87(dataDir, "loro"));
125609
125925
  const personalWebrtcAdapter = new WebRtcDataChannelAdapter();
125610
125926
  const peerRoleRegistry = createPeerRoleRegistry();
125611
125927
  const presencePoolRef = { pool: {} };
@@ -125713,14 +126029,14 @@ async function runServeBody(options, captureRefs) {
125713
126029
  current: []
125714
126030
  };
125715
126031
  const terminalPtys = /* @__PURE__ */ new Map();
125716
- const terminalLogsDir = join86(shipyardHome, "data", "terminals");
126032
+ const terminalLogsDir = join87(shipyardHome, "data", "terminals");
125717
126033
  await mkdir44(terminalLogsDir, { recursive: true, mode: 448 });
125718
126034
  const publishedArtifactStore = createPublishedArtifactStore({
125719
- rootDir: join86(dataDir, "published"),
126035
+ rootDir: join87(dataDir, "published"),
125720
126036
  log: logAdapter
125721
126037
  });
125722
126038
  const previewStateStore = createPreviewStateStore({
125723
- rootDir: join86(dataDir, "preview-state"),
126039
+ rootDir: join87(dataDir, "preview-state"),
125724
126040
  logger: log
125725
126041
  });
125726
126042
  let codexSkillsApplyScopedPatchRef = null;
@@ -125799,7 +126115,7 @@ async function runServeBody(options, captureRefs) {
125799
126115
  daemon.healthMetrics.start();
125800
126116
  publishedArtifactStore.setSendControlMessage((msg) => daemon.taskManager.broadcastControl(msg));
125801
126117
  previewStateStore.setSendControlMessage((msg) => daemon.taskManager.broadcastControl(msg));
125802
- const pluginsDir = join86(shipyardHome, "plugins");
126118
+ const pluginsDir = join87(shipyardHome, "plugins");
125803
126119
  await mkdir44(pluginsDir, { recursive: true });
125804
126120
  let loadedPlugins = [];
125805
126121
  const loadedPluginsRef = {
@@ -126324,4 +126640,4 @@ export {
126324
126640
  decideWorkspaceScope,
126325
126641
  serve
126326
126642
  };
126327
- //# sourceMappingURL=serve-HVLR5UQ7.js.map
126643
+ //# sourceMappingURL=serve-MSZSF5JT.js.map