@tangle-network/agent-runtime 0.353.3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/{activation-CFS31SWI.d.ts → activation-BuK2H7qx.d.ts} +2 -2
  2. package/dist/{activation-DTgC0RvI.js → activation-ZQWz04u8.js} +4 -4
  3. package/dist/{activation-DTgC0RvI.js.map → activation-ZQWz04u8.js.map} +1 -1
  4. package/dist/agent.d.ts +1 -1
  5. package/dist/agent.js +4 -3
  6. package/dist/agent.js.map +1 -1
  7. package/dist/{bundle-DeDkJbsr.js → bundle-PDYNXIEf.js} +2 -2
  8. package/dist/{bundle-DeDkJbsr.js.map → bundle-PDYNXIEf.js.map} +1 -1
  9. package/dist/candidate-execution/index.js +4 -4
  10. package/dist/{coordination-manager-D-J0Osf4.js → coordination-manager-BZLp9qpE.js} +8 -363
  11. package/dist/coordination-manager-BZLp9qpE.js.map +1 -0
  12. package/dist/coordination-map-routing-CUvwHAtv.js +362 -0
  13. package/dist/coordination-map-routing-CUvwHAtv.js.map +1 -0
  14. package/dist/{delegate-DfRQSGVk.js → delegate-C-MUU6ML.js} +2 -2
  15. package/dist/{delegate-DfRQSGVk.js.map → delegate-C-MUU6ML.js.map} +1 -1
  16. package/dist/durable-file-CksURHlH.js +1 -0
  17. package/dist/durable-file-DstVeHcj.js +228 -0
  18. package/dist/durable-file-DstVeHcj.js.map +1 -0
  19. package/dist/durable.d.ts +1 -1
  20. package/dist/durable.js +10 -8
  21. package/dist/durable.js.map +1 -1
  22. package/dist/{graph-BtGsqcOF.js → graph-CM-6CV_K.js} +6 -5
  23. package/dist/{graph-BtGsqcOF.js.map → graph-CM-6CV_K.js.map} +1 -1
  24. package/dist/graph-registry-72DlMnUl.js +585 -0
  25. package/dist/graph-registry-72DlMnUl.js.map +1 -0
  26. package/dist/graph-registry-CAX15LO6.js +1 -0
  27. package/dist/{improve-CL-Dsf63.js → improve-BuZ-I8DP.js} +9 -8
  28. package/dist/{improve-CL-Dsf63.js.map → improve-BuZ-I8DP.js.map} +1 -1
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +12 -11
  31. package/dist/index.js.map +1 -1
  32. package/dist/intelligence.d.ts +2 -2
  33. package/dist/intelligence.js +7 -7
  34. package/dist/{journal-store-BJPMUydp.js → journal-store-BpCOCDoO.js} +208 -635
  35. package/dist/journal-store-BpCOCDoO.js.map +1 -0
  36. package/dist/jsonl-file-BBP9Dz9w.js +429 -0
  37. package/dist/jsonl-file-BBP9Dz9w.js.map +1 -0
  38. package/dist/kernel.d.ts +1 -1
  39. package/dist/kernel.js +13 -12
  40. package/dist/{loop-runner-B1Xpgp2w.js → loop-runner-BWD6v1rd.js} +2 -2
  41. package/dist/{loop-runner-B1Xpgp2w.js.map → loop-runner-BWD6v1rd.js.map} +1 -1
  42. package/dist/{loop-runner-D9rcTNf2.d.ts → loop-runner-BpLTU_J4.d.ts} +2 -2
  43. package/dist/loop-runner-bin.d.ts +1 -1
  44. package/dist/loop-runner-bin.js +1 -1
  45. package/dist/mcp/bin.js +3 -3
  46. package/dist/mcp/index.d.ts +1 -1
  47. package/dist/mcp/index.js +5 -5
  48. package/dist/{prepare-XWwsOh8O.js → prepare-Bv6X2Wnx.js} +4 -4
  49. package/dist/{prepare-XWwsOh8O.js.map → prepare-Bv6X2Wnx.js.map} +1 -1
  50. package/dist/{profile-C01SQZKa.js → profile-Dir2A1_2.js} +2 -2
  51. package/dist/{profile-C01SQZKa.js.map → profile-Dir2A1_2.js.map} +1 -1
  52. package/dist/profiles.d.ts +1 -1
  53. package/dist/{provision-supervisor-CzCFHTxe.js → provision-supervisor-BWEeWag1.js} +8 -6
  54. package/dist/{provision-supervisor-CzCFHTxe.js.map → provision-supervisor-BWEeWag1.js.map} +1 -1
  55. package/dist/{run-cancellation-CoJRKvih.js → run-cancellation-Cpm-BIUu.js} +3 -2
  56. package/dist/{run-cancellation-CoJRKvih.js.map → run-cancellation-Cpm-BIUu.js.map} +1 -1
  57. package/dist/{runtime-DmdhgaTH.js → runtime-Cnr3213E.js} +18 -15
  58. package/dist/{runtime-DmdhgaTH.js.map → runtime-Cnr3213E.js.map} +1 -1
  59. package/dist/{sandbox-profile-compat-CXjozdWL.js → sandbox-profile-compat-DRiz9kME.js} +2 -2
  60. package/dist/{sandbox-profile-compat-CXjozdWL.js.map → sandbox-profile-compat-DRiz9kME.js.map} +1 -1
  61. package/dist/{search-executor-Djo4ssci.js → search-executor-__X1VKnU.js} +5 -5
  62. package/dist/{search-executor-Djo4ssci.js.map → search-executor-__X1VKnU.js.map} +1 -1
  63. package/dist/{server-DAanPY7z.js → server-CntFSTpW.js} +4 -4
  64. package/dist/{server-DAanPY7z.js.map → server-CntFSTpW.js.map} +1 -1
  65. package/dist/{structural-rollout-qqsbQW8M.js → structural-rollout-s8W19Mws.js} +5 -5
  66. package/dist/{structural-rollout-qqsbQW8M.js.map → structural-rollout-s8W19Mws.js.map} +1 -1
  67. package/dist/{supervise-DKwnVruI.js → supervise-Cl0F57m9.js} +13 -601
  68. package/dist/supervise-Cl0F57m9.js.map +1 -0
  69. package/dist/{supervision-discovery-CewADdEs.js → supervision-discovery-C3t0Xw-T.js} +5 -158
  70. package/dist/supervision-discovery-C3t0Xw-T.js.map +1 -0
  71. package/dist/{supervisor-C3JhrVVU.js → supervisor-L7u3pQ1j.js} +84 -140
  72. package/dist/supervisor-L7u3pQ1j.js.map +1 -0
  73. package/dist/task-queue-DbpZAp6C.js +49 -0
  74. package/dist/task-queue-DbpZAp6C.js.map +1 -0
  75. package/dist/task-queue.js +1 -49
  76. package/dist/testing.d.ts +1 -1
  77. package/dist/testing.js +13 -13
  78. package/dist/top-web-CkXPPXjI.js +1114 -0
  79. package/dist/top-web-CkXPPXjI.js.map +1 -0
  80. package/dist/tui/bin.js +4 -3
  81. package/dist/tui/bin.js.map +1 -1
  82. package/dist/tui/index.d.ts +22 -2
  83. package/dist/tui/index.js +3 -3
  84. package/dist/{types-DTHxh4X9.d.ts → types-BfnQq-_k.d.ts} +17 -21
  85. package/dist/{util-ebWMWzY0.js → util-BX20UJ9X.js} +2 -2
  86. package/dist/{util-ebWMWzY0.js.map → util-BX20UJ9X.js.map} +1 -1
  87. package/package.json +11 -11
  88. package/skills/agent-graphs/references/authoring.md +1 -0
  89. package/dist/coordination-manager-D-J0Osf4.js.map +0 -1
  90. package/dist/journal-store-BJPMUydp.js.map +0 -1
  91. package/dist/supervise-DKwnVruI.js.map +0 -1
  92. package/dist/supervision-discovery-CewADdEs.js.map +0 -1
  93. package/dist/supervisor-C3JhrVVU.js.map +0 -1
  94. package/dist/task-queue.js.map +0 -1
  95. package/dist/top-snapshot-DcV7VG4m.js +0 -520
  96. package/dist/top-snapshot-DcV7VG4m.js.map +0 -1
@@ -1,26 +1,16 @@
1
1
  import { m as ValidationError, r as BackendTransportError } from "./errors-AzSuGI23.js";
2
- import { D as assertResources, E as addResourceSpend, T as zeroTokenUsage, i as chargedTokens, k as withBudgetResources, l as fnv1a32Hex, m as promptCacheTokenClasses, o as cloneTokenUsage, r as addTokenUsage, u as hasCompleteCacheBreakdown } from "./util-ebWMWzY0.js";
2
+ import { D as assertResources, E as addResourceSpend, T as zeroTokenUsage, i as chargedTokens, k as withBudgetResources, l as fnv1a32Hex, m as promptCacheTokenClasses, o as cloneTokenUsage, r as addTokenUsage, u as hasCompleteCacheBreakdown } from "./util-BX20UJ9X.js";
3
+ import { d as detachedSnapshot, m as errMessage, n as isNoEntError, o as readCommittedJsonLines, p as causeChain, t as appendCommittedJsonLine } from "./jsonl-file-BBP9Dz9w.js";
4
+ import { n as assertNoSymlinkDescendant, o as captureRunWriteContext, r as publishExclusiveDurableFile } from "./durable-file-DstVeHcj.js";
5
+ import { a as withPursuitContext } from "./runtime-hooks-CsW2pfFk.js";
3
6
  import { AgentTurnResultSchema, CanonicalStreamEventSchema, RuntimeEventEnvelopeSchema } from "@tangle-network/agent-interface";
4
7
  import { isRecord } from "@tangle-network/agent-core";
5
8
  import { collectAgentFinalMessageText, collectAgentResponseText } from "@tangle-network/sandbox";
6
9
  import { z } from "zod";
7
- import { REDACTION_VERSION, redact } from "@tangle-network/agent-eval/traces";
8
- import { createHash, randomUUID } from "node:crypto";
9
- import { LedgerCanonicalizationError, canonicalString, jsonDocument, merkleLeafHash, merkleRoot, merkleSalt, tryAcquireAtomicFileLock } from "@tangle-network/agent-eval/ledger-core";
10
- import { closeSync, constants, existsSync, fchmodSync, fstatSync, fsyncSync, ftruncateSync, linkSync, lstatSync, openSync, readFileSync, readSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
11
- import { dirname, join, relative, resolve } from "node:path";
12
- import { AsyncLocalStorage } from "node:async_hooks";
13
- // \0rolldown/runtime.js
14
- var __defProp = Object.defineProperty;
15
- var __exportAll = (all, no_symbols) => {
16
- let target = {};
17
- for (var name in all) __defProp(target, name, {
18
- get: all[name],
19
- enumerable: true
20
- });
21
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
22
- return target;
23
- };
10
+ import { createHash } from "node:crypto";
11
+ import { LedgerCanonicalizationError, canonicalString, jsonDocument, merkleLeafHash, merkleRoot, merkleSalt } from "@tangle-network/agent-eval/ledger-core";
12
+ import { fstatSync, statSync } from "node:fs";
13
+ import { resolve } from "node:path";
24
14
  // src/record-value.ts
25
15
  function recordValue(value) {
26
16
  return isRecord(value) ? value : void 0;
@@ -1993,123 +1983,6 @@ function describeLeakedReservation(leak) {
1993
1983
  parts.push(`stage ${leak.stage}`);
1994
1984
  return parts.join(", ");
1995
1985
  }
1996
- // src/redact.ts
1997
- /**
1998
- * Redaction for values that may leave the Runtime process. The built-in
1999
- * redactor is agent-eval's redaction core (`@tangle-network/agent-eval/traces`,
2000
- * default profile): credentials found by field name or value shape are
2001
- * replaced whole, email, card, SSN and phone values in place, and token counts
2002
- * such as `inputTokens` or `max_tokens` are kept. A customer with
2003
- * domain-specific PII supplies their own `redact` hook.
2004
- *
2005
- * This is narrower than `src/sanitize.ts`, which drops fields of the runtime's
2006
- * *event envelope* unless the caller opts in: here the value is opaque customer
2007
- * payload, so the scrub is value-shaped, not schema-shaped.
2008
- *
2009
- * @experimental
2010
- */
2011
- /**
2012
- * The built-in redactor. Cycle-safe, depth-bounded and total: it never throws
2013
- * on customer input and never mutates it.
2014
- */
2015
- function defaultRedactor(value) {
2016
- return redact(value).value;
2017
- }
2018
- /** Stable identity input for saved work that depends on built-in redaction behavior. */
2019
- function defaultRedactorIdentityMaterial() {
2020
- return {
2021
- core: "@tangle-network/agent-eval/traces redact",
2022
- profile: "default",
2023
- version: REDACTION_VERSION
2024
- };
2025
- }
2026
- /**
2027
- * Resolve the redactor a client uses. A caller-supplied hook handles
2028
- * domain-specific values first, then the built-in scrubber still removes
2029
- * credentials and personal data. Returning `false` is the explicit opt-out for
2030
- * already-reviewed public values.
2031
- */
2032
- function resolveRedactor(redact) {
2033
- if (redact === false) return (value) => value;
2034
- if (!redact) return defaultRedactor;
2035
- return (value) => defaultRedactor(redact(value));
2036
- }
2037
- // src/runtime/supervise/error-message.ts
2038
- const maxCauseDepth = 4;
2039
- const maxMessageLength = 32768;
2040
- const maxPartLength = 2048;
2041
- /** A persisted failure must explain its wrappers without exporting credentials or unbounded data. */
2042
- function errorText(value) {
2043
- let text;
2044
- try {
2045
- text = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
2046
- } catch {
2047
- text = "[unprintable rejection]";
2048
- }
2049
- return String(defaultRedactor(text)).slice(0, maxMessageLength);
2050
- }
2051
- function errorProperty(error, field) {
2052
- try {
2053
- const value = error[field];
2054
- return value === void 0 ? void 0 : errorText(value).slice(0, field === "name" ? 256 : maxMessageLength);
2055
- } catch {
2056
- return `[unreadable error ${field}]`;
2057
- }
2058
- }
2059
- /** An HTTP status carried on a thrown error, read without trusting its getter. Provider SDKs put
2060
- * one on their own error classes, so this is how a refusal is recognised outside the transport
2061
- * taxonomy — for the persisted message here, and for the driver's retry verdict. */
2062
- function errorHttpStatus(error) {
2063
- try {
2064
- const status = Reflect.get(error, "status");
2065
- return typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? status : void 0;
2066
- } catch {
2067
- return;
2068
- }
2069
- }
2070
- /** Shared by child settlements, driver attempts, and the final no-winner result. */
2071
- function errMessage(error) {
2072
- const parts = [];
2073
- const seen = /* @__PURE__ */ new Set();
2074
- let current = error;
2075
- for (let depth = 0; depth <= maxCauseDepth; depth += 1) try {
2076
- if (!(current instanceof Error)) {
2077
- parts.push(errorText(current).slice(0, maxPartLength));
2078
- break;
2079
- }
2080
- if (seen.has(current)) {
2081
- parts.push("[circular]");
2082
- break;
2083
- }
2084
- seen.add(current);
2085
- const status = errorHttpStatus(current);
2086
- const message = (status === void 0 ? "" : `HTTP ${status}: `) + (errorProperty(current, "message") ?? "");
2087
- parts.push((depth === 0 ? message : `${errorProperty(current, "name")}: ${message}`).slice(0, maxPartLength));
2088
- current = current.cause;
2089
- if (current === void 0 || current === null) break;
2090
- if (depth === maxCauseDepth) parts.push("[cause chain truncated]");
2091
- } catch {
2092
- parts.push("[unreadable error cause]");
2093
- break;
2094
- }
2095
- return parts.join(": caused by ").slice(0, maxMessageLength);
2096
- }
2097
- /** The error and its causes (`cause`, and `errors` members), bounded and cycle-safe. */
2098
- function causeChain(error) {
2099
- const chain = [];
2100
- const queue = [error];
2101
- while (queue.length > 0 && chain.length < 12) {
2102
- const value = queue.shift();
2103
- if (typeof value !== "object" || value === null || chain.includes(value)) continue;
2104
- chain.push(value);
2105
- try {
2106
- const errors = Reflect.get(value, "errors");
2107
- if (Array.isArray(errors)) queue.push(...errors);
2108
- queue.push(Reflect.get(value, "cause"));
2109
- } catch {}
2110
- }
2111
- return chain;
2112
- }
2113
1986
  // src/runtime/supervise/credential-refusal.ts
2114
1987
  /**
2115
1988
  * The account owner refused an unsupported credential route before dispatch. This is the
@@ -2264,91 +2137,6 @@ function stableStringify(value) {
2264
2137
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
2265
2138
  return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
2266
2139
  }
2267
- // src/runtime/supervise/snapshot.ts
2268
- /** Deeply detach and freeze untrusted data at a runtime decision boundary. The clone prevents the
2269
- * caller from mutating it later; the freeze prevents downstream code from mutating the snapshot.
2270
- * Strings are immutable, so the snapshot shares the caller's strings instead of copying them. */
2271
- function detachedSnapshot(value, context) {
2272
- let clone;
2273
- try {
2274
- clone = structuredClone(value);
2275
- } catch (error) {
2276
- throw new ValidationError(`${context}: input must be structured-cloneable`, { cause: error });
2277
- }
2278
- return deepFreeze(shareSourceStrings(value, clone));
2279
- }
2280
- /** Detach a value from its caller and freeze it, without the `ValidationError` framing
2281
- * {@link detachedSnapshot} adds. For a boundary that has already validated its input. */
2282
- function detachedFrozen(value) {
2283
- return deepFreeze(shareSourceStrings(value, structuredClone(value)));
2284
- }
2285
- /**
2286
- * Point each string in a fresh structured clone at the equal string in its source.
2287
- *
2288
- * `structuredClone` copies every string, and a copied string buys no isolation: only objects and
2289
- * arrays can change through a shared reference. A spawned profile passes several boundaries, and
2290
- * each one kept its own copy: in a reproduction of Discovery's hosted coordinator, a child profile
2291
- * that mounts a 5.3 MB file packet was held four times per spawned child. A scope keeps every
2292
- * child it spawned, so the heap grew about 22 MB per spawn, and hosted coordinators died with V8
2293
- * heap OOM after 37 spawns.
2294
- *
2295
- * The walk pairs only own data properties of plain objects and arrays. Accessors, Maps, Sets and
2296
- * other cloned types keep the clone's values. A string is replaced only by a string equal to it,
2297
- * so the snapshot holds exactly the clone's value. The snapshot keeps the source's strings alive.
2298
- */
2299
- function shareSourceStrings(source, clone) {
2300
- const visited = /* @__PURE__ */ new Set();
2301
- const visit = (from, to) => {
2302
- if (from === null || to === null || typeof from !== "object" || typeof to !== "object") return;
2303
- const array = Array.isArray(to);
2304
- if (array !== Array.isArray(from)) return;
2305
- if (!array && Object.getPrototypeOf(to) !== Object.prototype) return;
2306
- if (visited.has(to)) return;
2307
- visited.add(to);
2308
- const target = to;
2309
- for (const key of Object.keys(target)) {
2310
- const own = Object.getOwnPropertyDescriptor(from, key);
2311
- if (own === void 0 || !("value" in own)) continue;
2312
- const cloned = target[key];
2313
- if (typeof cloned === "string") {
2314
- if (own.value === cloned) target[key] = own.value;
2315
- } else visit(own.value, cloned);
2316
- }
2317
- };
2318
- visit(source, clone);
2319
- return clone;
2320
- }
2321
- /**
2322
- * Replace each string in a freshly parsed JSON value with an equal string already in `pool`, and
2323
- * add the new ones. One recovery pass parses every interrupted child's profile from its own blob,
2324
- * so children that carry the same file packet would otherwise hold one copy each. The value is
2325
- * updated in place, so it must belong to the caller; frozen objects are left as they are.
2326
- */
2327
- function shareParsedStrings(value, pool) {
2328
- const visit = (node) => {
2329
- if (node === null || typeof node !== "object" || Object.isFrozen(node)) return;
2330
- if (!Array.isArray(node) && Object.getPrototypeOf(node) !== Object.prototype) return;
2331
- const target = node;
2332
- for (const key of Object.keys(target)) {
2333
- const item = target[key];
2334
- if (typeof item !== "string") {
2335
- visit(item);
2336
- continue;
2337
- }
2338
- const shared = pool.get(item);
2339
- if (shared === void 0) pool.set(item, item);
2340
- else target[key] = shared;
2341
- }
2342
- };
2343
- visit(value);
2344
- return value;
2345
- }
2346
- function deepFreeze(value, seen = /* @__PURE__ */ new Set()) {
2347
- if (value === null || typeof value !== "object" || ArrayBuffer.isView(value) || seen.has(value)) return value;
2348
- seen.add(value);
2349
- for (const child of Object.values(value)) deepFreeze(child, seen);
2350
- return Object.freeze(value);
2351
- }
2352
2140
  // src/runtime/supervise/trace-root.ts
2353
2141
  /**
2354
2142
  * Trace roots: one Merkle root over the events a turn archived, so a single event can be proven
@@ -3107,236 +2895,157 @@ function settlementFailureCause(down) {
3107
2895
  ...down.failure?.providerCode === void 0 ? {} : { providerCode: down.failure.providerCode }
3108
2896
  };
3109
2897
  }
3110
- // src/content-digest.ts
3111
- function sha256(value) {
3112
- return createHash("sha256").update(value).digest("hex");
3113
- }
3114
- // src/durable/run-fence.ts
3115
- const claims = new AsyncLocalStorage();
3116
- const publications = new AsyncLocalStorage();
3117
- /** Bind asynchronous work, including callbacks, to the generation that admitted it. @internal */
3118
- function runWithWriteClaim(claim, operation) {
3119
- return claims.run(claim, operation);
3120
- }
3121
- /** Capture a store's acquisition once, even when later called from an unrelated emitter. @internal */
3122
- function captureRunWriteContext() {
3123
- let claim = claims.getStore();
3124
- return (operation) => {
3125
- claim ??= claims.getStore();
3126
- return claims.run(claim, operation);
3127
- };
3128
- }
3129
- /** Serialize claim changes and file mutations under the same cross-process guard. @internal */
3130
- function mutateRunClaim(path, operation) {
3131
- const legacyGuard = `${path}.guard`;
3132
- if (existsSync(legacyGuard)) throw new Error(`supervisePursuit: cannot mutate ${path}; inspect ${legacyGuard} and remove it only after confirming no lock mutation is active`);
3133
- const deadline = performance.now() + 5e3;
3134
- const sleeper = new Int32Array(new SharedArrayBuffer(4));
3135
- for (;;) {
3136
- const acquisition = tryAcquireAtomicFileLock({ lockPath: `${path}.mutation` });
3137
- if (acquisition.acquired) try {
3138
- return operation();
3139
- } finally {
3140
- acquisition.lock.release();
3141
- }
3142
- if (performance.now() >= deadline) throw new Error(`run directory mutation is held: ${path}`);
3143
- Atomics.wait(sleeper, 0, 0, 10);
3144
- }
3145
- }
2898
+ // src/durable/observer-journal.ts
3146
2899
  /**
3147
- * Fence an actual file mutation, not an earlier advisory check. No await occurs between the
3148
- * ownership check and publication, and acquire/release use this same guard. Unclaimed standalone
3149
- * stores retain their existing single-writer contract; pursuit-owned work always carries a claim.
3150
- * @internal
3151
- */
3152
- function fenceRunWrite(operation) {
3153
- const claim = claims.getStore();
3154
- if (claim === void 0) return operation();
3155
- const publication = publications.getStore();
3156
- if (publication?.active && publication.claim.path === claim.path && publication.claim.generation === claim.generation) return operation();
3157
- return mutateRunClaim(claim.path, () => {
3158
- let holder;
3159
- try {
3160
- const fd = openSync(claim.path, "r");
3161
- try {
3162
- const stat = fstatSync(fd, { bigint: true });
3163
- if (stat.dev.toString() !== claim.lock.dev || stat.ino.toString() !== claim.lock.ino || stat.birthtimeNs.toString() !== claim.lock.birthtimeNs) throw new Error("run lock inode changed");
3164
- holder = JSON.parse(readFileSync(fd, "utf8"));
3165
- } finally {
3166
- closeSync(fd);
3167
- }
3168
- } catch (cause) {
3169
- throw new Error(`run directory generation ${claim.generation} lost its claim`, { cause });
3170
- }
3171
- if (holder?.generation !== claim.generation || holder.lock?.dev !== claim.lock.dev || holder.lock?.ino !== claim.lock.ino || holder.lock?.birthtimeNs !== claim.lock.birthtimeNs) throw new Error(`run directory generation ${claim.generation} lost its claim`);
3172
- const publication = {
3173
- claim,
3174
- active: true
3175
- };
3176
- try {
3177
- return publications.run(publication, operation);
3178
- } finally {
3179
- publication.active = false;
3180
- }
3181
- });
3182
- }
3183
- /** Persistent generation counter survives a clean release of supervise.lock. @internal */
3184
- function runGenerationPath(runDir) {
3185
- return resolve(runDir, "supervise.generation");
3186
- }
3187
- // src/runtime/supervise/durable-file.ts
3188
- /**
3189
- * Small filesystem durability primitives for Runtime-owned state.
3190
- *
3191
- * Callers must validate their path and parent directory before invoking these functions.
3192
- * The exclusive writer protects a no-clobber publication race; the atomic writer replaces one
3193
- * state file after its temporary contents reach stable storage.
2900
+ * Durable, append-only third-person history for one concrete Runtime execution.
2901
+ * It consumes Runtime's existing hook stream and does not participate in execution
2902
+ * decisions. A broken observer therefore cannot change what an agent is allowed to do.
3194
2903
  *
3195
- * @internal
3196
- */
3197
- var durable_file_exports = /* @__PURE__ */ __exportAll({
3198
- appendDurableFile: () => appendDurableFile,
3199
- assertNoSymlinkDescendant: () => assertNoSymlinkDescendant,
3200
- publishExclusiveDurableFile: () => publishExclusiveDurableFile,
3201
- syncDurableDirectory: () => syncDurableDirectory,
3202
- writeAtomicDurableFile: () => writeAtomicDurableFile
3203
- });
3204
- /**
3205
- * Publish a new file without replacing a concurrent winner. The published entry is a hard link
3206
- * of the temporary file, so a content function sees the published file's inode.
2904
+ * The write discipline deliberately matches `FileSpawnJournal`: serialized appends,
2905
+ * torn-tail recovery, short-write handling, and fsync before acknowledgement. One
2906
+ * execution owns one journal file; higher-level pursuit aggregation joins isolated
2907
+ * journals by `pursuitId` instead of making independent processes share a write head.
3207
2908
  */
3208
- function publishExclusiveDurableFile(filePath, content, options = {}) {
3209
- return fenceRunWrite(() => publishExclusiveDurableFileUnchecked(filePath, content, options));
3210
- }
3211
- function publishExclusiveDurableFileUnchecked(filePath, content, options = {}) {
3212
- const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
3213
- let operationFailed = false;
3214
- let operationError;
3215
- let published = false;
3216
- try {
3217
- writeExclusiveDurableFile(temporaryPath, content, options);
3218
- try {
3219
- linkSync(temporaryPath, filePath);
3220
- syncDurableDirectory(dirname(filePath));
3221
- published = true;
3222
- } catch (error) {
3223
- if (!isAlreadyExistsError(error)) throw error;
2909
+ var FileObserverJournal = class FileObserverJournal {
2910
+ path;
2911
+ pursuitId;
2912
+ writeContext = captureRunWriteContext();
2913
+ tail = Promise.resolve();
2914
+ initialized = false;
2915
+ sequence = 0;
2916
+ previousDigest;
2917
+ appendFailure;
2918
+ constructor(path, pursuitId) {
2919
+ const stableId = pursuitId.trim();
2920
+ if (stableId.length === 0) throw new TypeError("FileObserverJournal: pursuitId must be non-empty");
2921
+ this.path = resolve(path);
2922
+ this.pursuitId = stableId;
2923
+ }
2924
+ /** Open existing committed history by its recorded identity; never creates a file. */
2925
+ static async open(path) {
2926
+ for await (const record of readCommittedJsonLines(path, { allowMissing: true })) {
2927
+ if (typeof record?.pursuitId !== "string") throw new TypeError("observer journal: missing pursuit identity");
2928
+ const journal = new FileObserverJournal(path, record.pursuitId);
2929
+ verifyObserverRecords([record], journal.pursuitId);
2930
+ return journal;
3224
2931
  }
3225
- } catch (error) {
3226
- operationFailed = true;
3227
- operationError = error;
3228
2932
  }
3229
- let cleanupError;
3230
- try {
3231
- unlinkSync(temporaryPath);
3232
- } catch (error) {
3233
- if (!isNoEntryError(error)) cleanupError = error;
2933
+ hooks() {
2934
+ return withPursuitContext(this.pursuitId, {
2935
+ onEvent: (event) => this.appendEvent(event).then(() => void 0),
2936
+ onDecisionPoint: (point) => this.appendDecision(point).then(() => void 0)
2937
+ });
3234
2938
  }
3235
- if (operationFailed) throw operationError;
3236
- if (cleanupError !== void 0) throw cleanupError;
3237
- return published;
3238
- }
3239
- /** Fsync a directory after publishing a durable file entry. */
3240
- function syncDurableDirectory(directoryPath) {
3241
- const fd = openSync(directoryPath, "r");
3242
- try {
3243
- fsyncSync(fd);
3244
- } finally {
3245
- closeSync(fd);
2939
+ appendEvent(event) {
2940
+ return this.enqueue("event", event);
3246
2941
  }
3247
- }
3248
- /** Fsync one durable file after its contents reach stable storage. */
3249
- function syncDurableFile(filePath, exactMode) {
3250
- const fd = openSync(filePath, "r");
3251
- try {
3252
- if (exactMode !== void 0 && process.platform !== "win32") fchmodSync(fd, exactMode);
3253
- fsyncSync(fd);
3254
- } finally {
3255
- closeSync(fd);
2942
+ appendDecision(point) {
2943
+ return this.enqueue("decision", point);
3256
2944
  }
3257
- }
3258
- /** Append one record with O_APPEND, then fsync the file and containing directory. */
3259
- function appendDurableFile(filePath, content, options = {}) {
3260
- fenceRunWrite(() => appendDurableFileUnchecked(filePath, content, options));
3261
- }
3262
- function appendDurableFileUnchecked(filePath, content, options = {}) {
3263
- const flags = constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0);
3264
- const fd = openSync(filePath, flags, options.mode ?? 384);
3265
- try {
3266
- if (process.platform !== "win32") fchmodSync(fd, options.mode ?? 384);
3267
- const expectedBytes = Buffer.byteLength(content, "utf8");
3268
- const writtenBytes = writeSync(fd, content, void 0, "utf8");
3269
- if (writtenBytes !== expectedBytes) throw new Error(`durable append wrote ${writtenBytes} of ${expectedBytes} bytes to '${filePath}'`);
3270
- fsyncSync(fd);
3271
- } finally {
3272
- closeSync(fd);
3273
- }
3274
- syncDurableDirectory(dirname(filePath));
3275
- }
3276
- /** Reject a path that escapes its run directory or traverses a symbolic link. */
3277
- function assertNoSymlinkDescendant(root, target, label = "path") {
3278
- const base = resolve(root);
3279
- const exact = resolve(target);
3280
- const rel = relative(base, exact);
3281
- if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) throw new Error(`${label} path escapes its run directory`);
3282
- if (existsSync(base) && lstatSync(base).isSymbolicLink()) throw new Error(`${label} path contains a symbolic link: ${base}`);
3283
- let current = base;
3284
- for (const part of rel.split(/[\\/]/u).filter(Boolean)) {
3285
- current = join(current, part);
3286
- if (!existsSync(current)) continue;
3287
- if (lstatSync(current).isSymbolicLink()) throw new Error(`${label} path contains a symbolic link: ${current}`);
3288
- }
3289
- }
3290
- /** Write, fsync, atomically replace, and fsync one Runtime-owned state file.
3291
- * preserveMode retains ordinary access permissions despite the current umask; a new
3292
- * file keeps normal creation semantics. Callers still own path validation and write exclusion. */
3293
- function writeAtomicDurableFile(filePath, content, options = {}) {
3294
- fenceRunWrite(() => writeAtomicDurableFileUnchecked(filePath, content, options));
3295
- }
3296
- function writeAtomicDurableFileUnchecked(filePath, content, options = {}) {
3297
- const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
3298
- let operationFailed = false;
3299
- let operationError;
3300
- try {
3301
- let mode = options.mode;
3302
- let exactMode = false;
3303
- if (options.preserveMode) try {
3304
- mode = statSync(filePath).mode & 511;
3305
- exactMode = true;
3306
- } catch (error) {
3307
- if (!isNoEntryError(error)) throw error;
2945
+ async read() {
2946
+ await this.tail;
2947
+ this.assertComplete();
2948
+ const records = [];
2949
+ const verify = observerVerifier(this.pursuitId);
2950
+ for await (const record of this.readExistingUnsafe()) {
2951
+ verify(record);
2952
+ records.push(record);
3308
2953
  }
3309
- writeExclusiveDurableFile(temporaryPath, content, { mode }, exactMode);
3310
- renameSync(temporaryPath, filePath);
3311
- syncDurableDirectory(dirname(filePath));
3312
- } catch (error) {
3313
- operationFailed = true;
3314
- operationError = error;
2954
+ return Object.freeze(records);
2955
+ }
2956
+ async enqueue(kind, value) {
2957
+ if (value.pursuitId !== this.pursuitId) throw new Error(`FileObserverJournal: ${kind} pursuitId ${String(value.pursuitId)} does not match ${this.pursuitId}`);
2958
+ const snapshot = detachedSnapshot(value, "observer record");
2959
+ const operation = this.tail.then(() => this.writeContext(async () => {
2960
+ this.assertComplete();
2961
+ await this.initialize();
2962
+ const unsigned = {
2963
+ schemaVersion: 1,
2964
+ pursuitId: this.pursuitId,
2965
+ sequence: this.sequence + 1,
2966
+ kind,
2967
+ observedAt: Date.now(),
2968
+ ...this.previousDigest ? { previousDigest: this.previousDigest } : {},
2969
+ ...kind === "event" ? { event: snapshot } : { decision: snapshot }
2970
+ };
2971
+ const record = Object.freeze({
2972
+ ...unsigned,
2973
+ digest: observerRecordDigest(unsigned)
2974
+ });
2975
+ const fs = await import("node:fs/promises");
2976
+ const { dirname } = await import("node:path");
2977
+ await fs.mkdir(dirname(this.path), { recursive: true });
2978
+ await appendCommittedJsonLine(this.path, record);
2979
+ this.sequence = record.sequence;
2980
+ this.previousDigest = record.digest;
2981
+ return record;
2982
+ })).catch((error) => {
2983
+ this.appendFailure ??= toError(error);
2984
+ throw error;
2985
+ });
2986
+ this.tail = operation.then(() => void 0, () => void 0);
2987
+ return operation;
2988
+ }
2989
+ async initialize() {
2990
+ if (this.initialized) return;
2991
+ const verify = observerVerifier(this.pursuitId);
2992
+ let tail;
2993
+ for await (const record of this.readExistingUnsafe()) {
2994
+ verify(record);
2995
+ tail = record;
2996
+ }
2997
+ this.sequence = tail?.sequence ?? 0;
2998
+ this.previousDigest = tail?.digest;
2999
+ this.initialized = true;
3315
3000
  }
3316
- let cleanupError;
3317
- try {
3318
- unlinkSync(temporaryPath);
3319
- } catch (error) {
3320
- if (!isNoEntryError(error)) cleanupError = error;
3001
+ readExistingUnsafe() {
3002
+ return readCommittedJsonLines(this.path, { allowMissing: true });
3321
3003
  }
3322
- if (operationFailed) throw operationError;
3323
- if (cleanupError !== void 0) throw cleanupError;
3324
- }
3325
- function writeExclusiveDurableFile(filePath, content, options, exactMode = false) {
3326
- const fd = openSync(filePath, "wx", options.mode ?? 438);
3327
- try {
3328
- const text = typeof content === "string" ? content : content(fstatSync(fd, { bigint: true }));
3329
- writeFileSync(fd, text, "utf8");
3330
- } finally {
3331
- closeSync(fd);
3004
+ assertComplete() {
3005
+ if (!this.appendFailure) return;
3006
+ throw new Error("FileObserverJournal: a prior durable append failed; observer completeness is unknown", { cause: this.appendFailure });
3332
3007
  }
3333
- syncDurableFile(filePath, exactMode ? options.mode : void 0);
3008
+ };
3009
+ /** Verify identity, monotonic sequence, payload shape, and the complete digest chain. */
3010
+ function verifyObserverRecords(records, pursuitId) {
3011
+ const verify = observerVerifier(pursuitId);
3012
+ for (const record of records) verify(record);
3013
+ return Object.freeze([...records]);
3014
+ }
3015
+ /** The same full chain validation is used for replay, startup and in-memory evidence. */
3016
+ function observerVerifier(pursuitId) {
3017
+ let previousDigest;
3018
+ let expectedSequence = 1;
3019
+ return (record) => {
3020
+ if (record.schemaVersion !== 1) throw new Error("observer journal: unsupported schemaVersion");
3021
+ if (pursuitId !== void 0 && record.pursuitId !== pursuitId) throw new Error(`observer journal: pursuit identity mismatch at sequence ${record.sequence}`);
3022
+ if (record.sequence !== expectedSequence) throw new Error(`observer journal: non-contiguous sequence ${record.sequence}; expected ${expectedSequence}`);
3023
+ if (record.previousDigest !== previousDigest) throw new Error(`observer journal: digest-chain break at sequence ${record.sequence}`);
3024
+ for (const kind of ["event", "decision"]) {
3025
+ const payload = record[kind];
3026
+ if (record.kind === kind === (payload === void 0)) throw new Error(`observer journal: invalid ${kind} payload at sequence ${record.sequence}`);
3027
+ if (payload !== void 0 && payload.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested ${kind} pursuit mismatch at sequence ${record.sequence}`);
3028
+ }
3029
+ const { digest, ...unsigned } = record;
3030
+ if (digest !== observerRecordDigest(unsigned)) throw new Error(`observer journal: digest mismatch at sequence ${record.sequence}`);
3031
+ previousDigest = digest;
3032
+ expectedSequence += 1;
3033
+ };
3334
3034
  }
3335
- function isAlreadyExistsError(error) {
3336
- return error !== null && typeof error === "object" && "code" in error && error.code === "EEXIST";
3035
+ /** Compute the canonical SHA-256 digest for an unsigned observer record. */
3036
+ function observerRecordDigest(record) {
3037
+ return createHash("sha256").update(JSON.stringify(record)).digest("hex");
3337
3038
  }
3338
- function isNoEntryError(error) {
3339
- return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
3039
+ /** Build the canonical durable observer hook in one call. */
3040
+ function createFileObserverHooks(path, pursuitId) {
3041
+ const journal = new FileObserverJournal(path, pursuitId);
3042
+ return {
3043
+ journal,
3044
+ hooks: journal.hooks()
3045
+ };
3046
+ }
3047
+ function toError(error) {
3048
+ return error instanceof Error ? error : new Error(String(error));
3340
3049
  }
3341
3050
  // src/durable/spawn-journal/event-index.ts
3342
3051
  /**
@@ -3545,210 +3254,74 @@ function outsideCursorNamespace(ev) {
3545
3254
  function insideCursorNamespace(ev) {
3546
3255
  return !outsideKinds.has(ev.kind);
3547
3256
  }
3548
- // src/durable/jsonl-append.ts
3549
- /** Commit JSONL bytes and validate their descriptor under one generation fence. @internal */
3550
- function commitJsonLine(path, record, acknowledge) {
3551
- const encoded = JSON.stringify(record);
3552
- if (encoded === void 0) throw new TypeError("JSONL records must be JSON serializable");
3553
- return fenceRunWrite(() => {
3554
- const separator = prepareJsonlTail(path);
3555
- const fd = openSync(path, "a");
3556
- try {
3557
- const bytes = Buffer.from(`${separator ? "\n" : ""}${encoded}\n`);
3558
- let offset = 0;
3559
- while (offset < bytes.length) {
3560
- const written = writeSync(fd, bytes, offset, bytes.length - offset);
3561
- if (written <= 0) throw new Error(`append-only file write made no progress at byte ${offset}`);
3562
- offset += written;
3563
- }
3564
- fsyncSync(fd);
3565
- return acknowledge?.(fd);
3566
- } finally {
3567
- closeSync(fd);
3568
- }
3569
- });
3570
- }
3571
- /** Recover only an uncommitted tail. Caller holds the generation fence. @internal */
3572
- function prepareJsonlTail(path) {
3573
- let fd;
3574
- try {
3575
- fd = openSync(path, "r+");
3576
- } catch (error) {
3577
- if (error.code === "ENOENT") return false;
3578
- throw error;
3579
- }
3580
- try {
3581
- const size = fstatSync(fd).size;
3582
- if (size === 0) return false;
3583
- const last = Buffer.allocUnsafe(1);
3584
- readAt(fd, last, size - 1);
3585
- if (last[0] === 10) return false;
3586
- const chunks = [];
3587
- let end = size;
3588
- let tailStart = 0;
3589
- while (end > 0) {
3590
- const start = Math.max(0, end - 65536);
3591
- const chunk = Buffer.allocUnsafe(end - start);
3592
- readAt(fd, chunk, start);
3593
- const newline = chunk.lastIndexOf(10);
3594
- chunks.push(chunk.subarray(newline + 1));
3595
- if (newline >= 0) {
3596
- tailStart = start + newline + 1;
3597
- break;
3598
- }
3599
- end = start;
3600
- }
3601
- try {
3602
- JSON.parse(Buffer.concat(chunks.reverse()).toString("utf8"));
3603
- return true;
3604
- } catch (error) {
3605
- if (!(error instanceof SyntaxError)) throw error;
3606
- ftruncateSync(fd, tailStart);
3607
- fsyncSync(fd);
3608
- return false;
3609
- }
3610
- } finally {
3611
- closeSync(fd);
3612
- }
3613
- }
3614
- function readAt(fd, buffer, position) {
3615
- let offset = 0;
3616
- while (offset < buffer.length) {
3617
- const read = readSync(fd, buffer, offset, buffer.length - offset, position + offset);
3618
- if (read === 0) throw new Error("journal changed while preparing its append tail");
3619
- offset += read;
3620
- }
3621
- }
3622
- // src/durable/jsonl-file.ts
3623
- /** Read a fixed file prefix one record at a time, never allocating the whole journal as a string.
3624
- * A reader cannot chase concurrent appends forever. Missing files are empty only when requested;
3625
- * corruption, short reads, and other I/O errors remain failures. Closing the iterator closes the fd. */
3626
- async function* readCommittedJsonLines(path, options = {}) {
3627
- const fs = await import("node:fs/promises");
3628
- let handle;
3629
- try {
3630
- handle = await fs.open(path, "r");
3631
- } catch (error) {
3632
- if (options.allowMissing && isNoEntError(error)) return;
3633
- throw error;
3634
- }
3635
- try {
3636
- const { size } = await handle.stat();
3637
- if (size === 0) return;
3638
- const { StringDecoder } = await import("node:string_decoder");
3639
- const decoder = new StringDecoder("utf8");
3640
- const stream = handle.createReadStream({
3641
- autoClose: false,
3642
- end: size - 1
3257
+ // src/durable/spawn-journal/result-blob-store.ts
3258
+ /**
3259
+ * FS `ResultBlobStore`. One JSON file per artifact under `dir`, named by a
3260
+ * filesystem-safe encoding of the `outRef` (`sha256:<hex>` → `sha256-<hex>.json`).
3261
+ * `put` fsyncs so a crash between writes never loses an acknowledged blob.
3262
+ *
3263
+ * @stable
3264
+ */
3265
+ var FileResultBlobStore = class {
3266
+ dir;
3267
+ writeContext = captureRunWriteContext();
3268
+ constructor(dir) {
3269
+ this.dir = dir;
3270
+ }
3271
+ async put(outRef, artifact) {
3272
+ return this.writeContext(async () => {
3273
+ const { text } = encodeResultBlob(outRef, artifact);
3274
+ const filePath = this.blobPath(outRef);
3275
+ assertNoSymlinkDescendant(this.dir, filePath, "result blob");
3276
+ await (await import("node:fs/promises")).mkdir(this.dir, { recursive: true });
3277
+ assertNoSymlinkDescendant(this.dir, filePath, "result blob");
3278
+ if (!publishExclusiveDurableFile(filePath, text, { mode: 384 })) assertContentAddress(outRef, await this.get(outRef));
3643
3279
  });
3644
- const fragments = [];
3645
- let lineNumber = 1;
3280
+ }
3281
+ async get(outRef) {
3282
+ const filePath = this.blobPath(outRef);
3283
+ assertNoSymlinkDescendant(this.dir, filePath, "result blob");
3284
+ const fs = await import("node:fs/promises");
3285
+ const { constants } = await import("node:fs");
3286
+ let text;
3646
3287
  try {
3647
- for await (const chunk of stream) {
3648
- options.onBytes?.(chunk);
3649
- const text = decoder.write(chunk);
3650
- let start = 0;
3651
- let end = text.indexOf("\n", start);
3652
- while (end !== -1) {
3653
- fragments.push(text.slice(start, end));
3654
- const record = parseRecord(fragments.join(""), path, lineNumber++, true, options.allowWhitespaceLines);
3655
- fragments.length = 0;
3656
- if (record !== void 0) yield record.value;
3657
- start = end + 1;
3658
- end = text.indexOf("\n", start);
3659
- }
3660
- if (start < text.length) fragments.push(text.slice(start));
3288
+ const handle = await fs.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
3289
+ try {
3290
+ if (!(await handle.stat()).isFile()) throw new Error("result blob must be a regular file");
3291
+ text = await handle.readFile("utf8");
3292
+ } finally {
3293
+ await handle.close();
3661
3294
  }
3662
- if (stream.bytesRead !== size) throw new Error(`${path}: journal changed while reading its committed prefix`);
3663
- fragments.push(decoder.end());
3664
- const record = parseRecord(fragments.join(""), path, lineNumber, false, options.allowWhitespaceLines);
3665
- if (record !== void 0) yield record.value;
3666
- } finally {
3667
- stream.destroy();
3295
+ } catch (err) {
3296
+ if (isNoEntError(err)) return void 0;
3297
+ throw err;
3668
3298
  }
3669
- } finally {
3670
- await handle.close();
3299
+ const artifact = JSON.parse(text);
3300
+ assertContentAddress(outRef, artifact);
3301
+ return artifact;
3671
3302
  }
3672
- }
3673
- /** An invalid unterminated tail may be a torn write; other parse failures are not evidence of one. */
3674
- function parseRecord(line, source, lineNumber, terminated, allowWhitespaceLines = false) {
3675
- if (line.length === 0 || allowWhitespaceLines && line.trim().length === 0) return void 0;
3676
- try {
3677
- return { value: JSON.parse(line) };
3678
- } catch (cause) {
3679
- if (!(cause instanceof SyntaxError)) throw cause;
3680
- if (!terminated) return void 0;
3681
- throw new Error(`${source}: malformed JSONL record at line ${lineNumber}`, { cause });
3682
- }
3683
- }
3684
- async function appendCommittedJsonLine(path, record, acknowledge) {
3685
- return commitJsonLine(path, record, acknowledge);
3686
- }
3687
- /** Serialize JSONL file updates across processes and aliases using Eval's existing file lock.
3688
- * The caller owns validation and the record format. The lock path names the canonical file,
3689
- * including its first write, so two directory aliases cannot acquire different write heads.
3690
- * @internal
3691
- */
3692
- async function withJsonlFileLock(path, operation) {
3693
- const fs = await import("node:fs/promises");
3694
- const { dirname } = await import("node:path");
3695
- const { tryAcquireAtomicFileLock } = await import("@tangle-network/agent-eval/ledger-core");
3696
- await fs.mkdir(dirname(path), { recursive: true });
3697
- await (await fs.open(path, "a")).close();
3698
- const canonicalPath = await fs.realpath(path);
3699
- const deadline = Date.now() + 5e3;
3700
- for (;;) {
3701
- const acquisition = tryAcquireAtomicFileLock({ lockPath: `${canonicalPath}.lock` });
3702
- if (!acquisition.acquired) {
3703
- if (Date.now() >= deadline) throw new Error(`journal file lock unavailable after 5000ms: ${canonicalPath}`);
3704
- await new Promise((resolve) => setTimeout(resolve, 25));
3705
- continue;
3706
- }
3707
- try {
3708
- return await operation(canonicalPath);
3709
- } finally {
3710
- acquisition.lock.release();
3711
- }
3303
+ blobPath(outRef) {
3304
+ if (!/^sha256:[0-9a-f]{64}$/.test(outRef)) throw new Error("invalid result blob content address");
3305
+ return `${this.dir}/${outRef.replace(/:/g, "-")}.json`;
3712
3306
  }
3307
+ };
3308
+ /** @internal shared with the SQL-backed store. */
3309
+ function encodeResultBlob(outRef, artifact) {
3310
+ const detached = detachedSnapshot(artifact, "result blob");
3311
+ assertContentAddress(outRef, detached);
3312
+ const text = JSON.stringify(detached);
3313
+ if (text === void 0) throw new Error("result blob must contain JSON");
3314
+ const snapshot = JSON.parse(text);
3315
+ assertContentAddress(outRef, snapshot);
3316
+ return {
3317
+ snapshot,
3318
+ text
3319
+ };
3713
3320
  }
3714
- /** Read one keyed registry snapshot without an extra whole-file text allocation.
3715
- * Historical registry files allow whitespace-only separators; append journals remain strict.
3716
- * @internal
3717
- */
3718
- async function readCommittedJsonFile(path) {
3719
- const records = [];
3720
- for await (const record of readCommittedJsonLines(path, {
3721
- allowMissing: true,
3722
- allowWhitespaceLines: true
3723
- })) records.push(record);
3724
- return records;
3725
- }
3726
- /** Replace one keyed row and publish one durable snapshot. The caller holds the file lock.
3727
- * Keep unrelated rows in order and put the replacement last. No revision archive is retained.
3728
- * @internal
3729
- */
3730
- async function upsertCommittedJsonLine(path, record, key) {
3731
- const encoded = JSON.stringify(record);
3732
- if (encoded === void 0) throw new Error("JSONL records must be JSON serializable");
3733
- const snapshot = JSON.parse(encoded);
3734
- const id = key(snapshot);
3735
- const updated = [];
3736
- for await (const prior of readCommittedJsonLines(path, {
3737
- allowMissing: true,
3738
- allowWhitespaceLines: true
3739
- })) if (key(prior) !== id) updated.push(prior);
3740
- updated.push(snapshot);
3741
- const { writeAtomicDurableFile } = await Promise.resolve().then(() => durable_file_exports);
3742
- writeAtomicDurableFile(path, `${updated.map((row) => JSON.stringify(row)).join("\n")}\n`, { preserveMode: true });
3743
- }
3744
- /** Recover a torn tail under the same generation fence as durable publications. @internal */
3745
- async function prepareJsonlAppend(path) {
3746
- return fenceRunWrite(() => prepareJsonlTail(path));
3747
- }
3748
- /** True for a filesystem error meaning "the file is not there yet" — the one condition an
3749
- * append-only reader treats as an empty log rather than a fault. */
3750
- function isNoEntError(error) {
3751
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3321
+ /** @internal shared with the SQL-backed store. */
3322
+ function assertContentAddress(outRef, artifact) {
3323
+ const expected = contentAddress(artifact);
3324
+ if (outRef !== expected) throw new Error(`blob outRef '${outRef}' does not match the artifact content hash '${expected}'; a content-addressed store refuses a mismatched ref (breaks the replay invariant)`);
3752
3325
  }
3753
3326
  // src/durable/spawn-journal/record-events.ts
3754
3327
  /** Decode the file journal's historical single-event and atomic-group envelopes. @internal */
@@ -3920,6 +3493,6 @@ var FileSpawnJournal = class {
3920
3493
  function journalFileStamp(stat) {
3921
3494
  return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
3922
3495
  }
3923
- export { traceRootWriter as $, runtimeProviderCode as A, sandboxLeafOutputFromEvents as At, retainedExecutorContext as B, createSandboxUsageLedger as Bt, runGenerationPath as C, spendFromUsageEvents as Ct, runtimeFailureCause as D, createSandboxToolPartState as Dt, reportedFailureCause as E, assertValidSpend as Et, executorEvidenceWriter as F, extractTransportEventIdentity as Ft, cappedDoublingMs as G, sandboxTerminalUsageField as Gt, workspaceCaptureWriter as H, isSandboxTerminalEvent as Ht, isProvisioningRefusal as I, parseCanonicalTransportEvent as It, unavailableResumeAt as J, parseCodexUsageRecord as Jt, jitteredMs as K, sumSandboxUsage as Kt, leafContinuationExecutionId as L, assertSandboxServedModel as Lt, BUDGET_CONTINUATION_TASK as M, sandboxProgressEvents as Mt, DISPATCH_NOT_ADMITTED_CODE as N, terminalTextFromSandboxEvent as Nt, runtimeFailureCauseSchema as O, mapSandboxEvent as Ot, RetainedExecutionPendingError as P, canonicalStreamEventFromSandboxEvent as Pt, TRACE_SUMMARY_SCHEMA as Q, prepareRetainedExecutor as R, canonicalSandboxUsageMode as Rt, mutateRunClaim as S, newUsageTotals as St, sha256 as T, assertValidBudget as Tt, UNAVAILABLE_CODES as U, notifySandboxEventObserver as Ut, retainedExecutorSeamKey as V, extractLlmCallEvent as Vt, UNAVAILABLE_STATUSES as W, sandboxEventServedBackend as Wt, unavailableSignalOfFailure as X, __exportAll as Xt, unavailableSignalInText as Y, recordValue as Yt, TRACE_ROOT_SCHEMA as Z, publishExclusiveDurableFile as _, defaultRedactorIdentityMaterial as _t, isNoEntError as a, executorFailure as at, captureRunWriteContext as b, createBudgetPool as bt, readCommittedJsonLines as c, hashJson as ct, SpawnEventIndex as d, causeChain as dt, detachedFrozen as et, closesCursorSlot as f, errMessage as ft, assertNoSymlinkDescendant as g, defaultRedactor as gt, appendDurableFile as h, errorText as ht, appendCommittedJsonLine as i, contentRef as it, settlementFailureCause as j, sandboxOutputMarker as jt, runtimeFailureRemediationSchema as k, mapSandboxToolEvent as kt, upsertCommittedJsonLine as l, credentialCommandExit as lt, outsideCursorNamespace as m, errorProperty as mt, appendSpawnEvents as n, shareParsedStrings as nt, prepareJsonlAppend as o, executorFailureInfra as ot, insideCursorNamespace as p, errorHttpStatus as pt, unavailablePauseMs as q, decodeHarnessUsage as qt, spawnJournalRecordEvents as r, contentAddress as rt, readCommittedJsonFile as s, executorFailureReason as st, FileSpawnJournal as t, detachedSnapshot as tt, withJsonlFileLock as u, isCredentialRefusal as ut, syncDurableDirectory as v, resolveRedactor as vt, runWithWriteClaim as w, spendFromUsageTotals as wt, fenceRunWrite as x, meterUsageEvent as xt, writeAtomicDurableFile as y, BudgetReconcileFault as yt, registerRetainedExecutorPreparation as z, createSandboxTurnEventCollector as zt };
3496
+ export { newUsageTotals as $, workspaceCaptureWriter as A, TRACE_SUMMARY_SCHEMA as B, executorEvidenceWriter as C, sumSandboxUsage as Ct, registerRetainedExecutorPreparation as D, prepareRetainedExecutor as E, recordValue as Et, unavailablePauseMs as F, executorFailureInfra as G, contentAddress as H, unavailableResumeAt as I, credentialCommandExit as J, executorFailureReason as K, unavailableSignalInText as L, UNAVAILABLE_STATUSES as M, cappedDoublingMs as N, retainedExecutorContext as O, jitteredMs as P, meterUsageEvent as Q, unavailableSignalOfFailure as R, RetainedExecutionPendingError as S, sandboxTerminalUsageField as St, leafContinuationExecutionId as T, parseCodexUsageRecord as Tt, contentRef as U, traceRootWriter as V, executorFailure as W, BudgetReconcileFault as X, isCredentialRefusal as Y, createBudgetPool as Z, runtimeFailureRemediationSchema as _, createSandboxUsageLedger as _t, encodeResultBlob as a, mapSandboxEvent as at, BUDGET_CONTINUATION_TASK as b, notifySandboxEventObserver as bt, insideCursorNamespace as c, sandboxOutputMarker as ct, createFileObserverHooks as d, canonicalStreamEventFromSandboxEvent as dt, spendFromUsageEvents as et, observerRecordDigest as f, extractTransportEventIdentity as ft, runtimeFailureCauseSchema as g, createSandboxTurnEventCollector as gt, runtimeFailureCause as h, canonicalSandboxUsageMode as ht, FileResultBlobStore as i, createSandboxToolPartState as it, UNAVAILABLE_CODES as j, retainedExecutorSeamKey as k, outsideCursorNamespace as l, sandboxProgressEvents as lt, reportedFailureCause as m, assertSandboxServedModel as mt, appendSpawnEvents as n, assertValidBudget as nt, SpawnEventIndex as o, mapSandboxToolEvent as ot, verifyObserverRecords as p, parseCanonicalTransportEvent as pt, hashJson as q, spawnJournalRecordEvents as r, assertValidSpend as rt, closesCursorSlot as s, sandboxLeafOutputFromEvents as st, FileSpawnJournal as t, spendFromUsageTotals as tt, FileObserverJournal as u, terminalTextFromSandboxEvent as ut, runtimeProviderCode as v, extractLlmCallEvent as vt, isProvisioningRefusal as w, decodeHarnessUsage as wt, DISPATCH_NOT_ADMITTED_CODE as x, sandboxEventServedBackend as xt, settlementFailureCause as y, isSandboxTerminalEvent as yt, TRACE_ROOT_SCHEMA as z };
3924
3497
 
3925
- //# sourceMappingURL=journal-store-BJPMUydp.js.map
3498
+ //# sourceMappingURL=journal-store-BpCOCDoO.js.map