agentbox-app 0.1.12 → 0.1.16

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 (370) hide show
  1. package/README.md +24 -9
  2. package/bin/agentbox.mjs +23 -1
  3. package/briefs/message-rules.md +36 -8
  4. package/briefs/worker.md +46 -23
  5. package/main/account-discovery.mjs +122 -30
  6. package/main/account-tooling.mjs +29 -1
  7. package/main/account-usage.mjs +46 -0
  8. package/main/acp.mjs +296 -0
  9. package/main/agent-homes.mjs +158 -0
  10. package/main/agent-sessions.mjs +311 -260
  11. package/main/agent-updates.mjs +9 -22
  12. package/main/agents.mjs +179 -466
  13. package/main/analytics.mjs +42 -2
  14. package/main/answer-modes.mjs +103 -37
  15. package/main/approval-prompt-core.mjs +197 -0
  16. package/main/approval-prompt-server.mjs +4 -138
  17. package/main/approvals.mjs +101 -297
  18. package/main/artifact-path.mjs +47 -18
  19. package/main/claude-bin.mjs +45 -311
  20. package/main/claude-input.mjs +57 -7
  21. package/main/claude-launch-env.mjs +54 -0
  22. package/main/claude-models.mjs +39 -7
  23. package/main/claude-plan.mjs +64 -16
  24. package/main/claude-session-open.mjs +28 -0
  25. package/main/claude-usage-process.mjs +40 -0
  26. package/main/claude-usage.mjs +94 -59
  27. package/main/cloud/account.mjs +179 -0
  28. package/main/cloud/aws.mjs +127 -0
  29. package/main/cloud/browser-return.mjs +58 -0
  30. package/main/cloud/clients.mjs +15 -0
  31. package/main/cloud/codex-cloud.mjs +53 -0
  32. package/main/cloud/config.mjs +107 -0
  33. package/main/cloud/docs.mjs +219 -0
  34. package/main/cloud/exec.mjs +27 -0
  35. package/main/cloud/host.mjs +165 -0
  36. package/main/cloud/kit.mjs +80 -0
  37. package/main/cloud/logins.mjs +543 -0
  38. package/main/cloud/managed.mjs +115 -0
  39. package/main/cloud/marker.mjs +104 -0
  40. package/main/cloud/org.mjs +53 -0
  41. package/main/cloud/reuse.mjs +208 -0
  42. package/main/cloud/run-cli.mjs +177 -0
  43. package/main/cloud/run.mjs +1934 -0
  44. package/main/cloud/usage.mjs +76 -0
  45. package/main/cloud-permission.mjs +51 -0
  46. package/main/codex-account.mjs +78 -0
  47. package/main/codex-app-server.mjs +276 -409
  48. package/main/codex-approvals.mjs +148 -433
  49. package/main/codex-bin.mjs +51 -249
  50. package/main/codex-models.mjs +20 -2
  51. package/main/codex-session.mjs +246 -506
  52. package/main/codex-threads-worker.mjs +12 -0
  53. package/main/codex-threads.mjs +112 -11
  54. package/main/codex-usage-file.mjs +5 -3
  55. package/main/codex-usage.mjs +56 -7
  56. package/main/codex-watch.mjs +83 -5
  57. package/main/codex.mjs +95 -363
  58. package/main/config.mjs +37 -5
  59. package/main/corner-tag.mjs +810 -0
  60. package/main/crash-report.mjs +26 -3
  61. package/main/dev-window.mjs +1 -1
  62. package/main/engine-bin.mjs +545 -0
  63. package/main/engine-setup.mjs +25 -24
  64. package/main/feedback.mjs +14 -31
  65. package/main/folder-pictures.mjs +2 -2
  66. package/main/freeze-recorder-thread.mjs +86 -0
  67. package/main/freeze-recorder.mjs +146 -0
  68. package/main/fresh-user.mjs +20 -9
  69. package/main/git-change-offthread.mjs +17 -3
  70. package/main/git-change-thread.mjs +5 -2
  71. package/main/harnesses/claude-stream.mjs +208 -0
  72. package/main/harnesses/claude.mjs +105 -0
  73. package/main/harnesses/codex.mjs +72 -0
  74. package/main/harnesses/common.mjs +78 -0
  75. package/main/harnesses/instructions-file.mjs +116 -0
  76. package/main/harnesses/opencode.mjs +234 -0
  77. package/main/harnesses.mjs +65 -0
  78. package/main/importable-threads.mjs +96 -0
  79. package/main/ipc.mjs +520 -135
  80. package/main/leftovers.mjs +117 -25
  81. package/main/liquid-glass.mjs +26 -0
  82. package/main/live-replies.mjs +10 -4
  83. package/main/local-app-frames.mjs +82 -0
  84. package/main/main-went-red.mjs +330 -0
  85. package/main/main.mjs +248 -37
  86. package/main/memory-gate-history.mjs +85 -20
  87. package/main/memory-gate-server.mjs +129 -11
  88. package/main/memory-gate.mjs +62 -8
  89. package/main/memory-log.mjs +124 -0
  90. package/main/menu.mjs +4 -2
  91. package/main/message-priority.mjs +2 -2
  92. package/main/mic.mjs +38 -0
  93. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  94. package/main/new-chats-watch.mjs +110 -0
  95. package/main/new-user-window.mjs +25 -0
  96. package/main/notify.mjs +64 -5
  97. package/main/opencode-server.mjs +271 -0
  98. package/main/opencode.mjs +287 -0
  99. package/main/phone-alerts.mjs +117 -0
  100. package/main/phone-inbox.mjs +625 -0
  101. package/main/phone-link.mjs +571 -0
  102. package/main/phone-uploads.mjs +476 -0
  103. package/main/picture-shape.mjs +114 -0
  104. package/main/quit-sequence.mjs +143 -0
  105. package/main/refresh-agent-models.mjs +15 -2
  106. package/main/relay-address.mjs +28 -0
  107. package/main/relay-client.mjs +314 -0
  108. package/main/repeats.mjs +10 -4
  109. package/main/row-label.mjs +18 -47
  110. package/main/run-change.mjs +13 -0
  111. package/main/run-ledger.mjs +135 -0
  112. package/main/runs-on.mjs +32 -35
  113. package/main/save-attachments.mjs +41 -0
  114. package/main/serve.mjs +155 -9
  115. package/main/settings.mjs +364 -50
  116. package/main/shell-path.mjs +234 -0
  117. package/main/ship-conflict-brief.mjs +119 -0
  118. package/main/ship-queue.mjs +134 -20
  119. package/main/sign-in-files.mjs +3 -7
  120. package/main/source-updater.mjs +45 -11
  121. package/main/stage-attachment.mjs +68 -0
  122. package/main/stall-watch.mjs +50 -0
  123. package/main/start-hold.mjs +73 -0
  124. package/main/store/home.mjs +87 -232
  125. package/main/store/paths.mjs +11 -0
  126. package/main/store/teammates-files.mjs +99 -0
  127. package/main/store/work-items.mjs +231 -13
  128. package/main/store.mjs +227 -53
  129. package/main/supervisor.mjs +3865 -699
  130. package/main/task-commands.mjs +13 -17
  131. package/main/task-folders-offthread.mjs +6 -1
  132. package/main/task-folders.mjs +897 -105
  133. package/main/task-terminals.mjs +147 -41
  134. package/main/team/index.mjs +114 -10
  135. package/main/team/memory-cloud.mjs +183 -1
  136. package/main/team/room-hours.mjs +48 -0
  137. package/main/team/room-setup.mjs +31 -0
  138. package/main/team/rooms-memory.mjs +454 -0
  139. package/main/team/rooms-shape.mjs +42 -0
  140. package/main/team/rooms-supabase.mjs +324 -0
  141. package/main/team/rooms.mjs +575 -0
  142. package/main/team/session.mjs +96 -26
  143. package/main/team/supabase-backend.mjs +180 -0
  144. package/main/team/sync.mjs +109 -1
  145. package/main/teammates.mjs +580 -0
  146. package/main/terminal-host.mjs +143 -0
  147. package/main/terminal-hosts.mjs +261 -0
  148. package/main/transcript-search.mjs +80 -0
  149. package/main/updater.mjs +71 -9
  150. package/main/webview-guard.mjs +45 -0
  151. package/main/worktree-include.mjs +365 -0
  152. package/mcp/core/documents.mjs +3 -1
  153. package/mcp/core/repeats.mjs +88 -0
  154. package/mcp/core/teammates.mjs +222 -0
  155. package/mcp/core/work.mjs +76 -4
  156. package/mcp/stdio.mjs +24 -11
  157. package/mcp/tools.mjs +116 -8
  158. package/package.json +30 -5
  159. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
  160. package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
  161. package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
  162. package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
  163. package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
  164. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  165. package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
  166. package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
  167. package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
  168. package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
  169. package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
  170. package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
  171. package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
  172. package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
  173. package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
  174. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
  175. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  176. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  177. package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
  178. package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
  179. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
  180. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  181. package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
  182. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  183. package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
  184. package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
  185. package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
  186. package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
  187. package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
  188. package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
  189. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
  190. package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
  191. package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
  192. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
  193. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
  194. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
  195. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  196. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
  197. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  198. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
  199. package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
  200. package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
  201. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  202. package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
  203. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  204. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  205. package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
  206. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
  207. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  208. package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
  209. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
  210. package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
  211. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
  212. package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
  213. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
  214. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
  215. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
  216. package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
  217. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
  218. package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
  219. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  220. package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
  221. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
  222. package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
  223. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
  224. package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
  225. package/renderer/dist/corner-tag.html +13 -0
  226. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  227. package/renderer/dist/emulator/data/compression/README.md +11 -0
  228. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  229. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  230. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  231. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  232. package/renderer/dist/emulator/data/cores/README.md +22 -0
  233. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  234. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  235. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  236. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  237. package/renderer/dist/emulator/data/cores/package.json +65 -0
  238. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  239. package/renderer/dist/emulator/data/emulator.css +1614 -0
  240. package/renderer/dist/emulator/data/loader.js +170 -0
  241. package/renderer/dist/emulator/data/localization/README.md +63 -0
  242. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  243. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  244. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  245. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  246. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  247. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  248. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  249. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  250. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  251. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  252. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  253. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  254. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  255. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  256. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  257. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  258. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  259. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  260. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  261. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  262. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  263. package/renderer/dist/emulator/data/src/compression.js +142 -0
  264. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  265. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  266. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  267. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  268. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  269. package/renderer/dist/emulator/data/src/storage.js +125 -0
  270. package/renderer/dist/emulator/data/version.json +5 -0
  271. package/renderer/dist/emulator/host.js +297 -0
  272. package/renderer/dist/emulator/index.html +28 -0
  273. package/renderer/dist/emulator/provenance.json +6 -0
  274. package/renderer/dist/index.html +16 -3
  275. package/scripts/after-install.mjs +30 -8
  276. package/scripts/approval-server.sh +24 -5
  277. package/scripts/before-publish.mjs +12 -10
  278. package/scripts/cloud-shim.pl +57 -0
  279. package/shared/account-names.d.mts +6 -0
  280. package/shared/account-names.mjs +15 -0
  281. package/shared/agent-hours.mjs +136 -0
  282. package/shared/agent-import.mjs +62 -34
  283. package/shared/agents.d.mts +1 -0
  284. package/shared/agents.mjs +126 -522
  285. package/shared/analytics-events.mjs +12 -0
  286. package/shared/approval-card.mjs +36 -0
  287. package/shared/bridge-map.mjs +45 -1
  288. package/shared/claude-code-shape.mjs +6 -0
  289. package/shared/claude-commands.generated.mjs +6 -4
  290. package/shared/claude-models.generated.mjs +6 -4
  291. package/shared/claude-usage.d.mts +1 -0
  292. package/shared/claude-usage.mjs +42 -1
  293. package/shared/cloud-hand-back.d.mts +7 -0
  294. package/shared/cloud-hand-back.mjs +20 -0
  295. package/shared/cloud-kit.d.mts +219 -0
  296. package/shared/cloud-kit.mjs +341 -0
  297. package/shared/cloud-login-name.mjs +4 -0
  298. package/shared/cloud-start.mjs +17 -0
  299. package/shared/cloud-versions.mjs +23 -0
  300. package/shared/codex-import.mjs +2 -2
  301. package/shared/codex-modes.d.mts +22 -9
  302. package/shared/codex-modes.mjs +67 -11
  303. package/shared/codex-usage.mjs +108 -2
  304. package/shared/corner-tag.d.mts +36 -0
  305. package/shared/corner-tag.mjs +396 -0
  306. package/shared/dead-run-trace.mjs +19 -2
  307. package/shared/engines.mjs +76 -300
  308. package/shared/feedback.d.mts +7 -0
  309. package/shared/feedback.mjs +25 -0
  310. package/shared/first-run-practice.mjs +17 -24
  311. package/shared/harness-definitions.mjs +111 -0
  312. package/shared/instruction-defaults.json +2 -2
  313. package/shared/key-caps.d.mts +14 -0
  314. package/shared/key-caps.mjs +68 -0
  315. package/shared/message-edits.d.mts +7 -0
  316. package/shared/message-edits.mjs +35 -0
  317. package/shared/move-thread.d.mts +24 -0
  318. package/shared/move-thread.mjs +112 -0
  319. package/shared/named-threads.mjs +72 -0
  320. package/shared/not-shipped.d.mts +22 -0
  321. package/shared/not-shipped.mjs +89 -0
  322. package/shared/notify-rules.d.mts +31 -0
  323. package/shared/notify-rules.mjs +55 -8
  324. package/shared/phone-files.mjs +28 -0
  325. package/shared/phone-settings.mjs +118 -0
  326. package/shared/phone-snapshot.d.mts +16 -0
  327. package/shared/phone-snapshot.mjs +348 -0
  328. package/shared/picture-formats.d.mts +1 -0
  329. package/shared/picture-formats.mjs +13 -0
  330. package/shared/provider-commands.mjs +10 -2
  331. package/shared/rank.d.mts +4 -1
  332. package/shared/rank.mjs +82 -19
  333. package/shared/relay-address.mjs +6 -0
  334. package/shared/relay-crypto.mjs +165 -0
  335. package/shared/repeats.d.mts +8 -0
  336. package/shared/repeats.mjs +33 -2
  337. package/shared/room-cards.mjs +167 -0
  338. package/shared/room-links.d.mts +4 -0
  339. package/shared/room-links.mjs +21 -0
  340. package/shared/runs-on.d.mts +3 -13
  341. package/shared/runs-on.mjs +31 -52
  342. package/shared/shipped-note.d.mts +4 -0
  343. package/shared/shipped-note.mjs +6 -0
  344. package/shared/shortcuts.mjs +24 -10
  345. package/shared/spawn-trouble.d.mts +4 -0
  346. package/shared/spawn-trouble.mjs +484 -30
  347. package/shared/team-rules.mjs +9 -2
  348. package/shared/teammates.d.mts +19 -0
  349. package/shared/teammates.mjs +756 -0
  350. package/shared/terminal-focus.d.mts +1 -0
  351. package/shared/terminal-focus.mjs +13 -0
  352. package/shared/terminal-input.d.mts +1 -0
  353. package/shared/terminal-input.mjs +30 -0
  354. package/shared/terminal-keys.d.mts +1 -0
  355. package/shared/terminal-keys.mjs +16 -0
  356. package/shared/terminal-size.d.mts +4 -0
  357. package/shared/terminal-size.mjs +29 -0
  358. package/shared/thread-cards.mjs +4 -1
  359. package/shared/usage.d.mts +12 -0
  360. package/shared/usage.mjs +56 -3
  361. package/shared/websites.d.mts +15 -0
  362. package/shared/websites.mjs +61 -0
  363. package/shared/where-it-runs-from.d.mts +43 -0
  364. package/shared/where-it-runs-from.mjs +155 -0
  365. package/shared/work-items.mjs +150 -29
  366. package/shared/work-lines.mjs +93 -245
  367. package/shared/xterm-invalid-range.d.mts +2 -0
  368. package/shared/xterm-invalid-range.mjs +58 -0
  369. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  370. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -0,0 +1,348 @@
1
+ // ONE THREAD, AS HER PHONE SEES IT WITH THE MAC SHUT (docs/cloud/PHONE-PLAN.md).
2
+ //
3
+ // Her Mac writes this for each thread of a project she put on her phone, and
4
+ // the phone reads it. It is what the phone's own screens already draw: the row
5
+ // as the Mac folded it (the phone never decides a thread's state from raw
6
+ // lines; Codex's review of the plan, finding 5), and the thread's history as
7
+ // the window reads it (store.readHistory), which the thread view turns into
8
+ // the conversation. Long text is cut, and only the latest lines go. Browser-
9
+ // safe: no node imports.
10
+
11
+ import { checkSetting } from './phone-settings.mjs';
12
+
13
+ // How much of one thread goes up. The latest of a thread is what she reads on
14
+ // a phone; the whole of it stays on the Mac. `bytes` is measured in UTF-8, as
15
+ // the database measures it (Codex's review of step 1, finding 9).
16
+ export const SNAPSHOT_LIMITS = { lines: 400, textChars: 12_000, items: 300, bytes: 900_000 };
17
+
18
+ // WHAT A COPY MAY CARRY, BY NAME (Codex's review of step 1, finding 2). A copy
19
+ // is built out of these and nothing else, never by copying a row and deleting
20
+ // the bad parts: a lease, an epoch, who may see a team thread, a field some
21
+ // later version adds, none of it leaves unless it is named here.
22
+ const ROW_FIELDS = [
23
+ 'id', 'title', 'label', 'body', 'kind', 'labels', 'priority', 'parent', 'status',
24
+ 'result', 'note', 'answer', 'runAt', 'snoozedUntil', 'answeredThrough',
25
+ 'engine', 'model', 'start', 'due', 'problem', 'progress', 'solution',
26
+ 'createdAt', 'updatedAt',
27
+ ];
28
+ // The fields a line in a thread's history may carry in its patch: what the
29
+ // thread view draws as the conversation and its state changes.
30
+ const LINE_FIELDS = [
31
+ 'title', 'label', 'body', 'kind', 'labels', 'priority', 'status', 'result',
32
+ 'note', 'answer', 'runAt', 'snoozedUntil', 'start', 'due', 'problem',
33
+ 'progress', 'solution', 'inReplyTo',
34
+ ];
35
+ const SOURCES = new Set(['founder', 'agent', 'system']);
36
+
37
+ // KEYS AND TOKENS IN WHAT AN AGENT OR SHE WROTE, hidden. A conversation can
38
+ // quote one ("here is the key it used"), and a copy online is no place for it.
39
+ const SECRETS = [
40
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g,
41
+ /\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}/g,
42
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}\b/g,
43
+ /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g,
44
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
45
+ /\bxox[abposr]-[A-Za-z0-9-]{10,}/g,
46
+ /\bAIza[0-9A-Za-z_-]{35}\b/g,
47
+ /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
48
+ /\b(Bearer\s+)[A-Za-z0-9._~+/-]{20,}=*/gi,
49
+ /\b(Basic\s+)[A-Za-z0-9+/]{12,}=*/gi,
50
+ ];
51
+ // A VALUE GIVEN A SECRET'S NAME, whatever it looks like (Codex's second and
52
+ // third reviews): `AWS_SECRET_ACCESS_KEY=...`, `password: hunter22`,
53
+ // `PASSWORD=12345678`, `password="correct horse battery staple"`. The name
54
+ // stays, so the sentence still reads; a quoted value goes whole.
55
+ const STRONG = '[A-Za-z0-9_.-]*(?:passw(?:or)?d|passphrase|secret|api[_-]?key|access[_-]?key|private[_-]?key|credentials?|authorization|(?:auth|access|refresh|session|bearer|id)[_-]?token)[A-Za-z0-9_.-]*';
56
+ // "token" alone is a word people use ("token: unexpected identifier"), so it
57
+ // counts only as an ENV_STYLE name, or before a long run that looks like a key.
58
+ const WEAK = '(?:[A-Z0-9_]*TOKEN[A-Z0-9_]*|token)';
59
+ // A quoted value goes whole, however long, escaped quotes and all; a bare one
60
+ // is everything up to a space, brackets included (Codex's fourth review).
61
+ const VALUE = '(?:"((?:[^"\\\\\\n]|\\\\.)*)"|\'((?:[^\'\\\\\\n]|\\\\.)*)\'|([^\\s"\',;]+))';
62
+ const NAMED_STRONG = new RegExp(`\\b(${STRONG})(["']?\\s*[:=]\\s*)${VALUE}`, 'gi');
63
+ const NAMED_WEAK = new RegExp(`\\b(${WEAK})(["']?\\s*[:=]\\s*)${VALUE}`, 'g');
64
+ const hidden = (name, sep, dq, sq, bare) => `${name}${sep}${dq !== undefined ? '"[hidden]"' : sq !== undefined ? "'[hidden]'" : '[hidden]'}`;
65
+ function hideNamed(text) {
66
+ let out = text.replace(NAMED_STRONG, (m, name, sep, dq, sq, bare) => (bare === '[hidden]' ? m : hidden(name, sep, dq, sq, bare)));
67
+ out = out.replace(NAMED_WEAK, (m, name, sep, dq, sq, bare) => {
68
+ const value = dq ?? sq ?? bare ?? '';
69
+ // MAX_TOKENS=128000 and TOKEN_LIMIT=8192 are settings: a bare number is
70
+ // never taken for a key under this weaker name.
71
+ const envStyle = name !== 'token' && name === name.toUpperCase() && !/^[0-9]+$/.test(value);
72
+ const keyLike = value.length >= 16 && /[0-9]/.test(value) && !/\s/.test(value);
73
+ return value !== '[hidden]' && (envStyle || keyLike) ? hidden(name, sep, dq, sq, bare) : m;
74
+ });
75
+ return out;
76
+ }
77
+ // AND A LONG RUN OF LETTERS AND DIGITS THAT LOOKS LIKE NOTHING BUT A KEY: 32 or
78
+ // more, upper and lower case and digits all present. A commit id or a uuid is
79
+ // one case of hex and is left alone.
80
+ const KEYLIKE = /\b(?=[A-Za-z0-9_-]{32,}\b)(?=[A-Za-z0-9_-]*[a-z])(?=[A-Za-z0-9_-]*[A-Z])(?=[A-Za-z0-9_-]*[0-9])[A-Za-z0-9_-]{32,}\b/g;
81
+ export function hideSecrets(text, { home = null } = {}) {
82
+ let out = String(text);
83
+ for (const re of SECRETS) out = out.replace(re, (m, bearer) => (typeof bearer === 'string' && /^(Bearer|Basic)/i.test(bearer) ? `${bearer}[hidden]` : '[hidden]'));
84
+ out = hideNamed(out);
85
+ out = out.replace(KEYLIKE, '[hidden]');
86
+ // Her home folder is ~ online, as it is in a crash report (shared/crash-scrub.mjs).
87
+ if (home && home.length > 1) out = out.split(home).join('~');
88
+ return out;
89
+ }
90
+
91
+ const clip = (s, n, ctx) => {
92
+ const t = hideSecrets(s, ctx);
93
+ return t.length > n ? `${t.slice(0, n)}…` : t;
94
+ };
95
+ // A value of a named field: text cut and cleaned, numbers as they are, short
96
+ // lists of words, nothing nested.
97
+ function plain(v, n, ctx) {
98
+ if (typeof v === 'string') return clip(v, n, ctx);
99
+ if (typeof v === 'number' && Number.isFinite(v)) return v;
100
+ if (typeof v === 'boolean' || v === null) return v;
101
+ if (Array.isArray(v)) return v.filter((x) => typeof x === 'string').slice(0, 40).map((x) => clip(x, 200, ctx));
102
+ return undefined;
103
+ }
104
+ function pick(obj, fields, n, ctx) {
105
+ const out = {};
106
+ for (const f of fields) {
107
+ if (!obj || !(f in obj)) continue;
108
+ const v = plain(obj[f], n, ctx);
109
+ if (v !== undefined) out[f] = v;
110
+ }
111
+ return out;
112
+ }
113
+
114
+ export const bytesOf = (value) => new TextEncoder().encode(JSON.stringify(value)).length;
115
+
116
+ // `item` is the folded row; `history` is store.readHistory for it. `home` is
117
+ // her home folder, written ~ in the copy.
118
+ export function threadSnapshot(item, history = [], limits = SNAPSHOT_LIMITS, { home = null } = {}) {
119
+ const ctx = { home };
120
+ const row = pick(item, ROW_FIELDS, limits.textChars, ctx);
121
+ // Who wrote each field, which is how the screens tell her words from an
122
+ // agent's: the source and the moment, nothing else of it.
123
+ if (item?.wrote && typeof item.wrote === 'object') {
124
+ const wrote = {};
125
+ for (const f of ROW_FIELDS) {
126
+ const w = item.wrote[f];
127
+ if (w && SOURCES.has(w.source) && Number.isFinite(w.ts)) wrote[f] = { ts: w.ts, source: w.source };
128
+ }
129
+ row.wrote = wrote;
130
+ }
131
+ const lines = [];
132
+ for (const l of Array.isArray(history) ? history : []) {
133
+ if (!l || l.claim || l.release || l.heartbeat || !SOURCES.has(l.source) || !Number.isFinite(l.ts)) continue;
134
+ const patch = pick(l.patch, LINE_FIELDS, limits.textChars, ctx);
135
+ if (!Object.keys(patch).length) continue;
136
+ lines.push({ id: row.id, ts: l.ts, source: l.source, patch });
137
+ }
138
+ // Under the database's megabyte, with room: fewer lines until it fits, and
139
+ // then shorter text, so one long thread never stops the rest being sent.
140
+ for (let keep = limits.lines, chars = limits.textChars; ;) {
141
+ const kept = lines.slice(-keep);
142
+ const snap = {
143
+ v: 3,
144
+ item: chars === limits.textChars ? row : pick(row, ROW_FIELDS, chars, ctx),
145
+ history: chars === limits.textChars ? kept : kept.map((l) => ({ ...l, patch: pick(l.patch, LINE_FIELDS, chars, ctx) })),
146
+ olderLines: lines.length - kept.length,
147
+ };
148
+ if (row.wrote && snap.item !== row) snap.item.wrote = row.wrote;
149
+ if (bytesOf(snap) <= limits.bytes) return snap;
150
+ if (keep > 1) keep = Math.floor(keep / 2);
151
+ else if (chars > 200) chars = Math.floor(chars / 4);
152
+ else return { v: 3, item: { id: row.id, title: clip(row.title ?? '', 200, ctx), status: row.status, updatedAt: row.updatedAt }, history: [], olderLines: lines.length };
153
+ }
154
+ }
155
+
156
+ // WHICH PROJECTS ARE ON THE PHONE OF THE PERSON SIGNED IN.
157
+ //
158
+ // ALL OF THEM, UNLESS SHE SAYS OTHERWISE (2026-10-15: "95% of people just want
159
+ // all of their projects connected ... I just turn them all on"). One switch
160
+ // for the lot, on until she turns it off, and new projects go up with the
161
+ // rest; keeping a project on this computer only is the exception, named
162
+ // project by project (`except`).
163
+ //
164
+ // Her choice is kept with her id on it (`phoneProducts` in the config:
165
+ // { owner, all, except }), so somebody else signing in on this computer starts
166
+ // from the default rather than from her choices (Codex's review of step 1,
167
+ // finding 1). A choice saved in the first shape, a list of the projects she
168
+ // turned on ({ owner, slugs }), reads as all of them except the ones that list
169
+ // left out, so the projects she had on stay on.
170
+ // `every` is every project here, by slug.
171
+ // The projects that can go to a phone at all: not the walk's practice project.
172
+ export const phoneEligible = (products) => (Array.isArray(products) ? products : []).map((p) => p?.slug).filter((s) => typeof s === 'string' && s && s !== 'practice');
173
+ export function phoneChoiceOf(saved, personId, every = []) {
174
+ const mine = personId && saved && typeof saved === 'object' && saved.owner === personId ? saved : null;
175
+ if (!mine) return { all: true, except: [] };
176
+ if (mine.all === undefined && Array.isArray(mine.slugs)) {
177
+ const on = new Set(mine.slugs);
178
+ return { all: true, except: every.filter((s) => !on.has(s)) };
179
+ }
180
+ return { all: mine.all !== false, except: Array.isArray(mine.except) ? mine.except.filter((s) => typeof s === 'string' && s) : [] };
181
+ }
182
+ export function phoneProjectsOf(saved, personId, every = []) {
183
+ if (!personId) return [];
184
+ const { all, except } = phoneChoiceOf(saved, personId, every);
185
+ if (!all) return [];
186
+ const kept = new Set(except);
187
+ return every.filter((s) => typeof s === 'string' && s && !kept.has(s));
188
+ }
189
+ // One project on or off the phone; the rest as they were.
190
+ export function withPhoneProject(saved, personId, slug, on, every = []) {
191
+ if (!personId) throw new Error('sign in first');
192
+ const { all, except } = phoneChoiceOf(saved, personId, every);
193
+ const kept = new Set(except);
194
+ if (on) kept.delete(slug); else kept.add(slug);
195
+ return { owner: personId, all, except: [...kept] };
196
+ }
197
+ // The whole phone on or off; which projects stay local is remembered.
198
+ export function withPhoneAll(saved, personId, on, every = []) {
199
+ if (!personId) throw new Error('sign in first');
200
+ const { except } = phoneChoiceOf(saved, personId, every);
201
+ return { owner: personId, all: !!on, except };
202
+ }
203
+
204
+ // A NEW THREAD STARTED ON THE PHONE has a row there before the Mac makes it,
205
+ // under this id, and a reply to it before then is a reply to this id. The Mac
206
+ // remembers which real row it became (main/phone-inbox.mjs) (Codex's review of
207
+ // step 1, finding 8). Shaped like a row id, so every screen takes it.
208
+ export const madeHere = (commandId) => `w-${String(commandId).replace(/[^0-9a-f]/gi, '').slice(0, 10).toLowerCase()}`;
209
+
210
+ // The threads of a project worth carrying: everything not yet archived, and
211
+ // what was finished in the last fortnight, newest first, at most `items`.
212
+ export function threadsWorthCarrying(items, { now = Date.now(), limits = SNAPSHOT_LIMITS } = {}) {
213
+ const fortnight = 14 * 24 * 3600_000;
214
+ return (Array.isArray(items) ? items : [])
215
+ .filter((i) => i && !i.agent && (i.status !== 'done' || now - (i.updatedAt ?? 0) < fortnight))
216
+ .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))
217
+ .slice(0, limits.items);
218
+ }
219
+
220
+ // A cheap, stable fingerprint, so a thread that did not change is not sent.
221
+ export function snapshotKey(snapshot) {
222
+ const text = JSON.stringify(snapshot);
223
+ let h = 2166136261;
224
+ for (let i = 0; i < text.length; i += 1) { h ^= text.charCodeAt(i); h = Math.imul(h, 16777619); }
225
+ return `${text.length}:${(h >>> 0).toString(36)}`;
226
+ }
227
+
228
+ // WHAT HER PHONE MAY ASK HER MAC TO DO, and nothing else. A command that is not
229
+ // one of these, or names a project she did not put on her phone, or a thread
230
+ // that is not in it, is refused and says why.
231
+ // A `setting` belongs to the computer, not a project (shared/phone-settings.mjs).
232
+ export const PHONE_COMMANDS = ['reply', 'new', 'snooze', 'close', 'stop', 'run-now', 'send-now', 'reopen', 'schedule', 'approve', 'setting'];
233
+
234
+ // A PERMISSION CARD AS HER PHONE DRAWS IT: what the agent asks to do, with the
235
+ // secrets in it hidden as a thread's words are, and the fingerprint of the card
236
+ // her Mac drew, which her answer must carry back.
237
+ export function phoneApproval(request, card, ctx = {}) {
238
+ // Her Allow signs the whole request by its digest, so the phone may carry the
239
+ // digest only when it drew the request whole; a card that hid, clipped or
240
+ // dropped anything carries none and is answered on the computer. A home path
241
+ // written ~ is for her eyes, not a value hidden from what she signs.
242
+ let lossy = false;
243
+ const text = (s, n) => {
244
+ if (hideSecrets(s) !== s) lossy = true;
245
+ const t = hideSecrets(s, ctx);
246
+ if (t.length > n) { lossy = true; return `${t.slice(0, n)}…`; }
247
+ return t;
248
+ };
249
+ const clean = (v, depth = 0) => {
250
+ if (typeof v === 'string') return text(v, 4000);
251
+ if (typeof v === 'number' || typeof v === 'boolean' || v === null) return v;
252
+ if (depth > 3) { lossy = true; return undefined; }
253
+ if (Array.isArray(v)) { if (v.length > 40) lossy = true; return v.slice(0, 40).map((x) => clean(x, depth + 1)); }
254
+ if (v && typeof v === 'object') {
255
+ const entries = Object.entries(v);
256
+ if (entries.length > 40) lossy = true;
257
+ return Object.fromEntries(entries.slice(0, 40).map(([k, x]) => [k, clean(x, depth + 1)]));
258
+ }
259
+ return undefined;
260
+ };
261
+ const shown = { id: String(request.id), at: Number(request.at) || 0, tool: text(String(request.tool ?? 'unknown'), 200), input: clean(request.input ?? {}) };
262
+ return lossy ? { ...shown, card: null, answerOnComputer: true } : { ...shown, card };
263
+ }
264
+
265
+ // WHAT SHE PICKED WITH A REPLY OR A NEW THREAD, as the window's own handlers
266
+ // take it (main/ipc.mjs zero:answer, zero:compose): only these fields, each
267
+ // only in its right shape. Null on a reply clears a choice, as in the window.
268
+ const WORD = /^[A-Za-z][\w.:-]{0,79}$/;
269
+ const word = (v, n = 80) => typeof v === 'string' && v.length <= n && WORD.test(v);
270
+ const orNull = (ok) => (v) => v === null || ok(v);
271
+ const CHOICES = {
272
+ reply: {
273
+ permissionMode: orNull((v) => word(v, 40)), model: orNull(word), effort: orNull((v) => word(v, 20)),
274
+ priority: (v) => Number.isInteger(v) && v >= 0 && v <= 10, now: (v) => v === true, unschedule: (v) => v === true,
275
+ },
276
+ new: {
277
+ runAt: (v) => Number.isFinite(v) && v > 0, start: (v) => v === 'later' || v === 'now',
278
+ engine: (v) => word(v, 40), model: word, effort: (v) => word(v, 20), priority: (v) => Number.isInteger(v) && v >= 0 && v <= 10,
279
+ assignee: (v) => typeof v === 'string' && v.length >= 1 && v.length <= 80, due: (v) => typeof v === 'string' && v.length <= 40,
280
+ visibility: (v) => ['team', 'people', 'private'].includes(v),
281
+ visibleTo: (v) => Array.isArray(v) && v.length <= 50 && v.every((x) => typeof x === 'string' && x.length <= 80),
282
+ },
283
+ };
284
+ export function phoneChoices(kind, p) {
285
+ const out = {};
286
+ for (const [k, ok] of Object.entries(CHOICES[kind] ?? {})) if (p && k in p && p[k] !== undefined && ok(p[k])) out[k] = p[k];
287
+ return out;
288
+ }
289
+ export function checkCommand(cmd, { projects, hasItem }) {
290
+ if (!cmd || !PHONE_COMMANDS.includes(cmd.kind)) return 'not something the phone may do';
291
+ if (cmd.kind === 'setting') return cmd.project != null || cmd.item != null ? 'a setting names no project or thread' : checkSetting(cmd.payload);
292
+ if (!projects.includes(cmd.project)) return 'that project is not on your phone';
293
+ const p = cmd.payload || {};
294
+ if (cmd.kind === 'new') {
295
+ const title = typeof p.title === 'string' ? p.title.trim() : '';
296
+ const body = typeof p.body === 'string' ? p.body.trim() : '';
297
+ if (!title && !body) return 'a new thread needs some words';
298
+ return null;
299
+ }
300
+ if (!cmd.item || !hasItem(cmd.project, cmd.item)) return 'that thread is not on your phone';
301
+ if (cmd.kind === 'reply' && !(typeof p.text === 'string' && p.text.trim())) return 'an empty reply';
302
+ if (cmd.kind === 'snooze' && !(Number.isFinite(p.until) && p.until >= 0)) return 'a snooze needs a time';
303
+ if (cmd.kind === 'schedule' && !(Number.isFinite(p.runAt) && p.runAt >= 0)) return 'a schedule needs a time';
304
+ if (cmd.kind === 'approve' && !(typeof p.approval === 'string' && /^[\w-]{1,80}$/.test(p.approval) && typeof p.allow === 'boolean'
305
+ && typeof p.card === 'string' && /^[\w+/=-]{8,200}$/.test(p.card) && (p.note === undefined || (typeof p.note === 'string' && p.note.length <= 500)))) return 'an answer needs its card';
306
+ return null;
307
+ }
308
+
309
+ // A TURN THE CLOUD TOOK WITH THE COMPUTER SHUT, written into the thread's copy
310
+ // by the cloud server (cloud/gateway/phone.mjs), as the computer would have
311
+ // written it: her reply, the agent's answer and the row waiting on her
312
+ // (main/store.mjs recordSessionResult). The computer's own copy replaces this
313
+ // one once it is back and has brought the run home.
314
+ export function withCloudTurn(snapshot, { reply, answer, repliedAt, answeredAt, status = 'blocked' }, limits = SNAPSHOT_LIMITS) {
315
+ if (!snapshot || typeof snapshot !== 'object' || !snapshot.item?.id) return null;
316
+ const ctx = { home: null };
317
+ const id = snapshot.item.id;
318
+ // No reply for a new thread's first turn: her ask is already its first line.
319
+ const replied = reply !== null && reply !== undefined;
320
+ const said = clip(String(reply ?? ''), limits.textChars, ctx);
321
+ const result = clip(String(answer ?? ''), limits.textChars, ctx);
322
+ const lines = [
323
+ ...(replied ? [{ id, ts: repliedAt, source: 'founder', patch: { answer: said, status: 'open' } }] : []),
324
+ { id, ts: answeredAt, source: 'agent', patch: { result } },
325
+ { id, ts: answeredAt, source: 'system', patch: { status } },
326
+ ];
327
+ const wrote = { ...(snapshot.item.wrote ?? {}), ...(replied ? { answer: { ts: repliedAt, source: 'founder' } } : {}), result: { ts: answeredAt, source: 'agent' }, status: { ts: answeredAt, source: 'system' } };
328
+ const item = { ...snapshot.item, ...(replied ? { answer: said } : {}), result, status, updatedAt: answeredAt, wrote };
329
+ let history = [...(Array.isArray(snapshot.history) ? snapshot.history : []), ...lines];
330
+ let older = Number(snapshot.olderLines) || 0;
331
+ while (history.length > limits.lines || (history.length > lines.length && bytesOf({ ...snapshot, item, history }) > limits.bytes)) {
332
+ history = history.slice(1);
333
+ older += 1;
334
+ }
335
+ return { ...snapshot, item, history, olderLines: older };
336
+ }
337
+
338
+ // THE PHONE'S COPY OF A THREAD THE CLOUD STARTED FROM THE PHONE, before its
339
+ // computer has one: the row her ask made, being worked on.
340
+ export function newThreadSnapshot({ itemId, title, body = '', at }, limits = SNAPSHOT_LIMITS) {
341
+ const words = { title: String(title ?? ''), ...(body ? { body: String(body) } : {}) };
342
+ const said = { ts: at, source: 'founder' };
343
+ return threadSnapshot(
344
+ { id: itemId, ...words, kind: 'directive', labels: ['founder'], priority: 0, status: 'claimed', createdAt: at, updatedAt: at, wrote: { title: said, ...(body ? { body: said } : {}) } },
345
+ [{ ts: at, source: 'founder', patch: { ...words, status: 'open' } }],
346
+ limits,
347
+ );
348
+ }
@@ -0,0 +1 @@
1
+ export declare function turnedName(name: string | null | undefined): string | null;
@@ -0,0 +1,13 @@
1
+ // PICTURES THE WINDOW CANNOT DRAW, and what each is turned into so it can.
2
+ // Chromium decodes no TIFF and no HEIC, so one pasted or dropped into a reply
3
+ // box is turned into a PNG or a JPEG as it comes in (main/stage-attachment.mjs).
4
+ // Both sides read this: the window to know a dropped file is worth staging,
5
+ // the main process to turn it.
6
+ const TURNED = [[/\.tiff?$/i, '.png'], [/\.hei[cf]$/i, '.jpg']];
7
+
8
+ /** The name the picture goes on under once turned, or null when it is not one of these. */
9
+ export function turnedName(name) {
10
+ const s = String(name ?? '');
11
+ const hit = TURNED.find(([re]) => re.test(s));
12
+ return hit ? s.replace(/\.[^.]+$/, hit[1]) : null;
13
+ }
@@ -1,5 +1,6 @@
1
1
  // A command is an operation, never prose asking a model to imitate a CLI.
2
2
  import { CLAUDE_COMMANDS, commandWords } from './claude-commands.mjs';
3
+ import { harnessDefinition } from './harness-definitions.mjs';
3
4
  const command = (name, description, argumentHint = null, aliases = []) => ({ name, description, menuDescription: null, whole: true, argumentHint, aliases });
4
5
  export const CODEX_COMMANDS = [
5
6
  command('model','Show models or change this task’s model','[model]'),
@@ -23,12 +24,18 @@ export const LOCAL_COMMANDS = CODEX_COMMANDS.filter(c=>['model','effort','diff',
23
24
  // is refused on Codex for now, in a sentence that says why, because that call
24
25
  // has never been made against a real server here.
25
26
  const FORK = command('fork','Start a new task from this conversation','<what to try instead>');
27
+ const COMMAND_MENUS = {
28
+ codex: CODEX_COMMANDS,
29
+ claude: [command('remote-control','Continue this conversation in Claude on another device',null,['rc']),FORK,...CLAUDE_COMMANDS.map(c=>LOCAL_COMMANDS.find(l=>l.name===c.name)??c),...LOCAL_COMMANDS.filter(l=>!CLAUDE_COMMANDS.some(c=>c.name===l.name))],
30
+ local: LOCAL_COMMANDS,
31
+ };
26
32
  export function providerCommands(engine) {
27
33
  // NOT ON THE CODEX MENU, because this menu's rule is that it offers nothing
28
34
  // that engine cannot run, and Codex forking is not connected yet. Typed there
29
35
  // it gets the ordinary unavailable refusal, which points at the native Codex
30
36
  // client, where `codex fork` genuinely works.
31
- return engine==='codex' ? CODEX_COMMANDS : [command('remote-control','Continue this conversation in Claude on another device',null,['rc']),FORK,...CLAUDE_COMMANDS.map(c=>LOCAL_COMMANDS.find(l=>l.name===c.name)??c),...LOCAL_COMMANDS.filter(l=>!CLAUDE_COMMANDS.some(c=>c.name===l.name))];
37
+ const definition = harnessDefinition(engine) ?? harnessDefinition();
38
+ return COMMAND_MENUS[definition.commandMenu] ?? [];
32
39
  }
33
40
  export function providerCommand(text, engine) {
34
41
  if(typeof text!=='string')return null;
@@ -38,7 +45,8 @@ export function providerCommand(text, engine) {
38
45
  const cmd=providerCommands(engine).find(c=>commandWords(c).includes(word));
39
46
  if(!cmd) return {name:word,args,route:'unavailable'};
40
47
  if(cmd.name==='fork')return {name:'fork',args,route:'fork'};
41
- return {name:cmd.name,args,route:cmd.name==='remote-control'?'remote':engine==='codex'?'codex':LOCAL_COMMANDS.some(c=>c.name===cmd.name)?'local':'claude'};
48
+ const route = (harnessDefinition(engine) ?? harnessDefinition()).commandRoute;
49
+ return {name:cmd.name,args,route:cmd.name==='remote-control'?'remote':route==='codex'?'codex':LOCAL_COMMANDS.some(c=>c.name===cmd.name)?'local':route};
42
50
  }
43
51
  export function reviewTarget(args='') {
44
52
  args=args.trim();
package/shared/rank.d.mts CHANGED
@@ -8,7 +8,8 @@ export function placeScore(order: string[], slug: string, direct?: ReadonlySet<s
8
8
 
9
9
  export const DEFAULT_PRIORITY: number;
10
10
 
11
- export function itemPriority(item: { priority?: number; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): number;
11
+ export function itemPriority(item: { priority?: number | null; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): number;
12
+ export function shownPriority(item: { priority?: number | null; wrote?: Record<string, { source?: string } | undefined> } | null | undefined, conversation?: boolean): number | null;
12
13
  export const URGENT_PRIORITY: number;
13
14
  export function isUrgent(item: { priority?: number; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): boolean;
14
15
 
@@ -18,3 +19,5 @@ export function landingIndex(
18
19
  x: number, y: number,
19
20
  ): number;
20
21
  export function moveProduct(order: string[], slug: string, toIndex: number): string[];
22
+ export function placedScore(place: number | null | undefined, natural: number): number;
23
+ export function dropPlaces(rows: Array<{ id: string; score: number }>, from: number, to: number): Record<string, number> | null;
package/shared/rank.mjs CHANGED
@@ -69,19 +69,15 @@ export function placeScore(order, slug, direct) {
69
69
  /**
70
70
  * The number an item carries into that score.
71
71
  *
72
- * An AGENT'S tag counts for nothing. Agents used to set this themselves, on a
73
- * rule in the worker brief that told them 7 meant blocking; of the 442 items
74
- * agents filed, 127 came in at 7 or higher, which is not an order, it is
75
- * noise. She asked for the ordering to be the app's job, the rule is out of
76
- * the brief, and this is the half that also covers the items already sitting
77
- * in the store carrying an agent's 8.
78
- *
79
- * Everything else counts, and the test for "hers" is deliberately wide: her
80
- * compose box writes the number as 'system' rather than 'founder' (the split
81
- * in store.composeItem is words-versus-lifecycle, not authority), and a repeat
82
- * rule's number arrives the same way. Reading only 'founder' here would have
83
- * thrown away the Urgent tag on the very row she complained about
84
- * (tests/urgent-does-not-wait-behind-medium.test.mjs).
72
+ * A LEVEL COUNTS WHOEVER SET IT (w-5ac9c9006d, 2026-10-16). An agent's tag
73
+ * used to count for nothing, because agents once tagged 127 of 442 rows at 7
74
+ * or higher on an old brief rule. That made every level an agent wrote a lie
75
+ * on screen, and her answer was to stop discarding them: "If agent-determined
76
+ * priorities are ignored, then it seems like the whole prioritization system
77
+ * is ignored. If an agent sets something and I'm like, 'Oh that's the wrong
78
+ * priority,' I'll fix it. We should also always treat priorities as the
79
+ * source of truth, regardless of where they came from." Her fix is a new
80
+ * level, and the fold keeps hers over an agent's.
85
81
  *
86
82
  * Nought means untagged, not bottom. It is the seed the fold starts an item
87
83
  * at and the default her compose sends when she picks no number, and her own
@@ -93,11 +89,29 @@ export const DEFAULT_PRIORITY = 5;
93
89
 
94
90
  export function itemPriority(item) {
95
91
  if (!item) return DEFAULT_PRIORITY;
96
- if (item.wrote?.priority?.source === 'agent') return DEFAULT_PRIORITY;
97
92
  const n = item.priority;
98
93
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_PRIORITY;
99
94
  }
100
95
 
96
+ /**
97
+ * THE LEVEL A THREAD SHOWS IS THE LEVEL IT RUNS AT (w-5ac9c9006d, 2026-10-15).
98
+ *
99
+ * Every screen drew the raw stored number while the fleet ran `itemPriority`,
100
+ * so a thread an agent filed untagged read Low and ran as Medium, and an
101
+ * agent's own 8 read High and ran as Medium. In Progress then showed three
102
+ * "Low" threads running while a "High" and a "Medium" waited. An agent's level
103
+ * now counts (above); an untagged row reads Medium here as it runs.
104
+ *
105
+ * A CONVERSATION keeps the number it carries: its level is the message
106
+ * sorter's (main/message-priority.mjs), written as an agent on purpose, and a
107
+ * conversation never spawns, so there is no running order for it to disagree
108
+ * with. `conversation` is the caller's to say, since only it knows the product.
109
+ */
110
+ export function shownPriority(item, conversation = false) {
111
+ if (conversation) return item?.priority ?? null;
112
+ return itemPriority(item);
113
+ }
114
+
101
115
  /**
102
116
  * The top of her own scale, and the only level that INTERRUPTS.
103
117
  *
@@ -110,12 +124,11 @@ export function itemPriority(item) {
110
124
  export const URGENT_PRIORITY = 9;
111
125
 
112
126
  /**
113
- * Is this HER urgent row?
127
+ * Is this row Urgent?
114
128
  *
115
- * itemPriority already throws away an agent's number, so this cannot be
116
- * reached by a worker tagging its own proposal a nine. That matters more here
117
- * than anywhere else in this file: everything downstream of this predicate
118
- * takes a running session away from something else.
129
+ * Whoever set it, since 2026-10-16 (itemPriority). Everything downstream of
130
+ * this predicate takes a running session away from something else, so an
131
+ * agent's nine interrupts like hers; a wrong one is hers to lower.
119
132
  */
120
133
  export function isUrgent(item) {
121
134
  return itemPriority(item) >= URGENT_PRIORITY;
@@ -155,6 +168,56 @@ export function landingIndex(rects, x, y) {
155
168
  return rects.length;
156
169
  }
157
170
 
171
+ /**
172
+ * A THREAD'S OWN PLACE, SET BY DRAGGING IT IN THE LIST (w-6e5b532a95,
173
+ * 2026-10-07). Her words: "sometimes there are tasks that are higher priority
174
+ * than other projects and it's hard to articulate. I'd rather be able to drag
175
+ * things around here ... and have that priority remembered and that's how the
176
+ * app processes it." A project's place and a thread's level could never say
177
+ * that one Low thread matters more today than a whole project above it.
178
+ *
179
+ * So a dragged thread carries a PLACE: a number on the same scale as the score
180
+ * it would otherwise have, which replaces that score outright. It is a number
181
+ * and not a list of ids so that everything it was not dragged past keeps
182
+ * sorting the way it always did: a new Urgent thread still lands by its level,
183
+ * next to the placed ones rather than under all of them.
184
+ */
185
+ export function placedScore(place, natural) {
186
+ return Number.isFinite(place) ? place : natural;
187
+ }
188
+
189
+ /**
190
+ * The places a drop writes. `rows` is the list as drawn, top first, each with
191
+ * the score it sorts by (its place, or its project and level); `from` is the
192
+ * dragged row and `to` the row it was let go in front of, or the length for
193
+ * the end, both counted in `rows`. Null when it lands where it was.
194
+ *
195
+ * The dragged row goes between its new neighbours. When those two TIE (two
196
+ * Medium threads in one project, whose order is only which changed last) no
197
+ * number fits between them, so the tied rows above the drop are given places
198
+ * of their own first, spread in the order they were drawn. The rows below are
199
+ * never written.
200
+ */
201
+ export function dropPlaces(rows, from, to) {
202
+ if (!Array.isArray(rows) || from < 0 || from >= rows.length) return null;
203
+ const rest = rows.filter((_, i) => i !== from);
204
+ const at = Math.max(0, Math.min(rest.length, to > from ? to - 1 : to));
205
+ if (at === from || !rest.length) return null;
206
+ const close = (a, b) => Math.abs(a - b) < 1e-6;
207
+ const below = rest[at]?.score;
208
+ const floor = below ?? rest[at - 1].score - 2;
209
+ // The run of rows just above the drop that tie with the floor.
210
+ let top = at;
211
+ while (top > 0 && close(rest[top - 1].score, floor)) top -= 1;
212
+ const ceiling = top > 0 ? rest[top - 1].score : floor + 2;
213
+ const run = rest.slice(top, at);
214
+ const step = (ceiling - floor) / (run.length + 2);
215
+ const out = {};
216
+ run.forEach((r, j) => { out[r.id] = floor + (run.length + 1 - j) * step; });
217
+ out[rows[from].id] = floor + step;
218
+ return out;
219
+ }
220
+
158
221
  /**
159
222
  * Move one product to a new index, returning a fresh order. Products that were
160
223
  * never in the order join it on their first move, so dragging an unranked chip
@@ -0,0 +1,6 @@
1
+ // THE RELAY EVERY COPY OF THE APP USES (relay/worker.mjs), deployed to the
2
+ // founder's Cloudflare account on 2026-10-12. With an address here every
3
+ // computer offers "Anywhere" in Settings, then Phone, and starts new people on
4
+ // it; AGENTBOX_RELAY_URL or relay/relay.local.json still take precedence
5
+ // (main/relay-address.mjs). Redeploy with `cd relay && npx wrangler deploy`.
6
+ export const RELAY_URL = 'https://agentbox-relay.agentbox-app.workers.dev';