agentbox-app 0.1.13 → 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 (278) 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/agent-sessions.mjs +282 -268
  9. package/main/agents.mjs +179 -466
  10. package/main/analytics.mjs +12 -2
  11. package/main/answer-modes.mjs +103 -37
  12. package/main/approval-prompt-core.mjs +197 -0
  13. package/main/approval-prompt-server.mjs +4 -138
  14. package/main/approvals.mjs +101 -297
  15. package/main/artifact-path.mjs +47 -18
  16. package/main/claude-input.mjs +48 -4
  17. package/main/claude-plan.mjs +48 -0
  18. package/main/claude-session-open.mjs +28 -0
  19. package/main/claude-usage-process.mjs +40 -0
  20. package/main/claude-usage.mjs +18 -40
  21. package/main/cloud/account.mjs +179 -0
  22. package/main/cloud/aws.mjs +127 -0
  23. package/main/cloud/browser-return.mjs +58 -0
  24. package/main/cloud/clients.mjs +15 -0
  25. package/main/cloud/codex-cloud.mjs +53 -0
  26. package/main/cloud/config.mjs +107 -0
  27. package/main/cloud/docs.mjs +219 -0
  28. package/main/cloud/exec.mjs +27 -0
  29. package/main/cloud/host.mjs +165 -0
  30. package/main/cloud/kit.mjs +80 -0
  31. package/main/cloud/logins.mjs +543 -0
  32. package/main/cloud/managed.mjs +115 -0
  33. package/main/cloud/marker.mjs +104 -0
  34. package/main/cloud/org.mjs +53 -0
  35. package/main/cloud/reuse.mjs +208 -0
  36. package/main/cloud/run-cli.mjs +177 -0
  37. package/main/cloud/run.mjs +1934 -0
  38. package/main/cloud/usage.mjs +76 -0
  39. package/main/cloud-permission.mjs +51 -0
  40. package/main/codex-account.mjs +78 -0
  41. package/main/codex-app-server.mjs +276 -409
  42. package/main/codex-approvals.mjs +148 -433
  43. package/main/codex-models.mjs +20 -2
  44. package/main/codex-session.mjs +212 -507
  45. package/main/codex-threads-worker.mjs +12 -0
  46. package/main/codex-threads.mjs +71 -3
  47. package/main/codex-usage-file.mjs +5 -3
  48. package/main/codex-usage.mjs +56 -7
  49. package/main/codex-watch.mjs +17 -3
  50. package/main/codex.mjs +95 -363
  51. package/main/config.mjs +11 -1
  52. package/main/corner-tag.mjs +340 -19
  53. package/main/dev-window.mjs +1 -1
  54. package/main/engine-bin.mjs +232 -282
  55. package/main/engine-setup.mjs +21 -9
  56. package/main/feedback.mjs +3 -25
  57. package/main/folder-pictures.mjs +2 -2
  58. package/main/freeze-recorder-thread.mjs +86 -0
  59. package/main/freeze-recorder.mjs +146 -0
  60. package/main/fresh-user.mjs +20 -9
  61. package/main/git-change-offthread.mjs +17 -3
  62. package/main/git-change-thread.mjs +5 -2
  63. package/main/harnesses/claude.mjs +32 -11
  64. package/main/harnesses/codex.mjs +9 -2
  65. package/main/importable-threads.mjs +64 -6
  66. package/main/ipc.mjs +362 -111
  67. package/main/leftovers.mjs +117 -25
  68. package/main/liquid-glass.mjs +26 -0
  69. package/main/live-replies.mjs +10 -4
  70. package/main/main-went-red.mjs +330 -0
  71. package/main/main.mjs +199 -84
  72. package/main/memory-gate-history.mjs +36 -10
  73. package/main/memory-gate-server.mjs +62 -0
  74. package/main/memory-gate.mjs +14 -1
  75. package/main/menu.mjs +4 -2
  76. package/main/message-priority.mjs +2 -2
  77. package/main/mic.mjs +38 -0
  78. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  79. package/main/new-chats-watch.mjs +110 -0
  80. package/main/new-user-window.mjs +25 -0
  81. package/main/notify.mjs +24 -8
  82. package/main/phone-alerts.mjs +117 -0
  83. package/main/phone-inbox.mjs +625 -0
  84. package/main/phone-link.mjs +29 -9
  85. package/main/phone-uploads.mjs +476 -0
  86. package/main/picture-shape.mjs +114 -0
  87. package/main/quit-sequence.mjs +143 -0
  88. package/main/refresh-agent-models.mjs +15 -2
  89. package/main/relay-client.mjs +69 -1
  90. package/main/repeats.mjs +10 -4
  91. package/main/row-label.mjs +12 -41
  92. package/main/run-change.mjs +13 -0
  93. package/main/run-ledger.mjs +135 -0
  94. package/main/save-attachments.mjs +41 -0
  95. package/main/serve.mjs +45 -3
  96. package/main/settings.mjs +220 -44
  97. package/main/shell-path.mjs +185 -37
  98. package/main/ship-conflict-brief.mjs +119 -0
  99. package/main/ship-queue.mjs +115 -19
  100. package/main/source-updater.mjs +18 -10
  101. package/main/stage-attachment.mjs +68 -0
  102. package/main/stall-watch.mjs +50 -0
  103. package/main/store/home.mjs +87 -232
  104. package/main/store/paths.mjs +11 -0
  105. package/main/store/teammates-files.mjs +99 -0
  106. package/main/store/work-items.mjs +231 -13
  107. package/main/store.mjs +226 -52
  108. package/main/supervisor.mjs +3103 -341
  109. package/main/task-folders.mjs +430 -30
  110. package/main/task-terminals.mjs +110 -34
  111. package/main/team/index.mjs +114 -10
  112. package/main/team/memory-cloud.mjs +183 -1
  113. package/main/team/room-hours.mjs +48 -0
  114. package/main/team/room-setup.mjs +31 -0
  115. package/main/team/rooms-memory.mjs +454 -0
  116. package/main/team/rooms-shape.mjs +42 -0
  117. package/main/team/rooms-supabase.mjs +324 -0
  118. package/main/team/rooms.mjs +575 -0
  119. package/main/team/session.mjs +96 -26
  120. package/main/team/supabase-backend.mjs +180 -0
  121. package/main/team/sync.mjs +109 -1
  122. package/main/teammates.mjs +580 -0
  123. package/main/terminal-host.mjs +143 -0
  124. package/main/terminal-hosts.mjs +261 -0
  125. package/main/transcript-search.mjs +80 -0
  126. package/main/webview-guard.mjs +45 -0
  127. package/main/worktree-include.mjs +90 -219
  128. package/mcp/core/documents.mjs +3 -1
  129. package/mcp/core/repeats.mjs +88 -0
  130. package/mcp/core/teammates.mjs +222 -0
  131. package/mcp/core/work.mjs +76 -4
  132. package/mcp/stdio.mjs +24 -11
  133. package/mcp/tools.mjs +116 -8
  134. package/package.json +21 -5
  135. package/renderer/dist/assets/{abnfDiagram-YUKMZFIV-DPPuMWt-.js → abnfDiagram-YUKMZFIV-CoWB6YUK.js} +1 -1
  136. package/renderer/dist/assets/{arc-HmFfdseH.js → arc-Ch0NrCul.js} +1 -1
  137. package/renderer/dist/assets/{architectureDiagram-47P4ROYG-DxJ3zHrH.js → architectureDiagram-47P4ROYG-CXz9M5tQ.js} +1 -1
  138. package/renderer/dist/assets/{blockDiagram-E7TT5TSB-CbTXzOqQ.js → blockDiagram-E7TT5TSB-DchGUuHH.js} +1 -1
  139. package/renderer/dist/assets/{c4Diagram-CRA5TL53-X5ItFyLn.js → c4Diagram-CRA5TL53-Cip-zNyr.js} +1 -1
  140. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  141. package/renderer/dist/assets/{chunk-2Q5K7J3B-DC12Ho_L.js → chunk-2Q5K7J3B-BoXid_2r.js} +1 -1
  142. package/renderer/dist/assets/{chunk-5VM5RSS4-ClVtfPd0.js → chunk-5VM5RSS4-Cpez1rRz.js} +1 -1
  143. package/renderer/dist/assets/{chunk-7TKQ45FW-DgcOm0HT.js → chunk-7TKQ45FW-DKA84YkE.js} +1 -1
  144. package/renderer/dist/assets/{chunk-AW2ZBBNX-C01s4yoF.js → chunk-AW2ZBBNX-Dg-SumPG.js} +1 -1
  145. package/renderer/dist/assets/{chunk-FVRAUYC3-Bj0zxhR4.js → chunk-FVRAUYC3-06TCvPm1.js} +2 -2
  146. package/renderer/dist/assets/{chunk-HTAEGDNF-DWDeEhLU.js → chunk-HTAEGDNF-CMEBy1Td.js} +1 -1
  147. package/renderer/dist/assets/{chunk-JWPE2WC7-Dsqwv3eW.js → chunk-JWPE2WC7-BJ2ObmV-.js} +1 -1
  148. package/renderer/dist/assets/{chunk-KQW6MTUR-CE86ANL2.js → chunk-KQW6MTUR-CGWOYXUE.js} +1 -1
  149. package/renderer/dist/assets/{chunk-XXDRQBXY-CoNTASfs.js → chunk-XXDRQBXY-CiZp07XC.js} +1 -1
  150. package/renderer/dist/assets/{classDiagram-v2-K4WV4PDN-2wprLqtw.js → classDiagram-v2-K4WV4PDN-DJNIX719.js} +1 -1
  151. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  152. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  153. package/renderer/dist/assets/{cose-bilkent-JH36ORCC-enqMiYbn.js → cose-bilkent-JH36ORCC-CbkyZOUH.js} +1 -1
  154. package/renderer/dist/assets/{cynefin-EF2NZ3EQ-DHngLxvQ.js → cynefin-EF2NZ3EQ-78iOroZO.js} +1 -1
  155. package/renderer/dist/assets/{cynefinDiagram-3GCD6N5R-C0rZZIl3.js → cynefinDiagram-3GCD6N5R-GcCopMs7.js} +1 -1
  156. package/renderer/dist/assets/{dagre-W4DXFKR2-DQnaEWO7.js → dagre-W4DXFKR2-DHLlXLkz.js} +1 -1
  157. package/renderer/dist/assets/{diagram-2UJZ2QOL-CS8AlGPx.js → diagram-2UJZ2QOL-DuZSUHcH.js} +1 -1
  158. package/renderer/dist/assets/{diagram-OVF4WLC6-yiDMHIkY.js → diagram-OVF4WLC6-Ckxu3cqH.js} +1 -1
  159. package/renderer/dist/assets/{diagram-PFPMY2P6-XbQKzbBA.js → diagram-PFPMY2P6-CsTw9Ycn.js} +1 -1
  160. package/renderer/dist/assets/{diagram-UMYRVEAY-dhVvlfl_.js → diagram-UMYRVEAY-CTXd8qVg.js} +1 -1
  161. package/renderer/dist/assets/{diagram-YEKJPTXX-DZHe8SCf.js → diagram-YEKJPTXX-CJs1T_9m.js} +1 -1
  162. package/renderer/dist/assets/{diagram-ZIFT7M5P-B5h6aXVR.js → diagram-ZIFT7M5P-CvjI23mf.js} +1 -1
  163. package/renderer/dist/assets/{ebnfDiagram-VR2GEFS7-PR9ScDco.js → ebnfDiagram-VR2GEFS7-d_IeAzOJ.js} +1 -1
  164. package/renderer/dist/assets/{elk-IJKZMXRS-Cg-yQXPd.js → elk-IJKZMXRS-zo9KFP3N.js} +1 -1
  165. package/renderer/dist/assets/{erDiagram-O2IAPWRE-omOrJMxu.js → erDiagram-O2IAPWRE-DqfYf5Dt.js} +1 -1
  166. package/renderer/dist/assets/{flowDiagram-OXPTDLAJ-aOX2hzvT.js → flowDiagram-OXPTDLAJ-DAWsMLmo.js} +1 -1
  167. package/renderer/dist/assets/{ganttDiagram-R7TSEDQI-B1H3pWyI.js → ganttDiagram-R7TSEDQI-4ml_bhwR.js} +1 -1
  168. package/renderer/dist/assets/{gitGraphDiagram-XJZIOB7I-Cvw304HC.js → gitGraphDiagram-XJZIOB7I-B_WcqSId.js} +1 -1
  169. package/renderer/dist/assets/{infoDiagram-5W2HQ5XZ-_ZXcNViH.js → infoDiagram-5W2HQ5XZ-CI-jYqzc.js} +1 -1
  170. package/renderer/dist/assets/{ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js → ishikawaDiagram-K3B6WC7H-B56VWRlV.js} +1 -1
  171. package/renderer/dist/assets/{journeyDiagram-COXZFDF6-fr62QoAt.js → journeyDiagram-COXZFDF6-D1-p09Lr.js} +1 -1
  172. package/renderer/dist/assets/{kanban-definition-P3RFRI5V-J8zsabCe.js → kanban-definition-P3RFRI5V-Cv3w7JoN.js} +1 -1
  173. package/renderer/dist/assets/{linear-BmNdEtIi.js → linear-C35V2Spi.js} +1 -1
  174. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  175. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  176. package/renderer/dist/assets/{mermaid.core-CnYzvFkz.js → mermaid.core-BzGT5Vo9.js} +4 -4
  177. package/renderer/dist/assets/{mindmap-definition-LZFPQGKD-Bfcxc7PM.js → mindmap-definition-LZFPQGKD-BZUfuymC.js} +1 -1
  178. package/renderer/dist/assets/{pegDiagram-BGZESJAR-CwUJOq6X.js → pegDiagram-BGZESJAR-DFh9XNGe.js} +1 -1
  179. package/renderer/dist/assets/{pieDiagram-CAPJLFHJ-BrwMmqfC.js → pieDiagram-CAPJLFHJ-C65tPVsy.js} +1 -1
  180. package/renderer/dist/assets/{product-name-CTKLYNWY.js → product-name-CSNW6d7s.js} +1 -1
  181. package/renderer/dist/assets/{quadrantDiagram-GDMTTRAM-BN4Stoj-.js → quadrantDiagram-GDMTTRAM-Cushj_nk.js} +1 -1
  182. package/renderer/dist/assets/{railroadDiagram-SM67HX2B-CPZ5bbeR.js → railroadDiagram-SM67HX2B-DZGebGx5.js} +1 -1
  183. package/renderer/dist/assets/{requirementDiagram-X7JNWC4A-CgRnimVe.js → requirementDiagram-X7JNWC4A-DpAB0MNo.js} +1 -1
  184. package/renderer/dist/assets/{sankeyDiagram-UM26HJRW-Bt2rej__.js → sankeyDiagram-UM26HJRW-D-29rHra.js} +1 -1
  185. package/renderer/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js → sequenceDiagram-ZO4K6R2Y-FfOQAZol.js} +1 -1
  186. package/renderer/dist/assets/{sizeCapture-INFHLROL-usyNyXyh.js → sizeCapture-INFHLROL-C_n6fDBY.js} +1 -1
  187. package/renderer/dist/assets/{stateDiagram-v2-TBUQTH76-CRzUBb0u.js → stateDiagram-v2-TBUQTH76-BZjenzJG.js} +1 -1
  188. package/renderer/dist/assets/{swimlanes-N4OXWK64-C1GJvMS0.js → swimlanes-N4OXWK64-DxaMU9vX.js} +1 -1
  189. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  190. package/renderer/dist/assets/{timeline-definition-YOQAKGHF-caeyPlcc.js → timeline-definition-YOQAKGHF-DTg5HJKz.js} +1 -1
  191. package/renderer/dist/assets/{usecaseDiagram-VIAY4XPW-BN_SV6PC.js → usecaseDiagram-VIAY4XPW-Bc3MZDsl.js} +1 -1
  192. package/renderer/dist/assets/{vennDiagram-BLWOH2XV-BLGuaUX-.js → vennDiagram-BLWOH2XV-GIVGk9Id.js} +1 -1
  193. package/renderer/dist/assets/{wardleyDiagram-YMQ3BMBF-Da0cJeNc.js → wardleyDiagram-YMQ3BMBF-NmJnbQOg.js} +1 -1
  194. package/renderer/dist/assets/{xychartDiagram-TAQBALBS-cGfUffMk.js → xychartDiagram-TAQBALBS-p-kfGfnW.js} +1 -1
  195. package/renderer/dist/corner-tag.html +3 -3
  196. package/renderer/dist/index.html +3 -3
  197. package/scripts/after-install.mjs +30 -8
  198. package/scripts/cloud-shim.pl +57 -0
  199. package/shared/account-names.d.mts +6 -0
  200. package/shared/account-names.mjs +15 -0
  201. package/shared/agent-hours.mjs +136 -0
  202. package/shared/agent-import.mjs +60 -34
  203. package/shared/agents.mjs +126 -522
  204. package/shared/analytics-events.mjs +12 -0
  205. package/shared/approval-card.mjs +36 -0
  206. package/shared/bridge-map.mjs +27 -1
  207. package/shared/claude-code-shape.mjs +6 -0
  208. package/shared/claude-usage.d.mts +1 -0
  209. package/shared/claude-usage.mjs +42 -1
  210. package/shared/cloud-hand-back.d.mts +7 -0
  211. package/shared/cloud-hand-back.mjs +20 -0
  212. package/shared/cloud-kit.d.mts +219 -0
  213. package/shared/cloud-kit.mjs +341 -0
  214. package/shared/cloud-login-name.mjs +4 -0
  215. package/shared/cloud-start.mjs +17 -0
  216. package/shared/cloud-versions.mjs +23 -0
  217. package/shared/codex-import.mjs +2 -2
  218. package/shared/codex-modes.d.mts +22 -9
  219. package/shared/codex-modes.mjs +67 -11
  220. package/shared/codex-usage.mjs +108 -2
  221. package/shared/corner-tag.d.mts +23 -1
  222. package/shared/corner-tag.mjs +284 -29
  223. package/shared/dead-run-trace.mjs +19 -2
  224. package/shared/engines.mjs +70 -309
  225. package/shared/feedback.d.mts +7 -0
  226. package/shared/feedback.mjs +25 -0
  227. package/shared/first-run-practice.mjs +17 -24
  228. package/shared/harness-definitions.mjs +8 -0
  229. package/shared/instruction-defaults.json +2 -2
  230. package/shared/key-caps.d.mts +14 -0
  231. package/shared/key-caps.mjs +68 -0
  232. package/shared/message-edits.d.mts +7 -0
  233. package/shared/message-edits.mjs +35 -0
  234. package/shared/move-thread.d.mts +24 -0
  235. package/shared/move-thread.mjs +112 -0
  236. package/shared/named-threads.mjs +72 -0
  237. package/shared/not-shipped.d.mts +22 -0
  238. package/shared/not-shipped.mjs +89 -0
  239. package/shared/notify-rules.d.mts +6 -0
  240. package/shared/notify-rules.mjs +36 -8
  241. package/shared/phone-files.mjs +28 -0
  242. package/shared/phone-settings.mjs +118 -0
  243. package/shared/phone-snapshot.d.mts +16 -0
  244. package/shared/phone-snapshot.mjs +348 -0
  245. package/shared/picture-formats.d.mts +1 -0
  246. package/shared/picture-formats.mjs +13 -0
  247. package/shared/rank.d.mts +2 -1
  248. package/shared/rank.mjs +32 -19
  249. package/shared/repeats.d.mts +8 -0
  250. package/shared/repeats.mjs +33 -2
  251. package/shared/room-cards.mjs +167 -0
  252. package/shared/room-links.d.mts +4 -0
  253. package/shared/room-links.mjs +21 -0
  254. package/shared/runs-on.d.mts +3 -3
  255. package/shared/runs-on.mjs +8 -5
  256. package/shared/shipped-note.d.mts +4 -0
  257. package/shared/shipped-note.mjs +6 -0
  258. package/shared/shortcuts.mjs +14 -6
  259. package/shared/spawn-trouble.d.mts +4 -0
  260. package/shared/spawn-trouble.mjs +443 -40
  261. package/shared/team-rules.mjs +9 -2
  262. package/shared/teammates.d.mts +19 -0
  263. package/shared/teammates.mjs +756 -0
  264. package/shared/terminal-focus.d.mts +1 -0
  265. package/shared/terminal-focus.mjs +13 -0
  266. package/shared/thread-cards.mjs +4 -1
  267. package/shared/usage.d.mts +9 -0
  268. package/shared/usage.mjs +49 -3
  269. package/shared/websites.d.mts +15 -0
  270. package/shared/websites.mjs +61 -0
  271. package/shared/work-items.mjs +127 -30
  272. package/shared/work-lines.mjs +92 -244
  273. package/renderer/dist/assets/channel-BlT2g9Sz.js +0 -1
  274. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +0 -1
  275. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +0 -1
  276. package/renderer/dist/assets/main-ChNdnUvt.js +0 -1132
  277. package/renderer/dist/assets/main-nj0QR5kg.css +0 -32
  278. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +0 -8
@@ -0,0 +1 @@
1
+ export function terminalKeys(options?: { startOpen?: boolean; wasOpen?: boolean }): { takesKeys(revision?: number): boolean; hidden(): void };
@@ -0,0 +1,13 @@
1
+ // Whether a terminal takes the keyboard when TaskTerminal builds it. Opening
2
+ // it, showing it again, Retry and New session do. Coming back to a thread that
3
+ // left its terminal open does not: xterm's input is a textarea, and the app's
4
+ // shortcuts stand aside for a textarea. `startOpen` is a page that is only a
5
+ // terminal, which always takes it.
6
+ // Pinned by tests/coming-back-to-an-open-terminal-leaves-the-keyboard-alone.test.mjs.
7
+ export function terminalKeys({ startOpen = false, wasOpen = false } = {}) {
8
+ let cameBack = wasOpen === true && !startOpen;
9
+ return {
10
+ takesKeys: (revision = 0) => !cameBack || revision > 0,
11
+ hidden: () => { cameBack = false; },
12
+ };
13
+ }
@@ -16,6 +16,8 @@
16
16
  // private thread publishes no card at all (decided 2026-10-01
17
17
  // from user research): not a wordless one, not a count.
18
18
 
19
+ import { shownPriority } from './rank.mjs';
20
+
19
21
  export const THREAD_STATES = ['waiting', 'running', 'scheduled', 'done'];
20
22
 
21
23
  /** Which of the four states a thread is in. */
@@ -245,7 +247,8 @@ export function cardsFor({ products, readItems, now = Date.now(), since = null }
245
247
  // team. Never an empty list, which would have to mean both. Both clouds
246
248
  // carry it and refuse to show the card to anybody else.
247
249
  people: shownToPeople(item, product).length ? shownToPeople(item, product) : null,
248
- state, priority: Number.isFinite(item.priority) ? item.priority : null,
250
+ // The level it runs at, so a teammate reads what the owner reads.
251
+ state, priority: shownPriority(item),
249
252
  problem: s.problem || null, progress: s.progress || null, solution: s.solution || null,
250
253
  blockedBy: linked(item.blockedBy), blocks: linked(item.blocks), updatedAt: item.updatedAt ?? now,
251
254
  });
@@ -42,8 +42,17 @@ export declare function sessionLimit(limits: UsageLimit[] | null | undefined): U
42
42
  export declare function headlineLimit(limits: UsageLimit[] | null | undefined): UsageLimit | null;
43
43
  export declare const ANY_MOMENT: string;
44
44
  export declare function spanLeft(ms: number): string;
45
+ export declare const toMinute: (at: number) => number;
46
+ export declare function resetDay(at: number, now?: number): string | null;
45
47
  export declare function untilReset(ms: number): string;
46
48
 
49
+ /** The windows a reading can still answer for: one whose reset has gone past
50
+ * measures a window nobody is in any more. */
51
+ export declare function limitsStillRunning(
52
+ limits: UsageLimit[] | null | undefined,
53
+ now?: number,
54
+ ): UsageLimit[];
55
+
47
56
  export declare function limitRows(
48
57
  limits: UsageLimit[] | null | undefined,
49
58
  now?: number,
package/shared/usage.mjs CHANGED
@@ -234,6 +234,51 @@ export function spanLeft(ms) {
234
234
  return `${hours}h ${minutes % 60}m`;
235
235
  }
236
236
 
237
+ /**
238
+ * HOW LONG A WINDOW GOES ON BEING DRAWN AFTER ITS RESET, so a countdown
239
+ * crossing zero does not flicker out of the panel a minute before the next
240
+ * reading lands. Inside it "any moment" is what it says: the reset is here.
241
+ */
242
+ const JUST_RESET_MS = 2 * 60_000;
243
+
244
+ /**
245
+ * THE WINDOWS A READING CAN STILL ANSWER FOR (w-7b7182ef61).
246
+ *
247
+ * A window whose reset has gone past is over, and the one running in its place
248
+ * has never been read. shared/codex-usage.mjs already holds the rule for that:
249
+ * absence of data and a real zero are different facts and must never look the
250
+ * same. So it is DROPPED rather than relabelled -- no wording makes a
251
+ * percentage about a window nobody is in worth reading -- and the corner falls
252
+ * back to whatever the reading can still answer for (`headlineLimit`).
253
+ *
254
+ * A RESET WE COULD NOT PLACE ON A CLOCK KEEPS ITS ROW. `resetsAt` is null
255
+ * whenever Claude Code printed its stamp in a timezone we could not honestly
256
+ * convert, and a limit we cannot time is not a limit we may call finished.
257
+ */
258
+ export function limitsStillRunning(limits, now = Date.now()) {
259
+ return (limits ?? []).filter((l) => !Number.isFinite(l?.resetsAt) || l.resetsAt + JUST_RESET_MS > now);
260
+ }
261
+
262
+ /**
263
+ * A reset to the nearest minute. Claude Code gives one a moment before the
264
+ * minute (11:59:59.6) and the cloud's header the minute itself, and both name
265
+ * the same reset.
266
+ */
267
+ export const toMinute = (at) => Math.round(at / 60_000) * 60_000;
268
+
269
+ /**
270
+ * THE DAY A RESET FALLS ON, in this machine's calendar: null today, "Thu" this
271
+ * week, and "Tue Oct 27" six or more days out, where a weekday alone would
272
+ * read as today.
273
+ */
274
+ export function resetDay(at, now = Date.now()) {
275
+ const d = new Date(toMinute(at));
276
+ if (d.toDateString() === new Date(now).toDateString()) return null;
277
+ const weekday = d.toLocaleDateString('en-US', { weekday: 'short' });
278
+ if (at - now < 6 * 86_400_000) return weekday;
279
+ return `${weekday} ${d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`;
280
+ }
281
+
237
282
  /* -------------------------------- the panel ------------------------------- */
238
283
 
239
284
  /**
@@ -281,7 +326,7 @@ export function spanLeft(ms) {
281
326
  */
282
327
  export function limitRows(limits, now = Date.now()) {
283
328
  const here = localZone();
284
- return (limits ?? []).map((l) => ({
329
+ return limitsStillRunning(limits, now).map((l) => ({
285
330
  // Stable across redraws, and it has to carry the qualifier: Claude Code's
286
331
  // two week rows are otherwise the same string.
287
332
  key: `${l.span}:${l.qualifier ?? ''}`,
@@ -316,8 +361,9 @@ function whenItStartsOver(l, now, here) {
316
361
  * 2026-09-01.
317
362
  */
318
363
  export function usageSentence(limits, now = Date.now(), engineWord = null) {
319
- if (!limits?.length) return null;
320
- const said = limits.map((l) => {
364
+ const live = limitsStillRunning(limits, now);
365
+ if (!live.length) return null;
366
+ const said = live.map((l) => {
321
367
  const when = l.resetsAt ? `, ${untilReset(l.resetsAt - now)}` : l.resetsText ? `, resets ${l.resetsText}` : '';
322
368
  return `${l.name}: ${l.percent}% used${when}.`;
323
369
  }).join(' ');
@@ -0,0 +1,15 @@
1
+ // Types for shared/websites.mjs, the rules the main process and the screen
2
+ // share about a page off the open web: which addresses are one, what the
3
+ // pane's header calls it, and where its sign-ins go.
4
+
5
+ /** The session partition a website's cookies and sign-ins live in. */
6
+ export const WEBSITE_PARTITION: string;
7
+
8
+ /** Is this an ordinary page on the web, rather than a local app or a file? */
9
+ export function isWebsiteAddress(src: string | undefined | null): boolean;
10
+
11
+ /** What an address typed into the pane's bar means, with its scheme, or null. */
12
+ export function addressYouTyped(typed: string | undefined | null): string | null;
13
+
14
+ /** Can this page embed a website in a browser of its own (Electron only)? */
15
+ export function canFrameAWebsite(doc: { createElement(tag: string): unknown } | null | undefined): boolean;
@@ -0,0 +1,61 @@
1
+ // PURE. What counts as a website, and where its sign-ins live. Shared because
2
+ // the main process pins what an embedded page may do and the screen decides
3
+ // what to draw, and the two must agree.
4
+ //
5
+ // A local address is NOT a website here. A dev server is drawn in an ordinary
6
+ // frame with a liveness check in front of it (renderer/src/local-preview.ts)
7
+ // and its framing headers rewritten below the page (main/local-app-frames.mjs);
8
+ // none of that applies to a site on the web.
9
+
10
+ /**
11
+ * The store a website's cookies and sign-ins go into. Not `persist:junk`:
12
+ * that one is safe for an agent to drive precisely because it carries
13
+ * nothing, and a person may sign into a page here.
14
+ */
15
+ export const WEBSITE_PARTITION = 'persist:websites';
16
+
17
+ const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
18
+
19
+ /** Is this an ordinary page on the web, the kind the pane opens in a browser? */
20
+ export function isWebsiteAddress(src) {
21
+ if (typeof src !== 'string' || !src) return false;
22
+ try {
23
+ const url = new URL(src);
24
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
25
+ return !LOOPBACK.has(url.hostname);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * What an address typed into the pane's bar means: a page on the web or a
33
+ * local app, with the scheme a browser would add, or null. Never a search,
34
+ * which would send what you typed to somebody else.
35
+ */
36
+ export function addressYouTyped(typed) {
37
+ const text = typeof typed === 'string' ? typed.trim() : '';
38
+ if (!text || /\s/.test(text)) return null;
39
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(text);
40
+ const bareHost = text.split(/[/?#]/)[0].replace(/:\d+$/, '').toLowerCase();
41
+ const full = hasScheme ? text : `${LOOPBACK.has(bareHost) ? 'http' : 'https'}://${text}`;
42
+ let url;
43
+ try { url = new URL(full); } catch { return null; }
44
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
45
+ if (url.username || url.password) return null;
46
+ if (!LOOPBACK.has(url.hostname) && !url.hostname.includes('.')) return null;
47
+ return url.href;
48
+ }
49
+
50
+ /**
51
+ * Can this page embed a website in a browser of its own? Only the desktop
52
+ * window can: `<webview>` is Electron's, and nothing registers the tag in an
53
+ * ordinary tab or on a phone. Asking the tag beats guessing which door this is.
54
+ */
55
+ export function canFrameAWebsite(doc) {
56
+ try {
57
+ return !!doc && 'partition' in doc.createElement('webview');
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
@@ -32,6 +32,8 @@
32
32
  // epoch, and neither erases the other. Same rule as beats in
33
33
  // shared/dashboard.mjs.
34
34
 
35
+ import { resolveEdits } from './message-edits.mjs';
36
+
35
37
  /* ------------------------------- lifecycle ------------------------------- */
36
38
  // The only states the claim machinery reasons about. Everything else about what
37
39
  // a work item is (kind, labels) is free-form text the layer above interprets.
@@ -155,6 +157,16 @@ export const WORK_ITEM_FIELDS = [
155
157
  // mark on ONE line rather than a value the row holds, so the fold skips it;
156
158
  // the chat reads it per line (renderer/src/thread-history.ts).
157
159
  'inReplyTo',
160
+ // AN EDIT OR A DELETION OF ONE MESSAGE: `{ on, text }` or `{ on, gone: true }`,
161
+ // where `on` is the message's line uid. A delta like `react`, read back as
162
+ // `edits` and only from the message's own author (shared/message-edits.mjs).
163
+ 'edit',
164
+ // WHICH COMMAND FROM HER PHONE WROTE THIS LINE (docs/cloud/PHONE-PLAN.md):
165
+ // the command's id, on the line of the reply or the new thread it made. It
166
+ // is how the computer, starting again after stopping half way through a
167
+ // command, knows for certain whether that command already landed, rather
168
+ // than guessing from the words (Codex's third review of step 1, finding 5).
169
+ 'fromPhone',
158
170
  ];
159
171
 
160
172
  // THE SUMMARY IS SHARED BETWEEN THE PERSON AND THE AGENT. Everywhere else the
@@ -396,7 +408,7 @@ function coerceField(field, value) {
396
408
  // shared/effort-levels.mjs, and the spawn gates on them, so a value this
397
409
  // module does not recognise is a run at Claude Code's own choice rather
398
410
  // than a row that never runs.
399
- case 'kind': case 'product': case 'parent': case 'engine': return str(value, MAX_SHORT);
411
+ case 'kind': case 'product': case 'parent': return str(value, MAX_SHORT);
400
412
  // THESE TWO ARE CLEARED BY WRITING '' AND SO '' IS A REAL VALUE, exactly
401
413
  // as runAt's 0 and start's 'now' are, and for the same reason: a field that
402
414
  // coerces to undefined is dropped by the fold, so without a falsy value
@@ -412,7 +424,12 @@ function coerceField(field, value) {
412
424
  // picking a model and pressing send lost the message, with nothing on
413
425
  // screen to say so. Five of the 14 crashes since the Oct 6 launch, on two
414
426
  // installs (PostHog, 2026-10-08).
415
- case 'model': case 'effort': return clearable(value, MAX_SHORT);
427
+ //
428
+ // `engine` JOINED THEM WHEN A REPLY COULD FIRST CARRY ONE (w-c9da359466).
429
+ // It is written beside the model by the same call, so clearing it back to
430
+ // the workspace default would have thrown in exactly the place described
431
+ // above, before the answer was written, losing the message.
432
+ case 'model': case 'effort': case 'engine': return clearable(value, MAX_SHORT);
416
433
  case 'status': return WORK_ITEM_STATUSES.includes(value) ? value : undefined;
417
434
  case 'priority': return Number.isFinite(value) ? Math.trunc(value) : undefined;
418
435
  // When this item may next be acted on, in epoch ms. 0 means unscheduled,
@@ -440,10 +457,11 @@ function coerceField(field, value) {
440
457
  // A ts rather than a flag, so her NEXT answer outdates it by carrying a
441
458
  // later one, with nothing to clear and nobody who has to remember to.
442
459
  case 'answeredThrough': return Number.isFinite(value) ? Math.trunc(value) : undefined;
460
+ // An empty list is a real value: it is how the last label, `ship` above
461
+ // all, comes off.
443
462
  case 'labels': {
444
463
  if (!Array.isArray(value)) return undefined;
445
- const labels = value.map((l) => str(l, MAX_SHORT)).filter(Boolean).slice(0, MAX_LABELS);
446
- return labels.length ? labels : undefined;
464
+ return value.map((l) => str(l, MAX_SHORT)).filter(Boolean).slice(0, MAX_LABELS);
447
465
  }
448
466
  case 'assignee': case 'runner': return shortId(value) ?? undefined;
449
467
  case 'due': return calendarDay(value);
@@ -464,6 +482,15 @@ function coerceField(field, value) {
464
482
  return value.off === true ? { on, emoji, off: true } : { on, emoji };
465
483
  }
466
484
  case 'inReplyTo': return shortId(value) ?? undefined;
485
+ case 'edit': {
486
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
487
+ const on = shortId(value.on);
488
+ if (!on) return undefined;
489
+ if (value.gone === true) return { on, gone: true };
490
+ const text = str(value.text, MAX_TEXT_BYTES);
491
+ return text ? { on, text } : undefined;
492
+ }
493
+ case 'fromPhone': return typeof value === 'string' && /^[0-9a-f-]{8,64}$/i.test(value) ? value : undefined;
467
494
  case 'visibility': return value === 'private' || value === 'team' || value === 'people' ? value : undefined;
468
495
  // An empty list is a real value: it is how the last chosen person comes
469
496
  // off a thread, and the thread then reaches nobody until she names one.
@@ -521,12 +548,67 @@ function rollUpReactions(presses) {
521
548
  return out;
522
549
  }
523
550
 
524
- export function foldWorkItems(lines, now = Date.now()) {
525
- const items = new Map();
551
+ // Lease expiry is computed at READ time, never written. A held claim whose
552
+ // lease has lapsed reads as open again, which is what makes an abandoned item
553
+ // recoverable without anyone having to notice it was abandoned.
554
+ //
555
+ // The same derivation covers a released claim, and that is the point: only an
556
+ // item still sitting at `claimed` with nobody holding it goes back to open.
557
+ // Any status a worker or the founder actually chose (done, blocked, or one we
558
+ // have not invented yet) is theirs, and nothing about giving a claim back
559
+ // overwrites it.
560
+ export function finishItem(raw, now = Date.now()) {
561
+ // A copy, one level deep, so the raw item can be folded on and a finished
562
+ // one handed out is never changed under its holder.
563
+ const item = {};
564
+ for (const [k, v] of Object.entries(raw)) {
565
+ item[k] = Array.isArray(v) ? [...v]
566
+ : v instanceof Map ? new Map(v)
567
+ : v instanceof Set ? new Set(v)
568
+ : v && Object.getPrototypeOf(v) === Object.prototype ? { ...v } : v;
569
+ }
570
+ item.claimExpired = !!(item.claim && item.claim.leaseUntil < now);
571
+ if (item.status === 'claimed' && (!item.claim || item.claimExpired)) item.status = 'open';
572
+ if (item.reacts) {
573
+ const chips = rollUpReactions(item.reacts);
574
+ delete item.reacts;
575
+ if (chips) item.reactions = chips;
576
+ }
577
+ const closes = item.closes;
578
+ delete item.closes;
579
+ const editLines = item.editLines;
580
+ delete item.editLines;
581
+ const edits = editLines ? resolveEdits(item.said ?? [], editLines) : null;
582
+ if (edits?.size) item.edits = Object.fromEntries(edits);
583
+ if (item.said) {
584
+ const said = edits?.size ? item.said.flatMap((s) => {
585
+ const e = s.uid ? edits.get(s.uid) : null;
586
+ if (!e) return [s];
587
+ return e.gone ? [] : [{ ...s, text: e.text.trim().slice(0, TALK_TEXT) }];
588
+ }) : item.said;
589
+ delete item.said;
590
+ if (Array.isArray(item.people) && item.people.length) {
591
+ item.talk = talkOf(said);
592
+ const settled = settledBy(closes ?? [], said);
593
+ if (settled) item.talk.settled = settled;
594
+ }
595
+ }
596
+ return item;
597
+ }
598
+
599
+ /** A line that writes the row's short name and nothing else (see the fold's foot). */
600
+ function onlyTheName(line) {
601
+ if (line.claim || line.release || line.heartbeat) return false;
602
+ const fields = Object.keys(line.patch ?? {});
603
+ return fields.length === 1 && fields[0] === 'label';
604
+ }
526
605
 
606
+ /** Lines folded onto `items` in place, unfinished: `finishItem` makes each one readable. */
607
+ export function foldRawInto(items, lines, touched = null) {
527
608
  for (const raw of Array.isArray(lines) ? lines : []) {
528
609
  const line = normalizeLine(raw);
529
610
  if (!line) continue;
611
+ touched?.add(line.id);
530
612
 
531
613
  let item = items.get(line.id);
532
614
  if (!item) {
@@ -584,6 +666,11 @@ export function foldWorkItems(lines, now = Date.now()) {
584
666
  if (line.by && words && words.trim()) {
585
667
  (item.said ??= []).push({ uid: line.uid ?? null, by: line.by, ts: line.ts, text: words.trim().slice(0, TALK_TEXT), inReplyTo: patch.inReplyTo ?? null, body: typeof patch.answer !== 'string' });
586
668
  }
669
+ // Who closed the chat and when, so `whatWaits` can tell a reopen by your own
670
+ // message (still settled) from an undo of the close (not).
671
+ if (line.by && typeof patch.status === 'string') {
672
+ (item.closes ??= []).push({ by: line.by, ts: line.ts, done: patch.status === 'done', spoke: !!(words && words.trim()) });
673
+ }
587
674
 
588
675
  for (const [field, value] of Object.entries(patch)) {
589
676
  // A REACTION IS ACCUMULATED, NOT HELD. Every other field below keeps one
@@ -605,6 +692,10 @@ export function foldWorkItems(lines, now = Date.now()) {
605
692
  // WHICH MESSAGE A REPLY ANSWERS belongs to that reply's line alone. Held
606
693
  // as a value, the row would carry the newest reply's parent forever.
607
694
  if (field === 'inReplyTo') continue;
695
+ if (field === 'edit') {
696
+ (item.editLines ??= []).push({ on: value.on, by: line.by ?? null, ts: line.ts, ...(value.gone ? { gone: true } : { text: value.text }) });
697
+ continue;
698
+ }
608
699
  const held = item.wrote[field];
609
700
  // There used to be an exception here, for machinery spending a one-off
610
701
  // permission mode over the founder's own grant. That field is not on the
@@ -617,34 +708,23 @@ export function foldWorkItems(lines, now = Date.now()) {
617
708
  if (line.by) item.wrote[field].by = line.by;
618
709
  }
619
710
 
620
- item.updatedAt = Math.max(item.updatedAt, line.ts);
711
+ // THE APP WRITING A ROW'S NAME IS NOT NEWS ABOUT THE ROW. Lists break ties
712
+ // on `updatedAt`, so every unnamed thread the namer reached climbed to the
713
+ // top reading "updated just now". Read here rather than at the write, as
714
+ // the heartbeat rule above is, because the lines that did it are in the
715
+ // ledger already. Only the name ALONE: a line that carries it with anything
716
+ // else carries that too.
717
+ if (!onlyTheName(line)) item.updatedAt = Math.max(item.updatedAt, line.ts);
621
718
  if (line.ts < item.createdAt) item.createdAt = line.ts;
622
719
  }
623
720
 
624
- // Lease expiry is computed at READ time, never written. A held claim whose
625
- // lease has lapsed reads as open again, which is what makes an abandoned item
626
- // recoverable without anyone having to notice it was abandoned.
627
- //
628
- // The same derivation covers a released claim, and that is the point: only an
629
- // item still sitting at `claimed` with nobody holding it goes back to open.
630
- // Any status a worker or the founder actually chose (done, blocked, or one we
631
- // have not invented yet) is theirs, and nothing about giving a claim back
632
- // overwrites it.
633
- for (const item of items.values()) {
634
- item.claimExpired = !!(item.claim && item.claim.leaseUntil < now);
635
- if (item.status === 'claimed' && (!item.claim || item.claimExpired)) item.status = 'open';
636
- if (item.reacts) {
637
- const chips = rollUpReactions(item.reacts);
638
- delete item.reacts;
639
- if (chips) item.reactions = chips;
640
- }
641
- if (item.said) {
642
- const said = item.said;
643
- delete item.said;
644
- if (Array.isArray(item.people) && item.people.length) item.talk = talkOf(said);
645
- }
646
- }
721
+ }
647
722
 
723
+ export function foldWorkItems(lines, now = Date.now()) {
724
+ const raw = new Map();
725
+ foldRawInto(raw, lines);
726
+ const items = new Map();
727
+ for (const [id, item] of raw) items.set(id, finishItem(item, now));
648
728
  return items;
649
729
  }
650
730
 
@@ -664,6 +744,23 @@ const TALK_KEEP = 100;
664
744
  * `whatWaits` (shared/team-rules.mjs) and the row's one line; the messages
665
745
  * themselves are read off the ledger by the thread view, as before.
666
746
  */
747
+ /**
748
+ * For each person, the moment they last marked the chat done, or nothing if
749
+ * they reopened it since without saying anything (an undo). A reopen that
750
+ * carries their words keeps the moment: speaking is not taking the close back.
751
+ * So does one that follows their words since the close, because sending
752
+ * writes the message and the reopen as two lines (`answerItem`, main/store.mjs).
753
+ */
754
+ function settledBy(closes, said = []) {
755
+ const at = {};
756
+ const spokeSince = (by, from, to) => said.some((s) => s.by === by && !s.body && s.ts > from && s.ts <= to);
757
+ for (const c of [...closes].sort((a, b) => a.ts - b.ts)) {
758
+ if (c.done) at[c.by] = c.ts;
759
+ else if (!c.spoke && !spokeSince(c.by, at[c.by], c.ts)) delete at[c.by];
760
+ }
761
+ return Object.keys(at).length ? at : null;
762
+ }
763
+
667
764
  function talkOf(said) {
668
765
  const lines = [...said].sort((a, b) => a.ts - b.ts);
669
766
  const chat = [];