claude-yes 1.300.2 → 1.300.3

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 (33) hide show
  1. package/dist/{SUPPORTED_CLIS-Dxw9H99T.js → SUPPORTED_CLIS-DMQwDMfP.js} +2 -2
  2. package/dist/{SUPPORTED_CLIS-BX5_erb1.js → SUPPORTED_CLIS-jZFHwXHE.js} +3 -3
  3. package/dist/{agentShare-C178Fz-8.js → agentShare-BASAaPka.js} +2 -2
  4. package/dist/{callback-DDFaB2fy.js → callback-BnkktH-1.js} +2 -2
  5. package/dist/{callback-DEhpIaK7.js → callback-ZCnvZXqD.js} +3 -3
  6. package/dist/cli.js +5 -5
  7. package/dist/{cmdPs-C87Caduf.js → cmdPs-BM2Srp1S.js} +2 -2
  8. package/dist/index.js +2 -2
  9. package/dist/{notifyDaemon-NxyVEhVq.js → notifyDaemon-BtbS2lBT.js} +2 -2
  10. package/dist/{parentPingLoop-zAmYgESx.js → parentPingLoop-rLQBc0G5.js} +2 -2
  11. package/dist/{rustBinary-FjEdM28b.js → rustBinary-DHWoCbGC.js} +2 -2
  12. package/dist/{rustBinary-CicmJTei.js → rustBinary-Lzt3pVh_.js} +2 -2
  13. package/dist/{schedule-DPiovnJr.js → schedule-D45Ja0h0.js} +4 -4
  14. package/dist/{serve-C_9bMH27.js → serve-BPl5u9Mw.js} +18 -18
  15. package/dist/{setup-DVMeGj5u.js → setup-IbpYJdQm.js} +2 -2
  16. package/dist/{shareCmd-cNNr7i3a.js → shareCmd-8EPCR-WP.js} +3 -3
  17. package/dist/{subcommands-CzSrhEYG.js → subcommands-BusIzjLq.js} +143 -35
  18. package/dist/subcommands-CQINL3Uv.js +13 -0
  19. package/dist/{terminal-MTNQuGD5.js → terminal-Del-sL1m.js} +2 -2
  20. package/dist/trayApp-2yKAAVa7.js +5 -0
  21. package/dist/{trayApp-DO3YALy1.js → trayApp-Tp8PsCsA.js} +2 -2
  22. package/dist/{ts-C8dvu_cD.js → ts-BFVAQ0Zs.js} +3 -3
  23. package/dist/{versionChecker-Bmtx6fPv.js → versionChecker-DUIQTHWC.js} +2 -2
  24. package/dist/{widget-BCrKF9Vw.js → widget-E8nWj1fn.js} +3 -3
  25. package/dist/{ws-BTXQauqi.js → ws-CMkJamuh.js} +2 -2
  26. package/dist/{ws-D1ggWZ_I.js → ws-D9EBcPei.js} +2 -2
  27. package/package.json +1 -1
  28. package/ts/composerGuard.spec.ts +34 -0
  29. package/ts/composerGuard.ts +38 -1
  30. package/ts/subcommands.spec.ts +95 -1
  31. package/ts/subcommands.ts +105 -16
  32. package/dist/subcommands-2tF2Psrc.js +0 -13
  33. package/dist/trayApp-Cx9ktCQ-.js +0 -5
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-C8dvu_cD.js";
1
+ import { t as CLIS_CONFIG } from "./ts-BFVAQ0Zs.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-Dxw9H99T.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DMQwDMfP.js.map
@@ -1,12 +1,12 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-Bmtx6fPv.js";
3
- import "./ts-C8dvu_cD.js";
2
+ import "./versionChecker-DUIQTHWC.js";
3
+ import "./ts-BFVAQ0Zs.js";
4
4
  import "./todoAutomation-CbVVS4re.js";
5
5
  import "./JsonlStore-BqF7AQ6E.js";
6
6
  import "./globalPidIndex-CRH9L9tn.js";
7
7
  import "./pidStore-DXWqzTSz.js";
8
8
  import "./messageLog-Brx3w6zT.js";
9
9
  import "./todoStore-DF6vxHgV.js";
10
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Dxw9H99T.js";
10
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DMQwDMfP.js";
11
11
 
12
12
  export { SUPPORTED_CLIS };
@@ -5,7 +5,7 @@ import "./pidStore-DXWqzTSz.js";
5
5
  import "./messageLog-Brx3w6zT.js";
6
6
  import "./e2e-z1xpwSAg.js";
7
7
  import "./configShared-DKTgiz7K.js";
8
- import { tt as resolveOne } from "./subcommands-CzSrhEYG.js";
8
+ import { nt as resolveOne } from "./subcommands-BusIzjLq.js";
9
9
  import "./notifyStore-2SWKGwZI.js";
10
10
  import "./webrtcLink-DFRyWNMs.js";
11
11
  import "./remotes-B4v0Nowo.js";
@@ -232,4 +232,4 @@ function transformEvent(rawEvent, agentId, forwarded) {
232
232
 
233
233
  //#endregion
234
234
  export { createScopedShare, listShares, revokeAllShares, revokeShare };
235
- //# sourceMappingURL=agentShare-C178Fz-8.js.map
235
+ //# sourceMappingURL=agentShare-BASAaPka.js.map
@@ -5,11 +5,11 @@ import "./pidStore-DXWqzTSz.js";
5
5
  import "./messageLog-Brx3w6zT.js";
6
6
  import "./e2e-z1xpwSAg.js";
7
7
  import "./configShared-DKTgiz7K.js";
8
- import "./subcommands-CzSrhEYG.js";
8
+ import "./subcommands-BusIzjLq.js";
9
9
  import "./notifyStore-2SWKGwZI.js";
10
10
  import "./webrtcLink-DFRyWNMs.js";
11
11
  import "./remotes-B4v0Nowo.js";
12
12
  import "./callbackCore-Bl2gGcxg.js";
13
- import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-DEhpIaK7.js";
13
+ import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-ZCnvZXqD.js";
14
14
 
15
15
  export { cmdCallback };
@@ -1,5 +1,5 @@
1
1
  import { t as agentYesHome } from "./agentYesHome-BNtn19J4.js";
2
- import { tt as resolveOne } from "./subcommands-CzSrhEYG.js";
2
+ import { nt as resolveOne } from "./subcommands-BusIzjLq.js";
3
3
  import { a as mintCapability, o as parseExpires } from "./callbackCore-Bl2gGcxg.js";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { chmodSync, readFileSync, renameSync, writeFileSync } from "node:fs";
@@ -72,7 +72,7 @@ async function resolveBase(flag) {
72
72
  if (flag) return flag;
73
73
  if (process.env.AGENT_YES_CALLBACK_BASE) return process.env.AGENT_YES_CALLBACK_BASE;
74
74
  try {
75
- const { resolveLocalServeUrl } = await import("./serve-C_9bMH27.js");
75
+ const { resolveLocalServeUrl } = await import("./serve-BPl5u9Mw.js");
76
76
  const url = await resolveLocalServeUrl();
77
77
  if (url) return url;
78
78
  } catch {}
@@ -187,4 +187,4 @@ async function cmdCallback(rest) {
187
187
 
188
188
  //#endregion
189
189
  export { loadOrCreateCallbackSecret as i, isCallbackRevoked as n, loadCallbackSecretReadOnly as r, cmdCallback as t };
190
- //# sourceMappingURL=callback-DEhpIaK7.js.map
190
+ //# sourceMappingURL=callback-ZCnvZXqD.js.map
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
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 { a as versionString, r as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-Bmtx6fPv.js";
5
- import { d as getRustBinary } from "./rustBinary-FjEdM28b.js";
4
+ import { a as versionString, r as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-DUIQTHWC.js";
5
+ import { d as getRustBinary } from "./rustBinary-DHWoCbGC.js";
6
6
  import { argv } from "process";
7
7
  import { spawn } from "child_process";
8
8
  import ms from "ms";
@@ -295,7 +295,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
295
295
  const rawArg = process.argv[2];
296
296
  const managerCommands = !invokedCliName(process.argv);
297
297
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
298
- const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken, isBareManagerInvocation } = await import("./subcommands-2tF2Psrc.js");
298
+ const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken, isBareManagerInvocation } = await import("./subcommands-CQINL3Uv.js");
299
299
  if (isHelpFlag && process.argv.length === 3) {
300
300
  await cmdHelp(managerCommands);
301
301
  process.exit(0);
@@ -309,7 +309,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
309
309
  process.exit(code ?? 0);
310
310
  }
311
311
  {
312
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BX5_erb1.js");
312
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-jZFHwXHE.js");
313
313
  if (isUnknownManagerToken(rawArg, managerCommands, SUPPORTED_CLIS)) {
314
314
  process.stderr.write(`ay: unknown subcommand or CLI '${rawArg}'.\n See 'ay help' for subcommands. To run an agent, name a CLI:\n 'ay <cli> …' (e.g. 'ay claude …') or 'ay --cli <cli> …'.\n`);
315
315
  process.exit(1);
@@ -360,7 +360,7 @@ if (config.useRust) {
360
360
  }
361
361
  }
362
362
  if (rustBinary) {
363
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BX5_erb1.js");
363
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-jZFHwXHE.js");
364
364
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
365
365
  if (config.verbose) {
366
366
  console.log(`[rust] Using binary: ${rustBinary}`);
@@ -5,7 +5,7 @@ import "./pidStore-DXWqzTSz.js";
5
5
  import "./messageLog-Brx3w6zT.js";
6
6
  import "./e2e-z1xpwSAg.js";
7
7
  import "./configShared-DKTgiz7K.js";
8
- import { B as listRecords, _ as deriveLiveState, vt as buildAgentForest, yt as flattenForest } from "./subcommands-CzSrhEYG.js";
8
+ import { V as listRecords, bt as flattenForest, v as deriveLiveState, yt as buildAgentForest } from "./subcommands-BusIzjLq.js";
9
9
  import "./notifyStore-2SWKGwZI.js";
10
10
  import "./webrtcLink-DFRyWNMs.js";
11
11
  import "./remotes-B4v0Nowo.js";
@@ -330,4 +330,4 @@ function readPpidSync(pid) {
330
330
 
331
331
  //#endregion
332
332
  export { cmdPs };
333
- //# sourceMappingURL=cmdPs-C87Caduf.js.map
333
+ //# sourceMappingURL=cmdPs-BM2Srp1S.js.map
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-Bmtx6fPv.js";
3
- import { a as hasTodoStore, i as answerAsk, l as AgentContext, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-C8dvu_cD.js";
2
+ import "./versionChecker-DUIQTHWC.js";
3
+ import { a as hasTodoStore, i as answerAsk, l as AgentContext, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-BFVAQ0Zs.js";
4
4
  import { a as renderTree, c as describeBlock, i as renderDigest, l as monitorHint, o as unblockedTasks, r as openBlockers, t as reconcileTodos, u as removeControlCharacters } from "./todoAutomation-CbVVS4re.js";
5
5
  import "./JsonlStore-BqF7AQ6E.js";
6
6
  import "./globalPidIndex-CRH9L9tn.js";
@@ -5,7 +5,7 @@ import "./pidStore-DXWqzTSz.js";
5
5
  import "./messageLog-Brx3w6zT.js";
6
6
  import "./e2e-z1xpwSAg.js";
7
7
  import "./configShared-DKTgiz7K.js";
8
- import { B as listRecords, M as isPidAlive, Q as renderLogTailLines, _ as deriveLiveState } from "./subcommands-CzSrhEYG.js";
8
+ import { $ as renderLogTailLines, N as isPidAlive, V as listRecords, v as deriveLiveState } from "./subcommands-BusIzjLq.js";
9
9
  import { f as readInbox, g as daemonLockOwnerPath, h as daemonLockDir, i as gcInboxes, l as listInboxParents, m as shouldStealLock, n as appendEvent, s as hostId, u as liveWatchers, x as notifyDir } from "./notifyStore-2SWKGwZI.js";
10
10
  import "./webrtcLink-DFRyWNMs.js";
11
11
  import "./remotes-B4v0Nowo.js";
@@ -879,4 +879,4 @@ async function ensureDaemon() {
879
879
 
880
880
  //#endregion
881
881
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
882
- //# sourceMappingURL=notifyDaemon-NxyVEhVq.js.map
882
+ //# sourceMappingURL=notifyDaemon-BtbS2lBT.js.map
@@ -338,7 +338,7 @@ async function isParentWatching(parentWrapperPid, parentAgentPid, selfAgentPid,
338
338
  logger.debug("[parent-ping] watcher lookup failed:", error);
339
339
  }
340
340
  try {
341
- const { lastReadAt, READ_WINDOW_MS } = await import("./subcommands-2tF2Psrc.js");
341
+ const { lastReadAt, READ_WINDOW_MS } = await import("./subcommands-CQINL3Uv.js");
342
342
  const last = await lastReadAt(`agent:${parentAgentPid}`, selfAgentPid);
343
343
  if (last !== null && now - last <= READ_WINDOW_MS) return true;
344
344
  } catch (error) {
@@ -466,4 +466,4 @@ function startParentPingLoop(opts) {
466
466
 
467
467
  //#endregion
468
468
  export { startParentPingLoop };
469
- //# sourceMappingURL=parentPingLoop-zAmYgESx.js.map
469
+ //# sourceMappingURL=parentPingLoop-rLQBc0G5.js.map
@@ -1,4 +1,4 @@
1
- import { i as getInstalledPackage, n as compareVersions } from "./versionChecker-Bmtx6fPv.js";
1
+ import { i as getInstalledPackage, n as compareVersions } from "./versionChecker-DUIQTHWC.js";
2
2
  import { execFileSync } from "child_process";
3
3
  import { existsSync, mkdirSync, readdirSync, rmSync, statSync, unlinkSync } from "fs";
4
4
  import { chmod, copyFile } from "fs/promises";
@@ -371,4 +371,4 @@ async function getRustBinary(options = {}) {
371
371
 
372
372
  //#endregion
373
373
  export { findSpawnHiddenLauncher as a, getBinDir as c, getRustBinary as d, hasValidMacSignature as f, findRustBinary as i, getBinaryName as l, downloadBinary as n, findTrayLauncher as o, findAyrsBinary as r, gcOldBinaryDirs as s, devBuildInfo as t, getDownloadUrl as u };
374
- //# sourceMappingURL=rustBinary-FjEdM28b.js.map
374
+ //# sourceMappingURL=rustBinary-DHWoCbGC.js.map
@@ -1,4 +1,4 @@
1
- import "./versionChecker-Bmtx6fPv.js";
2
- import { a as findSpawnHiddenLauncher, c as getBinDir, d as getRustBinary, f as hasValidMacSignature, i as findRustBinary, l as getBinaryName, n as downloadBinary, o as findTrayLauncher, r as findAyrsBinary, s as gcOldBinaryDirs, t as devBuildInfo, u as getDownloadUrl } from "./rustBinary-FjEdM28b.js";
1
+ import "./versionChecker-DUIQTHWC.js";
2
+ import { a as findSpawnHiddenLauncher, c as getBinDir, d as getRustBinary, f as hasValidMacSignature, i as findRustBinary, l as getBinaryName, n as downloadBinary, o as findTrayLauncher, r as findAyrsBinary, s as gcOldBinaryDirs, t as devBuildInfo, u as getDownloadUrl } from "./rustBinary-DHWoCbGC.js";
3
3
 
4
4
  export { findAyrsBinary, gcOldBinaryDirs };
@@ -1,13 +1,13 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-Bmtx6fPv.js";
3
- import "./ts-C8dvu_cD.js";
2
+ import "./versionChecker-DUIQTHWC.js";
3
+ import "./ts-BFVAQ0Zs.js";
4
4
  import "./todoAutomation-CbVVS4re.js";
5
5
  import "./JsonlStore-BqF7AQ6E.js";
6
6
  import "./globalPidIndex-CRH9L9tn.js";
7
7
  import "./pidStore-DXWqzTSz.js";
8
8
  import "./messageLog-Brx3w6zT.js";
9
9
  import "./todoStore-DF6vxHgV.js";
10
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Dxw9H99T.js";
10
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DMQwDMfP.js";
11
11
  import { f as resolveSpawnCwd } from "./workspaceConfig-DTvPtr5j.js";
12
12
  import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CipjIwfO.js";
13
13
  import { createHash } from "node:crypto";
@@ -154,4 +154,4 @@ async function cmdSchedule(rest) {
154
154
 
155
155
  //#endregion
156
156
  export { cmdSchedule };
157
- //# sourceMappingURL=schedule-DPiovnJr.js.map
157
+ //# sourceMappingURL=schedule-D45Ja0h0.js.map
@@ -1,7 +1,7 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import { i as getInstalledPackage } from "./versionChecker-Bmtx6fPv.js";
3
- import { a as findSpawnHiddenLauncher } from "./rustBinary-FjEdM28b.js";
4
- import { c as permissionBadge, i as answerAsk, o as listAsks, u as CLAUDE_SESSION_PIN_ENV } from "./ts-C8dvu_cD.js";
2
+ import { i as getInstalledPackage } from "./versionChecker-DUIQTHWC.js";
3
+ import { a as findSpawnHiddenLauncher } from "./rustBinary-DHWoCbGC.js";
4
+ import { c as permissionBadge, i as answerAsk, o as listAsks, u as CLAUDE_SESSION_PIN_ENV } from "./ts-BFVAQ0Zs.js";
5
5
  import { t as agentYesHome$1 } from "./agentYesHome-BNtn19J4.js";
6
6
  import { u as removeControlCharacters } from "./todoAutomation-CbVVS4re.js";
7
7
  import "./JsonlStore-BqF7AQ6E.js";
@@ -12,19 +12,19 @@ import { t as pgidForWrapper } from "./reaper-UimuuTfO.js";
12
12
  import "./todoStore-DF6vxHgV.js";
13
13
  import "./e2e-z1xpwSAg.js";
14
14
  import "./configShared-DKTgiz7K.js";
15
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Dxw9H99T.js";
16
- import { $ as renderRawLog, B as listRecords, J as readNotes, L as isUserTyping, N as isSlashCommand, Q as renderLogTailLines, S as extractBadges, T as extractTaskCounts, X as recentMessageEdges, Y as readPtysize, Z as recentReadEdges, _t as TYPING_BADGE, d as cliDefaults, et as renderRawLogLines, gt as shouldFramePaste, ht as framePaste, m as controlCodeFromName, mt as withIpcLock, pt as writeToIpc, q as readLogForRender, st as snapshotStatus, tt as resolveOne, v as deriveLiveStatus, w as extractNeedsInput } from "./subcommands-CzSrhEYG.js";
15
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DMQwDMfP.js";
16
+ import { $ as renderLogTailLines, C as extractBadges, E as extractTaskCounts, J as readLogForRender, P as isSlashCommand, Q as recentReadEdges, R as isUserTyping, T as extractNeedsInput, V as listRecords, X as readPtysize, Y as readNotes, Z as recentMessageEdges, _t as shouldFramePaste, ct as snapshotStatus, et as renderRawLog, f as cliDefaults, gt as framePaste, h as controlCodeFromName, ht as withIpcLock, mt as writeToIpc, nt as resolveOne, tt as renderRawLogLines, vt as TYPING_BADGE, y as deriveLiveStatus } from "./subcommands-BusIzjLq.js";
17
17
  import { c as isTransientLockMkdirError } from "./notifyStore-2SWKGwZI.js";
18
18
  import { i as toWebrtcUrl, n as isWebrtcSpec } from "./webrtcLink-DFRyWNMs.js";
19
19
  import "./remotes-B4v0Nowo.js";
20
20
  import { i as frameVisitorMessage, s as verifyCapability, t as MAX_CALLBACK_MSG_BYTES } from "./callbackCore-Bl2gGcxg.js";
21
- import { n as isCallbackRevoked, r as loadCallbackSecretReadOnly } from "./callback-DEhpIaK7.js";
21
+ import { n as isCallbackRevoked, r as loadCallbackSecretReadOnly } from "./callback-ZCnvZXqD.js";
22
22
  import { n as sampleTrees } from "./procStats-CyD8h5nx.js";
23
23
  import { a as getProvisionRoot, d as isProvisionAllowed, f as resolveSpawnCwd, i as getProvisionHook, l as hasProvisionHook, o as getSpawnHook, t as allowsSkipPermissions, u as hasSpawnHook } from "./workspaceConfig-DTvPtr5j.js";
24
24
  import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CipjIwfO.js";
25
- import { i as importProvisionModule } from "./ws-D1ggWZ_I.js";
25
+ import { i as importProvisionModule } from "./ws-D9EBcPei.js";
26
26
  import { r as spawnRejectionReason } from "./spawnGate-YpHopveO.js";
27
- import { i as launchTray } from "./trayApp-DO3YALy1.js";
27
+ import { i as launchTray } from "./trayApp-Tp8PsCsA.js";
28
28
  import yargs from "yargs";
29
29
  import { appendFile, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "fs/promises";
30
30
  import { arch, cpus, freemem, homedir, hostname, loadavg, platform, totalmem, uptime, userInfo } from "os";
@@ -1704,7 +1704,7 @@ Options:
1704
1704
  const { planRustServe } = await import("./serveRust-DDQiZmMf.js");
1705
1705
  const plan = planRustServe(rest);
1706
1706
  if (plan.kind === "rust") {
1707
- const { findAyrsBinary } = await import("./rustBinary-CicmJTei.js");
1707
+ const { findAyrsBinary } = await import("./rustBinary-Lzt3pVh_.js");
1708
1708
  const ayrs = findAyrsBinary();
1709
1709
  if (ayrs) return runAyrsServe(ayrs, plan.argv);
1710
1710
  process.stderr.write("ay serve: ayrs binary not found — running the TypeScript server (bun run build:rs, or reinstall, to get the Rust daemon)\n");
@@ -2679,7 +2679,7 @@ Options:
2679
2679
  }
2680
2680
  });
2681
2681
  if (req.method === "GET" && p === "/api/ws") {
2682
- const ws = await import("./ws-BTXQauqi.js");
2682
+ const ws = await import("./ws-CMkJamuh.js");
2683
2683
  try {
2684
2684
  await ws.loadProvision();
2685
2685
  } catch (e) {
@@ -2699,7 +2699,7 @@ Options:
2699
2699
  if (req.method === "GET" && p === "/api/ws/status") {
2700
2700
  const dirRaw = url.searchParams.get("path");
2701
2701
  if (!dirRaw) return new Response("missing ?path=<workspace dir>", { status: 400 });
2702
- const ws = await import("./ws-BTXQauqi.js");
2702
+ const ws = await import("./ws-CMkJamuh.js");
2703
2703
  let prov;
2704
2704
  try {
2705
2705
  prov = await ws.loadProvision();
@@ -2721,7 +2721,7 @@ Options:
2721
2721
  }
2722
2722
  }
2723
2723
  if (req.method === "GET" && p === "/api/ws/repos") {
2724
- const ws = await import("./ws-BTXQauqi.js");
2724
+ const ws = await import("./ws-CMkJamuh.js");
2725
2725
  try {
2726
2726
  await ws.loadProvision();
2727
2727
  } catch (e) {
@@ -2802,7 +2802,7 @@ Options:
2802
2802
  const m = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(repoParam);
2803
2803
  if (!m || m[1] === "." || m[1] === ".." || m[2] === "." || m[2] === "..") return new Response("missing/invalid ?repo=<owner>/<repo>", { status: 400 });
2804
2804
  const [, owner, repo] = m;
2805
- const ws = await import("./ws-BTXQauqi.js");
2805
+ const ws = await import("./ws-CMkJamuh.js");
2806
2806
  let prov;
2807
2807
  try {
2808
2808
  prov = await ws.loadProvision();
@@ -3720,7 +3720,7 @@ Options:
3720
3720
  const perm = body.perm ?? "r";
3721
3721
  if (perm !== "r" && perm !== "rw") return new Response(`invalid perm ${perm} (want r or rw)`, { status: 400 });
3722
3722
  try {
3723
- const { createScopedShare } = await import("./agentShare-C178Fz-8.js");
3723
+ const { createScopedShare } = await import("./agentShare-BASAaPka.js");
3724
3724
  const share = await createScopedShare({
3725
3725
  agent: body.agent,
3726
3726
  perm,
@@ -3735,12 +3735,12 @@ Options:
3735
3735
  }
3736
3736
  }
3737
3737
  if (req.method === "GET" && p === "/api/shares") {
3738
- const { listShares } = await import("./agentShare-C178Fz-8.js");
3738
+ const { listShares } = await import("./agentShare-BASAaPka.js");
3739
3739
  return Response.json(listShares());
3740
3740
  }
3741
3741
  const revokeM = /^\/api\/share\/([^/]+)$/.exec(p);
3742
3742
  if (req.method === "DELETE" && revokeM) {
3743
- const { revokeShare } = await import("./agentShare-C178Fz-8.js");
3743
+ const { revokeShare } = await import("./agentShare-BASAaPka.js");
3744
3744
  const ok = revokeShare(decodeURIComponent(revokeM[1]));
3745
3745
  return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
3746
3746
  }
@@ -4052,7 +4052,7 @@ Options:
4052
4052
  const shutdown = (resolve) => {
4053
4053
  if (heartbeat) clearInterval(heartbeat);
4054
4054
  closeShare?.();
4055
- import("./agentShare-C178Fz-8.js").then((m) => m.revokeAllShares()).catch(() => {});
4055
+ import("./agentShare-BASAaPka.js").then((m) => m.revokeAllShares()).catch(() => {});
4056
4056
  server?.stop();
4057
4057
  Promise.resolve(releaseHostLock?.()).catch(() => {}).then(resolve);
4058
4058
  };
@@ -4065,4 +4065,4 @@ Options:
4065
4065
 
4066
4066
  //#endregion
4067
4067
  export { cmdServe, inspectServeDaemon, loadTokenReadOnly, mintScopedTermToken, resolveDaemonHttpBase, resolveLocalServeUrl };
4068
- //# sourceMappingURL=serve-C_9bMH27.js.map
4068
+ //# sourceMappingURL=serve-BPl5u9Mw.js.map
@@ -97,7 +97,7 @@ async function cmdSetup(rest) {
97
97
  } catch {}
98
98
  if (noShare) return 0;
99
99
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
100
- const { cmdServe } = await import("./serve-C_9bMH27.js");
100
+ const { cmdServe } = await import("./serve-BPl5u9Mw.js");
101
101
  return cmdServe([
102
102
  "install",
103
103
  "--share",
@@ -107,4 +107,4 @@ async function cmdSetup(rest) {
107
107
 
108
108
  //#endregion
109
109
  export { cmdSetup };
110
- //# sourceMappingURL=setup-DVMeGj5u.js.map
110
+ //# sourceMappingURL=setup-IbpYJdQm.js.map
@@ -243,7 +243,7 @@ shown first and needs a y at a [y/N] prompt.
243
243
  Options:
244
244
  --port N HTTP port (default: ${DEFAULT_SHARE_PORT}, or the daemon's)\n --path /ay tailscale mount path (default: ${DEFAULT_TS_PATH})\n -y, --yes approve the shown commands without prompting\n --json print {mode, url} as JSON\n\nThe URL carries this host's serve token: whoever holds it can read and steer\nevery agent here. Rotate: rm ~/.agent-yes/.serve-token && ay serve install\n`;
245
245
  async function cmdShareStatus(mount) {
246
- const { inspectServeDaemon } = await import("./serve-C_9bMH27.js");
246
+ const { inspectServeDaemon } = await import("./serve-BPl5u9Mw.js");
247
247
  const [d, ts] = await Promise.all([inspectServeDaemon(), detectTailscale()]);
248
248
  const line = (k, v) => process.stdout.write(`${k.padEnd(11)}${v}\n`);
249
249
  if (d.args === null) {
@@ -287,7 +287,7 @@ async function cmdShare(rest) {
287
287
  process.stderr.write(`ay share: unknown mode '${mode}' — one of ${SHARE_MODES.join(", ")}\n`);
288
288
  return 1;
289
289
  }
290
- const { inspectServeDaemon, cmdServe } = await import("./serve-C_9bMH27.js");
290
+ const { inspectServeDaemon, cmdServe } = await import("./serve-BPl5u9Mw.js");
291
291
  const [daemon, ts] = await Promise.all([inspectServeDaemon(), detectTailscale()]);
292
292
  const lanIp = pickLanIp(networkInterfaces());
293
293
  if (!mode) {
@@ -412,4 +412,4 @@ async function cmdShare(rest) {
412
412
 
413
413
  //#endregion
414
414
  export { cmdShare };
415
- //# sourceMappingURL=shareCmd-cNNr7i3a.js.map
415
+ //# sourceMappingURL=shareCmd-8EPCR-WP.js.map
@@ -1083,6 +1083,31 @@ function composerPromptRow(texts) {
1083
1083
  return texts.findLastIndex((t) => PROMPT_RE.test(t));
1084
1084
  }
1085
1085
  /**
1086
+ * Is Claude Code's composer box the bottom of the screen: a rule directly above a
1087
+ * prompt row, at most a few draft rows (none of them another prompt) up to the
1088
+ * rule or footer that closes the box, and below that nothing but a few footer
1089
+ * rows? A box a partial redraw left open does not count: what is under its
1090
+ * prompt cannot be told from history, so the caller looks further back. When it is not, composerPromptRow either fell back to the last
1091
+ * prompt-looking row (on a Claude screen, possibly an old turn in the transcript)
1092
+ * or found a box that history has since been drawn under, so it is a stale one.
1093
+ * A real draft longer than MAX_DRAFT_ROWS fails too and reads "hidden", which
1094
+ * parks the send just as a draft does.
1095
+ */
1096
+ function composerBoxed(texts) {
1097
+ const prompt = composerPromptRow(texts);
1098
+ if (prompt < 1 || !SEPARATOR_RE.test(texts[prompt - 1])) return false;
1099
+ const nonBlank = (rows) => rows.filter((t) => t.trim());
1100
+ const close = texts.findIndex((t, i) => i > prompt && isComposerChrome(t));
1101
+ if (close < 0) return false;
1102
+ const inside = nonBlank(texts.slice(prompt + 1, close));
1103
+ const below = nonBlank(texts.slice(close + 1));
1104
+ return inside.length <= MAX_DRAFT_ROWS && below.length <= MAX_ROWS_BELOW_BOX && ![...inside, ...below].some((t) => PROMPT_RE.test(t));
1105
+ }
1106
+ /** Draft rows under the prompt row, before the box is read as stale (see above). */
1107
+ const MAX_DRAFT_ROWS = 8;
1108
+ /** Footer rows Claude Code draws under its composer box (mode, hints, agents, notices). */
1109
+ const MAX_ROWS_BELOW_BOX = 4;
1110
+ /**
1086
1111
  * Is this row composer chrome (a rule or a Claude/Codex footer) — the end of the
1087
1112
  * input? Pass the row's `cells` when known: the loose footer match then also
1088
1113
  * requires every visible cell to be coloured, so a typed line is never cut off.
@@ -1986,7 +2011,7 @@ async function runSubcommand(argv) {
1986
2011
  switch (sub) {
1987
2012
  case "ls":
1988
2013
  case "list": return await cmdLs(rest);
1989
- case "ps": return await (await import("./cmdPs-C87Caduf.js")).cmdPs(rest);
2014
+ case "ps": return await (await import("./cmdPs-BM2Srp1S.js")).cmdPs(rest);
1990
2015
  case "status": return await cmdStatus(rest);
1991
2016
  case "whoami": return await cmdWhoami(rest);
1992
2017
  case "result": return await cmdResult(rest);
@@ -2034,35 +2059,35 @@ async function runSubcommand(argv) {
2034
2059
  return cmdCh(rest);
2035
2060
  }
2036
2061
  case "term": {
2037
- const { cmdTerm } = await import("./terminal-MTNQuGD5.js");
2062
+ const { cmdTerm } = await import("./terminal-Del-sL1m.js");
2038
2063
  return cmdTerm(rest);
2039
2064
  }
2040
2065
  case "widget": {
2041
- const { cmdWidget } = await import("./widget-BCrKF9Vw.js");
2066
+ const { cmdWidget } = await import("./widget-E8nWj1fn.js");
2042
2067
  return cmdWidget(rest);
2043
2068
  }
2044
2069
  case "mint": {
2045
- const { cmdMint } = await import("./widget-BCrKF9Vw.js");
2070
+ const { cmdMint } = await import("./widget-E8nWj1fn.js");
2046
2071
  return cmdMint(rest);
2047
2072
  }
2048
2073
  case "serve": {
2049
- const { cmdServe } = await import("./serve-C_9bMH27.js");
2074
+ const { cmdServe } = await import("./serve-BPl5u9Mw.js");
2050
2075
  return cmdServe(rest);
2051
2076
  }
2052
2077
  case "tray": {
2053
- const { cmdTray } = await import("./trayApp-Cx9ktCQ-.js");
2078
+ const { cmdTray } = await import("./trayApp-2yKAAVa7.js");
2054
2079
  return cmdTray(rest);
2055
2080
  }
2056
2081
  case "setup": {
2057
- const { cmdSetup } = await import("./setup-DVMeGj5u.js");
2082
+ const { cmdSetup } = await import("./setup-IbpYJdQm.js");
2058
2083
  return cmdSetup(rest);
2059
2084
  }
2060
2085
  case "ws": {
2061
- const { cmdWs } = await import("./ws-BTXQauqi.js");
2086
+ const { cmdWs } = await import("./ws-CMkJamuh.js");
2062
2087
  return cmdWs(rest);
2063
2088
  }
2064
2089
  case "schedule": {
2065
- const { cmdSchedule } = await import("./schedule-DPiovnJr.js");
2090
+ const { cmdSchedule } = await import("./schedule-D45Ja0h0.js");
2066
2091
  return cmdSchedule(rest);
2067
2092
  }
2068
2093
  case "remote": {
@@ -2070,7 +2095,7 @@ async function runSubcommand(argv) {
2070
2095
  return cmdRemote(rest);
2071
2096
  }
2072
2097
  case "share": {
2073
- const { cmdShare } = await import("./shareCmd-cNNr7i3a.js");
2098
+ const { cmdShare } = await import("./shareCmd-8EPCR-WP.js");
2074
2099
  return cmdShare(rest);
2075
2100
  }
2076
2101
  case "connect": {
@@ -2082,7 +2107,7 @@ async function runSubcommand(argv) {
2082
2107
  return cmdExpose(rest);
2083
2108
  }
2084
2109
  case "callback": {
2085
- const { cmdCallback } = await import("./callback-DDFaB2fy.js");
2110
+ const { cmdCallback } = await import("./callback-BnkktH-1.js");
2086
2111
  return cmdCallback(rest);
2087
2112
  }
2088
2113
  case "reap":
@@ -2093,7 +2118,7 @@ async function runSubcommand(argv) {
2093
2118
  return await cmdDsh(rest);
2094
2119
  }
2095
2120
  case "gc": {
2096
- const { gcOldBinaryDirs } = await import("./rustBinary-CicmJTei.js");
2121
+ const { gcOldBinaryDirs } = await import("./rustBinary-Lzt3pVh_.js");
2097
2122
  const { gcLogs } = await import("./globalPidIndex-DC93JhHQ.js");
2098
2123
  const bins = gcOldBinaryDirs();
2099
2124
  const logs = await gcLogs();
@@ -4229,37 +4254,119 @@ async function renderLogTailLines(logPath, n = 40, geom) {
4229
4254
  }
4230
4255
  }
4231
4256
  /**
4257
+ * Tail windows the composer check renders, smallest first. Rendering a tail
4258
+ * starts mid-stream, and Claude Code's renderer redraws only the rows that
4259
+ * changed: on a lane that has sat idle for hours the last 32 KB can hold
4260
+ * redraws of transcript rows (an old `❯` turn among them) but not the unchanged
4261
+ * rule and empty prompt row of the composer box, so the old turn was read as
4262
+ * the prompt and the transcript under it as a draft. A Claude screen without
4263
+ * its box is therefore re-rendered from further back, last of all from as far
4264
+ * back as `ay read` renders. When even that shows no box, a draft read under
4265
+ * some other prompt row is reported as "hidden" rather than as a draft: the
4266
+ * send still parks, and the drainer waits for the box.
4267
+ *
4268
+ * Logs run to 100+ MB and the drainer re-checks every few seconds, so the
4269
+ * rendered terminal is kept per log and later checks feed it only the bytes
4270
+ * appended since: each window is rendered at most once per process, and a
4271
+ * screen that stays without a box costs only its new output on every poll.
4272
+ */
4273
+ const COMPOSER_WINDOWS = [
4274
+ 32 * 1024,
4275
+ 1024 * 1024,
4276
+ 8 * 1024 * 1024,
4277
+ MAX_RENDER_BYTES
4278
+ ];
4279
+ const composerTerms = /* @__PURE__ */ new Map();
4280
+ const COMPOSER_TERMS_MAX = 64;
4281
+ function dropComposerTerm(logPath) {
4282
+ composerTerms.get(logPath)?.term.dispose();
4283
+ composerTerms.delete(logPath);
4284
+ }
4285
+ /**
4232
4286
  * What the target's composer holds right now, read from the same 32 KB tail
4233
- * `renderLogTailLines` renders, but keeping per-cell attributes (dim/inverse) so
4234
- * ghost text can be told from a real draft. See ts/composerGuard.ts.
4287
+ * `renderLogTailLines` renders (further back when a Claude screen shows no
4288
+ * composer box), keeping per-cell attributes (dim/inverse) so ghost text can be
4289
+ * told from a real draft. See ts/composerGuard.ts.
4235
4290
  */
4236
- async function readComposerState(record) {
4237
- if (!record.log_file) return {
4291
+ async function readComposerState(record, windows = COMPOSER_WINDOWS) {
4292
+ const logPath = record.log_file;
4293
+ if (!logPath) return {
4238
4294
  kind: "unknown",
4239
4295
  reason: "no log file recorded"
4240
4296
  };
4241
- const buf = await readLogTailBytes(record.log_file);
4242
- if (!buf) return {
4243
- kind: "unknown",
4244
- reason: "log unreadable or empty"
4245
- };
4246
4297
  const geom = await readAgentPtysize(record) ?? void 0;
4298
+ const key = `${geom?.cols}x${geom?.rows}:${record.cli}:${windows.join()}`;
4299
+ const claude = record.cli === "claude";
4247
4300
  try {
4248
- const term = new (await (loadXtermTerminal()))({
4249
- cols: geom?.cols && geom.cols > 0 ? geom.cols : 200,
4250
- rows: geom?.rows && geom.rows > 0 ? geom.rows : 50,
4251
- scrollback: 5e3,
4252
- allowProposedApi: true
4253
- });
4254
- await new Promise((resolve) => term.write(buf, resolve));
4255
- return classifyComposer(rowsFromXterm(term, 40));
4301
+ const { size } = await stat(logPath);
4302
+ if (size === 0) return {
4303
+ kind: "unknown",
4304
+ reason: "log unreadable or empty"
4305
+ };
4306
+ let ct = composerTerms.get(logPath);
4307
+ if (ct && (ct.key !== key || size < ct.offset)) {
4308
+ dropComposerTerm(logPath);
4309
+ ct = void 0;
4310
+ }
4311
+ let rows;
4312
+ let next = 0;
4313
+ if (ct) {
4314
+ if (size > ct.offset) {
4315
+ const delta = await readLogRange(logPath, ct.offset, size);
4316
+ await new Promise((resolve) => ct.term.write(delta, resolve));
4317
+ ct.offset += delta.length;
4318
+ }
4319
+ rows = rowsFromXterm(ct.term, 40);
4320
+ next = ct.whole ? windows.length : ct.window + 1;
4321
+ if (!claude || composerBoxed(rows.map((r) => r.text))) next = windows.length;
4322
+ }
4323
+ for (let w = next; w < windows.length; w++) {
4324
+ const from = Math.max(0, size - windows[w]);
4325
+ const buf = await readLogRange(logPath, from, size);
4326
+ const term = new (await (loadXtermTerminal()))({
4327
+ cols: geom?.cols && geom.cols > 0 ? geom.cols : 200,
4328
+ rows: geom?.rows && geom.rows > 0 ? geom.rows : 50,
4329
+ scrollback: 1e3,
4330
+ allowProposedApi: true
4331
+ });
4332
+ await new Promise((resolve) => term.write(buf, resolve));
4333
+ dropComposerTerm(logPath);
4334
+ composerTerms.set(logPath, {
4335
+ term,
4336
+ key,
4337
+ window: w,
4338
+ offset: from + buf.length,
4339
+ whole: from === 0
4340
+ });
4341
+ if (composerTerms.size > COMPOSER_TERMS_MAX) dropComposerTerm(composerTerms.keys().next().value);
4342
+ rows = rowsFromXterm(term, 40);
4343
+ if (!claude || from === 0 || composerBoxed(rows.map((r) => r.text))) break;
4344
+ }
4345
+ const state = classifyComposer(rows ?? []);
4346
+ if (claude && state.kind === "draft" && !composerBoxed((rows ?? []).map((r) => r.text))) return {
4347
+ kind: "hidden",
4348
+ reason: "its input box is not on screen"
4349
+ };
4350
+ return state;
4256
4351
  } catch (e) {
4352
+ dropComposerTerm(logPath);
4257
4353
  return {
4258
4354
  kind: "unknown",
4259
- reason: `render failed: ${e?.message ?? e}`
4355
+ reason: `log unreadable or render failed: ${e?.message ?? e}`
4260
4356
  };
4261
4357
  }
4262
4358
  }
4359
+ /** Bytes `[from, to)` of a log file. */
4360
+ async function readLogRange(logPath, from, to) {
4361
+ const fh = await open(logPath, "r");
4362
+ try {
4363
+ const buf = Buffer.alloc(Math.max(0, to - from));
4364
+ const { bytesRead } = await fh.read(buf, 0, buf.length, from);
4365
+ return new Uint8Array(buf.buffer, buf.byteOffset, bytesRead);
4366
+ } finally {
4367
+ await fh.close();
4368
+ }
4369
+ }
4263
4370
  async function readLogTailBytes(logPath) {
4264
4371
  const TAIL_BYTES = 32 * 1024;
4265
4372
  let buf;
@@ -4956,6 +5063,7 @@ async function cmdSend(rest) {
4956
5063
  if (!parkReason) {
4957
5064
  const composer = await readComposerState(record);
4958
5065
  if (composer.kind === "draft") parkReason = `its input box holds a draft (${composer.chars} chars)`;
5066
+ else if (composer.kind === "hidden") parkReason = composer.reason;
4959
5067
  else if (composer.kind === "unknown") process.stderr.write(`warning: ay send could not see pid ${record.pid}'s input box (${composer.reason}) — sending without the draft check.\n`);
4960
5068
  }
4961
5069
  if (dedupeKey) await noteRecentSend(record.pid, dedupeKey, body, parkReason ? "queued" : "sent");
@@ -5590,7 +5698,7 @@ async function cmdAttach(rest) {
5590
5698
  const winsizeDir = path.join(ayHome, "winsize");
5591
5699
  await mkdir(winsizeDir, { recursive: true });
5592
5700
  const winsizePath = path.join(winsizeDir, String(record.pid));
5593
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-C_9bMH27.js");
5701
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-BPl5u9Mw.js");
5594
5702
  const daemonBase = await resolveDaemonHttpBase().catch(() => null);
5595
5703
  const daemonToken = daemonBase ? await loadTokenReadOnly().catch(() => null) ?? "" : "";
5596
5704
  const capViewer = `attach:${process.pid}`;
@@ -6319,7 +6427,7 @@ async function cmdNotify(rest) {
6319
6427
  }
6320
6428
  const ensure = async () => {
6321
6429
  if (!argv["ensure-daemon"]) return;
6322
- const { ensureDaemon } = await import("./notifyDaemon-NxyVEhVq.js");
6430
+ const { ensureDaemon } = await import("./notifyDaemon-BtbS2lBT.js");
6323
6431
  await ensureDaemon().catch(() => null);
6324
6432
  };
6325
6433
  await heartbeatWatcher(parent, selfStartedAt);
@@ -6393,7 +6501,7 @@ async function cmdNotifyCursor(args) {
6393
6501
  }
6394
6502
  async function cmdNotifyd(rest) {
6395
6503
  const sub = rest[0] ?? "status";
6396
- const daemon = await import("./notifyDaemon-NxyVEhVq.js");
6504
+ const daemon = await import("./notifyDaemon-BtbS2lBT.js");
6397
6505
  switch (sub) {
6398
6506
  case "run": return daemon.runDaemon();
6399
6507
  case "once": return daemon.runDaemon({ once: true });
@@ -6419,5 +6527,5 @@ async function cmdNotifyd(rest) {
6419
6527
  }
6420
6528
 
6421
6529
  //#endregion
6422
- export { renderRawLog as $, isBareManagerInvocation as A, listRecords as B, extractMenu as C, inspectSubmission as D, finalizedLines as E, isUnknownManagerToken as F, readAgentPtysize as G, menuSelectKeys as H, isUnreachableWriteErrno as I, readNotes as J, readComposerState as K, isUserTyping as L, isPidAlive as M, isSlashCommand as N, isAgentIdShaped as O, isSubcommand as P, renderLogTailLines as Q, lastReadAt as R, extractBadges as S, extractTaskCounts as T, ownedCollapsedPaste as U, matchKeyword as V, probeStdinReachable as W, recentMessageEdges as X, readPtysize as Y, recentReadEdges as Z, deriveLiveState as _, TYPING_BADGE as _t, SEND_BODY_MAX_CHARS as a, runSubcommand as at, envelopeAttribution as b, TYPING_WINDOW_MS as c, stdinActivityPath as ct, cliDefaults as d, waitForLogQuiet as dt, renderRawLogLines as et, cmdHelp as f, writeKeysPaced as ft, cwdLabel as g, shouldFramePaste as gt, cursorAbs as h, framePaste as ht, READ_WINDOW_MS as i, restartHintLines as it, isExitRequest as j, isAgentStuck as k, backoffWhileTyping as l, stopTipForCli as lt, controlCodeFromName as m, withIpcLock as mt, MAX_RENDER_BYTES as n, resolveReadWindow as nt, SEND_EXIT_QUEUED as o, sendPayloadCapError as ot, confirmStdinUnreachable as p, writeToIpc as pt, readLogForRender as q, READ_PAGE_DEFAULT as r, resolveResumeArgs as rt, SEND_EXIT_UNREACHABLE as s, snapshotStatus as st, GRACEFUL_EXIT_COMMANDS as t, resolveOne as tt, buildEnvelope as u, submitAndConfirm as ut, deriveLiveStatus as v, buildAgentForest as vt, extractNeedsInput as w, envelopeCostFor as x, drainerLauncher as y, flattenForest as yt, lastStdinAt as z };
6423
- //# sourceMappingURL=subcommands-CzSrhEYG.js.map
6530
+ export { renderLogTailLines as $, isAgentStuck as A, lastStdinAt as B, extractBadges as C, finalizedLines as D, extractTaskCounts as E, isSubcommand as F, probeStdinReachable as G, matchKeyword as H, isUnknownManagerToken as I, readLogForRender as J, readAgentPtysize as K, isUnreachableWriteErrno as L, isExitRequest as M, isPidAlive as N, inspectSubmission as O, isSlashCommand as P, recentReadEdges as Q, isUserTyping as R, envelopeCostFor as S, extractNeedsInput as T, menuSelectKeys as U, listRecords as V, ownedCollapsedPaste as W, readPtysize as X, readNotes as Y, recentMessageEdges as Z, cwdLabel as _, shouldFramePaste as _t, READ_WINDOW_MS as a, restartHintLines as at, drainerLauncher as b, flattenForest as bt, SEND_EXIT_UNREACHABLE as c, snapshotStatus as ct, buildEnvelope as d, submitAndConfirm as dt, renderRawLog as et, cliDefaults as f, waitForLogQuiet as ft, cursorAbs as g, framePaste as gt, controlCodeFromName as h, withIpcLock as ht, READ_PAGE_DEFAULT as i, resolveResumeArgs as it, isBareManagerInvocation as j, isAgentIdShaped as k, TYPING_WINDOW_MS as l, stdinActivityPath as lt, confirmStdinUnreachable as m, writeToIpc as mt, GRACEFUL_EXIT_COMMANDS as n, resolveOne as nt, SEND_BODY_MAX_CHARS as o, runSubcommand as ot, cmdHelp as p, writeKeysPaced as pt, readComposerState as q, MAX_RENDER_BYTES as r, resolveReadWindow as rt, SEND_EXIT_QUEUED as s, sendPayloadCapError as st, COMPOSER_WINDOWS as t, renderRawLogLines as tt, backoffWhileTyping as u, stopTipForCli as ut, deriveLiveState as v, TYPING_BADGE as vt, extractMenu as w, envelopeAttribution as x, deriveLiveStatus as y, buildAgentForest as yt, lastReadAt as z };
6531
+ //# sourceMappingURL=subcommands-BusIzjLq.js.map
@@ -0,0 +1,13 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import "./JsonlStore-BqF7AQ6E.js";
3
+ import "./globalPidIndex-CRH9L9tn.js";
4
+ import "./pidStore-DXWqzTSz.js";
5
+ import "./messageLog-Brx3w6zT.js";
6
+ import "./e2e-z1xpwSAg.js";
7
+ import "./configShared-DKTgiz7K.js";
8
+ import { $ as renderLogTailLines, A as isAgentStuck, B as lastStdinAt, C as extractBadges, D as finalizedLines, E as extractTaskCounts, F as isSubcommand, G as probeStdinReachable, H as matchKeyword, I as isUnknownManagerToken, J as readLogForRender, K as readAgentPtysize, L as isUnreachableWriteErrno, M as isExitRequest, N as isPidAlive, O as inspectSubmission, P as isSlashCommand, Q as recentReadEdges, R as isUserTyping, S as envelopeCostFor, T as extractNeedsInput, U as menuSelectKeys, V as listRecords, W as ownedCollapsedPaste, X as readPtysize, Y as readNotes, Z as recentMessageEdges, _ as cwdLabel, a as READ_WINDOW_MS, at as restartHintLines, b as drainerLauncher, c as SEND_EXIT_UNREACHABLE, ct as snapshotStatus, d as buildEnvelope, dt as submitAndConfirm, et as renderRawLog, f as cliDefaults, ft as waitForLogQuiet, g as cursorAbs, h as controlCodeFromName, i as READ_PAGE_DEFAULT, it as resolveResumeArgs, j as isBareManagerInvocation, k as isAgentIdShaped, l as TYPING_WINDOW_MS, lt as stdinActivityPath, m as confirmStdinUnreachable, mt as writeToIpc, n as GRACEFUL_EXIT_COMMANDS, nt as resolveOne, o as SEND_BODY_MAX_CHARS, ot as runSubcommand, p as cmdHelp, pt as writeKeysPaced, q as readComposerState, r as MAX_RENDER_BYTES, rt as resolveReadWindow, s as SEND_EXIT_QUEUED, st as sendPayloadCapError, t as COMPOSER_WINDOWS, tt as renderRawLogLines, u as backoffWhileTyping, ut as stopTipForCli, v as deriveLiveState, w as extractMenu, x as envelopeAttribution, y as deriveLiveStatus, z as lastReadAt } from "./subcommands-BusIzjLq.js";
9
+ import "./notifyStore-2SWKGwZI.js";
10
+ import "./webrtcLink-DFRyWNMs.js";
11
+ import "./remotes-B4v0Nowo.js";
12
+
13
+ export { READ_WINDOW_MS, cmdHelp, isBareManagerInvocation, isSubcommand, isUnknownManagerToken, lastReadAt, runSubcommand };
@@ -98,7 +98,7 @@ async function cmdTermMint(args) {
98
98
  if (flags.ro && flags.interactive) throw new Error("--ro and --interactive are mutually exclusive");
99
99
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
100
100
  const canSend = flags.interactive === true;
101
- const { mintScopedTermToken } = await import("./serve-C_9bMH27.js");
101
+ const { mintScopedTermToken } = await import("./serve-BPl5u9Mw.js");
102
102
  const r = await mintScopedTermToken(pid, {
103
103
  ttlSec,
104
104
  canSend
@@ -135,4 +135,4 @@ async function cmdTerm(args) {
135
135
 
136
136
  //#endregion
137
137
  export { cmdTerm };
138
- //# sourceMappingURL=terminal-MTNQuGD5.js.map
138
+ //# sourceMappingURL=terminal-Del-sL1m.js.map
@@ -0,0 +1,5 @@
1
+ import "./versionChecker-DUIQTHWC.js";
2
+ import "./rustBinary-DHWoCbGC.js";
3
+ import { a as trayHiddenMarker, i as launchTray, n as hasDesktop, r as isTrayHidden, t as cmdTray } from "./trayApp-Tp8PsCsA.js";
4
+
5
+ export { cmdTray };
@@ -1,4 +1,4 @@
1
- import { o as findTrayLauncher } from "./rustBinary-FjEdM28b.js";
1
+ import { o as findTrayLauncher } from "./rustBinary-DHWoCbGC.js";
2
2
  import { t as agentYesHome } from "./agentYesHome-BNtn19J4.js";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
@@ -97,4 +97,4 @@ async function cmdTray(args) {
97
97
 
98
98
  //#endregion
99
99
  export { trayHiddenMarker as a, launchTray as i, hasDesktop as n, isTrayHidden as r, cmdTray as t };
100
- //# sourceMappingURL=trayApp-DO3YALy1.js.map
100
+ //# sourceMappingURL=trayApp-Tp8PsCsA.js.map
@@ -1,6 +1,6 @@
1
1
  import { n as __esmMin, r as __exportAll } from "./chunk-cZBX9HZv.js";
2
2
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
3
- import { i as getInstalledPackage } from "./versionChecker-Bmtx6fPv.js";
3
+ import { i as getInstalledPackage } from "./versionChecker-DUIQTHWC.js";
4
4
  import { t as agentYesHome } from "./agentYesHome-BNtn19J4.js";
5
5
  import { u as removeControlCharacters } from "./todoAutomation-CbVVS4re.js";
6
6
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-BOIPD_Vs.js";
@@ -2086,7 +2086,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
2086
2086
  }, 100);
2087
2087
  const cleanupHeartbeat = () => clearInterval(heartbeatInterval);
2088
2088
  shell.onExit(cleanupHeartbeat);
2089
- const { startParentPingLoop } = await import("./parentPingLoop-zAmYgESx.js");
2089
+ const { startParentPingLoop } = await import("./parentPingLoop-rLQBc0G5.js");
2090
2090
  const parentPingLoop = startParentPingLoop({
2091
2091
  parentPid,
2092
2092
  selfWrapperPid: process.pid,
@@ -2349,4 +2349,4 @@ function sleep(ms) {
2349
2349
 
2350
2350
  //#endregion
2351
2351
  export { hasTodoStore as a, permissionBadge as c, answerAsk as i, AgentContext as l, agentYes as n, listAsks as o, config as r, listAsksForProject as s, CLIS_CONFIG as t, CLAUDE_SESSION_PIN_ENV as u };
2352
- //# sourceMappingURL=ts-C8dvu_cD.js.map
2352
+ //# sourceMappingURL=ts-BFVAQ0Zs.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.300.2";
10
+ var version = "1.300.3";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -285,4 +285,4 @@ async function displayVersion() {
285
285
 
286
286
  //#endregion
287
287
  export { versionString as a, getInstalledPackage as i, compareVersions as n, displayVersion as r, checkAndAutoUpdate as t };
288
- //# sourceMappingURL=versionChecker-Bmtx6fPv.js.map
288
+ //# sourceMappingURL=versionChecker-DUIQTHWC.js.map
@@ -28,7 +28,7 @@ function parseFlags(args, known) {
28
28
  }
29
29
  /** Resolve the local daemon base URL + token (flags override discovery). */
30
30
  async function daemonTarget(flags) {
31
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-C_9bMH27.js");
31
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-BPl5u9Mw.js");
32
32
  const base = typeof flags.base === "string" ? flags.base : await resolveDaemonHttpBase();
33
33
  if (!base) throw new Error("no running ay serve daemon found — start `ay serve` or pass --base <url> (e.g. http://127.0.0.1:PORT)");
34
34
  const token = typeof flags.token === "string" ? flags.token : await loadTokenReadOnly() ?? "";
@@ -186,7 +186,7 @@ async function cmdMint(args) {
186
186
  const bad = caps.filter((c) => !KNOWN_CAPS.has(c));
187
187
  if (bad.length) throw new Error(`unknown cap(s): ${bad.join(", ")} (valid: ${[...KNOWN_CAPS].join(", ")})`);
188
188
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
189
- const { mintScopedTermToken } = await import("./serve-C_9bMH27.js");
189
+ const { mintScopedTermToken } = await import("./serve-BPl5u9Mw.js");
190
190
  const r = await mintScopedTermToken(target, {
191
191
  ttlSec,
192
192
  caps
@@ -202,4 +202,4 @@ async function cmdMint(args) {
202
202
 
203
203
  //#endregion
204
204
  export { cmdMint, cmdWidget };
205
- //# sourceMappingURL=widget-BCrKF9Vw.js.map
205
+ //# sourceMappingURL=widget-E8nWj1fn.js.map
@@ -5,10 +5,10 @@ import "./pidStore-DXWqzTSz.js";
5
5
  import "./messageLog-Brx3w6zT.js";
6
6
  import "./e2e-z1xpwSAg.js";
7
7
  import "./configShared-DKTgiz7K.js";
8
- import "./subcommands-CzSrhEYG.js";
8
+ import "./subcommands-BusIzjLq.js";
9
9
  import "./notifyStore-2SWKGwZI.js";
10
10
  import "./webrtcLink-DFRyWNMs.js";
11
11
  import "./remotes-B4v0Nowo.js";
12
- import { a as isPathInside, c as walkWorkspaces, i as importProvisionModule, l as workspaceStatus, n as cmdWs, o as loadProvision, r as collectWorkspaces, s as resolveOperand, t as WS_JSON_SCHEMA } from "./ws-D1ggWZ_I.js";
12
+ import { a as isPathInside, c as walkWorkspaces, i as importProvisionModule, l as workspaceStatus, n as cmdWs, o as loadProvision, r as collectWorkspaces, s as resolveOperand, t as WS_JSON_SCHEMA } from "./ws-D9EBcPei.js";
13
13
 
14
14
  export { WS_JSON_SCHEMA, cmdWs, collectWorkspaces, isPathInside, loadProvision, walkWorkspaces, workspaceStatus };
@@ -1,4 +1,4 @@
1
- import { B as listRecords } from "./subcommands-CzSrhEYG.js";
1
+ import { V as listRecords } from "./subcommands-BusIzjLq.js";
2
2
  import { a as getProvisionRoot } from "./workspaceConfig-DTvPtr5j.js";
3
3
  import { existsSync } from "fs";
4
4
  import { lstat, opendir } from "fs/promises";
@@ -424,4 +424,4 @@ async function cmdWs(args) {
424
424
 
425
425
  //#endregion
426
426
  export { isPathInside as a, walkWorkspaces as c, importProvisionModule as i, workspaceStatus as l, cmdWs as n, loadProvision as o, collectWorkspaces as r, resolveOperand as s, WS_JSON_SCHEMA as t };
427
- //# sourceMappingURL=ws-D1ggWZ_I.js.map
427
+ //# sourceMappingURL=ws-D9EBcPei.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.300.2",
3
+ "version": "1.300.3",
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",
@@ -6,6 +6,7 @@ import { Terminal } from "@xterm/headless";
6
6
  import {
7
7
  claimPending,
8
8
  classifyComposer,
9
+ composerBoxed,
9
10
  DUPLICATE_WINDOW_MS,
10
11
  enqueuePending,
11
12
  findRecentDuplicate,
@@ -277,3 +278,36 @@ describe("duplicate suppression at the sink", () => {
277
278
  );
278
279
  });
279
280
  });
281
+
282
+ describe("composerBoxed", () => {
283
+ const RULE = "─".repeat(40);
284
+ const FOOTER = " ? for shortcuts";
285
+ it("accepts the box at the bottom, closed by its rule or the footer", () => {
286
+ expect(composerBoxed(["● done", RULE, "❯ ", RULE, FOOTER])).toBe(true);
287
+ expect(composerBoxed(["● done", RULE, "❯ btw how", FOOTER])).toBe(true);
288
+ });
289
+
290
+ it("rejects a box a partial redraw left open, however short", () => {
291
+ expect(composerBoxed(["● done", RULE, "❯ btw how"])).toBe(false);
292
+ expect(composerBoxed([RULE, "❯ ", " answer line"])).toBe(false);
293
+ });
294
+ it("rejects a screen whose prompt row has no rule above it", () => {
295
+ expect(composerBoxed(["● done", "❯ please summarise", " answer"])).toBe(false);
296
+ });
297
+ it("rejects a stale box that history has been drawn under", () => {
298
+ expect(composerBoxed([RULE, "❯ old draft", RULE, "● done", "❯ please summarise", " answer"])).toBe(false);
299
+ expect(composerBoxed([RULE, "❯ ", RULE, " a", " b", " c", " d", " e"])).toBe(false);
300
+ // Left open by a partial redraw, with history drawn under it.
301
+ expect(composerBoxed([RULE, "❯ ", "● done", "❯ please summarise", " answer"])).toBe(false);
302
+ expect(composerBoxed([RULE, "❯ ", " a", " b", " c", " d", " e", " f", " g", " h", " i"])).toBe(false);
303
+ // Transcript drawn under a stale prompt, with chrome further down.
304
+ expect(composerBoxed([RULE, "❯ ", "● done", "❯ please summarise", " answer", RULE])).toBe(false);
305
+ expect(
306
+ composerBoxed([RULE, "❯ ", ...Array.from({ length: 9 }, (_, i) => ` row ${i}`), FOOTER]),
307
+ ).toBe(false);
308
+ });
309
+
310
+ it("accepts a short multi-line draft inside the box", () => {
311
+ expect(composerBoxed([RULE, "❯ first line", " second line", " third", RULE, FOOTER])).toBe(true);
312
+ });
313
+ });
@@ -49,7 +49,13 @@ export type ComposerState =
49
49
  /** Real input is sitting in the composer. `chars` counts the real cells. */
50
50
  | { kind: "draft"; chars: number }
51
51
  /** No composer is visible (menu, trust dialog, unknown CLI screen). */
52
- | { kind: "unknown"; reason: string };
52
+ | { kind: "unknown"; reason: string }
53
+ /**
54
+ * Text was read under a prompt row, but Claude Code's composer box is not on
55
+ * screen (a dialog, or no box in any window rendered), so it may be an old
56
+ * turn rather than a draft. Parks like a draft; unlike "unknown", which sends.
57
+ */
58
+ | { kind: "hidden"; reason: string };
53
59
 
54
60
  const PROMPT_RE = /^\s*[❯›]($|\s)/u;
55
61
  // Same chrome inspectSubmission stops at: the separator rule under the box and
@@ -84,6 +90,37 @@ export function composerPromptRow(texts: string[]): number {
84
90
  return texts.findLastIndex((t) => PROMPT_RE.test(t));
85
91
  }
86
92
 
93
+ /**
94
+ * Is Claude Code's composer box the bottom of the screen: a rule directly above a
95
+ * prompt row, at most a few draft rows (none of them another prompt) up to the
96
+ * rule or footer that closes the box, and below that nothing but a few footer
97
+ * rows? A box a partial redraw left open does not count: what is under its
98
+ * prompt cannot be told from history, so the caller looks further back. When it is not, composerPromptRow either fell back to the last
99
+ * prompt-looking row (on a Claude screen, possibly an old turn in the transcript)
100
+ * or found a box that history has since been drawn under, so it is a stale one.
101
+ * A real draft longer than MAX_DRAFT_ROWS fails too and reads "hidden", which
102
+ * parks the send just as a draft does.
103
+ */
104
+ export function composerBoxed(texts: string[]): boolean {
105
+ const prompt = composerPromptRow(texts);
106
+ if (prompt < 1 || !SEPARATOR_RE.test(texts[prompt - 1]!)) return false;
107
+ const nonBlank = (rows: string[]) => rows.filter((t) => t.trim());
108
+ const close = texts.findIndex((t, i) => i > prompt && isComposerChrome(t));
109
+ if (close < 0) return false;
110
+ const inside = nonBlank(texts.slice(prompt + 1, close));
111
+ const below = nonBlank(texts.slice(close + 1));
112
+ return (
113
+ inside.length <= MAX_DRAFT_ROWS &&
114
+ below.length <= MAX_ROWS_BELOW_BOX &&
115
+ ![...inside, ...below].some((t) => PROMPT_RE.test(t))
116
+ );
117
+ }
118
+
119
+ /** Draft rows under the prompt row, before the box is read as stale (see above). */
120
+ const MAX_DRAFT_ROWS = 8;
121
+ /** Footer rows Claude Code draws under its composer box (mode, hints, agents, notices). */
122
+ const MAX_ROWS_BELOW_BOX = 4;
123
+
87
124
  /**
88
125
  * Is this row composer chrome (a rule or a Claude/Codex footer) — the end of the
89
126
  * input? Pass the row's `cells` when known: the loose footer match then also
@@ -2070,7 +2070,8 @@ describe("subcommands.cmdSend end-to-end submit-confirm wiring", () => {
2070
2070
  const errs: string[] = [];
2071
2071
  const origErr = process.stderr.write.bind(process.stderr);
2072
2072
  process.stderr.write = ((c: string | Uint8Array) => (
2073
- errs.push(String(c)), true
2073
+ errs.push(String(c)),
2074
+ true
2074
2075
  )) as typeof process.stderr.write;
2075
2076
  let res: { code: number | null; stdout: string };
2076
2077
  try {
@@ -4704,3 +4705,96 @@ describe("drainerLauncher", () => {
4704
4705
  expect(drainerLauncher("C:\\tools\\ay.exe", undefined, null)).toEqual(["C:\\tools\\ay.exe"]);
4705
4706
  });
4706
4707
  });
4708
+
4709
+ describe("readComposerState on a lane idle for hours", { timeout: 60_000 }, () => {
4710
+ // A renderer that addresses rows absolutely and re-emits only the rows that
4711
+ // changed: the full frame (both rules, the empty prompt) is drawn once, then
4712
+ // >32 KB of redraws touch only transcript rows, an old "❯" turn among them.
4713
+ const COLS = 200;
4714
+ const GRAY = "\x1b[38;2;153;153;153m";
4715
+ const at = (row: number, text: string) => `\x1b[${row};1H\x1b[2K${text}`;
4716
+ function idleLog(composer: string, staleBox?: string, bytes = 200 * 1024): string {
4717
+ const screen: string[] = [];
4718
+ for (let i = 0; i < 38; i++) screen.push(`● step ${i}: lorem ipsum dolor sit amet`);
4719
+ screen.push("❯ please summarise the plan");
4720
+ for (let i = 0; i < 7; i++) screen.push(` answer line ${i}: sed do eiusmod tempor`);
4721
+ screen.push(
4722
+ `${GRAY}${"─".repeat(COLS)}\x1b[0m`,
4723
+ `❯ ${composer}`,
4724
+ `${GRAY}${"─".repeat(COLS)}\x1b[0m`,
4725
+ );
4726
+ screen.push(`${GRAY} ? for shortcuts\x1b[0m`);
4727
+ let log = "\x1b[2J" + screen.map((t, i) => at(i + 1, t)).join("") + "\x1b[48;3H";
4728
+ for (let k = 0; log.length < bytes; k++) {
4729
+ for (let r = 39; r <= 46; r++) log += at(r, screen[r - 1]! + (r === 46 ? ` ${k % 10}` : ""));
4730
+ // A fragment of an older frame's box, still holding what was typed then.
4731
+ if (staleBox !== undefined)
4732
+ log += at(30, `${GRAY}${"─".repeat(COLS)}\x1b[0m`) + at(31, `❯ ${staleBox}`) + at(32, `${GRAY}${"─".repeat(COLS)}\x1b[0m`);
4733
+ log += "\x1b[48;3H";
4734
+ }
4735
+ return log;
4736
+ }
4737
+ let logs = 0;
4738
+ async function stateOf(log: string, cli = "claude", windows?: number[]) {
4739
+ const { readComposerState } = await loadModule();
4740
+ const file = path.join(testHome, `${1111 + logs++}.raw.log`);
4741
+ await writeFile(file, log);
4742
+ return readComposerState({ pid: 1111, cli, log_file: file } as never, windows);
4743
+ }
4744
+
4745
+ it("reads the empty composer drawn before the last 32 KB, not the transcript", async () => {
4746
+ // The 32 KB tail alone renders the old turn as the prompt and its answer as a draft.
4747
+ expect(await stateOf(idleLog(""))).toEqual({ kind: "empty", ghost: false });
4748
+ });
4749
+
4750
+ it("searches as far back as `ay read` renders before giving up on the box", async () => {
4751
+ const { COMPOSER_WINDOWS, MAX_RENDER_BYTES } = await loadModule();
4752
+ expect(COMPOSER_WINDOWS.at(-1)).toBe(MAX_RENDER_BYTES);
4753
+ // Scaled down: windows that stop short of the box report it hidden, one more
4754
+ // that reaches it reads the empty composer.
4755
+ const log = idleLog("", undefined, 300 * 1024);
4756
+ expect((await stateOf(log, "claude", [32 * 1024, 64 * 1024])).kind).toBe("hidden");
4757
+ expect(await stateOf(log, "claude", [32 * 1024, 64 * 1024, 512 * 1024])).toEqual({
4758
+ kind: "empty",
4759
+ ghost: false,
4760
+ });
4761
+ });
4762
+
4763
+ it("does not stop at an older box that history was drawn under", async () => {
4764
+ expect(await stateOf(idleLog("", "old draft"))).toEqual({ kind: "empty", ghost: false });
4765
+ });
4766
+
4767
+ it("still reads a typed draft in that composer", async () => {
4768
+ expect(await stateOf(idleLog("btw how"))).toEqual({ kind: "draft", chars: 6 });
4769
+ });
4770
+
4771
+ it("reads a Claude screen that never shows its box as hidden, not as a draft", async () => {
4772
+ const turn = "❯ please summarise the plan\r\n answer: sed do eiusmod tempor\r\n";
4773
+ expect(await stateOf(turn.repeat(3000))).toEqual({
4774
+ kind: "hidden",
4775
+ reason: "its input box is not on screen",
4776
+ });
4777
+ // CLIs without a box keep reading the last prompt row in the 32 KB tail.
4778
+ expect((await stateOf(turn.repeat(3000), "codex")).kind).toBe("draft");
4779
+ });
4780
+
4781
+ it("follows what is appended after the first reading, and a truncated log", async () => {
4782
+ const { readComposerState } = await loadModule();
4783
+ const file = path.join(testHome, "2222.raw.log");
4784
+ const record = { pid: 2222, cli: "claude", log_file: file } as never;
4785
+ await writeFile(file, idleLog(""));
4786
+ expect(await readComposerState(record)).toEqual({ kind: "empty", ghost: false });
4787
+ appendFileSync(file, at(48, "❯ btw how"));
4788
+ expect(await readComposerState(record)).toEqual({ kind: "draft", chars: 6 });
4789
+ appendFileSync(file, at(48, "❯ "));
4790
+ expect(await readComposerState(record)).toEqual({ kind: "empty", ghost: false });
4791
+ const box = `${GRAY}${"─".repeat(COLS)}\x1b[0m\r\n❯ hi\r\n${GRAY}${"─".repeat(COLS)}\x1b[0m\r\n`;
4792
+ await writeFile(file, "x\r\n".repeat(100) + box);
4793
+ expect(await readComposerState(record)).toEqual({ kind: "draft", chars: 2 });
4794
+ });
4795
+
4796
+ it("keeps a typed draft on screen in the 32 KB tail", async () => {
4797
+ const box = `${GRAY}${"─".repeat(COLS)}\x1b[0m\r\n❯ btw how\r\n${GRAY}${"─".repeat(COLS)}\x1b[0m\r\n`;
4798
+ expect(await stateOf("x\r\n".repeat(20000) + box)).toEqual({ kind: "draft", chars: 6 });
4799
+ });
4800
+ });
package/ts/subcommands.ts CHANGED
@@ -103,6 +103,7 @@ import { isWithin, resolveWorkDir } from "./workDir.ts";
103
103
  import {
104
104
  bodyHash,
105
105
  classifyComposer,
106
+ composerBoxed,
106
107
  composerPromptRow,
107
108
  DUPLICATE_WINDOW_MS,
108
109
  findRecentDuplicate,
@@ -4302,28 +4303,115 @@ export async function renderLogTailLines(
4302
4303
  }
4303
4304
  }
4304
4305
 
4306
+ /**
4307
+ * Tail windows the composer check renders, smallest first. Rendering a tail
4308
+ * starts mid-stream, and Claude Code's renderer redraws only the rows that
4309
+ * changed: on a lane that has sat idle for hours the last 32 KB can hold
4310
+ * redraws of transcript rows (an old `❯` turn among them) but not the unchanged
4311
+ * rule and empty prompt row of the composer box, so the old turn was read as
4312
+ * the prompt and the transcript under it as a draft. A Claude screen without
4313
+ * its box is therefore re-rendered from further back, last of all from as far
4314
+ * back as `ay read` renders. When even that shows no box, a draft read under
4315
+ * some other prompt row is reported as "hidden" rather than as a draft: the
4316
+ * send still parks, and the drainer waits for the box.
4317
+ *
4318
+ * Logs run to 100+ MB and the drainer re-checks every few seconds, so the
4319
+ * rendered terminal is kept per log and later checks feed it only the bytes
4320
+ * appended since: each window is rendered at most once per process, and a
4321
+ * screen that stays without a box costs only its new output on every poll.
4322
+ */
4323
+ export const COMPOSER_WINDOWS = [32 * 1024, 1024 * 1024, 8 * 1024 * 1024, MAX_RENDER_BYTES];
4324
+
4325
+ /** A log's rendered terminal: fed up to `offset`, started from `windows[window]`. */
4326
+ type ComposerTerm = {
4327
+ term: InstanceType<typeof import("@xterm/headless").Terminal>;
4328
+ key: string;
4329
+ window: number;
4330
+ offset: number;
4331
+ whole: boolean;
4332
+ };
4333
+ const composerTerms = new Map<string, ComposerTerm>();
4334
+ const COMPOSER_TERMS_MAX = 64;
4335
+
4336
+ function dropComposerTerm(logPath: string) {
4337
+ composerTerms.get(logPath)?.term.dispose();
4338
+ composerTerms.delete(logPath);
4339
+ }
4340
+
4305
4341
  /**
4306
4342
  * What the target's composer holds right now, read from the same 32 KB tail
4307
- * `renderLogTailLines` renders, but keeping per-cell attributes (dim/inverse) so
4308
- * ghost text can be told from a real draft. See ts/composerGuard.ts.
4343
+ * `renderLogTailLines` renders (further back when a Claude screen shows no
4344
+ * composer box), keeping per-cell attributes (dim/inverse) so ghost text can be
4345
+ * told from a real draft. See ts/composerGuard.ts.
4309
4346
  */
4310
- export async function readComposerState(record: GlobalPidRecord): Promise<ComposerState> {
4311
- if (!record.log_file) return { kind: "unknown", reason: "no log file recorded" };
4312
- const buf = await readLogTailBytes(record.log_file);
4313
- if (!buf) return { kind: "unknown", reason: "log unreadable or empty" };
4347
+ export async function readComposerState(
4348
+ record: GlobalPidRecord,
4349
+ windows = COMPOSER_WINDOWS,
4350
+ ): Promise<ComposerState> {
4351
+ const logPath = record.log_file;
4352
+ if (!logPath) return { kind: "unknown", reason: "no log file recorded" };
4314
4353
  const geom = (await readAgentPtysize(record)) ?? undefined;
4354
+ const key = `${geom?.cols}x${geom?.rows}:${record.cli}:${windows.join()}`;
4355
+ const claude = record.cli === "claude";
4315
4356
  try {
4316
- const Terminal = await loadXtermTerminal();
4317
- const term = new Terminal({
4318
- cols: geom?.cols && geom.cols > 0 ? geom.cols : 200,
4319
- rows: geom?.rows && geom.rows > 0 ? geom.rows : 50,
4320
- scrollback: 5000,
4321
- allowProposedApi: true,
4322
- });
4323
- await new Promise<void>((resolve) => term.write(buf, resolve));
4324
- return classifyComposer(rowsFromXterm(term as never, 40));
4357
+ const { size } = await stat(logPath);
4358
+ if (size === 0) return { kind: "unknown", reason: "log unreadable or empty" };
4359
+ let ct = composerTerms.get(logPath);
4360
+ if (ct && (ct.key !== key || size < ct.offset)) {
4361
+ dropComposerTerm(logPath); // resized, or the log was truncated
4362
+ ct = undefined;
4363
+ }
4364
+ let rows: ReturnType<typeof rowsFromXterm> | undefined;
4365
+ let next = 0;
4366
+ if (ct) {
4367
+ if (size > ct.offset) {
4368
+ const delta = await readLogRange(logPath, ct.offset, size);
4369
+ await new Promise<void>((resolve) => ct!.term.write(delta, resolve));
4370
+ ct.offset += delta.length;
4371
+ }
4372
+ rows = rowsFromXterm(ct.term as never, 40);
4373
+ next = ct.whole ? windows.length : ct.window + 1;
4374
+ if (!claude || composerBoxed(rows.map((r) => r.text))) next = windows.length;
4375
+ }
4376
+ for (let w = next; w < windows.length; w++) {
4377
+ const from = Math.max(0, size - windows[w]!);
4378
+ const buf = await readLogRange(logPath, from, size);
4379
+ const Terminal = await loadXtermTerminal();
4380
+ const term = new Terminal({
4381
+ cols: geom?.cols && geom.cols > 0 ? geom.cols : 200,
4382
+ rows: geom?.rows && geom.rows > 0 ? geom.rows : 50,
4383
+ scrollback: 1000,
4384
+ allowProposedApi: true,
4385
+ });
4386
+ await new Promise<void>((resolve) => term.write(buf, resolve));
4387
+ dropComposerTerm(logPath);
4388
+ composerTerms.set(logPath, { term, key, window: w, offset: from + buf.length, whole: from === 0 });
4389
+ if (composerTerms.size > COMPOSER_TERMS_MAX)
4390
+ dropComposerTerm(composerTerms.keys().next().value!); // oldest first
4391
+ rows = rowsFromXterm(term as never, 40);
4392
+ if (!claude || from === 0 || composerBoxed(rows.map((r) => r.text))) break;
4393
+ }
4394
+ const state = classifyComposer(rows ?? []);
4395
+ // No window showed the box, so a "draft" was read under some other prompt
4396
+ // row, possibly an old turn: say so instead of reporting it as a draft.
4397
+ if (claude && state.kind === "draft" && !composerBoxed((rows ?? []).map((r) => r.text)))
4398
+ return { kind: "hidden", reason: "its input box is not on screen" };
4399
+ return state;
4325
4400
  } catch (e) {
4326
- return { kind: "unknown", reason: `render failed: ${(e as Error)?.message ?? e}` };
4401
+ dropComposerTerm(logPath);
4402
+ return { kind: "unknown", reason: `log unreadable or render failed: ${(e as Error)?.message ?? e}` };
4403
+ }
4404
+ }
4405
+
4406
+ /** Bytes `[from, to)` of a log file. */
4407
+ async function readLogRange(logPath: string, from: number, to: number): Promise<Uint8Array> {
4408
+ const fh = await open(logPath, "r");
4409
+ try {
4410
+ const buf = Buffer.alloc(Math.max(0, to - from));
4411
+ const { bytesRead } = await fh.read(buf, 0, buf.length, from);
4412
+ return new Uint8Array(buf.buffer, buf.byteOffset, bytesRead);
4413
+ } finally {
4414
+ await fh.close();
4327
4415
  }
4328
4416
  }
4329
4417
 
@@ -5485,6 +5573,7 @@ async function cmdSend(rest: string[]): Promise<number> {
5485
5573
  const composer = await readComposerState(record);
5486
5574
  if (composer.kind === "draft")
5487
5575
  parkReason = `its input box holds a draft (${composer.chars} chars)`;
5576
+ else if (composer.kind === "hidden") parkReason = composer.reason;
5488
5577
  else if (composer.kind === "unknown")
5489
5578
  process.stderr.write(
5490
5579
  `warning: ay send could not see pid ${record.pid}'s input box (${composer.reason}) — sending without the draft check.\n`,
@@ -1,13 +0,0 @@
1
- import "./logger-CDIsZ-Pp.js";
2
- import "./JsonlStore-BqF7AQ6E.js";
3
- import "./globalPidIndex-CRH9L9tn.js";
4
- import "./pidStore-DXWqzTSz.js";
5
- import "./messageLog-Brx3w6zT.js";
6
- import "./e2e-z1xpwSAg.js";
7
- import "./configShared-DKTgiz7K.js";
8
- import { $ as renderRawLog, A as isBareManagerInvocation, B as listRecords, C as extractMenu, D as inspectSubmission, E as finalizedLines, F as isUnknownManagerToken, G as readAgentPtysize, H as menuSelectKeys, I as isUnreachableWriteErrno, J as readNotes, K as readComposerState, L as isUserTyping, M as isPidAlive, N as isSlashCommand, O as isAgentIdShaped, P as isSubcommand, Q as renderLogTailLines, R as lastReadAt, S as extractBadges, T as extractTaskCounts, U as ownedCollapsedPaste, V as matchKeyword, W as probeStdinReachable, X as recentMessageEdges, Y as readPtysize, Z as recentReadEdges, _ as deriveLiveState, a as SEND_BODY_MAX_CHARS, at as runSubcommand, b as envelopeAttribution, c as TYPING_WINDOW_MS, ct as stdinActivityPath, d as cliDefaults, dt as waitForLogQuiet, et as renderRawLogLines, f as cmdHelp, ft as writeKeysPaced, g as cwdLabel, h as cursorAbs, i as READ_WINDOW_MS, it as restartHintLines, j as isExitRequest, k as isAgentStuck, l as backoffWhileTyping, lt as stopTipForCli, m as controlCodeFromName, n as MAX_RENDER_BYTES, nt as resolveReadWindow, o as SEND_EXIT_QUEUED, ot as sendPayloadCapError, p as confirmStdinUnreachable, pt as writeToIpc, q as readLogForRender, r as READ_PAGE_DEFAULT, rt as resolveResumeArgs, s as SEND_EXIT_UNREACHABLE, st as snapshotStatus, t as GRACEFUL_EXIT_COMMANDS, tt as resolveOne, u as buildEnvelope, ut as submitAndConfirm, v as deriveLiveStatus, w as extractNeedsInput, x as envelopeCostFor, y as drainerLauncher, z as lastStdinAt } from "./subcommands-CzSrhEYG.js";
9
- import "./notifyStore-2SWKGwZI.js";
10
- import "./webrtcLink-DFRyWNMs.js";
11
- import "./remotes-B4v0Nowo.js";
12
-
13
- export { READ_WINDOW_MS, cmdHelp, isBareManagerInvocation, isSubcommand, isUnknownManagerToken, lastReadAt, runSubcommand };
@@ -1,5 +0,0 @@
1
- import "./versionChecker-Bmtx6fPv.js";
2
- import "./rustBinary-FjEdM28b.js";
3
- import { a as trayHiddenMarker, i as launchTray, n as hasDesktop, r as isTrayHidden, t as cmdTray } from "./trayApp-DO3YALy1.js";
4
-
5
- export { cmdTray };