@herdingchats/friday 0.1.581 → 0.1.598

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/herd.mjs +292 -159
  2. package/package.json +2 -2
package/herd.mjs CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  var define_HERD_PACKAGE_default;
19
19
  var init_define_HERD_PACKAGE = __esm({
20
20
  "<define:HERD_PACKAGE>"() {
21
- define_HERD_PACKAGE_default = { version: "0.1.581", gitHead: "44cd77d45198d74bbfb12b60b241e35dc7adfdeb" };
21
+ define_HERD_PACKAGE_default = { version: "0.1.598", gitHead: "7d8c736673ed8d8010c9b9915d30f42b5733edf9" };
22
22
  }
23
23
  });
24
24
 
@@ -2115,21 +2115,21 @@ var init_usb = __esm({
2115
2115
  // ../common-board/src/time.ts
2116
2116
  function when(at, now, theme) {
2117
2117
  if (theme.mode === "live") return ago2(now - at);
2118
- const day = (time8) => new Intl.DateTimeFormat("en-CA", { timeZone: theme.timeZone }).format(time8);
2118
+ const day2 = (time8) => new Intl.DateTimeFormat("en-CA", { timeZone: theme.timeZone }).format(time8);
2119
2119
  const clock = new Intl.DateTimeFormat("en-US", {
2120
2120
  timeZone: theme.timeZone,
2121
2121
  hour: "numeric",
2122
2122
  minute: "2-digit"
2123
2123
  }).format(at);
2124
- if (day(at) === day(now)) return clock;
2124
+ if (day2(at) === day2(now)) return clock;
2125
2125
  const weekday = new Intl.DateTimeFormat("en-US", {
2126
2126
  timeZone: theme.timeZone,
2127
2127
  weekday: "short"
2128
2128
  }).format(at);
2129
2129
  return `${weekday} ${clock}`;
2130
2130
  }
2131
- function ago2(ms) {
2132
- const minutes = Math.floor(Math.max(0, ms) / 6e4);
2131
+ function ago2(ms2) {
2132
+ const minutes = Math.floor(Math.max(0, ms2) / 6e4);
2133
2133
  if (minutes < 1) return "just now";
2134
2134
  if (minutes < 60) return `${minutes}m ago`;
2135
2135
  const hours = Math.floor(minutes / 60);
@@ -2385,7 +2385,7 @@ var init_widget = __esm({
2385
2385
  });
2386
2386
 
2387
2387
  // ../common-board/src/widgets/conversations.tsx
2388
- import { z as z27 } from "zod";
2388
+ import { z as z28 } from "zod";
2389
2389
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2390
2390
  function badge(item) {
2391
2391
  if (pullRequestBadge(item)) {
@@ -2429,10 +2429,10 @@ var init_conversations = __esm({
2429
2429
  conversations2 = defineWidget({
2430
2430
  kind: "conversations",
2431
2431
  describe: "Blocked, working and queued conversations, then the latest done, with what each is doing, where it came from and who holds it",
2432
- options: z27.object({
2433
- states: z27.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
2434
- limit: z27.number().int().min(1).max(BOARD_ITEMS).default(4),
2435
- show: z27.array(z27.enum(DETAILS)).default([...DETAILS])
2432
+ options: z28.object({
2433
+ states: z28.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
2434
+ limit: z28.number().int().min(1).max(BOARD_ITEMS).default(4),
2435
+ show: z28.array(z28.enum(DETAILS)).default([...DETAILS])
2436
2436
  }),
2437
2437
  reads: ["items", "machines"],
2438
2438
  Component: ({ board, options, theme, now }) => {
@@ -2476,7 +2476,7 @@ var init_conversations = __esm({
2476
2476
  });
2477
2477
 
2478
2478
  // ../common-board/src/widgets/counts.tsx
2479
- import { z as z28 } from "zod";
2479
+ import { z as z29 } from "zod";
2480
2480
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2481
2481
  var COUNTS, LABELS2, counts;
2482
2482
  var init_counts = __esm({
@@ -2496,8 +2496,8 @@ var init_counts = __esm({
2496
2496
  counts = defineWidget({
2497
2497
  kind: "counts",
2498
2498
  describe: "How many conversations are stuck, unanswered, waiting and working, and done today",
2499
- options: z28.object({
2500
- show: z28.array(z28.enum(COUNTS)).min(1).max(5).default([...COUNTS])
2499
+ options: z29.object({
2500
+ show: z29.array(z29.enum(COUNTS)).min(1).max(5).default([...COUNTS])
2501
2501
  }),
2502
2502
  reads: ["counts", "doneToday"],
2503
2503
  Component: ({ board, options, theme }) => {
@@ -2536,7 +2536,7 @@ var init_counts = __esm({
2536
2536
  });
2537
2537
 
2538
2538
  // ../common-board/src/widgets/header.tsx
2539
- import { z as z29 } from "zod";
2539
+ import { z as z30 } from "zod";
2540
2540
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2541
2541
  function summary(board) {
2542
2542
  const parts = [
@@ -2561,9 +2561,9 @@ var init_header = __esm({
2561
2561
  header = defineWidget({
2562
2562
  kind: "header",
2563
2563
  describe: "The board's title, a line of how much is open, and when it was drawn",
2564
- options: z29.object({
2565
- title: z29.string().min(1).max(40).default(`@${BOT_NAME}`),
2566
- summary: z29.boolean().default(true)
2564
+ options: z30.object({
2565
+ title: z30.string().min(1).max(40).default(`@${BOT_NAME}`),
2566
+ summary: z30.boolean().default(true)
2567
2567
  }),
2568
2568
  reads: ["counts"],
2569
2569
  Component: ({ board, options, theme, now }) => {
@@ -2581,7 +2581,7 @@ var init_header = __esm({
2581
2581
  });
2582
2582
 
2583
2583
  // ../common-board/src/widgets/machines.tsx
2584
- import { z as z30 } from "zod";
2584
+ import { z as z31 } from "zod";
2585
2585
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2586
2586
  function usageText(worker) {
2587
2587
  const usage = worker.usage;
@@ -2612,7 +2612,7 @@ var init_machines2 = __esm({
2612
2612
  machines = defineWidget({
2613
2613
  kind: "machines",
2614
2614
  describe: "One bubble per agent on an online machine, with its usage, most recently active first",
2615
- options: z30.object({ limit: z30.number().int().min(1).max(8).default(3) }),
2615
+ options: z31.object({ limit: z31.number().int().min(1).max(8).default(3) }),
2616
2616
  reads: ["machines"],
2617
2617
  Component: ({ board, options, theme }) => {
2618
2618
  const workers2 = board.machines.filter((machine) => machine.online).flatMap((machine) => machine.workers.map((worker) => ({ ...worker, machine: machine.name }))).sort((a, b) => (b.reportedAt ?? 0) - (a.reportedAt ?? 0));
@@ -2729,9 +2729,9 @@ var init_board2 = __esm({
2729
2729
  });
2730
2730
 
2731
2731
  // ../common-board/src/composition.ts
2732
- import { z as z31 } from "zod";
2732
+ import { z as z32 } from "zod";
2733
2733
  function readComposition(stored) {
2734
- const shape = z31.object({ widgets: z31.array(placedWidgetSchema) }).safeParse(stored);
2734
+ const shape = z32.object({ widgets: z32.array(placedWidgetSchema) }).safeParse(stored);
2735
2735
  if (!shape.success) return DEFAULT_COMPOSITION;
2736
2736
  const known = shape.data.widgets.filter(
2737
2737
  (widget) => WIDGETS[widget.kind]?.options.safeParse(widget.options).success
@@ -2746,11 +2746,11 @@ var init_composition = __esm({
2746
2746
  init_registry();
2747
2747
  MAX_WIDGETS = 12;
2748
2748
  MAX_COMPOSITION_BYTES = 4096;
2749
- placedWidgetSchema = z31.object({
2750
- kind: z31.string().min(1),
2751
- options: z31.record(z31.string(), z31.unknown()).default({})
2749
+ placedWidgetSchema = z32.object({
2750
+ kind: z32.string().min(1),
2751
+ options: z32.record(z32.string(), z32.unknown()).default({})
2752
2752
  });
2753
- compositionSchema = z31.object({ widgets: z31.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
2753
+ compositionSchema = z32.object({ widgets: z32.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
2754
2754
  if (JSON.stringify(composition).length > MAX_COMPOSITION_BYTES) {
2755
2755
  context2.addIssue({
2756
2756
  code: "custom",
@@ -3031,13 +3031,13 @@ var init_png = __esm({
3031
3031
  "use strict";
3032
3032
  init_define_HERD_PACKAGE();
3033
3033
  CRC_TABLE = (() => {
3034
- const table = new Uint32Array(256);
3034
+ const table2 = new Uint32Array(256);
3035
3035
  for (let n = 0; n < 256; n++) {
3036
3036
  let c = n;
3037
3037
  for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
3038
- table[n] = c >>> 0;
3038
+ table2[n] = c >>> 0;
3039
3039
  }
3040
- return table;
3040
+ return table2;
3041
3041
  })();
3042
3042
  }
3043
3043
  });
@@ -3636,7 +3636,7 @@ function dayFolders(home) {
3636
3636
  const days = [];
3637
3637
  for (const year of list(root)) {
3638
3638
  for (const month of list(join2(root, year))) {
3639
- for (const day of list(join2(root, year, month))) days.push(join2(root, year, month, day));
3639
+ for (const day2 of list(join2(root, year, month))) days.push(join2(root, year, month, day2));
3640
3640
  }
3641
3641
  }
3642
3642
  return days;
@@ -3672,14 +3672,14 @@ var realpath = (path) => {
3672
3672
  };
3673
3673
  function findSessionByCwd(cwd, since, home = codexHome()) {
3674
3674
  const wanted = realpath(cwd);
3675
- for (const day of dayFolders(home).slice(0, 2)) {
3675
+ for (const day2 of dayFolders(home).slice(0, 2)) {
3676
3676
  let names;
3677
3677
  try {
3678
- names = readdirSync(day).filter((name) => /^rollout-.*\.jsonl$/.test(name));
3678
+ names = readdirSync(day2).filter((name) => /^rollout-.*\.jsonl$/.test(name));
3679
3679
  } catch {
3680
3680
  continue;
3681
3681
  }
3682
- const found = names.map((name) => join2(day, name)).filter((path) => {
3682
+ const found = names.map((name) => join2(day2, name)).filter((path) => {
3683
3683
  try {
3684
3684
  return statSync(path).mtimeMs >= since - 2e3;
3685
3685
  } catch {
@@ -3696,10 +3696,10 @@ function findSessionByCwd(cwd, since, home = codexHome()) {
3696
3696
  }
3697
3697
  function findSessionById(id2, home = codexHome()) {
3698
3698
  const suffix = `-${id2}.jsonl`;
3699
- for (const day of dayFolders(home)) {
3699
+ for (const day2 of dayFolders(home)) {
3700
3700
  try {
3701
- const name = readdirSync(day).find((n) => n.startsWith("rollout-") && n.endsWith(suffix));
3702
- if (name) return join2(day, name);
3701
+ const name = readdirSync(day2).find((n) => n.startsWith("rollout-") && n.endsWith(suffix));
3702
+ if (name) return join2(day2, name);
3703
3703
  } catch {
3704
3704
  }
3705
3705
  }
@@ -4965,7 +4965,7 @@ function activateDecision(request, code) {
4965
4965
  }
4966
4966
  };
4967
4967
  }
4968
- var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
4968
+ var sleep = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
4969
4969
  var label = (agent) => AGENTS[agent]?.label ?? agent;
4970
4970
  var Activator = class {
4971
4971
  constructor(options) {
@@ -5290,7 +5290,7 @@ function askToActivate(herdHome2, now = Date.now()) {
5290
5290
  requestActivation(herdHome2, now);
5291
5291
  return now;
5292
5292
  }
5293
- var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
5293
+ var sleep2 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
5294
5294
  function settledLine(cli, activation) {
5295
5295
  if (activation.state === "activated") return `${label2(cli)} is activated.`;
5296
5296
  return `${label2(cli)} ${activationText(cli, activation).replace(" there", " on this computer")}.`;
@@ -6178,11 +6178,11 @@ var Supervisor = class {
6178
6178
  await this.stopLeftoverDaemon();
6179
6179
  this.save();
6180
6180
  this.spawnDaemon();
6181
- const schedule = (ms) => {
6181
+ const schedule = (ms2) => {
6182
6182
  this.checkTimer = setTimeout(async () => {
6183
6183
  await this.check().catch((error) => this.log(`The release check failed: ${error}`));
6184
6184
  if (!this.stopping) schedule(this.options.checkMs ?? 12e4);
6185
- }, ms);
6185
+ }, ms2);
6186
6186
  };
6187
6187
  schedule(this.options.firstCheckMs ?? 5e3);
6188
6188
  }
@@ -7196,7 +7196,7 @@ async function registerRunner({
7196
7196
  url = buildsUrl(),
7197
7197
  fetchImpl = fetch,
7198
7198
  say = (line2) => console.log(line2),
7199
- sleep: sleep8 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
7199
+ sleep: sleep8 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2)),
7200
7200
  pollMs = 2e3,
7201
7201
  waitMs = 15 * 60 * 1e3
7202
7202
  }) {
@@ -7233,6 +7233,120 @@ async function registerRunner({
7233
7233
  );
7234
7234
  }
7235
7235
 
7236
+ // src/build-speed.ts
7237
+ init_define_HERD_PACKAGE();
7238
+ import { z as z25 } from "zod";
7239
+ var ms = z25.number().int().nullable();
7240
+ var figuresSchema = z25.object({ count: z25.number().int(), median: ms, p90: ms });
7241
+ var buildTimesSchema = z25.object({ run: figuresSchema, push: figuresSchema });
7242
+ var stepFiguresSchema = z25.object({
7243
+ ran: z25.number().int(),
7244
+ remembered: z25.number().int(),
7245
+ median: ms,
7246
+ p90: ms,
7247
+ overBudget: z25.number().int()
7248
+ });
7249
+ var kindSchema = z25.object({
7250
+ builds: z25.object({ current: buildTimesSchema, previous: buildTimesSchema }),
7251
+ steps: z25.array(
7252
+ z25.object({ label: z25.string(), current: stepFiguresSchema, previous: stepFiguresSchema })
7253
+ ),
7254
+ overBudget: z25.array(
7255
+ z25.object({ label: z25.string(), current: z25.number().int(), previous: z25.number().int() })
7256
+ )
7257
+ });
7258
+ var range = z25.object({ from: z25.number(), to: z25.number() });
7259
+ var speedReportSchema = z25.object({
7260
+ periods: z25.object({ current: range, previous: range }),
7261
+ repos: z25.array(
7262
+ z25.object({
7263
+ repo: z25.string(),
7264
+ capped: z25.boolean(),
7265
+ pullRequests: kindSchema,
7266
+ main: kindSchema
7267
+ })
7268
+ )
7269
+ });
7270
+ async function fetchSpeed(key, fetchImpl = fetch) {
7271
+ let response;
7272
+ try {
7273
+ response = await fetchImpl(`${key.url}/speed`, {
7274
+ headers: { Authorization: `Bearer ${key.key}` }
7275
+ });
7276
+ } catch (error) {
7277
+ throw new HerdError(`Couldn't reach the build service at ${key.url}: ${String(error)}`);
7278
+ }
7279
+ if (response.status === 404)
7280
+ throw new HerdError(
7281
+ "The build service doesn't accept this machine's runner key, as after it was revoked. Register it again with herd builds register."
7282
+ );
7283
+ if (!response.ok)
7284
+ throw new HerdError(`The build service couldn't give build speed (${response.status}).`);
7285
+ return speedReportSchema.parse(await response.json());
7286
+ }
7287
+ function duration(value) {
7288
+ if (value === null) return "-";
7289
+ if (value < 1e3) return `${value}ms`;
7290
+ if (value < 6e4) return `${(value / 1e3).toFixed(1)}s`;
7291
+ const seconds = Math.round(value / 1e3);
7292
+ return `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
7293
+ }
7294
+ function table(rows) {
7295
+ const widths = [];
7296
+ for (const row of rows) {
7297
+ row.forEach((cell, i) => {
7298
+ widths[i] = Math.max(widths[i] ?? 0, cell.length);
7299
+ });
7300
+ }
7301
+ return rows.map(
7302
+ (row) => row.map((cell, i) => i === 0 ? cell.padEnd(widths[i] ?? 0) : cell.padStart(widths[i] ?? 0)).join(" ").trimEnd()
7303
+ );
7304
+ }
7305
+ var day = (time8) => new Date(time8).toISOString().slice(0, 10);
7306
+ function kindLines(name, kind) {
7307
+ const { current, previous } = kind.builds;
7308
+ const rows = [
7309
+ [name, "median", "p90", "builds", "|", "median", "p90", "builds"],
7310
+ ...["run", "push"].map((which) => [
7311
+ which === "run" ? " start to finish" : " push to finish",
7312
+ duration(current[which].median),
7313
+ duration(current[which].p90),
7314
+ String(current[which].count),
7315
+ "|",
7316
+ duration(previous[which].median),
7317
+ duration(previous[which].p90),
7318
+ String(previous[which].count)
7319
+ ]),
7320
+ [" steps, slowest first", "", "", "ran/rem", "|", "", "", "ran/rem"],
7321
+ ...kind.steps.map((step) => [
7322
+ ` ${step.label}`,
7323
+ duration(step.current.median),
7324
+ duration(step.current.p90),
7325
+ `${step.current.ran}/${step.current.remembered}`,
7326
+ "|",
7327
+ duration(step.previous.median),
7328
+ duration(step.previous.p90),
7329
+ `${step.previous.ran}/${step.previous.remembered}`
7330
+ ])
7331
+ ];
7332
+ const over = kind.overBudget.map((s) => `${s.label} ${s.current}x (${s.previous}x before)`);
7333
+ return [...table(rows), ...over.length ? [` over budget most often: ${over.join(", ")}`] : []];
7334
+ }
7335
+ function formatSpeed(report) {
7336
+ const { current, previous } = report.periods;
7337
+ const lines = [
7338
+ `Last 14 days (${day(current.from)} to ${day(current.to)}) | 14 days before (${day(previous.from)} to ${day(previous.to)}).`,
7339
+ "Builds that passed or failed; steps only when they ran, not when remembered."
7340
+ ];
7341
+ if (report.repos.length === 0) lines.push("", "No repos are built for this machine's owner yet.");
7342
+ for (const repo of report.repos) {
7343
+ lines.push("", `${repo.repo}${repo.capped ? " (only the newest builds count)" : ""}`);
7344
+ lines.push(...kindLines("pull requests", repo.pullRequests), "");
7345
+ lines.push(...kindLines("main", repo.main));
7346
+ }
7347
+ return lines.join("\n");
7348
+ }
7349
+
7236
7350
  // src/builds-admin.ts
7237
7351
  init_define_HERD_PACKAGE();
7238
7352
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync10 } from "node:fs";
@@ -7270,6 +7384,14 @@ Usage:
7270
7384
  herd builds register Register it: prints a link its owner opens to approve it, then waits
7271
7385
  and keeps the runner key. Only registered runners are offered builds,
7272
7386
  and only the workspace's admin can register one.
7387
+ herd builds speed [--json]
7388
+ How long builds take: the median and 90th percentile of build time,
7389
+ start to finish and push to finish, for pull requests and main apart,
7390
+ over the last 14 days against the 14 before, and each step's when it
7391
+ ran, slowest first, with how often it was remembered or over budget.
7392
+ Read with this machine's runner key, so only a registered runner can.
7393
+ --json prints the figures as JSON. People see them on
7394
+ /manage/speed of the build service.
7273
7395
 
7274
7396
  How many builds a runner takes at once is "builds" in ~/.herd/herd.config.ts: true for one, or
7275
7397
  { max: <n> } for up to 4.`;
@@ -7295,6 +7417,16 @@ async function runBuilds(args) {
7295
7417
  console.log("The daemon connects as a runner once it restarts.");
7296
7418
  return 0;
7297
7419
  }
7420
+ if (sub === "speed") {
7421
+ const key2 = readRunnerKey();
7422
+ if (!key2)
7423
+ throw new HerdError(
7424
+ "Build speed is read with a runner key, and this machine isn't a build runner. herd builds register makes it one."
7425
+ );
7426
+ const report = await fetchSpeed(key2);
7427
+ console.log(args.includes("--json") ? JSON.stringify(report, null, 2) : formatSpeed(report));
7428
+ return 0;
7429
+ }
7298
7430
  if (sub !== void 0) throw new HerdError(`herd builds doesn't understand "${sub}".
7299
7431
 
7300
7432
  ${HELP3}`);
@@ -7556,7 +7688,7 @@ function withStartKey(command, startKey) {
7556
7688
  return startKey ? `HERD_START=${startKey} ${command}` : command;
7557
7689
  }
7558
7690
  var DEFAULT_TIMING = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
7559
- var sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
7691
+ var sleep3 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
7560
7692
  var EXIT_WAIT_MS = 5e3;
7561
7693
  var AgentDriver = class {
7562
7694
  constructor(terminals, timing = {}) {
@@ -7578,8 +7710,8 @@ var AgentDriver = class {
7578
7710
  async read(name) {
7579
7711
  return readClaudeScreen(await this.terminals.screen(name));
7580
7712
  }
7581
- async until(name, check2, ms) {
7582
- const end = Date.now() + ms;
7713
+ async until(name, check2, ms2) {
7714
+ const end = Date.now() + ms2;
7583
7715
  for (; ; ) {
7584
7716
  const reading = await this.read(name);
7585
7717
  if (check2(reading)) return reading;
@@ -7862,7 +7994,7 @@ function writeAgyFiles(worktree, herd) {
7862
7994
  // src/agy-driver.ts
7863
7995
  var AGY_FLAGS = `--dangerously-skip-permissions --settings ${AGY_SETTINGS_FILE}`;
7864
7996
  var DEFAULT_TIMING2 = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
7865
- var sleep4 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
7997
+ var sleep4 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
7866
7998
  var EXIT_WAIT_MS2 = 5e3;
7867
7999
  var AgyDriver = class {
7868
8000
  constructor(terminals, timing = {}) {
@@ -7879,8 +8011,8 @@ var AgyDriver = class {
7879
8011
  async read(name) {
7880
8012
  return readAgyScreen(await this.terminals.screen(name));
7881
8013
  }
7882
- async until(name, check2, ms) {
7883
- const end = Date.now() + ms;
8014
+ async until(name, check2, ms2) {
8015
+ const end = Date.now() + ms2;
7884
8016
  for (; ; ) {
7885
8017
  const reading = await this.read(name);
7886
8018
  if (check2(reading)) return reading;
@@ -8854,7 +8986,7 @@ var DEFAULT_TIMING3 = {
8854
8986
  confirmMs: 8e3,
8855
8987
  pasteMs: 600
8856
8988
  };
8857
- var sleep5 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
8989
+ var sleep5 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
8858
8990
  var EXIT_WAIT_MS3 = 5e3;
8859
8991
  var CodexDriver = class {
8860
8992
  constructor(terminals, timing = {}) {
@@ -8871,8 +9003,8 @@ var CodexDriver = class {
8871
9003
  async read(name) {
8872
9004
  return readCodexScreen(await this.terminals.screen(name));
8873
9005
  }
8874
- async until(name, check2, ms) {
8875
- const end = Date.now() + ms;
9006
+ async until(name, check2, ms2) {
9007
+ const end = Date.now() + ms2;
8876
9008
  for (; ; ) {
8877
9009
  const reading = await this.read(name);
8878
9010
  if (check2(reading)) return reading;
@@ -9334,7 +9466,7 @@ function machineFolderProblem(root, convos, checks = {}) {
9334
9466
  async function waitForFolders(problem, {
9335
9467
  timeoutMs = FOLDER_WAIT_MS,
9336
9468
  pollMs = 2e3,
9337
- sleep: sleep8 = (ms) => new Promise((done) => setTimeout(done, ms)),
9469
+ sleep: sleep8 = (ms2) => new Promise((done) => setTimeout(done, ms2)),
9338
9470
  now = Date.now,
9339
9471
  log = (line2) => console.log(line2)
9340
9472
  } = {}) {
@@ -9744,7 +9876,7 @@ var hostsDir = () => join25(herdHome(), "terminals");
9744
9876
  var legacyHostSocketPath = () => join25(herdHome(), "terminals.sock");
9745
9877
  var currentHostSocketPath = (version = hostVersion()) => join25(hostsDir(), `${version}.sock`);
9746
9878
  var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
9747
- var sleep6 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
9879
+ var sleep6 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
9748
9880
  function askHost(path, request) {
9749
9881
  return new Promise((resolve7, reject) => {
9750
9882
  const socket = connect2(path);
@@ -10045,7 +10177,7 @@ init_define_HERD_PACKAGE();
10045
10177
  init_src();
10046
10178
  init_self();
10047
10179
  import { join as join26 } from "node:path";
10048
- import { z as z25 } from "zod";
10180
+ import { z as z26 } from "zod";
10049
10181
  function hasThread(events) {
10050
10182
  return events.some(
10051
10183
  (event2) => event2.type === "prompt.opened" || event2.type === "message.created" && !(event2.payload.direction === "inbound" && event2.payload.automation) && !event2.payload.quiet && !event2.payload.post
@@ -10111,16 +10243,16 @@ var WAITING_NOTIFICATIONS = /* @__PURE__ */ new Set([
10111
10243
  "agent_needs_input",
10112
10244
  "elicitation_dialog"
10113
10245
  ]);
10114
- var agentMessageSchema = z25.object({
10115
- text: z25.string().trim().min(1).max(12e3),
10116
- files: z25.array(z25.object({ path: z25.string().min(1), title: z25.string().optional() })).max(10).optional()
10246
+ var agentMessageSchema = z26.object({
10247
+ text: z26.string().trim().min(1).max(12e3),
10248
+ files: z26.array(z26.object({ path: z26.string().min(1), title: z26.string().optional() })).max(10).optional()
10117
10249
  });
10118
- var workspaceRepoSchema = z25.object({
10119
- folder: z25.string(),
10120
- remote: z25.string(),
10121
- branch: z25.string(),
10250
+ var workspaceRepoSchema = z26.object({
10251
+ folder: z26.string(),
10252
+ remote: z26.string(),
10253
+ branch: z26.string(),
10122
10254
  /** For a conversation from before folders: the name of the repo it was allowed as. */
10123
- name: z25.string().optional()
10255
+ name: z26.string().optional()
10124
10256
  });
10125
10257
  function fromBeforeFolders(raw) {
10126
10258
  if (!raw || typeof raw !== "object" || "folder" in raw) return raw;
@@ -10141,106 +10273,106 @@ function fromBeforeFolders(raw) {
10141
10273
  legacy: true
10142
10274
  };
10143
10275
  }
10144
- var workspaceFields = z25.object({
10145
- conversationId: z25.string(),
10276
+ var workspaceFields = z26.object({
10277
+ conversationId: z26.string(),
10146
10278
  /** The conversation's own folder, where its terminals start. */
10147
- folder: z25.string(),
10279
+ folder: z26.string(),
10148
10280
  /** The repos in it, in the order they were added. */
10149
- worktrees: z25.array(workspaceRepoSchema).default([]),
10281
+ worktrees: z26.array(workspaceRepoSchema).default([]),
10150
10282
  /** A conversation from before folders, which works in its one repo's worktree. */
10151
- legacy: z25.boolean().default(false),
10152
- lead: z25.string(),
10153
- secret: z25.string(),
10154
- readSeq: z25.number(),
10155
- claimId: z25.string().nullable(),
10156
- claimRenewedAt: z25.number(),
10157
- started: z25.boolean(),
10283
+ legacy: z26.boolean().default(false),
10284
+ lead: z26.string(),
10285
+ secret: z26.string(),
10286
+ readSeq: z26.number(),
10287
+ claimId: z26.string().nullable(),
10288
+ claimRenewedAt: z26.number(),
10289
+ started: z26.boolean(),
10158
10290
  /** Messages to deliver once the current turn ends. */
10159
- deferred: z25.boolean(),
10291
+ deferred: z26.boolean(),
10160
10292
  /** A `herd run` command finished, which the agent hears about even with nothing to claim. */
10161
- terminalNotice: z25.boolean().default(false),
10162
- status: z25.string().nullable(),
10293
+ terminalNotice: z26.boolean().default(false),
10294
+ status: z26.string().nullable(),
10163
10295
  /** Set once the thread has been told the agent is waiting, so it's told once per wait. */
10164
- toldWaiting: z25.boolean(),
10165
- runs: z25.array(z25.object({ name: z25.string(), secret: z25.string(), reported: z25.boolean() })),
10296
+ toldWaiting: z26.boolean(),
10297
+ runs: z26.array(z26.object({ name: z26.string(), secret: z26.string(), reported: z26.boolean() })),
10166
10298
  /** The subscription usage Claude Code last reported in this worktree. */
10167
10299
  usage: usageSchema.nullable().default(null),
10168
10300
  /** When that usage was reported, or 0 if it was before this was kept. */
10169
- usageAt: z25.number().default(0),
10301
+ usageAt: z26.number().default(0),
10170
10302
  /** The lead agent's Claude Code session, as its `SessionStart` hook reported it. */
10171
- sessionId: z25.string().nullable().default(null),
10303
+ sessionId: z26.string().nullable().default(null),
10172
10304
  /** When the agent's last turn ended with nothing left to do, for shutting it down later. */
10173
- idleSince: z25.number().nullable().default(null),
10305
+ idleSince: z26.number().nullable().default(null),
10174
10306
  /** The agent ran `herd exit`, so its session ends after this turn. */
10175
- exitRequested: z25.boolean().default(false),
10307
+ exitRequested: z26.boolean().default(false),
10176
10308
  /** Whether the agent sent anything with `herd send` this turn. */
10177
- sentThisTurn: z25.boolean().default(false),
10309
+ sentThisTurn: z26.boolean().default(false),
10178
10310
  /** Whether it's been asked this turn to reply. */
10179
- askedToReply: z25.boolean().default(false),
10311
+ askedToReply: z26.boolean().default(false),
10180
10312
  /** Whether it ran `herd handled` this turn, which handles the turn's messages with no reply. */
10181
- handledThisTurn: z25.boolean().default(false),
10313
+ handledThisTurn: z26.boolean().default(false),
10182
10314
  /** The newest waiting message it's been pointed at, so one arrival is pointed out once. */
10183
- nudgedSeq: z25.number().default(0),
10315
+ nudgedSeq: z26.number().default(0),
10184
10316
  /** When a `Stop` hook last ended a turn. */
10185
- hookIdleAt: z25.number().default(0),
10317
+ hookIdleAt: z26.number().default(0),
10186
10318
  /** When a turn last ended with background work still running, which keeps its messages held. */
10187
- pausedAt: z25.number().nullable().default(null),
10319
+ pausedAt: z26.number().nullable().default(null),
10188
10320
  /** The version of the terminal host the lead terminal was opened in, if it was recorded. */
10189
- hostVersion: z25.string().nullable().default(null),
10321
+ hostVersion: z26.string().nullable().default(null),
10190
10322
  /** When a notice last failed for a reason other than someone's draft, to wait before retrying. */
10191
- noticeFailedAt: z25.number().nullable().default(null),
10323
+ noticeFailedAt: z26.number().nullable().default(null),
10192
10324
  /** Why the last notice failed, so the history records each reason once, not every attempt. */
10193
- noticeFailure: z25.string().nullable().default(null),
10325
+ noticeFailure: z26.string().nullable().default(null),
10194
10326
  /** A recorded dialog herd pressed its way out of, so a dialog that stays isn't pressed again. */
10195
- closedDialog: z25.string().nullable().default(null),
10327
+ closedDialog: z26.string().nullable().default(null),
10196
10328
  /**
10197
10329
  * Prompts Claude Code's hooks reported that haven't had their `Stop`. A turn is open while this
10198
10330
  * is above zero. It's a count because a prompt queued mid-turn is reported as it's queued, and
10199
10331
  * runs as a turn of its own, ending with a second `Stop`.
10200
10332
  */
10201
- promptsOpen: z25.number().default(0),
10333
+ promptsOpen: z26.number().default(0),
10202
10334
  /** The messages the latest claim or addition took, for choosing which notice to type. */
10203
- claimedSeqs: z25.array(z25.number()).default([]),
10335
+ claimedSeqs: z26.array(z26.number()).default([]),
10204
10336
  /** The investigator looking at a screen herd can't read, from opening to the owner's answer. */
10205
- investigation: z25.object({
10206
- terminal: z25.string(),
10207
- secret: z25.string(),
10208
- startedAt: z25.number(),
10337
+ investigation: z26.object({
10338
+ terminal: z26.string(),
10339
+ secret: z26.string(),
10340
+ startedAt: z26.number(),
10209
10341
  /** The stuck screen when it opened, saved with the key if the owner approves one. */
10210
- screen: z25.array(z25.string()),
10211
- key: z25.string().nullable(),
10212
- promptId: z25.string().nullable()
10342
+ screen: z26.array(z26.string()),
10343
+ key: z26.string().nullable(),
10344
+ promptId: z26.string().nullable()
10213
10345
  }).nullable().default(null),
10214
10346
  /** The failed notice an investigator was opened for, so each failure gets one. */
10215
- investigatedFailure: z25.number().nullable().default(null),
10347
+ investigatedFailure: z26.number().nullable().default(null),
10216
10348
  /** The agent CLI this conversation's worker runs, as its offer named it. */
10217
- agent: z25.string().default("claude-code"),
10349
+ agent: z26.string().default("claude-code"),
10218
10350
  /** Whether what the current claim took needs a reply, which other agents' messages don't. */
10219
- replyWanted: z25.boolean().default(true),
10351
+ replyWanted: z26.boolean().default(true),
10220
10352
  /** For a delegate: the conversation that started it, which its starting prompt names. */
10221
- parent: z25.string().nullable().default(null),
10353
+ parent: z26.string().nullable().default(null),
10222
10354
  /** Codex's session file for this worktree, once found, and how far the daemon has read it. */
10223
- codexFile: z25.string().nullable().default(null),
10224
- codexOffset: z25.number().default(0),
10355
+ codexFile: z26.string().nullable().default(null),
10356
+ codexOffset: z26.number().default(0),
10225
10357
  /** When the agent was last started, so Codex's new session file is told from older ones. */
10226
- agentStartedAt: z25.number().default(0),
10358
+ agentStartedAt: z26.number().default(0),
10227
10359
  /**
10228
10360
  * The key the agent was last started with, as `HERD_START`. Its `herd inbox` carrying this key
10229
10361
  * is what proves the start works: never a hook, a prompt being taken, or the screen.
10230
10362
  */
10231
- startKey: z25.string().nullable().default(null),
10363
+ startKey: z26.string().nullable().default(null),
10232
10364
  /** Whether the current start has checked in. One from before keys counts as proven. */
10233
- startProven: z25.boolean().default(true),
10365
+ startProven: z26.boolean().default(true),
10234
10366
  /** When the check-in window of an unproven start began, or null once it's proven or for none. */
10235
- checkInFrom: z25.number().nullable().default(null),
10367
+ checkInFrom: z26.number().nullable().default(null),
10236
10368
  /** Whether this start has been flagged as needing someone at its terminal, so it's said once. */
10237
- needsAttention: z25.boolean().default(false),
10369
+ needsAttention: z26.boolean().default(false),
10238
10370
  /** What agy's status line last said its agent was doing, and when. */
10239
- agentState: z25.enum(["working", "idle"]).nullable().default(null),
10240
- agentStateAt: z25.number().default(0)
10371
+ agentState: z26.enum(["working", "idle"]).nullable().default(null),
10372
+ agentStateAt: z26.number().default(0)
10241
10373
  });
10242
- var workspaceSchema = z25.preprocess(fromBeforeFolders, workspaceFields);
10243
- var stateSchema = z25.object({ workspaces: z25.record(z25.string(), workspaceSchema) });
10374
+ var workspaceSchema = z26.preprocess(fromBeforeFolders, workspaceFields);
10375
+ var stateSchema = z26.object({ workspaces: z26.record(z26.string(), workspaceSchema) });
10244
10376
 
10245
10377
  // src/workspaces/claims.ts
10246
10378
  var holder = (ws, workspace) => `${ws.options.machineName()}/${workspace.lead}`;
@@ -11335,19 +11467,19 @@ function resolveAttachments(worktree, files) {
11335
11467
  // src/workers.ts
11336
11468
  init_define_HERD_PACKAGE();
11337
11469
  init_src();
11338
- import { z as z26 } from "zod";
11339
- var workerListingSchema = z26.object({
11340
- workers: z26.array(workerSchema),
11341
- machines: z26.array(
11342
- z26.object({
11343
- id: z26.string(),
11344
- name: z26.string(),
11345
- online: z26.boolean(),
11346
- missing: z26.array(z26.string()),
11347
- configError: z26.string().optional()
11470
+ import { z as z27 } from "zod";
11471
+ var workerListingSchema = z27.object({
11472
+ workers: z27.array(workerSchema),
11473
+ machines: z27.array(
11474
+ z27.object({
11475
+ id: z27.string(),
11476
+ name: z27.string(),
11477
+ online: z27.boolean(),
11478
+ missing: z27.array(z27.string()),
11479
+ configError: z27.string().optional()
11348
11480
  })
11349
11481
  ),
11350
- configType: z26.string()
11482
+ configType: z27.string()
11351
11483
  });
11352
11484
  async function readWorkers(fetch2) {
11353
11485
  const response = await fetch2("/api/workers");
@@ -11411,20 +11543,20 @@ function formatWorkers(listing, current = null) {
11411
11543
  );
11412
11544
  return lines.join("\n");
11413
11545
  }
11414
- var routingCheckSchema = z26.object({
11415
- suits: z26.array(z26.object({ key: z26.string(), name: z26.string() })),
11416
- eligible: z26.array(z26.object({ key: z26.string(), name: z26.string() })),
11417
- removed: z26.array(z26.object({ key: z26.string(), name: z26.string(), rule: z26.string() })),
11418
- questions: z26.array(
11419
- z26.object({ id: z26.string(), question: z26.string(), probability: z26.number().nullable() })
11546
+ var routingCheckSchema = z27.object({
11547
+ suits: z27.array(z27.object({ key: z27.string(), name: z27.string() })),
11548
+ eligible: z27.array(z27.object({ key: z27.string(), name: z27.string() })),
11549
+ removed: z27.array(z27.object({ key: z27.string(), name: z27.string(), rule: z27.string() })),
11550
+ questions: z27.array(
11551
+ z27.object({ id: z27.string(), question: z27.string(), probability: z27.number().nullable() })
11420
11552
  ),
11421
- fallback: z26.string().optional(),
11422
- fits: z26.array(
11423
- z26.object({
11424
- key: z26.string(),
11425
- fit: z26.number(),
11426
- statement: z26.string().optional(),
11427
- level: z26.string().optional()
11553
+ fallback: z27.string().optional(),
11554
+ fits: z27.array(
11555
+ z27.object({
11556
+ key: z27.string(),
11557
+ fit: z27.number(),
11558
+ statement: z27.string().optional(),
11559
+ level: z27.string().optional()
11428
11560
  })
11429
11561
  ).optional()
11430
11562
  });
@@ -13088,11 +13220,11 @@ function agyAnswer(event2, output2) {
13088
13220
  }
13089
13221
 
13090
13222
  // src/hook-client.ts
13091
- var sleep7 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
13092
- function within2(promise, ms) {
13223
+ var sleep7 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
13224
+ function within2(promise, ms2) {
13093
13225
  let timer;
13094
13226
  const timeout = new Promise((resolve7) => {
13095
- timer = setTimeout(() => resolve7(null), ms);
13227
+ timer = setTimeout(() => resolve7(null), ms2);
13096
13228
  });
13097
13229
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
13098
13230
  }
@@ -13625,7 +13757,7 @@ async function runTerminalHost(rest) {
13625
13757
  // src/commands/repo.ts
13626
13758
  init_define_HERD_PACKAGE();
13627
13759
  import { join as join35 } from "node:path";
13628
- import { z as z32 } from "zod";
13760
+ import { z as z33 } from "zod";
13629
13761
  var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
13630
13762
 
13631
13763
  Checks a repo out in this conversation's folder, on a branch named for the conversation, started
@@ -13639,16 +13771,16 @@ var REPOS_HELP = `Usage: herd repos [--json]
13639
13771
 
13640
13772
  Lists the repos in this conversation's folder, the repos checked out in this machine's root, and
13641
13773
  the repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
13642
- var repoContextSchema = z32.object({
13643
- folder: z32.string(),
13644
- root: z32.string(),
13645
- branch: z32.string(),
13646
- legacy: z32.boolean(),
13647
- legacyReason: z32.string().optional(),
13648
- recorded: z32.array(z32.object({ folder: z32.string(), remote: z32.string(), branch: z32.string() })),
13649
- cloned: z32.array(z32.object({ key: z32.string(), remote: z32.string() })),
13650
- described: z32.array(
13651
- z32.object({ remote: z32.string(), key: z32.string().nullable(), about: z32.string().optional() })
13774
+ var repoContextSchema = z33.object({
13775
+ folder: z33.string(),
13776
+ root: z33.string(),
13777
+ branch: z33.string(),
13778
+ legacy: z33.boolean(),
13779
+ legacyReason: z33.string().optional(),
13780
+ recorded: z33.array(z33.object({ folder: z33.string(), remote: z33.string(), branch: z33.string() })),
13781
+ cloned: z33.array(z33.object({ key: z33.string(), remote: z33.string() })),
13782
+ described: z33.array(
13783
+ z33.object({ remote: z33.string(), key: z33.string().nullable(), about: z33.string().optional() })
13652
13784
  )
13653
13785
  });
13654
13786
  var isUrl = (text4) => /^[a-z][a-z0-9+.-]*:\/\//i.test(text4) || text4.startsWith("/") || /^[^@/\s]+@[^:/\s]+:/.test(text4);
@@ -14018,7 +14150,7 @@ async function runWatch(command, rest) {
14018
14150
  // src/commands/work.ts
14019
14151
  init_define_HERD_PACKAGE();
14020
14152
  init_src();
14021
- import { z as z33 } from "zod";
14153
+ import { z as z34 } from "zod";
14022
14154
  var WORK_HELP = `Usage: herd work <url> [--kind <kind>] [--title "<title>"] [--status <status>]
14023
14155
  [--detail "<detail>"] [--made] [--phase <phase>]
14024
14156
  herd work --remove <url>
@@ -14110,9 +14242,9 @@ ${WORK_HELP}`
14110
14242
  };
14111
14243
  return { kind: "record", change, json };
14112
14244
  }
14113
- var followSchema = z33.union([
14114
- z33.object({ following: z33.literal(true) }),
14115
- z33.object({ following: z33.literal(false), reason: z33.string() })
14245
+ var followSchema = z34.union([
14246
+ z34.object({ following: z34.literal(true) }),
14247
+ z34.object({ following: z34.literal(false), reason: z34.string() })
14116
14248
  ]);
14117
14249
  function formatFollow(follow) {
14118
14250
  return follow.following ? "Following it: its reviews, CI and merge will reach herd inbox." : `Not followed: ${follow.reason}`;
@@ -14128,7 +14260,7 @@ function formatWork(work, own) {
14128
14260
  return ` ${parts.join(" \xB7 ")}${work.made ? " made here" : ""}
14129
14261
  ${work.url}${whose}`;
14130
14262
  }
14131
- var listSchema = z33.array(workObjectSchema);
14263
+ var listSchema = z34.array(workObjectSchema);
14132
14264
  async function runWork(rest) {
14133
14265
  const parsed = parseWork(rest);
14134
14266
  if (parsed.kind === "help") {
@@ -14377,8 +14509,9 @@ On this computer:
14377
14509
  Run this machine's daemon, working from its root folder. Repos given with
14378
14510
  --repo are only listed as known, since herd works in its own clones
14379
14511
  attach [<terminal>] Watch or take over a terminal. Ctrl+\\ detaches. With no name, lists them
14380
- builds [register] Whether this machine is a build runner, or register it, with its owner's
14381
- approval in the browser. See builds --help
14512
+ builds [register|speed]
14513
+ Whether this machine is a build runner, register it, with its owner's
14514
+ approval in the browser, or show how long builds take. See builds --help
14382
14515
  setup Show what's set up for Friday and what's left, with how to do each step.
14383
14516
  --json prints it as JSON. Inside a terminal, it's your owner's
14384
14517
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herdingchats/friday",
3
- "version": "0.1.581",
3
+ "version": "0.1.598",
4
4
  "description": "Friday, HerdingChats' agent daemon, and the herd command it runs agents with.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,5 +22,5 @@
22
22
  "satori": "0.32.0",
23
23
  "zod": "4.6.7"
24
24
  },
25
- "gitHead": "44cd77d45198d74bbfb12b60b241e35dc7adfdeb"
25
+ "gitHead": "7d8c736673ed8d8010c9b9915d30f42b5733edf9"
26
26
  }