@songsid/agend 2.3.5-alpha.5 → 2.3.5-beta.1

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 (976) hide show
  1. package/README.md +0 -2
  2. package/README.zh-TW.md +0 -2
  3. package/THIRD_PARTY_NOTICES.md +0 -68
  4. package/dist/agent-endpoint.d.ts +1 -5
  5. package/dist/agent-endpoint.js +2 -12
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-file-read.js +1 -1
  8. package/dist/agent-file-read.js.map +1 -1
  9. package/dist/auth-api.d.ts +0 -48
  10. package/dist/auth-api.js +4 -250
  11. package/dist/auth-api.js.map +1 -1
  12. package/dist/authority/session-authorizations.d.ts +2 -0
  13. package/dist/authority/session-authorizations.js +7 -1
  14. package/dist/authority/session-authorizations.js.map +1 -1
  15. package/dist/authority/web-authority.d.ts +2 -12
  16. package/dist/authority/web-authority.js +4 -25
  17. package/dist/authority/web-authority.js.map +1 -1
  18. package/dist/backend/claude-code.d.ts +12 -15
  19. package/dist/backend/claude-code.js +133 -152
  20. package/dist/backend/claude-code.js.map +1 -1
  21. package/dist/backend/codex-footer-hints.d.ts +0 -3
  22. package/dist/backend/codex-footer-hints.js +0 -14
  23. package/dist/backend/codex-footer-hints.js.map +1 -1
  24. package/dist/backend/codex-package.d.ts +2 -5
  25. package/dist/backend/codex-package.js +5 -1
  26. package/dist/backend/codex-package.js.map +1 -1
  27. package/dist/backend/codex-quota-history.js +19 -4
  28. package/dist/backend/codex-quota-history.js.map +1 -1
  29. package/dist/backend/codex.d.ts +115 -142
  30. package/dist/backend/codex.js +1094 -1187
  31. package/dist/backend/codex.js.map +1 -1
  32. package/dist/backend/grok.d.ts +0 -2
  33. package/dist/backend/grok.js +0 -22
  34. package/dist/backend/grok.js.map +1 -1
  35. package/dist/backend/types.d.ts +2 -7
  36. package/dist/backend/types.js.map +1 -1
  37. package/dist/bundled-tmux-guard.d.ts +21 -0
  38. package/dist/bundled-tmux-guard.js +96 -0
  39. package/dist/bundled-tmux-guard.js.map +1 -0
  40. package/dist/channel/ipc-bridge.d.ts +0 -2
  41. package/dist/channel/mcp-tools.js +2 -2
  42. package/dist/channel/mcp-tools.js.map +1 -1
  43. package/dist/classic-channel-manager.d.ts +2 -0
  44. package/dist/classic-channel-manager.js +5 -1
  45. package/dist/classic-channel-manager.js.map +1 -1
  46. package/dist/cli.js +30 -127
  47. package/dist/cli.js.map +1 -1
  48. package/dist/config-validator.js +13 -74
  49. package/dist/config-validator.js.map +1 -1
  50. package/dist/config.js +3 -17
  51. package/dist/config.js.map +1 -1
  52. package/dist/daemon.d.ts +5 -72
  53. package/dist/daemon.js +275 -869
  54. package/dist/daemon.js.map +1 -1
  55. package/dist/delivery-consumed-watch.d.ts +3 -15
  56. package/dist/delivery-consumed-watch.js +1 -23
  57. package/dist/delivery-consumed-watch.js.map +1 -1
  58. package/dist/delivery-outbox.d.ts +0 -5
  59. package/dist/delivery-outbox.js +4 -32
  60. package/dist/delivery-outbox.js.map +1 -1
  61. package/dist/delivery-reconciliation.d.ts +42 -2
  62. package/dist/delivery-reconciliation.js +164 -2
  63. package/dist/delivery-reconciliation.js.map +1 -1
  64. package/dist/doctor.js +9 -20
  65. package/dist/doctor.js.map +1 -1
  66. package/dist/fleet-context.d.ts +2 -2
  67. package/dist/fleet-level-config.d.ts +1 -1
  68. package/dist/fleet-level-config.js +0 -2
  69. package/dist/fleet-level-config.js.map +1 -1
  70. package/dist/fleet-manager.d.ts +9 -498
  71. package/dist/fleet-manager.js +202 -2849
  72. package/dist/fleet-manager.js.map +1 -1
  73. package/dist/general-instance.d.ts +0 -7
  74. package/dist/general-instance.js +3 -20
  75. package/dist/general-instance.js.map +1 -1
  76. package/dist/general-knowledge/skills/concepts/SKILL.md +18 -50
  77. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +2 -5
  78. package/dist/general-knowledge/skills/fleet-health/SKILL.md +2 -2
  79. package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
  80. package/dist/general-knowledge/skills/tui-effort/SKILL.md +10 -10
  81. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +13 -23
  82. package/dist/instance-config-impact.js +4 -8
  83. package/dist/instance-config-impact.js.map +1 -1
  84. package/dist/instance-lifecycle.d.ts +3 -22
  85. package/dist/instance-lifecycle.js +176 -393
  86. package/dist/instance-lifecycle.js.map +1 -1
  87. package/dist/instructions.d.ts +0 -2
  88. package/dist/instructions.js +0 -6
  89. package/dist/instructions.js.map +1 -1
  90. package/dist/locale.js +2 -147
  91. package/dist/locale.js.map +1 -1
  92. package/dist/login-flows.d.ts +7 -8
  93. package/dist/login-flows.js +1 -24
  94. package/dist/login-flows.js.map +1 -1
  95. package/dist/ls-rows.js +4 -3
  96. package/dist/ls-rows.js.map +1 -1
  97. package/dist/muse-usage-relay.d.ts +2 -0
  98. package/dist/muse-usage-relay.js +5 -3
  99. package/dist/muse-usage-relay.js.map +1 -1
  100. package/dist/needs-you-hub.d.ts +0 -5
  101. package/dist/needs-you-hub.js +2 -18
  102. package/dist/needs-you-hub.js.map +1 -1
  103. package/dist/needs-you.d.ts +2 -45
  104. package/dist/needs-you.js +2 -26
  105. package/dist/needs-you.js.map +1 -1
  106. package/dist/office-share-page.js +3 -3
  107. package/dist/office-share-page.js.map +1 -1
  108. package/dist/office-share.d.ts +0 -11
  109. package/dist/office-share.js +0 -14
  110. package/dist/office-share.js.map +1 -1
  111. package/dist/outbound-handlers.d.ts +2 -30
  112. package/dist/outbound-handlers.js +4 -109
  113. package/dist/outbound-handlers.js.map +1 -1
  114. package/dist/outbound-schemas.d.ts +1 -71
  115. package/dist/outbound-schemas.js +14 -36
  116. package/dist/outbound-schemas.js.map +1 -1
  117. package/dist/pane-quota-history.d.ts +0 -2
  118. package/dist/pane-quota-history.js +0 -20
  119. package/dist/pane-quota-history.js.map +1 -1
  120. package/dist/pause-marker.d.ts +1 -1
  121. package/dist/pause-marker.js +1 -1
  122. package/dist/pause-marker.js.map +1 -1
  123. package/dist/public-web-gateway.js +0 -16
  124. package/dist/public-web-gateway.js.map +1 -1
  125. package/dist/public-web-link.d.ts +0 -11
  126. package/dist/public-web-link.js +0 -13
  127. package/dist/public-web-link.js.map +1 -1
  128. package/dist/quickstart-api.js +5 -9
  129. package/dist/quickstart-api.js.map +1 -1
  130. package/dist/quickstart.d.ts +0 -6
  131. package/dist/quickstart.js +6 -44
  132. package/dist/quickstart.js.map +1 -1
  133. package/dist/quota-notice-config.d.ts +22 -0
  134. package/dist/quota-notice-config.js +41 -0
  135. package/dist/quota-notice-config.js.map +1 -0
  136. package/dist/scheduler/task-board-text.js +1 -1
  137. package/dist/scheduler/task-board-text.js.map +1 -1
  138. package/dist/scheduler/task-board.d.ts +0 -4
  139. package/dist/scheduler/task-board.js +3 -9
  140. package/dist/scheduler/task-board.js.map +1 -1
  141. package/dist/service-installer.d.ts +2 -7
  142. package/dist/service-installer.js +10 -13
  143. package/dist/service-installer.js.map +1 -1
  144. package/dist/settings-api.d.ts +0 -12
  145. package/dist/settings-api.js +7 -487
  146. package/dist/settings-api.js.map +1 -1
  147. package/dist/settings-baseline.d.ts +0 -7
  148. package/dist/settings-baseline.js +1 -3
  149. package/dist/settings-baseline.js.map +1 -1
  150. package/dist/settings-change.js +7 -35
  151. package/dist/settings-change.js.map +1 -1
  152. package/dist/settings-confirmation.d.ts +1 -16
  153. package/dist/settings-confirmation.js +9 -47
  154. package/dist/settings-confirmation.js.map +1 -1
  155. package/dist/settings-effect.d.ts +0 -17
  156. package/dist/settings-effect.js +1 -155
  157. package/dist/settings-effect.js.map +1 -1
  158. package/dist/settings-http-confirmation.d.ts +0 -4
  159. package/dist/settings-http-confirmation.js +3 -43
  160. package/dist/settings-http-confirmation.js.map +1 -1
  161. package/dist/settings-instance-patch.js +1 -1
  162. package/dist/settings-instance-patch.js.map +1 -1
  163. package/dist/settings-request-capability.d.ts +0 -2
  164. package/dist/settings-request-capability.js +0 -4
  165. package/dist/settings-request-capability.js.map +1 -1
  166. package/dist/settings-transaction.d.ts +0 -4
  167. package/dist/settings-transaction.js +0 -9
  168. package/dist/settings-transaction.js.map +1 -1
  169. package/dist/setup-form.js +3 -6
  170. package/dist/setup-form.js.map +1 -1
  171. package/dist/setup-i18n.js +0 -3
  172. package/dist/setup-i18n.js.map +1 -1
  173. package/dist/setup-wizard.js +2 -6
  174. package/dist/setup-wizard.js.map +1 -1
  175. package/dist/task-board-tool.d.ts +1 -2
  176. package/dist/task-board-tool.js +6 -62
  177. package/dist/task-board-tool.js.map +1 -1
  178. package/dist/tmux-control.d.ts +1 -2
  179. package/dist/tmux-control.js +4 -4
  180. package/dist/tmux-control.js.map +1 -1
  181. package/dist/tmux-exec-helper.js +1 -32
  182. package/dist/tmux-exec-helper.js.map +1 -1
  183. package/dist/tmux-manager.d.ts +2 -3
  184. package/dist/tmux-manager.js +36 -45
  185. package/dist/tmux-manager.js.map +1 -1
  186. package/dist/tmux-read.js +3 -4
  187. package/dist/tmux-read.js.map +1 -1
  188. package/dist/tool-permissions.js +1 -2
  189. package/dist/tool-permissions.js.map +1 -1
  190. package/dist/topic-commands.js +5 -9
  191. package/dist/topic-commands.js.map +1 -1
  192. package/dist/transcript-delta-reader.d.ts +1 -1
  193. package/dist/transcript-delta-reader.js +1 -1
  194. package/dist/transcript-delta-reader.js.map +1 -1
  195. package/dist/transcript-monitor.d.ts +0 -3
  196. package/dist/transcript-monitor.js +51 -27
  197. package/dist/transcript-monitor.js.map +1 -1
  198. package/dist/transcript-sources.d.ts +0 -5
  199. package/dist/transcript-sources.js +79 -8
  200. package/dist/transcript-sources.js.map +1 -1
  201. package/dist/types.d.ts +3 -29
  202. package/dist/ui/app.html +8 -1
  203. package/dist/ui/backend-signin.js +21 -39
  204. package/dist/ui/chat-ambient.js +1 -1
  205. package/dist/ui/chat-md-strings.js +9 -3
  206. package/dist/ui/chat-render.js +670 -3
  207. package/dist/ui/chat-store.js +3 -14
  208. package/dist/ui/chat-strings.js +285 -3
  209. package/dist/ui/chat-thread.js +969 -3
  210. package/dist/ui/chat-time.js +80 -3
  211. package/dist/ui/chat-tour.js +1 -1
  212. package/dist/ui/fleet-office-pack.d.ts +0 -9
  213. package/dist/ui/fleet-office-pack.js +4 -76
  214. package/dist/ui/fleet-office-skin.js +5 -23
  215. package/dist/ui/fleet-office-store.js +0 -5
  216. package/dist/ui/fleet-office.js +6 -173
  217. package/dist/ui/fleet-task-card.js +4 -8
  218. package/dist/ui/fleet-task-common.js +2 -2
  219. package/dist/ui/fleet-tasks.js +39 -125
  220. package/dist/ui/html-attachment.js +40 -3
  221. package/dist/ui/office-share-page.js +2 -4
  222. package/dist/ui/panel-chat.js +75 -90
  223. package/dist/ui/panel-details.js +2 -3
  224. package/dist/ui/panel-fleet.js +18 -105
  225. package/dist/ui/panel-log.js +2 -2
  226. package/dist/ui/panel-needs.js +4 -162
  227. package/dist/ui/panel-notices.js +4 -3
  228. package/dist/ui/panel-settings.js +202 -281
  229. package/dist/ui/preview-pages.js +1 -1
  230. package/dist/ui/preview-panel.js +13 -21
  231. package/dist/ui/preview-versions.js +1 -1
  232. package/dist/ui/preview.js +476 -3
  233. package/dist/ui/search-dialog.js +3 -4
  234. package/dist/ui/settings-appearance.js +23 -122
  235. package/dist/ui/settings-catalog.js +10 -37
  236. package/dist/ui/settings-confirm.js +1 -3
  237. package/dist/ui/settings-dialogs.js +20 -48
  238. package/dist/ui/settings-guests.js +14 -27
  239. package/dist/ui/settings-model.js +1 -10
  240. package/dist/ui/settings-quota-notice.js +100 -0
  241. package/dist/ui/settings-registry.js +5 -15
  242. package/dist/ui/settings-strings.js +32 -105
  243. package/dist/ui/settings-web-authority.js +13 -18
  244. package/dist/ui/shared/app-i18n.js +2 -2
  245. package/dist/ui/shared/app-route.js +3 -12
  246. package/dist/ui/shared/app-session.js +2 -6
  247. package/dist/ui/shared/app-shell.js +26 -87
  248. package/dist/ui/shared/app-store.js +2 -3
  249. package/dist/ui/shared/app-stream.js +1 -3
  250. package/dist/ui/shared/app.css +5 -1368
  251. package/dist/ui/shared/app.js +5 -17
  252. package/dist/ui/shared/header-tools.js +9 -9
  253. package/dist/ui/shared/instance-nav.js +7 -34
  254. package/dist/ui/shared/instance-switch.js +3 -4
  255. package/dist/ui/shared/loading.css +37 -0
  256. package/dist/ui/shared/signin.js +6 -211
  257. package/dist/ui/shared/tokens.css +0 -1
  258. package/dist/ui/shared/ui-dialog.js +2 -2
  259. package/dist/ui/shared/ui-menu.js +10 -30
  260. package/dist/ui/shared/ui-states.js +14 -3
  261. package/dist/ui/shared/vendor/vendor.json +2 -19
  262. package/dist/ui/shared/view-strings.js +0 -8
  263. package/dist/ui/signin.html +5 -23
  264. package/dist/unattended-install.js +1 -2
  265. package/dist/unattended-install.js.map +1 -1
  266. package/dist/usage/providers.d.ts +5 -0
  267. package/dist/usage/providers.js +6 -2
  268. package/dist/usage/providers.js.map +1 -1
  269. package/dist/usage/quota-notices.d.ts +38 -0
  270. package/dist/usage/quota-notices.js +160 -0
  271. package/dist/usage/quota-notices.js.map +1 -0
  272. package/dist/usage/usage-api.d.ts +1 -0
  273. package/dist/usage/usage-api.js +16 -0
  274. package/dist/usage/usage-api.js.map +1 -1
  275. package/dist/view-api.js +7 -5
  276. package/dist/view-api.js.map +1 -1
  277. package/dist/web-api.d.ts +2 -80
  278. package/dist/web-api.js +10 -513
  279. package/dist/web-api.js.map +1 -1
  280. package/dist/web-backend-login.d.ts +1 -23
  281. package/dist/web-backend-login.js +17 -11
  282. package/dist/web-backend-login.js.map +1 -1
  283. package/dist/web-chat-history.d.ts +2 -19
  284. package/dist/web-chat-history.js +3 -72
  285. package/dist/web-chat-history.js.map +1 -1
  286. package/dist/web-office-skins.d.ts +0 -9
  287. package/dist/web-office-skins.js +1 -16
  288. package/dist/web-office-skins.js.map +1 -1
  289. package/dist/web-only-fleet.d.ts +0 -20
  290. package/dist/web-only-fleet.js +0 -21
  291. package/dist/web-only-fleet.js.map +1 -1
  292. package/dist/web-session.d.ts +0 -42
  293. package/dist/web-session.js +0 -9
  294. package/dist/web-session.js.map +1 -1
  295. package/dist/web-settings-patch.d.ts +1 -3
  296. package/dist/web-settings-patch.js +1 -4
  297. package/dist/web-settings-patch.js.map +1 -1
  298. package/dist/web-shell-routes.d.ts +1 -10
  299. package/dist/web-shell-routes.js +2 -14
  300. package/dist/web-shell-routes.js.map +1 -1
  301. package/dist/web-terminal.d.ts +30 -4
  302. package/dist/web-terminal.js +6 -9
  303. package/dist/web-terminal.js.map +1 -1
  304. package/dist/yaml-shared-nodes.js +3 -8
  305. package/dist/yaml-shared-nodes.js.map +1 -1
  306. package/launcher/agend-install.cjs +1 -8
  307. package/launcher/postinstall.cjs +2 -27
  308. package/package.json +3 -10
  309. package/templates/launchd.plist.ejs +1 -3
  310. package/dist/accounts-view.d.ts +0 -97
  311. package/dist/accounts-view.js +0 -116
  312. package/dist/accounts-view.js.map +0 -1
  313. package/dist/agent-runtime-api.d.ts +0 -4
  314. package/dist/agent-runtime-api.js +0 -59
  315. package/dist/agent-runtime-api.js.map +0 -1
  316. package/dist/agent-runtime-change.d.ts +0 -76
  317. package/dist/agent-runtime-change.js +0 -114
  318. package/dist/agent-runtime-change.js.map +0 -1
  319. package/dist/agent-workspace.d.ts +0 -93
  320. package/dist/agent-workspace.js +0 -353
  321. package/dist/agent-workspace.js.map +0 -1
  322. package/dist/backend/grok-model-cache.d.ts +0 -7
  323. package/dist/backend/grok-model-cache.js +0 -65
  324. package/dist/backend/grok-model-cache.js.map +0 -1
  325. package/dist/backend/native-pane.d.ts +0 -11
  326. package/dist/backend/native-pane.js +0 -59
  327. package/dist/backend/native-pane.js.map +0 -1
  328. package/dist/build-provenance-cli.d.ts +0 -3
  329. package/dist/build-provenance-cli.js +0 -11
  330. package/dist/build-provenance-cli.js.map +0 -1
  331. package/dist/build-provenance.d.ts +0 -20
  332. package/dist/build-provenance.js +0 -67
  333. package/dist/build-provenance.js.map +0 -1
  334. package/dist/build-provenance.json +0 -1
  335. package/dist/claude-auth-status.d.ts +0 -48
  336. package/dist/claude-auth-status.js +0 -149
  337. package/dist/claude-auth-status.js.map +0 -1
  338. package/dist/fleet-windows-runtime.d.ts +0 -98
  339. package/dist/fleet-windows-runtime.js +0 -467
  340. package/dist/fleet-windows-runtime.js.map +0 -1
  341. package/dist/general-binding.d.ts +0 -85
  342. package/dist/general-binding.js +0 -298
  343. package/dist/general-binding.js.map +0 -1
  344. package/dist/general-host-owner.d.ts +0 -8
  345. package/dist/general-host-owner.js +0 -26
  346. package/dist/general-host-owner.js.map +0 -1
  347. package/dist/general-role.d.ts +0 -5
  348. package/dist/general-role.js +0 -27
  349. package/dist/general-role.js.map +0 -1
  350. package/dist/hub/admission.d.ts +0 -28
  351. package/dist/hub/admission.js +0 -62
  352. package/dist/hub/admission.js.map +0 -1
  353. package/dist/hub/attachments/limits.d.ts +0 -20
  354. package/dist/hub/attachments/limits.js +0 -48
  355. package/dist/hub/attachments/limits.js.map +0 -1
  356. package/dist/hub/attachments/store.d.ts +0 -116
  357. package/dist/hub/attachments/store.js +0 -246
  358. package/dist/hub/attachments/store.js.map +0 -1
  359. package/dist/hub/audit/index.d.ts +0 -62
  360. package/dist/hub/audit/index.js +0 -344
  361. package/dist/hub/audit/index.js.map +0 -1
  362. package/dist/hub/browser-certificate.d.ts +0 -38
  363. package/dist/hub/browser-certificate.js +0 -247
  364. package/dist/hub/browser-certificate.js.map +0 -1
  365. package/dist/hub/buttons-model.d.ts +0 -9
  366. package/dist/hub/buttons-model.js +0 -42
  367. package/dist/hub/buttons-model.js.map +0 -1
  368. package/dist/hub/buttons.d.ts +0 -54
  369. package/dist/hub/buttons.js +0 -346
  370. package/dist/hub/buttons.js.map +0 -1
  371. package/dist/hub/catalog.d.ts +0 -74
  372. package/dist/hub/catalog.js +0 -195
  373. package/dist/hub/catalog.js.map +0 -1
  374. package/dist/hub/client.d.ts +0 -69
  375. package/dist/hub/client.js +0 -300
  376. package/dist/hub/client.js.map +0 -1
  377. package/dist/hub/compose/member.d.ts +0 -139
  378. package/dist/hub/compose/member.js +0 -450
  379. package/dist/hub/compose/member.js.map +0 -1
  380. package/dist/hub/compose/server.d.ts +0 -77
  381. package/dist/hub/compose/server.js +0 -349
  382. package/dist/hub/compose/server.js.map +0 -1
  383. package/dist/hub/compose/views.d.ts +0 -12
  384. package/dist/hub/compose/views.js +0 -4
  385. package/dist/hub/compose/views.js.map +0 -1
  386. package/dist/hub/db.d.ts +0 -56
  387. package/dist/hub/db.js +0 -188
  388. package/dist/hub/db.js.map +0 -1
  389. package/dist/hub/directory/config.d.ts +0 -39
  390. package/dist/hub/directory/config.js +0 -108
  391. package/dist/hub/directory/config.js.map +0 -1
  392. package/dist/hub/directory/directory.d.ts +0 -18
  393. package/dist/hub/directory/directory.js +0 -320
  394. package/dist/hub/directory/directory.js.map +0 -1
  395. package/dist/hub/directory/index.d.ts +0 -7
  396. package/dist/hub/directory/index.js +0 -4
  397. package/dist/hub/directory/index.js.map +0 -1
  398. package/dist/hub/directory/profiles.d.ts +0 -41
  399. package/dist/hub/directory/profiles.js +0 -90
  400. package/dist/hub/directory/profiles.js.map +0 -1
  401. package/dist/hub/directory/types.d.ts +0 -71
  402. package/dist/hub/directory/types.js +0 -14
  403. package/dist/hub/directory/types.js.map +0 -1
  404. package/dist/hub/execution/cli-provider.d.ts +0 -23
  405. package/dist/hub/execution/cli-provider.js +0 -341
  406. package/dist/hub/execution/cli-provider.js.map +0 -1
  407. package/dist/hub/execution/index.d.ts +0 -13
  408. package/dist/hub/execution/index.js +0 -8
  409. package/dist/hub/execution/index.js.map +0 -1
  410. package/dist/hub/execution/model-broker.d.ts +0 -24
  411. package/dist/hub/execution/model-broker.js +0 -208
  412. package/dist/hub/execution/model-broker.js.map +0 -1
  413. package/dist/hub/execution/provider-setup.d.ts +0 -11
  414. package/dist/hub/execution/provider-setup.js +0 -21
  415. package/dist/hub/execution/provider-setup.js.map +0 -1
  416. package/dist/hub/execution/sandbox-runner.d.ts +0 -1
  417. package/dist/hub/execution/sandbox-runner.js +0 -221
  418. package/dist/hub/execution/sandbox-runner.js.map +0 -1
  419. package/dist/hub/execution/service.d.ts +0 -136
  420. package/dist/hub/execution/service.js +0 -459
  421. package/dist/hub/execution/service.js.map +0 -1
  422. package/dist/hub/execution/store.d.ts +0 -61
  423. package/dist/hub/execution/store.js +0 -182
  424. package/dist/hub/execution/store.js.map +0 -1
  425. package/dist/hub/execution/views.d.ts +0 -20
  426. package/dist/hub/execution/views.js +0 -48
  427. package/dist/hub/execution/views.js.map +0 -1
  428. package/dist/hub/http/compose.d.ts +0 -20
  429. package/dist/hub/http/compose.js +0 -518
  430. package/dist/hub/http/compose.js.map +0 -1
  431. package/dist/hub/http/core.d.ts +0 -49
  432. package/dist/hub/http/core.js +0 -124
  433. package/dist/hub/http/core.js.map +0 -1
  434. package/dist/hub/http/deps.d.ts +0 -61
  435. package/dist/hub/http/deps.js +0 -2
  436. package/dist/hub/http/deps.js.map +0 -1
  437. package/dist/hub/http/schema.d.ts +0 -14
  438. package/dist/hub/http/schema.js +0 -80
  439. package/dist/hub/http/schema.js.map +0 -1
  440. package/dist/hub/identity.d.ts +0 -32
  441. package/dist/hub/identity.js +0 -80
  442. package/dist/hub/identity.js.map +0 -1
  443. package/dist/hub/init/browser-certificate.d.ts +0 -9
  444. package/dist/hub/init/browser-certificate.js +0 -78
  445. package/dist/hub/init/browser-certificate.js.map +0 -1
  446. package/dist/hub/init/browser-tls.d.ts +0 -18
  447. package/dist/hub/init/browser-tls.js +0 -62
  448. package/dist/hub/init/browser-tls.js.map +0 -1
  449. package/dist/hub/init/cli.d.ts +0 -3
  450. package/dist/hub/init/cli.js +0 -83
  451. package/dist/hub/init/cli.js.map +0 -1
  452. package/dist/hub/init/directory-certificate.d.ts +0 -24
  453. package/dist/hub/init/directory-certificate.js +0 -222
  454. package/dist/hub/init/directory-certificate.js.map +0 -1
  455. package/dist/hub/init/existing-fleet.d.ts +0 -6
  456. package/dist/hub/init/existing-fleet.js +0 -58
  457. package/dist/hub/init/existing-fleet.js.map +0 -1
  458. package/dist/hub/init/handoff.d.ts +0 -8
  459. package/dist/hub/init/handoff.js +0 -36
  460. package/dist/hub/init/handoff.js.map +0 -1
  461. package/dist/hub/init/identity.d.ts +0 -5
  462. package/dist/hub/init/identity.js +0 -24
  463. package/dist/hub/init/identity.js.map +0 -1
  464. package/dist/hub/init/initializer.d.ts +0 -24
  465. package/dist/hub/init/initializer.js +0 -385
  466. package/dist/hub/init/initializer.js.map +0 -1
  467. package/dist/hub/init/inventory.d.ts +0 -17
  468. package/dist/hub/init/inventory.js +0 -33
  469. package/dist/hub/init/inventory.js.map +0 -1
  470. package/dist/hub/init/layout.d.ts +0 -32
  471. package/dist/hub/init/layout.js +0 -53
  472. package/dist/hub/init/layout.js.map +0 -1
  473. package/dist/hub/init/network.d.ts +0 -18
  474. package/dist/hub/init/network.js +0 -104
  475. package/dist/hub/init/network.js.map +0 -1
  476. package/dist/hub/init/plan-digest.d.ts +0 -5
  477. package/dist/hub/init/plan-digest.js +0 -47
  478. package/dist/hub/init/plan-digest.js.map +0 -1
  479. package/dist/hub/init/prompt.d.ts +0 -26
  480. package/dist/hub/init/prompt.js +0 -110
  481. package/dist/hub/init/prompt.js.map +0 -1
  482. package/dist/hub/init/secret-file.d.ts +0 -1
  483. package/dist/hub/init/secret-file.js +0 -59
  484. package/dist/hub/init/secret-file.js.map +0 -1
  485. package/dist/hub/init/text.d.ts +0 -127
  486. package/dist/hub/init/text.js +0 -247
  487. package/dist/hub/init/text.js.map +0 -1
  488. package/dist/hub/init/types.d.ts +0 -114
  489. package/dist/hub/init/types.js +0 -9
  490. package/dist/hub/init/types.js.map +0 -1
  491. package/dist/hub/init/wizard.d.ts +0 -34
  492. package/dist/hub/init/wizard.js +0 -554
  493. package/dist/hub/init/wizard.js.map +0 -1
  494. package/dist/hub/initialization.d.ts +0 -23
  495. package/dist/hub/initialization.js +0 -28
  496. package/dist/hub/initialization.js.map +0 -1
  497. package/dist/hub/member/agent-mode.d.ts +0 -6
  498. package/dist/hub/member/agent-mode.js +0 -23
  499. package/dist/hub/member/agent-mode.js.map +0 -1
  500. package/dist/hub/member/attachments.d.ts +0 -29
  501. package/dist/hub/member/attachments.js +0 -107
  502. package/dist/hub/member/attachments.js.map +0 -1
  503. package/dist/hub/member/exposure-api.d.ts +0 -87
  504. package/dist/hub/member/exposure-api.js +0 -64
  505. package/dist/hub/member/exposure-api.js.map +0 -1
  506. package/dist/hub/member/exposures.d.ts +0 -123
  507. package/dist/hub/member/exposures.js +0 -282
  508. package/dist/hub/member/exposures.js.map +0 -1
  509. package/dist/hub/member/pairing-api.d.ts +0 -85
  510. package/dist/hub/member/pairing-api.js +0 -385
  511. package/dist/hub/member/pairing-api.js.map +0 -1
  512. package/dist/hub/member/quota.d.ts +0 -20
  513. package/dist/hub/member/quota.js +0 -98
  514. package/dist/hub/member/quota.js.map +0 -1
  515. package/dist/hub/member-journal.d.ts +0 -104
  516. package/dist/hub/member-journal.js +0 -336
  517. package/dist/hub/member-journal.js.map +0 -1
  518. package/dist/hub/member-reconciliation.d.ts +0 -14
  519. package/dist/hub/member-reconciliation.js +0 -97
  520. package/dist/hub/member-reconciliation.js.map +0 -1
  521. package/dist/hub/mount.d.ts +0 -39
  522. package/dist/hub/mount.js +0 -123
  523. package/dist/hub/mount.js.map +0 -1
  524. package/dist/hub/pairing-client.d.ts +0 -17
  525. package/dist/hub/pairing-client.js +0 -108
  526. package/dist/hub/pairing-client.js.map +0 -1
  527. package/dist/hub/pairing.d.ts +0 -65
  528. package/dist/hub/pairing.js +0 -211
  529. package/dist/hub/pairing.js.map +0 -1
  530. package/dist/hub/policy.d.ts +0 -63
  531. package/dist/hub/policy.js +0 -147
  532. package/dist/hub/policy.js.map +0 -1
  533. package/dist/hub/preview.d.ts +0 -32
  534. package/dist/hub/preview.js +0 -64
  535. package/dist/hub/preview.js.map +0 -1
  536. package/dist/hub/private-files.d.ts +0 -5
  537. package/dist/hub/private-files.js +0 -44
  538. package/dist/hub/private-files.js.map +0 -1
  539. package/dist/hub/profile.d.ts +0 -59
  540. package/dist/hub/profile.js +0 -73
  541. package/dist/hub/profile.js.map +0 -1
  542. package/dist/hub/protocol.d.ts +0 -779
  543. package/dist/hub/protocol.js +0 -93
  544. package/dist/hub/protocol.js.map +0 -1
  545. package/dist/hub/quota.d.ts +0 -24
  546. package/dist/hub/quota.js +0 -112
  547. package/dist/hub/quota.js.map +0 -1
  548. package/dist/hub/representative.d.ts +0 -141
  549. package/dist/hub/representative.js +0 -528
  550. package/dist/hub/representative.js.map +0 -1
  551. package/dist/hub/retention.d.ts +0 -17
  552. package/dist/hub/retention.js +0 -45
  553. package/dist/hub/retention.js.map +0 -1
  554. package/dist/hub/rooms.d.ts +0 -120
  555. package/dist/hub/rooms.js +0 -535
  556. package/dist/hub/rooms.js.map +0 -1
  557. package/dist/hub/runtime.d.ts +0 -106
  558. package/dist/hub/runtime.js +0 -298
  559. package/dist/hub/runtime.js.map +0 -1
  560. package/dist/hub/server.d.ts +0 -56
  561. package/dist/hub/server.js +0 -202
  562. package/dist/hub/server.js.map +0 -1
  563. package/dist/hub/service-start.d.ts +0 -17
  564. package/dist/hub/service-start.js +0 -109
  565. package/dist/hub/service-start.js.map +0 -1
  566. package/dist/hub/sessions.d.ts +0 -53
  567. package/dist/hub/sessions.js +0 -196
  568. package/dist/hub/sessions.js.map +0 -1
  569. package/dist/hub/socket.d.ts +0 -24
  570. package/dist/hub/socket.js +0 -79
  571. package/dist/hub/socket.js.map +0 -1
  572. package/dist/hub/store.d.ts +0 -77
  573. package/dist/hub/store.js +0 -88
  574. package/dist/hub/store.js.map +0 -1
  575. package/dist/hub/tls.d.ts +0 -31
  576. package/dist/hub/tls.js +0 -135
  577. package/dist/hub/tls.js.map +0 -1
  578. package/dist/hub/types.d.ts +0 -401
  579. package/dist/hub/types.js +0 -2
  580. package/dist/hub/types.js.map +0 -1
  581. package/dist/hub/validation.d.ts +0 -33
  582. package/dist/hub/validation.js +0 -136
  583. package/dist/hub/validation.js.map +0 -1
  584. package/dist/hub/version-floor.d.ts +0 -10
  585. package/dist/hub/version-floor.js +0 -45
  586. package/dist/hub/version-floor.js.map +0 -1
  587. package/dist/hub-browser-certificate-policy.d.ts +0 -31
  588. package/dist/hub-browser-certificate-policy.js +0 -25
  589. package/dist/hub-browser-certificate-policy.js.map +0 -1
  590. package/dist/hub-membership-settings.d.ts +0 -6
  591. package/dist/hub-membership-settings.js +0 -21
  592. package/dist/hub-membership-settings.js.map +0 -1
  593. package/dist/hub-mode.d.ts +0 -15
  594. package/dist/hub-mode.js +0 -17
  595. package/dist/hub-mode.js.map +0 -1
  596. package/dist/hub-pairing-summary.d.ts +0 -48
  597. package/dist/hub-pairing-summary.js +0 -36
  598. package/dist/hub-pairing-summary.js.map +0 -1
  599. package/dist/hub-report-target.d.ts +0 -32
  600. package/dist/hub-report-target.js +0 -33
  601. package/dist/hub-report-target.js.map +0 -1
  602. package/dist/hub-retention-policy.d.ts +0 -14
  603. package/dist/hub-retention-policy.js +0 -15
  604. package/dist/hub-retention-policy.js.map +0 -1
  605. package/dist/launchd-service-domain.d.ts +0 -5
  606. package/dist/launchd-service-domain.js +0 -21
  607. package/dist/launchd-service-domain.js.map +0 -1
  608. package/dist/local-pane-endpoint.d.ts +0 -46
  609. package/dist/local-pane-endpoint.js +0 -189
  610. package/dist/local-pane-endpoint.js.map +0 -1
  611. package/dist/local-pane-launcher.d.ts +0 -2
  612. package/dist/local-pane-launcher.js +0 -38
  613. package/dist/local-pane-launcher.js.map +0 -1
  614. package/dist/local-pane-stage.ps1 +0 -101
  615. package/dist/model-catalog-defaults.d.ts +0 -14
  616. package/dist/model-catalog-defaults.js +0 -20
  617. package/dist/model-catalog-defaults.js.map +0 -1
  618. package/dist/native-agent-admission.d.ts +0 -7
  619. package/dist/native-agent-admission.js +0 -26
  620. package/dist/native-agent-admission.js.map +0 -1
  621. package/dist/native-daemon-client.d.ts +0 -13
  622. package/dist/native-daemon-client.js +0 -28
  623. package/dist/native-daemon-client.js.map +0 -1
  624. package/dist/native-instance-api.d.ts +0 -13
  625. package/dist/native-instance-api.js +0 -217
  626. package/dist/native-instance-api.js.map +0 -1
  627. package/dist/native-instance-input.d.ts +0 -87
  628. package/dist/native-instance-input.js +0 -59
  629. package/dist/native-instance-input.js.map +0 -1
  630. package/dist/native-instance-registry.d.ts +0 -38
  631. package/dist/native-instance-registry.js +0 -129
  632. package/dist/native-instance-registry.js.map +0 -1
  633. package/dist/native-mcp.d.ts +0 -43
  634. package/dist/native-mcp.js +0 -291
  635. package/dist/native-mcp.js.map +0 -1
  636. package/dist/native-model-catalog.d.ts +0 -5
  637. package/dist/native-model-catalog.js +0 -76
  638. package/dist/native-model-catalog.js.map +0 -1
  639. package/dist/native-pane-crash-budget.d.ts +0 -20
  640. package/dist/native-pane-crash-budget.js +0 -60
  641. package/dist/native-pane-crash-budget.js.map +0 -1
  642. package/dist/native-pane-create.d.ts +0 -26
  643. package/dist/native-pane-create.js +0 -53
  644. package/dist/native-pane-create.js.map +0 -1
  645. package/dist/native-pane-lifecycle.d.ts +0 -73
  646. package/dist/native-pane-lifecycle.js +0 -239
  647. package/dist/native-pane-lifecycle.js.map +0 -1
  648. package/dist/native-pane-manager.d.ts +0 -59
  649. package/dist/native-pane-manager.js +0 -592
  650. package/dist/native-pane-manager.js.map +0 -1
  651. package/dist/native-pane-output-log.d.ts +0 -15
  652. package/dist/native-pane-output-log.js +0 -108
  653. package/dist/native-pane-output-log.js.map +0 -1
  654. package/dist/native-pane-reconciliation.d.ts +0 -4
  655. package/dist/native-pane-reconciliation.js +0 -40
  656. package/dist/native-pane-reconciliation.js.map +0 -1
  657. package/dist/native-pane-recovery.d.ts +0 -12
  658. package/dist/native-pane-recovery.js +0 -10
  659. package/dist/native-pane-recovery.js.map +0 -1
  660. package/dist/native-pane-tools.d.ts +0 -2
  661. package/dist/native-pane-tools.js +0 -14
  662. package/dist/native-pane-tools.js.map +0 -1
  663. package/dist/office-talk.d.ts +0 -26
  664. package/dist/office-talk.js +0 -50
  665. package/dist/office-talk.js.map +0 -1
  666. package/dist/pane-port.d.ts +0 -118
  667. package/dist/pane-port.js +0 -2
  668. package/dist/pane-port.js.map +0 -1
  669. package/dist/runtime-api.d.ts +0 -10
  670. package/dist/runtime-api.js +0 -189
  671. package/dist/runtime-api.js.map +0 -1
  672. package/dist/runtime-assets/@xterm/addon-serialize/LICENSE +0 -21
  673. package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.js +0 -2
  674. package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.mjs +0 -19
  675. package/dist/runtime-assets/@xterm/addon-serialize/package.json +0 -29
  676. package/dist/runtime-assets/@xterm/headless/LICENSE +0 -21
  677. package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.js +0 -2
  678. package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.mjs +0 -46
  679. package/dist/runtime-assets/@xterm/headless/package.json +0 -29
  680. package/dist/runtime-assets/node-pty/CONPTY-LICENSE +0 -21
  681. package/dist/runtime-assets/node-pty/CONPTY-NOTICE.md +0 -503
  682. package/dist/runtime-assets/node-pty/LICENSE +0 -69
  683. package/dist/runtime-assets/node-pty/lib/conpty_console_list_agent.js +0 -16
  684. package/dist/runtime-assets/node-pty/lib/eventEmitter2.js +0 -47
  685. package/dist/runtime-assets/node-pty/lib/index.js +0 -52
  686. package/dist/runtime-assets/node-pty/lib/interfaces.js +0 -7
  687. package/dist/runtime-assets/node-pty/lib/shared/conout.js +0 -11
  688. package/dist/runtime-assets/node-pty/lib/terminal.js +0 -190
  689. package/dist/runtime-assets/node-pty/lib/types.js +0 -7
  690. package/dist/runtime-assets/node-pty/lib/unixTerminal.js +0 -346
  691. package/dist/runtime-assets/node-pty/lib/utils.js +0 -39
  692. package/dist/runtime-assets/node-pty/lib/windowsConoutConnection.js +0 -125
  693. package/dist/runtime-assets/node-pty/lib/windowsPtyAgent.js +0 -320
  694. package/dist/runtime-assets/node-pty/lib/windowsTerminal.js +0 -199
  695. package/dist/runtime-assets/node-pty/lib/worker/conoutSocketWorker.js +0 -22
  696. package/dist/runtime-assets/node-pty/package.json +0 -64
  697. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  698. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  699. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  700. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  701. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  702. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  703. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  704. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  705. package/dist/runtime-build-info.d.ts +0 -9
  706. package/dist/runtime-build-info.js +0 -59
  707. package/dist/runtime-build-info.js.map +0 -1
  708. package/dist/runtime-bundle.d.ts +0 -22
  709. package/dist/runtime-bundle.js +0 -158
  710. package/dist/runtime-bundle.js.map +0 -1
  711. package/dist/runtime-cli.d.ts +0 -14
  712. package/dist/runtime-cli.js +0 -271
  713. package/dist/runtime-cli.js.map +0 -1
  714. package/dist/runtime-client-store.d.ts +0 -75
  715. package/dist/runtime-client-store.js +0 -372
  716. package/dist/runtime-client-store.js.map +0 -1
  717. package/dist/runtime-client.d.ts +0 -174
  718. package/dist/runtime-client.js +0 -1010
  719. package/dist/runtime-client.js.map +0 -1
  720. package/dist/runtime-connection.d.ts +0 -22
  721. package/dist/runtime-connection.js +0 -93
  722. package/dist/runtime-connection.js.map +0 -1
  723. package/dist/runtime-coordinator.d.ts +0 -98
  724. package/dist/runtime-coordinator.js +0 -1089
  725. package/dist/runtime-coordinator.js.map +0 -1
  726. package/dist/runtime-credential-profiles.d.ts +0 -78
  727. package/dist/runtime-credential-profiles.js +0 -280
  728. package/dist/runtime-credential-profiles.js.map +0 -1
  729. package/dist/runtime-general-protocol.d.ts +0 -22
  730. package/dist/runtime-general-protocol.js +0 -57
  731. package/dist/runtime-general-protocol.js.map +0 -1
  732. package/dist/runtime-general-workspace.d.ts +0 -17
  733. package/dist/runtime-general-workspace.js +0 -213
  734. package/dist/runtime-general-workspace.js.map +0 -1
  735. package/dist/runtime-install.d.ts +0 -50
  736. package/dist/runtime-install.js +0 -186
  737. package/dist/runtime-install.js.map +0 -1
  738. package/dist/runtime-pane-assets.d.ts +0 -17
  739. package/dist/runtime-pane-assets.js +0 -23
  740. package/dist/runtime-pane-assets.js.map +0 -1
  741. package/dist/runtime-pane-boot.d.ts +0 -9
  742. package/dist/runtime-pane-boot.js +0 -47
  743. package/dist/runtime-pane-boot.js.map +0 -1
  744. package/dist/runtime-pane-codex-index.d.ts +0 -10
  745. package/dist/runtime-pane-codex-index.js +0 -151
  746. package/dist/runtime-pane-codex-index.js.map +0 -1
  747. package/dist/runtime-pane-config.d.ts +0 -16
  748. package/dist/runtime-pane-config.js +0 -54
  749. package/dist/runtime-pane-config.js.map +0 -1
  750. package/dist/runtime-pane-host.d.ts +0 -63
  751. package/dist/runtime-pane-host.js +0 -467
  752. package/dist/runtime-pane-host.js.map +0 -1
  753. package/dist/runtime-pane-job-owner.d.ts +0 -1
  754. package/dist/runtime-pane-job-owner.js +0 -29
  755. package/dist/runtime-pane-job-owner.js.map +0 -1
  756. package/dist/runtime-pane-job.ps1 +0 -116
  757. package/dist/runtime-pane-link.d.ts +0 -47
  758. package/dist/runtime-pane-link.js +0 -337
  759. package/dist/runtime-pane-link.js.map +0 -1
  760. package/dist/runtime-pane-mcp-protocol.d.ts +0 -32
  761. package/dist/runtime-pane-mcp-protocol.js +0 -39
  762. package/dist/runtime-pane-mcp-protocol.js.map +0 -1
  763. package/dist/runtime-pane-mcp-relay.d.ts +0 -18
  764. package/dist/runtime-pane-mcp-relay.js +0 -80
  765. package/dist/runtime-pane-mcp-relay.js.map +0 -1
  766. package/dist/runtime-pane-mcp.d.ts +0 -55
  767. package/dist/runtime-pane-mcp.js +0 -153
  768. package/dist/runtime-pane-mcp.js.map +0 -1
  769. package/dist/runtime-pane-native.d.ts +0 -75
  770. package/dist/runtime-pane-native.js +0 -114
  771. package/dist/runtime-pane-native.js.map +0 -1
  772. package/dist/runtime-pane-output.d.ts +0 -19
  773. package/dist/runtime-pane-output.js +0 -53
  774. package/dist/runtime-pane-output.js.map +0 -1
  775. package/dist/runtime-pane-port.d.ts +0 -144
  776. package/dist/runtime-pane-port.js +0 -574
  777. package/dist/runtime-pane-port.js.map +0 -1
  778. package/dist/runtime-pane-private.d.ts +0 -14
  779. package/dist/runtime-pane-private.js +0 -51
  780. package/dist/runtime-pane-private.js.map +0 -1
  781. package/dist/runtime-pane-private.ps1 +0 -14
  782. package/dist/runtime-pane-process.d.ts +0 -4
  783. package/dist/runtime-pane-process.js +0 -117
  784. package/dist/runtime-pane-process.js.map +0 -1
  785. package/dist/runtime-pane-protocol.d.ts +0 -182
  786. package/dist/runtime-pane-protocol.js +0 -217
  787. package/dist/runtime-pane-protocol.js.map +0 -1
  788. package/dist/runtime-pane-quota-history.d.ts +0 -6
  789. package/dist/runtime-pane-quota-history.js +0 -44
  790. package/dist/runtime-pane-quota-history.js.map +0 -1
  791. package/dist/runtime-pane-screen.d.ts +0 -19
  792. package/dist/runtime-pane-screen.js +0 -76
  793. package/dist/runtime-pane-screen.js.map +0 -1
  794. package/dist/runtime-pane-service.d.ts +0 -16
  795. package/dist/runtime-pane-service.js +0 -256
  796. package/dist/runtime-pane-service.js.map +0 -1
  797. package/dist/runtime-pane-stdio-protocol.d.ts +0 -54
  798. package/dist/runtime-pane-stdio-protocol.js +0 -94
  799. package/dist/runtime-pane-stdio-protocol.js.map +0 -1
  800. package/dist/runtime-pane-stdio.d.ts +0 -3
  801. package/dist/runtime-pane-stdio.js +0 -123
  802. package/dist/runtime-pane-stdio.js.map +0 -1
  803. package/dist/runtime-pane-store.d.ts +0 -33
  804. package/dist/runtime-pane-store.js +0 -164
  805. package/dist/runtime-pane-store.js.map +0 -1
  806. package/dist/runtime-pane-transcript-protocol.d.ts +0 -43
  807. package/dist/runtime-pane-transcript-protocol.js +0 -58
  808. package/dist/runtime-pane-transcript-protocol.js.map +0 -1
  809. package/dist/runtime-pane-transcript.d.ts +0 -50
  810. package/dist/runtime-pane-transcript.js +0 -666
  811. package/dist/runtime-pane-transcript.js.map +0 -1
  812. package/dist/runtime-pane-worker.d.ts +0 -1
  813. package/dist/runtime-pane-worker.js +0 -167
  814. package/dist/runtime-pane-worker.js.map +0 -1
  815. package/dist/runtime-protocol.d.ts +0 -177
  816. package/dist/runtime-protocol.js +0 -49
  817. package/dist/runtime-protocol.js.map +0 -1
  818. package/dist/runtime-switches.d.ts +0 -79
  819. package/dist/runtime-switches.js +0 -90
  820. package/dist/runtime-switches.js.map +0 -1
  821. package/dist/runtime-transcript-delta-reader.d.ts +0 -23
  822. package/dist/runtime-transcript-delta-reader.js +0 -74
  823. package/dist/runtime-transcript-delta-reader.js.map +0 -1
  824. package/dist/runtime-transcript-source.d.ts +0 -36
  825. package/dist/runtime-transcript-source.js +0 -189
  826. package/dist/runtime-transcript-source.js.map +0 -1
  827. package/dist/runtime-update-activity.d.ts +0 -21
  828. package/dist/runtime-update-activity.js +0 -51
  829. package/dist/runtime-update-activity.js.map +0 -1
  830. package/dist/runtime-update-artifact.d.ts +0 -31
  831. package/dist/runtime-update-artifact.js +0 -252
  832. package/dist/runtime-update-artifact.js.map +0 -1
  833. package/dist/runtime-update-install.d.ts +0 -31
  834. package/dist/runtime-update-install.js +0 -205
  835. package/dist/runtime-update-install.js.map +0 -1
  836. package/dist/runtime-update-journal.d.ts +0 -44
  837. package/dist/runtime-update-journal.js +0 -121
  838. package/dist/runtime-update-journal.js.map +0 -1
  839. package/dist/runtime-update-lock.d.ts +0 -48
  840. package/dist/runtime-update-lock.js +0 -217
  841. package/dist/runtime-update-lock.js.map +0 -1
  842. package/dist/runtime-update-owners.d.ts +0 -29
  843. package/dist/runtime-update-owners.js +0 -240
  844. package/dist/runtime-update-owners.js.map +0 -1
  845. package/dist/runtime-update-protocol.d.ts +0 -15
  846. package/dist/runtime-update-protocol.js +0 -13
  847. package/dist/runtime-update-protocol.js.map +0 -1
  848. package/dist/runtime-windows-bridge.d.ts +0 -42
  849. package/dist/runtime-windows-bridge.js +0 -167
  850. package/dist/runtime-windows-bridge.js.map +0 -1
  851. package/dist/service-drain.d.ts +0 -19
  852. package/dist/service-drain.js +0 -64
  853. package/dist/service-drain.js.map +0 -1
  854. package/dist/settings-route-lifetime.d.ts +0 -14
  855. package/dist/settings-route-lifetime.js +0 -48
  856. package/dist/settings-route-lifetime.js.map +0 -1
  857. package/dist/tmux-binary.d.ts +0 -59
  858. package/dist/tmux-binary.js +0 -200
  859. package/dist/tmux-binary.js.map +0 -1
  860. package/dist/tmux-pane-host.d.ts +0 -26
  861. package/dist/tmux-pane-host.js +0 -25
  862. package/dist/tmux-pane-host.js.map +0 -1
  863. package/dist/transcript-needles.d.ts +0 -42
  864. package/dist/transcript-needles.js +0 -166
  865. package/dist/transcript-needles.js.map +0 -1
  866. package/dist/transcript-records.d.ts +0 -21
  867. package/dist/transcript-records.js +0 -141
  868. package/dist/transcript-records.js.map +0 -1
  869. package/dist/ui/account-identity.js +0 -27
  870. package/dist/ui/accounts-view.js +0 -214
  871. package/dist/ui/agent-proposals.js +0 -90
  872. package/dist/ui/agent-runtime-settings.js +0 -68
  873. package/dist/ui/agent-runtime.js +0 -124
  874. package/dist/ui/cli-picker.js +0 -42
  875. package/dist/ui/credential-profile-select.js +0 -54
  876. package/dist/ui/fleet-office-dialogue.d.ts +0 -30
  877. package/dist/ui/fleet-office-dialogue.js +0 -382
  878. package/dist/ui/fleet-task-menu.js +0 -188
  879. package/dist/ui/fleet-task-stats.js +0 -124
  880. package/dist/ui/fleet-task-view.js +0 -340
  881. package/dist/ui/general-location.js +0 -71
  882. package/dist/ui/hub-agent-copy.js +0 -23
  883. package/dist/ui/hub-button-actions.js +0 -65
  884. package/dist/ui/hub-chat-store.js +0 -158
  885. package/dist/ui/hub-chat-strings.js +0 -77
  886. package/dist/ui/hub-client.js +0 -329
  887. package/dist/ui/hub-owner-strings.js +0 -51
  888. package/dist/ui/hub-provider-guidance.js +0 -31
  889. package/dist/ui/hub-quota.js +0 -85
  890. package/dist/ui/hub-strings.js +0 -162
  891. package/dist/ui/hub-switcher.js +0 -27
  892. package/dist/ui/native-instance-settings.js +0 -44
  893. package/dist/ui/native-pane-recovery.js +0 -45
  894. package/dist/ui/panel-hub.js +0 -619
  895. package/dist/ui/panel-runtimes.js +0 -425
  896. package/dist/ui/panel-windows-agent.js +0 -86
  897. package/dist/ui/panel-windows-cli.js +0 -189
  898. package/dist/ui/provider-marks.js +0 -83
  899. package/dist/ui/runtime-catalog.js +0 -55
  900. package/dist/ui/runtime-commands.d.ts +0 -4
  901. package/dist/ui/runtime-commands.js +0 -59
  902. package/dist/ui/settings-access-strings.js +0 -42
  903. package/dist/ui/settings-agent-filters.js +0 -94
  904. package/dist/ui/settings-build-version.js +0 -35
  905. package/dist/ui/settings-devices.js +0 -253
  906. package/dist/ui/settings-hub-browser-certificate.js +0 -74
  907. package/dist/ui/settings-hub-membership.js +0 -72
  908. package/dist/ui/settings-hub-retention.js +0 -52
  909. package/dist/ui/settings-hub.js +0 -196
  910. package/dist/ui/settings-layout.js +0 -242
  911. package/dist/ui/settings-outside.js +0 -242
  912. package/dist/ui/settings-shared-strings.js +0 -143
  913. package/dist/ui/settings-shared.js +0 -345
  914. package/dist/ui/settings-shortcuts.js +0 -65
  915. package/dist/ui/shared/accounts.css +0 -15
  916. package/dist/ui/shared/chat-composer.js +0 -68
  917. package/dist/ui/shared/chat-md-strings.js +0 -9
  918. package/dist/ui/shared/chat-render.js +0 -682
  919. package/dist/ui/shared/chat-strings.js +0 -333
  920. package/dist/ui/shared/chat-thread.js +0 -969
  921. package/dist/ui/shared/chat-time.js +0 -80
  922. package/dist/ui/shared/html-attachment.js +0 -42
  923. package/dist/ui/shared/hub-chat.css +0 -85
  924. package/dist/ui/shared/hub-owner.css +0 -33
  925. package/dist/ui/shared/hub-quota.css +0 -8
  926. package/dist/ui/shared/hub.css +0 -71
  927. package/dist/ui/shared/machine-name.js +0 -13
  928. package/dist/ui/shared/page-head.js +0 -8
  929. package/dist/ui/shared/preview.js +0 -476
  930. package/dist/ui/shared/qrcode.module.js +0 -2237
  931. package/dist/ui/shared/runtime-install.css +0 -26
  932. package/dist/ui/shared/settings-width.js +0 -31
  933. package/dist/ui/shared/side-rail.js +0 -15
  934. package/dist/ui/shared/task-board.css +0 -95
  935. package/dist/ui/shared/vendor/qrcode-generator.LICENSE +0 -27
  936. package/dist/ui/shared/workspace.css +0 -20
  937. package/dist/ui/windows-agents-nav.js +0 -2
  938. package/dist/ui/windows-cli-catalog.js +0 -108
  939. package/dist/ui/workspace-panel.js +0 -189
  940. package/dist/ui/workspace-strings.js +0 -35
  941. package/dist/web-device-login.d.ts +0 -153
  942. package/dist/web-device-login.js +0 -278
  943. package/dist/web-device-login.js.map +0 -1
  944. package/dist/web-remote-access.d.ts +0 -152
  945. package/dist/web-remote-access.js +0 -368
  946. package/dist/web-remote-access.js.map +0 -1
  947. package/dist/windows-agents.d.ts +0 -127
  948. package/dist/windows-agents.js +0 -491
  949. package/dist/windows-agents.js.map +0 -1
  950. package/dist/windows-cli-api.d.ts +0 -34
  951. package/dist/windows-cli-api.js +0 -185
  952. package/dist/windows-cli-api.js.map +0 -1
  953. package/dist/windows-cli-providers.d.ts +0 -106
  954. package/dist/windows-cli-providers.js +0 -424
  955. package/dist/windows-cli-providers.js.map +0 -1
  956. package/dist/windows-cli-worker.d.ts +0 -83
  957. package/dist/windows-cli-worker.js +0 -570
  958. package/dist/windows-cli-worker.js.map +0 -1
  959. package/dist/windows-cli.d.ts +0 -126
  960. package/dist/windows-cli.js +0 -416
  961. package/dist/windows-cli.js.map +0 -1
  962. package/dist/windows-native-mcp.d.ts +0 -19
  963. package/dist/windows-native-mcp.js +0 -143
  964. package/dist/windows-native-mcp.js.map +0 -1
  965. package/dist/windows-pane-mcp.d.ts +0 -26
  966. package/dist/windows-pane-mcp.js +0 -187
  967. package/dist/windows-pane-mcp.js.map +0 -1
  968. package/launcher/launchd-session-type.cjs +0 -14
  969. package/launcher/tmux-bundle.cjs +0 -247
  970. package/templates/runtime-assets/conpty-LICENSE +0 -21
  971. package/templates/runtime-assets/conpty-NOTICE.md +0 -503
  972. package/templates/runtime-assets/local-pane-stage.ps1 +0 -101
  973. package/templates/runtime-assets/runtime-pane-job.ps1 +0 -116
  974. package/templates/runtime-assets/runtime-pane-private.ps1 +0 -14
  975. package/templates/runtime-assets/xterm-LICENSE +0 -21
  976. package/templates/runtime-install/install.ps1 +0 -270
@@ -4,13 +4,11 @@ import { execFileSync } from "node:child_process";
4
4
  import { execFile } from "../child-process.js";
5
5
  import { promisify } from "node:util";
6
6
  import { basename, dirname, join, resolve } from "node:path";
7
- import { stripCodexPassiveHintRow, withCodexPassiveHintTail, CODEX_EMPTY_PASSIVE_COMPOSER, isCodexStatusAutocompleteComposer } from "./codex-footer-hints.js";
7
+ import { stripCodexPassiveHintRow, withCodexPassiveHintTail } from "./codex-footer-hints.js";
8
8
  import { codexPackageWarning } from "./codex-package.js";
9
9
  import { probeCliVersion, resolveBinary, shellQuote, validateModel, validateProvider, warnIfModelMismatch } from "./types.js";
10
10
  import { credentialHomeSpec, credentialProfileHome, prepareCredentialProfileHome, resolveCredentialProfile, } from "./credential-profile.js";
11
11
  import { getAgendHome } from "../paths.js";
12
- import { selectedGeneralHostSession } from "../general-binding.js";
13
- import { ResumeBlockedError } from "./types.js";
14
12
  import { CODEX_MODELS_CACHE_MAX_BYTES, codexShortHomeFor, readCodexEffortLevels, readCodexModelEffortLevels, readCodexModelServiceTiers, resolveCodexLaunchModel } from "./codex-metadata.js";
15
13
  import { EFFORT_CAPABILITIES } from "./effort-metadata.js";
16
14
  import { codexEffort } from "./launch-effort.js";
@@ -307,12 +305,12 @@ function setProjectTrusted(content, root) {
307
305
  throw new Error("Codex project trust is not effective");
308
306
  return updated;
309
307
  }
308
+ /** Only the bottom, live Codex 0.156 folder-access screen can own stdin. */
310
309
  /** A folder-trust prompt's cancel option, and the footer that must come with it. */
311
310
  const CODEX_TRUST_CANCEL_FOOTER = {
312
311
  "Quit": /^\s*enter continue\s*·\s*esc quit\s*$/i,
313
312
  "Back to Agent Command Center": /^\s*enter continue\s*·\s*esc back\s*$/i,
314
313
  };
315
- /** Only the bottom, live Codex 0.156 folder-access screen can own stdin. */
316
314
  function codexTrustPromptState(pane) {
317
315
  const noPrompt = { active: false, folder: null, root: null, rootNote: "absent", safeChoice: false, trustCursor: false };
318
316
  const rows = pane.replace(/\r/g, "").split("\n");
@@ -619,20 +617,6 @@ function codexWorkspaceLimitPromptVisible(pane) {
619
617
  }
620
618
  return false;
621
619
  }
622
- /**
623
- * The composer's own `›` row above the footer, or -1. Walking up from the footer, only blank rows and indented rows (the
624
- * composer's wrapped text, however many lines a long paste takes) may come before it. The first column-zero row decides:
625
- * a `›` there is the composer; a numbered `›` row is a selection card; any other column-zero row (`•`/`■`/`⚠` output, a
626
- * status line) means a `›` above it is transcript history, never the composer.
627
- */
628
- function ownedComposerRow(rows) {
629
- for (let i = rows.length - 1; i >= 0; i--) {
630
- if (!rows[i].trim() || /^[ \t]/.test(rows[i]))
631
- continue;
632
- return /^[>›][ \t]+\S/.test(rows[i]) && !/^[>›][ \t]+\d+\./.test(rows[i]) ? i : -1;
633
- }
634
- return -1;
635
- }
636
620
  /** Unknown pickers own stdin too; never type or press Enter into one. */
637
621
  function codexUnknownSelectionVisible(pane) {
638
622
  const rows = pane.replace(/\r/g, "").split("\n");
@@ -1012,15 +996,8 @@ export const CODEX_RESUME_ATTEMPT_FILE = "codex-resume-attempt.json";
1012
996
  export const CODEX_RESUME_FAILED_FILE = "codex-resume-failed.json";
1013
997
  /** Above this, a resumed rollout carries a note that codex may compact it first. */
1014
998
  export const CODEX_RESUME_LARGE_BYTES = 20 * 1024 * 1024;
1015
- /** Pane-only detectors: construction performs no provider, config or session discovery. */
1016
- export class CodexPaneDetectors {
1017
- paneDetectorContext;
1018
- constructor(paneDetectorContext) {
1019
- this.paneDetectorContext = paneDetectorContext;
1020
- }
1021
- readConfiguredStatusLineItems() { return this.paneDetectorContext?.statusLineItems()?.slice() ?? null; }
1022
- trustPaneFields(pane) { const state = codexTrustPromptState(pane); return { promptFolder: state.folder, promptRoot: state.root, rootNote: state.rootNote }; }
1023
- trustHoldReason(pane) { return { ...this.trustPaneFields(pane), reason: "Folder permission needs confirmation on this execution machine" }; }
999
+ export class CodexBackend {
1000
+ instanceDir;
1024
1001
  /**
1025
1002
  * #1510: the reply-completion guard covers codex, its turn end read from the rollout (task_complete /
1026
1003
  * turn_aborted, recorded on 0.162.0 in tests/fixtures/reply-guard-1510): the pane alone stays busy through a tool
@@ -1029,10 +1006,123 @@ export class CodexPaneDetectors {
1029
1006
  replyCompletionGuard = true;
1030
1007
  turnEndFromTranscript = true;
1031
1008
  binaryName = "codex";
1009
+ binaryPath;
1010
+ sharedCodexHome;
1011
+ isolatedCodexHome;
1012
+ /** Which subscription this instance runs on, or null for the shared login. */
1013
+ credentialProfile = null;
1014
+ /** Set only after preTrust wrote and read back this instance's private config. */
1015
+ authorizedTrust = null;
1032
1016
  configuredStatusLineItems;
1033
1017
  configuredStatusLinePattern;
1018
+ /** Set by buildCommand when the resume plan fell back; read once by the daemon. */
1019
+ launchWarning = null;
1034
1020
  /** Whether the last buildCommand launched a resume (`resume <id>` or `--last`). */
1035
1021
  lastLaunchResumes = true;
1022
+ quotaReplay = null;
1023
+ quotaReplayHistory() { return this.quotaReplay; }
1024
+ /** Set by writeConfig when the status_line could not be made verifiable (#931). */
1025
+ statusLineWarning = null;
1026
+ /** #2357: set by buildCommand when the codex being launched lacks a package companion (codex-code-mode-host). */
1027
+ packageWarning = null;
1028
+ constructor(instanceDir) {
1029
+ this.instanceDir = instanceDir;
1030
+ this.binaryPath = resolveBinary("codex");
1031
+ this.sharedCodexHome = resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
1032
+ this.isolatedCodexHome = CodexBackend.resolveShortHome(instanceDir);
1033
+ }
1034
+ /**
1035
+ * Compute and (on first call for an existing instance) migrate to a SHORT
1036
+ * persistent CODEX_HOME under `~/.agend/cx/<8-char-hash>/`.
1037
+ *
1038
+ * Background (#953): codex 0.157.0 added `app-server-control.sock` under
1039
+ * CODEX_HOME. For instances with long `-t<topic_id>` suffixes the full path
1040
+ * exceeds the Unix socket SUN_LEN (~107 chars). codex canonicalises the path
1041
+ * before binding the socket, so a symlink workaround does not help — the real
1042
+ * path must be short.
1043
+ *
1044
+ * Migration steps (each is idempotent; half-completed states self-heal):
1045
+ * 1. If shortHome does not exist AND legacyHome is a real directory (not a
1046
+ * symlink) → atomic renameSync. On EXDEV or any other error, fall back
1047
+ * safely: keep using the legacy long path (instance can still start, just
1048
+ * without the short-path fix).
1049
+ * 2. If shortHome exists but legacyHome is missing → recreate the backward-
1050
+ * compat symlink (self-heal for rename-succeeded-but-symlink-failed crash).
1051
+ * 3. If both shortHome and legacyHome (as symlink) exist → nothing to do.
1052
+ * 4. New instance (no legacyHome) → create shortHome directly.
1053
+ *
1054
+ * Fail-safe guarantee: migration failure must never make the instance worse.
1055
+ * The caller always receives a valid path it can use as CODEX_HOME.
1056
+ */
1057
+ /** Exposed for fleet-manager to delete the short home on instance removal. */
1058
+ static shortHomeFor(instanceDir) {
1059
+ return codexShortHomeFor(instanceDir);
1060
+ }
1061
+ static resolveShortHome(instanceDir) {
1062
+ const shortHome = CodexBackend.shortHomeFor(instanceDir);
1063
+ const shortBase = join(getAgendHome(), "cx");
1064
+ const canonical = resolve(instanceDir);
1065
+ const legacyHome = resolve(instanceDir, "codex-home");
1066
+ // Only migrate/create when the instance directory itself exists.
1067
+ // Constructing CodexBackend for a non-existent dir (cli-env probes, test
1068
+ // backends that never ran) must not litter ~/.agend/cx/ with orphan dirs.
1069
+ if (!existsSync(canonical))
1070
+ return shortHome;
1071
+ const legacyExists = existsSync(legacyHome);
1072
+ const legacyIsRealDir = legacyExists && !lstatSync(legacyHome).isSymbolicLink();
1073
+ const shortExists = existsSync(shortHome);
1074
+ if (!shortExists) {
1075
+ mkdirSync(shortBase, { recursive: true, mode: 0o700 });
1076
+ if (legacyIsRealDir) {
1077
+ // Attempt atomic rename. Requires same device; fails with EXDEV otherwise.
1078
+ try {
1079
+ renameSync(legacyHome, shortHome);
1080
+ // Log the one-time migration for observability.
1081
+ try {
1082
+ const logPath = join(shortBase, `${shortHome.split("/").at(-1)}.migrated`);
1083
+ writeFileSync(logPath, `${new Date().toISOString()} migrated from ${legacyHome}\n`);
1084
+ }
1085
+ catch { /* log failure is non-fatal */ }
1086
+ }
1087
+ catch (err) {
1088
+ // EXDEV (cross-device) or any other rename failure → fail-safe:
1089
+ // fall back to the legacy long path so the instance still works.
1090
+ // The socket-length bug persists for this instance but no data is lost.
1091
+ const code = err.code ?? "unknown";
1092
+ try {
1093
+ const warnPath = join(shortBase, `${shortHome.split("/").at(-1)}.migration-failed`);
1094
+ writeFileSync(warnPath, `${new Date().toISOString()} rename failed (${code}): ${err.message}\n`
1095
+ + `legacy path remains in use: ${legacyHome}\n`);
1096
+ }
1097
+ catch { /* warn log failure is also non-fatal */ }
1098
+ // Return legacy long path so the instance starts (socket may still fail
1099
+ // on 0.157.0 for long names, but no data is lost or corrupted).
1100
+ return legacyHome;
1101
+ }
1102
+ }
1103
+ else if (!legacyExists) {
1104
+ // New instance — no data to migrate.
1105
+ mkdirSync(shortHome, { recursive: true, mode: 0o700 });
1106
+ }
1107
+ // If legacyExists but is already a symlink: shortHome was deleted externally
1108
+ // while the symlink still points to it. Re-create shortHome as a fresh dir.
1109
+ if (!existsSync(shortHome)) {
1110
+ mkdirSync(shortHome, { recursive: true, mode: 0o700 });
1111
+ }
1112
+ }
1113
+ // Backward-compat symlink: instanceDir/codex-home → shortHome.
1114
+ // Also serves as self-heal: if a previous run renamed successfully but
1115
+ // crashed before creating the symlink, we recreate it here.
1116
+ if (!legacyExists || legacyIsRealDir) {
1117
+ // legacyIsRealDir means rename just happened (now shortHome exists, legacyHome gone).
1118
+ // !legacyExists means new instance or symlink was deleted — create it.
1119
+ try {
1120
+ symlinkSync(shortHome, legacyHome);
1121
+ }
1122
+ catch { /* race or already exists */ }
1123
+ }
1124
+ return shortHome;
1125
+ }
1036
1126
  supportsQueuedInput() {
1037
1127
  return true;
1038
1128
  }
@@ -1057,12 +1147,25 @@ export class CodexPaneDetectors {
1057
1147
  }
1058
1148
  /** Observed on real Codex 0.156.0: the live input row precedes its footer. */
1059
1149
  isDeliveryInputReadyPane(pane) {
1060
- if (this.nativeContextlessEmpty(pane))
1061
- return true;
1062
- const frame = this.composerFrame(pane);
1063
- if (!frame)
1150
+ // A composer with no app-server behind it accepts text and never submits it (#1099).
1151
+ if (codexAppServerDisconnected(pane))
1152
+ return false;
1153
+ const rows = pane.replace(/\r/g, "").split("\n");
1154
+ while (rows.length && !rows[rows.length - 1].trim())
1155
+ rows.pop();
1156
+ stripCodexPassiveHintRow(rows);
1157
+ const footer = rows.pop() ?? "";
1158
+ // Codex preserves other configured status-line items after the context
1159
+ // meter (observed on 0.156.0: "Context 100% left · GPT-6-Astra"). They
1160
+ // are footer chrome, not evidence that the input row is unavailable.
1161
+ const contextFooter = this.isAnyContextFooter(footer);
1162
+ // A deliberately configured status_line may omit Context entirely. In
1163
+ // that case require a recognized value from the actual configured item
1164
+ // list after Codex's exact empty live-composer row. An arbitrary non-empty
1165
+ // footer or a recent transcript echo is not a readiness signal; that was
1166
+ // the false-ready hole in the old Layer 2 deny-list attempt (#931/#947).
1167
+ if (!contextFooter && !this.isConfiguredStatusLineFooter(footer))
1064
1168
  return false;
1065
- const { rows, contextFooter } = frame;
1066
1169
  if (!contextFooter) {
1067
1170
  if (this.getBusyPattern().test(pane))
1068
1171
  return false;
@@ -1083,75 +1186,6 @@ export class CodexPaneDetectors {
1083
1186
  }
1084
1187
  return false;
1085
1188
  }
1086
- /**
1087
- * Complete ownership of a long pasted envelope: the `›` composer row and every wrapped row down to a context footer are
1088
- * proven by ownedComposerRow, however many rows they take. The Enter gate accepts this as well as isDeliveryInputReadyPane,
1089
- * never instead of the dialog, busy and expected-seq checks.
1090
- */
1091
- ownsComposerRows(pane) {
1092
- const frame = this.composerFrame(pane);
1093
- if (frame?.contextFooter)
1094
- return ownedComposerRow(frame.rows) >= 0;
1095
- // A paste owns its wrapped rows, not a new-paste readiness verdict. The native caller still proves its
1096
- // exact payload/operation and current sequence before Enter; a passive footer alone never grants input.
1097
- const rows = this.nativeContextlessRows(pane);
1098
- return rows !== null && ownedComposerRow(rows) >= 0;
1099
- }
1100
- // Codex 0.165 can restore a limit-only session before any Context value exists. This is the known native
1101
- // empty-composer/passive-footer layout, only for a resumed launch with our context-only status configuration.
1102
- // Other layouts retain their existing guard; this is not native's unknown-layout stability fallback.
1103
- nativeContextlessPattern() {
1104
- if (!this.paneDetectorContext?.windowsConpty || this.paneDetectorContext.contextlessResume !== true)
1105
- return null;
1106
- const items = this.readConfiguredStatusLineItems();
1107
- return items?.length === 1 && items[0] === "context-remaining" ? CODEX_EMPTY_PASSIVE_COMPOSER : null;
1108
- }
1109
- nativeContextlessEmpty(pane) {
1110
- return !!this.nativeContextlessPattern()?.test(pane) && !codexAppServerDisconnected(pane) && !this.getBusyPattern().test(pane)
1111
- && ![...this.getStartupDialogs(), ...this.getRuntimeDialogs()].some(dialog => dialog.pattern.test(pane) && (!dialog.isActive || dialog.isActive(pane)));
1112
- }
1113
- nativeContextlessRows(pane) {
1114
- if (!this.nativeContextlessPattern() || codexAppServerDisconnected(pane))
1115
- return null;
1116
- const rows = pane.replace(/\r/g, "").split("\n");
1117
- while (rows.length && !rows.at(-1).trim())
1118
- rows.pop();
1119
- if (isCodexStatusAutocompleteComposer(pane))
1120
- return rows;
1121
- const before = rows.length;
1122
- stripCodexPassiveHintRow(rows);
1123
- if (rows.length === before)
1124
- return null;
1125
- const remaining = [...rows];
1126
- stripCodexPassiveHintRow(remaining);
1127
- return remaining.length === rows.length ? rows : null;
1128
- }
1129
- /**
1130
- * The screen above Codex's footer, shared by both readers: null when the footer is not a Codex footer at all or the
1131
- * app server behind the composer is gone (#1099). `rows` excludes the footer.
1132
- */
1133
- composerFrame(pane) {
1134
- // A composer with no app-server behind it accepts text and never submits it (#1099).
1135
- if (codexAppServerDisconnected(pane))
1136
- return null;
1137
- const rows = pane.replace(/\r/g, "").split("\n");
1138
- while (rows.length && !rows[rows.length - 1].trim())
1139
- rows.pop();
1140
- stripCodexPassiveHintRow(rows);
1141
- const footer = rows.pop() ?? "";
1142
- // Codex preserves other configured status-line items after the context
1143
- // meter (observed on 0.156.0: "Context 100% left · GPT-6-Astra"). They
1144
- // are footer chrome, not evidence that the input row is unavailable.
1145
- const contextFooter = this.isAnyContextFooter(footer);
1146
- // A deliberately configured status_line may omit Context entirely. In
1147
- // that case require a recognized value from the actual configured item
1148
- // list after Codex's exact empty live-composer row. An arbitrary non-empty
1149
- // footer or a recent transcript echo is not a readiness signal; that was
1150
- // the false-ready hole in the old Layer 2 deny-list attempt (#931/#947).
1151
- if (!contextFooter && !this.isConfiguredStatusLineFooter(footer))
1152
- return null;
1153
- return { rows, contextFooter };
1154
- }
1155
1189
  /**
1156
1190
  * Live status chrome that must veto the broad prompt/context ready match.
1157
1191
  *
@@ -1186,8 +1220,6 @@ export class CodexPaneDetectors {
1186
1220
  isPeriodicRedrawIdlePane(pane) {
1187
1221
  if (codexAppServerDisconnected(pane))
1188
1222
  return false;
1189
- if (this.nativeContextlessEmpty(pane))
1190
- return true;
1191
1223
  // Astra's 0.154 theme animates U+22C6 star points across otherwise-stable
1192
1224
  // idle chrome. Remove only that observed decorative glyph; broader
1193
1225
  // punctuation stripping could turn real tool output into a false prompt.
@@ -1223,869 +1255,233 @@ export class CodexPaneDetectors {
1223
1255
  return false;
1224
1256
  return true;
1225
1257
  }
1226
- getReadyPattern() {
1227
- // Header and context text persist in inline scrollback and even while the
1228
- // CLI is loading or a modal owns stdin. Require the live prompt followed
1229
- // by the Context footer at the *end* of the capture. A valid custom
1230
- // status_line without Context gets a separate configured-item pattern;
1231
- // unknown footer values still fail closed. getBusyPattern vetoes a working
1232
- // turn whose empty composer remains visible.
1233
- // U+22C6 is Codex's observed cosmetic starfield; it can be drawn in the
1234
- // prompt, between prompt/footer, and below the footer. A drafted composer
1235
- // is also idle once this same bottom footer proves it owns the screen.
1236
- const contextReady = withCodexPassiveHintTail(/(?:^|\n)[>›][ \t⋆]+(?!\d+\.)\S[^\r\n]*\r?\n(?:[ \t⋆]*\r?\n){0,3}[ \t⋆]+(?:[0-9a-f-]{36}[ \t]+·[ \t]+)?Context[ \t]+(?:\d+%[ \t]+(?:left|used)|\d+…|…)[^\r\n]*(?:\r?\n[ \t⋆]*)*$/i);
1237
- const footerPattern = this.configuredStatusLineFooterPattern();
1238
- if (!footerPattern)
1239
- return contextReady;
1240
- // The no-Context alternative requires the exact empty composer followed by
1241
- // a recognized configured Codex status_line value. If run-state is
1242
- // configured, that value must specifically be Ready. This is positive
1243
- // structural proof, never a deny-list of known dialogs.
1244
- const footerSource = footerPattern.source.slice(1, -1);
1245
- const noContextReady = withCodexPassiveHintTail(new RegExp(String.raw `(?:^|\n)[>›][ \t⋆]*Ask Codex to do anything[ \t⋆]*\r?\n(?:[ \t⋆]*\r?\n){0,3}${footerSource}(?:\r?\n[ \t⋆]*)*$`, "iu"));
1246
- // #978: a configured footer that carries a Context item in any position
1247
- // is the Context footer: same drafted-composer rule as Context-first. The
1248
- // whole row must match the configured item grammar, so an arbitrary
1249
- // indented line that merely contains `Context N% left` cannot pass.
1250
- const configuredContextReady = this.configuredHasContextItem()
1251
- ? withCodexPassiveHintTail(new RegExp(String.raw `(?:^|\n)[>›][ \t⋆]+(?!\d+\.)\S[^\r\n]*\r?\n(?:[ \t⋆]*\r?\n){0,3}(?=[^\r\n]*${CODEX_CONTEXT_ITEM})${footerSource}(?:\r?\n[ \t⋆]*)*$`, "iu"))
1252
- : null;
1253
- return new RegExp([contextReady.source, noContextReady.source, configuredContextReady?.source, this.nativeContextlessPattern()?.source]
1254
- .filter((source) => !!source)
1255
- .map(source => `(?:${source})`)
1256
- .join("|"), "iu");
1257
- }
1258
- configuredStatusLineFooterPattern() {
1259
- if (this.configuredStatusLinePattern !== undefined)
1260
- return this.configuredStatusLinePattern;
1261
- const items = this.readConfiguredStatusLineItems();
1262
- if (!items?.length)
1263
- return this.configuredStatusLinePattern = null;
1264
- const configuredPatterns = items
1265
- .map(item => CODEX_STATUS_LINE_VALUE_PATTERNS[item])
1266
- .filter((pattern) => pattern !== undefined);
1267
- if (!configuredPatterns.length)
1268
- return this.configuredStatusLinePattern = null;
1269
- const segment = `(?:${configuredPatterns.join("|")})`;
1270
- const separator = String.raw `[ \t]+·[ \t]+`;
1271
- const hasRunState = items.some(item => CODEX_RUN_STATE_STATUS_ITEMS.has(item));
1272
- // Ready must occupy its own status-line segment. Other configured items
1273
- // may precede/follow it, as Codex joins visible values with ` · `.
1274
- const line = hasRunState
1275
- ? `(?:${segment}${separator})*Ready(?:${separator}${segment})*`
1276
- : `${segment}(?:${separator}${segment})*`;
1277
- // The configured items, then (optionally) the CLI's own native fields — the same rule as the Context-first footer, so a
1278
- // `model · Context 46% left Goal achieved (1h 6m)` row is the footer too, and the readiness regex built from this
1279
- // source (footerSource) says the same.
1280
- this.configuredStatusLinePattern = new RegExp(`^[ \t⋆]*${line}(?:${CODEX_NATIVE_FOOTER_FIELDS})?[ \t⋆]*$`, "iu");
1281
- return this.configuredStatusLinePattern;
1282
- }
1283
- isConfiguredStatusLineFooter(row) {
1284
- const pattern = this.configuredStatusLineFooterPattern();
1285
- if (!pattern)
1286
- return false;
1287
- pattern.lastIndex = 0;
1288
- return pattern.test(row);
1289
- }
1290
- configuredHasContextItem() {
1291
- return (this.readConfiguredStatusLineItems() ?? []).some(item => CODEX_CONTEXT_STATUS_ITEMS.has(item));
1292
- }
1293
1258
  /**
1294
- * #978: a Context item in ANY position, accepted only as part of a footer
1295
- * whose every segment matches the configured status_line items. Codex keeps
1296
- * the user's order (`gpt-5.6-sol medium · Context 46% left · ~/x`); an
1297
- * arbitrary indented transcript line that happens to contain the item does
1298
- * not match that grammar. A stale config fails closed, and the daemon's
1299
- * stable-composer escape hatch then keeps the instance from latching.
1259
+ * The row codex paints for input it has taken into its own queue instead of
1260
+ * submitting: `↳ <message>` under "Messages to be submitted after next tool
1261
+ * call (press esc to interrupt and send immediately)".
1300
1262
  */
1301
- // The configured grammar already makes Context a whole segment, so the item
1302
- // test only has to establish that one is present.
1303
- isConfiguredContextFooter(row) {
1304
- return this.configuredHasContextItem()
1305
- && new RegExp(CODEX_CONTEXT_ITEM, "iu").test(row)
1306
- && this.isConfiguredStatusLineFooter(row);
1263
+ getQueuedInputMarker() {
1264
+ return /↳/;
1307
1265
  }
1308
- /** Any footer that proves the Context meter owns the bottom row. */
1309
- isAnyContextFooter(row) {
1310
- return isCodexContextFooter(row) || this.isConfiguredContextFooter(row);
1266
+ /** #1811: what the last launch left out and why — the Log tab and daemon.log, never a channel. */
1267
+ quietLaunchNotes = [];
1268
+ consumeQuietLaunchNotes() { const notes = this.quietLaunchNotes; this.quietLaunchNotes = []; return notes; }
1269
+ buildCommand(config) {
1270
+ this.lastKnownModel = config.model?.trim() || null;
1271
+ this.credentialProfile = this.readProfile(config);
1272
+ // #2886: `--full-auto` is gone from codex (no supported version, 0.155 on, accepts it: the instance never started);
1273
+ // spelled out, it was a workspace-write sandbox that asks for approval on request.
1274
+ const approvalFlag = config.skipPermissions !== false
1275
+ ? "--dangerously-bypass-approvals-and-sandbox"
1276
+ : "--sandbox workspace-write --ask-for-approval on-request";
1277
+ // #984: never trust `codex resume --last` to pick this instance's session.
1278
+ // Codex 0.157 scopes it to the git REPOSITORY, so in a worktree it resumes
1279
+ // the newest session of any sibling worktree (a lock screen while that
1280
+ // instance lives, a silent hijack otherwise), and a new instance in a
1281
+ // shared repo does not start fresh. Resume the exact-cwd thread from the
1282
+ // shared state DB instead; `--last` is only the fallback for an unreadable
1283
+ // DB when no other Codex instance shares the repository.
1284
+ let cmd;
1285
+ this.launchWarning = null;
1286
+ this.lastLaunchResumes = false;
1287
+ this.quotaReplay = null;
1288
+ // #2357: read-only (codex is never run here); a launch is not held for it — the operator is told once per launch.
1289
+ this.packageWarning = codexPackageWarning(this.binaryPath);
1290
+ if (config.skipResume) {
1291
+ cmd = `${this.binaryPath} ${approvalFlag}`;
1292
+ }
1293
+ else {
1294
+ const resumeBy = (id, why, bytes, path) => {
1295
+ this.lastLaunchResumes = true;
1296
+ this.quotaReplay = snapshotCodexQuotaHistory(path);
1297
+ this.writeResumeAttempt(id);
1298
+ const large = bytes !== null && bytes > CODEX_RESUME_LARGE_BYTES ? ` ${t("codex.resume_large", String(Math.round(bytes / (1024 * 1024))))}` : "";
1299
+ this.launchWarning = why + large;
1300
+ return `${this.binaryPath} resume ${shellQuote(id)} ${approvalFlag}`;
1301
+ };
1302
+ const sessionsDir = join(this.isolatedCodexHome, "sessions");
1303
+ // M-2266: the previous launch was told to resume a conversation by id. Codex records a thread as it opens it,
1304
+ // so a resume that did not take (codex exited with "No saved session found…") left no row: this launch starts
1305
+ // fresh, once, and says so. A database that cannot be read proves nothing, and planning goes on as usual.
1306
+ // A new one-shot ask comes first: it is the person's explicit choice for this launch.
1307
+ const oneShot = this.takeResumeNext();
1308
+ const attempt = this.takeResumeAttempt();
1309
+ const failed = !oneShot && attempt !== null && codexThreadRowExists(this.stateDbPath(), attempt) === false;
1310
+ if (failed) {
1311
+ // Remembered, so the auto-resume never picks this conversation again on a later launch.
1312
+ this.rememberFailedResume(attempt);
1313
+ cmd = `${this.binaryPath} ${approvalFlag}`;
1314
+ this.launchWarning = t("codex.resume_failed", config.workingDirectory, attempt);
1315
+ }
1316
+ else if (oneShot) {
1317
+ // M-2266: validated when it was asked for (restart_instance); checked again now, as the rollout may have moved.
1318
+ const check = checkResumeId(sessionsDir, config.workingDirectory, oneShot);
1319
+ if (check.ok)
1320
+ cmd = resumeBy(check.rollout.id, t("codex.resume_oneshot", config.workingDirectory, check.rollout.id), check.rollout.bytes, check.rollout.path);
1321
+ else {
1322
+ cmd = this.plannedCommand(config, approvalFlag, sessionsDir, resumeBy);
1323
+ this.launchWarning = [t("codex.resume_oneshot_invalid", config.workingDirectory, oneShot, check.reason), this.launchWarning].filter(Boolean).join("\n");
1324
+ }
1325
+ }
1326
+ else {
1327
+ cmd = this.plannedCommand(config, approvalFlag, sessionsDir, resumeBy);
1328
+ }
1329
+ }
1330
+ if (config.model) {
1331
+ const model = validateModel(config.model);
1332
+ warnIfModelMismatch("codex", model);
1333
+ cmd += ` -c ${shellQuote(`model="${model}"`)}`;
1334
+ }
1335
+ if (config.backendOptions?.provider) {
1336
+ const provider = validateProvider(String(config.backendOptions.provider));
1337
+ cmd += ` -c ${shellQuote(`model_provider="${provider}"`)}`;
1338
+ }
1339
+ // #1811: the configured effort, when the catalog entry of the model that will run lists it (launch-effort.ts); else
1340
+ // a quiet note. That model is proven from the command line or the config files codex reads (#1813 review).
1341
+ const runs = resolveCodexLaunchModel({
1342
+ isolatedHome: this.isolatedCodexHome, sharedHome: this.sharedCodexHome,
1343
+ model: config.model, workingDirectory: config.workingDirectory,
1344
+ });
1345
+ const effort = codexEffort(config.effort, runs.proven
1346
+ ? readCodexModelEffortLevels({ isolatedHome: this.isolatedCodexHome, sharedHome: this.sharedCodexHome, model: runs.model })
1347
+ : { model: runs.model, levels: null, unproven: true });
1348
+ if (effort.arg)
1349
+ cmd += effort.arg;
1350
+ // M-2446: the per-agent speed (codex-speed.ts) — Fast only where the running model's catalog entry offers it.
1351
+ const speed = codexSpeed(config.backendOptions?.speed, runs.proven
1352
+ ? readCodexModelServiceTiers({ isolatedHome: this.isolatedCodexHome, sharedHome: this.sharedCodexHome, model: runs.model })
1353
+ : { model: runs.model, tiers: null, unproven: true });
1354
+ if (speed.arg)
1355
+ cmd += speed.arg;
1356
+ this.quietLaunchNotes = [effort.note, speed.note].filter((n) => !!n);
1357
+ // AgEnD instances are unattended processes: an interactive self-update
1358
+ // picker blocks delivery and must never be enabled by a copied global or
1359
+ // managed config layer. Keep this last so the CLI override is authoritative.
1360
+ // Both observed Codex 0.155.0 and 0.156.1 support this launch flag. Pin
1361
+ // the initial layout so a user/global fullscreen preference cannot make a
1362
+ // header-only pane look ready. A later unknown layout still fails closed.
1363
+ cmd += " -c check_for_update_on_startup=false --no-alt-screen";
1364
+ // codex 0.159's opt-in `instant_interrupt` makes new input steer the
1365
+ // running response instead of queueing behind it, which AgEnD's delivery
1366
+ // and queued-input (↳) handling assume. Keep it off whatever a user or
1367
+ // global config says. Codex before 0.159 ignores the key with a startup
1368
+ // warning ("`features.instant_interrupt` is ignored"); nothing else.
1369
+ cmd += " -c features.instant_interrupt=false";
1370
+ // CODEX_HOME is the only Codex-supported way to isolate the complete base
1371
+ // config. A profile only layers over the shared config and would therefore
1372
+ // still load every globally registered AgEnD MCP server.
1373
+ return `CODEX_HOME=${shellQuote(this.isolatedCodexHome)} ${cmd}`;
1374
+ }
1375
+ writeConfig(config) {
1376
+ this.authorizedTrust = null;
1377
+ // Set before the home is prepared: which login this instance gets is a
1378
+ // property of the home, and the home is built here.
1379
+ this.credentialProfile = this.readProfile(config);
1380
+ this.prepareIsolatedHome();
1381
+ this.cleanSharedConfig();
1382
+ // Copy all user settings but no AgEnD MCP entries into a private base
1383
+ // config, then append only this instance's server(s). Each instance writes
1384
+ // a distinct file, eliminating the old concurrent global-config race.
1385
+ let content = "";
1386
+ try {
1387
+ content = stripAgendMcpTables(readFileSync(join(this.sharedCodexHome, "config.toml"), "utf-8"));
1388
+ }
1389
+ catch { /* a first-time Codex user may have no global config */ }
1390
+ if (content && !content.endsWith("\n"))
1391
+ content += "\n";
1392
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
1393
+ content += `\n${renderMcpServer(name, entry, config.instanceName)}`;
1394
+ }
1395
+ atomicWritePrivate(join(this.isolatedCodexHome, "config.toml"), content);
1396
+ this.enableContextStatusLine();
1397
+ this.disableStartupUpdateCheck();
1398
+ this.hideRateLimitModelNudge();
1399
+ // Write fleet instructions into AGENTS.md (additive via marker block)
1400
+ if (config.instructions) {
1401
+ try {
1402
+ const agentsMd = join(config.workingDirectory, "AGENTS.md");
1403
+ appendWithMarker(agentsMd, config.instanceName, config.instructions);
1404
+ // Warn if file exceeds Codex's project_doc_max_bytes limit
1405
+ try {
1406
+ const size = statSync(agentsMd).size;
1407
+ if (size > CODEX_PROJECT_DOC_MAX_BYTES) {
1408
+ console.warn(`[agend] AGENTS.md is ${size} bytes, exceeds Codex limit of ${CODEX_PROJECT_DOC_MAX_BYTES} — instructions may be truncated`);
1409
+ }
1410
+ }
1411
+ catch { /* stat failed — skip size check */ }
1412
+ }
1413
+ catch { /* best effort */ }
1414
+ }
1311
1415
  }
1312
1416
  /**
1313
- * #978 escape hatch: the live, EMPTY Codex composer within the last few
1314
- * non-blank rows, no busy marker, and none of the known selection screens.
1315
- * Deliberately says nothing about the footer, which is what is unknown.
1417
+ * Stop Codex opening its "Update available!" picker when an instance starts.
1418
+ *
1419
+ * That dialog owns the input loop until someone answers it, so after a fleet
1420
+ * restart every Codex instance sat waiting on a keypress nobody was going to
1421
+ * send. AgEnD then reported it as a stuck pane, which is true but unhelpful.
1422
+ *
1423
+ * `check_for_update_on_startup = false` is Codex's own config key (verified in
1424
+ * the binary), so this prevents the prompt rather than racing to dismiss it.
1425
+ * There is no CLI flag for it — `codex update` is a subcommand, not a switch.
1426
+ *
1427
+ * Placement matters: this is a TOP-LEVEL key, so it has to go above the first
1428
+ * `[section]` header or TOML would read it as belonging to that section. And a
1429
+ * AgEnD's isolated home is unattended, so an inherited `true` is replaced
1430
+ * rather than preserved. TOML rejects duplicate keys, so replace in place.
1316
1431
  */
1317
- isStableUnknownLayoutIdlePane(pane) {
1318
- if (this.getBusyPattern().test(pane))
1319
- return false;
1320
- // The composer is live-looking and the footer is unknown — exactly this
1321
- // proof's blind spot — but there is no server behind it (#1099).
1322
- if (codexAppServerDisconnected(pane))
1323
- return false;
1324
- // (No second, wider "esc to interrupt" match here: getBusyPattern() above already reads a live status row of ANY
1325
- // title — #964 — and anchors it to the whole row at column zero, so a reply that quotes the phrase, indented or
1326
- // in prose, cannot pin the escape hatch shut (#991).)
1327
- // A retained queued message (`↳ …` under "Messages to be submitted after
1328
- // next tool call") means work is pending, whatever the composer shows.
1329
- if (/↳|Messages to be submitted/i.test(pane))
1330
- return false;
1331
- if (codexUsageLimitMenuVisible(pane) || codexRateSwitchVisible(pane)
1332
- || codexUnknownSelectionVisible(pane) || codexUpdatePickerVisible(pane))
1333
- return false;
1334
- const rows = pane.replace(/\r/g, "").split("\n").filter(row => row.trim() !== "");
1335
- return rows.slice(-6).some(row => /^[>›][ \t⋆]*Ask Codex to do anything[ \t⋆]*$/.test(row));
1336
- }
1337
- isCodexIdleFooter(row) {
1338
- return isCodexContextFooter(row) || this.isConfiguredStatusLineFooter(row);
1432
+ disableStartupUpdateCheck() {
1433
+ const configPath = join(this.isolatedCodexHome, "config.toml");
1434
+ let content = "";
1435
+ try {
1436
+ content = readFileSync(configPath, "utf-8");
1437
+ }
1438
+ catch {
1439
+ return;
1440
+ }
1441
+ const existing = /^\s*check_for_update_on_startup\s*=\s*(?:true|false)\s*(?:#.*)?$/m;
1442
+ if (existing.test(content)) {
1443
+ const updated = content.replace(existing, "check_for_update_on_startup = false");
1444
+ if (updated !== content) {
1445
+ try {
1446
+ atomicWritePrivate(configPath, updated);
1447
+ }
1448
+ catch { /* best effort */ }
1449
+ }
1450
+ return;
1451
+ }
1452
+ const LINE = "check_for_update_on_startup = false\n";
1453
+ const firstSection = content.search(/^\s*\[/m);
1454
+ const updated = firstSection === -1
1455
+ ? (content.length && !content.endsWith("\n") ? `${content}\n${LINE}` : `${content}${LINE}`)
1456
+ : `${content.slice(0, firstSection)}${LINE}${content.slice(firstSection)}`;
1457
+ try {
1458
+ atomicWritePrivate(configPath, updated);
1459
+ }
1460
+ catch { /* best effort */ }
1339
1461
  }
1340
- /** A proxy reply filters chrome per line; whole-pane readiness is separate. */
1341
- isProxyReplyChromeLine(line) {
1342
- return /^\s*[›>]\s+Ask Codex to do anything\s*$/.test(line)
1343
- || this.isAnyContextFooter(line);
1344
- }
1345
- getErrorPatterns() {
1346
- return [
1347
- // Specific quota codes must precede the generic HTTP 429 classifier:
1348
- // OpenAI returns insufficient_quota with status 429, but switching models
1349
- // cannot repair an exhausted account and would only start a failover loop.
1350
- {
1351
- pattern: new RegExp(String.raw `^\s*(?:${CODEX_STATUS_GLYPH}|Error:|API Error:)\s*[\s\S]{0,240}?\b(?:insufficient_quota|billing_hard_limit_reached|exceeded\s+your\s+current\s+quota)\b|^(?:${CODEX_STATUS_GLYPH}) Quota exceeded\. Check your plan and billing details\.\r?$`, "im"),
1352
- type: "quota",
1353
- action: "pause",
1354
- message: "OpenAI quota exceeded",
1355
- notice: () => quotaPausedLine("OpenAI", "api_quota"),
1356
- },
1357
- {
1358
- // Codex already exhausted HTTP retries and returned to its composer.
1359
- // The existing conditional continuation preserves context without
1360
- // replaying earlier tool effects. Quota remains a preceding pause.
1361
- pattern: new RegExp(`^${CODEX_EXHAUSTED_429_ROW}$`, "im"),
1362
- type: "rate_limit",
1363
- action: "nudge_continue",
1364
- message: t("inst.codex_rate_retry_stopped"),
1365
- skipCooldown: true,
1366
- skipRecoveryWait: true,
1367
- },
1368
- {
1369
- // Pane history contains the agent's own prose, source code and search
1370
- // results. Bare `rate limit` used to fail over an otherwise healthy
1371
- // Codex merely for discussing this regex. Match machine-readable API
1372
- // forms or an error-decorated terminal line instead.
1373
- pattern: new RegExp(String.raw `^\s*unexpected\s+status\s+429\b|^(?![\s\S]{0,240}?${CODEX_EXHAUSTED_429_ROW})[ \t]*(?:${CODEX_STATUS_GLYPH}|Error:|API Error:)\s*[\s\S]{0,240}?(?:["'](?:status|code)["']\s*:\s*429\b|\b(?:rate_limit_exceeded|too_many_requests)\b|\brate limit(?:ed| exceeded| reached)?\b|\btoo many requests\b)`, "im"),
1374
- type: "rate_limit",
1375
- action: "failover",
1376
- message: "OpenAI rate limit reached",
1377
- },
1378
- {
1379
- // Same false-positive boundary as rate limits: `authentication` is a
1380
- // normal English/code word. Require a structured 401/code or a
1381
- // decorated CLI error line before pausing every instance on the shared
1382
- // credential.
1383
- pattern: new RegExp(String.raw `^\s*unexpected\s+status\s+401\b|^\s*(?:${CODEX_STATUS_GLYPH}|Error:|API Error:)\s*[\s\S]{0,240}?(?:["'](?:status|code)["']\s*:\s*401\b|\b(?:invalid_api_key|authentication_error)\b|\b401\s+Unauthorized\b|\bauthentication (?:failed|error)\b|\binvalid api key\b)`, "im"),
1384
- type: "auth_error",
1385
- action: "pause",
1386
- message: "OpenAI authentication error",
1387
- },
1388
- // Codex writes the apostrophe as U+2019 ("You’ve hit your usage limit.") since
1389
- // 0.156 and as ASCII before (0.153.4): both must match, or no usage limit
1390
- // ever pauses the instance (#1098).
1391
- { pattern: /you['’]ve hit your usage limit/i, type: "quota", action: "pause", message: "Codex usage limit reached — upgrade plan required",
1392
- notice: () => quotaPausedLine("Codex", "usage") },
1393
- {
1394
- // The terminal stage of a lost app-server (#1099): Codex has given up
1395
- // reconnecting and says so, with a counter that never stops. Nothing
1396
- // recovers from here without a relaunch, and every message sent in the
1397
- // meantime waits for an idle that cannot come. Only the LIVE row counts:
1398
- // directly above the LAST composer with no later transcript item or composer
1399
- // (the same test as codexAppServerDisconnected, as a lookahead), so text that
1400
- // merely quotes it does not raise this.
1401
- pattern: new RegExp(String.raw `^[ \t]*•[ \t]+Reconnect failed[ \t]+—[ \t]+check the endpoint, then relaunch[ \t]+\(\d+[hms](?:[ \t]+\d+[hms])*\)[ \t]*$`
1402
- + String.raw `(?=\n(?:[ \t]*\n)*[›>][^\n]*(?:\n(?![•■⚠›>])[^\n]*)*(?![\s\S]))`, "mu"),
1403
- type: "network",
1404
- action: "notify",
1405
- message: "Codex lost its app-server connection and could not reconnect — restart the instance (messages cannot be delivered until then)",
1406
- },
1407
- {
1408
- // Codex reports an unknown model either as a TUI metadata fallback or
1409
- // as a ChatGPT-account API rejection. Use whitespace-aware phrases so
1410
- // capture-pane hard wraps do not hide either form.
1411
- // Codex quotes the slug with BACKTICKS ("Model metadata for `x` not found.",
1412
- // verified live on 0.160.0 and in the 0.153.4–0.160.0 binaries). The old
1413
- // pattern only took ASCII quotes, so it could never match a real line.
1414
- pattern: /model\s+metadata\s+for\s+['"`‘’“”][^'"`‘’“”]+['"`‘’“”]\s+not\s+found\.\s+defaulting\s+to\s+fallback\s+metadata|model\s+is\s+not\s+supported\s+when\s+using\s+codex\s+with\s+a\s+chatgpt\s+account/i,
1415
- type: "model_error",
1416
- action: "notify",
1417
- message: "Codex model unavailable — use /model to switch",
1418
- },
1419
- {
1420
- // A capacity rejection is a completed failed turn: Codex returns to its
1421
- // prompt without an answer, with its context intact. Keep this anchored to
1422
- // the exact decorated TUI line so ordinary prose about model capacity
1423
- // cannot trigger anything on an otherwise healthy instance. The status
1424
- // glyph is decoration that changes between Codex versions and error classes
1425
- // (`■` U+25A0 on 0.159.2 and 0.160.0, `⚠` U+26A0 before), so any one
1426
- // "Other Symbol" glyph (optionally with its emoji selector) opens the line —
1427
- // never a bullet, dash, quote marker or number, which prose and the model's
1428
- // own transcript items use (`•`, `-`, `>`). The sentence must fill the line,
1429
- // from column 0. Box drawing and block elements (U+2500–U+259F) are
1430
- // excluded up front: they are `So` too, but a left-border popup or
1431
- // table row (`│ …`) is chrome, not a status line. The CLI is
1432
- // already back at the prompt, so skipRecoveryWait avoids an extra wait.
1433
- // action "nudge_continue": tell the user, then about a minute later tell
1434
- // the agent to keep going (the lifecycle bounds this to 3 per 30 minutes,
1435
- // then pauses; see #905). No restart — nothing would re-send the turn — and
1436
- // no model switch. skipCooldown: each NEW capacity line is a new episode,
1437
- // including a second one right after the nudge.
1438
- pattern: /^(?![─-▟])\p{So}\uFE0F? Selected model is at capacity\. Please try a different model\.\r?$/mu,
1439
- type: "model_error",
1440
- action: "nudge_continue",
1441
- message: t("inst.codex_model_capacity"),
1442
- skipCooldown: true,
1443
- skipRecoveryWait: true,
1444
- },
1445
- // Workspace (business) accounts report exhaustion differently from personal
1446
- // ones, with codex's own rows (codex-rs/protocol error.rs and tui
1447
- // turn_runtime.rs, unchanged 0.160.0–0.165.0):
1448
- // member: "■ Your workspace is out of credits. Ask your workspace owner to refill in order to continue."
1449
- // "■ You hit your spend cap set by the owner of your workspace. Ask an owner to increase your spend cap to continue."
1450
- // owner: "■ You're out of credits. Your workspace is out of credits. Add credits to continue using Codex."
1451
- // "■ Your workspace is out of credits. Add credits to continue."
1452
- // "■ You hit your spend cap set in your workspace. Increase your spend cap to continue."
1453
- // "■ Usage limit reached. You've reached your usage limit. Increase your limits to continue using codex."
1454
- // Neither `insufficient_quota|billing` nor `you've hit your usage limit`
1455
- // matches them.
1456
- //
1457
- // pause, not notify: nothing works until someone refills or raises the cap.
1458
- // Anchored to the status glyph in COLUMN 0, because these skip the
1459
- // usage-window check (a workspace limit is not one of those windows, so the
1460
- // check would call it "available"): codex draws its own error cells there,
1461
- // while an agent's message, quote or code block is indented under its `•`
1462
- // (captured live, tests/fixtures/codex-0165-workspace-quoted.pane.txt).
1463
- // `\s+` at each word gap: capture-pane runs without -J, so tmux's hard wrap
1464
- // can land a newline anywhere in the sentence. A resumed transcript reprints
1465
- // these rows, and the first live composer of a spawn takes them as history.
1466
- {
1467
- pattern: new RegExp(String.raw `^(?:${CODEX_STATUS_GLYPH})[ \t]+(?:You['’]re\s+out\s+of\s+credits\.\s+)?Your\s+workspace\s+is\s+out\s+of\s+credits\.`, "m"),
1468
- type: "quota",
1469
- action: "pause",
1470
- message: "Codex workspace credits exhausted — workspace owner must refill",
1471
- notice: () => quotaPausedLine("Codex", "workspace_credits"),
1472
- skipUsageVerify: true,
1473
- historyUntilLiveComposer: true,
1474
- },
1475
- {
1476
- pattern: new RegExp(String.raw `^(?:${CODEX_STATUS_GLYPH})[ \t]+You\s+hit\s+your\s+spend\s+cap\s+set\s+(?:by\s+the\s+owner\s+of|in)\s+your\s+workspace\.`, "m"),
1477
- type: "quota",
1478
- action: "pause",
1479
- message: "Codex workspace spend cap reached — workspace owner must raise it",
1480
- notice: () => t("inst.quota_spend_cap"),
1481
- skipUsageVerify: true,
1482
- historyUntilLiveComposer: true,
1483
- },
1484
- {
1485
- pattern: new RegExp(String.raw `^(?:${CODEX_STATUS_GLYPH})[ \t]+Usage\s+limit\s+reached\.\s+You['’]ve\s+reached\s+your\s+usage\s+limit\.\s+Increase\s+your\s+limits`, "m"),
1486
- type: "quota",
1487
- action: "pause",
1488
- message: "Codex workspace usage limit reached — increase the workspace limits to continue",
1489
- notice: () => quotaPausedLine("Codex", "workspace_usage"),
1490
- skipUsageVerify: true,
1491
- historyUntilLiveComposer: true,
1492
- },
1493
- // Codex warns at 10% and 5% remaining, and scopes the limit by period —
1494
- // "weekly" alone missed every `monthly limit` warning.
1495
- //
1496
- // `\s+` at EVERY word gap (not literal spaces) because capture-pane runs without -J,
1497
- // so tmux's hard wrap can land a newline (plus continuation-line padding)
1498
- // at any of these word gaps. The trailing "Run /status for a breakdown."
1499
- // is deliberately NOT part of the pattern — that tail is what actually
1500
- // wrapped in the reported case.
1501
- //
1502
- // `of your <period> limit` is load-bearing: it's what keeps the pattern
1503
- // off the agent's own prose about percentages.
1504
- {
1505
- pattern: /less\s+than\s+(\d+)\s*%\s+of\s+your\s+(hourly|daily|weekly|monthly)\s+limit/i,
1506
- type: "quota",
1507
- action: "notify",
1508
- message: "Codex usage limit running low",
1509
- formatMessage: (m) => `Codex ${m[2].toLowerCase()} limit: less than ${m[1]}% left`,
1510
- notice: (m) => (m ? quotaLowLine(m[2], m[1]) : quotaLine("Codex", "usage")),
1511
- },
1512
- ];
1513
- }
1514
- getStartupDialogs() {
1515
- const trustHold = this.trustHoldDialog();
1516
- return [
1517
- this.autoTrustDialog(),
1518
- trustHold,
1519
- this.updatePickerDialog(),
1520
- this.rateSwitchPickerDialog(),
1521
- ...this.sessionHoldDialogs(),
1522
- this.unknownSelectionHoldDialog(),
1523
- ];
1524
- }
1525
- /**
1526
- * #984: session-selection screens only a human may answer. Held (never
1527
- * keyed) so the delivery gate keeps messages queued and dialog_parked tells
1528
- * the operator; listed before the generic selection hold for a clear notice.
1529
- */
1530
- sessionHoldDialogs() {
1531
- return [
1532
- {
1533
- pattern: /This conversation is open in another app/,
1534
- keys: [],
1535
- description: "Codex session is open in another process (thread lock) — close the other one and press r, or fork manually",
1536
- holdOnly: true,
1537
- blocksDelivery: true,
1538
- inputBlocked: true,
1539
- remoteCard: codexSelectionCard,
1540
- isActive: codexSessionLockVisible,
1541
- },
1542
- {
1543
- pattern: /Working directory · resume/,
1544
- keys: [],
1545
- description: "Codex resume selected a session from another directory (sibling git worktree) — needs a human choice",
1546
- holdOnly: true,
1547
- blocksDelivery: true,
1548
- inputBlocked: true,
1549
- remoteCard: codexSelectionCard,
1550
- isActive: codexResumeCwdPickerVisible,
1551
- },
1552
- ];
1553
- }
1554
- /**
1555
- * The user's decision (hmsg-53600a2c): codex's folder-trust prompt in an AgEnD session is always answered Trust — the
1556
- * workspace is the agent's own, whatever preTrust covered (preTrust stays, so the prompt rarely shows at all). Enter,
1557
- * only with the cursor on "Trust and continue" among the known options (trustCursor); any other layout or row is
1558
- * held (trustHoldDialog). It trusts the folder only: per-action approvals are separate dialogs and are not touched.
1559
- */
1560
- autoTrustDialog() {
1561
- return {
1562
- pattern: /^\s*Trust this folder\?/m,
1563
- keys: ["Enter"],
1564
- description: "Codex folder trust — trusting the agent's own folder",
1565
- blocksDelivery: true,
1566
- inputBlocked: true,
1567
- autoResolutionKey: "codex-folder-auto-trust",
1568
- // One Enter per prompt (claimed, then checked): a prompt still up after it is held, never pressed again (#2896 review).
1569
- verifyAfterKeys: true,
1570
- isActive: pane => codexTrustPromptState(pane).trustCursor,
1571
- answerDetail: pane => ({ autoTrusted: true, ...this.trustPaneFields(pane) }),
1572
- };
1573
- }
1574
- trustHoldDialog() {
1575
- return {
1576
- pattern: /^\s*(?:Folder access|Trust this folder\?|Do you trust the files in this folder\?)/im,
1577
- keys: [],
1578
- description: "Codex folder trust needs human confirmation",
1579
- holdOnly: true,
1580
- blocksDelivery: true,
1581
- inputBlocked: true,
1582
- isActive: codexTrustVariantActive,
1583
- remoteCard: codexSelectionCard,
1584
- heldReason: pane => this.trustHoldReason(pane),
1585
- };
1586
- }
1587
- /**
1588
- * #1008: the rate-limit model-switch picker parked unattended instances (it
1589
- * waits forever and its default option changes the model). Prevention is
1590
- * `notice.hide_rate_limit_model_nudge` (hideRateLimitModelNudge — real and
1591
- * effective on 0.157.1 and 0.159.2, verified live); this is the answer for a
1592
- * picker that appears anyway (config not applied, a CLI that moved the key).
1593
- *
1594
- * Escape, not a navigated Enter: it selects nothing, so it can never take
1595
- * option 1 (switch model) or option 3 (writes "never show again" into the
1596
- * config). Deliberately not one-shot: if the picker swallowed the key it is
1597
- * simply sent again on the next poll, and a repeated Escape is harmless.
1598
- */
1599
- rateSwitchPickerDialog() {
1600
- return {
1601
- // Documentation only: Daemon.dialogMatches uses isActive INSTEAD of this
1602
- // pattern, so it filters nothing here — the structural check below is the
1603
- // whole decision, and ordinary prose about the picker fails it. The words
1604
- // are the picker's own, kept so a reader can find the screen from the log.
1605
- pattern: /Keep current model \(never show again\)/,
1606
- keys: ["Escape"],
1607
- description: "Codex rate-limit model-switch picker — Escape keeps the current model",
1608
- blocksDelivery: true,
1609
- inputBlocked: true,
1610
- isActive: codexRateSwitchPickerVisible,
1611
- };
1612
- }
1613
- updatePickerDialog() {
1614
- return {
1615
- // Defense in depth for config written by older AgEnD versions or a Codex
1616
- // regression. Match the release URL as well as the banner so ordinary
1617
- // agent prose cannot trigger a keypress.
1618
- pattern: /Update available![\s\S]{0,200}Release notes: https:\/\/github\.com\/openai\/codex\/releases/m,
1619
- keys: ["Escape"],
1620
- description: "Codex startup update-available picker",
1621
- // A delivery must never land on this screen. Its selected option is
1622
- // `› 1. Update now (runs … curl … | sh)` — the same `›` the input row
1623
- // uses — so an Enter here runs an installer instead of sending a message.
1624
- // The runtime dismisser presses Escape, but it only polls every few
1625
- // seconds and a delivery can arrive first (hit live on codex-cli 0.153.4,
1626
- // which parks on this picker for as long as nobody answers it).
1627
- blocksDelivery: true,
1628
- // Daemon.dialogMatches uses isActive INSTEAD OF pattern when present.
1629
- // Check the complete current picker here; a different Enter-only menu
1630
- // must never receive this automatic Escape key.
1631
- isActive: codexUpdatePickerVisible,
1632
- };
1633
- }
1634
- usageLimitLunaReserveDialog() {
1635
- // The real codex usage-limit dialog states codex has ALREADY switched to
1636
- // Luna Reserve and labels Escape as "continue working". Pressing Escape via
1637
- // sendSpecialKey is the ONLY safe key:
1638
- //
1639
- // • Escape is sent via sendSpecialKey — never via pasteText (which adds
1640
- // an implicit bracketed-paste Enter that could confirm the highlighted
1641
- // server action: Upgrade, Reset usage, Add Credits, …).
1642
- // • A digit selects its row at once, and pasted its implicit Enter would
1643
- // confirm whatever it landed on. This path is explicitly NOT used.
1644
- //
1645
- // verifyAfterKeys: true — confirm the menu is gone after Escape.
1646
- // No confirmBeforeEnter / keysAfterConfirm: Escape needs no confirm gate.
1647
- return {
1648
- pattern: /Press enter to confirm or esc to continue working/i,
1649
- keys: ["Escape"],
1650
- description: "Codex usage limit — pressing Escape to continue with Luna Reserve",
1651
- blocksDelivery: true,
1652
- inputBlocked: true,
1653
- isActive: codexUsageLimitMenuVisible,
1654
- verifyAfterKeys: true,
1655
- autoResolutionKey: "codex-usage-limit-luna-reserve",
1656
- postDismissNotice: {
1657
- text: t("inst.codex_usage_limit_luna_reserve", ""), // instance name injected by daemon
1658
- label: "codex-usage-limit-selected",
1659
- },
1660
- };
1661
- }
1662
- workspaceLimitPromptDialog() {
1663
- // Escape via sendSpecialKey, the same as the Luna Reserve menu: never a paste
1664
- // (its implicit Enter would confirm the cursor's row) and never "y". The error
1665
- // row codex printed above the prompt stays, and the quota pause takes it from
1666
- // there: one notice, no parked dialog.
1667
- return {
1668
- pattern: /^\s*enter select\s*[·•]\s*esc back\s*$/im,
1669
- keys: ["Escape"],
1670
- description: "Codex workspace limit prompt — pressing Escape (never asking the owner)",
1671
- blocksDelivery: true,
1672
- inputBlocked: true,
1673
- isActive: codexWorkspaceLimitPromptVisible,
1674
- verifyAfterKeys: true,
1675
- autoResolutionKey: "codex-workspace-limit-prompt",
1676
- answeredByAgend: true,
1677
- marksLiveIncident: true,
1678
- };
1679
- }
1680
- unknownSelectionHoldDialog() {
1681
- return {
1682
- pattern: /^\s*[›❯>]\s+\S/m,
1683
- keys: [],
1684
- description: "Codex interactive selection needs human input",
1685
- holdOnly: true,
1686
- blocksDelivery: true,
1687
- inputBlocked: true,
1688
- remoteCard: codexSelectionCard,
1689
- isActive: codexUnknownSelectionVisible,
1690
- };
1691
- }
1692
- getRuntimeDialogs() {
1693
- return [
1694
- this.autoTrustDialog(),
1695
- this.trustHoldDialog(),
1696
- this.rateSwitchPickerDialog(),
1697
- {
1698
- // Codex 0.156 may change the wording/order of this credit-cost choice.
1699
- // Never navigate it by position: a moved option could switch to a
1700
- // more expensive model. A live picker holds delivery and notifies a
1701
- // human; old transcript mentions are excluded by the tail matcher.
1702
- pattern: /(?:Approaching rate limits|Switch to [^\r\n]{1,120} for lower credit usage\?)/i,
1703
- keys: [],
1704
- description: "Codex rate limit model switch dialog needs human choice",
1705
- holdOnly: true,
1706
- blocksDelivery: true,
1707
- inputBlocked: true,
1708
- remoteCard: codexSelectionCard,
1709
- isActive: codexRateSwitchVisible,
1710
- },
1711
- this.updatePickerDialog(),
1712
- this.usageLimitLunaReserveDialog(),
1713
- this.workspaceLimitPromptDialog(),
1714
- ...this.sessionHoldDialogs(),
1715
- this.unknownSelectionHoldDialog(),
1716
- ];
1717
- }
1718
- getInputUnavailableTransients() {
1719
- return [{
1720
- pattern: /(?:^|\n)\s*Resuming session…\s*(?:\n|$)/,
1721
- description: "Codex session resume in progress",
1722
- isActive: (pane) => {
1723
- const rows = pane.split(/\r?\n/);
1724
- return codexBoxedResumeLoading(rows) || codexUnboxedResumeLoading(rows);
1725
- },
1726
- }];
1727
- }
1728
- /** Only a resume paints "Resuming session…"; a fresh launch never does. */
1729
- launchMayShowInputTransient() {
1730
- return this.paneDetectorContext?.launchMayResume() ?? this.lastLaunchResumes;
1731
- }
1732
- getQuitCommand() { return "/quit"; }
1733
- getCompactCommand() { return "/compact"; }
1734
- getClearCommand() { return "/clear"; }
1735
- getCancelKey() { return "Escape"; }
1736
- }
1737
- export class CodexBackend extends CodexPaneDetectors {
1738
- instanceDir;
1739
- binaryPath;
1740
- sharedCodexHome;
1741
- isolatedCodexHome;
1742
- /** Which subscription this instance runs on, or null for the shared login. */
1743
- credentialProfile = null;
1744
- /** Set only after preTrust wrote and read back this instance's private config. */
1745
- authorizedTrust = null;
1746
- /** Set by buildCommand when the resume plan fell back; read once by the daemon. */
1747
- launchWarning = null;
1748
- quotaReplay = null;
1749
- quotaReplayHistory() { return this.quotaReplay; }
1750
- /** Set by writeConfig when the status_line could not be made verifiable (#931). */
1751
- statusLineWarning = null;
1752
- /** #2357: set by buildCommand when the codex being launched lacks a package companion (codex-code-mode-host). */
1753
- packageWarning = null;
1754
- constructor(instanceDir) {
1755
- super();
1756
- this.instanceDir = instanceDir;
1757
- this.binaryPath = resolveBinary("codex");
1758
- this.sharedCodexHome = resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
1759
- this.isolatedCodexHome = CodexBackend.resolveShortHome(instanceDir);
1760
- }
1761
- /**
1762
- * Compute and (on first call for an existing instance) migrate to a SHORT
1763
- * persistent CODEX_HOME under `~/.agend/cx/<8-char-hash>/`.
1764
- *
1765
- * Background (#953): codex 0.157.0 added `app-server-control.sock` under
1766
- * CODEX_HOME. For instances with long `-t<topic_id>` suffixes the full path
1767
- * exceeds the Unix socket SUN_LEN (~107 chars). codex canonicalises the path
1768
- * before binding the socket, so a symlink workaround does not help — the real
1769
- * path must be short.
1770
- *
1771
- * Migration steps (each is idempotent; half-completed states self-heal):
1772
- * 1. If shortHome does not exist AND legacyHome is a real directory (not a
1773
- * symlink) → atomic renameSync. On EXDEV or any other error, fall back
1774
- * safely: keep using the legacy long path (instance can still start, just
1775
- * without the short-path fix).
1776
- * 2. If shortHome exists but legacyHome is missing → recreate the backward-
1777
- * compat symlink (self-heal for rename-succeeded-but-symlink-failed crash).
1778
- * 3. If both shortHome and legacyHome (as symlink) exist → nothing to do.
1779
- * 4. New instance (no legacyHome) → create shortHome directly.
1780
- *
1781
- * Fail-safe guarantee: migration failure must never make the instance worse.
1782
- * The caller always receives a valid path it can use as CODEX_HOME.
1783
- */
1784
- /** Exposed for fleet-manager to delete the short home on instance removal. */
1785
- static shortHomeFor(instanceDir) {
1786
- return codexShortHomeFor(instanceDir);
1787
- }
1788
- static resolveShortHome(instanceDir) {
1789
- const shortHome = CodexBackend.shortHomeFor(instanceDir);
1790
- const shortBase = join(getAgendHome(), "cx");
1791
- const canonical = resolve(instanceDir);
1792
- const legacyHome = resolve(instanceDir, "codex-home");
1793
- // Only migrate/create when the instance directory itself exists.
1794
- // Constructing CodexBackend for a non-existent dir (cli-env probes, test
1795
- // backends that never ran) must not litter ~/.agend/cx/ with orphan dirs.
1796
- if (!existsSync(canonical))
1797
- return shortHome;
1798
- const legacyExists = existsSync(legacyHome);
1799
- const legacyIsRealDir = legacyExists && !lstatSync(legacyHome).isSymbolicLink();
1800
- const shortExists = existsSync(shortHome);
1801
- if (!shortExists) {
1802
- mkdirSync(shortBase, { recursive: true, mode: 0o700 });
1803
- if (legacyIsRealDir) {
1804
- // Attempt atomic rename. Requires same device; fails with EXDEV otherwise.
1805
- try {
1806
- renameSync(legacyHome, shortHome);
1807
- // Log the one-time migration for observability.
1808
- try {
1809
- const logPath = join(shortBase, `${shortHome.split("/").at(-1)}.migrated`);
1810
- writeFileSync(logPath, `${new Date().toISOString()} migrated from ${legacyHome}\n`);
1811
- }
1812
- catch { /* log failure is non-fatal */ }
1813
- }
1814
- catch (err) {
1815
- // EXDEV (cross-device) or any other rename failure → fail-safe:
1816
- // fall back to the legacy long path so the instance still works.
1817
- // The socket-length bug persists for this instance but no data is lost.
1818
- const code = err.code ?? "unknown";
1819
- try {
1820
- const warnPath = join(shortBase, `${shortHome.split("/").at(-1)}.migration-failed`);
1821
- writeFileSync(warnPath, `${new Date().toISOString()} rename failed (${code}): ${err.message}\n`
1822
- + `legacy path remains in use: ${legacyHome}\n`);
1823
- }
1824
- catch { /* warn log failure is also non-fatal */ }
1825
- // Return legacy long path so the instance starts (socket may still fail
1826
- // on 0.157.0 for long names, but no data is lost or corrupted).
1827
- return legacyHome;
1828
- }
1829
- }
1830
- else if (!legacyExists) {
1831
- // New instance — no data to migrate.
1832
- mkdirSync(shortHome, { recursive: true, mode: 0o700 });
1833
- }
1834
- // If legacyExists but is already a symlink: shortHome was deleted externally
1835
- // while the symlink still points to it. Re-create shortHome as a fresh dir.
1836
- if (!existsSync(shortHome)) {
1837
- mkdirSync(shortHome, { recursive: true, mode: 0o700 });
1838
- }
1839
- }
1840
- // Backward-compat symlink: instanceDir/codex-home → shortHome.
1841
- // Also serves as self-heal: if a previous run renamed successfully but
1842
- // crashed before creating the symlink, we recreate it here.
1843
- if (!legacyExists || legacyIsRealDir) {
1844
- // legacyIsRealDir means rename just happened (now shortHome exists, legacyHome gone).
1845
- // !legacyExists means new instance or symlink was deleted — create it.
1846
- try {
1847
- symlinkSync(shortHome, legacyHome);
1848
- }
1849
- catch { /* race or already exists */ }
1850
- }
1851
- return shortHome;
1852
- }
1853
- /**
1854
- * The row codex paints for input it has taken into its own queue instead of
1855
- * submitting: `↳ <message>` under "Messages to be submitted after next tool
1856
- * call (press esc to interrupt and send immediately)".
1857
- */
1858
- getQueuedInputMarker() {
1859
- return /↳/;
1860
- }
1861
- /** #1811: what the last launch left out and why — the Log tab and daemon.log, never a channel. */
1862
- quietLaunchNotes = [];
1863
- consumeQuietLaunchNotes() { const notes = this.quietLaunchNotes; this.quietLaunchNotes = []; return notes; }
1864
- buildCommand(config) {
1865
- this.lastKnownModel = config.model?.trim() || null;
1866
- this.credentialProfile = this.readProfile(config);
1867
- // #2886: `--full-auto` is gone from codex (no supported version, 0.155 on, accepts it: the instance never started);
1868
- // spelled out, it was a workspace-write sandbox that asks for approval on request.
1869
- const approvalFlag = config.skipPermissions !== false
1870
- ? "--dangerously-bypass-approvals-and-sandbox"
1871
- : "--sandbox workspace-write --ask-for-approval on-request";
1872
- // #984: never trust `codex resume --last` to pick this instance's session.
1873
- // Codex 0.157 scopes it to the git REPOSITORY, so in a worktree it resumes
1874
- // the newest session of any sibling worktree (a lock screen while that
1875
- // instance lives, a silent hijack otherwise), and a new instance in a
1876
- // shared repo does not start fresh. Resume the exact-cwd thread from the
1877
- // shared state DB instead; `--last` is only the fallback for an unreadable
1878
- // DB when no other Codex instance shares the repository.
1879
- let cmd;
1880
- this.launchWarning = null;
1881
- this.lastLaunchResumes = false;
1882
- this.quotaReplay = null;
1883
- // #2357: read-only (codex is never run here); a launch is not held for it — the operator is told once per launch.
1884
- this.packageWarning = codexPackageWarning(this.binaryPath);
1885
- const retainedGeneral = selectedGeneralHostSession(this.instanceDir, config.instanceName, "codex", config.workingDirectory);
1886
- if (retainedGeneral) {
1887
- const check = checkResumeId(join(this.isolatedCodexHome, "sessions"), config.workingDirectory, retainedGeneral);
1888
- if (!check.ok)
1889
- throw new ResumeBlockedError(t("general_location.start_failed"), t("general_location.start_failed"));
1890
- this.lastLaunchResumes = true;
1891
- this.quotaReplay = snapshotCodexQuotaHistory(check.rollout.path);
1892
- cmd = `${this.binaryPath} resume ${shellQuote(retainedGeneral)} ${approvalFlag}`;
1893
- }
1894
- else if (config.skipResume) {
1895
- cmd = `${this.binaryPath} ${approvalFlag}`;
1896
- }
1897
- else {
1898
- const resumeBy = (id, why, bytes, path) => {
1899
- this.lastLaunchResumes = true;
1900
- this.quotaReplay = snapshotCodexQuotaHistory(path);
1901
- this.writeResumeAttempt(id);
1902
- const large = bytes !== null && bytes > CODEX_RESUME_LARGE_BYTES ? ` ${t("codex.resume_large", String(Math.round(bytes / (1024 * 1024))))}` : "";
1903
- this.launchWarning = why + large;
1904
- return `${this.binaryPath} resume ${shellQuote(id)} ${approvalFlag}`;
1905
- };
1906
- const sessionsDir = join(this.isolatedCodexHome, "sessions");
1907
- // M-2266: the previous launch was told to resume a conversation by id. Codex records a thread as it opens it,
1908
- // so a resume that did not take (codex exited with "No saved session found…") left no row: this launch starts
1909
- // fresh, once, and says so. A database that cannot be read proves nothing, and planning goes on as usual.
1910
- // A new one-shot ask comes first: it is the person's explicit choice for this launch.
1911
- const oneShot = this.takeResumeNext();
1912
- const attempt = this.takeResumeAttempt();
1913
- const failed = !oneShot && attempt !== null && codexThreadRowExists(this.stateDbPath(), attempt) === false;
1914
- if (failed) {
1915
- // Remembered, so the auto-resume never picks this conversation again on a later launch.
1916
- this.rememberFailedResume(attempt);
1917
- cmd = `${this.binaryPath} ${approvalFlag}`;
1918
- this.launchWarning = t("codex.resume_failed", config.workingDirectory, attempt);
1919
- }
1920
- else if (oneShot) {
1921
- // M-2266: validated when it was asked for (restart_instance); checked again now, as the rollout may have moved.
1922
- const check = checkResumeId(sessionsDir, config.workingDirectory, oneShot);
1923
- if (check.ok)
1924
- cmd = resumeBy(check.rollout.id, t("codex.resume_oneshot", config.workingDirectory, check.rollout.id), check.rollout.bytes, check.rollout.path);
1925
- else {
1926
- cmd = this.plannedCommand(config, approvalFlag, sessionsDir, resumeBy);
1927
- this.launchWarning = [t("codex.resume_oneshot_invalid", config.workingDirectory, oneShot, check.reason), this.launchWarning].filter(Boolean).join("\n");
1928
- }
1929
- }
1930
- else {
1931
- cmd = this.plannedCommand(config, approvalFlag, sessionsDir, resumeBy);
1932
- }
1933
- }
1934
- if (config.model) {
1935
- const model = validateModel(config.model);
1936
- warnIfModelMismatch("codex", model);
1937
- cmd += ` -c ${shellQuote(`model="${model}"`)}`;
1938
- }
1939
- if (config.backendOptions?.provider) {
1940
- const provider = validateProvider(String(config.backendOptions.provider));
1941
- cmd += ` -c ${shellQuote(`model_provider="${provider}"`)}`;
1942
- }
1943
- // #1811: the configured effort, when the catalog entry of the model that will run lists it (launch-effort.ts); else
1944
- // a quiet note. That model is proven from the command line or the config files codex reads (#1813 review).
1945
- const runs = resolveCodexLaunchModel({
1946
- isolatedHome: this.isolatedCodexHome, sharedHome: this.sharedCodexHome,
1947
- model: config.model, workingDirectory: config.workingDirectory,
1948
- });
1949
- const effort = codexEffort(config.effort, runs.proven
1950
- ? readCodexModelEffortLevels({ isolatedHome: this.isolatedCodexHome, sharedHome: this.sharedCodexHome, model: runs.model })
1951
- : { model: runs.model, levels: null, unproven: true });
1952
- if (effort.arg)
1953
- cmd += effort.arg;
1954
- // M-2446: the per-agent speed (codex-speed.ts) — Fast only where the running model's catalog entry offers it.
1955
- const speed = codexSpeed(config.backendOptions?.speed, runs.proven
1956
- ? readCodexModelServiceTiers({ isolatedHome: this.isolatedCodexHome, sharedHome: this.sharedCodexHome, model: runs.model })
1957
- : { model: runs.model, tiers: null, unproven: true });
1958
- if (speed.arg)
1959
- cmd += speed.arg;
1960
- this.quietLaunchNotes = [effort.note, speed.note].filter((n) => !!n);
1961
- // AgEnD instances are unattended processes: an interactive self-update
1962
- // picker blocks delivery and must never be enabled by a copied global or
1963
- // managed config layer. Keep this last so the CLI override is authoritative.
1964
- // Both observed Codex 0.155.0 and 0.156.1 support this launch flag. Pin
1965
- // the initial layout so a user/global fullscreen preference cannot make a
1966
- // header-only pane look ready. A later unknown layout still fails closed.
1967
- cmd += " -c check_for_update_on_startup=false --no-alt-screen";
1968
- // codex 0.159's opt-in `instant_interrupt` makes new input steer the
1969
- // running response instead of queueing behind it, which AgEnD's delivery
1970
- // and queued-input (↳) handling assume. Keep it off whatever a user or
1971
- // global config says. Codex before 0.159 ignores the key with a startup
1972
- // warning ("`features.instant_interrupt` is ignored"); nothing else.
1973
- cmd += " -c features.instant_interrupt=false";
1974
- // CODEX_HOME is the only Codex-supported way to isolate the complete base
1975
- // config. A profile only layers over the shared config and would therefore
1976
- // still load every globally registered AgEnD MCP server.
1977
- return `CODEX_HOME=${shellQuote(this.isolatedCodexHome)} ${cmd}`;
1978
- }
1979
- writeConfig(config) {
1980
- this.authorizedTrust = null;
1981
- // Set before the home is prepared: which login this instance gets is a
1982
- // property of the home, and the home is built here.
1983
- this.credentialProfile = this.readProfile(config);
1984
- this.prepareIsolatedHome();
1985
- this.cleanSharedConfig();
1986
- // Copy all user settings but no AgEnD MCP entries into a private base
1987
- // config, then append only this instance's server(s). Each instance writes
1988
- // a distinct file, eliminating the old concurrent global-config race.
1989
- let content = "";
1990
- try {
1991
- content = stripAgendMcpTables(readFileSync(join(this.sharedCodexHome, "config.toml"), "utf-8"));
1992
- }
1993
- catch { /* a first-time Codex user may have no global config */ }
1994
- if (content && !content.endsWith("\n"))
1995
- content += "\n";
1996
- for (const [name, entry] of Object.entries(config.mcpServers)) {
1997
- content += `\n${renderMcpServer(name, entry, config.instanceName)}`;
1998
- }
1999
- atomicWritePrivate(join(this.isolatedCodexHome, "config.toml"), content);
2000
- this.enableContextStatusLine();
2001
- this.disableStartupUpdateCheck();
2002
- this.hideRateLimitModelNudge();
2003
- // Write fleet instructions into AGENTS.md (additive via marker block)
2004
- if (config.instructions) {
2005
- try {
2006
- const agentsMd = join(config.workingDirectory, "AGENTS.md");
2007
- appendWithMarker(agentsMd, config.instanceName, config.instructions);
2008
- // Warn if file exceeds Codex's project_doc_max_bytes limit
2009
- try {
2010
- const size = statSync(agentsMd).size;
2011
- if (size > CODEX_PROJECT_DOC_MAX_BYTES) {
2012
- console.warn(`[agend] AGENTS.md is ${size} bytes, exceeds Codex limit of ${CODEX_PROJECT_DOC_MAX_BYTES} — instructions may be truncated`);
2013
- }
2014
- }
2015
- catch { /* stat failed — skip size check */ }
2016
- }
2017
- catch { /* best effort */ }
2018
- }
2019
- }
2020
- /**
2021
- * Stop Codex opening its "Update available!" picker when an instance starts.
2022
- *
2023
- * That dialog owns the input loop until someone answers it, so after a fleet
2024
- * restart every Codex instance sat waiting on a keypress nobody was going to
2025
- * send. AgEnD then reported it as a stuck pane, which is true but unhelpful.
2026
- *
2027
- * `check_for_update_on_startup = false` is Codex's own config key (verified in
2028
- * the binary), so this prevents the prompt rather than racing to dismiss it.
2029
- * There is no CLI flag for it — `codex update` is a subcommand, not a switch.
2030
- *
2031
- * Placement matters: this is a TOP-LEVEL key, so it has to go above the first
2032
- * `[section]` header or TOML would read it as belonging to that section. And a
2033
- * AgEnD's isolated home is unattended, so an inherited `true` is replaced
2034
- * rather than preserved. TOML rejects duplicate keys, so replace in place.
2035
- */
2036
- disableStartupUpdateCheck() {
2037
- const configPath = join(this.isolatedCodexHome, "config.toml");
2038
- let content = "";
2039
- try {
2040
- content = readFileSync(configPath, "utf-8");
2041
- }
2042
- catch {
2043
- return;
2044
- }
2045
- const existing = /^\s*check_for_update_on_startup\s*=\s*(?:true|false)\s*(?:#.*)?$/m;
2046
- if (existing.test(content)) {
2047
- const updated = content.replace(existing, "check_for_update_on_startup = false");
2048
- if (updated !== content) {
2049
- try {
2050
- atomicWritePrivate(configPath, updated);
2051
- }
2052
- catch { /* best effort */ }
2053
- }
2054
- return;
2055
- }
2056
- const LINE = "check_for_update_on_startup = false\n";
2057
- const firstSection = content.search(/^\s*\[/m);
2058
- const updated = firstSection === -1
2059
- ? (content.length && !content.endsWith("\n") ? `${content}\n${LINE}` : `${content}${LINE}`)
2060
- : `${content.slice(0, firstSection)}${LINE}${content.slice(firstSection)}`;
2061
- try {
2062
- atomicWritePrivate(configPath, updated);
2063
- }
2064
- catch { /* best effort */ }
2065
- }
2066
- /**
2067
- * Suppress Codex's rate-limit model-switch reminder for unattended instances.
2068
- * Codex documents this as `notice.hide_rate_limit_model_nudge` (a boolean in
2069
- * the `[notice]` table). This changes only the per-instance config; the
2070
- * runtime holdOnly dialog remains as defense-in-depth for older or changed
2071
- * CLI behavior. No model option is selected automatically.
2072
- */
2073
- hideRateLimitModelNudge() {
2074
- const configPath = join(this.isolatedCodexHome, "config.toml");
2075
- let content = "";
2076
- try {
2077
- content = readFileSync(configPath, "utf-8");
2078
- }
2079
- catch {
2080
- return;
2081
- }
2082
- const updated = setTomlTableBoolean(content, "notice", "hide_rate_limit_model_nudge");
2083
- if (updated !== content) {
2084
- try {
2085
- atomicWritePrivate(configPath, updated);
2086
- }
2087
- catch { /* best effort */ }
2088
- }
1462
+ /**
1463
+ * Suppress Codex's rate-limit model-switch reminder for unattended instances.
1464
+ * Codex documents this as `notice.hide_rate_limit_model_nudge` (a boolean in
1465
+ * the `[notice]` table). This changes only the per-instance config; the
1466
+ * runtime holdOnly dialog remains as defense-in-depth for older or changed
1467
+ * CLI behavior. No model option is selected automatically.
1468
+ */
1469
+ hideRateLimitModelNudge() {
1470
+ const configPath = join(this.isolatedCodexHome, "config.toml");
1471
+ let content = "";
1472
+ try {
1473
+ content = readFileSync(configPath, "utf-8");
1474
+ }
1475
+ catch {
1476
+ return;
1477
+ }
1478
+ const updated = setTomlTableBoolean(content, "notice", "hide_rate_limit_model_nudge");
1479
+ if (updated !== content) {
1480
+ try {
1481
+ atomicWritePrivate(configPath, updated);
1482
+ }
1483
+ catch { /* best effort */ }
1484
+ }
2089
1485
  }
2090
1486
  /**
2091
1487
  * Ensure Codex's TUI status line shows context usage: the Context item is
@@ -2243,253 +1639,615 @@ export class CodexBackend extends CodexPaneDetectors {
2243
1639
  unlinkSync(target);
2244
1640
  }
2245
1641
  }
2246
- catch { /* absent, or another startup already detached the link */ }
2247
- }
2248
- // The session dirs must exist in the SHARED home before the symlink pass:
2249
- // on a fresh install they don't yet, so no link was created, and the first
2250
- // archive made Codex create a REAL dir in the instance-private home — a
2251
- // permanent fork the terminal CLI could never see (#506). mkdir -p is
2252
- // EEXIST-safe under concurrent instance startup.
2253
- for (const dir of CodexBackend.SHARED_SESSION_DIRS) {
2254
- try {
2255
- mkdirSync(join(this.sharedCodexHome, dir), { recursive: true, mode: 0o700 });
1642
+ catch { /* absent, or another startup already detached the link */ }
1643
+ }
1644
+ // The session dirs must exist in the SHARED home before the symlink pass:
1645
+ // on a fresh install they don't yet, so no link was created, and the first
1646
+ // archive made Codex create a REAL dir in the instance-private home — a
1647
+ // permanent fork the terminal CLI could never see (#506). mkdir -p is
1648
+ // EEXIST-safe under concurrent instance startup.
1649
+ for (const dir of CodexBackend.SHARED_SESSION_DIRS) {
1650
+ try {
1651
+ mkdirSync(join(this.sharedCodexHome, dir), { recursive: true, mode: 0o700 });
1652
+ }
1653
+ catch { /* best effort */ }
1654
+ }
1655
+ // Heal homes that already forked: merge the private real dir back into the
1656
+ // shared one (never overwriting), then replace it with the symlink.
1657
+ for (const dir of CodexBackend.SHARED_SESSION_DIRS) {
1658
+ this.migrateDivergedSessionDir(dir);
1659
+ }
1660
+ // Older versions linked SQLite's WAL/SHM files independently of their
1661
+ // base database. If the base file had already been created privately,
1662
+ // that split one SQLite database across two homes and caused CANTOPEN (or
1663
+ // worse, cross-database journal recovery). Remove only links AgEnD made to
1664
+ // the matching shared-home path; real private sidecars remain untouched.
1665
+ const healedSidecars = [];
1666
+ for (const name of readdirSync(this.isolatedCodexHome)) {
1667
+ if (!SQLITE_SIDECAR_RE.test(name))
1668
+ continue;
1669
+ const target = join(this.isolatedCodexHome, name);
1670
+ try {
1671
+ if (!lstatSync(target).isSymbolicLink())
1672
+ continue;
1673
+ const linkTarget = resolve(dirname(target), readlinkSync(target));
1674
+ if (linkTarget !== join(this.sharedCodexHome, name))
1675
+ continue;
1676
+ unlinkSync(target);
1677
+ healedSidecars.push(name);
1678
+ }
1679
+ catch {
1680
+ // A concurrent process may remove an ephemeral sidecar/link first.
1681
+ }
1682
+ }
1683
+ if (healedSidecars.length > 0) {
1684
+ console.warn(`[agend] removed unsafe Codex SQLite sidecar links: ${healedSidecars.join(", ")}`);
1685
+ }
1686
+ // One file, possibly from somewhere else. Everything below is unchanged:
1687
+ // sessions, the thread/state/memory databases and every cache still come
1688
+ // from the shared home, which is why switching subscription does not throw
1689
+ // the conversation away.
1690
+ this.linkAuthFile();
1691
+ for (const name of readdirSync(this.sharedCodexHome)) {
1692
+ if (CodexBackend.PRIVATE_RUNTIME_DIRS.has(name))
1693
+ continue;
1694
+ if (name === "config.toml" || name === AGEND_MCP_CLEANUP_LOCK || name.startsWith(".config.toml."))
1695
+ continue;
1696
+ if (name === CODEX_AUTH_FILE)
1697
+ continue; // handled above, possibly from a profile
1698
+ // SQLite resolves a symlinked base DB to the shared path and creates its
1699
+ // own adjacent sidecars there. Linking sidecars separately is redundant
1700
+ // for shared bases and corrupts the file set for private bases.
1701
+ if (SQLITE_SIDECAR_RE.test(name))
1702
+ continue;
1703
+ const source = join(this.sharedCodexHome, name);
1704
+ const target = join(this.isolatedCodexHome, name);
1705
+ if (existsSync(target))
1706
+ continue;
1707
+ try {
1708
+ const type = lstatSync(source).isDirectory() ? "dir" : "file";
1709
+ symlinkSync(source, target, type);
1710
+ }
1711
+ catch {
1712
+ // State/cache links are compatibility aids; config isolation must not
1713
+ // fail merely because a concurrently-created cache entry disappeared.
1714
+ }
1715
+ }
1716
+ }
1717
+ /**
1718
+ * Point this home's `auth.json` at whichever login it should use.
1719
+ *
1720
+ * Codex refreshes the token by writing through the link rather than replacing
1721
+ * it — observed across every instance home on a machine that has been running
1722
+ * this arrangement for months, with the shared file's mtime moving. That is
1723
+ * behaviour, though, not a contract: a release that starts renaming over the
1724
+ * file would leave a real file here and quietly fork the login, with refreshes
1725
+ * landing somewhere the profile never sees. So a real file where a link
1726
+ * belongs is treated as exactly that — reported, and put back.
1727
+ */
1728
+ linkAuthFile() {
1729
+ const source = join(this.authSourceDir(), CODEX_AUTH_FILE);
1730
+ const target = join(this.isolatedCodexHome, CODEX_AUTH_FILE);
1731
+ let existing = null;
1732
+ try {
1733
+ existing = lstatSync(target);
1734
+ }
1735
+ catch { /* absent */ }
1736
+ if (existing?.isSymbolicLink()) {
1737
+ let current = "";
1738
+ try {
1739
+ current = resolve(dirname(target), readlinkSync(target));
1740
+ }
1741
+ catch { /* unreadable */ }
1742
+ if (current === source)
1743
+ return;
1744
+ try {
1745
+ unlinkSync(target);
1746
+ }
1747
+ catch { /* raced */ }
1748
+ }
1749
+ else if (existing) {
1750
+ // Not a link: codex replaced it, or an older AgEnD copied it. Either way
1751
+ // the refreshes it has been collecting are stranded in this instance's
1752
+ // home, so say so rather than deleting them silently.
1753
+ const stranded = `${target}.replaced-${Date.now()}`;
1754
+ try {
1755
+ renameSync(target, stranded);
1756
+ console.warn(`[agend] codex replaced ${CODEX_AUTH_FILE} in ${this.isolatedCodexHome} with a real file — `
1757
+ + `its login was no longer shared. Kept a copy at ${stranded} and re-linked to ${source}.`);
1758
+ }
1759
+ catch {
1760
+ return; // cannot move it; leave the instance working on what it has
1761
+ }
1762
+ }
1763
+ if (!existsSync(source))
1764
+ return; // not logged in yet; codex will create it
1765
+ try {
1766
+ symlinkSync(source, target, "file");
1767
+ }
1768
+ catch {
1769
+ // EEXIST from a concurrent start is the ordinary case and means the link
1770
+ // is already there.
1771
+ }
1772
+ }
1773
+ /**
1774
+ * One-time heal for an instance whose isolated home grew a REAL session dir
1775
+ * (#506): merge it into the shared home without overwriting anything, and
1776
+ * only after both the copy and the removal succeed put the symlink in its
1777
+ * place. Any failure keeps the private dir — sessions are never the thing
1778
+ * sacrificed for tidiness — and logs what happened.
1779
+ */
1780
+ migrateDivergedSessionDir(name) {
1781
+ const target = join(this.isolatedCodexHome, name);
1782
+ let st;
1783
+ try {
1784
+ st = lstatSync(target);
1785
+ }
1786
+ catch {
1787
+ return; // absent — the symlink pass below will create the link
1788
+ }
1789
+ if (st.isSymbolicLink())
1790
+ return; // already correct
1791
+ if (!st.isDirectory()) {
1792
+ console.warn(`[agend] codex-home/${name} is neither a symlink nor a directory — leaving it untouched`);
1793
+ return;
1794
+ }
1795
+ const shared = join(this.sharedCodexHome, name);
1796
+ try {
1797
+ mkdirSync(shared, { recursive: true, mode: 0o700 });
1798
+ // cp -rn semantics: collisions keep the shared copy. Session rollout
1799
+ // filenames are timestamp+uuid, so a genuine collision means the same
1800
+ // file — skipping is lossless either way.
1801
+ cpSync(target, shared, { recursive: true, force: false, errorOnExist: false });
1802
+ rmSync(target, { recursive: true });
1803
+ symlinkSync(shared, target, "dir");
1804
+ console.warn(`[agend] migrated diverged codex ${name} into shared home: ${shared}`);
1805
+ }
1806
+ catch (err) {
1807
+ // Keep whatever is left of the private dir. The copy never overwrites, so
1808
+ // a retry on next start is safe; worst case is duplicated files in the
1809
+ // shared home, never lost ones.
1810
+ console.warn(`[agend] codex ${name} migration failed — keeping the instance-private dir (${err.message})`);
1811
+ }
1812
+ }
1813
+ /**
1814
+ * One-time migration for installations polluted by the old global `codex
1815
+ * mcp add` path. The lock protects separate fleet processes/upgrades, and
1816
+ * atomic rename prevents readers from observing a truncated config.
1817
+ */
1818
+ cleanSharedConfig() {
1819
+ if (this.sharedCodexHome === this.isolatedCodexHome)
1820
+ return;
1821
+ mkdirSync(this.sharedCodexHome, { recursive: true, mode: 0o700 });
1822
+ const lockPath = join(this.sharedCodexHome, AGEND_MCP_CLEANUP_LOCK);
1823
+ let lockFd;
1824
+ const acquire = () => {
1825
+ try {
1826
+ lockFd = openSync(lockPath, "wx", 0o600);
1827
+ return true;
1828
+ }
1829
+ catch {
1830
+ return false;
1831
+ }
1832
+ };
1833
+ if (!acquire()) {
1834
+ try {
1835
+ if (Date.now() - statSync(lockPath).mtimeMs > AGEND_MCP_CLEANUP_LOCK_STALE_MS) {
1836
+ unlinkSync(lockPath);
1837
+ if (!acquire())
1838
+ return;
1839
+ }
1840
+ else {
1841
+ return;
1842
+ }
1843
+ }
1844
+ catch {
1845
+ return;
2256
1846
  }
2257
- catch { /* best effort */ }
2258
- }
2259
- // Heal homes that already forked: merge the private real dir back into the
2260
- // shared one (never overwriting), then replace it with the symlink.
2261
- for (const dir of CodexBackend.SHARED_SESSION_DIRS) {
2262
- this.migrateDivergedSessionDir(dir);
2263
1847
  }
2264
- // Older versions linked SQLite's WAL/SHM files independently of their
2265
- // base database. If the base file had already been created privately,
2266
- // that split one SQLite database across two homes and caused CANTOPEN (or
2267
- // worse, cross-database journal recovery). Remove only links AgEnD made to
2268
- // the matching shared-home path; real private sidecars remain untouched.
2269
- const healedSidecars = [];
2270
- for (const name of readdirSync(this.isolatedCodexHome)) {
2271
- if (!SQLITE_SIDECAR_RE.test(name))
2272
- continue;
2273
- const target = join(this.isolatedCodexHome, name);
1848
+ try {
1849
+ const configPath = join(this.sharedCodexHome, "config.toml");
1850
+ let content;
2274
1851
  try {
2275
- if (!lstatSync(target).isSymbolicLink())
2276
- continue;
2277
- const linkTarget = resolve(dirname(target), readlinkSync(target));
2278
- if (linkTarget !== join(this.sharedCodexHome, name))
2279
- continue;
2280
- unlinkSync(target);
2281
- healedSidecars.push(name);
1852
+ content = readFileSync(configPath, "utf-8");
2282
1853
  }
2283
1854
  catch {
2284
- // A concurrent process may remove an ephemeral sidecar/link first.
1855
+ return;
2285
1856
  }
1857
+ const cleaned = stripAgendMcpTables(content);
1858
+ if (cleaned !== content)
1859
+ atomicWritePrivate(configPath, cleaned);
2286
1860
  }
2287
- if (healedSidecars.length > 0) {
2288
- console.warn(`[agend] removed unsafe Codex SQLite sidecar links: ${healedSidecars.join(", ")}`);
2289
- }
2290
- // One file, possibly from somewhere else. Everything below is unchanged:
2291
- // sessions, the thread/state/memory databases and every cache still come
2292
- // from the shared home, which is why switching subscription does not throw
2293
- // the conversation away.
2294
- this.linkAuthFile();
2295
- for (const name of readdirSync(this.sharedCodexHome)) {
2296
- if (CodexBackend.PRIVATE_RUNTIME_DIRS.has(name))
2297
- continue;
2298
- if (name === "config.toml" || name === AGEND_MCP_CLEANUP_LOCK || name.startsWith(".config.toml."))
2299
- continue;
2300
- if (name === CODEX_AUTH_FILE)
2301
- continue; // handled above, possibly from a profile
2302
- // SQLite resolves a symlinked base DB to the shared path and creates its
2303
- // own adjacent sidecars there. Linking sidecars separately is redundant
2304
- // for shared bases and corrupts the file set for private bases.
2305
- if (SQLITE_SIDECAR_RE.test(name))
2306
- continue;
2307
- const source = join(this.sharedCodexHome, name);
2308
- const target = join(this.isolatedCodexHome, name);
2309
- if (existsSync(target))
2310
- continue;
2311
- try {
2312
- const type = lstatSync(source).isDirectory() ? "dir" : "file";
2313
- symlinkSync(source, target, type);
1861
+ finally {
1862
+ if (lockFd !== undefined) {
1863
+ try {
1864
+ closeSync(lockFd);
1865
+ }
1866
+ catch { }
2314
1867
  }
2315
- catch {
2316
- // State/cache links are compatibility aids; config isolation must not
2317
- // fail merely because a concurrently-created cache entry disappeared.
1868
+ try {
1869
+ unlinkSync(lockPath);
2318
1870
  }
1871
+ catch { }
1872
+ }
1873
+ }
1874
+ getReadyPattern() {
1875
+ // Header and context text persist in inline scrollback and even while the
1876
+ // CLI is loading or a modal owns stdin. Require the live prompt followed
1877
+ // by the Context footer at the *end* of the capture. A valid custom
1878
+ // status_line without Context gets a separate configured-item pattern;
1879
+ // unknown footer values still fail closed. getBusyPattern vetoes a working
1880
+ // turn whose empty composer remains visible.
1881
+ // U+22C6 is Codex's observed cosmetic starfield; it can be drawn in the
1882
+ // prompt, between prompt/footer, and below the footer. A drafted composer
1883
+ // is also idle once this same bottom footer proves it owns the screen.
1884
+ const contextReady = withCodexPassiveHintTail(/(?:^|\n)[>›][ \t⋆]+(?!\d+\.)\S[^\r\n]*\r?\n(?:[ \t⋆]*\r?\n){0,3}[ \t⋆]+(?:[0-9a-f-]{36}[ \t]+·[ \t]+)?Context[ \t]+(?:\d+%[ \t]+(?:left|used)|\d+…|…)[^\r\n]*(?:\r?\n[ \t⋆]*)*$/i);
1885
+ const footerPattern = this.configuredStatusLineFooterPattern();
1886
+ if (!footerPattern)
1887
+ return contextReady;
1888
+ // The no-Context alternative requires the exact empty composer followed by
1889
+ // a recognized configured Codex status_line value. If run-state is
1890
+ // configured, that value must specifically be Ready. This is positive
1891
+ // structural proof, never a deny-list of known dialogs.
1892
+ const footerSource = footerPattern.source.slice(1, -1);
1893
+ const noContextReady = withCodexPassiveHintTail(new RegExp(String.raw `(?:^|\n)[>›][ \t⋆]*Ask Codex to do anything[ \t⋆]*\r?\n(?:[ \t⋆]*\r?\n){0,3}${footerSource}(?:\r?\n[ \t⋆]*)*$`, "iu"));
1894
+ // #978: a configured footer that carries a Context item in any position
1895
+ // is the Context footer: same drafted-composer rule as Context-first. The
1896
+ // whole row must match the configured item grammar, so an arbitrary
1897
+ // indented line that merely contains `Context N% left` cannot pass.
1898
+ const configuredContextReady = this.configuredHasContextItem()
1899
+ ? withCodexPassiveHintTail(new RegExp(String.raw `(?:^|\n)[>›][ \t⋆]+(?!\d+\.)\S[^\r\n]*\r?\n(?:[ \t⋆]*\r?\n){0,3}(?=[^\r\n]*${CODEX_CONTEXT_ITEM})${footerSource}(?:\r?\n[ \t⋆]*)*$`, "iu"))
1900
+ : null;
1901
+ return new RegExp([contextReady.source, noContextReady.source, configuredContextReady?.source]
1902
+ .filter((source) => !!source)
1903
+ .map(source => `(?:${source})`)
1904
+ .join("|"), "iu");
1905
+ }
1906
+ readConfiguredStatusLineItems() {
1907
+ if (this.configuredStatusLineItems !== undefined)
1908
+ return this.configuredStatusLineItems;
1909
+ try {
1910
+ const config = tomlTable(parseToml(readFileSync(join(this.isolatedCodexHome, "config.toml"), "utf-8")));
1911
+ const tui = tomlTable(config?.tui);
1912
+ const items = tui?.status_line;
1913
+ this.configuredStatusLineItems = Array.isArray(items) && items.every(item => typeof item === "string")
1914
+ ? items
1915
+ : null;
2319
1916
  }
1917
+ catch {
1918
+ this.configuredStatusLineItems = null;
1919
+ }
1920
+ return this.configuredStatusLineItems;
1921
+ }
1922
+ configuredStatusLineFooterPattern() {
1923
+ if (this.configuredStatusLinePattern !== undefined)
1924
+ return this.configuredStatusLinePattern;
1925
+ const items = this.readConfiguredStatusLineItems();
1926
+ if (!items?.length)
1927
+ return this.configuredStatusLinePattern = null;
1928
+ const configuredPatterns = items
1929
+ .map(item => CODEX_STATUS_LINE_VALUE_PATTERNS[item])
1930
+ .filter((pattern) => pattern !== undefined);
1931
+ if (!configuredPatterns.length)
1932
+ return this.configuredStatusLinePattern = null;
1933
+ const segment = `(?:${configuredPatterns.join("|")})`;
1934
+ const separator = String.raw `[ \t]+·[ \t]+`;
1935
+ const hasRunState = items.some(item => CODEX_RUN_STATE_STATUS_ITEMS.has(item));
1936
+ // Ready must occupy its own status-line segment. Other configured items
1937
+ // may precede/follow it, as Codex joins visible values with ` · `.
1938
+ const line = hasRunState
1939
+ ? `(?:${segment}${separator})*Ready(?:${separator}${segment})*`
1940
+ : `${segment}(?:${separator}${segment})*`;
1941
+ // The configured items, then (optionally) the CLI's own native fields — the same rule as the Context-first footer, so a
1942
+ // `model · Context 46% left Goal achieved (1h 6m)` row is the footer too, and the readiness regex built from this
1943
+ // source (footerSource) says the same.
1944
+ this.configuredStatusLinePattern = new RegExp(`^[ \t⋆]*${line}(?:${CODEX_NATIVE_FOOTER_FIELDS})?[ \t⋆]*$`, "iu");
1945
+ return this.configuredStatusLinePattern;
1946
+ }
1947
+ isConfiguredStatusLineFooter(row) {
1948
+ const pattern = this.configuredStatusLineFooterPattern();
1949
+ if (!pattern)
1950
+ return false;
1951
+ pattern.lastIndex = 0;
1952
+ return pattern.test(row);
1953
+ }
1954
+ configuredHasContextItem() {
1955
+ return (this.readConfiguredStatusLineItems() ?? []).some(item => CODEX_CONTEXT_STATUS_ITEMS.has(item));
1956
+ }
1957
+ /**
1958
+ * #978: a Context item in ANY position, accepted only as part of a footer
1959
+ * whose every segment matches the configured status_line items. Codex keeps
1960
+ * the user's order (`gpt-5.6-sol medium · Context 46% left · ~/x`); an
1961
+ * arbitrary indented transcript line that happens to contain the item does
1962
+ * not match that grammar. A stale config fails closed, and the daemon's
1963
+ * stable-composer escape hatch then keeps the instance from latching.
1964
+ */
1965
+ // The configured grammar already makes Context a whole segment, so the item
1966
+ // test only has to establish that one is present.
1967
+ isConfiguredContextFooter(row) {
1968
+ return this.configuredHasContextItem()
1969
+ && new RegExp(CODEX_CONTEXT_ITEM, "iu").test(row)
1970
+ && this.isConfiguredStatusLineFooter(row);
1971
+ }
1972
+ /** Any footer that proves the Context meter owns the bottom row. */
1973
+ isAnyContextFooter(row) {
1974
+ return isCodexContextFooter(row) || this.isConfiguredContextFooter(row);
1975
+ }
1976
+ /**
1977
+ * #978 escape hatch: the live, EMPTY Codex composer within the last few
1978
+ * non-blank rows, no busy marker, and none of the known selection screens.
1979
+ * Deliberately says nothing about the footer, which is what is unknown.
1980
+ */
1981
+ isStableUnknownLayoutIdlePane(pane) {
1982
+ if (this.getBusyPattern().test(pane))
1983
+ return false;
1984
+ // The composer is live-looking and the footer is unknown — exactly this
1985
+ // proof's blind spot — but there is no server behind it (#1099).
1986
+ if (codexAppServerDisconnected(pane))
1987
+ return false;
1988
+ // (No second, wider "esc to interrupt" match here: getBusyPattern() above already reads a live status row of ANY
1989
+ // title — #964 — and anchors it to the whole row at column zero, so a reply that quotes the phrase, indented or
1990
+ // in prose, cannot pin the escape hatch shut (#991).)
1991
+ // A retained queued message (`↳ …` under "Messages to be submitted after
1992
+ // next tool call") means work is pending, whatever the composer shows.
1993
+ if (/↳|Messages to be submitted/i.test(pane))
1994
+ return false;
1995
+ if (codexUsageLimitMenuVisible(pane) || codexRateSwitchVisible(pane)
1996
+ || codexUnknownSelectionVisible(pane) || codexUpdatePickerVisible(pane))
1997
+ return false;
1998
+ const rows = pane.replace(/\r/g, "").split("\n").filter(row => row.trim() !== "");
1999
+ return rows.slice(-6).some(row => /^[>›][ \t⋆]*Ask Codex to do anything[ \t⋆]*$/.test(row));
2000
+ }
2001
+ isCodexIdleFooter(row) {
2002
+ return isCodexContextFooter(row) || this.isConfiguredStatusLineFooter(row);
2003
+ }
2004
+ /** A proxy reply filters chrome per line; whole-pane readiness is separate. */
2005
+ isProxyReplyChromeLine(line) {
2006
+ return /^\s*[›>]\s+Ask Codex to do anything\s*$/.test(line)
2007
+ || this.isAnyContextFooter(line);
2008
+ }
2009
+ getErrorPatterns() {
2010
+ return [
2011
+ // Specific quota codes must precede the generic HTTP 429 classifier:
2012
+ // OpenAI returns insufficient_quota with status 429, but switching models
2013
+ // cannot repair an exhausted account and would only start a failover loop.
2014
+ {
2015
+ pattern: new RegExp(String.raw `^\s*(?:${CODEX_STATUS_GLYPH}|Error:|API Error:)\s*[\s\S]{0,240}?\b(?:insufficient_quota|billing_hard_limit_reached|exceeded\s+your\s+current\s+quota)\b|^(?:${CODEX_STATUS_GLYPH}) Quota exceeded\. Check your plan and billing details\.\r?$`, "im"),
2016
+ type: "quota",
2017
+ action: "pause",
2018
+ message: "OpenAI quota exceeded",
2019
+ notice: () => quotaPausedLine("OpenAI", "api_quota"),
2020
+ },
2021
+ {
2022
+ // Codex already exhausted HTTP retries and returned to its composer.
2023
+ // The existing conditional continuation preserves context without
2024
+ // replaying earlier tool effects. Quota remains a preceding pause.
2025
+ pattern: new RegExp(`^${CODEX_EXHAUSTED_429_ROW}$`, "im"),
2026
+ type: "rate_limit",
2027
+ action: "nudge_continue",
2028
+ message: t("inst.codex_rate_retry_stopped"),
2029
+ skipCooldown: true,
2030
+ skipRecoveryWait: true,
2031
+ },
2032
+ {
2033
+ // Pane history contains the agent's own prose, source code and search
2034
+ // results. Bare `rate limit` used to fail over an otherwise healthy
2035
+ // Codex merely for discussing this regex. Match machine-readable API
2036
+ // forms or an error-decorated terminal line instead.
2037
+ pattern: new RegExp(String.raw `^\s*unexpected\s+status\s+429\b|^(?![\s\S]{0,240}?${CODEX_EXHAUSTED_429_ROW})[ \t]*(?:${CODEX_STATUS_GLYPH}|Error:|API Error:)\s*[\s\S]{0,240}?(?:["'](?:status|code)["']\s*:\s*429\b|\b(?:rate_limit_exceeded|too_many_requests)\b|\brate limit(?:ed| exceeded| reached)?\b|\btoo many requests\b)`, "im"),
2038
+ type: "rate_limit",
2039
+ action: "failover",
2040
+ message: "OpenAI rate limit reached",
2041
+ },
2042
+ {
2043
+ // Same false-positive boundary as rate limits: `authentication` is a
2044
+ // normal English/code word. Require a structured 401/code or a
2045
+ // decorated CLI error line before pausing every instance on the shared
2046
+ // credential.
2047
+ pattern: new RegExp(String.raw `^\s*unexpected\s+status\s+401\b|^\s*(?:${CODEX_STATUS_GLYPH}|Error:|API Error:)\s*[\s\S]{0,240}?(?:["'](?:status|code)["']\s*:\s*401\b|\b(?:invalid_api_key|authentication_error)\b|\b401\s+Unauthorized\b|\bauthentication (?:failed|error)\b|\binvalid api key\b)`, "im"),
2048
+ type: "auth_error",
2049
+ action: "pause",
2050
+ message: "OpenAI authentication error",
2051
+ },
2052
+ // Codex writes the apostrophe as U+2019 ("You’ve hit your usage limit.") since
2053
+ // 0.156 and as ASCII before (0.153.4): both must match, or no usage limit
2054
+ // ever pauses the instance (#1098).
2055
+ { pattern: /you['’]ve hit your usage limit/i, type: "quota", action: "pause", message: "Codex usage limit reached — upgrade plan required",
2056
+ notice: () => quotaPausedLine("Codex", "usage") },
2057
+ {
2058
+ // The terminal stage of a lost app-server (#1099): Codex has given up
2059
+ // reconnecting and says so, with a counter that never stops. Nothing
2060
+ // recovers from here without a relaunch, and every message sent in the
2061
+ // meantime waits for an idle that cannot come. Only the LIVE row counts:
2062
+ // directly above the LAST composer with no later transcript item or composer
2063
+ // (the same test as codexAppServerDisconnected, as a lookahead), so text that
2064
+ // merely quotes it does not raise this.
2065
+ pattern: new RegExp(String.raw `^[ \t]*•[ \t]+Reconnect failed[ \t]+—[ \t]+check the endpoint, then relaunch[ \t]+\(\d+[hms](?:[ \t]+\d+[hms])*\)[ \t]*$`
2066
+ + String.raw `(?=\n(?:[ \t]*\n)*[›>][^\n]*(?:\n(?![•■⚠›>])[^\n]*)*(?![\s\S]))`, "mu"),
2067
+ type: "network",
2068
+ action: "notify",
2069
+ message: "Codex lost its app-server connection and could not reconnect — restart the instance (messages cannot be delivered until then)",
2070
+ },
2071
+ {
2072
+ // Codex reports an unknown model either as a TUI metadata fallback or
2073
+ // as a ChatGPT-account API rejection. Use whitespace-aware phrases so
2074
+ // capture-pane hard wraps do not hide either form.
2075
+ // Codex quotes the slug with BACKTICKS ("Model metadata for `x` not found.",
2076
+ // verified live on 0.160.0 and in the 0.153.4–0.160.0 binaries). The old
2077
+ // pattern only took ASCII quotes, so it could never match a real line.
2078
+ pattern: /model\s+metadata\s+for\s+['"`‘’“”][^'"`‘’“”]+['"`‘’“”]\s+not\s+found\.\s+defaulting\s+to\s+fallback\s+metadata|model\s+is\s+not\s+supported\s+when\s+using\s+codex\s+with\s+a\s+chatgpt\s+account/i,
2079
+ type: "model_error",
2080
+ action: "notify",
2081
+ message: "Codex model unavailable — use /model to switch",
2082
+ },
2083
+ {
2084
+ // A capacity rejection is a completed failed turn: Codex returns to its
2085
+ // prompt without an answer, with its context intact. Keep this anchored to
2086
+ // the exact decorated TUI line so ordinary prose about model capacity
2087
+ // cannot trigger anything on an otherwise healthy instance. The status
2088
+ // glyph is decoration that changes between Codex versions and error classes
2089
+ // (`■` U+25A0 on 0.159.2 and 0.160.0, `⚠` U+26A0 before), so any one
2090
+ // "Other Symbol" glyph (optionally with its emoji selector) opens the line —
2091
+ // never a bullet, dash, quote marker or number, which prose and the model's
2092
+ // own transcript items use (`•`, `-`, `>`). The sentence must fill the line,
2093
+ // from column 0. Box drawing and block elements (U+2500–U+259F) are
2094
+ // excluded up front: they are `So` too, but a left-border popup or
2095
+ // table row (`│ …`) is chrome, not a status line. The CLI is
2096
+ // already back at the prompt, so skipRecoveryWait avoids an extra wait.
2097
+ // action "nudge_continue": tell the user, then about a minute later tell
2098
+ // the agent to keep going (the lifecycle bounds this to 3 per 30 minutes,
2099
+ // then pauses; see #905). No restart — nothing would re-send the turn — and
2100
+ // no model switch. skipCooldown: each NEW capacity line is a new episode,
2101
+ // including a second one right after the nudge.
2102
+ pattern: /^(?![─-▟])\p{So}\uFE0F? Selected model is at capacity\. Please try a different model\.\r?$/mu,
2103
+ type: "model_error",
2104
+ action: "nudge_continue",
2105
+ message: t("inst.codex_model_capacity"),
2106
+ skipCooldown: true,
2107
+ skipRecoveryWait: true,
2108
+ },
2109
+ // Workspace (business) accounts report exhaustion differently from personal
2110
+ // ones, with codex's own rows (codex-rs/protocol error.rs and tui
2111
+ // turn_runtime.rs, unchanged 0.160.0–0.165.0):
2112
+ // member: "■ Your workspace is out of credits. Ask your workspace owner to refill in order to continue."
2113
+ // "■ You hit your spend cap set by the owner of your workspace. Ask an owner to increase your spend cap to continue."
2114
+ // owner: "■ You're out of credits. Your workspace is out of credits. Add credits to continue using Codex."
2115
+ // "■ Your workspace is out of credits. Add credits to continue."
2116
+ // "■ You hit your spend cap set in your workspace. Increase your spend cap to continue."
2117
+ // "■ Usage limit reached. You've reached your usage limit. Increase your limits to continue using codex."
2118
+ // Neither `insufficient_quota|billing` nor `you've hit your usage limit`
2119
+ // matches them.
2120
+ //
2121
+ // pause, not notify: nothing works until someone refills or raises the cap.
2122
+ // Anchored to the status glyph in COLUMN 0, because these skip the
2123
+ // usage-window check (a workspace limit is not one of those windows, so the
2124
+ // check would call it "available"): codex draws its own error cells there,
2125
+ // while an agent's message, quote or code block is indented under its `•`
2126
+ // (captured live, tests/fixtures/codex-0165-workspace-quoted.pane.txt).
2127
+ // `\s+` at each word gap: capture-pane runs without -J, so tmux's hard wrap
2128
+ // can land a newline anywhere in the sentence. A resumed transcript reprints
2129
+ // these rows, and the first live composer of a spawn takes them as history.
2130
+ {
2131
+ pattern: new RegExp(String.raw `^(?:${CODEX_STATUS_GLYPH})[ \t]+(?:You['’]re\s+out\s+of\s+credits\.\s+)?Your\s+workspace\s+is\s+out\s+of\s+credits\.`, "m"),
2132
+ type: "quota",
2133
+ action: "pause",
2134
+ message: "Codex workspace credits exhausted — workspace owner must refill",
2135
+ notice: () => quotaPausedLine("Codex", "workspace_credits"),
2136
+ skipUsageVerify: true,
2137
+ historyUntilLiveComposer: true,
2138
+ },
2139
+ {
2140
+ pattern: new RegExp(String.raw `^(?:${CODEX_STATUS_GLYPH})[ \t]+You\s+hit\s+your\s+spend\s+cap\s+set\s+(?:by\s+the\s+owner\s+of|in)\s+your\s+workspace\.`, "m"),
2141
+ type: "quota",
2142
+ action: "pause",
2143
+ message: "Codex workspace spend cap reached — workspace owner must raise it",
2144
+ notice: () => t("inst.quota_spend_cap"),
2145
+ skipUsageVerify: true,
2146
+ historyUntilLiveComposer: true,
2147
+ },
2148
+ {
2149
+ pattern: new RegExp(String.raw `^(?:${CODEX_STATUS_GLYPH})[ \t]+Usage\s+limit\s+reached\.\s+You['’]ve\s+reached\s+your\s+usage\s+limit\.\s+Increase\s+your\s+limits`, "m"),
2150
+ type: "quota",
2151
+ action: "pause",
2152
+ message: "Codex workspace usage limit reached — increase the workspace limits to continue",
2153
+ notice: () => quotaPausedLine("Codex", "workspace_usage"),
2154
+ skipUsageVerify: true,
2155
+ historyUntilLiveComposer: true,
2156
+ },
2157
+ // Codex warns at 10% and 5% remaining, and scopes the limit by period —
2158
+ // "weekly" alone missed every `monthly limit` warning.
2159
+ //
2160
+ // `\s+` at EVERY word gap (not literal spaces) because capture-pane runs without -J,
2161
+ // so tmux's hard wrap can land a newline (plus continuation-line padding)
2162
+ // at any of these word gaps. The trailing "Run /status for a breakdown."
2163
+ // is deliberately NOT part of the pattern — that tail is what actually
2164
+ // wrapped in the reported case.
2165
+ //
2166
+ // `of your <period> limit` is load-bearing: it's what keeps the pattern
2167
+ // off the agent's own prose about percentages.
2168
+ {
2169
+ pattern: /less\s+than\s+(\d+)\s*%\s+of\s+your\s+(hourly|daily|weekly|monthly)\s+limit/i,
2170
+ type: "quota",
2171
+ action: "notify",
2172
+ message: "Codex usage limit running low",
2173
+ quotaAdvisory: true,
2174
+ formatMessage: (m) => `Codex ${m[2].toLowerCase()} limit: less than ${m[1]}% left`,
2175
+ notice: (m) => (m ? quotaLowLine(m[2], m[1]) : quotaLine("Codex", "usage")),
2176
+ },
2177
+ ];
2320
2178
  }
2321
- /**
2322
- * Point this home's `auth.json` at whichever login it should use.
2323
- *
2324
- * Codex refreshes the token by writing through the link rather than replacing
2325
- * it — observed across every instance home on a machine that has been running
2326
- * this arrangement for months, with the shared file's mtime moving. That is
2327
- * behaviour, though, not a contract: a release that starts renaming over the
2328
- * file would leave a real file here and quietly fork the login, with refreshes
2329
- * landing somewhere the profile never sees. So a real file where a link
2330
- * belongs is treated as exactly that — reported, and put back.
2331
- */
2332
- linkAuthFile() {
2333
- const source = join(this.authSourceDir(), CODEX_AUTH_FILE);
2334
- const target = join(this.isolatedCodexHome, CODEX_AUTH_FILE);
2335
- let existing = null;
2336
- try {
2337
- existing = lstatSync(target);
2338
- }
2339
- catch { /* absent */ }
2340
- if (existing?.isSymbolicLink()) {
2341
- let current = "";
2342
- try {
2343
- current = resolve(dirname(target), readlinkSync(target));
2344
- }
2345
- catch { /* unreadable */ }
2346
- if (current === source)
2347
- return;
2348
- try {
2349
- unlinkSync(target);
2350
- }
2351
- catch { /* raced */ }
2352
- }
2353
- else if (existing) {
2354
- // Not a link: codex replaced it, or an older AgEnD copied it. Either way
2355
- // the refreshes it has been collecting are stranded in this instance's
2356
- // home, so say so rather than deleting them silently.
2357
- const stranded = `${target}.replaced-${Date.now()}`;
2358
- try {
2359
- renameSync(target, stranded);
2360
- console.warn(`[agend] codex replaced ${CODEX_AUTH_FILE} in ${this.isolatedCodexHome} with a real file — `
2361
- + `its login was no longer shared. Kept a copy at ${stranded} and re-linked to ${source}.`);
2362
- }
2363
- catch {
2364
- return; // cannot move it; leave the instance working on what it has
2365
- }
2366
- }
2367
- if (!existsSync(source))
2368
- return; // not logged in yet; codex will create it
2369
- try {
2370
- symlinkSync(source, target, "file");
2371
- }
2372
- catch {
2373
- // EEXIST from a concurrent start is the ordinary case and means the link
2374
- // is already there.
2375
- }
2179
+ getStartupDialogs() {
2180
+ const trustHold = this.trustHoldDialog();
2181
+ return [
2182
+ this.autoTrustDialog(),
2183
+ trustHold,
2184
+ this.updatePickerDialog(),
2185
+ this.rateSwitchPickerDialog(),
2186
+ ...this.sessionHoldDialogs(),
2187
+ this.unknownSelectionHoldDialog(),
2188
+ ];
2376
2189
  }
2377
2190
  /**
2378
- * One-time heal for an instance whose isolated home grew a REAL session dir
2379
- * (#506): merge it into the shared home without overwriting anything, and
2380
- * only after both the copy and the removal succeed put the symlink in its
2381
- * place. Any failure keeps the private dir — sessions are never the thing
2382
- * sacrificed for tidiness — and logs what happened.
2191
+ * #984: session-selection screens only a human may answer. Held (never
2192
+ * keyed) so the delivery gate keeps messages queued and dialog_parked tells
2193
+ * the operator; listed before the generic selection hold for a clear notice.
2383
2194
  */
2384
- migrateDivergedSessionDir(name) {
2385
- const target = join(this.isolatedCodexHome, name);
2386
- let st;
2387
- try {
2388
- st = lstatSync(target);
2389
- }
2390
- catch {
2391
- return; // absent — the symlink pass below will create the link
2392
- }
2393
- if (st.isSymbolicLink())
2394
- return; // already correct
2395
- if (!st.isDirectory()) {
2396
- console.warn(`[agend] codex-home/${name} is neither a symlink nor a directory — leaving it untouched`);
2397
- return;
2398
- }
2399
- const shared = join(this.sharedCodexHome, name);
2400
- try {
2401
- mkdirSync(shared, { recursive: true, mode: 0o700 });
2402
- // cp -rn semantics: collisions keep the shared copy. Session rollout
2403
- // filenames are timestamp+uuid, so a genuine collision means the same
2404
- // file — skipping is lossless either way.
2405
- cpSync(target, shared, { recursive: true, force: false, errorOnExist: false });
2406
- rmSync(target, { recursive: true });
2407
- symlinkSync(shared, target, "dir");
2408
- console.warn(`[agend] migrated diverged codex ${name} into shared home: ${shared}`);
2409
- }
2410
- catch (err) {
2411
- // Keep whatever is left of the private dir. The copy never overwrites, so
2412
- // a retry on next start is safe; worst case is duplicated files in the
2413
- // shared home, never lost ones.
2414
- console.warn(`[agend] codex ${name} migration failed — keeping the instance-private dir (${err.message})`);
2415
- }
2195
+ sessionHoldDialogs() {
2196
+ return [
2197
+ {
2198
+ pattern: /This conversation is open in another app/,
2199
+ keys: [],
2200
+ description: "Codex session is open in another process (thread lock) — close the other one and press r, or fork manually",
2201
+ holdOnly: true,
2202
+ blocksDelivery: true,
2203
+ inputBlocked: true,
2204
+ remoteCard: codexSelectionCard,
2205
+ isActive: codexSessionLockVisible,
2206
+ },
2207
+ {
2208
+ pattern: /Working directory · resume/,
2209
+ keys: [],
2210
+ description: "Codex resume selected a session from another directory (sibling git worktree) — needs a human choice",
2211
+ holdOnly: true,
2212
+ blocksDelivery: true,
2213
+ inputBlocked: true,
2214
+ remoteCard: codexSelectionCard,
2215
+ isActive: codexResumeCwdPickerVisible,
2216
+ },
2217
+ ];
2416
2218
  }
2417
2219
  /**
2418
- * One-time migration for installations polluted by the old global `codex
2419
- * mcp add` path. The lock protects separate fleet processes/upgrades, and
2420
- * atomic rename prevents readers from observing a truncated config.
2220
+ * The user's decision (hmsg-53600a2c): codex's folder-trust prompt in an AgEnD session is always answered Trust — the
2221
+ * workspace is the agent's own, whatever preTrust covered (preTrust stays, so the prompt rarely shows at all). Enter,
2222
+ * only with the cursor on "Trust and continue" among the known options (trustCursor); any other layout or row is
2223
+ * held (trustHoldDialog). It trusts the folder only: per-action approvals are separate dialogs and are not touched.
2421
2224
  */
2422
- cleanSharedConfig() {
2423
- if (this.sharedCodexHome === this.isolatedCodexHome)
2424
- return;
2425
- mkdirSync(this.sharedCodexHome, { recursive: true, mode: 0o700 });
2426
- const lockPath = join(this.sharedCodexHome, AGEND_MCP_CLEANUP_LOCK);
2427
- let lockFd;
2428
- const acquire = () => {
2429
- try {
2430
- lockFd = openSync(lockPath, "wx", 0o600);
2431
- return true;
2432
- }
2433
- catch {
2434
- return false;
2435
- }
2225
+ autoTrustDialog() {
2226
+ return {
2227
+ pattern: /^\s*Trust this folder\?/m,
2228
+ keys: ["Enter"],
2229
+ description: "Codex folder trust — trusting the agent's own folder",
2230
+ blocksDelivery: true,
2231
+ inputBlocked: true,
2232
+ autoResolutionKey: "codex-folder-auto-trust",
2233
+ // One Enter per prompt (claimed, then checked): a prompt still up after it is held, never pressed again (#2896 review).
2234
+ verifyAfterKeys: true,
2235
+ isActive: pane => codexTrustPromptState(pane).trustCursor,
2236
+ answerDetail: pane => ({ autoTrusted: true, ...this.trustPaneFields(pane) }),
2436
2237
  };
2437
- if (!acquire()) {
2438
- try {
2439
- if (Date.now() - statSync(lockPath).mtimeMs > AGEND_MCP_CLEANUP_LOCK_STALE_MS) {
2440
- unlinkSync(lockPath);
2441
- if (!acquire())
2442
- return;
2443
- }
2444
- else {
2445
- return;
2446
- }
2447
- }
2448
- catch {
2449
- return;
2450
- }
2451
- }
2452
- try {
2453
- const configPath = join(this.sharedCodexHome, "config.toml");
2454
- let content;
2455
- try {
2456
- content = readFileSync(configPath, "utf-8");
2457
- }
2458
- catch {
2459
- return;
2460
- }
2461
- const cleaned = stripAgendMcpTables(content);
2462
- if (cleaned !== content)
2463
- atomicWritePrivate(configPath, cleaned);
2464
- }
2465
- finally {
2466
- if (lockFd !== undefined) {
2467
- try {
2468
- closeSync(lockFd);
2469
- }
2470
- catch { }
2471
- }
2472
- try {
2473
- unlinkSync(lockPath);
2474
- }
2475
- catch { }
2476
- }
2477
2238
  }
2478
- readConfiguredStatusLineItems() {
2479
- if (this.configuredStatusLineItems !== undefined)
2480
- return this.configuredStatusLineItems;
2481
- try {
2482
- const config = tomlTable(parseToml(readFileSync(join(this.isolatedCodexHome, "config.toml"), "utf-8")));
2483
- const tui = tomlTable(config?.tui);
2484
- const items = tui?.status_line;
2485
- this.configuredStatusLineItems = Array.isArray(items) && items.every(item => typeof item === "string")
2486
- ? items
2487
- : null;
2488
- }
2489
- catch {
2490
- this.configuredStatusLineItems = null;
2491
- }
2492
- return this.configuredStatusLineItems;
2239
+ trustHoldDialog() {
2240
+ return {
2241
+ pattern: /^\s*(?:Folder access|Trust this folder\?|Do you trust the files in this folder\?)/im,
2242
+ keys: [],
2243
+ description: "Codex folder trust needs human confirmation",
2244
+ holdOnly: true,
2245
+ blocksDelivery: true,
2246
+ inputBlocked: true,
2247
+ isActive: codexTrustVariantActive,
2248
+ remoteCard: codexSelectionCard,
2249
+ heldReason: pane => this.trustHoldReason(pane),
2250
+ };
2493
2251
  }
2494
2252
  /**
2495
2253
  * #2893: why the authorized folder-trust answer did not take this prompt (log only): what the pane shows against what
@@ -2524,6 +2282,147 @@ export class CodexBackend extends CodexPaneDetectors {
2524
2282
  authorizedCwd: authorized?.cwd ?? null, authorizedRoot: authorized?.root ?? null,
2525
2283
  configPath: join(this.isolatedCodexHome, "config.toml") };
2526
2284
  }
2285
+ /**
2286
+ * #1008: the rate-limit model-switch picker parked unattended instances (it
2287
+ * waits forever and its default option changes the model). Prevention is
2288
+ * `notice.hide_rate_limit_model_nudge` (hideRateLimitModelNudge — real and
2289
+ * effective on 0.157.1 and 0.159.2, verified live); this is the answer for a
2290
+ * picker that appears anyway (config not applied, a CLI that moved the key).
2291
+ *
2292
+ * Escape, not a navigated Enter: it selects nothing, so it can never take
2293
+ * option 1 (switch model) or option 3 (writes "never show again" into the
2294
+ * config). Deliberately not one-shot: if the picker swallowed the key it is
2295
+ * simply sent again on the next poll, and a repeated Escape is harmless.
2296
+ */
2297
+ rateSwitchPickerDialog() {
2298
+ return {
2299
+ // Documentation only: Daemon.dialogMatches uses isActive INSTEAD of this
2300
+ // pattern, so it filters nothing here — the structural check below is the
2301
+ // whole decision, and ordinary prose about the picker fails it. The words
2302
+ // are the picker's own, kept so a reader can find the screen from the log.
2303
+ pattern: /Keep current model \(never show again\)/,
2304
+ keys: ["Escape"],
2305
+ description: "Codex rate-limit model-switch picker — Escape keeps the current model",
2306
+ blocksDelivery: true,
2307
+ inputBlocked: true,
2308
+ isActive: codexRateSwitchPickerVisible,
2309
+ };
2310
+ }
2311
+ updatePickerDialog() {
2312
+ return {
2313
+ // Defense in depth for config written by older AgEnD versions or a Codex
2314
+ // regression. Match the release URL as well as the banner so ordinary
2315
+ // agent prose cannot trigger a keypress.
2316
+ pattern: /Update available![\s\S]{0,200}Release notes: https:\/\/github\.com\/openai\/codex\/releases/m,
2317
+ keys: ["Escape"],
2318
+ description: "Codex startup update-available picker",
2319
+ // A delivery must never land on this screen. Its selected option is
2320
+ // `› 1. Update now (runs … curl … | sh)` — the same `›` the input row
2321
+ // uses — so an Enter here runs an installer instead of sending a message.
2322
+ // The runtime dismisser presses Escape, but it only polls every few
2323
+ // seconds and a delivery can arrive first (hit live on codex-cli 0.153.4,
2324
+ // which parks on this picker for as long as nobody answers it).
2325
+ blocksDelivery: true,
2326
+ // Daemon.dialogMatches uses isActive INSTEAD OF pattern when present.
2327
+ // Check the complete current picker here; a different Enter-only menu
2328
+ // must never receive this automatic Escape key.
2329
+ isActive: codexUpdatePickerVisible,
2330
+ };
2331
+ }
2332
+ usageLimitLunaReserveDialog() {
2333
+ // The real codex usage-limit dialog states codex has ALREADY switched to
2334
+ // Luna Reserve and labels Escape as "continue working". Pressing Escape via
2335
+ // sendSpecialKey is the ONLY safe key:
2336
+ //
2337
+ // • Escape is sent via sendSpecialKey — never via pasteText (which adds
2338
+ // an implicit bracketed-paste Enter that could confirm the highlighted
2339
+ // server action: Upgrade, Reset usage, Add Credits, …).
2340
+ // • A digit selects its row at once, and pasted its implicit Enter would
2341
+ // confirm whatever it landed on. This path is explicitly NOT used.
2342
+ //
2343
+ // verifyAfterKeys: true — confirm the menu is gone after Escape.
2344
+ // No confirmBeforeEnter / keysAfterConfirm: Escape needs no confirm gate.
2345
+ return {
2346
+ pattern: /Press enter to confirm or esc to continue working/i,
2347
+ keys: ["Escape"],
2348
+ description: "Codex usage limit — pressing Escape to continue with Luna Reserve",
2349
+ blocksDelivery: true,
2350
+ inputBlocked: true,
2351
+ isActive: codexUsageLimitMenuVisible,
2352
+ verifyAfterKeys: true,
2353
+ autoResolutionKey: "codex-usage-limit-luna-reserve",
2354
+ postDismissNotice: {
2355
+ text: t("inst.codex_usage_limit_luna_reserve", ""), // instance name injected by daemon
2356
+ label: "codex-usage-limit-selected",
2357
+ },
2358
+ };
2359
+ }
2360
+ workspaceLimitPromptDialog() {
2361
+ // Escape via sendSpecialKey, the same as the Luna Reserve menu: never a paste
2362
+ // (its implicit Enter would confirm the cursor's row) and never "y". The error
2363
+ // row codex printed above the prompt stays, and the quota pause takes it from
2364
+ // there: one notice, no parked dialog.
2365
+ return {
2366
+ pattern: /^\s*enter select\s*[·•]\s*esc back\s*$/im,
2367
+ keys: ["Escape"],
2368
+ description: "Codex workspace limit prompt — pressing Escape (never asking the owner)",
2369
+ blocksDelivery: true,
2370
+ inputBlocked: true,
2371
+ isActive: codexWorkspaceLimitPromptVisible,
2372
+ verifyAfterKeys: true,
2373
+ autoResolutionKey: "codex-workspace-limit-prompt",
2374
+ answeredByAgend: true,
2375
+ marksLiveIncident: true,
2376
+ };
2377
+ }
2378
+ unknownSelectionHoldDialog() {
2379
+ return {
2380
+ pattern: /^\s*[›❯>]\s+\S/m,
2381
+ keys: [],
2382
+ description: "Codex interactive selection needs human input",
2383
+ holdOnly: true,
2384
+ blocksDelivery: true,
2385
+ inputBlocked: true,
2386
+ remoteCard: codexSelectionCard,
2387
+ isActive: codexUnknownSelectionVisible,
2388
+ };
2389
+ }
2390
+ getRuntimeDialogs() {
2391
+ return [
2392
+ this.autoTrustDialog(),
2393
+ this.trustHoldDialog(),
2394
+ this.rateSwitchPickerDialog(),
2395
+ {
2396
+ // Codex 0.156 may change the wording/order of this credit-cost choice.
2397
+ // Never navigate it by position: a moved option could switch to a
2398
+ // more expensive model. A live picker holds delivery and notifies a
2399
+ // human; old transcript mentions are excluded by the tail matcher.
2400
+ pattern: /(?:Approaching rate limits|Switch to [^\r\n]{1,120} for lower credit usage\?)/i,
2401
+ keys: [],
2402
+ description: "Codex rate limit model switch dialog needs human choice",
2403
+ holdOnly: true,
2404
+ blocksDelivery: true,
2405
+ inputBlocked: true,
2406
+ remoteCard: codexSelectionCard,
2407
+ isActive: codexRateSwitchVisible,
2408
+ },
2409
+ this.updatePickerDialog(),
2410
+ this.usageLimitLunaReserveDialog(),
2411
+ this.workspaceLimitPromptDialog(),
2412
+ ...this.sessionHoldDialogs(),
2413
+ this.unknownSelectionHoldDialog(),
2414
+ ];
2415
+ }
2416
+ getInputUnavailableTransients() {
2417
+ return [{
2418
+ pattern: /(?:^|\n)\s*Resuming session…\s*(?:\n|$)/,
2419
+ description: "Codex session resume in progress",
2420
+ isActive: (pane) => {
2421
+ const rows = pane.split(/\r?\n/);
2422
+ return codexBoxedResumeLoading(rows) || codexUnboxedResumeLoading(rows);
2423
+ },
2424
+ }];
2425
+ }
2527
2426
  getContextUsage() {
2528
2427
  return null;
2529
2428
  }
@@ -2638,6 +2537,10 @@ export class CodexBackend extends CodexPaneDetectors {
2638
2537
  return peers ? codexSiblingState(config.workingDirectory, peers) : "siblings";
2639
2538
  });
2640
2539
  }
2540
+ /** Only a resume paints "Resuming session…"; a fresh launch never does. */
2541
+ launchMayShowInputTransient() {
2542
+ return this.lastLaunchResumes;
2543
+ }
2641
2544
  consumeLaunchWarning() {
2642
2545
  const warnings = [this.packageWarning, this.statusLineWarning, this.launchWarning].filter((w) => !!w);
2643
2546
  this.packageWarning = null;
@@ -2645,6 +2548,10 @@ export class CodexBackend extends CodexPaneDetectors {
2645
2548
  this.launchWarning = null;
2646
2549
  return warnings.length ? warnings.join("\n") : null;
2647
2550
  }
2551
+ getQuitCommand() { return "/quit"; }
2552
+ getCompactCommand() { return "/compact"; }
2553
+ getClearCommand() { return "/clear"; }
2554
+ getCancelKey() { return "Escape"; }
2648
2555
  // Codex has no `/effort`; reasoning effort is the `model_reasoning_effort`
2649
2556
  // config key (settable per launch with `-c`). The TUI reads it at startup, so
2650
2557
  // a change needs a respawn — restart, not runtime.