agentbox-app 0.1.6

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 (302) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +176 -0
  3. package/bin/agentbox.mjs +103 -0
  4. package/briefs/adhd-mode.md +5 -0
  5. package/briefs/message-rules.md +95 -0
  6. package/briefs/worker.md +230 -0
  7. package/briefs/writing-rules.md +336 -0
  8. package/main/account-discovery.mjs +145 -0
  9. package/main/account-tooling.mjs +261 -0
  10. package/main/agent-activity.mjs +56 -0
  11. package/main/agent-files.mjs +422 -0
  12. package/main/agent-schedule.mjs +254 -0
  13. package/main/agent-sessions.mjs +435 -0
  14. package/main/agents.mjs +1095 -0
  15. package/main/analytics.mjs +206 -0
  16. package/main/answer-modes.mjs +206 -0
  17. package/main/approval-prompt-server.mjs +160 -0
  18. package/main/approvals.mjs +487 -0
  19. package/main/artifact-path.mjs +144 -0
  20. package/main/claude-bin.mjs +311 -0
  21. package/main/claude-input.mjs +117 -0
  22. package/main/claude-models.mjs +244 -0
  23. package/main/claude-plan.mjs +148 -0
  24. package/main/claude-usage.mjs +149 -0
  25. package/main/code-change.mjs +383 -0
  26. package/main/code-file.mjs +116 -0
  27. package/main/codex-account.mjs +146 -0
  28. package/main/codex-app-server.mjs +796 -0
  29. package/main/codex-approvals.mjs +712 -0
  30. package/main/codex-bin.mjs +261 -0
  31. package/main/codex-compaction.mjs +49 -0
  32. package/main/codex-models.mjs +194 -0
  33. package/main/codex-session.mjs +797 -0
  34. package/main/codex-threads.mjs +268 -0
  35. package/main/codex-usage-file.mjs +192 -0
  36. package/main/codex-usage.mjs +149 -0
  37. package/main/codex-watch.mjs +35 -0
  38. package/main/codex.mjs +757 -0
  39. package/main/config.mjs +345 -0
  40. package/main/copy-selection.mjs +55 -0
  41. package/main/crash-report.mjs +158 -0
  42. package/main/demo.mjs +254 -0
  43. package/main/dev-window.mjs +103 -0
  44. package/main/doc-file.mjs +99 -0
  45. package/main/doc-scheme.mjs +126 -0
  46. package/main/first-run.mjs +190 -0
  47. package/main/folders.mjs +91 -0
  48. package/main/fresh-user.mjs +120 -0
  49. package/main/git-change.mjs +323 -0
  50. package/main/img-scheme.mjs +90 -0
  51. package/main/instruction-settings.mjs +205 -0
  52. package/main/ipc.mjs +1558 -0
  53. package/main/live-replies.mjs +77 -0
  54. package/main/machine.mjs +113 -0
  55. package/main/main.mjs +972 -0
  56. package/main/menu.mjs +177 -0
  57. package/main/notify.mjs +155 -0
  58. package/main/open-source-file.mjs +8 -0
  59. package/main/project-identity.mjs +149 -0
  60. package/main/provider-commands.mjs +32 -0
  61. package/main/rail-note.mjs +74 -0
  62. package/main/repeats.mjs +261 -0
  63. package/main/row-label.mjs +185 -0
  64. package/main/screen-detail.mjs +44 -0
  65. package/main/serve.mjs +262 -0
  66. package/main/settings.mjs +912 -0
  67. package/main/staleness.mjs +76 -0
  68. package/main/store/chats.mjs +188 -0
  69. package/main/store/home.mjs +383 -0
  70. package/main/store/migrate.mjs +40 -0
  71. package/main/store/paths.mjs +82 -0
  72. package/main/store/pending-writes.mjs +122 -0
  73. package/main/store/project-lock.mjs +200 -0
  74. package/main/store/project.mjs +523 -0
  75. package/main/store/queues.mjs +31 -0
  76. package/main/store/work-items.mjs +299 -0
  77. package/main/store/write-queue.mjs +14 -0
  78. package/main/store-modules.mjs +23 -0
  79. package/main/store.mjs +1269 -0
  80. package/main/supervisor.mjs +6875 -0
  81. package/main/task-commands.mjs +161 -0
  82. package/main/task-folders-offthread.mjs +84 -0
  83. package/main/task-folders-thread.mjs +13 -0
  84. package/main/task-folders.mjs +409 -0
  85. package/main/task-remote-control.mjs +65 -0
  86. package/main/task-terminals.mjs +169 -0
  87. package/main/terminal-access.mjs +6 -0
  88. package/main/updater.mjs +240 -0
  89. package/main/zoom-keys.mjs +54 -0
  90. package/main/zoom-readout.mjs +20 -0
  91. package/mcp/agentbox-mcp.sh +34 -0
  92. package/mcp/core/account.mjs +77 -0
  93. package/mcp/core/chat.mjs +61 -0
  94. package/mcp/core/documents.mjs +238 -0
  95. package/mcp/core/page.mjs +152 -0
  96. package/mcp/core/products.mjs +110 -0
  97. package/mcp/core/skills.mjs +126 -0
  98. package/mcp/core/work.mjs +377 -0
  99. package/mcp/stdio.mjs +70 -0
  100. package/mcp/tools.mjs +257 -0
  101. package/package.json +186 -0
  102. package/renderer/dist/assets/Geist-Variable-Bj2R_7yk.woff2 +0 -0
  103. package/renderer/dist/assets/GeistMono-Variable-Dispecij.woff2 +0 -0
  104. package/renderer/dist/assets/addon-fit-YJmn1quW.js +12 -0
  105. package/renderer/dist/assets/addon-serialize-CsFQTvP_.js +13 -0
  106. package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
  107. package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
  108. package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
  109. package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
  110. package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
  111. package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
  112. package/renderer/dist/assets/gouache-valley-ChSu14-t.webp +0 -0
  113. package/renderer/dist/assets/gouache-valley-d9qasI6k.webp +0 -0
  114. package/renderer/dist/assets/gouache-valley-sm-CdLjT5Ec.webp +0 -0
  115. package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
  116. package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
  117. package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
  118. package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
  119. package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
  120. package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
  121. package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
  122. package/renderer/dist/assets/index-CwiDv5bL.css +32 -0
  123. package/renderer/dist/assets/index-D9hCAVPl.js +684 -0
  124. package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
  125. package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
  126. package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
  127. package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
  128. package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
  129. package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
  130. package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
  131. package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
  132. package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
  133. package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
  134. package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
  135. package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
  136. package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
  137. package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
  138. package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
  139. package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
  140. package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
  141. package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
  142. package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
  143. package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
  144. package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
  145. package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
  146. package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
  147. package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
  148. package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
  149. package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
  150. package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
  151. package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
  152. package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
  153. package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
  154. package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
  155. package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
  156. package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
  157. package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
  158. package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
  159. package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
  160. package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
  161. package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
  162. package/renderer/dist/assets/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
  163. package/renderer/dist/assets/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
  164. package/renderer/dist/assets/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
  165. package/renderer/dist/assets/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
  166. package/renderer/dist/assets/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
  167. package/renderer/dist/assets/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
  168. package/renderer/dist/assets/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
  169. package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
  170. package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
  171. package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
  172. package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
  173. package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
  174. package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
  175. package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
  176. package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
  177. package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
  178. package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
  179. package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
  180. package/renderer/dist/assets/xterm-BT7tBf8A.js +27 -0
  181. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  182. package/renderer/dist/emulator/data/compression/README.md +11 -0
  183. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  184. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  185. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  186. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  187. package/renderer/dist/emulator/data/cores/README.md +22 -0
  188. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  189. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  190. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  191. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  192. package/renderer/dist/emulator/data/cores/package.json +65 -0
  193. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  194. package/renderer/dist/emulator/data/emulator.css +1614 -0
  195. package/renderer/dist/emulator/data/loader.js +170 -0
  196. package/renderer/dist/emulator/data/localization/README.md +63 -0
  197. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  198. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  199. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  200. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  201. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  202. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  203. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  204. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  205. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  206. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  207. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  208. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  209. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  210. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  211. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  212. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  213. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  214. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  215. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  216. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  217. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  218. package/renderer/dist/emulator/data/src/compression.js +142 -0
  219. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  220. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  221. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  222. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  223. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  224. package/renderer/dist/emulator/data/src/storage.js +125 -0
  225. package/renderer/dist/emulator/data/version.json +5 -0
  226. package/renderer/dist/emulator/host.js +299 -0
  227. package/renderer/dist/emulator/index.html +28 -0
  228. package/renderer/dist/emulator/provenance.json +6 -0
  229. package/renderer/dist/fixtures/onboard/designs/w-n13/the-first-row.html +1 -0
  230. package/renderer/dist/index.html +13 -0
  231. package/scripts/after-install.mjs +59 -0
  232. package/scripts/approval-server.sh +43 -0
  233. package/scripts/before-publish.mjs +133 -0
  234. package/shared/agent-import.d.mts +13 -0
  235. package/shared/agent-import.mjs +252 -0
  236. package/shared/agents.d.mts +190 -0
  237. package/shared/agents.mjs +937 -0
  238. package/shared/analytics-events.mjs +63 -0
  239. package/shared/answers.d.mts +12 -0
  240. package/shared/answers.mjs +80 -0
  241. package/shared/artifact-keys.d.mts +28 -0
  242. package/shared/artifact-keys.mjs +140 -0
  243. package/shared/bridge-map.mjs +128 -0
  244. package/shared/claude-commands.d.mts +20 -0
  245. package/shared/claude-commands.generated.mjs +149 -0
  246. package/shared/claude-commands.mjs +131 -0
  247. package/shared/claude-models.generated.d.mts +15 -0
  248. package/shared/claude-models.generated.mjs +26 -0
  249. package/shared/claude-usage.d.mts +16 -0
  250. package/shared/claude-usage.mjs +172 -0
  251. package/shared/codex-commands.d.mts +4 -0
  252. package/shared/codex-commands.mjs +17 -0
  253. package/shared/codex-import.d.mts +38 -0
  254. package/shared/codex-import.mjs +232 -0
  255. package/shared/codex-modes.mjs +159 -0
  256. package/shared/codex-usage.mjs +180 -0
  257. package/shared/contracts.mjs +412 -0
  258. package/shared/crash-scrub.mjs +182 -0
  259. package/shared/dashboard.mjs +481 -0
  260. package/shared/dead-run-trace.mjs +127 -0
  261. package/shared/demo-world.mjs +415 -0
  262. package/shared/effort-levels.d.mts +15 -0
  263. package/shared/effort-levels.mjs +76 -0
  264. package/shared/engines.d.mts +62 -0
  265. package/shared/engines.mjs +389 -0
  266. package/shared/first-run-examples.mjs +60 -0
  267. package/shared/first-run-line.mjs +149 -0
  268. package/shared/first-run-practice.d.mts +32 -0
  269. package/shared/first-run-practice.mjs +254 -0
  270. package/shared/first-run-shape.mjs +100 -0
  271. package/shared/fresh-user-home.mjs +189 -0
  272. package/shared/hook-failure.mjs +22 -0
  273. package/shared/install-id.mjs +39 -0
  274. package/shared/instruction-defaults.json +5 -0
  275. package/shared/library-folder.mjs +28 -0
  276. package/shared/notify-rules.mjs +120 -0
  277. package/shared/product-name.d.mts +6 -0
  278. package/shared/product-name.mjs +144 -0
  279. package/shared/project-folder-check.d.mts +22 -0
  280. package/shared/project-folder-check.mjs +152 -0
  281. package/shared/provider-commands.d.mts +8 -0
  282. package/shared/provider-commands.mjs +58 -0
  283. package/shared/rank.d.mts +19 -0
  284. package/shared/rank.mjs +146 -0
  285. package/shared/recovery.mjs +26 -0
  286. package/shared/referenced-files.d.mts +11 -0
  287. package/shared/referenced-files.mjs +122 -0
  288. package/shared/repeats.d.mts +45 -0
  289. package/shared/repeats.mjs +193 -0
  290. package/shared/schemes.mjs +33 -0
  291. package/shared/settings-terminal.d.mts +1 -0
  292. package/shared/settings-terminal.mjs +14 -0
  293. package/shared/side-build.mjs +57 -0
  294. package/shared/spawn-trouble.d.mts +8 -0
  295. package/shared/spawn-trouble.mjs +599 -0
  296. package/shared/terminal-state.d.mts +1 -0
  297. package/shared/terminal-state.mjs +58 -0
  298. package/shared/usage.d.mts +53 -0
  299. package/shared/usage.mjs +319 -0
  300. package/shared/work-items.mjs +561 -0
  301. package/shared/work-lines.mjs +360 -0
  302. package/worker-permissions.json +6 -0
@@ -0,0 +1,161 @@
1
+ import fs from 'node:fs';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import { providerCommand, providerCommands, reviewTarget } from '../shared/provider-commands.mjs';
5
+ import { claudeModelRows } from './claude-models.mjs';
6
+ import { EFFORT_LEVELS } from '../shared/effort-levels.mjs';
7
+ import { codexModels, codexDefaultModel, codexModelLevels } from './codex-models.mjs';
8
+ import { runCodexCommand } from './provider-commands.mjs';
9
+ import { NAME } from '../shared/product-name.mjs';
10
+ const exec = promisify(execFile);
11
+
12
+ /**
13
+ * A FORK IS A NEW TASK THAT STARTS WHERE THIS ONE GOT TO (MP-08, w-5ebf7bf7bb).
14
+ *
15
+ * Claude Code resumes a conversation under a new id with `--fork-session`, so
16
+ * the original thread is untouched and the new one knows everything it knew.
17
+ * The new ROW is what carries it here, rather than a second thread on this row,
18
+ * because a row is the unit of everything else: the inbox, the folder its code
19
+ * lives in, the change card, the session her next reply resumes. Two threads on
20
+ * one row would have to answer which of them a reply belongs to.
21
+ *
22
+ * The words after the command are the new task's own instruction, so a fork is
23
+ * never an empty room. The label `fork:<source row>` is the whole of the
24
+ * mechanism: `Supervisor#spawnPlan` reads it on the new row's first run, finds
25
+ * the source row's chat, and resumes it with `--fork-session`. Once the fork has
26
+ * a chat of its own, that takes over and nothing forks twice.
27
+ */
28
+ /**
29
+ * A row name out of the sentence she typed after `/fork`: her first sentence,
30
+ * or the first sixty characters of it, so the inbox line says which fork this
31
+ * is rather than which row it came from. Whitespace flattened because a title
32
+ * is one line and she can paste a paragraph.
33
+ */
34
+ export function forkTitle(words, cap = 60) {
35
+ const flat = String(words ?? '').replace(/\s+/g, ' ').trim();
36
+ if (!flat) return 'A fork of this conversation';
37
+ const stop = flat.search(/[.!?](\s|$)/);
38
+ const first = stop > 0 ? flat.slice(0, stop) : flat;
39
+ return first.length <= cap ? first : `${first.slice(0, cap - 1).trimEnd()}…`;
40
+ }
41
+
42
+ function forkTask(sup, productSlug, id, item, engine, op, publish) {
43
+ if (engine === 'codex') {
44
+ return publish('failed', 'Forking a Codex conversation is not connected yet. Its protocol has thread/fork and that call has never been made here, so this refuses rather than pretending. Your draft has been kept.', op.name);
45
+ }
46
+ // NAMING THE KEY, because the menu closes at the space and plain return then
47
+ // writes a newline, the way it does on every other reply here. Without that
48
+ // sentence she types the words, presses return, and is back to a key that
49
+ // does nothing (hers, 2026-09-23: "Fork does nothing when I type /fork then
50
+ // hit return").
51
+ if (!op.args) return publish('failed', 'Say what the fork should try, as in /fork try it without the modal, then press command and return.', op.name);
52
+ const chat = sup.rowSessionFor(item, sup.store.listProducts().find((p) => p.slug === productSlug));
53
+ if (!chat?.sessionId) return publish('failed', 'This task has no saved conversation to fork yet. Send a message first.', op.name);
54
+ const made = sup.store.composeItem(productSlug, {
55
+ // THE FORK IS NAMED AFTER WHAT SHE ASKED IT TO TRY, not after the row it
56
+ // came from. Naming it `<parent title> · fork` put two rows in her inbox
57
+ // whose names differ by one word at the end, and the first thing she said
58
+ // after using it was that she could not tell which agent she was talking to
59
+ // (2026-09-23). The instruction is the only thing that distinguishes them,
60
+ // so it is what the row is called; the parent is still on the row as the
61
+ // `fork:` label, which is where the machinery reads it anyway.
62
+ title: forkTitle(op.args),
63
+ body: op.args,
64
+ kind: 'directive',
65
+ priority: item.priority ?? 0,
66
+ labels: [`fork:${id}`],
67
+ ...(item.engine ? { engine: item.engine } : {}),
68
+ });
69
+ if (!made?.id) return publish('failed', 'The fork could not be created. Your draft has been kept.', op.name);
70
+ sup.onChange?.();
71
+ return publish('done', `Forked into a new task, starting from everything this conversation knows: ${made.title}`, op.name);
72
+ }
73
+
74
+ export async function taskCommand(sup, productSlug, id, text) {
75
+ const key=JSON.stringify([productSlug,id]);
76
+ const publish=(state,message,name)=>{
77
+ const value={state,text:message,name,at:Date.now()};
78
+ sup._compactions??={};sup._compactions[key]=value;sup._saveState();sup.onChange?.();return value;
79
+ };
80
+ const item=sup.store.readItem(productSlug,id);
81
+ if(!item||item.agent)return publish('failed','This task is not available.');
82
+ const engine=sup._engineFor(item), op=providerCommand(text,engine);
83
+ if(!op)return publish('failed','Enter a slash command.');
84
+ if(engine !== 'codex' && op.route === 'unavailable' && (sup._nativeCommands?.[id]?.includes(op.name) || (op.name === 'review' && sup._nativeCommands?.[id]?.includes('code-review')))) return publish('forward');
85
+ if(op.route==='unavailable')return publish('failed',`/${op.name} is not supported in ${NAME} yet. Your draft has been kept. Use the native ${engine==='codex'?'Codex':'Claude Code'} client for this command.`,op.name);
86
+ if(op.route==='remote'){if(op.args)return publish('failed','Run /remote-control on its own.',op.name);sup.remoteControl(productSlug,id,'toggle').catch(()=>{});return publish('done','',op.name);}
87
+ if(op.route==='fork')return forkTask(sup,productSlug,id,item,engine,op,publish);
88
+ if(op.route==='claude')return publish('forward');
89
+ if(op.name==='compact')return op.args ? publish('failed','Run /compact on its own. Your draft has been kept.',op.name) : sup.compactItem(productSlug,id);
90
+ if(op.name==='review') {
91
+ try { reviewTarget(op.args); return publish('forward'); }
92
+ catch(error) { return publish('failed',error.message,op.name); }
93
+ }
94
+ if(sup._compactionJobs?.has(key))return {state:'busy',text:'A command is already running on this task. Your draft has been kept.',at:Date.now()};
95
+ const readOnly = ['status','usage','skills','diff','copy','help'].includes(op.name) || (['model','effort'].includes(op.name) && !op.args);
96
+ if(sup.sessions.has(id) && !readOnly)return publish('busy',sup.sessions.get(id).remoteIdle?'Turn off Remote Control before changing this task’s local settings. Your draft has been kept.':'Wait for the current response to finish. Your draft has been kept.',op.name);
97
+ const product=sup.store.listProducts().find(p=>p.slug===productSlug);
98
+ if(!product)return publish('failed','This project is no longer available.',op.name);
99
+ const cwd=product.repoPath&&fs.existsSync(product.repoPath)?product.repoPath:product.dir;
100
+ const rec=sup.rowSessionFor(item,product);
101
+ sup._compactionJobs??=new Map();
102
+ if(rec?.sessionId&&[...sup._compactionJobs.values()].some(job=>job.threadId===rec.sessionId&&job.profile===rec.profile))return publish('busy','This conversation is busy. Your draft has been kept.',op.name);
103
+ sup._compactionJobs.set(key,{threadId:rec?.sessionId,profile:rec?.profile});
104
+ try {
105
+ let result;
106
+ const write=patch=>sup.store.modules.workItemsDisk.updateWorkItem(sup.store.productDir(productSlug),id,patch,{source:'founder'});
107
+ const home=engine==='codex'?sup._codexProfileHome(rec?.profile??'default'):null;
108
+ const model=item.model||(engine==='codex'?(sup._codexWorkspaceModel()||codexDefaultModel({home})):null);
109
+ if(op.name==='model') {
110
+ // Both halves read off this Mac, so /model lists what its CLIs can actually
111
+ // be asked for today rather than what was true when Agentbox was built.
112
+ const models=engine==='codex'?codexModels({home}):claudeModelRows({bin:sup.config?.claudeBin??null}).map(m=>({id:m.alias,label:m.label}));
113
+ if(!op.args)result=`Task model: ${model||'provider default'}\n${models.map(m=>`/model ${m.id} · ${m.label}`).join('\n')}`;
114
+ else {if(!models.some(m=>m.id===op.args))throw new Error('Choose one of the models listed by /model. Your draft has been kept.');
115
+ // Model changes cannot carry an incompatible effort into the next run.
116
+ const levels=engine==='codex'?codexModelLevels(op.args,{home}):EFFORT_LEVELS.map(e=>e.id);
117
+ write({model:op.args,...(item.effort&&levels&&!levels.includes(item.effort)?{effort:null}:{})});
118
+ result=`This task will use ${op.args} on its next response.`;}
119
+ } else if(op.name==='effort') {
120
+ const levels=engine==='codex'?codexModelLevels(model,{home}):EFFORT_LEVELS.map(e=>e.id);
121
+ if(!op.args)result=`Task effort: ${item.effort||'provider default'}\n${levels?.map(l=>`/effort ${l}`).join('\n')||'The provider has not reported available levels.'}`;
122
+ else {if(!levels?.includes(op.args))throw new Error('Choose one of the levels listed by /effort. Your draft has been kept.');write({effort:op.args});result=`This task will use ${op.args} effort on its next response.`;}
123
+ } else if(op.name==='rename') {
124
+ if(!op.args||op.args.includes('\n')||op.args.length>200)throw new Error('Use /rename followed by a title of up to 200 characters.');
125
+ write({title:op.args});result=`Task renamed to ${op.args}.`;
126
+ } else if(op.name==='help') {
127
+ if(op.args)throw new Error('Run /help on its own.');
128
+ const names = (sup._nativeCommands?.[id] ?? []).filter(n=>!providerCommands(engine).some(c=>c.name===n));
129
+ // WHAT IS STILL MISSING, KEPT TRUE. Forking is connected now, on Claude Code
130
+ // (MP-08), so the old sentence naming it was out of date the day it shipped.
131
+ // A list of gaps nobody maintains is worse than no list: it teaches her that
132
+ // what this app says about itself is stale.
133
+ const missing = engine==='codex'
134
+ ? 'Native terminal settings, forking a conversation, rewind and integration setup are not connected here yet.'
135
+ : 'Native terminal settings, rewind and integration setup are not connected here yet.';
136
+ result=[...providerCommands(engine).map(c=>`/${c.name}${c.argumentHint?' '+c.argumentHint:''} · ${c.description}`), ...(engine==='codex'?[]:names.map(n=>`/${n} · Native Claude Code command`))].join('\n')+`\n\n${missing} Unavailable commands keep your draft and never run as a prompt.`;
137
+ } else if(op.name==='diff') {
138
+ if(op.args)throw new Error('Run /diff on its own.');
139
+ const opts={cwd,timeout:15000,maxBuffer:2*1024*1024};
140
+ const unstaged=await exec('git',['--no-pager','diff','--no-ext-diff','--no-textconv'],opts);
141
+ const staged=await exec('git',['--no-pager','diff','--cached','--no-ext-diff','--no-textconv'],opts);
142
+ const untracked=await exec('git',['ls-files','--others','--exclude-standard'],opts);
143
+ result=[unstaged.stdout&&'Unstaged\n'+unstaged.stdout,staged.stdout&&'Staged\n'+staged.stdout,untracked.stdout&&'Untracked files (contents not included)\n'+untracked.stdout].filter(Boolean).join('\n')||'No local changes.';
144
+ } else if(op.name==='usage') {
145
+ if(op.args)throw new Error('Run /usage on its own.');
146
+ const usage = home === sup._codexUsageHome() ? sup.codexUsage() : null;
147
+ result=usage ? `Last reported ${new Date(usage.at).toLocaleString()} (may be stale)\n${usage.limits.map(l=>`${l.name}: ${l.percent}% used${l.resetsAt ? '; resets '+new Date(l.resetsAt).toLocaleString() : ''}`).join('\n')}` : 'No usage has been reported for this conversation’s account yet.';
148
+ } else {
149
+ if(op.name==='goal' && op.args && !['pause','clear'].includes(op.args)) throw new Error(`Setting or resuming a Codex goal is not connected to ${NAME}’s worker lifecycle yet. Your draft has been kept.`);
150
+ if(!rec?.sessionId&&op.name!=='skills')throw new Error('This task has no saved conversation yet. Send a message first.');
151
+ const {client,handshake}=sup._codexServer(home);await handshake;
152
+ result=await runCodexCommand({server:client,threadId:rec?.sessionId,cwd,...op});
153
+ if(op.name==='status')result=`Task model: ${model||'provider default'}\nTask effort: ${item.effort||'provider default'}\n${result}`;
154
+ }
155
+ if(op.name==='copy')return {state:'copy',text:result,at:Date.now()};
156
+ return publish('done',result,op.name);
157
+ } catch(error) {
158
+ const message=error?.code===-32601||/unknown (method|variant)|method not found/i.test(error?.message??'')?'This command is not available in the installed provider version. Your draft has been kept.':error.message||'The command failed. Your draft has been kept.';
159
+ return publish('failed',message,op.name);
160
+ } finally {sup._compactionJobs.delete(key);}
161
+ }
@@ -0,0 +1,84 @@
1
+ // TASK FOLDERS ARE MADE AND PUT AWAY OFF THE THREAD THAT DRAWS HER WINDOW.
2
+ //
3
+ // Measured on her Mac, 2026-09-27 (w-7deaeca493), with three agents running:
4
+ // the app's main thread sat inside a synchronous child process for 19 seconds
5
+ // straight, from 14:49:33 to 14:49:52, and again for 6 seconds from 14:50:41,
6
+ // the second time caught in `ps` as `git worktree remove` on an astral-video
7
+ // task folder. macOS draws the spinning wheel over a window whose thread stops
8
+ // answering for about two seconds, so both were the thing she reported:
9
+ // "especially when I complete a task or maybe complete a task and go into a new
10
+ // one". On a throwaway copy of that repository `ensureTaskFolder` took 9.6
11
+ // seconds and the removal 3.3, because a folder carries a block clone of
12
+ // node_modules and a 1.4 GB `.next`, and `cp -c` still pays for every file.
13
+ //
14
+ // main/task-folders.mjs stays synchronous, because it is one argument about git
15
+ // and a second, async copy of it would be two. It runs whole inside a worker
16
+ // thread instead: `execFileSync` there blocks that thread and nothing else. One
17
+ // thread, jobs in order, so folder work is serialised exactly as it was.
18
+ //
19
+ // IF THE THREAD CANNOT START, THE JOB RUNS HERE, the old way. A packaged build
20
+ // reads its code out of an asar archive, and a folder made slowly is better
21
+ // than a task that never gets one.
22
+ import { Worker } from 'node:worker_threads';
23
+ import * as folders from './task-folders.mjs';
24
+
25
+ const JOBS = new Set(['restoreTaskFolder', 'releaseTaskFolder', 'parkTaskFolder', 'listTaskFolders']);
26
+
27
+ let worker = null;
28
+ let broken = false;
29
+ let seq = 0;
30
+ const waiting = new Map();
31
+
32
+ function runHere({ name, args, resolve, reject }) {
33
+ try { resolve(folders[name](...args)); } catch (error) { reject(error); }
34
+ }
35
+
36
+ function giveUp() {
37
+ broken = true;
38
+ worker = null;
39
+ const left = [...waiting.values()];
40
+ waiting.clear();
41
+ for (const job of left) runHere(job);
42
+ }
43
+
44
+ function thread() {
45
+ if (worker || broken) return worker;
46
+ try {
47
+ worker = new Worker(new URL('./task-folders-thread.mjs', import.meta.url));
48
+ } catch (error) {
49
+ console.warn('zero: task folders will be made on the main thread:', error.message);
50
+ broken = true;
51
+ return null;
52
+ }
53
+ worker.on('message', ({ id, ok, value, error }) => {
54
+ const job = waiting.get(id);
55
+ if (!job) return;
56
+ waiting.delete(id);
57
+ if (!waiting.size) worker?.unref();
58
+ if (ok) job.resolve(value); else job.reject(new Error(error));
59
+ });
60
+ worker.on('error', (error) => {
61
+ console.warn('zero: the task folder thread failed:', error.message);
62
+ giveUp();
63
+ });
64
+ worker.on('exit', () => { if (worker) giveUp(); });
65
+ // Holding the process open only while a job is out, so a finished job is never
66
+ // the reason the app, or a test run, stays up. After the listeners, because
67
+ // adding a 'message' listener refs the port again.
68
+ worker.unref();
69
+ return worker;
70
+ }
71
+
72
+ /** Run one of task-folders.mjs's exports without blocking this thread. */
73
+ export function folderJob(name, ...args) {
74
+ if (!JOBS.has(name)) return Promise.reject(new Error(`not a folder job: ${name}`));
75
+ return new Promise((resolve, reject) => {
76
+ const job = { name, args, resolve, reject };
77
+ const w = thread();
78
+ if (!w) { runHere(job); return; }
79
+ const id = ++seq;
80
+ waiting.set(id, job);
81
+ w.ref();
82
+ w.postMessage({ id, name, args });
83
+ });
84
+ }
@@ -0,0 +1,13 @@
1
+ // THE OTHER END OF main/task-folders-offthread.mjs. One job at a time, in the
2
+ // order they were posted, which is what keeps two git commands from fighting
3
+ // over the same repository's lock files.
4
+ import { parentPort } from 'node:worker_threads';
5
+ import * as folders from './task-folders.mjs';
6
+
7
+ parentPort.on('message', ({ id, name, args }) => {
8
+ try {
9
+ parentPort.postMessage({ id, ok: true, value: folders[name](...args) });
10
+ } catch (error) {
11
+ parentPort.postMessage({ id, ok: false, error: String(error?.message ?? error) });
12
+ }
13
+ });