hub-runner 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main.mjs +74 -23
- package/package.json +6 -5
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_)/;
|
|
@@ -4052,7 +4056,7 @@ function needsCopy({ from, to, paths = [] }) {
|
|
|
4052
4056
|
}
|
|
4053
4057
|
|
|
4054
4058
|
// ../../workspaces/session-workspaces.mjs
|
|
4055
|
-
var ATTACHMENTS = "/attachments/";
|
|
4059
|
+
var ATTACHMENTS = ["/attachments/", "/.eve/attachments/"];
|
|
4056
4060
|
var expand = (p) => resolve3(p === "~" ? homedir() : p.startsWith("~/") ? join11(homedir(), p.slice(2)) : p);
|
|
4057
4061
|
function createSessionWorkspaces({
|
|
4058
4062
|
stateDir,
|
|
@@ -4120,15 +4124,16 @@ function createSessionWorkspaces({
|
|
|
4120
4124
|
const rel = execFileSync2("git", ["-C", c.repo, "rev-parse", "--git-path", "info/exclude"], { encoding: "utf8" }).trim();
|
|
4121
4125
|
const file = resolve3(c.repo, rel);
|
|
4122
4126
|
const text = existsSync8(file) ? readFileSync7(file, "utf8") : "";
|
|
4123
|
-
|
|
4127
|
+
const missing = ATTACHMENTS.filter((p) => !text.split("\n").includes(p));
|
|
4128
|
+
if (missing.length) {
|
|
4124
4129
|
mkdirSync6(dirname5(file), { recursive: true });
|
|
4125
4130
|
appendFileSync(file, `${text && !text.endsWith("\n") ? "\n" : ""}# hub: eve's copies of thread images
|
|
4126
|
-
${
|
|
4131
|
+
${missing.join("\n")}
|
|
4127
4132
|
`);
|
|
4128
4133
|
}
|
|
4129
4134
|
excluded.add(c.repo);
|
|
4130
4135
|
} catch (error) {
|
|
4131
|
-
log2.warn(`[workspaces] couldn't exclude ${ATTACHMENTS} in ${c.repo}: ${error.message}`);
|
|
4136
|
+
log2.warn(`[workspaces] couldn't exclude ${ATTACHMENTS.join(" ")} in ${c.repo}: ${error.message}`);
|
|
4132
4137
|
}
|
|
4133
4138
|
}
|
|
4134
4139
|
async function provision(rec, c) {
|
|
@@ -4494,6 +4499,52 @@ function runnerWorktrees(dir2, workspace) {
|
|
|
4494
4499
|
};
|
|
4495
4500
|
}
|
|
4496
4501
|
|
|
4502
|
+
// ../../hosted-hub/runner/model-credential.ts
|
|
4503
|
+
import { renameSync as renameSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4504
|
+
import { join as join15 } from "node:path";
|
|
4505
|
+
var ENV_FILE = ".env.development.local";
|
|
4506
|
+
var REFRESH_BEFORE_MS = 50 * 6e4;
|
|
4507
|
+
function expiryOf(token) {
|
|
4508
|
+
const exp = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString()).exp;
|
|
4509
|
+
if (typeof exp !== "number") throw new Error("model credential: the hub answered a token with no expiry");
|
|
4510
|
+
return exp * 1e3;
|
|
4511
|
+
}
|
|
4512
|
+
function modelCredential(o) {
|
|
4513
|
+
const now = o.now ?? Date.now;
|
|
4514
|
+
let expiresAt = 0, timer, fetching, stopped = false;
|
|
4515
|
+
async function refresh() {
|
|
4516
|
+
const url = new URL(`${o.hubUrl}/api/model-credential`);
|
|
4517
|
+
if (o.bypass) url.searchParams.set("x-vercel-protection-bypass", o.bypass);
|
|
4518
|
+
const res = await o.fetch(url, { headers: { authorization: `Bearer ${o.botToken()}` }, signal: AbortSignal.timeout(15e3) });
|
|
4519
|
+
if (!res.ok) throw new Error(`model credential: HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
4520
|
+
const { token } = await res.json();
|
|
4521
|
+
if (typeof token !== "string" || !token) throw new Error("model credential: the hub answered no token");
|
|
4522
|
+
const expires = expiryOf(token);
|
|
4523
|
+
if (expires - REFRESH_BEFORE_MS <= now()) throw new Error("model credential: the hub answered a token that is already due");
|
|
4524
|
+
const file = join15(o.dir, ENV_FILE);
|
|
4525
|
+
writeFileSync6(`${file}.tmp`, `VERCEL_OIDC_TOKEN=${token}
|
|
4526
|
+
`, { mode: 384 });
|
|
4527
|
+
renameSync5(`${file}.tmp`, file);
|
|
4528
|
+
expiresAt = expires;
|
|
4529
|
+
clearTimeout(timer);
|
|
4530
|
+
if (stopped) return;
|
|
4531
|
+
timer = setTimeout(() => {
|
|
4532
|
+
void ensure().catch((e) => o.log("model credential", e.message));
|
|
4533
|
+
}, expires - REFRESH_BEFORE_MS - now());
|
|
4534
|
+
timer.unref?.();
|
|
4535
|
+
}
|
|
4536
|
+
function ensure() {
|
|
4537
|
+
if (expiresAt - REFRESH_BEFORE_MS > now()) return Promise.resolve();
|
|
4538
|
+
return fetching ??= refresh().finally(() => {
|
|
4539
|
+
fetching = void 0;
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4542
|
+
return { ensure, stop: () => {
|
|
4543
|
+
stopped = true;
|
|
4544
|
+
clearTimeout(timer);
|
|
4545
|
+
} };
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4497
4548
|
// ../../hosted-hub/runner/hub-runner.ts
|
|
4498
4549
|
var realBots = { stage: stageLocal, supervise: createSupervisor };
|
|
4499
4550
|
var HeldElsewhere = class extends Error {
|
|
@@ -4507,9 +4558,7 @@ var RETRY_MS = 2e3;
|
|
|
4507
4558
|
var BATCH = 200;
|
|
4508
4559
|
var ACK_MS = 15e3;
|
|
4509
4560
|
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);
|
|
4561
|
+
const inherited = Object.entries(cleanEnv(env)).filter(([k]) => !/^(HUB_|ROSTER_)/.test(k) && k !== "VERCEL_OIDC_TOKEN");
|
|
4513
4562
|
return {
|
|
4514
4563
|
...Object.fromEntries(inherited),
|
|
4515
4564
|
HUB_URL: config.hubUrl,
|
|
@@ -4519,9 +4568,7 @@ function botEnv(env, config, bot, token) {
|
|
|
4519
4568
|
HUB_WORKSPACE: config.workspace,
|
|
4520
4569
|
HUB_SANDBOX_SERVICES: "none",
|
|
4521
4570
|
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
|
|
4571
|
+
...config.bypass ? { HUB_PROTECTION_BYPASS: config.bypass } : {}
|
|
4525
4572
|
};
|
|
4526
4573
|
}
|
|
4527
4574
|
var log = (...args) => console.error((/* @__PURE__ */ new Date()).toISOString(), "[hub-runner]", ...args);
|
|
@@ -4560,6 +4607,7 @@ async function run3(config, bots = realBots) {
|
|
|
4560
4607
|
if (!claim.ok) throw new Error(`claim: HTTP ${claim.status} ${(await claim.text()).slice(0, 200)}`);
|
|
4561
4608
|
const claimed = await claim.json();
|
|
4562
4609
|
token = String(claimed.token);
|
|
4610
|
+
const model = modelCredential({ fetch: net, hubUrl: config.hubUrl, bypass: config.bypass, botToken: () => claimed.botToken, dir: config.dir, log });
|
|
4563
4611
|
let workspace = "", commit = "";
|
|
4564
4612
|
let told = null, telling = Promise.resolve(), stopping = false, printed = "";
|
|
4565
4613
|
const tell = (failedCommit) => telling = telling.catch(() => {
|
|
@@ -4689,6 +4737,7 @@ async function run3(config, bots = realBots) {
|
|
|
4689
4737
|
if (created instanceof CreateRefused) return answered(d, { kind: "refused", error: created.message });
|
|
4690
4738
|
const eveSessionId = d.eveSessionId ?? (await hubJson(`/api/sessions/${encodeURIComponent(d.sessionId)}/eve`, { method: "POST", body: JSON.stringify({ eveSessionId: created }) })).eveSessionId;
|
|
4691
4739
|
await worktrees.ensure({ ...d, sessionId: eveSessionId });
|
|
4740
|
+
await model.ensure();
|
|
4692
4741
|
const { answer } = await sendToBot(net, eve(d.bot, work.human), { ...d, status: d.status ?? null, eveSessionId }, d.workspace, loadImage);
|
|
4693
4742
|
if (answer.kind === "ended") {
|
|
4694
4743
|
await upload({ sessionId: d.sessionId, eveSessionId }, d.bot, work.human, 0, { toTail: true });
|
|
@@ -4802,6 +4851,7 @@ async function run3(config, bots = realBots) {
|
|
|
4802
4851
|
async function compactOne({ sessionId, eveSessionId, bot, requestedAt }, human) {
|
|
4803
4852
|
if (compacted.get(sessionId)?.requestedAt !== requestedAt) {
|
|
4804
4853
|
const agent = running.get(bot);
|
|
4854
|
+
if (agent) await model.ensure();
|
|
4805
4855
|
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
4856
|
compacted.set(sessionId, { requestedAt, answer, reported: false });
|
|
4807
4857
|
}
|
|
@@ -4909,6 +4959,7 @@ async function run3(config, bots = realBots) {
|
|
|
4909
4959
|
stopping = true;
|
|
4910
4960
|
if (clear) await tell(null).catch((e) => log("setup report", e.message));
|
|
4911
4961
|
shutdown.abort();
|
|
4962
|
+
model.stop();
|
|
4912
4963
|
clearTimeout(retry);
|
|
4913
4964
|
await supervisor.stopAll();
|
|
4914
4965
|
await Promise.allSettled([feed, syncing]);
|
|
@@ -4925,22 +4976,22 @@ async function run3(config, bots = realBots) {
|
|
|
4925
4976
|
var need = (name) => process.env[name] || (() => {
|
|
4926
4977
|
throw new Error(`${name} is required`);
|
|
4927
4978
|
})();
|
|
4928
|
-
var configured = process.env.HUB_RUNNER_DIR ||
|
|
4979
|
+
var configured = process.env.HUB_RUNNER_DIR || join16(homedir4(), ".hub", "local");
|
|
4929
4980
|
mkdirSync7(configured, { recursive: true });
|
|
4930
4981
|
var dir = realpathSync4(configured);
|
|
4931
|
-
mkdirSync7(
|
|
4932
|
-
statusFile(
|
|
4982
|
+
mkdirSync7(join16(dir, "status"), { recursive: true });
|
|
4983
|
+
statusFile(join16(dir, "status", `${process.pid}.json`), { pid: process.pid, state: "starting", build: process.env.HUB_RUNNER_BUILD ?? "" });
|
|
4933
4984
|
process.on("exit", exitStatus);
|
|
4934
4985
|
var drainAsked = new Promise((resolve6) => process.on("SIGUSR2", () => resolve6()));
|
|
4935
|
-
var release = await import_proper_lockfile.default.lock(dir, { lockfilePath:
|
|
4986
|
+
var release = await import_proper_lockfile.default.lock(dir, { lockfilePath: join16(dir, "runner.lock"), stale: 1e4, realpath: false }).catch((error) => {
|
|
4936
4987
|
if (error.code !== "ELOCKED") throw error;
|
|
4937
4988
|
status("busy-here");
|
|
4938
4989
|
process.exit(5);
|
|
4939
4990
|
});
|
|
4940
|
-
var deviceFile =
|
|
4941
|
-
if (!existsSync11(deviceFile))
|
|
4991
|
+
var deviceFile = join16(dir, "device-id");
|
|
4992
|
+
if (!existsSync11(deviceFile)) writeFileSync7(deviceFile, randomUUID());
|
|
4942
4993
|
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(
|
|
4994
|
+
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
4995
|
var deviceName = process.env.HUB_DEVICE_NAME || device;
|
|
4945
4996
|
var started = await run3({
|
|
4946
4997
|
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.1",
|
|
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",
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=24"
|
|
17
17
|
},
|
|
18
|
-
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
},
|
|
21
18
|
"scripts": {
|
|
22
19
|
"build": "node scripts/build.mjs",
|
|
20
|
+
"prepack": "node scripts/build.mjs",
|
|
23
21
|
"test": "node --test --test-timeout=60000 --test-force-exit test/*.test.mjs"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
24
25
|
}
|
|
25
|
-
}
|
|
26
|
+
}
|