aicodeman 1.26.0 → 1.26.2

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 (131) hide show
  1. package/README.md +1 -0
  2. package/dist/codex-transcript.d.ts +84 -0
  3. package/dist/codex-transcript.d.ts.map +1 -0
  4. package/dist/codex-transcript.js +341 -0
  5. package/dist/codex-transcript.js.map +1 -0
  6. package/dist/config/buffer-limits.d.ts +20 -0
  7. package/dist/config/buffer-limits.d.ts.map +1 -1
  8. package/dist/config/buffer-limits.js +42 -0
  9. package/dist/config/buffer-limits.js.map +1 -1
  10. package/dist/config/cli-registry/schema.d.ts +4 -0
  11. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  12. package/dist/config/cli-registry/schema.js +15 -1
  13. package/dist/config/cli-registry/schema.js.map +1 -1
  14. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  15. package/dist/config/cli-registry/stock.js +12 -0
  16. package/dist/config/cli-registry/stock.js.map +1 -1
  17. package/dist/config/cli-registry/types.d.ts +23 -0
  18. package/dist/config/cli-registry/types.d.ts.map +1 -1
  19. package/dist/mux-interface.d.ts +6 -1
  20. package/dist/mux-interface.d.ts.map +1 -1
  21. package/dist/services/unified-session-service.d.ts +28 -10
  22. package/dist/services/unified-session-service.d.ts.map +1 -1
  23. package/dist/services/unified-session-service.js +11 -6
  24. package/dist/services/unified-session-service.js.map +1 -1
  25. package/dist/session.d.ts +11 -0
  26. package/dist/session.d.ts.map +1 -1
  27. package/dist/session.js +90 -41
  28. package/dist/session.js.map +1 -1
  29. package/dist/tmux-manager.d.ts +38 -5
  30. package/dist/tmux-manager.d.ts.map +1 -1
  31. package/dist/tmux-manager.js +91 -32
  32. package/dist/tmux-manager.js.map +1 -1
  33. package/dist/web/public/admin-ui.js.gz +0 -0
  34. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  35. package/dist/web/public/{app.3a89ac57.js → app.c4dbfcfb.js} +2 -2
  36. package/dist/web/public/app.c4dbfcfb.js.br +0 -0
  37. package/dist/web/public/{app.3a89ac57.js.gz → app.c4dbfcfb.js.gz} +0 -0
  38. package/dist/web/public/approvals-ui.js.gz +0 -0
  39. package/dist/web/public/{constants.7a635e34.js → constants.e6d48776.js} +16 -0
  40. package/dist/web/public/constants.e6d48776.js.br +0 -0
  41. package/dist/web/public/constants.e6d48776.js.gz +0 -0
  42. package/dist/web/public/cron-ui.js.gz +0 -0
  43. package/dist/web/public/entrance-animations.js +9 -1
  44. package/dist/web/public/entrance-animations.js.br +0 -0
  45. package/dist/web/public/entrance-animations.js.gz +0 -0
  46. package/dist/web/public/home-sessions.js.gz +0 -0
  47. package/dist/web/public/i18n.9db8c4e0.js.gz +0 -0
  48. package/dist/web/public/{image-input.ee16ad88.js → image-input.cd4b97c4.js} +13 -2
  49. package/dist/web/public/image-input.cd4b97c4.js.br +0 -0
  50. package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
  51. package/dist/web/public/index.html +12 -11
  52. package/dist/web/public/index.html.br +0 -0
  53. package/dist/web/public/index.html.gz +0 -0
  54. package/dist/web/public/input-cjk.87f14251.js.gz +0 -0
  55. package/dist/web/public/keyboard-accessory.daf18302.js.gz +0 -0
  56. package/dist/web/public/{mobile-handlers.0f83a608.js → mobile-handlers.2e9c214a.js} +7 -0
  57. package/dist/web/public/mobile-handlers.2e9c214a.js.br +0 -0
  58. package/dist/web/public/mobile-handlers.2e9c214a.js.gz +0 -0
  59. package/dist/web/public/mobile-overview.js +12 -1
  60. package/dist/web/public/mobile-overview.js.br +0 -0
  61. package/dist/web/public/mobile-overview.js.gz +0 -0
  62. package/dist/web/public/mobile.65176832.css +1 -0
  63. package/dist/web/public/mobile.65176832.css.br +0 -0
  64. package/dist/web/public/mobile.65176832.css.gz +0 -0
  65. package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
  66. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  67. package/dist/web/public/{panels-ui.db4785bf.js → panels-ui.9eb5b235.js} +1 -1
  68. package/dist/web/public/panels-ui.9eb5b235.js.br +0 -0
  69. package/dist/web/public/panels-ui.9eb5b235.js.gz +0 -0
  70. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  71. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  72. package/dist/web/public/readmymind-ui.js.gz +0 -0
  73. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  74. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  75. package/dist/web/public/session-lineage.js.gz +0 -0
  76. package/dist/web/public/{session-ui.a9c126c6.js → session-ui.cd222710.js} +1 -1
  77. package/dist/web/public/session-ui.cd222710.js.br +0 -0
  78. package/dist/web/public/session-ui.cd222710.js.gz +0 -0
  79. package/dist/web/public/settings-ui.554f9cdd.js.gz +0 -0
  80. package/dist/web/public/styles.89069bbc.css +1 -0
  81. package/dist/web/public/styles.89069bbc.css.br +0 -0
  82. package/dist/web/public/styles.89069bbc.css.gz +0 -0
  83. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  84. package/dist/web/public/sw.js.gz +0 -0
  85. package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
  86. package/dist/web/public/terminal-ui.1547ce5e.js +2 -0
  87. package/dist/web/public/terminal-ui.1547ce5e.js.br +0 -0
  88. package/dist/web/public/terminal-ui.1547ce5e.js.gz +0 -0
  89. package/dist/web/public/ultracode-panel.js.gz +0 -0
  90. package/dist/web/public/ultracode-windows.js.gz +0 -0
  91. package/dist/web/public/upload.html.gz +0 -0
  92. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  93. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  94. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  95. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  96. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  97. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  98. package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
  99. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
  100. package/dist/web/public/vendor/xterm.css.gz +0 -0
  101. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  102. package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
  103. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  104. package/dist/web/public/webview-tabs.js.gz +0 -0
  105. package/dist/web/routes/file-routes.d.ts.map +1 -1
  106. package/dist/web/routes/file-routes.js +16 -26
  107. package/dist/web/routes/file-routes.js.map +1 -1
  108. package/dist/web/routes/session-routes.d.ts.map +1 -1
  109. package/dist/web/routes/session-routes.js +78 -4
  110. package/dist/web/routes/session-routes.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/web/public/app.3a89ac57.js.br +0 -0
  113. package/dist/web/public/constants.7a635e34.js.br +0 -0
  114. package/dist/web/public/constants.7a635e34.js.gz +0 -0
  115. package/dist/web/public/image-input.ee16ad88.js.br +0 -0
  116. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  117. package/dist/web/public/mobile-handlers.0f83a608.js.br +0 -0
  118. package/dist/web/public/mobile-handlers.0f83a608.js.gz +0 -0
  119. package/dist/web/public/mobile.7112f27d.css +0 -1
  120. package/dist/web/public/mobile.7112f27d.css.br +0 -0
  121. package/dist/web/public/mobile.7112f27d.css.gz +0 -0
  122. package/dist/web/public/panels-ui.db4785bf.js.br +0 -0
  123. package/dist/web/public/panels-ui.db4785bf.js.gz +0 -0
  124. package/dist/web/public/session-ui.a9c126c6.js.br +0 -0
  125. package/dist/web/public/session-ui.a9c126c6.js.gz +0 -0
  126. package/dist/web/public/styles.298f81f2.css +0 -1
  127. package/dist/web/public/styles.298f81f2.css.br +0 -0
  128. package/dist/web/public/styles.298f81f2.css.gz +0 -0
  129. package/dist/web/public/terminal-ui.70445f81.js +0 -2
  130. package/dist/web/public/terminal-ui.70445f81.js.br +0 -0
  131. package/dist/web/public/terminal-ui.70445f81.js.gz +0 -0
package/README.md CHANGED
@@ -691,6 +691,7 @@ The web UI remains the primary surface; see **[docs/tui.md](docs/tui.md)** for t
691
691
  | `Ctrl+Shift+{` / `Ctrl+Shift+}` | Move active tab left / right |
692
692
  | `Ctrl/Cmd+C` | Copy selection, or interrupt when nothing is selected |
693
693
  | `Ctrl+Shift+C` | Copy selection (never interrupts) |
694
+ | `Ctrl/Cmd+V` | Paste, or upload a clipboard image and paste its path |
694
695
  | `Ctrl/Cmd+L` | Clear terminal |
695
696
  | `Ctrl+Shift+R` | Restore terminal size |
696
697
  | `Ctrl+Shift+V` | Toggle voice input |
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @fileoverview Scan `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl` for Past
3
+ * Sessions rows, the codex analog of what `scanOmpSessionsHistory()`
4
+ * (omp-transcript.ts) does for omp and `scanProjectDir()` (session-routes.ts)
5
+ * does for Claude's own `~/.claude/projects` transcripts.
6
+ *
7
+ * Without this a codex conversation is invisible to Codeman the moment its
8
+ * session record goes away, even though codex itself never forgot it: the
9
+ * unified list is built from `~/.claude/projects` plus omp's own store, and
10
+ * codex writes to neither. A user who wanted to pick a codex thread back up had
11
+ * to find its id by hand and pass `codexConfig.resumeSessionId` to the API.
12
+ *
13
+ * ## Why this reads windows rather than whole files
14
+ *
15
+ * An omp session file is the conversation only, so its scanner reads each file
16
+ * whole. A codex rollout is not comparable: it carries every reasoning block and
17
+ * every tool call, and its `session_meta` line alone embeds the full base
18
+ * instructions. Measured on a real store of 519 rollouts, the median file is
19
+ * 407 KiB, the 90th percentile 1.3 MiB and the largest 25 MiB, for 381 MiB in
20
+ * total. So this reads a head window for the identity and the opening prompt,
21
+ * and a tail window for the most recent one.
22
+ *
23
+ * The head budget is 128 KiB because `session_meta` runs to roughly 19 KiB and
24
+ * the first real user message lands near 69 KiB behind it, both measured on
25
+ * codex 0.152.1.
26
+ *
27
+ * ## Where the prompt text comes from
28
+ *
29
+ * Codex has emitted user input under three shapes, and this reads all of them,
30
+ * preferring the ones that carry real input only:
31
+ *
32
+ * - `event_msg` / `item_completed` with an `item.type` of `UserMessage`, which
33
+ * is what codex 0.152.1 writes.
34
+ * - `event_msg` / `user_message`, which older versions wrote.
35
+ * - `response_item` rows with `role: 'user'`, the last resort. These mix real
36
+ * input with injected context (AGENTS.md, environment context, compaction
37
+ * summaries), so they are read only when neither shape above appears, and
38
+ * the obvious injections are dropped.
39
+ *
40
+ * @module codex-transcript
41
+ */
42
+ export interface CodexHistorySession {
43
+ /** The rollout's own thread id — the token `codex resume <id>` expects. */
44
+ sessionId: string;
45
+ /**
46
+ * `session_meta.originator`, which codex stamps from
47
+ * CODEX_INTERNAL_ORIGINATOR_OVERRIDE — `codeman_<sessionId>` for every pane
48
+ * Codeman spawns. The only link between a FRESH codex pane and the rollout it
49
+ * is writing, since such a pane knows no thread id of its own.
50
+ */
51
+ originator?: string;
52
+ workingDir: string;
53
+ sizeBytes: number;
54
+ /** ISO timestamp, from the file's own mtime. */
55
+ lastModified: string;
56
+ firstPrompt?: string;
57
+ lastPrompt?: string;
58
+ }
59
+ /**
60
+ * Codex conversations on this host, newest first, for the unified session list.
61
+ *
62
+ * Sub-agent threads are left out: codex spawns them for itself, they are not
63
+ * something a person picks back up, and on a real store they outnumber the
64
+ * threads that are.
65
+ */
66
+ export declare function scanCodexSessionsHistory(): Promise<CodexHistorySession[]>;
67
+ /**
68
+ * Which codex thread each Codeman-spawned pane is writing, keyed by Codeman
69
+ * session id.
70
+ *
71
+ * Codeman spawns every codex pane with
72
+ * CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codeman_<sessionId>, and codex stamps that
73
+ * into `session_meta.originator`. That is the ONLY link between a fresh codex
74
+ * pane and the rollout it is writing: such a pane knows no thread id of its own,
75
+ * so it cannot be folded into its own Past-Sessions row from its own side.
76
+ *
77
+ * Newest wins. `/new` typed inside the codex TUI leaves several rollouts sharing
78
+ * one originator, and the pane is on the most recent — so this expects `rows`
79
+ * newest-first, as `scanCodexSessionsHistory()` returns them.
80
+ */
81
+ export declare function codexThreadBySessionId(rows: CodexHistorySession[]): Map<string, string>;
82
+ /** Test seam: drop the per-path identity cache. */
83
+ export declare function __clearCodexIdentityCache(): void;
84
+ //# sourceMappingURL=codex-transcript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-transcript.d.ts","sourceRoot":"","sources":["../src/codex-transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAqCH,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAwOD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAgC/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOvF;AAED,mDAAmD;AACnD,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
@@ -0,0 +1,341 @@
1
+ /**
2
+ * @fileoverview Scan `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl` for Past
3
+ * Sessions rows, the codex analog of what `scanOmpSessionsHistory()`
4
+ * (omp-transcript.ts) does for omp and `scanProjectDir()` (session-routes.ts)
5
+ * does for Claude's own `~/.claude/projects` transcripts.
6
+ *
7
+ * Without this a codex conversation is invisible to Codeman the moment its
8
+ * session record goes away, even though codex itself never forgot it: the
9
+ * unified list is built from `~/.claude/projects` plus omp's own store, and
10
+ * codex writes to neither. A user who wanted to pick a codex thread back up had
11
+ * to find its id by hand and pass `codexConfig.resumeSessionId` to the API.
12
+ *
13
+ * ## Why this reads windows rather than whole files
14
+ *
15
+ * An omp session file is the conversation only, so its scanner reads each file
16
+ * whole. A codex rollout is not comparable: it carries every reasoning block and
17
+ * every tool call, and its `session_meta` line alone embeds the full base
18
+ * instructions. Measured on a real store of 519 rollouts, the median file is
19
+ * 407 KiB, the 90th percentile 1.3 MiB and the largest 25 MiB, for 381 MiB in
20
+ * total. So this reads a head window for the identity and the opening prompt,
21
+ * and a tail window for the most recent one.
22
+ *
23
+ * The head budget is 128 KiB because `session_meta` runs to roughly 19 KiB and
24
+ * the first real user message lands near 69 KiB behind it, both measured on
25
+ * codex 0.152.1.
26
+ *
27
+ * ## Where the prompt text comes from
28
+ *
29
+ * Codex has emitted user input under three shapes, and this reads all of them,
30
+ * preferring the ones that carry real input only:
31
+ *
32
+ * - `event_msg` / `item_completed` with an `item.type` of `UserMessage`, which
33
+ * is what codex 0.152.1 writes.
34
+ * - `event_msg` / `user_message`, which older versions wrote.
35
+ * - `response_item` rows with `role: 'user'`, the last resort. These mix real
36
+ * input with injected context (AGENTS.md, environment context, compaction
37
+ * summaries), so they are read only when neither shape above appears, and
38
+ * the obvious injections are dropped.
39
+ *
40
+ * @module codex-transcript
41
+ */
42
+ import { open, readdir, stat } from 'node:fs/promises';
43
+ import { homedir } from 'node:os';
44
+ import { join } from 'node:path';
45
+ import { LRUMap } from './utils/lru-map.js';
46
+ /** Covers `session_meta` (~19 KiB) plus the first user message (~69 KiB behind it). */
47
+ const HEAD_BYTES = 131072;
48
+ /** Enough to hold the last few turns' worth of lines without re-reading the file. */
49
+ const TAIL_BYTES = 65536;
50
+ /**
51
+ * Newest rollouts to REPORT. Counted in emitted rows, not files scanned: the
52
+ * store is mostly sub-agent threads this never returns, so capping files first
53
+ * would spend the budget on rows nobody sees.
54
+ */
55
+ const MAX_ROLLOUTS = 400;
56
+ /**
57
+ * How many emitted rows also get a tail read for `lastPrompt`. The head read is
58
+ * cached (see below) but the tail cannot be, because appending to a rollout is
59
+ * exactly what changes it, so this is the one genuinely per-request cost and it
60
+ * stays bounded. Counted in emitted rows for the same reason as above — against
61
+ * file index a store of sub-agent threads spends the whole budget before the
62
+ * first row that needed it.
63
+ */
64
+ const MAX_TAIL_READS = 100;
65
+ /** Directory nesting under `sessions/` is year/month/day; stop well past that. */
66
+ const MAX_WALK_DEPTH = 5;
67
+ /** A rollout shorter than this cannot hold a complete `session_meta` line. */
68
+ const MIN_ROLLOUT_BYTES = 100;
69
+ /**
70
+ * `session_meta` is written once and never rewritten — the same fact
71
+ * `readCodexRolloutMetaCached()` in session-routes.ts relies on — so a path's
72
+ * identity is cached, and a rescan costs a `stat` per file plus head reads for
73
+ * rollouts this process has not seen before.
74
+ *
75
+ * ⚠️ The first user message is NOT written up front: codex writes it when the
76
+ * user submits. Caching before then pins `firstPrompt: undefined` for the life
77
+ * of the process, and every scan of the home screen, the command palette and the
78
+ * search-index refresh can land in that window — so the row reads as having no
79
+ * prompt until a restart. `shouldCacheIdentity()` is the guard.
80
+ *
81
+ * Bounded, unlike a plain Map: this process runs for days and every sub-agent
82
+ * rollout adds an entry. Same reason and same size as `codexRolloutMetaCache`.
83
+ */
84
+ const identityCache = new LRUMap({ maxSize: 4096 });
85
+ /**
86
+ * Is this identity settled enough to keep?
87
+ *
88
+ * A known `firstPrompt` settles it. So does a head read that FILLED its window,
89
+ * which means the prompt is genuinely not in the first `HEAD_BYTES` rather than
90
+ * not written yet. A short file with no prompt is the ambiguous case — codex is
91
+ * still to write one — so that one is re-read next scan.
92
+ */
93
+ function shouldCacheIdentity(identity, fileSize) {
94
+ if (!identity.threadId)
95
+ return false;
96
+ return identity.firstPrompt !== undefined || fileSize >= HEAD_BYTES;
97
+ }
98
+ function codexSessionsRoot() {
99
+ const home = process.env.CODEX_HOME || join(homedir(), '.codex');
100
+ return join(home, 'sessions');
101
+ }
102
+ /** Read at most `bytes` from the front of a file. Returns '' when unreadable. */
103
+ async function readHead(path, bytes) {
104
+ const fh = await open(path, 'r').catch(() => null);
105
+ if (!fh)
106
+ return '';
107
+ try {
108
+ const buf = Buffer.alloc(bytes);
109
+ const { bytesRead } = await fh.read(buf, 0, bytes, 0);
110
+ return buf.subarray(0, bytesRead).toString('utf-8');
111
+ }
112
+ catch {
113
+ return '';
114
+ }
115
+ finally {
116
+ await fh.close().catch(() => { });
117
+ }
118
+ }
119
+ /**
120
+ * Read at most `bytes` from the end of a file, dropping the leading partial
121
+ * line so every line handed back parses.
122
+ */
123
+ async function readTail(path, size, bytes) {
124
+ const fh = await open(path, 'r').catch(() => null);
125
+ if (!fh)
126
+ return '';
127
+ try {
128
+ const want = Math.min(bytes, size);
129
+ const buf = Buffer.alloc(want);
130
+ const { bytesRead } = await fh.read(buf, 0, want, size - want);
131
+ const text = buf.subarray(0, bytesRead).toString('utf-8');
132
+ if (want >= size)
133
+ return text; // whole file, nothing was cut
134
+ const nl = text.indexOf('\n');
135
+ return nl === -1 ? '' : text.slice(nl + 1);
136
+ }
137
+ catch {
138
+ return '';
139
+ }
140
+ finally {
141
+ await fh.close().catch(() => { });
142
+ }
143
+ }
144
+ /** Flatten codex's message content, which is a string or an array of text blocks. */
145
+ function contentText(content) {
146
+ if (typeof content === 'string')
147
+ return content.trim();
148
+ if (!Array.isArray(content))
149
+ return '';
150
+ return content
151
+ .filter((b) => !!b && typeof b === 'object' && typeof b.text === 'string')
152
+ .map((b) => b.text)
153
+ .join('\n')
154
+ .trim();
155
+ }
156
+ /** One line's user-prompt text, whichever of the three shapes it is. */
157
+ function userPromptFromLine(entry) {
158
+ const p = entry.payload;
159
+ if (!p)
160
+ return null;
161
+ if (entry.type === 'event_msg' && p.type === 'item_completed' && p.item?.type === 'UserMessage') {
162
+ const text = contentText(p.item.content);
163
+ return text ? { text, injectionProne: false } : null;
164
+ }
165
+ if (entry.type === 'event_msg' && p.type === 'user_message') {
166
+ const text = typeof p.message === 'string' ? p.message.trim() : contentText(p.message);
167
+ return text ? { text, injectionProne: false } : null;
168
+ }
169
+ if (entry.type === 'response_item' && p.role === 'user') {
170
+ const text = contentText(p.content);
171
+ return text ? { text, injectionProne: true } : null;
172
+ }
173
+ return null;
174
+ }
175
+ /**
176
+ * Injected context rather than something the user typed. Codex prepends the
177
+ * repository's AGENTS.md and wraps environment context in a tag, and both arrive
178
+ * as `response_item` user rows.
179
+ */
180
+ function isInjectedContext(text) {
181
+ return text.startsWith('#') || text.startsWith('<');
182
+ }
183
+ /** Collapse to one line and cap, so a row carries a title rather than an essay. */
184
+ function asPreview(text) {
185
+ const flat = text.replace(/\s+/g, ' ').trim();
186
+ return flat.length > 200 ? `${flat.slice(0, 200)}…` : flat;
187
+ }
188
+ /** Parse a head window into the facts about a rollout that never change. */
189
+ function parseIdentity(head) {
190
+ const out = {};
191
+ let fallback;
192
+ for (const line of head.split('\n')) {
193
+ if (!line)
194
+ continue;
195
+ let entry;
196
+ try {
197
+ entry = JSON.parse(line);
198
+ }
199
+ catch {
200
+ continue; // truncated tail of the window, or a malformed line
201
+ }
202
+ const p = entry.payload;
203
+ if (entry.type === 'session_meta' && p) {
204
+ out.threadId ??= p.id || p.session_id;
205
+ out.cwd ??= p.cwd;
206
+ out.threadSource ??= p.thread_source;
207
+ out.originator ??= p.originator;
208
+ }
209
+ else if (entry.type === 'turn_context' && p) {
210
+ out.cwd ??= p.cwd;
211
+ }
212
+ if (out.firstPrompt)
213
+ continue;
214
+ const prompt = userPromptFromLine(entry);
215
+ if (!prompt)
216
+ continue;
217
+ if (!prompt.injectionProne) {
218
+ out.firstPrompt = asPreview(prompt.text);
219
+ }
220
+ else if (!fallback && !isInjectedContext(prompt.text)) {
221
+ fallback = asPreview(prompt.text);
222
+ }
223
+ }
224
+ out.firstPrompt ??= fallback;
225
+ return out;
226
+ }
227
+ /** The most recent user prompt in a tail window, or undefined. */
228
+ function parseLastPrompt(tail) {
229
+ let best;
230
+ let fallback;
231
+ for (const line of tail.split('\n')) {
232
+ if (!line)
233
+ continue;
234
+ try {
235
+ const prompt = userPromptFromLine(JSON.parse(line));
236
+ if (!prompt)
237
+ continue;
238
+ if (!prompt.injectionProne)
239
+ best = asPreview(prompt.text);
240
+ else if (!isInjectedContext(prompt.text))
241
+ fallback = asPreview(prompt.text);
242
+ }
243
+ catch {
244
+ // Malformed line — keep scanning.
245
+ }
246
+ }
247
+ return best ?? fallback;
248
+ }
249
+ /** Every rollout file under `sessions/`, newest first. */
250
+ async function listRollouts(root) {
251
+ const files = [];
252
+ const walk = async (dir, depth) => {
253
+ if (depth > MAX_WALK_DEPTH)
254
+ return;
255
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
256
+ if (!entries)
257
+ return;
258
+ for (const entry of entries) {
259
+ const full = join(dir, entry.name);
260
+ if (entry.isDirectory()) {
261
+ await walk(full, depth + 1);
262
+ continue;
263
+ }
264
+ if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
265
+ continue;
266
+ const st = await stat(full).catch(() => null);
267
+ if (!st || st.size < MIN_ROLLOUT_BYTES)
268
+ continue;
269
+ files.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
270
+ }
271
+ };
272
+ await walk(root, 0);
273
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs);
274
+ return files;
275
+ }
276
+ /**
277
+ * Codex conversations on this host, newest first, for the unified session list.
278
+ *
279
+ * Sub-agent threads are left out: codex spawns them for itself, they are not
280
+ * something a person picks back up, and on a real store they outnumber the
281
+ * threads that are.
282
+ */
283
+ export async function scanCodexSessionsHistory() {
284
+ const files = await listRollouts(codexSessionsRoot());
285
+ const out = [];
286
+ for (const file of files) {
287
+ if (out.length >= MAX_ROLLOUTS)
288
+ break;
289
+ let identity = identityCache.get(file.path);
290
+ if (!identity) {
291
+ identity = parseIdentity(await readHead(file.path, HEAD_BYTES));
292
+ if (shouldCacheIdentity(identity, file.size))
293
+ identityCache.set(file.path, identity);
294
+ }
295
+ if (!identity.threadId || identity.threadSource === 'subagent')
296
+ continue;
297
+ // A row with no directory has nowhere to resume INTO, and emitting an empty
298
+ // one makes a click post `workingDir: ''`. omp drops such a row; so does this.
299
+ if (!identity.cwd)
300
+ continue;
301
+ const lastPrompt = out.length < MAX_TAIL_READS ? parseLastPrompt(await readTail(file.path, file.size, TAIL_BYTES)) : undefined;
302
+ out.push({
303
+ sessionId: identity.threadId,
304
+ originator: identity.originator,
305
+ workingDir: identity.cwd,
306
+ sizeBytes: file.size,
307
+ lastModified: new Date(file.mtimeMs).toISOString(),
308
+ firstPrompt: identity.firstPrompt,
309
+ lastPrompt: lastPrompt ?? identity.firstPrompt,
310
+ });
311
+ }
312
+ return out;
313
+ }
314
+ /**
315
+ * Which codex thread each Codeman-spawned pane is writing, keyed by Codeman
316
+ * session id.
317
+ *
318
+ * Codeman spawns every codex pane with
319
+ * CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codeman_<sessionId>, and codex stamps that
320
+ * into `session_meta.originator`. That is the ONLY link between a fresh codex
321
+ * pane and the rollout it is writing: such a pane knows no thread id of its own,
322
+ * so it cannot be folded into its own Past-Sessions row from its own side.
323
+ *
324
+ * Newest wins. `/new` typed inside the codex TUI leaves several rollouts sharing
325
+ * one originator, and the pane is on the most recent — so this expects `rows`
326
+ * newest-first, as `scanCodexSessionsHistory()` returns them.
327
+ */
328
+ export function codexThreadBySessionId(rows) {
329
+ const out = new Map();
330
+ for (const row of rows) {
331
+ const owner = /^codeman_(.+)$/.exec(row.originator ?? '')?.[1];
332
+ if (owner && !out.has(owner))
333
+ out.set(owner, row.sessionId);
334
+ }
335
+ return out;
336
+ }
337
+ /** Test seam: drop the per-path identity cache. */
338
+ export function __clearCodexIdentityCache() {
339
+ identityCache.clear();
340
+ }
341
+ //# sourceMappingURL=codex-transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-transcript.js","sourceRoot":"","sources":["../src/codex-transcript.ts"],"names":[],"mappings":"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"}
@@ -72,4 +72,24 @@ export declare const FILE_PEEK_BYTES: number;
72
72
  * Override: CODEMAN_MAX_PASTE_IMAGE_BYTES (bytes)
73
73
  */
74
74
  export declare const MAX_PASTE_IMAGE_BYTES: number;
75
+ /**
76
+ * Maximum size (bytes) of a file served by the raw/download file routes:
77
+ * `GET /api/sessions/:id/file-raw` (the Files panel's download link and the
78
+ * file-preview overlay), the attachment `/raw` route, and `GET /api/download`.
79
+ *
80
+ * ⚠️ This is a sanity bound, NOT memory protection. All three bodies are
81
+ * STREAMED and `Range`-aware (`sendFileBody` in file-routes.ts), so a large
82
+ * file costs one read stream rather than its size in RSS. The historical 50MB
83
+ * cap predates that streaming rewrite and its "prevent memory exhaustion"
84
+ * comment described a `readFile()` that no longer exists — all it did was
85
+ * refuse legitimate downloads of build artifacts, videos and archives.
86
+ *
87
+ * Set `CODEMAN_MAX_DOWNLOAD_BYTES=0` to remove the cap entirely.
88
+ * Override: CODEMAN_MAX_DOWNLOAD_BYTES (bytes)
89
+ */
90
+ export declare const MAX_FILE_DOWNLOAD_BYTES: number;
91
+ /** True when `size` exceeds the download cap (a cap of 0 means unlimited). */
92
+ export declare function exceedsDownloadLimit(size: number): boolean;
93
+ /** Human-readable "File too large (…)" message for a refused download. */
94
+ export declare function downloadTooLargeMessage(size: number): string;
75
95
  //# sourceMappingURL=buffer-limits.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buffer-limits.d.ts","sourceRoot":"","sources":["../../src/config/buffer-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,QAAoC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAqC,CAAC;AAMnE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAyE,CAAC;AAE3G;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAiE,CAAC;AAM3F;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAA2D,CAAC;AAMrF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAY,CAAC;AAM9C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAkB,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAMjD;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAe,CAAC;AAM5C;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,QAAgF,CAAC"}
1
+ {"version":3,"file":"buffer-limits.d.ts","sourceRoot":"","sources":["../../src/config/buffer-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,QAAoC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAqC,CAAC;AAMnE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAyE,CAAC;AAE3G;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAiE,CAAC;AAM3F;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAA2D,CAAC;AAMrF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAY,CAAC;AAM9C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAkB,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAMjD;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAe,CAAC;AAM5C;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,QAAgF,CAAC;AAmBnH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,QAGnC,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D"}
@@ -94,4 +94,46 @@ export const FILE_PEEK_BYTES = 8 * 1024 - 1; // 8KB (inclusive end offset)
94
94
  * Override: CODEMAN_MAX_PASTE_IMAGE_BYTES (bytes)
95
95
  */
96
96
  export const MAX_PASTE_IMAGE_BYTES = parseInt(process.env.CODEMAN_MAX_PASTE_IMAGE_BYTES || '') || 50 * 1024 * 1024; // 50MB
97
+ // ============================================================================
98
+ // File Download Limits
99
+ // ============================================================================
100
+ /**
101
+ * Parse a byte-limit env var, where `0` explicitly means "no limit".
102
+ *
103
+ * The `parseInt(...) || default` idiom used elsewhere in this file cannot
104
+ * express that: it treats 0 as falsy and silently restores the default.
105
+ */
106
+ function parseByteLimitEnv(raw, fallback) {
107
+ if (raw === undefined || raw.trim() === '')
108
+ return fallback;
109
+ const parsed = Number.parseInt(raw, 10);
110
+ if (!Number.isFinite(parsed) || parsed < 0)
111
+ return fallback;
112
+ return parsed;
113
+ }
114
+ /**
115
+ * Maximum size (bytes) of a file served by the raw/download file routes:
116
+ * `GET /api/sessions/:id/file-raw` (the Files panel's download link and the
117
+ * file-preview overlay), the attachment `/raw` route, and `GET /api/download`.
118
+ *
119
+ * ⚠️ This is a sanity bound, NOT memory protection. All three bodies are
120
+ * STREAMED and `Range`-aware (`sendFileBody` in file-routes.ts), so a large
121
+ * file costs one read stream rather than its size in RSS. The historical 50MB
122
+ * cap predates that streaming rewrite and its "prevent memory exhaustion"
123
+ * comment described a `readFile()` that no longer exists — all it did was
124
+ * refuse legitimate downloads of build artifacts, videos and archives.
125
+ *
126
+ * Set `CODEMAN_MAX_DOWNLOAD_BYTES=0` to remove the cap entirely.
127
+ * Override: CODEMAN_MAX_DOWNLOAD_BYTES (bytes)
128
+ */
129
+ export const MAX_FILE_DOWNLOAD_BYTES = parseByteLimitEnv(process.env.CODEMAN_MAX_DOWNLOAD_BYTES, 2 * 1024 * 1024 * 1024 // 2GB
130
+ );
131
+ /** True when `size` exceeds the download cap (a cap of 0 means unlimited). */
132
+ export function exceedsDownloadLimit(size) {
133
+ return MAX_FILE_DOWNLOAD_BYTES > 0 && size > MAX_FILE_DOWNLOAD_BYTES;
134
+ }
135
+ /** Human-readable "File too large (…)" message for a refused download. */
136
+ export function downloadTooLargeMessage(size) {
137
+ return `File too large (${Math.round(size / 1024 / 1024)}MB > ${Math.round(MAX_FILE_DOWNLOAD_BYTES / 1024 / 1024)}MB limit). Raise or remove it with CODEMAN_MAX_DOWNLOAD_BYTES (0 = unlimited).`;
138
+ }
97
139
  //# sourceMappingURL=buffer-limits.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"buffer-limits.js","sourceRoot":"","sources":["../../src/config/buffer-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,iCAAiC,EAAE,kCAAkC,EAAE,MAAM,uBAAuB,CAAC;AAE9G,+EAA+E;AAC/E,yBAAyB;AACzB,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,iCAAiC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,kCAAkC,CAAC;AAEnE,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3G;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC;AAE3F,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC;AAErF,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO;AAEtD,+EAA+E;AAC/E,mCAAmC;AACnC,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM;AAE9D;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,QAAQ;AAE1D,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,6BAA6B;AAE1E,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,6BAA6B,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO"}
1
+ {"version":3,"file":"buffer-limits.js","sourceRoot":"","sources":["../../src/config/buffer-limits.ts"],"names":[],"mappings":"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"}
@@ -215,6 +215,10 @@ export declare const CliEntrySchema: z.ZodObject<{
215
215
  effort: z.ZodBoolean;
216
216
  agentSkillInjection: z.ZodBoolean;
217
217
  statusLineTelemetry: z.ZodBoolean;
218
+ workDetect: z.ZodOptional<z.ZodObject<{
219
+ promptGlyph: z.ZodString;
220
+ workingLine: z.ZodString;
221
+ }, z.core.$strict>>;
218
222
  model: z.ZodObject<{
219
223
  source: z.ZodEnum<{
220
224
  none: "none";
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/config/cli-registry/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkUxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAyFvB,CAAC;AAEL,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/config/cli-registry/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmVxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAyFvB,CAAC;AAEL,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
@@ -12,7 +12,7 @@
12
12
  * @module config/cli-registry/schema
13
13
  */
14
14
  import { z } from 'zod';
15
- import { TOKEN_PATTERNS } from './patterns.js';
15
+ import { compileVersionRegex, TOKEN_PATTERNS } from './patterns.js';
16
16
  import { isKnownLauncherProfile, isKnownSetenvProfile } from './profiles.js';
17
17
  /** A bare CLI id: lowercase, starts with a letter, at most 24 chars. Also used as a CSS/URL token. */
18
18
  const cliId = z
@@ -253,6 +253,20 @@ const capabilitiesSchema = z
253
253
  effort: z.boolean(),
254
254
  agentSkillInjection: z.boolean(),
255
255
  statusLineTelemetry: z.boolean(),
256
+ workDetect: z
257
+ .object({
258
+ promptGlyph: z.string().min(1).max(8),
259
+ // Config-supplied regex, so it goes through the same guard as `version.regex`:
260
+ // ~/.codeman/clis.json can set this, and the compiled pattern runs on the PTY
261
+ // hot path, where a nested quantifier would be a ReDoS against the event loop.
262
+ // A broken pattern must also fail at LOAD time rather than inside a data handler.
263
+ workingLine: z
264
+ .string()
265
+ .min(1)
266
+ .refine((src) => compileVersionRegex(src) !== null, 'workingLine must be a regex compileVersionRegex() accepts: at most 200 characters, no nested quantifiers'),
267
+ })
268
+ .strict()
269
+ .optional(),
256
270
  model: z
257
271
  .object({ source: z.enum(['flag', 'claude-settings-file', 'none']), param: z.string().optional() })
258
272
  .strict(),