claude-yes 1.167.0 → 1.168.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-s3wYccKf.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DBwi5BWW.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-DFBATC85.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-D0nNJGNj.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-DBwi5BWW.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-DVv-WHIp.js";
4
+ import "./pidStore-BfoBWUjc.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D0nNJGNj.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-uqM2YYA7.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-BvR6tV3u.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DVv-WHIp.js";
5
5
  import { argv } from "process";
6
6
  import { execFileSync, spawn } from "child_process";
7
7
  import ms from "ms";
@@ -481,7 +481,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
481
481
  const rawArg = process.argv[2];
482
482
  const managerCommands = !invokedCliName(process.argv);
483
483
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
484
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-B-WoBVhk.js");
484
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-B2QAXBkH.js");
485
485
  if (isHelpFlag && process.argv.length === 3) {
486
486
  await cmdHelp(managerCommands);
487
487
  process.exit(0);
@@ -523,7 +523,7 @@ if (config.useRust) {
523
523
  }
524
524
  }
525
525
  if (rustBinary) {
526
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DLFqRE8W.js");
526
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-Dy_o9sj6.js");
527
527
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
528
528
  if (config.verbose) {
529
529
  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-s3wYccKf.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DBwi5BWW.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-BvR6tV3u.js";
3
+ import "./versionChecker-DVv-WHIp.js";
4
4
  import "./pidStore-BfoBWUjc.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
@@ -1,9 +1,9 @@
1
- import "./ts-s3wYccKf.js";
1
+ import "./ts-DBwi5BWW.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-BvR6tV3u.js";
3
+ import "./versionChecker-DVv-WHIp.js";
4
4
  import "./pidStore-BfoBWUjc.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFBATC85.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D0nNJGNj.js";
7
7
  import { l as resolveSpawnCwd } from "./workspaceConfig-D3OH7and.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-C2RMA3qm.js.map
144
+ //# sourceMappingURL=schedule-BPUyALF_.js.map
@@ -1,16 +1,16 @@
1
- import "./ts-s3wYccKf.js";
1
+ import "./ts-DBwi5BWW.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-BvR6tV3u.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-DVv-WHIp.js";
4
4
  import "./pidStore-BfoBWUjc.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-CEyhl0WH.js";
8
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFBATC85.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D0nNJGNj.js";
9
9
  import "./e2e-Bfw7qL9O.js";
10
10
  import "./webrtcLink-CBZkZ-LT.js";
11
11
  import "./remotes-T6nf0t3K.js";
12
12
  import { c as isProvisionAllowed, i as getSpawnHook, l as resolveSpawnCwd, r as getProvisionRoot, s as hasSpawnHook } from "./workspaceConfig-D3OH7and.js";
13
- import { A as writeToIpc, D as snapshotStatus, S as resolveOne, b as renderRawLog, c as extractNeedsInput, h as listRecords, i as controlCodeFromName, l as extractTaskCounts, o as deriveLiveStatus, v as readNotes, y as readPtysize } from "./subcommands-DWeo7ZLc.js";
13
+ import { A as writeToIpc, D as snapshotStatus, S as resolveOne, b as renderRawLog, c as extractNeedsInput, h as listRecords, i as controlCodeFromName, l as extractTaskCounts, o as deriveLiveStatus, v as readNotes, y as readPtysize } from "./subcommands-VimbMSTG.js";
14
14
  import { r as spawnRejectionReason } from "./spawnGate-B_VDMXYL.js";
15
15
  import yargs from "yargs";
16
16
  import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
@@ -1445,4 +1445,4 @@ Options:
1445
1445
 
1446
1446
  //#endregion
1447
1447
  export { cmdServe };
1448
- //# sourceMappingURL=serve-mLqsRwfR.js.map
1448
+ //# sourceMappingURL=serve-DAuLdt_d.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-mLqsRwfR.js");
35
+ const { cmdServe } = await import("./serve-DAuLdt_d.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-BtqKZx3q.js.map
45
+ //# sourceMappingURL=setup-YLHUtCC0.js.map
@@ -4,6 +4,6 @@ import "./configShared-CEyhl0WH.js";
4
4
  import "./e2e-Bfw7qL9O.js";
5
5
  import "./webrtcLink-CBZkZ-LT.js";
6
6
  import "./remotes-T6nf0t3K.js";
7
- import { A as writeToIpc, C as resolveReadWindow, D as snapshotStatus, E as runSubcommand, O as stopTipForCli, S as resolveOne, T as restartHintLines, _ as menuSelectKeys, a as cursorAbs, b as renderRawLog, c as extractNeedsInput, d as isAgentStuck, f as isExitRequest, g as matchKeyword, h as listRecords, i as controlCodeFromName, k as writeKeysPaced, l as extractTaskCounts, m as isSubcommand, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isPidAlive, r as cmdHelp, s as extractMenu, t as GRACEFUL_EXIT_COMMANDS, u as finalizedLines, v as readNotes, w as resolveResumeArgs, x as renderRawLogLines, y as readPtysize } from "./subcommands-DWeo7ZLc.js";
7
+ import { A as writeToIpc, C as resolveReadWindow, D as snapshotStatus, E as runSubcommand, O as stopTipForCli, S as resolveOne, T as restartHintLines, _ as menuSelectKeys, a as cursorAbs, b as renderRawLog, c as extractNeedsInput, d as isAgentStuck, f as isExitRequest, g as matchKeyword, h as listRecords, i as controlCodeFromName, k as writeKeysPaced, l as extractTaskCounts, m as isSubcommand, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isPidAlive, r as cmdHelp, s as extractMenu, t as GRACEFUL_EXIT_COMMANDS, u as finalizedLines, v as readNotes, w as resolveResumeArgs, x as renderRawLogLines, y as readPtysize } from "./subcommands-VimbMSTG.js";
8
8
 
9
9
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -597,15 +597,15 @@ async function runSubcommand(argv) {
597
597
  case "restart": return await cmdRestart(rest);
598
598
  case "note": return await cmdNote(rest);
599
599
  case "serve": {
600
- const { cmdServe } = await import("./serve-mLqsRwfR.js");
600
+ const { cmdServe } = await import("./serve-DAuLdt_d.js");
601
601
  return cmdServe(rest);
602
602
  }
603
603
  case "setup": {
604
- const { cmdSetup } = await import("./setup-BtqKZx3q.js");
604
+ const { cmdSetup } = await import("./setup-YLHUtCC0.js");
605
605
  return cmdSetup(rest);
606
606
  }
607
607
  case "schedule": {
608
- const { cmdSchedule } = await import("./schedule-C2RMA3qm.js");
608
+ const { cmdSchedule } = await import("./schedule-BPUyALF_.js");
609
609
  return cmdSchedule(rest);
610
610
  }
611
611
  case "remote": {
@@ -3025,4 +3025,4 @@ async function cmdResultSet(rest) {
3025
3025
 
3026
3026
  //#endregion
3027
3027
  export { writeToIpc as A, resolveReadWindow as C, snapshotStatus as D, runSubcommand as E, stopTipForCli as O, resolveOne as S, restartHintLines as T, menuSelectKeys as _, cursorAbs as a, renderRawLog as b, extractNeedsInput as c, isAgentStuck as d, isExitRequest as f, matchKeyword as g, listRecords as h, controlCodeFromName as i, writeKeysPaced as k, extractTaskCounts as l, isSubcommand as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isPidAlive as p, cmdHelp as r, extractMenu as s, GRACEFUL_EXIT_COMMANDS as t, finalizedLines as u, readNotes as v, resolveResumeArgs as w, renderRawLogLines as x, readPtysize as y };
3028
- //# sourceMappingURL=subcommands-DWeo7ZLc.js.map
3028
+ //# sourceMappingURL=subcommands-VimbMSTG.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-BvR6tV3u.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DVv-WHIp.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-BfoBWUjc.js";
@@ -289,8 +289,14 @@ var pty_default = pty;
289
289
 
290
290
  //#endregion
291
291
  //#region ts/removeControlCharacters.ts
292
+ const ESC = String.fromCharCode(27);
293
+ const C1 = String.fromCharCode(155);
294
+ const BACKSLASH = String.fromCharCode(92);
295
+ const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
296
+ const OSC_PATTERN = new RegExp(ESC + "][^\x07\x1B]*(?:\x07|" + ST_PATTERN_SOURCE + ")", "g");
297
+ const CSI_PATTERN = new RegExp("[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]", "g");
292
298
  function removeControlCharacters(str) {
293
- return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
299
+ return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
294
300
  }
295
301
 
296
302
  //#endregion
@@ -474,6 +480,10 @@ var IdleWaiter = class {
474
480
  this.lastActivityTime = Date.now();
475
481
  return this;
476
482
  }
483
+ /** Milliseconds since the last ping. */
484
+ idleTimeMs() {
485
+ return Date.now() - this.lastActivityTime;
486
+ }
477
487
  async wait(ms) {
478
488
  while (this.lastActivityTime >= Date.now() - ms) await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
479
489
  }
@@ -511,8 +521,12 @@ async function sendEnter(context, waitms = 1e3) {
511
521
  logger.debug(`sendEnter| idleWait took ${String(Date.now() - st)}ms`);
512
522
  context.nextStdout.unready();
513
523
  context.shell.write("\r");
524
+ context.idleWaiter.ping();
514
525
  for (const ms of [1e3, 3e3]) await Promise.race([context.nextStdout.wait(), new Promise((resolve) => setTimeout(() => {
515
- if (!context.nextStdout.isReady) context.shell.write("\r");
526
+ if (!context.nextStdout.isReady) {
527
+ context.shell.write("\r");
528
+ context.idleWaiter.ping();
529
+ }
516
530
  resolve();
517
531
  }, ms))]);
518
532
  }
@@ -543,11 +557,21 @@ async function sendMessage(context, message, { waitForReady = true } = {}) {
543
557
  const AUTO_RETRY_BASE_SECS = 8;
544
558
  const AUTO_RETRY_MAX_DELAY_SECS = 256;
545
559
  const AUTO_RETRY_GIVE_UP_MS = 8 * 3600 * 1e3;
560
+ const AUTO_RETRY_MIN_IDLE_MS = 5e3;
546
561
  /** Backoff (ms) before the Nth consecutive auto-retry — doubles, then caps. */
547
562
  function autoRetryBackoffMs(streak) {
548
563
  const shift = Math.min(streak, 20);
549
564
  return Math.min(AUTO_RETRY_BASE_SECS * 2 ** shift, AUTO_RETRY_MAX_DELAY_SECS) * 1e3;
550
565
  }
566
+ /**
567
+ * Whether a scheduled auto-retry may actually fire: the agent must be sitting
568
+ * idle at a ready prompt (not mid-work) AND the terminal must have been quiet
569
+ * for at least `minIdleMs` — see AUTO_RETRY_MIN_IDLE_MS. Mirrors Rust's
570
+ * should_fire_retry in rs/src/context.rs.
571
+ */
572
+ function shouldFireRetry(working, ready, idleMs, minIdleMs) {
573
+ return !working && ready && idleMs >= minIdleMs;
574
+ }
551
575
 
552
576
  //#endregion
553
577
  //#region ts/core/logging.ts
@@ -1503,17 +1527,13 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1503
1527
  retryNextAt = null;
1504
1528
  retryStartedAt = null;
1505
1529
  retryStreak = 0;
1506
- } else if (now >= retryNextAt) {
1507
- const working = conf.working?.some((rx) => rx.test(autoRetryScreen)) ?? false;
1508
- const readyNow = conf.ready?.some((rx) => rx.test(autoRetryScreen)) ?? false;
1509
- if (working || !readyNow) retryNextAt = now + 500;
1510
- else {
1511
- retryStreak += 1;
1512
- logger.warn(`[${cli}-yes] auto-retry: typing 'retry' (attempt ${retryStreak})`);
1513
- ctx.messageContext.shell.write("retry\r");
1514
- ctx.idleWaiter.ping();
1515
- retryNextAt = now + autoRetryBackoffMs(retryStreak);
1516
- }
1530
+ } else if (now >= retryNextAt) if (!shouldFireRetry(conf.working?.some((rx) => rx.test(autoRetryScreen)) ?? false, conf.ready?.some((rx) => rx.test(autoRetryScreen)) ?? false, ctx.idleWaiter.idleTimeMs(), AUTO_RETRY_MIN_IDLE_MS)) retryNextAt = now + 500;
1531
+ else {
1532
+ retryStreak += 1;
1533
+ logger.warn(`[${cli}-yes] auto-retry: typing 'retry' (attempt ${retryStreak})`);
1534
+ ctx.messageContext.shell.write("retry\r");
1535
+ ctx.idleWaiter.ping();
1536
+ retryNextAt = now + autoRetryBackoffMs(retryStreak);
1517
1537
  }
1518
1538
  }
1519
1539
  if (rendered === lastHeartbeatRendered) return;
@@ -1689,10 +1709,11 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1689
1709
  writable: new WritableStream({ write: async (data) => {
1690
1710
  await ctx.stdinReady.wait();
1691
1711
  shell.write(data);
1712
+ ctx.idleWaiter.ping();
1692
1713
  } }),
1693
1714
  readable: xtermProxy.readable
1694
- }).forEach(() => {
1695
- ctx.idleWaiter.ping();
1715
+ }).forEach((chunk) => {
1716
+ if (removeControlCharacters(chunk).trim()) ctx.idleWaiter.ping();
1696
1717
  pidStore.updateStatus(shell.pid, "active").catch(() => null);
1697
1718
  ctx.nextStdout.ready();
1698
1719
  }).forkTo(async function rawLogger(f) {
@@ -1803,4 +1824,4 @@ function sleep(ms) {
1803
1824
 
1804
1825
  //#endregion
1805
1826
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1806
- //# sourceMappingURL=ts-s3wYccKf.js.map
1827
+ //# sourceMappingURL=ts-DBwi5BWW.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "claude-yes";
10
- var version = "1.167.0";
10
+ var version = "1.168.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-BvR6tV3u.js.map
218
+ //# sourceMappingURL=versionChecker-DVv-WHIp.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.167.0",
3
+ "version": "1.168.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",
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { AUTO_RETRY_MAX_DELAY_SECS, autoRetryBackoffMs } from "./autoRetry.ts";
2
+ import { AUTO_RETRY_MAX_DELAY_SECS, autoRetryBackoffMs, shouldFireRetry } from "./autoRetry.ts";
3
3
 
4
4
  describe("autoRetryBackoffMs", () => {
5
5
  it("doubles 8,16,32,…,256 then caps", () => {
@@ -17,3 +17,18 @@ describe("autoRetryBackoffMs", () => {
17
17
  expect(Number.isFinite(autoRetryBackoffMs(1000))).toBe(true);
18
18
  });
19
19
  });
20
+
21
+ describe("shouldFireRetry", () => {
22
+ it("requires ready + not working + past the idle window", () => {
23
+ // Busy — never fire even if otherwise ready and quiet.
24
+ expect(shouldFireRetry(true, true, 10_000, 5_000)).toBe(false);
25
+ // Not at a recognized ready prompt — don't fire.
26
+ expect(shouldFireRetry(false, false, 10_000, 5_000)).toBe(false);
27
+ // Ready and idle, but the quiet window hasn't elapsed yet (user may still
28
+ // be mid-typing) — defer.
29
+ expect(shouldFireRetry(false, true, 4_999, 5_000)).toBe(false);
30
+ // Ready, idle, and past the quiet window — fire.
31
+ expect(shouldFireRetry(false, true, 5_000, 5_000)).toBe(true);
32
+ expect(shouldFireRetry(false, true, 10_000, 5_000)).toBe(true);
33
+ });
34
+ });
package/ts/autoRetry.ts CHANGED
@@ -8,9 +8,32 @@ export const AUTO_RETRY_BASE_SECS = 8; // first backoff; doubles each consecutiv
8
8
  export const AUTO_RETRY_MAX_DELAY_SECS = 256; // cap: 8,16,32,…,256 then hold
9
9
  export const AUTO_RETRY_GIVE_UP_MS = 8 * 3600 * 1000; // stop after 8h (claude's usage window is ~5h)
10
10
 
11
+ // Minimum quiet time (no CLI output, no forwarded stdin — see idleWaiter.ping()
12
+ // call sites) required before a scheduled auto-retry may actually fire, on top
13
+ // of the backoff delay above. The backoff schedule alone can elapse while the
14
+ // user is mid-typing into the prompt; typing "retry" + Enter over that would
15
+ // submit a mangled line. Deliberately short — this only debounces against
16
+ // active typing, not a real excuse to delay recovery.
17
+ export const AUTO_RETRY_MIN_IDLE_MS = 5_000;
18
+
11
19
  /** Backoff (ms) before the Nth consecutive auto-retry — doubles, then caps. */
12
20
  export function autoRetryBackoffMs(streak: number): number {
13
21
  const shift = Math.min(streak, 20); // guard against absurd streaks blowing up 2 ** n
14
22
  const secs = Math.min(AUTO_RETRY_BASE_SECS * 2 ** shift, AUTO_RETRY_MAX_DELAY_SECS);
15
23
  return secs * 1000;
16
24
  }
25
+
26
+ /**
27
+ * Whether a scheduled auto-retry may actually fire: the agent must be sitting
28
+ * idle at a ready prompt (not mid-work) AND the terminal must have been quiet
29
+ * for at least `minIdleMs` — see AUTO_RETRY_MIN_IDLE_MS. Mirrors Rust's
30
+ * should_fire_retry in rs/src/context.rs.
31
+ */
32
+ export function shouldFireRetry(
33
+ working: boolean,
34
+ ready: boolean,
35
+ idleMs: number,
36
+ minIdleMs: number,
37
+ ): boolean {
38
+ return !working && ready && idleMs >= minIdleMs;
39
+ }
@@ -59,6 +59,18 @@ describe("IdleWaiter", () => {
59
59
  expect(result).toBe(waiter);
60
60
  });
61
61
 
62
+ it("idleTimeMs reflects elapsed time since the last ping", async () => {
63
+ const waiter = new IdleWaiter();
64
+ waiter.ping();
65
+ expect(waiter.idleTimeMs()).toBeLessThan(20);
66
+
67
+ await new Promise((r) => setTimeout(r, 50));
68
+ expect(waiter.idleTimeMs()).toBeGreaterThanOrEqual(50);
69
+
70
+ waiter.ping();
71
+ expect(waiter.idleTimeMs()).toBeLessThan(20);
72
+ });
73
+
62
74
  it("should wait until idle period has passed", async () => {
63
75
  const waiter = new IdleWaiter();
64
76
  waiter.checkInterval = 10;
package/ts/idleWaiter.ts CHANGED
@@ -24,6 +24,11 @@ export class IdleWaiter {
24
24
  return this;
25
25
  }
26
26
 
27
+ /** Milliseconds since the last ping. */
28
+ idleTimeMs() {
29
+ return Date.now() - this.lastActivityTime;
30
+ }
31
+
27
32
  async wait(ms: number) {
28
33
  while (this.lastActivityTime >= Date.now() - ms)
29
34
  await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
package/ts/index.ts CHANGED
@@ -18,7 +18,12 @@ import { logger } from "./logger.ts";
18
18
  import { createFifoStream } from "./beta/fifo.ts";
19
19
  import { PidStore } from "./pidStore.ts";
20
20
  import { sendEnter, sendMessage } from "./core/messaging.ts";
21
- import { AUTO_RETRY_GIVE_UP_MS, autoRetryBackoffMs } from "./autoRetry.ts";
21
+ import {
22
+ AUTO_RETRY_GIVE_UP_MS,
23
+ AUTO_RETRY_MIN_IDLE_MS,
24
+ autoRetryBackoffMs,
25
+ shouldFireRetry,
26
+ } from "./autoRetry.ts";
22
27
  import {
23
28
  initializeLogPaths,
24
29
  setupDebugLogging,
@@ -805,8 +810,12 @@ export default async function agentYes({
805
810
  } else if (now >= retryNextAt) {
806
811
  const working = conf.working?.some((rx: RegExp) => rx.test(autoRetryScreen)) ?? false;
807
812
  const readyNow = conf.ready?.some((rx: RegExp) => rx.test(autoRetryScreen)) ?? false;
808
- if (working || !readyNow) {
809
- retryNextAt = now + 500; // busy / not at prompt — re-check shortly
813
+ // Also require a few quiet seconds on top of the backoff delay, so a
814
+ // scheduled retry doesn't collide with a line the user is actively
815
+ // typing into the prompt (see AUTO_RETRY_MIN_IDLE_MS).
816
+ const idleMs = ctx.idleWaiter.idleTimeMs();
817
+ if (!shouldFireRetry(working, readyNow, idleMs, AUTO_RETRY_MIN_IDLE_MS)) {
818
+ retryNextAt = now + 500; // busy / not at prompt / still active — re-check shortly
810
819
  } else {
811
820
  retryStreak += 1;
812
821
  logger.warn(`[${cli}-yes] auto-retry: typing 'retry' (attempt ${retryStreak})`);
@@ -1096,13 +1105,22 @@ export default async function agentYes({
1096
1105
  write: async (data) => {
1097
1106
  await ctx.stdinReady.wait();
1098
1107
  shell.write(data);
1108
+ // Forwarded user input counts as activity too (mirrors the Rust
1109
+ // runtime's ping on stdin forward) — the auto-retry idle gate below
1110
+ // must not fire while the user is actively typing.
1111
+ ctx.idleWaiter.ping();
1099
1112
  },
1100
1113
  }),
1101
1114
  readable: xtermProxy.readable,
1102
1115
  })
1103
1116
 
1104
- .forEach(() => {
1105
- ctx.idleWaiter.ping();
1117
+ .forEach((chunk) => {
1118
+ // Only ping activity if there's visible content (not just ANSI/cursor
1119
+ // control sequences) — mirrors the Rust runtime's handle_output gate.
1120
+ // Without this, periodic control-only chatter (e.g. cursor position
1121
+ // queries) would keep resetting the auto-retry idle clock and the
1122
+ // scheduled retry could defer indefinitely without ever firing.
1123
+ if (removeControlCharacters(chunk).trim()) ctx.idleWaiter.ping();
1106
1124
  pidStore.updateStatus(shell.pid, "active").catch(() => null);
1107
1125
  ctx.nextStdout.ready();
1108
1126
  })
@@ -70,4 +70,25 @@ describe("removeControlCharacters", () => {
70
70
  const expected = "TextClearLine";
71
71
  expect(removeControlCharacters(input)).toBe(expected);
72
72
  });
73
+
74
+ it("should remove OSC sequences (e.g. window title updates)", () => {
75
+ const input = "Before\u001b]0;window title\u0007After";
76
+ const expected = "BeforeAfter";
77
+ expect(removeControlCharacters(input)).toBe(expected);
78
+ });
79
+
80
+ it("does NOT strip trailing text when an OSC sequence is unterminated (no BEL)", () => {
81
+ const input = "Before\u001b]0;titleAfter";
82
+ expect(removeControlCharacters(input)).toBe(input);
83
+ });
84
+
85
+ it("should remove OSC sequences terminated by ST (ESC backslash), not just BEL", () => {
86
+ const input = "Before\u001b]8;;https://example.com\u001b\\After";
87
+ expect(removeControlCharacters(input)).toBe("BeforeAfter");
88
+ });
89
+
90
+ it("does not let an ST-terminated OSC run on and eat real text before a later BEL", () => {
91
+ const input = "Before\u001b]0;t\u001b\\visible\u0007After";
92
+ expect(removeControlCharacters(input)).toBe("Beforevisible\u0007After");
93
+ });
73
94
  });
@@ -1,8 +1,34 @@
1
+ const ESC = String.fromCharCode(0x1b);
2
+ const C1 = String.fromCharCode(0x9b);
3
+ const BEL = String.fromCharCode(0x07);
4
+ const BACKSLASH = String.fromCharCode(0x5c);
5
+ // String Terminator (ESC \) as REGEX SOURCE text: a literal backslash inside a
6
+ // regex pattern is itself an escape character, so matching one literal
7
+ // backslash requires two backslash characters in the pattern source.
8
+ const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
9
+
10
+ // OSC sequences (window/tab title updates, hyperlinks, etc.): ESC ] ...
11
+ // terminated by either BEL or ST (ESC \) — both are valid per-spec and used
12
+ // by real terminal apps. Not covered by the CSI pattern below — without this,
13
+ // e.g. a periodic title update would count as "visible" content to callers
14
+ // that gate activity on non-empty output.
15
+ // The terminator is required, not optional: an unterminated ESC]... (the
16
+ // terminator never arrives, e.g. a truncated chunk boundary) must NOT strip
17
+ // through to end-of-string — that would eat real trailing text as if it were
18
+ // part of the title sequence. The body excludes ESC too (not just BEL) so an
19
+ // ST-terminated sequence can't accidentally run on and swallow real text up
20
+ // to some unrelated, later BEL.
21
+ const OSC_PATTERN = new RegExp(
22
+ ESC + "][^" + BEL + ESC + "]*(?:" + BEL + "|" + ST_PATTERN_SOURCE + ")",
23
+ "g",
24
+ );
25
+
26
+ // Matches control characters in the C0 and C1 ranges, including Delete (U+007F)
27
+ const CSI_PATTERN = new RegExp(
28
+ "[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]",
29
+ "g",
30
+ );
31
+
1
32
  export function removeControlCharacters(str: string): string {
2
- // Matches control characters in the C0 and C1 ranges, including Delete (U+007F)
3
- return str.replace(
4
- // eslint-disable-next-line no-control-regex This is a control regex
5
- /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
6
- "",
7
- );
33
+ return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
8
34
  }
@@ -1,8 +0,0 @@
1
- import "./ts-s3wYccKf.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-BvR6tV3u.js";
4
- import "./pidStore-BfoBWUjc.js";
5
- import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFBATC85.js";
7
-
8
- export { SUPPORTED_CLIS };