@tangle-network/agent-runtime 0.342.2 → 0.343.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/{activation-DOH7Fnyp.js → activation-C5rFWXGj.js} +4 -4
  2. package/dist/{activation-DOH7Fnyp.js.map → activation-C5rFWXGj.js.map} +1 -1
  3. package/dist/{activation-CXqtijue.d.ts → activation-NJiV0MHb.d.ts} +2 -2
  4. package/dist/agent.d.ts +1 -1
  5. package/dist/agent.js +3 -3
  6. package/dist/{bundle-Cwpit6Tg.js → bundle-DKQtOO9d.js} +2 -2
  7. package/dist/{bundle-Cwpit6Tg.js.map → bundle-DKQtOO9d.js.map} +1 -1
  8. package/dist/candidate-execution/index.js +4 -4
  9. package/dist/{coordination-manager-HQE0bIav.js → coordination-manager-T98ZbsEt.js} +22 -314
  10. package/dist/coordination-manager-T98ZbsEt.js.map +1 -0
  11. package/dist/{delegate-DmShSdjy.js → delegate-CjdtLf1v.js} +2 -2
  12. package/dist/{delegate-DmShSdjy.js.map → delegate-CjdtLf1v.js.map} +1 -1
  13. package/dist/{durable-file-gNI9ILAn.js → durable-file-DLcrzrjL.js} +19 -14
  14. package/dist/durable-file-DLcrzrjL.js.map +1 -0
  15. package/dist/durable.d.ts +15 -11
  16. package/dist/durable.js +193 -80
  17. package/dist/durable.js.map +1 -1
  18. package/dist/{graph-BCghJFlS.js → graph-kgzTaNMA.js} +4 -4
  19. package/dist/{graph-BCghJFlS.js.map → graph-kgzTaNMA.js.map} +1 -1
  20. package/dist/{improve-Dga6OYt-.js → improve-DevAd83a.js} +7 -8
  21. package/dist/{improve-Dga6OYt-.js.map → improve-DevAd83a.js.map} +1 -1
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +9 -9
  24. package/dist/intelligence.d.ts +2 -2
  25. package/dist/intelligence.js +6 -6
  26. package/dist/kernel.d.ts +1 -1
  27. package/dist/kernel.js +10 -11
  28. package/dist/{loop-runner-CW87Vqoj.js → loop-runner-CyUoVMkv.js} +2 -2
  29. package/dist/{loop-runner-CW87Vqoj.js.map → loop-runner-CyUoVMkv.js.map} +1 -1
  30. package/dist/{loop-runner-CuhbgVGo.d.ts → loop-runner-D56Rpd2z.d.ts} +2 -2
  31. package/dist/loop-runner-bin.d.ts +1 -1
  32. package/dist/loop-runner-bin.js +1 -1
  33. package/dist/mcp/bin.js +3 -3
  34. package/dist/mcp/index.d.ts +1 -1
  35. package/dist/mcp/index.js +6 -6
  36. package/dist/mcp/index.js.map +1 -1
  37. package/dist/platform.js +1 -1
  38. package/dist/platform.js.map +1 -1
  39. package/dist/{prepare-C-cWCyt3.js → prepare-TEEHSJwq.js} +3 -3
  40. package/dist/{prepare-C-cWCyt3.js.map → prepare-TEEHSJwq.js.map} +1 -1
  41. package/dist/{profile-CbMw5hAn.js → profile-6OLpbjvV.js} +2 -2
  42. package/dist/{profile-CbMw5hAn.js.map → profile-6OLpbjvV.js.map} +1 -1
  43. package/dist/profiles.d.ts +1 -1
  44. package/dist/profiles.js +1 -1
  45. package/dist/profiles.js.map +1 -1
  46. package/dist/{provision-supervisor-BWR_TJFg.js → provision-supervisor-CysVhSqV.js} +6 -6
  47. package/dist/{provision-supervisor-BWR_TJFg.js.map → provision-supervisor-CysVhSqV.js.map} +1 -1
  48. package/dist/{run-cancellation-C9oL62sW.js → run-cancellation-CEDiqfhC.js} +8 -4
  49. package/dist/{run-cancellation-C9oL62sW.js.map → run-cancellation-CEDiqfhC.js.map} +1 -1
  50. package/dist/{runtime-CuRTZFe6.js → runtime-CefFtXC2.js} +15 -17
  51. package/dist/{runtime-CuRTZFe6.js.map → runtime-CefFtXC2.js.map} +1 -1
  52. package/dist/{sandbox-profile-compat-FN2kab2-.js → sandbox-profile-compat-_fEtHPCj.js} +2 -2
  53. package/dist/sandbox-profile-compat-_fEtHPCj.js.map +1 -0
  54. package/dist/{search-executor-0-SjHAao.js → search-executor-t05_9Wwk.js} +3 -3
  55. package/dist/{search-executor-0-SjHAao.js.map → search-executor-t05_9Wwk.js.map} +1 -1
  56. package/dist/{server-DTUK2wW1.js → server-DjrVWzH2.js} +4 -4
  57. package/dist/{server-DTUK2wW1.js.map → server-DjrVWzH2.js.map} +1 -1
  58. package/dist/{structural-rollout-kfaZgAkd.js → structural-rollout-CpJknIaL.js} +3 -3
  59. package/dist/{structural-rollout-kfaZgAkd.js.map → structural-rollout-CpJknIaL.js.map} +1 -1
  60. package/dist/{supervise-C_GAgiJU.js → supervise-BuVVC7Bl.js} +415 -107
  61. package/dist/supervise-BuVVC7Bl.js.map +1 -0
  62. package/dist/{supervisor-C_03z693.js → supervisor-D3M9ab1v.js} +627 -27
  63. package/dist/supervisor-D3M9ab1v.js.map +1 -0
  64. package/dist/testing.d.ts +1 -1
  65. package/dist/testing.js +13 -13
  66. package/dist/{top-snapshot-CbZE1JsU.js → top-snapshot-BGYI6G6K.js} +2 -2
  67. package/dist/{top-snapshot-CbZE1JsU.js.map → top-snapshot-BGYI6G6K.js.map} +1 -1
  68. package/dist/tui/bin.js +5 -3
  69. package/dist/tui/bin.js.map +1 -1
  70. package/dist/tui/index.d.ts +1 -1
  71. package/dist/tui/index.js +2 -2
  72. package/dist/{types-Cgs89PQd.d.ts → types-D0ZRqIJh.d.ts} +131 -17
  73. package/package.json +7 -6
  74. package/dist/content-digest-sBTLujlK.js +0 -9
  75. package/dist/content-digest-sBTLujlK.js.map +0 -1
  76. package/dist/coordination-manager-HQE0bIav.js.map +0 -1
  77. package/dist/durable-file-gNI9ILAn.js.map +0 -1
  78. package/dist/graph-registry-BnO64cn8.js +0 -197
  79. package/dist/graph-registry-BnO64cn8.js.map +0 -1
  80. package/dist/jsonl-file-C9Kde_0Z.js +0 -206
  81. package/dist/jsonl-file-C9Kde_0Z.js.map +0 -1
  82. package/dist/record-value-B6bwJxLa.js +0 -12
  83. package/dist/record-value-B6bwJxLa.js.map +0 -1
  84. package/dist/sandbox-profile-compat-FN2kab2-.js.map +0 -1
  85. package/dist/supervise-C_GAgiJU.js.map +0 -1
  86. package/dist/supervisor-C_03z693.js.map +0 -1
package/dist/durable.js CHANGED
@@ -1,23 +1,22 @@
1
- import { i as publishExclusiveDurableFile, o as writeAtomicDurableFile } from "./durable-file-gNI9ILAn.js";
2
- import { Hr as authoredProfileDigest, S as FileSpawnJournal, Si as contentAddress, ar as isFiniteNumber, at as loadSpawnForest, ir as failedItems, xi as detachedSnapshot } from "./supervisor-C_03z693.js";
1
+ import { a as syncDurableDirectory, i as publishExclusiveDurableFile, o as writeAtomicDurableFile } from "./durable-file-DLcrzrjL.js";
2
+ import { Bi as assertValidBudget, C as FileSpawnJournal, Mi as contentAddress, Qr as authoredProfileDigest, _t as readCommittedJsonLines, ct as loadSpawnForest, hr as isFiniteNumber, ji as detachedSnapshot, mr as failedItems, mt as isNoEntError, pt as appendCommittedJsonLine, r as assertResumeLimits } from "./supervisor-D3M9ab1v.js";
3
3
  import { f as RuntimeRunStateError, m as ValidationError } from "./errors-DodWX-cb.js";
4
- import { t as isRecord } from "./record-value-B6bwJxLa.js";
5
- import { W as addSpend, X as errorMessage, ft as zeroSpend, q as cloneSpend } from "./sandbox-profile-compat-FN2kab2-.js";
4
+ import { W as addSpend, X as errorMessage, ft as zeroSpend, q as cloneSpend } from "./sandbox-profile-compat-_fEtHPCj.js";
6
5
  import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-tXpAarhW.js";
7
- import { a as readCommittedJsonLines, n as isNoEntError, t as appendCommittedJsonLine } from "./jsonl-file-C9Kde_0Z.js";
8
- import { J as readRootStreamReceipt, a as superviseRootProfile, q as readRootStream, t as supervise } from "./supervise-C_GAgiJU.js";
9
- import { a as applyExactAgentProfileDiff } from "./profile-CbMw5hAn.js";
10
- import { g as supervisorRunDir } from "./run-cancellation-C9oL62sW.js";
11
- import { i as superviseAgentGraph } from "./graph-BCghJFlS.js";
6
+ import { a as applyExactAgentProfileDiff } from "./profile-6OLpbjvV.js";
7
+ import { a as superviseRootProfile, ot as readRootStream, st as readRootStreamReceipt, t as supervise } from "./supervise-BuVVC7Bl.js";
8
+ import { a as runCancelRequestFile, o as runCancellationFile, v as supervisorRunDir, x as workerCancellationsDir } from "./run-cancellation-CEDiqfhC.js";
9
+ import { i as superviseAgentGraph } from "./graph-kgzTaNMA.js";
12
10
  import { agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest, canonicalCandidateJson, sha256Bytes } from "@tangle-network/agent-interface";
11
+ import { isRecord } from "@tangle-network/agent-core";
13
12
  import { createHash } from "node:crypto";
13
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync } from "node:fs";
14
14
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
15
- import { execFile } from "node:child_process";
16
- import { access, mkdir, readFile, realpath, rmdir, unlink } from "node:fs/promises";
17
- import { promisify } from "node:util";
15
+ import { access, mkdir, open, readFile, realpath, rm, rmdir, unlink } from "node:fs/promises";
18
16
  import { SearchRecorder, developmentClaim, incumbent, openSearchLedger, parseSearchLedgerLine, runSearch, uniform } from "@tangle-network/agent-eval/campaign";
19
17
  import { pathToFileURL } from "node:url";
20
18
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
19
+ import { lockHolderVerdict, lockIdentity, parseLockHolderRecord, readLockHolderIdentity } from "@tangle-network/agent-eval/lock-holder";
21
20
  //#region src/durable/chat-engine.ts
22
21
  const encoder = new TextEncoder();
23
22
  function encodeLine(event) {
@@ -897,7 +896,7 @@ var SettledRunDirectoryError = class extends Error {
897
896
  /** The root of the recorded run, which is its `runId`. */
898
897
  recordedRunId;
899
898
  constructor(path, recordedRunId, requestedRunId) {
900
- super(recordedRunId === requestedRunId ? `supervisePursuit: ${path} records that run '${recordedRunId}' already settled; a settled run is not re-entered` : `supervisePursuit: ${path} records that run '${recordedRunId}' already settled in this directory; a directory holds one settle record, so run '${requestedRunId}' needs its own runDir`);
899
+ super(recordedRunId === requestedRunId ? `supervisePursuit: ${path} records that run '${recordedRunId}' already settled; a settled run is not re-entered unless the call passes reopen: { operator, reason, operationId }` : `supervisePursuit: ${path} records that run '${recordedRunId}' already settled in this directory; a directory holds one settle record, so run '${requestedRunId}' needs its own runDir`);
901
900
  this.name = "SettledRunDirectoryError";
902
901
  this.path = path;
903
902
  this.recordedRunId = recordedRunId;
@@ -949,6 +948,10 @@ function jsonValue(result) {
949
948
  function settleRecordJson(result) {
950
949
  return canonicalCandidateJson(jsonValue(result));
951
950
  }
951
+ /** The digest the settle record's bytes hash to: the candidate digest of the result's JSON value. */
952
+ function settleRecordDigest(result) {
953
+ return canonicalCandidateDigest(jsonValue(result));
954
+ }
952
955
  /** Publish `result.json` in the run directory once. Throws when a record is already there. */
953
956
  async function writeSettleRecord(runDir, result) {
954
957
  const path = resolve(runDir, SETTLE_RECORD_FILE);
@@ -2040,28 +2043,37 @@ var RunDirectoryLockedError = class extends Error {
2040
2043
  * Take `runDir/supervise.lock`, or refuse.
2041
2044
  *
2042
2045
  * The file is published with its full content or not at all, so a contender never reads a
2043
- * half-written holder. A lock whose holder is gone (its pid no longer exists, or the pid now
2044
- * belongs to a process with a different start token) is stale and is removed under the mutation guard;
2045
- * a pid this process may not signal (`EPERM`) is alive and refuses. An empty file names no
2046
- * holder and is reclaimed. A file with unreadable content is left in place and refused:
2046
+ * half-written holder. A lock whose holder is proved dead (its pid no longer exists, the pid now
2047
+ * belongs to a process with a different start token, or the host rebooted) is stale and is removed
2048
+ * under the mutation guard; a holder that is alive or cannot be proved dead refuses. An empty file
2049
+ * names no holder and is reclaimed. A file with unreadable content is left in place and refused:
2047
2050
  * reclaiming it could evict a live holder written by something other than this module.
2048
2051
  */
2049
2052
  async function acquireRunDirectoryLock(runDir, runId, now = Date.now) {
2050
2053
  const dir = resolve(runDir);
2051
2054
  const path = resolve(dir, RUN_DIRECTORY_LOCK_FILE);
2052
2055
  await mkdir(dir, { recursive: true });
2053
- const processStart = await readProcessStart(process.pid);
2054
- const holder = Object.freeze({
2055
- pid: process.pid,
2056
+ const { pid, ...identity } = readLockHolderIdentity(process.pid);
2057
+ const owner = {
2058
+ pid,
2056
2059
  startedAt: new Date(now()).toISOString(),
2057
2060
  runId,
2058
- ...processStart === void 0 ? {} : { processStart }
2059
- });
2061
+ ...identity
2062
+ };
2060
2063
  return withMutationGuard(path, async () => {
2061
2064
  const existing = await readLockFile(path);
2062
- if (existing.state === "holder" && await holderIsLive(existing.holder)) throw new RunDirectoryLockedError(path, existing.holder);
2065
+ if (existing.state === "holder" && !holderIsDead(existing)) throw new RunDirectoryLockedError(path, existing.holder);
2063
2066
  if (existing.state !== "missing") await removeIfPresent(path);
2064
- if (!publishExclusiveDurableFile(path, `${JSON.stringify(holder)}\n`)) throw new Error(`supervisePursuit: could not take ${path}`);
2067
+ const written = {};
2068
+ const published = publishExclusiveDurableFile(path, (file) => {
2069
+ written.holder = Object.freeze({
2070
+ ...owner,
2071
+ lock: lockIdentity(file)
2072
+ });
2073
+ return `${JSON.stringify(written.holder)}\n`;
2074
+ });
2075
+ const holder = written.holder;
2076
+ if (!published || holder === void 0) throw new Error(`supervisePursuit: could not take ${path}`);
2065
2077
  let released = false;
2066
2078
  return Object.freeze({
2067
2079
  ...holder,
@@ -2098,47 +2110,21 @@ async function runDirectoryHolderIsLive(runDir) {
2098
2110
  const existing = await readLockFile(resolve(runDir, RUN_DIRECTORY_LOCK_FILE));
2099
2111
  if (existing.state !== "holder") return { live: false };
2100
2112
  return {
2101
- live: await holderIsLive(existing.holder),
2113
+ live: !holderIsDead(existing),
2102
2114
  holder: existing.holder
2103
2115
  };
2104
2116
  }
2105
- const execFileAsync = promisify(execFile);
2106
- /**
2107
- * The OS's start token for a live pid: on Linux the `starttime` field of `/proc/<pid>/stat`
2108
- * (clock ticks since boot), elsewhere the `ps` `lstart` column. The same process reports the
2109
- * same token for its whole life and a later holder of the pid reports a different one.
2110
- * `undefined` when the host cannot report one: the pid is gone, there is no `/proc` and no
2111
- * `ps`, or the platform is Windows.
2112
- */
2113
- async function readProcessStart(pid) {
2114
- if (!Number.isInteger(pid) || pid <= 0 || process.platform === "win32") return void 0;
2115
- if (process.platform === "linux") {
2116
- let stat;
2117
- try {
2118
- stat = await readFile(`/proc/${pid}/stat`, "utf8");
2119
- } catch {
2120
- return;
2121
- }
2122
- const startTime = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u)[19];
2123
- return startTime !== void 0 && /^\d+$/u.test(startTime) ? startTime : void 0;
2124
- }
2125
- try {
2126
- const { stdout } = await execFileAsync("ps", [
2127
- "-o",
2128
- "lstart=",
2129
- "-p",
2130
- String(pid)
2131
- ]);
2132
- const token = stdout.trim();
2133
- return token.length === 0 ? void 0 : token;
2134
- } catch {
2135
- return;
2136
- }
2137
- }
2138
2117
  async function readLockFile(path) {
2139
2118
  let text;
2119
+ let current;
2140
2120
  try {
2141
- text = await readFile(path, "utf8");
2121
+ const file = await open(path, "r");
2122
+ try {
2123
+ current = lockIdentity(await file.stat({ bigint: true }));
2124
+ text = await file.readFile("utf8");
2125
+ } finally {
2126
+ await file.close();
2127
+ }
2142
2128
  } catch (error) {
2143
2129
  if (isNoEntError(error)) return { state: "missing" };
2144
2130
  throw error;
@@ -2153,30 +2139,24 @@ async function readLockFile(path) {
2153
2139
  const record = parsed;
2154
2140
  if (typeof record !== "object" || record === null || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0 || typeof record.startedAt !== "string" || typeof record.runId !== "string" || record.processStart !== void 0 && typeof record.processStart !== "string") throw new Error(`supervisePursuit: ${path} is not a readable lock file; remove it by hand`);
2155
2141
  const { pid, startedAt, runId, processStart } = record;
2142
+ const verdictRecord = parseLockHolderRecord(parsed);
2143
+ const { pid: _pid, ...identity } = verdictRecord ?? { pid };
2156
2144
  return {
2157
2145
  state: "holder",
2158
2146
  holder: Object.freeze({
2159
2147
  pid,
2160
2148
  startedAt,
2161
2149
  runId,
2162
- ...processStart === void 0 ? {} : { processStart }
2163
- })
2150
+ ...processStart === void 0 ? {} : { processStart },
2151
+ ...identity
2152
+ }),
2153
+ record: verdictRecord,
2154
+ current
2164
2155
  };
2165
2156
  }
2166
- async function holderIsLive(holder) {
2167
- if (!processExists(holder.pid)) return false;
2168
- if (holder.processStart === void 0) return true;
2169
- const current = await readProcessStart(holder.pid);
2170
- return current === void 0 || current === holder.processStart;
2171
- }
2172
- function processExists(pid) {
2173
- if (!Number.isInteger(pid) || pid <= 0) return false;
2174
- try {
2175
- process.kill(pid, 0);
2176
- return true;
2177
- } catch (error) {
2178
- return error.code !== "ESRCH";
2179
- }
2157
+ /** agent-eval's dead-owner rule: only a holder proved dead is stale; alive or unknown stays held. */
2158
+ function holderIsDead(existing) {
2159
+ return lockHolderVerdict(existing.record, existing.current).liveness === "dead";
2180
2160
  }
2181
2161
  /** Remove the lock only while it still names this holder; a reclaimed lock belongs to its new owner. */
2182
2162
  async function releaseHolder(path, holder) {
@@ -2322,6 +2302,119 @@ function startPursuitObserverDelivery(delivery, read, warn = (message) => consol
2322
2302
  };
2323
2303
  }
2324
2304
  //#endregion
2305
+ //#region src/durable/run-reopen.ts
2306
+ /** The directory that holds a run's archived settle records. */
2307
+ const SETTLE_ARCHIVE_DIR = "settles";
2308
+ /** Refuse a reopen that names no operator or reason, or that is combined with a fork or versions. */
2309
+ function assertRunReopen(reopen, opts) {
2310
+ if (reopen.operator.trim().length === 0) throw new TypeError("supervisePursuit: reopen.operator must be non-empty");
2311
+ if (reopen.reason.trim().length === 0) throw new TypeError("supervisePursuit: reopen.reason must be non-empty");
2312
+ if (reopen.operationId.trim().length === 0) throw new TypeError("supervisePursuit: reopen.operationId must be non-empty");
2313
+ if (reopen.budget !== void 0) assertValidBudget(reopen.budget, "reopen.budget");
2314
+ if (opts.fork !== void 0 || opts.versions !== void 0) throw new TypeError("supervisePursuit: reopen continues this run under its own id; fork and versions start new runs");
2315
+ }
2316
+ /**
2317
+ * Archive the settled segment of the run at `runDir` and clear it for a resume. Runs under the
2318
+ * directory lock. Throws when there is nothing to reopen, or when `operationId` already reopened a
2319
+ * segment that has since settled again.
2320
+ */
2321
+ async function reopenSettledRun(input) {
2322
+ const { runDir, runId, reopen, observer, pursuitId, now } = input;
2323
+ const operationId = reopen.operationId.trim();
2324
+ const settlePath = resolve(runDir, SETTLE_RECORD_FILE);
2325
+ const journal = input.journal ?? new FileSpawnJournal(resolve(runDir, "spawn-journal.jsonl"));
2326
+ const events = await journal.loadTree(runId) ?? [];
2327
+ const reopened = events.filter((event) => event.kind === "reopened" && event.id === runId);
2328
+ const mine = reopened.find((event) => event.operationId === operationId);
2329
+ const settled = await readBytes(settlePath);
2330
+ if (settled === void 0) {
2331
+ if (mine !== void 0) return;
2332
+ throw new Error(`supervisePursuit: ${runDir} holds no ${SETTLE_RECORD_FILE}; nothing to reopen; resume it`);
2333
+ }
2334
+ assertResumeLimits(events, {
2335
+ runId,
2336
+ budget: reopen.budget ?? input.limits.budget,
2337
+ ...input.limits.reservationPolicy === void 0 ? {} : { reservationPolicy: input.limits.reservationPolicy }
2338
+ }, mine === void 0 ? reopen.budget : void 0);
2339
+ const result = JSON.parse(settled);
2340
+ const digest = settleRecordDigest(result);
2341
+ if (mine !== void 0 && mine.prior.digest !== digest) throw new Error(`supervisePursuit: reopen '${operationId}' already reopened run '${runId}', which has settled again; reopen it under a new operationId`);
2342
+ const archivedAs = mine?.prior.archivedAs ?? await archiveName(runDir, settled);
2343
+ const archivePath = resolve(runDir, archivedAs);
2344
+ if (!publishExclusiveDurableFile(archivePath, settled) && await readBytes(archivePath) !== settled) throw new Error(`supervisePursuit: ${archivePath} already holds a different settle record; reopen refused`);
2345
+ const at = new Date(now()).toISOString();
2346
+ if (mine === void 0) await journal.appendEvent(runId, {
2347
+ kind: "reopened",
2348
+ id: runId,
2349
+ operator: reopen.operator,
2350
+ reason: reopen.reason,
2351
+ operationId,
2352
+ prior: {
2353
+ kind: String(result.kind),
2354
+ ...typeof result.reason === "string" ? { reason: result.reason } : {},
2355
+ digest,
2356
+ archivedAs
2357
+ },
2358
+ ...reopen.budget === void 0 ? {} : { budget: reopen.budget },
2359
+ seq: reopened.length,
2360
+ at
2361
+ });
2362
+ const eventId = `${runId}:pursuit:reopened:${operationId}`;
2363
+ if (!(await observer.read()).some((record) => record.event?.id === eventId)) await observer.appendEvent({
2364
+ id: eventId,
2365
+ pursuitId,
2366
+ runId,
2367
+ target: "agent.run",
2368
+ phase: "event",
2369
+ timestamp: now(),
2370
+ payload: { reopened: {
2371
+ operator: reopen.operator,
2372
+ reason: reopen.reason,
2373
+ archivedAs
2374
+ } }
2375
+ });
2376
+ setCancellationAside(runDir, operationId);
2377
+ await rm(settlePath);
2378
+ syncDurableDirectory(runDir);
2379
+ }
2380
+ /** The next free `settles/<n>.json`, or the archive that already holds these bytes (a reopen that
2381
+ * crashed after archiving and before its journal row). */
2382
+ async function archiveName(runDir, settled) {
2383
+ const dir = resolve(runDir, SETTLE_ARCHIVE_DIR);
2384
+ mkdirSync(dir, { recursive: true });
2385
+ const numbers = readdirSync(dir).map((name) => /^(\d+)\.json$/u.exec(name)?.[1]).filter((n) => n !== void 0).map(Number).sort((a, b) => a - b);
2386
+ for (const n of numbers) if (await readBytes(join(dir, `${n}.json`)) === settled) return `${SETTLE_ARCHIVE_DIR}/${n}.json`;
2387
+ return `${SETTLE_ARCHIVE_DIR}/${(numbers.at(-1) ?? 0) + 1}.json`;
2388
+ }
2389
+ /** Move the settled run's cancel request and acknowledgement aside under the reopen's and the
2390
+ * request's operation ids, so the reopened run accepts a new `cancelRun`. Idempotent: an absent
2391
+ * file is skipped. A reopen's operation id names one segment, so a later segment cancelled under
2392
+ * the same cancel operation id lands beside this one instead of over it. */
2393
+ function setCancellationAside(runDir, reopenOperationId) {
2394
+ const requestFile = runCancelRequestFile(runDir);
2395
+ const ackFile = runCancellationFile(runDir);
2396
+ if (!existsSync(requestFile) && !existsSync(ackFile)) return;
2397
+ const source = existsSync(requestFile) ? requestFile : ackFile;
2398
+ const { operationId } = JSON.parse(readFileSync(source, "utf8"));
2399
+ const reopenedDir = join(workerCancellationsDir(runDir), "reopened");
2400
+ const aside = join(reopenedDir, encodeURIComponent(reopenOperationId), encodeURIComponent(operationId));
2401
+ mkdirSync(aside, { recursive: true });
2402
+ if (existsSync(ackFile)) renameSync(ackFile, join(aside, "run.json"));
2403
+ if (existsSync(requestFile)) renameSync(requestFile, join(aside, "run.request.json"));
2404
+ syncDurableDirectory(aside);
2405
+ syncDurableDirectory(join(reopenedDir, encodeURIComponent(reopenOperationId)));
2406
+ syncDurableDirectory(reopenedDir);
2407
+ syncDurableDirectory(workerCancellationsDir(runDir));
2408
+ }
2409
+ async function readBytes(path) {
2410
+ try {
2411
+ return await readFile(path, "utf8");
2412
+ } catch (error) {
2413
+ if (error.code === "ENOENT") return void 0;
2414
+ throw error;
2415
+ }
2416
+ }
2417
+ //#endregion
2325
2418
  //#region src/durable/supervise-pursuit.ts
2326
2419
  /** A failed Runtime execution whose complete third-person projection was retained. */
2327
2420
  var SupervisePursuitError = class extends Error {
@@ -2359,16 +2452,33 @@ var SupervisePursuitError = class extends Error {
2359
2452
  */
2360
2453
  async function supervisePursuit(profile, task, opts) {
2361
2454
  assertPursuitDirectory(opts, "supervisePursuit");
2455
+ if (opts.reopen !== void 0) assertRunReopen(opts.reopen, opts);
2362
2456
  if (opts.versions !== void 0) return runPursuitVersions(profile, task, opts, supervisePursuit);
2363
- const { pursuitId: _pursuitId, hooks: _hooks, fork, versions: _versions, observerDelivery: _observerDelivery, ...superviseOptions } = opts;
2457
+ const { pursuitId: _pursuitId, hooks: _hooks, fork, versions: _versions, observerDelivery: _observerDelivery, reopen, ...superviseOptions } = opts;
2364
2458
  const forked = fork === void 0 ? void 0 : await prepareRunFork(profile, task, superviseOptions, fork);
2365
2459
  return runDurablePursuit(opts, ({ hooks, steerDir }) => supervise(forked?.profile ?? profile, task, {
2366
2460
  ...superviseOptions,
2367
2461
  ...forked === void 0 ? {} : { execution: forked.execution },
2462
+ ...reopen === void 0 ? {} : {
2463
+ resume: true,
2464
+ ...reopen.budget === void 0 ? {} : { budget: reopen.budget }
2465
+ },
2368
2466
  steerDir,
2369
2467
  retainedAtSettlement: "release",
2370
2468
  hooks
2371
- }), (result) => result);
2469
+ }), (result) => result, reopen === void 0 ? void 0 : (observer) => reopenSettledRun({
2470
+ runDir: opts.runDir.trim(),
2471
+ runId: opts.runId ?? "supervise",
2472
+ reopen,
2473
+ journal: opts.journal,
2474
+ limits: {
2475
+ budget: opts.budget,
2476
+ ...opts.reservationPolicy === void 0 ? {} : { reservationPolicy: opts.reservationPolicy }
2477
+ },
2478
+ observer,
2479
+ pursuitId: opts.pursuitId.trim(),
2480
+ now: opts.now ?? Date.now
2481
+ }));
2372
2482
  }
2373
2483
  /**
2374
2484
  * One-call durable execution of an `AgentGraph`: the graph twin of {@link supervisePursuit}.
@@ -2404,7 +2514,7 @@ function assertPursuitDirectory(opts, entry) {
2404
2514
  * journal, failure record and settle record. `execute` receives the observer-composed hooks and
2405
2515
  * the steer directory the run's control plane uses; `settled` names the value `result.json` holds.
2406
2516
  */
2407
- async function runDurablePursuit(opts, execute, settled) {
2517
+ async function runDurablePursuit(opts, execute, settled, reopen) {
2408
2518
  const pursuitId = opts.pursuitId.trim();
2409
2519
  const runDir = opts.runDir.trim();
2410
2520
  const observerPath = resolve(runDir, "observer.jsonl");
@@ -2416,9 +2526,12 @@ async function runDurablePursuit(opts, execute, settled) {
2416
2526
  let delivery;
2417
2527
  let terminalState = "failed";
2418
2528
  try {
2419
- const prior = await readSettleRecord(runDir);
2420
- if (prior !== void 0) throw new SettledRunDirectoryError(settlePath, prior.tree.root, runId);
2421
2529
  const observer = createFileObserverHooks(observerPath, pursuitId);
2530
+ if (reopen !== void 0) await reopen(observer.journal);
2531
+ else {
2532
+ const prior = await readSettleRecord(runDir);
2533
+ if (prior !== void 0) throw new SettledRunDirectoryError(settlePath, prior.tree.root, runId);
2534
+ }
2422
2535
  await observer.journal.appendEvent(rootEvent(pursuitId, runId, "before", now()));
2423
2536
  delivery = opts.observerDelivery === void 0 ? void 0 : startPursuitObserverDelivery(opts.observerDelivery, async () => projectPursuit(await observer.journal.read()));
2424
2537
  let result;