@tangle-network/agent-runtime 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activation-BRASQAee.d.ts → activation-BpkpEn0T.d.ts} +2 -2
- package/dist/{activation-D7-ZwyKD.js → activation-D9KoX_R4.js} +2 -2
- package/dist/{activation-D7-ZwyKD.js.map → activation-D9KoX_R4.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -3
- package/dist/{coordination-manager-DbNKO53e.js → coordination-manager-CqZGCqjV.js} +4 -4
- package/dist/{coordination-manager-DbNKO53e.js.map → coordination-manager-CqZGCqjV.js.map} +1 -1
- package/dist/{delegate-CBFbETCq.js → delegate-B5XFrkTg.js} +2 -2
- package/dist/{delegate-CBFbETCq.js.map → delegate-B5XFrkTg.js.map} +1 -1
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +6 -6
- package/dist/{graph-D05w51EQ.js → graph-Ds6VUJzW.js} +5 -5
- package/dist/{graph-D05w51EQ.js.map → graph-Ds6VUJzW.js.map} +1 -1
- package/dist/{improve-Byp45uiM.js → improve-DbfENElN.js} +5 -5
- package/dist/{improve-Byp45uiM.js.map → improve-DbfENElN.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +3 -3
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +11 -11
- package/dist/{loop-runner-DBH2LY57.js → loop-runner-CL_5TsT8.js} +2 -2
- package/dist/{loop-runner-DBH2LY57.js.map → loop-runner-CL_5TsT8.js.map} +1 -1
- package/dist/{loop-runner-Bjy-ALf4.d.ts → loop-runner-DjYZrgqT.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 +4 -4
- package/dist/profiles.d.ts +1 -1
- package/dist/{provision-supervisor-Bvqnu3xm.js → provision-supervisor-Cmej51pt.js} +4 -5
- package/dist/{provision-supervisor-Bvqnu3xm.js.map → provision-supervisor-Cmej51pt.js.map} +1 -1
- package/dist/{journal-store-E6TojmWB.js → root-stream-To4qAc0e.js} +710 -8
- package/dist/root-stream-To4qAc0e.js.map +1 -0
- package/dist/run-cancellation-BhP0Nw6G.js +229 -0
- package/dist/run-cancellation-BhP0Nw6G.js.map +1 -0
- package/dist/{runtime-DlmYZZpp.js → runtime-Dm8QlK_z.js} +11 -11
- package/dist/{runtime-DlmYZZpp.js.map → runtime-Dm8QlK_z.js.map} +1 -1
- package/dist/{search-executor-DWFivIJq.js → search-executor-CN2Axxt9.js} +3 -3
- package/dist/{search-executor-DWFivIJq.js.map → search-executor-CN2Axxt9.js.map} +1 -1
- package/dist/{server-DKYzcVEO.js → server-DlKT7AgQ.js} +3 -3
- package/dist/{server-DKYzcVEO.js.map → server-DlKT7AgQ.js.map} +1 -1
- package/dist/{structural-rollout-ChERY66G.js → structural-rollout-BPz5dLDB.js} +3 -3
- package/dist/{structural-rollout-ChERY66G.js.map → structural-rollout-BPz5dLDB.js.map} +1 -1
- package/dist/{supervise-DQkPeVi6.js → supervise-BtjM7_0G.js} +7 -185
- package/dist/supervise-BtjM7_0G.js.map +1 -0
- package/dist/{supervision-discovery-P89Ej0yJ.js → supervision-discovery-CVpmTroK.js} +2 -2
- package/dist/{supervision-discovery-P89Ej0yJ.js.map → supervision-discovery-CVpmTroK.js.map} +1 -1
- package/dist/{supervisor-DorLcfvJ.js → supervisor-BsW69Ccw.js} +123 -28
- package/dist/supervisor-BsW69Ccw.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +11 -11
- package/dist/{top-web-CWcNwaRb.js → top-web-Bjb_UlNI.js} +7 -4
- package/dist/top-web-Bjb_UlNI.js.map +1 -0
- package/dist/tui/bin.js +3 -2
- 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-C2fJMeEi.d.ts → types-BDmLQLNj.d.ts} +5 -4
- package/package.json +1 -1
- package/dist/journal-store-E6TojmWB.js.map +0 -1
- package/dist/run-cancellation-Cpm-BIUu.js +0 -743
- package/dist/run-cancellation-Cpm-BIUu.js.map +0 -1
- package/dist/supervise-DQkPeVi6.js.map +0 -1
- package/dist/supervisor-DorLcfvJ.js.map +0 -1
- package/dist/top-web-CWcNwaRb.js.map +0 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { m as ValidationError, r as BackendTransportError } from "./errors-AzSuGI23.js";
|
|
2
|
-
import { D as assertResources, E as addResourceSpend, T as zeroTokenUsage, i as chargedTokens, k as withBudgetResources, l as fnv1a32Hex, m as promptCacheTokenClasses, o as cloneTokenUsage, r as addTokenUsage, u as hasCompleteCacheBreakdown } from "./util-BX20UJ9X.js";
|
|
3
|
-
import { d as detachedSnapshot, m as errMessage, n as isNoEntError, o as readCommittedJsonLines, p as causeChain, t as appendCommittedJsonLine } from "./jsonl-file-BBP9Dz9w.js";
|
|
4
|
-
import { n as assertNoSymlinkDescendant, o as captureRunWriteContext, r as publishExclusiveDurableFile } from "./durable-file-DstVeHcj.js";
|
|
2
|
+
import { D as assertResources, E as addResourceSpend, T as zeroTokenUsage, c as errorMessage, i as chargedTokens, k as withBudgetResources, l as fnv1a32Hex, m as promptCacheTokenClasses, o as cloneTokenUsage, r as addTokenUsage, u as hasCompleteCacheBreakdown } from "./util-BX20UJ9X.js";
|
|
3
|
+
import { d as detachedSnapshot, i as prepareJsonlAppend, l as sha256, m as errMessage, n as isNoEntError, o as readCommittedJsonLines, p as causeChain, t as appendCommittedJsonLine } from "./jsonl-file-BBP9Dz9w.js";
|
|
4
|
+
import { a as writeAtomicDurableFile, n as assertNoSymlinkDescendant, o as captureRunWriteContext, r as publishExclusiveDurableFile, s as fenceRunWrite, t as appendDurableFile } from "./durable-file-DstVeHcj.js";
|
|
5
5
|
import { a as withPursuitContext } from "./runtime-hooks-CsW2pfFk.js";
|
|
6
|
-
import { AgentTurnResultSchema, CanonicalStreamEventSchema, RuntimeEventEnvelopeSchema } from "@tangle-network/agent-interface";
|
|
6
|
+
import { AgentTurnResultSchema, CanonicalStreamEventSchema, RuntimeEventEnvelopeSchema, canonicalCandidateDigest } from "@tangle-network/agent-interface";
|
|
7
7
|
import { isRecord } from "@tangle-network/agent-core";
|
|
8
8
|
import { collectAgentFinalMessageText, collectAgentResponseText } from "@tangle-network/sandbox";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { LedgerCanonicalizationError, canonicalString, jsonDocument, merkleLeafHash, merkleRoot, merkleSalt } from "@tangle-network/agent-eval/ledger-core";
|
|
12
|
-
import { fstatSync, statSync } from "node:fs";
|
|
13
|
-
import { resolve } from "node:path";
|
|
12
|
+
import { appendFileSync, closeSync, constants, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, statSync, writeSync } from "node:fs";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { mkdir } from "node:fs/promises";
|
|
14
15
|
// src/record-value.ts
|
|
15
16
|
function recordValue(value) {
|
|
16
17
|
return isRecord(value) ? value : void 0;
|
|
@@ -3545,6 +3546,707 @@ var FileSpawnJournal = class {
|
|
|
3545
3546
|
function journalFileStamp(stat) {
|
|
3546
3547
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
3547
3548
|
}
|
|
3548
|
-
|
|
3549
|
+
// src/runtime/supervise/run-layout.ts
|
|
3550
|
+
/**
|
|
3551
|
+
* The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.
|
|
3552
|
+
*
|
|
3553
|
+
* This is the durable, cross-process face of a supervisor run — the counterpart to the in-process
|
|
3554
|
+
* `Inbox` seam in `./inbox`. A run persists its state under one directory so that any OTHER process
|
|
3555
|
+
* can find it after the fact: `@tangle-network/traces` reads exactly this layout via
|
|
3556
|
+
* `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds
|
|
3557
|
+
* handles to, and a human can steer a live worker by appending one NDJSON line. Until now the
|
|
3558
|
+
* layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a
|
|
3559
|
+
* published reader depending on an unpublished writer's convention — so the contract is promoted
|
|
3560
|
+
* here, names preserved.
|
|
3561
|
+
*
|
|
3562
|
+
* `.agent` is the one dot-dir for ALL agent-owned state (skills already write
|
|
3563
|
+
* `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than
|
|
3564
|
+
* under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —
|
|
3565
|
+
* readers that must see those keep a legacy fallback; this writer never creates `.loops` again.
|
|
3566
|
+
*
|
|
3567
|
+
* Layout, relative to `supervisorRunDir(root, id)`:
|
|
3568
|
+
*
|
|
3569
|
+
* steers/requests/<hash>.json one exact, caller-idempotent {@link WorkerSteerRequest}
|
|
3570
|
+
* steers/acks/<hash>.json runtime acknowledgement for that steer operation
|
|
3571
|
+
* workers/<id>.inbox.ndjson best-effort readable projection of admitted steer requests
|
|
3572
|
+
* workers/<label>.ndjson best-effort per-worker control-event log (delivery bookkeeping)
|
|
3573
|
+
* cancellations/requests.ndjson worker-scoped cancel requests (durable inbox); each line is a
|
|
3574
|
+
* {@link WorkerCancelRequest}
|
|
3575
|
+
* cancellations/<opId>.json the acknowledgement for one cancel operation — a
|
|
3576
|
+
* {@link WorkerCancellation} written ONLY by the runtime
|
|
3577
|
+
* acknowledger in the OWNING manager's turn loop: exact node
|
|
3578
|
+
* ids route to the manager that parents them (any depth);
|
|
3579
|
+
* label/profile-name references to the root manager alone
|
|
3580
|
+
* cancellations/run.request.json the run-scoped cancel request — one {@link RunCancelRequest},
|
|
3581
|
+
* the whole RUN rather than one worker
|
|
3582
|
+
* cancellations/run.json the acknowledgement for the run-scoped request — a
|
|
3583
|
+
* {@link RunCancellation} written ONLY by the runtime (the root
|
|
3584
|
+
* manager issues the abort; the `supervise()` settle path records
|
|
3585
|
+
* what the run actually did)
|
|
3586
|
+
*
|
|
3587
|
+
* Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line
|
|
3588
|
+
* never poisons the rest of the file — later valid lines still matter. Duplicate operation ids
|
|
3589
|
+
* collapse to the first admitted request, while a payload conflict fails closed.
|
|
3590
|
+
*
|
|
3591
|
+
* Promoted from `loops/src/supervisor-control.ts`. Steer admission now requires the exact worker id
|
|
3592
|
+
* and a caller-owned operation id. One atomic request file is the durable inbox; the NDJSON file is
|
|
3593
|
+
* a readable projection only and never controls idempotency.
|
|
3594
|
+
*
|
|
3595
|
+
* @experimental
|
|
3596
|
+
*/
|
|
3597
|
+
/** The root every supervisor run of one workspace lives under. */
|
|
3598
|
+
function supervisorRunsRoot(rootDir) {
|
|
3599
|
+
return join(resolve(rootDir), ".agent", "supervisor");
|
|
3600
|
+
}
|
|
3601
|
+
/** The run directory every artifact of one supervisor run lives under. */
|
|
3602
|
+
function supervisorRunDir(rootDir, id) {
|
|
3603
|
+
return join(supervisorRunsRoot(rootDir), id);
|
|
3604
|
+
}
|
|
3605
|
+
/**
|
|
3606
|
+
* Where a pre-rename writer put the same run (`<root>/.loops/supervisor/<id>`). Readers that must
|
|
3607
|
+
* see historical runs check {@link supervisorRunDir} first and fall back to this; nothing writes
|
|
3608
|
+
* here anymore.
|
|
3609
|
+
*/
|
|
3610
|
+
function legacySupervisorRunDir(rootDir, id) {
|
|
3611
|
+
return join(legacySupervisorRunsRoot(rootDir), id);
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* The pre-rename runs root (`<root>/.loops/supervisor`). Only readers that ENUMERATE historical
|
|
3615
|
+
* runs need this — the per-id form is {@link legacySupervisorRunDir}. Nothing writes here.
|
|
3616
|
+
*/
|
|
3617
|
+
function legacySupervisorRunsRoot(rootDir) {
|
|
3618
|
+
return join(resolve(rootDir), ".loops", "supervisor");
|
|
3619
|
+
}
|
|
3620
|
+
/** A worker label reduced to a safe filename stem. Empty labels get a stable fallback. */
|
|
3621
|
+
function safeWorkerFile(label) {
|
|
3622
|
+
const safe = label.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
3623
|
+
return safe.length > 0 ? safe : "worker";
|
|
3624
|
+
}
|
|
3625
|
+
/** The directory holding every per-worker file of one run (inboxes and control-event logs). */
|
|
3626
|
+
function supervisorWorkersDir(eventDir) {
|
|
3627
|
+
return join(eventDir, "workers");
|
|
3628
|
+
}
|
|
3629
|
+
/** Same, addressed from an already-known run directory (the reader's usual entry point). */
|
|
3630
|
+
function workerInboxFileFromEventDir(eventDir, worker) {
|
|
3631
|
+
return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.inbox.ndjson`);
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* The best-effort control-event log for one worker (`workers/<label>.ndjson`) — delivery
|
|
3635
|
+
* bookkeeping for steers, plus whatever lifecycle events a writer chooses to append. Distinct from
|
|
3636
|
+
* the inbox: the inbox is the durable down-leg queue, this is the record of what happened to it.
|
|
3637
|
+
*/
|
|
3638
|
+
function workerControlLogFile(eventDir, worker) {
|
|
3639
|
+
return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.ndjson`);
|
|
3640
|
+
}
|
|
3641
|
+
/** Directory containing atomically admitted steer requests and runtime acknowledgements. */
|
|
3642
|
+
function workerSteersDir(eventDir) {
|
|
3643
|
+
return join(eventDir, "steers");
|
|
3644
|
+
}
|
|
3645
|
+
/** Directory containing one canonical request file per steer operation. */
|
|
3646
|
+
function workerSteerRequestsDir(eventDir) {
|
|
3647
|
+
return join(workerSteersDir(eventDir), "requests");
|
|
3648
|
+
}
|
|
3649
|
+
/** Directory containing one runtime acknowledgement per steer operation. */
|
|
3650
|
+
function workerSteerAcknowledgementsDir(eventDir) {
|
|
3651
|
+
return join(workerSteersDir(eventDir), "acks");
|
|
3652
|
+
}
|
|
3653
|
+
/** Canonical request file for one caller-owned steer operation id. */
|
|
3654
|
+
function workerSteerRequestFile(eventDir, operationId) {
|
|
3655
|
+
return join(workerSteerRequestsDir(eventDir), `${operationFileHash(operationId)}.json`);
|
|
3656
|
+
}
|
|
3657
|
+
/** Runtime acknowledgement file for one caller-owned steer operation id. */
|
|
3658
|
+
function workerSteerAcknowledgementFile(eventDir, operationId) {
|
|
3659
|
+
return join(workerSteerAcknowledgementsDir(eventDir), `${operationFileHash(operationId)}.json`);
|
|
3660
|
+
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Admit one steer exactly once under a caller-owned operation id.
|
|
3663
|
+
*
|
|
3664
|
+
* Pass `supervisorId` as `worker` to address the root. Router roots consume requests between
|
|
3665
|
+
* turns; native roots use their existing accepting inbox during execution, or acknowledge
|
|
3666
|
+
* `unsupported` when none is available. Delivery does not prove model consumption.
|
|
3667
|
+
* This filesystem API requires trusted write access to the run directory; it adds no MCP grant.
|
|
3668
|
+
*
|
|
3669
|
+
* The per-operation request file is linked into place atomically after its bytes reach disk. A
|
|
3670
|
+
* same-body retry returns the winner's request. A changed-body retry fails loud. The NDJSON inbox
|
|
3671
|
+
* and control log are readable projections written only by the admission winner.
|
|
3672
|
+
* @stable
|
|
3673
|
+
*/
|
|
3674
|
+
function writeWorkerSteer(rootDir, supervisorId, worker, options) {
|
|
3675
|
+
const workerId = worker.trim();
|
|
3676
|
+
if (!workerId) throw new Error("writeWorkerSteer: worker id is empty");
|
|
3677
|
+
const operationId = options.operationId.trim();
|
|
3678
|
+
if (!operationId) throw new Error("writeWorkerSteer: operationId is empty");
|
|
3679
|
+
const trimmed = options.message.trim();
|
|
3680
|
+
if (!trimmed) throw new Error("steer message is empty");
|
|
3681
|
+
const source = options.source?.trim() || "human";
|
|
3682
|
+
const interrupt = options.interrupt === true;
|
|
3683
|
+
if (options.eventDir !== void 0 && !options.eventDir.trim()) throw new Error("writeWorkerSteer: eventDir is empty");
|
|
3684
|
+
const dir = options.eventDir === void 0 ? supervisorRunDir(rootDir, supervisorId) : resolve(options.eventDir);
|
|
3685
|
+
const requestDigest = workerSteerRequestDigest({
|
|
3686
|
+
operationId,
|
|
3687
|
+
worker: workerId,
|
|
3688
|
+
message: trimmed,
|
|
3689
|
+
source,
|
|
3690
|
+
interrupt
|
|
3691
|
+
});
|
|
3692
|
+
const request = {
|
|
3693
|
+
schemaVersion: 1,
|
|
3694
|
+
operationId,
|
|
3695
|
+
requestDigest,
|
|
3696
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3697
|
+
source,
|
|
3698
|
+
worker: workerId,
|
|
3699
|
+
message: trimmed,
|
|
3700
|
+
interrupt
|
|
3701
|
+
};
|
|
3702
|
+
const file = workerSteerRequestFile(dir, operationId);
|
|
3703
|
+
assertNoSymlinkDescendant(dir, file, "steer request");
|
|
3704
|
+
mkdirSync(workerSteerRequestsDir(dir), { recursive: true });
|
|
3705
|
+
assertNoSymlinkDescendant(dir, file, "steer request");
|
|
3706
|
+
const admitted = admitSteerRequest(file, request);
|
|
3707
|
+
if (admitted.replayed) return {
|
|
3708
|
+
worker: admitted.request.worker,
|
|
3709
|
+
file,
|
|
3710
|
+
request: admitted.request,
|
|
3711
|
+
...readWorkerSteerAcknowledgement(dir, operationId) === void 0 ? {} : { acknowledgement: readWorkerSteerAcknowledgement(dir, operationId) },
|
|
3712
|
+
replayed: true
|
|
3713
|
+
};
|
|
3714
|
+
mkdirSync(supervisorWorkersDir(dir), { recursive: true });
|
|
3715
|
+
const projection = workerInboxFileFromEventDir(dir, workerId);
|
|
3716
|
+
try {
|
|
3717
|
+
fenceRunWrite(() => appendFileSync(projection, `${JSON.stringify(request)}\n`, "utf8"));
|
|
3718
|
+
} catch {}
|
|
3719
|
+
appendWorkerControlEvent(dir, workerId, {
|
|
3720
|
+
kind: "message",
|
|
3721
|
+
direction: "down",
|
|
3722
|
+
source,
|
|
3723
|
+
operationId,
|
|
3724
|
+
requestDigest,
|
|
3725
|
+
message: trimmed,
|
|
3726
|
+
interrupt,
|
|
3727
|
+
queued: true,
|
|
3728
|
+
delivered: false
|
|
3729
|
+
});
|
|
3730
|
+
return {
|
|
3731
|
+
worker: workerId,
|
|
3732
|
+
file,
|
|
3733
|
+
request,
|
|
3734
|
+
replayed: false
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
/** Read every atomically admitted request for one exact worker id, in admission order. @stable */
|
|
3738
|
+
function readWorkerSteerRequests(eventDir, worker) {
|
|
3739
|
+
const workerId = worker?.trim();
|
|
3740
|
+
if (worker !== void 0 && !workerId) return [];
|
|
3741
|
+
let names;
|
|
3742
|
+
assertNoSymlinkDescendant(eventDir, workerSteerRequestsDir(eventDir), "steer request");
|
|
3743
|
+
try {
|
|
3744
|
+
names = readdirSync(workerSteerRequestsDir(eventDir));
|
|
3745
|
+
} catch {
|
|
3746
|
+
return [];
|
|
3747
|
+
}
|
|
3748
|
+
const out = [];
|
|
3749
|
+
for (const name of names) {
|
|
3750
|
+
if (!name.endsWith(".json")) continue;
|
|
3751
|
+
try {
|
|
3752
|
+
const file = join(workerSteerRequestsDir(eventDir), name);
|
|
3753
|
+
assertNoSymlinkDescendant(eventDir, file, "steer request");
|
|
3754
|
+
const parsed = parseWorkerSteerRequest(JSON.parse(readFileSync(file, "utf8")));
|
|
3755
|
+
if (name !== `${operationFileHash(parsed.operationId)}.json`) continue;
|
|
3756
|
+
if (workerId === void 0 || parsed.worker === workerId) out.push(parsed);
|
|
3757
|
+
} catch {}
|
|
3758
|
+
}
|
|
3759
|
+
return out.sort((left, right) => left.at === right.at ? left.operationId.localeCompare(right.operationId) : left.at.localeCompare(right.at));
|
|
3760
|
+
}
|
|
3761
|
+
/** Read one runtime steer acknowledgement, or `undefined` while no manager has answered. @stable */
|
|
3762
|
+
function readWorkerSteerAcknowledgement(eventDir, operationId) {
|
|
3763
|
+
const id = operationId.trim();
|
|
3764
|
+
if (!id) throw new Error("readWorkerSteerAcknowledgement: operationId is empty");
|
|
3765
|
+
const file = workerSteerAcknowledgementFile(eventDir, id);
|
|
3766
|
+
assertNoSymlinkDescendant(eventDir, file, "steer acknowledgement");
|
|
3767
|
+
if (!existsSync(file)) return void 0;
|
|
3768
|
+
const record = parseWorkerSteerAcknowledgement(JSON.parse(readFileSync(file, "utf8")));
|
|
3769
|
+
if (record.operationId !== id) throw new Error(`steer acknowledgement '${file}' belongs to another operation`);
|
|
3770
|
+
return record;
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Claim one steer for delivery with an atomic no-clobber acknowledgement.
|
|
3774
|
+
* Returns `true` only to the process that created the pre-delivery record.
|
|
3775
|
+
* @internal
|
|
3776
|
+
*/
|
|
3777
|
+
function claimWorkerSteerDelivery(eventDir, record) {
|
|
3778
|
+
const exact = parseWorkerSteerAcknowledgement(record);
|
|
3779
|
+
if (exact.effect !== "unknown") throw new Error("steer delivery claim must use effect unknown");
|
|
3780
|
+
const request = readWorkerSteerRequest(eventDir, exact.operationId);
|
|
3781
|
+
if (request === void 0 || request.requestDigest !== exact.requestDigest) throw new Error("steer delivery claim does not match an admitted request");
|
|
3782
|
+
const dir = workerSteerAcknowledgementsDir(eventDir);
|
|
3783
|
+
assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
|
|
3784
|
+
mkdirSync(dir, { recursive: true });
|
|
3785
|
+
assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
|
|
3786
|
+
const file = workerSteerAcknowledgementFile(eventDir, exact.operationId);
|
|
3787
|
+
if (publishExclusiveDurableFile(file, `${JSON.stringify(exact, null, 2)}\n`)) return true;
|
|
3788
|
+
const winner = readWorkerSteerAcknowledgement(eventDir, exact.operationId);
|
|
3789
|
+
if (winner === void 0 || winner.operationId !== exact.operationId || winner.requestDigest !== exact.requestDigest) throw new Error("steer delivery claim conflicts with another operation");
|
|
3790
|
+
return false;
|
|
3791
|
+
}
|
|
3792
|
+
/** Atomically replace the runtime acknowledgement for one steer operation. @internal */
|
|
3793
|
+
function writeWorkerSteerAcknowledgement(eventDir, record) {
|
|
3794
|
+
const exact = parseWorkerSteerAcknowledgement(record);
|
|
3795
|
+
const request = readWorkerSteerRequest(eventDir, exact.operationId);
|
|
3796
|
+
if (request === void 0 || request.requestDigest !== exact.requestDigest) throw new Error("steer acknowledgement does not match an admitted request");
|
|
3797
|
+
const dir = workerSteerAcknowledgementsDir(eventDir);
|
|
3798
|
+
assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
|
|
3799
|
+
mkdirSync(dir, { recursive: true });
|
|
3800
|
+
assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
|
|
3801
|
+
const file = workerSteerAcknowledgementFile(eventDir, exact.operationId);
|
|
3802
|
+
const prior = readWorkerSteerAcknowledgement(eventDir, exact.operationId);
|
|
3803
|
+
if (prior !== void 0 && prior.effect !== "unknown" && canonicalCandidateDigest(prior) !== canonicalCandidateDigest(exact)) throw new Error(`steer operation '${exact.operationId}' already has a terminal acknowledgement`);
|
|
3804
|
+
writeAtomicDurableFile(file, `${JSON.stringify(exact, null, 2)}\n`);
|
|
3805
|
+
}
|
|
3806
|
+
/** The directory holding every cancellation artifact of one run (request inbox + acknowledgements). */
|
|
3807
|
+
function workerCancellationsDir(eventDir) {
|
|
3808
|
+
return join(eventDir, "cancellations");
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* The durable cancel-request inbox of one run — one NDJSON line per {@link WorkerCancelRequest}.
|
|
3812
|
+
* It stays append-only so independent operations retain admission order and readers can skip a
|
|
3813
|
+
* partial trailing line. `appendDurableFile` uses O_APPEND, one write, file fsync, and directory
|
|
3814
|
+
* fsync; single-record acknowledgements use atomic replacement instead.
|
|
3815
|
+
*/
|
|
3816
|
+
function workerCancelRequestsFile(eventDir) {
|
|
3817
|
+
return join(workerCancellationsDir(eventDir), "requests.ndjson");
|
|
3818
|
+
}
|
|
3819
|
+
/**
|
|
3820
|
+
* The acknowledgement file for one cancel operation. The filename is a sanitized stem of the
|
|
3821
|
+
* `operationId`; the record inside carries the exact id, and readers verify it so two distinct
|
|
3822
|
+
* ids that sanitize to one stem fail loud instead of answering for each other.
|
|
3823
|
+
*/
|
|
3824
|
+
function workerCancellationFile(eventDir, operationId) {
|
|
3825
|
+
return join(workerCancellationsDir(eventDir), `${safeOperationFile(operationId)}.json`);
|
|
3826
|
+
}
|
|
3827
|
+
function safeOperationFile(operationId) {
|
|
3828
|
+
const safe = operationId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
3829
|
+
if (safe.length === 0) throw new Error("cancel operationId is empty");
|
|
3830
|
+
return safe;
|
|
3831
|
+
}
|
|
3832
|
+
/** Read every valid cancel request in the run's cancellation inbox. Corrupt lines are skipped. */
|
|
3833
|
+
function readWorkerCancelRequests(eventDir) {
|
|
3834
|
+
const dir = workerCancellationsDir(eventDir);
|
|
3835
|
+
const file = workerCancelRequestsFile(eventDir);
|
|
3836
|
+
assertNoSymlinkDescendant(eventDir, dir, "cancel request");
|
|
3837
|
+
assertNoSymlinkDescendant(eventDir, file, "cancel request");
|
|
3838
|
+
if (!existsSync(file)) return [];
|
|
3839
|
+
const out = [];
|
|
3840
|
+
const byOperationId = /* @__PURE__ */ new Map();
|
|
3841
|
+
let raw = "";
|
|
3842
|
+
try {
|
|
3843
|
+
raw = readFileSync(file, "utf8");
|
|
3844
|
+
} catch {
|
|
3845
|
+
return out;
|
|
3846
|
+
}
|
|
3847
|
+
for (const line of raw.split("\n")) {
|
|
3848
|
+
const trimmed = line.trim();
|
|
3849
|
+
if (!trimmed) continue;
|
|
3850
|
+
let parsed;
|
|
3851
|
+
try {
|
|
3852
|
+
parsed = JSON.parse(trimmed);
|
|
3853
|
+
} catch {
|
|
3854
|
+
continue;
|
|
3855
|
+
}
|
|
3856
|
+
if (!isWorkerCancelRequest(parsed)) continue;
|
|
3857
|
+
const prior = byOperationId.get(parsed.operationId);
|
|
3858
|
+
if (prior !== void 0) assertSameWorkerCancelRequest(prior, parsed);
|
|
3859
|
+
else {
|
|
3860
|
+
byOperationId.set(parsed.operationId, parsed);
|
|
3861
|
+
out.push(parsed);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
return out;
|
|
3865
|
+
}
|
|
3866
|
+
/**
|
|
3867
|
+
* Read the acknowledgement for one cancel operation. `undefined` when the runtime has not
|
|
3868
|
+
* answered. A record whose stored `operationId` differs from the requested one is a filename
|
|
3869
|
+
* collision between two sanitized ids — fail loud rather than return another operation's answer.
|
|
3870
|
+
*/
|
|
3871
|
+
function readWorkerCancellation(eventDir, operationId) {
|
|
3872
|
+
const dir = workerCancellationsDir(eventDir);
|
|
3873
|
+
const file = workerCancellationFile(eventDir, operationId);
|
|
3874
|
+
assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
|
|
3875
|
+
assertNoSymlinkDescendant(eventDir, file, "cancel acknowledgement");
|
|
3876
|
+
if (!existsSync(file)) return void 0;
|
|
3877
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
3878
|
+
if (parsed.operationId !== operationId) throw new Error(`cancel acknowledgement collision: '${file}' holds operation '${parsed.operationId}', not '${operationId}' — use operation ids that stay distinct after filename sanitization`);
|
|
3879
|
+
return parsed;
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3882
|
+
* Durably write one acknowledgement record. The shared writer fsyncs the file and containing
|
|
3883
|
+
* directory, so a concurrent reader sees the prior complete record or the new complete record.
|
|
3884
|
+
*
|
|
3885
|
+
* @internal The runtime acknowledger is the only intended writer; clients read via
|
|
3886
|
+
* {@link readWorkerCancellation} or {@link cancelWorker}.
|
|
3887
|
+
*/
|
|
3888
|
+
function writeWorkerCancellation(eventDir, record) {
|
|
3889
|
+
const dir = workerCancellationsDir(eventDir);
|
|
3890
|
+
assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
|
|
3891
|
+
mkdirSync(dir, { recursive: true });
|
|
3892
|
+
assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
|
|
3893
|
+
const file = workerCancellationFile(eventDir, record.operationId);
|
|
3894
|
+
assertNoSymlinkDescendant(eventDir, file, "cancel acknowledgement");
|
|
3895
|
+
writeAtomicDurableFile(file, `${JSON.stringify(record, null, 2)}\n`, { mode: 384 });
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Request the cancellation of ONE worker, idempotently, and return the operation's current
|
|
3899
|
+
* durable state.
|
|
3900
|
+
*
|
|
3901
|
+
* The write half of the acknowledged-cancellation contract (`writeWorkerSteer` is the steer
|
|
3902
|
+
* analog): append the request to the run's cancellation inbox, where the OWNING manager's
|
|
3903
|
+
* acknowledger (its turn loop — the root for label/profile references, the parent manager for an
|
|
3904
|
+
* exact node id at any depth) applies it — aborting exactly that worker's subtree and recording
|
|
3905
|
+
* what it proved. This function never applies the cancellation itself; writing a
|
|
3906
|
+
* request file is not an acknowledgement.
|
|
3907
|
+
*
|
|
3908
|
+
* Idempotency is a lookup: when an acknowledgement for `operationId` already exists, it is
|
|
3909
|
+
* returned AS-IS and nothing is appended — repeating one operation can never apply twice. A
|
|
3910
|
+
* retry that changes the worker, source, or reason fails closed. A request the runtime has not
|
|
3911
|
+
* answered yet returns `effect: 'unknown'` (never a success); call again with the same
|
|
3912
|
+
* `operationId` — or `readWorkerCancellation` — to read the acknowledged result after a
|
|
3913
|
+
* reconnect.
|
|
3914
|
+
*/
|
|
3915
|
+
function cancelWorker(eventDir, worker, operationId, options = {}) {
|
|
3916
|
+
const ref = worker.trim();
|
|
3917
|
+
if (!ref) throw new Error("cancelWorker: worker reference is empty");
|
|
3918
|
+
const opId = operationId.trim();
|
|
3919
|
+
if (!opId) throw new Error("cancelWorker: operationId is empty");
|
|
3920
|
+
const pending = readWorkerCancelRequests(eventDir).find((r) => r.operationId === opId);
|
|
3921
|
+
const candidate = {
|
|
3922
|
+
operationId: opId,
|
|
3923
|
+
worker: ref,
|
|
3924
|
+
source: options.source ?? "human",
|
|
3925
|
+
...options.reason === void 0 ? {} : { reason: options.reason }
|
|
3926
|
+
};
|
|
3927
|
+
if (pending !== void 0) assertSameWorkerCancelRequest(pending, candidate);
|
|
3928
|
+
const acknowledged = readWorkerCancellation(eventDir, opId);
|
|
3929
|
+
if (acknowledged !== void 0) {
|
|
3930
|
+
if (pending === void 0 && options.source !== void 0) throw new Error(`cancelWorker: operation '${opId}' cannot verify source without its admitted request`);
|
|
3931
|
+
assertWorkerCancellationMatchesCandidate(acknowledged, candidate);
|
|
3932
|
+
return acknowledged;
|
|
3933
|
+
}
|
|
3934
|
+
const source = options.source ?? "human";
|
|
3935
|
+
const request = pending ?? {
|
|
3936
|
+
operationId: opId,
|
|
3937
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3938
|
+
source,
|
|
3939
|
+
worker: ref,
|
|
3940
|
+
...options.reason === void 0 ? {} : { reason: options.reason }
|
|
3941
|
+
};
|
|
3942
|
+
if (!pending) {
|
|
3943
|
+
const dir = workerCancellationsDir(eventDir);
|
|
3944
|
+
assertNoSymlinkDescendant(eventDir, dir, "cancel request");
|
|
3945
|
+
mkdirSync(dir, { recursive: true });
|
|
3946
|
+
assertNoSymlinkDescendant(eventDir, dir, "cancel request");
|
|
3947
|
+
const file = workerCancelRequestsFile(eventDir);
|
|
3948
|
+
assertNoSymlinkDescendant(eventDir, file, "cancel request");
|
|
3949
|
+
appendDurableFile(file, `${JSON.stringify(request)}\n`, { mode: 384 });
|
|
3950
|
+
appendWorkerControlEvent(eventDir, ref, {
|
|
3951
|
+
kind: "cancel-request",
|
|
3952
|
+
operationId: opId,
|
|
3953
|
+
source,
|
|
3954
|
+
queued: true,
|
|
3955
|
+
...options.reason === void 0 ? {} : { reason: options.reason }
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
return {
|
|
3959
|
+
operationId: opId,
|
|
3960
|
+
worker: request.worker,
|
|
3961
|
+
effect: "unknown",
|
|
3962
|
+
requestedAt: request.at,
|
|
3963
|
+
observedAt: request.at,
|
|
3964
|
+
...request.reason === void 0 ? {} : { reason: request.reason },
|
|
3965
|
+
detail: "request queued; no runtime acknowledger has answered yet",
|
|
3966
|
+
terminated: []
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
function assertSameWorkerCancelRequest(existing, candidate) {
|
|
3970
|
+
if (existing.operationId !== candidate.operationId) throw new Error(`cancelWorker: operation '${candidate.operationId}' has an id collision`);
|
|
3971
|
+
if (existing.worker !== candidate.worker) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (worker '${existing.worker}' != '${candidate.worker}')`);
|
|
3972
|
+
if (existing.source !== candidate.source) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (source '${existing.source}' != '${candidate.source}')`);
|
|
3973
|
+
if (existing.reason !== candidate.reason) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (reason differs)`);
|
|
3974
|
+
}
|
|
3975
|
+
function assertWorkerCancellationMatchesCandidate(existing, candidate) {
|
|
3976
|
+
if (existing.worker !== candidate.worker) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its acknowledgement (worker '${existing.worker}' != '${candidate.worker}')`);
|
|
3977
|
+
if (existing.reason !== candidate.reason) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its acknowledgement (reason differs)`);
|
|
3978
|
+
}
|
|
3979
|
+
function isWorkerCancelRequest(value) {
|
|
3980
|
+
return typeof value.operationId === "string" && value.operationId.length > 0 && typeof value.at === "string" && typeof value.source === "string" && typeof value.worker === "string" && value.worker.length > 0 && (value.reason === void 0 || typeof value.reason === "string");
|
|
3981
|
+
}
|
|
3982
|
+
function appendWorkerControlEvent(eventDir, label, event) {
|
|
3983
|
+
try {
|
|
3984
|
+
fenceRunWrite(() => {
|
|
3985
|
+
mkdirSync(supervisorWorkersDir(eventDir), { recursive: true });
|
|
3986
|
+
appendFileSync(workerControlLogFile(eventDir, label), `${JSON.stringify({
|
|
3987
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3988
|
+
label,
|
|
3989
|
+
...event
|
|
3990
|
+
})}\n`, "utf8");
|
|
3991
|
+
});
|
|
3992
|
+
} catch {}
|
|
3993
|
+
}
|
|
3994
|
+
function workerSteerRequestDigest(value) {
|
|
3995
|
+
return canonicalCandidateDigest({
|
|
3996
|
+
kind: "worker-steer-request.v1",
|
|
3997
|
+
...value
|
|
3998
|
+
});
|
|
3999
|
+
}
|
|
4000
|
+
function operationFileHash(operationId) {
|
|
4001
|
+
const id = operationId.trim();
|
|
4002
|
+
if (!id) throw new Error("steer operationId is empty");
|
|
4003
|
+
return sha256(id);
|
|
4004
|
+
}
|
|
4005
|
+
function admitSteerRequest(file, request) {
|
|
4006
|
+
const existing = readWorkerSteerRequestFile(file);
|
|
4007
|
+
if (existing !== void 0) {
|
|
4008
|
+
assertSameSteerRequest(existing, request);
|
|
4009
|
+
return {
|
|
4010
|
+
request: existing,
|
|
4011
|
+
replayed: true
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
if (publishExclusiveDurableFile(file, `${JSON.stringify(request, null, 2)}\n`)) return {
|
|
4015
|
+
request,
|
|
4016
|
+
replayed: false
|
|
4017
|
+
};
|
|
4018
|
+
const winner = readWorkerSteerRequestFile(file);
|
|
4019
|
+
if (winner === void 0) throw new Error("steer request publication lost its winner");
|
|
4020
|
+
assertSameSteerRequest(winner, request);
|
|
4021
|
+
return {
|
|
4022
|
+
request: winner,
|
|
4023
|
+
replayed: true
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
function readWorkerSteerRequest(eventDir, operationId) {
|
|
4027
|
+
const file = workerSteerRequestFile(eventDir, operationId);
|
|
4028
|
+
assertNoSymlinkDescendant(eventDir, file, "steer request");
|
|
4029
|
+
const request = readWorkerSteerRequestFile(file);
|
|
4030
|
+
if (request !== void 0 && request.operationId !== operationId) throw new Error("steer request file belongs to another operation");
|
|
4031
|
+
return request;
|
|
4032
|
+
}
|
|
4033
|
+
function readWorkerSteerRequestFile(file) {
|
|
4034
|
+
if (!existsSync(file)) return void 0;
|
|
4035
|
+
return parseWorkerSteerRequest(JSON.parse(readFileSync(file, "utf8")));
|
|
4036
|
+
}
|
|
4037
|
+
function parseWorkerSteerRequest(value) {
|
|
4038
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("steer request is malformed");
|
|
4039
|
+
const request = value;
|
|
4040
|
+
if (request.schemaVersion !== 1 || typeof request.operationId !== "string" || request.operationId.length === 0 || typeof request.requestDigest !== "string" || typeof request.at !== "string" || typeof request.source !== "string" || request.source.length === 0 || typeof request.worker !== "string" || request.worker.length === 0 || typeof request.message !== "string" || request.message.trim().length === 0 || typeof request.interrupt !== "boolean") throw new Error("steer request is malformed");
|
|
4041
|
+
const exact = request;
|
|
4042
|
+
const expected = workerSteerRequestDigest({
|
|
4043
|
+
operationId: exact.operationId,
|
|
4044
|
+
worker: exact.worker,
|
|
4045
|
+
message: exact.message,
|
|
4046
|
+
source: exact.source,
|
|
4047
|
+
interrupt: exact.interrupt
|
|
4048
|
+
});
|
|
4049
|
+
if (exact.requestDigest !== expected) throw new Error("steer request digest does not match");
|
|
4050
|
+
return exact;
|
|
4051
|
+
}
|
|
4052
|
+
function parseWorkerSteerAcknowledgement(value) {
|
|
4053
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("steer acknowledgement is malformed");
|
|
4054
|
+
const record = value;
|
|
4055
|
+
if (record.schemaVersion !== 1 || typeof record.operationId !== "string" || record.operationId.length === 0 || typeof record.requestDigest !== "string" || typeof record.worker !== "string" || record.worker.length === 0 || record.effect !== "unknown" && record.effect !== "delivered" && record.effect !== "not_live" && record.effect !== "unsupported" && record.effect !== "refused" || typeof record.requestedAt !== "string" || typeof record.observedAt !== "string" || typeof record.detail !== "string") throw new Error("steer acknowledgement is malformed");
|
|
4056
|
+
return record;
|
|
4057
|
+
}
|
|
4058
|
+
function assertSameSteerRequest(existing, candidate) {
|
|
4059
|
+
if (existing.operationId !== candidate.operationId) throw new Error("steer operation file collision");
|
|
4060
|
+
if (existing.requestDigest !== candidate.requestDigest) throw new Error(`writeWorkerSteer: operation '${candidate.operationId}' conflicts with its admitted request`);
|
|
4061
|
+
}
|
|
4062
|
+
// src/runtime/supervise/root-stream.ts
|
|
4063
|
+
/**
|
|
4064
|
+
* The root manager's own provider stream, retained where Runtime owns it.
|
|
4065
|
+
*
|
|
4066
|
+
* Measured motive (agent-runtime#1233): a CHILD's full event stream — reasoning, text,
|
|
4067
|
+
* and tool parts, 28k to 76k reasoning characters per child — is retained in the blob its
|
|
4068
|
+
* `settled` record names as `outRef`. The ROOT's stream reached nobody: `driveHarnessFromBackend`
|
|
4069
|
+
* drained the root executor for accounting and discarded every `progress` event, and on a
|
|
4070
|
+
* `winner` run `result.outRef` is the SELECTED CHILD's artifact, not the root's. A root that died
|
|
4071
|
+
* at its join barrier (fourier-e, 63 children) left a 607-byte `failure.json` and nothing it
|
|
4072
|
+
* thought; the Lab's only transcript of a root was a post-hoc scrape of the harness's own
|
|
4073
|
+
* database.
|
|
4074
|
+
*
|
|
4075
|
+
* `<runDir>/root-stream.jsonl` is the record: one JSONL line per `ExecutorProgressEvent`, written
|
|
4076
|
+
* AS IT ARRIVES, so a root whose process is killed mid-turn keeps every line already written. The
|
|
4077
|
+
* file itself is the evidence; the receipt (`ref`, the content address of the file's bytes, and
|
|
4078
|
+
* `events`, its committed line count) is what `result.json` and `failure.json` carry. The winner's
|
|
4079
|
+
* `outRef` keeps its meaning; this is a separate reference.
|
|
4080
|
+
*
|
|
4081
|
+
* Durability boundary: each line is written synchronously to an `O_APPEND` descriptor before the
|
|
4082
|
+
* next event is drained, and the file is fsynced at every drive-attempt boundary and at close, not
|
|
4083
|
+
* per line. An fsync per line cost 7.1 ms per event over
|
|
4084
|
+
* 1,000 events and 8.1 ms over 5,000 (~80 s for a root emitting ten thousand text deltas, all of
|
|
4085
|
+
* it serialized inside the drain loop that also meters the budget); an async `FileHandle.write`
|
|
4086
|
+
* still cost 0.9 to 1.1 ms per line in the thread pool the children's journal writes share; a
|
|
4087
|
+
* `writeSync` costs 6 µs. A process kill loses nothing written; only a kernel crash or power
|
|
4088
|
+
* loss can drop lines written since the last fsync, and the reader's torn-tail rule keeps such a
|
|
4089
|
+
* file readable.
|
|
4090
|
+
*
|
|
4091
|
+
* The bridge wire carries text and tool calls only: cli-bridge's SSE frames decode
|
|
4092
|
+
* `delta.content` and `delta.tool_calls`, never a reasoning delta. A bridge-placed root's retained
|
|
4093
|
+
* stream is therefore text plus tool calls; reasoning arrives only on the provider/sandbox path,
|
|
4094
|
+
* which projects `message.part.updated` reasoning parts to `reasoning_delta`.
|
|
4095
|
+
*/
|
|
4096
|
+
/** The root stream: one JSONL line per progress event the root's executor observed. */
|
|
4097
|
+
const ROOT_STREAM_FILE = "root-stream.jsonl";
|
|
4098
|
+
/** Each other node's stream, in the same record shape, keyed by its node id. A name the filename
|
|
4099
|
+
* rules changed carries a digest of the id, so `a:b` and `a_b` never share a file. */
|
|
4100
|
+
function nodeStreamFile(nodeId) {
|
|
4101
|
+
const safe = safeWorkerFile(nodeId);
|
|
4102
|
+
return `streams/${safe === nodeId ? safe : `${safe}.${createHash("sha256").update(nodeId).digest("hex").slice(0, 12)}`}.jsonl`;
|
|
4103
|
+
}
|
|
4104
|
+
/** Create the writer for `<runDir>/root-stream.jsonl`, or for another `file` in the run directory.
|
|
4105
|
+
* Nothing touches disk until the first `beginAttempt`, so a run whose root never drives leaves no
|
|
4106
|
+
* file and no receipt. The root numbers attempts per process; a node stream, whose writer closes
|
|
4107
|
+
* after every execution attempt, sets `continueAttempts` so a later attempt never reuses a number. */
|
|
4108
|
+
function createRootStreamSink(runDir, now, file = ROOT_STREAM_FILE, options = {}) {
|
|
4109
|
+
const withClaim = captureRunWriteContext();
|
|
4110
|
+
const dir = resolve(runDir);
|
|
4111
|
+
const path = resolve(dir, file);
|
|
4112
|
+
let fd;
|
|
4113
|
+
let attempt = 0;
|
|
4114
|
+
let seq = 0;
|
|
4115
|
+
let closed = false;
|
|
4116
|
+
const openOnce = async () => {
|
|
4117
|
+
if (fd !== void 0) return;
|
|
4118
|
+
await mkdir(dirname(path), { recursive: true });
|
|
4119
|
+
assertNoSymlinkDescendant(dir, path, "root stream");
|
|
4120
|
+
const needsSeparator = await prepareJsonlAppend(path);
|
|
4121
|
+
seq = 0;
|
|
4122
|
+
let priorAttempts = 0;
|
|
4123
|
+
for await (const record of readCommittedJsonLines(path, { allowMissing: true })) {
|
|
4124
|
+
seq += 1;
|
|
4125
|
+
priorAttempts = Math.max(priorAttempts, record.attempt);
|
|
4126
|
+
}
|
|
4127
|
+
if (options.continueAttempts === true) attempt += priorAttempts;
|
|
4128
|
+
fd = fenceRunWrite(() => {
|
|
4129
|
+
const writer = openSync(path, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0), 384);
|
|
4130
|
+
try {
|
|
4131
|
+
if (needsSeparator) writeLine(writer, "");
|
|
4132
|
+
return writer;
|
|
4133
|
+
} catch (error) {
|
|
4134
|
+
closeSync(writer);
|
|
4135
|
+
throw error;
|
|
4136
|
+
}
|
|
4137
|
+
});
|
|
4138
|
+
};
|
|
4139
|
+
return {
|
|
4140
|
+
async beginAttempt() {
|
|
4141
|
+
return withClaim(async () => {
|
|
4142
|
+
if (closed) throw new Error("root stream: beginAttempt after close");
|
|
4143
|
+
attempt += 1;
|
|
4144
|
+
const writer = fd;
|
|
4145
|
+
if (writer !== void 0) fenceRunWrite(() => fsyncSync(writer));
|
|
4146
|
+
await openOnce();
|
|
4147
|
+
});
|
|
4148
|
+
},
|
|
4149
|
+
append(event) {
|
|
4150
|
+
return withClaim(() => fenceRunWrite(() => {
|
|
4151
|
+
if (closed) throw new Error("root stream: append after close");
|
|
4152
|
+
if (fd === void 0) throw new Error("root stream: append before beginAttempt");
|
|
4153
|
+
const at = new Date(now()).toISOString();
|
|
4154
|
+
const record = {
|
|
4155
|
+
seq: seq + 1,
|
|
4156
|
+
at,
|
|
4157
|
+
attempt,
|
|
4158
|
+
event
|
|
4159
|
+
};
|
|
4160
|
+
let line;
|
|
4161
|
+
try {
|
|
4162
|
+
line = JSON.stringify(record);
|
|
4163
|
+
} catch (error) {
|
|
4164
|
+
line = JSON.stringify({
|
|
4165
|
+
seq: seq + 1,
|
|
4166
|
+
at,
|
|
4167
|
+
attempt,
|
|
4168
|
+
dropped: {
|
|
4169
|
+
kind: event.kind,
|
|
4170
|
+
reason: errorMessage(error)
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
if (line === void 0) line = JSON.stringify({
|
|
4175
|
+
seq: seq + 1,
|
|
4176
|
+
at,
|
|
4177
|
+
attempt,
|
|
4178
|
+
dropped: {
|
|
4179
|
+
kind: event.kind,
|
|
4180
|
+
reason: "event serialized to nothing"
|
|
4181
|
+
}
|
|
4182
|
+
});
|
|
4183
|
+
writeLine(fd, line);
|
|
4184
|
+
seq += 1;
|
|
4185
|
+
}));
|
|
4186
|
+
},
|
|
4187
|
+
async close() {
|
|
4188
|
+
return withClaim(async () => {
|
|
4189
|
+
if (closed) throw new Error("root stream: closed twice");
|
|
4190
|
+
closed = true;
|
|
4191
|
+
const writer = fd;
|
|
4192
|
+
if (writer === void 0) return void 0;
|
|
4193
|
+
try {
|
|
4194
|
+
fenceRunWrite(() => fsyncSync(writer));
|
|
4195
|
+
} finally {
|
|
4196
|
+
closeSync(writer);
|
|
4197
|
+
fd = void 0;
|
|
4198
|
+
}
|
|
4199
|
+
return readRootStreamReceipt(dir, file);
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
/** `writeSync` may legally make a short write; loop until the whole line and its newline land. */
|
|
4205
|
+
function writeLine(fd, line) {
|
|
4206
|
+
const bytes = Buffer.from(`${line}\n`, "utf8");
|
|
4207
|
+
let offset = 0;
|
|
4208
|
+
while (offset < bytes.byteLength) {
|
|
4209
|
+
const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
|
|
4210
|
+
if (written <= 0) throw new Error(`root stream: append made no progress at byte ${offset}`);
|
|
4211
|
+
offset += written;
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
/**
|
|
4215
|
+
* The receipt for the root stream a run directory holds, recomputed from the file's bytes, or
|
|
4216
|
+
* `undefined` when the directory holds none. This is what a run that never settled — a root that
|
|
4217
|
+
* died mid-turn — gets on its failure record, and it equals what `close()` returned for a run
|
|
4218
|
+
* that did.
|
|
4219
|
+
*/
|
|
4220
|
+
async function readRootStreamReceipt(runDir, file = ROOT_STREAM_FILE) {
|
|
4221
|
+
const path = resolve(runDir, file);
|
|
4222
|
+
const hash = createHash("sha256");
|
|
4223
|
+
let events = 0;
|
|
4224
|
+
try {
|
|
4225
|
+
for await (const _record of readCommittedJsonLines(path, { onBytes: (bytes) => {
|
|
4226
|
+
hash.update(bytes);
|
|
4227
|
+
} })) events += 1;
|
|
4228
|
+
} catch (error) {
|
|
4229
|
+
if (isNoEntError(error)) return void 0;
|
|
4230
|
+
throw error;
|
|
4231
|
+
}
|
|
4232
|
+
return Object.freeze({
|
|
4233
|
+
ref: `sha256:${hash.digest("hex")}`,
|
|
4234
|
+
events
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
/** Every committed line of the root stream, in order, or `undefined` when there is no file. A
|
|
4238
|
+
* torn final line from a process that died mid-write is not a record and is left out. */
|
|
4239
|
+
async function readRootStream(runDir) {
|
|
4240
|
+
const path = resolve(runDir, ROOT_STREAM_FILE);
|
|
4241
|
+
const records = [];
|
|
4242
|
+
try {
|
|
4243
|
+
for await (const record of readCommittedJsonLines(path)) records.push(record);
|
|
4244
|
+
} catch (error) {
|
|
4245
|
+
if (isNoEntError(error)) return void 0;
|
|
4246
|
+
throw error;
|
|
4247
|
+
}
|
|
4248
|
+
return records;
|
|
4249
|
+
}
|
|
4250
|
+
export { workspaceCaptureWriter as $, closesCursorSlot as A, sandboxLeafOutputFromEvents as At, runtimeFailureRemediationSchema as B, createSandboxUsageLedger as Bt, writeWorkerSteerAcknowledgement as C, spendFromUsageEvents as Ct, FileResultBlobStore as D, createSandboxToolPartState as Dt, spawnJournalRecordEvents as E, assertValidSpend as Et, observerRecordDigest as F, extractTransportEventIdentity as Ft, RetainedExecutionPendingError as G, sandboxTerminalUsageField as Gt, settlementFailureCause as H, isSandboxTerminalEvent as Ht, verifyObserverRecords as I, parseCanonicalTransportEvent as It, leafContinuationExecutionId as J, parseCodexUsageRecord as Jt, executorEvidenceWriter as K, sumSandboxUsage as Kt, reportedFailureCause as L, assertSandboxServedModel as Lt, outsideCursorNamespace as M, sandboxProgressEvents as Mt, FileObserverJournal as N, terminalTextFromSandboxEvent as Nt, encodeResultBlob as O, mapSandboxEvent as Ot, createFileObserverHooks as P, canonicalStreamEventFromSandboxEvent as Pt, retainedExecutorSeamKey as Q, runtimeFailureCause as R, canonicalSandboxUsageMode as Rt, writeWorkerSteer as S, newUsageTotals as St, appendSpawnEvents as T, assertValidBudget as Tt, BUDGET_CONTINUATION_TASK as U, notifySandboxEventObserver as Ut, runtimeProviderCode as V, extractLlmCallEvent as Vt, DISPATCH_NOT_ADMITTED_CODE as W, sandboxEventServedBackend as Wt, registerRetainedExecutorPreparation as X, prepareRetainedExecutor as Y, recordValue as Yt, retainedExecutorContext as Z, supervisorRunsRoot as _, credentialCommandExit as _t, readRootStreamReceipt as a, unavailableResumeAt as at, workerInboxFileFromEventDir as b, createBudgetPool as bt, claimWorkerSteerDelivery as c, TRACE_ROOT_SCHEMA as ct, readWorkerCancelRequests as d, contentAddress as dt, UNAVAILABLE_CODES as et, readWorkerCancellation as f, contentRef as ft, supervisorRunDir as g, hashJson as gt, safeWorkerFile as h, executorFailureReason as ht, readRootStream as i, unavailablePauseMs as it, insideCursorNamespace as j, sandboxOutputMarker as jt, SpawnEventIndex as k, mapSandboxToolEvent as kt, legacySupervisorRunDir as l, TRACE_SUMMARY_SCHEMA as lt, readWorkerSteerRequests as m, executorFailureInfra as mt, createRootStreamSink as n, cappedDoublingMs as nt, appendWorkerControlEvent as o, unavailableSignalInText as ot, readWorkerSteerAcknowledgement as p, executorFailure as pt, isProvisioningRefusal as q, decodeHarnessUsage as qt, nodeStreamFile as r, jitteredMs as rt, cancelWorker as s, unavailableSignalOfFailure as st, ROOT_STREAM_FILE as t, UNAVAILABLE_STATUSES as tt, legacySupervisorRunsRoot as u, traceRootWriter as ut, supervisorWorkersDir as v, isCredentialRefusal as vt, FileSpawnJournal as w, spendFromUsageTotals as wt, writeWorkerCancellation as x, meterUsageEvent as xt, workerCancellationsDir as y, BudgetReconcileFault as yt, runtimeFailureCauseSchema as z, createSandboxTurnEventCollector as zt };
|
|
3549
4251
|
|
|
3550
|
-
//# sourceMappingURL=
|
|
4252
|
+
//# sourceMappingURL=root-stream-To4qAc0e.js.map
|