agentbox-app 0.1.6

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 (302) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +176 -0
  3. package/bin/agentbox.mjs +103 -0
  4. package/briefs/adhd-mode.md +5 -0
  5. package/briefs/message-rules.md +95 -0
  6. package/briefs/worker.md +230 -0
  7. package/briefs/writing-rules.md +336 -0
  8. package/main/account-discovery.mjs +145 -0
  9. package/main/account-tooling.mjs +261 -0
  10. package/main/agent-activity.mjs +56 -0
  11. package/main/agent-files.mjs +422 -0
  12. package/main/agent-schedule.mjs +254 -0
  13. package/main/agent-sessions.mjs +435 -0
  14. package/main/agents.mjs +1095 -0
  15. package/main/analytics.mjs +206 -0
  16. package/main/answer-modes.mjs +206 -0
  17. package/main/approval-prompt-server.mjs +160 -0
  18. package/main/approvals.mjs +487 -0
  19. package/main/artifact-path.mjs +144 -0
  20. package/main/claude-bin.mjs +311 -0
  21. package/main/claude-input.mjs +117 -0
  22. package/main/claude-models.mjs +244 -0
  23. package/main/claude-plan.mjs +148 -0
  24. package/main/claude-usage.mjs +149 -0
  25. package/main/code-change.mjs +383 -0
  26. package/main/code-file.mjs +116 -0
  27. package/main/codex-account.mjs +146 -0
  28. package/main/codex-app-server.mjs +796 -0
  29. package/main/codex-approvals.mjs +712 -0
  30. package/main/codex-bin.mjs +261 -0
  31. package/main/codex-compaction.mjs +49 -0
  32. package/main/codex-models.mjs +194 -0
  33. package/main/codex-session.mjs +797 -0
  34. package/main/codex-threads.mjs +268 -0
  35. package/main/codex-usage-file.mjs +192 -0
  36. package/main/codex-usage.mjs +149 -0
  37. package/main/codex-watch.mjs +35 -0
  38. package/main/codex.mjs +757 -0
  39. package/main/config.mjs +345 -0
  40. package/main/copy-selection.mjs +55 -0
  41. package/main/crash-report.mjs +158 -0
  42. package/main/demo.mjs +254 -0
  43. package/main/dev-window.mjs +103 -0
  44. package/main/doc-file.mjs +99 -0
  45. package/main/doc-scheme.mjs +126 -0
  46. package/main/first-run.mjs +190 -0
  47. package/main/folders.mjs +91 -0
  48. package/main/fresh-user.mjs +120 -0
  49. package/main/git-change.mjs +323 -0
  50. package/main/img-scheme.mjs +90 -0
  51. package/main/instruction-settings.mjs +205 -0
  52. package/main/ipc.mjs +1558 -0
  53. package/main/live-replies.mjs +77 -0
  54. package/main/machine.mjs +113 -0
  55. package/main/main.mjs +972 -0
  56. package/main/menu.mjs +177 -0
  57. package/main/notify.mjs +155 -0
  58. package/main/open-source-file.mjs +8 -0
  59. package/main/project-identity.mjs +149 -0
  60. package/main/provider-commands.mjs +32 -0
  61. package/main/rail-note.mjs +74 -0
  62. package/main/repeats.mjs +261 -0
  63. package/main/row-label.mjs +185 -0
  64. package/main/screen-detail.mjs +44 -0
  65. package/main/serve.mjs +262 -0
  66. package/main/settings.mjs +912 -0
  67. package/main/staleness.mjs +76 -0
  68. package/main/store/chats.mjs +188 -0
  69. package/main/store/home.mjs +383 -0
  70. package/main/store/migrate.mjs +40 -0
  71. package/main/store/paths.mjs +82 -0
  72. package/main/store/pending-writes.mjs +122 -0
  73. package/main/store/project-lock.mjs +200 -0
  74. package/main/store/project.mjs +523 -0
  75. package/main/store/queues.mjs +31 -0
  76. package/main/store/work-items.mjs +299 -0
  77. package/main/store/write-queue.mjs +14 -0
  78. package/main/store-modules.mjs +23 -0
  79. package/main/store.mjs +1269 -0
  80. package/main/supervisor.mjs +6875 -0
  81. package/main/task-commands.mjs +161 -0
  82. package/main/task-folders-offthread.mjs +84 -0
  83. package/main/task-folders-thread.mjs +13 -0
  84. package/main/task-folders.mjs +409 -0
  85. package/main/task-remote-control.mjs +65 -0
  86. package/main/task-terminals.mjs +169 -0
  87. package/main/terminal-access.mjs +6 -0
  88. package/main/updater.mjs +240 -0
  89. package/main/zoom-keys.mjs +54 -0
  90. package/main/zoom-readout.mjs +20 -0
  91. package/mcp/agentbox-mcp.sh +34 -0
  92. package/mcp/core/account.mjs +77 -0
  93. package/mcp/core/chat.mjs +61 -0
  94. package/mcp/core/documents.mjs +238 -0
  95. package/mcp/core/page.mjs +152 -0
  96. package/mcp/core/products.mjs +110 -0
  97. package/mcp/core/skills.mjs +126 -0
  98. package/mcp/core/work.mjs +377 -0
  99. package/mcp/stdio.mjs +70 -0
  100. package/mcp/tools.mjs +257 -0
  101. package/package.json +186 -0
  102. package/renderer/dist/assets/Geist-Variable-Bj2R_7yk.woff2 +0 -0
  103. package/renderer/dist/assets/GeistMono-Variable-Dispecij.woff2 +0 -0
  104. package/renderer/dist/assets/addon-fit-YJmn1quW.js +12 -0
  105. package/renderer/dist/assets/addon-serialize-CsFQTvP_.js +13 -0
  106. package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
  107. package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
  108. package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
  109. package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
  110. package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
  111. package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
  112. package/renderer/dist/assets/gouache-valley-ChSu14-t.webp +0 -0
  113. package/renderer/dist/assets/gouache-valley-d9qasI6k.webp +0 -0
  114. package/renderer/dist/assets/gouache-valley-sm-CdLjT5Ec.webp +0 -0
  115. package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
  116. package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
  117. package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
  118. package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
  119. package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
  120. package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
  121. package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
  122. package/renderer/dist/assets/index-CwiDv5bL.css +32 -0
  123. package/renderer/dist/assets/index-D9hCAVPl.js +684 -0
  124. package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
  125. package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
  126. package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
  127. package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
  128. package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
  129. package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
  130. package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
  131. package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
  132. package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
  133. package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
  134. package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
  135. package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
  136. package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
  137. package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
  138. package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
  139. package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
  140. package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
  141. package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
  142. package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
  143. package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
  144. package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
  145. package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
  146. package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
  147. package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
  148. package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
  149. package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
  150. package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
  151. package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
  152. package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
  153. package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
  154. package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
  155. package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
  156. package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
  157. package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
  158. package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
  159. package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
  160. package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
  161. package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
  162. package/renderer/dist/assets/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
  163. package/renderer/dist/assets/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
  164. package/renderer/dist/assets/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
  165. package/renderer/dist/assets/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
  166. package/renderer/dist/assets/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
  167. package/renderer/dist/assets/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
  168. package/renderer/dist/assets/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
  169. package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
  170. package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
  171. package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
  172. package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
  173. package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
  174. package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
  175. package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
  176. package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
  177. package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
  178. package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
  179. package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
  180. package/renderer/dist/assets/xterm-BT7tBf8A.js +27 -0
  181. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  182. package/renderer/dist/emulator/data/compression/README.md +11 -0
  183. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  184. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  185. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  186. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  187. package/renderer/dist/emulator/data/cores/README.md +22 -0
  188. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  189. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  190. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  191. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  192. package/renderer/dist/emulator/data/cores/package.json +65 -0
  193. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  194. package/renderer/dist/emulator/data/emulator.css +1614 -0
  195. package/renderer/dist/emulator/data/loader.js +170 -0
  196. package/renderer/dist/emulator/data/localization/README.md +63 -0
  197. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  198. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  199. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  200. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  201. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  202. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  203. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  204. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  205. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  206. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  207. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  208. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  209. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  210. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  211. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  212. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  213. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  214. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  215. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  216. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  217. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  218. package/renderer/dist/emulator/data/src/compression.js +142 -0
  219. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  220. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  221. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  222. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  223. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  224. package/renderer/dist/emulator/data/src/storage.js +125 -0
  225. package/renderer/dist/emulator/data/version.json +5 -0
  226. package/renderer/dist/emulator/host.js +299 -0
  227. package/renderer/dist/emulator/index.html +28 -0
  228. package/renderer/dist/emulator/provenance.json +6 -0
  229. package/renderer/dist/fixtures/onboard/designs/w-n13/the-first-row.html +1 -0
  230. package/renderer/dist/index.html +13 -0
  231. package/scripts/after-install.mjs +59 -0
  232. package/scripts/approval-server.sh +43 -0
  233. package/scripts/before-publish.mjs +133 -0
  234. package/shared/agent-import.d.mts +13 -0
  235. package/shared/agent-import.mjs +252 -0
  236. package/shared/agents.d.mts +190 -0
  237. package/shared/agents.mjs +937 -0
  238. package/shared/analytics-events.mjs +63 -0
  239. package/shared/answers.d.mts +12 -0
  240. package/shared/answers.mjs +80 -0
  241. package/shared/artifact-keys.d.mts +28 -0
  242. package/shared/artifact-keys.mjs +140 -0
  243. package/shared/bridge-map.mjs +128 -0
  244. package/shared/claude-commands.d.mts +20 -0
  245. package/shared/claude-commands.generated.mjs +149 -0
  246. package/shared/claude-commands.mjs +131 -0
  247. package/shared/claude-models.generated.d.mts +15 -0
  248. package/shared/claude-models.generated.mjs +26 -0
  249. package/shared/claude-usage.d.mts +16 -0
  250. package/shared/claude-usage.mjs +172 -0
  251. package/shared/codex-commands.d.mts +4 -0
  252. package/shared/codex-commands.mjs +17 -0
  253. package/shared/codex-import.d.mts +38 -0
  254. package/shared/codex-import.mjs +232 -0
  255. package/shared/codex-modes.mjs +159 -0
  256. package/shared/codex-usage.mjs +180 -0
  257. package/shared/contracts.mjs +412 -0
  258. package/shared/crash-scrub.mjs +182 -0
  259. package/shared/dashboard.mjs +481 -0
  260. package/shared/dead-run-trace.mjs +127 -0
  261. package/shared/demo-world.mjs +415 -0
  262. package/shared/effort-levels.d.mts +15 -0
  263. package/shared/effort-levels.mjs +76 -0
  264. package/shared/engines.d.mts +62 -0
  265. package/shared/engines.mjs +389 -0
  266. package/shared/first-run-examples.mjs +60 -0
  267. package/shared/first-run-line.mjs +149 -0
  268. package/shared/first-run-practice.d.mts +32 -0
  269. package/shared/first-run-practice.mjs +254 -0
  270. package/shared/first-run-shape.mjs +100 -0
  271. package/shared/fresh-user-home.mjs +189 -0
  272. package/shared/hook-failure.mjs +22 -0
  273. package/shared/install-id.mjs +39 -0
  274. package/shared/instruction-defaults.json +5 -0
  275. package/shared/library-folder.mjs +28 -0
  276. package/shared/notify-rules.mjs +120 -0
  277. package/shared/product-name.d.mts +6 -0
  278. package/shared/product-name.mjs +144 -0
  279. package/shared/project-folder-check.d.mts +22 -0
  280. package/shared/project-folder-check.mjs +152 -0
  281. package/shared/provider-commands.d.mts +8 -0
  282. package/shared/provider-commands.mjs +58 -0
  283. package/shared/rank.d.mts +19 -0
  284. package/shared/rank.mjs +146 -0
  285. package/shared/recovery.mjs +26 -0
  286. package/shared/referenced-files.d.mts +11 -0
  287. package/shared/referenced-files.mjs +122 -0
  288. package/shared/repeats.d.mts +45 -0
  289. package/shared/repeats.mjs +193 -0
  290. package/shared/schemes.mjs +33 -0
  291. package/shared/settings-terminal.d.mts +1 -0
  292. package/shared/settings-terminal.mjs +14 -0
  293. package/shared/side-build.mjs +57 -0
  294. package/shared/spawn-trouble.d.mts +8 -0
  295. package/shared/spawn-trouble.mjs +599 -0
  296. package/shared/terminal-state.d.mts +1 -0
  297. package/shared/terminal-state.mjs +58 -0
  298. package/shared/usage.d.mts +53 -0
  299. package/shared/usage.mjs +319 -0
  300. package/shared/work-items.mjs +561 -0
  301. package/shared/work-lines.mjs +360 -0
  302. package/worker-permissions.json +6 -0
@@ -0,0 +1,159 @@
1
+ // WHAT A CODEX WORKER MAY DO, SAID ONCE, IN CODEX'S OWN TERMS.
2
+ //
3
+ // The sister of renderer/src/modes.ts, which does this for Claude Code. That
4
+ // file's rule holds here too: every value below is a value Codex really takes,
5
+ // and nothing is invented to make a tidier row.
6
+ //
7
+ // WHY THERE ARE THREE AND NOT SIX. Claude Code has six permission modes and we
8
+ // print all six because they are its own list. Codex does not have six of
9
+ // anything. It has a SANDBOX with three settings and an APPROVAL POLICY that
10
+ // sits on top of it, and the two are not independent in any way a person would
11
+ // want to choose between: sixteen combinations, of which three are the ones
12
+ // anybody means. Read off `codex app-server generate-json-schema` on
13
+ // codex-cli 0.153.4, 2026-09-23:
14
+ //
15
+ // SandboxMode read-only | workspace-write | danger-full-access
16
+ // AskForApproval untrusted | on-request | never | {granular:{...}}
17
+ //
18
+ // So each row here is a PAIR, and the pair is the thing she picks.
19
+ //
20
+ // `untrusted` IS DELIBERATELY NOT OFFERED. It is the one Agentbox used to run
21
+ // and it is why Codex was unusable here: it cards every command before it runs,
22
+ // and it adds no containment the sandbox was not already providing. The
23
+ // measurements are in main/codex-session.mjs. Leaving it off the list is not
24
+ // hiding a choice, it is declining to offer a broken one. `granular` is not
25
+ // offered either: it is an object of five switches, which is a settings screen
26
+ // of its own and not a mode.
27
+ //
28
+ // THE LABELS ARE OURS, unlike the Claude Code file's, because Codex's own
29
+ // surfaces disagree with each other about what to call these. Its CLI says
30
+ // `--sandbox read-only`; its TUI says "Read Only"; its docs say "Auto". Since
31
+ // there is no single authority to copy, these are written for the person
32
+ // reading them, in the words the rest of this app uses.
33
+
34
+ /** Her three, in order of what they let an agent do. */
35
+ export const CODEX_MODE_ORDER = ['read-only', 'auto', 'full-access'];
36
+
37
+ /** The one a worker runs in when nothing has been picked. */
38
+ export const CODEX_DEFAULT_MODE = 'auto';
39
+
40
+ /**
41
+ * THE PAIR EACH MODE REALLY IS. `sandbox` and `approvalPolicy` go straight into
42
+ * `thread/start`, so this table is the whole of the mapping and there is no
43
+ * second copy of it anywhere.
44
+ *
45
+ * `approvalPolicy` is `on-request` on the two sandboxed rows: the sandbox does
46
+ * the refusing, and `on-request` leaves the agent able to ASK to step outside
47
+ * rather than simply failing. On `full-access` there is nothing left to ask
48
+ * about, so it is `never`.
49
+ */
50
+ export const CODEX_MODES = {
51
+ 'read-only': {
52
+ label: 'Read only',
53
+ sandbox: 'read-only',
54
+ approvalPolicy: 'on-request',
55
+ // What runs without asking, in the same voice as the Claude Code list.
56
+ what: 'Reads and searches. Writes nothing and runs nothing that changes anything.',
57
+ },
58
+ auto: {
59
+ label: 'Auto',
60
+ sandbox: 'workspace-write',
61
+ approvalPolicy: 'on-request',
62
+ // WHO ANSWERS THOSE ASKS, SAID HERE RATHER THAN LEFT TO A FILE. This used
63
+ // to come only from `approvals_reviewer` in her `~/.codex/config.toml`, so
64
+ // a second login's home, which starts with no config at all, sent every ask
65
+ // to her inbox instead (2026-09-24, w-ca48e69535). One mode, one behaviour,
66
+ // whichever account the run is on.
67
+ approvalsReviewer: 'auto_review',
68
+ what: 'Works inside the project folder. Asks before going outside it or onto the network.',
69
+ },
70
+ 'full-access': {
71
+ label: 'Full access',
72
+ sandbox: 'danger-full-access',
73
+ approvalPolicy: 'never',
74
+ what: 'Anything, anywhere on this Mac, without asking.',
75
+ },
76
+ };
77
+
78
+ /** Whether a word is one of the three. */
79
+ export function isCodexMode(word) {
80
+ return typeof word === 'string' && Object.hasOwn(CODEX_MODES, word);
81
+ }
82
+
83
+ /**
84
+ * The pair for a mode, or the default's pair when the word is not one of ours.
85
+ * FALLING BACK RATHER THAN THROWING IS DELIBERATE at this level: a stale word
86
+ * in her config should start a worker in the safe-ish default, not refuse to
87
+ * start one. The writers (main/settings.mjs) validate on the way IN, which is
88
+ * where a typo can still be reported to the person who made it.
89
+ */
90
+ export function codexPosture(mode) {
91
+ const row = CODEX_MODES[mode] ?? CODEX_MODES[CODEX_DEFAULT_MODE];
92
+ const posture = { sandbox: row.sandbox, approvalPolicy: row.approvalPolicy };
93
+ if (row.approvalsReviewer) posture.approvalsReviewer = row.approvalsReviewer;
94
+ return posture;
95
+ }
96
+
97
+ /** The label for a mode, for any surface that has to name one. */
98
+ export function codexModeWords(mode) {
99
+ return (CODEX_MODES[mode] ?? CODEX_MODES[CODEX_DEFAULT_MODE]).label;
100
+ }
101
+
102
+ /**
103
+ * THE WORD SHE TYPES AFTER THE SLASH, one per mode.
104
+ *
105
+ * Deliberately NOT the same words as Claude Code's six. `/auto` exists on both
106
+ * engines, which is fine and is the one overlap: a row has one engine, so the
107
+ * menu she is looking at only ever offers one of the two, and both mean "the
108
+ * sensible default" on their own engine.
109
+ */
110
+ export const CODEX_MODE_COMMAND = {
111
+ 'read-only': '/read-only',
112
+ auto: '/auto',
113
+ 'full-access': '/full-access',
114
+ };
115
+
116
+ /** One sentence per row of the menu, in the same voice as the Claude Code list. */
117
+ export const CODEX_MODE_HINT = {
118
+ 'read-only': 'reads and searches, writes nothing',
119
+ auto: 'works in the project folder, asks to leave it',
120
+ 'full-access': 'anything, anywhere, without asking',
121
+ };
122
+
123
+ /**
124
+ * The rows the menu draws for what has been typed. Mirrors `menuRowsFor` on the
125
+ * Claude Code side, including the way back when a mode is already set.
126
+ */
127
+ export function codexMenuRowsFor(query, set) {
128
+ if (query === null) return [];
129
+ const rows = CODEX_MODE_ORDER.filter((id) => CODEX_MODE_COMMAND[id].slice(1).startsWith(query.toLowerCase()));
130
+ // `/clear` puts it back to whatever the project is already set to, and is
131
+ // only offered when there is something to clear.
132
+ const back = set && 'clear'.startsWith(query.toLowerCase()) ? [null] : [];
133
+ return [...rows, ...back];
134
+ }
135
+
136
+ /**
137
+ * WHAT THE CHIP AND THE TOAST SAY while a mode is on. Same shape as Claude
138
+ * Code's `MODE_STATUS`, which is that engine's own status line with " on"
139
+ * after it. Codex prints no such line, so these are ours, kept in the same
140
+ * rhythm so the two engines read alike on one screen.
141
+ */
142
+ export const CODEX_MODE_STATUS = {
143
+ 'read-only': 'read only on',
144
+ auto: 'auto on',
145
+ 'full-access': 'full access on',
146
+ };
147
+
148
+ /** The next one round the loop, for Shift+Tab. */
149
+ export function nextCodexMode(mode) {
150
+ const at = CODEX_MODE_ORDER.indexOf(mode);
151
+ return CODEX_MODE_ORDER[(at + 1) % CODEX_MODE_ORDER.length];
152
+ }
153
+
154
+ /** The mode a typed `/word ` means, or null when it means nothing. */
155
+ export function exactCodexMode(word) {
156
+ if (!word) return null;
157
+ const hit = CODEX_MODE_ORDER.find((id) => CODEX_MODE_COMMAND[id] === `/${word}`);
158
+ return hit ?? null;
159
+ }
@@ -0,0 +1,180 @@
1
+ // WHAT IS LEFT OF HER LIMIT, OUT OF `codex app-server`'S OWN RATE LIMITS.
2
+ //
3
+ // The pure half, and the mirror of shared/claude-usage.mjs: the payload in, the
4
+ // limits out. Everything after that -- the countdown, the rows, the sentence --
5
+ // is shared/usage.mjs, because a limit is a limit whichever agent reported it.
6
+ // Asking for the payload is main/codex-usage.mjs.
7
+ //
8
+ // NOTHING IS SPAWNED TO DRAW THIS AND NOTHING IS ASKED FOR IT, which is the
9
+ // whole reason this reader looks nothing like the other one. `claude -p /usage`
10
+ // costs a child process and twenty-five seconds, which is what shaped every line
11
+ // of main/claude-usage.mjs. Codex's figures ride the app-server the fleet is
12
+ // ALREADY running and arrive unprompted as `account/rateLimits/updated` during a
13
+ // turn. That push is the only source: the explicit read that once sat beside it
14
+ // turned out to be a request to OpenAI's backend rather than a local lookup, and
15
+ // main/codex-usage.mjs holds the measurement and the reason it is deliberately
16
+ // gone. So there is no 25-second cost here to design around, and there is no
17
+ // reading at all until a turn reports one -- which is honest, and is better than
18
+ // paying for a picture.
19
+ //
20
+ // ---------------------------------------------------------------------------
21
+ // MEASURED ON THIS MAC, 2026-09-05, codex-cli 0.148.0, by running a real
22
+ // app-server against a scratch CODEX_HOME and asking it once BY HAND, which is
23
+ // the only thing that has ever asked -- Agentbox does not, per the paragraph
24
+ // above. Verbatim, at now(s) = 1788565827:
25
+ //
26
+ // "rateLimits": { "limitId": "codex", "limitName": null,
27
+ // "primary": { "usedPercent": 33, "windowDurationMins": 300, "resetsAt": 1788573509 },
28
+ // "secondary": { "usedPercent": 63, "windowDurationMins": 10080, "resetsAt": 1788798916 } }
29
+ //
30
+ // PRIMARY IS THE FIVE HOUR WINDOW AND SECONDARY IS THE WEEK, and that is read
31
+ // off `windowDurationMins` rather than assumed: 300 minutes is five hours and
32
+ // 10080 is seven days. So they take her own names for the same two spans --
33
+ // "This session" and "This week" -- and no new vocabulary is invented for a
34
+ // fact she already has a word for.
35
+ //
36
+ // `resetsAt` IS UNIX SECONDS. Two proofs off that one capture, because a unit
37
+ // read wrong by a thousand is the confident wrong number this whole surface
38
+ // exists to avoid. The `base_model_inference` bucket on the same response read
39
+ // `{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1789170627}`, and
40
+ // 1789170627 - 1788565827 is 604800 exactly, which is those 10080 minutes to the
41
+ // second. And the schema's neighbouring `RateLimitResetCredit.expiresAt` is
42
+ // documented "Unix timestamp in seconds". It is multiplied by 1000 once, below,
43
+ // and nowhere else.
44
+ //
45
+ // A WINDOW THAT IS NOT THERE IS ORDINARY, NOT AN ERROR. Off
46
+ // `codex app-server generate-json-schema` on the same machine: in
47
+ // `RateLimitSnapshot` both `primary` and `secondary` are
48
+ // `anyOf [RateLimitWindow, null]` and the object has NO required field at all;
49
+ // `usedPercent` is the one required field of a window, and `resetsAt` and
50
+ // `windowDurationMins` are both nullable. The live read proved it too --
51
+ // `base_model_inference` came back with `"secondary": null`.
52
+ //
53
+ // SO AN ABSENT WINDOW IS ABSENT AND NEVER A ZERO. That is the dashboard law:
54
+ // absence of data and a real zero are different facts and must never look the
55
+ // same. A window that really reported 0% keeps its row and its empty bar; one
56
+ // that is not there has no row.
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // AND `primary` IS NOT ALWAYS THE FIVE HOUR WINDOW. The block above read one
60
+ // scratch account on 2026-09-05 and took the pairing it happened to see as the
61
+ // shape. Measured again on HER OWN account, 2026-09-18, across the 1,140
62
+ // rate-limit records in her twenty-five most recent Codex sessions: EVERY ONE of
63
+ // them had `secondary: null` and a `primary` whose window was 10080 minutes. Not
64
+ // one five hour window exists on her plan.
65
+ //
66
+ // So the old table would have drawn her SEVEN DAY limit under the words "This
67
+ // session", which is a confident wrong number about her own subscription, and
68
+ // the corner would have counted a week down as though it were an afternoon.
69
+ //
70
+ // THE WINDOW'S OWN LENGTH DECIDES WHAT IT IS CALLED, and the slot it arrived in
71
+ // decides nothing. 300 minutes is her session, 10080 is her week, and an account
72
+ // reporting only one of them gets only that one row. The slot order is still the
73
+ // tie-breaker when a payload names no duration at all, because that is the one
74
+ // case where there is nothing better to go on.
75
+ //
76
+ // TWO SPELLINGS REACH HERE, and both are real. The app-server's push is
77
+ // camelCase (`usedPercent`, `windowDurationMins`, `resetsAt`); the session log on
78
+ // disk, which main/codex-usage-file.mjs reads when no push has arrived, is
79
+ // snake_case (`used_percent`, `window_minutes`, `resets_at`). They are the same
80
+ // numbers from the same service and neither is converted upstream, so both are
81
+ // accepted right here rather than in two places that could drift apart.
82
+
83
+ const plain = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
84
+
85
+ /**
86
+ * The order the panel draws them in, and the fallback when nothing says how
87
+ * long a window is. */
88
+ const WINDOWS = ['primary', 'secondary'];
89
+
90
+ const FALLBACK = { primary: ['session', 'This session'], secondary: ['week', 'This week'] };
91
+
92
+ /** Either spelling of the same field, because two sources feed this. */
93
+ const field = (window, camel, snake) => {
94
+ const value = window?.[camel] ?? window?.[snake];
95
+ return Number.isFinite(value) ? value : null;
96
+ };
97
+
98
+ /**
99
+ * WHAT TO CALL A WINDOW OF THIS MANY MINUTES, in the words she already uses.
100
+ *
101
+ * Her two names came off and no new vocabulary is invented for a span she has a
102
+ * word for. Anything that is neither of those two is named after its own length
103
+ * rather than guessed at, because a limit drawn under the wrong span is worse
104
+ * than one drawn under a plain one.
105
+ */
106
+ export function windowSpan(minutes) {
107
+ if (!Number.isFinite(minutes) || minutes <= 0) return null;
108
+ if (minutes <= 60 * 12) return ['session', 'This session'];
109
+ if (minutes >= 60 * 24 * 6 && minutes <= 60 * 24 * 8) return ['week', 'This week'];
110
+ if (minutes >= 60 * 24 * 27 && minutes <= 60 * 24 * 32) return ['month', 'This month'];
111
+ const days = Math.round(minutes / (60 * 24));
112
+ if (days >= 2) return ['window', `These ${days} days`];
113
+ const hours = Math.round(minutes / 60);
114
+ return ['window', `These ${hours} hours`];
115
+ }
116
+
117
+ /**
118
+ * EVERY WINDOW THE SNAPSHOT REALLY CARRIES, in the shape shared/usage.mjs draws.
119
+ *
120
+ * `qualifier` is null and stays null: it is Claude Code's own word for which
121
+ * model a weekly line is about, and nothing in this payload is about a model.
122
+ * `resetsText`, `resetsOn` and `zone` are null for the same kind of reason --
123
+ * Codex prints no clock time and names no timezone, it hands over the instant
124
+ * itself, so the panel counts down to it rather than re-deriving a wall time we
125
+ * were never given.
126
+ */
127
+ export function codexLimits(snapshot) {
128
+ const out = [];
129
+ for (const key of WINDOWS) {
130
+ const window = snapshot?.[key];
131
+ if (!plain(window)) continue;
132
+ const percent = field(window, 'usedPercent', 'used_percent');
133
+ if (percent === null) continue;
134
+ const minutes = field(window, 'windowDurationMins', 'window_minutes');
135
+ const [span, name] = windowSpan(minutes) ?? FALLBACK[key];
136
+ const resets = field(window, 'resetsAt', 'resets_at');
137
+ out.push({
138
+ span,
139
+ qualifier: null,
140
+ name,
141
+ percent: Math.max(0, Math.min(100, Math.round(percent))),
142
+ resetsText: null,
143
+ resetsOn: null,
144
+ resetsAt: resets === null ? null : resets * 1000,
145
+ zone: null,
146
+ });
147
+ }
148
+ return out;
149
+ }
150
+
151
+ /**
152
+ * FOLD A ROLLING UPDATE INTO WHAT WE ALREADY HAD, WHICH IS THE PROTOCOL'S OWN
153
+ * INSTRUCTION AND NOT A KINDNESS.
154
+ *
155
+ * A CLIENT THAT REPLACED WOULD EMPTY THE PANEL AT RANDOM. Every notification
156
+ * carrying only the five hour window would take the week away, and one carrying
157
+ * neither would take both -- and this repo already puts exactly that on the wire
158
+ * (`{ rateLimits: {} }`, in
159
+ * tests/one-codex-process-carries-many-threads-and-a-card-nobody-answers-denies.test.mjs).
160
+ * The failure would look like the corner going quiet for no reason, which is the
161
+ * complaint this whole surface started from.
162
+ *
163
+ * AND FOLDING IS THE WHOLE OF IT, because the push is the whole of the input.
164
+ * `previous` is null exactly once, on the first update ever seen: there is no
165
+ * read response to start from, deliberately (main/codex-usage.mjs). The schema's
166
+ * other instruction -- "or refetch that snapshot" -- is the branch this app does
167
+ * not take, and the reason is written down there rather than repeated here.
168
+ *
169
+ * Only the two windows are carried. Nothing else in the payload is drawn, and a
170
+ * merge that kept fields nobody reads would be inviting somebody to read them.
171
+ */
172
+ export function mergeRateLimits(previous, update) {
173
+ const out = {};
174
+ for (const key of WINDOWS) {
175
+ if (plain(update?.[key])) out[key] = update[key];
176
+ else if (plain(previous?.[key])) out[key] = previous[key];
177
+ else out[key] = null;
178
+ }
179
+ return out;
180
+ }