aicodeman 1.45.0 → 1.47.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 (312) hide show
  1. package/README.md +26 -6
  2. package/dist/agent-codex-identity.d.ts +26 -0
  3. package/dist/agent-codex-identity.d.ts.map +1 -0
  4. package/dist/agent-codex-identity.js +253 -0
  5. package/dist/agent-codex-identity.js.map +1 -0
  6. package/dist/agent-inspection.d.ts +32 -0
  7. package/dist/agent-inspection.d.ts.map +1 -0
  8. package/dist/agent-inspection.js +196 -0
  9. package/dist/agent-inspection.js.map +1 -0
  10. package/dist/agent-staging.d.ts +130 -0
  11. package/dist/agent-staging.d.ts.map +1 -0
  12. package/dist/agent-staging.js +312 -0
  13. package/dist/agent-staging.js.map +1 -0
  14. package/dist/cli-agent.d.ts +31 -2
  15. package/dist/cli-agent.d.ts.map +1 -1
  16. package/dist/cli-agent.js +279 -34
  17. package/dist/cli-agent.js.map +1 -1
  18. package/dist/cli-history.d.ts +20 -0
  19. package/dist/cli-history.d.ts.map +1 -0
  20. package/dist/cli-history.js +61 -0
  21. package/dist/cli-history.js.map +1 -0
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +6 -1
  24. package/dist/cli.js.map +1 -1
  25. package/dist/codex-rollout-format.d.ts +66 -0
  26. package/dist/codex-rollout-format.d.ts.map +1 -0
  27. package/dist/codex-rollout-format.js +111 -0
  28. package/dist/codex-rollout-format.js.map +1 -0
  29. package/dist/codex-transcript.d.ts +3 -0
  30. package/dist/codex-transcript.d.ts.map +1 -1
  31. package/dist/codex-transcript.js +13 -41
  32. package/dist/codex-transcript.js.map +1 -1
  33. package/dist/config/cli-registry/schema.d.ts +12 -7
  34. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  35. package/dist/config/cli-registry/schema.js +26 -0
  36. package/dist/config/cli-registry/schema.js.map +1 -1
  37. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  38. package/dist/config/cli-registry/stock.js +40 -1
  39. package/dist/config/cli-registry/stock.js.map +1 -1
  40. package/dist/config/cli-registry/types.d.ts +57 -0
  41. package/dist/config/cli-registry/types.d.ts.map +1 -1
  42. package/dist/config/server-timing.d.ts +6 -0
  43. package/dist/config/server-timing.d.ts.map +1 -1
  44. package/dist/config/server-timing.js +9 -0
  45. package/dist/config/server-timing.js.map +1 -1
  46. package/dist/daemon-control.d.ts +1 -0
  47. package/dist/daemon-control.d.ts.map +1 -1
  48. package/dist/daemon-control.js +2 -0
  49. package/dist/daemon-control.js.map +1 -1
  50. package/dist/deepseek-transcript.d.ts +9 -7
  51. package/dist/deepseek-transcript.d.ts.map +1 -1
  52. package/dist/deepseek-transcript.js +9 -7
  53. package/dist/deepseek-transcript.js.map +1 -1
  54. package/dist/docker-hosts.d.ts +8 -0
  55. package/dist/docker-hosts.d.ts.map +1 -1
  56. package/dist/docker-hosts.js +29 -0
  57. package/dist/docker-hosts.js.map +1 -1
  58. package/dist/git-fetch-scheduler.d.ts +124 -0
  59. package/dist/git-fetch-scheduler.d.ts.map +1 -0
  60. package/dist/git-fetch-scheduler.js +249 -0
  61. package/dist/git-fetch-scheduler.js.map +1 -0
  62. package/dist/git-fetch.d.ts +241 -0
  63. package/dist/git-fetch.d.ts.map +1 -0
  64. package/dist/git-fetch.js +392 -0
  65. package/dist/git-fetch.js.map +1 -0
  66. package/dist/git-workspace-status.d.ts +36 -1
  67. package/dist/git-workspace-status.d.ts.map +1 -1
  68. package/dist/git-workspace-status.js +42 -5
  69. package/dist/git-workspace-status.js.map +1 -1
  70. package/dist/hooks-config.d.ts +14 -2
  71. package/dist/hooks-config.d.ts.map +1 -1
  72. package/dist/hooks-config.js +39 -3
  73. package/dist/hooks-config.js.map +1 -1
  74. package/dist/local-history.d.ts +71 -0
  75. package/dist/local-history.d.ts.map +1 -0
  76. package/dist/local-history.js +750 -0
  77. package/dist/local-history.js.map +1 -0
  78. package/dist/mcp-server.d.ts.map +1 -1
  79. package/dist/mcp-server.js +2 -2
  80. package/dist/mcp-server.js.map +1 -1
  81. package/dist/mux-interface.d.ts +5 -2
  82. package/dist/mux-interface.d.ts.map +1 -1
  83. package/dist/prompt-cache-keepwarm.d.ts.map +1 -1
  84. package/dist/prompt-cache-keepwarm.js +5 -3
  85. package/dist/prompt-cache-keepwarm.js.map +1 -1
  86. package/dist/ralph-fix-plan-watcher.d.ts +13 -1
  87. package/dist/ralph-fix-plan-watcher.d.ts.map +1 -1
  88. package/dist/ralph-fix-plan-watcher.js +21 -3
  89. package/dist/ralph-fix-plan-watcher.js.map +1 -1
  90. package/dist/ralph-tracker.d.ts +6 -0
  91. package/dist/ralph-tracker.d.ts.map +1 -1
  92. package/dist/ralph-tracker.js +8 -0
  93. package/dist/ralph-tracker.js.map +1 -1
  94. package/dist/session.d.ts +113 -0
  95. package/dist/session.d.ts.map +1 -1
  96. package/dist/session.js +341 -13
  97. package/dist/session.js.map +1 -1
  98. package/dist/terminal-input-classifier.d.ts +8 -0
  99. package/dist/terminal-input-classifier.d.ts.map +1 -0
  100. package/dist/terminal-input-classifier.js +60 -0
  101. package/dist/terminal-input-classifier.js.map +1 -0
  102. package/dist/tmux-manager.d.ts +23 -1
  103. package/dist/tmux-manager.d.ts.map +1 -1
  104. package/dist/tmux-manager.js +54 -6
  105. package/dist/tmux-manager.js.map +1 -1
  106. package/dist/types/session.d.ts +15 -0
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/session.js.map +1 -1
  109. package/dist/web/network-auth-policy.d.ts +13 -3
  110. package/dist/web/network-auth-policy.d.ts.map +1 -1
  111. package/dist/web/network-auth-policy.js +15 -3
  112. package/dist/web/network-auth-policy.js.map +1 -1
  113. package/dist/web/ports/infra-port.d.ts +2 -0
  114. package/dist/web/ports/infra-port.d.ts.map +1 -1
  115. package/dist/web/public/admin-ui.js.gz +0 -0
  116. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  117. package/dist/web/public/app.fe8a6b4c.js +43 -0
  118. package/dist/web/public/app.fe8a6b4c.js.br +0 -0
  119. package/dist/web/public/app.fe8a6b4c.js.gz +0 -0
  120. package/dist/web/public/approvals-ui.js.gz +0 -0
  121. package/dist/web/public/{constants.0b3cdde9.js → constants.affcdde6.js} +611 -12
  122. package/dist/web/public/constants.affcdde6.js.br +0 -0
  123. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  124. package/dist/web/public/cron-ui.js.gz +0 -0
  125. package/dist/web/public/entrance-animations.js.gz +0 -0
  126. package/dist/web/public/git-status-ui.js +88 -5
  127. package/dist/web/public/git-status-ui.js.br +0 -0
  128. package/dist/web/public/git-status-ui.js.gz +0 -0
  129. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  130. package/dist/web/public/home-sessions.js.gz +0 -0
  131. package/dist/web/public/host-wake-ui.js.gz +0 -0
  132. package/dist/web/public/i18n.a618ef4e.js +2 -0
  133. package/dist/web/public/i18n.a618ef4e.js.br +0 -0
  134. package/dist/web/public/i18n.a618ef4e.js.gz +0 -0
  135. package/dist/web/public/{image-input.941386e7.js → image-input.a5d0f8f3.js} +18 -9
  136. package/dist/web/public/image-input.a5d0f8f3.js.br +0 -0
  137. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  138. package/dist/web/public/index.html +110 -19
  139. package/dist/web/public/index.html.br +0 -0
  140. package/dist/web/public/index.html.gz +0 -0
  141. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  142. package/dist/web/public/{keyboard-accessory.4e8b20ac.js → keyboard-accessory.ff64fafc.js} +5 -1
  143. package/dist/web/public/keyboard-accessory.ff64fafc.js.br +0 -0
  144. package/dist/web/public/keyboard-accessory.ff64fafc.js.gz +0 -0
  145. package/dist/web/public/{mobile-handlers.1c0cbcd8.js → mobile-handlers.f7c3f692.js} +22 -0
  146. package/dist/web/public/mobile-handlers.f7c3f692.js.br +0 -0
  147. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  148. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  149. package/dist/web/public/mobile-overview.js.gz +0 -0
  150. package/dist/web/public/mobile.1bbd2d67.css +1 -0
  151. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  152. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  153. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  154. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  155. package/dist/web/public/{panels-ui.78c3defb.js → panels-ui.4a56ee63.js} +1 -1
  156. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  157. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  158. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  159. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  160. package/dist/web/public/readmymind-ui.js.gz +0 -0
  161. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  162. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  163. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  164. package/dist/web/public/session-lineage.js.gz +0 -0
  165. package/dist/web/public/{session-ui.ea6b1994.js → session-ui.426fb671.js} +8 -8
  166. package/dist/web/public/session-ui.426fb671.js.br +0 -0
  167. package/dist/web/public/session-ui.426fb671.js.gz +0 -0
  168. package/dist/web/public/{settings-ui.085dd202.js → settings-ui.a191154e.js} +11 -11
  169. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  170. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  171. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  172. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  173. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  174. package/dist/web/public/styles.4f0c67a0.css +1 -0
  175. package/dist/web/public/styles.4f0c67a0.css.br +0 -0
  176. package/dist/web/public/styles.4f0c67a0.css.gz +0 -0
  177. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  178. package/dist/web/public/sw.js +2 -2
  179. package/dist/web/public/sw.js.br +0 -0
  180. package/dist/web/public/sw.js.gz +0 -0
  181. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  182. package/dist/web/public/{tab-rail-resize.7157e0bd.js → tab-rail-resize.b5cdceaf.js} +1 -1
  183. package/dist/web/public/tab-rail-resize.b5cdceaf.js.br +0 -0
  184. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  185. package/dist/web/public/terminal-keycode229-recovery.98843069.js +1 -0
  186. package/dist/web/public/terminal-keycode229-recovery.98843069.js.br +0 -0
  187. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  188. package/dist/web/public/terminal-split.js.gz +0 -0
  189. package/dist/web/public/terminal-tile.js +10 -6
  190. package/dist/web/public/terminal-tile.js.br +0 -0
  191. package/dist/web/public/terminal-tile.js.gz +0 -0
  192. package/dist/web/public/terminal-ui.7e787829.js +4 -0
  193. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  194. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
  195. package/dist/web/public/tile-grid.js.gz +0 -0
  196. package/dist/web/public/ultracode-panel.js.gz +0 -0
  197. package/dist/web/public/ultracode-windows.js.gz +0 -0
  198. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  199. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  200. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  201. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  202. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  203. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  204. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  205. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  206. package/dist/web/public/vendor/{xterm-predictive-echo.ae5e3d5d.js → xterm-predictive-echo.276b11d9.js} +1 -1
  207. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.br +0 -0
  208. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  209. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js +5 -0
  210. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.br +0 -0
  211. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  212. package/dist/web/public/vendor/xterm.css.gz +0 -0
  213. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  214. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  215. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  216. package/dist/web/public/webview-tabs.js.gz +0 -0
  217. package/dist/web/public/whats-new.json +8 -9
  218. package/dist/web/request-body-parser.d.ts +23 -0
  219. package/dist/web/request-body-parser.d.ts.map +1 -0
  220. package/dist/web/request-body-parser.js +57 -0
  221. package/dist/web/request-body-parser.js.map +1 -0
  222. package/dist/web/routes/agent-session-routes.d.ts +20 -0
  223. package/dist/web/routes/agent-session-routes.d.ts.map +1 -0
  224. package/dist/web/routes/agent-session-routes.js +160 -0
  225. package/dist/web/routes/agent-session-routes.js.map +1 -0
  226. package/dist/web/routes/case-routes.d.ts.map +1 -1
  227. package/dist/web/routes/case-routes.js +32 -26
  228. package/dist/web/routes/case-routes.js.map +1 -1
  229. package/dist/web/routes/config-backup-routes.js +2 -2
  230. package/dist/web/routes/config-backup-routes.js.map +1 -1
  231. package/dist/web/routes/file-routes.d.ts +5 -0
  232. package/dist/web/routes/file-routes.d.ts.map +1 -1
  233. package/dist/web/routes/file-routes.js +5 -1
  234. package/dist/web/routes/file-routes.js.map +1 -1
  235. package/dist/web/routes/git-status-routes.d.ts +21 -7
  236. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  237. package/dist/web/routes/git-status-routes.js +78 -12
  238. package/dist/web/routes/git-status-routes.js.map +1 -1
  239. package/dist/web/routes/index.d.ts +1 -0
  240. package/dist/web/routes/index.d.ts.map +1 -1
  241. package/dist/web/routes/index.js +1 -0
  242. package/dist/web/routes/index.js.map +1 -1
  243. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  244. package/dist/web/routes/ralph-routes.js +52 -18
  245. package/dist/web/routes/ralph-routes.js.map +1 -1
  246. package/dist/web/routes/session-routes.d.ts.map +1 -1
  247. package/dist/web/routes/session-routes.js +101 -28
  248. package/dist/web/routes/session-routes.js.map +1 -1
  249. package/dist/web/routes/system-routes.d.ts.map +1 -1
  250. package/dist/web/routes/system-routes.js +87 -59
  251. package/dist/web/routes/system-routes.js.map +1 -1
  252. package/dist/web/routes/ws-routes.d.ts +5 -0
  253. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  254. package/dist/web/routes/ws-routes.js +84 -47
  255. package/dist/web/routes/ws-routes.js.map +1 -1
  256. package/dist/web/schemas.d.ts +30 -7
  257. package/dist/web/schemas.d.ts.map +1 -1
  258. package/dist/web/schemas.js +40 -1
  259. package/dist/web/schemas.js.map +1 -1
  260. package/dist/web/server.d.ts +5 -2
  261. package/dist/web/server.d.ts.map +1 -1
  262. package/dist/web/server.js +102 -18
  263. package/dist/web/server.js.map +1 -1
  264. package/dist/web/settings-writer.d.ts +47 -0
  265. package/dist/web/settings-writer.d.ts.map +1 -0
  266. package/dist/web/settings-writer.js +110 -0
  267. package/dist/web/settings-writer.js.map +1 -0
  268. package/package.json +1 -1
  269. package/skills/codeman/SKILL.md +25 -6
  270. package/skills/codeman/reference/history.md +84 -0
  271. package/skills/codeman/reference/inspection-and-staging.md +152 -0
  272. package/skills/codeman/reference/messaging.md +1 -1
  273. package/skills/codeman/reference/verbs.md +18 -3
  274. package/dist/web/public/app.42d0abae.js +0 -43
  275. package/dist/web/public/app.42d0abae.js.br +0 -0
  276. package/dist/web/public/app.42d0abae.js.gz +0 -0
  277. package/dist/web/public/constants.0b3cdde9.js.br +0 -0
  278. package/dist/web/public/constants.0b3cdde9.js.gz +0 -0
  279. package/dist/web/public/i18n.1e68220d.js +0 -2
  280. package/dist/web/public/i18n.1e68220d.js.br +0 -0
  281. package/dist/web/public/i18n.1e68220d.js.gz +0 -0
  282. package/dist/web/public/image-input.941386e7.js.br +0 -0
  283. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  284. package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
  285. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  286. package/dist/web/public/mobile-handlers.1c0cbcd8.js.br +0 -0
  287. package/dist/web/public/mobile-handlers.1c0cbcd8.js.gz +0 -0
  288. package/dist/web/public/mobile.12fb18de.css +0 -1
  289. package/dist/web/public/mobile.12fb18de.css.br +0 -0
  290. package/dist/web/public/mobile.12fb18de.css.gz +0 -0
  291. package/dist/web/public/panels-ui.78c3defb.js.br +0 -0
  292. package/dist/web/public/panels-ui.78c3defb.js.gz +0 -0
  293. package/dist/web/public/session-ui.ea6b1994.js.br +0 -0
  294. package/dist/web/public/session-ui.ea6b1994.js.gz +0 -0
  295. package/dist/web/public/settings-ui.085dd202.js.br +0 -0
  296. package/dist/web/public/settings-ui.085dd202.js.gz +0 -0
  297. package/dist/web/public/styles.3b59b41d.css +0 -1
  298. package/dist/web/public/styles.3b59b41d.css.br +0 -0
  299. package/dist/web/public/styles.3b59b41d.css.gz +0 -0
  300. package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
  301. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  302. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js +0 -1
  303. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.br +0 -0
  304. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  305. package/dist/web/public/terminal-ui.fb2a27f5.js +0 -2
  306. package/dist/web/public/terminal-ui.fb2a27f5.js.br +0 -0
  307. package/dist/web/public/terminal-ui.fb2a27f5.js.gz +0 -0
  308. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.br +0 -0
  309. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  310. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +0 -4
  311. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
  312. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
package/README.md CHANGED
@@ -460,7 +460,7 @@ PTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xt
460
460
  - **Background daemon & service install** — `codeman web -d` runs the server detached with a pidfile, `~/.codeman/web.log`, and verified startup (it polls the server until it answers, so a port clash never reads as success); `codeman service install` writes a systemd user unit (Linux) or LaunchAgent (macOS) with your shell's PATH baked in, so an nvm or Homebrew `node`, `tmux` and `claude` are actually found. Secrets are never written into unit files
461
461
  - **Diagnostics in Settings** — **App Settings → System → Diagnostics → Run checks** runs `codeman doctor` on the server and lists Node, tmux, every agent CLI and the optional office tools with versions, paths and install hints. Besides the `PATH`, it also looks in each CLI's usual install directories (`~/.local/bin`, `~/.npm-global/bin` and the like), so most installs are found under a service with a minimal `PATH`. Admin only in multi-user mode.
462
462
  - **Self-update** — git-clone installs under systemd/launchd update in place from **App Settings → System → Updates**: it detects the latest release, auto-stashes a dirty tree, and streams build progress across the service restart (npm installs report as non-updatable)
463
- - **Git status in the bottom bar** — off by default (**App Settings → Header & Panels → Bottom bar → Git status**, per device). A small indicator at the right of the bottom bar shows the active session's repository at a glance: `● 3` uncommitted files, `↑ 2` commits not pushed, `⚠` merge conflicts, `✓` all committed and pushed. Click it for a draggable window listing the staged, not-staged, untracked and conflicted files (grouped under collapsed folders, or as a flat list if you turn that setting off) and the unpushed commits; **click a file to see its diff** (new files as all additions, deleted files as all removals), with **Open file** to jump to the viewer. A folder that holds several projects gets one collapsible section per repository found up to two levels down, all collapsed until you open them. Read-only and offline (Codeman never fetches or changes the repo); not shown for Docker or remote sessions.
463
+ - **Git status in the bottom bar** — off by default (**App Settings → Header & Panels → Bottom bar → Git status**, per device). A small indicator at the right of the bottom bar shows the active session's repository at a glance: `● 3` uncommitted files, `↑ 2` commits not pushed, `⚠` merge conflicts, `✓` all committed and pushed. Click it for a draggable window listing the staged, not-staged, untracked and conflicted files (grouped under collapsed folders, or as a flat list if you turn that setting off) and the unpushed commits; **click a file to see its diff** (new files as all additions, deleted files as all removals), with **Open file** to jump to the viewer. A folder that holds several projects gets one collapsible section per repository found up to two levels down, all collapsed until you open them. **Fetch** in that window's header runs `git fetch` for the repositories it lists, on your click or on an interval you turn on (**App Settings → Bottom bar**, off by default; **Session Options → Git** overrides both the switch and the interval per session). Sessions open on the same folder share one run and each sees its result. Apart from that fetch Codeman never fetches or changes the repo, so "behind" is as of the last fetch; not shown for Docker or remote sessions.
464
464
  - **Clone a GitHub repo as a case** — paste a repository URL into **Add Case → Clone Repo** and Codeman clones it into `~/codeman-cases/<name>` and registers it as a normal case, ready to run an agent in. It preflights the URL while you type (tells you whether it can be cloned anonymously and offers the repo's real branches and tags for the optional branch/tag field), fills the case name in from the URL, and lets you pick which CLI the Run button should use. Public repositories over `https://`; Codeman never collects or stores credentials
465
465
  - **Create a case in a custom folder** — tick **Create in a custom folder** in **Add Case → Create New**, pick a parent folder (Browse included) and a name, and Codeman scaffolds the new case there instead of `~/codeman-cases`. The target must be a new or empty folder; system directories, your home folder itself, credential trees such as `~/.ssh`, and Codeman's own data folder are refused. Admin only in multi-user mode.
466
466
  - **Multi-CLI** — run **Claude Code**, **OpenCode**, **Codex**, **Antigravity**, **Gemini**, **Pi**, **Grok**, **DeepSeek Harness**, **OMP**, or **GitHub Copilot CLI** per session; env-var prefixes auto-gate (`CLAUDE_CODE_*` vs `OPENCODE_*` vs `CODEX_*` vs `ANTIGRAVITY_*` vs `GEMINI_*`/`GOOGLE_*` vs `PI_*` vs `GROK_*`/`XAI_*` vs `DSH_*`/`DEEPSEEK_*` vs `OMP_*` vs `COPILOT_*`). See [`docs/opencode-integration.md`](docs/opencode-integration.md), [`docs/pi-integration.md`](docs/pi-integration.md), [`docs/grok-integration.md`](docs/grok-integration.md), [`docs/deepseek-integration.md`](docs/deepseek-integration.md) [`docs/omp-integration.md`](docs/omp-integration.md) and [`docs/copilot-integration.md`](docs/copilot-integration.md)
@@ -584,6 +584,8 @@ loginctl enable-linger $USER
584
584
 
585
585
  **Always set `CODEMAN_PASSWORD`** before exposing via tunnel — without it, anyone with the URL has full access to your sessions.
586
586
 
587
+ The tunnel Codeman manages (App Settings → System → Remote access) refuses to start without a login (`CODEMAN_PASSWORD` or multi-user mode) unless `CODEMAN_ALLOW_UNAUTHENTICATED_TUNNEL=1` or `codeman web --allow-unauthenticated-tunnel` acknowledges the exposure, or you accept the browser's confirm, which it asks before every enable while no login is set. `CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK` covers only the network bind: it does not let the tunnel start.
588
+
587
589
  </details>
588
590
 
589
591
  ### QR Code Authentication
@@ -825,12 +827,14 @@ Those `DONE_<task>_<random>` strings are the skill's **split marker** trick, and
825
827
  | [`reference/recipes.md`](skills/codeman/reference/recipes.md) | 8 worked flows: claude, DeepSeek Harness and shell workers, fan-out, blocked-worker watch, messaging fan-out. On demand. |
826
828
  | [`reference/endpoints.md`](skills/codeman/reference/endpoints.md) | Full endpoint tables, error codes, per-mode signal table, capacity limits. On demand. |
827
829
  | [`reference/messaging.md`](skills/codeman/reference/messaging.md) | Talking to claude workers directly via Claude Code cross-session messaging. On demand. |
830
+ | [`reference/history.md`](skills/codeman/reference/history.md) | Reading saved Codex conversations offline with `codeman history read`: selectors, filters, forks, budgets. On demand. |
831
+ | [`reference/inspection-and-staging.md`](skills/codeman/reference/inspection-and-staging.md) | Experimental and opt-in: bounded pane inspection and guarded submission with `codeman agent inspect`, `stage` and `submit`. On demand. |
828
832
 
829
833
  Every recipe in there was verified against a live server, and the comments record the failure modes that were measured rather than guessed.
830
834
 
831
835
  #### Two things worth knowing
832
836
 
833
- - **It self-gates.** Outside a Codeman session (`CODEMAN_MUX` unset) the skill refuses to act and does not guess an API URL, so a global install costs an unrelated Claude Code session nothing.
837
+ - **It self-gates.** It acts inside a Codeman-managed session. Outside one (`CODEMAN_MUX` unset) it only reads saved Codex conversations offline (`codeman history read`) and refuses everything else, and it never guesses an API URL, so a global install costs an unrelated Claude Code session nothing.
834
838
  - **It is deliberately conservative.** Unprompted, it may only spawn sessions, prompt them, and delete ones **it created in that same conversation, by exact id**, through a fail-closed guard that refuses to delete the agent's own session. Deleting a case (which erases a real directory of your code), bulk kills, respawn/ralph/cron/orchestrator changes and settings writes all require you to ask, naming the target.
835
839
 
836
840
  ⚠️ Turning `agentSkillEnabled` back off **does not remove already-injected copies** (a create-time sweep would yank the skill out from under other live sessions sharing that workspace). Remove them per case with `codeman skill uninstall --case <name>`.
@@ -977,14 +981,18 @@ codeman tui 3 # attach to session 3 of that list
977
981
 
978
982
  ### `codeman agent` — session-to-session verbs in every CLI mode
979
983
 
980
- The skill above drives the API with `curl` and `jq` from a preamble Codeman seeds per session. Where it does not reach (a `pi` or `shell` session, a machine without `jq`), the same environment is still there (`CODEMAN_MUX=1`, `CODEMAN_SESSION_ID`, `CODEMAN_API_URL` are exported into every pane), so `codeman agent` packages the same verbs as commands. It is a thin client over the endpoints listed under [API](#api): no new route, no new transport, auth and ownership unchanged. One line in a case's `AGENTS.md` is enough: *"other sessions: `codeman agent --help`"*.
984
+ The skill above drives the API with `curl` and `jq` from a preamble Codeman seeds per session. Where it does not reach (a `pi` or `shell` session, a machine without `jq`), the same environment is still there (`CODEMAN_MUX=1`, `CODEMAN_SESSION_ID`, `CODEMAN_API_URL` are exported into every pane), so `codeman agent` packages the same verbs as commands. It is a thin client over the endpoints listed under [API](#api): auth and ownership apply to every endpoint, including bounded inspection and guarded input. The `inspect`, `stage` and `submit` commands and their HTTP routes are **experimental** and **opt-in**: turn on **Experimental agent coordination** in App Settings (`agentCoordinationEnabled`, synced, off by default; until then the routes answer `403 FORBIDDEN` naming it). Their commands, flags, paths, payloads and behavior may change or be removed in minor or patch releases, so pin a compatible version when automating them; see [Experimental agent coordination](docs/versioning-policy.md#experimental-agent-coordination). One line in a case's `AGENTS.md` is enough: *"other sessions: `codeman agent --help`"*.
981
985
 
982
986
  ```bash
983
987
  codeman agent ls # sessions; * marks this one
984
988
  SID=$(codeman agent spawn scratch-1 --mode claude) # quick-start + wait for the composer where the mode has a ready mark
989
+ SID=$(codeman agent spawn --working-dir ~/src/app --mode codex) # no case: an existing directory (cwd by default)
985
990
  codeman agent send "$SID" 'review src/, then say DONE' --until stop,exit --timeout 300000 # --wait = default signal set
986
991
  codeman agent read "$SID" # last answer (as the server reads it for that mode)
987
992
  codeman agent read "$SID" --tail 3000 # terminal tail, ANSI stripped (every mode)
993
+ codeman agent inspect "$SID" --max-bytes 8192 --json # recent evidence + opaque cursor (pane snapshots are labeled)
994
+ codeman agent stage "$SID" --message-file /tmp/prompt.txt --json # no Enter; known local Codex composer only
995
+ codeman agent submit "$SID" --stage-id "$STAGE_ID" --confirm-sha "$SHA256" --json # one conditional Enter
988
996
  codeman agent send "$SID" 'run the tests, then print WORKDONE followed by _4711' # hook-less modes (opencode, pi, …): ask for the marker in halves …
989
997
  codeman agent wait "$SID" --match WORKDONE_4711 # … and wait on the joined form, which the prompt's echo never contains
990
998
  codeman agent interrupt "$SID" # a bare ESC, conversation intact
@@ -994,11 +1002,23 @@ codeman agent ack <msg-id> # remove what you hand
994
1002
  codeman agent rm "$SID" # any session except this one
995
1003
  ```
996
1004
 
997
- Rules the commands enforce rather than document: they refuse outside a Codeman session and never guess a URL; `send` transmits printable text plus Enter only (a control byte such as `Ctrl+C` is `app_exit` in opencode — ESC exists solely as `interrupt`, which never appends Enter); `rm` refuses an empty id, an unprovable self id and a prefix match in either direction. Ids may be the 8-character prefixes `ls` prints (resolved through the list; an ambiguous prefix refuses, anything shorter than 8 characters refuses on every verb). A prompt that starts with `-` goes after `--` (`send "$SID" -- "- fix the bug"`). The echo of the prompt you sent is output too: a `--match` marker that appears verbatim in the prompt matches at once, before the worker has done anything, so the prompt asks for it in halves. A remote session whose host is asleep answers a fire-and-forget `send` with `buffered` (Codeman wakes the host and types the prompt once the pane is back) or `dropped` (over the wake buffer's cap, nothing will be typed: exit `1`). Exit codes: `0` delivered/matched/signal, `1` error, `2` timeout, `3` the worker exited or the wait ended without an answer (`delivered:false`, `ended:true`), `4` refused. `spawn` prints the id alone on stdout (prose goes to stderr), so `SID=$(…)` captures exactly the id. `--json` prints the envelope's `data` for every verb. `--until stop` on a mode without hook signals is the server's 400, passed through — the marker path (`--match`) is the answer there, exactly as for the skill.
1005
+ Rules the commands enforce rather than document: they refuse outside a Codeman session and never guess a URL; `send` transmits printable text plus Enter only (a control byte such as `Ctrl+C` is `app_exit` in opencode — ESC exists solely as `interrupt`, which never appends Enter); `rm` refuses an empty id, an unprovable self id and a prefix match in either direction. Ids may be the 8-character prefixes `ls` prints (resolved through the list; an ambiguous prefix refuses, anything shorter than 8 characters refuses on every verb). A prompt that starts with `-` goes after `--` (`send "$SID" -- "- fix the bug"`). The echo of the prompt you sent is output too: a `--match` marker that appears verbatim in the prompt matches at once, before the worker has done anything, so the prompt asks for it in halves. A remote session whose host is asleep answers a fire-and-forget `send` with `buffered` (Codeman wakes the host and types the prompt once the pane is back) or `dropped` (over the wake buffer's cap, nothing will be typed: exit `1`). Exit codes: `0` delivered/matched/signal, `1` error, `2` timeout, `3` the worker exited or the wait ended without an answer (`delivered:false`, `ended:true`), `4` refused. `spawn` prints the id alone on stdout (prose goes to stderr), so `SID=$(…)` captures exactly the id. `spawn` with no case, or with `--working-dir`, starts the worker in that existing directory through `POST /api/v1/sessions` plus `POST /api/v1/sessions/:id/interactive` (or `/shell`) instead of quick-start, so no case is created and the directory is never labelled agent scratch; an empty `--working-dir` refuses (exit `4`) rather than falling back to cwd, and so does a cwd that is your home directory (Codeman's hooks would land in `~/.claude`), which needs an explicit `--working-dir ~`; if the start fails it still prints the created id and exits `1`. `--json` prints the envelope's `data` for every verb, except a directory `spawn`, which prints `{ sessionId, workingDir, created: true }`, plus `ready` when a readiness wait ran, `readinessError` when that call failed, and `startupError` when the start failed. `--until stop` on a mode without hook signals is the server's 400, passed through — the marker path (`--match`) is the answer there, exactly as for the skill.
1006
+
1007
+ ### `codeman history read`: saved Codex conversations, offline
1008
+
1009
+ `codeman history read` reads Codex's own saved rollouts (`$CODEX_HOME/sessions`, else `~/.codex/sessions`) straight from disk: no server, no managed session, nothing written. Unlike `codeman agent` it works anywhere, including on a machine where Codeman is not running.
1010
+
1011
+ ```bash
1012
+ codeman history read --session-id 019fc604 # one conversation, by native id or 8+ character prefix
1013
+ codeman history read --root-session-id <uuid> --topic auth # a conversation and every sub-agent or fork under it
1014
+ codeman history read --file /path/to/rollout.jsonl --format text
1015
+ ```
1016
+
1017
+ Filters keep whole user/assistant turns (`--topic` is a literal, case-insensitive match; `--after`/`--before` are inclusive UTC dates). A forked or spawned thread's copy of its parent's history is dropped, so every record belongs to the thread that wrote it. Output is JSON by default and carries `complete: false` whenever the evidence is partial (a file that could not be attributed, a compressed `.jsonl.zst` rollout, a record a running Codex is still writing, an unknown ancestor). Every bound fails closed and is raisable: `--metadata-bytes` (attributing the store, default 1 GiB), `--max-entries` (default 65536), `--scan-bytes` (the selected transcripts, default 16 MiB) and `--max-bytes` (output, default 8192). The full reference is [`skills/codeman/reference/history.md`](skills/codeman/reference/history.md).
998
1018
 
999
1019
  ### MCP server (`codeman mcp`): drive Codeman from any MCP client
1000
1020
 
1001
- `codeman mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server on stdio, so any MCP client (Claude Code, Claude Desktop, Codex, Gemini CLI, Cursor, ...) can start Codeman sessions in any installed agent CLI, prompt them, wait for them, read their answers and clean up, from inside Codeman or from outside it. Like `codeman agent`, it is a client of the HTTP API: no new route, auth and ownership unchanged.
1021
+ `codeman mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server on stdio, so any MCP client (Claude Code, Claude Desktop, Codex, Gemini CLI, Cursor, ...) can start Codeman sessions in any installed agent CLI, prompt them, wait for them, read their answers and clean up, from inside Codeman or from outside it. Like `codeman agent`, it is a client of the HTTP API. The MCP server adds no routes of its own, and auth and ownership are unchanged.
1002
1022
 
1003
1023
  ```bash
1004
1024
  # Claude Code
@@ -1227,7 +1247,7 @@ Full details: [`docs/archive/code-structure-findings.md`](docs/archive/code-stru
1227
1247
 
1228
1248
  [![npm](https://img.shields.io/npm/v/xterm-zerolag-input?style=flat-square&color=22c55e)](https://www.npmjs.com/package/xterm-zerolag-input)
1229
1249
 
1230
- Instant keystroke feedback overlay for xterm.js. Eliminates perceived input latency over high-RTT connections by rendering typed characters immediately as a pixel-perfect DOM overlay. Zero dependencies, 6.1 kB gzipped, configurable prompt detection, CJK/emoji wide-character support, full state machine with 238 tests.
1250
+ Instant keystroke feedback overlay for xterm.js. Eliminates perceived input latency over high-RTT connections by rendering typed characters immediately as a pixel-perfect DOM overlay. Zero dependencies, 5.7 kB minified and gzipped, configurable prompt detection, CJK/emoji wide-character support, full state machine with 276 tests. Typing cost does not grow with prompt length.
1231
1251
 
1232
1252
  ```bash
1233
1253
  npm install xterm-zerolag-input
@@ -0,0 +1,26 @@
1
+ export interface CodexAttributionOptions {
2
+ codexHome: string;
3
+ sessionId: string;
4
+ workingDir: string;
5
+ resumeSessionId?: string;
6
+ }
7
+ export interface CodexAttributionBudget {
8
+ maxEntries: number;
9
+ maxBytes: number;
10
+ /** Separate bound for cheap negative prefixes; full candidate metadata has its own budget. */
11
+ maxPrefixBytes?: number;
12
+ }
13
+ /** Match the CLI's relative CODEX_HOME interpretation without exposing env credentials. */
14
+ export declare function resolveEffectiveCodexHome(sessionHome: string | undefined, workingDir: string, env?: Readonly<Record<string, string | undefined>>): string;
15
+ /** Test-only: forget every remembered attribution. */
16
+ export declare function __clearCodexAttributionMemo(): void;
17
+ /**
18
+ * A configured resume and a newly owned rollout conflict rather than pinning an
19
+ * old launch forever after /new. TUI resumes of foreign-originator threads cannot
20
+ * be authoritatively tracked from this metadata; this returns attributed evidence.
21
+ */
22
+ export declare function findExactCodexConversation(options: CodexAttributionOptions, budget?: CodexAttributionBudget): Promise<{
23
+ id: string;
24
+ path: string;
25
+ } | undefined>;
26
+ //# sourceMappingURL=agent-codex-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-codex-identity.d.ts","sourceRoot":"","sources":["../src/agent-codex-identity.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AACD,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAcD,2FAA2F;AAC3F,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GAC9D,MAAM,CAGR;AA4CD,sDAAsD;AACtD,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAeD;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,uBAAuB,EAChC,MAAM,GAAE,sBAAgE,GACvE,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAsDnD"}
@@ -0,0 +1,253 @@
1
+ /**
2
+ * @fileoverview Bounded Codex rollout attribution for agent evidence. Never uses
3
+ * newest-mtime, prompt timestamps or cwd alone as proof of session ownership.
4
+ * The first call walks the store under explicit budgets; the attributed file is
5
+ * then remembered per session and only re-verified (plus a scan of the date
6
+ * directories not older than it) on later calls, since codex never prunes
7
+ * `~/.codex/sessions`.
8
+ */
9
+ import { open, opendir, realpath } from 'node:fs/promises';
10
+ import { probePathKind } from './utils/bounded-path-probe.js';
11
+ import { tmpdir } from 'node:os';
12
+ import { dirname, join, relative, resolve, sep } from 'node:path';
13
+ import { LRUMap } from './utils/lru-map.js';
14
+ const UUID_AT_END = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
15
+ const HEAD_BYTES = 131072;
16
+ const PREFIX_SCAN_BYTES = 8 * 1024 * 1024;
17
+ async function canonical(path) {
18
+ if ((await probePathKind(path, { pastCap: true })) !== 'directory')
19
+ return undefined;
20
+ try {
21
+ return await realpath(path);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /** Match the CLI's relative CODEX_HOME interpretation without exposing env credentials. */
28
+ export function resolveEffectiveCodexHome(sessionHome, workingDir, env = process.env) {
29
+ const path = resolve(workingDir, sessionHome || env.CODEX_HOME || join(env.HOME || tmpdir(), '.codex'));
30
+ return path;
31
+ }
32
+ /** Complete metadata only: a prefix or a partially flushed JSONL row proves nothing. */
33
+ function strictMetadata(bytes) {
34
+ const rows = bytes.toString('utf8').split('\n');
35
+ rows.pop();
36
+ let found;
37
+ for (const line of rows) {
38
+ try {
39
+ const row = JSON.parse(line);
40
+ if (row.type !== 'session_meta')
41
+ continue;
42
+ if (found ||
43
+ typeof row.payload?.id !== 'string' ||
44
+ typeof row.payload.cwd !== 'string' ||
45
+ (row.payload.originator !== undefined && typeof row.payload.originator !== 'string'))
46
+ return undefined;
47
+ found = row.payload;
48
+ }
49
+ catch {
50
+ /* Incomplete or malformed rows are not identity evidence. */
51
+ }
52
+ }
53
+ return found;
54
+ }
55
+ const attributionMemo = new LRUMap({ maxSize: 1024 });
56
+ /** Test-only: forget every remembered attribution. */
57
+ export function __clearCodexAttributionMemo() {
58
+ attributionMemo.clear();
59
+ }
60
+ /** Short first read: `originator` sits right after `id`, `timestamp` and `cwd`. */
61
+ const PREFIX_FIRST_BYTES = 1024;
62
+ /** Fallback read when the first window holds no complete `originator` field. */
63
+ const PREFIX_MAX_BYTES = 4096;
64
+ const ORIGINATOR_FIELD = /"originator"\s*:\s*"(?:[^"\\]|\\.)*"/;
65
+ /**
66
+ * Is a date-shaped directory name older than the remembered one at the same depth?
67
+ * Only all-digit names compare (codex writes `sessions/YYYY/MM/DD/`); anything else
68
+ * is never pruned, so an unexpected layout costs a wider scan, not a missed rollout.
69
+ */
70
+ function olderDatePart(name, memo) {
71
+ if (memo === undefined || !/^\d+$/.test(name) || !/^\d+$/.test(memo))
72
+ return false;
73
+ return Number(name) < Number(memo);
74
+ }
75
+ /**
76
+ * A configured resume and a newly owned rollout conflict rather than pinning an
77
+ * old launch forever after /new. TUI resumes of foreign-originator threads cannot
78
+ * be authoritatively tracked from this metadata; this returns attributed evidence.
79
+ */
80
+ export async function findExactCodexConversation(options, budget = { maxEntries: 8192, maxBytes: 1048576 }) {
81
+ const workspace = await canonical(options.workingDir);
82
+ const home = await canonical(options.codexHome);
83
+ if (!workspace || !home)
84
+ return undefined;
85
+ const sessions = join(home, 'sessions');
86
+ if ((await probePathKind(sessions, { pastCap: true })) !== 'directory')
87
+ return undefined;
88
+ const key = [home, options.sessionId, options.resumeSessionId ?? '', workspace].join('\0');
89
+ const remembered = attributionMemo.get(key);
90
+ if (remembered) {
91
+ const verified = await verifyRemembered(remembered, workspace, budget);
92
+ if (verified) {
93
+ const others = await scanCandidates(sessions, options, budget, remembered.dirParts);
94
+ if (others && others.length === 1 && others[0].path === remembered.path) {
95
+ return { id: remembered.id, path: remembered.path };
96
+ }
97
+ }
98
+ attributionMemo.delete(key);
99
+ // A file that no longer verifies (replaced, rewritten, gone) gets one full walk
100
+ // now. A verified file whose incremental scan failed does not: that scan reads a
101
+ // subset of what a full walk reads under the same budget, so a second candidate,
102
+ // an exhausted budget or an unreadable entry it hit is one the full walk would hit
103
+ // too, and walking the whole never-pruned store could only end the same way.
104
+ if (verified)
105
+ return undefined;
106
+ }
107
+ const candidates = await scanCandidates(sessions, options, budget);
108
+ if (!candidates || candidates.length !== 1)
109
+ return undefined;
110
+ const candidate = candidates[0];
111
+ if (!candidate.meta.cwd || (await canonical(candidate.meta.cwd)) !== workspace)
112
+ return undefined;
113
+ const handle = await open(candidate.path, 'r').catch(() => undefined);
114
+ if (!handle)
115
+ return undefined;
116
+ try {
117
+ const info = await handle.stat();
118
+ const bytes = Buffer.alloc(HEAD_BYTES);
119
+ const read = await handle.read(bytes, 0, bytes.length, 0);
120
+ const meta = strictMetadata(bytes.subarray(0, read.bytesRead));
121
+ if (!meta ||
122
+ meta.id !== candidate.id ||
123
+ meta.cwd !== candidate.meta.cwd ||
124
+ meta.originator !== candidate.meta.originator)
125
+ return undefined;
126
+ attributionMemo.set(key, {
127
+ id: candidate.id,
128
+ path: candidate.path,
129
+ dev: info.dev,
130
+ ino: info.ino,
131
+ meta,
132
+ dirParts: relative(sessions, dirname(candidate.path)).split(sep).filter(Boolean),
133
+ });
134
+ return { id: candidate.id, path: candidate.path };
135
+ }
136
+ finally {
137
+ await handle.close();
138
+ }
139
+ }
140
+ /** Same file (`dev:ino`), same strict metadata, same canonical workspace: nothing else. */
141
+ async function verifyRemembered(memo, workspace, budget) {
142
+ if (HEAD_BYTES > budget.maxBytes)
143
+ return false;
144
+ if (!memo.meta.cwd || (await canonical(memo.meta.cwd)) !== workspace)
145
+ return false;
146
+ const handle = await open(memo.path, 'r').catch(() => undefined);
147
+ if (!handle)
148
+ return false;
149
+ try {
150
+ const info = await handle.stat();
151
+ if (!info.isFile() || info.dev !== memo.dev || info.ino !== memo.ino)
152
+ return false;
153
+ const bytes = Buffer.alloc(HEAD_BYTES);
154
+ const read = await handle.read(bytes, 0, bytes.length, 0);
155
+ const meta = strictMetadata(bytes.subarray(0, read.bytesRead));
156
+ return !!meta && meta.id === memo.id && meta.cwd === memo.meta.cwd && meta.originator === memo.meta.originator;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ finally {
162
+ await handle.close();
163
+ }
164
+ }
165
+ /**
166
+ * Every rollout under `sessions` whose strict metadata names this session (or the
167
+ * configured resume id), or undefined when the walk hit a bound or an unreadable
168
+ * entry (an incomplete inventory proves nothing). With `notOlderThan`, date-shaped
169
+ * directories older than those components are skipped.
170
+ */
171
+ async function scanCandidates(sessions, options, budget, notOlderThan) {
172
+ // `onBoundary`: every ancestor so far equals the remembered path, so pruning applies.
173
+ const directories = [{ path: sessions, depth: 0, onBoundary: notOlderThan !== undefined }];
174
+ const candidates = [];
175
+ const originator = `codeman_${options.sessionId}`;
176
+ const originatorJson = JSON.stringify(originator);
177
+ const explicit = options.resumeSessionId;
178
+ let entries = 0;
179
+ let prefixes = 0;
180
+ const prefixLimit = Math.min(budget.maxPrefixBytes ?? PREFIX_SCAN_BYTES, PREFIX_SCAN_BYTES);
181
+ let scanned = HEAD_BYTES; // Reserve an independent final verification.
182
+ while (directories.length) {
183
+ const dir = directories.pop();
184
+ const handle = await opendir(dir.path).catch(() => undefined);
185
+ if (!handle)
186
+ return undefined;
187
+ for await (const entry of handle) {
188
+ if (++entries > budget.maxEntries)
189
+ return undefined;
190
+ const path = join(dir.path, entry.name);
191
+ if (entry.isDirectory()) {
192
+ if (dir.depth >= 8)
193
+ return undefined;
194
+ const part = notOlderThan?.[dir.depth];
195
+ if (dir.onBoundary && olderDatePart(entry.name, part))
196
+ continue;
197
+ directories.push({ path, depth: dir.depth + 1, onBoundary: dir.onBoundary && entry.name === part });
198
+ }
199
+ else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
200
+ const id = entry.name.match(UUID_AT_END)?.[1];
201
+ if (!id)
202
+ continue; // no rollout id in the name: never a candidate, read nothing
203
+ const file = await open(path, 'r').catch(() => undefined);
204
+ if (!file)
205
+ return undefined;
206
+ try {
207
+ const info = await file.stat();
208
+ if (!info.isFile())
209
+ return undefined;
210
+ if (id !== explicit) {
211
+ // Cheap negative filter only; a match still needs full parsed metadata.
212
+ // A short first window decides when it already holds the whole
213
+ // `originator` field; only otherwise is the wider window read.
214
+ let window = Math.min(info.size, PREFIX_FIRST_BYTES);
215
+ let prefix = '';
216
+ for (;;) {
217
+ if (prefixes + window > prefixLimit)
218
+ return undefined;
219
+ prefixes += window;
220
+ const buffer = Buffer.alloc(window);
221
+ const first = await file.read(buffer, 0, window, 0);
222
+ prefix = buffer.subarray(0, first.bytesRead).toString('utf8');
223
+ if (prefix.includes(originatorJson) || ORIGINATOR_FIELD.test(prefix))
224
+ break;
225
+ const wider = Math.min(info.size, PREFIX_MAX_BYTES);
226
+ if (wider <= window)
227
+ break;
228
+ window = wider;
229
+ }
230
+ if (!prefix.includes(originatorJson))
231
+ continue;
232
+ }
233
+ const size = Math.min(info.size, HEAD_BYTES);
234
+ if (scanned + size > budget.maxBytes)
235
+ return undefined;
236
+ scanned += size;
237
+ const bytes = Buffer.alloc(size);
238
+ const read = await file.read(bytes, 0, size, 0);
239
+ const meta = strictMetadata(bytes.subarray(0, read.bytesRead));
240
+ if (!meta || meta.id !== id)
241
+ continue;
242
+ if (id === explicit || meta.originator === originator)
243
+ candidates.push({ id, path, meta });
244
+ }
245
+ finally {
246
+ await file.close();
247
+ }
248
+ }
249
+ }
250
+ }
251
+ return candidates;
252
+ }
253
+ //# sourceMappingURL=agent-codex-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-codex-identity.js","sourceRoot":"","sources":["../src/agent-codex-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export declare const INSPECT_MAX_BYTES = 65536;
2
+ export declare const INSPECT_SCAN_BYTES = 262144;
3
+ export interface InspectionScope {
4
+ owner: string;
5
+ sessionId: string;
6
+ generation: string;
7
+ conversationId: string;
8
+ }
9
+ export declare class InspectionConflict extends Error {
10
+ }
11
+ /** UTF8-safe suffix, also used for explicitly non-incremental pane evidence. */
12
+ export declare function boundedUtf8Tail(text: string, maxBytes: number): string;
13
+ /** Cursors kept per owner and session; older ones are retired first. */
14
+ export declare const INSPECT_CURSORS_PER_SCOPE = 8;
15
+ export declare class InspectionReader {
16
+ private readonly now;
17
+ private readonly capacity;
18
+ private readonly ttlMs;
19
+ private readonly perScope;
20
+ private readonly cursors;
21
+ constructor(now?: () => number, capacity?: number, ttlMs?: number, perScope?: number);
22
+ read(path: string, scope: InspectionScope, maxBytes: number, since?: string): Promise<{
23
+ evidence: string;
24
+ cursor: `${string}-${string}-${string}-${string}-${string}`;
25
+ omittedBeforeBytes: number;
26
+ truncated: boolean;
27
+ blockedReason: string | undefined;
28
+ scannedBytes: number;
29
+ }>;
30
+ clear(): void;
31
+ }
32
+ //# sourceMappingURL=agent-inspection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-inspection.d.ts","sourceRoot":"","sources":["../src/agent-inspection.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AACvC,eAAO,MAAM,kBAAkB,SAAS,CAAC;AACzC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAcD,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAEhD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOtE;AAUD,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,qBAAa,gBAAgB;IAGzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;gBAEvC,GAAG,eAAW,EACd,QAAQ,SAAO,EACf,KAAK,SAAS,EACd,QAAQ,SAA4B;IAGjD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;;;;;;;;IA6IjF,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @fileoverview Bounded append-only JSONL evidence reads for agent inspection.
3
+ * Opaque, expiring server-side cursors contain no public filesystem paths. A cursor
4
+ * binds the caller, pane generation, attributed conversation and opened file identity.
5
+ * Complete records only; an oversized row stalls explicitly rather than losing evidence.
6
+ */
7
+ import { open } from 'node:fs/promises';
8
+ import { createHash, randomUUID } from 'node:crypto';
9
+ import { isUtf8 } from 'node:buffer';
10
+ export const INSPECT_MAX_BYTES = 65536;
11
+ export const INSPECT_SCAN_BYTES = 262144;
12
+ export class InspectionConflict extends Error {
13
+ }
14
+ /** UTF8-safe suffix, also used for explicitly non-incremental pane evidence. */
15
+ export function boundedUtf8Tail(text, maxBytes) {
16
+ // A UTF8 suffix fits within this many UTF16 units. Include a pair of extra
17
+ // units so slicing cannot introduce a surrogate replacement inside the result.
18
+ const bytes = Buffer.from(text.slice(-maxBytes - 2));
19
+ let start = Math.max(0, bytes.length - maxBytes);
20
+ while (start < bytes.length && (bytes[start] & 0xc0) === 0x80)
21
+ start++;
22
+ return bytes.subarray(start).toString('utf8');
23
+ }
24
+ async function anchor(handle, offset) {
25
+ const count = Math.min(offset, 4096);
26
+ const head = Buffer.alloc(count);
27
+ const tail = Buffer.alloc(count);
28
+ await handle.read(head, 0, count, 0);
29
+ await handle.read(tail, 0, count, Math.max(0, offset - count));
30
+ return createHash('sha256').update(head).update(tail).digest('hex');
31
+ }
32
+ /** Cursors kept per owner and session; older ones are retired first. */
33
+ export const INSPECT_CURSORS_PER_SCOPE = 8;
34
+ export class InspectionReader {
35
+ now;
36
+ capacity;
37
+ ttlMs;
38
+ perScope;
39
+ cursors = new Map();
40
+ constructor(now = Date.now, capacity = 1024, ttlMs = 900000, perScope = INSPECT_CURSORS_PER_SCOPE) {
41
+ this.now = now;
42
+ this.capacity = capacity;
43
+ this.ttlMs = ttlMs;
44
+ this.perScope = perScope;
45
+ }
46
+ async read(path, scope, maxBytes, since) {
47
+ if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > INSPECT_MAX_BYTES)
48
+ throw new Error('Invalid byte budget');
49
+ const scopeKey = JSON.stringify(scope);
50
+ const previous = since ? this.cursors.get(since) : undefined;
51
+ if (since &&
52
+ (!previous || previous.expiresAt <= this.now() || previous.scope !== scopeKey || previous.path !== path)) {
53
+ throw new InspectionConflict('Invalid or expired inspection cursor; inspect without since to start again');
54
+ }
55
+ const handle = await open(path, 'r');
56
+ try {
57
+ const before = await handle.stat();
58
+ if (!before.isFile())
59
+ throw new InspectionConflict('Inspection source is not a regular file');
60
+ const identity = `${before.dev}:${before.ino}:${before.birthtimeMs}`;
61
+ let offset = previous?.offset ?? Math.max(0, before.size - INSPECT_SCAN_BYTES + 1);
62
+ if (previous &&
63
+ (identity !== previous.identity ||
64
+ before.size < previous.observedSize ||
65
+ (before.size === previous.observedSize &&
66
+ (before.mtimeMs !== previous.modifiedAt || before.ctimeMs !== previous.changedAt)) ||
67
+ (await anchor(handle, offset)) !== previous.anchor)) {
68
+ throw new InspectionConflict('Inspection cursor source changed; inspect without since to start again');
69
+ }
70
+ let startsAtBoundary = offset === 0;
71
+ if (!previous && offset > 0) {
72
+ const preceding = Buffer.alloc(1);
73
+ await handle.read(preceding, 0, 1, offset - 1);
74
+ startsAtBoundary = preceding[0] === 10;
75
+ }
76
+ const scan = Buffer.alloc(Math.min(INSPECT_SCAN_BYTES - (!previous && offset > 0 ? 1 : 0), Math.max(0, before.size - offset)));
77
+ const { bytesRead } = await handle.read(scan, 0, scan.length, offset);
78
+ let selectedStart = 0;
79
+ let consumed = 0;
80
+ let blockedReason;
81
+ let omittedBeforeBytes = 0;
82
+ if (!previous) {
83
+ const leading = startsAtBoundary ? 0 : scan.indexOf(10) + 1;
84
+ const completeEnd = scan.lastIndexOf(10, bytesRead - 1) + 1;
85
+ if (!startsAtBoundary && leading === 0) {
86
+ // The tail lies wholly inside one unfinished oversized record. Its
87
+ // beginning is not known: retain the known file boundary rather than
88
+ // manufacture a cursor pointing into a JSON/UTF8 fragment.
89
+ offset = 0;
90
+ blockedReason = 'record-exceeds-scan-limit';
91
+ }
92
+ else {
93
+ selectedStart = completeEnd;
94
+ consumed = completeEnd;
95
+ while (selectedStart > leading) {
96
+ const recordStart = Math.max(leading, scan.lastIndexOf(10, selectedStart - 2) + 1);
97
+ if (completeEnd - recordStart > maxBytes) {
98
+ if (selectedStart === completeEnd) {
99
+ selectedStart = recordStart;
100
+ consumed = recordStart;
101
+ blockedReason = 'record-exceeds-output-budget';
102
+ }
103
+ break;
104
+ }
105
+ selectedStart = recordStart;
106
+ }
107
+ if (completeEnd < bytesRead && !blockedReason)
108
+ blockedReason = 'partial-record';
109
+ // If the only known boundary is the end of a skipped leading row,
110
+ // the remainder is a partial record and stays pending there.
111
+ if (completeEnd < leading)
112
+ selectedStart = consumed = leading;
113
+ omittedBeforeBytes = offset + selectedStart;
114
+ }
115
+ }
116
+ else {
117
+ while (consumed < bytesRead) {
118
+ const end = scan.indexOf(10, consumed);
119
+ if (end < 0) {
120
+ blockedReason = bytesRead === INSPECT_SCAN_BYTES ? 'record-exceeds-scan-limit' : 'partial-record';
121
+ break;
122
+ }
123
+ if (end + 1 > maxBytes) {
124
+ if (consumed === 0)
125
+ blockedReason = 'record-exceeds-output-budget';
126
+ break;
127
+ }
128
+ consumed = end + 1;
129
+ }
130
+ }
131
+ const evidenceBytes = scan.subarray(selectedStart, consumed);
132
+ if (!isUtf8(evidenceBytes))
133
+ throw new InspectionConflict('Inspection source contains invalid UTF8');
134
+ for (const row of evidenceBytes.toString('utf8').split('\n').slice(0, -1)) {
135
+ try {
136
+ JSON.parse(row);
137
+ }
138
+ catch {
139
+ throw new InspectionConflict('Inspection source contains invalid JSONL');
140
+ }
141
+ }
142
+ const after = await handle.stat();
143
+ if (after.size < before.size ||
144
+ after.ino !== before.ino ||
145
+ (after.size === before.size && (after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs)))
146
+ throw new InspectionConflict('Inspection source changed during read');
147
+ const nextAnchor = await anchor(handle, offset + consumed);
148
+ const cursor = randomUUID();
149
+ // No await between capacity enforcement and insertion: concurrent reads
150
+ // must not all reserve the same remaining slot while hashing anchors.
151
+ // Each owner and session keeps only its newest cursors, so one agent polling in
152
+ // a loop retires its own oldest ones instead of pushing every other caller's out
153
+ // of the shared map. The cursor just used stays among the newest, so a retry of
154
+ // the last call still works. The global capacity is only a backstop.
155
+ const holder = JSON.stringify([scope.owner, scope.sessionId]);
156
+ const mine = [];
157
+ for (const [key, value] of this.cursors) {
158
+ if (value.expiresAt <= this.now())
159
+ this.cursors.delete(key);
160
+ else if (value.holder === holder)
161
+ mine.push(key);
162
+ }
163
+ while (mine.length >= this.perScope)
164
+ this.cursors.delete(mine.shift());
165
+ while (this.cursors.size >= this.capacity)
166
+ this.cursors.delete(this.cursors.keys().next().value);
167
+ this.cursors.set(cursor, {
168
+ scope: scopeKey,
169
+ holder,
170
+ path,
171
+ identity,
172
+ offset: offset + consumed,
173
+ observedSize: before.size,
174
+ modifiedAt: before.mtimeMs,
175
+ changedAt: before.ctimeMs,
176
+ anchor: nextAnchor,
177
+ expiresAt: this.now() + this.ttlMs,
178
+ });
179
+ return {
180
+ evidence: evidenceBytes.toString('utf8'),
181
+ cursor,
182
+ omittedBeforeBytes,
183
+ truncated: omittedBeforeBytes > 0 || offset + consumed < before.size,
184
+ blockedReason,
185
+ scannedBytes: bytesRead,
186
+ };
187
+ }
188
+ finally {
189
+ await handle.close();
190
+ }
191
+ }
192
+ clear() {
193
+ this.cursors.clear();
194
+ }
195
+ }
196
+ //# sourceMappingURL=agent-inspection.js.map