agent-yes 1.201.0 → 1.203.0

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 (38) hide show
  1. package/default.config.yaml +8 -8
  2. package/dist/SUPPORTED_CLIS-C2KLyNR9.js +9 -0
  3. package/dist/{SUPPORTED_CLIS-CnGjmp53.js → SUPPORTED_CLIS-CME2rXAO.js} +2 -2
  4. package/dist/{agentShare-lDreSLQE.js → agentShare-LmDLDvCN.js} +2 -2
  5. package/dist/cli.js +4 -4
  6. package/dist/index.js +2 -2
  7. package/dist/{notifyDaemon-C4VzLmmO.js → notifyDaemon-CW7E0ON1.js} +2 -2
  8. package/dist/{rustBinary-CVZsX0za.js → rustBinary-5fGeqzPe.js} +2 -2
  9. package/dist/{schedule-hdbXBwpG.js → schedule-bPb_0xnY.js} +4 -4
  10. package/dist/{serve-DEEesVYn.js → serve-B9RuI_Hb.js} +12 -12
  11. package/dist/{setup-DZbnDas9.js → setup-CnzMLgtM.js} +2 -2
  12. package/dist/{subcommands-IwpLJA-n.js → subcommands-cqz_xIKK.js} +1 -1
  13. package/dist/{subcommands-De-OHc0k.js → subcommands-t8QPivG7.js} +53 -11
  14. package/dist/{ts-ZOEYyQAX.js → ts-DVxj4HLs.js} +2 -2
  15. package/dist/{versionChecker-CBtAasFe.js → versionChecker-mq5B0hM3.js} +2 -2
  16. package/dist/{ws-DLiI3C6R.js → ws-BSyEbL--.js} +2 -2
  17. package/lab/ui/console-logic.js +43 -4
  18. package/lab/ui/index.html +65 -21
  19. package/lab/ui/qrcode.js +548 -565
  20. package/lab/ui/room-client.js +70 -91
  21. package/lab/ui/sw.js +17 -7
  22. package/package.json +7 -7
  23. package/scripts/build-rgui.ts +3 -1
  24. package/ts/agentShare.spec.ts +5 -3
  25. package/ts/badges.spec.ts +84 -1
  26. package/ts/badges.ts +69 -5
  27. package/ts/notifyDaemon.spec.ts +2 -8
  28. package/ts/notifyDaemon.ts +32 -33
  29. package/ts/notifyInbox.spec.ts +2 -1
  30. package/ts/notifyRouter.spec.ts +7 -11
  31. package/ts/notifyStore.spec.ts +4 -1
  32. package/ts/notifyStore.ts +18 -14
  33. package/ts/serve.spec.ts +4 -6
  34. package/ts/serve.ts +69 -17
  35. package/ts/subcommands.ts +46 -18
  36. package/ts/ws.spec.ts +31 -7
  37. package/ts/ws.ts +15 -4
  38. package/dist/SUPPORTED_CLIS-BbWt8-ON.js +0 -9
package/ts/badges.ts CHANGED
@@ -11,11 +11,20 @@
11
11
  export interface BadgeDef {
12
12
  /** Stable id, used as the wire value and as the key when re-deriving the label. */
13
13
  id: string;
14
- /** Short chip text, e.g. "goal". Keep it a couple of characters — the chip is tiny. */
14
+ /**
15
+ * Short chip text, e.g. "goal". Keep it a couple of characters — the chip is
16
+ * tiny. May contain `$1`, replaced with the pattern's first capture group
17
+ * (see the dynamic footer-counter defs below).
18
+ */
15
19
  label: string;
16
20
  /** Tooltip shown on hover, explaining what the badge means. */
17
21
  title: string;
18
- /** Matched against the tail-rendered screen text (lines joined with \n). */
22
+ /**
23
+ * Matched against the tail-rendered screen text (lines joined with \n).
24
+ * A pattern with a capture group makes the badge DYNAMIC: the wire id
25
+ * becomes `id:capture` (e.g. "shells:4 shells") and `$1` in label/title is
26
+ * substituted with the captured text when the chip is rendered.
27
+ */
19
28
  pattern: RegExp;
20
29
  }
21
30
 
@@ -46,15 +55,55 @@ export const BADGE_DEFS: BadgeDef[] = [
46
55
  title: "Waiting for the API — the CLI is auto-retrying on its own backoff (no action needed)",
47
56
  pattern: /Waiting for API response[\s\S]{0,40}will retry in \d/i,
48
57
  },
58
+ // ---- Dynamic footer counters -------------------------------------------
59
+ // claude's status footer lists live counters between "·" separators, e.g.
60
+ // ⏸ manual mode on · 1 shell · ctrl+t to hide tasks · ← for agents · ↓ to manage
61
+ // ⏸ manual mode on · 3 monitors · esc to interrupt · ↓ to manage
62
+ // ⏸ manual mode on · ? for shortcuts · ← 3 agents
63
+ // ⏸ manual mode on · PR #310
64
+ // Each pattern anchors on that chrome (the "· " separator / the "←" arrow),
65
+ // not the bare phrase, so ordinary conversation text about "4 shells" can't
66
+ // light the chip. The capture keeps the CLI's own singular/plural wording,
67
+ // so the chip reads exactly like the footer ("1 shell", "4 shells").
68
+ {
69
+ id: "shells",
70
+ label: "$1",
71
+ title: "Background shells running in this session ($1 in the CLI footer)",
72
+ pattern: /· (\d+ shells?)(?= ·|\s*$)/m,
73
+ },
74
+ {
75
+ id: "monitors",
76
+ label: "$1",
77
+ title: "Active Monitor watchers in this session ($1 in the CLI footer)",
78
+ pattern: /· (\d+ monitors?)(?= ·|\s*$)/m,
79
+ },
80
+ {
81
+ id: "bg-agents",
82
+ label: "$1",
83
+ title: "Background subagents running in this session ($1 in the CLI footer)",
84
+ pattern: /← (\d+ agents?)(?= ·|\s*$)/m,
85
+ },
86
+ {
87
+ id: "pr",
88
+ label: "$1",
89
+ title: "This session is linked to a GitHub pull request ($1 in the CLI footer)",
90
+ pattern: /· (PR #\d+)(?= ·|\s*$)/m,
91
+ },
49
92
  ];
50
93
 
51
94
  /**
52
95
  * Which badge ids match the given rendered screen lines. Pure so it's
53
- * unit-tested without a live PTY/log file.
96
+ * unit-tested without a live PTY/log file. A def whose pattern captured a
97
+ * group yields `id:capture` (e.g. "shells:4 shells") so the count travels
98
+ * with the id over the wire; static defs yield the bare id as before.
54
99
  */
55
100
  export function matchBadges(lines: string[], defs: BadgeDef[] = BADGE_DEFS): string[] {
56
101
  const text = lines.join("\n");
57
- return defs.filter((d) => d.pattern.test(text)).map((d) => d.id);
102
+ return defs.flatMap((d) => {
103
+ const m = d.pattern.exec(text);
104
+ if (!m) return [];
105
+ return [m[1] !== undefined ? `${d.id}:${m[1]}` : d.id];
106
+ });
58
107
  }
59
108
 
60
109
  /**
@@ -72,5 +121,20 @@ export const TYPING_BADGE: BadgeDef = {
72
121
  };
73
122
 
74
123
  export function badgeDef(id: string, defs: BadgeDef[] = BADGE_DEFS): BadgeDef | undefined {
75
- return defs.find((d) => d.id === id) ?? (id === TYPING_BADGE.id ? TYPING_BADGE : undefined);
124
+ // Dynamic ids carry their captured text after a ":" ("shells:4 shells")
125
+ // strip it so the base def resolves.
126
+ const base = id.includes(":") ? id.slice(0, id.indexOf(":")) : id;
127
+ return defs.find((d) => d.id === base) ?? (base === TYPING_BADGE.id ? TYPING_BADGE : undefined);
128
+ }
129
+
130
+ /**
131
+ * Rendered chip text for a (possibly dynamic) badge id: resolves the def and
132
+ * substitutes `$1` in its label with the id's captured text. Unknown ids fall
133
+ * back to the raw id so a newer server's badge never renders blank.
134
+ */
135
+ export function badgeLabel(id: string, defs: BadgeDef[] = BADGE_DEFS): string {
136
+ const def = badgeDef(id, defs);
137
+ if (!def) return id;
138
+ const arg = id.includes(":") ? id.slice(id.indexOf(":") + 1) : "";
139
+ return def.label.includes("$1") ? def.label.replace("$1", arg) : def.label;
76
140
  }
@@ -91,10 +91,7 @@ describe("notifyd singleton lock", () => {
91
91
  expect(await acquireDaemonLock(() => true)).toBe(true);
92
92
  const owner = JSON.parse(await readFile(daemonLockOwnerPath(), "utf8"));
93
93
  // Rewrite the owner to a DIFFERENT, "alive" pid to simulate another daemon.
94
- await writeFile(
95
- daemonLockOwnerPath(),
96
- JSON.stringify({ ...owner, pid: owner.pid + 1 }),
97
- );
94
+ await writeFile(daemonLockOwnerPath(), JSON.stringify({ ...owner, pid: owner.pid + 1 }));
98
95
  expect(await acquireDaemonLock((pid) => pid === owner.pid + 1)).toBe(false);
99
96
  });
100
97
  });
@@ -139,10 +136,7 @@ describe("notifyd identity (status/stop safety)", () => {
139
136
  it("returns null for an INCOMPLETE owner missing started_at (I1)", async () => {
140
137
  await mkdir(daemonLockDir(), { recursive: true });
141
138
  // pid alive + fresh ts, but no started_at → identity incomplete, not trusted.
142
- await writeFile(
143
- daemonLockOwnerPath(),
144
- JSON.stringify({ pid: process.pid, ts: Date.now() }),
145
- );
139
+ await writeFile(daemonLockOwnerPath(), JSON.stringify({ pid: process.pid, ts: Date.now() }));
146
140
  expect(await daemonStatus()).toBeNull();
147
141
  });
148
142
 
@@ -18,17 +18,8 @@
18
18
  import { execFile } from "node:child_process";
19
19
  import { randomUUID } from "node:crypto";
20
20
  import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
21
- import {
22
- type ChildObservation,
23
- type RouterState,
24
- stepRouter,
25
- } from "./notifyRouter.ts";
26
- import {
27
- type NotifyEvent,
28
- daemonLockDir,
29
- daemonLockOwnerPath,
30
- notifyDir,
31
- } from "./notifyInbox.ts";
21
+ import { type ChildObservation, type RouterState, stepRouter } from "./notifyRouter.ts";
22
+ import { type NotifyEvent, daemonLockDir, daemonLockOwnerPath, notifyDir } from "./notifyInbox.ts";
32
23
  import {
33
24
  appendEvent,
34
25
  gcInboxes,
@@ -303,7 +294,12 @@ async function writeOwner(): Promise<boolean> {
303
294
  try {
304
295
  await writeFile(
305
296
  tmp,
306
- JSON.stringify({ pid: process.pid, started_at: daemonStartedAt, ts: Date.now(), token: daemonToken }),
297
+ JSON.stringify({
298
+ pid: process.pid,
299
+ started_at: daemonStartedAt,
300
+ ts: Date.now(),
301
+ token: daemonToken,
302
+ }),
307
303
  );
308
304
  await rename(tmp, daemonLockOwnerPath());
309
305
  return true;
@@ -321,7 +317,9 @@ async function writeOwner(): Promise<boolean> {
321
317
  * mkdir→writeOwner window of another daemon coming up), so two concurrent starts
322
318
  * can't both "win". The liveness predicate is injectable for tests.
323
319
  */
324
- export async function acquireDaemonLock(isAlive: (pid: number) => boolean = isPidAlive): Promise<boolean> {
320
+ export async function acquireDaemonLock(
321
+ isAlive: (pid: number) => boolean = isPidAlive,
322
+ ): Promise<boolean> {
325
323
  // The lock dir lives under notify/ — ensure that exists first, else mkdir of
326
324
  // the lock throws ENOENT which must NOT be mistaken for "someone holds it".
327
325
  await mkdir(notifyDir(), { recursive: true }).catch(() => {});
@@ -414,20 +412,23 @@ export async function runDaemon(opts: DaemonOptions = {}): Promise<number> {
414
412
  // tick — many watched children, slow log I/O — can't let the heartbeat cross
415
413
  // OWNER_TTL and have another `watch` steal the lock as "stale" → double daemon.
416
414
  // Refresh well inside the TTL; cleared on shutdown.
417
- const ownerBeat = setInterval(() => {
418
- void (async () => {
419
- // Refresh ONLY while the owner still carries OUR token. Any other value — a
420
- // different token (superseded) OR null/absent (a new daemon's mkdir→write
421
- // window) means we no longer own it, so stop rather than clobber an
422
- // unknown/absent owner. Atomic writeOwner means a null read is never our own
423
- // write in flight.
424
- if ((await readDaemonOwnerToken()) !== daemonToken) {
425
- clearInterval(ownerBeat);
426
- return;
427
- }
428
- await writeOwner();
429
- })();
430
- }, Math.max(1000, Math.floor(OWNER_TTL_MS / 3)));
415
+ const ownerBeat = setInterval(
416
+ () => {
417
+ void (async () => {
418
+ // Refresh ONLY while the owner still carries OUR token. Any other value — a
419
+ // different token (superseded) OR null/absent (a new daemon's mkdir→write
420
+ // window) means we no longer own it, so stop rather than clobber an
421
+ // unknown/absent owner. Atomic writeOwner means a null read is never our own
422
+ // write in flight.
423
+ if ((await readDaemonOwnerToken()) !== daemonToken) {
424
+ clearInterval(ownerBeat);
425
+ return;
426
+ }
427
+ await writeOwner();
428
+ })();
429
+ },
430
+ Math.max(1000, Math.floor(OWNER_TTL_MS / 3)),
431
+ );
431
432
  if (typeof ownerBeat.unref === "function") ownerBeat.unref();
432
433
  const cleanup = async () => {
433
434
  running = false;
@@ -443,11 +444,7 @@ export async function runDaemon(opts: DaemonOptions = {}): Promise<number> {
443
444
  // Seed the router from prior inbox state — scoped to currently-watched parents
444
445
  // and guarded against pid reuse by the current registry snapshot.
445
446
  const watchedAtStart = new Set((await liveWatchers()).keys());
446
- let prev = await reconcileFromInboxes(
447
- host,
448
- await liveChildrenSnapshot(),
449
- watchedAtStart,
450
- );
447
+ let prev = await reconcileFromInboxes(host, await liveChildrenSnapshot(), watchedAtStart);
451
448
  let ticks = 0;
452
449
  let emptySince: number | null = null;
453
450
  // Durable across ticks: edges whose append failed, retried until they land.
@@ -556,7 +553,9 @@ async function tickState(
556
553
  pendingRetry.set(retryKey(stored), stored);
557
554
  logger.warn(`[notifyd] append failed for parent ${stored.parent_pid} — queued for retry`);
558
555
  } else {
559
- logger.warn(`[notifyd] retry queue full (${RETRY_CAP}) — dropping edge for parent ${stored.parent_pid}`);
556
+ logger.warn(
557
+ `[notifyd] retry queue full (${RETRY_CAP}) — dropping edge for parent ${stored.parent_pid}`,
558
+ );
560
559
  }
561
560
  }
562
561
  return { next, watcherCount };
@@ -58,7 +58,8 @@ describe("notifyInbox — NDJSON round-trip + torn-line tolerance", () => {
58
58
  });
59
59
 
60
60
  it("skips a torn final line from a mid-append writer", () => {
61
- const text = serializeEvent(ev({ seq: 1 })) + "\n" + serializeEvent(ev({ seq: 2 })) + "\n{ \"seq\": 3, \"ed";
61
+ const text =
62
+ serializeEvent(ev({ seq: 1 })) + "\n" + serializeEvent(ev({ seq: 2 })) + '\n{ "seq": 3, "ed';
62
63
  const got = parseInboxText(text);
63
64
  expect(got.map((e) => e.seq)).toEqual([1, 2]);
64
65
  });
@@ -1,10 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { classifyNeedsInput } from "./needsInput.ts";
3
- import {
4
- type ChildObservation,
5
- type RouterState,
6
- stepRouter,
7
- } from "./notifyRouter.ts";
3
+ import { type ChildObservation, type RouterState, stepRouter } from "./notifyRouter.ts";
8
4
 
9
5
  // Mirror the claude `needsInput`/`working` config (rs/default.config.yaml): the
10
6
  // menu cursor sits on a NUMBERED option, and a spinner marker means "working".
@@ -75,7 +71,11 @@ describe("notifyRouter — idle hysteresis (P1)", () => {
75
71
 
76
72
  describe("notifyRouter — needs_input", () => {
77
73
  it("emits immediately on entering needs_input, with the question", () => {
78
- const r = step(new Map(), [child({ state: "needs_input", question: "Approve? 1.Yes 2.No" })], 0);
74
+ const r = step(
75
+ new Map(),
76
+ [child({ state: "needs_input", question: "Approve? 1.Yes 2.No" })],
77
+ 0,
78
+ );
79
79
  expect(r.events).toHaveLength(1);
80
80
  expect(r.events[0]!.edge).toBe("needs_input");
81
81
  expect(r.events[0]!.question).toBe("Approve? 1.Yes 2.No");
@@ -167,11 +167,7 @@ describe("notifyRouter — idle-prompt fixture (P1 regression)", () => {
167
167
  });
168
168
 
169
169
  it("an ACTIVE menu (cursor on a number) still classifies as needs_input", () => {
170
- const activeMenu = [
171
- " Do you want to proceed?",
172
- "❯ 1. Yes",
173
- " 2. No, keep planning",
174
- ];
170
+ const activeMenu = [" Do you want to proceed?", "❯ 1. Yes", " 2. No, keep planning"];
175
171
  expect(classifyNeedsInput(activeMenu, CFG)).not.toBeNull();
176
172
  });
177
173
 
@@ -176,7 +176,10 @@ describe("notifyStore (fs)", () => {
176
176
  await mkdir(inboxDir(host), { recursive: true });
177
177
  // Simulate a crash between "reserve seq 5" and "append line": the counter
178
178
  // says 5, but the inbox only has 1..3 (4 and 5 are reserved gaps).
179
- await writeFile(inboxPath(host, 700), [1, 2, 3].map((s) => JSON.stringify(baseEvent("idle", { seq: s }))).join("\n") + "\n");
179
+ await writeFile(
180
+ inboxPath(host, 700),
181
+ [1, 2, 3].map((s) => JSON.stringify(baseEvent("idle", { seq: s }))).join("\n") + "\n",
182
+ );
180
183
  await writeFile(seqPath(host, 700), "5");
181
184
  const next = await appendEvent(700, baseEvent("idle"));
182
185
  expect(next).toBe(6); // continues past the reserved gap, never reuses 4 or 5
package/ts/notifyStore.ts CHANGED
@@ -138,10 +138,7 @@ async function atomicWrite(file: string, data: string): Promise<boolean> {
138
138
  * deliberately NO wall-clock backstop: elapsed wait time alone never steals, so a
139
139
  * live holder is inviolable.
140
140
  */
141
- export async function acquireLock(
142
- lockDir: string,
143
- staleMs = 30_000,
144
- ): Promise<() => Promise<void>> {
141
+ export async function acquireLock(lockDir: string, staleMs = 30_000): Promise<() => Promise<void>> {
145
142
  const ownerFile = path.join(lockDir, "owner");
146
143
  // A fencing token unique to THIS acquisition. Everything we do to the lock is
147
144
  // gated on the owner file still carrying our token — so if we were stale-stolen
@@ -194,15 +191,18 @@ export async function acquireLock(
194
191
  // window) — means we no longer own it, so we stop rather than clobber an
195
192
  // unknown/absent owner. Atomic writes above mean a null read is never just
196
193
  // our own write in flight. Refresh well inside staleMs; cleared on release.
197
- const beat = setInterval(() => {
198
- void (async () => {
199
- if ((await readOwnerToken()) !== token) {
200
- clearInterval(beat);
201
- return;
202
- }
203
- await stampOwner();
204
- })();
205
- }, Math.max(500, Math.floor(staleMs / 3)));
194
+ const beat = setInterval(
195
+ () => {
196
+ void (async () => {
197
+ if ((await readOwnerToken()) !== token) {
198
+ clearInterval(beat);
199
+ return;
200
+ }
201
+ await stampOwner();
202
+ })();
203
+ },
204
+ Math.max(500, Math.floor(staleMs / 3)),
205
+ );
206
206
  if (typeof beat.unref === "function") beat.unref();
207
207
  return async () => {
208
208
  clearInterval(beat);
@@ -296,7 +296,11 @@ export async function listInboxParents(host: string): Promise<number[]> {
296
296
  return pids;
297
297
  }
298
298
 
299
- export async function getCursor(host: string, parentPid: number, consumer = "parent"): Promise<number> {
299
+ export async function getCursor(
300
+ host: string,
301
+ parentPid: number,
302
+ consumer = "parent",
303
+ ): Promise<number> {
300
304
  const text = await readFile(cursorPath(host, parentPid, consumer), "utf8").catch(() => null);
301
305
  return parseCursor(text).seq;
302
306
  }
package/ts/serve.spec.ts CHANGED
@@ -45,9 +45,9 @@ describe("isNoNodeExecError", () => {
45
45
  });
46
46
 
47
47
  it("matches Windows cmd output", () => {
48
- expect(
49
- isNoNodeExecError("'node' is not recognized as an internal or external command"),
50
- ).toBe(true);
48
+ expect(isNoNodeExecError("'node' is not recognized as an internal or external command")).toBe(
49
+ true,
50
+ );
51
51
  });
52
52
 
53
53
  it("matches localized-then-normalized output (probe pins LC_ALL=C)", () => {
@@ -58,9 +58,7 @@ describe("isNoNodeExecError", () => {
58
58
 
59
59
  it("does not match a real native/glibc failure", () => {
60
60
  expect(
61
- isNoNodeExecError(
62
- "oxmgr: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.39' not found",
63
- ),
61
+ isNoNodeExecError("oxmgr: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.39' not found"),
64
62
  ).toBe(false);
65
63
  expect(isNoNodeExecError("")).toBe(false);
66
64
  });
package/ts/serve.ts CHANGED
@@ -3,7 +3,18 @@ import { existsSync, renameSync, watch, writeFileSync } from "node:fs";
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { createHash, randomBytes, timingSafeEqual } from "crypto";
6
- import { arch, cpus, freemem, homedir, hostname, loadavg, platform, totalmem, uptime, userInfo } from "os";
6
+ import {
7
+ arch,
8
+ cpus,
9
+ freemem,
10
+ homedir,
11
+ hostname,
12
+ loadavg,
13
+ platform,
14
+ totalmem,
15
+ uptime,
16
+ userInfo,
17
+ } from "os";
7
18
  import path from "path";
8
19
  import yargs from "yargs";
9
20
  import {
@@ -400,9 +411,7 @@ function resolveDaemonManager(): DaemonManager | null {
400
411
  // - npm: `npm prefix -g` prints the prefix; the bin lives at <prefix>/bin.
401
412
  async function globalBinDir(installer: string[]): Promise<string | null> {
402
413
  const isBun = installer[1] === "add";
403
- const query = isBun
404
- ? [installer[0]!, "pm", "bin", "-g"]
405
- : [installer[0]!, "prefix", "-g"];
414
+ const query = isBun ? [installer[0]!, "pm", "bin", "-g"] : [installer[0]!, "prefix", "-g"];
406
415
  const p = Bun.spawn(query, { stdout: "pipe", stderr: "ignore" });
407
416
  if ((await p.exited) !== 0) return null;
408
417
  const out = (await new Response(p.stdout).text()).trim();
@@ -1292,7 +1301,11 @@ export async function cmdServe(rest: string[]): Promise<number> {
1292
1301
  // by the console's HTML escaper.
1293
1302
  if (title) {
1294
1303
  // eslint-disable-next-line no-control-regex
1295
- title = title.replace(/[\x00-\x1f\x7f-\x9f]/g, "").slice(0, 256).trim() || null;
1304
+ title =
1305
+ title
1306
+ .replace(/[\x00-\x1f\x7f-\x9f]/g, "")
1307
+ .slice(0, 256)
1308
+ .trim() || null;
1296
1309
  }
1297
1310
  titleCache.set(logFile, { size, mtimeMs, title });
1298
1311
  return title;
@@ -1311,7 +1324,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
1311
1324
  // the SOURCE: secret-shaped lines are dropped to "···" and long credential-ish
1312
1325
  // blobs masked — the feed is token-gated, but tails travel to other rgui hosts.
1313
1326
  const previewCache = new Map<string, { size: number; mtimeMs: number; lines: string[] | null }>();
1314
- const SECRETISH = /(api[-_]?key|secret|token|password|passwd|bearer|authorization|private[-_]?key)\s*[=:]/i;
1327
+ const SECRETISH =
1328
+ /(api[-_]?key|secret|token|password|passwd|bearer|authorization|private[-_]?key)\s*[=:]/i;
1315
1329
  const logPreviewLines = async (logFile: string | null | undefined): Promise<string[] | null> => {
1316
1330
  if (!logFile) return null;
1317
1331
  try {
@@ -1860,7 +1874,10 @@ export async function cmdServe(rest: string[]): Promise<number> {
1860
1874
  category: "agent-yes",
1861
1875
  owner: `agent-yes:${hostname()}`,
1862
1876
  status: r.status,
1863
- parent: r.parent_pid && byWrapper.has(r.parent_pid) ? nid(byWrapper.get(r.parent_pid)!) : undefined,
1877
+ parent:
1878
+ r.parent_pid && byWrapper.has(r.parent_pid)
1879
+ ? nid(byWrapper.get(r.parent_pid)!)
1880
+ : undefined,
1864
1881
  pos: { x: (i % 8) * (NODE_W + 64), y: Math.floor(i / 8) * 480 },
1865
1882
  size: await sizeOf(r.pid),
1866
1883
  inputs: [textIn, envIn],
@@ -1887,7 +1904,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
1887
1904
  configPublic: {
1888
1905
  scope: "native-device",
1889
1906
  runtime: "native",
1890
- caps: { send: true, kill: true, spawn: true, spawnHook: hasSpawnHook(), provision: hasProvisionHook() },
1907
+ caps: {
1908
+ send: true,
1909
+ kill: true,
1910
+ spawn: true,
1911
+ spawnHook: hasSpawnHook(),
1912
+ provision: hasProvisionHook(),
1913
+ },
1891
1914
  },
1892
1915
  } as unknown as (typeof nodes)[number]); // configPublic is envelope-legal but absent from the agent-node literal type
1893
1916
  const codex = records
@@ -1936,11 +1959,19 @@ export async function cmdServe(rest: string[]): Promise<number> {
1936
1959
  {
1937
1960
  kind: "rgui-federated-graph",
1938
1961
  schema: "org.rgui.graph.v1",
1939
- producer: { app: "ay", origin, deviceId: hostname(), label: `agent-yes fleet @ ${hostname()}` },
1962
+ producer: {
1963
+ app: "ay",
1964
+ origin,
1965
+ deviceId: hostname(),
1966
+ label: `agent-yes fleet @ ${hostname()}`,
1967
+ },
1940
1968
  revision: Date.now(),
1941
1969
  ts: Date.now(),
1942
1970
  graph: { nodes, edges },
1943
- capabilities: { nodeTypes: [...new Set(nodes.map((n) => n.type))], portTypes: ["text", "environment"] },
1971
+ capabilities: {
1972
+ nodeTypes: [...new Set(nodes.map((n) => n.type))],
1973
+ portTypes: ["text", "environment"],
1974
+ },
1944
1975
  },
1945
1976
  { headers: { "Access-Control-Allow-Origin": "*" } },
1946
1977
  );
@@ -2205,7 +2236,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
2205
2236
  // verify before demoting: give it 100ms to fire, and only
2206
2237
  // then demote this stream to the fast poll it would have used
2207
2238
  // had watch() failed outright.
2208
- if (viaPoll && watcher && !demoteCheck && Date.now() - lastWatcherFireAt > POLL_WATCHED - 50) {
2239
+ if (
2240
+ viaPoll &&
2241
+ watcher &&
2242
+ !demoteCheck &&
2243
+ Date.now() - lastWatcherFireAt > POLL_WATCHED - 50
2244
+ ) {
2209
2245
  const suspectAt = Date.now();
2210
2246
  demoteCheck = setTimeout(() => {
2211
2247
  demoteCheck = null;
@@ -2673,7 +2709,11 @@ export async function cmdServe(rest: string[]): Promise<number> {
2673
2709
  // Allowlist gate — only when a provision hook did NOT already gate this
2674
2710
  // (a configured hook overrides the allowlist). The fork runs setup-repo.sh
2675
2711
  // (code exec), the same risk surface as `from`.
2676
- if (!forkHook.ran && result.spec && !isProvisionAllowed(result.spec.owner, result.spec.repo))
2712
+ if (
2713
+ !forkHook.ran &&
2714
+ result.spec &&
2715
+ !isProvisionAllowed(result.spec.owner, result.spec.repo)
2716
+ )
2677
2717
  return new Response(
2678
2718
  `forking '${result.spec.owner}/${result.spec.repo}' is not allowed — add the owner ` +
2679
2719
  `to provisionAllowlist in ~/.agent-yes/config.json (or "*" to allow all), ` +
@@ -2912,7 +2952,11 @@ export async function cmdServe(rest: string[]): Promise<number> {
2912
2952
  return Response.json(share);
2913
2953
  } catch (e) {
2914
2954
  const msg = (e as Error).message;
2915
- const status = /too many active shares/.test(msg) ? 409 : /no agent matched|no stable/.test(msg) ? 404 : 500;
2955
+ const status = /too many active shares/.test(msg)
2956
+ ? 409
2957
+ : /no agent matched|no stable/.test(msg)
2958
+ ? 404
2959
+ : 500;
2916
2960
  return new Response(msg, { status });
2917
2961
  }
2918
2962
  }
@@ -2947,7 +2991,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
2947
2991
  try {
2948
2992
  const { ensureExposure } = await import("./expose.ts");
2949
2993
  const h = await ensureExposure(port, body.relay);
2950
- return Response.json({ id: h.id, port: h.port, url: h.url, claim: h.mintClaim(), createdAt: h.createdAt });
2994
+ return Response.json({
2995
+ id: h.id,
2996
+ port: h.port,
2997
+ url: h.url,
2998
+ claim: h.mintClaim(),
2999
+ createdAt: h.createdAt,
3000
+ });
2951
3001
  } catch (e) {
2952
3002
  return new Response(`expose failed: ${(e as Error).message}`, { status: 502 });
2953
3003
  }
@@ -3000,7 +3050,10 @@ export async function cmdServe(rest: string[]): Promise<number> {
3000
3050
  const RGUI_CSP = CONSOLE_CSP.replace("frame-ancestors 'none'", "frame-ancestors *")
3001
3051
  // #feed= mirrors fetch OTHER rgui hosts' envelopes (federation consumers):
3002
3052
  // otoji.org in prod, localhost/loopback for wrangler-dev feeds
3003
- .replace("connect-src 'self'", "connect-src 'self' https://otoji.org http://127.0.0.1:* http://localhost:*");
3053
+ .replace(
3054
+ "connect-src 'self'",
3055
+ "connect-src 'self' https://otoji.org http://127.0.0.1:* http://localhost:*",
3056
+ );
3004
3057
  const serveUiFile = async (name: string, type: string, csp = CONSOLE_CSP): Promise<Response> => {
3005
3058
  try {
3006
3059
  const buf = await readFile(path.join(uiDir, name));
@@ -3046,8 +3099,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
3046
3099
  return serveUiFile("sw.js", "text/javascript; charset=utf-8");
3047
3100
  if (req.method === "GET" && p === "/manifest.webmanifest")
3048
3101
  return serveUiFile("manifest.webmanifest", "application/manifest+json");
3049
- if (req.method === "GET" && p === "/icon.svg")
3050
- return serveUiFile("icon.svg", "image/svg+xml");
3102
+ if (req.method === "GET" && p === "/icon.svg") return serveUiFile("icon.svg", "image/svg+xml");
3051
3103
  if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
3052
3104
  return apiFetch(req);
3053
3105
  };