agent-yes 1.140.2 → 1.141.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.
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-B1-x2rKP.js";
1
+ import { t as CLIS_CONFIG } from "./ts-CMMwNq--.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-CTOnaXTX.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-CnBoRIDN.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-CMMwNq--.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-DMpCuaZe.js";
4
+ import "./pidStore-fqXqTKkh.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CnBoRIDN.js";
7
+
8
+ export { SUPPORTED_CLIS };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { t as invokedCliName } from "./invokedCli-zdFbz1ST.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-Bhn4NygE.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DMpCuaZe.js";
5
5
  import { argv } from "process";
6
6
  import { execFileSync, spawn } from "child_process";
7
7
  import ms from "ms";
@@ -480,7 +480,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
480
480
  const rawArg = process.argv[2];
481
481
  const managerCommands = !invokedCliName(process.argv);
482
482
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
483
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DKizMTs9.js");
483
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-3BaA-AmB.js");
484
484
  if (isHelpFlag && process.argv.length === 3) {
485
485
  cmdHelp(managerCommands);
486
486
  process.exit(0);
@@ -513,7 +513,7 @@ if (config.useRust) {
513
513
  }
514
514
  }
515
515
  if (rustBinary) {
516
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DYcaH-bR.js");
516
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-bW-3DoHX.js");
517
517
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
518
518
  if (config.verbose) {
519
519
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-B1-x2rKP.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CMMwNq--.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-Bhn4NygE.js";
3
+ import "./versionChecker-DMpCuaZe.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
@@ -1,9 +1,9 @@
1
- import "./ts-B1-x2rKP.js";
1
+ import "./ts-CMMwNq--.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-Bhn4NygE.js";
3
+ import "./versionChecker-DMpCuaZe.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CTOnaXTX.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CnBoRIDN.js";
7
7
  import { n as resolveSpawnCwd } from "./workspaceConfig-BCOqRBEW.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-DZb7ZQgW.js.map
144
+ //# sourceMappingURL=schedule-PVnlPldE.js.map
@@ -1,13 +1,13 @@
1
- import "./ts-B1-x2rKP.js";
1
+ import "./ts-CMMwNq--.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-Bhn4NygE.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-DMpCuaZe.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import { a as updateGlobalPidStatus } from "./globalPidIndex-DlmmJlO8.js";
6
6
  import { t as pgidForWrapper } from "./reaper-C-eWAxIj.js";
7
7
  import "./configShared-C1C04bbq.js";
8
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CTOnaXTX.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CnBoRIDN.js";
9
9
  import "./remotes-PKKjfTI1.js";
10
- import { S as writeToIpc, _ as resolveOne, b as snapshotStatus, c as extractTaskCounts, f as listRecords, h as renderRawLog, i as controlCodeFromName, m as readNotes, o as deriveLiveStatus } from "./subcommands-Cz7-sHOT.js";
10
+ import { C as writeToIpc, c as extractTaskCounts, g as renderRawLog, h as readNotes, i as controlCodeFromName, o as deriveLiveStatus, p as listRecords, v as resolveOne, x as snapshotStatus } from "./subcommands-dbx7EoK-.js";
11
11
  import yargs from "yargs";
12
12
  import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
13
13
  import { homedir, hostname, userInfo } from "os";
@@ -1175,4 +1175,4 @@ Options:
1175
1175
 
1176
1176
  //#endregion
1177
1177
  export { cmdServe };
1178
- //# sourceMappingURL=serve-BBwXw8gY.js.map
1178
+ //# sourceMappingURL=serve-D8UEMOas.js.map
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
32
32
  if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
33
33
  if (noShare) return 0;
34
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
35
- const { cmdServe } = await import("./serve-BBwXw8gY.js");
35
+ const { cmdServe } = await import("./serve-D8UEMOas.js");
36
36
  return cmdServe([
37
37
  "install",
38
38
  "--share",
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
42
42
 
43
43
  //#endregion
44
44
  export { cmdSetup };
45
- //# sourceMappingURL=setup-S9LoYWK5.js.map
45
+ //# sourceMappingURL=setup-BPAdHGDG.js.map
@@ -0,0 +1,7 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import "./globalPidIndex-DlmmJlO8.js";
3
+ import "./configShared-C1C04bbq.js";
4
+ import "./remotes-PKKjfTI1.js";
5
+ import { C as writeToIpc, S as stopTipForCli, _ as renderRawLogLines, a as cursorAbs, b as runSubcommand, c as extractTaskCounts, d as isPidAlive, f as isSubcommand, g as renderRawLog, h as readNotes, i as controlCodeFromName, l as finalizedLines, m as matchKeyword, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as listRecords, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as resolveOne, x as snapshotStatus, y as resolveReadWindow } from "./subcommands-dbx7EoK-.js";
6
+
7
+ export { cmdHelp, isSubcommand, runSubcommand };
@@ -182,6 +182,19 @@ function classifyNeedsInput(lines, cfg) {
182
182
  const end = Math.min(lines.length, last + 6);
183
183
  return { question: lines.slice(start, end).map((l) => l.trim()).filter((l) => l && !isChromeLine(l)).join(" • ").slice(0, 400) };
184
184
  }
185
+ /**
186
+ * True when the rendered screen still shows a "busy" marker (config `working`,
187
+ * e.g. claude's `esc to interrupt`). Paired with a long-quiet log this is the
188
+ * `stuck` signal: a live spinner writes to the log every frame, so a busy marker
189
+ * on screen WITHOUT recent output means the agent wedged mid-stream (a silent
190
+ * API stream stall) rather than finishing. Pure + synchronous like the rest of
191
+ * this module so it's trivially unit-testable.
192
+ */
193
+ function isWorkingScreen(lines, working) {
194
+ if (!working?.length) return false;
195
+ const text = lines.join("\n");
196
+ return working.some((re) => reTest(re, text));
197
+ }
185
198
 
186
199
  //#endregion
187
200
  //#region ts/lsWatch.ts
@@ -498,6 +511,10 @@ const SUBCOMMANDS = new Set([
498
511
  ]);
499
512
  const MANAGER_SUBCOMMANDS = new Set(["setup"]);
500
513
  const IDLE_THRESHOLD_MS = 60 * 1e3;
514
+ const STUCK_THRESHOLD_MS = (() => {
515
+ const n = Number(process.env.AGENT_YES_STUCK_MS);
516
+ return Number.isFinite(n) && n > 0 ? n : 300 * 1e3;
517
+ })();
501
518
  /**
502
519
  * Whether `name` is a subcommand. `managerCommands` (default true, for the
503
520
  * generic `ay`/`agent-yes` entry) additionally admits manager-only commands
@@ -534,15 +551,15 @@ async function runSubcommand(argv) {
534
551
  case "restart": return await cmdRestart(rest);
535
552
  case "note": return await cmdNote(rest);
536
553
  case "serve": {
537
- const { cmdServe } = await import("./serve-BBwXw8gY.js");
554
+ const { cmdServe } = await import("./serve-D8UEMOas.js");
538
555
  return cmdServe(rest);
539
556
  }
540
557
  case "setup": {
541
- const { cmdSetup } = await import("./setup-S9LoYWK5.js");
558
+ const { cmdSetup } = await import("./setup-BPAdHGDG.js");
542
559
  return cmdSetup(rest);
543
560
  }
544
561
  case "schedule": {
545
- const { cmdSchedule } = await import("./schedule-DZb7ZQgW.js");
562
+ const { cmdSchedule } = await import("./schedule-PVnlPldE.js");
546
563
  return cmdSchedule(rest);
547
564
  }
548
565
  case "remote": {
@@ -977,6 +994,10 @@ async function deriveLiveState(r) {
977
994
  state: "needs_input",
978
995
  question: ni.question
979
996
  };
997
+ if (base === "idle" && await isAgentStuck(r)) return {
998
+ state: "stuck",
999
+ question: null
1000
+ };
980
1001
  }
981
1002
  return {
982
1003
  state: base,
@@ -1000,7 +1021,7 @@ async function cmdLs(rest) {
1000
1021
  alias: "w",
1001
1022
  type: "boolean",
1002
1023
  default: false,
1003
- description: "Stream agent state transitions (needs_input | idle | active | stopped) as NDJSON across all matched agents — one event stream for a whole fan-out, instead of N per-pid `ay status --watch`es. Runs until Ctrl-C."
1024
+ description: "Stream agent state transitions (needs_input | idle | active | stuck | stopped) as NDJSON across all matched agents — one event stream for a whole fan-out, instead of N per-pid `ay status --watch`es. Runs until Ctrl-C."
1004
1025
  }).option("interval", {
1005
1026
  type: "number",
1006
1027
  default: 2,
@@ -1646,9 +1667,12 @@ function cliDefaults() {
1646
1667
  * and renders it through xterm, then runs the CLI's `needsInput`/`working`
1647
1668
  * patterns. Returns null when no menu is detected (or the CLI defines none).
1648
1669
  */
1649
- async function extractNeedsInput(logPath, cli) {
1650
- const cfg = (await cliDefaults())[cli];
1651
- if (!cfg?.needsInput?.length) return null;
1670
+ /**
1671
+ * Render the last `n` lines of a raw PTY log (reads only the final 32KB). Returns
1672
+ * null on any read/render error or an empty log. Shared by the needs_input and
1673
+ * stuck classifiers so they don't each re-implement the tail read.
1674
+ */
1675
+ async function renderLogTailLines(logPath, n = 40) {
1652
1676
  const TAIL_BYTES = 32 * 1024;
1653
1677
  let buf;
1654
1678
  try {
@@ -1671,17 +1695,41 @@ async function extractNeedsInput(logPath, cli) {
1671
1695
  return null;
1672
1696
  }
1673
1697
  try {
1674
- return classifyNeedsInput((await renderRawLog(buf, {
1698
+ return (await renderRawLog(buf, {
1675
1699
  mode: "tail",
1676
- n: 40
1677
- })).split("\n"), {
1678
- needsInput: cfg.needsInput,
1679
- working: cfg.working
1680
- });
1700
+ n
1701
+ })).split("\n");
1681
1702
  } catch {
1682
1703
  return null;
1683
1704
  }
1684
1705
  }
1706
+ async function extractNeedsInput(logPath, cli) {
1707
+ const cfg = (await cliDefaults())[cli];
1708
+ if (!cfg?.needsInput?.length) return null;
1709
+ const lines = await renderLogTailLines(logPath, 40);
1710
+ if (!lines) return null;
1711
+ return classifyNeedsInput(lines, {
1712
+ needsInput: cfg.needsInput,
1713
+ working: cfg.working
1714
+ });
1715
+ }
1716
+ /**
1717
+ * Whether an alive agent is wedged: its log has been silent for at least
1718
+ * STUCK_THRESHOLD_MS yet its screen still shows a `working` busy marker (a live
1719
+ * spinner keeps writing, so busy + long-silent = a mid-stream stall). Pass the
1720
+ * already-stat'd log mtime to skip a redundant stat. Returns false when the CLI
1721
+ * has no `working` markers configured (nothing to key off).
1722
+ */
1723
+ async function isAgentStuck(record, logMtimeMs) {
1724
+ if (!record.log_file) return false;
1725
+ const cfg = (await cliDefaults())[record.cli];
1726
+ if (!cfg?.working?.length) return false;
1727
+ const mtime = logMtimeMs ?? await stat(record.log_file).then((s) => s.mtimeMs).catch(() => null);
1728
+ if (mtime === null || Date.now() - mtime < STUCK_THRESHOLD_MS) return false;
1729
+ const lines = await renderLogTailLines(record.log_file, 40);
1730
+ if (!lines) return false;
1731
+ return isWorkingScreen(lines, cfg.working);
1732
+ }
1685
1733
  function extractActivityFromLines(lines) {
1686
1734
  const isChrome = (l) => {
1687
1735
  const s = l.trim();
@@ -2189,7 +2237,7 @@ async function snapshotStatus(record) {
2189
2237
  if (ni) {
2190
2238
  state = "needs_input";
2191
2239
  question = ni.question;
2192
- }
2240
+ } else if (state === "idle" && await isAgentStuck(record, logMtimeMs)) state = "stuck";
2193
2241
  }
2194
2242
  const note = (await readNotes()).get(record.pid) ?? null;
2195
2243
  return {
@@ -2269,7 +2317,7 @@ async function cmdStatus(rest) {
2269
2317
  const startedAt = Date.now();
2270
2318
  for (;;) {
2271
2319
  const snap = await snapshotStatus(record);
2272
- if (snap.state === "needs_input" || snap.state === "idle" || snap.state === "stopped") {
2320
+ if (snap.state === "needs_input" || snap.state === "idle" || snap.state === "stuck" || snap.state === "stopped") {
2273
2321
  emit(snap);
2274
2322
  return 0;
2275
2323
  }
@@ -2284,7 +2332,7 @@ async function cmdStatus(rest) {
2284
2332
  const startedAt = Date.now();
2285
2333
  for (;;) {
2286
2334
  const snap = await snapshotStatus(record);
2287
- if (snap.state === "idle") {
2335
+ if (snap.state === "idle" || snap.state === "stuck") {
2288
2336
  emit(snap);
2289
2337
  return 0;
2290
2338
  }
@@ -2456,5 +2504,5 @@ async function cmdResultSet(rest) {
2456
2504
  }
2457
2505
 
2458
2506
  //#endregion
2459
- export { writeToIpc as S, resolveOne as _, cursorAbs as a, snapshotStatus as b, extractTaskCounts as c, isSubcommand as d, listRecords as f, renderRawLogLines as g, renderRawLog as h, controlCodeFromName as i, finalizedLines as l, readNotes as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, matchKeyword as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isPidAlive as u, resolveReadWindow as v, stopTipForCli as x, runSubcommand as y };
2460
- //# sourceMappingURL=subcommands-Cz7-sHOT.js.map
2507
+ export { writeToIpc as C, stopTipForCli as S, renderRawLogLines as _, cursorAbs as a, runSubcommand as b, extractTaskCounts as c, isPidAlive as d, isSubcommand as f, renderRawLog as g, readNotes as h, controlCodeFromName as i, finalizedLines as l, matchKeyword as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, listRecords as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, resolveOne as v, snapshotStatus as x, resolveReadWindow as y };
2508
+ //# sourceMappingURL=subcommands-dbx7EoK-.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-Bhn4NygE.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DMpCuaZe.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-V4qvXgAw.js";
5
5
  import { t as PidStore } from "./pidStore-fqXqTKkh.js";
@@ -1788,4 +1788,4 @@ function sleep(ms) {
1788
1788
 
1789
1789
  //#endregion
1790
1790
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1791
- //# sourceMappingURL=ts-B1-x2rKP.js.map
1791
+ //# sourceMappingURL=ts-CMMwNq--.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.140.2";
10
+ var version = "1.141.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-Bhn4NygE.js.map
218
+ //# sourceMappingURL=versionChecker-DMpCuaZe.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.140.2",
3
+ "version": "1.141.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
package/ts/lsWatch.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * `needsInput.ts`.
11
11
  */
12
12
 
13
- export type LiveState = "active" | "idle" | "stopped" | "needs_input";
13
+ export type LiveState = "active" | "idle" | "stopped" | "needs_input" | "stuck";
14
14
 
15
15
  /** The observable state of one agent at a single tick. */
16
16
  export interface LsAgentState {
@@ -1,5 +1,5 @@
1
1
  import { expect, test } from "vitest";
2
- import { classifyNeedsInput } from "./needsInput.ts";
2
+ import { classifyNeedsInput, isWorkingScreen } from "./needsInput.ts";
3
3
  import { loadSharedCliDefaults } from "./configShared.ts";
4
4
 
5
5
  // Use the REAL shipped claude/codex patterns so the test guards the actual config.
@@ -11,6 +11,25 @@ test("claude config actually ships a needsInput pattern", () => {
11
11
  expect(claude.needsInput?.length).toBeGreaterThan(0);
12
12
  });
13
13
 
14
+ test("claude config ships a working busy marker (the stuck detector keys off it)", () => {
15
+ expect(claude.working?.length).toBeGreaterThan(0);
16
+ });
17
+
18
+ test("isWorkingScreen: true when the shipped claude busy marker is on screen", () => {
19
+ const screen = ["⏺ Running the test suite…", "", "esc to interrupt · ← for agents"];
20
+ expect(isWorkingScreen(screen, claude.working)).toBe(true);
21
+ });
22
+
23
+ test("isWorkingScreen: false at a finished/idle prompt (no busy marker)", () => {
24
+ const screen = ["⏺ Done — all tests pass.", "", "❯", "", "? for shortcuts"];
25
+ expect(isWorkingScreen(screen, claude.working)).toBe(false);
26
+ });
27
+
28
+ test("isWorkingScreen: false when no working patterns are configured", () => {
29
+ expect(isWorkingScreen(["esc to interrupt"], undefined)).toBe(false);
30
+ expect(isWorkingScreen(["esc to interrupt"], [])).toBe(false);
31
+ });
32
+
14
33
  test("detects a claude AskUserQuestion selection menu", () => {
15
34
  const screen = [
16
35
  "Which auth method should we use?",
package/ts/needsInput.ts CHANGED
@@ -66,3 +66,17 @@ export function classifyNeedsInput(
66
66
  .filter((l) => l && !isChromeLine(l));
67
67
  return { question: block.join(" • ").slice(0, 400) };
68
68
  }
69
+
70
+ /**
71
+ * True when the rendered screen still shows a "busy" marker (config `working`,
72
+ * e.g. claude's `esc to interrupt`). Paired with a long-quiet log this is the
73
+ * `stuck` signal: a live spinner writes to the log every frame, so a busy marker
74
+ * on screen WITHOUT recent output means the agent wedged mid-stream (a silent
75
+ * API stream stall) rather than finishing. Pure + synchronous like the rest of
76
+ * this module so it's trivially unit-testable.
77
+ */
78
+ export function isWorkingScreen(lines: string[], working?: RegExp[]): boolean {
79
+ if (!working?.length) return false;
80
+ const text = lines.join("\n");
81
+ return working.some((re) => reTest(re, text));
82
+ }
@@ -1571,3 +1571,74 @@ describe("subcommands.deriveLiveStatus", () => {
1571
1571
  }
1572
1572
  });
1573
1573
  });
1574
+
1575
+ describe("subcommands.isAgentStuck / stuck state", () => {
1576
+ const rec = (over: any) => ({
1577
+ pid: process.pid,
1578
+ cli: "claude",
1579
+ prompt: null,
1580
+ cwd: "/tmp",
1581
+ log_file: null,
1582
+ fifo_file: null,
1583
+ status: "active",
1584
+ exit_code: null,
1585
+ exit_reason: null,
1586
+ started_at: 0,
1587
+ ...over,
1588
+ });
1589
+ // A log whose rendered tail shows claude's shipped `working` busy marker.
1590
+ const BUSY = "⏺ Cogitating…\r\nesc to interrupt · ← for agents\r\n";
1591
+ const tenMinAgo = () => new Date(Date.now() - 10 * 60 * 1000);
1592
+
1593
+ it("isAgentStuck: true when a busy marker is on screen and the log is long-silent", async () => {
1594
+ const dir = await mkdtemp(path.join(tmpdir(), "ay-stuck-"));
1595
+ try {
1596
+ const log = path.join(dir, "a.log");
1597
+ await writeFile(log, BUSY);
1598
+ await utimes(log, tenMinAgo(), tenMinAgo());
1599
+ const mod = await loadModule();
1600
+ expect(await mod.isAgentStuck(rec({ log_file: log }))).toBe(true);
1601
+ } finally {
1602
+ await rm(dir, { recursive: true, force: true }).catch(() => null);
1603
+ }
1604
+ });
1605
+
1606
+ it("isAgentStuck: false when the busy log was written recently (still working)", async () => {
1607
+ const dir = await mkdtemp(path.join(tmpdir(), "ay-stuck-"));
1608
+ try {
1609
+ const log = path.join(dir, "a.log");
1610
+ await writeFile(log, BUSY); // fresh mtime — under the stuck threshold
1611
+ const mod = await loadModule();
1612
+ expect(await mod.isAgentStuck(rec({ log_file: log }))).toBe(false);
1613
+ } finally {
1614
+ await rm(dir, { recursive: true, force: true }).catch(() => null);
1615
+ }
1616
+ });
1617
+
1618
+ it("isAgentStuck: false when long-silent but no busy marker on screen (genuinely idle)", async () => {
1619
+ const dir = await mkdtemp(path.join(tmpdir(), "ay-stuck-"));
1620
+ try {
1621
+ const log = path.join(dir, "a.log");
1622
+ await writeFile(log, "⏺ Done — all green.\r\n❯\r\n");
1623
+ await utimes(log, tenMinAgo(), tenMinAgo());
1624
+ const mod = await loadModule();
1625
+ expect(await mod.isAgentStuck(rec({ log_file: log }))).toBe(false);
1626
+ } finally {
1627
+ await rm(dir, { recursive: true, force: true }).catch(() => null);
1628
+ }
1629
+ });
1630
+
1631
+ it("snapshotStatus: reports 'stuck' for a long-silent busy agent (not 'idle')", async () => {
1632
+ const dir = await mkdtemp(path.join(tmpdir(), "ay-stuck-"));
1633
+ try {
1634
+ const log = path.join(dir, "a.log");
1635
+ await writeFile(log, BUSY);
1636
+ await utimes(log, tenMinAgo(), tenMinAgo());
1637
+ const mod = await loadModule();
1638
+ const snap = await mod.snapshotStatus(rec({ log_file: log }));
1639
+ expect(snap.state).toBe("stuck");
1640
+ } finally {
1641
+ await rm(dir, { recursive: true, force: true }).catch(() => null);
1642
+ }
1643
+ });
1644
+ });
package/ts/subcommands.ts CHANGED
Binary file
@@ -1,8 +0,0 @@
1
- import "./ts-B1-x2rKP.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-Bhn4NygE.js";
4
- import "./pidStore-fqXqTKkh.js";
5
- import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CTOnaXTX.js";
7
-
8
- export { SUPPORTED_CLIS };
@@ -1,7 +0,0 @@
1
- import "./logger-CDIsZ-Pp.js";
2
- import "./globalPidIndex-DlmmJlO8.js";
3
- import "./configShared-C1C04bbq.js";
4
- import "./remotes-PKKjfTI1.js";
5
- import { S as writeToIpc, _ as resolveOne, a as cursorAbs, b as snapshotStatus, c as extractTaskCounts, d as isSubcommand, f as listRecords, g as renderRawLogLines, h as renderRawLog, i as controlCodeFromName, l as finalizedLines, m as readNotes, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as matchKeyword, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isPidAlive, v as resolveReadWindow, x as stopTipForCli, y as runSubcommand } from "./subcommands-Cz7-sHOT.js";
6
-
7
- export { cmdHelp, isSubcommand, runSubcommand };