hub-runner 0.2.3 → 0.2.5
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/check.mjs +151 -187
- package/main.mjs +244 -266
- package/package.json +1 -1
package/main.mjs
CHANGED
|
@@ -127,29 +127,29 @@ var require_polyfills = __commonJS({
|
|
|
127
127
|
})(fs.rename);
|
|
128
128
|
}
|
|
129
129
|
fs.read = typeof fs.read !== "function" ? fs.read : (function(fs$read) {
|
|
130
|
-
function read(fd, buffer, offset, length,
|
|
130
|
+
function read(fd, buffer, offset, length, position2, callback_) {
|
|
131
131
|
var callback;
|
|
132
132
|
if (callback_ && typeof callback_ === "function") {
|
|
133
133
|
var eagCounter = 0;
|
|
134
134
|
callback = function(er, _, __) {
|
|
135
135
|
if (er && er.code === "EAGAIN" && eagCounter < 10) {
|
|
136
136
|
eagCounter++;
|
|
137
|
-
return fs$read.call(fs, fd, buffer, offset, length,
|
|
137
|
+
return fs$read.call(fs, fd, buffer, offset, length, position2, callback);
|
|
138
138
|
}
|
|
139
139
|
callback_.apply(this, arguments);
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
-
return fs$read.call(fs, fd, buffer, offset, length,
|
|
142
|
+
return fs$read.call(fs, fd, buffer, offset, length, position2, callback);
|
|
143
143
|
}
|
|
144
144
|
if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read);
|
|
145
145
|
return read;
|
|
146
146
|
})(fs.read);
|
|
147
147
|
fs.readSync = typeof fs.readSync !== "function" ? fs.readSync : /* @__PURE__ */ (function(fs$readSync) {
|
|
148
|
-
return function(fd, buffer, offset, length,
|
|
148
|
+
return function(fd, buffer, offset, length, position2) {
|
|
149
149
|
var eagCounter = 0;
|
|
150
150
|
while (true) {
|
|
151
151
|
try {
|
|
152
|
-
return fs$readSync.call(fs, fd, buffer, offset, length,
|
|
152
|
+
return fs$readSync.call(fs, fd, buffer, offset, length, position2);
|
|
153
153
|
} catch (er) {
|
|
154
154
|
if (er.code === "EAGAIN" && eagCounter < 10) {
|
|
155
155
|
eagCounter++;
|
|
@@ -461,10 +461,10 @@ var require_graceful_fs = __commonJS({
|
|
|
461
461
|
}
|
|
462
462
|
function noop() {
|
|
463
463
|
}
|
|
464
|
-
function publishQueue(context,
|
|
464
|
+
function publishQueue(context, queue4) {
|
|
465
465
|
Object.defineProperty(context, gracefulQueue, {
|
|
466
466
|
get: function() {
|
|
467
|
-
return
|
|
467
|
+
return queue4;
|
|
468
468
|
}
|
|
469
469
|
});
|
|
470
470
|
}
|
|
@@ -478,8 +478,8 @@ var require_graceful_fs = __commonJS({
|
|
|
478
478
|
console.error(m);
|
|
479
479
|
};
|
|
480
480
|
if (!fs[gracefulQueue]) {
|
|
481
|
-
|
|
482
|
-
publishQueue(fs,
|
|
481
|
+
queue3 = global[gracefulQueue] || [];
|
|
482
|
+
publishQueue(fs, queue3);
|
|
483
483
|
fs.close = (function(fs$close) {
|
|
484
484
|
function close(fd, cb) {
|
|
485
485
|
return fs$close.call(fs, fd, function(err) {
|
|
@@ -496,14 +496,14 @@ var require_graceful_fs = __commonJS({
|
|
|
496
496
|
return close;
|
|
497
497
|
})(fs.close);
|
|
498
498
|
fs.closeSync = (function(fs$closeSync) {
|
|
499
|
-
function
|
|
499
|
+
function closeSync4(fd) {
|
|
500
500
|
fs$closeSync.apply(fs, arguments);
|
|
501
501
|
resetQueue();
|
|
502
502
|
}
|
|
503
|
-
Object.defineProperty(
|
|
503
|
+
Object.defineProperty(closeSync4, previousSymbol, {
|
|
504
504
|
value: fs$closeSync
|
|
505
505
|
});
|
|
506
|
-
return
|
|
506
|
+
return closeSync4;
|
|
507
507
|
})(fs.closeSync);
|
|
508
508
|
if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
|
|
509
509
|
process.on("exit", function() {
|
|
@@ -512,7 +512,7 @@ var require_graceful_fs = __commonJS({
|
|
|
512
512
|
});
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
|
-
var
|
|
515
|
+
var queue3;
|
|
516
516
|
if (!global[gracefulQueue]) {
|
|
517
517
|
publishQueue(global, fs[gracefulQueue]);
|
|
518
518
|
}
|
|
@@ -1619,7 +1619,7 @@ import { randomUUID } from "node:crypto";
|
|
|
1619
1619
|
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1620
1620
|
import { join as join16 } from "node:path";
|
|
1621
1621
|
|
|
1622
|
-
// ../../lib/clean-env.mjs
|
|
1622
|
+
// ../../hosted-hub/lib/clean-env.mjs
|
|
1623
1623
|
var INHERITED_ENV = /^(EVE_|WORKFLOW_)/;
|
|
1624
1624
|
function cleanEnv(env = process.env) {
|
|
1625
1625
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !INHERITED_ENV.test(key)));
|
|
@@ -1629,12 +1629,12 @@ function cleanGitEnv(env = process.env) {
|
|
|
1629
1629
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !GIT_REPO_ENV.test(key)));
|
|
1630
1630
|
}
|
|
1631
1631
|
|
|
1632
|
-
// ../../lib/processes.mjs
|
|
1633
|
-
import { spawn
|
|
1634
|
-
import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2,
|
|
1632
|
+
// ../../hosted-hub/lib/processes.mjs
|
|
1633
|
+
import { spawn } from "node:child_process";
|
|
1634
|
+
import { closeSync as closeSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync2, renameSync, rmSync, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync2, writeSync } from "node:fs";
|
|
1635
1635
|
import { join as join2 } from "node:path";
|
|
1636
1636
|
|
|
1637
|
-
// ../../lib/fingerprint.mjs
|
|
1637
|
+
// ../../hosted-hub/lib/fingerprint.mjs
|
|
1638
1638
|
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
1639
1639
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
1640
1640
|
import { join, relative } from "node:path";
|
|
@@ -1675,23 +1675,23 @@ function botFingerprint({ stateDir, workspace, name, env, argv, shared }) {
|
|
|
1675
1675
|
const envKeys = Object.fromEntries(
|
|
1676
1676
|
Object.keys(env ?? {}).filter((k) => !VOLATILE_ENV.test(k)).sort().map((k) => [k, short(createHmac("sha256", key).update(`${k}\0${env[k]}`).digest("hex"))])
|
|
1677
1677
|
);
|
|
1678
|
-
const
|
|
1678
|
+
const parts2 = {
|
|
1679
1679
|
env: short(sha(JSON.stringify(envKeys))),
|
|
1680
1680
|
command: short(sha(JSON.stringify(argv))),
|
|
1681
1681
|
config: configDigest(workspace, name),
|
|
1682
1682
|
...shared
|
|
1683
1683
|
};
|
|
1684
|
-
const hash = short(sha(JSON.stringify(Object.keys(
|
|
1685
|
-
return { hash, parts, envKeys };
|
|
1684
|
+
const hash = short(sha(JSON.stringify(Object.keys(parts2).sort().map((k) => [k, parts2[k]]))));
|
|
1685
|
+
return { hash, parts: parts2, envKeys };
|
|
1686
1686
|
}
|
|
1687
1687
|
function fingerprintDiff(recorded, current) {
|
|
1688
1688
|
if (!recorded?.hash) return "it was started without a fingerprint (by an older registry)";
|
|
1689
1689
|
if (recorded.hash === current.hash) return null;
|
|
1690
|
-
const
|
|
1690
|
+
const parts2 = Object.keys({ ...recorded.parts, ...current.parts }).filter((k) => recorded.parts?.[k] !== current.parts[k]);
|
|
1691
1691
|
const out = [];
|
|
1692
|
-
for (const
|
|
1693
|
-
if (
|
|
1694
|
-
out.push(
|
|
1692
|
+
for (const part2 of parts2) {
|
|
1693
|
+
if (part2 !== "env" || !recorded.envKeys) {
|
|
1694
|
+
out.push(part2);
|
|
1695
1695
|
continue;
|
|
1696
1696
|
}
|
|
1697
1697
|
const keys = [.../* @__PURE__ */ new Set([...Object.keys(recorded.envKeys), ...Object.keys(current.envKeys)])].filter((k) => recorded.envKeys[k] !== current.envKeys[k]).sort();
|
|
@@ -1701,22 +1701,44 @@ function fingerprintDiff(recorded, current) {
|
|
|
1701
1701
|
}
|
|
1702
1702
|
var BUILD_SKIP = /* @__PURE__ */ new Set([...TREE_SKIP, ".eve", ".output"]);
|
|
1703
1703
|
|
|
1704
|
-
// ../../lib/
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1704
|
+
// ../../hosted-hub/lib/log-tail.mjs
|
|
1705
|
+
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
1706
|
+
function createLogTail(log2) {
|
|
1707
|
+
const tails = /* @__PURE__ */ new Map();
|
|
1708
|
+
function follow(name, file, offset) {
|
|
1709
|
+
tails.set(name, { file, offset, carry: "", prefix: `[${name}]`.padEnd(20) });
|
|
1710
|
+
}
|
|
1711
|
+
function pump() {
|
|
1712
|
+
for (const tail of tails.values()) {
|
|
1713
|
+
let size;
|
|
1714
|
+
try {
|
|
1715
|
+
size = statSync(tail.file).size;
|
|
1716
|
+
} catch {
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
if (size < tail.offset) tail.offset = 0;
|
|
1720
|
+
if (size === tail.offset) continue;
|
|
1721
|
+
const length = Math.min(size - tail.offset, 1e6);
|
|
1722
|
+
const buffer = Buffer.alloc(length);
|
|
1723
|
+
const fd = openSync(tail.file, "r");
|
|
1724
|
+
try {
|
|
1725
|
+
readSync(fd, buffer, 0, length, tail.offset);
|
|
1726
|
+
} finally {
|
|
1727
|
+
closeSync(fd);
|
|
1728
|
+
}
|
|
1729
|
+
tail.offset += length;
|
|
1730
|
+
const lines = (tail.carry + buffer.toString("utf8")).split("\n");
|
|
1731
|
+
tail.carry = lines.pop();
|
|
1732
|
+
for (const line of lines) if (line.trim() && !line.startsWith("Migration file of version")) log2(`${tail.prefix} ${line}`);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return { follow, pump };
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// ../../hosted-hub/lib/process-tree.mjs
|
|
1739
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
1709
1740
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1710
|
-
var CRASH_LIMIT = num("ROSTER_CRASH_LIMIT", 5);
|
|
1711
|
-
var CRASH_WINDOW_MS = num("ROSTER_CRASH_WINDOW_MS", 10 * 6e4);
|
|
1712
|
-
var BACKOFF_BASE_MS = num("ROSTER_BACKOFF_BASE_MS", 1e3);
|
|
1713
|
-
var BACKOFF_MAX_MS = num("ROSTER_BACKOFF_MAX_MS", 6e4);
|
|
1714
|
-
var HEALTH_TIMEOUT_MS = num("ROSTER_START_HEALTH_TIMEOUT_MS", 12e4);
|
|
1715
|
-
var PORT_RECHECK_MS = num("ROSTER_PORT_RECHECK_MS", 5e3);
|
|
1716
1741
|
var KILL_GRACE_MS = 3e3;
|
|
1717
|
-
var WATCH_MS = 1e3;
|
|
1718
|
-
var TREE_MS = 5e3;
|
|
1719
|
-
var LOG_ROTATE_BYTES = 2e7;
|
|
1720
1742
|
function groupAlive(pgid) {
|
|
1721
1743
|
if (!(pgid > 1)) return false;
|
|
1722
1744
|
try {
|
|
@@ -1739,7 +1761,7 @@ var C = { ...process.env, LC_ALL: "C" };
|
|
|
1739
1761
|
var SLOW_MS = 1e3;
|
|
1740
1762
|
function slow(what, started2) {
|
|
1741
1763
|
const ms = Date.now() - started2;
|
|
1742
|
-
if (ms >= SLOW_MS) console.warn(`[
|
|
1764
|
+
if (ms >= SLOW_MS) console.warn(`[runner] ${what} took ${ms}ms`);
|
|
1743
1765
|
}
|
|
1744
1766
|
var LSTART = String.raw`(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})`;
|
|
1745
1767
|
function table() {
|
|
@@ -1755,17 +1777,17 @@ function table() {
|
|
|
1755
1777
|
}
|
|
1756
1778
|
function treeOf(pid, rows) {
|
|
1757
1779
|
const byPid = new Map(rows.map((r) => [r.pid, r]));
|
|
1758
|
-
const
|
|
1759
|
-
for (const r of rows) (
|
|
1780
|
+
const children2 = /* @__PURE__ */ new Map();
|
|
1781
|
+
for (const r of rows) (children2.get(r.ppid) ?? children2.set(r.ppid, []).get(r.ppid)).push(r);
|
|
1760
1782
|
const out = [];
|
|
1761
|
-
const
|
|
1783
|
+
const queue3 = byPid.has(pid) ? [byPid.get(pid)] : [];
|
|
1762
1784
|
const seen = /* @__PURE__ */ new Set();
|
|
1763
|
-
while (
|
|
1764
|
-
const r =
|
|
1785
|
+
while (queue3.length) {
|
|
1786
|
+
const r = queue3.shift();
|
|
1765
1787
|
if (seen.has(r.pid)) continue;
|
|
1766
1788
|
seen.add(r.pid);
|
|
1767
1789
|
out.push({ pid: r.pid, pgid: r.pgid, startedAt: r.startedAt });
|
|
1768
|
-
|
|
1790
|
+
queue3.push(...children2.get(r.pid) ?? []);
|
|
1769
1791
|
}
|
|
1770
1792
|
return out;
|
|
1771
1793
|
}
|
|
@@ -1802,6 +1824,73 @@ async function healthy(port, ms = 1e3) {
|
|
|
1802
1824
|
}
|
|
1803
1825
|
}
|
|
1804
1826
|
var merge = (...lists) => [...new Map(lists.flat().map((m) => [m.pid, m])).values()];
|
|
1827
|
+
var treeAlive = (pgid, tree) => groupAlive(pgid) || liveMembers(tree, table()).length > 0;
|
|
1828
|
+
async function untilTreeGone(pgid, tree, ms) {
|
|
1829
|
+
const deadline = Date.now() + ms;
|
|
1830
|
+
while (treeAlive(pgid, tree)) {
|
|
1831
|
+
if (Date.now() >= deadline) return false;
|
|
1832
|
+
await sleep(200);
|
|
1833
|
+
}
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
function killTree(pgid, tree) {
|
|
1837
|
+
signalGroup(pgid, "SIGKILL");
|
|
1838
|
+
for (const g of new Set(liveMembers(tree, table()).map((m) => m.pgid))) signalGroup(g, "SIGKILL");
|
|
1839
|
+
}
|
|
1840
|
+
async function stopTree(pgid, tree, { name, timeoutMs, warn }) {
|
|
1841
|
+
if (!treeAlive(pgid, tree)) return true;
|
|
1842
|
+
let membersTermed = false;
|
|
1843
|
+
const termMembers = () => {
|
|
1844
|
+
membersTermed = true;
|
|
1845
|
+
for (const g of new Set(liveMembers(tree, table()).map((m) => m.pgid))) if (g !== pgid) signalGroup(g, "SIGTERM");
|
|
1846
|
+
};
|
|
1847
|
+
if (groupAlive(pgid)) signalGroup(pgid, "SIGTERM");
|
|
1848
|
+
else termMembers();
|
|
1849
|
+
const deadline = Date.now() + timeoutMs;
|
|
1850
|
+
let gone = true;
|
|
1851
|
+
while (treeAlive(pgid, tree)) {
|
|
1852
|
+
if (!membersTermed && !groupAlive(pgid)) termMembers();
|
|
1853
|
+
if (Date.now() >= deadline) {
|
|
1854
|
+
gone = false;
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
await sleep(200);
|
|
1858
|
+
}
|
|
1859
|
+
if (gone) return true;
|
|
1860
|
+
warn(`[runner] ${name} didn't stop within ${timeoutMs / 1e3}s of SIGTERM; sending SIGKILL`);
|
|
1861
|
+
killTree(pgid, tree);
|
|
1862
|
+
return untilTreeGone(pgid, tree, KILL_GRACE_MS);
|
|
1863
|
+
}
|
|
1864
|
+
function ownsLeader(record, rows, signature) {
|
|
1865
|
+
if (!(record.pid > 1) || record.pgid !== record.pid) return false;
|
|
1866
|
+
const row = rows.find((r) => r.pid === record.pid);
|
|
1867
|
+
if (!row || row.pgid !== record.pgid) return false;
|
|
1868
|
+
if (!` ${row.command} `.includes(signature)) return false;
|
|
1869
|
+
const member = record.tree?.find((m) => m.pid === record.pid);
|
|
1870
|
+
if (member) return member.startedAt === row.startedAt;
|
|
1871
|
+
const recorded = Date.parse(record.startedAt);
|
|
1872
|
+
return !(Number.isFinite(row.startedAt) && Number.isFinite(recorded) && Math.abs(row.startedAt - recorded) > 5e3);
|
|
1873
|
+
}
|
|
1874
|
+
function ourTree(record, rows, signature) {
|
|
1875
|
+
const leader = ownsLeader(record, rows, signature);
|
|
1876
|
+
return { leader, tree: merge(leader ? treeOf(record.pid, rows) : [], liveMembers(record.tree, rows)) };
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// ../../hosted-hub/lib/processes.mjs
|
|
1880
|
+
var num = (key, fallback) => {
|
|
1881
|
+
const raw = process.env[key];
|
|
1882
|
+
return raw === void 0 || raw === "" || !Number.isFinite(Number(raw)) ? fallback : Number(raw);
|
|
1883
|
+
};
|
|
1884
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1885
|
+
var CRASH_LIMIT = num("ROSTER_CRASH_LIMIT", 5);
|
|
1886
|
+
var CRASH_WINDOW_MS = num("ROSTER_CRASH_WINDOW_MS", 10 * 6e4);
|
|
1887
|
+
var BACKOFF_BASE_MS = num("ROSTER_BACKOFF_BASE_MS", 1e3);
|
|
1888
|
+
var BACKOFF_MAX_MS = num("ROSTER_BACKOFF_MAX_MS", 6e4);
|
|
1889
|
+
var HEALTH_TIMEOUT_MS = num("ROSTER_START_HEALTH_TIMEOUT_MS", 12e4);
|
|
1890
|
+
var PORT_RECHECK_MS = num("ROSTER_PORT_RECHECK_MS", 5e3);
|
|
1891
|
+
var WATCH_MS = 1e3;
|
|
1892
|
+
var TREE_MS = 5e3;
|
|
1893
|
+
var LOG_ROTATE_BYTES = 2e7;
|
|
1805
1894
|
function createSupervisor({
|
|
1806
1895
|
root,
|
|
1807
1896
|
// the workspace: eve dev's cwd
|
|
@@ -1814,18 +1903,12 @@ function createSupervisor({
|
|
|
1814
1903
|
isCurrent = () => true,
|
|
1815
1904
|
// is this agent object still on the roster?
|
|
1816
1905
|
// ({ agent, env, argv }) -> what a server spawned now runs with
|
|
1817
|
-
// (lib/fingerprint.mjs), or null. A previous
|
|
1906
|
+
// (lib/fingerprint.mjs), or null. A previous runner's server is
|
|
1818
1907
|
// adopted only if its recorded fingerprint matches.
|
|
1819
1908
|
fingerprint = null,
|
|
1820
1909
|
onChange = () => {
|
|
1821
1910
|
},
|
|
1822
1911
|
// status changed (broadcast)
|
|
1823
|
-
onUsage = () => {
|
|
1824
|
-
},
|
|
1825
|
-
// a running tree's memory changed (status() reports rssKb)
|
|
1826
|
-
onRecovered = () => {
|
|
1827
|
-
},
|
|
1828
|
-
// healthy again after a crash restart
|
|
1829
1912
|
onUp = () => {
|
|
1830
1913
|
},
|
|
1831
1914
|
// a server is serving: adopted, spawned, or respawned ({ startedAt, adopted, recovered })
|
|
@@ -1853,10 +1936,7 @@ function createSupervisor({
|
|
|
1853
1936
|
status: e.status,
|
|
1854
1937
|
...e.tree?.length ? { tree: e.tree } : {},
|
|
1855
1938
|
...e.lastExit ? { lastExit: e.lastExit } : {},
|
|
1856
|
-
...e.fingerprint ? { fingerprint: e.fingerprint } : {}
|
|
1857
|
-
// The daemon token it was spawned with (HUB_INSTANCE): an adopted bot
|
|
1858
|
-
// keeps its spawner's, and lib/daemon.mjs selfStopRefusal checks it here.
|
|
1859
|
-
...e.instance ? { instance: e.instance } : {}
|
|
1939
|
+
...e.fingerprint ? { fingerprint: e.fingerprint } : {}
|
|
1860
1940
|
}));
|
|
1861
1941
|
mkdirSync2(stateDir, { recursive: true });
|
|
1862
1942
|
const tmp = `${recordsPath}.${process.pid}.tmp`;
|
|
@@ -1877,34 +1957,7 @@ function createSupervisor({
|
|
|
1877
1957
|
persist();
|
|
1878
1958
|
onChange(entry.name);
|
|
1879
1959
|
}
|
|
1880
|
-
const
|
|
1881
|
-
function follow(name, file, offset) {
|
|
1882
|
-
tails.set(name, { file, offset, carry: "", prefix: `[${name}]`.padEnd(20) });
|
|
1883
|
-
}
|
|
1884
|
-
function pump() {
|
|
1885
|
-
for (const tail of tails.values()) {
|
|
1886
|
-
let size;
|
|
1887
|
-
try {
|
|
1888
|
-
size = statSync(tail.file).size;
|
|
1889
|
-
} catch {
|
|
1890
|
-
continue;
|
|
1891
|
-
}
|
|
1892
|
-
if (size < tail.offset) tail.offset = 0;
|
|
1893
|
-
if (size === tail.offset) continue;
|
|
1894
|
-
const length = Math.min(size - tail.offset, 1e6);
|
|
1895
|
-
const buffer = Buffer.alloc(length);
|
|
1896
|
-
const fd = openSync(tail.file, "r");
|
|
1897
|
-
try {
|
|
1898
|
-
readSync(fd, buffer, 0, length, tail.offset);
|
|
1899
|
-
} finally {
|
|
1900
|
-
closeSync(fd);
|
|
1901
|
-
}
|
|
1902
|
-
tail.offset += length;
|
|
1903
|
-
const lines = (tail.carry + buffer.toString("utf8")).split("\n");
|
|
1904
|
-
tail.carry = lines.pop();
|
|
1905
|
-
for (const line of lines) if (line.trim() && !line.startsWith("Migration file of version")) log2(`${tail.prefix} ${line}`);
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1960
|
+
const { follow, pump } = createLogTail(log2);
|
|
1908
1961
|
const pumpTimer = setInterval(pump, 250);
|
|
1909
1962
|
pumpTimer.unref();
|
|
1910
1963
|
function refreshTree(entry, rows = table()) {
|
|
@@ -1912,10 +1965,8 @@ function createSupervisor({
|
|
|
1912
1965
|
const before = JSON.stringify(entry.tree ?? []);
|
|
1913
1966
|
entry.tree = merge(treeOf(entry.pid, rows), liveMembers(entry.tree, rows));
|
|
1914
1967
|
const own = rows.filter((r) => r.pid === entry.pid || r.ppid === entry.pid);
|
|
1915
|
-
const rss = entry.rssKb;
|
|
1916
1968
|
if (own.length) entry.rssKb = own.reduce((sum, r) => sum + (r.rssKb || 0), 0);
|
|
1917
1969
|
if (JSON.stringify(entry.tree) !== before) persist();
|
|
1918
|
-
if (entry.rssKb !== rss) onUsage(entry.name);
|
|
1919
1970
|
}
|
|
1920
1971
|
const treeTimer = setInterval(() => {
|
|
1921
1972
|
const live = [...entries.values()].filter((e) => e.pid && !e.stopping);
|
|
@@ -1924,45 +1975,8 @@ function createSupervisor({
|
|
|
1924
1975
|
for (const e of live) refreshTree(e, rows);
|
|
1925
1976
|
}, TREE_MS);
|
|
1926
1977
|
treeTimer.unref();
|
|
1927
|
-
const treeAlive = (pgid, tree) => groupAlive(pgid) || liveMembers(tree, table()).length > 0;
|
|
1928
|
-
async function untilTreeGone(pgid, tree, ms) {
|
|
1929
|
-
const deadline = Date.now() + ms;
|
|
1930
|
-
while (treeAlive(pgid, tree)) {
|
|
1931
|
-
if (Date.now() >= deadline) return false;
|
|
1932
|
-
await sleep(200);
|
|
1933
|
-
}
|
|
1934
|
-
return true;
|
|
1935
|
-
}
|
|
1936
|
-
function killTree(pgid, tree) {
|
|
1937
|
-
signalGroup(pgid, "SIGKILL");
|
|
1938
|
-
for (const g of new Set(liveMembers(tree, table()).map((m) => m.pgid))) signalGroup(g, "SIGKILL");
|
|
1939
|
-
}
|
|
1940
|
-
async function stopTree(pgid, tree, name) {
|
|
1941
|
-
if (!treeAlive(pgid, tree)) return true;
|
|
1942
|
-
let membersTermed = false;
|
|
1943
|
-
const termMembers = () => {
|
|
1944
|
-
membersTermed = true;
|
|
1945
|
-
for (const g of new Set(liveMembers(tree, table()).map((m) => m.pgid))) if (g !== pgid) signalGroup(g, "SIGTERM");
|
|
1946
|
-
};
|
|
1947
|
-
if (groupAlive(pgid)) signalGroup(pgid, "SIGTERM");
|
|
1948
|
-
else termMembers();
|
|
1949
|
-
const deadline = Date.now() + stopTimeoutMs;
|
|
1950
|
-
let gone = true;
|
|
1951
|
-
while (treeAlive(pgid, tree)) {
|
|
1952
|
-
if (!membersTermed && !groupAlive(pgid)) termMembers();
|
|
1953
|
-
if (Date.now() >= deadline) {
|
|
1954
|
-
gone = false;
|
|
1955
|
-
break;
|
|
1956
|
-
}
|
|
1957
|
-
await sleep(200);
|
|
1958
|
-
}
|
|
1959
|
-
if (gone) return true;
|
|
1960
|
-
warn(`[registry] ${name} didn't stop within ${stopTimeoutMs / 1e3}s of SIGTERM; sending SIGKILL`);
|
|
1961
|
-
killTree(pgid, tree);
|
|
1962
|
-
return untilTreeGone(pgid, tree, KILL_GRACE_MS);
|
|
1963
|
-
}
|
|
1964
1978
|
const logFile = (name) => join2(logDir, `${name}.log`);
|
|
1965
|
-
const sizeOf = (file) => existsSync2(file) ?
|
|
1979
|
+
const sizeOf = (file) => existsSync2(file) ? statSync2(file).size : 0;
|
|
1966
1980
|
function entryFor(agent) {
|
|
1967
1981
|
let entry = entries.get(agent.name);
|
|
1968
1982
|
if (!entry) {
|
|
@@ -1973,33 +1987,19 @@ function createSupervisor({
|
|
|
1973
1987
|
entry.port = agent.port;
|
|
1974
1988
|
return entry;
|
|
1975
1989
|
}
|
|
1976
|
-
function ownsLeader(record, rows) {
|
|
1977
|
-
if (!(record.pid > 1) || record.pgid !== record.pid) return false;
|
|
1978
|
-
const row = rows.find((r) => r.pid === record.pid);
|
|
1979
|
-
if (!row || row.pgid !== record.pgid) return false;
|
|
1980
|
-
if (!` ${row.command} `.includes(signature(record.agent, record.port))) return false;
|
|
1981
|
-
const member = record.tree?.find((m) => m.pid === record.pid);
|
|
1982
|
-
if (member) return member.startedAt === row.startedAt;
|
|
1983
|
-
const recorded = Date.parse(record.startedAt);
|
|
1984
|
-
return !(Number.isFinite(row.startedAt) && Number.isFinite(recorded) && Math.abs(row.startedAt - recorded) > 5e3);
|
|
1985
|
-
}
|
|
1986
|
-
function ourTree(record, rows) {
|
|
1987
|
-
const leader = ownsLeader(record, rows);
|
|
1988
|
-
return { leader, tree: merge(leader ? treeOf(record.pid, rows) : [], liveMembers(record.tree, rows)) };
|
|
1989
|
-
}
|
|
1990
1990
|
async function killStale(record, why) {
|
|
1991
1991
|
const rows = table();
|
|
1992
|
-
const { leader, tree } = ourTree(record, rows);
|
|
1992
|
+
const { leader, tree } = ourTree(record, rows, signature(record.agent, record.port));
|
|
1993
1993
|
if (!leader && !tree.length) {
|
|
1994
|
-
if (groupAlive(record.pgid)) warn(`[
|
|
1994
|
+
if (groupAlive(record.pgid)) warn(`[runner] ! ${record.agent}: process group ${record.pgid} from processes.json is no longer its server; leaving it alone`);
|
|
1995
1995
|
return true;
|
|
1996
1996
|
}
|
|
1997
|
-
warn(`[
|
|
1998
|
-
return stopTree(leader ? record.pgid : null, tree, record.agent);
|
|
1997
|
+
warn(`[runner] ${record.agent}: stopping its stale server (pid ${record.pid}): ${why}`);
|
|
1998
|
+
return stopTree(leader ? record.pgid : null, tree, { name: record.agent, timeoutMs: stopTimeoutMs, warn });
|
|
1999
1999
|
}
|
|
2000
2000
|
async function vetAdoption(agent, record) {
|
|
2001
2001
|
const rows = table();
|
|
2002
|
-
const { leader, tree } = ourTree(record, rows);
|
|
2002
|
+
const { leader, tree } = ourTree(record, rows, signature(record.agent, record.port));
|
|
2003
2003
|
if (!leader && !tree.length) return { adopted: false };
|
|
2004
2004
|
if (!leader) return { adopted: false, stale: true, why: "its leader is gone" };
|
|
2005
2005
|
if (record.port !== agent.port) return { adopted: false, stale: true, why: `its port moved from ${record.port} to ${agent.port}` };
|
|
@@ -2031,7 +2031,7 @@ function createSupervisor({
|
|
|
2031
2031
|
const { tree } = vetted;
|
|
2032
2032
|
if (closed) return carry(agent, record, tree);
|
|
2033
2033
|
const entry = entryFor(agent);
|
|
2034
|
-
Object.assign(entry, { pid: record.pid, pgid: record.pgid, startedAt: record.startedAt, restarts: record.restarts ?? 0, status: "up", adopted: true, child: null, stopping: false, error: void 0, tree, fingerprint: record.fingerprint,
|
|
2034
|
+
Object.assign(entry, { pid: record.pid, pgid: record.pgid, startedAt: record.startedAt, restarts: record.restarts ?? 0, status: "up", adopted: true, child: null, stopping: false, error: void 0, tree, fingerprint: record.fingerprint, rssKb: void 0 });
|
|
2035
2035
|
const gen = ++entry.gen;
|
|
2036
2036
|
follow(agent.name, logFile(agent.name), sizeOf(logFile(agent.name)));
|
|
2037
2037
|
entry.watch = setInterval(() => {
|
|
@@ -2042,14 +2042,14 @@ function createSupervisor({
|
|
|
2042
2042
|
}
|
|
2043
2043
|
}, WATCH_MS);
|
|
2044
2044
|
entry.watch.unref();
|
|
2045
|
-
log2(`[
|
|
2045
|
+
log2(`[runner] adopted ${agent.name} (pid ${record.pid}, port ${agent.port})`);
|
|
2046
2046
|
changed(entry);
|
|
2047
2047
|
onUp(agent, { startedAt: entry.startedAt, adopted: true, recovered: false });
|
|
2048
2048
|
return { adopted: true };
|
|
2049
2049
|
}
|
|
2050
2050
|
function carry(agent, record, tree = record.tree ?? []) {
|
|
2051
2051
|
const entry = entryFor(agent);
|
|
2052
|
-
Object.assign(entry, { pid: record.pid, pgid: record.pgid, startedAt: record.startedAt, restarts: record.restarts ?? 0, status: record.status ?? "up", adopted: true, child: null, stopping: false, tree, fingerprint: record.fingerprint
|
|
2052
|
+
Object.assign(entry, { pid: record.pid, pgid: record.pgid, startedAt: record.startedAt, restarts: record.restarts ?? 0, status: record.status ?? "up", adopted: true, child: null, stopping: false, tree, fingerprint: record.fingerprint });
|
|
2053
2053
|
entry.gen++;
|
|
2054
2054
|
return { adopted: true, carried: true };
|
|
2055
2055
|
}
|
|
@@ -2075,7 +2075,7 @@ function createSupervisor({
|
|
|
2075
2075
|
return spawnAgent(entry, { recovered });
|
|
2076
2076
|
}
|
|
2077
2077
|
const error = `port ${port} is held by another process (${holders.map((pid) => describe(pid, rows)).join("; ")}), not by ${name}'s server; not touching it. ${name} starts once the port is free.`;
|
|
2078
|
-
if (entry.error !== error || entry.status !== "blocked") warn(`[
|
|
2078
|
+
if (entry.error !== error || entry.status !== "blocked") warn(`[runner] ! ${error}`);
|
|
2079
2079
|
Object.assign(entry, { status: "blocked", error, pid: null, pgid: null });
|
|
2080
2080
|
entry.timer = setTimeout(() => spawnAgent(entry, { recovered }), PORT_RECHECK_MS);
|
|
2081
2081
|
entry.timer.unref();
|
|
@@ -2084,7 +2084,7 @@ function createSupervisor({
|
|
|
2084
2084
|
mkdirSync2(logDir, { recursive: true });
|
|
2085
2085
|
const file = logFile(name);
|
|
2086
2086
|
if (sizeOf(file) > LOG_ROTATE_BYTES) renameSync(file, `${file}.1`);
|
|
2087
|
-
const fd =
|
|
2087
|
+
const fd = openSync2(file, "a");
|
|
2088
2088
|
writeSync(fd, `
|
|
2089
2089
|
--- ${(/* @__PURE__ */ new Date()).toISOString()} hub: starting ${name} on port ${port}
|
|
2090
2090
|
`);
|
|
@@ -2094,7 +2094,7 @@ function createSupervisor({
|
|
|
2094
2094
|
try {
|
|
2095
2095
|
fp = fingerprintFor(agent, spawnEnv);
|
|
2096
2096
|
} catch (error) {
|
|
2097
|
-
warn(`[
|
|
2097
|
+
warn(`[runner] ! ${name}: couldn't fingerprint its inputs (${error.message}); the next runner respawns it instead of adopting it`);
|
|
2098
2098
|
}
|
|
2099
2099
|
let child;
|
|
2100
2100
|
try {
|
|
@@ -2102,17 +2102,17 @@ function createSupervisor({
|
|
|
2102
2102
|
cwd: root,
|
|
2103
2103
|
env: spawnEnv,
|
|
2104
2104
|
detached: true,
|
|
2105
|
-
// own process group; survives the
|
|
2105
|
+
// own process group; survives the runner
|
|
2106
2106
|
stdio: ["ignore", fd, fd]
|
|
2107
2107
|
});
|
|
2108
2108
|
} finally {
|
|
2109
|
-
|
|
2109
|
+
closeSync2(fd);
|
|
2110
2110
|
}
|
|
2111
2111
|
const gen = ++entry.gen;
|
|
2112
|
-
Object.assign(entry, { child, pid: child.pid ?? null, pgid: child.pid ?? null, startedAt: (/* @__PURE__ */ new Date()).toISOString(), status: "starting", adopted: false, stopping: false, error: void 0, nextRestartAt: void 0, tree: [], slowWarned: false, fingerprint: fp ?? void 0,
|
|
2112
|
+
Object.assign(entry, { child, pid: child.pid ?? null, pgid: child.pid ?? null, startedAt: (/* @__PURE__ */ new Date()).toISOString(), status: "starting", adopted: false, stopping: false, error: void 0, nextRestartAt: void 0, tree: [], slowWarned: false, fingerprint: fp ?? void 0, rssKb: void 0 });
|
|
2113
2113
|
follow(name, file, sizeOf(file));
|
|
2114
2114
|
child.once("error", (error) => {
|
|
2115
|
-
warn(`[
|
|
2115
|
+
warn(`[runner] ! couldn't start ${name}: ${error.message}`);
|
|
2116
2116
|
exited(entry, gen, { code: null, signal: null, error: error.message });
|
|
2117
2117
|
});
|
|
2118
2118
|
child.once("exit", (code, signal) => exited(entry, gen, { code, signal }));
|
|
@@ -2137,15 +2137,14 @@ function createSupervisor({
|
|
|
2137
2137
|
entry.status = "up";
|
|
2138
2138
|
changed(entry);
|
|
2139
2139
|
onUp(entry.agent, { startedAt: entry.startedAt, adopted: false, recovered });
|
|
2140
|
-
if (recovered) onRecovered(entry.agent);
|
|
2141
2140
|
return;
|
|
2142
2141
|
}
|
|
2143
2142
|
}
|
|
2144
2143
|
if (Date.now() - started2 > HEALTH_TIMEOUT_MS && !entry.slowWarned) {
|
|
2145
|
-
warn(`[
|
|
2144
|
+
warn(`[runner] ! ${entry.name} isn't healthy ${HEALTH_TIMEOUT_MS / 1e3}s after starting; see ${logFile(entry.name)}`);
|
|
2146
2145
|
entry.slowWarned = true;
|
|
2147
2146
|
}
|
|
2148
|
-
await
|
|
2147
|
+
await sleep2(500);
|
|
2149
2148
|
}
|
|
2150
2149
|
}
|
|
2151
2150
|
function exited(entry, gen, { code, signal, error }) {
|
|
@@ -2171,13 +2170,13 @@ function createSupervisor({
|
|
|
2171
2170
|
if (entry.crashes.length >= CRASH_LIMIT) {
|
|
2172
2171
|
entry.status = "crashed";
|
|
2173
2172
|
entry.error = `exited ${entry.crashes.length} times within ${Math.round(CRASH_WINDOW_MS / 6e4)} min (last: ${how}); not restarting until restart_agent. Log: ${logFile(entry.name)}`;
|
|
2174
|
-
warn(`[
|
|
2173
|
+
warn(`[runner] ! ${entry.name} crashed: ${entry.error}`);
|
|
2175
2174
|
return changed(entry);
|
|
2176
2175
|
}
|
|
2177
2176
|
const delay3 = Math.min(BACKOFF_BASE_MS * 2 ** (entry.crashes.length - 1), BACKOFF_MAX_MS);
|
|
2178
2177
|
entry.status = "down";
|
|
2179
2178
|
entry.nextRestartAt = new Date(now + delay3).toISOString();
|
|
2180
|
-
warn(`[
|
|
2179
|
+
warn(`[runner] ${entry.name} exited unexpectedly (${how}); restarting in ${delay3 / 1e3}s`);
|
|
2181
2180
|
entry.timer = setTimeout(async () => {
|
|
2182
2181
|
await untilTreeGone(pgid, tree, KILL_GRACE_MS);
|
|
2183
2182
|
if (entry.status !== "down" || entry.stopping) return;
|
|
@@ -2189,7 +2188,7 @@ function createSupervisor({
|
|
|
2189
2188
|
changed(entry);
|
|
2190
2189
|
}
|
|
2191
2190
|
return {
|
|
2192
|
-
// Startup: adopt what's still running from a previous
|
|
2191
|
+
// Startup: adopt what's still running from a previous runner, start the rest.
|
|
2193
2192
|
startAll(agents) {
|
|
2194
2193
|
starting = (async () => {
|
|
2195
2194
|
const records = readRecords();
|
|
@@ -2207,7 +2206,7 @@ function createSupervisor({
|
|
|
2207
2206
|
const result = results.get(agent.name);
|
|
2208
2207
|
if (result.adopted) return;
|
|
2209
2208
|
if (closed && !result.superseded) {
|
|
2210
|
-
if (ourTree(record, table()).tree.length) carry(agent, record);
|
|
2209
|
+
if (ourTree(record, table(), signature(record.agent, record.port)).tree.length) carry(agent, record);
|
|
2211
2210
|
return;
|
|
2212
2211
|
}
|
|
2213
2212
|
await killStale(record, result.why ?? "it isn't serving");
|
|
@@ -2251,7 +2250,7 @@ function createSupervisor({
|
|
|
2251
2250
|
}
|
|
2252
2251
|
entry.status = "stopping";
|
|
2253
2252
|
changed(entry);
|
|
2254
|
-
const gone = await stopTree(pgid, tree, agent.name);
|
|
2253
|
+
const gone = await stopTree(pgid, tree, { name: agent.name, timeoutMs: stopTimeoutMs, warn });
|
|
2255
2254
|
if (gone) {
|
|
2256
2255
|
if (entry.pgid === pgid) {
|
|
2257
2256
|
entry.gen++;
|
|
@@ -2273,42 +2272,6 @@ function createSupervisor({
|
|
|
2273
2272
|
pump();
|
|
2274
2273
|
persist();
|
|
2275
2274
|
},
|
|
2276
|
-
// Exit without stopping any bot (a `hub restart` that keeps them): nothing
|
|
2277
|
-
// new starts, pending (re)starts are cancelled, each tree is re-read and
|
|
2278
|
-
// saved, and no signal is sent. The next registry vets every record
|
|
2279
|
-
// (vetAdoption) and adopts what still matches. Returns the bots left running.
|
|
2280
|
-
async detach() {
|
|
2281
|
-
closed = true;
|
|
2282
|
-
await starting.catch(() => {
|
|
2283
|
-
});
|
|
2284
|
-
clearInterval(pumpTimer);
|
|
2285
|
-
clearInterval(treeTimer);
|
|
2286
|
-
const rows = table();
|
|
2287
|
-
for (const e of entries.values()) {
|
|
2288
|
-
clearTimeout(e.timer);
|
|
2289
|
-
e.timer = void 0;
|
|
2290
|
-
e.claim = (e.claim ?? 0) + 1;
|
|
2291
|
-
clearInterval(e.watch);
|
|
2292
|
-
e.gen++;
|
|
2293
|
-
if (e.pid) refreshTree(e, rows);
|
|
2294
|
-
}
|
|
2295
|
-
pump();
|
|
2296
|
-
persist();
|
|
2297
|
-
return [...entries.values()].filter((e) => e.pid).map((e) => ({ name: e.name, pid: e.pid }));
|
|
2298
|
-
},
|
|
2299
|
-
// Drop a removed bot's record (after its server is confirmed down).
|
|
2300
|
-
forget(name) {
|
|
2301
|
-
const entry = entries.get(name);
|
|
2302
|
-
if (entry) {
|
|
2303
|
-
clearTimeout(entry.timer);
|
|
2304
|
-
clearInterval(entry.watch);
|
|
2305
|
-
entry.gen++;
|
|
2306
|
-
entry.claim = (entry.claim ?? 0) + 1;
|
|
2307
|
-
}
|
|
2308
|
-
entries.delete(name);
|
|
2309
|
-
tails.delete(name);
|
|
2310
|
-
persist();
|
|
2311
|
-
},
|
|
2312
2275
|
// Resolves true once the bot is up (spawned by us and healthy); false
|
|
2313
2276
|
// once it can't be: it crashed, left the roster, or this supervisor closed.
|
|
2314
2277
|
async waitUp(agent, timeoutMs) {
|
|
@@ -2318,21 +2281,10 @@ function createSupervisor({
|
|
|
2318
2281
|
if (closed || !isCurrent(agent)) return false;
|
|
2319
2282
|
if (entry?.agent === agent && entry.status === "up") return true;
|
|
2320
2283
|
if (entry?.status === "crashed") return false;
|
|
2321
|
-
await
|
|
2284
|
+
await sleep2(250);
|
|
2322
2285
|
}
|
|
2323
2286
|
return false;
|
|
2324
2287
|
},
|
|
2325
|
-
// A running bot's memory as of the last tree refresh (eve dev + its
|
|
2326
|
-
// server) and its uptime (an adopted one keeps its start). Kept out of
|
|
2327
|
-
// status(): that's in every state snapshot, and these change constantly.
|
|
2328
|
-
usage(name) {
|
|
2329
|
-
const e = entries.get(name);
|
|
2330
|
-
if (!e?.pid || e.status !== "up") return void 0;
|
|
2331
|
-
return {
|
|
2332
|
-
...e.rssKb ? { rssMb: Math.round(e.rssKb / 1024) } : {},
|
|
2333
|
-
...e.startedAt ? { uptimeS: Math.max(0, Math.round((Date.now() - Date.parse(e.startedAt)) / 1e3)) } : {}
|
|
2334
|
-
};
|
|
2335
|
-
},
|
|
2336
2288
|
status(name) {
|
|
2337
2289
|
const e = entries.get(name);
|
|
2338
2290
|
if (!e) return null;
|
|
@@ -2606,7 +2558,7 @@ var status = (state, extra = {}) => {
|
|
|
2606
2558
|
console.log(JSON.stringify({ state, ...extra }));
|
|
2607
2559
|
};
|
|
2608
2560
|
|
|
2609
|
-
// ../../lib/v26-facts.mjs
|
|
2561
|
+
// ../../hosted-hub/lib/v26-facts.mjs
|
|
2610
2562
|
function isV26Boundary(event) {
|
|
2611
2563
|
const type = event?.type;
|
|
2612
2564
|
return type === "session.waiting" || type === "session.completed" || type === "session.failed";
|
|
@@ -2663,9 +2615,9 @@ function committedOf(type, data, deliveryIds) {
|
|
|
2663
2615
|
case "message.received": {
|
|
2664
2616
|
const turnId = str(data.turnId);
|
|
2665
2617
|
if (turnId === void 0) return [];
|
|
2666
|
-
const
|
|
2618
|
+
const parts2 = userParts(data);
|
|
2667
2619
|
const ids = deliveryIds.length ? deliveryIds : [deliveryIdOf(data)];
|
|
2668
|
-
return ids.map((deliveryId) => fact("delivery.consumed", { deliveryId, turnId, parts }, data));
|
|
2620
|
+
return ids.map((deliveryId) => fact("delivery.consumed", { deliveryId, turnId, parts: parts2 }, data));
|
|
2669
2621
|
}
|
|
2670
2622
|
case "step.started": {
|
|
2671
2623
|
const runId = runIdOf(data), turnId = str(data.turnId);
|
|
@@ -2830,7 +2782,8 @@ function fact(type, data, source) {
|
|
|
2830
2782
|
}
|
|
2831
2783
|
var NO_RUN_SCOPE = /* @__PURE__ */ new Set(["context.started", "context.settled", "interaction.opened", "interaction.settled", "response.submitted", "response.settled"]);
|
|
2832
2784
|
function content(data, kind, phase, value) {
|
|
2833
|
-
|
|
2785
|
+
const run4 = typeof data.stepIndex === "number" ? { runId: runIdOf(data) } : {};
|
|
2786
|
+
return fact("content.completed", { partId: partIdOf(data, kind), ...run4, kind, phase, value }, data);
|
|
2834
2787
|
}
|
|
2835
2788
|
function turnSettled(data, settled) {
|
|
2836
2789
|
const turnId = str(data.turnId);
|
|
@@ -2849,12 +2802,12 @@ function sessionStarted(data) {
|
|
|
2849
2802
|
}
|
|
2850
2803
|
function userParts(data) {
|
|
2851
2804
|
if (Array.isArray(data.parts)) {
|
|
2852
|
-
return data.parts.flatMap((
|
|
2853
|
-
if (!isRecord(
|
|
2854
|
-
if (
|
|
2855
|
-
if (
|
|
2856
|
-
const url = str(
|
|
2857
|
-
return [{ kind: "file", mediaType:
|
|
2805
|
+
return data.parts.flatMap((part2) => {
|
|
2806
|
+
if (!isRecord(part2)) return [];
|
|
2807
|
+
if (part2.type === "text" && typeof part2.text === "string") return [{ kind: "text", text: part2.text }];
|
|
2808
|
+
if (part2.type === "file" && typeof part2.mediaType === "string") {
|
|
2809
|
+
const url = str(part2.url);
|
|
2810
|
+
return [{ kind: "file", mediaType: part2.mediaType, ...str(part2.filename) === void 0 ? {} : { filename: part2.filename }, ...typeof part2.size === "number" ? { size: part2.size } : {}, ...url === void 0 ? { unavailable: true } : { url } }];
|
|
2858
2811
|
}
|
|
2859
2812
|
return [];
|
|
2860
2813
|
});
|
|
@@ -2923,13 +2876,13 @@ function signInRequest(data) {
|
|
|
2923
2876
|
function signInOutcome(outcome) {
|
|
2924
2877
|
return outcome === "authorized" ? "accepted" : outcome === "declined" ? "declined" : outcome === "timed-out" ? "expired" : "failed";
|
|
2925
2878
|
}
|
|
2926
|
-
function usageOf(
|
|
2879
|
+
function usageOf(usage2) {
|
|
2927
2880
|
return {
|
|
2928
|
-
inputTokens: num2(
|
|
2929
|
-
outputTokens: num2(
|
|
2930
|
-
cacheReadTokens: num2(
|
|
2931
|
-
cacheWriteTokens: num2(
|
|
2932
|
-
...typeof
|
|
2881
|
+
inputTokens: num2(usage2.inputTokens),
|
|
2882
|
+
outputTokens: num2(usage2.outputTokens),
|
|
2883
|
+
cacheReadTokens: num2(usage2.cacheReadTokens),
|
|
2884
|
+
cacheWriteTokens: num2(usage2.cacheWriteTokens),
|
|
2885
|
+
...typeof usage2.costUsd === "number" ? { costUsd: usage2.costUsd } : {}
|
|
2933
2886
|
};
|
|
2934
2887
|
}
|
|
2935
2888
|
function errorOf(data) {
|
|
@@ -2940,7 +2893,7 @@ var str = (value) => typeof value === "string" ? value : void 0;
|
|
|
2940
2893
|
var num2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2941
2894
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2942
2895
|
|
|
2943
|
-
// ../../lib/session-stream.mjs
|
|
2896
|
+
// ../../hosted-hub/lib/session-stream.mjs
|
|
2944
2897
|
async function* readSessionLines(body, startIndex) {
|
|
2945
2898
|
let line = startIndex;
|
|
2946
2899
|
let lastAt = null;
|
|
@@ -3021,16 +2974,26 @@ async function* streamLines(eve, sessionId, o) {
|
|
|
3021
2974
|
query.set("follow", "false");
|
|
3022
2975
|
query.set("includeTailIndex", "1");
|
|
3023
2976
|
}
|
|
3024
|
-
const
|
|
3025
|
-
let timer = setTimeout(() =>
|
|
2977
|
+
const idle3 = new AbortController(), signal = o.signal ? AbortSignal.any([o.signal, idle3.signal]) : idle3.signal;
|
|
2978
|
+
let timer = setTimeout(() => idle3.abort(), idleMs);
|
|
2979
|
+
const opened = Date.now();
|
|
2980
|
+
let firstByte = false, lineAt = opened;
|
|
3026
2981
|
const quiet = () => {
|
|
3027
2982
|
clearTimeout(timer);
|
|
3028
|
-
timer = setTimeout(() =>
|
|
2983
|
+
timer = setTimeout(() => idle3.abort(), idleMs);
|
|
2984
|
+
if (!firstByte) {
|
|
2985
|
+
firstByte = true;
|
|
2986
|
+
console.log(JSON.stringify({ event: "hub.stream_first_byte", sessionId, startIndex: next, ms: Date.now() - opened }));
|
|
2987
|
+
}
|
|
2988
|
+
if (Date.now() - lineAt >= idleMs) {
|
|
2989
|
+
console.log(JSON.stringify({ event: "hub.stream_no_lines", sessionId, next, ms: Date.now() - lineAt }));
|
|
2990
|
+
lineAt = Date.now();
|
|
2991
|
+
}
|
|
3029
2992
|
};
|
|
3030
2993
|
let renewed = false;
|
|
3031
2994
|
try {
|
|
3032
2995
|
const res = await fetchFn(`${eve.url}/v1/session/${encodeURIComponent(sessionId)}/stream?${query}`, { headers: eve.headers, cache: "no-store", redirect: "error", signal }).catch((error) => {
|
|
3033
|
-
if (o.signal?.aborted ||
|
|
2996
|
+
if (o.signal?.aborted || idle3.signal.aborted) return void 0;
|
|
3034
2997
|
throw error;
|
|
3035
2998
|
});
|
|
3036
2999
|
if (o.signal?.aborted) return;
|
|
@@ -3053,12 +3016,13 @@ async function* streamLines(eve, sessionId, o) {
|
|
|
3053
3016
|
break;
|
|
3054
3017
|
}
|
|
3055
3018
|
yield record;
|
|
3019
|
+
lineAt = Date.now();
|
|
3056
3020
|
next = record.line + 1;
|
|
3057
3021
|
reopens = o.reopens ?? 3;
|
|
3058
3022
|
}
|
|
3059
3023
|
} catch (error) {
|
|
3060
3024
|
if (o.signal?.aborted) return;
|
|
3061
|
-
if (!
|
|
3025
|
+
if (!idle3.signal.aborted) throw error;
|
|
3062
3026
|
}
|
|
3063
3027
|
}
|
|
3064
3028
|
} finally {
|
|
@@ -3075,7 +3039,16 @@ async function* watched(body, onBytes) {
|
|
|
3075
3039
|
}
|
|
3076
3040
|
}
|
|
3077
3041
|
|
|
3042
|
+
// ../../hosted-hub/node_modules/eve/dist/src/protocol/session-events/catalog.js
|
|
3043
|
+
var FACT_CATALOG = { "call.requested": { family: `call`, idField: `callId`, role: `introduces` }, "call.settled": { family: `call`, idField: `callId`, role: `terminal` }, "call.started": { family: `call`, idField: `callId`, role: `updates` }, "child.opened": { family: `child`, idField: `sessionId`, role: `introduces` }, "child.sent": { family: `child`, idField: `sessionId`, role: `record` }, "content.completed": { family: `content`, idField: `partId`, role: `complete` }, "context.settled": { family: `context`, idField: `changeId`, role: `terminal` }, "context.started": { family: `context`, idField: `changeId`, role: `introduces` }, "delivery.admitted": { family: `delivery`, idField: `deliveryId`, role: `introduces` }, "delivery.consumed": { family: `delivery`, idField: `deliveryId`, role: `updates` }, "delivery.settled": { family: `delivery`, idField: `deliveryId`, role: `terminal` }, "interaction.opened": { family: `interaction`, idField: `interactionId`, role: `introduces` }, "interaction.settled": { family: `interaction`, idField: `interactionId`, role: `terminal` }, "model.requested": { family: `model`, idField: `runId`, role: `introduces` }, "model.retried": { family: `model`, idField: `runId`, role: `updates` }, "model.settled": { family: `model`, idField: `runId`, role: `terminal` }, "model.started": { family: `model`, idField: `runId`, role: `updates` }, "response.admitted": { family: `response`, idField: `responseId`, role: `updates` }, "response.settled": { family: `response`, idField: `responseId`, role: `terminal` }, "response.submitted": { family: `response`, idField: `responseId`, role: `introduces` }, "session.ended": { family: `session`, idField: ``, role: `terminal` }, "session.started": { family: `session`, idField: ``, role: `introduces` }, "task.ended": { family: `task`, idField: `taskId`, role: `terminal` }, "task.started": { family: `task`, idField: `taskId`, role: `introduces` }, "turn.paused": { family: `turn`, idField: `turnId`, role: `updates` }, "turn.settled": { family: `turn`, idField: `turnId`, role: `terminal` }, "turn.started": { family: `turn`, idField: `turnId`, role: `introduces` }, "usage.recorded": { family: `usage`, idField: ``, role: `record` } };
|
|
3044
|
+
|
|
3045
|
+
// ../../hosted-hub/node_modules/eve/dist/src/protocol/session-projection/session-view.js
|
|
3046
|
+
var KEY = /* @__PURE__ */ Symbol.for(`eve.session-view-registry.v1`);
|
|
3047
|
+
var snapshots = globalThis[KEY] ??= /* @__PURE__ */ new WeakMap();
|
|
3048
|
+
|
|
3078
3049
|
// ../../hosted-hub/src/store/facts.ts
|
|
3050
|
+
var PROGRESS_TYPES2 = ["call.input", "call.progress", "content.delta"];
|
|
3051
|
+
var V27_TYPES = /* @__PURE__ */ new Set([...Object.keys(FACT_CATALOG), ...PROGRESS_TYPES2]);
|
|
3079
3052
|
var TURN_SETTLED = "turn.settled";
|
|
3080
3053
|
var SESSION_ENDED = "session.ended";
|
|
3081
3054
|
var TURN_PAUSED = "turn.paused";
|
|
@@ -3192,7 +3165,7 @@ async function downloadWorkspace(res, dir2) {
|
|
|
3192
3165
|
}
|
|
3193
3166
|
|
|
3194
3167
|
// ../../hosted-hub/runner/bot-state.ts
|
|
3195
|
-
import { closeSync as
|
|
3168
|
+
import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync3 } from "node:fs";
|
|
3196
3169
|
var MARKER = "hub: starting";
|
|
3197
3170
|
var WINDOW = 16 * 1024;
|
|
3198
3171
|
var LINES = 5;
|
|
@@ -3200,13 +3173,13 @@ var CHARS = 500;
|
|
|
3200
3173
|
function latestRun(file) {
|
|
3201
3174
|
let text;
|
|
3202
3175
|
try {
|
|
3203
|
-
const size =
|
|
3176
|
+
const size = statSync3(file).size, fd = openSync3(file, "r");
|
|
3204
3177
|
try {
|
|
3205
3178
|
const buf = Buffer.alloc(Math.min(size, WINDOW));
|
|
3206
3179
|
readSync2(fd, buf, 0, buf.length, size - buf.length);
|
|
3207
3180
|
text = buf.toString("utf8");
|
|
3208
3181
|
} finally {
|
|
3209
|
-
|
|
3182
|
+
closeSync3(fd);
|
|
3210
3183
|
}
|
|
3211
3184
|
} catch {
|
|
3212
3185
|
return void 0;
|
|
@@ -3363,7 +3336,7 @@ async function diskUsage(path) {
|
|
|
3363
3336
|
|
|
3364
3337
|
// ../../workspaces/snapshot.mjs
|
|
3365
3338
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3366
|
-
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as
|
|
3339
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
|
|
3367
3340
|
import { copyFile, mkdir, rm } from "node:fs/promises";
|
|
3368
3341
|
import { tmpdir } from "node:os";
|
|
3369
3342
|
import { dirname as dirname2, join as join6, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
@@ -3471,7 +3444,7 @@ async function saveCheckout(info, { threadId, name, snapshotRoot, root, label =
|
|
|
3471
3444
|
const target = existsSync5(bundle) ? bundle.replace(/\.bundle$/, `-${sha2.slice(0, 8)}.bundle`) : bundle;
|
|
3472
3445
|
await git(checkout, ["bundle", "create", "-q", target, tmpRef, "--branches", "--not", "--remotes"]);
|
|
3473
3446
|
await git(checkout, ["bundle", "verify", "-q", target]);
|
|
3474
|
-
return { checkout, kind: "bundle", bundle: target, bytes:
|
|
3447
|
+
return { checkout, kind: "bundle", bundle: target, bytes: statSync4(target).size, sha: sha2, dirty: info.dirty, unsaved: info.unsaved };
|
|
3475
3448
|
} finally {
|
|
3476
3449
|
await git(checkout, ["update-ref", "-d", tmpRef], { allowFail: true });
|
|
3477
3450
|
}
|
|
@@ -3811,28 +3784,28 @@ function createWorkspaceManager({
|
|
|
3811
3784
|
} else if (!snapshotRoot) throw new Error("purge needs a snapshotRoot");
|
|
3812
3785
|
const bytes = await sizeOf(rec.path) ?? 0;
|
|
3813
3786
|
if (dryRun) return { purged: true, dryRun, bytes, record: rec };
|
|
3814
|
-
let
|
|
3787
|
+
let snapshots2 = [];
|
|
3815
3788
|
if (lossless) {
|
|
3816
3789
|
if (!await stillEligible()) return no("ineligible");
|
|
3817
3790
|
const r = await repoOp(rec.repo, () => git(rec.repo, ["worktree", "remove", rec.path], { allowFail: true }));
|
|
3818
3791
|
if (r.code !== 0) return no("remove-failed", { error: r.stderr.trim() });
|
|
3819
3792
|
} else {
|
|
3820
3793
|
try {
|
|
3821
|
-
|
|
3794
|
+
snapshots2 = await saveAll(rec.path, { threadId: rec.threadId, name: rec.agent, snapshotRoot });
|
|
3822
3795
|
} catch (error) {
|
|
3823
3796
|
return no("snapshot-failed", { error: error.message, bytes });
|
|
3824
3797
|
}
|
|
3825
|
-
if (!await stillEligible()) return no("ineligible", { snapshots });
|
|
3798
|
+
if (!await stillEligible()) return no("ineligible", { snapshots: snapshots2 });
|
|
3826
3799
|
const nested = [...new Set(linkedWorktrees(rec.path).map((w) => w.common).filter((r) => r && normPath(r) !== normPath(rec.repo) && !normPath(r).startsWith(normPath(rec.path) + "/")))];
|
|
3827
3800
|
await rm2(rec.path, { recursive: true, force: true });
|
|
3828
3801
|
await repoOp(rec.repo, () => git(rec.repo, ["worktree", "prune"], { allowFail: true }));
|
|
3829
3802
|
for (const repo of nested) await git(repo, ["worktree", "prune"], { allowFail: true });
|
|
3830
|
-
rec.purged = { at: now(), bytes, snapshots:
|
|
3803
|
+
rec.purged = { at: now(), bytes, snapshots: snapshots2.map(({ kind, repo, ref, bundle, bytes: b, sha: sha2, dirty, unsaved }) => ({ kind, repo, ref, bundle, bytes: b, sha: sha2, dirty, unsaved })) };
|
|
3831
3804
|
}
|
|
3832
3805
|
rec.state = "released";
|
|
3833
3806
|
rec.headSha = head;
|
|
3834
3807
|
rec.releasedAt = now();
|
|
3835
|
-
return { purged: true, bytes, snapshots, record: await put(rec) };
|
|
3808
|
+
return { purged: true, bytes, snapshots: snapshots2, record: await put(rec) };
|
|
3836
3809
|
}
|
|
3837
3810
|
async function purge(ref, opts) {
|
|
3838
3811
|
const r = await refFrom(ref);
|
|
@@ -3938,11 +3911,11 @@ function fileRecords(dir2) {
|
|
|
3938
3911
|
}
|
|
3939
3912
|
|
|
3940
3913
|
// ../../workspaces/bare-guard.mjs
|
|
3941
|
-
import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as
|
|
3914
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
|
|
3942
3915
|
import { join as join9 } from "node:path";
|
|
3943
3916
|
async function repairBareFlag(repo) {
|
|
3944
3917
|
const dotgit = join9(repo, ".git");
|
|
3945
|
-
if (!existsSync7(dotgit) || !
|
|
3918
|
+
if (!existsSync7(dotgit) || !statSync5(dotgit).isDirectory()) return false;
|
|
3946
3919
|
const file = join9(dotgit, "config");
|
|
3947
3920
|
try {
|
|
3948
3921
|
const text = readFileSync6(file, "utf8");
|
|
@@ -4336,7 +4309,7 @@ var MODEL_AUTH = ["project", "user", "codex"];
|
|
|
4336
4309
|
var KINDS = ["coordinator", "planner", "builder", "reviewer", "git-ops", "demo"];
|
|
4337
4310
|
var WorkspaceError = class extends Error {
|
|
4338
4311
|
};
|
|
4339
|
-
var
|
|
4312
|
+
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4340
4313
|
var fail = (file, message) => {
|
|
4341
4314
|
throw new WorkspaceError(`${file}: ${message}`);
|
|
4342
4315
|
};
|
|
@@ -4346,13 +4319,13 @@ function agentRunsOn(parsed, name) {
|
|
|
4346
4319
|
return agent.runsOn ?? parsed.runsOn ?? "hosted";
|
|
4347
4320
|
}
|
|
4348
4321
|
function parseWorkspace(data, file = WORKSPACE_FILE) {
|
|
4349
|
-
if (!
|
|
4322
|
+
if (!isObject2(data)) fail(file, 'must be a JSON object with an "agents" array');
|
|
4350
4323
|
if (!Array.isArray(data.agents)) fail(file, '"agents" must be an array');
|
|
4351
4324
|
const seen = /* @__PURE__ */ new Set();
|
|
4352
4325
|
const ports = /* @__PURE__ */ new Set();
|
|
4353
4326
|
const agents = data.agents.map((agent, i) => {
|
|
4354
4327
|
const at = `agents[${i}]`;
|
|
4355
|
-
if (!
|
|
4328
|
+
if (!isObject2(agent)) fail(file, `${at} must be an object`);
|
|
4356
4329
|
if (typeof agent.name !== "string" || !NAME2.test(agent.name)) fail(file, `${at}.name must be a lowercase handle like deep-diver (got ${JSON.stringify(agent.name)})`);
|
|
4357
4330
|
const label = `${at} (${agent.name})`;
|
|
4358
4331
|
if (seen.has(agent.name)) fail(file, `${label}: duplicate name`);
|
|
@@ -4362,13 +4335,13 @@ function parseWorkspace(data, file = WORKSPACE_FILE) {
|
|
|
4362
4335
|
}
|
|
4363
4336
|
if (agent.color !== void 0 && (typeof agent.color !== "string" || !COLOR.test(agent.color))) fail(file, `${label}.color must be a hex color like #a78bfa`);
|
|
4364
4337
|
if (agent.subagents !== void 0) {
|
|
4365
|
-
if (!
|
|
4338
|
+
if (!isObject2(agent.subagents)) fail(file, `${label}.subagents must be an object of { name: { model?, reasoning? } }`);
|
|
4366
4339
|
for (const [sub, spec] of Object.entries(agent.subagents)) {
|
|
4367
|
-
if (!
|
|
4340
|
+
if (!isObject2(spec)) fail(file, `${label}.subagents.${sub} must be an object`);
|
|
4368
4341
|
if (spec.when !== void 0 && (typeof spec.when !== "string" || !spec.when.trim() || /\n/.test(spec.when.trim()))) fail(file, `${label}.subagents.${sub}.when must be a one-line string`);
|
|
4369
4342
|
}
|
|
4370
4343
|
}
|
|
4371
|
-
if (agent.subagentCap !== void 0 && !
|
|
4344
|
+
if (agent.subagentCap !== void 0 && !isObject2(agent.subagentCap)) fail(file, `${label}.subagentCap must be an object`);
|
|
4372
4345
|
if (agent.limits !== void 0 && limitsError(agent.limits)) fail(file, `${label}.limits${limitsError(agent.limits)}`);
|
|
4373
4346
|
if (agent.port !== void 0) {
|
|
4374
4347
|
if (!Number.isInteger(agent.port) || agent.port < 1024 || agent.port > 65535) fail(file, `${label}.port must be an integer between 1024 and 65535`);
|
|
@@ -4394,14 +4367,14 @@ function parseWorkspace(data, file = WORKSPACE_FILE) {
|
|
|
4394
4367
|
if (data.humans !== void 0) {
|
|
4395
4368
|
if (!Array.isArray(data.humans) || !data.humans.length) fail(file, '"humans" must be a non-empty array');
|
|
4396
4369
|
out.humans = data.humans.map((h, i) => {
|
|
4397
|
-
if (!
|
|
4370
|
+
if (!isObject2(h) || typeof h.name !== "string" || !HUMAN_NAME.test(h.name)) fail(file, `humans[${i}].name must be a lowercase handle like alice`);
|
|
4398
4371
|
if (seen.has(h.name)) fail(file, `humans[${i}] (${h.name}) has the same name as an agent`);
|
|
4399
4372
|
if (h.color !== void 0 && (typeof h.color !== "string" || !COLOR.test(h.color))) fail(file, `humans[${i}].color must be a hex color`);
|
|
4400
4373
|
return { ...h };
|
|
4401
4374
|
});
|
|
4402
4375
|
}
|
|
4403
4376
|
if (data.subagentCap !== void 0) {
|
|
4404
|
-
if (!
|
|
4377
|
+
if (!isObject2(data.subagentCap)) fail(file, '"subagentCap" must be an object');
|
|
4405
4378
|
out.subagentCap = data.subagentCap;
|
|
4406
4379
|
}
|
|
4407
4380
|
if (data.limits !== void 0) {
|
|
@@ -4561,8 +4534,8 @@ function heldReports(send, failed) {
|
|
|
4561
4534
|
if (reason) told.set(bot, reason);
|
|
4562
4535
|
else told.delete(bot);
|
|
4563
4536
|
});
|
|
4564
|
-
return (
|
|
4565
|
-
for (const bot of /* @__PURE__ */ new Set([...
|
|
4537
|
+
return (waiting3, why) => {
|
|
4538
|
+
for (const bot of /* @__PURE__ */ new Set([...waiting3, ...told.keys()])) {
|
|
4566
4539
|
const reason = why(bot);
|
|
4567
4540
|
if (now(bot) !== reason) tell(bot, reason).catch(failed);
|
|
4568
4541
|
}
|
|
@@ -4710,15 +4683,15 @@ async function run3(config, bots = realBots) {
|
|
|
4710
4683
|
async function retireDead(name, work) {
|
|
4711
4684
|
const open = work.sessions.filter((s) => s.bot === name);
|
|
4712
4685
|
for (const s of open) {
|
|
4713
|
-
const res = await cancelTurn(net, eve(name, work.human), s.eveSessionId);
|
|
4714
|
-
if (!res.ok) throw new Error(`cancel ${s.sessionId}: HTTP ${res.status}`);
|
|
4686
|
+
const res = await cancelTurn(net, eve(name, work.human), s.eveSessionId), body = res.ok ? "" : await res.text();
|
|
4687
|
+
if (!res.ok && !sessionEnded(res.status, body)) throw new Error(`cancel ${s.sessionId}: HTTP ${res.status} ${body.slice(0, 200)}`);
|
|
4715
4688
|
}
|
|
4716
|
-
|
|
4689
|
+
return retireEnded(open, name, work.human, "started afresh");
|
|
4690
|
+
}
|
|
4691
|
+
async function retireEnded(open, name, human, why) {
|
|
4692
|
+
for (const s of open) await upload(s, name, human, s.foldedIndex + 1, { toTail: true });
|
|
4717
4693
|
const sessionIds = open.map((s) => s.sessionId);
|
|
4718
|
-
if (sessionIds.length) {
|
|
4719
|
-
const { retired } = await hubJson("/api/local-runner/lost", { method: "POST", body: JSON.stringify({ sessionIds }) });
|
|
4720
|
-
log(name, "started afresh: retired", String(retired), "session(s) its last run left open");
|
|
4721
|
-
}
|
|
4694
|
+
if (sessionIds.length) log(name, `${why}: retired`, String((await hubJson("/api/local-runner/lost", { method: "POST", body: JSON.stringify({ sessionIds }) })).retired), "session(s)");
|
|
4722
4695
|
return sessionIds;
|
|
4723
4696
|
}
|
|
4724
4697
|
const worktrees = runnerWorktrees(config.dir, () => workspace);
|
|
@@ -4787,7 +4760,7 @@ async function run3(config, bots = realBots) {
|
|
|
4787
4760
|
}
|
|
4788
4761
|
async function upload({ sessionId, eveSessionId }, bot, human, from, { toTail = false, signal } = {}) {
|
|
4789
4762
|
const { url, headers } = eve(bot, human);
|
|
4790
|
-
let pending = [], posting = Promise.resolve(),
|
|
4763
|
+
let pending = [], posting = Promise.resolve(), failure3, dropped = false;
|
|
4791
4764
|
const halt = new AbortController();
|
|
4792
4765
|
const post = (expired = false) => posting = posting.then(async () => {
|
|
4793
4766
|
const lines = pending;
|
|
@@ -4809,7 +4782,7 @@ async function run3(config, bots = realBots) {
|
|
|
4809
4782
|
pending.push({ line: line.line, at: line.at ?? void 0, events: line.v26 ? [line.v26] : line.facts });
|
|
4810
4783
|
const flushed = post();
|
|
4811
4784
|
flushed.catch((e) => {
|
|
4812
|
-
|
|
4785
|
+
failure3 ??= e;
|
|
4813
4786
|
halt.abort();
|
|
4814
4787
|
});
|
|
4815
4788
|
const end = !toTail && boundaryOf(line.facts, line.v26, 0);
|
|
@@ -4820,7 +4793,7 @@ async function run3(config, bots = realBots) {
|
|
|
4820
4793
|
} catch (error) {
|
|
4821
4794
|
await posting.catch(() => {
|
|
4822
4795
|
});
|
|
4823
|
-
if (
|
|
4796
|
+
if (failure3) throw failure3;
|
|
4824
4797
|
if (dropped) return;
|
|
4825
4798
|
if (toTail && error instanceof StreamError && error.status === 404) return;
|
|
4826
4799
|
if (!ended(error)) throw error;
|
|
@@ -4846,8 +4819,13 @@ async function run3(config, bots = realBots) {
|
|
|
4846
4819
|
}
|
|
4847
4820
|
async function cancel(s, human) {
|
|
4848
4821
|
for (const turnId of s.cancel) {
|
|
4849
|
-
const res = await cancelTurn(net, eve(s.bot, human), s.eveSessionId, turnId);
|
|
4850
|
-
if (
|
|
4822
|
+
const res = await cancelTurn(net, eve(s.bot, human), s.eveSessionId, turnId), body = res.ok ? "" : await res.text();
|
|
4823
|
+
if (sessionEnded(res.status, body)) {
|
|
4824
|
+
await retireEnded([s], s.bot, human, "stop");
|
|
4825
|
+
wake();
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
if (!res.ok) throw new Error(`cancel ${s.sessionId}: HTTP ${res.status} ${body.slice(0, 200)}`);
|
|
4851
4829
|
}
|
|
4852
4830
|
}
|
|
4853
4831
|
const restarting = /* @__PURE__ */ new Map();
|