agent-yes 1.265.3 → 1.265.4

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 (39) hide show
  1. package/dist/{SUPPORTED_CLIS-X3jH37iY.js → SUPPORTED_CLIS-C3tcRetU.js} +2 -2
  2. package/dist/{SUPPORTED_CLIS-DnYyo_km.js → SUPPORTED_CLIS-CY3SaV0D.js} +3 -3
  3. package/dist/{agentShare-D8Qx06xK.js → agentShare-g6N1A6A_.js} +3 -3
  4. package/dist/{callback-CByNygd_.js → callback-L4k-v2GG.js} +3 -3
  5. package/dist/{callback-C5A5zDzb.js → callback-z-w0ND5z.js} +3 -3
  6. package/dist/cli.js +8 -8
  7. package/dist/hist-CoXBBjJt.js +442 -0
  8. package/dist/index.js +2 -2
  9. package/dist/{nodeRuntime-vhOBffJ3.js → nodeRuntime-CT0w6zsB.js} +1 -1
  10. package/dist/{notifyDaemon-DuKvOHJJ.js → notifyDaemon-BL1tmxUK.js} +3 -3
  11. package/dist/{openBrowser-D-aQjh3y.js → openBrowser-Cw8hg7ok.js} +1 -1
  12. package/dist/{remotes-lOhHSoSS.js → remotes-DWVw-Q7V.js} +1 -1
  13. package/dist/{remotes-Cn-wszbK.js → remotes-iIvqiFWK.js} +2 -2
  14. package/dist/{rustBinary-CkTiZVJ4.js → rustBinary-DSVRhDzO.js} +2 -2
  15. package/dist/{schedule-R6C567wG.js → schedule-i4o5SJ_h.js} +6 -6
  16. package/dist/{serve-bXCIgnq_.js → serve-CpGJD2Kt.js} +23 -23
  17. package/dist/{setup-CtUFv2Se.js → setup-B-iQKsQW.js} +4 -4
  18. package/dist/{spawnGate-D0E1uRP7.js → spawnGate-BmsTv340.js} +2 -2
  19. package/dist/{spawnGate-DqKBA07k.js → spawnGate-S6EoFdzX.js} +1 -1
  20. package/dist/{subcommands-Cbhtldub.js → subcommands-CQPnT5qu.js} +22 -18
  21. package/dist/{subcommands-CnARtyVR.js → subcommands-Cgt2-kjg.js} +2 -2
  22. package/dist/{systemPathLink-DTAhFXFx.js → systemPathLink-CjLmiPbB.js} +1 -1
  23. package/dist/{terminal-B8jbz_HG.js → terminal-CybRgMZu.js} +2 -2
  24. package/dist/{todoCli-CngDeaT2.js → todoCli-DZmOmcvd.js} +1 -1
  25. package/dist/{tray-wWMvGWeY.js → tray-Cjm3a8JH.js} +1 -1
  26. package/dist/trayApp-CmwwBg8v.js +5 -0
  27. package/dist/{trayApp-CZR7Thm6.js → trayApp-UglHbk_m.js} +2 -2
  28. package/dist/{ts-ZKWh7obB.js → ts-nuUnrVcf.js} +2 -2
  29. package/dist/{versionChecker-C6RmYXss.js → versionChecker-CMOLbEEi.js} +2 -2
  30. package/dist/{webrtcRemote-BkZcA5yq.js → webrtcRemote-zQybN9IR.js} +1 -1
  31. package/dist/{widget-BOcpTCTQ.js → widget-y5Bpvwsa.js} +3 -3
  32. package/dist/{workspaceConfig-Bxuk_MOq.js → workspaceConfig-BDqp7fYl.js} +1 -1
  33. package/dist/{ws-CPJxObp_.js → ws-BrLV-AlH.js} +3 -3
  34. package/dist/{ws-C9NmPMjN.js → ws-CH8GzcQn.js} +3 -3
  35. package/package.json +1 -1
  36. package/ts/hist.spec.ts +18 -3
  37. package/ts/hist.ts +13 -4
  38. package/ts/subcommands.ts +8 -0
  39. package/dist/trayApp-Bx0qlt3A.js +0 -5
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-ZKWh7obB.js";
1
+ import { t as CLIS_CONFIG } from "./ts-nuUnrVcf.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-X3jH37iY.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-C3tcRetU.js.map
@@ -1,11 +1,11 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-C6RmYXss.js";
3
- import "./ts-ZKWh7obB.js";
2
+ import "./versionChecker-CMOLbEEi.js";
3
+ import "./ts-nuUnrVcf.js";
4
4
  import "./todoAutomation-BQRW614S.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
7
7
  import "./pidStore-DJqSZPJi.js";
8
8
  import "./messageLog-BYFwioFY.js";
9
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-X3jH37iY.js";
9
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C3tcRetU.js";
10
10
 
11
11
  export { SUPPORTED_CLIS };
@@ -3,9 +3,9 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-CUMZC53I.js";
5
5
  import "./configShared-DtHMBCl7.js";
6
- import { R as resolveOne } from "./subcommands-Cbhtldub.js";
6
+ import { R as resolveOne } from "./subcommands-CQPnT5qu.js";
7
7
  import "./webrtcLink-CS4D-3G1.js";
8
- import "./remotes-Cn-wszbK.js";
8
+ import "./remotes-iIvqiFWK.js";
9
9
  import { a as startShare } from "./share-B-e_9WBx.js";
10
10
 
11
11
  //#region ts/agentShare.ts
@@ -229,4 +229,4 @@ function transformEvent(rawEvent, agentId, forwarded) {
229
229
 
230
230
  //#endregion
231
231
  export { createScopedShare, listShares, revokeAllShares, revokeShare };
232
- //# sourceMappingURL=agentShare-D8Qx06xK.js.map
232
+ //# sourceMappingURL=agentShare-g6N1A6A_.js.map
@@ -3,10 +3,10 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-CUMZC53I.js";
5
5
  import "./configShared-DtHMBCl7.js";
6
- import "./subcommands-Cbhtldub.js";
6
+ import "./subcommands-CQPnT5qu.js";
7
7
  import "./webrtcLink-CS4D-3G1.js";
8
- import "./remotes-Cn-wszbK.js";
8
+ import "./remotes-iIvqiFWK.js";
9
9
  import "./callbackCore-Cg3Ye0ZS.js";
10
- import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-C5A5zDzb.js";
10
+ import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-z-w0ND5z.js";
11
11
 
12
12
  export { cmdCallback };
@@ -1,5 +1,5 @@
1
1
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
2
- import { R as resolveOne } from "./subcommands-Cbhtldub.js";
2
+ import { R as resolveOne } from "./subcommands-CQPnT5qu.js";
3
3
  import { a as mintCapability, o as parseExpires } from "./callbackCore-Cg3Ye0ZS.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-bXCIgnq_.js");
75
+ const { resolveLocalServeUrl } = await import("./serve-CpGJD2Kt.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-C5A5zDzb.js.map
190
+ //# sourceMappingURL=callback-z-w0ND5z.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-uqM2YYA7.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-C6RmYXss.js";
5
- import { r as getRustBinary } from "./rustBinary-CkTiZVJ4.js";
4
+ import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-CMOLbEEi.js";
5
+ import { r as getRustBinary } from "./rustBinary-DSVRhDzO.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-CnARtyVR.js");
298
+ const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken, isBareManagerInvocation } = await import("./subcommands-Cgt2-kjg.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-DnYyo_km.js");
312
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CY3SaV0D.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);
@@ -328,16 +328,16 @@ await checkAndAutoUpdate();
328
328
  logger.info(versionString());
329
329
  const config = parseCliArgs(process.argv);
330
330
  if (config.tray) {
331
- const { startTray } = await import("./tray-wWMvGWeY.js");
331
+ const { startTray } = await import("./tray-Cjm3a8JH.js");
332
332
  await startTray();
333
333
  await new Promise(() => {});
334
334
  }
335
335
  {
336
- const { ensureTray } = await import("./tray-wWMvGWeY.js");
336
+ const { ensureTray } = await import("./tray-Cjm3a8JH.js");
337
337
  ensureTray();
338
338
  }
339
339
  if (!config.showVersion && !config.appendPrompt && !config.tray) {
340
- const { waitForSpawnCapacity } = await import("./spawnGate-DqKBA07k.js");
340
+ const { waitForSpawnCapacity } = await import("./spawnGate-S6EoFdzX.js");
341
341
  await waitForSpawnCapacity({
342
342
  onWait: (reason, waitedMs) => {
343
343
  if (config.verbose || waitedMs === 0) console.error(`[agent-yes] spawn gate: ${reason} — waiting…`);
@@ -360,7 +360,7 @@ if (config.useRust) {
360
360
  }
361
361
  }
362
362
  if (rustBinary) {
363
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DnYyo_km.js");
363
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CY3SaV0D.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}`);
@@ -0,0 +1,442 @@
1
+ import yargs from "yargs";
2
+ import { open, readdir, stat } from "fs/promises";
3
+ import { homedir } from "os";
4
+ import path from "path";
5
+
6
+ //#region ts/histStore.ts
7
+ /**
8
+ * `ay hist` — read the tail of *coding-agent conversation transcripts* that
9
+ * already exist on this machine (Claude Code, Codex), newest-last.
10
+ *
11
+ * This is deliberately NOT `ay tail`. `ay tail` follows the live PTY log of an
12
+ * agent process agent-yes spawned; `ay hist` reads the durable JSONL transcript
13
+ * the CLI itself writes, including sessions from before agent-yes was involved
14
+ * and sessions whose process already exited.
15
+ *
16
+ * Design notes (the two that actually matter):
17
+ *
18
+ * 1. **Read backwards.** Transcript lines are whole JSON records and routinely
19
+ * reach 1.3MB (tool outputs, base64 images). Reading a file forward — or
20
+ * `readFile().split("\n")` — costs megabytes to show six records. We seek to
21
+ * EOF and walk back in chunks, stopping as soon as the caller has enough, so
22
+ * cost is proportional to what's displayed, not to session length.
23
+ *
24
+ * 2. **No index.** These files are append-only, so a sidecar index storing
25
+ * `indexed_up_to_bytes` would let a future `ay hist search` re-scan only the
26
+ * tail delta. Tailing needs none of that, and the 90% case is tailing — so
27
+ * v1 ships with zero index, zero daemon, zero cache invalidation.
28
+ */
29
+ const NEWLINE = 10;
30
+ /**
31
+ * Claude Code names each project directory after its cwd with every
32
+ * non-alphanumeric byte replaced by `-`:
33
+ * `/code/snomiao/cv.snomiao.com` → `-code-snomiao-cv-snomiao-com`.
34
+ *
35
+ * Encoding the cwd and comparing is exact and costs no file reads — decoding
36
+ * the slug back to a path is not possible, since `-` is ambiguous.
37
+ */
38
+ function encodeProjectSlug(cwd) {
39
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
40
+ }
41
+ function claudeProjectsDir(home = homedir()) {
42
+ return path.join(home, ".claude", "projects");
43
+ }
44
+ function codexSessionsDir(home = homedir()) {
45
+ return path.join(home, ".codex", "sessions");
46
+ }
47
+ async function listJsonlDeep(dir, out = [], depth = 0) {
48
+ if (depth > 4) return out;
49
+ let entries;
50
+ try {
51
+ entries = await readdir(dir, { withFileTypes: true });
52
+ } catch {
53
+ return out;
54
+ }
55
+ for (const e of entries) {
56
+ const full = path.join(dir, e.name);
57
+ if (e.isDirectory()) await listJsonlDeep(full, out, depth + 1);
58
+ else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
59
+ }
60
+ return out;
61
+ }
62
+ /**
63
+ * Locate transcripts, newest first. Only stats files — never opens them — so
64
+ * this stays cheap even with thousands of sessions on disk.
65
+ */
66
+ async function discoverTranscripts(opts = {}) {
67
+ const home = opts.home ?? homedir();
68
+ const sources = opts.sources ?? ["claude", "codex"];
69
+ const found = [];
70
+ if (sources.includes("claude")) {
71
+ const root = claudeProjectsDir(home);
72
+ const wanted = opts.cwd ? encodeProjectSlug(opts.cwd) : null;
73
+ let projects = [];
74
+ try {
75
+ projects = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
76
+ } catch {
77
+ projects = [];
78
+ }
79
+ for (const slug of projects) {
80
+ if (wanted && slug !== wanted) continue;
81
+ for (const file of await listJsonlDeep(path.join(root, slug))) {
82
+ const st = await stat(file).catch(() => null);
83
+ if (!st?.isFile() || st.size === 0) continue;
84
+ found.push({
85
+ path: file,
86
+ source: "claude",
87
+ sessionId: path.basename(file, ".jsonl"),
88
+ mtimeMs: st.mtimeMs,
89
+ size: st.size
90
+ });
91
+ }
92
+ }
93
+ }
94
+ if (sources.includes("codex")) for (const file of await listJsonlDeep(codexSessionsDir(home))) {
95
+ if (!path.basename(file).startsWith("rollout-")) continue;
96
+ const st = await stat(file).catch(() => null);
97
+ if (!st?.isFile() || st.size === 0) continue;
98
+ found.push({
99
+ path: file,
100
+ source: "codex",
101
+ sessionId: path.basename(file, ".jsonl"),
102
+ mtimeMs: st.mtimeMs,
103
+ size: st.size
104
+ });
105
+ }
106
+ found.sort((a, b) => b.mtimeMs - a.mtimeMs);
107
+ if (opts.cwd && sources.includes("codex")) {
108
+ const kept = [];
109
+ for (const f of found) {
110
+ if (f.source !== "codex") {
111
+ kept.push(f);
112
+ continue;
113
+ }
114
+ const cwd = await readCodexCwd(f.path);
115
+ if (cwd === opts.cwd) kept.push({
116
+ ...f,
117
+ cwd
118
+ });
119
+ }
120
+ return kept;
121
+ }
122
+ return found;
123
+ }
124
+ /** Read the leading `session_meta` record of a Codex rollout to learn its cwd. */
125
+ async function readCodexCwd(file) {
126
+ let fh;
127
+ try {
128
+ fh = await open(file, "r");
129
+ const buf = Buffer.alloc(64 * 1024);
130
+ const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
131
+ const nl = buf.indexOf(NEWLINE);
132
+ const end = nl === -1 ? bytesRead : nl;
133
+ return JSON.parse(buf.subarray(0, end).toString("utf8")).payload?.cwd;
134
+ } catch {
135
+ return;
136
+ } finally {
137
+ await fh?.close();
138
+ }
139
+ }
140
+ /**
141
+ * Yield whole lines from EOF toward the start, newest first, as
142
+ * `{ offset, text }`. Lazy: the generator only reads another chunk when the
143
+ * consumer asks for another line, so `take(6)` reads ~one chunk regardless of
144
+ * whether the file is 4KB or 400MB.
145
+ *
146
+ * A trailing partial line (a session being appended to right now) is dropped —
147
+ * we stop at the last complete newline rather than hand back half a record.
148
+ */
149
+ async function* readLinesBackward(file, opts = {}) {
150
+ const chunkSize = opts.chunkSize ?? 64 * 1024;
151
+ const maxRecordBytes = opts.maxRecordBytes ?? 16 * 1024 * 1024;
152
+ const fh = await open(file, "r");
153
+ try {
154
+ let pos = (await fh.stat()).size;
155
+ if (pos === 0) return;
156
+ let pending = Buffer.alloc(0);
157
+ let base = pos;
158
+ let first = true;
159
+ while (pos > 0) {
160
+ const len = Math.min(chunkSize, pos);
161
+ pos -= len;
162
+ const buf = Buffer.alloc(len);
163
+ await fh.read(buf, 0, len, pos);
164
+ const combined = pending.length ? Buffer.concat([buf, pending]) : buf;
165
+ base = pos;
166
+ let end = combined.length;
167
+ if (first) {
168
+ first = false;
169
+ const last = combined.lastIndexOf(NEWLINE);
170
+ end = last === -1 ? 0 : last;
171
+ }
172
+ while (end > 0) {
173
+ const nl = combined.lastIndexOf(NEWLINE, end - 1);
174
+ if (nl === -1) break;
175
+ const text = combined.subarray(nl + 1, end).toString("utf8");
176
+ if (text.trim()) yield {
177
+ offset: base + nl + 1,
178
+ text
179
+ };
180
+ end = nl;
181
+ }
182
+ pending = combined.subarray(0, end);
183
+ if (pending.length > maxRecordBytes) pending = Buffer.alloc(0);
184
+ }
185
+ const head = pending.toString("utf8");
186
+ if (head.trim()) yield {
187
+ offset: 0,
188
+ text: head
189
+ };
190
+ } finally {
191
+ await fh.close();
192
+ }
193
+ }
194
+ function claudeText(content, includeTools) {
195
+ if (typeof content === "string") return content;
196
+ if (!Array.isArray(content)) return "";
197
+ const parts = [];
198
+ for (const raw of content) {
199
+ if (!raw || typeof raw !== "object") continue;
200
+ if (raw.type === "text" && typeof raw.text === "string") parts.push(raw.text);
201
+ else if (includeTools && raw.type === "thinking" && typeof raw.thinking === "string") parts.push(`(thinking) ${raw.thinking}`);
202
+ else if (includeTools && raw.type === "tool_use") parts.push(`[tool: ${raw.name ?? "?"}]`);
203
+ else if (includeTools && raw.type === "tool_result") parts.push(`[tool result]`);
204
+ }
205
+ return parts.join("\n").trim();
206
+ }
207
+ /**
208
+ * Reduce one raw transcript line to a displayable turn, or null when the record
209
+ * is not conversational (metadata, token counts, tool plumbing, …).
210
+ *
211
+ * Returning null for the great majority of records is the point: it is what
212
+ * makes six records of *conversation* cost six records of output rather than
213
+ * six records of JSON noise.
214
+ */
215
+ function projectRecord(line, source, opts = {}) {
216
+ let rec;
217
+ try {
218
+ rec = JSON.parse(line);
219
+ } catch {
220
+ return null;
221
+ }
222
+ if (!rec || typeof rec !== "object") return null;
223
+ if (source === "claude") {
224
+ if (rec.type !== "user" && rec.type !== "assistant") return null;
225
+ if (rec.isSidechain && !opts.includeSidechains) return null;
226
+ const role = rec.message?.role === "assistant" ? "assistant" : "user";
227
+ const text = claudeText(rec.message?.content, opts.includeTools ?? false);
228
+ if (!text) return null;
229
+ return {
230
+ ts: typeof rec.timestamp === "string" ? rec.timestamp : null,
231
+ role,
232
+ text
233
+ };
234
+ }
235
+ if (rec.type !== "event_msg") return null;
236
+ const kind = rec.payload?.type;
237
+ if (kind !== "user_message" && kind !== "agent_message") return null;
238
+ const text = typeof rec.payload?.message === "string" ? rec.payload.message.trim() : "";
239
+ if (!text) return null;
240
+ return {
241
+ ts: typeof rec.timestamp === "string" ? rec.timestamp : null,
242
+ role: kind === "user_message" ? "user" : "assistant",
243
+ text
244
+ };
245
+ }
246
+ function encodeCursor(r) {
247
+ return `${r.ts ?? ""}|${r.sessionId}|${r.offset}`;
248
+ }
249
+ /** Parse an encoded cursor; returns null for a bare timestamp or garbage. */
250
+ function decodeCursor(raw) {
251
+ const parts = raw.split("|");
252
+ if (parts.length !== 3) return null;
253
+ const offset = Number(parts[2]);
254
+ if (!Number.isFinite(offset)) return null;
255
+ return {
256
+ ts: parts[0],
257
+ sessionId: parts[1],
258
+ offset
259
+ };
260
+ }
261
+ /** Total order over records: timestamp, then session, then byte offset. */
262
+ function compareRecords(a, b) {
263
+ return (a.ts ?? "").localeCompare(b.ts ?? "") || a.sessionId.localeCompare(b.sessionId) || a.offset - b.offset;
264
+ }
265
+ /**
266
+ * Newest `limit` conversational records of one transcript, oldest-first.
267
+ *
268
+ * Stops reading as soon as `limit` records are collected — the whole reason the
269
+ * backward reader is a generator.
270
+ */
271
+ async function tailTranscript(file, opts) {
272
+ const out = [];
273
+ for await (const { offset, text } of readLinesBackward(file.path, opts)) {
274
+ const projected = projectRecord(text, file.source, opts);
275
+ if (!projected) continue;
276
+ if (opts.role && projected.role !== opts.role) continue;
277
+ const record = {
278
+ ...projected,
279
+ source: file.source,
280
+ sessionId: file.sessionId,
281
+ file: file.path,
282
+ offset
283
+ };
284
+ if (opts.cursor) {
285
+ if (compareRecords(record, opts.cursor) >= 0) continue;
286
+ } else if (opts.before && projected.ts && projected.ts >= opts.before) continue;
287
+ out.push(record);
288
+ if (out.length >= opts.limit) break;
289
+ }
290
+ return out.reverse();
291
+ }
292
+ /**
293
+ * Merge the tails of the most recently touched transcripts into one
294
+ * chronological page — "what have my agents been saying", across sessions.
295
+ */
296
+ async function histPage(q) {
297
+ const considered = (await discoverTranscripts({
298
+ home: q.home,
299
+ cwd: q.cwd,
300
+ sources: q.sources
301
+ })).slice(0, q.maxFiles ?? 40);
302
+ const perFile = q.limit > 0 ? q.limit + 1 : Number.MAX_SAFE_INTEGER;
303
+ const cursor = q.cursor ?? (q.before ? decodeCursor(q.before) : null);
304
+ const merged = (await Promise.all(considered.map((f) => tailTranscript(f, {
305
+ ...q,
306
+ limit: perFile,
307
+ cursor: cursor ?? void 0
308
+ })))).flat();
309
+ merged.sort(compareRecords);
310
+ const records = q.limit > 0 ? merged.slice(-q.limit) : merged;
311
+ const oldest = records[0];
312
+ return {
313
+ records,
314
+ cursor: merged.length > records.length && oldest ? encodeCursor(oldest) : null,
315
+ scanned: considered.length
316
+ };
317
+ }
318
+
319
+ //#endregion
320
+ //#region ts/hist.ts
321
+ /**
322
+ * `ay hist` — CLI surface over {@link ./histStore.ts}.
323
+ *
324
+ * Reads past coding-agent conversations (Claude Code, Codex) that already exist
325
+ * on disk. Distinct from `ay tail`, which follows the live PTY log of a process
326
+ * agent-yes spawned; `ay hist` reads the CLI's own durable transcript, so it
327
+ * still works for sessions that already exited.
328
+ */
329
+ const DEFAULT_LIMIT = 6;
330
+ const DEFAULT_SNIPPET = 600;
331
+ const DIM = "\x1B[2m";
332
+ const CYAN = "\x1B[36m";
333
+ const GREEN = "\x1B[32m";
334
+ const RESET = "\x1B[0m";
335
+ function shortTime(ts) {
336
+ if (!ts) return "??:??";
337
+ const d = new Date(ts);
338
+ if (Number.isNaN(d.getTime())) return "??:??";
339
+ const now = Date.now();
340
+ const sameDay = new Date(now).toDateString() === d.toDateString();
341
+ const hhmm = d.toTimeString().slice(0, 5);
342
+ return sameDay ? hhmm : `${d.toISOString().slice(5, 10)} ${hhmm}`;
343
+ }
344
+ /**
345
+ * Collapse a turn to `max` chars, noting how much was withheld.
346
+ *
347
+ * `color` gates the dim tint on that note. Everything else in the renderer
348
+ * already honours it, but this note hardcoded the escapes — so `ay hist | cat`
349
+ * stayed clean only until a turn was long enough to truncate, i.e. the common
350
+ * case. Defaults true so a direct `snippet()` call keeps its old look.
351
+ */
352
+ function snippet(text, max, color = true) {
353
+ const trimmed = text.trim();
354
+ if (max <= 0 || trimmed.length <= max) return trimmed;
355
+ const head = trimmed.slice(0, max);
356
+ const hiddenLines = trimmed.slice(max).split("\n").length;
357
+ const dim = color ? DIM : "";
358
+ const off = color ? RESET : "";
359
+ return `${head}… ${dim}(+${trimmed.length - max} chars, ${hiddenLines} more lines)${off}`;
360
+ }
361
+ function renderRecord(r, opts) {
362
+ const who = r.role === "user" ? "user" : r.source;
363
+ const tint = opts.color ? r.role === "user" ? CYAN : GREEN : "";
364
+ const dim = opts.color ? DIM : "";
365
+ const off = opts.color ? RESET : "";
366
+ const body = snippet(r.text, opts.max, opts.color).split("\n").map((l) => ` ${l}`).join("\n");
367
+ return `${`${tint}${who}${off} ${dim}${shortTime(r.ts)} ${r.sessionId.slice(0, 8)}${off}`}\n${body}\n`;
368
+ }
369
+ async function cmdHist(rest) {
370
+ const argv = await yargs(rest).usage("Usage: ay hist [options]\n\nTail past coding-agent conversations (Claude Code, Codex) from this machine.\nDefaults to sessions for the current directory, newest last.\n\nPagination: rerun with --before <cursor> using the cursor printed at the end.").option("n", {
371
+ type: "number",
372
+ default: DEFAULT_LIMIT,
373
+ description: `Number of turns (default ${DEFAULT_LIMIT}; 0 = no cap)`
374
+ }).option("all", {
375
+ type: "boolean",
376
+ default: false,
377
+ description: "All projects, not just the current directory"
378
+ }).option("cwd", {
379
+ type: "string",
380
+ description: "Scope to this directory instead of $PWD"
381
+ }).option("source", {
382
+ choices: ["claude", "codex"],
383
+ description: "Only one agent's transcripts"
384
+ }).option("role", {
385
+ choices: ["user", "assistant"],
386
+ description: "Only my prompts, or only the agent's replies"
387
+ }).option("before", {
388
+ type: "string",
389
+ description: "Cursor: only turns older than this ISO timestamp"
390
+ }).option("tools", {
391
+ type: "boolean",
392
+ default: false,
393
+ description: "Include tool calls/results and thinking as markers"
394
+ }).option("sidechains", {
395
+ type: "boolean",
396
+ default: false,
397
+ description: "Include Claude sub-agent turns"
398
+ }).option("full", {
399
+ type: "boolean",
400
+ default: false,
401
+ description: "Do not truncate turns"
402
+ }).option("json", {
403
+ type: "boolean",
404
+ default: false,
405
+ description: "JSONL for scripts/agents"
406
+ }).help().parse();
407
+ const cwd = argv.all ? void 0 : argv.cwd ?? process.cwd();
408
+ const page = await histPage({
409
+ cwd,
410
+ limit: Number(argv.n),
411
+ before: argv.before,
412
+ role: argv.role,
413
+ sources: argv.source ? [argv.source] : void 0,
414
+ includeTools: Boolean(argv.tools),
415
+ includeSidechains: Boolean(argv.sidechains)
416
+ });
417
+ if (argv.json) {
418
+ for (const r of page.records) process.stdout.write(JSON.stringify(r) + "\n");
419
+ return page.records.length ? 0 : 1;
420
+ }
421
+ if (!page.records.length) {
422
+ const where = cwd ? `for ${cwd}` : "on this machine";
423
+ process.stderr.write(`no agent conversation history ${where} (scanned ${page.scanned} transcript${page.scanned === 1 ? "" : "s"}).\n` + (cwd ? `try: ay hist --all\n` : ""));
424
+ return 1;
425
+ }
426
+ const color = process.stdout.isTTY === true;
427
+ const max = argv.full ? 0 : DEFAULT_SNIPPET;
428
+ for (const r of page.records) process.stdout.write(renderRecord(r, {
429
+ color,
430
+ max
431
+ }) + "\n");
432
+ if (page.cursor) {
433
+ const dim = color ? DIM : "";
434
+ const off = color ? RESET : "";
435
+ process.stderr.write(`${dim}older: ay hist --before ${page.cursor}${off}\n`);
436
+ }
437
+ return 0;
438
+ }
439
+
440
+ //#endregion
441
+ export { cmdHist };
442
+ //# sourceMappingURL=hist-CoXBBjJt.js.map
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-C6RmYXss.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-ZKWh7obB.js";
2
+ import "./versionChecker-CMOLbEEi.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-nuUnrVcf.js";
4
4
  import { _ as isKnownKind, a as renderTree, b as statesOf, c as monitorHint, d as openStore, f as DONE_STATE, g as initialState, h as canTransition, i as renderDigest, l as CycleError, m as ORPHANED_STATE, o as unblockedTasks, p as LIFECYCLES, r as openBlockers, s as describeBlock, t as reconcileTodos, u as TodoStore, v as nextStates, x as removeControlCharacters, y as requiredGate } from "./todoAutomation-BQRW614S.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
@@ -53,4 +53,4 @@ async function ensureNodeRuntime(which = Bun.which) {
53
53
 
54
54
  //#endregion
55
55
  export { liveEnv as n, ensureNodeRuntime as t };
56
- //# sourceMappingURL=nodeRuntime-vhOBffJ3.js.map
56
+ //# sourceMappingURL=nodeRuntime-CT0w6zsB.js.map
@@ -3,9 +3,9 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-CUMZC53I.js";
5
5
  import "./configShared-DtHMBCl7.js";
6
- import { E as listRecords, F as renderLogTailLines, Q as hostId, X as appendEvent, Z as gcInboxes, at as daemonLockOwnerPath, b as isPidAlive, et as listInboxParents, it as daemonLockDir, nt as readInbox, ot as notifyDir, rt as shouldStealLock, tt as liveWatchers, u as deriveLiveState } from "./subcommands-Cbhtldub.js";
6
+ import { E as listRecords, F as renderLogTailLines, Q as hostId, X as appendEvent, Z as gcInboxes, at as daemonLockOwnerPath, b as isPidAlive, et as listInboxParents, it as daemonLockDir, nt as readInbox, ot as notifyDir, rt as shouldStealLock, tt as liveWatchers, u as deriveLiveState } from "./subcommands-CQPnT5qu.js";
7
7
  import "./webrtcLink-CS4D-3G1.js";
8
- import "./remotes-Cn-wszbK.js";
8
+ import "./remotes-iIvqiFWK.js";
9
9
  import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
10
10
  import { randomUUID } from "node:crypto";
11
11
  import { execFile, execFileSync } from "node:child_process";
@@ -836,4 +836,4 @@ async function ensureDaemon() {
836
836
 
837
837
  //#endregion
838
838
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
839
- //# sourceMappingURL=notifyDaemon-DuKvOHJJ.js.map
839
+ //# sourceMappingURL=notifyDaemon-BL1tmxUK.js.map
@@ -68,4 +68,4 @@ async function offerOpenInBrowser(url, prompt = "Open in browser now? [Y/n] ") {
68
68
 
69
69
  //#endregion
70
70
  export { offerOpenInBrowser };
71
- //# sourceMappingURL=openBrowser-D-aQjh3y.js.map
71
+ //# sourceMappingURL=openBrowser-Cw8hg7ok.js.map
@@ -1,5 +1,5 @@
1
1
  import "./e2e-CUMZC53I.js";
2
2
  import "./webrtcLink-CS4D-3G1.js";
3
- import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-Cn-wszbK.js";
3
+ import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-iIvqiFWK.js";
4
4
 
5
5
  export { cmdRemote };
@@ -86,7 +86,7 @@ async function resolveRemoteSpec(spec) {
86
86
  * The bridge lives for the rest of the process (torn down on `process.exit`).
87
87
  */
88
88
  async function resolveWebrtc(link, keyword) {
89
- const { startWebrtcBridge } = await import("./webrtcRemote-BkZcA5yq.js");
89
+ const { startWebrtcBridge } = await import("./webrtcRemote-zQybN9IR.js");
90
90
  const bridge = await startWebrtcBridge(link);
91
91
  return {
92
92
  url: bridge.baseUrl,
@@ -173,4 +173,4 @@ async function cmdRemote(rest) {
173
173
 
174
174
  //#endregion
175
175
  export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
176
- //# sourceMappingURL=remotes-Cn-wszbK.js.map
176
+ //# sourceMappingURL=remotes-iIvqiFWK.js.map
@@ -1,4 +1,4 @@
1
- import { r as getInstalledPackage } from "./versionChecker-C6RmYXss.js";
1
+ import { r as getInstalledPackage } from "./versionChecker-CMOLbEEi.js";
2
2
  import { execFileSync } from "child_process";
3
3
  import { existsSync, mkdirSync, unlinkSync } from "fs";
4
4
  import { chmod, copyFile } from "fs/promises";
@@ -243,4 +243,4 @@ async function getRustBinary(options = {}) {
243
243
 
244
244
  //#endregion
245
245
  export { findTrayLauncher as n, getRustBinary as r, findSpawnHiddenLauncher as t };
246
- //# sourceMappingURL=rustBinary-CkTiZVJ4.js.map
246
+ //# sourceMappingURL=rustBinary-DSVRhDzO.js.map