@vibe-commit/capture 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +84 -5
  2. package/dist/agent_hooks.d.ts +8 -0
  3. package/dist/agent_hooks.js +31 -1
  4. package/dist/agent_hooks.js.map +1 -1
  5. package/dist/agents/claude_code.d.ts +34 -0
  6. package/dist/agents/claude_code.js +141 -8
  7. package/dist/agents/claude_code.js.map +1 -1
  8. package/dist/agents/codex_cli.d.ts +13 -0
  9. package/dist/agents/codex_cli.js +241 -2
  10. package/dist/agents/codex_cli.js.map +1 -1
  11. package/dist/agents/cursor.d.ts +34 -0
  12. package/dist/agents/cursor.js +115 -1
  13. package/dist/agents/cursor.js.map +1 -1
  14. package/dist/agents/session_listing.d.ts +29 -0
  15. package/dist/agents/session_listing.js +49 -0
  16. package/dist/agents/session_listing.js.map +1 -0
  17. package/dist/agents/types.d.ts +118 -8
  18. package/dist/agents/types.js +17 -3
  19. package/dist/agents/types.js.map +1 -1
  20. package/dist/commands/approval_pending.d.ts +9 -0
  21. package/dist/commands/approval_pending.js +23 -0
  22. package/dist/commands/approval_pending.js.map +1 -0
  23. package/dist/commands/auth.js +31 -11
  24. package/dist/commands/auth.js.map +1 -1
  25. package/dist/commands/backfill.d.ts +160 -0
  26. package/dist/commands/backfill.js +748 -0
  27. package/dist/commands/backfill.js.map +1 -0
  28. package/dist/commands/browser_credential.d.ts +2 -0
  29. package/dist/commands/browser_credential.js +31 -14
  30. package/dist/commands/browser_credential.js.map +1 -1
  31. package/dist/commands/connect.d.ts +137 -0
  32. package/dist/commands/connect.js +203 -109
  33. package/dist/commands/connect.js.map +1 -1
  34. package/dist/commands/connect_picker.d.ts +27 -0
  35. package/dist/commands/connect_picker.js +415 -0
  36. package/dist/commands/connect_picker.js.map +1 -0
  37. package/dist/commands/context.d.ts +16 -0
  38. package/dist/commands/context.js +27 -0
  39. package/dist/commands/context.js.map +1 -1
  40. package/dist/commands/credential_problem.d.ts +15 -4
  41. package/dist/commands/credential_problem.js +29 -22
  42. package/dist/commands/credential_problem.js.map +1 -1
  43. package/dist/commands/device_credential.d.ts +2 -0
  44. package/dist/commands/device_credential.js +16 -14
  45. package/dist/commands/device_credential.js.map +1 -1
  46. package/dist/commands/finalize.d.ts +95 -0
  47. package/dist/commands/finalize.js +249 -74
  48. package/dist/commands/finalize.js.map +1 -1
  49. package/dist/commands/finish_mint.d.ts +2 -6
  50. package/dist/commands/finish_mint.js +10 -2
  51. package/dist/commands/finish_mint.js.map +1 -1
  52. package/dist/commands/report.js +38 -30
  53. package/dist/commands/report.js.map +1 -1
  54. package/dist/commands/scan.d.ts +162 -0
  55. package/dist/commands/scan.js +507 -0
  56. package/dist/commands/scan.js.map +1 -0
  57. package/dist/commands/status.js +119 -22
  58. package/dist/commands/status.js.map +1 -1
  59. package/dist/commands/status_hooks.d.ts +14 -0
  60. package/dist/commands/status_hooks.js +81 -0
  61. package/dist/commands/status_hooks.js.map +1 -0
  62. package/dist/commands/status_machine.d.ts +18 -0
  63. package/dist/commands/status_machine.js +149 -0
  64. package/dist/commands/status_machine.js.map +1 -0
  65. package/dist/commands/why.js +44 -31
  66. package/dist/commands/why.js.map +1 -1
  67. package/dist/consent.d.ts +111 -0
  68. package/dist/consent.js +203 -1
  69. package/dist/consent.js.map +1 -1
  70. package/dist/copy/error_codes.d.ts +222 -0
  71. package/dist/copy/error_codes.js +105 -0
  72. package/dist/copy/error_codes.js.map +1 -0
  73. package/dist/copy/index.d.ts +2 -2
  74. package/dist/copy/index.js +2 -2
  75. package/dist/copy/index.js.map +1 -1
  76. package/dist/copy/strings.d.ts +497 -29
  77. package/dist/copy/strings.js +601 -35
  78. package/dist/copy/strings.js.map +1 -1
  79. package/dist/copy/time.d.ts +11 -0
  80. package/dist/copy/time.js +18 -0
  81. package/dist/copy/time.js.map +1 -1
  82. package/dist/credential.d.ts +5 -1
  83. package/dist/credential.js +2 -2
  84. package/dist/credential.js.map +1 -1
  85. package/dist/git.d.ts +43 -0
  86. package/dist/git.js +95 -0
  87. package/dist/git.js.map +1 -1
  88. package/dist/hooks/entry.d.ts +18 -1
  89. package/dist/hooks/entry.js +110 -13
  90. package/dist/hooks/entry.js.map +1 -1
  91. package/dist/hooks/post_commit.js +3 -3
  92. package/dist/hooks/post_commit.js.map +1 -1
  93. package/dist/hooks/post_rewrite.js +6 -4
  94. package/dist/hooks/post_rewrite.js.map +1 -1
  95. package/dist/hooks/session_start.d.ts +93 -0
  96. package/dist/hooks/session_start.js +199 -0
  97. package/dist/hooks/session_start.js.map +1 -0
  98. package/dist/index.d.ts +1 -1
  99. package/dist/index.js +56 -8
  100. package/dist/index.js.map +1 -1
  101. package/dist/install.d.ts +57 -0
  102. package/dist/install.js +54 -20
  103. package/dist/install.js.map +1 -1
  104. package/dist/json.d.ts +32 -9
  105. package/dist/json.js +35 -8
  106. package/dist/json.js.map +1 -1
  107. package/dist/machine_state.d.ts +52 -0
  108. package/dist/machine_state.js +63 -0
  109. package/dist/machine_state.js.map +1 -0
  110. package/dist/oauth/session.d.ts +1 -0
  111. package/dist/org_pending.d.ts +52 -0
  112. package/dist/org_pending.js +109 -0
  113. package/dist/org_pending.js.map +1 -0
  114. package/dist/paths.d.ts +50 -0
  115. package/dist/paths.js +64 -0
  116. package/dist/paths.js.map +1 -1
  117. package/dist/picker.d.ts +180 -0
  118. package/dist/picker.js +477 -0
  119. package/dist/picker.js.map +1 -0
  120. package/dist/picker_checklist.d.ts +125 -0
  121. package/dist/picker_checklist.js +375 -0
  122. package/dist/picker_checklist.js.map +1 -0
  123. package/dist/policy.d.ts +54 -0
  124. package/dist/policy.js +47 -0
  125. package/dist/policy.js.map +1 -1
  126. package/dist/post.d.ts +50 -16
  127. package/dist/post.js +265 -58
  128. package/dist/post.js.map +1 -1
  129. package/dist/state.d.ts +6 -0
  130. package/dist/state.js +38 -1
  131. package/dist/state.js.map +1 -1
  132. package/dist/system_message.d.ts +8 -1
  133. package/dist/system_message.js +19 -1
  134. package/dist/system_message.js.map +1 -1
  135. package/dist/term.d.ts +28 -2
  136. package/dist/term.js +23 -2
  137. package/dist/term.js.map +1 -1
  138. package/dist/verb_help.d.ts +13 -0
  139. package/dist/verb_help.js +40 -0
  140. package/dist/verb_help.js.map +1 -0
  141. package/dist/version.d.ts +1 -1
  142. package/dist/version.js +1 -1
  143. package/package.json +5 -9
@@ -11,7 +11,11 @@
11
11
  * measured by this task.** They are recorded as the brief stated them so a later
12
12
  * reader can check them against the product rather than against this file.
13
13
  */
14
- import { codexHooksPath, codexTranscriptRoot, isTranscriptFile } from "../paths.js";
14
+ import { readdirSync, realpathSync } from "node:fs";
15
+ import { createRequire } from "node:module";
16
+ import { join } from "node:path";
17
+ import { CODEX_INDEX_FILE, codexHooksPath, codexIndexDir, codexTranscriptRoot, isInside, isTranscriptFile, } from "../paths.js";
18
+ import { readEntries, sessionFile } from "./session_listing.js";
15
19
  /**
16
20
  * `SessionEnd` is the tight one — 3 s registered, 2.2 s client.
17
21
  *
@@ -24,6 +28,226 @@ const CODEX_SESSION_END_CLIENT_MS = 2_200;
24
28
  /** `Stop` and `PreCompact` are registered far wider — 30 s, client 8 s. */
25
29
  const CODEX_TURN_REGISTERED_MS = 30_000;
26
30
  const CODEX_TURN_CLIENT_MS = 8_000;
31
+ /**
32
+ * `SessionStart` takes the turn clocks, with no fast path of its own.
33
+ *
34
+ * ⛔ **A logged shortcut** — gstack-shortcut(dec-e650afbf), eng review D7 · B.
35
+ * The ceiling: a hung `SessionStart` can hold a session's start up by as long as
36
+ * the REGISTERED timeout (30 s here). Upgrade when a measurement or a user report
37
+ * shows session-start delay, or before slice 2's grants fetch lands in this handler.
38
+ */
39
+ const CODEX_SESSION_START_REGISTERED_MS = CODEX_TURN_REGISTERED_MS; // gstack-shortcut(dec-e650afbf)
40
+ const CODEX_SESSION_START_CLIENT_MS = CODEX_TURN_CLIENT_MS; // gstack-shortcut(dec-e650afbf)
41
+ /**
42
+ * ⛔ **THE FOUR COLUMNS, AND NO OTHER — `D11 · A`, Alexander's approved read.**
43
+ *
44
+ * The thread index also holds `first_user_message`, `title`, `preview` and more,
45
+ * which are CONTENT. Selecting one would turn a metadata read into a transcript
46
+ * read, so the statement names exactly these four and `test/scan-codex-index.test.ts`
47
+ * asserts the SQL carries nothing else. ⚠ **`archived` is not here either, on
48
+ * purpose** (VQ, 2026-10-24): an archived thread's `rollout_path` is decided by
49
+ * the admission below, and a fifth column would change the approved read surface.
50
+ */
51
+ export const CODEX_INDEX_COLUMNS = ["cwd", "git_origin_url", "rollout_path", "created_at"];
52
+ /** The one statement the adapter runs against a thread row. */
53
+ export const CODEX_INDEX_SQL = `SELECT ${CODEX_INDEX_COLUMNS.join(", ")} FROM threads`;
54
+ /** How long a locked index is waited for before the adapter gives up (eng review D2). */
55
+ const CODEX_BUSY_TIMEOUT_MS = 1_000;
56
+ /**
57
+ * `node:sqlite`, loaded here and nowhere else, with ITS warning and only its
58
+ * warning dropped — eng review D2 · A.
59
+ *
60
+ * Node 22 prints `ExperimentalWarning: SQLite is an experimental feature…` to
61
+ * stderr the first time the module loads, which would land mid-screen in the
62
+ * picker and breaks DESIGN.md §13's output rules. `process.emitWarning` is
63
+ * wrapped for the duration of the one `require` and restored in a `finally`;
64
+ * any OTHER warning, in that window or after it, still prints. Measured on
65
+ * node 22.17.0.
66
+ *
67
+ * ⚠ **A `require` and not a dynamic `import()`**, so the whole adapter stays
68
+ * synchronous like every other lister (`scan` is a chain of `execFileSync`
69
+ * anyway) and the wrapped window is exactly the load, not a promise turn that
70
+ * something else could emit a warning inside. Lazy: a machine with no Codex
71
+ * never loads the module at all.
72
+ */
73
+ function loadSqlite() {
74
+ const original = process.emitWarning;
75
+ process.emitWarning = ((warning, ...rest) => {
76
+ const type = typeof warning === "string" ? rest[0] : warning.name;
77
+ const message = typeof warning === "string" ? warning : warning.message;
78
+ if (type === "ExperimentalWarning" && message.startsWith("SQLite"))
79
+ return;
80
+ return original.call(process, warning, ...rest);
81
+ });
82
+ try {
83
+ return createRequire(import.meta.url)("node:sqlite");
84
+ }
85
+ finally {
86
+ process.emitWarning = original;
87
+ }
88
+ }
89
+ /** Does the `threads` table carry all four columns? A bare name test, nothing deeper. */
90
+ function hasIndexColumns(db) {
91
+ const present = new Set(db.prepare("PRAGMA table_info(threads)").all().map((c) => String(c.name)));
92
+ return CODEX_INDEX_COLUMNS.every((column) => present.has(column));
93
+ }
94
+ /** A locked database is a different line from an unrecognised one. */
95
+ function readFailure(err) {
96
+ return /locked|busy/i.test(err instanceof Error ? err.message : "")
97
+ ? "index locked"
98
+ : "unreadable";
99
+ }
100
+ /**
101
+ * Rollout files under `sessions/` that NO index row names — counted, never opened.
102
+ *
103
+ * Open Question 6's text, as VQ ruled (PR 1 ruling 5c): an index which cannot see a
104
+ * session is a thing to REPORT, not to guess about. The walk lists directory entries — names
105
+ * and file types, nothing else — and counts each regular `.jsonl`; a symlink is
106
+ * not a file here (`Dirent.isFile` is false for it).
107
+ *
108
+ * Compared as REALPATHS on both sides, because the index stores the path Codex
109
+ * wrote and the walk builds its own from `home`; a symlinked home would otherwise
110
+ * make every rollout look unindexed. A row whose rollout has gone cannot be
111
+ * resolved and names nothing, which is correct: it is not on disk to be matched.
112
+ */
113
+ function countUnindexed(root, rolloutPaths) {
114
+ const named = new Set();
115
+ for (const path of rolloutPaths) {
116
+ try {
117
+ named.add(realpathSync(path));
118
+ }
119
+ catch {
120
+ // Gone from disk: it names no file that the walk could find.
121
+ }
122
+ }
123
+ let base;
124
+ try {
125
+ base = realpathSync(root);
126
+ }
127
+ catch {
128
+ return 0;
129
+ }
130
+ let count = 0;
131
+ const visit = (dir) => {
132
+ let entries;
133
+ try {
134
+ entries = readEntries(dir);
135
+ }
136
+ catch {
137
+ return;
138
+ }
139
+ for (const entry of entries ?? []) {
140
+ const path = join(dir, entry.name);
141
+ if (entry.isDirectory())
142
+ visit(path);
143
+ else if (entry.isFile() && entry.name.endsWith(".jsonl") && !named.has(path))
144
+ count += 1;
145
+ }
146
+ };
147
+ visit(base);
148
+ return count;
149
+ }
150
+ /**
151
+ * Codex's sessions, from its thread index — `D11 · A`, eng review D2, `scan`.
152
+ *
153
+ * Codex keeps rollouts DATE-keyed (`sessions/YYYY/MM/DD/`), so a folder walk
154
+ * cannot say which repo one belongs to; its `threads` table can, because it
155
+ * records the real `cwd`. That row is the whole reason this adapter exists and
156
+ * the reason a Codex row is never ambiguous.
157
+ *
158
+ * ## ⛔ What is read, and how
159
+ *
160
+ * - **exactly `state_5.sqlite`**, and any other `state_N` is `unrecognised
161
+ * index` (Open Question 6; see `codexIndexDir`).
162
+ * - **`readOnly`, never `immutable`.** Codex keeps everything since its last
163
+ * checkpoint in the `-wal` file (on the author's machine, every thread since
164
+ * Sep 30), and an `immutable` open ignores it.
165
+ * - **the four columns only** (`CODEX_INDEX_SQL`), after a `PRAGMA table_info`
166
+ * check, and a 1000 ms busy timeout. Any failure is this agent's
167
+ * `unreadable` answer, so the other two agents' rows are untouched.
168
+ *
169
+ * ## ⛔ The rollout file is `lstat`ed, never opened
170
+ *
171
+ * A row is admitted only if its `rollout_path` lies under the sessions root and
172
+ * is a regular file there. That is also what decides an ARCHIVED thread, whose
173
+ * rollout is not under `sessions/`. The row's `git_origin_url` is carried but is
174
+ * not the slug source: the scan names a repo from the CLONE (`resolveRepoSlug`),
175
+ * so there is one normalisation and not two.
176
+ */
177
+ function codexSessionSource(home, env) {
178
+ const dir = codexIndexDir(home, env);
179
+ const file = join(dir, CODEX_INDEX_FILE);
180
+ let present;
181
+ try {
182
+ present = readdirSync(dir);
183
+ }
184
+ catch (err) {
185
+ const code = err.code;
186
+ return code === "ENOENT" || code === "ENOTDIR"
187
+ ? { kind: "rows", rows: [], unindexed: 0 }
188
+ : { kind: "unreadable", reason: "unreadable" };
189
+ }
190
+ if (!present.includes(CODEX_INDEX_FILE)) {
191
+ // A Codex that keeps its index under another generation's name is a store
192
+ // this client was not checked against. One with no index at all has simply
193
+ // never recorded a thread — and its rollouts, if any, are all unindexed.
194
+ return present.some((name) => /^state_\d+\.sqlite$/.test(name))
195
+ ? { kind: "unreadable", reason: "unrecognised index" }
196
+ : { kind: "rows", rows: [], unindexed: countUnindexed(codexTranscriptRoot(home, env), []) };
197
+ }
198
+ let sqlite;
199
+ try {
200
+ sqlite = loadSqlite();
201
+ }
202
+ catch {
203
+ return { kind: "unreadable", reason: "unreadable" };
204
+ }
205
+ let db = null;
206
+ try {
207
+ // ⚠ `readOnly` stops THIS CODE writing the index and does not stop SQLite creating its own
208
+ // `-wal`/`-shm` files beside it, which any reader of a WAL database whose writer has closed does
209
+ // (measured by VQ on node 22.17, PR 1 F1). Nothing is written to `state_5.sqlite` itself.
210
+ db = new sqlite.DatabaseSync(file, { readOnly: true });
211
+ db.exec(`PRAGMA busy_timeout = ${CODEX_BUSY_TIMEOUT_MS}`);
212
+ if (!hasIndexColumns(db))
213
+ return { kind: "unreadable", reason: "unrecognised index" };
214
+ const root = codexTranscriptRoot(home, env);
215
+ const rows = [];
216
+ const named = [];
217
+ for (const row of db.prepare(CODEX_INDEX_SQL).all()) {
218
+ const { cwd, git_origin_url: origin, rollout_path: rollout, created_at: created } = row;
219
+ if (typeof cwd !== "string" || typeof rollout !== "string")
220
+ continue;
221
+ // Every path an index row names counts as INDEXED, admitted or not: an archived thread's rollout
222
+ // is not "missing from the index", it is simply not capturable (`D11 · A`).
223
+ named.push(rollout);
224
+ if (!isInside(root, rollout) || !isTranscriptFile(root, rollout))
225
+ continue;
226
+ const session = sessionFile(rollout);
227
+ if (session === null)
228
+ continue;
229
+ rows.push({
230
+ cwd,
231
+ origin: typeof origin === "string" ? origin : null,
232
+ // `created_at` is whole SECONDS (measured), the rest of this client's
233
+ // times are milliseconds.
234
+ session: typeof created === "number" ? { ...session, startedMs: created * 1000 } : session,
235
+ });
236
+ }
237
+ return { kind: "rows", rows, unindexed: countUnindexed(root, named) };
238
+ }
239
+ catch (err) {
240
+ return { kind: "unreadable", reason: readFailure(err) };
241
+ }
242
+ finally {
243
+ try {
244
+ db?.close();
245
+ }
246
+ catch {
247
+ // Closing a read-only handle that failed to open is not a second finding.
248
+ }
249
+ }
250
+ }
27
251
  export const CODEX_CLI = {
28
252
  id: "codex-cli",
29
253
  transport: "ndjson",
@@ -46,7 +270,17 @@ export const CODEX_CLI = {
46
270
  // against three throwaway `CODEX_HOME`s settled it: `SessionEnd` is recognised
47
271
  // and says so by clamping, while `session_end` and a bogus key produce NO
48
272
  // diagnostic — a file that parses, registers nothing, and reports success.
49
- eventNames: { Stop: "Stop", PreCompact: "PreCompact", SessionEnd: "SessionEnd" },
273
+ // `SessionStart` is in that schema list too, spelled the same way, and MEASURED
274
+ // live (Open Question 1, Codex 0.148.0): it fires before the first prompt — in
275
+ // the TUI lazily, when the first prompt is submitted — and accepts a
276
+ // `systemMessage`. ⚠ Those runs were under cmux's `--dangerously-bypass-hook-trust`;
277
+ // stock Codex >= 0.131.0 runs a user-level hook only after the user trusts it.
278
+ eventNames: {
279
+ Stop: "Stop",
280
+ PreCompact: "PreCompact",
281
+ SessionEnd: "SessionEnd",
282
+ SessionStart: "SessionStart",
283
+ },
50
284
  // ⛔ `~/.codex/hooks.json`, honouring `CODEX_HOME` — MEASURED, since every
51
285
  // probe in this unit drove a real `codex` through a throwaway `CODEX_HOME`
52
286
  // and it read the `hooks.json` there. The NESTED shape, also measured: a
@@ -66,6 +300,10 @@ export const CODEX_CLI = {
66
300
  registeredTimeoutMs: CODEX_SESSION_END_REGISTERED_MS,
67
301
  clientBudgetMs: CODEX_SESSION_END_CLIENT_MS,
68
302
  },
303
+ SessionStart: {
304
+ registeredTimeoutMs: CODEX_SESSION_START_REGISTERED_MS,
305
+ clientBudgetMs: CODEX_SESSION_START_CLIENT_MS,
306
+ },
69
307
  },
70
308
  // The delegated path ARRIVES on stdin as `agent_transcript_path` — there is no
71
309
  // directory to glob. Nothing parses it yet; the discriminator is here so the
@@ -79,6 +317,7 @@ export const CODEX_CLI = {
79
317
  // of a measurement, not as a fact about the agent. Measuring it is a change to
80
318
  // this line and nothing else.
81
319
  sessionIdFromEnv: () => null,
320
+ sessionSource: codexSessionSource,
82
321
  // ⛔ NO `locateCurrentTranscript`, and the omission is the decision. Finding
83
322
  // the repository would mean reading a `cwd` out of the transcript's own
84
323
  // records — ANALYSIS, which D60 §D6 forbids this client. `connect` therefore
@@ -1 +1 @@
1
- {"version":3,"file":"codex_cli.js","sourceRoot":"","sources":["../../src/agents/codex_cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpF;;;;;;GAMG;AACH,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAC9C,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAE1C,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC,MAAM,CAAC,MAAM,SAAS,GAAsB;IAC1C,EAAE,EAAE,WAAW;IACf,SAAS,EAAE,QAAQ;IACnB,cAAc,EAAE,mBAAmB;IACnC,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,+DAA+D;IAC/D,UAAU,EAAE,gBAAgB;IAC5B,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,+EAA+E;IAC/E,mEAAmE;IACnE,EAAE;IACF,kEAAkE;IAClE,4EAA4E;IAC5E,+EAA+E;IAC/E,+EAA+E;IAC/E,0EAA0E;IAC1E,2EAA2E;IAC3E,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE;IAChF,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,gEAAgE;IAChE,UAAU,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE;IACrD,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,+BAA+B;YACpD,cAAc,EAAE,2BAA2B;SAC5C;KACF;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,oEAAoE;IACpE,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,WAAW;IACtB,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,8BAA8B;IAC9B,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,oDAAoD;CACrD,CAAC"}
1
+ {"version":3,"file":"codex_cli.js","sourceRoot":"","sources":["../../src/agents/codex_cli.ts"],"names":[],"mappings":"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"}
@@ -18,4 +18,38 @@
18
18
  * difference is marked on each member below rather than left to a reader.
19
19
  */
20
20
  import type { TranscriptDialect } from "./types.js";
21
+ /**
22
+ * The longest folder name Cursor writes verbatim.
23
+ *
24
+ * ⛔ **MEASURED (Open Question 3, VS, 2026-10-24), with `cursor-agent`, which
25
+ * creates the project folder even when it is not logged in.** A name up to 61
26
+ * characters is kept; a longer one becomes its first 53 characters, `-`, and 7
27
+ * hex characters (the hash varied with the input, so it is not a constant).
28
+ */
29
+ export declare const CURSOR_NAME_LIMIT = 61;
30
+ /**
31
+ * The folder name Cursor gives a workspace path — Open Question 3.
32
+ *
33
+ * ⛔ **THIS IS NOT CLAUDE CODE'S RULE, and the difference is the measurement.**
34
+ * Every RUN of non-alphanumerics, a literal `--` included, collapses to ONE `-`;
35
+ * leading and trailing separators are dropped (so `/tmp/q/trail.` is `…-trail`
36
+ * and there is no leading `-`); case is kept. An emoji and `é'` each gave a single
37
+ * dash where Claude Code gives two.
38
+ *
39
+ * ⚠ **Which makes Cursor's names collide far more:** `a-b`, `a.b`, `a--b` and
40
+ * `a.b.` are all one folder. The scan's walk encodes every directory entry with
41
+ * THIS function and matches on the result, so the collapsing applies to every
42
+ * candidate rather than being assumed away.
43
+ */
44
+ export declare function encodeProjectPath(path: string): string;
45
+ /**
46
+ * Is this folder name one Cursor cut and hashed? See `CURSOR_NAME_LIMIT`.
47
+ *
48
+ * ⚠ **A hashed name is always exactly 61 long, and so is a verbatim name that
49
+ * happens to be 61 long** — length alone cannot tell them apart. The test is the
50
+ * hash's shape too (`-` and 7 hex). A verbatim 61-character name that ends the
51
+ * same way is read as hashed, which is the safe error: it is skipped and
52
+ * counted, never matched to a path it might not be.
53
+ */
54
+ export declare function isHashedProjectName(name: string): boolean;
21
55
  export declare const CURSOR: TranscriptDialect;
@@ -17,7 +17,9 @@
17
17
  * ⚠ **What is DATA here and what is MEASURED is not the same set**, and the
18
18
  * difference is marked on each member below rather than left to a reader.
19
19
  */
20
+ import { join } from "node:path";
20
21
  import { cursorHooksPath, cursorTranscriptRoot, isCursorTranscript } from "../paths.js";
22
+ import { readEntries, sessionFile, subdirectories } from "./session_listing.js";
21
23
  /**
22
24
  * ⭐ **MEASURED. `D5`'s SECOND HALF IS ANSWERED (`TODOS[130]`), AND THE ANSWER IS
23
25
  * THAT CURSOR IMPOSES NO NUMBER OF ITS OWN.**
@@ -79,6 +81,97 @@ import { cursorHooksPath, cursorTranscriptRoot, isCursorTranscript } from "../pa
79
81
  */
80
82
  const CURSOR_REGISTERED_MS = 3_000;
81
83
  const CURSOR_CLIENT_MS = 2_200;
84
+ /**
85
+ * The longest folder name Cursor writes verbatim.
86
+ *
87
+ * ⛔ **MEASURED (Open Question 3, VS, 2026-10-24), with `cursor-agent`, which
88
+ * creates the project folder even when it is not logged in.** A name up to 61
89
+ * characters is kept; a longer one becomes its first 53 characters, `-`, and 7
90
+ * hex characters (the hash varied with the input, so it is not a constant).
91
+ */
92
+ export const CURSOR_NAME_LIMIT = 61;
93
+ /**
94
+ * The folder name Cursor gives a workspace path — Open Question 3.
95
+ *
96
+ * ⛔ **THIS IS NOT CLAUDE CODE'S RULE, and the difference is the measurement.**
97
+ * Every RUN of non-alphanumerics, a literal `--` included, collapses to ONE `-`;
98
+ * leading and trailing separators are dropped (so `/tmp/q/trail.` is `…-trail`
99
+ * and there is no leading `-`); case is kept. An emoji and `é'` each gave a single
100
+ * dash where Claude Code gives two.
101
+ *
102
+ * ⚠ **Which makes Cursor's names collide far more:** `a-b`, `a.b`, `a--b` and
103
+ * `a.b.` are all one folder. The scan's walk encodes every directory entry with
104
+ * THIS function and matches on the result, so the collapsing applies to every
105
+ * candidate rather than being assumed away.
106
+ */
107
+ export function encodeProjectPath(path) {
108
+ return path.replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
109
+ }
110
+ /**
111
+ * Is this folder name one Cursor cut and hashed? See `CURSOR_NAME_LIMIT`.
112
+ *
113
+ * ⚠ **A hashed name is always exactly 61 long, and so is a verbatim name that
114
+ * happens to be 61 long** — length alone cannot tell them apart. The test is the
115
+ * hash's shape too (`-` and 7 hex). A verbatim 61-character name that ends the
116
+ * same way is read as hashed, which is the safe error: it is skipped and
117
+ * counted, never matched to a path it might not be.
118
+ */
119
+ export function isHashedProjectName(name) {
120
+ return name.length === CURSOR_NAME_LIMIT && /-[0-9a-f]{7}$/.test(name);
121
+ }
122
+ /**
123
+ * The folder Cursor uses for a window with no workspace open. It is not a path,
124
+ * so it can never resolve to one and is not worth a "folder no longer exists"
125
+ * count. Seen by NAME on the author's machine; nothing in it was read.
126
+ */
127
+ const CURSOR_NO_WORKSPACE = "empty-window";
128
+ /**
129
+ * Cursor's sessions, by folder — `D11 · A`, `scan`.
130
+ *
131
+ * `<root>/<encoded workspace>/agent-transcripts/<cid>/<cid>.jsonl`. Only the
132
+ * conversation's own file counts: `<cid>/subagents/…` is delegated work that
133
+ * belongs to the parent (`delegatedTranscripts: "announce"`). A flat
134
+ * `agent-transcripts/<cid>.jsonl` is accepted too, because `isCursorTranscript`
135
+ * admits any `.jsonl` at that depth and a count that disagreed with the gate
136
+ * would be a count of nothing.
137
+ *
138
+ * ⛔ Names and times only; see `claudeSessionSource` for the rule and its reason.
139
+ */
140
+ function cursorSessionSource(home) {
141
+ const root = cursorTranscriptRoot(home);
142
+ const folders = [];
143
+ try {
144
+ for (const folder of subdirectories(root)) {
145
+ if (folder.name === CURSOR_NO_WORKSPACE)
146
+ continue;
147
+ const transcripts = join(folder.path, "agent-transcripts");
148
+ const sessions = [];
149
+ for (const entry of readEntries(transcripts) ?? []) {
150
+ const file = entry.isDirectory()
151
+ ? join(transcripts, entry.name, `${entry.name}.jsonl`)
152
+ : entry.name.endsWith(".jsonl")
153
+ ? join(transcripts, entry.name)
154
+ : null;
155
+ const session = file === null ? null : sessionFile(file);
156
+ if (session !== null)
157
+ sessions.push(session);
158
+ }
159
+ if (sessions.length > 0)
160
+ folders.push({ name: folder.name, sessions });
161
+ }
162
+ }
163
+ catch {
164
+ return { kind: "unreadable", reason: "unreadable" };
165
+ }
166
+ // Cursor keeps no record of the folders it has run in, so there is no second signal.
167
+ return {
168
+ kind: "folders",
169
+ folders,
170
+ encode: encodeProjectPath,
171
+ isHashed: isHashedProjectName,
172
+ recorded: [],
173
+ };
174
+ }
82
175
  export const CURSOR = {
83
176
  id: "cursor",
84
177
  transport: "ndjson",
@@ -103,7 +196,22 @@ export const CURSOR = {
103
196
  // ⛔ Against the client's `SessionEnd`, `sessionEnd` matched NOTHING — not an
104
197
  // error, a `false` — so a Cursor session took no settle on its final turn and
105
198
  // lost it permanently. That is the whole of the bug.
106
- eventNames: { Stop: "stop", PreCompact: "preCompact", SessionEnd: "sessionEnd" },
199
+ //
200
+ // ⛔ **`sessionStart` is STATIC, NOT OBSERVED** (Open Question 1, 2026-10-24).
201
+ // `cursor-agent` was not logged in, so no session ran; the spelling and the
202
+ // payload (`conversation_id`, `generation_id`, `model`, `is_background_agent`,
203
+ // `composer_mode`) come from the CLI bundle 2026.08.11-e8db854, which runs it
204
+ // for a NEW chat before the first turn. It reads `env` and `additional_context`
205
+ // from the output — and `additional_context` goes into the agent's context and
206
+ // is NOT shown to the user. There is no user-visible field, so this client
207
+ // never uses it to carry a notice: Cursor users learn the state from `status`,
208
+ // and the first-`Stop` install is kept as the fallback for exactly this reason.
209
+ eventNames: {
210
+ Stop: "stop",
211
+ PreCompact: "preCompact",
212
+ SessionEnd: "sessionEnd",
213
+ SessionStart: "sessionStart",
214
+ },
107
215
  // ⛔ `~/.cursor/hooks.json`, and the `cursor` SHAPE — the two are a pair.
108
216
  // Read out of the shipped bundle's validator: a top-level numeric `version`
109
217
  // is REQUIRED, entries are a FLAT array of hook scripts each needing a
@@ -125,6 +233,11 @@ export const CURSOR = {
125
233
  registeredTimeoutMs: CURSOR_REGISTERED_MS,
126
234
  clientBudgetMs: CURSOR_CLIENT_MS,
127
235
  },
236
+ // gstack-shortcut(dec-e650afbf): `Stop`'s clocks, no fast path (eng review D7 · B).
237
+ SessionStart: {
238
+ registeredTimeoutMs: CURSOR_REGISTERED_MS, // gstack-shortcut(dec-e650afbf)
239
+ clientBudgetMs: CURSOR_CLIENT_MS, // gstack-shortcut(dec-e650afbf)
240
+ },
128
241
  },
129
242
  // ⛔ DECLARED EXPLICITLY, and it is load-bearing now in a way it was not
130
243
  // before (D177 §2, §9). `subagentsDir()` would compute
@@ -142,6 +255,7 @@ export const CURSOR = {
142
255
  // measurement, not as a fact about the agent. Measuring it is a change to this
143
256
  // line and nothing else.
144
257
  sessionIdFromEnv: () => null,
258
+ sessionSource: (home) => cursorSessionSource(home),
145
259
  // ⛔ NO `locateCurrentTranscript`, and the omission is the decision — the same
146
260
  // one Codex made. Finding the conversation for THIS project would mean munging
147
261
  // a workspace path this client is never told, or reading a `cwd` out of the
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGxF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,oBAAoB,GAAG,KAAK,CAAC;AACnC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,MAAM,CAAC,MAAM,MAAM,GAAsB;IACvC,EAAE,EAAE,QAAQ;IACZ,SAAS,EAAE,QAAQ;IACnB,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,uDAAuD;IACvD,cAAc,EAAE,oBAAoB;IACpC,wEAAwE;IACxE,uEAAuE;IACvE,UAAU,EAAE,kBAAkB;IAC9B,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,iEAAiE;IACjE,+EAA+E;IAC/E,mEAAmE;IACnE,EAAE;IACF,8EAA8E;IAC9E,4EAA4E;IAC5E,EAAE;IACF,6EAA6E;IAC7E,8EAA8E;IAC9E,qDAAqD;IACrD,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE;IAChF,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,6EAA6E;IAC7E,2EAA2E;IAC3E,uDAAuD;IACvD,yEAAyE;IACzE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;IACtE,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;KACF;IACD,wEAAwE;IACxE,uDAAuD;IACvD,+EAA+E;IAC/E,+EAA+E;IAC/E,8EAA8E;IAC9E,8EAA8E;IAC9E,+DAA+D;IAC/D,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,QAAQ;IACnB,+EAA+E;IAC/E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,yBAAyB;IACzB,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,8EAA8E;IAC9E,+EAA+E;IAC/E,4EAA4E;IAC5E,6EAA6E;IAC7E,0CAA0C;CAC3C,CAAC"}
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Listing a folder-keyed agent's sessions — `D11 · A`, `scan`.
3
+ *
4
+ * The two folder-keyed dialects (Claude Code, Cursor) differ in how a path is
5
+ * encoded and where a session sits under the folder, and agree on everything
6
+ * this file does: read directory ENTRIES, `lstat` a candidate for its time, and
7
+ * never open it.
8
+ *
9
+ * ⛔ **`lstat`, REGULAR FILES ONLY.** A symlink named `x.jsonl` is not a session
10
+ * (Cursor's gate resolves for exactly that reason), and a FIFO or socket under a
11
+ * root this client does not own is a thing a later `readFileSync` hangs on or
12
+ * throws on. A listing that only ever `lstat`s cannot be made to block, which is
13
+ * the property `scan` needs from the one command that walks the whole root.
14
+ */
15
+ import { type Dirent } from "node:fs";
16
+ import type { SourceSession } from "./types.js";
17
+ /**
18
+ * The entries of a directory, `null` when it is simply not there, and a THROW
19
+ * for anything else (permissions, I/O) — the caller turns a throw into the
20
+ * agent's `unreadable` answer, so an absent store and a broken one stay apart.
21
+ */
22
+ export declare function readEntries(dir: string): Dirent[] | null;
23
+ /** One regular file's times, or null for anything that is not one. Never opens it. */
24
+ export declare function sessionFile(path: string): SourceSession | null;
25
+ /** `<dir>/<name>` for each entry that is a real directory (a symlink is not one). */
26
+ export declare function subdirectories(dir: string): {
27
+ name: string;
28
+ path: string;
29
+ }[];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Listing a folder-keyed agent's sessions — `D11 · A`, `scan`.
3
+ *
4
+ * The two folder-keyed dialects (Claude Code, Cursor) differ in how a path is
5
+ * encoded and where a session sits under the folder, and agree on everything
6
+ * this file does: read directory ENTRIES, `lstat` a candidate for its time, and
7
+ * never open it.
8
+ *
9
+ * ⛔ **`lstat`, REGULAR FILES ONLY.** A symlink named `x.jsonl` is not a session
10
+ * (Cursor's gate resolves for exactly that reason), and a FIFO or socket under a
11
+ * root this client does not own is a thing a later `readFileSync` hangs on or
12
+ * throws on. A listing that only ever `lstat`s cannot be made to block, which is
13
+ * the property `scan` needs from the one command that walks the whole root.
14
+ */
15
+ import { lstatSync, readdirSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ /**
18
+ * The entries of a directory, `null` when it is simply not there, and a THROW
19
+ * for anything else (permissions, I/O) — the caller turns a throw into the
20
+ * agent's `unreadable` answer, so an absent store and a broken one stay apart.
21
+ */
22
+ export function readEntries(dir) {
23
+ try {
24
+ return readdirSync(dir, { withFileTypes: true });
25
+ }
26
+ catch (err) {
27
+ const code = err.code;
28
+ if (code === "ENOENT" || code === "ENOTDIR")
29
+ return null;
30
+ throw err;
31
+ }
32
+ }
33
+ /** One regular file's times, or null for anything that is not one. Never opens it. */
34
+ export function sessionFile(path) {
35
+ try {
36
+ const stat = lstatSync(path);
37
+ return stat.isFile() ? { path, mtimeMs: stat.mtimeMs } : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /** `<dir>/<name>` for each entry that is a real directory (a symlink is not one). */
44
+ export function subdirectories(dir) {
45
+ return (readEntries(dir) ?? [])
46
+ .filter((entry) => entry.isDirectory())
47
+ .map((entry) => ({ name: entry.name, path: join(dir, entry.name) }));
48
+ }
49
+ //# sourceMappingURL=session_listing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_listing.js","sourceRoot":"","sources":["../../src/agents/session_listing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACzD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;SACtC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,CAAC"}