@tangle-network/agent-runtime 6.0.7 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +23 -9
  3. package/dist/{activation-Bb2ovvM5.js → activation-Ba9IAgQ3.js} +2 -2
  4. package/dist/{activation-Bb2ovvM5.js.map → activation-Ba9IAgQ3.js.map} +1 -1
  5. package/dist/{activation-DPz8kGpT.d.ts → activation-DIkTEBWF.d.ts} +2 -2
  6. package/dist/agent.d.ts +1 -1
  7. package/dist/agent.js +4 -4
  8. package/dist/{coordination-manager-Cow3jgu1.js → coordination-manager-DQZ2u6L-.js} +58 -6
  9. package/dist/coordination-manager-DQZ2u6L-.js.map +1 -0
  10. package/dist/{delegate-zUOoWutU.js → delegate-DFLSVjFG.js} +2 -2
  11. package/dist/{delegate-zUOoWutU.js.map → delegate-DFLSVjFG.js.map} +1 -1
  12. package/dist/durable.d.ts +71 -4
  13. package/dist/durable.js +159 -11
  14. package/dist/durable.js.map +1 -1
  15. package/dist/{failure-cause-BzjUUsRz.js → failure-cause-rJTm_Tx8.js} +6 -9
  16. package/dist/failure-cause-rJTm_Tx8.js.map +1 -0
  17. package/dist/{graph-BS9p0hHV.js → graph-BjQF2syl.js} +7 -7
  18. package/dist/{graph-BS9p0hHV.js.map → graph-BjQF2syl.js.map} +1 -1
  19. package/dist/{graph-registry-DbCwfHMY.js → graph-registry-7X2nrreR.js} +2 -2
  20. package/dist/{graph-registry-DbCwfHMY.js.map → graph-registry-7X2nrreR.js.map} +1 -1
  21. package/dist/{graph-registry-C4ASeL2P.js → graph-registry-gYbq4L5Z.js} +1 -1
  22. package/dist/{improve-Ca4PSA2z.js → improve-BolbYegO.js} +7 -7
  23. package/dist/{improve-Ca4PSA2z.js.map → improve-BolbYegO.js.map} +1 -1
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +10 -10
  26. package/dist/intelligence.d.ts +2 -2
  27. package/dist/intelligence.js +4 -4
  28. package/dist/{jsonl-file-BeUa5EDx.js → jsonl-file-DsxVI0E_.js} +5 -5
  29. package/dist/jsonl-file-DsxVI0E_.js.map +1 -0
  30. package/dist/kernel.d.ts +2 -2
  31. package/dist/kernel.js +14 -14
  32. package/dist/{loop-runner-B_K3oLGC.js → loop-runner-C9W3T_3T.js} +2 -2
  33. package/dist/{loop-runner-B_K3oLGC.js.map → loop-runner-C9W3T_3T.js.map} +1 -1
  34. package/dist/{loop-runner-9surW8mg.d.ts → loop-runner-Cs9IJ6Nk.d.ts} +2 -2
  35. package/dist/loop-runner-bin.d.ts +1 -1
  36. package/dist/loop-runner-bin.js +1 -1
  37. package/dist/mcp/bin.js +3 -3
  38. package/dist/mcp/index.d.ts +1 -1
  39. package/dist/mcp/index.js +6 -6
  40. package/dist/mcp/memory-bin.js +1 -1
  41. package/dist/profiles.d.ts +1 -1
  42. package/dist/{provision-supervisor-ZG-ZxIe9.js → provision-supervisor-BQFHjCtN.js} +5 -5
  43. package/dist/{provision-supervisor-ZG-ZxIe9.js.map → provision-supervisor-BQFHjCtN.js.map} +1 -1
  44. package/dist/{run-cancellation-CqF01-zj.js → run-cancellation-B84X8ZAP.js} +3 -3
  45. package/dist/{run-cancellation-CqF01-zj.js.map → run-cancellation-B84X8ZAP.js.map} +1 -1
  46. package/dist/{run-state-CeMgqULw.js → run-state-Dh1bGERI.js} +25 -19
  47. package/dist/run-state-Dh1bGERI.js.map +1 -0
  48. package/dist/{runtime-CBt8PWn5.js → runtime-C9F1PG2A.js} +17 -17
  49. package/dist/{runtime-CBt8PWn5.js.map → runtime-C9F1PG2A.js.map} +1 -1
  50. package/dist/{search-executor-CoLgtigZ.js → search-executor-CKR9_6NR.js} +4 -4
  51. package/dist/{search-executor-CoLgtigZ.js.map → search-executor-CKR9_6NR.js.map} +1 -1
  52. package/dist/{server-Dv_wLT9_.js → server-Dh3SQGQT.js} +4 -4
  53. package/dist/{server-Dv_wLT9_.js.map → server-Dh3SQGQT.js.map} +1 -1
  54. package/dist/{settle-record-DTh0cvF-.js → settle-record-C6LdVvok.js} +13 -5
  55. package/dist/settle-record-C6LdVvok.js.map +1 -0
  56. package/dist/{structural-rollout-ccetqcnq.js → structural-rollout-Dapf2aZY.js} +25 -7
  57. package/dist/structural-rollout-Dapf2aZY.js.map +1 -0
  58. package/dist/{supervise-BMqPWSv7.js → supervise-xwl2D6q3.js} +10 -8
  59. package/dist/{supervise-BMqPWSv7.js.map → supervise-xwl2D6q3.js.map} +1 -1
  60. package/dist/{supervision-discovery-mF3cWh36.js → supervision-discovery-B47U8TH5.js} +4 -4
  61. package/dist/{supervision-discovery-mF3cWh36.js.map → supervision-discovery-B47U8TH5.js.map} +1 -1
  62. package/dist/{scope-BF3RUg3U.js → supervisor-accounting-DxLQ_TQe.js} +343 -52
  63. package/dist/supervisor-accounting-DxLQ_TQe.js.map +1 -0
  64. package/dist/{supervisor-CigTbS9A.js → supervisor-gC-u_PRk.js} +12 -315
  65. package/dist/supervisor-gC-u_PRk.js.map +1 -0
  66. package/dist/testing.d.ts +1 -1
  67. package/dist/testing.js +11 -11
  68. package/dist/{tool-server-B9q2dtnW.js → tool-server-Dg350Bgz.js} +2 -2
  69. package/dist/{tool-server-B9q2dtnW.js.map → tool-server-Dg350Bgz.js.map} +1 -1
  70. package/dist/{top-web-DBhkr1lN.js → top-web-BJLnFQdr.js} +7 -7
  71. package/dist/{top-web-DBhkr1lN.js.map → top-web-BJLnFQdr.js.map} +1 -1
  72. package/dist/tui/bin.js +6 -6
  73. package/dist/tui/bin.js.map +1 -1
  74. package/dist/tui/index.d.ts +1 -1
  75. package/dist/tui/index.js +2 -2
  76. package/dist/{types-DmJTtmgm.d.ts → types-Jg274chm.d.ts} +156 -310
  77. package/package.json +1 -1
  78. package/dist/coordination-manager-Cow3jgu1.js.map +0 -1
  79. package/dist/failure-cause-BzjUUsRz.js.map +0 -1
  80. package/dist/jsonl-file-BeUa5EDx.js.map +0 -1
  81. package/dist/run-state-CeMgqULw.js.map +0 -1
  82. package/dist/scope-BF3RUg3U.js.map +0 -1
  83. package/dist/settle-record-DTh0cvF-.js.map +0 -1
  84. package/dist/structural-rollout-ccetqcnq.js.map +0 -1
  85. package/dist/supervisor-CigTbS9A.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"jsonl-file-BeUa5EDx.js","names":[],"sources":["../src/content-digest.ts","../src/durable/jsonl-append.ts","../src/durable/jsonl-file.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\n\nexport function sha256(value: string): string {\n return createHash('sha256').update(value).digest('hex')\n}\n","import {\n closeSync,\n fstatSync,\n fsync,\n fsyncSync,\n ftruncateSync,\n openSync,\n readSync,\n writeSync,\n} from 'node:fs'\nimport { promisify } from 'node:util'\nimport { fenceRunWrite } from './run-fence'\n\n/** Append one JSONL record under the caller's existing single-writer discipline.\n * The caller creates the parent directory. Recover only an uncommitted tail and handle short\n * writes under one generation fence; fsync after the fence, off the event loop (#2362), and\n * resolve only once the bytes are durable. An optional acknowledgement reads the same open\n * descriptor inside the fence (the spawn journal validates its file stamp here).\n * @internal\n */\nexport function appendCommittedJsonLine(path: string, record: unknown): Promise<void>\nexport function appendCommittedJsonLine<T>(\n path: string,\n record: unknown,\n acknowledge: (fd: number) => T,\n): Promise<T>\nexport async function appendCommittedJsonLine<T>(\n path: string,\n record: unknown,\n acknowledge?: (fd: number) => T,\n): Promise<T | undefined> {\n const encoded = JSON.stringify(record)\n if (encoded === undefined) throw new TypeError('JSONL records must be JSON serializable')\n let fd: number | undefined\n try {\n const acknowledged = fenceRunWrite(() => {\n const bytes = Buffer.from(`${prepareJsonlTail(path) ? '\\n' : ''}${encoded}\\n`)\n fd = openSync(path, 'a')\n let offset = 0\n while (offset < bytes.length) {\n const written = writeSync(fd, bytes, offset, bytes.length - offset)\n if (written <= 0)\n throw new Error(`append-only file write made no progress at byte ${offset}`)\n offset += written\n }\n return acknowledge?.(fd)\n })\n await promisify(fsync)(fd as number)\n return acknowledged\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\n/** Recover only an uncommitted tail. Caller holds the generation fence. @internal */\nexport function prepareJsonlTail(path: string): boolean {\n let fd: number\n try {\n fd = openSync(path, 'r+')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false\n throw error\n }\n try {\n const size = fstatSync(fd).size\n if (size === 0) return false\n const last = Buffer.allocUnsafe(1)\n readAt(fd, last, size - 1)\n if (last[0] === 0x0a) return false\n const chunks: Buffer[] = []\n let end = size\n let tailStart = 0\n while (end > 0) {\n const start = Math.max(0, end - 65_536)\n const chunk = Buffer.allocUnsafe(end - start)\n readAt(fd, chunk, start)\n const newline = chunk.lastIndexOf(0x0a)\n chunks.push(chunk.subarray(newline + 1))\n if (newline >= 0) {\n tailStart = start + newline + 1\n break\n }\n end = start\n }\n try {\n JSON.parse(Buffer.concat(chunks.reverse()).toString('utf8'))\n return true\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error\n ftruncateSync(fd, tailStart)\n fsyncSync(fd)\n return false\n }\n } finally {\n closeSync(fd)\n }\n}\n\nfunction readAt(fd: number, buffer: Buffer, position: number): void {\n let offset = 0\n while (offset < buffer.length) {\n const read = readSync(fd, buffer, offset, buffer.length - offset, position + offset)\n if (read === 0) throw new Error('journal changed while preparing its append tail')\n offset += read\n }\n}\n","import type { FileHandle } from 'node:fs/promises'\nimport { prepareJsonlTail } from './jsonl-append'\nimport { fenceRunWrite } from './run-fence'\n\n/** Parse in-memory evidence with the same commit boundary as streamed journal reads. */\nexport function parseCommittedJsonLines<T>(text: string, source: string): T[] {\n const lines = text.split('\\n')\n const records: T[] = []\n for (const [index, line] of lines.entries()) {\n const record = parseRecord<T>(line, source, index + 1, index < lines.length - 1)\n if (record !== undefined) records.push(record.value)\n }\n return records\n}\n\n/** Read a fixed file prefix one record at a time, never allocating the whole journal as a string.\n * A reader cannot chase concurrent appends forever. Missing files are empty only when requested;\n * corruption, short reads, and other I/O errors remain failures. Closing the iterator closes the fd. */\nexport async function* readCommittedJsonLines<T>(\n path: string,\n options: {\n allowMissing?: boolean\n allowWhitespaceLines?: boolean\n onBytes?: (bytes: Uint8Array) => void\n /** Internal fixed-prefix range. The caller supplies a record boundary and owns the handle. */\n handle?: FileHandle\n start?: number\n end?: number\n strictUtf8?: boolean\n onCommit?: (offset: number) => void\n } = {},\n): AsyncGenerator<T> {\n const fs = await import('node:fs/promises')\n let handle: FileHandle\n try {\n handle = options.handle ?? (await fs.open(path, 'r'))\n } catch (error) {\n if (options.allowMissing && isNoEntError(error)) return\n throw error\n }\n try {\n const start = options.start ?? 0\n const end = options.end ?? (await handle.stat()).size\n if (end <= start) return\n const fragments: Buffer[] = []\n let offset = start\n let lineNumber = 1\n const parse = (terminated: boolean): { value: T } | undefined => {\n const bytes = Buffer.concat(fragments)\n let line: string\n try {\n line = options.strictUtf8\n ? new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n : bytes.toString('utf8')\n } catch (cause) {\n if (!terminated) return undefined\n throw new Error(`${path}: malformed UTF-8 at line ${lineNumber}`, { cause })\n }\n return parseRecord<T>(line, path, lineNumber++, terminated, options.allowWhitespaceLines)\n }\n let position = start\n while (position < end) {\n const buffer = Buffer.alloc(Math.min(65_536, end - position))\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, position)\n if (bytesRead === 0) {\n throw new Error(`${path}: journal changed while reading its committed prefix`)\n }\n position += bytesRead\n const bytes = buffer.subarray(0, bytesRead)\n options.onBytes?.(bytes)\n let start = 0\n for (let end = bytes.indexOf(10); end !== -1; end = bytes.indexOf(10, start)) {\n fragments.push(bytes.subarray(start, end))\n const record = parse(true)\n offset += fragments.reduce((size, part) => size + part.length, 0) + 1\n fragments.length = 0\n options.onCommit?.(offset)\n if (record !== undefined) yield record.value\n start = end + 1\n }\n if (start < bytes.length) fragments.push(bytes.subarray(start))\n }\n const record = parse(false)\n if (record !== undefined) {\n options.onCommit?.(end)\n yield record.value\n }\n } finally {\n if (options.handle === undefined) await handle.close()\n }\n}\n\n/** An invalid unterminated tail may be a torn write; other parse failures are not evidence of one. */\nfunction parseRecord<T>(\n line: string,\n source: string,\n lineNumber: number,\n terminated: boolean,\n allowWhitespaceLines = false,\n): { value: T } | undefined {\n if (line.length === 0 || (allowWhitespaceLines && line.trim().length === 0)) return undefined\n try {\n return { value: JSON.parse(line) as T }\n } catch (cause) {\n if (!(cause instanceof SyntaxError)) throw cause\n if (!terminated) return undefined\n throw new Error(`${source}: malformed JSONL record at line ${lineNumber}`, { cause })\n }\n}\n\nexport { appendCommittedJsonLine } from './jsonl-append'\n\n/** Serialize JSONL file updates across processes and aliases using Eval's existing file lock.\n * The caller owns validation and the record format. The lock path names the canonical file,\n * including its first write, so two directory aliases cannot acquire different write heads.\n * @internal\n */\nexport async function withJsonlFileLock<T>(\n path: string,\n operation: (canonicalPath: string) => Promise<T>,\n): Promise<T> {\n const fs = await import('node:fs/promises')\n const { dirname } = await import('node:path')\n const { tryAcquireAtomicFileLock } = await import('@tangle-network/agent-eval/ledger-core')\n await fs.mkdir(dirname(path), { recursive: true })\n const file = await fs.open(path, 'a')\n await file.close()\n const canonicalPath = await fs.realpath(path)\n const deadline = Date.now() + 5000\n for (;;) {\n const acquisition = tryAcquireAtomicFileLock({ lockPath: `${canonicalPath}.lock` })\n if (!acquisition.acquired) {\n if (Date.now() >= deadline) {\n throw new Error(`journal file lock unavailable after 5000ms: ${canonicalPath}`)\n }\n await new Promise((resolve) => setTimeout(resolve, 25))\n continue\n }\n try {\n return await operation(canonicalPath)\n } finally {\n acquisition.lock.release()\n }\n }\n}\n\n/** Read one keyed registry snapshot without an extra whole-file text allocation.\n * Historical registry files allow whitespace-only separators; append journals remain strict.\n * @internal\n */\nexport async function readCommittedJsonFile<T>(path: string): Promise<T[]> {\n const records: T[] = []\n for await (const record of readCommittedJsonLines<T>(path, {\n allowMissing: true,\n allowWhitespaceLines: true,\n })) {\n records.push(record)\n }\n return records\n}\n\n/** Replace one keyed row and publish one durable snapshot. The caller holds the file lock.\n * Keep unrelated rows in order and put the replacement last. No revision archive is retained.\n * @internal\n */\nexport async function upsertCommittedJsonLine<T>(\n path: string,\n record: T,\n key: (record: T) => string,\n): Promise<void> {\n const encoded = JSON.stringify(record)\n if (encoded === undefined) throw new Error('JSONL records must be JSON serializable')\n const snapshot: T = JSON.parse(encoded)\n const id = key(snapshot)\n const updated: T[] = []\n for await (const prior of readCommittedJsonLines<T>(path, {\n allowMissing: true,\n allowWhitespaceLines: true,\n })) {\n if (key(prior) !== id) updated.push(prior)\n }\n updated.push(snapshot)\n // Reuse Runtime's file publication owner; this module owns JSONL framing, not another\n // durability implementation. Preserve the existing snapshot's permissions across rename.\n const { writeAtomicDurableFile } = await import('../runtime/supervise/durable-file')\n writeAtomicDurableFile(path, `${updated.map((row) => JSON.stringify(row)).join('\\n')}\\n`, {\n preserveMode: true,\n })\n}\n\n/** Recover a torn tail under the same generation fence as durable publications. @internal */\nexport async function prepareJsonlAppend(path: string): Promise<boolean> {\n return fenceRunWrite(() => prepareJsonlTail(path))\n}\n\n/** True for a filesystem error meaning \"the file is not there yet\" — the one condition an\n * append-only reader treats as an empty log rather than a fault. */\nexport function isNoEntError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: unknown }).code === 'ENOENT'\n )\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"run-state-CeMgqULw.js","names":["delay"],"sources":["../src/durable/run-lock.ts","../src/durable/spawn-journal/read.ts","../src/durable/run-state.ts"],"sourcesContent":["import {\n closeSync,\n existsSync,\n fstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n unlinkSync,\n} from 'node:fs'\nimport { resolve } from 'node:path'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport {\n type LockHolderRecord,\n type LockIdentity,\n lockHolderVerdict,\n lockIdentity,\n parseLockHolderRecord,\n readLockHolderIdentity,\n} from '@tangle-network/agent-eval/lock-holder'\nimport {\n publishExclusiveDurableFile,\n writeAtomicDurableFile,\n} from '../runtime/supervise/durable-file'\nimport { isNoEntError } from './jsonl-file'\nimport { mutateRunClaim, runGenerationPath, runWithWriteClaim } from './run-fence'\n\n/*\n * Exclusive ownership of one durable run directory for the life of one `supervisePursuit` call.\n *\n * Measured motive (discovery-lab recursive smoke r1): the recovery protocol is\n * `SIGKILL` the runner while a child is live, then resume on the same directory. PID 1527 was\n * killed at 06:20:27Z and PID 2058 resumed at 06:24:59Z. Nothing refused a second process while\n * the first was still alive; two processes on one directory both append to `spawn-journal.jsonl`\n * and `observer.jsonl`, and the recorded assignment keys and settlements stop being one sequence.\n * The operator relied on timing. This lock makes the second process refuse and name the holder.\n *\n * The holder is identified by its pid and the OS's start token for that pid. r1 ran in a\n * container whose pids are small (1527, 2058) and restart from 1, so a pid alone names the wrong\n * process after enough spawns: a lock left by a killed run would then match an unrelated live\n * process and hold the directory until an operator removed the file. Whether a holder is dead is\n * agent-eval's one dead-owner rule (`@tangle-network/agent-eval/lock-holder`): the record carries\n * the start token, host, boot id, pid namespace and the lock file's own inode identity, and a\n * holder is reclaimed only when those prove it gone or its pid reused. A record that cannot prove\n * that, such as one written before these fields existed, is refused and is removed by hand.\n *\n * The lock is operational state, not evidence: replay, projection, and the settle record ignore\n * it. A holder whose process is gone is reclaimed through Eval's crash-recoverable mutation lock.\n */\n\n/** The lock file `supervisePursuit` holds inside a run directory for the life of one call. */\nexport const RUN_DIRECTORY_LOCK_FILE = 'supervise.lock'\n\n/** What the lock file records about its holder. */\nexport interface RunDirectoryLockHolder {\n readonly pid: number\n /** Monotonic acquisition generation; absent on legacy lock records. */\n readonly generation?: number\n /** ISO instant the holder took the lock. */\n readonly startedAt: string\n readonly runId: string\n /** The OS's start token for `pid` when the host reports one. A holder without one is never proved dead. */\n readonly processStart?: string\n /** Hostname of the holder. Records written before it existed are never proved dead. */\n readonly host?: string\n readonly bootId?: string\n readonly pidNamespace?: string\n /** Inode identity of the lock file this record was written into. */\n readonly lock?: LockIdentity\n}\n\n/** A held lock. `release()` removes the file; it is safe to call more than once. */\nexport interface RunDirectoryLock extends RunDirectoryLockHolder {\n readonly generation: number\n readonly path: string\n /** Bind durable writes to this acquisition, including work queued before release. */\n run<T>(operation: () => T): T\n release(): Promise<void>\n}\n\n/** The directory is held by a live process. `holder` is what that process recorded. */\nexport class RunDirectoryLockedError extends Error {\n readonly path: string\n readonly holder: RunDirectoryLockHolder\n\n constructor(path: string, holder: RunDirectoryLockHolder) {\n super(\n `supervisePursuit: ${path} is held by pid ${holder.pid} (run '${holder.runId}', since ${holder.startedAt}); one process drives a run directory at a time`,\n )\n this.name = 'RunDirectoryLockedError'\n this.path = path\n this.holder = holder\n }\n}\n\n/**\n * Take `runDir/supervise.lock`, or refuse.\n *\n * The file is published with its full content or not at all, so a contender never reads a\n * half-written holder. A lock whose holder is proved dead (its pid no longer exists, the pid now\n * belongs to a process with a different start token, or the host rebooted) is stale and is removed\n * under the mutation guard; a holder that is alive or cannot be proved dead refuses. An empty file\n * names no holder and is reclaimed. A file with unreadable content is left in place and refused:\n * reclaiming it could evict a live holder written by something other than this module.\n */\nexport async function acquireRunDirectoryLock(\n runDir: string,\n runId: string,\n now: () => number = Date.now,\n): Promise<RunDirectoryLock> {\n const dir = resolve(runDir)\n const path = resolve(dir, RUN_DIRECTORY_LOCK_FILE)\n mkdirSync(dir, { recursive: true })\n const { pid, ...identity } = readLockHolderIdentity(process.pid)\n const owner = { pid, startedAt: new Date(now()).toISOString(), runId, ...identity }\n return mutateRunClaim(path, () => {\n const existing = readLockFile(path)\n if (existing.state === 'holder' && !holderIsDead(existing)) {\n throw new RunDirectoryLockedError(path, existing.holder)\n }\n let previousGeneration = 0\n try {\n previousGeneration = Number(readFileSync(runGenerationPath(dir), 'utf8'))\n if (!Number.isSafeInteger(previousGeneration) || previousGeneration < 1) {\n throw new Error('invalid run directory generation counter')\n }\n } catch (error) {\n if (!isNoEntError(error)) throw error\n }\n const generation =\n Math.max(\n previousGeneration,\n existing.state === 'holder' ? (existing.holder.generation ?? 0) : 0,\n ) + 1\n if (!Number.isSafeInteger(generation)) throw new Error('run directory generation exhausted')\n writeAtomicDurableFile(runGenerationPath(dir), `${generation}\\n`)\n if (existing.state !== 'missing') removeIfPresent(path)\n const written: { holder?: RunDirectoryLockHolder } = {}\n // The lock file is a hard link of the temporary file, so the temporary file's inode identity\n // is the lock's, and a later verdict can prove this record was written for this very lock.\n const published = publishExclusiveDurableFile(path, (file) => {\n written.holder = Object.freeze({ ...owner, generation, lock: lockIdentity(file) })\n return `${JSON.stringify(written.holder)}\\n`\n })\n const holder = written.holder\n if (!published || holder === undefined) {\n throw new Error(`supervisePursuit: could not take ${path}`)\n }\n let released = false\n return Object.freeze({\n ...holder,\n generation,\n path,\n run: <T>(operation: () => T): T => {\n if (holder.lock === undefined) throw new Error('run lock has no inode identity')\n return runWithWriteClaim({ path, generation, lock: holder.lock }, operation)\n },\n release: async () => {\n if (released) return\n await releaseHolder(path, holder)\n released = true\n },\n })\n })\n}\n\n/** Read the holder a lock file names, or `undefined` when no lock file names one. */\nexport async function readRunDirectoryLock(\n runDir: string,\n): Promise<RunDirectoryLockHolder | undefined> {\n const existing = await readLockFile(resolve(runDir, RUN_DIRECTORY_LOCK_FILE))\n return existing.state === 'holder' ? existing.holder : undefined\n}\n\n/** What {@link runDirectoryHolderIsLive} proved about a run directory's recorded holder. */\nexport interface RunDirectoryHolderLiveness {\n /** False only when the holder that took the lock is proved dead; a holder that cannot be proved dead reads as live. */\n readonly live: boolean\n /** The holder could not be verified on this host. `live` stays conservative for exclusion. */\n readonly unknown?: true\n /** The holder the lock file names. Absent when no lock file names one, which reads as not live. */\n readonly holder?: RunDirectoryLockHolder\n}\n\n/**\n * Whether a run directory is still held by the live process that took its lock.\n *\n * The same rule `acquireRunDirectoryLock` applies to decide whether a lock is stale, exposed so a\n * caller — a supervisor picking up an abandoned directory, an operator tool listing runs — asks\n * the question instead of hand-rolling `process.kill(pid, 0)`. A bare signal probe cannot tell a\n * live holder from an unrelated process that later took the same pid, which is the failure this\n * lock's start token exists to prevent.\n *\n * The answer is ADVISORY: it is read outside the mutation guard `acquireRunDirectoryLock` holds,\n * so a `false` can be stale by the time the caller acts on it and a concurrent acquire can take\n * the directory in between. Reporting and listing are what this is for. A caller that intends to\n * TAKE the directory calls `acquireRunDirectoryLock`, which evaluates the same rule under the\n * guard and refuses atomically.\n */\nexport async function runDirectoryHolderIsLive(\n runDir: string,\n): Promise<RunDirectoryHolderLiveness> {\n const existing = await readLockFile(resolve(runDir, RUN_DIRECTORY_LOCK_FILE))\n if (existing.state !== 'holder') return { live: false }\n const verdict = lockHolderVerdict(existing.record, existing.current)\n return {\n live: verdict.liveness !== 'dead',\n holder: existing.holder,\n ...(verdict.liveness === 'unknown' ? { unknown: true as const } : {}),\n }\n}\n\ntype LockFileState =\n | { readonly state: 'missing' }\n | { readonly state: 'empty' }\n | {\n readonly state: 'holder'\n readonly holder: RunDirectoryLockHolder\n /** The holder as agent-eval's rule reads it; absent when the record names no host. */\n readonly record: LockHolderRecord | undefined\n /** Inode identity of the file the holder was read from. */\n readonly current: LockIdentity\n }\n\nfunction readLockFile(path: string): LockFileState {\n let text: string\n let current: LockIdentity\n try {\n const file = openSync(path, 'r')\n try {\n current = lockIdentity(fstatSync(file, { bigint: true }))\n text = readFileSync(file, 'utf8')\n } finally {\n closeSync(file)\n }\n } catch (error) {\n if (isNoEntError(error)) return { state: 'missing' }\n throw error\n }\n // This module publishes the file with its full content or not at all, so an empty file was\n // not left by a live holder.\n if (text.trim().length === 0) return { state: 'empty' }\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch (cause) {\n throw new Error(`supervisePursuit: ${path} is not a readable lock file; remove it by hand`, {\n cause,\n })\n }\n const record = parsed as Partial<RunDirectoryLockHolder> | null\n if (\n typeof record !== 'object' ||\n record === null ||\n typeof record.pid !== 'number' ||\n !Number.isInteger(record.pid) ||\n record.pid <= 0 ||\n typeof record.startedAt !== 'string' ||\n typeof record.runId !== 'string' ||\n (record.processStart !== undefined && typeof record.processStart !== 'string') ||\n (record.generation !== undefined &&\n (!Number.isSafeInteger(record.generation) || record.generation < 1))\n ) {\n throw new Error(`supervisePursuit: ${path} is not a readable lock file; remove it by hand`)\n }\n const { pid, startedAt, runId, processStart } = record\n // A missing or malformed identity field is dropped, which leaves the holder never provably dead.\n const verdictRecord = parseLockHolderRecord(parsed)\n const { pid: _pid, ...identity } = verdictRecord ?? { pid }\n return {\n state: 'holder',\n holder: Object.freeze({\n pid,\n startedAt,\n runId,\n ...(record.generation === undefined ? {} : { generation: record.generation }),\n ...(processStart === undefined ? {} : { processStart }),\n ...identity,\n }),\n record: verdictRecord,\n current,\n }\n}\n\n/** agent-eval's dead-owner rule: only a holder proved dead is stale; alive or unknown stays held. */\nfunction holderIsDead(existing: Extract<LockFileState, { state: 'holder' }>): boolean {\n return lockHolderVerdict(existing.record, existing.current).liveness === 'dead'\n}\n\n/** Remove the lock only while it still names this holder; a reclaimed lock belongs to its new owner. */\nasync function releaseHolder(path: string, holder: RunDirectoryLockHolder): Promise<void> {\n const deadline = performance.now() + 1_000\n while (existsSync(`${path}.guard`) && performance.now() < deadline) await delay(10)\n mutateRunClaim(path, () => {\n const existing = readLockFile(path)\n if (existing.state !== 'holder') return\n if (\n existing.holder.pid !== holder.pid ||\n existing.holder.startedAt !== holder.startedAt ||\n existing.holder.runId !== holder.runId ||\n existing.holder.processStart !== holder.processStart ||\n existing.holder.generation !== holder.generation ||\n existing.holder.lock?.dev !== holder.lock?.dev ||\n existing.holder.lock?.ino !== holder.lock?.ino ||\n existing.holder.lock?.birthtimeNs !== holder.lock?.birthtimeNs\n )\n return\n removeIfPresent(path)\n })\n}\n\nfunction removeIfPresent(path: string): void {\n try {\n unlinkSync(path)\n } catch (error) {\n if (!isNoEntError(error)) throw error\n }\n}\n","import { createHash } from 'node:crypto'\nimport type { BigIntStats } from 'node:fs'\nimport { type FileHandle, open, stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { isRecord } from '@tangle-network/agent-core'\nimport type { NodeId, SpawnEvent } from '../../runtime/supervise/types'\nimport { isNoEntError, readCommittedJsonLines } from '../jsonl-file'\nimport { spawnJournalRecordEvents } from './record-events'\n\n/** Serializable checkpoint of a committed byte boundary, bound to one file and its bytes. */\nexport interface SpawnJournalCursor {\n readonly file: string\n readonly offset: number\n readonly boundary: string\n}\n\n/** Physical groups expand to event records; the owning tree is preserved for every event. */\nexport type SpawnJournalReadRecord =\n | { readonly kind: 'begin'; readonly root: NodeId; readonly at: string }\n | { readonly kind: 'event'; readonly root: NodeId; readonly event: SpawnEvent }\n\nexport interface SpawnJournalReadOptions {\n readonly after?: SpawnJournalCursor\n /** Initial opening-and-tail window, or an incremental page's byte budget. A crossing record\n * is read whole, so one large record can exceed this budget. Omit for complete history. */\n readonly tailBytes?: number\n}\n\nexport interface SpawnJournalReadResult {\n readonly records: ReadonlyArray<SpawnJournalReadRecord>\n /** Absent only when the journal does not exist. Treat as opaque and persist unchanged. */\n readonly cursor?: SpawnJournalCursor\n /** The supplied cursor no longer matches; this page starts over. */\n readonly reset: boolean\n /** An initial opening-and-tail window omitted records between its two windows. */\n readonly truncated: boolean\n /** Bytes after the committed cursor in this snapshot, including an incomplete final write. */\n readonly remainingBytes: number\n}\n\n/** Read a fixed journal prefix, with historical envelopes and framing owned by Runtime.\n * Missing journals return an empty page. Malformed committed records fail; incomplete final\n * writes remain unread for the next call. Cursors detect replacement, truncation and edits at\n * their sampled opening/boundary; they are not integrity proofs against arbitrary file edits.\n */\nexport async function readSpawnJournal(\n runDir: string,\n options: SpawnJournalReadOptions = {},\n): Promise<SpawnJournalReadResult> {\n const { after, tailBytes } = options\n if (tailBytes !== undefined && (!Number.isSafeInteger(tailBytes) || tailBytes < 1)) {\n throw new RangeError('readSpawnJournal: tailBytes must be a positive safe integer')\n }\n const path = join(runDir, 'spawn-journal.jsonl')\n let handle: FileHandle\n try {\n handle = await open(path, 'r')\n } catch (error) {\n if (isNoEntError(error)) {\n return { records: [], reset: after !== undefined, truncated: false, remainingBytes: 0 }\n }\n throw error\n }\n try {\n const snapshot = await handle.stat({ bigint: true })\n if (!snapshot.isFile()) throw new Error(`${path}: expected a regular file`)\n const size = Number(snapshot.size)\n const file = identity(snapshot)\n const resumed =\n after !== undefined &&\n after.file === file &&\n Number.isSafeInteger(after.offset) &&\n after.offset >= 0 &&\n after.offset <= size &&\n after.boundary === (await boundary(handle, after.offset))\n let offset = resumed ? after.offset : 0\n const records: SpawnJournalReadRecord[] = []\n const read = async (start: number, end: number, firstOnly = false): Promise<void> => {\n offset = start\n for await (const record of readCommittedJsonLines<unknown>(path, {\n handle,\n start,\n end,\n strictUtf8: true,\n onCommit: (committed) => {\n offset = committed\n },\n })) {\n if (!isRecord(record) || typeof record.root !== 'string') {\n throw new Error(`${path}: spawn journal record has no root`)\n }\n if (record.kind === 'begin') {\n if (typeof record.at !== 'string') throw new Error(`${path}: begin record has no at`)\n records.push({ kind: 'begin', root: record.root, at: record.at })\n } else {\n for (const event of spawnJournalRecordEvents(record)) {\n if (\n !isRecord(event) ||\n typeof event.kind !== 'string' ||\n typeof event.id !== 'string'\n ) {\n throw new Error(`${path}: spawn journal record has no event identity`)\n }\n records.push({ kind: 'event', root: record.root, event })\n }\n }\n if (firstOnly) break\n }\n }\n let truncated = false\n if (!resumed && tailBytes !== undefined && size > tailBytes) {\n await read(0, size, true)\n const openingEnd = offset\n const tailStart = await lineBoundary(handle, size - tailBytes, size, -1)\n truncated = tailStart > openingEnd\n await read(Math.max(openingEnd, tailStart), size)\n // A long torn tail must not hide the latest committed activity before it.\n if (offset === tailStart && tailStart > openingEnd) {\n const previous = await lineBoundary(handle, tailStart - 1, size, -1)\n truncated = previous > openingEnd\n await read(Math.max(openingEnd, previous), tailStart)\n }\n } else {\n const end =\n resumed && tailBytes !== undefined && size - offset > tailBytes\n ? await lineBoundary(handle, offset + tailBytes, size, 1)\n : size\n await read(offset, end)\n }\n if (identity(await stat(path, { bigint: true })) !== file || (await handle.stat()).size < size)\n throw new Error(`${path}: journal changed while reading its committed prefix`)\n return {\n records,\n cursor: { file, offset, boundary: await boundary(handle, offset) },\n reset: after !== undefined && !resumed,\n truncated,\n remainingBytes: size - offset,\n }\n } finally {\n await handle.close()\n }\n}\n\nfunction identity(info: BigIntStats): string {\n return `${info.dev}:${info.ino}:${info.birthtimeNs}`\n}\n\nasync function bytes(handle: FileHandle, start: number, length: number): Promise<Buffer> {\n const buffer = Buffer.alloc(length)\n const { bytesRead } = await handle.read(buffer, 0, length, start)\n if (bytesRead !== length) throw new Error('spawn journal changed while reading its boundary')\n return buffer\n}\n\nasync function boundary(handle: FileHandle, offset: number): Promise<string> {\n const length = Math.min(offset, 128)\n return createHash('sha256')\n .update(await bytes(handle, 0, length))\n .update(await bytes(handle, offset - length, length))\n .digest('hex')\n}\n\n/** Include the record crossing a tail window or incremental page boundary. */\nasync function lineBoundary(\n handle: FileHandle,\n position: number,\n size: number,\n direction: -1 | 1,\n): Promise<number> {\n while (position > 0 && position < size) {\n const start = direction === -1 ? Math.max(0, position - 65_536) : position\n const end = direction === -1 ? position : Math.min(size, position + 65_536)\n const part = await bytes(handle, start, end - start)\n const newline = direction === -1 ? part.lastIndexOf(10) : part.indexOf(10)\n if (newline !== -1) return start + newline + 1\n position = direction === -1 ? start : end\n }\n return position\n}\n","import type { RuntimeFailureCause } from '../runtime/failure-cause'\nimport { type RunCancelRequest, readRunCancelRequest } from '../runtime/supervise/run-cancellation'\nimport { readRunParking } from '../runtime/supervise/run-parking'\nimport type { SupervisedResult } from '../runtime/supervise/types'\nimport { runDirectoryHolderIsLive } from './run-lock'\nimport { readFailureRecord, readSettleRecord } from './settle-record'\nimport { readSpawnJournal } from './spawn-journal/read'\n\n/** Runtime evidence only. Product-specific launch, checks and trace completeness belong to callers. */\nexport interface RunState {\n /** Absent when a recorded holder cannot be verified on this host. */\n readonly phase?: 'running' | 'parked' | 'cancelling' | 'settled' | 'down'\n /** A persisted typed root failure; legacy failure messages do not establish a code. */\n readonly failureCode?: RuntimeFailureCause['code']\n readonly settle?: SupervisedResult<unknown>\n readonly cancelRequest?: RunCancelRequest\n /** Absent when holder liveness is unknown. This is advisory, never ownership authorization. */\n readonly holderLive?: boolean\n /** Latest explicit Runtime timestamp observed, as ISO UTC; never a filesystem mtime. */\n readonly lastActivityAt?: string\n}\n\n/** Compose maintained record readers. Terminal settlement outranks cancellation and parking;\n * a request remains cancelling until settlement. An unheld run with evidence is down.\n * No Runtime evidence (including a missing or empty directory) returns undefined. Reads are\n * advisory snapshots across independently published files, not a transaction or a resume gate.\n */\nexport async function readRunState(runDir: string): Promise<RunState | undefined> {\n const [settle, failure, parking, holder, journal] = await Promise.all([\n readSettleRecord(runDir),\n readFailureRecord(runDir),\n readRunParking(runDir),\n runDirectoryHolderIsLive(runDir),\n readSpawnJournal(runDir, { tailBytes: 65_536 }),\n ])\n const cancelRequest = readRunCancelRequest(runDir)\n if (\n !settle &&\n !failure &&\n !parking &&\n !holder.holder &&\n !cancelRequest &&\n journal.records.length === 0\n ) {\n return undefined\n }\n const holderLive = holder.unknown ? undefined : holder.live\n const phase = settle\n ? 'settled'\n : cancelRequest\n ? 'cancelling'\n : parking\n ? 'parked'\n : holderLive === true\n ? 'running'\n : holderLive === false\n ? 'down'\n : undefined\n const root = settle?.tree.nodes?.find((node) => node.id === settle.tree.root)\n const failureCode =\n (settle?.kind === 'no-winner' && settle.reason === 'driver-failed'\n ? settle.error?.failure?.code\n : undefined) ?? root?.failure?.code\n let latest: number | undefined\n const observe = (at: string | number | undefined): void => {\n const timestamp = at === undefined ? undefined : new Date(at).getTime()\n if (timestamp !== undefined && Number.isFinite(timestamp)) {\n latest = latest === undefined ? timestamp : Math.max(latest, timestamp)\n }\n }\n observe(failure?.at)\n observe(parking?.since)\n observe(cancelRequest?.at)\n observe(holder.holder?.startedAt)\n for (const record of journal.records)\n observe(record.kind === 'begin' ? record.at : record.event.at)\n for (const node of settle?.tree.nodes ?? []) {\n observe(node.spawnedAt)\n observe(node.settledAt)\n }\n const lastActivityAt = latest === undefined ? undefined : new Date(latest).toISOString()\n return {\n ...(phase === undefined ? {} : { phase }),\n ...(failureCode === undefined ? {} : { failureCode }),\n ...(settle === undefined ? {} : { settle }),\n ...(cancelRequest === undefined ? {} : { cancelRequest }),\n ...(holderLive === undefined ? {} : { holderLive }),\n ...(lastActivityAt === undefined ? {} : { lastActivityAt }),\n }\n}\n"],"mappings":"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"}