aicodeman 1.44.0 → 1.46.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 (351) hide show
  1. package/README.md +32 -8
  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/case-rename.d.ts +65 -0
  15. package/dist/case-rename.d.ts.map +1 -0
  16. package/dist/case-rename.js +198 -0
  17. package/dist/case-rename.js.map +1 -0
  18. package/dist/cli-agent.d.ts +91 -3
  19. package/dist/cli-agent.d.ts.map +1 -1
  20. package/dist/cli-agent.js +501 -44
  21. package/dist/cli-agent.js.map +1 -1
  22. package/dist/cli-history.d.ts +20 -0
  23. package/dist/cli-history.d.ts.map +1 -0
  24. package/dist/cli-history.js +61 -0
  25. package/dist/cli-history.js.map +1 -0
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +6 -1
  28. package/dist/cli.js.map +1 -1
  29. package/dist/codex-rollout-format.d.ts +66 -0
  30. package/dist/codex-rollout-format.d.ts.map +1 -0
  31. package/dist/codex-rollout-format.js +111 -0
  32. package/dist/codex-rollout-format.js.map +1 -0
  33. package/dist/codex-transcript.d.ts +3 -0
  34. package/dist/codex-transcript.d.ts.map +1 -1
  35. package/dist/codex-transcript.js +13 -41
  36. package/dist/codex-transcript.js.map +1 -1
  37. package/dist/config/auth-config.d.ts +16 -1
  38. package/dist/config/auth-config.d.ts.map +1 -1
  39. package/dist/config/auth-config.js +16 -1
  40. package/dist/config/auth-config.js.map +1 -1
  41. package/dist/config/cli-registry/schema.d.ts +12 -7
  42. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  43. package/dist/config/cli-registry/schema.js +26 -0
  44. package/dist/config/cli-registry/schema.js.map +1 -1
  45. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  46. package/dist/config/cli-registry/stock.js +40 -1
  47. package/dist/config/cli-registry/stock.js.map +1 -1
  48. package/dist/config/cli-registry/types.d.ts +57 -0
  49. package/dist/config/cli-registry/types.d.ts.map +1 -1
  50. package/dist/config/server-timing.d.ts +6 -0
  51. package/dist/config/server-timing.d.ts.map +1 -1
  52. package/dist/config/server-timing.js +9 -0
  53. package/dist/config/server-timing.js.map +1 -1
  54. package/dist/daemon-control.d.ts +1 -0
  55. package/dist/daemon-control.d.ts.map +1 -1
  56. package/dist/daemon-control.js +2 -0
  57. package/dist/daemon-control.js.map +1 -1
  58. package/dist/deepseek-transcript.d.ts +9 -7
  59. package/dist/deepseek-transcript.d.ts.map +1 -1
  60. package/dist/deepseek-transcript.js +9 -7
  61. package/dist/deepseek-transcript.js.map +1 -1
  62. package/dist/docker-hosts.d.ts +8 -0
  63. package/dist/docker-hosts.d.ts.map +1 -1
  64. package/dist/docker-hosts.js +29 -0
  65. package/dist/docker-hosts.js.map +1 -1
  66. package/dist/git-fetch-scheduler.d.ts +124 -0
  67. package/dist/git-fetch-scheduler.d.ts.map +1 -0
  68. package/dist/git-fetch-scheduler.js +249 -0
  69. package/dist/git-fetch-scheduler.js.map +1 -0
  70. package/dist/git-fetch.d.ts +241 -0
  71. package/dist/git-fetch.d.ts.map +1 -0
  72. package/dist/git-fetch.js +392 -0
  73. package/dist/git-fetch.js.map +1 -0
  74. package/dist/git-workspace-status.d.ts +36 -1
  75. package/dist/git-workspace-status.d.ts.map +1 -1
  76. package/dist/git-workspace-status.js +42 -5
  77. package/dist/git-workspace-status.js.map +1 -1
  78. package/dist/hooks-config.d.ts +14 -2
  79. package/dist/hooks-config.d.ts.map +1 -1
  80. package/dist/hooks-config.js +39 -3
  81. package/dist/hooks-config.js.map +1 -1
  82. package/dist/host-sensors.d.ts +115 -0
  83. package/dist/host-sensors.d.ts.map +1 -0
  84. package/dist/host-sensors.js +457 -0
  85. package/dist/host-sensors.js.map +1 -0
  86. package/dist/local-history.d.ts +71 -0
  87. package/dist/local-history.d.ts.map +1 -0
  88. package/dist/local-history.js +750 -0
  89. package/dist/local-history.js.map +1 -0
  90. package/dist/mcp-server.d.ts.map +1 -1
  91. package/dist/mcp-server.js +2 -2
  92. package/dist/mcp-server.js.map +1 -1
  93. package/dist/mux-interface.d.ts +5 -2
  94. package/dist/mux-interface.d.ts.map +1 -1
  95. package/dist/prompt-cache-keepwarm.d.ts.map +1 -1
  96. package/dist/prompt-cache-keepwarm.js +5 -3
  97. package/dist/prompt-cache-keepwarm.js.map +1 -1
  98. package/dist/remote-hosts.d.ts +7 -0
  99. package/dist/remote-hosts.d.ts.map +1 -1
  100. package/dist/remote-hosts.js +17 -3
  101. package/dist/remote-hosts.js.map +1 -1
  102. package/dist/remote-pane-history.d.ts.map +1 -1
  103. package/dist/remote-pane-history.js +2 -2
  104. package/dist/remote-pane-history.js.map +1 -1
  105. package/dist/session.d.ts +113 -0
  106. package/dist/session.d.ts.map +1 -1
  107. package/dist/session.js +354 -13
  108. package/dist/session.js.map +1 -1
  109. package/dist/terminal-input-classifier.d.ts +8 -0
  110. package/dist/terminal-input-classifier.d.ts.map +1 -0
  111. package/dist/terminal-input-classifier.js +60 -0
  112. package/dist/terminal-input-classifier.js.map +1 -0
  113. package/dist/tmux-manager.d.ts +30 -4
  114. package/dist/tmux-manager.d.ts.map +1 -1
  115. package/dist/tmux-manager.js +101 -21
  116. package/dist/tmux-manager.js.map +1 -1
  117. package/dist/tmux-prefix-table.d.ts +37 -5
  118. package/dist/tmux-prefix-table.d.ts.map +1 -1
  119. package/dist/tmux-prefix-table.js +40 -5
  120. package/dist/tmux-prefix-table.js.map +1 -1
  121. package/dist/tui/tui-app.d.ts +8 -1
  122. package/dist/tui/tui-app.d.ts.map +1 -1
  123. package/dist/tui/tui-app.js +49 -12
  124. package/dist/tui/tui-app.js.map +1 -1
  125. package/dist/tui/tui-client.d.ts +90 -3
  126. package/dist/tui/tui-client.d.ts.map +1 -1
  127. package/dist/tui/tui-client.js +195 -26
  128. package/dist/tui/tui-client.js.map +1 -1
  129. package/dist/types/session.d.ts +15 -0
  130. package/dist/types/session.d.ts.map +1 -1
  131. package/dist/types/session.js.map +1 -1
  132. package/dist/web/agent-inbox-persistence.d.ts +52 -0
  133. package/dist/web/agent-inbox-persistence.d.ts.map +1 -0
  134. package/dist/web/agent-inbox-persistence.js +139 -0
  135. package/dist/web/agent-inbox-persistence.js.map +1 -0
  136. package/dist/web/agent-inbox.d.ts +140 -0
  137. package/dist/web/agent-inbox.d.ts.map +1 -0
  138. package/dist/web/agent-inbox.js +258 -0
  139. package/dist/web/agent-inbox.js.map +1 -0
  140. package/dist/web/middleware/auth.d.ts +2 -0
  141. package/dist/web/middleware/auth.d.ts.map +1 -1
  142. package/dist/web/middleware/auth.js +145 -52
  143. package/dist/web/middleware/auth.js.map +1 -1
  144. package/dist/web/network-auth-policy.d.ts +13 -3
  145. package/dist/web/network-auth-policy.d.ts.map +1 -1
  146. package/dist/web/network-auth-policy.js +15 -3
  147. package/dist/web/network-auth-policy.js.map +1 -1
  148. package/dist/web/ports/infra-port.d.ts +2 -0
  149. package/dist/web/ports/infra-port.d.ts.map +1 -1
  150. package/dist/web/public/admin-ui.js.gz +0 -0
  151. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  152. package/dist/web/public/app.3093527f.js +43 -0
  153. package/dist/web/public/app.3093527f.js.br +0 -0
  154. package/dist/web/public/app.3093527f.js.gz +0 -0
  155. package/dist/web/public/approvals-ui.js.gz +0 -0
  156. package/dist/web/public/{constants.4866ecc3.js → constants.affcdde6.js} +622 -12
  157. package/dist/web/public/constants.affcdde6.js.br +0 -0
  158. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  159. package/dist/web/public/cron-ui.js.gz +0 -0
  160. package/dist/web/public/entrance-animations.js.gz +0 -0
  161. package/dist/web/public/git-status-ui.js +88 -5
  162. package/dist/web/public/git-status-ui.js.br +0 -0
  163. package/dist/web/public/git-status-ui.js.gz +0 -0
  164. package/dist/web/public/header-divider.eb2d2463.js +1 -0
  165. package/dist/web/public/header-divider.eb2d2463.js.br +0 -0
  166. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  167. package/dist/web/public/home-sessions.js.gz +0 -0
  168. package/dist/web/public/host-wake-ui.js.gz +0 -0
  169. package/dist/web/public/i18n.70265257.js +2 -0
  170. package/dist/web/public/i18n.70265257.js.br +0 -0
  171. package/dist/web/public/i18n.70265257.js.gz +0 -0
  172. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  173. package/dist/web/public/index.html +170 -16
  174. package/dist/web/public/index.html.br +0 -0
  175. package/dist/web/public/index.html.gz +0 -0
  176. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  177. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  178. package/dist/web/public/{mobile-handlers.44adaa07.js → mobile-handlers.f7c3f692.js} +26 -2
  179. package/dist/web/public/mobile-handlers.f7c3f692.js.br +0 -0
  180. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  181. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  182. package/dist/web/public/mobile-overview.js.gz +0 -0
  183. package/dist/web/public/mobile.1bbd2d67.css +1 -0
  184. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  185. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  186. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  187. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  188. package/dist/web/public/{panels-ui.cbb24ba1.js → panels-ui.4a56ee63.js} +76 -70
  189. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  190. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  191. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  192. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  193. package/dist/web/public/readmymind-ui.js.gz +0 -0
  194. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  195. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  196. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  197. package/dist/web/public/session-lineage.js.gz +0 -0
  198. package/dist/web/public/session-ui.186f9419.js +106 -0
  199. package/dist/web/public/session-ui.186f9419.js.br +0 -0
  200. package/dist/web/public/session-ui.186f9419.js.gz +0 -0
  201. package/dist/web/public/settings-ui.a191154e.js +106 -0
  202. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  203. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  204. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  205. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  206. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  207. package/dist/web/public/styles.7238eae8.css +1 -0
  208. package/dist/web/public/styles.7238eae8.css.br +0 -0
  209. package/dist/web/public/styles.7238eae8.css.gz +0 -0
  210. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  211. package/dist/web/public/sw.js +2 -2
  212. package/dist/web/public/sw.js.br +0 -0
  213. package/dist/web/public/sw.js.gz +0 -0
  214. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  215. package/dist/web/public/{tab-rail-resize.7157e0bd.js → tab-rail-resize.b5cdceaf.js} +1 -1
  216. package/dist/web/public/tab-rail-resize.b5cdceaf.js.br +0 -0
  217. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  218. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  219. package/dist/web/public/terminal-split.js.gz +0 -0
  220. package/dist/web/public/terminal-tile.js +10 -1
  221. package/dist/web/public/terminal-tile.js.br +0 -0
  222. package/dist/web/public/terminal-tile.js.gz +0 -0
  223. package/dist/web/public/terminal-ui.7e787829.js +4 -0
  224. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  225. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
  226. package/dist/web/public/tile-grid.js.gz +0 -0
  227. package/dist/web/public/ultracode-panel.js.gz +0 -0
  228. package/dist/web/public/ultracode-windows.js.gz +0 -0
  229. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  230. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  231. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  232. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  233. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  234. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  235. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  236. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  237. package/dist/web/public/vendor/{xterm-predictive-echo.ae5e3d5d.js → xterm-predictive-echo.276b11d9.js} +1 -1
  238. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.br +0 -0
  239. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  240. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js +5 -0
  241. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.br +0 -0
  242. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  243. package/dist/web/public/vendor/xterm.css.gz +0 -0
  244. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  245. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  246. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  247. package/dist/web/public/webview-tabs.js.gz +0 -0
  248. package/dist/web/public/whats-new.json +9 -9
  249. package/dist/web/route-helpers.d.ts +57 -6
  250. package/dist/web/route-helpers.d.ts.map +1 -1
  251. package/dist/web/route-helpers.js +72 -4
  252. package/dist/web/route-helpers.js.map +1 -1
  253. package/dist/web/routes/agent-session-routes.d.ts +20 -0
  254. package/dist/web/routes/agent-session-routes.d.ts.map +1 -0
  255. package/dist/web/routes/agent-session-routes.js +160 -0
  256. package/dist/web/routes/agent-session-routes.js.map +1 -0
  257. package/dist/web/routes/case-routes.d.ts.map +1 -1
  258. package/dist/web/routes/case-routes.js +203 -16
  259. package/dist/web/routes/case-routes.js.map +1 -1
  260. package/dist/web/routes/config-backup-routes.js +2 -2
  261. package/dist/web/routes/config-backup-routes.js.map +1 -1
  262. package/dist/web/routes/git-status-routes.d.ts +21 -7
  263. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  264. package/dist/web/routes/git-status-routes.js +78 -12
  265. package/dist/web/routes/git-status-routes.js.map +1 -1
  266. package/dist/web/routes/inbox-routes.d.ts +43 -0
  267. package/dist/web/routes/inbox-routes.d.ts.map +1 -0
  268. package/dist/web/routes/inbox-routes.js +148 -0
  269. package/dist/web/routes/inbox-routes.js.map +1 -0
  270. package/dist/web/routes/index.d.ts +2 -0
  271. package/dist/web/routes/index.d.ts.map +1 -1
  272. package/dist/web/routes/index.js +2 -0
  273. package/dist/web/routes/index.js.map +1 -1
  274. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  275. package/dist/web/routes/ralph-routes.js +7 -16
  276. package/dist/web/routes/ralph-routes.js.map +1 -1
  277. package/dist/web/routes/session-routes.d.ts.map +1 -1
  278. package/dist/web/routes/session-routes.js +120 -74
  279. package/dist/web/routes/session-routes.js.map +1 -1
  280. package/dist/web/routes/system-routes.d.ts.map +1 -1
  281. package/dist/web/routes/system-routes.js +107 -59
  282. package/dist/web/routes/system-routes.js.map +1 -1
  283. package/dist/web/routes/ws-routes.d.ts +5 -0
  284. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  285. package/dist/web/routes/ws-routes.js +84 -47
  286. package/dist/web/routes/ws-routes.js.map +1 -1
  287. package/dist/web/schemas.d.ts +56 -7
  288. package/dist/web/schemas.d.ts.map +1 -1
  289. package/dist/web/schemas.js +88 -1
  290. package/dist/web/schemas.js.map +1 -1
  291. package/dist/web/sensitive-path.d.ts.map +1 -1
  292. package/dist/web/sensitive-path.js +2 -0
  293. package/dist/web/sensitive-path.js.map +1 -1
  294. package/dist/web/server.d.ts +7 -2
  295. package/dist/web/server.d.ts.map +1 -1
  296. package/dist/web/server.js +144 -16
  297. package/dist/web/server.js.map +1 -1
  298. package/dist/web/session-wait-registry.d.ts +48 -4
  299. package/dist/web/session-wait-registry.d.ts.map +1 -1
  300. package/dist/web/session-wait-registry.js +144 -6
  301. package/dist/web/session-wait-registry.js.map +1 -1
  302. package/dist/web/settings-writer.d.ts +47 -0
  303. package/dist/web/settings-writer.d.ts.map +1 -0
  304. package/dist/web/settings-writer.js +110 -0
  305. package/dist/web/settings-writer.js.map +1 -0
  306. package/dist/web/sse-events.d.ts +4 -1
  307. package/dist/web/sse-events.d.ts.map +1 -1
  308. package/dist/web/sse-events.js +4 -1
  309. package/dist/web/sse-events.js.map +1 -1
  310. package/package.json +1 -1
  311. package/skills/codeman/SKILL.md +30 -8
  312. package/skills/codeman/preamble.sh +3 -3
  313. package/skills/codeman/reference/endpoints.md +24 -3
  314. package/skills/codeman/reference/history.md +84 -0
  315. package/skills/codeman/reference/inspection-and-staging.md +152 -0
  316. package/skills/codeman/reference/messaging.md +1 -1
  317. package/skills/codeman/reference/verbs.md +65 -3
  318. package/dist/web/public/app.9314ee60.js +0 -43
  319. package/dist/web/public/app.9314ee60.js.br +0 -0
  320. package/dist/web/public/app.9314ee60.js.gz +0 -0
  321. package/dist/web/public/constants.4866ecc3.js.br +0 -0
  322. package/dist/web/public/constants.4866ecc3.js.gz +0 -0
  323. package/dist/web/public/i18n.22ca902b.js +0 -2
  324. package/dist/web/public/i18n.22ca902b.js.br +0 -0
  325. package/dist/web/public/i18n.22ca902b.js.gz +0 -0
  326. package/dist/web/public/mobile-handlers.44adaa07.js.br +0 -0
  327. package/dist/web/public/mobile-handlers.44adaa07.js.gz +0 -0
  328. package/dist/web/public/mobile.12fb18de.css +0 -1
  329. package/dist/web/public/mobile.12fb18de.css.br +0 -0
  330. package/dist/web/public/mobile.12fb18de.css.gz +0 -0
  331. package/dist/web/public/panels-ui.cbb24ba1.js.br +0 -0
  332. package/dist/web/public/panels-ui.cbb24ba1.js.gz +0 -0
  333. package/dist/web/public/session-ui.9c1f009b.js +0 -104
  334. package/dist/web/public/session-ui.9c1f009b.js.br +0 -0
  335. package/dist/web/public/session-ui.9c1f009b.js.gz +0 -0
  336. package/dist/web/public/settings-ui.fea9ddae.js +0 -106
  337. package/dist/web/public/settings-ui.fea9ddae.js.br +0 -0
  338. package/dist/web/public/settings-ui.fea9ddae.js.gz +0 -0
  339. package/dist/web/public/styles.e096fcd6.css +0 -1
  340. package/dist/web/public/styles.e096fcd6.css.br +0 -0
  341. package/dist/web/public/styles.e096fcd6.css.gz +0 -0
  342. package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
  343. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  344. package/dist/web/public/terminal-ui.68dbd352.js +0 -2
  345. package/dist/web/public/terminal-ui.68dbd352.js.br +0 -0
  346. package/dist/web/public/terminal-ui.68dbd352.js.gz +0 -0
  347. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.br +0 -0
  348. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  349. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +0 -4
  350. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
  351. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-inspection.js","sourceRoot":"","sources":["../src/agent-inspection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ import type { CliEntry } from './config/cli-registry/types.js';
2
+ /**
3
+ * The screen chrome a guarded composer read relies on, all from the CLI registry
4
+ * (`capabilities.workDetect`): the composer glyph, the pattern every row below an
5
+ * idle composer must match, and what opens a popup or turns Enter into a command.
6
+ * Nothing here names a CLI; a CLI takes part in guarded input by declaring
7
+ * `composerFooterLine`.
8
+ */
9
+ export interface GuardedComposerChrome {
10
+ promptGlyph: string;
11
+ footerLine: RegExp;
12
+ /** A row of a suggestion popup drawn directly above the composer (`composerPopupLine`). */
13
+ popupLine?: RegExp;
14
+ /** First characters of a draft that make Enter run a command (`composerCommandPrefixes`). */
15
+ commandPrefixes?: string;
16
+ /** First characters of the word at the caret that open a completion popup (`composerCompletionSigils`). */
17
+ completionSigils?: string;
18
+ }
19
+ /** The chrome a registry entry declares, or null (no guarded input for that CLI). */
20
+ export declare function guardedComposerChrome(entry: CliEntry | undefined): GuardedComposerChrome | null;
21
+ /**
22
+ * Why a message cannot be staged into this composer, or null. Typing it would change
23
+ * what Enter does rather than what it sends: a draft starting with a command prefix
24
+ * (codex: `/` for a slash command, `!` for a shell command) is run, not sent as a
25
+ * prompt, and a last word starting with a completion sigil (codex: `@` file and
26
+ * mention search, `$` skills) opens a popup at the caret whose highlighted entry Enter
27
+ * would accept. Checked before anything is typed. Only the LAST word matters for the
28
+ * sigils because the caret is at the end of the message and the popup follows the
29
+ * caret; a sigil word earlier in the message opens nothing.
30
+ */
31
+ export declare function composerTriggerRefusal(message: string, chrome: GuardedComposerChrome): string | null;
32
+ export declare const STAGE_MAX_BYTES = 8192;
33
+ export declare const promptSha256: (text: string) => string;
34
+ export interface GuardedInputSnapshot {
35
+ generation: string;
36
+ revision: number;
37
+ composerSha: string;
38
+ /** Actual captured pane width minus the rendered composer prefix (including indentation). */
39
+ availableColumns: number;
40
+ }
41
+ export interface GuardedInputTarget {
42
+ id: string;
43
+ /** The session's composer chrome, read before typing for what a message would trigger. */
44
+ chrome(): GuardedComposerChrome | null;
45
+ snapshot(): GuardedInputSnapshot | null;
46
+ write(text: string): Promise<boolean>;
47
+ submit(expected: GuardedInputSnapshot): Promise<boolean>;
48
+ }
49
+ export declare class GuardedInputConflict extends Error {
50
+ }
51
+ /**
52
+ * How long stage keeps reading the pane after typing for a frame that shows the
53
+ * message, and how often. A capture taken right after the keys land is often a
54
+ * frame from before them: codex repaints its composer 30 to 60 ms after input
55
+ * arrives (measured on a live 0.162.0 pane), so the first read can show the old,
56
+ * empty composer, or nothing provable at all. A second of 50 ms reads covers that
57
+ * many times over and still answers the caller quickly.
58
+ */
59
+ export declare const STAGE_SETTLE_TIMEOUT_MS = 1000;
60
+ export declare const STAGE_SETTLE_INTERVAL_MS = 50;
61
+ /**
62
+ * The least time between the staged keys and the conditional Enter. Stage types its
63
+ * text as plain keys (`send-keys -l`), which codex's paste-burst detector reads as a
64
+ * paste, and for 120 ms after such a burst codex turns Enter into a NEWLINE rather
65
+ * than a submit (`PASTE_ENTER_SUPPRESS_WINDOW`, codex-rs/tui/src/bottom_pane/
66
+ * paste_burst.rs at rust-v0.162.0). A caller that submits the moment stage answers
67
+ * would land inside it and be told one Enter was delivered while codex only broke
68
+ * the line. Submit waits out the rest of this window first; a keystroke in that
69
+ * time still invalidates the receipt.
70
+ */
71
+ export declare const SUBMIT_QUIET_MS = 250;
72
+ /** Settle-window overrides for tests: a fake clock comes from the store's `now`. */
73
+ export interface StageSettleOptions {
74
+ timeoutMs?: number;
75
+ intervalMs?: number;
76
+ sleep?: (ms: number) => Promise<void>;
77
+ }
78
+ /**
79
+ * Every refusal after the keys went out says so: stage cannot tell whether the text
80
+ * landed, so the caller must look before staging again (the recovery steps clear a
81
+ * stranded draft with Ctrl+U).
82
+ */
83
+ export declare const MAY_BE_TYPED = "no receipt was issued. The message may already be in the composer: inspect the pane and follow the stranded-draft recovery steps (Ctrl+U) before staging again";
84
+ /**
85
+ * Conservatively read a composer: exactly one visible, unwrapped row and a known
86
+ * footer, both as the registry describes them. Never remove internal spaces, infer
87
+ * an unknown shell prompt, or interpret dialog options as authority. An unsupported
88
+ * frame yields no proof.
89
+ */
90
+ export declare function readGuardedComposer(screen: string, chrome: GuardedComposerChrome): string | null;
91
+ /** Require the capture's caret at the exact end of the unwrapped composer. */
92
+ export declare function readGuardedSnapshot(snapshot: string, chrome: GuardedComposerChrome): string | null;
93
+ /** Exact input-row prefix width, shared by proof and preflight wrapping checks. */
94
+ export declare function readGuardedSnapshotDetails(snapshot: string, chrome: GuardedComposerChrome): {
95
+ composer: string;
96
+ prefixWidth: number;
97
+ } | null;
98
+ export declare class GuardedStageStore {
99
+ private readonly now;
100
+ private readonly capacity;
101
+ private readonly ttlMs;
102
+ private readonly receipts;
103
+ private readonly busy;
104
+ private readonly settleTimeoutMs;
105
+ private readonly settleIntervalMs;
106
+ private readonly sleep;
107
+ constructor(now?: () => number, capacity?: number, ttlMs?: number, settle?: StageSettleOptions);
108
+ stage(target: GuardedInputTarget, owner: string, message: string): Promise<{
109
+ stageId: `${string}-${string}-${string}-${string}-${string}`;
110
+ sha256: string;
111
+ expiresAt: number;
112
+ sessionId: string;
113
+ submitted: false;
114
+ }>;
115
+ /**
116
+ * The first frame after the write that shows exactly this message, with the input
117
+ * revision the write itself moved and nothing else changed. A null or stale frame
118
+ * (the old composer, a half-painted one) is read again until the settle window
119
+ * ends; a frame showing another writer's keystroke, a new pane lifecycle or a new
120
+ * geometry is refused at once, since waiting cannot undo that.
121
+ */
122
+ private settle;
123
+ submit(target: GuardedInputTarget, owner: string, stageId: string, confirmSha: string): Promise<{
124
+ stageId: string;
125
+ sessionId: string;
126
+ submitted: true;
127
+ }>;
128
+ clear(): void;
129
+ }
130
+ //# sourceMappingURL=agent-staging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-staging.d.ts","sourceRoot":"","sources":["../src/agent-staging.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @fileoverview Short-lived, bounded guarded prompt receipts. No prompt content is
3
+ * retained: only caller/session identity, lifecycle/input revisions and SHA-256.
4
+ * A receipt is consumed synchronously before verification/delivery, including failures.
5
+ */
6
+ import { createHash, randomUUID } from 'node:crypto';
7
+ import { stripAnsi } from './utils/regex-patterns.js';
8
+ import { visibleWidth } from './tui/tui-ansi.js';
9
+ import { compileVersionRegex } from './config/cli-registry/patterns.js';
10
+ /** The chrome a registry entry declares, or null (no guarded input for that CLI). */
11
+ export function guardedComposerChrome(entry) {
12
+ const detect = entry?.capabilities.workDetect;
13
+ if (!detect?.promptGlyph || !detect.composerFooterLine)
14
+ return null;
15
+ // Config-supplied (~/.codeman/clis.json): compiled through the ReDoS guard, null on refusal.
16
+ const footerLine = compileVersionRegex(detect.composerFooterLine);
17
+ if (!footerLine)
18
+ return null;
19
+ // A declared popup pattern the guard refuses is no chrome at all: dropping it would
20
+ // silently read a popup frame as an idle composer.
21
+ const popupLine = detect.composerPopupLine ? compileVersionRegex(detect.composerPopupLine) : undefined;
22
+ if (popupLine === null)
23
+ return null;
24
+ return {
25
+ promptGlyph: detect.promptGlyph,
26
+ footerLine,
27
+ ...(popupLine ? { popupLine } : {}),
28
+ ...(detect.composerCommandPrefixes ? { commandPrefixes: detect.composerCommandPrefixes } : {}),
29
+ ...(detect.composerCompletionSigils ? { completionSigils: detect.composerCompletionSigils } : {}),
30
+ };
31
+ }
32
+ /**
33
+ * Why a message cannot be staged into this composer, or null. Typing it would change
34
+ * what Enter does rather than what it sends: a draft starting with a command prefix
35
+ * (codex: `/` for a slash command, `!` for a shell command) is run, not sent as a
36
+ * prompt, and a last word starting with a completion sigil (codex: `@` file and
37
+ * mention search, `$` skills) opens a popup at the caret whose highlighted entry Enter
38
+ * would accept. Checked before anything is typed. Only the LAST word matters for the
39
+ * sigils because the caret is at the end of the message and the popup follows the
40
+ * caret; a sigil word earlier in the message opens nothing.
41
+ */
42
+ export function composerTriggerRefusal(message, chrome) {
43
+ const first = message.trimStart().charAt(0);
44
+ if (first && chrome.commandPrefixes?.includes(first))
45
+ return `Stage refuses a message starting with "${first}": the CLI runs it as a command instead of sending it as a prompt. Send commands with \`codeman agent send\` (POST /api/v1/sessions/:id/input) instead`;
46
+ const last = message.split(/\s/).at(-1)?.charAt(0);
47
+ if (last && chrome.completionSigils?.includes(last))
48
+ return `Stage refuses a message whose last word starts with "${last}": it opens a completion popup at the caret and Enter would accept the highlighted entry instead of sending the message. End the message with another word, or use \`codeman agent send\``;
49
+ return null;
50
+ }
51
+ /** How many rows above the composer a popup check reads: the popup cap (8) plus header and hint rows. */
52
+ const POPUP_WINDOW_ROWS = 12;
53
+ /** Blank rows a popup can leave between itself and the composer row (the composer's own top padding, plus a popup's). */
54
+ const POPUP_GAP_ROWS = 3;
55
+ const escapeRegex = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
56
+ const composerRow = (chrome) => new RegExp(String.raw `^\s*${escapeRegex(chrome.promptGlyph)}(?:\s|$)`);
57
+ export const STAGE_MAX_BYTES = 8192;
58
+ export const promptSha256 = (text) => createHash('sha256').update(text).digest('hex');
59
+ export class GuardedInputConflict extends Error {
60
+ }
61
+ /**
62
+ * How long stage keeps reading the pane after typing for a frame that shows the
63
+ * message, and how often. A capture taken right after the keys land is often a
64
+ * frame from before them: codex repaints its composer 30 to 60 ms after input
65
+ * arrives (measured on a live 0.162.0 pane), so the first read can show the old,
66
+ * empty composer, or nothing provable at all. A second of 50 ms reads covers that
67
+ * many times over and still answers the caller quickly.
68
+ */
69
+ export const STAGE_SETTLE_TIMEOUT_MS = 1000;
70
+ export const STAGE_SETTLE_INTERVAL_MS = 50;
71
+ /**
72
+ * The least time between the staged keys and the conditional Enter. Stage types its
73
+ * text as plain keys (`send-keys -l`), which codex's paste-burst detector reads as a
74
+ * paste, and for 120 ms after such a burst codex turns Enter into a NEWLINE rather
75
+ * than a submit (`PASTE_ENTER_SUPPRESS_WINDOW`, codex-rs/tui/src/bottom_pane/
76
+ * paste_burst.rs at rust-v0.162.0). A caller that submits the moment stage answers
77
+ * would land inside it and be told one Enter was delivered while codex only broke
78
+ * the line. Submit waits out the rest of this window first; a keystroke in that
79
+ * time still invalidates the receipt.
80
+ */
81
+ export const SUBMIT_QUIET_MS = 250;
82
+ /**
83
+ * Every refusal after the keys went out says so: stage cannot tell whether the text
84
+ * landed, so the caller must look before staging again (the recovery steps clear a
85
+ * stranded draft with Ctrl+U).
86
+ */
87
+ export const MAY_BE_TYPED = 'no receipt was issued. The message may already be in the composer: inspect the pane and follow the stranded-draft recovery steps (Ctrl+U) before staging again';
88
+ /**
89
+ * Conservatively read a composer: exactly one visible, unwrapped row and a known
90
+ * footer, both as the registry describes them. Never remove internal spaces, infer
91
+ * an unknown shell prompt, or interpret dialog options as authority. An unsupported
92
+ * frame yields no proof.
93
+ */
94
+ export function readGuardedComposer(screen, chrome) {
95
+ const isComposer = composerRow(chrome);
96
+ const lines = stripAnsi(screen)
97
+ .split('\n')
98
+ .map((line) => line.trimEnd());
99
+ const rows = lines.map((line, index) => ({ line, index })).filter((row) => isComposer.test(row.line));
100
+ const last = rows.at(-1);
101
+ if (!last)
102
+ return null;
103
+ const below = lines.slice(last.index + 1).filter((line) => line.trim());
104
+ if (below.length === 0 || below.length > 3 || below.some((line) => !chrome.footerLine.test(line)))
105
+ return null;
106
+ if (chrome.popupLine && popupAbove(lines, last.index, chrome.popupLine))
107
+ return null;
108
+ // Preserve exact printable content; only SGR-dim placeholder/suggestion text is excluded.
109
+ const raw = screen.split('\n')[last.index];
110
+ let dim = false;
111
+ let visible = '';
112
+ // eslint-disable-next-line no-control-regex
113
+ for (const token of raw.split(/(\x1b\[[0-9;]*m)/)) {
114
+ // eslint-disable-next-line no-control-regex
115
+ if (/^\x1b\[/.test(token)) {
116
+ const codes = token.slice(2, -1).split(';').map(Number);
117
+ for (let i = 0; i < codes.length; i++) {
118
+ // Extended color payloads may contain 0, 2 or 22; those are not SGR modes.
119
+ if (codes[i] === 38 || codes[i] === 48 || codes[i] === 58) {
120
+ i += codes[i + 1] === 2 ? 4 : codes[i + 1] === 5 ? 2 : 0;
121
+ }
122
+ else if (codes[i] === 0 || codes[i] === 22)
123
+ dim = false;
124
+ else if (codes[i] === 2)
125
+ dim = true;
126
+ }
127
+ }
128
+ else if (!dim)
129
+ visible += token;
130
+ }
131
+ // The glyph and the one space after it are the prefix, not input.
132
+ return stripAnsi(visible)
133
+ .trimStart()
134
+ .slice(chrome.promptGlyph.length + 1)
135
+ .trimEnd();
136
+ }
137
+ /**
138
+ * Does a suggestion popup sit directly above the composer row? A popup draws over
139
+ * the rows above the composer and leaves the footer below it alone, so the footer
140
+ * check alone reads a popup frame as an idle composer. Read the contiguous block of
141
+ * rows just above (past at most `POPUP_GAP_ROWS` blank rows), never the whole pane:
142
+ * the transcript further up is the agent's own and may print anything. A transcript
143
+ * row that happens to match costs a refusal, never a receipt.
144
+ */
145
+ function popupAbove(lines, composerIndex, popupLine) {
146
+ let index = composerIndex - 1;
147
+ for (let gap = 0; gap < POPUP_GAP_ROWS && index >= 0 && !lines[index].trim(); gap++)
148
+ index--;
149
+ for (let seen = 0; seen < POPUP_WINDOW_ROWS && index >= 0 && lines[index].trim(); seen++, index--) {
150
+ if (popupLine.test(lines[index]))
151
+ return true;
152
+ }
153
+ return false;
154
+ }
155
+ /** Require the capture's caret at the exact end of the unwrapped composer. */
156
+ export function readGuardedSnapshot(snapshot, chrome) {
157
+ return readGuardedSnapshotDetails(snapshot, chrome)?.composer ?? null;
158
+ }
159
+ /** Exact input-row prefix width, shared by proof and preflight wrapping checks. */
160
+ export function readGuardedSnapshotDetails(snapshot, chrome) {
161
+ // formatPaneSnapshot emits one absolute row address per line, then the caret.
162
+ // Plain captures lack caret evidence and cannot prove invisible trailing edits.
163
+ // eslint-disable-next-line no-control-regex
164
+ const positions = [...snapshot.matchAll(/\x1b\[(\d+);(\d+)H/g)];
165
+ if (positions.length < 2)
166
+ return null;
167
+ const caret = positions.at(-1);
168
+ if (caret.index + caret[0].length !== snapshot.length)
169
+ return null;
170
+ const rows = [];
171
+ for (let i = 0; i < positions.length - 1; i++) {
172
+ const position = positions[i];
173
+ if (Number(position[1]) !== i + 1 || position[2] !== '1')
174
+ return null;
175
+ rows.push(snapshot.slice(position.index + position[0].length, positions[i + 1].index));
176
+ }
177
+ const composer = readGuardedComposer(rows.join('\n'), chrome);
178
+ if (composer === null)
179
+ return null;
180
+ const isComposer = composerRow(chrome);
181
+ let row = -1;
182
+ for (let i = rows.length - 1; i >= 0; i--)
183
+ if (isComposer.test(stripAnsi(rows[i]))) {
184
+ row = i;
185
+ break;
186
+ }
187
+ const prefix = stripAnsi(rows[row]).match(new RegExp(String.raw `^(\s*${escapeRegex(chrome.promptGlyph)}[ \u00a0])`));
188
+ if (!prefix || Number(caret[1]) !== row + 1 || Number(caret[2]) !== visibleWidth(prefix[1] + composer) + 1)
189
+ return null;
190
+ return { composer, prefixWidth: visibleWidth(prefix[1]) };
191
+ }
192
+ export class GuardedStageStore {
193
+ now;
194
+ capacity;
195
+ ttlMs;
196
+ receipts = new Map();
197
+ busy = new Set();
198
+ settleTimeoutMs;
199
+ settleIntervalMs;
200
+ sleep;
201
+ constructor(now = Date.now, capacity = 1024, ttlMs = 120000, settle = {}) {
202
+ this.now = now;
203
+ this.capacity = capacity;
204
+ this.ttlMs = ttlMs;
205
+ this.settleTimeoutMs = settle.timeoutMs ?? STAGE_SETTLE_TIMEOUT_MS;
206
+ this.settleIntervalMs = settle.intervalMs ?? STAGE_SETTLE_INTERVAL_MS;
207
+ this.sleep = settle.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
208
+ }
209
+ async stage(target, owner, message) {
210
+ // eslint-disable-next-line no-control-regex
211
+ const hasControls = /[\x00-\x1f\x7f-\x9f]/.test(message);
212
+ if (!message ||
213
+ message !== message.trimEnd() ||
214
+ Buffer.byteLength(message) > STAGE_MAX_BYTES ||
215
+ Buffer.from(message).toString('utf8') !== message ||
216
+ hasControls)
217
+ throw new GuardedInputConflict('Stage requires printable single-line input within 8192 UTF8 bytes, without trailing whitespace');
218
+ const chrome = target.chrome();
219
+ const refusal = chrome && composerTriggerRefusal(message, chrome);
220
+ if (refusal)
221
+ throw new GuardedInputConflict(refusal);
222
+ const key = target.id;
223
+ if (this.busy.has(key))
224
+ throw new GuardedInputConflict('Another guarded input operation is in progress');
225
+ this.busy.add(key);
226
+ try {
227
+ const before = target.snapshot();
228
+ if (!before || before.composerSha !== promptSha256(''))
229
+ throw new GuardedInputConflict('An empty, verifiable composer is required');
230
+ if (!Number.isFinite(before.availableColumns) || visibleWidth(message) >= before.availableColumns)
231
+ throw new GuardedInputConflict('Message would reach the pane edge or wrap; shorten it or widen the pane');
232
+ for (const [id, receipt] of this.receipts)
233
+ if (receipt.expiresAt <= this.now() || receipt.sessionId === target.id)
234
+ this.receipts.delete(id);
235
+ // Refuse capacity before typing. Do not evict a caller's still-live receipt.
236
+ // Include in-flight stages (this operation already owns one busy slot),
237
+ // so different sessions cannot type concurrently into the same last slot.
238
+ if (this.receipts.size + this.busy.size > this.capacity)
239
+ throw new GuardedInputConflict('Guarded stage receipt capacity reached');
240
+ if (!(await target.write(message)))
241
+ throw new GuardedInputConflict(`Staging delivery failed or ambiguous; ${MAY_BE_TYPED}`);
242
+ const typedAt = this.now();
243
+ const after = await this.settle(target, before, promptSha256(message));
244
+ const stageId = randomUUID();
245
+ const expiresAt = this.now() + this.ttlMs;
246
+ const sha256 = promptSha256(message);
247
+ this.receipts.set(stageId, { ...after, owner, sessionId: target.id, expiresAt, sha256, typedAt });
248
+ return { stageId, sha256, expiresAt, sessionId: target.id, submitted: false };
249
+ }
250
+ finally {
251
+ this.busy.delete(key);
252
+ }
253
+ }
254
+ /**
255
+ * The first frame after the write that shows exactly this message, with the input
256
+ * revision the write itself moved and nothing else changed. A null or stale frame
257
+ * (the old composer, a half-painted one) is read again until the settle window
258
+ * ends; a frame showing another writer's keystroke, a new pane lifecycle or a new
259
+ * geometry is refused at once, since waiting cannot undo that.
260
+ */
261
+ async settle(target, before, expectedSha) {
262
+ const deadline = this.now() + this.settleTimeoutMs;
263
+ for (;;) {
264
+ const frame = target.snapshot();
265
+ if (frame &&
266
+ (frame.generation !== before.generation ||
267
+ frame.revision !== before.revision + 1 ||
268
+ frame.availableColumns !== before.availableColumns))
269
+ throw new GuardedInputConflict(`Input or pane changed while staging; ${MAY_BE_TYPED}`);
270
+ if (frame?.composerSha === expectedSha)
271
+ return frame;
272
+ if (this.now() >= deadline)
273
+ throw new GuardedInputConflict(`The composer did not show the staged message within ${this.settleTimeoutMs} ms; ${MAY_BE_TYPED}`);
274
+ await this.sleep(this.settleIntervalMs);
275
+ }
276
+ }
277
+ async submit(target, owner, stageId, confirmSha) {
278
+ const receipt = this.receipts.get(stageId);
279
+ if (!receipt || receipt.owner !== owner || receipt.sessionId !== target.id)
280
+ throw new GuardedInputConflict('Unknown, used, or foreign stage receipt');
281
+ // One shot, even a wrong hash or unprovable composer. No retry of ambiguous Enter.
282
+ this.receipts.delete(stageId);
283
+ if (this.busy.has(target.id))
284
+ throw new GuardedInputConflict('Another guarded input operation is in progress');
285
+ this.busy.add(target.id);
286
+ try {
287
+ if (receipt.expiresAt <= this.now() || receipt.sha256 !== confirmSha)
288
+ throw new GuardedInputConflict('Expired stage receipt or confirmation hash mismatch');
289
+ const quiet = receipt.typedAt + SUBMIT_QUIET_MS - this.now();
290
+ if (quiet > 0)
291
+ await this.sleep(quiet);
292
+ const current = target.snapshot();
293
+ if (!current ||
294
+ current.generation !== receipt.generation ||
295
+ current.revision !== receipt.revision ||
296
+ current.availableColumns !== receipt.availableColumns ||
297
+ current.composerSha !== receipt.sha256)
298
+ throw new GuardedInputConflict('Staged composer, input, or pane identity changed; nothing submitted');
299
+ if (!(await target.submit(current)))
300
+ throw new GuardedInputConflict('Submission delivery failed or ambiguous; receipt consumed, inspect the pane');
301
+ return { stageId, sessionId: target.id, submitted: true };
302
+ }
303
+ finally {
304
+ this.busy.delete(target.id);
305
+ }
306
+ }
307
+ clear() {
308
+ this.receipts.clear();
309
+ this.busy.clear();
310
+ }
311
+ }
312
+ //# sourceMappingURL=agent-staging.js.map