@tangle-network/agent-runtime 0.342.2 → 0.343.2
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.
- package/dist/{activation-DOH7Fnyp.js → activation-BMjalBNv.js} +4 -4
- package/dist/{activation-DOH7Fnyp.js.map → activation-BMjalBNv.js.map} +1 -1
- package/dist/{activation-CXqtijue.d.ts → activation-NJiV0MHb.d.ts} +2 -2
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -3
- package/dist/{bundle-Cwpit6Tg.js → bundle-DKQtOO9d.js} +2 -2
- package/dist/{bundle-Cwpit6Tg.js.map → bundle-DKQtOO9d.js.map} +1 -1
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{coordination-manager-HQE0bIav.js → coordination-manager-DIC19cgD.js} +22 -314
- package/dist/coordination-manager-DIC19cgD.js.map +1 -0
- package/dist/{delegate-DmShSdjy.js → delegate-BWXq1BeR.js} +2 -2
- package/dist/{delegate-DmShSdjy.js.map → delegate-BWXq1BeR.js.map} +1 -1
- package/dist/{durable-file-gNI9ILAn.js → durable-file-CA0kr1Pi.js} +8 -3
- package/dist/durable-file-CA0kr1Pi.js.map +1 -0
- package/dist/durable.d.ts +2 -2
- package/dist/durable.js +153 -16
- package/dist/durable.js.map +1 -1
- package/dist/{graph-BCghJFlS.js → graph-B89lApkU.js} +4 -4
- package/dist/{graph-BCghJFlS.js.map → graph-B89lApkU.js.map} +1 -1
- package/dist/{improve-Dga6OYt-.js → improve-CII-AhGN.js} +7 -8
- package/dist/{improve-Dga6OYt-.js.map → improve-CII-AhGN.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -9
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +6 -6
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +10 -11
- package/dist/{loop-runner-CW87Vqoj.js → loop-runner-Bbk9It72.js} +2 -2
- package/dist/{loop-runner-CW87Vqoj.js.map → loop-runner-Bbk9It72.js.map} +1 -1
- package/dist/{loop-runner-CuhbgVGo.d.ts → loop-runner-D56Rpd2z.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +6 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/platform.js +1 -1
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-C-cWCyt3.js → prepare-TEEHSJwq.js} +3 -3
- package/dist/{prepare-C-cWCyt3.js.map → prepare-TEEHSJwq.js.map} +1 -1
- package/dist/{profile-CbMw5hAn.js → profile-6OLpbjvV.js} +2 -2
- package/dist/{profile-CbMw5hAn.js.map → profile-6OLpbjvV.js.map} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -1
- package/dist/profiles.js.map +1 -1
- package/dist/{provision-supervisor-BWR_TJFg.js → provision-supervisor-it-VCGyZ.js} +6 -6
- package/dist/{provision-supervisor-BWR_TJFg.js.map → provision-supervisor-it-VCGyZ.js.map} +1 -1
- package/dist/{run-cancellation-C9oL62sW.js → run-cancellation-DSpwvFmQ.js} +8 -4
- package/dist/{run-cancellation-C9oL62sW.js.map → run-cancellation-DSpwvFmQ.js.map} +1 -1
- package/dist/{runtime-CuRTZFe6.js → runtime-fKVK1rVb.js} +15 -17
- package/dist/{runtime-CuRTZFe6.js.map → runtime-fKVK1rVb.js.map} +1 -1
- package/dist/{sandbox-profile-compat-FN2kab2-.js → sandbox-profile-compat-_fEtHPCj.js} +2 -2
- package/dist/sandbox-profile-compat-_fEtHPCj.js.map +1 -0
- package/dist/{search-executor-0-SjHAao.js → search-executor-Ymr-tAKJ.js} +3 -3
- package/dist/{search-executor-0-SjHAao.js.map → search-executor-Ymr-tAKJ.js.map} +1 -1
- package/dist/{server-DTUK2wW1.js → server-BngfDubR.js} +4 -4
- package/dist/{server-DTUK2wW1.js.map → server-BngfDubR.js.map} +1 -1
- package/dist/{structural-rollout-kfaZgAkd.js → structural-rollout-BmKAIWWR.js} +3 -3
- package/dist/{structural-rollout-kfaZgAkd.js.map → structural-rollout-BmKAIWWR.js.map} +1 -1
- package/dist/{supervise-C_GAgiJU.js → supervise-DyCUu8Io.js} +415 -107
- package/dist/supervise-DyCUu8Io.js.map +1 -0
- package/dist/{supervisor-C_03z693.js → supervisor-gDn4C1o5.js} +627 -27
- package/dist/supervisor-gDn4C1o5.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +13 -13
- package/dist/{top-snapshot-CbZE1JsU.js → top-snapshot-OjHdhai1.js} +2 -2
- package/dist/{top-snapshot-CbZE1JsU.js.map → top-snapshot-OjHdhai1.js.map} +1 -1
- package/dist/tui/bin.js +5 -3
- package/dist/tui/bin.js.map +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +2 -2
- package/dist/{types-Cgs89PQd.d.ts → types-D0ZRqIJh.d.ts} +131 -17
- package/package.json +6 -6
- package/dist/content-digest-sBTLujlK.js +0 -9
- package/dist/content-digest-sBTLujlK.js.map +0 -1
- package/dist/coordination-manager-HQE0bIav.js.map +0 -1
- package/dist/durable-file-gNI9ILAn.js.map +0 -1
- package/dist/graph-registry-BnO64cn8.js +0 -197
- package/dist/graph-registry-BnO64cn8.js.map +0 -1
- package/dist/jsonl-file-C9Kde_0Z.js +0 -206
- package/dist/jsonl-file-C9Kde_0Z.js.map +0 -1
- package/dist/record-value-B6bwJxLa.js +0 -12
- package/dist/record-value-B6bwJxLa.js.map +0 -1
- package/dist/sandbox-profile-compat-FN2kab2-.js.map +0 -1
- package/dist/supervise-C_GAgiJU.js.map +0 -1
- package/dist/supervisor-C_03z693.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"durable-file-gNI9ILAn.js","names":["fsConstants"],"sources":["../src/runtime/supervise/durable-file.ts"],"sourcesContent":["/**\n * Small filesystem durability primitives for Runtime-owned state.\n *\n * Callers must validate their path and parent directory before invoking these functions.\n * The exclusive writer protects a no-clobber publication race; the atomic writer replaces one\n * state file after its temporary contents reach stable storage.\n *\n * @internal\n */\n\nimport { randomUUID } from 'node:crypto'\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n constants as fsConstants,\n fsyncSync,\n linkSync,\n lstatSync,\n openSync,\n renameSync,\n statSync,\n unlinkSync,\n writeFileSync,\n writeSync,\n} from 'node:fs'\nimport { dirname, join, relative, resolve } from 'node:path'\n\ninterface DurableFileOptions {\n readonly mode?: number\n}\n\n/** Publish a new file without replacing a concurrent winner. */\nexport function publishExclusiveDurableFile(\n filePath: string,\n content: string,\n options: DurableFileOptions = {},\n): boolean {\n const temporaryPath = `${filePath}.${randomUUID()}.tmp`\n let operationFailed = false\n let operationError: unknown\n let published = false\n try {\n writeExclusiveDurableFile(temporaryPath, content, options)\n try {\n linkSync(temporaryPath, filePath)\n syncDurableDirectory(dirname(filePath))\n published = true\n } catch (error) {\n if (!isAlreadyExistsError(error)) throw error\n }\n } catch (error) {\n operationFailed = true\n operationError = error\n }\n let cleanupError: unknown\n try {\n unlinkSync(temporaryPath)\n } catch (error) {\n if (!isNoEntryError(error)) cleanupError = error\n }\n if (operationFailed) throw operationError\n if (cleanupError !== undefined) throw cleanupError\n return published\n}\n\n/** Fsync a directory after publishing a durable file entry. */\nexport function syncDurableDirectory(directoryPath: string): void {\n const fd = openSync(directoryPath, 'r')\n try {\n fsyncSync(fd)\n } finally {\n closeSync(fd)\n }\n}\n\n/** Fsync one durable file after its contents reach stable storage. */\nfunction syncDurableFile(filePath: string, exactMode?: number): void {\n const fd = openSync(filePath, 'r')\n try {\n if (exactMode !== undefined && process.platform !== 'win32') fchmodSync(fd, exactMode)\n fsyncSync(fd)\n } finally {\n closeSync(fd)\n }\n}\n\n/** Append one record with O_APPEND, then fsync the file and containing directory. */\nexport function appendDurableFile(\n filePath: string,\n content: string,\n options: DurableFileOptions = {},\n): void {\n const flags =\n fsConstants.O_APPEND |\n fsConstants.O_CREAT |\n fsConstants.O_WRONLY |\n (typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0)\n const fd = openSync(filePath, flags, options.mode ?? 0o600)\n try {\n if (process.platform !== 'win32') fchmodSync(fd, options.mode ?? 0o600)\n const expectedBytes = Buffer.byteLength(content, 'utf8')\n const writtenBytes = writeSync(fd, content, undefined, 'utf8')\n if (writtenBytes !== expectedBytes) {\n throw new Error(\n `durable append wrote ${writtenBytes} of ${expectedBytes} bytes to '${filePath}'`,\n )\n }\n fsyncSync(fd)\n } finally {\n closeSync(fd)\n }\n syncDurableDirectory(dirname(filePath))\n}\n\n/** Reject a path that escapes its run directory or traverses a symbolic link. */\nexport function assertNoSymlinkDescendant(root: string, target: string, label = 'path'): void {\n const base = resolve(root)\n const exact = resolve(target)\n const rel = relative(base, exact)\n if (rel === '..' || rel.startsWith(`..${process.platform === 'win32' ? '\\\\' : '/'}`)) {\n throw new Error(`${label} path escapes its run directory`)\n }\n if (existsSync(base) && lstatSync(base).isSymbolicLink()) {\n throw new Error(`${label} path contains a symbolic link: ${base}`)\n }\n let current = base\n for (const part of rel.split(/[\\\\/]/u).filter(Boolean)) {\n current = join(current, part)\n if (!existsSync(current)) continue\n if (lstatSync(current).isSymbolicLink()) {\n throw new Error(`${label} path contains a symbolic link: ${current}`)\n }\n }\n}\n\n/** Write, fsync, atomically replace, and fsync one Runtime-owned state file.\n * preserveMode retains ordinary access permissions despite the current umask; a new\n * file keeps normal creation semantics. Callers still own path validation and write exclusion. */\nexport function writeAtomicDurableFile(\n filePath: string,\n content: string,\n options: DurableFileOptions & { readonly preserveMode?: boolean } = {},\n): void {\n const temporaryPath = `${filePath}.${randomUUID()}.tmp`\n let operationFailed = false\n let operationError: unknown\n try {\n let mode = options.mode\n let exactMode = false\n if (options.preserveMode) {\n try {\n mode = statSync(filePath).mode & 0o777\n exactMode = true\n } catch (error) {\n if (!isNoEntryError(error)) throw error\n }\n }\n writeExclusiveDurableFile(temporaryPath, content, { mode }, exactMode)\n renameSync(temporaryPath, filePath)\n syncDurableDirectory(dirname(filePath))\n } catch (error) {\n operationFailed = true\n operationError = error\n }\n let cleanupError: unknown\n try {\n unlinkSync(temporaryPath)\n } catch (error) {\n if (!isNoEntryError(error)) cleanupError = error\n // A successful rename removes the temporary directory entry.\n }\n if (operationFailed) throw operationError\n if (cleanupError !== undefined) throw cleanupError\n}\n\nfunction writeExclusiveDurableFile(\n filePath: string,\n content: string,\n options: DurableFileOptions,\n exactMode = false,\n): void {\n if (options.mode === undefined) {\n writeFileSync(filePath, content, { encoding: 'utf8', flag: 'wx' })\n } else {\n writeFileSync(filePath, content, { encoding: 'utf8', flag: 'wx', mode: options.mode })\n }\n syncDurableFile(filePath, exactMode ? options.mode : undefined)\n}\n\nfunction isAlreadyExistsError(error: unknown): boolean {\n return (\n error !== null &&\n typeof error === 'object' &&\n 'code' in error &&\n (error as { code?: unknown }).code === 'EEXIST'\n )\n}\n\nfunction isNoEntryError(error: unknown): boolean {\n return (\n error !== null &&\n typeof error === 'object' &&\n 'code' in error &&\n (error as { code?: unknown }).code === 'ENOENT'\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { o as writeAtomicDurableFile, s as __exportAll } from "./durable-file-gNI9ILAn.js";
|
|
2
|
-
import { m as ValidationError } from "./errors-DodWX-cb.js";
|
|
3
|
-
import { t as sha256 } from "./content-digest-sBTLujlK.js";
|
|
4
|
-
import { i as readCommittedJsonFile, o as upsertCommittedJsonLine, s as withJsonlFileLock } from "./jsonl-file-C9Kde_0Z.js";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { mkdir, readFile, readdir } from "node:fs/promises";
|
|
7
|
-
//#region src/runtime/supervise/graph-registry.ts
|
|
8
|
-
/**
|
|
9
|
-
* `graph-registry` — the designer's memory: graphs and their outcomes as files.
|
|
10
|
-
*
|
|
11
|
-
* The AgentGraph algebra makes a graph PLAIN DATA (nodes with pinned profiles,
|
|
12
|
-
* edges with registry-versioned directives, a budget, a deliverable
|
|
13
|
-
* description), which is the property that lets a graph be stored, replayed
|
|
14
|
-
* byte-identical, and mutated like code. This module is the thin durable layer
|
|
15
|
-
* on top: one JSONL record per run, the exact graph content-addressed beside
|
|
16
|
-
* it. The natural home is a SANDBOX WORKSPACE — the platform's snapshot story
|
|
17
|
-
* (Firecracker restore, warm leases, `continuity: 'resume'`) then makes the
|
|
18
|
-
* registry the agent's compounding execution memory across runs: a rerun over
|
|
19
|
-
* the same sandbox finds every graph the designer ever ran, what each cost,
|
|
20
|
-
* and whether it delivered.
|
|
21
|
-
*
|
|
22
|
-
* ORACLES STAY ENVIRONMENT-OWNED. `DeliverableSpec.check` is a function and is
|
|
23
|
-
* deliberately NOT stored: a stored graph keeps the deliverable's `describe`
|
|
24
|
-
* and an optional `checkRef`, and replay re-binds the check from the caller's
|
|
25
|
-
* environment. This is the graph contract's own doctrine — graders are
|
|
26
|
-
* environment, never workers — applied to persistence.
|
|
27
|
-
*
|
|
28
|
-
* Scoring is a documented heuristic, not a learned model: delivered beats
|
|
29
|
-
* failed, then cheaper (tokens, then iterations), then most recent. A designer
|
|
30
|
-
* that needs better selection replaces `pickBest`, not the records.
|
|
31
|
-
*/
|
|
32
|
-
var graph_registry_exports = /* @__PURE__ */ __exportAll({
|
|
33
|
-
digestOfGraph: () => digestOfGraph,
|
|
34
|
-
graphDigest: () => graphDigest,
|
|
35
|
-
graphFromStored: () => graphFromStored,
|
|
36
|
-
graphRegistry: () => graphRegistry,
|
|
37
|
-
graphRunsFor: () => graphRunsFor,
|
|
38
|
-
listGraphRuns: () => listGraphRuns,
|
|
39
|
-
loadGraph: () => loadGraph,
|
|
40
|
-
lookupGraph: () => lookupGraph,
|
|
41
|
-
pickBestGraph: () => pickBestGraph,
|
|
42
|
-
recordGraphRun: () => recordGraphRun,
|
|
43
|
-
storedGraphDigests: () => storedGraphDigests,
|
|
44
|
-
taskShapeDigest: () => taskShapeDigest
|
|
45
|
-
});
|
|
46
|
-
const RECORDS_FILE = "records.jsonl";
|
|
47
|
-
const GRAPHS_DIR = "graphs";
|
|
48
|
-
/** Canonical serialization for content-addressing: stable key order, sorted. */
|
|
49
|
-
function canonicalJson(value) {
|
|
50
|
-
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
51
|
-
if (value && typeof value === "object") 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)}:${canonicalJson(v)}`).join(",")}}`;
|
|
52
|
-
return JSON.stringify(value);
|
|
53
|
-
}
|
|
54
|
-
/** Digest of the exact graph (nodes + edges + budget + deliverable describe, and a flow graph's
|
|
55
|
-
* entry and gates). An absent key is not serialized, so a graph without `entry` keeps the digest
|
|
56
|
-
* it had before flow graphs were stored. */
|
|
57
|
-
function graphDigest(graph) {
|
|
58
|
-
return sha256(canonicalJson(graph));
|
|
59
|
-
}
|
|
60
|
-
/** Digest of the normalized task shape a designer looks records up by. */
|
|
61
|
-
function taskShapeDigest(taskShape) {
|
|
62
|
-
return sha256(canonicalJson(taskShape));
|
|
63
|
-
}
|
|
64
|
-
/** Digest of a runnable graph exactly as the registry stores it — the value
|
|
65
|
-
* `recordGraphRun` demands. Callers recording real runs hash through this,
|
|
66
|
-
* never hand-rolled, so a record always matches its graph. */
|
|
67
|
-
function digestOfGraph(graph) {
|
|
68
|
-
return storeGraph(graph).digest;
|
|
69
|
-
}
|
|
70
|
-
function storeGraph(graph) {
|
|
71
|
-
const content = {
|
|
72
|
-
nodes: graph.nodes,
|
|
73
|
-
edges: graph.edges,
|
|
74
|
-
budget: graph.budget,
|
|
75
|
-
deliverable: { ...graph.deliverable.describe ? { describe: graph.deliverable.describe } : {} },
|
|
76
|
-
...graph.entry === void 0 ? {} : { entry: graph.entry },
|
|
77
|
-
...graph.gates === void 0 ? {} : { gates: graph.gates }
|
|
78
|
-
};
|
|
79
|
-
return {
|
|
80
|
-
digest: graphDigest(content),
|
|
81
|
-
...content
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function assertRecord(record) {
|
|
85
|
-
if (typeof record.graphDigest !== "string" || record.graphDigest.length === 0) throw new ValidationError("graph-registry: record.graphDigest is required");
|
|
86
|
-
if (typeof record.taskDigest !== "string" || record.taskDigest.length === 0) throw new ValidationError("graph-registry: record.taskDigest is required");
|
|
87
|
-
if (record.outcome.kind !== "delivered" && record.outcome.kind !== "no-winner") throw new ValidationError("graph-registry: record.outcome.kind must be 'delivered' | 'no-winner'");
|
|
88
|
-
}
|
|
89
|
-
function recordKey(record) {
|
|
90
|
-
assertRecord(record);
|
|
91
|
-
return JSON.stringify([
|
|
92
|
-
record.taskDigest,
|
|
93
|
-
record.graphDigest,
|
|
94
|
-
record.startedAt
|
|
95
|
-
]);
|
|
96
|
-
}
|
|
97
|
-
/** Open (or lazily create) a registry rooted at a workspace directory. The
|
|
98
|
-
* directory is the sandbox workspace; snapshots carry it across runs. */
|
|
99
|
-
function graphRegistry(root) {
|
|
100
|
-
if (!root || root.trim().length === 0) throw new ValidationError("graphRegistry: root directory is required");
|
|
101
|
-
return { root };
|
|
102
|
-
}
|
|
103
|
-
/** Append one run record; the exact graph is content-addressed beside it.
|
|
104
|
-
* Idempotent per (taskDigest, graphDigest, startedAt): a replayed append with
|
|
105
|
-
* the same key replaces the prior row rather than duplicating it. */
|
|
106
|
-
async function recordGraphRun(reg, graph, record) {
|
|
107
|
-
assertRecord(record);
|
|
108
|
-
const stored = storeGraph(graph);
|
|
109
|
-
if (stored.digest !== record.graphDigest) throw new ValidationError(`graph-registry: record.graphDigest ${record.graphDigest} does not match the supplied graph (${stored.digest}) — record the graph that actually ran`);
|
|
110
|
-
await mkdir(join(reg.root, GRAPHS_DIR), { recursive: true });
|
|
111
|
-
return withJsonlFileLock(join(reg.root, RECORDS_FILE), async (canonicalPath) => {
|
|
112
|
-
writeAtomicDurableFile(join(reg.root, GRAPHS_DIR, `${stored.digest}.json`), JSON.stringify(stored), { preserveMode: true });
|
|
113
|
-
await upsertCommittedJsonLine(canonicalPath, record, recordKey);
|
|
114
|
-
return stored;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
/** All records, newest last. */
|
|
118
|
-
async function listGraphRuns(reg) {
|
|
119
|
-
return readCommittedJsonFile(join(reg.root, RECORDS_FILE));
|
|
120
|
-
}
|
|
121
|
-
/** Records for one task shape, newest last. */
|
|
122
|
-
async function graphRunsFor(reg, taskDigest) {
|
|
123
|
-
return (await listGraphRuns(reg)).filter((row) => row.taskDigest === taskDigest);
|
|
124
|
-
}
|
|
125
|
-
/** The documented heuristic: delivered beats failed; among delivered, cheaper
|
|
126
|
-
* (tokens, then iterations) wins; ties go to the most recent. */
|
|
127
|
-
function pickBestGraph(records) {
|
|
128
|
-
const delivered = records.filter((row) => row.outcome.kind === "delivered");
|
|
129
|
-
if (delivered.length === 0) return void 0;
|
|
130
|
-
const better = (a, b) => {
|
|
131
|
-
const aTokens = a.cost.tokens ?? Number.POSITIVE_INFINITY;
|
|
132
|
-
const bTokens = b.cost.tokens ?? Number.POSITIVE_INFINITY;
|
|
133
|
-
if (aTokens !== bTokens) return aTokens < bTokens ? a : b;
|
|
134
|
-
const aIter = a.cost.iterations ?? Number.POSITIVE_INFINITY;
|
|
135
|
-
const bIter = b.cost.iterations ?? Number.POSITIVE_INFINITY;
|
|
136
|
-
if (aIter !== bIter) return aIter < bIter ? a : b;
|
|
137
|
-
return a.finishedAt >= b.finishedAt ? a : b;
|
|
138
|
-
};
|
|
139
|
-
return delivered.reduce((best, row) => better(best, row) === best ? best : row);
|
|
140
|
-
}
|
|
141
|
-
/** Load a stored graph by digest. */
|
|
142
|
-
async function loadGraph(reg, digest) {
|
|
143
|
-
try {
|
|
144
|
-
return JSON.parse(await readFile(join(reg.root, GRAPHS_DIR, `${digest}.json`), "utf8"));
|
|
145
|
-
} catch {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/** The designer's lookup: replay the known-good graph for this task shape, or
|
|
150
|
-
* author a new one — with the reason stated either way (the decision is data). */
|
|
151
|
-
async function lookupGraph(reg, taskDigest) {
|
|
152
|
-
const records = await graphRunsFor(reg, taskDigest);
|
|
153
|
-
const best = pickBestGraph(records);
|
|
154
|
-
if (best) {
|
|
155
|
-
const graph = await loadGraph(reg, best.graphDigest);
|
|
156
|
-
if (graph) return {
|
|
157
|
-
kind: "replay",
|
|
158
|
-
graph,
|
|
159
|
-
record: best
|
|
160
|
-
};
|
|
161
|
-
return {
|
|
162
|
-
kind: "author",
|
|
163
|
-
reason: `best record's graph ${best.graphDigest.slice(0, 12)} is missing from the registry`
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
kind: "author",
|
|
168
|
-
reason: records.length === 0 ? "first run for this task shape" : "no graph delivered for this task shape yet"
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
/** Rehydrate a stored graph into a runnable one. The check comes from the
|
|
172
|
-
* CALLER's environment (oracles are environment, never stored). */
|
|
173
|
-
function graphFromStored(stored, check) {
|
|
174
|
-
return {
|
|
175
|
-
nodes: stored.nodes,
|
|
176
|
-
edges: stored.edges,
|
|
177
|
-
budget: stored.budget,
|
|
178
|
-
deliverable: {
|
|
179
|
-
check,
|
|
180
|
-
...stored.deliverable.describe ? { describe: stored.deliverable.describe } : {}
|
|
181
|
-
},
|
|
182
|
-
...stored.entry === void 0 ? {} : { entry: stored.entry },
|
|
183
|
-
...stored.gates === void 0 ? {} : { gates: stored.gates }
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
/** Digests present on disk (integrity probe for tests and audits). */
|
|
187
|
-
async function storedGraphDigests(reg) {
|
|
188
|
-
try {
|
|
189
|
-
return (await readdir(join(reg.root, GRAPHS_DIR))).filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -5)).sort();
|
|
190
|
-
} catch {
|
|
191
|
-
return [];
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
//#endregion
|
|
195
|
-
export { graphRunsFor as a, loadGraph as c, recordGraphRun as d, storedGraphDigests as f, graphRegistry as i, lookupGraph as l, graphDigest as n, graph_registry_exports as o, taskShapeDigest as p, graphFromStored as r, listGraphRuns as s, digestOfGraph as t, pickBestGraph as u };
|
|
196
|
-
|
|
197
|
-
//# sourceMappingURL=graph-registry-BnO64cn8.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"graph-registry-BnO64cn8.js","names":[],"sources":["../src/runtime/supervise/graph-registry.ts"],"sourcesContent":["/**\n * `graph-registry` — the designer's memory: graphs and their outcomes as files.\n *\n * The AgentGraph algebra makes a graph PLAIN DATA (nodes with pinned profiles,\n * edges with registry-versioned directives, a budget, a deliverable\n * description), which is the property that lets a graph be stored, replayed\n * byte-identical, and mutated like code. This module is the thin durable layer\n * on top: one JSONL record per run, the exact graph content-addressed beside\n * it. The natural home is a SANDBOX WORKSPACE — the platform's snapshot story\n * (Firecracker restore, warm leases, `continuity: 'resume'`) then makes the\n * registry the agent's compounding execution memory across runs: a rerun over\n * the same sandbox finds every graph the designer ever ran, what each cost,\n * and whether it delivered.\n *\n * ORACLES STAY ENVIRONMENT-OWNED. `DeliverableSpec.check` is a function and is\n * deliberately NOT stored: a stored graph keeps the deliverable's `describe`\n * and an optional `checkRef`, and replay re-binds the check from the caller's\n * environment. This is the graph contract's own doctrine — graders are\n * environment, never workers — applied to persistence.\n *\n * Scoring is a documented heuristic, not a learned model: delivered beats\n * failed, then cheaper (tokens, then iterations), then most recent. A designer\n * that needs better selection replaces `pickBest`, not the records.\n */\n\nimport { mkdir, readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { AgentProfile } from '@tangle-network/agent-interface'\nimport { sha256 } from '../../content-digest'\nimport {\n readCommittedJsonFile,\n upsertCommittedJsonLine,\n withJsonlFileLock,\n} from '../../durable/jsonl-file'\nimport { ValidationError } from '../../errors'\nimport { writeAtomicDurableFile } from './durable-file'\nimport type { AgentGraph, GraphEdge } from './graph'\n\n/** A graph as stored: every axis serializable; the check re-bound at replay. */\nexport interface StoredGraph {\n readonly digest: string\n readonly nodes: ReadonlyArray<{ readonly id: string; readonly profile: AgentProfile }>\n readonly edges: ReadonlyArray<GraphEdge>\n readonly budget: AgentGraph['budget']\n readonly deliverable: { readonly describe?: string; readonly checkRef?: string }\n /** A flow graph's entry and gates; absent for any other graph. */\n readonly entry?: AgentGraph['entry']\n readonly gates?: AgentGraph['gates']\n}\n\n/** One completed run of one graph, for one task shape. */\nexport interface GraphRunRecord {\n /** Content-address of the exact graph that ran. */\n readonly graphDigest: string\n /** Digest of the normalized task shape the designer computed for lookup. */\n readonly taskDigest: string\n readonly startedAt: string\n readonly finishedAt: string\n readonly outcome:\n | { readonly kind: 'delivered'; readonly summary?: string }\n | { readonly kind: 'no-winner'; readonly reason?: string }\n /** Cost from the run's terminal accounting. Unknown channels omitted. */\n readonly cost: {\n readonly iterations?: number\n readonly tokens?: number\n readonly usd?: number\n readonly ms?: number\n }\n /** Ledger size + exhausted edges: the trace's shape, for the designer's read. */\n readonly traversals: number\n readonly exhaustedEdges?: ReadonlyArray<string>\n}\n\nexport interface GraphRegistryDir {\n readonly root: string\n}\n\nconst RECORDS_FILE = 'records.jsonl'\nconst GRAPHS_DIR = 'graphs'\n\n/** Canonical serialization for content-addressing: stable key order, sorted. */\nfunction canonicalJson(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n if (value && typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n/** Digest of the exact graph (nodes + edges + budget + deliverable describe, and a flow graph's\n * entry and gates). An absent key is not serialized, so a graph without `entry` keeps the digest\n * it had before flow graphs were stored. */\nexport function graphDigest(graph: Omit<StoredGraph, 'digest'>): string {\n return sha256(canonicalJson(graph))\n}\n\n/** Digest of the normalized task shape a designer looks records up by. */\nexport function taskShapeDigest(taskShape: Record<string, unknown>): string {\n return sha256(canonicalJson(taskShape))\n}\n\n/** Digest of a runnable graph exactly as the registry stores it — the value\n * `recordGraphRun` demands. Callers recording real runs hash through this,\n * never hand-rolled, so a record always matches its graph. */\nexport function digestOfGraph(graph: AgentGraph): string {\n return storeGraph(graph).digest\n}\n\nfunction storeGraph(graph: AgentGraph): StoredGraph {\n const content: Omit<StoredGraph, 'digest'> = {\n nodes: graph.nodes,\n edges: graph.edges,\n budget: graph.budget,\n deliverable: {\n ...(graph.deliverable.describe ? { describe: graph.deliverable.describe } : {}),\n },\n ...(graph.entry === undefined ? {} : { entry: graph.entry }),\n ...(graph.gates === undefined ? {} : { gates: graph.gates }),\n }\n return { digest: graphDigest(content), ...content }\n}\n\nfunction assertRecord(record: GraphRunRecord): void {\n if (typeof record.graphDigest !== 'string' || record.graphDigest.length === 0)\n throw new ValidationError('graph-registry: record.graphDigest is required')\n if (typeof record.taskDigest !== 'string' || record.taskDigest.length === 0)\n throw new ValidationError('graph-registry: record.taskDigest is required')\n if (record.outcome.kind !== 'delivered' && record.outcome.kind !== 'no-winner')\n throw new ValidationError(\n \"graph-registry: record.outcome.kind must be 'delivered' | 'no-winner'\",\n )\n}\n\nfunction recordKey(record: GraphRunRecord): string {\n assertRecord(record)\n return JSON.stringify([record.taskDigest, record.graphDigest, record.startedAt])\n}\n\n/** Open (or lazily create) a registry rooted at a workspace directory. The\n * directory is the sandbox workspace; snapshots carry it across runs. */\nexport function graphRegistry(root: string): GraphRegistryDir {\n if (!root || root.trim().length === 0)\n throw new ValidationError('graphRegistry: root directory is required')\n return { root }\n}\n\n/** Append one run record; the exact graph is content-addressed beside it.\n * Idempotent per (taskDigest, graphDigest, startedAt): a replayed append with\n * the same key replaces the prior row rather than duplicating it. */\nexport async function recordGraphRun(\n reg: GraphRegistryDir,\n graph: AgentGraph,\n record: GraphRunRecord,\n): Promise<StoredGraph> {\n assertRecord(record)\n const stored = storeGraph(graph)\n if (stored.digest !== record.graphDigest) {\n throw new ValidationError(\n `graph-registry: record.graphDigest ${record.graphDigest} does not match the supplied graph (${stored.digest}) — record the graph that actually ran`,\n )\n }\n await mkdir(join(reg.root, GRAPHS_DIR), { recursive: true })\n const file = join(reg.root, RECORDS_FILE)\n return withJsonlFileLock(file, async (canonicalPath) => {\n const revisionPath = join(reg.root, GRAPHS_DIR, `${stored.digest}.json`)\n writeAtomicDurableFile(revisionPath, JSON.stringify(stored), { preserveMode: true })\n await upsertCommittedJsonLine(canonicalPath, record, recordKey)\n return stored\n })\n}\n\n/** All records, newest last. */\nexport async function listGraphRuns(reg: GraphRegistryDir): Promise<GraphRunRecord[]> {\n return readCommittedJsonFile<GraphRunRecord>(join(reg.root, RECORDS_FILE))\n}\n\n/** Records for one task shape, newest last. */\nexport async function graphRunsFor(\n reg: GraphRegistryDir,\n taskDigest: string,\n): Promise<GraphRunRecord[]> {\n return (await listGraphRuns(reg)).filter((row) => row.taskDigest === taskDigest)\n}\n\nexport type GraphSelection =\n | { readonly kind: 'replay'; readonly graph: StoredGraph; readonly record: GraphRunRecord }\n | { readonly kind: 'author'; readonly reason: string }\n\n/** The documented heuristic: delivered beats failed; among delivered, cheaper\n * (tokens, then iterations) wins; ties go to the most recent. */\nexport function pickBestGraph(records: ReadonlyArray<GraphRunRecord>): GraphRunRecord | undefined {\n const delivered = records.filter((row) => row.outcome.kind === 'delivered')\n if (delivered.length === 0) return undefined\n const better = (a: GraphRunRecord, b: GraphRunRecord): GraphRunRecord => {\n const aTokens = a.cost.tokens ?? Number.POSITIVE_INFINITY\n const bTokens = b.cost.tokens ?? Number.POSITIVE_INFINITY\n if (aTokens !== bTokens) return aTokens < bTokens ? a : b\n const aIter = a.cost.iterations ?? Number.POSITIVE_INFINITY\n const bIter = b.cost.iterations ?? Number.POSITIVE_INFINITY\n if (aIter !== bIter) return aIter < bIter ? a : b\n return a.finishedAt >= b.finishedAt ? a : b\n }\n return delivered.reduce((best, row) => (better(best, row) === best ? best : row))\n}\n\n/** Load a stored graph by digest. */\nexport async function loadGraph(\n reg: GraphRegistryDir,\n digest: string,\n): Promise<StoredGraph | undefined> {\n try {\n return JSON.parse(\n await readFile(join(reg.root, GRAPHS_DIR, `${digest}.json`), 'utf8'),\n ) as StoredGraph\n } catch {\n return undefined\n }\n}\n\n/** The designer's lookup: replay the known-good graph for this task shape, or\n * author a new one — with the reason stated either way (the decision is data). */\nexport async function lookupGraph(\n reg: GraphRegistryDir,\n taskDigest: string,\n): Promise<GraphSelection> {\n const records = await graphRunsFor(reg, taskDigest)\n const best = pickBestGraph(records)\n if (best) {\n const graph = await loadGraph(reg, best.graphDigest)\n if (graph) return { kind: 'replay', graph, record: best }\n return {\n kind: 'author',\n reason: `best record's graph ${best.graphDigest.slice(0, 12)} is missing from the registry`,\n }\n }\n return {\n kind: 'author',\n reason:\n records.length === 0\n ? 'first run for this task shape'\n : 'no graph delivered for this task shape yet',\n }\n}\n\n/** Rehydrate a stored graph into a runnable one. The check comes from the\n * CALLER's environment (oracles are environment, never stored). */\nexport function graphFromStored(\n stored: StoredGraph,\n check: AgentGraph['deliverable']['check'],\n): AgentGraph {\n return {\n nodes: stored.nodes,\n edges: stored.edges,\n budget: stored.budget,\n deliverable: {\n check,\n ...(stored.deliverable.describe ? { describe: stored.deliverable.describe } : {}),\n },\n ...(stored.entry === undefined ? {} : { entry: stored.entry }),\n ...(stored.gates === undefined ? {} : { gates: stored.gates }),\n }\n}\n\n/** Digests present on disk (integrity probe for tests and audits). */\nexport async function storedGraphDigests(reg: GraphRegistryDir): Promise<string[]> {\n try {\n return (await readdir(join(reg.root, GRAPHS_DIR)))\n .filter((name) => name.endsWith('.json'))\n .map((name) => name.slice(0, -'.json'.length))\n .sort()\n } catch {\n return []\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
//#region src/durable/jsonl-file.ts
|
|
2
|
-
/** Read a fixed file prefix one record at a time, never allocating the whole journal as a string.
|
|
3
|
-
* A reader cannot chase concurrent appends forever. Missing files are empty only when requested;
|
|
4
|
-
* corruption, short reads, and other I/O errors remain failures. Closing the iterator closes the fd. */
|
|
5
|
-
async function* readCommittedJsonLines(path, options = {}) {
|
|
6
|
-
const fs = await import("node:fs/promises");
|
|
7
|
-
let handle;
|
|
8
|
-
try {
|
|
9
|
-
handle = await fs.open(path, "r");
|
|
10
|
-
} catch (error) {
|
|
11
|
-
if (options.allowMissing && isNoEntError(error)) return;
|
|
12
|
-
throw error;
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
const { size } = await handle.stat();
|
|
16
|
-
if (size === 0) return;
|
|
17
|
-
const { StringDecoder } = await import("node:string_decoder");
|
|
18
|
-
const decoder = new StringDecoder("utf8");
|
|
19
|
-
const stream = handle.createReadStream({
|
|
20
|
-
autoClose: false,
|
|
21
|
-
end: size - 1
|
|
22
|
-
});
|
|
23
|
-
const fragments = [];
|
|
24
|
-
let lineNumber = 1;
|
|
25
|
-
try {
|
|
26
|
-
for await (const chunk of stream) {
|
|
27
|
-
options.onBytes?.(chunk);
|
|
28
|
-
const text = decoder.write(chunk);
|
|
29
|
-
let start = 0;
|
|
30
|
-
let end = text.indexOf("\n", start);
|
|
31
|
-
while (end !== -1) {
|
|
32
|
-
fragments.push(text.slice(start, end));
|
|
33
|
-
const record = parseRecord(fragments.join(""), path, lineNumber++, true, options.allowWhitespaceLines);
|
|
34
|
-
fragments.length = 0;
|
|
35
|
-
if (record !== void 0) yield record.value;
|
|
36
|
-
start = end + 1;
|
|
37
|
-
end = text.indexOf("\n", start);
|
|
38
|
-
}
|
|
39
|
-
if (start < text.length) fragments.push(text.slice(start));
|
|
40
|
-
}
|
|
41
|
-
if (stream.bytesRead !== size) throw new Error(`${path}: journal changed while reading its committed prefix`);
|
|
42
|
-
fragments.push(decoder.end());
|
|
43
|
-
const record = parseRecord(fragments.join(""), path, lineNumber, false, options.allowWhitespaceLines);
|
|
44
|
-
if (record !== void 0) yield record.value;
|
|
45
|
-
} finally {
|
|
46
|
-
stream.destroy();
|
|
47
|
-
}
|
|
48
|
-
} finally {
|
|
49
|
-
await handle.close();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/** An invalid unterminated tail may be a torn write; other parse failures are not evidence of one. */
|
|
53
|
-
function parseRecord(line, source, lineNumber, terminated, allowWhitespaceLines = false) {
|
|
54
|
-
if (line.length === 0 || allowWhitespaceLines && line.trim().length === 0) return void 0;
|
|
55
|
-
try {
|
|
56
|
-
return { value: JSON.parse(line) };
|
|
57
|
-
} catch (cause) {
|
|
58
|
-
if (!(cause instanceof SyntaxError)) throw cause;
|
|
59
|
-
if (!terminated) return void 0;
|
|
60
|
-
throw new Error(`${source}: malformed JSONL record at line ${lineNumber}`, { cause });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
async function appendCommittedJsonLine(path, record, acknowledge) {
|
|
64
|
-
const fs = await import("node:fs/promises");
|
|
65
|
-
const needsSeparator = await prepareJsonlAppend(path);
|
|
66
|
-
const handle = await fs.open(path, "a");
|
|
67
|
-
try {
|
|
68
|
-
await writeAllBytes(handle, `${needsSeparator ? "\n" : ""}${JSON.stringify(record)}\n`);
|
|
69
|
-
await handle.sync();
|
|
70
|
-
return await acknowledge?.(handle);
|
|
71
|
-
} finally {
|
|
72
|
-
await handle.close();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/** Serialize JSONL file updates across processes and aliases using Eval's existing file lock.
|
|
76
|
-
* The caller owns validation and the record format. The lock path names the canonical file,
|
|
77
|
-
* including its first write, so two directory aliases cannot acquire different write heads.
|
|
78
|
-
* @internal
|
|
79
|
-
*/
|
|
80
|
-
async function withJsonlFileLock(path, operation) {
|
|
81
|
-
const fs = await import("node:fs/promises");
|
|
82
|
-
const { dirname } = await import("node:path");
|
|
83
|
-
const { tryAcquireAtomicFileLock } = await import("@tangle-network/agent-eval/ledger-core");
|
|
84
|
-
await fs.mkdir(dirname(path), { recursive: true });
|
|
85
|
-
await (await fs.open(path, "a")).close();
|
|
86
|
-
const canonicalPath = await fs.realpath(path);
|
|
87
|
-
const deadline = Date.now() + 5e3;
|
|
88
|
-
for (;;) {
|
|
89
|
-
const acquisition = tryAcquireAtomicFileLock({ lockPath: `${canonicalPath}.lock` });
|
|
90
|
-
if (!acquisition.acquired) {
|
|
91
|
-
if (Date.now() >= deadline) throw new Error(`journal file lock unavailable after 5000ms: ${canonicalPath}`);
|
|
92
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
return await operation(canonicalPath);
|
|
97
|
-
} finally {
|
|
98
|
-
acquisition.lock.release();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
/** Read one keyed registry snapshot without an extra whole-file text allocation.
|
|
103
|
-
* Historical registry files allow whitespace-only separators; append journals remain strict.
|
|
104
|
-
* @internal
|
|
105
|
-
*/
|
|
106
|
-
async function readCommittedJsonFile(path) {
|
|
107
|
-
const records = [];
|
|
108
|
-
for await (const record of readCommittedJsonLines(path, {
|
|
109
|
-
allowMissing: true,
|
|
110
|
-
allowWhitespaceLines: true
|
|
111
|
-
})) records.push(record);
|
|
112
|
-
return records;
|
|
113
|
-
}
|
|
114
|
-
/** Replace one keyed row and publish one durable snapshot. The caller holds the file lock.
|
|
115
|
-
* Keep unrelated rows in order and put the replacement last. No revision archive is retained.
|
|
116
|
-
* @internal
|
|
117
|
-
*/
|
|
118
|
-
async function upsertCommittedJsonLine(path, record, key) {
|
|
119
|
-
const encoded = JSON.stringify(record);
|
|
120
|
-
if (encoded === void 0) throw new Error("JSONL records must be JSON serializable");
|
|
121
|
-
const snapshot = JSON.parse(encoded);
|
|
122
|
-
const id = key(snapshot);
|
|
123
|
-
const updated = [];
|
|
124
|
-
for await (const prior of readCommittedJsonLines(path, {
|
|
125
|
-
allowMissing: true,
|
|
126
|
-
allowWhitespaceLines: true
|
|
127
|
-
})) if (key(prior) !== id) updated.push(prior);
|
|
128
|
-
updated.push(snapshot);
|
|
129
|
-
const { writeAtomicDurableFile } = await import("./durable-file-gNI9ILAn.js").then((n) => n.r);
|
|
130
|
-
writeAtomicDurableFile(path, `${updated.map((row) => JSON.stringify(row)).join("\n")}\n`, { preserveMode: true });
|
|
131
|
-
}
|
|
132
|
-
/** FileHandle.write may legally make a short write. Loop until every byte is appended. */
|
|
133
|
-
async function writeAllBytes(handle, value) {
|
|
134
|
-
const bytes = typeof value === "string" ? Buffer.from(value) : Buffer.from(value);
|
|
135
|
-
let offset = 0;
|
|
136
|
-
while (offset < bytes.byteLength) {
|
|
137
|
-
const { bytesWritten } = await handle.write(bytes, offset, bytes.byteLength - offset, null);
|
|
138
|
-
if (bytesWritten <= 0) throw new Error(`append-only file write made no progress at byte ${offset}`);
|
|
139
|
-
offset += bytesWritten;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/** Prepare the tail under the journal owner's existing single-writer discipline.
|
|
143
|
-
* Healthy appends read one byte, regardless of history size. Only recovery scans backward,
|
|
144
|
-
* retaining a valid unterminated value or truncating an uncommitted torn tail. */
|
|
145
|
-
async function prepareJsonlAppend(path) {
|
|
146
|
-
const fs = await import("node:fs/promises");
|
|
147
|
-
let handle;
|
|
148
|
-
try {
|
|
149
|
-
handle = await fs.open(path, "r+");
|
|
150
|
-
} catch (error) {
|
|
151
|
-
if (isNoEntError(error)) return false;
|
|
152
|
-
throw error;
|
|
153
|
-
}
|
|
154
|
-
try {
|
|
155
|
-
const size = (await handle.stat()).size;
|
|
156
|
-
if (size === 0) return false;
|
|
157
|
-
const lastByte = Buffer.allocUnsafe(1);
|
|
158
|
-
await readAt(handle, lastByte, size - 1);
|
|
159
|
-
if (lastByte[0] === 10) return false;
|
|
160
|
-
const chunks = [];
|
|
161
|
-
let end = size;
|
|
162
|
-
let tailStart = 0;
|
|
163
|
-
while (end > 0) {
|
|
164
|
-
const start = Math.max(0, end - 65536);
|
|
165
|
-
const chunk = Buffer.allocUnsafe(end - start);
|
|
166
|
-
await readAt(handle, chunk, start);
|
|
167
|
-
const newline = chunk.lastIndexOf(10);
|
|
168
|
-
chunks.push(chunk.subarray(newline + 1));
|
|
169
|
-
if (newline >= 0) {
|
|
170
|
-
tailStart = start + newline + 1;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
end = start;
|
|
174
|
-
}
|
|
175
|
-
const tail = Buffer.concat(chunks.reverse()).toString("utf8");
|
|
176
|
-
try {
|
|
177
|
-
JSON.parse(tail);
|
|
178
|
-
return true;
|
|
179
|
-
} catch (error) {
|
|
180
|
-
if (!(error instanceof SyntaxError)) throw error;
|
|
181
|
-
await handle.truncate(tailStart);
|
|
182
|
-
await handle.sync();
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
} finally {
|
|
186
|
-
await handle.close();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/** A short read is legal; an unexpected EOF means the owner's snapshot changed. */
|
|
190
|
-
async function readAt(handle, buffer, position) {
|
|
191
|
-
let offset = 0;
|
|
192
|
-
while (offset < buffer.length) {
|
|
193
|
-
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, position + offset);
|
|
194
|
-
if (bytesRead === 0) throw new Error("journal changed while preparing its append tail");
|
|
195
|
-
offset += bytesRead;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
/** True for a filesystem error meaning "the file is not there yet" — the one condition an
|
|
199
|
-
* append-only reader treats as an empty log rather than a fault. */
|
|
200
|
-
function isNoEntError(error) {
|
|
201
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
202
|
-
}
|
|
203
|
-
//#endregion
|
|
204
|
-
export { readCommittedJsonLines as a, readCommittedJsonFile as i, isNoEntError as n, upsertCommittedJsonLine as o, prepareJsonlAppend as r, withJsonlFileLock as s, appendCommittedJsonLine as t };
|
|
205
|
-
|
|
206
|
-
//# sourceMappingURL=jsonl-file-C9Kde_0Z.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsonl-file-C9Kde_0Z.js","names":[],"sources":["../src/durable/jsonl-file.ts"],"sourcesContent":["import type { FileHandle } from 'node:fs/promises'\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 } = {},\n): AsyncGenerator<T> {\n const fs = await import('node:fs/promises')\n let handle: FileHandle\n try {\n handle = await fs.open(path, 'r')\n } catch (error) {\n if (options.allowMissing && isNoEntError(error)) return\n throw error\n }\n try {\n const { size } = await handle.stat()\n if (size === 0) return\n const { StringDecoder } = await import('node:string_decoder')\n const decoder = new StringDecoder('utf8')\n const stream = handle.createReadStream({ autoClose: false, end: size - 1 })\n const fragments: string[] = []\n let lineNumber = 1\n try {\n for await (const chunk of stream) {\n options.onBytes?.(chunk as Buffer)\n const text = decoder.write(chunk as Buffer)\n let start = 0\n let end = text.indexOf('\\n', start)\n while (end !== -1) {\n fragments.push(text.slice(start, end))\n const record = parseRecord<T>(\n fragments.join(''),\n path,\n lineNumber++,\n true,\n options.allowWhitespaceLines,\n )\n fragments.length = 0\n if (record !== undefined) yield record.value\n start = end + 1\n end = text.indexOf('\\n', start)\n }\n if (start < text.length) fragments.push(text.slice(start))\n }\n if (stream.bytesRead !== size) {\n throw new Error(`${path}: journal changed while reading its committed prefix`)\n }\n fragments.push(decoder.end())\n const record = parseRecord<T>(\n fragments.join(''),\n path,\n lineNumber,\n false,\n options.allowWhitespaceLines,\n )\n if (record !== undefined) yield record.value\n } finally {\n stream.destroy()\n }\n } finally {\n 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\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, handle short\n * writes, and fsync before acknowledging.\n * An optional acknowledgement reads the same open descriptor after sync (the spawn journal\n * validates its file stamp here). Queueing, validation, and failure policy belong to the owner.\n * @internal\n */\nexport function appendCommittedJsonLine(path: string, record: unknown): Promise<void>\nexport function appendCommittedJsonLine<T>(\n path: string,\n record: unknown,\n acknowledge: (handle: FileHandle) => Promise<T>,\n): Promise<T>\nexport async function appendCommittedJsonLine<T>(\n path: string,\n record: unknown,\n acknowledge?: (handle: FileHandle) => Promise<T>,\n): Promise<T | undefined> {\n const fs = await import('node:fs/promises')\n const needsSeparator = await prepareJsonlAppend(path)\n const handle = await fs.open(path, 'a')\n try {\n await writeAllBytes(handle, `${needsSeparator ? '\\n' : ''}${JSON.stringify(record)}\\n`)\n await handle.sync()\n return await acknowledge?.(handle)\n } finally {\n await handle.close()\n }\n}\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/** FileHandle.write may legally make a short write. Loop until every byte is appended. */\nasync function writeAllBytes(\n handle: Pick<FileHandle, 'write'>,\n value: string | Uint8Array,\n): Promise<void> {\n const bytes = typeof value === 'string' ? Buffer.from(value) : Buffer.from(value)\n let offset = 0\n while (offset < bytes.byteLength) {\n const { bytesWritten } = await handle.write(bytes, offset, bytes.byteLength - offset, null)\n if (bytesWritten <= 0) {\n throw new Error(`append-only file write made no progress at byte ${offset}`)\n }\n offset += bytesWritten\n }\n}\n\n/** Prepare the tail under the journal owner's existing single-writer discipline.\n * Healthy appends read one byte, regardless of history size. Only recovery scans backward,\n * retaining a valid unterminated value or truncating an uncommitted torn tail. */\nexport async function prepareJsonlAppend(path: string): Promise<boolean> {\n const fs = await import('node:fs/promises')\n let handle: FileHandle\n try {\n handle = await fs.open(path, 'r+')\n } catch (error) {\n if (isNoEntError(error)) return false\n throw error\n }\n try {\n const size = (await handle.stat()).size\n if (size === 0) return false\n const lastByte = Buffer.allocUnsafe(1)\n await readAt(handle, lastByte, size - 1)\n if (lastByte[0] === 0x0a) return false\n\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 await readAt(handle, 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 const tail = Buffer.concat(chunks.reverse()).toString('utf8')\n try {\n JSON.parse(tail)\n return true\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error\n await handle.truncate(tailStart)\n await handle.sync()\n return false\n }\n } finally {\n await handle.close()\n }\n}\n\n/** A short read is legal; an unexpected EOF means the owner's snapshot changed. */\nasync function readAt(handle: FileHandle, buffer: Buffer, position: number): Promise<void> {\n let offset = 0\n while (offset < buffer.length) {\n const { bytesRead } = await handle.read(\n buffer,\n offset,\n buffer.length - offset,\n position + offset,\n )\n if (bytesRead === 0) throw new Error('journal changed while preparing its append tail')\n offset += bytesRead\n }\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,12 +0,0 @@
|
|
|
1
|
-
//#region src/record-value.ts
|
|
2
|
-
/** Admit non-null, non-array objects without restricting their prototype or member values. */
|
|
3
|
-
function isRecord(value) {
|
|
4
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
-
}
|
|
6
|
-
function recordValue(value) {
|
|
7
|
-
return isRecord(value) ? value : void 0;
|
|
8
|
-
}
|
|
9
|
-
//#endregion
|
|
10
|
-
export { recordValue as n, isRecord as t };
|
|
11
|
-
|
|
12
|
-
//# sourceMappingURL=record-value-B6bwJxLa.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"record-value-B6bwJxLa.js","names":[],"sources":["../src/record-value.ts"],"sourcesContent":["/** Admit non-null, non-array objects without restricting their prototype or member values. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nexport function recordValue(value: unknown): Record<string, unknown> | undefined {\n return isRecord(value) ? value : undefined\n}\n"],"mappings":";;AACA,SAAgB,SAAS,OAAkD;CACzE,OAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAgB,YAAY,OAAqD;CAC/E,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACnC"}
|