@tangle-network/agent-runtime 0.196.0 → 0.198.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.
- package/README.md +1 -2
- package/dist/{activation-BxMZybuo.js → activation-CwdO0ml2.js} +2 -2
- package/dist/{activation-BxMZybuo.js.map → activation-CwdO0ml2.js.map} +1 -1
- package/dist/{improve-DZs0KXhK.d.ts → activation-DFRTurvU.d.ts} +99 -5
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/candidate-execution/index.d.ts +370 -2
- package/dist/candidate-execution/index.js +828 -4
- package/dist/candidate-execution/index.js.map +1 -0
- package/dist/{coordination-driver-zkCrfEbS.js → coordination-driver-Cf638-6q.js} +2 -5
- package/dist/{coordination-driver-zkCrfEbS.js.map → coordination-driver-Cf638-6q.js.map} +1 -1
- package/dist/{delegate-D7sonS78.js → delegate-Bez07Dlh.js} +2 -2
- package/dist/{delegate-D7sonS78.js.map → delegate-Bez07Dlh.js.map} +1 -1
- package/dist/durable.d.ts +110 -6
- package/dist/durable.js +404 -47
- package/dist/durable.js.map +1 -1
- package/dist/{graph-BuS-NxXD.js → graph-DBk3Unto.js} +3 -3
- package/dist/{graph-BuS-NxXD.js.map → graph-DBk3Unto.js.map} +1 -1
- package/dist/{improvement-cycle-Tw5nYT5R.js → improvement-cycle-Bma8TJs-.js} +8 -10
- package/dist/{improvement-cycle-Tw5nYT5R.js.map → improvement-cycle-Bma8TJs-.js.map} +1 -1
- package/dist/{index-CYOJsxSg.d.ts → index-D8LWK3Ha.d.ts} +6 -7
- package/dist/index.d.ts +694 -16
- package/dist/index.js +1780 -17
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +3 -7
- package/dist/intelligence.js +5 -6
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +2 -5
- package/dist/kernel.js +8 -10
- package/dist/{loop-runner-bin-XKMc3Ld-.js → loop-runner-bin-Bd3dHQuw.js} +3 -3
- package/dist/{loop-runner-bin-XKMc3Ld-.js.map → loop-runner-bin-Bd3dHQuw.js.map} +1 -1
- package/dist/{loop-runner-bin-DwrSB04m.d.ts → loop-runner-bin-Bn-0o_4k.d.ts} +3 -3
- 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 +2 -3
- package/dist/mcp/index.js +4 -5
- package/dist/mcp/index.js.map +1 -1
- package/dist/{prepare-DDGp0-rW.js → prepare-CAO1yXov.js} +2407 -2407
- package/dist/prepare-CAO1yXov.js.map +1 -0
- package/dist/{protected-model-port-DxFN8DLS.js → protected-model-port-B5avcRiQ.js} +2 -2
- package/dist/{protected-model-port-DxFN8DLS.js.map → protected-model-port-B5avcRiQ.js.map} +1 -1
- package/dist/{provision-supervisor-CpMMShE_.js → provision-supervisor-C97q5NN_.js} +3 -6
- package/dist/{provision-supervisor-CpMMShE_.js.map → provision-supervisor-C97q5NN_.js.map} +1 -1
- package/dist/{redact-Cbl2O-4N.js → redact-CVyxp2AU.js} +5232 -2525
- package/dist/redact-CVyxp2AU.js.map +1 -0
- package/dist/{runtime-DqRieJ6e.js → runtime-BrTBBSfC.js} +427 -23
- package/dist/runtime-BrTBBSfC.js.map +1 -0
- package/dist/{server-BjB6ay4N.js → server-CO4bvAwe.js} +4 -4
- package/dist/{server-BjB6ay4N.js.map → server-CO4bvAwe.js.map} +1 -1
- package/dist/{types-DFLZMaeh.d.ts → stream-agent-turn-CLOQr497.d.ts} +1986 -6
- package/dist/{structural-rollout-DPbZWgEm.js → structural-rollout-D__lmWXe.js} +1101 -8
- package/dist/structural-rollout-D__lmWXe.js.map +1 -0
- package/dist/{supervise-D9aNi8_f.js → supervise-KSe4JEcg.js} +30 -7
- package/dist/supervise-KSe4JEcg.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +13 -13
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{workspace-archive-Ybomp7AN.js → workspace-archive-BMOnloFf.js} +3 -3
- package/dist/{workspace-archive-Ybomp7AN.js.map → workspace-archive-BMOnloFf.js.map} +1 -1
- package/package.json +4 -31
- package/dist/activation-DyWB0K6E.d.ts +0 -98
- package/dist/authored-code-URmkdgjv.js +0 -37
- package/dist/authored-code-URmkdgjv.js.map +0 -1
- package/dist/candidate-execution-nvqVIMyS.js +0 -829
- package/dist/candidate-execution-nvqVIMyS.js.map +0 -1
- package/dist/conversation-BxJ0SIBM.js +0 -1363
- package/dist/conversation-BxJ0SIBM.js.map +0 -1
- package/dist/conversation.d.ts +0 -2
- package/dist/conversation.js +0 -2
- package/dist/environment-provider-1fKZh2zl.js +0 -2281
- package/dist/environment-provider-1fKZh2zl.js.map +0 -1
- package/dist/environment-provider-B-I2jlQy.d.ts +0 -143
- package/dist/environment-provider.d.ts +0 -2
- package/dist/environment-provider.js +0 -2
- package/dist/graph.d.ts +0 -753
- package/dist/graph.js +0 -2111
- package/dist/graph.js.map +0 -1
- package/dist/index-CUosKU4N.d.ts +0 -372
- package/dist/index-D9mb6fn2.d.ts +0 -691
- package/dist/index-ZnxSe6iK.d.ts +0 -138
- package/dist/jsonl-file-BEpaEYjT.js +0 -141
- package/dist/jsonl-file-BEpaEYjT.js.map +0 -1
- package/dist/knowledge-CPzT_Ve7.js +0 -428
- package/dist/knowledge-CPzT_Ve7.js.map +0 -1
- package/dist/knowledge.d.ts +0 -2
- package/dist/knowledge.js +0 -2
- package/dist/materialization-Cy0oM8tb.js +0 -672
- package/dist/materialization-Cy0oM8tb.js.map +0 -1
- package/dist/prepare-DDGp0-rW.js.map +0 -1
- package/dist/primeintellect/index.d.ts +0 -218
- package/dist/primeintellect/index.js +0 -739
- package/dist/primeintellect/index.js.map +0 -1
- package/dist/redact-Cbl2O-4N.js.map +0 -1
- package/dist/runtime-0xNaV6TJ.d.ts +0 -1699
- package/dist/runtime-DqRieJ6e.js.map +0 -1
- package/dist/stream-agent-turn-Dt5mZpc3.js +0 -1103
- package/dist/stream-agent-turn-Dt5mZpc3.js.map +0 -1
- package/dist/stream-agent-turn-urHpmO_Z.d.ts +0 -160
- package/dist/structural-rollout-DPbZWgEm.js.map +0 -1
- package/dist/supervise-D9aNi8_f.js.map +0 -1
package/dist/durable.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { E as zeroSpend, s as addSpend, u as cloneSpend } from "./environment-provider-1fKZh2zl.js";
|
|
1
|
+
import { Mr as parseCommittedJsonLines, Nr as prepareJsonlAppend, Pr as writeAllBytes, gn as addSpend, jr as isNoEntError, kn as zeroSpend, vn as cloneSpend } from "./redact-CVyxp2AU.js";
|
|
3
2
|
import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-tXpAarhW.js";
|
|
4
|
-
import {
|
|
3
|
+
import { i as writeAtomicDurableFile, r as publishExclusiveDurableFile } from "./durable-file-D24y9zg7.js";
|
|
4
|
+
import { n as supervise } from "./supervise-KSe4JEcg.js";
|
|
5
|
+
import { canonicalCandidateJson } from "@tangle-network/agent-interface";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
|
-
import {
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
7
8
|
import { resolve } from "node:path";
|
|
9
|
+
import { mkdir, readFile, rmdir, unlink } from "node:fs/promises";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { setTimeout } from "node:timers/promises";
|
|
8
12
|
//#region src/durable/chat-engine.ts
|
|
9
13
|
const encoder = new TextEncoder();
|
|
10
14
|
function encodeLine(event) {
|
|
@@ -300,8 +304,9 @@ function toError(error) {
|
|
|
300
304
|
*
|
|
301
305
|
* Topology comes only from Runtime's canonical `agent.spawn` facts. Terminal node
|
|
302
306
|
* state comes only from `agent.child`; concrete run state comes only from the root
|
|
303
|
-
* `agent.run` lifecycle emitted by `supervisePursuit
|
|
304
|
-
* concrete Runtime run so independent trees may both contain `root:s0` without
|
|
307
|
+
* `agent.run` lifecycle emitted by `supervisePursuit`, one row per attempt. Node identity is
|
|
308
|
+
* scoped to the concrete Runtime run so independent trees may both contain `root:s0` without
|
|
309
|
+
* aliasing, and a node belongs to the attempt that spawned it.
|
|
305
310
|
*
|
|
306
311
|
* Usage, cost and timing are reported at the class the runtime measured them at. A missing
|
|
307
312
|
* class stays ABSENT and the run names the node in `spendGaps`; nothing here converts an
|
|
@@ -311,14 +316,14 @@ function projectPursuit(records) {
|
|
|
311
316
|
if (records.length === 0) throw new TypeError("projectPursuit: at least one observer record is required");
|
|
312
317
|
const pursuitId = records[0].pursuitId;
|
|
313
318
|
const verified = verifyObserverRecords(records, pursuitId);
|
|
314
|
-
const
|
|
319
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
315
320
|
const nodes = /* @__PURE__ */ new Map();
|
|
316
321
|
let eventCount = 0;
|
|
317
322
|
let decisionCount = 0;
|
|
318
323
|
for (const record of verified) {
|
|
319
324
|
const observed = record.event ?? record.decision;
|
|
320
325
|
if (!observed) throw new Error(`projectPursuit: record ${record.sequence} has no observation`);
|
|
321
|
-
const run =
|
|
326
|
+
const run = currentAttempt(attempts, observed.runId, record);
|
|
322
327
|
run.lastSequence = record.sequence;
|
|
323
328
|
run.lastObservedAt = record.observedAt;
|
|
324
329
|
if (record.event) {
|
|
@@ -326,20 +331,21 @@ function projectPursuit(records) {
|
|
|
326
331
|
run.eventCount += 1;
|
|
327
332
|
increment(run.targets, record.event.target);
|
|
328
333
|
projectRunActivity(run, record);
|
|
329
|
-
projectSpawnNode(nodes, record);
|
|
334
|
+
projectSpawnNode(nodes, record, run.attemptIndex);
|
|
330
335
|
projectNodeActivity(nodes, record);
|
|
331
|
-
projectTurn(
|
|
336
|
+
projectTurn(run, nodes, record);
|
|
332
337
|
} else if (record.decision) {
|
|
333
338
|
decisionCount += 1;
|
|
334
339
|
run.decisionCount += 1;
|
|
335
340
|
increment(run.decisions, record.decision.kind);
|
|
336
341
|
}
|
|
337
342
|
}
|
|
338
|
-
const
|
|
343
|
+
const byAttempt = /* @__PURE__ */ new Map();
|
|
339
344
|
for (const node of nodes.values()) {
|
|
340
|
-
const
|
|
345
|
+
const key = attemptKey(node.runId, node.attemptIndex);
|
|
346
|
+
const list = byAttempt.get(key);
|
|
341
347
|
if (list) list.push(node);
|
|
342
|
-
else
|
|
348
|
+
else byAttempt.set(key, [node]);
|
|
343
349
|
}
|
|
344
350
|
const first = verified[0];
|
|
345
351
|
const last = verified.at(-1);
|
|
@@ -349,17 +355,37 @@ function projectPursuit(records) {
|
|
|
349
355
|
chainTip: last.digest,
|
|
350
356
|
firstObservedAt: first.observedAt,
|
|
351
357
|
lastObservedAt: last.observedAt,
|
|
352
|
-
runs: Object.freeze([...
|
|
358
|
+
runs: Object.freeze([...attempts.values()].flat().sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId)).map((run) => freezeRun(run, byAttempt.get(attemptKey(run.runId, run.attemptIndex)) ?? []))),
|
|
353
359
|
nodes: Object.freeze([...nodes.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId) || a.id.localeCompare(b.id)).map(freezeNode)),
|
|
354
360
|
eventCount,
|
|
355
361
|
decisionCount
|
|
356
362
|
});
|
|
357
363
|
}
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
364
|
+
function attemptKey(runId, attemptIndex) {
|
|
365
|
+
return `${runId}\u0000${attemptIndex}`;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The attempt a record belongs to. An `agent.run` `before` opens a new attempt when the run has
|
|
369
|
+
* none or its latest attempt already settled; on an open attempt it is a resume. Every other
|
|
370
|
+
* record joins the latest attempt, and a journal that never emitted `before` gets attempt 0.
|
|
371
|
+
*/
|
|
372
|
+
function currentAttempt(attempts, runId, record) {
|
|
373
|
+
const list = attempts.get(runId) ?? [];
|
|
374
|
+
if (list.length === 0) attempts.set(runId, list);
|
|
375
|
+
const latest = list.at(-1);
|
|
376
|
+
const isStart = record.event?.target === "agent.run" && record.event.phase === "before";
|
|
377
|
+
if (latest !== void 0 && (!isStart || latest.status === "running")) {
|
|
378
|
+
if (isStart) {
|
|
379
|
+
if (latest.started) latest.resumeCount += 1;
|
|
380
|
+
latest.started = true;
|
|
381
|
+
}
|
|
382
|
+
return latest;
|
|
383
|
+
}
|
|
361
384
|
const created = {
|
|
362
385
|
runId,
|
|
386
|
+
attemptIndex: list.length,
|
|
387
|
+
resumeCount: 0,
|
|
388
|
+
started: isStart,
|
|
363
389
|
status: "running",
|
|
364
390
|
firstSequence: record.sequence,
|
|
365
391
|
lastSequence: record.sequence,
|
|
@@ -370,7 +396,7 @@ function getRun(runs, runId, record) {
|
|
|
370
396
|
targets: {},
|
|
371
397
|
decisions: {}
|
|
372
398
|
};
|
|
373
|
-
|
|
399
|
+
list.push(created);
|
|
374
400
|
return created;
|
|
375
401
|
}
|
|
376
402
|
function projectRunActivity(run, record) {
|
|
@@ -392,7 +418,7 @@ function projectRunActivity(run, record) {
|
|
|
392
418
|
function nodeKey(runId, nodeId) {
|
|
393
419
|
return `${runId}\u0000${nodeId}`;
|
|
394
420
|
}
|
|
395
|
-
function projectSpawnNode(nodes, record) {
|
|
421
|
+
function projectSpawnNode(nodes, record, attemptIndex) {
|
|
396
422
|
const event = record.event;
|
|
397
423
|
if (event?.target !== "agent.spawn") return;
|
|
398
424
|
const payload = objectRecord(event.payload);
|
|
@@ -416,6 +442,7 @@ function projectSpawnNode(nodes, record) {
|
|
|
416
442
|
id: childId,
|
|
417
443
|
...event.parentId ? { parentId: event.parentId } : {},
|
|
418
444
|
runId: event.runId,
|
|
445
|
+
attemptIndex,
|
|
419
446
|
...label ? { label } : {},
|
|
420
447
|
...runtime ? { runtime } : {},
|
|
421
448
|
...depth !== void 0 ? { depth } : {},
|
|
@@ -507,7 +534,7 @@ function attachSettlementEvidence(node, payload) {
|
|
|
507
534
|
* root drove belongs to no spawned node and lands on the run instead, so the run's totals stay
|
|
508
535
|
* complete without inventing a node for the root.
|
|
509
536
|
*/
|
|
510
|
-
function projectTurn(
|
|
537
|
+
function projectTurn(run, nodes, record) {
|
|
511
538
|
const event = record.event;
|
|
512
539
|
if (event?.target !== "agent.turn") return;
|
|
513
540
|
const payload = objectRecord(event.payload);
|
|
@@ -517,8 +544,6 @@ function projectTurn(runs, nodes, record) {
|
|
|
517
544
|
const node = nodes.get(nodeKey(event.runId, subject));
|
|
518
545
|
if (!node) {
|
|
519
546
|
if (!spend) return;
|
|
520
|
-
const run = runs.get(event.runId);
|
|
521
|
-
if (!run) return;
|
|
522
547
|
run.rootInference = run.rootInference ? addSpend(run.rootInference, spend) : spend;
|
|
523
548
|
return;
|
|
524
549
|
}
|
|
@@ -679,7 +704,7 @@ function timingOf(node) {
|
|
|
679
704
|
}
|
|
680
705
|
function freezeRun(run, nodes) {
|
|
681
706
|
const gaps = runSpendGaps(nodes);
|
|
682
|
-
const { rootInference: _rootInference, ...rest } = run;
|
|
707
|
+
const { rootInference: _rootInference, started: _started, ...rest } = run;
|
|
683
708
|
return Object.freeze({
|
|
684
709
|
...rest,
|
|
685
710
|
targets: Object.freeze({ ...run.targets }),
|
|
@@ -689,7 +714,7 @@ function freezeRun(run, nodes) {
|
|
|
689
714
|
});
|
|
690
715
|
}
|
|
691
716
|
function freezeNode(node) {
|
|
692
|
-
const { modelCalls, reasoningTokens, startedAt: _startedAt, ...rest } = node;
|
|
717
|
+
const { modelCalls, reasoningTokens, startedAt: _startedAt, attemptIndex: _attemptIndex, ...rest } = node;
|
|
693
718
|
const total = nodeTotal(node);
|
|
694
719
|
const timing = timingOf(node);
|
|
695
720
|
return Object.freeze({
|
|
@@ -732,16 +757,308 @@ function booleanField(value, key) {
|
|
|
732
757
|
return typeof field === "boolean" ? field : void 0;
|
|
733
758
|
}
|
|
734
759
|
//#endregion
|
|
760
|
+
//#region src/durable/run-lock.ts
|
|
761
|
+
/** The lock file `supervisePursuit` holds inside a run directory for the life of one call. */
|
|
762
|
+
const RUN_DIRECTORY_LOCK_FILE = "supervise.lock";
|
|
763
|
+
/** The directory is held by a live process. `holder` is what that process recorded. */
|
|
764
|
+
var RunDirectoryLockedError = class extends Error {
|
|
765
|
+
path;
|
|
766
|
+
holder;
|
|
767
|
+
constructor(path, holder) {
|
|
768
|
+
super(`supervisePursuit: ${path} is held by pid ${holder.pid} (run '${holder.runId}', since ${holder.startedAt}); one process drives a run directory at a time`);
|
|
769
|
+
this.name = "RunDirectoryLockedError";
|
|
770
|
+
this.path = path;
|
|
771
|
+
this.holder = holder;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* Take `runDir/supervise.lock`, or refuse.
|
|
776
|
+
*
|
|
777
|
+
* The file is published with its full content or not at all, so a contender never reads a
|
|
778
|
+
* half-written holder. A lock whose holder is gone (its pid no longer exists, or the pid now
|
|
779
|
+
* belongs to a process with a different start token) is stale and is removed under the mutation guard;
|
|
780
|
+
* a pid this process may not signal (`EPERM`) is alive and refuses. An empty file names no
|
|
781
|
+
* holder and is reclaimed. A file with unreadable content is left in place and refused:
|
|
782
|
+
* reclaiming it could evict a live holder written by something other than this module.
|
|
783
|
+
*/
|
|
784
|
+
async function acquireRunDirectoryLock(runDir, runId, now = Date.now) {
|
|
785
|
+
const dir = resolve(runDir);
|
|
786
|
+
const path = resolve(dir, RUN_DIRECTORY_LOCK_FILE);
|
|
787
|
+
await mkdir(dir, { recursive: true });
|
|
788
|
+
const processStart = await readProcessStart(process.pid);
|
|
789
|
+
const holder = Object.freeze({
|
|
790
|
+
pid: process.pid,
|
|
791
|
+
startedAt: new Date(now()).toISOString(),
|
|
792
|
+
runId,
|
|
793
|
+
...processStart === void 0 ? {} : { processStart }
|
|
794
|
+
});
|
|
795
|
+
return withMutationGuard(path, async () => {
|
|
796
|
+
const existing = await readLockFile(path);
|
|
797
|
+
if (existing.state === "holder" && await holderIsLive(existing.holder)) throw new RunDirectoryLockedError(path, existing.holder);
|
|
798
|
+
if (existing.state !== "missing") await removeIfPresent(path);
|
|
799
|
+
if (!publishExclusiveDurableFile(path, `${JSON.stringify(holder)}\n`)) throw new Error(`supervisePursuit: could not take ${path}`);
|
|
800
|
+
let released = false;
|
|
801
|
+
return Object.freeze({
|
|
802
|
+
...holder,
|
|
803
|
+
path,
|
|
804
|
+
release: async () => {
|
|
805
|
+
if (released) return;
|
|
806
|
+
await releaseHolder(path, holder);
|
|
807
|
+
released = true;
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/** Read the holder a lock file names, or `undefined` when no lock file names one. */
|
|
813
|
+
async function readRunDirectoryLock(runDir) {
|
|
814
|
+
const existing = await readLockFile(resolve(runDir, RUN_DIRECTORY_LOCK_FILE));
|
|
815
|
+
return existing.state === "holder" ? existing.holder : void 0;
|
|
816
|
+
}
|
|
817
|
+
const execFileAsync = promisify(execFile);
|
|
818
|
+
/**
|
|
819
|
+
* The OS's start token for a live pid: on Linux the `starttime` field of `/proc/<pid>/stat`
|
|
820
|
+
* (clock ticks since boot), elsewhere the `ps` `lstart` column. The same process reports the
|
|
821
|
+
* same token for its whole life and a later holder of the pid reports a different one.
|
|
822
|
+
* `undefined` when the host cannot report one: the pid is gone, there is no `/proc` and no
|
|
823
|
+
* `ps`, or the platform is Windows.
|
|
824
|
+
*/
|
|
825
|
+
async function readProcessStart(pid) {
|
|
826
|
+
if (!Number.isInteger(pid) || pid <= 0 || process.platform === "win32") return void 0;
|
|
827
|
+
if (process.platform === "linux") {
|
|
828
|
+
let stat;
|
|
829
|
+
try {
|
|
830
|
+
stat = await readFile(`/proc/${pid}/stat`, "utf8");
|
|
831
|
+
} catch {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const startTime = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u)[19];
|
|
835
|
+
return startTime !== void 0 && /^\d+$/u.test(startTime) ? startTime : void 0;
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
const { stdout } = await execFileAsync("ps", [
|
|
839
|
+
"-o",
|
|
840
|
+
"lstart=",
|
|
841
|
+
"-p",
|
|
842
|
+
String(pid)
|
|
843
|
+
]);
|
|
844
|
+
const token = stdout.trim();
|
|
845
|
+
return token.length === 0 ? void 0 : token;
|
|
846
|
+
} catch {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
async function readLockFile(path) {
|
|
851
|
+
let text;
|
|
852
|
+
try {
|
|
853
|
+
text = await readFile(path, "utf8");
|
|
854
|
+
} catch (error) {
|
|
855
|
+
if (isNoEntError(error)) return { state: "missing" };
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
858
|
+
if (text.trim().length === 0) return { state: "empty" };
|
|
859
|
+
let parsed;
|
|
860
|
+
try {
|
|
861
|
+
parsed = JSON.parse(text);
|
|
862
|
+
} catch (cause) {
|
|
863
|
+
throw new Error(`supervisePursuit: ${path} is not a readable lock file; remove it by hand`, { cause });
|
|
864
|
+
}
|
|
865
|
+
const record = parsed;
|
|
866
|
+
if (typeof record !== "object" || record === null || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0 || typeof record.startedAt !== "string" || typeof record.runId !== "string" || record.processStart !== void 0 && typeof record.processStart !== "string") throw new Error(`supervisePursuit: ${path} is not a readable lock file; remove it by hand`);
|
|
867
|
+
const { pid, startedAt, runId, processStart } = record;
|
|
868
|
+
return {
|
|
869
|
+
state: "holder",
|
|
870
|
+
holder: Object.freeze({
|
|
871
|
+
pid,
|
|
872
|
+
startedAt,
|
|
873
|
+
runId,
|
|
874
|
+
...processStart === void 0 ? {} : { processStart }
|
|
875
|
+
})
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
async function holderIsLive(holder) {
|
|
879
|
+
if (!processExists(holder.pid)) return false;
|
|
880
|
+
if (holder.processStart === void 0) return true;
|
|
881
|
+
const current = await readProcessStart(holder.pid);
|
|
882
|
+
return current === void 0 || current === holder.processStart;
|
|
883
|
+
}
|
|
884
|
+
function processExists(pid) {
|
|
885
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
886
|
+
try {
|
|
887
|
+
process.kill(pid, 0);
|
|
888
|
+
return true;
|
|
889
|
+
} catch (error) {
|
|
890
|
+
return error.code !== "ESRCH";
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
/** Remove the lock only while it still names this holder; a reclaimed lock belongs to its new owner. */
|
|
894
|
+
async function releaseHolder(path, holder) {
|
|
895
|
+
await withMutationGuard(path, async () => {
|
|
896
|
+
const existing = await readLockFile(path);
|
|
897
|
+
if (existing.state !== "holder") return;
|
|
898
|
+
if (existing.holder.pid !== holder.pid || existing.holder.startedAt !== holder.startedAt || existing.holder.runId !== holder.runId || existing.holder.processStart !== holder.processStart) return;
|
|
899
|
+
await removeIfPresent(path);
|
|
900
|
+
}, true);
|
|
901
|
+
}
|
|
902
|
+
/** Serialize stale reclamation and release; an abandoned guard requires operator recovery. */
|
|
903
|
+
async function withMutationGuard(path, mutate, waitForRelease = false) {
|
|
904
|
+
const guard = `${path}.guard`;
|
|
905
|
+
const deadline = performance.now() + (waitForRelease ? 1e3 : 0);
|
|
906
|
+
for (;;) try {
|
|
907
|
+
await mkdir(guard);
|
|
908
|
+
break;
|
|
909
|
+
} catch (cause) {
|
|
910
|
+
if (waitForRelease && cause.code === "EEXIST" && performance.now() < deadline) {
|
|
911
|
+
await setTimeout(10);
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
throw new Error(`supervisePursuit: cannot mutate ${path}; inspect ${guard} and remove it only after confirming no lock mutation is active`, { cause });
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
return await mutate();
|
|
918
|
+
} finally {
|
|
919
|
+
await rmdir(guard);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
async function removeIfPresent(path) {
|
|
923
|
+
try {
|
|
924
|
+
await unlink(path);
|
|
925
|
+
} catch (error) {
|
|
926
|
+
if (!isNoEntError(error)) throw error;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/durable/settle-record.ts
|
|
931
|
+
/** The settle record: the returned `SupervisedResult` as canonical JSON, written once. */
|
|
932
|
+
const SETTLE_RECORD_FILE = "result.json";
|
|
933
|
+
/** The failure record: the most recent throw, replaced by a later throw. */
|
|
934
|
+
const FAILURE_RECORD_FILE = "failure.json";
|
|
935
|
+
/** The directory already holds a settle record, so the run it records must not be re-entered. */
|
|
936
|
+
var SettledRunDirectoryError = class extends Error {
|
|
937
|
+
path;
|
|
938
|
+
/** The root of the recorded run, which is its `runId`. */
|
|
939
|
+
recordedRunId;
|
|
940
|
+
constructor(path, recordedRunId, requestedRunId) {
|
|
941
|
+
super(recordedRunId === requestedRunId ? `supervisePursuit: ${path} records that run '${recordedRunId}' already settled; a settled run is not re-entered` : `supervisePursuit: ${path} records that run '${recordedRunId}' already settled in this directory; a directory holds one settle record, so run '${requestedRunId}' needs its own runDir`);
|
|
942
|
+
this.name = "SettledRunDirectoryError";
|
|
943
|
+
this.path = path;
|
|
944
|
+
this.recordedRunId = recordedRunId;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
/** A result member whose JSON form would misstate it. */
|
|
948
|
+
var UnrecordableSettleValueError = class extends TypeError {
|
|
949
|
+
path;
|
|
950
|
+
constructor(path, kind) {
|
|
951
|
+
super(`supervisePursuit: result${path} is a ${kind}, which JSON cannot record faithfully; a settle record holds plain data only`);
|
|
952
|
+
this.name = "UnrecordableSettleValueError";
|
|
953
|
+
this.path = path;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
const PLAIN_PROTOTYPES = /* @__PURE__ */ new Set([
|
|
957
|
+
Object.prototype,
|
|
958
|
+
Array.prototype,
|
|
959
|
+
null
|
|
960
|
+
]);
|
|
961
|
+
/**
|
|
962
|
+
* The JSON value of a result, or a throw naming the first member JSON would misstate. An
|
|
963
|
+
* `undefined` member and a function are dropped, exactly as any JSON consumer drops them; a Map,
|
|
964
|
+
* a Set, a Date, a typed array, or a class instance would be rewritten to `{}` or a string and
|
|
965
|
+
* so is refused.
|
|
966
|
+
*/
|
|
967
|
+
function jsonValue(result) {
|
|
968
|
+
const seen = [];
|
|
969
|
+
const visit = (value, path) => {
|
|
970
|
+
if (value === null || typeof value !== "object") return;
|
|
971
|
+
if (value instanceof Map) throw new UnrecordableSettleValueError(path, "Map");
|
|
972
|
+
if (value instanceof Set) throw new UnrecordableSettleValueError(path, "Set");
|
|
973
|
+
if (value instanceof Date) throw new UnrecordableSettleValueError(path, "Date");
|
|
974
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) throw new UnrecordableSettleValueError(path, "binary buffer");
|
|
975
|
+
if (!PLAIN_PROTOTYPES.has(Object.getPrototypeOf(value))) throw new UnrecordableSettleValueError(path, "class instance");
|
|
976
|
+
if (seen.includes(value)) throw new UnrecordableSettleValueError(path, "cycle");
|
|
977
|
+
seen.push(value);
|
|
978
|
+
if (Array.isArray(value)) for (const [index, item] of value.entries()) visit(item, `${path}[${index}]`);
|
|
979
|
+
else for (const [key, item] of Object.entries(value)) visit(item, `${path}.${key}`);
|
|
980
|
+
seen.pop();
|
|
981
|
+
};
|
|
982
|
+
visit(result, "");
|
|
983
|
+
return JSON.parse(JSON.stringify(result));
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* The exact bytes `result.json` holds for a result: its JSON value serialized as RFC 8785
|
|
987
|
+
* canonical JSON. Throws `UnrecordableSettleValueError` before any byte is written when the
|
|
988
|
+
* result carries a value JSON would misstate.
|
|
989
|
+
*/
|
|
990
|
+
function settleRecordJson(result) {
|
|
991
|
+
return canonicalCandidateJson(jsonValue(result));
|
|
992
|
+
}
|
|
993
|
+
/** Publish `runDir/result.json` once. Throws when a record is already there. */
|
|
994
|
+
async function writeSettleRecord(runDir, result) {
|
|
995
|
+
const path = resolve(runDir, SETTLE_RECORD_FILE);
|
|
996
|
+
if (!publishExclusiveDurableFile(path, settleRecordJson(result))) throw new Error(`supervisePursuit: ${path} already exists; a settle record is written once`);
|
|
997
|
+
return path;
|
|
998
|
+
}
|
|
999
|
+
/** Replace `runDir/failure.json` with the most recent throw. */
|
|
1000
|
+
async function writeFailureRecord(runDir, record) {
|
|
1001
|
+
const path = resolve(runDir, FAILURE_RECORD_FILE);
|
|
1002
|
+
writeAtomicDurableFile(path, `${JSON.stringify(record, null, 2)}\n`);
|
|
1003
|
+
return path;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Read the settle record a run directory holds, or `undefined` when it holds none. A file that
|
|
1007
|
+
* is present but is not a settle record is corruption and fails loud.
|
|
1008
|
+
*/
|
|
1009
|
+
async function readSettleRecord(runDir) {
|
|
1010
|
+
const path = resolve(runDir, SETTLE_RECORD_FILE);
|
|
1011
|
+
let text;
|
|
1012
|
+
try {
|
|
1013
|
+
text = await readFile(path, "utf8");
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
if (isNoEntError(error)) return void 0;
|
|
1016
|
+
throw error;
|
|
1017
|
+
}
|
|
1018
|
+
let parsed;
|
|
1019
|
+
try {
|
|
1020
|
+
parsed = JSON.parse(text);
|
|
1021
|
+
} catch (cause) {
|
|
1022
|
+
throw new Error(`supervisePursuit: ${path} is not valid JSON`, { cause });
|
|
1023
|
+
}
|
|
1024
|
+
const record = parsed;
|
|
1025
|
+
if (typeof record !== "object" || record === null || typeof record.kind !== "string" || typeof record.tree !== "object" || record.tree === null || typeof record.tree.root !== "string") throw new Error(`supervisePursuit: ${path} is not a settle record (no kind or tree.root)`);
|
|
1026
|
+
return parsed;
|
|
1027
|
+
}
|
|
1028
|
+
/** Read the most recent failure record, or `undefined` when the directory holds none. */
|
|
1029
|
+
async function readFailureRecord(runDir) {
|
|
1030
|
+
const path = resolve(runDir, FAILURE_RECORD_FILE);
|
|
1031
|
+
let text;
|
|
1032
|
+
try {
|
|
1033
|
+
text = await readFile(path, "utf8");
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
if (isNoEntError(error)) return void 0;
|
|
1036
|
+
throw error;
|
|
1037
|
+
}
|
|
1038
|
+
let parsed;
|
|
1039
|
+
try {
|
|
1040
|
+
parsed = JSON.parse(text);
|
|
1041
|
+
} catch (cause) {
|
|
1042
|
+
throw new Error(`supervisePursuit: ${path} is not valid JSON`, { cause });
|
|
1043
|
+
}
|
|
1044
|
+
const record = parsed;
|
|
1045
|
+
if (typeof record !== "object" || record === null || typeof record.runId !== "string" || typeof record.pursuitId !== "string" || typeof record.at !== "string" || typeof record.error?.name !== "string" || typeof record.error.message !== "string") throw new Error(`supervisePursuit: ${path} is not a failure record`);
|
|
1046
|
+
return parsed;
|
|
1047
|
+
}
|
|
1048
|
+
//#endregion
|
|
735
1049
|
//#region src/durable/supervise-pursuit.ts
|
|
736
1050
|
/** A failed Runtime execution whose complete third-person projection was retained. */
|
|
737
1051
|
var SupervisePursuitError = class extends Error {
|
|
738
1052
|
pursuit;
|
|
739
1053
|
observerPath;
|
|
740
|
-
|
|
1054
|
+
/** `runDir/failure.json`: the record of this throw. */
|
|
1055
|
+
failurePath;
|
|
1056
|
+
constructor(cause, pursuit, observerPath, failurePath) {
|
|
741
1057
|
super(`supervisePursuit: ${errorMessage(cause)}`, { cause });
|
|
742
1058
|
this.name = "SupervisePursuitError";
|
|
743
1059
|
this.pursuit = pursuit;
|
|
744
1060
|
this.observerPath = observerPath;
|
|
1061
|
+
this.failurePath = failurePath;
|
|
745
1062
|
}
|
|
746
1063
|
};
|
|
747
1064
|
/**
|
|
@@ -755,6 +1072,14 @@ var SupervisePursuitError = class extends Error {
|
|
|
755
1072
|
* Every concrete execution writes only inside its own `runDir`. Cross-run pursuit
|
|
756
1073
|
* aggregation is therefore lock-free at the observer layer: reuse `pursuitId` across
|
|
757
1074
|
* run directories and let Intelligence join the independently verified projections.
|
|
1075
|
+
*
|
|
1076
|
+
* The directory's terminal state is recorded beside `observer.jsonl`: `result.json` holds the
|
|
1077
|
+
* returned result once the run settles and `failure.json` the most recent throw. A directory
|
|
1078
|
+
* whose `result.json` exists refuses re-entry before any compute; a failure record alone does
|
|
1079
|
+
* not, because a caller can correct its input and drive the same run again. For the life of the
|
|
1080
|
+
* call the directory is held by `supervise.lock`, so a second process on the same directory
|
|
1081
|
+
* refuses and names the holder instead of sharing one journal.
|
|
1082
|
+
* An abandoned `supervise.lock.guard` requires removal after confirming no lock mutation is active.
|
|
758
1083
|
*/
|
|
759
1084
|
async function supervisePursuit(profile, task, opts) {
|
|
760
1085
|
const pursuitId = opts.pursuitId.trim();
|
|
@@ -762,36 +1087,65 @@ async function supervisePursuit(profile, task, opts) {
|
|
|
762
1087
|
const runDir = opts.runDir.trim();
|
|
763
1088
|
if (runDir.length === 0) throw new TypeError("supervisePursuit: runDir must be non-empty");
|
|
764
1089
|
const observerPath = resolve(runDir, "observer.jsonl");
|
|
1090
|
+
const settlePath = resolve(runDir, SETTLE_RECORD_FILE);
|
|
1091
|
+
const failurePath = resolve(runDir, FAILURE_RECORD_FILE);
|
|
765
1092
|
const { pursuitId: _pursuitId, hooks, ...superviseOptions } = opts;
|
|
766
|
-
const observer = createFileObserverHooks(observerPath, pursuitId);
|
|
767
1093
|
const runId = superviseOptions.runId ?? "supervise";
|
|
768
1094
|
const now = superviseOptions.now ?? Date.now;
|
|
769
|
-
await
|
|
1095
|
+
const lock = await acquireRunDirectoryLock(runDir, runId, now);
|
|
770
1096
|
try {
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1097
|
+
const settled = await readSettleRecord(runDir);
|
|
1098
|
+
if (settled !== void 0) throw new SettledRunDirectoryError(settlePath, settled.tree.root, runId);
|
|
1099
|
+
const observer = createFileObserverHooks(observerPath, pursuitId);
|
|
1100
|
+
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "before", now()));
|
|
1101
|
+
let result;
|
|
1102
|
+
try {
|
|
1103
|
+
result = await supervise(profile, task, {
|
|
1104
|
+
...superviseOptions,
|
|
1105
|
+
hooks: withPursuitContext(pursuitId, composeRuntimeHooks(observer.hooks, hooks))
|
|
1106
|
+
});
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
let pursuit;
|
|
1109
|
+
let observerError;
|
|
1110
|
+
try {
|
|
1111
|
+
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "error", now(), {
|
|
1112
|
+
status: "failed",
|
|
1113
|
+
error: errorMessage(error)
|
|
1114
|
+
}));
|
|
1115
|
+
pursuit = projectPursuit(await observer.journal.read());
|
|
1116
|
+
} catch (failure) {
|
|
1117
|
+
observerError = failure;
|
|
1118
|
+
}
|
|
1119
|
+
if (observerError !== void 0 || pursuit === void 0) throw new Error("supervisePursuit: Runtime failed and durable observer completeness could not be proven", { cause: new AggregateError(observerError === void 0 ? [error] : [error, observerError]) });
|
|
1120
|
+
try {
|
|
1121
|
+
await writeFailureRecord(runDir, {
|
|
1122
|
+
runId,
|
|
1123
|
+
pursuitId,
|
|
1124
|
+
at: new Date(now()).toISOString(),
|
|
1125
|
+
error: {
|
|
1126
|
+
name: errorName(error),
|
|
1127
|
+
message: errorMessage(error)
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
} catch (recordError) {
|
|
1131
|
+
throw new Error(`supervisePursuit: Runtime failed and the failure record ${failurePath} could not be written`, { cause: new AggregateError([error, recordError]) });
|
|
1132
|
+
}
|
|
1133
|
+
throw new SupervisePursuitError(error, pursuit, observerPath, failurePath);
|
|
1134
|
+
}
|
|
1135
|
+
try {
|
|
1136
|
+
await writeSettleRecord(runDir, result);
|
|
1137
|
+
} catch (cause) {
|
|
1138
|
+
throw new Error(`supervisePursuit: Runtime settled but the settle record ${settlePath} could not be written`, { cause });
|
|
1139
|
+
}
|
|
775
1140
|
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "after", now(), { status: "done" }));
|
|
776
1141
|
return Object.freeze({
|
|
777
1142
|
result,
|
|
778
1143
|
pursuit: projectPursuit(await observer.journal.read()),
|
|
779
|
-
observerPath
|
|
1144
|
+
observerPath,
|
|
1145
|
+
settlePath
|
|
780
1146
|
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
let observerError;
|
|
784
|
-
try {
|
|
785
|
-
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "error", now(), {
|
|
786
|
-
status: "failed",
|
|
787
|
-
error: errorMessage(error)
|
|
788
|
-
}));
|
|
789
|
-
pursuit = projectPursuit(await observer.journal.read());
|
|
790
|
-
} catch (failure) {
|
|
791
|
-
observerError = failure;
|
|
792
|
-
}
|
|
793
|
-
if (observerError !== void 0 || pursuit === void 0) throw new Error("supervisePursuit: Runtime failed and durable observer completeness could not be proven", { cause: new AggregateError(observerError === void 0 ? [error] : [error, observerError]) });
|
|
794
|
-
throw new SupervisePursuitError(error, pursuit, observerPath);
|
|
1147
|
+
} finally {
|
|
1148
|
+
await lock.release();
|
|
795
1149
|
}
|
|
796
1150
|
}
|
|
797
1151
|
function rootEvent(pursuitId, runId, phase, timestamp, payload) {
|
|
@@ -808,6 +1162,9 @@ function rootEvent(pursuitId, runId, phase, timestamp, payload) {
|
|
|
808
1162
|
function errorMessage(error) {
|
|
809
1163
|
return error instanceof Error ? error.message : String(error);
|
|
810
1164
|
}
|
|
1165
|
+
function errorName(error) {
|
|
1166
|
+
return error instanceof Error ? error.name : "NonError";
|
|
1167
|
+
}
|
|
811
1168
|
//#endregion
|
|
812
1169
|
//#region src/durable/supervision-discovery.ts
|
|
813
1170
|
/**
|
|
@@ -885,6 +1242,6 @@ function compareText(left, right) {
|
|
|
885
1242
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
886
1243
|
}
|
|
887
1244
|
//#endregion
|
|
888
|
-
export { FileObserverJournal, SupervisePursuitError, createFileObserverHooks, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, supervisePursuit, verifyObserverRecords };
|
|
1245
|
+
export { FAILURE_RECORD_FILE, FileObserverJournal, RUN_DIRECTORY_LOCK_FILE, RunDirectoryLockedError, SETTLE_RECORD_FILE, SettledRunDirectoryError, SupervisePursuitError, acquireRunDirectoryLock, createFileObserverHooks, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, readFailureRecord, readRunDirectoryLock, readSettleRecord, settleRecordJson, supervisePursuit, verifyObserverRecords };
|
|
889
1246
|
|
|
890
1247
|
//# sourceMappingURL=durable.js.map
|