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.
Files changed (2) hide show
  1. package/main.mjs +74 -23
  2. 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
- 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_)/;
@@ -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
- if (!text.split("\n").includes(ATTACHMENTS)) {
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
- ${ATTACHMENTS}
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 || join15(homedir4(), ".hub", "local");
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(join15(dir, "status"), { recursive: true });
4932
- statusFile(join15(dir, "status", `${process.pid}.json`), { pid: process.pid, state: "starting", build: process.env.HUB_RUNNER_BUILD ?? "" });
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: join15(dir, "runner.lock"), stale: 1e4, realpath: false }).catch((error) => {
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 = join15(dir, "device-id");
4941
- if (!existsSync11(deviceFile)) writeFileSync6(deviceFile, randomUUID());
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(join15(dir, "credentials.json"), "utf8"));
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.0",
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
+ }