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,79 @@
1
+ // WHAT THE AGENTS ARE RUNNING ON, IN WORDS.
2
+ //
3
+ // From a session with a new user, 2026-10-07: "He didn't realize it
4
+ // auto-connected to Claude/Codex; he wasn't sure how it was even running. Would
5
+ // be nice to have some small indication."
6
+ //
7
+ // He was right that nothing said. The app finds whichever coding agent is
8
+ // already signed in on the Mac and runs every worker on that subscription
9
+ // (main/claude-plan.mjs, main/codex-account.mjs), and the walk is SILENT about
10
+ // it by design: `needsPlan` in renderer/src/plan-setup.ts skips the plan
11
+ // question entirely when one tool is found and signed in, which is the right
12
+ // call and leaves nobody told. The only screen that has ever named the account
13
+ // is Settings, two clicks in, and you have to suspect there is something to
14
+ // look for before you go.
15
+ //
16
+ // THE PLAN'S VOCABULARY, NOT THE TOOL'S. "Claude" and "ChatGPT" here, never
17
+ // "Claude Code" and "Codex", which is the rule the walk already follows
18
+ // (renderer/src/plan-setup.ts: words people know, "never the names of the tools
19
+ // underneath"). The fact being shown is WHOSE SUBSCRIPTION IS BEING SPENT, and
20
+ // nobody pays for a CLI. The tool names stay where they belong: on a byline
21
+ // about which agent is working a row (`engineWordFor`, renderer/src/byline.ts),
22
+ // which is a different question with a different answer.
23
+ //
24
+ // NOTHING IS SAID UNTIL SOMETHING IS KNOWN. Every function here answers null on
25
+ // an engine it cannot name, and the screens draw nothing at all for null rather
26
+ // than "checking…" or a dash. A corner that says it does not know is worse than
27
+ // a corner that says nothing: it spends the glance and answers no question.
28
+
29
+ /** The word for the plan behind an engine. Null for an engine we cannot name. */
30
+ export function runsOnName(engine) {
31
+ if (engine === 'claude') return 'Claude';
32
+ if (engine === 'codex') return 'ChatGPT';
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * THE LINE ON A GLANCE SURFACE. "Claude · Max 20x", or just "Claude" where the
38
+ * plan cannot be read.
39
+ *
40
+ * The plan is the raw label the tool itself reports, already turned into words
41
+ * by whoever read it (`planLabel` in main/claude-plan.mjs for Claude, the
42
+ * named-plan table in main/codex-account.mjs for ChatGPT). It is never
43
+ * invented here: a plan nobody measured, printed confidently, is the app
44
+ * lying about what somebody is paying for.
45
+ */
46
+ export function runsOnLine({ engine = null, plan = null } = {}) {
47
+ const name = runsOnName(engine);
48
+ if (!name) return null;
49
+ const words = String(plan ?? '').trim();
50
+ return words ? `${name} · ${words}` : name;
51
+ }
52
+
53
+ /**
54
+ * THE WHOLE SENTENCE, for a hover and for whatever reads the screen aloud. The
55
+ * glance surface carries two words and a plan; this is where the rest of the
56
+ * answer lives, and it is the half that tells a new person what they are
57
+ * looking at.
58
+ */
59
+ export function runsOnTitle({ engine = null, plan = null } = {}) {
60
+ const name = runsOnName(engine);
61
+ if (!name) return null;
62
+ const words = String(plan ?? '').trim();
63
+ const plansentence = words ? ` Your ${words} plan.` : '';
64
+ return `Your agents run on the ${name} account already signed in on this Mac.${plansentence}`;
65
+ }
66
+
67
+ /**
68
+ * THE ONE LINE DURING THE WALK, on the Mac where there was nothing to ask.
69
+ *
70
+ * It replaces silence, not a question: somebody with no coding agent set up
71
+ * still gets the plan question and the setup card, and never sees this.
72
+ */
73
+ export function foundOnThisMac({ engine = null, plan = null } = {}) {
74
+ const name = runsOnName(engine);
75
+ if (!name) return null;
76
+ const words = String(plan ?? '').trim();
77
+ if (words) return `Your agents will run on the ${name} ${words} plan already signed in on this Mac.`;
78
+ return `Your agents will run on the ${name} account already signed in on this Mac.`;
79
+ }
@@ -0,0 +1,11 @@
1
+ // Types for shared/schemes.mjs, for the window side.
2
+ //
3
+ // The lists are built from the app's past names at load, so their length is
4
+ // not a constant and nothing here pretends otherwise. `isScheme` takes the
5
+ // protocol as `URL` hands it over, with its colon on the end.
6
+ export declare const DOC_SCHEME: string;
7
+ export declare const IMG_SCHEME: string;
8
+ export declare const DOC_SCHEMES: readonly string[];
9
+ export declare const IMG_SCHEMES: readonly string[];
10
+ export declare const ALL_SCHEMES: readonly string[];
11
+ export declare function isScheme(schemes: readonly string[], protocol: string): boolean;
@@ -10,7 +10,7 @@
10
10
  // of these schemes is STORED: a product's logo is kept in the store as
11
11
  // `<scheme>://file/…` and a message she has already been sent holds picture
12
12
  // urls in its body. Dropping the old scheme would not degrade those, it would
13
- // break them, and the symptom is a broken image in a row she wrote months ago.
13
+ // break them, and the symptom is a broken image in a row written months ago.
14
14
  // So the current name is what new urls are built with, and every name this app
15
15
  // has had is still registered and still answered.
16
16
 
@@ -0,0 +1,41 @@
1
+ // Types for shared/shortcuts.mjs, which is plain ESM so that the Settings page
2
+ // (Vite) and the supervisor (Node) read one list of keys. See the module for
3
+ // why each row reads the way it does.
4
+
5
+ export interface Shortcut {
6
+ /**
7
+ * The caps, drawn in order. A chord is ONE cap ('⌘K', '⌘↵'), the way the
8
+ * walk already draws them, because ⌘ and K are not two keys to a reader.
9
+ *
10
+ * AND THE HOVER HINT NO LONGER AGREES WITH THAT. ⌘J was drawn both ways on
11
+ * w-2f7fac6027, one cap per key won, because a single ⌘J cap looked
12
+ * condensed next to the usual way shortcuts are drawn. So
13
+ * `capsFor` in hint-plate.ts splits a chord into one cap per glyph, and this
14
+ * page does not. That is a real disagreement between two surfaces and it is
15
+ * left standing rather than quietly resolved: this is a reference page where
16
+ * a chord is read as one thing to press, and the hint is a label on a
17
+ * control she is pointing at. If the page ever looks condensed too,
18
+ * the fix is to take `capsFor` here, not to put one cap back there. */
19
+ keys: string[];
20
+ /**
21
+ * The word set between two caps. Absent means they simply sit side by side,
22
+ * which is what ↑ ↓ means: both of them, either way. */
23
+ join?: 'or' | 'to';
24
+ /** What it does, in plain English. ONE sentence, under seventy characters. */
25
+ what: string;
26
+ }
27
+
28
+ export interface ShortcutGroup {
29
+ /** THE HEADING SAYS WHEN THE KEYS WORK, LITERALLY, AND IT IS THE ONLY PROSE
30
+ * A GROUP GETS. There is deliberately no note field: see the module. */
31
+ label: string;
32
+ keys: Shortcut[];
33
+ }
34
+
35
+ export const SHORTCUTS: ShortcutGroup[];
36
+
37
+ /** Every cap drawn on the page, flattened. The test walks this. */
38
+ export function everyKey(groups?: ShortcutGroup[]): string[];
39
+
40
+ /** The keys as one block of plain text, for an agent's system prompt. */
41
+ export function keysForAgents(groups?: ShortcutGroup[]): string;
@@ -0,0 +1,236 @@
1
+ // PURE. EVERY KEY AGENTBOX HAS, IN THE ORDER A PERSON MEETS THEM.
2
+ //
3
+ // A shortcuts page in settings, because onboarding a non-technical tester
4
+ // meant saying each key out loud, one at a time: the app had a switch for
5
+ // whether it WHISPERS its keys and no page anywhere that says what they are.
6
+ //
7
+ // WHY THIS IS A FILE AND NOT MARKUP IN THE COMPONENT. A page that prints a key
8
+ // the handler does not have is worse than no page at all — that is the rule
9
+ // `list-rules.ts` already keeps for the hint on a row, and the reason it holds
10
+ // even harder here is that a hint is read for a second and a reference page is
11
+ // believed. So the list is a value, and
12
+ // `tests/the-shortcuts-page-lists-keys-that-work.test.mjs` reads every entry
13
+ // below against the branch in App.tsx (or main/main.mjs, or Compose.tsx) that
14
+ // actually runs it. A key added here without a handler fails the suite.
15
+ //
16
+ // WHY IT IS IN shared/ AND PLAIN ESM (w-7ec8553e23, 2026-10-04). It lived in
17
+ // renderer/src as TypeScript until an agent told the user to "Press S on this
18
+ // task" three days after S stopped scheduling: nothing a session is handed
19
+ // said what the keys are, so it went by an old memory. The supervisor now
20
+ // hands every run `keysForAgents()`, read off this same list, and main cannot
21
+ // import TypeScript. Types are in shortcuts.d.mts beside it.
22
+ //
23
+ // ONE SENTENCE A ROW, AND IT IS SHORT (w-1bc916a880). Every
24
+ // sentence here is now under seventy characters and the test enforces it. A row
25
+ // that wants a second idea becomes its own row, or it does not go on the page.
26
+ //
27
+ // AND NO KEY HIDES INSIDE A SENTENCE.
28
+ //
29
+ // HOW IT IS GROUPED, and it is not how the code is arranged. The code has one
30
+ // switch for the list, one branch for an open task, a chord block above both
31
+ // and a main-process handler under all of it. Nobody thinks in those. Four
32
+ // headings, and every key sits under the one that matches when she would want
33
+ // it: in your inbox, in a task you have opened, closing and scheduling and
34
+ // undoing, anywhere in the app.
35
+ //
36
+ // EVERY HEADING IS LITERAL AND THERE IS NO SUBHEADING AT ALL (w-1bc916a880,
37
+ // second round).
38
+ //
39
+ // The plainest point first: that heading named one of the two
40
+ // directions its own rows draw. Three of the four were figures of speech, and a
41
+ // figure of speech is a second thing to decode on a page whose only job is to
42
+ // be looked up. So a heading now says WHEN the keys under it work, in the words
43
+ // the app itself uses for those places, and the one group that cannot be named
44
+ // that way is named by what its keys do instead, because all four of its keys
45
+ // work in both places.
46
+ //
47
+ // What the three of them carried, and where it went:
48
+ //
49
+ // "In Scheduled, E brings a task back instead of closing it." This was the
50
+ // most confusing one. It is also the same class of thing as G, B and
51
+ // the import letters below: a behaviour that only happens in one view. The
52
+ // page's existing rule is that those stay off it, so this now obeys the rule
53
+ // the rest of the file already kept.
54
+ //
55
+ // The two ↵ rows say what ↵ does when it does something.
56
+ //
57
+ // "Anything without a key of its own is in ⌘K by name." The lede at the top
58
+ // of the page already says this, so it was the same sentence twice.
59
+ //
60
+ // WHAT IS DELIBERATELY NOT HERE, so nobody adds it back as an oversight:
61
+ //
62
+ // ⌘1..4, for PRIORITY or for anything else. Priority was removed deliberately
63
+ // and that handler no longer fires. The chord then went to a section of the
64
+ // sidebar until 2026-10-02, and that is gone as well: Tab moves along the tabs
65
+ // now and is listed above.
66
+ //
67
+ // Y, N and I on a row in the list. Real since w-9741ed0e0b, but they answer
68
+ // a Codex conversation's import row and are dead on every other row in the
69
+ // app. A reference page that lists a key which does nothing on nineteen
70
+ // screens out of twenty teaches somebody that the app ignores them.
71
+ //
72
+ // ⇥ inside the new task card, which changes the project it goes to. It is
73
+ // gated to that one card, the card draws its projects where she can see
74
+ // them, and it used to ride along inside the C row's sentence, which is the
75
+ // fault the NO KEY HIDES INSIDE A SENTENCE block above is about.
76
+ //
77
+ // ⇧⇥ in the reply box, which cycles Claude Code's permission modes. Real,
78
+ // and it belongs to the reply box rather than to the app; it is also the one
79
+ // key here that cannot be said without the word "permissions", and Settings
80
+ // already has a whole group explaining those.
81
+ //
82
+ // ⌘R, ⌘=, ⌘- and ⌘0. The Mac's own chords, answered in main.mjs so they work
83
+ // over the game. Nobody needs to be taught reload and zoom.
84
+ //
85
+ // EVERY SENTENCE IS PLAIN. A tester does not know what a modal, a pane, a
86
+ // binary or a view is. No row uses a word the app does not print on screen
87
+ // somewhere she can see it. THE SIDEBAR ROW IS WHY THAT NOW MATTERS TWICE: it
88
+ // used to name the sections, "Inbox, Scheduled, In progress, Closed", and
89
+ // that went wrong in both halves. The real order is Inbox, In
90
+ // progress, Scheduled, Closed (`workspaceDestinations`), and Scheduled is
91
+ // drawn only when something is actually scheduled. A reference
92
+ // page that recites a list the sidebar can change goes stale without anybody
93
+ // touching it, so the row names no sections at all.
94
+
95
+ import { NAME } from './product-name.mjs';
96
+
97
+ /** @type {import('./shortcuts.d.mts').ShortcutGroup[]} */
98
+ export const SHORTCUTS = [
99
+ {
100
+ // App.tsx, the `switch (e.key)` at the foot of the key handler: this is the
101
+ // list with nothing open, which is where anybody starts.
102
+ //
103
+ // "IN YOUR INBOX", BECAUSE INBOX IS A WORD THE APP PRINTS AND TASK LIST IS
104
+ // NOT (w-1bc916a880, third round).
105
+ //
106
+ // The mistake is worth naming because it is the same one
107
+ // the round before was meant to fix. "Task list" was reached for to cover
108
+ // the fact that these keys work in all four sidebar sections rather than
109
+ // only the first, which is an accurate thing about the code and a phrase
110
+ // nobody sees on a screen. Inbox is the first row of the
111
+ // sidebar. The heading claims these keys work there; it does not claim they
112
+ // work ONLY there, and one of its rows moves between the sections anyway.
113
+ label: 'In your inbox',
114
+ keys: [
115
+ // case 'j': case 'J': case 'ArrowDown' → selected + 1
116
+ { keys: ['J', '↓'], join: 'or', what: 'Move down the list.' },
117
+ // case 'k': case 'K': case 'ArrowUp' → selected - 1
118
+ { keys: ['K', '↑'], join: 'or', what: 'Move up the list.' },
119
+ // case 'ArrowLeft': case 'ArrowRight' → boardSideways (w-23fc91bff5).
120
+ // "Board" is the word the View and filters menu prints.
121
+ { keys: ['←', '→'], what: 'Move to the next column on the board.' },
122
+ // case 'Enter' → setFocused(pointed)
123
+ { keys: ['↵'], what: 'Open the task you are on.' },
124
+ // App.tsx, `if (e.key === 'Tab')`: nextTab(stateTabOrder, view, shiftKey)
125
+ // walks the tabs along the top of the inbox, wrapping. It replaced ⌘1 to
126
+ // ⌘4 on 2026-10-02 (w-914b16eab6), which jumped through the sidebar's
127
+ // sections before those became these tabs. Shift-Tab goes back and is
128
+ // left unlisted on her word: "just show the Next tab".
129
+ { keys: ['⇥'], what: 'Move to the next tab along the top.' },
130
+ // case 'b': case 'B' → setInboxDisplay(flipView(inboxDisplay)), asked
131
+ // for by name (w-58c8f466e7). "List" and "Board" are the words the View
132
+ // and filters menu prints, so the sentence uses those.
133
+ { keys: ['B'], what: 'Switch between the list and the board.' },
134
+ // Escape in the focused branch: doc → task → list. In the list switch it
135
+ // clears a selection and closes search.
136
+ { keys: ['esc'], what: 'Go back one step: a file, then the task, then the list.' },
137
+ ],
138
+ },
139
+ {
140
+ // App.tsx, the `if (focused)` branch: a task open in front of her. NOT
141
+ // "answering an agent" any more, because ⌘J opens its terminal and that is
142
+ // not an answer; the heading says when the keys work, which is the scheme.
143
+ label: 'In a task you have opened',
144
+ keys: [
145
+ // focused branch: 'r' → setModal('reply'); list switch: 'r' in the inbox
146
+ { keys: ['R'], what: 'Write back to the agent.' },
147
+ // Compose.tsx onKeyDown: (meta||ctrl) && Enter → send; Focus.tsx the same
148
+ { keys: ['⌘↵'], what: 'Send what you have typed.' },
149
+ // focused branch: ArrowDown / ArrowUp walk opts
150
+ { keys: ['↑', '↓'], what: 'Move through the options an agent has offered.' },
151
+ // focused branch: Enter sends optionSel. Its own row rather than a clause
152
+ // at the end of the one above, so somebody scanning for ↵ finds it.
153
+ { keys: ['↵'], what: 'Send the option you have selected.' },
154
+ // focused branch: /^[1-9]$/ → pickOption(focused, Number(e.key))
155
+ { keys: ['1', '9'], join: 'to', what: 'Pick an option by its number.' },
156
+ // ⌘J, above the inInput gate in App.tsx: dispatches task-terminal-toggle,
157
+ // and toasts "Open a task to use its terminal" when there is none
158
+ // (w-ef92663cb1). It landed after this list last changed, so it was
159
+ // missing here at first.
160
+ { keys: ['⌘J'], what: 'Show or hide this task’s terminal.' },
161
+ // threads/Summary.tsx, `useSummaryShortcut`: it listens in the capture
162
+ // phase and stops the key there, so S belongs to the summary on a thread
163
+ // that has one. It is under THIS heading and not the one below because it
164
+ // is the one key in the app that works only inside an open thread.
165
+ { keys: ['S'], what: 'Show or hide the summary.' },
166
+ ],
167
+ },
168
+ {
169
+ // The one group that cannot be named by WHERE she is, because all four of
170
+ // its keys work both in the list and inside an open task. So it is named by
171
+ // what the keys do, literally and in the order they are drawn.
172
+ label: 'Closing, scheduling and undoing',
173
+ keys: [
174
+ // focused branch: 'e' → markDone. List switch: inbox → markDone,
175
+ // snoozed → unsnooze. It never approves anything.
176
+ { keys: ['E'], what: 'Close the task and take it out of your inbox.' },
177
+ // focused branch and list switch: 'l' → openSnooze. IT WAS S UNTIL
178
+ // 2026-10-01 AND THAT IS THE WHOLE OF WHY IT MOVED. S had come to mean
179
+ // the summary inside a thread and still meant the schedule picker in the
180
+ // list, so this page taught one letter for two different things and an
181
+ // office manager testing the app met both inside a minute. One meaning
182
+ // per letter: S is the summary, L is later.
183
+ { keys: ['L'], what: 'Put the task off until a time you pick.' },
184
+ // focused branch and list switch: 'z' → undo
185
+ { keys: ['Z'], what: 'Undo the last thing you did.' },
186
+ // (meta||ctrl) && 'a' && !inInput && !modal → tick the whole list
187
+ { keys: ['⌘A'], what: 'Tick every task in the list. Press it again to untick.' },
188
+ ],
189
+ },
190
+ {
191
+ label: 'Anywhere in the app',
192
+ keys: [
193
+ // (meta||ctrl) && 'k' → the palette, above every other branch, and
194
+ // forwarded from inside an open file by main.mjs as well.
195
+ { keys: ['⌘K', 'Ctrl+K'], join: 'or', what: `The command bar: everything ${NAME} can do, by name.` },
196
+ // 'n' in both branches → setModal('compose'). C does the same and is
197
+ // left off: N is the default (w-fb9051e597).
198
+ // "Threads", not tasks, since w-ec62ab6b38.
199
+ { keys: ['N'], what: 'Start a new thread.' },
200
+ // '/' above the focused branch → openSearch
201
+ { keys: ['/'], what: 'Search your threads.' },
202
+ // '\\' above the focused branch → togglePanel
203
+ { keys: ['\\'], what: 'Show or hide the left sidebar.' },
204
+ // main/main.mjs before-input-event: ⌘Y / ⌘N answer the oldest approval.
205
+ // Its own card prints "⌘Y / ⌘N" beside Allow and Deny, so the words here
206
+ // are the card's words. It lives under this heading rather than under an
207
+ // open task because it is answered from anywhere at all.
208
+ { keys: ['⌘Y', '⌘N'], join: 'or', what: 'Allow or deny a card asking to run something.' },
209
+ ],
210
+ },
211
+ ];
212
+
213
+ /** Every cap drawn on the page, flattened. The test walks this. */
214
+ export function everyKey(groups = SHORTCUTS) {
215
+ return groups.flatMap((g) => g.keys.flatMap((k) => k.keys));
216
+ }
217
+
218
+ // WHAT AN AGENT IS TOLD, and it is this page and nothing else, so a key that
219
+ // moves here has moved for every agent on its next run. The closing line is
220
+ // the point: an agent's own saved memory is read before anything we hand it,
221
+ // and on 2026-10-04 one said "press S" three days after S stopped scheduling.
222
+ /** The keys as one block of plain text, for an agent's system prompt. */
223
+ export function keysForAgents(groups = SHORTCUTS) {
224
+ const lines = [
225
+ `The keys in ${NAME}, the app this person reads you in, as they are today.`,
226
+ 'When you tell them to press something, use these. Keys have moved before,',
227
+ 'so a key you remember from an earlier session, a memory or a note that',
228
+ 'disagrees with this list is out of date. Anything without a key is in ⌘K',
229
+ 'by name.',
230
+ ];
231
+ for (const g of groups) {
232
+ lines.push('', `${g.label}:`);
233
+ for (const k of g.keys) lines.push(`- ${k.keys.join(k.join ? ` ${k.join} ` : ' ')}: ${k.what}`);
234
+ }
235
+ return lines.join('\n');
236
+ }
@@ -6,3 +6,5 @@
6
6
  // main and by the tests, which are both plain JS and need nothing here.
7
7
 
8
8
  export function strandedTitle(count: number): string;
9
+ /** The line in a thread where a run stopped, in our words; `raw` is the tool's own refusal. */
10
+ export function runStoppedLine(raw: string): string;
@@ -6,11 +6,11 @@
6
6
  // across the top of her inbox. Her three complaints were all correct. There is
7
7
  // no OAuth in Agentbox, so the sentence names a thing that does not exist here.
8
8
  // It is ugly. And it was on screen while agents were working perfectly well,
9
- // because it was about one of her two Claude accounts and not about the app.
9
+ // because it was about one Claude account among several and not about the app.
10
10
  //
11
11
  // So the raw text never reaches the renderer again. It goes in the session's
12
12
  // own trace log, which is where a person debugging wants it, and what she is
13
- // shown is one sentence of ours written in her words.
13
+ // shown is one sentence of ours written in plain words.
14
14
  //
15
15
  // Every sentence here has to survive the same test: read it out loud to
16
16
  // somebody who has never heard of a subprocess, and they know what happened
@@ -24,7 +24,7 @@
24
24
  // CODEX. It is right about Codex anyway, and that is measured rather than
25
25
  // assumed. On 2026-09-05 `codex app-server` (codex-cli 0.148.0) was driven the
26
26
  // way main/codex-app-server.mjs drives it, with CODEX_HOME pointed at an empty
27
- // directory so there was no auth.json and her real ~/.codex login was neither
27
+ // directory so there was no auth.json and the real ~/.codex login was neither
28
28
  // read nor written. The turn retried ten times and failed with
29
29
  //
30
30
  // unexpected status 401 Unauthorized: Missing bearer or basic authentication in
@@ -41,18 +41,18 @@
41
41
 
42
42
  // AN ACCOUNT THAT IS SIGNED IN AND NOT ALLOWED TO RUN.
43
43
  //
44
- // She was. Her second login was intact and her browser sign-in succeeded every
45
- // time she did it; the WORKSPACE that account belongs to has Claude Code turned
46
- // off. Run by hand on her Mac, this is the whole of what the CLI says:
44
+ // It happens. A login can be intact, with the browser sign-in succeeding every
45
+ // time, while the WORKSPACE that account belongs to has Claude Code turned
46
+ // off. Run by hand, this is the whole of what the CLI says:
47
47
  //
48
48
  // Your organization has disabled Claude subscription access for Claude Code ·
49
49
  // Use an Anthropic API key instead, or ask your admin to enable access
50
50
  //
51
51
  // "subscription access" is in the list below, so every one of those was filed
52
- // as a login that had run out and she was told to type /login. She did, twice
53
- // in one afternoon; both logins worked; nothing changed, because no login can
54
- // change an admin's policy. FOUR dead runs on one row that day, all four
55
- // carrying "Sign that account back in and it picks straight up."
52
+ // as a login that had run out and the person was told to type /login. Doing
53
+ // it again and again changes nothing, because no login can change an admin's
54
+ // policy, and every dead run on the row carried "Sign that account back in
55
+ // and it picks straight up."
56
56
  //
57
57
  // TESTED BEFORE SIGNED_OUT, which is the whole of the fix: these lines say
58
58
  // "subscription" and "access" and mean something else entirely.
@@ -188,9 +188,8 @@ export function troubleCause(raw) {
188
188
  //
189
189
  // carries the one thing she actually wants to know, and we were throwing it
190
190
  // away and then telling her the vaguer "when the limit resets". The timezone
191
- // is dropped rather than shown: this Mac is set to America/Los_Angeles and
192
- // every limit line in her store prints that same zone, so naming it would only
193
- // add a word she has to read. Null when there is no time to quote, and then
191
+ // is dropped rather than shown: the limit line prints the zone the Mac is
192
+ // already set to, so naming it would only add a word she has to read. Null when there is no time to quote, and then
194
193
  // the sentence falls back to the wording that has always been there.
195
194
  export function limitResetsAt(raw) {
196
195
  // Both wordings are real: the CLI's own line is "resets 6pm" and the API's
@@ -202,14 +201,14 @@ export function limitResetsAt(raw) {
202
201
  const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
203
202
 
204
203
  // THE SAME HOUR AS A MOMENT, so the fleet can wait exactly until it
205
- // (2026-09-22: a limit that reset at 6:50pm held every agent until 7:03pm,
204
+ // (a limit that reset at, say, 6:50pm could hold every agent until 7:03pm,
206
205
  // because the app waited a fixed thirty minutes instead of reading this line).
207
206
  // The next local occurrence of that clock time at or after `from`; null when
208
207
  // the line names no hour.
209
208
  export function limitResetMoment(raw, from = Date.now()) {
210
209
  // CODEX NAMES A DAY, NOT AN HOUR: "try again at Sep 28th, 2026 1:05 PM"
211
210
  // (codex-cli 0.153.4, 2026-09-24). A weekly limit read as "thirty minutes"
212
- // put the capped login back in the pool every half hour for four days.
211
+ // put the capped login back in the pool every half hour for days.
213
212
  const dated = /try again at ([a-z]{3,9})\.? (\d{1,2})(?:st|nd|rd|th)?,? (\d{4}),? (\d{1,2}):(\d{2})\s*(am|pm)/i.exec(String(raw ?? ''));
214
213
  if (dated) {
215
214
  const month = MONTHS.indexOf(dated[1].slice(0, 3).toLowerCase());
@@ -250,7 +249,7 @@ export function troubleSentence(cause) {
250
249
  if (cause === 'at-limit') return 'No agents can start. Your Claude plan is at its limit.';
251
250
  if (cause === 'workspace') return 'No agents can start. Claude will not run in this folder.';
252
251
  if (cause === 'interrupted') return 'No agents can start. The connection to Claude keeps dropping.';
253
- return 'No agents can start on this Mac right now.';
252
+ return 'No agents can start on this computer right now.';
254
253
  }
255
254
 
256
255
  // THE SECOND LINE: what happens next, so the first line is never a dead end.
@@ -343,8 +342,8 @@ export function engineTroubleNote(cause, engineWord = 'Codex') {
343
342
  // The count is the point, and it is why this is a different sentence from
344
343
  // `troubleSentence` above rather than a rewording of it. That one is about the
345
344
  // FLEET, and it can only ever be true when every account on the Mac is down at
346
- // once. That is why her screen stayed quiet: four Codex tasks and four on a
347
- // signed-out login sat there all day while her other agents worked, so the
345
+ // once. That is why the screen could stay quiet: Codex tasks and tasks on a
346
+ // signed-out login could sit there all day while the other agents worked, so the
348
347
  // fleet was never down and the one line the app owns was structurally unable to
349
348
  // speak. The subject here is her TASKS, which is the thing she went looking for
350
349
  // and the thing that was actually stuck.
@@ -443,6 +442,28 @@ const DEAD_RUN_OPENERS = {
443
442
  unknown: 'stopped before it did anything, so this task has not been started.',
444
443
  };
445
444
 
445
+ /**
446
+ * THE LINE IN A THREAD WHERE A RUN STOPPED, in our words.
447
+ *
448
+ * The CLI types its own refusal into the run as if the agent had said it
449
+ * ("Failed to authenticate: OAuth session expired..."), and the thread drew it
450
+ * as the agent's message. This is about one run, not the task: a run that
451
+ * stops mid-thread has usually done plenty before it, so it says nothing about
452
+ * what has or has not been done.
453
+ */
454
+ const RUN_STOPPED = {
455
+ 'signed-out': 'This run stopped because the account was signed out.',
456
+ 'org-blocked': 'This run stopped because that account is switched off by its organization.',
457
+ 'at-limit': 'This run stopped because the account hit its usage limit.',
458
+ workspace: "This run stopped because it would not start in this project's folder.",
459
+ interrupted: 'This run was cut off in the middle.',
460
+ unknown: 'This run stopped before it finished.',
461
+ };
462
+
463
+ export function runStoppedLine(raw) {
464
+ return RUN_STOPPED[troubleCause(raw)] ?? RUN_STOPPED.unknown;
465
+ }
466
+
446
467
  export function deadRunSentence({ engineWord = 'The agent', cause = 'unknown', runs = 1, resetsAt = null } = {}) {
447
468
  const opener = `${engineWord} ${DEAD_RUN_OPENERS[cause] ?? DEAD_RUN_OPENERS.unknown}`;
448
469
 
@@ -468,7 +489,7 @@ export function deadRunSentence({ engineWord = 'The agent', cause = 'unknown', r
468
489
  // WHAT GOES ON HER ROW WHEN THE RUN WAS FINE AND HER ANSWER STILL WENT NOWHERE.
469
490
  //
470
491
  // `settleDelivery` is two independent tests joined with an AND: did the process
471
- // exit cleanly, and did an agent leave a word on the row since she wrote. A run
492
+ // exit cleanly, and did an agent leave a word on the row since the user wrote. A run
472
493
  // can pass the first and fail the second, and several ordinary ones do -- a
473
494
  // Codex turn that talks in its thread and never calls a store tool, a worker
474
495
  // that claims a row and finishes without writing, any session on an install
@@ -0,0 +1,17 @@
1
+ // Types for shared/team-rules.mjs, read by the renderer.
2
+ type Row = { createdBy?: string | null; assignee?: string; runner?: string; people?: string[]; status?: string };
3
+ type ProductLike = { team?: { projectId?: string; sharedBy?: string | null } | null } | null | undefined;
4
+
5
+ export function isShared(product: ProductLike): boolean;
6
+ export function runnerOf(item: Row, product: ProductLike): string | null;
7
+ export function heldByAPerson(item: Row): boolean;
8
+ export function mayRunHere(item: Row, product: ProductLike, me: string | null): boolean;
9
+ export function inMyInbox(item: Row, product: ProductLike, me: string | null): boolean;
10
+ export function lastSpeaker(item: Row): string | null;
11
+ export function iSpokeLast(item: Row, product: ProductLike, me: string | null): boolean;
12
+ export function handedOnByReply(item: Row, product: ProductLike, me: string | null): string | null;
13
+ /** One thread you are in where somebody spoke after you (w-920461cbe6). */
14
+ export interface ThreadWaiting { uid: string; mine: boolean; fresh: number; people: string[]; text: string; last: string; lastBy: string }
15
+ /** What in a conversation waits on you; null on a row from before threads. */
16
+ export interface Waits { chat: number; threads: ThreadWaiting[] }
17
+ export function whatWaits(item: Row & { talk?: unknown }, me: string | null): Waits | null;