@tangle-network/agent-runtime 0.342.2 → 0.343.3
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-C5rFWXGj.js} +4 -4
- package/dist/{activation-DOH7Fnyp.js.map → activation-C5rFWXGj.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-T98ZbsEt.js} +22 -314
- package/dist/coordination-manager-T98ZbsEt.js.map +1 -0
- package/dist/{delegate-DmShSdjy.js → delegate-CjdtLf1v.js} +2 -2
- package/dist/{delegate-DmShSdjy.js.map → delegate-CjdtLf1v.js.map} +1 -1
- package/dist/{durable-file-gNI9ILAn.js → durable-file-DLcrzrjL.js} +19 -14
- package/dist/durable-file-DLcrzrjL.js.map +1 -0
- package/dist/durable.d.ts +15 -11
- package/dist/durable.js +193 -80
- package/dist/durable.js.map +1 -1
- package/dist/{graph-BCghJFlS.js → graph-kgzTaNMA.js} +4 -4
- package/dist/{graph-BCghJFlS.js.map → graph-kgzTaNMA.js.map} +1 -1
- package/dist/{improve-Dga6OYt-.js → improve-DevAd83a.js} +7 -8
- package/dist/{improve-Dga6OYt-.js.map → improve-DevAd83a.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-CyUoVMkv.js} +2 -2
- package/dist/{loop-runner-CW87Vqoj.js.map → loop-runner-CyUoVMkv.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-CysVhSqV.js} +6 -6
- package/dist/{provision-supervisor-BWR_TJFg.js.map → provision-supervisor-CysVhSqV.js.map} +1 -1
- package/dist/{run-cancellation-C9oL62sW.js → run-cancellation-CEDiqfhC.js} +8 -4
- package/dist/{run-cancellation-C9oL62sW.js.map → run-cancellation-CEDiqfhC.js.map} +1 -1
- package/dist/{runtime-CuRTZFe6.js → runtime-CefFtXC2.js} +15 -17
- package/dist/{runtime-CuRTZFe6.js.map → runtime-CefFtXC2.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-t05_9Wwk.js} +3 -3
- package/dist/{search-executor-0-SjHAao.js.map → search-executor-t05_9Wwk.js.map} +1 -1
- package/dist/{server-DTUK2wW1.js → server-DjrVWzH2.js} +4 -4
- package/dist/{server-DTUK2wW1.js.map → server-DjrVWzH2.js.map} +1 -1
- package/dist/{structural-rollout-kfaZgAkd.js → structural-rollout-CpJknIaL.js} +3 -3
- package/dist/{structural-rollout-kfaZgAkd.js.map → structural-rollout-CpJknIaL.js.map} +1 -1
- package/dist/{supervise-C_GAgiJU.js → supervise-BuVVC7Bl.js} +415 -107
- package/dist/supervise-BuVVC7Bl.js.map +1 -0
- package/dist/{supervisor-C_03z693.js → supervisor-D3M9ab1v.js} +627 -27
- package/dist/supervisor-D3M9ab1v.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-BGYI6G6K.js} +2 -2
- package/dist/{top-snapshot-CbZE1JsU.js.map → top-snapshot-BGYI6G6K.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 +7 -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
package/dist/durable.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { i as publishExclusiveDurableFile, o as writeAtomicDurableFile } from "./durable-file-
|
|
2
|
-
import {
|
|
1
|
+
import { a as syncDurableDirectory, i as publishExclusiveDurableFile, o as writeAtomicDurableFile } from "./durable-file-DLcrzrjL.js";
|
|
2
|
+
import { Bi as assertValidBudget, C as FileSpawnJournal, Mi as contentAddress, Qr as authoredProfileDigest, _t as readCommittedJsonLines, ct as loadSpawnForest, hr as isFiniteNumber, ji as detachedSnapshot, mr as failedItems, mt as isNoEntError, pt as appendCommittedJsonLine, r as assertResumeLimits } from "./supervisor-D3M9ab1v.js";
|
|
3
3
|
import { f as RuntimeRunStateError, m as ValidationError } from "./errors-DodWX-cb.js";
|
|
4
|
-
import {
|
|
5
|
-
import { W as addSpend, X as errorMessage, ft as zeroSpend, q as cloneSpend } from "./sandbox-profile-compat-FN2kab2-.js";
|
|
4
|
+
import { W as addSpend, X as errorMessage, ft as zeroSpend, q as cloneSpend } from "./sandbox-profile-compat-_fEtHPCj.js";
|
|
6
5
|
import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-tXpAarhW.js";
|
|
7
|
-
import { a as
|
|
8
|
-
import {
|
|
9
|
-
import { a as
|
|
10
|
-
import {
|
|
11
|
-
import { i as superviseAgentGraph } from "./graph-BCghJFlS.js";
|
|
6
|
+
import { a as applyExactAgentProfileDiff } from "./profile-6OLpbjvV.js";
|
|
7
|
+
import { a as superviseRootProfile, ot as readRootStream, st as readRootStreamReceipt, t as supervise } from "./supervise-BuVVC7Bl.js";
|
|
8
|
+
import { a as runCancelRequestFile, o as runCancellationFile, v as supervisorRunDir, x as workerCancellationsDir } from "./run-cancellation-CEDiqfhC.js";
|
|
9
|
+
import { i as superviseAgentGraph } from "./graph-kgzTaNMA.js";
|
|
12
10
|
import { agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest, canonicalCandidateJson, sha256Bytes } from "@tangle-network/agent-interface";
|
|
11
|
+
import { isRecord } from "@tangle-network/agent-core";
|
|
13
12
|
import { createHash } from "node:crypto";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync } from "node:fs";
|
|
14
14
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
15
|
-
import {
|
|
16
|
-
import { access, mkdir, readFile, realpath, rmdir, unlink } from "node:fs/promises";
|
|
17
|
-
import { promisify } from "node:util";
|
|
15
|
+
import { access, mkdir, open, readFile, realpath, rm, rmdir, unlink } from "node:fs/promises";
|
|
18
16
|
import { SearchRecorder, developmentClaim, incumbent, openSearchLedger, parseSearchLedgerLine, runSearch, uniform } from "@tangle-network/agent-eval/campaign";
|
|
19
17
|
import { pathToFileURL } from "node:url";
|
|
20
18
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
19
|
+
import { lockHolderVerdict, lockIdentity, parseLockHolderRecord, readLockHolderIdentity } from "@tangle-network/agent-eval/lock-holder";
|
|
21
20
|
//#region src/durable/chat-engine.ts
|
|
22
21
|
const encoder = new TextEncoder();
|
|
23
22
|
function encodeLine(event) {
|
|
@@ -897,7 +896,7 @@ var SettledRunDirectoryError = class extends Error {
|
|
|
897
896
|
/** The root of the recorded run, which is its `runId`. */
|
|
898
897
|
recordedRunId;
|
|
899
898
|
constructor(path, recordedRunId, requestedRunId) {
|
|
900
|
-
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`);
|
|
899
|
+
super(recordedRunId === requestedRunId ? `supervisePursuit: ${path} records that run '${recordedRunId}' already settled; a settled run is not re-entered unless the call passes reopen: { operator, reason, operationId }` : `supervisePursuit: ${path} records that run '${recordedRunId}' already settled in this directory; a directory holds one settle record, so run '${requestedRunId}' needs its own runDir`);
|
|
901
900
|
this.name = "SettledRunDirectoryError";
|
|
902
901
|
this.path = path;
|
|
903
902
|
this.recordedRunId = recordedRunId;
|
|
@@ -949,6 +948,10 @@ function jsonValue(result) {
|
|
|
949
948
|
function settleRecordJson(result) {
|
|
950
949
|
return canonicalCandidateJson(jsonValue(result));
|
|
951
950
|
}
|
|
951
|
+
/** The digest the settle record's bytes hash to: the candidate digest of the result's JSON value. */
|
|
952
|
+
function settleRecordDigest(result) {
|
|
953
|
+
return canonicalCandidateDigest(jsonValue(result));
|
|
954
|
+
}
|
|
952
955
|
/** Publish `result.json` in the run directory once. Throws when a record is already there. */
|
|
953
956
|
async function writeSettleRecord(runDir, result) {
|
|
954
957
|
const path = resolve(runDir, SETTLE_RECORD_FILE);
|
|
@@ -2040,28 +2043,37 @@ var RunDirectoryLockedError = class extends Error {
|
|
|
2040
2043
|
* Take `runDir/supervise.lock`, or refuse.
|
|
2041
2044
|
*
|
|
2042
2045
|
* The file is published with its full content or not at all, so a contender never reads a
|
|
2043
|
-
* half-written holder. A lock whose holder is
|
|
2044
|
-
* belongs to a process with a different start token) is stale and is removed
|
|
2045
|
-
*
|
|
2046
|
-
* holder and is reclaimed. A file with unreadable content is left in place and refused:
|
|
2046
|
+
* half-written holder. A lock whose holder is proved dead (its pid no longer exists, the pid now
|
|
2047
|
+
* belongs to a process with a different start token, or the host rebooted) is stale and is removed
|
|
2048
|
+
* under the mutation guard; a holder that is alive or cannot be proved dead refuses. An empty file
|
|
2049
|
+
* names no holder and is reclaimed. A file with unreadable content is left in place and refused:
|
|
2047
2050
|
* reclaiming it could evict a live holder written by something other than this module.
|
|
2048
2051
|
*/
|
|
2049
2052
|
async function acquireRunDirectoryLock(runDir, runId, now = Date.now) {
|
|
2050
2053
|
const dir = resolve(runDir);
|
|
2051
2054
|
const path = resolve(dir, RUN_DIRECTORY_LOCK_FILE);
|
|
2052
2055
|
await mkdir(dir, { recursive: true });
|
|
2053
|
-
const
|
|
2054
|
-
const
|
|
2055
|
-
pid
|
|
2056
|
+
const { pid, ...identity } = readLockHolderIdentity(process.pid);
|
|
2057
|
+
const owner = {
|
|
2058
|
+
pid,
|
|
2056
2059
|
startedAt: new Date(now()).toISOString(),
|
|
2057
2060
|
runId,
|
|
2058
|
-
...
|
|
2059
|
-
}
|
|
2061
|
+
...identity
|
|
2062
|
+
};
|
|
2060
2063
|
return withMutationGuard(path, async () => {
|
|
2061
2064
|
const existing = await readLockFile(path);
|
|
2062
|
-
if (existing.state === "holder" &&
|
|
2065
|
+
if (existing.state === "holder" && !holderIsDead(existing)) throw new RunDirectoryLockedError(path, existing.holder);
|
|
2063
2066
|
if (existing.state !== "missing") await removeIfPresent(path);
|
|
2064
|
-
|
|
2067
|
+
const written = {};
|
|
2068
|
+
const published = publishExclusiveDurableFile(path, (file) => {
|
|
2069
|
+
written.holder = Object.freeze({
|
|
2070
|
+
...owner,
|
|
2071
|
+
lock: lockIdentity(file)
|
|
2072
|
+
});
|
|
2073
|
+
return `${JSON.stringify(written.holder)}\n`;
|
|
2074
|
+
});
|
|
2075
|
+
const holder = written.holder;
|
|
2076
|
+
if (!published || holder === void 0) throw new Error(`supervisePursuit: could not take ${path}`);
|
|
2065
2077
|
let released = false;
|
|
2066
2078
|
return Object.freeze({
|
|
2067
2079
|
...holder,
|
|
@@ -2098,47 +2110,21 @@ async function runDirectoryHolderIsLive(runDir) {
|
|
|
2098
2110
|
const existing = await readLockFile(resolve(runDir, RUN_DIRECTORY_LOCK_FILE));
|
|
2099
2111
|
if (existing.state !== "holder") return { live: false };
|
|
2100
2112
|
return {
|
|
2101
|
-
live:
|
|
2113
|
+
live: !holderIsDead(existing),
|
|
2102
2114
|
holder: existing.holder
|
|
2103
2115
|
};
|
|
2104
2116
|
}
|
|
2105
|
-
const execFileAsync = promisify(execFile);
|
|
2106
|
-
/**
|
|
2107
|
-
* The OS's start token for a live pid: on Linux the `starttime` field of `/proc/<pid>/stat`
|
|
2108
|
-
* (clock ticks since boot), elsewhere the `ps` `lstart` column. The same process reports the
|
|
2109
|
-
* same token for its whole life and a later holder of the pid reports a different one.
|
|
2110
|
-
* `undefined` when the host cannot report one: the pid is gone, there is no `/proc` and no
|
|
2111
|
-
* `ps`, or the platform is Windows.
|
|
2112
|
-
*/
|
|
2113
|
-
async function readProcessStart(pid) {
|
|
2114
|
-
if (!Number.isInteger(pid) || pid <= 0 || process.platform === "win32") return void 0;
|
|
2115
|
-
if (process.platform === "linux") {
|
|
2116
|
-
let stat;
|
|
2117
|
-
try {
|
|
2118
|
-
stat = await readFile(`/proc/${pid}/stat`, "utf8");
|
|
2119
|
-
} catch {
|
|
2120
|
-
return;
|
|
2121
|
-
}
|
|
2122
|
-
const startTime = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u)[19];
|
|
2123
|
-
return startTime !== void 0 && /^\d+$/u.test(startTime) ? startTime : void 0;
|
|
2124
|
-
}
|
|
2125
|
-
try {
|
|
2126
|
-
const { stdout } = await execFileAsync("ps", [
|
|
2127
|
-
"-o",
|
|
2128
|
-
"lstart=",
|
|
2129
|
-
"-p",
|
|
2130
|
-
String(pid)
|
|
2131
|
-
]);
|
|
2132
|
-
const token = stdout.trim();
|
|
2133
|
-
return token.length === 0 ? void 0 : token;
|
|
2134
|
-
} catch {
|
|
2135
|
-
return;
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
2117
|
async function readLockFile(path) {
|
|
2139
2118
|
let text;
|
|
2119
|
+
let current;
|
|
2140
2120
|
try {
|
|
2141
|
-
|
|
2121
|
+
const file = await open(path, "r");
|
|
2122
|
+
try {
|
|
2123
|
+
current = lockIdentity(await file.stat({ bigint: true }));
|
|
2124
|
+
text = await file.readFile("utf8");
|
|
2125
|
+
} finally {
|
|
2126
|
+
await file.close();
|
|
2127
|
+
}
|
|
2142
2128
|
} catch (error) {
|
|
2143
2129
|
if (isNoEntError(error)) return { state: "missing" };
|
|
2144
2130
|
throw error;
|
|
@@ -2153,30 +2139,24 @@ async function readLockFile(path) {
|
|
|
2153
2139
|
const record = parsed;
|
|
2154
2140
|
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`);
|
|
2155
2141
|
const { pid, startedAt, runId, processStart } = record;
|
|
2142
|
+
const verdictRecord = parseLockHolderRecord(parsed);
|
|
2143
|
+
const { pid: _pid, ...identity } = verdictRecord ?? { pid };
|
|
2156
2144
|
return {
|
|
2157
2145
|
state: "holder",
|
|
2158
2146
|
holder: Object.freeze({
|
|
2159
2147
|
pid,
|
|
2160
2148
|
startedAt,
|
|
2161
2149
|
runId,
|
|
2162
|
-
...processStart === void 0 ? {} : { processStart }
|
|
2163
|
-
|
|
2150
|
+
...processStart === void 0 ? {} : { processStart },
|
|
2151
|
+
...identity
|
|
2152
|
+
}),
|
|
2153
|
+
record: verdictRecord,
|
|
2154
|
+
current
|
|
2164
2155
|
};
|
|
2165
2156
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
const current = await readProcessStart(holder.pid);
|
|
2170
|
-
return current === void 0 || current === holder.processStart;
|
|
2171
|
-
}
|
|
2172
|
-
function processExists(pid) {
|
|
2173
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
2174
|
-
try {
|
|
2175
|
-
process.kill(pid, 0);
|
|
2176
|
-
return true;
|
|
2177
|
-
} catch (error) {
|
|
2178
|
-
return error.code !== "ESRCH";
|
|
2179
|
-
}
|
|
2157
|
+
/** agent-eval's dead-owner rule: only a holder proved dead is stale; alive or unknown stays held. */
|
|
2158
|
+
function holderIsDead(existing) {
|
|
2159
|
+
return lockHolderVerdict(existing.record, existing.current).liveness === "dead";
|
|
2180
2160
|
}
|
|
2181
2161
|
/** Remove the lock only while it still names this holder; a reclaimed lock belongs to its new owner. */
|
|
2182
2162
|
async function releaseHolder(path, holder) {
|
|
@@ -2322,6 +2302,119 @@ function startPursuitObserverDelivery(delivery, read, warn = (message) => consol
|
|
|
2322
2302
|
};
|
|
2323
2303
|
}
|
|
2324
2304
|
//#endregion
|
|
2305
|
+
//#region src/durable/run-reopen.ts
|
|
2306
|
+
/** The directory that holds a run's archived settle records. */
|
|
2307
|
+
const SETTLE_ARCHIVE_DIR = "settles";
|
|
2308
|
+
/** Refuse a reopen that names no operator or reason, or that is combined with a fork or versions. */
|
|
2309
|
+
function assertRunReopen(reopen, opts) {
|
|
2310
|
+
if (reopen.operator.trim().length === 0) throw new TypeError("supervisePursuit: reopen.operator must be non-empty");
|
|
2311
|
+
if (reopen.reason.trim().length === 0) throw new TypeError("supervisePursuit: reopen.reason must be non-empty");
|
|
2312
|
+
if (reopen.operationId.trim().length === 0) throw new TypeError("supervisePursuit: reopen.operationId must be non-empty");
|
|
2313
|
+
if (reopen.budget !== void 0) assertValidBudget(reopen.budget, "reopen.budget");
|
|
2314
|
+
if (opts.fork !== void 0 || opts.versions !== void 0) throw new TypeError("supervisePursuit: reopen continues this run under its own id; fork and versions start new runs");
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Archive the settled segment of the run at `runDir` and clear it for a resume. Runs under the
|
|
2318
|
+
* directory lock. Throws when there is nothing to reopen, or when `operationId` already reopened a
|
|
2319
|
+
* segment that has since settled again.
|
|
2320
|
+
*/
|
|
2321
|
+
async function reopenSettledRun(input) {
|
|
2322
|
+
const { runDir, runId, reopen, observer, pursuitId, now } = input;
|
|
2323
|
+
const operationId = reopen.operationId.trim();
|
|
2324
|
+
const settlePath = resolve(runDir, SETTLE_RECORD_FILE);
|
|
2325
|
+
const journal = input.journal ?? new FileSpawnJournal(resolve(runDir, "spawn-journal.jsonl"));
|
|
2326
|
+
const events = await journal.loadTree(runId) ?? [];
|
|
2327
|
+
const reopened = events.filter((event) => event.kind === "reopened" && event.id === runId);
|
|
2328
|
+
const mine = reopened.find((event) => event.operationId === operationId);
|
|
2329
|
+
const settled = await readBytes(settlePath);
|
|
2330
|
+
if (settled === void 0) {
|
|
2331
|
+
if (mine !== void 0) return;
|
|
2332
|
+
throw new Error(`supervisePursuit: ${runDir} holds no ${SETTLE_RECORD_FILE}; nothing to reopen; resume it`);
|
|
2333
|
+
}
|
|
2334
|
+
assertResumeLimits(events, {
|
|
2335
|
+
runId,
|
|
2336
|
+
budget: reopen.budget ?? input.limits.budget,
|
|
2337
|
+
...input.limits.reservationPolicy === void 0 ? {} : { reservationPolicy: input.limits.reservationPolicy }
|
|
2338
|
+
}, mine === void 0 ? reopen.budget : void 0);
|
|
2339
|
+
const result = JSON.parse(settled);
|
|
2340
|
+
const digest = settleRecordDigest(result);
|
|
2341
|
+
if (mine !== void 0 && mine.prior.digest !== digest) throw new Error(`supervisePursuit: reopen '${operationId}' already reopened run '${runId}', which has settled again; reopen it under a new operationId`);
|
|
2342
|
+
const archivedAs = mine?.prior.archivedAs ?? await archiveName(runDir, settled);
|
|
2343
|
+
const archivePath = resolve(runDir, archivedAs);
|
|
2344
|
+
if (!publishExclusiveDurableFile(archivePath, settled) && await readBytes(archivePath) !== settled) throw new Error(`supervisePursuit: ${archivePath} already holds a different settle record; reopen refused`);
|
|
2345
|
+
const at = new Date(now()).toISOString();
|
|
2346
|
+
if (mine === void 0) await journal.appendEvent(runId, {
|
|
2347
|
+
kind: "reopened",
|
|
2348
|
+
id: runId,
|
|
2349
|
+
operator: reopen.operator,
|
|
2350
|
+
reason: reopen.reason,
|
|
2351
|
+
operationId,
|
|
2352
|
+
prior: {
|
|
2353
|
+
kind: String(result.kind),
|
|
2354
|
+
...typeof result.reason === "string" ? { reason: result.reason } : {},
|
|
2355
|
+
digest,
|
|
2356
|
+
archivedAs
|
|
2357
|
+
},
|
|
2358
|
+
...reopen.budget === void 0 ? {} : { budget: reopen.budget },
|
|
2359
|
+
seq: reopened.length,
|
|
2360
|
+
at
|
|
2361
|
+
});
|
|
2362
|
+
const eventId = `${runId}:pursuit:reopened:${operationId}`;
|
|
2363
|
+
if (!(await observer.read()).some((record) => record.event?.id === eventId)) await observer.appendEvent({
|
|
2364
|
+
id: eventId,
|
|
2365
|
+
pursuitId,
|
|
2366
|
+
runId,
|
|
2367
|
+
target: "agent.run",
|
|
2368
|
+
phase: "event",
|
|
2369
|
+
timestamp: now(),
|
|
2370
|
+
payload: { reopened: {
|
|
2371
|
+
operator: reopen.operator,
|
|
2372
|
+
reason: reopen.reason,
|
|
2373
|
+
archivedAs
|
|
2374
|
+
} }
|
|
2375
|
+
});
|
|
2376
|
+
setCancellationAside(runDir, operationId);
|
|
2377
|
+
await rm(settlePath);
|
|
2378
|
+
syncDurableDirectory(runDir);
|
|
2379
|
+
}
|
|
2380
|
+
/** The next free `settles/<n>.json`, or the archive that already holds these bytes (a reopen that
|
|
2381
|
+
* crashed after archiving and before its journal row). */
|
|
2382
|
+
async function archiveName(runDir, settled) {
|
|
2383
|
+
const dir = resolve(runDir, SETTLE_ARCHIVE_DIR);
|
|
2384
|
+
mkdirSync(dir, { recursive: true });
|
|
2385
|
+
const numbers = readdirSync(dir).map((name) => /^(\d+)\.json$/u.exec(name)?.[1]).filter((n) => n !== void 0).map(Number).sort((a, b) => a - b);
|
|
2386
|
+
for (const n of numbers) if (await readBytes(join(dir, `${n}.json`)) === settled) return `${SETTLE_ARCHIVE_DIR}/${n}.json`;
|
|
2387
|
+
return `${SETTLE_ARCHIVE_DIR}/${(numbers.at(-1) ?? 0) + 1}.json`;
|
|
2388
|
+
}
|
|
2389
|
+
/** Move the settled run's cancel request and acknowledgement aside under the reopen's and the
|
|
2390
|
+
* request's operation ids, so the reopened run accepts a new `cancelRun`. Idempotent: an absent
|
|
2391
|
+
* file is skipped. A reopen's operation id names one segment, so a later segment cancelled under
|
|
2392
|
+
* the same cancel operation id lands beside this one instead of over it. */
|
|
2393
|
+
function setCancellationAside(runDir, reopenOperationId) {
|
|
2394
|
+
const requestFile = runCancelRequestFile(runDir);
|
|
2395
|
+
const ackFile = runCancellationFile(runDir);
|
|
2396
|
+
if (!existsSync(requestFile) && !existsSync(ackFile)) return;
|
|
2397
|
+
const source = existsSync(requestFile) ? requestFile : ackFile;
|
|
2398
|
+
const { operationId } = JSON.parse(readFileSync(source, "utf8"));
|
|
2399
|
+
const reopenedDir = join(workerCancellationsDir(runDir), "reopened");
|
|
2400
|
+
const aside = join(reopenedDir, encodeURIComponent(reopenOperationId), encodeURIComponent(operationId));
|
|
2401
|
+
mkdirSync(aside, { recursive: true });
|
|
2402
|
+
if (existsSync(ackFile)) renameSync(ackFile, join(aside, "run.json"));
|
|
2403
|
+
if (existsSync(requestFile)) renameSync(requestFile, join(aside, "run.request.json"));
|
|
2404
|
+
syncDurableDirectory(aside);
|
|
2405
|
+
syncDurableDirectory(join(reopenedDir, encodeURIComponent(reopenOperationId)));
|
|
2406
|
+
syncDurableDirectory(reopenedDir);
|
|
2407
|
+
syncDurableDirectory(workerCancellationsDir(runDir));
|
|
2408
|
+
}
|
|
2409
|
+
async function readBytes(path) {
|
|
2410
|
+
try {
|
|
2411
|
+
return await readFile(path, "utf8");
|
|
2412
|
+
} catch (error) {
|
|
2413
|
+
if (error.code === "ENOENT") return void 0;
|
|
2414
|
+
throw error;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
//#endregion
|
|
2325
2418
|
//#region src/durable/supervise-pursuit.ts
|
|
2326
2419
|
/** A failed Runtime execution whose complete third-person projection was retained. */
|
|
2327
2420
|
var SupervisePursuitError = class extends Error {
|
|
@@ -2359,16 +2452,33 @@ var SupervisePursuitError = class extends Error {
|
|
|
2359
2452
|
*/
|
|
2360
2453
|
async function supervisePursuit(profile, task, opts) {
|
|
2361
2454
|
assertPursuitDirectory(opts, "supervisePursuit");
|
|
2455
|
+
if (opts.reopen !== void 0) assertRunReopen(opts.reopen, opts);
|
|
2362
2456
|
if (opts.versions !== void 0) return runPursuitVersions(profile, task, opts, supervisePursuit);
|
|
2363
|
-
const { pursuitId: _pursuitId, hooks: _hooks, fork, versions: _versions, observerDelivery: _observerDelivery, ...superviseOptions } = opts;
|
|
2457
|
+
const { pursuitId: _pursuitId, hooks: _hooks, fork, versions: _versions, observerDelivery: _observerDelivery, reopen, ...superviseOptions } = opts;
|
|
2364
2458
|
const forked = fork === void 0 ? void 0 : await prepareRunFork(profile, task, superviseOptions, fork);
|
|
2365
2459
|
return runDurablePursuit(opts, ({ hooks, steerDir }) => supervise(forked?.profile ?? profile, task, {
|
|
2366
2460
|
...superviseOptions,
|
|
2367
2461
|
...forked === void 0 ? {} : { execution: forked.execution },
|
|
2462
|
+
...reopen === void 0 ? {} : {
|
|
2463
|
+
resume: true,
|
|
2464
|
+
...reopen.budget === void 0 ? {} : { budget: reopen.budget }
|
|
2465
|
+
},
|
|
2368
2466
|
steerDir,
|
|
2369
2467
|
retainedAtSettlement: "release",
|
|
2370
2468
|
hooks
|
|
2371
|
-
}), (result) => result)
|
|
2469
|
+
}), (result) => result, reopen === void 0 ? void 0 : (observer) => reopenSettledRun({
|
|
2470
|
+
runDir: opts.runDir.trim(),
|
|
2471
|
+
runId: opts.runId ?? "supervise",
|
|
2472
|
+
reopen,
|
|
2473
|
+
journal: opts.journal,
|
|
2474
|
+
limits: {
|
|
2475
|
+
budget: opts.budget,
|
|
2476
|
+
...opts.reservationPolicy === void 0 ? {} : { reservationPolicy: opts.reservationPolicy }
|
|
2477
|
+
},
|
|
2478
|
+
observer,
|
|
2479
|
+
pursuitId: opts.pursuitId.trim(),
|
|
2480
|
+
now: opts.now ?? Date.now
|
|
2481
|
+
}));
|
|
2372
2482
|
}
|
|
2373
2483
|
/**
|
|
2374
2484
|
* One-call durable execution of an `AgentGraph`: the graph twin of {@link supervisePursuit}.
|
|
@@ -2404,7 +2514,7 @@ function assertPursuitDirectory(opts, entry) {
|
|
|
2404
2514
|
* journal, failure record and settle record. `execute` receives the observer-composed hooks and
|
|
2405
2515
|
* the steer directory the run's control plane uses; `settled` names the value `result.json` holds.
|
|
2406
2516
|
*/
|
|
2407
|
-
async function runDurablePursuit(opts, execute, settled) {
|
|
2517
|
+
async function runDurablePursuit(opts, execute, settled, reopen) {
|
|
2408
2518
|
const pursuitId = opts.pursuitId.trim();
|
|
2409
2519
|
const runDir = opts.runDir.trim();
|
|
2410
2520
|
const observerPath = resolve(runDir, "observer.jsonl");
|
|
@@ -2416,9 +2526,12 @@ async function runDurablePursuit(opts, execute, settled) {
|
|
|
2416
2526
|
let delivery;
|
|
2417
2527
|
let terminalState = "failed";
|
|
2418
2528
|
try {
|
|
2419
|
-
const prior = await readSettleRecord(runDir);
|
|
2420
|
-
if (prior !== void 0) throw new SettledRunDirectoryError(settlePath, prior.tree.root, runId);
|
|
2421
2529
|
const observer = createFileObserverHooks(observerPath, pursuitId);
|
|
2530
|
+
if (reopen !== void 0) await reopen(observer.journal);
|
|
2531
|
+
else {
|
|
2532
|
+
const prior = await readSettleRecord(runDir);
|
|
2533
|
+
if (prior !== void 0) throw new SettledRunDirectoryError(settlePath, prior.tree.root, runId);
|
|
2534
|
+
}
|
|
2422
2535
|
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "before", now()));
|
|
2423
2536
|
delivery = opts.observerDelivery === void 0 ? void 0 : startPursuitObserverDelivery(opts.observerDelivery, async () => projectPursuit(await observer.journal.read()));
|
|
2424
2537
|
let result;
|