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
@@ -1,54 +1,8 @@
1
- // PURE. WHAT A TOOL CALL SAYS ON SCREEN.
2
- //
3
- // WHAT MESSY ACTUALLY WAS, measured over seven live imported sessions, off the
4
- // drawn screen and not off the source:
5
- //
6
- // 535 work lines in 162 runs 328 of them (61%) ran past the end of their box
7
- // and were cut by the ellipsis, so they could not be read at all 368 of them
8
- // (69%) were over sixty characters the six worst showed 2% to 4% of
9
- // themselves; every one of those was `cd /Users/you/Desktop/dev/... &&
10
- // python3 - <<'PY' ...` 18 distinct raw tool names were printed as verbs, the
11
- // worst being `mcp__plugin_paper-desktop_paper__write_html`, 31 times in one
12
- // thread the longest unbroken run was 20 lines, and that happened three times
13
- //
14
- // So there are three separate defects wearing one word, and this file is the
15
- // three answers.
16
- //
17
- // ONE. THE INTERESTING PART OF A COMMAND IS NEVER AT THE FRONT. Every one of
18
- // the six worst lines was a real command hiding behind a `cd` into an absolute
19
- // path, or an `export PATH=` prelude, or both. Truncating from the right, which
20
- // is what an ellipsis does, therefore threw away the only part that mattered and
21
- // kept the part every line shares. `plainCommand` drops the prelude instead, so
22
- // the line STARTS at the thing that ran.
23
- //
24
- // TWO. A PATH IS SHOWN FROM THE END. `/Users/you/Desktop/dev/zero/
25
- // renderer/src/components/Tweaks.tsx` is 63 characters of which the last 11 are
26
- // the answer. Under the session's own folder it becomes
27
- // `renderer/src/components/Tweaks.tsx`; deeper than that it keeps the last
28
- // three segments behind a leading ellipsis. Nothing is invented and nothing is
29
- // silently dropped: the whole string is one press away on the line itself.
30
- //
31
- // THREE. A RUN AGGREGATES. Consecutive tool calls fold into one line that says
32
- // how many and what kind. The threshold is three and it is measured, not
33
- // chosen: the run-length histogram is
34
- // {1:38, 2:42, 3:36, 4:18, 5:6, 6:6, 7:8, 8:2, 11:2, 13:1, 20:3}. Folding at
35
- // two takes the screen down 70% and folding at three takes it down 62%, so the
36
- // last eight points cost the 42 pairs, and a pair read as two short clean
37
- // lines says more than "2 things it ran" does. Three is where a wall starts.
38
- //
39
- // NOTHING HERE HIDES A FAILURE. A folded run says on its own line that
40
- // something in it failed, in the same words and the same colour a single line
41
- // would, because a fold she has to open to find bad news in is worse than the
42
- // wall it replaced.
43
-
44
- /* ------------------------------ WHAT IT DID ------------------------------- */
45
- // PLAIN WORDS AND NOT OUR TOOL NAMES. The design law forbids jargon in UI copy,
46
- // so "Bash" and "Glob" are ours, not the reader's.
47
- //
48
- // `changed` RATHER THAN `edited` (2026-10-01). A tester who is not a programmer
49
- // reads "edited" as a document being revised; the thing that happened is that a
50
- // file in the code is now different, and the line next to it already offers the
51
- // file as a chip into the change. One word for one event.
1
+ // Pure. Turns a tool call into the line a thread shows for it. The full string
2
+ // is always one press away on the line, so shortening here never loses it.
3
+
4
+ // Plain words, not tool names. Edit is "changed" because a reader who is not a
5
+ // programmer takes "edited" to mean a document was revised.
52
6
  const VERBS = {
53
7
  Bash: 'ran',
54
8
  Read: 'read',
@@ -65,8 +19,6 @@ const VERBS = {
65
19
  ListAgents: 'listed the other sessions',
66
20
  SendMessage: 'messaged',
67
21
  ToolSearch: 'looked up a tool',
68
- // Measured in her own threads on 2026-08-20 and printed raw until now:
69
- // `skill` 3 times and `askuserquestion` twice, both as bare lowercase names.
70
22
  Skill: 'used a skill',
71
23
  AskUserQuestion: 'asked a question',
72
24
  BashOutput: 'checked on a command',
@@ -74,48 +26,32 @@ const VERBS = {
74
26
  ExitPlanMode: 'finished planning',
75
27
  };
76
28
 
77
- // THE PREFIX STRIP WAS BROKEN AND HER LONGEST THREAD IS WHERE IT SHOWED. The
78
- // old pattern was `^mcp__[^_]+__`, which needs the server's name to hold no
79
- // underscore. `mcp__plugin_paper-desktop_paper__write_html` went through
80
- // untouched and she read that string 31 times in one afternoon. An MCP name is
81
- // `mcp__<server>__<tool>` and a server may contain anything, so the tool is
82
- // what follows the LAST double underscore.
29
+ // An MCP tool name is `mcp__<server>__<tool>` and the server name may itself
30
+ // hold underscores (`mcp__plugin_paper-desktop_paper__write_html`), so the tool
31
+ // is what follows the last double underscore.
83
32
  export function workVerb(name) {
84
33
  const n = String(name ?? '').trim();
85
34
  if (!n) return 'did something';
86
35
  if (VERBS[n]) return VERBS[n];
87
36
  const mcp = n.startsWith('mcp__') ? n.slice(n.lastIndexOf('__') + 2) : n;
88
- // Underscores are how a machine spells a space. She reads the result, so it
89
- // gets the space: `list_work_items` is jargon and `list work items` is a
90
- // name, which is all this line has ever claimed to be.
91
37
  return mcp.replace(/_+/g, ' ').trim().toLowerCase() || 'did something';
92
38
  }
93
39
 
94
- /* ------------------------------ A PATH, SHORT ----------------------------- */
95
- // From the END, because that is where the answer is. Three segments is the
96
- // budget: measured over her 535 lines, three segments holds the whole of every
97
- // path that sits inside a repo and the meaningful tail of every scratch path.
40
+ // A path is shortened from the end, where the file name is. Three segments
41
+ // hold the whole of most paths inside a repo.
98
42
  const PATH_SEGMENTS = 3;
99
43
 
100
- // `home` IS THE MACHINE'S, AND IT HAS NO DEFAULT. It used to default to
101
- // `/Users/you`, which meant every path this shortened was shortened correctly
102
- // on exactly one Mac in the world. Agentbox is downloaded now: on anybody
103
- // else's, `~/notes.md` came out as `/Users/leon/notes.md`, which is the
104
- // prefix-only line this whole file exists to stop. Nothing is guessed when
105
- // nobody says whose Mac it is; the path is shown whole, which is worse than a
106
- // tilde and is at least true.
44
+ // `home` has no default on purpose: with none given, a path is shown whole
45
+ // rather than shortened against a guess at whose machine this is.
107
46
  export function shortPath(raw, cwd = '', home = '') {
108
47
  const s = String(raw ?? '').trim();
109
48
  if (!s) return '';
110
49
  if (!s.startsWith('/') && !s.startsWith('~')) return s;
111
50
 
112
51
  let p = s;
113
- // `/private/tmp` and `/tmp` are the same folder on a Mac and she has never
114
- // typed either. Saying `/private` first is the filesystem talking.
52
+ // On macOS `/tmp` is a link to `/private/tmp`; show the one people type.
115
53
  p = p.replace(/^\/private\/tmp\//, '/tmp/');
116
54
 
117
- // Inside the folder this session is running in, a path is what she would
118
- // type: relative, with nothing above it.
119
55
  const base = String(cwd ?? '').replace(/^\/private\/tmp\//, '/tmp/').replace(/\/+$/, '');
120
56
  if (base && (p === base || p.startsWith(`${base}/`))) {
121
57
  const rel = p === base ? '' : p.slice(base.length + 1);
@@ -128,13 +64,11 @@ export function shortPath(raw, cwd = '', home = '') {
128
64
  return trimSegments(p);
129
65
  }
130
66
 
131
- // Keep the last few segments and say, with an ellipsis, that there were more.
132
- // The leading `…/` is the whole of the honesty here: a bare `components/
133
- // Tweaks.tsx` would read as a path from the root of something.
67
+ // The leading `…/` says segments were dropped. Without it `components/Tweaks.tsx`
68
+ // would read as a path from the root of something.
134
69
  function trimSegments(p) {
135
- // The lead is kept whole when nothing is trimmed, because `/x/store.mjs` and
136
- // `x/store.mjs` are two different files and dropping the slash would be this
137
- // function quietly saying the wrong one.
70
+ // The lead is kept when nothing is trimmed, because `/x/store.mjs` and
71
+ // `x/store.mjs` are different files.
138
72
  const lead = p.startsWith('~/') ? '~/' : p.startsWith('/') ? '/' : '';
139
73
  const body = p.slice(lead.length);
140
74
  const parts = body.split('/').filter(Boolean);
@@ -142,22 +76,9 @@ function trimSegments(p) {
142
76
  return `…/${parts.slice(-PATH_SEGMENTS).join('/')}`;
143
77
  }
144
78
 
145
- /* --------------------------- A COMMAND, PLAIN ----------------------------- */
146
- // THE PRELUDE COMES OFF THE FRONT. Every shape below was counted in her own
147
- // sessions on 2026-08-20; none of it is hypothetical.
148
- //
149
- // `cd /Users/you/Desktop/dev/zero && …` the commonest by far
150
- // `export PATH="$HOME/.nvm/versions/node/…" ; …` 29 times in one thread
151
- // `SP=/private/tmp/claude-501/-Users-…/… ; …` a bare assignment
152
- // `source ~/.zshrc && …`
153
- //
154
- // Each is scaffolding that says nothing about what the agent was doing, and
155
- // each is long enough on its own to fill the line. They come off one at a time
156
- // until what is left is a command.
157
- //
158
- // WHAT IT NEVER DOES IS INVENT. If stripping leaves nothing, the original
159
- // stands: an agent that genuinely only ran `cd somewhere` did that, and a line
160
- // reading empty would be this function lying about it.
79
+ // The interesting part of a command is never at the front, and an ellipsis cuts
80
+ // from the right. So these preludes come off the front, one at a time, until
81
+ // what is left is the command that ran. If nothing is left, the original stands.
161
82
  const PRELUDE = [
162
83
  // cd <path> && |; | \n
163
84
  /^cd\s+(?:"[^"]*"|'[^']*'|[^\s;&|]+)\s*(?:&&|;|\n)\s*/,
@@ -168,33 +89,25 @@ const PRELUDE = [
168
89
  // source|. <file> && |; | \n
169
90
  /^(?:source|\.)\s+(?:"[^"]*"|'[^']*'|[^\s;&|]+)\s*(?:&&|;|\n)\s*/,
170
91
  // echo "=== a banner ===" && |; | \n
171
- //
172
- // ONLY A BANNER, and the `===` or `---` is what makes it one. An agent
173
- // labelling its own terminal output is talking to itself, and four of the 31
174
- // worst lines left on her screen after the preludes came off were exactly
175
- // this. A plain `echo something` is a real command and is left alone, because
176
- // it may be the whole of what ran.
92
+ // Only a banner, marked by `===` or `---`. A plain `echo` may be the whole of
93
+ // what ran, so it stays.
177
94
  /^echo\s+(?:"[^"]*(?:===|---)[^"]*"|'[^']*(?:===|---)[^']*')\s*(?:&&|;|\n)\s*/,
178
95
  ];
179
96
 
180
- // A heredoc is a whole script pretending to be an argument. The longest in her
181
- // sessions was 84 lines of python inside one `ran`. Everything from the marker
182
- // onward becomes an ellipsis, so the line says which program was fed a script
183
- // without printing the script.
97
+ // Everything from a heredoc marker onward becomes an ellipsis, so the line
98
+ // names the program that was fed a script without printing the script.
184
99
  const HEREDOC = /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1([\s\S]*)$/;
185
100
 
186
- // EXCEPT WHEN THE PROGRAM IS THE WHOLE OF WHAT IS LEFT. Drawn over her own
187
- // thread on 2026-08-20 the first cut put three `python3 - …` lines in a row
188
- // inside one fold, which distinguishes nothing. In every one of those the file
189
- // being worked on was named on the script's FIRST line, so when the command in
190
- // front of the marker is this short the first line comes with it.
101
+ // When the command before the marker is this short (`python3 -`), the script's
102
+ // first line comes with it. That line usually names the file, and without it a
103
+ // row of `python3 - …` lines cannot be told apart.
191
104
  const HEREDOC_HEAD = 12;
192
105
 
193
106
  function firstScriptLine(body, marker) {
194
107
  for (const line of String(body ?? '').split('\n')) {
195
108
  const t = line.trim();
196
- // The marker itself closes the heredoc and is not a line of the script.
197
- // Matched exactly, because a one-word line of script looks just like one.
109
+ // The closing marker is not a line of the script. It is matched exactly,
110
+ // because a one-word line of script looks just like one.
198
111
  if (!t || t === marker) continue;
199
112
  if (t.startsWith('#') || t.startsWith('//')) continue;
200
113
  return t;
@@ -207,8 +120,7 @@ export function plainCommand(raw) {
207
120
  if (!s) return '';
208
121
  const whole = s;
209
122
 
210
- // Peel the scaffolding, repeatedly: `cd x && export Y=z && npm test` is all
211
- // three of these in a row.
123
+ // Repeated, because `cd x && export Y=z && npm test` has two preludes.
212
124
  for (let i = 0; i < 6; i += 1) {
213
125
  const before = s;
214
126
  for (const re of PRELUDE) s = s.replace(re, '');
@@ -224,35 +136,18 @@ export function plainCommand(raw) {
224
136
  return s || whole.replace(/\s+/g, ' ').trim();
225
137
  }
226
138
 
227
- /* --------------------- THE STORE'S OWN BOOKKEEPING ------------------------ */
228
- // NOT WORK, AND NOT SHOWN AT ALL.
229
- //
230
- // Testers who are not programmers read the top of every thread as noise, and
231
- // the top of every thread is this: a tool lookup, a claim, and later an update
232
- // to the item. MEASURED over 3,344 real session traces from 2026-09-20 to
233
- // 2026-10-01: 55,500 tool lines, of which 2,462 are the store's own records
234
- // (claim 1,212, update 999, release 138, the lists 113) and 498 are a tool
235
- // lookup. Three per cent of the lines and the first two lines of nearly every
236
- // thread.
237
- //
238
- // NOTHING OF THE WORK IS HIDDEN BY THIS. Reading a document, writing one,
239
- // filing a question and looking at a page are what the session was asked to do;
240
- // taking the item and writing its status back are how it tells the app it is
241
- // running. The second kind is what comes off the screen.
242
- //
243
- // The server is named after the product (`mcp__agentbox__`, `mcp__daydream__`),
244
- // so the prefix is never one fixed string and the tool is what follows the last
245
- // double underscore, the same reading `workVerb` makes.
139
+ // The store's own bookkeeping is not shown. Claiming an item, writing its
140
+ // status back and listing what is there are how a session tells the app it is
141
+ // running. Reading or writing a document is the work, and stays on screen.
246
142
  const BOOKKEEPING = new Set([
247
143
  'claim_work_item', 'release_work_item', 'update_work_item',
248
144
  'list_work_items', 'list_documents', 'list_products', 'list_skills',
249
145
  'list_chats', 'get_product',
250
146
  ]);
251
147
 
252
- // The name arrives three ways and all three are the same tool: the full
253
- // `mcp__<server>__<tool>` a Claude transcript writes, the bare tool Codex
254
- // reports beside its server, and `ToolSearch`, which is the harness loading a
255
- // schema and is nobody's work.
148
+ // A Claude transcript writes `mcp__<server>__<tool>`, with the server named
149
+ // after the product, and Codex reports the bare tool. `ToolSearch` is Claude
150
+ // Code loading a tool's schema.
256
151
  export function isBookkeeping(name) {
257
152
  const n = String(name ?? '').trim();
258
153
  if (!n) return false;
@@ -260,34 +155,17 @@ export function isBookkeeping(name) {
260
155
  return BOOKKEEPING.has(n.startsWith('mcp__') ? n.slice(n.lastIndexOf('__') + 2) : n);
261
156
  }
262
157
 
263
- /* ------------------------- A COMMAND, IN PLAIN WORDS ---------------------- */
264
- // "Running cat README.md; echo ---; cat package.json" is what a tester was
265
- // shown. What happened was that it read two files.
266
- //
267
- // MEASURED over the same 3,344 traces: 32,349 Bash lines, 361 distinct leading
268
- // programs, and the eleven commonest cover 80% of them (grep 5,325, git 3,998,
269
- // sed 3,894, npx 2,471, python3 2,435, cat 2,074, ls 1,814, a shell -c 1,464,
270
- // arch 947, a for loop 894, node 689). 55% of the lines are compound (`a && b;
271
- // c`) and 48% hold a pipe, so the thing on the screen is a shell one-liner and
272
- // not a sentence.
273
- //
274
- // THE RULE IS THAT A GUESS IS WORSE THAN A COMMAND. Every segment has to be
275
- // recognised and they all have to agree on one verb; one unknown part, or two
276
- // different verbs, and this returns null and the line stays the command it
277
- // always was. A wrong verb on her screen cannot be checked against anything,
278
- // and the raw command at least is true. The exact string is one press away
279
- // either way (`full` on the work line).
280
- //
281
- // `doing` IS THE SAME EVENT WHILE IT IS STILL HAPPENING, because the live line
282
- // under a running row reads "Reading README.md" and the thread above it reads
283
- // "Read README.md". One table, so the two can never drift.
158
+ // A shell command in words: `cat README.md; echo ---; cat package.json` reads
159
+ // "read README.md and package.json". A wrong verb is worse than the raw
160
+ // command, so every segment must be recognised and all must agree on one verb,
161
+ // or the line stays the command. `doing` is the same verb for the live line
162
+ // under a running row, in the same table so the two cannot drift.
284
163
 
285
164
  // Scaffolding between the real steps of a line. A banner, a `cd`, a wait.
286
165
  const NOISE = new Set(['echo', 'cd', 'pwd', 'true', 'false', ':', 'sleep', 'clear', 'date', 'printf', 'set']);
287
166
 
288
- // `2>&1` and `2>/dev/null` say nothing about what ran and are on a third of
289
- // these lines. Any OTHER redirect writes or reads a file we have not accounted
290
- // for, so it gives up rather than guess.
167
+ // `2>&1` and `2>/dev/null` say nothing about what ran. Any other redirect reads
168
+ // or writes a file this does not account for, so it gives up rather than guess.
291
169
  const QUIET_REDIRECT = /^[0-9]*>>?&?(?:[0-9]+|\/dev\/null)$/;
292
170
 
293
171
  const FAMILIES = [
@@ -306,8 +184,8 @@ const FAMILIES = [
306
184
  const BY_HEAD = new Map();
307
185
  for (const family of FAMILIES) for (const head of family.heads) BY_HEAD.set(head, family);
308
186
 
309
- // A `git` subcommand is the verb; the ones not listed keep their command,
310
- // because "checked the code" over `git merge-base` says less than the line did.
187
+ // Subcommands not listed keep their command, because a vague verb over
188
+ // `git merge-base` says less than the command does.
311
189
  const GIT = {
312
190
  status: { verb: 'checked what changed', doing: 'Checking what changed' },
313
191
  diff: { verb: 'checked what changed', doing: 'Checking what changed' },
@@ -341,9 +219,8 @@ function unquote(token) {
341
219
  return t;
342
220
  }
343
221
 
344
- // Split on separators that are OUTSIDE quotes. A `;` inside a sed script or a
345
- // grep pattern is not a new command, and splitting on it is how a reader of
346
- // shell lines invents steps that never ran.
222
+ // Splits only outside quotes: a `;` inside a sed script or a grep pattern is
223
+ // not a new command.
347
224
  function splitOutside(text, separators) {
348
225
  const out = [];
349
226
  let buffer = '';
@@ -379,8 +256,7 @@ function argumentsOf(words, skip = []) {
379
256
  const token = unquote(raw);
380
257
  if (!quoted) {
381
258
  if (QUIET_REDIRECT.test(token)) continue;
382
- // Anything else involving a file handle, a subshell or a glob of
383
- // commands is a shape nobody taught this.
259
+ // Any other redirect, a subshell or backticks is a shape this cannot read.
384
260
  if (/[<>`]/.test(token) || token.includes('$(')) return null;
385
261
  }
386
262
  if (isFlag(token)) {
@@ -400,7 +276,7 @@ function stageWords(stage, cwd, home) {
400
276
  const head = unquote(words[0]);
401
277
  const rest = words.slice(1);
402
278
  if (NOISE.has(head)) {
403
- // `echo something > a file` writes a file, and this is not the reader of it.
279
+ // `echo something > a file` writes a file, so it is not noise.
404
280
  const args = argumentsOf(rest);
405
281
  return args === null ? null : 'noise';
406
282
  }
@@ -423,9 +299,8 @@ function stageWords(stage, cwd, home) {
423
299
  if (SCRIPTS.has(head) || /\.(?:sh|mjs|js|py)$/.test(head)) {
424
300
  const args = argumentsOf(rest);
425
301
  if (args === null) return null;
426
- // `python3 - <<'PY'` is a script with no name, and `plainCommand` already
427
- // puts its first line after the ellipsis. That line is what a developer
428
- // reads to tell one of 2,793 of these from another, so it is kept.
302
+ // `python3 - <<'PY'` has no file name. `plainCommand` put the script's
303
+ // first line after the ellipsis, and that line tells these apart.
429
304
  if (stage.includes('…')) {
430
305
  const first = stage.slice(stage.lastIndexOf('…') + 1).trim();
431
306
  return { ...SCRIPT, names: first ? [first] : [] };
@@ -447,8 +322,8 @@ const URL_LIKE = /^(?:https?:\/\/|www\.|localhost|127\.0\.0\.1)/;
447
322
 
448
323
  function namesFor(family, args, cwd, home) {
449
324
  if (family.take === 'first') return args.slice(0, 1);
450
- // A fetch names the page, which is not always the first argument: `curl -s -o
451
- // /dev/null -w '%{http_code}' <url>` had three before it.
325
+ // The URL is not always the first argument, as in
326
+ // `curl -s -o /dev/null -w '%{http_code}' <url>`.
452
327
  if (family.take === 'url') {
453
328
  const url = args.find((a) => URL_LIKE.test(a));
454
329
  return url ? [url] : args.slice(0, 1);
@@ -462,10 +337,8 @@ function namesFor(family, args, cwd, home) {
462
337
  return files;
463
338
  }
464
339
 
465
- // WHAT THE SUBCOMMAND NAMED, because a developer reads these lines for the
466
- // file, the branch or the message, and a verb on its own tells them less than
467
- // the command did. Measured over the same traces: `git add`, `git commit`,
468
- // `git push` and `git merge` are 563 lines that said nothing but their verb.
340
+ // Keeps what the subcommand named (the file, the branch or the message),
341
+ // because a bare verb tells a developer less than the command did.
469
342
  function gitWords(rest, cwd, home) {
470
343
  const words = [...rest];
471
344
  // `git -C <path> <subcommand>`: the folder is not the verb.
@@ -484,8 +357,7 @@ const NOT_A_REF = new Set(['origin', 'upstream', '.', '--', 'HEAD']);
484
357
 
485
358
  function gitNames(sub, rest, cwd, home) {
486
359
  if (sub === 'commit') {
487
- // The message, which is the one thing a commit line is read for. It is
488
- // absent when the message came in on stdin (`git commit -F -`).
360
+ // The message is absent when it came in on stdin (`git commit -F -`).
489
361
  const at = rest.findIndex((w) => unquote(w) === '-m' || unquote(w) === '--message');
490
362
  const message = at >= 0 ? unquote(rest[at + 1] ?? '') : '';
491
363
  return message ? [message] : [];
@@ -497,9 +369,8 @@ function gitNames(sub, rest, cwd, home) {
497
369
  return refs.length ? [refs[refs.length - 1]] : [];
498
370
  }
499
371
  const files = args.filter(looksLikeAFile).map((a) => shortPath(a, cwd, home));
500
- // A HISTORY COMMAND OFTEN NAMES SOMETHING THAT IS NOT A PATH, and it is the
501
- // point of the line: `git log --grep w-4ac1af8c99` is a search for one
502
- // thread. The value a flag carried survives when no file does.
372
+ // A history command often names something that is not a path, like
373
+ // `git log --grep <word>`, so the first argument is kept when no file is.
503
374
  if (files.length) return files;
504
375
  return args.length ? [args[0]] : [];
505
376
  }
@@ -512,15 +383,14 @@ function sedWords(rest, cwd, home) {
512
383
  if (args === null) return null;
513
384
  const flags = rest.map(unquote).filter(isFlag);
514
385
  const inPlace = flags.some((f) => f.startsWith('-i'));
515
- // THE FILE IS THE LAST ARGUMENT, AND A SED SCRIPT IS NOT IT. A trace line is
516
- // cut at 200 characters, so the file is sometimes not on it at all and the
517
- // script (`s#a#b#`) is the last thing that looks like a path.
386
+ // The file is the last argument that is not a sed script. A trace line is cut
387
+ // at 200 characters, so the file may be missing and the script (`s#a#b#`)
388
+ // is then the last thing that looks like a path.
518
389
  const file = [...args].reverse().find((a) => looksLikeAFile(a) && !SED_SCRIPT.test(a));
519
390
  if (inPlace) {
520
391
  return file ? { verb: 'changed', doing: 'Changing', names: [shortPath(file, cwd, home)] } : null;
521
392
  }
522
- // `sed -n 1,200p <file>` is how an agent reads part of a file; 3,529 of the
523
- // 3,894 sed lines measured were exactly that, against 325 that edited.
393
+ // `sed -n 1,200p <file>` is how an agent reads part of a file.
524
394
  if (!flags.some((f) => f === '-n' || f === '-e' || f === '-E')) return null;
525
395
  return file ? { verb: 'read', doing: 'Reading', names: [shortPath(file, cwd, home)] } : null;
526
396
  }
@@ -546,15 +416,15 @@ function runnerWords(rest, cwd, home) {
546
416
 
547
417
  // A trace line is cut at 200 characters, so the last test file on a long
548
418
  // `vitest run a b c` often arrives without its extension. Anything that looks
549
- // like a path counts, which is how 586 lines that named nothing got their name.
419
+ // like a path counts.
550
420
  function testWords(rest) {
551
421
  const args = rest.map(unquote).filter((w) => !isFlag(w) && w !== 'run');
552
422
  const files = args.filter((w) => TEST_FILE.test(w) || w.includes('/'));
553
423
  return { ...TESTS, names: files.map(lastSegment) };
554
424
  }
555
425
 
556
- // Up to three names, then the count. Her standing objection to a cap is one
557
- // that rounds off in silence, so the count says what it counted.
426
+ // Up to three names, then a count that says what it counted, so the cap is
427
+ // never silent.
558
428
  function joinNames(names, many = 'files') {
559
429
  const list = [...new Set(names.filter(Boolean))];
560
430
  if (!list.length) return '';
@@ -566,10 +436,8 @@ function joinNames(names, many = 'files') {
566
436
  /**
567
437
  * What a shell command did, in words, or null to leave it as the command.
568
438
  *
569
- * `cwd` and `home` are the folder the session ran in and the Mac's home, the
570
- * same two arguments `shortPath` takes and for the same reason: a path is only
571
- * shortenable against the folder it sits under, and nothing is guessed when
572
- * nobody says.
439
+ * `cwd` and `home` are the session's folder and the machine's home, passed on
440
+ * to `shortPath`.
573
441
  */
574
442
  export function commandWork(raw, cwd = '', home = '') {
575
443
  const command = plainCommand(raw);
@@ -594,10 +462,8 @@ export function commandWork(raw, cwd = '', home = '') {
594
462
  return { verb: said.verb, doing: said.doing, subject: joinNames(names, first.many) };
595
463
  }
596
464
 
597
- /* ------------------------------- THE SUBJECT ------------------------------ */
598
- // WHICH ONE THIS WAS, in as few characters as the truth allows. `cwd` is the
599
- // folder the session is running in, which is what makes a path relative; it is
600
- // optional so that nothing that calls this without one gets worse than before.
465
+ // What the call acted on, as short as it can be without being wrong. With a
466
+ // `cwd`, paths under it are shown relative to it.
601
467
  export function workSubject(input, cwd = '', home = '') {
602
468
  const i = input ?? {};
603
469
  if (i.command != null && String(i.command).trim()) {
@@ -609,9 +475,7 @@ export function workSubject(input, cwd = '', home = '') {
609
475
  return String(raw).replace(/\s+/g, ' ').trim();
610
476
  }
611
477
 
612
- // THE WHOLE OF IT, for the line she opened. Nothing this file shortens is
613
- // lost: the full string goes back on the screen above the output, and this is
614
- // the function that says whether there is anything to put there.
478
+ // The whole string, shown above the output when a line is opened.
615
479
  export function fullSubject(input) {
616
480
  const i = input ?? {};
617
481
  const raw = i.command ?? i.file_path ?? i.path ?? i.notebook_path
@@ -619,10 +483,8 @@ export function fullSubject(input) {
619
483
  return String(raw).replace(/\s+$/, '');
620
484
  }
621
485
 
622
- /* -------------------------------- THE RUN --------------------------------- */
623
- // AGGREGATE, HER WORD. Three or more consecutive tool calls fold into one line.
624
- // Two do not, because two clean lines say more than a fold does and 42 of her
625
- // 162 runs are exactly two.
486
+ // Three or more consecutive tool calls fold into one line. Two stay as two
487
+ // lines, which say more than a fold does.
626
488
  export const RUN_MIN = 3;
627
489
 
628
490
  export function groupWork(events, { min = RUN_MIN } = {}) {
@@ -635,8 +497,8 @@ export function groupWork(events, { min = RUN_MIN } = {}) {
635
497
  run = [];
636
498
  };
637
499
  for (const e of events ?? []) {
638
- // SOMETHING SHE DID IS NOT THE AGENT'S WORK and never folds into it: three
639
- // snoozes are three lines, not one "You put it off" (w-49b4e45403).
500
+ // What the person did is not the agent's work and never folds into it:
501
+ // three snoozes are three lines.
640
502
  if (e && e.kind === 'work' && !e.yours && !e.standalone) { run.push(e); continue; }
641
503
  flush();
642
504
  out.push(e);
@@ -645,22 +507,15 @@ export function groupWork(events, { min = RUN_MIN } = {}) {
645
507
  return out;
646
508
  }
647
509
 
648
- // WHAT THE FOLDED LINE SAYS. It says the number first, because the number is
649
- // the thing a fold owes her: her standing objection to every cap in this
650
- // codebase is a total that was rounded off without saying so.
510
+ // The folded line says the total first, then up to three verbs with their
511
+ // counts and one count for the rest:
651
512
  //
652
- // one verb, and one we have a plain word for `20 things it ran`
653
- // one verb we do not `31 things it did · write html 31`
654
- // several `24 things it did · ran 18 · read 4 · edited 2`
513
+ // one verb with a plain word `20 things it ran`
514
+ // one verb without `31 things it did · write html 31`
515
+ // several `24 things it did · ran 18 · read 4 · changed 2`
655
516
  //
656
- // Three verbs is the tally's budget; past that the rest is one more count, so
657
- // the line never grows past what fits.
658
- //
659
- // A COUNT OF ONE IS NOT PRINTED. Drawn over her own threads on 2026-08-20 the
660
- // first cut read `8 things it did · ran 5 · read 2 · asked a question 1`, and
661
- // that trailing 1 reads as part of the name rather than as a number. A bare
662
- // verb already says once.
663
- const PLAIN = new Set(['ran', 'read', 'wrote', 'edited', 'looked for', 'searched for', 'fetched', 'messaged']);
517
+ // A count of one is not printed, because a trailing 1 reads as part of the name.
518
+ const PLAIN = new Set(['ran', 'read', 'wrote', 'changed', 'looked for', 'searched for', 'fetched', 'messaged']);
664
519
  const TALLY_VERBS = 3;
665
520
 
666
521
  export function runSummary(items) {
@@ -679,22 +534,17 @@ export function runSummary(items) {
679
534
  return `${list.length} things it did · ${shown.join(' · ')}`;
680
535
  }
681
536
 
682
- // THE BAD NEWS IS NEVER BEHIND THE FOLD. Said in plain words on the closed line,
683
- // in the one colour this surface uses.
537
+ // A failure inside a fold is said on the closed line, so nobody has to open it
538
+ // to find bad news.
684
539
  export function runFailures(items) {
685
540
  const bad = (items ?? []).filter((e) => e && e.failed).length;
686
541
  if (!bad) return '';
687
542
  return `${bad} failed`;
688
543
  }
689
544
 
690
- /* --------------------------- THE FILE IT CHANGED -------------------------- */
691
- // A WORK LINE THAT SAYS IT CHANGED A FILE HANDS THE FILE OVER, so the thread
692
- // can make it a chip.
693
- //
694
- // The same four tools main/code-change.mjs reads, and they have to stay the
695
- // same four: one list saying which calls become a diff and another saying which
696
- // lines become a chip is two lists that drift, and the symptom of the drift is
697
- // a chip that opens onto a file the change does not hold.
545
+ // The tools whose file the thread shows as a chip into the change. These must
546
+ // be the same tools main/code-change.mjs reads, or a chip opens onto a file the
547
+ // change does not hold.
698
548
  export const CHANGES_A_FILE = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
699
549
 
700
550
  /**
@@ -711,15 +561,13 @@ export function changedFile(name, input) {
711
561
  /**
712
562
  * Which entry of a change a path on a work line is, or null.
713
563
  *
714
- * A transcript records the ABSOLUTE path it wrote; a change records the path
715
- * relative to one of the run's roots when it sits under one, and the absolute
716
- * path when it does not (main/code-change.mjs). So the two shapes have to meet,
717
- * and matching on the tail is what makes them: the change's key is either the
718
- * whole string or a suffix of it at a segment boundary.
564
+ * A transcript records the absolute path it wrote. A change records the path
565
+ * relative to one of the run's roots when it sits under one, and absolute when
566
+ * not (main/code-change.mjs). So a key matches when it is the whole path or a
567
+ * suffix of it at a segment boundary.
719
568
  *
720
- * The LONGEST match wins. `src/a.ts` and `renderer/src/a.ts` are different
721
- * files and both are suffixes of the second one's absolute path; taking the
722
- * first match would open whichever the change happened to list first.
569
+ * The longest match wins: `src/a.ts` and `renderer/src/a.ts` are both suffixes
570
+ * of the second one's absolute path.
723
571
  */
724
572
  export function fileInChange(paths, raw) {
725
573
  const p = String(raw ?? '').trim().replace(/^\/private\/tmp\//, '/tmp/');
@@ -1 +0,0 @@
1
- import{ao as r,ap as n}from"./mermaid.core-CnYzvFkz.js";const t=(a,o)=>r.lang.round(n.parse(a)[o]);export{t as c};
@@ -1 +0,0 @@
1
- :root{--ct-ink: #1d1d1f;--ct-dim: rgba(29, 29, 31, .68);--ct-faint: rgba(29, 29, 31, .48);--ct-mark: rgba(29, 29, 31, .42);--ct-tint: rgba(243, 244, 247, .95);--ct-tint-hover: rgba(249, 249, 251, .97);--ct-edge: inset 0 0 0 .5px rgba(0, 0, 0, .15);--ct-rule: rgba(29, 29, 31, .1);--ct-press: rgba(29, 29, 31, .06)}@media (prefers-color-scheme: dark){:root:not([data-theme=light]){--ct-ink: rgba(255, 255, 255, .86);--ct-dim: rgba(255, 255, 255, .62);--ct-faint: rgba(255, 255, 255, .44);--ct-mark: rgba(255, 255, 255, .42);--ct-tint: rgba(39, 39, 43, .95);--ct-tint-hover: rgba(50, 50, 55, .96);--ct-edge: inset 0 0 0 .5px rgba(0, 0, 0, .6), inset 0 0 0 1.5px rgba(255, 255, 255, .13);--ct-rule: rgba(255, 255, 255, .1);--ct-press: rgba(255, 255, 255, .07)}}:root[data-theme=dark]{--ct-ink: rgba(255, 255, 255, .86);--ct-dim: rgba(255, 255, 255, .62);--ct-faint: rgba(255, 255, 255, .44);--ct-mark: rgba(255, 255, 255, .42);--ct-tint: rgba(39, 39, 43, .95);--ct-tint-hover: rgba(50, 50, 55, .96);--ct-edge: inset 0 0 0 .5px rgba(0, 0, 0, .6), inset 0 0 0 1.5px rgba(255, 255, 255, .13);--ct-rule: rgba(255, 255, 255, .1);--ct-press: rgba(255, 255, 255, .07)}html,body,#root{margin:0;padding:0;background:transparent;overflow:hidden;height:100%}body{font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,sans-serif;-webkit-font-smoothing:antialiased;-webkit-user-select:none;user-select:none;cursor:default;color:var(--ct-ink)}.ct-glass{position:absolute;left:0;top:0;color:var(--ct-ink);background:var(--ct-tint);-webkit-backdrop-filter:blur(28px) saturate(180%);backdrop-filter:blur(28px) saturate(180%);box-shadow:var(--ct-edge)}.ct-tag{height:28px;padding:0 12px 0 10px;border-radius:6px;box-sizing:border-box;display:flex;align-items:center;gap:7px;font-size:12.5px;white-space:nowrap;transition:background-color .12s ease}.ct-tag .ct-words{font-weight:600}.ct-tag.working .ct-words{font-weight:500;color:var(--ct-dim)}.ct-tag:hover,.ct-tag.is-open{background:var(--ct-tint-hover)}.ct-tag.is-dragging{cursor:grabbing}.ct-go{display:flex;align-items:center;gap:8px;margin-left:1px;color:var(--ct-dim)}.ct-go:before{content:"";width:1px;height:14px;background:var(--ct-rule)}.ct-tag{padding-right:9px}.ct-go svg{display:block}.ct-dot{width:8px;height:8px;border-radius:50%;flex:none;background:radial-gradient(circle at 32% 28%,#fff 0,#fff,#f8eef6 48%,#e7ecf6,#d3d9e7);box-shadow:0 0 0 .5px #3c465a14,0 0 8px 2px #e8d0f0d9}@media (prefers-color-scheme: dark){:root:not([data-theme=light]) .ct-dot{width:7px;height:7px;background:#fff;box-shadow:0 0 6px #ffffff8c}}:root[data-theme=dark] .ct-dot{width:7px;height:7px;background:#fff;box-shadow:0 0 6px #ffffff8c}.ct-half{flex:none;display:block;color:var(--ct-mark)}.ct-card{border-radius:8px;padding:9px 8px 8px;box-sizing:border-box;width:max-content;max-width:440px;min-width:240px}.ct-line{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:9px;width:100%;font-size:13px;white-space:nowrap;padding:5px 6px;border-radius:4px;cursor:pointer;color:var(--ct-ink)}.ct-line:hover{background:var(--ct-press)}.ct-line .ct-name{font-weight:600;overflow:hidden;text-overflow:ellipsis;max-width:220px}.ct-line .ct-says{color:var(--ct-dim)}.ct-line .ct-when{margin-left:auto;padding-left:14px;font-size:12px;color:var(--ct-faint)}.ct-quiet{font-size:12.5px;color:var(--ct-faint)}.ct-line.ct-quiet{color:var(--ct-faint)}.ct-static{padding:5px 6px}.ct-mark-sp{width:8px;flex:none}.ct-foot{display:flex;align-items:center;gap:2px;margin:6px 0 0;padding:7px 6px 0;border-top:1px solid var(--ct-rule);font-size:11.5px;color:var(--ct-faint);white-space:nowrap}.ct-foot>span:first-child{margin-right:2px}.ct-foot button{all:unset;cursor:pointer;padding:2px 6px;border-radius:5px;color:var(--ct-dim)}.ct-foot button:hover{background:var(--ct-press);color:var(--ct-ink)}.ct-foot .ct-sep{flex:1;min-width:16px}
@@ -1 +0,0 @@
1
- import{c as C,j as n,r as c,N as L}from"./product-name-CTKLYNWY.js";const P=[{key:"5m",label:"5 min"},{key:"30m",label:"30 min"},{key:"today",label:"Today"}];function S({ready:t=0,working:r=0}={}){return t>0?{kind:"ready",text:`${t} waiting`}:r>0?{kind:"working",text:`${r} working`}:null}function $(t,r){return[...t??[]].sort((o,s)=>(Number.isFinite(s.since)?s.since:r)-(Number.isFinite(o.since)?o.since:r))}function R(t,r){if(!Number.isFinite(t))return"";const o=Math.floor((r-t)/6e4);return o<1?"now":o<60?`${o} min`:`${Math.floor(o/60)} h`}function D(t,r,o=3){const s=$(t,r),d=s.slice(0,o).map(l=>({...l,waited:R(l.since,r)}));return{lines:d,more:Math.max(0,s.length-d.length)}}const e=window.cornerTag??null,m=new URLSearchParams(location.search),w=m.get("part")==="card"?"card":"tag";function T(){const t=m.get("demo");if(!t)return null;const r=Date.now(),o=Number(m.get("n")??2),s=["Pricing page","Login redirect","Onboarding emails","A much longer thread title that will not fit on one line","Settings page","Competitor research","Release notes"];return{state:{ready:t.startsWith("ready")?Array.from({length:o},(l,i)=>({id:`demo-${i}`,title:s[i%s.length],says:i===1?"needs a yes":"is ready for you",since:r-[2,9,25,40,70,95,130][i%7]*6e4})):[],working:t.startsWith("ready")?20-o:20,now:r},open:t.endsWith("-open")}}const u=T(),g=m.get("theme");document.documentElement.classList.toggle("ct-demo",!!u);(g==="dark"||g==="light")&&(document.documentElement.dataset.theme=g);function I(){const[t,r]=c.useState((u==null?void 0:u.state)??{ready:[],working:0,now:Date.now()});return c.useEffect(()=>e==null?void 0:e.onState(o=>r(o)),[]),t}function j({size:t=11}){return n.jsxs("svg",{className:"ct-half",width:t,height:t,viewBox:"0 0 12 12","aria-hidden":!0,children:[n.jsx("circle",{cx:"6",cy:"6",r:"5.2",fill:"none",stroke:"currentColor",strokeWidth:"1.3"}),n.jsx("path",{d:"M6 2.4a3.6 3.6 0 0 1 0 7.2z",fill:"currentColor"})]})}function M(){return n.jsx("span",{className:"ct-go","aria-hidden":!0,children:n.jsx("svg",{width:"7",height:"10",viewBox:"0 0 7 10",children:n.jsx("path",{d:"M1.5 1.5 5 5 1.5 8.5",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})})}function v(t,r,o){c.useLayoutEffect(()=>{const s=t.current;if(!s||!e)return;const d=()=>{const i=s.getBoundingClientRect();e.size(r,i.width,i.height)};d();const l=new ResizeObserver(d);return l.observe(s),()=>l.disconnect()},[o])}function y({state:t,forceOpen:r=!1}){const[o,s]=c.useState(r),[d,l]=c.useState(!1),i=c.useRef(null),h=c.useRef(!1),N={ready:t.ready.length,working:t.working},f=S(N),p=f==null?void 0:f.text;if(c.useEffect(()=>e==null?void 0:e.onOpen(a=>s(!!a)),[]),c.useEffect(()=>e==null?void 0:e.onDragging(a=>l(!!a)),[]),c.useEffect(()=>e==null?void 0:e.onReset(()=>{h.current=!1,l(!1),s(!1)}),[]),c.useEffect(()=>{const a=E=>{E.key==="Escape"&&o&&(e==null||e.toggle())};return window.addEventListener("keydown",a),()=>window.removeEventListener("keydown",a)},[o]),v(i,"tag",p),!f)return null;const x=a=>{h.current&&(h.current=!1,e==null||e.release(a))};return n.jsxs("div",{ref:i,role:"button","aria-label":`${p}. Click to open ${L}, drag to move.`,"aria-expanded":o,className:`ct-glass ct-tag ${f.kind}${o?" is-open":""}${d?" is-dragging":""}`,style:u?{position:"relative"}:void 0,onPointerEnter:()=>e==null?void 0:e.hover("tag",!0),onPointerLeave:()=>{h.current||e==null||e.hover("tag",!1)},onPointerDown:a=>{a.button===0&&(a.currentTarget.setPointerCapture(a.pointerId),h.current=!0,e==null||e.press(a.screenX,a.screenY))},onPointerUp:a=>{x(!1),a.currentTarget.hasPointerCapture(a.pointerId)&&a.currentTarget.releasePointerCapture(a.pointerId)},onPointerCancel:()=>x(!0),onLostPointerCapture:()=>x(!0),onContextMenu:a=>{a.preventDefault(),e==null||e.menu()},children:[f.kind==="ready"?n.jsx("span",{className:"ct-dot"}):n.jsx(j,{}),n.jsx("span",{className:"ct-words",children:p}),n.jsx(M,{})]})}function O({state:t}){const{lines:r,more:o}=D(t.ready,t.now);return n.jsxs(n.Fragment,{children:[r.map(s=>n.jsxs("button",{className:"ct-line",onClick:()=>e==null?void 0:e.go(s.open===void 0?s.id:s.open),children:[n.jsx("span",{className:"ct-dot"}),n.jsx("span",{className:"ct-name",children:s.title}),n.jsx("span",{className:"ct-says",children:s.says}),n.jsx("span",{className:"ct-when",children:s.waited})]},s.id)),o>0&&n.jsxs("button",{className:"ct-line ct-quiet",onClick:()=>e==null?void 0:e.go(null),children:[n.jsx("span",{className:"ct-mark-sp"}),"and ",o," more"]}),!r.length&&n.jsx("div",{className:"ct-quiet ct-static",children:"Nothing is ready for you yet"}),t.working>0&&n.jsxs("button",{className:"ct-line ct-quiet",onClick:()=>e==null?void 0:e.go(null),children:[n.jsx(j,{size:10}),r.length?`${t.working} others working`:`${t.working} working`]}),n.jsxs("div",{className:"ct-foot",children:[n.jsx("span",{children:"Hide for"}),P.map(s=>n.jsx("button",{onClick:()=>e==null?void 0:e.hide(s.key),children:s.label},s.key)),n.jsx("span",{className:"ct-sep"}),n.jsx("button",{className:"ct-off",onClick:()=>e==null?void 0:e.settings(),children:"Turn off…"})]})]})}function k({state:t}){const r=c.useRef(null);return v(r,"card",null),c.useEffect(()=>{const o=s=>{s.key==="Escape"&&(e==null||e.toggle())};return window.addEventListener("keydown",o),()=>window.removeEventListener("keydown",o)},[]),n.jsx("div",{ref:r,className:"ct-glass ct-card",style:u?{position:"relative"}:void 0,onPointerEnter:()=>e==null?void 0:e.hover("card",!0),onPointerLeave:()=>e==null?void 0:e.hover("card",!1),children:n.jsx(O,{state:t})})}function B(){const t=I();return c.useEffect(()=>{e==null||e.ready(w)},[]),u?n.jsxs("div",{style:{position:"absolute",right:16,bottom:16,display:"flex",flexDirection:"column",alignItems:"flex-end",gap:8},children:[u.open&&n.jsx(k,{state:t}),n.jsx(y,{state:t,forceOpen:u.open})]}):w==="card"?n.jsx(k,{state:t}):n.jsx(y,{state:t})}C(document.getElementById("root")).render(n.jsx(B,{}));