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/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
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
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 writeFileSync6 } from "node:fs";
1616
- import { join as join15 } from "node:path";
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
- if (!text.split("\n").includes(ATTACHMENTS)) {
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
- ${ATTACHMENTS}
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 || join15(homedir4(), ".hub", "local");
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(join15(dir, "status"), { recursive: true });
4932
- statusFile(join15(dir, "status", `${process.pid}.json`), { pid: process.pid, state: "starting", build: process.env.HUB_RUNNER_BUILD ?? "" });
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: join15(dir, "runner.lock"), stale: 1e4, realpath: false }).catch((error) => {
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 = join15(dir, "device-id");
4941
- if (!existsSync11(deviceFile)) writeFileSync6(deviceFile, randomUUID());
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(join15(dir, "credentials.json"), "utf8"));
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.0",
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
+ }