aicodeman 1.45.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 (283) 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/session.d.ts +113 -0
  87. package/dist/session.d.ts.map +1 -1
  88. package/dist/session.js +341 -13
  89. package/dist/session.js.map +1 -1
  90. package/dist/terminal-input-classifier.d.ts +8 -0
  91. package/dist/terminal-input-classifier.d.ts.map +1 -0
  92. package/dist/terminal-input-classifier.js +60 -0
  93. package/dist/terminal-input-classifier.js.map +1 -0
  94. package/dist/tmux-manager.d.ts +23 -1
  95. package/dist/tmux-manager.d.ts.map +1 -1
  96. package/dist/tmux-manager.js +54 -6
  97. package/dist/tmux-manager.js.map +1 -1
  98. package/dist/types/session.d.ts +15 -0
  99. package/dist/types/session.d.ts.map +1 -1
  100. package/dist/types/session.js.map +1 -1
  101. package/dist/web/network-auth-policy.d.ts +13 -3
  102. package/dist/web/network-auth-policy.d.ts.map +1 -1
  103. package/dist/web/network-auth-policy.js +15 -3
  104. package/dist/web/network-auth-policy.js.map +1 -1
  105. package/dist/web/ports/infra-port.d.ts +2 -0
  106. package/dist/web/ports/infra-port.d.ts.map +1 -1
  107. package/dist/web/public/admin-ui.js.gz +0 -0
  108. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  109. package/dist/web/public/app.3093527f.js +43 -0
  110. package/dist/web/public/app.3093527f.js.br +0 -0
  111. package/dist/web/public/app.3093527f.js.gz +0 -0
  112. package/dist/web/public/approvals-ui.js.gz +0 -0
  113. package/dist/web/public/{constants.0b3cdde9.js → constants.affcdde6.js} +611 -12
  114. package/dist/web/public/constants.affcdde6.js.br +0 -0
  115. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  116. package/dist/web/public/cron-ui.js.gz +0 -0
  117. package/dist/web/public/entrance-animations.js.gz +0 -0
  118. package/dist/web/public/git-status-ui.js +88 -5
  119. package/dist/web/public/git-status-ui.js.br +0 -0
  120. package/dist/web/public/git-status-ui.js.gz +0 -0
  121. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  122. package/dist/web/public/home-sessions.js.gz +0 -0
  123. package/dist/web/public/host-wake-ui.js.gz +0 -0
  124. package/dist/web/public/i18n.70265257.js +2 -0
  125. package/dist/web/public/i18n.70265257.js.br +0 -0
  126. package/dist/web/public/i18n.70265257.js.gz +0 -0
  127. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  128. package/dist/web/public/index.html +107 -16
  129. package/dist/web/public/index.html.br +0 -0
  130. package/dist/web/public/index.html.gz +0 -0
  131. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  132. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  133. package/dist/web/public/{mobile-handlers.1c0cbcd8.js → mobile-handlers.f7c3f692.js} +22 -0
  134. package/dist/web/public/mobile-handlers.f7c3f692.js.br +0 -0
  135. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  136. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  137. package/dist/web/public/mobile-overview.js.gz +0 -0
  138. package/dist/web/public/mobile.1bbd2d67.css +1 -0
  139. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  140. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  141. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  142. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  143. package/dist/web/public/{panels-ui.78c3defb.js → panels-ui.4a56ee63.js} +1 -1
  144. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  145. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  146. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  147. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  148. package/dist/web/public/readmymind-ui.js.gz +0 -0
  149. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  150. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  151. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  152. package/dist/web/public/session-lineage.js.gz +0 -0
  153. package/dist/web/public/{session-ui.ea6b1994.js → session-ui.186f9419.js} +8 -8
  154. package/dist/web/public/session-ui.186f9419.js.br +0 -0
  155. package/dist/web/public/session-ui.186f9419.js.gz +0 -0
  156. package/dist/web/public/{settings-ui.085dd202.js → settings-ui.a191154e.js} +11 -11
  157. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  158. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  159. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  160. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  161. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  162. package/dist/web/public/styles.7238eae8.css +1 -0
  163. package/dist/web/public/styles.7238eae8.css.br +0 -0
  164. package/dist/web/public/styles.7238eae8.css.gz +0 -0
  165. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  166. package/dist/web/public/sw.js +2 -2
  167. package/dist/web/public/sw.js.br +0 -0
  168. package/dist/web/public/sw.js.gz +0 -0
  169. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  170. package/dist/web/public/{tab-rail-resize.7157e0bd.js → tab-rail-resize.b5cdceaf.js} +1 -1
  171. package/dist/web/public/tab-rail-resize.b5cdceaf.js.br +0 -0
  172. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  173. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  174. package/dist/web/public/terminal-split.js.gz +0 -0
  175. package/dist/web/public/terminal-tile.js +2 -0
  176. package/dist/web/public/terminal-tile.js.br +0 -0
  177. package/dist/web/public/terminal-tile.js.gz +0 -0
  178. package/dist/web/public/terminal-ui.7e787829.js +4 -0
  179. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  180. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
  181. package/dist/web/public/tile-grid.js.gz +0 -0
  182. package/dist/web/public/ultracode-panel.js.gz +0 -0
  183. package/dist/web/public/ultracode-windows.js.gz +0 -0
  184. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  185. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  186. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  187. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  188. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  189. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  190. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  191. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  192. package/dist/web/public/vendor/{xterm-predictive-echo.ae5e3d5d.js → xterm-predictive-echo.276b11d9.js} +1 -1
  193. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.br +0 -0
  194. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  195. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js +5 -0
  196. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.br +0 -0
  197. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  198. package/dist/web/public/vendor/xterm.css.gz +0 -0
  199. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  200. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  201. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  202. package/dist/web/public/webview-tabs.js.gz +0 -0
  203. package/dist/web/public/whats-new.json +9 -9
  204. package/dist/web/routes/agent-session-routes.d.ts +20 -0
  205. package/dist/web/routes/agent-session-routes.d.ts.map +1 -0
  206. package/dist/web/routes/agent-session-routes.js +160 -0
  207. package/dist/web/routes/agent-session-routes.js.map +1 -0
  208. package/dist/web/routes/case-routes.d.ts.map +1 -1
  209. package/dist/web/routes/case-routes.js +32 -26
  210. package/dist/web/routes/case-routes.js.map +1 -1
  211. package/dist/web/routes/config-backup-routes.js +2 -2
  212. package/dist/web/routes/config-backup-routes.js.map +1 -1
  213. package/dist/web/routes/git-status-routes.d.ts +21 -7
  214. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  215. package/dist/web/routes/git-status-routes.js +78 -12
  216. package/dist/web/routes/git-status-routes.js.map +1 -1
  217. package/dist/web/routes/index.d.ts +1 -0
  218. package/dist/web/routes/index.d.ts.map +1 -1
  219. package/dist/web/routes/index.js +1 -0
  220. package/dist/web/routes/index.js.map +1 -1
  221. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  222. package/dist/web/routes/ralph-routes.js +7 -16
  223. package/dist/web/routes/ralph-routes.js.map +1 -1
  224. package/dist/web/routes/session-routes.d.ts.map +1 -1
  225. package/dist/web/routes/session-routes.js +96 -28
  226. package/dist/web/routes/session-routes.js.map +1 -1
  227. package/dist/web/routes/system-routes.d.ts.map +1 -1
  228. package/dist/web/routes/system-routes.js +84 -57
  229. package/dist/web/routes/system-routes.js.map +1 -1
  230. package/dist/web/routes/ws-routes.d.ts +5 -0
  231. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  232. package/dist/web/routes/ws-routes.js +84 -47
  233. package/dist/web/routes/ws-routes.js.map +1 -1
  234. package/dist/web/schemas.d.ts +30 -7
  235. package/dist/web/schemas.d.ts.map +1 -1
  236. package/dist/web/schemas.js +40 -1
  237. package/dist/web/schemas.js.map +1 -1
  238. package/dist/web/server.d.ts +5 -2
  239. package/dist/web/server.d.ts.map +1 -1
  240. package/dist/web/server.js +84 -14
  241. package/dist/web/server.js.map +1 -1
  242. package/dist/web/settings-writer.d.ts +47 -0
  243. package/dist/web/settings-writer.d.ts.map +1 -0
  244. package/dist/web/settings-writer.js +110 -0
  245. package/dist/web/settings-writer.js.map +1 -0
  246. package/package.json +1 -1
  247. package/skills/codeman/SKILL.md +25 -6
  248. package/skills/codeman/reference/history.md +84 -0
  249. package/skills/codeman/reference/inspection-and-staging.md +152 -0
  250. package/skills/codeman/reference/messaging.md +1 -1
  251. package/skills/codeman/reference/verbs.md +18 -3
  252. package/dist/web/public/app.42d0abae.js +0 -43
  253. package/dist/web/public/app.42d0abae.js.br +0 -0
  254. package/dist/web/public/app.42d0abae.js.gz +0 -0
  255. package/dist/web/public/constants.0b3cdde9.js.br +0 -0
  256. package/dist/web/public/constants.0b3cdde9.js.gz +0 -0
  257. package/dist/web/public/i18n.1e68220d.js +0 -2
  258. package/dist/web/public/i18n.1e68220d.js.br +0 -0
  259. package/dist/web/public/i18n.1e68220d.js.gz +0 -0
  260. package/dist/web/public/mobile-handlers.1c0cbcd8.js.br +0 -0
  261. package/dist/web/public/mobile-handlers.1c0cbcd8.js.gz +0 -0
  262. package/dist/web/public/mobile.12fb18de.css +0 -1
  263. package/dist/web/public/mobile.12fb18de.css.br +0 -0
  264. package/dist/web/public/mobile.12fb18de.css.gz +0 -0
  265. package/dist/web/public/panels-ui.78c3defb.js.br +0 -0
  266. package/dist/web/public/panels-ui.78c3defb.js.gz +0 -0
  267. package/dist/web/public/session-ui.ea6b1994.js.br +0 -0
  268. package/dist/web/public/session-ui.ea6b1994.js.gz +0 -0
  269. package/dist/web/public/settings-ui.085dd202.js.br +0 -0
  270. package/dist/web/public/settings-ui.085dd202.js.gz +0 -0
  271. package/dist/web/public/styles.3b59b41d.css +0 -1
  272. package/dist/web/public/styles.3b59b41d.css.br +0 -0
  273. package/dist/web/public/styles.3b59b41d.css.gz +0 -0
  274. package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
  275. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  276. package/dist/web/public/terminal-ui.fb2a27f5.js +0 -2
  277. package/dist/web/public/terminal-ui.fb2a27f5.js.br +0 -0
  278. package/dist/web/public/terminal-ui.fb2a27f5.js.gz +0 -0
  279. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.br +0 -0
  280. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  281. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +0 -4
  282. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
  283. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/agent-session-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAehE,+FAA+F;AAC/F,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAGrE;AAED,sFAAsF;AACtF,eAAO,MAAM,mCAAmC,6KAC0H,CAAC;AAO3K,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIhD;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CA+InG"}
@@ -0,0 +1,160 @@
1
+ import { getCli } from '../../config/cli-registry/registry.js';
2
+ import { findExactCodexConversation, resolveEffectiveCodexHome } from '../../agent-codex-identity.js';
3
+ import { InspectionReader, InspectionConflict, boundedUtf8Tail } from '../../agent-inspection.js';
4
+ import { GuardedStageStore, GuardedInputConflict, guardedComposerChrome, MAY_BE_TYPED, } from '../../agent-staging.js';
5
+ import { ApiErrorCode, createErrorResponse } from '../../types.js';
6
+ import { findSessionOrFail, getAuthUser, parseBody, readJsonConfig, SETTINGS_PATH } from '../route-helpers.js';
7
+ import { AgentInspectQuerySchema, AgentStageSchema, AgentSubmitSchema } from '../schemas.js';
8
+ /** Default OFF, same shape as `readMcpSyncEnabled`: read fresh so a toggle applies at once. */
9
+ export async function readAgentCoordinationEnabled() {
10
+ const settings = await readJsonConfig(SETTINGS_PATH, 'settings.json', {});
11
+ return settings.agentCoordinationEnabled === true;
12
+ }
13
+ /** The refusal every route sends while the setting is off. Exported for the tests. */
14
+ export const AGENT_COORDINATION_DISABLED_MESSAGE = 'Experimental agent coordination (inspect, stage, submit) is disabled. Turn on "Experimental agent coordination" in Settings (agentCoordinationEnabled) and save first.';
15
+ async function gate() {
16
+ if (await readAgentCoordinationEnabled())
17
+ return null;
18
+ return createErrorResponse(ApiErrorCode.FORBIDDEN, AGENT_COORDINATION_DISABLED_MESSAGE);
19
+ }
20
+ /** A Node error code (`EACCES`, `EIO`, ...) or the error's class name; never its message. */
21
+ export function errorCode(error) {
22
+ const code = error?.code;
23
+ if (typeof code === 'string' && /^[A-Z0-9_]{1,40}$/.test(code))
24
+ return code;
25
+ return error instanceof Error ? error.constructor.name : 'unknown';
26
+ }
27
+ export function registerAgentSessionRoutes(app, ctx) {
28
+ const reader = new InspectionReader();
29
+ const stages = new GuardedStageStore();
30
+ app.addHook('onClose', async () => {
31
+ reader.clear();
32
+ stages.clear();
33
+ });
34
+ const generation = (session) => session.inspectionGeneration;
35
+ const target = (session) => ({
36
+ id: session.id,
37
+ chrome: () => guardedComposerChrome(getCli(session.mode)),
38
+ snapshot: () => {
39
+ if (ctx.sessions.get(session.id) !== session)
40
+ return null;
41
+ const snapshot = session.guardedInputSnapshot?.();
42
+ return snapshot ?? null;
43
+ },
44
+ write: (text) => session.stageGuardedInput(text),
45
+ submit: (expected) => session.submitGuardedInput(expected),
46
+ });
47
+ app.get('/api/sessions/:id/inspect', async (req, reply) => {
48
+ const refused = await gate();
49
+ if (refused)
50
+ return reply.code(403).send(refused);
51
+ const { id } = req.params;
52
+ const session = findSessionOrFail(ctx, id, req);
53
+ const { maxBytes, since } = parseBody(AgentInspectQuerySchema, req.query);
54
+ const initialGeneration = generation(session);
55
+ const identity = { sessionId: session.id, mode: session.mode };
56
+ const activity = { status: session.status, lastSubmitAt: session.lastSubmitAt || null, observedAt: Date.now() };
57
+ try {
58
+ if (getCli(session.mode)?.capabilities.transcript === 'codex-rollout' && !session.remote && !session.docker) {
59
+ const conversation = await findExactCodexConversation({
60
+ codexHome: resolveEffectiveCodexHome(session.getCodexHomeOverride(), session.workingDir),
61
+ sessionId: session.id,
62
+ workingDir: session.workingDir,
63
+ resumeSessionId: session.codexConfig?.resumeSessionId,
64
+ }, { maxEntries: 8192, maxBytes: 1048576 });
65
+ if (generation(session) !== initialGeneration || ctx.sessions.get(id) !== session)
66
+ throw new InspectionConflict('Session changed during inspection');
67
+ if (conversation) {
68
+ const result = await reader.read(conversation.path, {
69
+ owner: getAuthUser(req).username,
70
+ sessionId: id,
71
+ generation: initialGeneration,
72
+ conversationId: conversation.id,
73
+ }, maxBytes, since);
74
+ if (generation(session) !== initialGeneration || ctx.sessions.get(id) !== session)
75
+ throw new InspectionConflict('Session changed during inspection');
76
+ return {
77
+ success: true,
78
+ data: {
79
+ source: 'codex-jsonl',
80
+ identity: { ...identity, conversationId: conversation.id },
81
+ activity,
82
+ incremental: true,
83
+ maxBytes,
84
+ ...result,
85
+ },
86
+ };
87
+ }
88
+ }
89
+ if (since)
90
+ throw new InspectionConflict('Attributed transcript unavailable; this cursor cannot resume a pane snapshot');
91
+ const frame = session.muxName ? ctx.mux.capturePaneText?.(session.muxName) : null;
92
+ const evidence = frame ?? session.terminalBuffer;
93
+ return {
94
+ success: true,
95
+ data: {
96
+ source: 'pane-snapshot',
97
+ identity,
98
+ activity,
99
+ incremental: false,
100
+ cursor: null,
101
+ maxBytes,
102
+ evidence: boundedUtf8Tail(evidence, maxBytes),
103
+ truncated: evidence.length > maxBytes || Buffer.byteLength(evidence) > maxBytes,
104
+ },
105
+ };
106
+ }
107
+ catch (error) {
108
+ if (error instanceof InspectionConflict)
109
+ return reply.code(409).send(createErrorResponse(ApiErrorCode.CONFLICT, error.message));
110
+ // Anything else (an unreadable rollout, an IO error) is not a retryable cursor
111
+ // conflict. Log the session and the error code only: the message can carry the
112
+ // native transcript path, which must never reach the log or the response.
113
+ console.warn(`[agent-inspect] session ${session.id}: inspection failed (${errorCode(error)})`);
114
+ return reply.code(422).send(createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Inspection source unavailable'));
115
+ }
116
+ });
117
+ app.post('/api/sessions/:id/stage', async (req, reply) => {
118
+ const refused = await gate();
119
+ if (refused)
120
+ return reply.code(403).send(refused);
121
+ const session = findSessionOrFail(ctx, req.params.id, req);
122
+ const { message } = parseBody(AgentStageSchema, req.body);
123
+ try {
124
+ const receipt = await stages.stage(target(session), getAuthUser(req).username, message);
125
+ return { success: true, data: receipt };
126
+ }
127
+ catch (error) {
128
+ if (!(error instanceof GuardedInputConflict))
129
+ console.warn(`[agent-stage] session ${session.id}: delivery failed (${errorCode(error)})`);
130
+ return reply
131
+ .code(409)
132
+ .send(createErrorResponse(ApiErrorCode.CONFLICT, error instanceof GuardedInputConflict
133
+ ? error.message
134
+ : `Stage delivery failed or ambiguous; ${MAY_BE_TYPED}`));
135
+ }
136
+ });
137
+ app.post('/api/sessions/:id/submit', async (req, reply) => {
138
+ const refused = await gate();
139
+ if (refused)
140
+ return reply.code(403).send(refused);
141
+ const session = findSessionOrFail(ctx, req.params.id, req);
142
+ const { stageId, confirmSha } = parseBody(AgentSubmitSchema, req.body);
143
+ try {
144
+ return {
145
+ success: true,
146
+ data: await stages.submit(target(session), getAuthUser(req).username, stageId, confirmSha),
147
+ };
148
+ }
149
+ catch (error) {
150
+ if (!(error instanceof GuardedInputConflict))
151
+ console.warn(`[agent-submit] session ${session.id}: delivery failed (${errorCode(error)})`);
152
+ return reply
153
+ .code(409)
154
+ .send(createErrorResponse(ApiErrorCode.CONFLICT, error instanceof GuardedInputConflict
155
+ ? error.message
156
+ : 'Submit delivery failed or ambiguous; receipt consumed, inspect the pane'));
157
+ }
158
+ });
159
+ }
160
+ //# sourceMappingURL=agent-session-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-session-routes.js","sourceRoot":"","sources":["../../../src/web/routes/agent-session-routes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"case-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/case-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA2E1C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA4D9C;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAE1E;AAgID,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GACxD,IAAI,CAo+DN"}
1
+ {"version":3,"file":"case-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/case-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAiF1C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA4D9C;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAE1E;AAgID,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GACxD,IAAI,CA+9DN"}
@@ -22,6 +22,7 @@ import { boundedPathExists, describeUnknownPath, probePath, probePathKind } from
22
22
  import { readAgentCaseMarker } from '../../agent-case-marker.js';
23
23
  import { settingsWriteBlocker, writeHooksConfig } from '../../hooks-config.js';
24
24
  import { canAccessOwned, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, requireAdmin, resolveCasesDir, SETTINGS_PATH, validatePathWithinBase, parseBody, readJsonConfig, } from '../route-helpers.js';
25
+ import { assertSettingsWritable, mutateSettingsFile, SettingsWriteRefusedError, settingsWriteErrorResponse, } from '../settings-writer.js';
25
26
  import { isMultiUserMode } from '../../config/multiuser.js';
26
27
  import { SseEvent } from '../sse-events.js';
27
28
  import { ownerLayoutKey } from '../../tab-layout-persistence.js';
@@ -1529,6 +1530,19 @@ export function registerCaseRoutes(app, ctx) {
1529
1530
  if (open.some((s) => !canAccessOwned(user, s.owner))) {
1530
1531
  return fail(403, ApiErrorCode.FORBIDDEN, "Another user's session has this case open");
1531
1532
  }
1533
+ // The admin's rename writes settings.json only after the sessions are ended and the folder moved, and
1534
+ // the writer refuses a file that is not a JSON object: check it by the writer's own rule now, so no
1535
+ // session is ended for a rename that cannot complete. Not between the starting check and the lock below.
1536
+ if (isAdmin(req)) {
1537
+ try {
1538
+ await assertSettingsWritable();
1539
+ }
1540
+ catch (err) {
1541
+ if (err instanceof SettingsWriteRefusedError)
1542
+ return fail(409, ApiErrorCode.CONFLICT, err.message);
1543
+ throw err;
1544
+ }
1545
+ }
1532
1546
  // A session being created in the folder is not in ctx.sessions yet, so it could be neither listed nor
1533
1547
  // stopped: it would start in the old path after the move. Checked in the same tick that takes the lock
1534
1548
  // below (nothing is awaited in between), and the Session constructor refuses the folder from then on.
@@ -1551,20 +1565,24 @@ export function registerCaseRoutes(app, ctx) {
1551
1565
  if (moveFolder)
1552
1566
  await moveCaseFolder(oldPath, newPath);
1553
1567
  const originalLinks = isLinked ? await readLinkedCases() : undefined;
1554
- // The settings.json content before this rename wrote it, set only once a write is attempted (an
1555
- // absent file reads as {}, which must not be written back as a file that never existed).
1556
- let originalSettings;
1568
+ // The settings.json values this rename replaced, set only once it wrote them. A failure puts back
1569
+ // these keys alone, so a settings save that landed in between is kept.
1570
+ let settingsUndo;
1557
1571
  try {
1558
1572
  // settings.json is server-wide and caseOrder/lastUsedCase name the admin's cases, so only the
1559
1573
  // admin's rename touches it (a non-admin's own rename must not rewrite someone else's list).
1560
1574
  // Written before linked-cases.json so a failure here leaves the registry untouched.
1561
1575
  if (isAdmin(req)) {
1562
- const existing = await readJsonConfig(SETTINGS_PATH, 'settings.json', {});
1563
- const order = renameInOrder(existing.caseOrder, name, newName);
1564
- const last = existing.lastUsedCase === name ? newName : undefined;
1565
- if (order || last) {
1566
- originalSettings = existing;
1567
- await fs.writeFile(SETTINGS_PATH, JSON.stringify({ ...existing, ...(order ? { caseOrder: order } : {}), ...(last ? { lastUsedCase: last } : {}) }, null, 2));
1576
+ const { before, after } = await mutateSettingsFile((existing) => {
1577
+ const order = renameInOrder(existing.caseOrder, name, newName);
1578
+ const last = existing.lastUsedCase === name ? newName : undefined;
1579
+ if (!order && !last)
1580
+ return undefined;
1581
+ return { ...existing, ...(order ? { caseOrder: order } : {}), ...(last ? { lastUsedCase: last } : {}) };
1582
+ });
1583
+ if (after) {
1584
+ const keys = ['caseOrder', 'lastUsedCase'].filter((k) => after[k] !== before[k]);
1585
+ settingsUndo = Object.fromEntries(keys.map((k) => [k, before[k]]));
1568
1586
  }
1569
1587
  }
1570
1588
  if (originalLinks) {
@@ -1583,9 +1601,9 @@ export function registerCaseRoutes(app, ctx) {
1583
1601
  catch (err) {
1584
1602
  // The folder moved but the registries did not follow: put settings.json and the folder back
1585
1603
  // rather than leave a case whose name and folder disagree.
1586
- if (originalSettings) {
1587
- await fs.writeFile(SETTINGS_PATH, JSON.stringify(originalSettings, null, 2)).catch(() => { });
1588
- }
1604
+ const undo = settingsUndo;
1605
+ if (undo)
1606
+ await mutateSettingsFile((current) => ({ ...current, ...undo })).catch(() => { });
1589
1607
  if (moveFolder) {
1590
1608
  const movedBack = await fs.rename(newPath, oldPath).then(() => true, () => false);
1591
1609
  if (!movedBack) {
@@ -1638,24 +1656,12 @@ export function registerCaseRoutes(app, ctx) {
1638
1656
  return;
1639
1657
  const { order } = parseBody(CaseOrderSchema, req.body, 'Invalid order data');
1640
1658
  try {
1641
- const dir = getDataDir();
1642
- if (!existsSync(dir)) {
1643
- mkdirSync(dir, { recursive: true });
1644
- }
1645
- let existing = {};
1646
- try {
1647
- existing = JSON.parse(await fs.readFile(SETTINGS_PATH, 'utf-8'));
1648
- }
1649
- catch {
1650
- /* ignore */
1651
- }
1652
- const merged = { ...existing, caseOrder: order };
1653
- await fs.writeFile(SETTINGS_PATH, JSON.stringify(merged, null, 2));
1659
+ await mutateSettingsFile((current) => ({ ...current, caseOrder: order }));
1654
1660
  ctx.broadcast(SseEvent.CaseOrderChanged, { order });
1655
1661
  return { success: true, data: { order } };
1656
1662
  }
1657
1663
  catch (err) {
1658
- return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
1664
+ return settingsWriteErrorResponse(err);
1659
1665
  }
1660
1666
  });
1661
1667
  app.get('/api/cases/:name', async (req) => {