hub-runner 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hub-runner.mjs +8 -2
- package/check.mjs +1336 -0
- package/main.mjs +76 -23
- package/package.json +8 -6
package/main.mjs
CHANGED
|
@@ -12,7 +12,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
12
12
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
13
|
});
|
|
14
14
|
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
throw mod = 0, e;
|
|
19
|
+
}
|
|
16
20
|
};
|
|
17
21
|
var __copyProps = (to, from, except, desc) => {
|
|
18
22
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -449,8 +453,8 @@ var require_graceful_fs = __commonJS({
|
|
|
449
453
|
var gracefulQueue;
|
|
450
454
|
var previousSymbol;
|
|
451
455
|
if (typeof Symbol === "function" && typeof Symbol.for === "function") {
|
|
452
|
-
gracefulQueue = Symbol.for("graceful-fs.queue");
|
|
453
|
-
previousSymbol = Symbol.for("graceful-fs.previous");
|
|
456
|
+
gracefulQueue = /* @__PURE__ */ Symbol.for("graceful-fs.queue");
|
|
457
|
+
previousSymbol = /* @__PURE__ */ Symbol.for("graceful-fs.previous");
|
|
454
458
|
} else {
|
|
455
459
|
gracefulQueue = "___graceful-fs.queue";
|
|
456
460
|
previousSymbol = "___graceful-fs.previous";
|
|
@@ -1232,7 +1236,7 @@ var require_signal_exit = __commonJS({
|
|
|
1232
1236
|
var require_mtime_precision = __commonJS({
|
|
1233
1237
|
"../../hosted-hub/node_modules/proper-lockfile/lib/mtime-precision.js"(exports, module) {
|
|
1234
1238
|
"use strict";
|
|
1235
|
-
var cacheSymbol = Symbol();
|
|
1239
|
+
var cacheSymbol = /* @__PURE__ */ Symbol();
|
|
1236
1240
|
function probe(file, fs, callback) {
|
|
1237
1241
|
const cachedPrecision = fs[cacheSymbol];
|
|
1238
1242
|
if (cachedPrecision) {
|
|
@@ -1612,8 +1616,8 @@ var require_proper_lockfile = __commonJS({
|
|
|
1612
1616
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
1613
1617
|
import { homedir as homedir4 } from "node:os";
|
|
1614
1618
|
import { randomUUID } from "node:crypto";
|
|
1615
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync4, writeFileSync as
|
|
1616
|
-
import { join as
|
|
1619
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1620
|
+
import { join as join16 } from "node:path";
|
|
1617
1621
|
|
|
1618
1622
|
// ../../lib/clean-env.mjs
|
|
1619
1623
|
var INHERITED_ENV = /^(EVE_|WORKFLOW_)/;
|
|
@@ -3126,6 +3130,8 @@ async function stageLocal(workspace, dir2, pnpm) {
|
|
|
3126
3130
|
for (const entry of readdirSync3(staged)) if (!SKIP.has(entry)) rmSync2(join3(staged, entry), { recursive: true, force: true });
|
|
3127
3131
|
}
|
|
3128
3132
|
cpSync(join3(workspace, "agents"), join3(dir2, "agents"), { recursive: true, force: true, filter: (p) => !SKIP.has(p.split("/").at(-1)) });
|
|
3133
|
+
rmSync2(join3(dir2, "lib"), { recursive: true, force: true });
|
|
3134
|
+
if (existsSync3(join3(workspace, "lib"))) cpSync(join3(workspace, "lib"), join3(dir2, "lib"), { recursive: true, filter: (p) => !SKIP.has(p.split("/").at(-1)) });
|
|
3129
3135
|
const config = join3(dir2, "hub.workspace.json");
|
|
3130
3136
|
if (existsSync3(config) || (() => {
|
|
3131
3137
|
try {
|
|
@@ -4052,7 +4058,7 @@ function needsCopy({ from, to, paths = [] }) {
|
|
|
4052
4058
|
}
|
|
4053
4059
|
|
|
4054
4060
|
// ../../workspaces/session-workspaces.mjs
|
|
4055
|
-
var ATTACHMENTS = "/attachments/";
|
|
4061
|
+
var ATTACHMENTS = ["/attachments/", "/.eve/attachments/"];
|
|
4056
4062
|
var expand = (p) => resolve3(p === "~" ? homedir() : p.startsWith("~/") ? join11(homedir(), p.slice(2)) : p);
|
|
4057
4063
|
function createSessionWorkspaces({
|
|
4058
4064
|
stateDir,
|
|
@@ -4120,15 +4126,16 @@ function createSessionWorkspaces({
|
|
|
4120
4126
|
const rel = execFileSync2("git", ["-C", c.repo, "rev-parse", "--git-path", "info/exclude"], { encoding: "utf8" }).trim();
|
|
4121
4127
|
const file = resolve3(c.repo, rel);
|
|
4122
4128
|
const text = existsSync8(file) ? readFileSync7(file, "utf8") : "";
|
|
4123
|
-
|
|
4129
|
+
const missing = ATTACHMENTS.filter((p) => !text.split("\n").includes(p));
|
|
4130
|
+
if (missing.length) {
|
|
4124
4131
|
mkdirSync6(dirname5(file), { recursive: true });
|
|
4125
4132
|
appendFileSync(file, `${text && !text.endsWith("\n") ? "\n" : ""}# hub: eve's copies of thread images
|
|
4126
|
-
${
|
|
4133
|
+
${missing.join("\n")}
|
|
4127
4134
|
`);
|
|
4128
4135
|
}
|
|
4129
4136
|
excluded.add(c.repo);
|
|
4130
4137
|
} catch (error) {
|
|
4131
|
-
log2.warn(`[workspaces] couldn't exclude ${ATTACHMENTS} in ${c.repo}: ${error.message}`);
|
|
4138
|
+
log2.warn(`[workspaces] couldn't exclude ${ATTACHMENTS.join(" ")} in ${c.repo}: ${error.message}`);
|
|
4132
4139
|
}
|
|
4133
4140
|
}
|
|
4134
4141
|
async function provision(rec, c) {
|
|
@@ -4494,6 +4501,52 @@ function runnerWorktrees(dir2, workspace) {
|
|
|
4494
4501
|
};
|
|
4495
4502
|
}
|
|
4496
4503
|
|
|
4504
|
+
// ../../hosted-hub/runner/model-credential.ts
|
|
4505
|
+
import { renameSync as renameSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4506
|
+
import { join as join15 } from "node:path";
|
|
4507
|
+
var ENV_FILE = ".env.development.local";
|
|
4508
|
+
var REFRESH_BEFORE_MS = 50 * 6e4;
|
|
4509
|
+
function expiryOf(token) {
|
|
4510
|
+
const exp = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString()).exp;
|
|
4511
|
+
if (typeof exp !== "number") throw new Error("model credential: the hub answered a token with no expiry");
|
|
4512
|
+
return exp * 1e3;
|
|
4513
|
+
}
|
|
4514
|
+
function modelCredential(o) {
|
|
4515
|
+
const now = o.now ?? Date.now;
|
|
4516
|
+
let expiresAt = 0, timer, fetching, stopped = false;
|
|
4517
|
+
async function refresh() {
|
|
4518
|
+
const url = new URL(`${o.hubUrl}/api/model-credential`);
|
|
4519
|
+
if (o.bypass) url.searchParams.set("x-vercel-protection-bypass", o.bypass);
|
|
4520
|
+
const res = await o.fetch(url, { headers: { authorization: `Bearer ${o.botToken()}` }, signal: AbortSignal.timeout(15e3) });
|
|
4521
|
+
if (!res.ok) throw new Error(`model credential: HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
4522
|
+
const { token } = await res.json();
|
|
4523
|
+
if (typeof token !== "string" || !token) throw new Error("model credential: the hub answered no token");
|
|
4524
|
+
const expires = expiryOf(token);
|
|
4525
|
+
if (expires - REFRESH_BEFORE_MS <= now()) throw new Error("model credential: the hub answered a token that is already due");
|
|
4526
|
+
const file = join15(o.dir, ENV_FILE);
|
|
4527
|
+
writeFileSync6(`${file}.tmp`, `VERCEL_OIDC_TOKEN=${token}
|
|
4528
|
+
`, { mode: 384 });
|
|
4529
|
+
renameSync5(`${file}.tmp`, file);
|
|
4530
|
+
expiresAt = expires;
|
|
4531
|
+
clearTimeout(timer);
|
|
4532
|
+
if (stopped) return;
|
|
4533
|
+
timer = setTimeout(() => {
|
|
4534
|
+
void ensure().catch((e) => o.log("model credential", e.message));
|
|
4535
|
+
}, expires - REFRESH_BEFORE_MS - now());
|
|
4536
|
+
timer.unref?.();
|
|
4537
|
+
}
|
|
4538
|
+
function ensure() {
|
|
4539
|
+
if (expiresAt - REFRESH_BEFORE_MS > now()) return Promise.resolve();
|
|
4540
|
+
return fetching ??= refresh().finally(() => {
|
|
4541
|
+
fetching = void 0;
|
|
4542
|
+
});
|
|
4543
|
+
}
|
|
4544
|
+
return { ensure, stop: () => {
|
|
4545
|
+
stopped = true;
|
|
4546
|
+
clearTimeout(timer);
|
|
4547
|
+
} };
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4497
4550
|
// ../../hosted-hub/runner/hub-runner.ts
|
|
4498
4551
|
var realBots = { stage: stageLocal, supervise: createSupervisor };
|
|
4499
4552
|
var HeldElsewhere = class extends Error {
|
|
@@ -4507,9 +4560,7 @@ var RETRY_MS = 2e3;
|
|
|
4507
4560
|
var BATCH = 200;
|
|
4508
4561
|
var ACK_MS = 15e3;
|
|
4509
4562
|
function botEnv(env, config, bot, token) {
|
|
4510
|
-
const inherited = Object.entries(cleanEnv(env)).filter(([k]) => !/^(HUB_|ROSTER_)/.test(k));
|
|
4511
|
-
const credential = new URL(`${config.hubUrl}/api/model-credential`);
|
|
4512
|
-
if (config.bypass) credential.searchParams.set("x-vercel-protection-bypass", config.bypass);
|
|
4563
|
+
const inherited = Object.entries(cleanEnv(env)).filter(([k]) => !/^(HUB_|ROSTER_)/.test(k) && k !== "VERCEL_OIDC_TOKEN");
|
|
4513
4564
|
return {
|
|
4514
4565
|
...Object.fromEntries(inherited),
|
|
4515
4566
|
HUB_URL: config.hubUrl,
|
|
@@ -4519,9 +4570,7 @@ function botEnv(env, config, bot, token) {
|
|
|
4519
4570
|
HUB_WORKSPACE: config.workspace,
|
|
4520
4571
|
HUB_SANDBOX_SERVICES: "none",
|
|
4521
4572
|
HUB_SESSION_WORKTREE_DIR: worktreeDir(config.dir),
|
|
4522
|
-
...config.bypass ? { HUB_PROTECTION_BYPASS: config.bypass } : {}
|
|
4523
|
-
EVE_MODEL_CREDENTIAL_URL: credential.href,
|
|
4524
|
-
EVE_MODEL_CREDENTIAL_TOKEN: token
|
|
4573
|
+
...config.bypass ? { HUB_PROTECTION_BYPASS: config.bypass } : {}
|
|
4525
4574
|
};
|
|
4526
4575
|
}
|
|
4527
4576
|
var log = (...args) => console.error((/* @__PURE__ */ new Date()).toISOString(), "[hub-runner]", ...args);
|
|
@@ -4560,6 +4609,7 @@ async function run3(config, bots = realBots) {
|
|
|
4560
4609
|
if (!claim.ok) throw new Error(`claim: HTTP ${claim.status} ${(await claim.text()).slice(0, 200)}`);
|
|
4561
4610
|
const claimed = await claim.json();
|
|
4562
4611
|
token = String(claimed.token);
|
|
4612
|
+
const model = modelCredential({ fetch: net, hubUrl: config.hubUrl, bypass: config.bypass, botToken: () => claimed.botToken, dir: config.dir, log });
|
|
4563
4613
|
let workspace = "", commit = "";
|
|
4564
4614
|
let told = null, telling = Promise.resolve(), stopping = false, printed = "";
|
|
4565
4615
|
const tell = (failedCommit) => telling = telling.catch(() => {
|
|
@@ -4689,6 +4739,7 @@ async function run3(config, bots = realBots) {
|
|
|
4689
4739
|
if (created instanceof CreateRefused) return answered(d, { kind: "refused", error: created.message });
|
|
4690
4740
|
const eveSessionId = d.eveSessionId ?? (await hubJson(`/api/sessions/${encodeURIComponent(d.sessionId)}/eve`, { method: "POST", body: JSON.stringify({ eveSessionId: created }) })).eveSessionId;
|
|
4691
4741
|
await worktrees.ensure({ ...d, sessionId: eveSessionId });
|
|
4742
|
+
await model.ensure();
|
|
4692
4743
|
const { answer } = await sendToBot(net, eve(d.bot, work.human), { ...d, status: d.status ?? null, eveSessionId }, d.workspace, loadImage);
|
|
4693
4744
|
if (answer.kind === "ended") {
|
|
4694
4745
|
await upload({ sessionId: d.sessionId, eveSessionId }, d.bot, work.human, 0, { toTail: true });
|
|
@@ -4802,6 +4853,7 @@ async function run3(config, bots = realBots) {
|
|
|
4802
4853
|
async function compactOne({ sessionId, eveSessionId, bot, requestedAt }, human) {
|
|
4803
4854
|
if (compacted.get(sessionId)?.requestedAt !== requestedAt) {
|
|
4804
4855
|
const agent = running.get(bot);
|
|
4856
|
+
if (agent) await model.ensure();
|
|
4805
4857
|
const answer = agent ? await compactSession(net, eve(bot, human), eveSessionId).then((r) => ({ status: r.status, body: typeof r.body === "string" ? r.body : JSON.stringify(r.body) })) : { status: 503, body: "the bot is not running on this Mac" };
|
|
4806
4858
|
compacted.set(sessionId, { requestedAt, answer, reported: false });
|
|
4807
4859
|
}
|
|
@@ -4909,6 +4961,7 @@ async function run3(config, bots = realBots) {
|
|
|
4909
4961
|
stopping = true;
|
|
4910
4962
|
if (clear) await tell(null).catch((e) => log("setup report", e.message));
|
|
4911
4963
|
shutdown.abort();
|
|
4964
|
+
model.stop();
|
|
4912
4965
|
clearTimeout(retry);
|
|
4913
4966
|
await supervisor.stopAll();
|
|
4914
4967
|
await Promise.allSettled([feed, syncing]);
|
|
@@ -4925,22 +4978,22 @@ async function run3(config, bots = realBots) {
|
|
|
4925
4978
|
var need = (name) => process.env[name] || (() => {
|
|
4926
4979
|
throw new Error(`${name} is required`);
|
|
4927
4980
|
})();
|
|
4928
|
-
var configured = process.env.HUB_RUNNER_DIR ||
|
|
4981
|
+
var configured = process.env.HUB_RUNNER_DIR || join16(homedir4(), ".hub", "local");
|
|
4929
4982
|
mkdirSync7(configured, { recursive: true });
|
|
4930
4983
|
var dir = realpathSync4(configured);
|
|
4931
|
-
mkdirSync7(
|
|
4932
|
-
statusFile(
|
|
4984
|
+
mkdirSync7(join16(dir, "status"), { recursive: true });
|
|
4985
|
+
statusFile(join16(dir, "status", `${process.pid}.json`), { pid: process.pid, state: "starting", build: process.env.HUB_RUNNER_BUILD ?? "" });
|
|
4933
4986
|
process.on("exit", exitStatus);
|
|
4934
4987
|
var drainAsked = new Promise((resolve6) => process.on("SIGUSR2", () => resolve6()));
|
|
4935
|
-
var release = await import_proper_lockfile.default.lock(dir, { lockfilePath:
|
|
4988
|
+
var release = await import_proper_lockfile.default.lock(dir, { lockfilePath: join16(dir, "runner.lock"), stale: 1e4, realpath: false }).catch((error) => {
|
|
4936
4989
|
if (error.code !== "ELOCKED") throw error;
|
|
4937
4990
|
status("busy-here");
|
|
4938
4991
|
process.exit(5);
|
|
4939
4992
|
});
|
|
4940
|
-
var deviceFile =
|
|
4941
|
-
if (!existsSync11(deviceFile))
|
|
4993
|
+
var deviceFile = join16(dir, "device-id");
|
|
4994
|
+
if (!existsSync11(deviceFile)) writeFileSync7(deviceFile, randomUUID());
|
|
4942
4995
|
var device = readFileSync10(deviceFile, "utf8").trim();
|
|
4943
|
-
var credentials = process.env.HUB_URL || process.env.HUB_SESSION ? { hubUrl: need("HUB_URL"), session: need("HUB_SESSION"), bypass: process.env.HUB_PROTECTION_BYPASS || void 0 } : JSON.parse(readFileSync10(
|
|
4996
|
+
var credentials = process.env.HUB_URL || process.env.HUB_SESSION ? { hubUrl: need("HUB_URL"), session: need("HUB_SESSION"), bypass: process.env.HUB_PROTECTION_BYPASS || void 0 } : JSON.parse(readFileSync10(join16(dir, "credentials.json"), "utf8"));
|
|
4944
4997
|
var deviceName = process.env.HUB_DEVICE_NAME || device;
|
|
4945
4998
|
var started = await run3({
|
|
4946
4999
|
hubUrl: credentials.hubUrl.replace(/\/$/, ""),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hub-runner",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Runs a hub workspace's Mac bots on this Mac under launchd, with no app needed.",
|
|
6
6
|
"type": "module",
|
|
@@ -10,16 +10,18 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
12
|
"lib",
|
|
13
|
-
"main.mjs"
|
|
13
|
+
"main.mjs",
|
|
14
|
+
"check.mjs"
|
|
14
15
|
],
|
|
15
16
|
"engines": {
|
|
16
17
|
"node": ">=24"
|
|
17
18
|
},
|
|
18
|
-
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
},
|
|
21
19
|
"scripts": {
|
|
22
20
|
"build": "node scripts/build.mjs",
|
|
21
|
+
"prepack": "node scripts/build.mjs",
|
|
23
22
|
"test": "node --test --test-timeout=60000 --test-force-exit test/*.test.mjs"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
24
26
|
}
|
|
25
|
-
}
|
|
27
|
+
}
|