agentbox-app 0.1.12 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/README.md +24 -9
  2. package/bin/agentbox.mjs +23 -1
  3. package/briefs/message-rules.md +36 -8
  4. package/briefs/worker.md +46 -23
  5. package/main/account-discovery.mjs +122 -30
  6. package/main/account-tooling.mjs +29 -1
  7. package/main/account-usage.mjs +46 -0
  8. package/main/acp.mjs +296 -0
  9. package/main/agent-homes.mjs +158 -0
  10. package/main/agent-sessions.mjs +311 -260
  11. package/main/agent-updates.mjs +9 -22
  12. package/main/agents.mjs +179 -466
  13. package/main/analytics.mjs +42 -2
  14. package/main/answer-modes.mjs +103 -37
  15. package/main/approval-prompt-core.mjs +197 -0
  16. package/main/approval-prompt-server.mjs +4 -138
  17. package/main/approvals.mjs +101 -297
  18. package/main/artifact-path.mjs +47 -18
  19. package/main/claude-bin.mjs +45 -311
  20. package/main/claude-input.mjs +57 -7
  21. package/main/claude-launch-env.mjs +54 -0
  22. package/main/claude-models.mjs +39 -7
  23. package/main/claude-plan.mjs +64 -16
  24. package/main/claude-session-open.mjs +28 -0
  25. package/main/claude-usage-process.mjs +40 -0
  26. package/main/claude-usage.mjs +94 -59
  27. package/main/cloud/account.mjs +179 -0
  28. package/main/cloud/aws.mjs +127 -0
  29. package/main/cloud/browser-return.mjs +58 -0
  30. package/main/cloud/clients.mjs +15 -0
  31. package/main/cloud/codex-cloud.mjs +53 -0
  32. package/main/cloud/config.mjs +107 -0
  33. package/main/cloud/docs.mjs +219 -0
  34. package/main/cloud/exec.mjs +27 -0
  35. package/main/cloud/host.mjs +165 -0
  36. package/main/cloud/kit.mjs +80 -0
  37. package/main/cloud/logins.mjs +543 -0
  38. package/main/cloud/managed.mjs +115 -0
  39. package/main/cloud/marker.mjs +104 -0
  40. package/main/cloud/org.mjs +53 -0
  41. package/main/cloud/reuse.mjs +208 -0
  42. package/main/cloud/run-cli.mjs +177 -0
  43. package/main/cloud/run.mjs +1934 -0
  44. package/main/cloud/usage.mjs +76 -0
  45. package/main/cloud-permission.mjs +51 -0
  46. package/main/codex-account.mjs +78 -0
  47. package/main/codex-app-server.mjs +276 -409
  48. package/main/codex-approvals.mjs +148 -433
  49. package/main/codex-bin.mjs +51 -249
  50. package/main/codex-models.mjs +20 -2
  51. package/main/codex-session.mjs +246 -506
  52. package/main/codex-threads-worker.mjs +12 -0
  53. package/main/codex-threads.mjs +112 -11
  54. package/main/codex-usage-file.mjs +5 -3
  55. package/main/codex-usage.mjs +56 -7
  56. package/main/codex-watch.mjs +83 -5
  57. package/main/codex.mjs +95 -363
  58. package/main/config.mjs +37 -5
  59. package/main/corner-tag.mjs +810 -0
  60. package/main/crash-report.mjs +26 -3
  61. package/main/dev-window.mjs +1 -1
  62. package/main/engine-bin.mjs +545 -0
  63. package/main/engine-setup.mjs +25 -24
  64. package/main/feedback.mjs +14 -31
  65. package/main/folder-pictures.mjs +2 -2
  66. package/main/freeze-recorder-thread.mjs +86 -0
  67. package/main/freeze-recorder.mjs +146 -0
  68. package/main/fresh-user.mjs +20 -9
  69. package/main/git-change-offthread.mjs +17 -3
  70. package/main/git-change-thread.mjs +5 -2
  71. package/main/harnesses/claude-stream.mjs +208 -0
  72. package/main/harnesses/claude.mjs +105 -0
  73. package/main/harnesses/codex.mjs +72 -0
  74. package/main/harnesses/common.mjs +78 -0
  75. package/main/harnesses/instructions-file.mjs +116 -0
  76. package/main/harnesses/opencode.mjs +234 -0
  77. package/main/harnesses.mjs +65 -0
  78. package/main/importable-threads.mjs +96 -0
  79. package/main/ipc.mjs +520 -135
  80. package/main/leftovers.mjs +117 -25
  81. package/main/liquid-glass.mjs +26 -0
  82. package/main/live-replies.mjs +10 -4
  83. package/main/local-app-frames.mjs +82 -0
  84. package/main/main-went-red.mjs +330 -0
  85. package/main/main.mjs +248 -37
  86. package/main/memory-gate-history.mjs +85 -20
  87. package/main/memory-gate-server.mjs +129 -11
  88. package/main/memory-gate.mjs +62 -8
  89. package/main/memory-log.mjs +124 -0
  90. package/main/menu.mjs +4 -2
  91. package/main/message-priority.mjs +2 -2
  92. package/main/mic.mjs +38 -0
  93. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  94. package/main/new-chats-watch.mjs +110 -0
  95. package/main/new-user-window.mjs +25 -0
  96. package/main/notify.mjs +64 -5
  97. package/main/opencode-server.mjs +271 -0
  98. package/main/opencode.mjs +287 -0
  99. package/main/phone-alerts.mjs +117 -0
  100. package/main/phone-inbox.mjs +625 -0
  101. package/main/phone-link.mjs +571 -0
  102. package/main/phone-uploads.mjs +476 -0
  103. package/main/picture-shape.mjs +114 -0
  104. package/main/quit-sequence.mjs +143 -0
  105. package/main/refresh-agent-models.mjs +15 -2
  106. package/main/relay-address.mjs +28 -0
  107. package/main/relay-client.mjs +314 -0
  108. package/main/repeats.mjs +10 -4
  109. package/main/row-label.mjs +18 -47
  110. package/main/run-change.mjs +13 -0
  111. package/main/run-ledger.mjs +135 -0
  112. package/main/runs-on.mjs +32 -35
  113. package/main/save-attachments.mjs +41 -0
  114. package/main/serve.mjs +155 -9
  115. package/main/settings.mjs +364 -50
  116. package/main/shell-path.mjs +234 -0
  117. package/main/ship-conflict-brief.mjs +119 -0
  118. package/main/ship-queue.mjs +134 -20
  119. package/main/sign-in-files.mjs +3 -7
  120. package/main/source-updater.mjs +45 -11
  121. package/main/stage-attachment.mjs +68 -0
  122. package/main/stall-watch.mjs +50 -0
  123. package/main/start-hold.mjs +73 -0
  124. package/main/store/home.mjs +87 -232
  125. package/main/store/paths.mjs +11 -0
  126. package/main/store/teammates-files.mjs +99 -0
  127. package/main/store/work-items.mjs +231 -13
  128. package/main/store.mjs +227 -53
  129. package/main/supervisor.mjs +3865 -699
  130. package/main/task-commands.mjs +13 -17
  131. package/main/task-folders-offthread.mjs +6 -1
  132. package/main/task-folders.mjs +897 -105
  133. package/main/task-terminals.mjs +147 -41
  134. package/main/team/index.mjs +114 -10
  135. package/main/team/memory-cloud.mjs +183 -1
  136. package/main/team/room-hours.mjs +48 -0
  137. package/main/team/room-setup.mjs +31 -0
  138. package/main/team/rooms-memory.mjs +454 -0
  139. package/main/team/rooms-shape.mjs +42 -0
  140. package/main/team/rooms-supabase.mjs +324 -0
  141. package/main/team/rooms.mjs +575 -0
  142. package/main/team/session.mjs +96 -26
  143. package/main/team/supabase-backend.mjs +180 -0
  144. package/main/team/sync.mjs +109 -1
  145. package/main/teammates.mjs +580 -0
  146. package/main/terminal-host.mjs +143 -0
  147. package/main/terminal-hosts.mjs +261 -0
  148. package/main/transcript-search.mjs +80 -0
  149. package/main/updater.mjs +71 -9
  150. package/main/webview-guard.mjs +45 -0
  151. package/main/worktree-include.mjs +365 -0
  152. package/mcp/core/documents.mjs +3 -1
  153. package/mcp/core/repeats.mjs +88 -0
  154. package/mcp/core/teammates.mjs +222 -0
  155. package/mcp/core/work.mjs +76 -4
  156. package/mcp/stdio.mjs +24 -11
  157. package/mcp/tools.mjs +116 -8
  158. package/package.json +30 -5
  159. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
  160. package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
  161. package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
  162. package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
  163. package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
  164. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  165. package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
  166. package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
  167. package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
  168. package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
  169. package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
  170. package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
  171. package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
  172. package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
  173. package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
  174. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
  175. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  176. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  177. package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
  178. package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
  179. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
  180. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  181. package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
  182. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  183. package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
  184. package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
  185. package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
  186. package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
  187. package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
  188. package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
  189. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
  190. package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
  191. package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
  192. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
  193. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
  194. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
  195. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  196. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
  197. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  198. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
  199. package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
  200. package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
  201. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  202. package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
  203. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  204. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  205. package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
  206. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
  207. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  208. package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
  209. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
  210. package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
  211. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
  212. package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
  213. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
  214. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
  215. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
  216. package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
  217. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
  218. package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
  219. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  220. package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
  221. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
  222. package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
  223. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
  224. package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
  225. package/renderer/dist/corner-tag.html +13 -0
  226. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  227. package/renderer/dist/emulator/data/compression/README.md +11 -0
  228. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  229. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  230. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  231. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  232. package/renderer/dist/emulator/data/cores/README.md +22 -0
  233. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  234. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  235. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  236. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  237. package/renderer/dist/emulator/data/cores/package.json +65 -0
  238. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  239. package/renderer/dist/emulator/data/emulator.css +1614 -0
  240. package/renderer/dist/emulator/data/loader.js +170 -0
  241. package/renderer/dist/emulator/data/localization/README.md +63 -0
  242. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  243. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  244. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  245. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  246. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  247. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  248. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  249. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  250. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  251. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  252. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  253. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  254. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  255. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  256. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  257. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  258. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  259. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  260. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  261. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  262. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  263. package/renderer/dist/emulator/data/src/compression.js +142 -0
  264. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  265. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  266. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  267. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  268. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  269. package/renderer/dist/emulator/data/src/storage.js +125 -0
  270. package/renderer/dist/emulator/data/version.json +5 -0
  271. package/renderer/dist/emulator/host.js +297 -0
  272. package/renderer/dist/emulator/index.html +28 -0
  273. package/renderer/dist/emulator/provenance.json +6 -0
  274. package/renderer/dist/index.html +16 -3
  275. package/scripts/after-install.mjs +30 -8
  276. package/scripts/approval-server.sh +24 -5
  277. package/scripts/before-publish.mjs +12 -10
  278. package/scripts/cloud-shim.pl +57 -0
  279. package/shared/account-names.d.mts +6 -0
  280. package/shared/account-names.mjs +15 -0
  281. package/shared/agent-hours.mjs +136 -0
  282. package/shared/agent-import.mjs +62 -34
  283. package/shared/agents.d.mts +1 -0
  284. package/shared/agents.mjs +126 -522
  285. package/shared/analytics-events.mjs +12 -0
  286. package/shared/approval-card.mjs +36 -0
  287. package/shared/bridge-map.mjs +45 -1
  288. package/shared/claude-code-shape.mjs +6 -0
  289. package/shared/claude-commands.generated.mjs +6 -4
  290. package/shared/claude-models.generated.mjs +6 -4
  291. package/shared/claude-usage.d.mts +1 -0
  292. package/shared/claude-usage.mjs +42 -1
  293. package/shared/cloud-hand-back.d.mts +7 -0
  294. package/shared/cloud-hand-back.mjs +20 -0
  295. package/shared/cloud-kit.d.mts +219 -0
  296. package/shared/cloud-kit.mjs +341 -0
  297. package/shared/cloud-login-name.mjs +4 -0
  298. package/shared/cloud-start.mjs +17 -0
  299. package/shared/cloud-versions.mjs +23 -0
  300. package/shared/codex-import.mjs +2 -2
  301. package/shared/codex-modes.d.mts +22 -9
  302. package/shared/codex-modes.mjs +67 -11
  303. package/shared/codex-usage.mjs +108 -2
  304. package/shared/corner-tag.d.mts +36 -0
  305. package/shared/corner-tag.mjs +396 -0
  306. package/shared/dead-run-trace.mjs +19 -2
  307. package/shared/engines.mjs +76 -300
  308. package/shared/feedback.d.mts +7 -0
  309. package/shared/feedback.mjs +25 -0
  310. package/shared/first-run-practice.mjs +17 -24
  311. package/shared/harness-definitions.mjs +111 -0
  312. package/shared/instruction-defaults.json +2 -2
  313. package/shared/key-caps.d.mts +14 -0
  314. package/shared/key-caps.mjs +68 -0
  315. package/shared/message-edits.d.mts +7 -0
  316. package/shared/message-edits.mjs +35 -0
  317. package/shared/move-thread.d.mts +24 -0
  318. package/shared/move-thread.mjs +112 -0
  319. package/shared/named-threads.mjs +72 -0
  320. package/shared/not-shipped.d.mts +22 -0
  321. package/shared/not-shipped.mjs +89 -0
  322. package/shared/notify-rules.d.mts +31 -0
  323. package/shared/notify-rules.mjs +55 -8
  324. package/shared/phone-files.mjs +28 -0
  325. package/shared/phone-settings.mjs +118 -0
  326. package/shared/phone-snapshot.d.mts +16 -0
  327. package/shared/phone-snapshot.mjs +348 -0
  328. package/shared/picture-formats.d.mts +1 -0
  329. package/shared/picture-formats.mjs +13 -0
  330. package/shared/provider-commands.mjs +10 -2
  331. package/shared/rank.d.mts +4 -1
  332. package/shared/rank.mjs +82 -19
  333. package/shared/relay-address.mjs +6 -0
  334. package/shared/relay-crypto.mjs +165 -0
  335. package/shared/repeats.d.mts +8 -0
  336. package/shared/repeats.mjs +33 -2
  337. package/shared/room-cards.mjs +167 -0
  338. package/shared/room-links.d.mts +4 -0
  339. package/shared/room-links.mjs +21 -0
  340. package/shared/runs-on.d.mts +3 -13
  341. package/shared/runs-on.mjs +31 -52
  342. package/shared/shipped-note.d.mts +4 -0
  343. package/shared/shipped-note.mjs +6 -0
  344. package/shared/shortcuts.mjs +24 -10
  345. package/shared/spawn-trouble.d.mts +4 -0
  346. package/shared/spawn-trouble.mjs +484 -30
  347. package/shared/team-rules.mjs +9 -2
  348. package/shared/teammates.d.mts +19 -0
  349. package/shared/teammates.mjs +756 -0
  350. package/shared/terminal-focus.d.mts +1 -0
  351. package/shared/terminal-focus.mjs +13 -0
  352. package/shared/terminal-input.d.mts +1 -0
  353. package/shared/terminal-input.mjs +30 -0
  354. package/shared/terminal-keys.d.mts +1 -0
  355. package/shared/terminal-keys.mjs +16 -0
  356. package/shared/terminal-size.d.mts +4 -0
  357. package/shared/terminal-size.mjs +29 -0
  358. package/shared/thread-cards.mjs +4 -1
  359. package/shared/usage.d.mts +12 -0
  360. package/shared/usage.mjs +56 -3
  361. package/shared/websites.d.mts +15 -0
  362. package/shared/websites.mjs +61 -0
  363. package/shared/where-it-runs-from.d.mts +43 -0
  364. package/shared/where-it-runs-from.mjs +155 -0
  365. package/shared/work-items.mjs +150 -29
  366. package/shared/work-lines.mjs +93 -245
  367. package/shared/xterm-invalid-range.d.mts +2 -0
  368. package/shared/xterm-invalid-range.mjs +58 -0
  369. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  370. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -0,0 +1,365 @@
1
+ // The untracked local files a new task folder is given: dependencies, a `.env`,
2
+ // a machine's own config. A worktree holds only the commit, so without this
3
+ // they are missing.
4
+ //
5
+ // `.worktreeinclude` is the file Conductor and Claude Code already read: a file
6
+ // at the repository root in .gitignore syntax naming the ignored files to carry.
7
+ // Git matches the patterns (`git ls-files --others --ignored --exclude-from`),
8
+ // so negation, anchoring and globs behave exactly as git defines them.
9
+ //
10
+ // That command does not consult the repository's own .gitignore. A path it
11
+ // selects that the repository does not ignore would stay untracked in the
12
+ // folder, and `parkTaskFolder` runs `git add -A`, so closing the task would
13
+ // commit it, credentials included. Every selected path is therefore checked
14
+ // with `git check-ignore` in both checkouts.
15
+ //
16
+ // Every path that is not carried, and every link that cannot be vouched for,
17
+ // refuses the whole folder by name. Nothing is dropped quietly.
18
+ import { execFileSync } from 'node:child_process';
19
+ import fs from 'node:fs';
20
+ import os from 'node:os';
21
+ import path from 'node:path';
22
+
23
+ export const INCLUDE_FILE = '.worktreeinclude';
24
+
25
+ /**
26
+ * macOS `cp -c` asks APFS for a block-sharing clone. GNU `cp` has no `-c`;
27
+ * `-a --reflink=auto` shares blocks where the filesystem can and copies where
28
+ * it cannot. Lives here rather than in `main/task-folders.mjs`, the other
29
+ * caller, because that file imports this one.
30
+ */
31
+ export function copyTree(from, to) {
32
+ const args = process.platform === 'darwin'
33
+ ? ['-c', '-R', from, to]
34
+ : ['-a', '--reflink=auto', from, to];
35
+ execFileSync('cp', args, { stdio: ['ignore', 'ignore', 'pipe'] });
36
+ }
37
+
38
+ /**
39
+ * For a repository with no `.worktreeinclude`: the root's dependencies. No
40
+ * trailing slash on purpose. `/node_modules/` matches directories only, so a
41
+ * `node_modules` that is a symlink to a shared one would be skipped without a
42
+ * word. Without the slash it is selected and then refused by name.
43
+ */
44
+ export const DEFAULT_PATTERNS = ['/node_modules'];
45
+
46
+ /** The app's own task folders. They are ignored, so a pattern like `**` selects
47
+ * them, and carrying them would copy every other task into this one. */
48
+ const MANAGED = '.claude/worktrees';
49
+
50
+ const git = (cwd, args, input) => execFileSync('git', args, {
51
+ cwd, input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
52
+ });
53
+
54
+ const patternsIn = (text) => text.split('\n')
55
+ .map((line) => line.replace(/\r$/, ''))
56
+ .filter((line) => line.trim() !== '' && !line.trimStart().startsWith('#'));
57
+
58
+ /**
59
+ * An empty `.worktreeinclude` carries nothing rather than the default, so a
60
+ * repository can ask for a bare folder. One that exists and cannot be read
61
+ * throws rather than falling back to a guess.
62
+ */
63
+ export function askedFor(root) {
64
+ const file = path.join(root, INCLUDE_FILE);
65
+ if (!fs.existsSync(file)) return { patterns: DEFAULT_PATTERNS, source: 'the default list' };
66
+ try { return { patterns: patternsIn(fs.readFileSync(file, 'utf8')), source: INCLUDE_FILE }; }
67
+ catch (error) { throw Error(`${INCLUDE_FILE} is there and could not be read: ${error.message}`); }
68
+ }
69
+
70
+ /**
71
+ * `--directory` collapses a wholly untracked directory to one entry, so
72
+ * node_modules is one `cp`. `-z` because a filename may contain a newline.
73
+ */
74
+ export function localFilesIn(root) {
75
+ const { patterns, source } = askedFor(root);
76
+ if (!patterns.length) return { paths: [], patterns, source };
77
+ const held = fs.mkdtempSync(path.join(os.tmpdir(), 'worktree-include-'));
78
+ const file = path.join(held, 'patterns');
79
+ try {
80
+ fs.writeFileSync(file, `${patterns.join('\n')}\n`);
81
+ const out = git(root, ['ls-files', '-z', '--others', '--ignored', '--directory', `--exclude-from=${file}`]);
82
+ const paths = out.split('\0')
83
+ .map((p) => p.replace(/\/$/, ''))
84
+ .filter(Boolean)
85
+ .filter(keepable);
86
+ return { paths, patterns, source };
87
+ } catch (error) {
88
+ throw Error(`${source} could not be read against this checkout: ${String(error?.stderr ?? error?.message ?? '').trim()}`);
89
+ } finally {
90
+ try { fs.rmSync(held, { recursive: true, force: true }); } catch { /* a temp dir */ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Exclusions no pattern can override: `.git` and the task folders. A path that
96
+ * contains the task folders (`**` collapses to `.claude`) is dropped whole
97
+ * rather than narrowed by a matcher of ours. A repository that needs one file
98
+ * from `.claude` names it exactly, and that still comes through.
99
+ */
100
+ function keepable(rel) {
101
+ if (rel.split('/')[0] === '.git') return false;
102
+ if (rel === MANAGED || rel.startsWith(`${MANAGED}/`)) return false;
103
+ if (MANAGED.startsWith(`${rel}/`)) return false;
104
+ return true;
105
+ }
106
+
107
+ /**
108
+ * Which of these paths the checkout's own rules ignore. `git check-ignore`
109
+ * exits 1 for "none of them"; any other failure throws rather than reading as
110
+ * "not ignored".
111
+ */
112
+ function ignoredIn(cwd, paths) {
113
+ if (!paths.length) return new Set();
114
+ try {
115
+ const out = git(cwd, ['check-ignore', '-z', '--stdin'], `${paths.join('\0')}\0`);
116
+ return new Set(out.split('\0').filter(Boolean));
117
+ } catch (error) {
118
+ if (error?.status === 1) return new Set();
119
+ throw Error(`git could not say what this checkout ignores: ${String(error?.stderr ?? error?.message ?? '').trim()}`);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * A path that cannot be looked at is not a path with nothing at it. ENOENT and
125
+ * ENOTDIR are real absences; any other error throws, because these answers
126
+ * decide whether something may be written through a link nobody inspected.
127
+ * Pinned by tests/a-file-the-app-cannot-look-at-is-not-a-file-that-is-missing.test.mjs.
128
+ */
129
+ function lookAt(p) {
130
+ try { return fs.lstatSync(p); }
131
+ catch (error) {
132
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return null;
133
+ throw Error(`${p} could not be looked at: ${error.message}`);
134
+ }
135
+ }
136
+
137
+ const isLink = (p) => lookAt(p)?.isSymbolicLink() === true;
138
+ /** On disk at all, including a link with nothing at the end of it. */
139
+ const stillThere = (p) => lookAt(p) !== null;
140
+
141
+ /**
142
+ * Where a link points, or null if it went away (ENOENT) or is no longer a link
143
+ * (EINVAL). A link that cannot be read cannot be judged, so that throws.
144
+ */
145
+ function linkTarget(p) {
146
+ try { return fs.readlinkSync(p); }
147
+ catch (error) {
148
+ if (['ENOENT', 'ENOTDIR', 'EINVAL'].includes(error?.code)) return null;
149
+ throw Error(`${p} is a link whose target could not be read: ${error.message}`);
150
+ }
151
+ }
152
+
153
+ function reachedThroughALink(folder, rel) {
154
+ let at = folder;
155
+ for (const part of rel.split('/').slice(0, -1)) {
156
+ at = path.join(at, part);
157
+ if (isLink(at)) return true;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Whether a path is under a folder, by its text only. `..` is checked as a
164
+ * whole component so a directory named `..deps` is not read as a climb out.
165
+ */
166
+ const climbsOut = (rel) => rel === '..' || rel.startsWith(`..${path.sep}`);
167
+ const within = (folder, abs) => {
168
+ const rel = path.relative(folder, abs);
169
+ return rel !== '' && !climbsOut(rel) && !path.isAbsolute(rel);
170
+ };
171
+ const withinOrIs = (folder, abs) => {
172
+ const rel = path.relative(folder, abs);
173
+ return !climbsOut(rel) && !path.isAbsolute(rel);
174
+ };
175
+
176
+ /**
177
+ * The real path of the nearest ancestor on disk. This lets a dangling link be
178
+ * judged: with a tracked `bridge -> /shared`, a carried link to
179
+ * `../../bridge/newfile` dangles but would write into `/shared`.
180
+ */
181
+ function nearestReal(abs) {
182
+ let at = abs;
183
+ for (;;) {
184
+ if (stillThere(at)) {
185
+ try { return fs.realpathSync(at); }
186
+ catch (error) {
187
+ // ENOENT means it went away between the two calls. Any other error
188
+ // would skip the containment check, so it throws.
189
+ if (error?.code === 'ENOENT') return null;
190
+ throw Error(`${at} could not be followed: ${error.message}`);
191
+ }
192
+ }
193
+ const up = path.dirname(at);
194
+ if (up === at) return null;
195
+ at = up;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Links in a carried entry that would let the folder write somewhere else, such
201
+ * as `node_modules -> ../other-checkout/node_modules`, where an install rewrites
202
+ * another folder's dependencies.
203
+ *
204
+ * An absolute link is never carried, even into this checkout, because after the
205
+ * copy it still points at the original. A relative one is carried when it lands
206
+ * inside the folder, as workspace links do. Every link in the subtree is checked,
207
+ * because `node_modules/@scope/` hides links a level down. `find` does not follow
208
+ * links while walking, so a link cannot lead the walk out.
209
+ */
210
+ function linksThatDoNotHold(root, folder, rel) {
211
+ const to = path.join(folder, rel);
212
+ let found = [];
213
+ try {
214
+ // `find <path>` on a path that is itself a link reports that link, so an
215
+ // entry that is a link gets the same check as the links inside a directory.
216
+ found = execFileSync('find', [to, '-type', 'l', '-print0'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
217
+ .split('\0').filter(Boolean);
218
+ } catch (error) {
219
+ throw Error(`the links inside ${rel} could not be checked: ${String(error?.stderr ?? error?.message ?? '').trim()}`);
220
+ }
221
+ const bad = [];
222
+ for (const link of found) {
223
+ const name = path.relative(folder, link);
224
+ const target = linkTarget(link);
225
+ if (target === null) continue; // gone from under us, or no longer a link
226
+ if (path.isAbsolute(target)) { bad.push({ path: name, why: 'it is a link to somewhere outside this folder' }); continue; }
227
+ const resolved = path.resolve(path.dirname(link), target);
228
+ // Checked even when the link dangles: the first write through a dangling
229
+ // link to `../../../w-neighbour/newfile` lands in another task's folder.
230
+ if (!within(folder, resolved)) { bad.push({ path: name, why: 'it is a link that leads out of this folder' }); continue; }
231
+ // A path inside the folder can still arrive outside it through a link on
232
+ // the way, and those links exist even when the last name does not.
233
+ const home = nearestReal(folder);
234
+ const reached = nearestReal(resolved);
235
+ if (home && reached && !withinOrIs(home, reached)) {
236
+ bad.push({ path: name, why: 'it is a link that leads out of this folder through another link' });
237
+ continue;
238
+ }
239
+ if (fs.existsSync(resolved)) continue;
240
+ // Dangling but contained. It is refused only if the copy broke it, meaning
241
+ // the target exists in the checkout and was not carried. A link already
242
+ // dangling in the checkout is left as it was.
243
+ const sourceSide = path.resolve(path.dirname(path.join(root, name)), target);
244
+ if (fs.existsSync(sourceSide)) bad.push({ path: name, why: 'it points at something that was not carried in with it' });
245
+ }
246
+ return bad;
247
+ }
248
+
249
+ /**
250
+ * Carries the selected local files into a folder that has just been made.
251
+ * A pattern that matches nothing is fine, since a repository may name a `.env`
252
+ * this machine lacks. Anything else that cannot be carried throws, with every
253
+ * path and reason, so the caller gets a complete folder or none.
254
+ */
255
+ export function carryLocalFiles(root, folder) {
256
+ const { paths, source } = localFilesIn(root);
257
+ const theirs = ignoredIn(root, paths);
258
+ const carried = [];
259
+ const refused = [];
260
+ const no = (rel, why) => refused.push({ path: rel, why });
261
+
262
+ for (const rel of paths) {
263
+ const from = path.join(root, rel);
264
+ const to = path.join(folder, rel);
265
+ if (!fs.existsSync(from) && !isLink(from)) continue; // it went away; silent
266
+ if (!theirs.has(rel)) { no(rel, 'the repository does not ignore it, so carrying it in could commit it'); continue; }
267
+ // The folder may be on a different commit from the checkout, where this
268
+ // path is tracked.
269
+ if (tracksIt(folder, rel)) { no(rel, 'this branch tracks it'); continue; }
270
+ if (reachedThroughALink(folder, rel)) { no(rel, 'a link stands where it would have to be written'); continue; }
271
+ if (fs.existsSync(to)) continue; // already there; silent
272
+ if (isLink(from) && !carriableLink(root, from)) { no(rel, 'it is a link to somewhere outside the repository'); continue; }
273
+ try {
274
+ fs.mkdirSync(path.dirname(to), { recursive: true });
275
+ copyTree(from, to);
276
+ } catch (error) {
277
+ no(rel, `it could not be copied: ${String(error?.stderr ?? error?.message ?? '').trim()}`);
278
+ continue;
279
+ }
280
+ // Asked only after the copy: a trailing-slash pattern like `node_modules/`
281
+ // matches directories only, so git answers "not ignored" for a path not on
282
+ // disk yet. What the folder's branch does not ignore is removed again,
283
+ // because closing the task runs `git add -A`.
284
+ if (!ignoredIn(folder, [rel]).has(rel)) {
285
+ try { fs.rmSync(to, { recursive: true, force: true }); } catch { /* best effort */ }
286
+ no(rel, 'the branch in this folder does not ignore it, so closing the task would commit it');
287
+ continue;
288
+ }
289
+ carried.push(rel);
290
+ }
291
+
292
+ // Links are judged only after every copy. `node_modules/thing ->
293
+ // ../packages/thing` dangles until `packages`, listed later, is copied.
294
+ for (const rel of carried) refused.push(...linksThatDoNotHold(root, folder, rel));
295
+
296
+ if (refused.length) {
297
+ throw Error(`${source} names files this folder could not be given:\n${
298
+ refused.map((r) => ` ${r.path}: ${r.why}`).join('\n')}`);
299
+ }
300
+ return { carried, source };
301
+ }
302
+
303
+ /**
304
+ * An entry that is itself a link, judged in the checkout before copying. The
305
+ * check that matters runs in the folder after every copy; this one keeps an
306
+ * obviously shared link from being copied at all.
307
+ */
308
+ function carriableLink(root, abs) {
309
+ const target = linkTarget(abs);
310
+ if (target === null) return false;
311
+ if (path.isAbsolute(target)) return false;
312
+ const resolved = path.resolve(path.dirname(abs), target);
313
+ if (!within(root, resolved)) return false;
314
+ const home = nearestReal(root);
315
+ const reached = nearestReal(resolved);
316
+ return !home || !reached || withinOrIs(home, reached);
317
+ }
318
+
319
+ const tracksIt = (folder, rel) => {
320
+ try { git(folder, ['ls-files', '--error-unmatch', '-z', '--', rel]); return true; }
321
+ catch (error) { if (error?.status === 1) return false; throw Error(`git could not say whether this branch tracks ${rel}: ${String(error?.stderr ?? error?.message ?? '').trim()}`); }
322
+ };
323
+
324
+ /**
325
+ * Clears untracked files a clone brought along, such as `renderer/dist` inside
326
+ * a tracked top-level directory, so a cloned folder matches a checked-out one.
327
+ * Runs before anything is carried in, so it never removes what was asked for.
328
+ */
329
+ export function onlyTrackedContent(folder) {
330
+ let found;
331
+ try { found = git(folder, ['ls-files', '-z', '--others', '--directory', '--no-empty-directory']); }
332
+ catch (error) { throw Error(`what is in this folder could not be listed: ${String(error?.stderr ?? error?.message ?? '').trim()}`); }
333
+ const gone = [];
334
+ for (const rel of found.split('\0').map((p) => p.replace(/\/$/, '')).filter(Boolean)) {
335
+ if (!keepable(rel)) continue;
336
+ const at = path.join(folder, rel);
337
+ try { fs.rmSync(at, { recursive: true, force: true }); }
338
+ // `lstat`, because existsSync follows links and would report a dangling
339
+ // link that failed to be removed as gone.
340
+ catch (error) { if (stillThere(at)) throw Error(`${rel} came with the clone and could not be cleared: ${error.message}`); }
341
+ gone.push(rel);
342
+ }
343
+ return gone;
344
+ }
345
+
346
+ /**
347
+ * The top-level entries holding tracked content, which the clone copies.
348
+ * Anything else wanted arrives by name through `.worktreeinclude`.
349
+ *
350
+ * A submodule (mode 160000) stops the clone: copying it would hand the folder
351
+ * another repository's git data, and `git worktree move` refuses a worktree
352
+ * holding one. A plain `git worktree add` handles submodules.
353
+ */
354
+ export function trackedTopLevel(root) {
355
+ let listed;
356
+ try { listed = git(root, ['ls-files', '-s', '-z']); }
357
+ catch (error) { throw Error(`the tracked files could not be listed: ${String(error?.stderr ?? error?.message ?? '').trim()}`); }
358
+ const top = new Set();
359
+ for (const line of listed.split('\0').filter(Boolean)) {
360
+ const [modes, rel] = line.split('\t');
361
+ if (modes?.startsWith('160000')) return { entries: [], submodules: true };
362
+ if (rel) top.add(rel.split('/')[0]);
363
+ }
364
+ return { entries: [...top], submodules: false };
365
+ }
@@ -12,7 +12,7 @@ import path from 'node:path';
12
12
  import crypto from 'node:crypto';
13
13
  import { readIndex, readCreationContent, saveCreation, setCreationStatus, CREATION_STATUSES } from '../../main/store/project.mjs';
14
14
  import { runForProject } from '../../main/store/queues.mjs';
15
- import { assertInside, deniesGitOrEnvPath } from '../../main/store/paths.mjs';
15
+ import { assertInside, deniesGitOrEnvPath, deniesMachineryPath } from '../../main/store/paths.mjs';
16
16
  import { AGENT_CREATION_KINDS, SCHEMA_VERSION } from '../../shared/contracts.mjs';
17
17
  import { libraryFolderOf } from '../../shared/library-folder.mjs';
18
18
  import { resolveProduct } from './account.mjs';
@@ -142,6 +142,7 @@ export function readDocument(ref, { id = null, path: relPath = null } = {}) {
142
142
  // are what make an agent-supplied path safe to open at all.
143
143
  if (id) throw new Error(`no document with id ${id} in ${project.name}`);
144
144
  if (deniesGitOrEnvPath(relPath)) throw new Error(`refusing to read a .git/.env path: ${relPath}`);
145
+ if (deniesMachineryPath(relPath)) throw new Error(`refusing to read a reserved store path: ${relPath}`);
145
146
  let content;
146
147
  try {
147
148
  content = fs.readFileSync(assertInside(project.dir, relPath), 'utf8');
@@ -181,6 +182,7 @@ export async function writeDocument(ref, { id = null, path: relPath = null, titl
181
182
  const nextPath = relPath ?? existing?.path;
182
183
  if (!nextPath) throw new Error('a new document needs a path, e.g. "reports/first-run.md" or "personas/marcus.md"');
183
184
  if (deniesGitOrEnvPath(nextPath)) throw new Error(`refusing to write a .git/.env path: ${nextPath}`);
185
+ if (deniesMachineryPath(nextPath)) throw new Error(`refusing to write a reserved store path: ${nextPath}`);
184
186
 
185
187
  // THE PATH ALREADY SAYS WHAT THIS IS, so a caller that did not name a kind
186
188
  // gets the one its extension implies rather than a blanket 'markdown'. Two
@@ -0,0 +1,88 @@
1
+ // The standing instruction behind a repeating task, for the agent on one of its
2
+ // runs.
3
+ //
4
+ // WHICH RUN THE SESSION IS ON COMES FROM THE APP, NOT FROM THE MODEL.
5
+ // `ZERO_ITEM` and `ZERO_PRODUCT` are written into a worker's environment when
6
+ // the app spawns it (main/supervisor.mjs), which is why no argument below names
7
+ // the task: a session reaches the rule it is standing on or no rule at all.
8
+ //
9
+ // AND ONLY THE WORDS, THOUGH `patchRule` TAKES ANYTHING. The app gates `engine`
10
+ // and `model` at the door (`engineOffered`, `modelOffered` in main/ipc.mjs) so a
11
+ // rule cannot be marked for a coding agent the machine never offered; this
12
+ // server cannot ask that question, so it does not accept the answer. The hour
13
+ // and the day stay on the person's own screen.
14
+
15
+ import { Repeats } from '../../main/repeats.mjs';
16
+ import { readWorkItem } from '../../main/store/work-items.mjs';
17
+ import { listProjects } from '../../main/store/project.mjs';
18
+ import { nextRunAt, ruleIdOf, ruleLabel, standingInstruction } from '../../shared/repeats.mjs';
19
+ import { resolveProduct } from './account.mjs';
20
+
21
+ const NO_THREAD = 'this session is not on a thread, so there is no repeating task behind it';
22
+
23
+ // Read and patch only, so the store modules `serve` needs are never built here:
24
+ // nothing below creates a work item or takes the project lock.
25
+ const rules = new Repeats(null);
26
+
27
+ function projectOf(itemId, product) {
28
+ if (product) return resolveProduct(product);
29
+ for (const p of listProjects()) {
30
+ try { if (readWorkItem(p.dir, itemId)) return p; } catch { /* a product with no ledger */ }
31
+ }
32
+ throw new Error(`no thread ${itemId} in any product`);
33
+ }
34
+
35
+ /** The rule this session's run belongs to, or a sentence saying why there is none. */
36
+ function ruleBehindThisRun({ env = process.env, now = Date.now() } = {}) {
37
+ const itemId = String(env.ZERO_ITEM ?? '').trim();
38
+ if (!itemId) throw new Error(NO_THREAD);
39
+
40
+ const project = projectOf(itemId, String(env.ZERO_PRODUCT ?? '').trim() || null);
41
+ const item = readWorkItem(project.dir, itemId, now);
42
+ if (!item) throw new Error(`no thread ${itemId} in ${project.name}`);
43
+
44
+ const ruleId = ruleIdOf(item);
45
+ if (!ruleId) {
46
+ throw new Error(`${itemId} is not a run of a repeating task, so it has no standing instruction to change`);
47
+ }
48
+ const rule = rules.get(project.dir, ruleId, now);
49
+ if (!rule) throw new Error(`${itemId} names the repeating task ${ruleId}, which is no longer on file`);
50
+ return { project, item, ruleId, rule };
51
+ }
52
+
53
+ function asAnswer({ project, item, ruleId, rule }, now) {
54
+ return {
55
+ repeat: ruleId,
56
+ product: project.id,
57
+ run: item.id,
58
+ label: rule.title,
59
+ instruction: standingInstruction(rule),
60
+ schedule: ruleLabel(rule),
61
+ nextRun: nextRunAt(rule, now),
62
+ ended: !!rule.endedAt,
63
+ };
64
+ }
65
+
66
+ /** What every run after this one is told, as it stands. */
67
+ export function readStandingInstruction({ env = process.env, now = Date.now() } = {}) {
68
+ return asAnswer(ruleBehindThisRun({ env, now }), now);
69
+ }
70
+
71
+ /**
72
+ * Change it. The whole instruction, replaced, because that is the operation the
73
+ * person's own screen performs and the only one that can also take a sentence
74
+ * back out.
75
+ */
76
+ export function setStandingInstruction({ instruction }, { env = process.env, now = Date.now() } = {}) {
77
+ const words = typeof instruction === 'string' ? instruction.trim() : '';
78
+ if (!words) throw new Error('a repeating task needs an instruction; send the whole of it, not an empty one');
79
+
80
+ const found = ruleBehindThisRun({ env, now });
81
+ if (found.rule.endedAt) throw new Error(`the repeating task ${found.ruleId} was ended, so it has no next run to change`);
82
+
83
+ // Only the body. The title is a label the app derived from what the person
84
+ // typed, and a line added to a standing instruction is not a new name for the
85
+ // task.
86
+ const rule = rules.patchRule(found.project.dir, found.ruleId, { body: words }, now);
87
+ return asAnswer({ ...found, rule }, now);
88
+ }