agentbox-app 0.1.7 → 0.1.12

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 (255) hide show
  1. package/README.md +220 -58
  2. package/bin/agentbox.mjs +18 -3
  3. package/briefs/message-rules.md +24 -3
  4. package/briefs/worker.md +63 -49
  5. package/briefs/writing-rules.md +90 -131
  6. package/main/account-discovery.mjs +21 -20
  7. package/main/account-tooling.mjs +11 -11
  8. package/main/agent-activity.mjs +45 -5
  9. package/main/agent-schedule.mjs +14 -14
  10. package/main/agent-sessions.mjs +94 -10
  11. package/main/agent-updates.mjs +116 -0
  12. package/main/agents.mjs +52 -46
  13. package/main/analytics.mjs +27 -0
  14. package/main/approval-prompt-server.mjs +2 -2
  15. package/main/approvals.mjs +14 -5
  16. package/main/artifact-path.mjs +11 -0
  17. package/main/claude-bin.mjs +32 -5
  18. package/main/claude-input.mjs +45 -3
  19. package/main/claude-plan.mjs +25 -13
  20. package/main/code-change.mjs +3 -3
  21. package/main/codex-account.mjs +1 -1
  22. package/main/codex-bin.mjs +18 -0
  23. package/main/codex-launch-env.mjs +31 -0
  24. package/main/codex-memory-gate.mjs +260 -0
  25. package/main/codex-models.mjs +12 -7
  26. package/main/codex-threads.mjs +46 -23
  27. package/main/codex-usage-file.mjs +7 -7
  28. package/main/codex-usage.mjs +4 -4
  29. package/main/codex-watch.mjs +12 -12
  30. package/main/config.mjs +82 -17
  31. package/main/demo.mjs +17 -14
  32. package/main/dev-window.mjs +9 -9
  33. package/main/doc-scheme.mjs +2 -0
  34. package/main/engine-setup.mjs +204 -0
  35. package/main/feedback.mjs +55 -0
  36. package/main/folder-pictures.mjs +48 -0
  37. package/main/frame-scrollbars.mjs +90 -0
  38. package/main/fresh-user.mjs +7 -4
  39. package/main/git-change-offthread.mjs +70 -0
  40. package/main/git-change-thread.mjs +11 -0
  41. package/main/git-change.mjs +9 -3
  42. package/main/img-scheme.mjs +70 -1
  43. package/main/instruction-settings.mjs +134 -60
  44. package/main/ipc.mjs +335 -42
  45. package/main/leftovers.mjs +518 -0
  46. package/main/live-replies.mjs +43 -5
  47. package/main/machine.mjs +48 -1
  48. package/main/main.mjs +138 -15
  49. package/main/memory-gate-history.mjs +254 -0
  50. package/main/memory-gate-server.mjs +461 -0
  51. package/main/memory-gate.mjs +206 -0
  52. package/main/message-priority.mjs +87 -0
  53. package/main/notify.mjs +1 -1
  54. package/main/rail-note.mjs +2 -2
  55. package/main/refresh-agent-models.mjs +21 -0
  56. package/main/repeats.mjs +4 -4
  57. package/main/row-label.mjs +115 -51
  58. package/main/runs-on.mjs +93 -0
  59. package/main/serve.mjs +77 -5
  60. package/main/settings.mjs +245 -26
  61. package/main/ship-queue.mjs +279 -0
  62. package/main/sign-in-files.mjs +39 -0
  63. package/main/source-updater.mjs +298 -0
  64. package/main/store/home.mjs +84 -7
  65. package/main/store/project.mjs +15 -19
  66. package/main/store/work-items.mjs +150 -6
  67. package/main/store.mjs +245 -13
  68. package/main/supervisor.mjs +1634 -417
  69. package/main/task-commands.mjs +8 -10
  70. package/main/task-folders.mjs +94 -16
  71. package/main/task-remote-control.mjs +3 -3
  72. package/main/task-terminals.mjs +14 -2
  73. package/main/team/index.mjs +440 -0
  74. package/main/team/memory-cloud.mjs +247 -0
  75. package/main/team/projects.mjs +162 -0
  76. package/main/team/session.mjs +225 -0
  77. package/main/team/sign-in.mjs +106 -0
  78. package/main/team/supabase-backend.mjs +231 -0
  79. package/main/team/sync.mjs +163 -0
  80. package/main/updater.mjs +15 -5
  81. package/main/write-before-reload.mjs +50 -0
  82. package/mcp/core/page.mjs +10 -12
  83. package/mcp/core/work.mjs +123 -26
  84. package/mcp/tools.mjs +13 -6
  85. package/package.json +21 -9
  86. package/renderer/dist/assets/agentbox-icon-F1oN9nj_.svg +15 -0
  87. package/renderer/dist/assets/index-ChKsQkRw.js +1169 -0
  88. package/renderer/dist/assets/index-ClBXnq3B.css +32 -0
  89. package/renderer/dist/index.html +2 -2
  90. package/scripts/after-install.mjs +14 -0
  91. package/scripts/approval-server.sh +7 -2
  92. package/scripts/memory-gate-hook.sh +59 -0
  93. package/shared/agent-import.mjs +39 -16
  94. package/shared/agents.d.mts +26 -8
  95. package/shared/agents.mjs +100 -108
  96. package/shared/analytics-events.mjs +19 -2
  97. package/shared/answers.mjs +12 -0
  98. package/shared/bridge-map.d.mts +12 -0
  99. package/shared/bridge-map.mjs +38 -1
  100. package/shared/claude-commands.generated.mjs +2 -2
  101. package/shared/claude-models.generated.mjs +6 -6
  102. package/shared/claude-usage.mjs +6 -6
  103. package/shared/codex-import.mjs +7 -7
  104. package/shared/codex-modes.d.mts +50 -0
  105. package/shared/codex-modes.mjs +1 -1
  106. package/shared/codex-usage.mjs +7 -8
  107. package/shared/demo-world.mjs +6 -6
  108. package/shared/engines.mjs +27 -10
  109. package/shared/feedback.d.mts +14 -0
  110. package/shared/feedback.mjs +44 -0
  111. package/shared/first-run-examples.mjs +4 -4
  112. package/shared/first-run-practice.d.mts +16 -0
  113. package/shared/first-run-practice.mjs +176 -70
  114. package/shared/fresh-user-home.mjs +34 -4
  115. package/shared/instruction-defaults.json +2 -2
  116. package/shared/product-name.mjs +4 -2
  117. package/shared/project-folder-check.mjs +9 -3
  118. package/shared/rank.d.mts +1 -0
  119. package/shared/rank.mjs +22 -0
  120. package/shared/referenced-files.mjs +1 -1
  121. package/shared/runs-on.d.mts +20 -0
  122. package/shared/runs-on.mjs +79 -0
  123. package/shared/schemes.d.mts +11 -0
  124. package/shared/schemes.mjs +1 -1
  125. package/shared/shortcuts.d.mts +41 -0
  126. package/shared/shortcuts.mjs +236 -0
  127. package/shared/spawn-trouble.d.mts +2 -0
  128. package/shared/spawn-trouble.mjs +40 -19
  129. package/shared/team-rules.d.mts +17 -0
  130. package/shared/team-rules.mjs +230 -0
  131. package/shared/team-status.d.mts +11 -0
  132. package/shared/team-status.mjs +90 -0
  133. package/shared/terminal-links.d.mts +2 -0
  134. package/shared/terminal-links.mjs +55 -0
  135. package/shared/terminal-state.d.mts +1 -0
  136. package/shared/terminal-state.mjs +16 -0
  137. package/shared/thread-cards.d.mts +19 -0
  138. package/shared/thread-cards.mjs +254 -0
  139. package/shared/usage.d.mts +1 -1
  140. package/shared/usage.mjs +6 -7
  141. package/shared/work-items.mjs +258 -28
  142. package/shared/work-lines.mjs +393 -16
  143. package/worker-permissions.json +1 -1
  144. package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
  145. package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
  146. package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
  147. package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
  148. package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
  149. package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
  150. package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
  151. package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
  152. package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
  153. package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
  154. package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
  155. package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
  156. package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
  157. package/renderer/dist/assets/index-B7hhfHrT.js +0 -684
  158. package/renderer/dist/assets/index-CwiDv5bL.css +0 -32
  159. package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
  160. package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
  161. package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
  162. package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
  163. package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
  164. package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
  165. package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
  166. package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
  167. package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
  168. package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
  169. package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
  170. package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
  171. package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
  172. package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
  173. package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
  174. package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
  175. package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
  176. package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
  177. package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
  178. package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
  179. package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
  180. package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
  181. package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
  182. package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
  183. package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
  184. package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
  185. package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
  186. package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
  187. package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
  188. package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
  189. package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
  190. package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
  191. package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
  192. package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
  193. package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
  194. package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
  195. package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
  196. package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
  197. package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
  198. package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
  199. package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
  200. package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
  201. package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
  202. package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
  203. package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
  204. package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
  205. package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
  206. package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
  207. package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
  208. package/renderer/dist/emulator/audio-rules.mjs +0 -74
  209. package/renderer/dist/emulator/data/compression/README.md +0 -11
  210. package/renderer/dist/emulator/data/compression/extract7z.js +0 -1
  211. package/renderer/dist/emulator/data/compression/extractzip.js +0 -1
  212. package/renderer/dist/emulator/data/compression/libunrar.js +0 -519
  213. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  214. package/renderer/dist/emulator/data/cores/README.md +0 -22
  215. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  216. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  217. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  218. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  219. package/renderer/dist/emulator/data/cores/package.json +0 -65
  220. package/renderer/dist/emulator/data/cores/reports/mgba.json +0 -1
  221. package/renderer/dist/emulator/data/emulator.css +0 -1614
  222. package/renderer/dist/emulator/data/loader.js +0 -170
  223. package/renderer/dist/emulator/data/localization/README.md +0 -63
  224. package/renderer/dist/emulator/data/localization/af-FR.json +0 -339
  225. package/renderer/dist/emulator/data/localization/ar-AR.json +0 -301
  226. package/renderer/dist/emulator/data/localization/ben-BEN.json +0 -301
  227. package/renderer/dist/emulator/data/localization/de-GER.json +0 -301
  228. package/renderer/dist/emulator/data/localization/el-GR.json +0 -301
  229. package/renderer/dist/emulator/data/localization/en-US.json +0 -339
  230. package/renderer/dist/emulator/data/localization/es-ES.json +0 -310
  231. package/renderer/dist/emulator/data/localization/fa-AF.json +0 -310
  232. package/renderer/dist/emulator/data/localization/hi-HI.json +0 -301
  233. package/renderer/dist/emulator/data/localization/it-IT.json +0 -302
  234. package/renderer/dist/emulator/data/localization/ja-JA.json +0 -301
  235. package/renderer/dist/emulator/data/localization/jv-JV.json +0 -301
  236. package/renderer/dist/emulator/data/localization/ko-KO.json +0 -336
  237. package/renderer/dist/emulator/data/localization/pt-BR.json +0 -361
  238. package/renderer/dist/emulator/data/localization/retroarch.json +0 -617
  239. package/renderer/dist/emulator/data/localization/ro-RO.json +0 -310
  240. package/renderer/dist/emulator/data/localization/ru-RU.json +0 -301
  241. package/renderer/dist/emulator/data/localization/tr-TR.json +0 -310
  242. package/renderer/dist/emulator/data/localization/vi-VN.json +0 -302
  243. package/renderer/dist/emulator/data/localization/zh-CN.json +0 -351
  244. package/renderer/dist/emulator/data/src/GameManager.js +0 -464
  245. package/renderer/dist/emulator/data/src/compression.js +0 -142
  246. package/renderer/dist/emulator/data/src/emulator.js +0 -6158
  247. package/renderer/dist/emulator/data/src/gamepad.js +0 -167
  248. package/renderer/dist/emulator/data/src/nipplejs.js +0 -1
  249. package/renderer/dist/emulator/data/src/shaders.js +0 -236
  250. package/renderer/dist/emulator/data/src/socket.io.min.js +0 -7
  251. package/renderer/dist/emulator/data/src/storage.js +0 -125
  252. package/renderer/dist/emulator/data/version.json +0 -5
  253. package/renderer/dist/emulator/host.js +0 -299
  254. package/renderer/dist/emulator/index.html +0 -28
  255. package/renderer/dist/emulator/provenance.json +0 -6
@@ -0,0 +1,254 @@
1
+ // A THREAD'S STATE, ITS SUMMARY, AND THE CARD A TEAMMATE SEES OF IT.
2
+ //
3
+ // Approved 2026-10-01 (w-e731ca9376). Three pure rules, shared by the Mac that
4
+ // publishes cards (main/team) and the window that draws them (renderer), so
5
+ // the owner and a teammate are always shown the same words.
6
+ //
7
+ // threadState one of four states and only four: waiting, running,
8
+ // scheduled, done. A status is always one of those
9
+ // predefined states, never "needs your pick".
10
+ // summaryOf problem, progress and solution. The agent keeps them
11
+ // current (update_work_item); until it has written one, the
12
+ // ask and the latest word on the thread stand in, so a new
13
+ // thread is never a blank summary.
14
+ // cardsFor every thread worth showing the team, as a card: the summary
15
+ // and nothing more; a card never adds information. A
16
+ // private thread publishes no card at all (decided 2026-10-01
17
+ // from user research): not a wordless one, not a count.
18
+
19
+ export const THREAD_STATES = ['waiting', 'running', 'scheduled', 'done'];
20
+
21
+ /** Which of the four states a thread is in. */
22
+ export function threadState(item, now = Date.now()) {
23
+ if (item.status === 'done') return 'done';
24
+ // A thread in Later is not running and nobody has to act on it, which is the
25
+ // scheduled family. It has no moment, so what it SAYS is "Not started"
26
+ // (threadStateWords in the window); the four card states are unchanged.
27
+ if (item.start === 'later') return 'scheduled';
28
+ if (Number.isFinite(item.runAt) && item.runAt > now) return 'scheduled';
29
+ if (item.status === 'claimed' && item.claim && !item.claimExpired) return 'running';
30
+ // Sent to an agent and not yet picked up: it is about to run, nobody has to
31
+ // act on it, so it is not waiting on anyone.
32
+ const sentToAnAgent = (item.labels ?? []).includes('founder') && !item.result && !item.note
33
+ && item.kind !== 'question' && item.kind !== 'review' && !personHolds(item);
34
+ return sentToAnAgent ? 'running' : 'waiting';
35
+ }
36
+
37
+ const personHolds = (item) => typeof item.assignee === 'string' && item.assignee !== 'agent';
38
+
39
+ // ONLY EMPHASIS IS STRIPPED, AND ONLY WHERE IT IS A PAIR (w-a33b339772).
40
+ //
41
+ // This used to be `.replace(/[*_`>]/g, '')`: every star, underscore, backtick
42
+ // and angle bracket in the text, gone. A pasted search command
43
+ // (`--include=*.md --include=*.json`) lost all three of its stars on the
44
+ // summary line, and `user_id_v2` came back as `useridv2`.
45
+ //
46
+ // A pair is a mark, then a WORD character, then the closing mark: that is what
47
+ // `*bold*` looks like and what a glob never does, because a glob's star is
48
+ // followed by a dot or a slash. Underscores have to clear the word on either
49
+ // side too, or `user_id_v2` reads as emphasis around `id`. A mark with no
50
+ // partner is left exactly where the person put it.
51
+ const EMPHASIS = [
52
+ [/\*\*(\w[^*\n]*?)\*\*/g, '$1'],
53
+ [/(?<!\w)__(\w[^_\n]*?)__(?!\w)/g, '$1'],
54
+ [/\*(\w[^*\n]*?)\*/g, '$1'],
55
+ [/(?<!\w)_(\w[^_\n]*?)_(?!\w)/g, '$1'],
56
+ [/`([^`\n]+)`/g, '$1'],
57
+ ];
58
+
59
+ // WHERE A SENTENCE REALLY ENDS. The old cut was "any of .!? then a space", so
60
+ // the lone ` . ` that means "this directory" ended the sentence and the
61
+ // summary showed `grep -r foo .` and nothing else. A full stop ends a sentence
62
+ // when it closes a word AND what follows is a new sentence: whitespace then a
63
+ // capital (through an opening quote or bracket), or the end of the text.
64
+ const SENTENCE = /^(.+?\w[.!?]["'”’)\]]?)(?=\s+["“'(\[]?[A-Z]|\s*$)/;
65
+
66
+ // The first sentence of some prose, without markdown, short enough for a line.
67
+ export function firstSentence(text, max = 240) {
68
+ if (typeof text !== 'string') return '';
69
+ let plain = text
70
+ .replace(/^#+\s.*$/gm, '')
71
+ .replace(/^##\s*Options[\s\S]*$/m, '')
72
+ // A blockquote's mark is the one at the start of its line. Elsewhere `>`
73
+ // is a redirect or an arrow and belongs to whoever typed it.
74
+ .replace(/^\s*>\s?/gm, '');
75
+ for (const [re, to] of EMPHASIS) plain = plain.replace(re, to);
76
+ plain = plain.replace(/\s+/g, ' ').trim();
77
+ const m = plain.match(SENTENCE);
78
+ const one = (m ? m[1] : plain).trim();
79
+ return one.length > max ? `${one.slice(0, max - 1).trimEnd()}…` : one;
80
+ }
81
+
82
+ // HOW LONG A SUMMARY LINE AN AGENT WRITES MAY BE (w-b51b2e1c86): about four
83
+ // lines of the panel, so the name, the three lines and the properties fit one
84
+ // window without scrolling. Asked for on 2026-10-04 off a summary of 50, 63 and
85
+ // 95 words that ran the properties off the bottom; agents' lines then ran a
86
+ // median of 30 to 36 words. The store tool refuses a longer one
87
+ // (mcp/core/work.mjs); a person's own edits are not limited.
88
+ export const SUMMARY_WORDS = 25;
89
+
90
+ // THE SUMMARY IS CONTEXT, THEN DONE (w-54e9c7243f, 2026-10-05). `problem` is
91
+ // drawn as Context, a few sentences that bring the thread back to mind, and
92
+ // `progress` as Done, the steps taken so far, one per line, newest last.
93
+ // `solution` is no longer asked for or drawn. Measured on the approved
94
+ // pictures: about 35 words of context filled five lines and was enough to
95
+ // answer from; steps ran 4 to 15 words, and six left the properties on screen.
96
+ // The old field names stay so the team cloud needs no migration.
97
+ export const CONTEXT_WORDS = 45;
98
+ export const DONE_STEPS = 6;
99
+ export const DONE_STEP_WORDS = 15;
100
+
101
+ export function wordsIn(text) {
102
+ return typeof text === 'string' ? text.split(/\s+/).filter(Boolean).length : 0;
103
+ }
104
+
105
+ /** The Done steps in `progress`: one per line, the marks a writer puts in front of a step taken off. */
106
+ export function doneSteps(progress) {
107
+ if (typeof progress !== 'string') return [];
108
+ return progress.split('\n').map((l) => l.trim().replace(/^(?:[-*•✓✔]\s*)+/, '').trim()).filter(Boolean);
109
+ }
110
+
111
+ /** The thread's summary: what the agent or the person wrote, else stand-ins. */
112
+ export function summaryOf(item) {
113
+ const has = (f) => typeof item[f] === 'string';
114
+ // A finished turn's result is what was done, so it stands in for the
115
+ // Solution, and a checkpoint for the Progress. Read the other way round, a
116
+ // persona saw "Progress: Done: ..." over "Solution: Not written yet" and
117
+ // could not tell whether the thread was finished or stuck.
118
+ // A result that asks something is still progress: the thread waits on its
119
+ // owner's answer, and nothing is solved yet.
120
+ const said = firstSentence(item.result || '');
121
+ const asks = said.endsWith('?');
122
+ return {
123
+ problem: has('problem') ? item.problem : firstSentence(item.body || item.title),
124
+ progress: has('progress') ? item.progress : (asks ? said : firstSentence(item.result ? '' : item.note || '')),
125
+ solution: has('solution') ? item.solution : (asks ? '' : said),
126
+ written: has('problem') || has('progress') || has('solution'),
127
+ };
128
+ }
129
+
130
+ const startOfDay = (now) => { const d = new Date(now); d.setHours(0, 0, 0, 0); return d.getTime(); };
131
+
132
+ /**
133
+ * One card per thread worth showing the team: every open thread, and the ones
134
+ * finished today. Message threads between two people are never carded; they
135
+ * are nobody else's business and they are not work. Nor is a private thread.
136
+ */
137
+ /**
138
+ * NOTHING FROM BEFORE YOU JOINED IS SHARED UNLESS YOU SHARE IT (2026-10-01).
139
+ * Her own store holds hundreds of threads written for nobody but her, and the
140
+ * first sign-in would have put a summary of every one of them on the team's
141
+ * board. So a thread is shown when it was marked Team, or when it is not
142
+ * private and was started after `since`, the moment this person began sharing
143
+ * on this Mac. No `since` is the old rule: everything not private.
144
+ */
145
+ /**
146
+ * AND A THREAD MAY BE SHARED WITH CHOSEN PEOPLE RATHER THAN THE TEAM
147
+ * (w-41ff964775: a thread may be seen by a few people, not only by everyone
148
+ * or nobody). That is still sharing, so this stays the one rule for
149
+ * whether a card is published at all, and `shownToPeople` says who it reaches.
150
+ *
151
+ * Chosen people naming NOBODY reaches nobody, so nothing is published: a card
152
+ * carrying an empty list would otherwise read as the whole team's, and that is
153
+ * the one mistake nobody can take back.
154
+ */
155
+ // A THREAD WITH NO WORD OF ITS OWN FOLLOWS ITS PROJECT (w-b989839656). A
156
+ // thread from before you joined still stays yours whatever the project says.
157
+ export function shownToTeam(item, since = null, product = null) {
158
+ const own = ownVisibility(item);
159
+ if (own === 'team') return true;
160
+ if (own === 'people') return shownToPeople(item, product).length > 0;
161
+ if (own === 'private') return false;
162
+ if (since && (item?.createdAt ?? 0) < since) return false;
163
+ return projectSeenBy(product).who !== 'private';
164
+ }
165
+
166
+ const ownVisibility = (item) => {
167
+ const v = item?.visibility;
168
+ return v === 'team' || v === 'people' || v === 'private' ? v : undefined;
169
+ };
170
+ const cleanIds = (ids) => [...new Set((Array.isArray(ids) ? ids : []).filter((id) => typeof id === 'string' && id))];
171
+
172
+ /**
173
+ * WHO SEES A PROJECT'S THREADS (w-b989839656, approved 2026-10-05). Projects
174
+ * stay on each Mac; what a project decides is only whose Team page its
175
+ * threads' summaries reach. 'private' (Just you), 'team', or 'people' with
176
+ * the people named. A project from before this setting existed keeps the team,
177
+ * which is what it already did; a new one is made Just you (main/store.mjs);
178
+ * My Workspace is Just you whatever its file says. Chosen people naming nobody
179
+ * reaches nobody, so it reads as Just you and never as the team.
180
+ */
181
+ export function projectSeenBy(product) {
182
+ if (product?.personal === true || product?.seenBy === 'private') return { who: 'private', people: [] };
183
+ if (product?.seenBy === 'people') {
184
+ const people = cleanIds(product.seenByPeople);
185
+ return people.length ? { who: 'people', people } : { who: 'private', people: [] };
186
+ }
187
+ return { who: 'team', people: [] };
188
+ }
189
+
190
+ /**
191
+ * THE WORD A THREAD'S VISIBILITY READS AS, once its project has had its say:
192
+ * its own word if it has one, else its project's. Undefined where the project
193
+ * leaves it to the team, so the "before you joined" rule still applies.
194
+ * Failing closed on the project, rather than writing a word on each new
195
+ * thread, is what covers every path a thread arrives by at once: the
196
+ * composer, an agent filing a proposal, a repeat.
197
+ */
198
+ export function visibilityOf(item, product = null) {
199
+ const own = ownVisibility(item);
200
+ if (own) return own;
201
+ const { who } = projectSeenBy(product);
202
+ return who === 'team' ? undefined : who;
203
+ }
204
+
205
+ /** Whom a shared thread reaches, by id, or [] for the whole team. */
206
+ export function shownToPeople(item, product = null) {
207
+ const own = ownVisibility(item);
208
+ if (own === 'people') return cleanIds(item.visibleTo);
209
+ if (own) return [];
210
+ const seen = projectSeenBy(product);
211
+ return seen.who === 'people' ? seen.people : [];
212
+ }
213
+
214
+ export function cardsFor({ products, readItems, now = Date.now(), since = null }) {
215
+ const cards = [];
216
+ const titleOf = new Map();
217
+ const all = [];
218
+ for (const product of products) {
219
+ if (product.team?.direct) continue;
220
+ for (const item of readItems(product)) {
221
+ if (item.agent) continue;
222
+ // A LINK NAMES A THREAD ONLY IF THAT THREAD IS ITSELF VISIBLE. A public
223
+ // card that is blocked by a private thread said the private thread's
224
+ // title out loud (review, 2026-10-01); now it says only that one exists.
225
+ //
226
+ // A thread shared with chosen people is named by nothing either
227
+ // (w-41ff964775): every card is read by its own set of people, and a
228
+ // blocker shared with two of them would have told a third its title.
229
+ // Only a thread the whole team may read is safe in anybody's links.
230
+ const open = shownToTeam(item, since, product) && !shownToPeople(item, product).length;
231
+ titleOf.set(item.id, open ? item.label || item.title : null);
232
+ all.push({ item, product });
233
+ }
234
+ }
235
+ const linked = (ids) => (Array.isArray(ids) ? ids : []).map((id) => ({ id, title: titleOf.get(id) ?? null }));
236
+ const today = startOfDay(now);
237
+ for (const { item, product } of all) {
238
+ if (!shownToTeam(item, since, product)) continue;
239
+ const state = threadState(item, now);
240
+ if (state === 'done' && !(item.updatedAt >= today)) continue;
241
+ const s = summaryOf(item);
242
+ cards.push({
243
+ threadId: item.id, visible: true, title: item.label || item.title, project: product.name,
244
+ // WHO IT REACHES: the chosen people, or NO LIST AT ALL for the whole
245
+ // team. Never an empty list, which would have to mean both. Both clouds
246
+ // carry it and refuse to show the card to anybody else.
247
+ people: shownToPeople(item, product).length ? shownToPeople(item, product) : null,
248
+ state, priority: Number.isFinite(item.priority) ? item.priority : null,
249
+ problem: s.problem || null, progress: s.progress || null, solution: s.solution || null,
250
+ blockedBy: linked(item.blockedBy), blocks: linked(item.blocks), updatedAt: item.updatedAt ?? now,
251
+ });
252
+ }
253
+ return cards;
254
+ }
@@ -6,7 +6,7 @@
6
6
  export interface UsageLimit {
7
7
  span: 'session' | 'week';
8
8
  qualifier: string | null;
9
- /** What the panel calls it, in her words, stamped on by whichever reader
9
+ /** What the panel calls it, in plain words, stamped on by whichever reader
10
10
  * produced it. See renderer/src/types.ts for why it is not derived here. */
11
11
  name: string;
12
12
  percent: number;
package/shared/usage.mjs CHANGED
@@ -26,7 +26,7 @@
26
26
  // AND A LIMIT NOW CARRIES ITS OWN NAME. `limitName` used to derive one from
27
27
  // Claude Code's three spans and its qualifier, which is how "This week, Fable"
28
28
  // -- a Claude model -- could have appeared over a Codex reading. Each reader
29
- // names its own limits, in her words, and this file prints the name it is
29
+ // names its own limits, in plain words, and this file prints the name it is
30
30
  // given.
31
31
 
32
32
  import { DEFAULT_ENGINE } from './engines.mjs';
@@ -171,12 +171,11 @@ export function sessionLimit(limits) {
171
171
  * THE ONE LIMIT THE CORNER IS ABOUT, on an account that may not have a five hour
172
172
  * window at all.
173
173
  *
174
- * The bar used to be the session limit or nothing, and on 2026-09-18 that turned
175
- * out to mean NOTHING on the founder's own Codex plan: measured across the 1,140
176
- * rate-limit records in her twenty-five most recent Codex sessions, every window
177
- * she has is a seven day one and `secondary` is never populated. Her weekly limit
178
- * was at 100% used that day, which is exactly the moment a person needs the
179
- * corner, and the corner would have been blank.
174
+ * The bar used to be the session limit or nothing, and that turned out to mean
175
+ * NOTHING on some Codex plans: every window such an account has is a seven day
176
+ * one and `secondary` is never populated. A weekly limit at 100% used is
177
+ * exactly the moment a person needs the corner, and the corner would have been
178
+ * blank.
180
179
  *
181
180
  * The session window still wins whenever there is one, because it is the one that
182
181
  * stops her today. When there is not, the fullest window she does have is drawn