toolroll 0.9.62 → 0.9.65

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 (345) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +66 -8
  3. package/dist/acp-agents.d.ts +43 -0
  4. package/dist/acp-agents.js +62 -0
  5. package/dist/acp-attest.d.ts +44 -0
  6. package/dist/acp-attest.js +269 -0
  7. package/dist/acp-check.d.ts +28 -0
  8. package/dist/acp-check.js +410 -0
  9. package/dist/acp-client.d.ts +163 -0
  10. package/dist/acp-client.js +738 -0
  11. package/dist/acp-inspect.d.ts +23 -0
  12. package/dist/acp-inspect.js +109 -0
  13. package/dist/agent-board.d.ts +19 -6
  14. package/dist/agent-board.js +30 -9
  15. package/dist/agent-fence.d.ts +203 -10
  16. package/dist/agent-fence.js +623 -41
  17. package/dist/agent-pulse.js +9 -2
  18. package/dist/agent-state-words.d.ts +19 -0
  19. package/dist/agent-state-words.js +20 -0
  20. package/dist/agent-state.d.ts +65 -0
  21. package/dist/agent-state.js +229 -0
  22. package/dist/agentconfig.js +36 -13
  23. package/dist/agents-board.d.ts +34 -2
  24. package/dist/agents-board.js +110 -1
  25. package/dist/approval-rules-ui.js +2 -2
  26. package/dist/assignment-delivery.d.ts +14 -0
  27. package/dist/assignment-delivery.js +13 -0
  28. package/dist/assignment.js +13 -0
  29. package/dist/attest.js +7 -0
  30. package/dist/backup-ui.js +2 -2
  31. package/dist/browser/THIRD_PARTY_NOTICES.txt +48 -0
  32. package/dist/browser/terminal.css +37 -0
  33. package/dist/browser/terminal.js +117 -0
  34. package/dist/browser/workspace.css +2 -2
  35. package/dist/browser/workspace.js +42 -38
  36. package/dist/browser-shell.js +6 -3
  37. package/dist/browser-workspace.d.ts +263 -1
  38. package/dist/browser-workspace.js +60 -0
  39. package/dist/builder.d.ts +6 -1
  40. package/dist/builder.js +34 -13
  41. package/dist/chat-actions.d.ts +13 -1
  42. package/dist/chat-actions.js +199 -17
  43. package/dist/chat-ask.d.ts +4 -0
  44. package/dist/chat-ask.js +14 -0
  45. package/dist/chat-channel.js +20 -0
  46. package/dist/chat-continuity.js +5 -4
  47. package/dist/chat-decide.d.ts +0 -14
  48. package/dist/chat-decide.js +4 -1
  49. package/dist/chat-delivery.d.ts +9 -0
  50. package/dist/chat-delivery.js +45 -6
  51. package/dist/chat-messages.d.ts +8 -0
  52. package/dist/chat-messages.js +36 -4
  53. package/dist/check-levels-ui.js +2 -2
  54. package/dist/check-levels.js +3 -0
  55. package/dist/checkout-proof.d.ts +28 -0
  56. package/dist/checkout-proof.js +62 -0
  57. package/dist/claim.d.ts +12 -0
  58. package/dist/claim.js +20 -1
  59. package/dist/cli-help.d.ts +1 -1
  60. package/dist/cli-help.js +5 -1
  61. package/dist/cli.d.ts +3 -0
  62. package/dist/cli.js +24 -2
  63. package/dist/coding-ui.js +6 -4
  64. package/dist/console-instance.d.ts +124 -0
  65. package/dist/console-instance.js +337 -0
  66. package/dist/contracts/chat-actions.d.ts +16 -0
  67. package/dist/contracts/chat-actions.js +4 -0
  68. package/dist/contracts/chat-content.d.ts +19 -0
  69. package/dist/contracts/chat-content.js +6 -0
  70. package/dist/contracts/cli.d.ts +8 -0
  71. package/dist/contracts/cli.js +3 -1
  72. package/dist/contracts/console-api.d.ts +30 -4
  73. package/dist/contracts/console-api.js +13 -4
  74. package/dist/contracts/flow-card.d.ts +4 -0
  75. package/dist/contracts/flow-card.js +3 -0
  76. package/dist/contracts/flow.d.ts +46 -0
  77. package/dist/contracts/flow.js +11 -2
  78. package/dist/contracts/integration-metadata.d.ts +2 -1
  79. package/dist/contracts/integration-metadata.js +2 -1
  80. package/dist/contracts/lead-tools.d.ts +92 -0
  81. package/dist/contracts/lead-tools.js +13 -0
  82. package/dist/contracts/provider.d.ts +11 -1
  83. package/dist/contracts/provider.js +1 -1
  84. package/dist/contracts/route.d.ts +76 -1
  85. package/dist/contracts/route.js +1 -1
  86. package/dist/contracts/store-json.d.ts +2 -0
  87. package/dist/control-setup.d.ts +1 -1
  88. package/dist/control-ui.js +2 -2
  89. package/dist/converse.d.ts +16 -0
  90. package/dist/converse.js +32 -4
  91. package/dist/demo-chat.js +4 -4
  92. package/dist/demo.js +2 -2
  93. package/dist/desktop-update-gate.js +6 -0
  94. package/dist/discord-chat.js +4 -1
  95. package/dist/dispatch.js +7 -4
  96. package/dist/doctor.d.ts +53 -0
  97. package/dist/doctor.js +427 -0
  98. package/dist/egress-bridge.d.ts +17 -0
  99. package/dist/egress-bridge.js +111 -0
  100. package/dist/egress.d.ts +119 -0
  101. package/dist/egress.js +423 -0
  102. package/dist/evidence-pack.d.ts +1 -1
  103. package/dist/evidence-pack.js +2 -2
  104. package/dist/exec.d.ts +81 -0
  105. package/dist/exec.js +368 -9
  106. package/dist/export-ui.js +2 -2
  107. package/dist/flow-code.d.ts +2 -2
  108. package/dist/flow-code.js +4 -15
  109. package/dist/flow-engine.js +35 -11
  110. package/dist/flow-gallery-ui.js +2 -2
  111. package/dist/flow-gallery.js +27 -8
  112. package/dist/flow-share.js +16 -5
  113. package/dist/flow-triggers.d.ts +84 -11
  114. package/dist/flow-triggers.js +554 -87
  115. package/dist/flow-unattended-words.d.ts +28 -0
  116. package/dist/flow-unattended-words.js +50 -0
  117. package/dist/flow-unattended.d.ts +227 -0
  118. package/dist/flow-unattended.js +776 -0
  119. package/dist/flows-cli.d.ts +28 -1
  120. package/dist/flows-cli.js +63 -12
  121. package/dist/flows-ui.d.ts +2 -3
  122. package/dist/flows-ui.js +56 -5
  123. package/dist/flows.d.ts +4 -0
  124. package/dist/flows.js +9 -1
  125. package/dist/google-mail.d.ts +1 -0
  126. package/dist/google-mail.js +61 -18
  127. package/dist/html.d.ts +9 -0
  128. package/dist/html.js +11 -0
  129. package/dist/integrations-ui.d.ts +2 -1
  130. package/dist/integrations-ui.js +6 -3
  131. package/dist/integrations.d.ts +17 -3
  132. package/dist/integrations.js +38 -18
  133. package/dist/invoke.d.ts +17 -0
  134. package/dist/invoke.js +510 -182
  135. package/dist/keys.d.ts +18 -14
  136. package/dist/keys.js +5 -0
  137. package/dist/landed-cleanup.d.ts +3 -2
  138. package/dist/landed-cleanup.js +12 -5
  139. package/dist/lead-capabilities.js +3 -2
  140. package/dist/lead-contract.js +2 -0
  141. package/dist/lead-doors.d.ts +2 -0
  142. package/dist/lead-doors.js +7 -0
  143. package/dist/lead-follow.d.ts +2 -0
  144. package/dist/lead-follow.js +58 -2
  145. package/dist/lead-progress.js +1 -0
  146. package/dist/lead-queue-runtime.d.ts +46 -0
  147. package/dist/lead-queue-runtime.js +191 -0
  148. package/dist/lead-queue.d.ts +104 -0
  149. package/dist/lead-queue.js +187 -0
  150. package/dist/lead-sweep.d.ts +67 -0
  151. package/dist/lead-sweep.js +99 -0
  152. package/dist/lead-tools.d.ts +22 -0
  153. package/dist/lead-tools.js +163 -4
  154. package/dist/lead.d.ts +13 -5
  155. package/dist/lead.js +89 -29
  156. package/dist/limits-ui.d.ts +8 -1
  157. package/dist/limits-ui.js +26 -0
  158. package/dist/live-bus.d.ts +8 -0
  159. package/dist/live-bus.js +40 -4
  160. package/dist/live-detail.d.ts +27 -0
  161. package/dist/live-detail.js +57 -0
  162. package/dist/live-notes.d.ts +20 -0
  163. package/dist/live-notes.js +47 -0
  164. package/dist/live-records.d.ts +140 -0
  165. package/dist/live-records.js +935 -0
  166. package/dist/live-terminal.d.ts +258 -0
  167. package/dist/live-terminal.js +649 -0
  168. package/dist/live.d.ts +38 -27
  169. package/dist/live.js +142 -124
  170. package/dist/mcp-connect.d.ts +8 -7
  171. package/dist/mcp-connect.js +49 -51
  172. package/dist/mcp-oauth.d.ts +6 -3
  173. package/dist/mcp-oauth.js +8 -4
  174. package/dist/memory-pass.js +2 -2
  175. package/dist/mobile-viewport.d.ts +5 -1
  176. package/dist/mobile-viewport.js +18 -2
  177. package/dist/model-catalog.js +4 -0
  178. package/dist/monitoring-ui.js +2 -2
  179. package/dist/needs-you.d.ts +1 -1
  180. package/dist/needs-you.js +11 -4
  181. package/dist/oauth-run-token.d.ts +51 -0
  182. package/dist/oauth-run-token.js +127 -0
  183. package/dist/operate.d.ts +2 -0
  184. package/dist/operate.js +282 -18
  185. package/dist/passwordless.d.ts +23 -0
  186. package/dist/passwordless.js +26 -0
  187. package/dist/peek-cli.d.ts +12 -4
  188. package/dist/peek-cli.js +19 -11
  189. package/dist/phase-routing.js +10 -3
  190. package/dist/plan-auto.d.ts +11 -0
  191. package/dist/plan-auto.js +62 -11
  192. package/dist/planner-source.d.ts +3 -0
  193. package/dist/planner-source.js +14 -0
  194. package/dist/planner.d.ts +2 -1
  195. package/dist/planner.js +7 -1
  196. package/dist/policy-ui.js +2 -2
  197. package/dist/policy.d.ts +1 -20
  198. package/dist/policy.js +29 -2
  199. package/dist/process-recovery-eligibility.d.ts +1 -1
  200. package/dist/project-access.d.ts +3 -0
  201. package/dist/project-access.js +3 -0
  202. package/dist/project-delete-ui.d.ts +12 -0
  203. package/dist/project-delete-ui.js +41 -9
  204. package/dist/project-delete.d.ts +3 -1
  205. package/dist/project-delete.js +12 -6
  206. package/dist/project-file.d.ts +3 -0
  207. package/dist/project-file.js +16 -0
  208. package/dist/project-memory.d.ts +3 -1
  209. package/dist/project-memory.js +57 -25
  210. package/dist/project-tools.d.ts +103 -27
  211. package/dist/project-tools.js +399 -100
  212. package/dist/provider-auth.d.ts +4 -3
  213. package/dist/provider-auth.js +5 -4
  214. package/dist/provider-connection.d.ts +2 -1
  215. package/dist/provider-connection.js +5 -0
  216. package/dist/provider-home.d.ts +22 -0
  217. package/dist/provider-home.js +99 -0
  218. package/dist/provider.d.ts +42 -5
  219. package/dist/provider.js +97 -19
  220. package/dist/pull-request-ui.js +2 -2
  221. package/dist/push.js +1 -1
  222. package/dist/quick-start.d.ts +24 -0
  223. package/dist/quick-start.js +127 -0
  224. package/dist/release.d.ts +3 -0
  225. package/dist/release.js +14 -8
  226. package/dist/remote-main.d.ts +4 -0
  227. package/dist/remote-main.js +3 -3
  228. package/dist/request-budget-ui.js +2 -2
  229. package/dist/result-review.d.ts +1 -1
  230. package/dist/result-review.js +9 -3
  231. package/dist/retention-ui.js +2 -2
  232. package/dist/retention.js +10 -1
  233. package/dist/run-efficiency.d.ts +73 -0
  234. package/dist/run-efficiency.js +228 -0
  235. package/dist/runner.d.ts +5 -1
  236. package/dist/runner.js +22 -1
  237. package/dist/scope.d.ts +171 -6
  238. package/dist/scope.js +251 -31
  239. package/dist/scout-net.d.ts +4 -1
  240. package/dist/scout-net.js +37 -12
  241. package/dist/scout.d.ts +20 -6
  242. package/dist/scout.js +27 -3
  243. package/dist/serve.js +258 -43
  244. package/dist/server/chat.d.ts +1 -1
  245. package/dist/server/chat.js +122 -31
  246. package/dist/server/chrome.d.ts +6 -1
  247. package/dist/server/chrome.js +18 -7
  248. package/dist/server/console.css +52 -38
  249. package/dist/server/flows.js +65 -13
  250. package/dist/server/guards.d.ts +1 -0
  251. package/dist/server/guards.js +23 -9
  252. package/dist/server/http.d.ts +7 -0
  253. package/dist/server/http.js +3 -2
  254. package/dist/server/live.d.ts +34 -1
  255. package/dist/server/live.js +39 -17
  256. package/dist/server/pages-assets.js +5 -0
  257. package/dist/server/pages.js +90 -13
  258. package/dist/server/people-tokens.d.ts +3 -0
  259. package/dist/server/people-tokens.js +55 -17
  260. package/dist/server/remote-hooks.js +12 -6
  261. package/dist/server/remote.d.ts +2 -1
  262. package/dist/server/remote.js +14 -5
  263. package/dist/server/render-chat.d.ts +12 -3
  264. package/dist/server/render-chat.js +47 -16
  265. package/dist/server/render-pages.d.ts +4 -1
  266. package/dist/server/render-pages.js +5 -4
  267. package/dist/server/render-people.d.ts +3 -2
  268. package/dist/server/render-people.js +3 -2
  269. package/dist/server/render-results.d.ts +18 -2
  270. package/dist/server/render-results.js +37 -28
  271. package/dist/server/render-settings.js +6 -5
  272. package/dist/server/render-tasks.d.ts +35 -10
  273. package/dist/server/render-tasks.js +110 -27
  274. package/dist/server/request-context.d.ts +2 -0
  275. package/dist/server/request-context.js +2 -1
  276. package/dist/server/route-table.d.ts +3 -0
  277. package/dist/server/route-table.js +49 -5
  278. package/dist/server/runtime.d.ts +34 -3
  279. package/dist/server/session.d.ts +25 -1
  280. package/dist/server/session.js +18 -4
  281. package/dist/server/settings.js +132 -13
  282. package/dist/server/sweeps.d.ts +4 -0
  283. package/dist/server/sweeps.js +58 -0
  284. package/dist/server/task-views.d.ts +2 -1
  285. package/dist/server/task-views.js +96 -6
  286. package/dist/server/tasks.js +320 -74
  287. package/dist/server/terminals.d.ts +31 -0
  288. package/dist/server/terminals.js +365 -0
  289. package/dist/setup-guide.js +3 -2
  290. package/dist/slack-chat.js +4 -1
  291. package/dist/spend-ui.js +3 -3
  292. package/dist/spend.js +2 -1
  293. package/dist/steer-capabilities.d.ts +32 -0
  294. package/dist/steer-capabilities.js +60 -0
  295. package/dist/steer-controls.d.ts +14 -0
  296. package/dist/steer-controls.js +36 -0
  297. package/dist/steer-status.js +2 -2
  298. package/dist/storage-sweep-child.js +3 -1
  299. package/dist/storage-sweep-process.d.ts +12 -5
  300. package/dist/storage-sweep-process.js +23 -6
  301. package/dist/storage-ui.js +4 -4
  302. package/dist/store-baseline.d.ts +1 -1
  303. package/dist/store-baseline.js +1 -1
  304. package/dist/store.d.ts +466 -19
  305. package/dist/store.js +1185 -63
  306. package/dist/subagent-desk.js +1 -1
  307. package/dist/subagent-tools.d.ts +1 -1
  308. package/dist/summary.js +3 -0
  309. package/dist/surface.js +6 -4
  310. package/dist/task-source.d.ts +58 -0
  311. package/dist/task-source.js +93 -0
  312. package/dist/team-leads.js +1 -1
  313. package/dist/teams-chat.js +4 -1
  314. package/dist/teardown-git.d.ts +14 -0
  315. package/dist/teardown-git.js +119 -0
  316. package/dist/telegram-lead.d.ts +3 -0
  317. package/dist/telegram-lead.js +44 -7
  318. package/dist/telegram.d.ts +11 -2
  319. package/dist/telegram.js +51 -17
  320. package/dist/terminal-catalog.d.ts +11 -0
  321. package/dist/terminal-catalog.js +129 -0
  322. package/dist/text-limits.d.ts +2 -0
  323. package/dist/text-limits.js +2 -0
  324. package/dist/tool-broker.d.ts +39 -0
  325. package/dist/tool-broker.js +370 -0
  326. package/dist/tool-launcher.js +5 -4
  327. package/dist/tool-secret-store.d.ts +170 -0
  328. package/dist/tool-secret-store.js +834 -0
  329. package/dist/toolroll-update-ui.js +2 -2
  330. package/dist/toolroll-update.js +14 -3
  331. package/dist/tools-ui.d.ts +7 -1
  332. package/dist/tools-ui.js +19 -10
  333. package/dist/webhook-limits.d.ts +23 -0
  334. package/dist/webhook-limits.js +30 -0
  335. package/dist/webhook-readers.d.ts +56 -0
  336. package/dist/webhook-readers.js +138 -0
  337. package/dist/webhook-signing.d.ts +27 -0
  338. package/dist/webhook-signing.js +119 -0
  339. package/dist/work-index.d.ts +29 -0
  340. package/dist/work-index.js +21 -5
  341. package/dist/workspace-revision.js +3 -1
  342. package/dist/worktree.d.ts +10 -4
  343. package/dist/worktree.js +70 -29
  344. package/package.json +13 -2
  345. package/scripts/provider-canary.mjs +210 -31
@@ -16,7 +16,7 @@ import { type ContractResult } from "./contract.js";
16
16
  export declare const ROUTE_PHASES: readonly ["plan", "build", "repair", "review"];
17
17
  /** provider.ts's provider ids, restated (provider.ts sits above phase-routing.ts in the module graph) and pinned to
18
18
  * them by the test suite. */
19
- export declare const ROUTE_PROVIDERS: readonly ["claude", "codex", "openrouter", "gemini"];
19
+ export declare const ROUTE_PROVIDERS: readonly ["claude", "codex", "openrouter", "gemini", "opencode", "cursor", "cline", "qwen", "goose"];
20
20
  /** provider.ts's MODEL_ID, restated for the same reason: an exact model id is argv-safe — no leading dash, no
21
21
  * whitespace or control bytes. */
22
22
  export declare const MODEL_ID_SHAPE: RegExp;
@@ -49,9 +49,14 @@ export declare const routeOverrideSchema: z.ZodObject<{
49
49
  }>;
50
50
  provider: z.ZodEnum<{
51
51
  claude: "claude";
52
+ cline: "cline";
52
53
  codex: "codex";
54
+ cursor: "cursor";
53
55
  gemini: "gemini";
56
+ goose: "goose";
57
+ opencode: "opencode";
54
58
  openrouter: "openrouter";
59
+ qwen: "qwen";
55
60
  }>;
56
61
  model: z.ZodString;
57
62
  by: z.ZodString;
@@ -67,9 +72,14 @@ export declare const routeLegSchema: z.ZodObject<{
67
72
  }>;
68
73
  provider: z.ZodEnum<{
69
74
  claude: "claude";
75
+ cline: "cline";
70
76
  codex: "codex";
77
+ cursor: "cursor";
71
78
  gemini: "gemini";
79
+ goose: "goose";
80
+ opencode: "opencode";
72
81
  openrouter: "openrouter";
82
+ qwen: "qwen";
73
83
  }>;
74
84
  model: z.ZodString;
75
85
  tier: z.ZodEnum<{
@@ -85,9 +95,14 @@ export declare const routeLegSchema: z.ZodObject<{
85
95
  recommended: z.ZodObject<{
86
96
  provider: z.ZodEnum<{
87
97
  claude: "claude";
98
+ cline: "cline";
88
99
  codex: "codex";
100
+ cursor: "cursor";
89
101
  gemini: "gemini";
102
+ goose: "goose";
103
+ opencode: "opencode";
90
104
  openrouter: "openrouter";
105
+ qwen: "qwen";
91
106
  }>;
92
107
  model: z.ZodString;
93
108
  tier: z.ZodEnum<{
@@ -135,9 +150,14 @@ export declare const sealedRouteV1Schema: z.ZodObject<{
135
150
  }>;
136
151
  provider: z.ZodEnum<{
137
152
  claude: "claude";
153
+ cline: "cline";
138
154
  codex: "codex";
155
+ cursor: "cursor";
139
156
  gemini: "gemini";
157
+ goose: "goose";
158
+ opencode: "opencode";
140
159
  openrouter: "openrouter";
160
+ qwen: "qwen";
141
161
  }>;
142
162
  model: z.ZodString;
143
163
  tier: z.ZodEnum<{
@@ -152,9 +172,14 @@ export declare const sealedRouteV1Schema: z.ZodObject<{
152
172
  recommended: z.ZodObject<{
153
173
  provider: z.ZodEnum<{
154
174
  claude: "claude";
175
+ cline: "cline";
155
176
  codex: "codex";
177
+ cursor: "cursor";
156
178
  gemini: "gemini";
179
+ goose: "goose";
180
+ opencode: "opencode";
157
181
  openrouter: "openrouter";
182
+ qwen: "qwen";
158
183
  }>;
159
184
  model: z.ZodString;
160
185
  tier: z.ZodEnum<{
@@ -174,9 +199,14 @@ export declare const sealedRouteV1Schema: z.ZodObject<{
174
199
  }>;
175
200
  provider: z.ZodEnum<{
176
201
  claude: "claude";
202
+ cline: "cline";
177
203
  codex: "codex";
204
+ cursor: "cursor";
178
205
  gemini: "gemini";
206
+ goose: "goose";
207
+ opencode: "opencode";
179
208
  openrouter: "openrouter";
209
+ qwen: "qwen";
180
210
  }>;
181
211
  model: z.ZodString;
182
212
  by: z.ZodString;
@@ -219,9 +249,14 @@ export declare const sealedRouteV2Schema: z.ZodObject<{
219
249
  }>;
220
250
  provider: z.ZodEnum<{
221
251
  claude: "claude";
252
+ cline: "cline";
222
253
  codex: "codex";
254
+ cursor: "cursor";
223
255
  gemini: "gemini";
256
+ goose: "goose";
257
+ opencode: "opencode";
224
258
  openrouter: "openrouter";
259
+ qwen: "qwen";
225
260
  }>;
226
261
  model: z.ZodString;
227
262
  tier: z.ZodEnum<{
@@ -237,9 +272,14 @@ export declare const sealedRouteV2Schema: z.ZodObject<{
237
272
  recommended: z.ZodObject<{
238
273
  provider: z.ZodEnum<{
239
274
  claude: "claude";
275
+ cline: "cline";
240
276
  codex: "codex";
277
+ cursor: "cursor";
241
278
  gemini: "gemini";
279
+ goose: "goose";
280
+ opencode: "opencode";
242
281
  openrouter: "openrouter";
282
+ qwen: "qwen";
243
283
  }>;
244
284
  model: z.ZodString;
245
285
  tier: z.ZodEnum<{
@@ -260,9 +300,14 @@ export declare const sealedRouteV2Schema: z.ZodObject<{
260
300
  }>;
261
301
  provider: z.ZodEnum<{
262
302
  claude: "claude";
303
+ cline: "cline";
263
304
  codex: "codex";
305
+ cursor: "cursor";
264
306
  gemini: "gemini";
307
+ goose: "goose";
308
+ opencode: "opencode";
265
309
  openrouter: "openrouter";
310
+ qwen: "qwen";
266
311
  }>;
267
312
  model: z.ZodString;
268
313
  by: z.ZodString;
@@ -320,9 +365,14 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
320
365
  }>;
321
366
  provider: z.ZodEnum<{
322
367
  claude: "claude";
368
+ cline: "cline";
323
369
  codex: "codex";
370
+ cursor: "cursor";
324
371
  gemini: "gemini";
372
+ goose: "goose";
373
+ opencode: "opencode";
325
374
  openrouter: "openrouter";
375
+ qwen: "qwen";
326
376
  }>;
327
377
  model: z.ZodString;
328
378
  tier: z.ZodEnum<{
@@ -337,9 +387,14 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
337
387
  recommended: z.ZodObject<{
338
388
  provider: z.ZodEnum<{
339
389
  claude: "claude";
390
+ cline: "cline";
340
391
  codex: "codex";
392
+ cursor: "cursor";
341
393
  gemini: "gemini";
394
+ goose: "goose";
395
+ opencode: "opencode";
342
396
  openrouter: "openrouter";
397
+ qwen: "qwen";
343
398
  }>;
344
399
  model: z.ZodString;
345
400
  tier: z.ZodEnum<{
@@ -359,9 +414,14 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
359
414
  }>;
360
415
  provider: z.ZodEnum<{
361
416
  claude: "claude";
417
+ cline: "cline";
362
418
  codex: "codex";
419
+ cursor: "cursor";
363
420
  gemini: "gemini";
421
+ goose: "goose";
422
+ opencode: "opencode";
364
423
  openrouter: "openrouter";
424
+ qwen: "qwen";
365
425
  }>;
366
426
  model: z.ZodString;
367
427
  by: z.ZodString;
@@ -403,9 +463,14 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
403
463
  }>;
404
464
  provider: z.ZodEnum<{
405
465
  claude: "claude";
466
+ cline: "cline";
406
467
  codex: "codex";
468
+ cursor: "cursor";
407
469
  gemini: "gemini";
470
+ goose: "goose";
471
+ opencode: "opencode";
408
472
  openrouter: "openrouter";
473
+ qwen: "qwen";
409
474
  }>;
410
475
  model: z.ZodString;
411
476
  tier: z.ZodEnum<{
@@ -421,9 +486,14 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
421
486
  recommended: z.ZodObject<{
422
487
  provider: z.ZodEnum<{
423
488
  claude: "claude";
489
+ cline: "cline";
424
490
  codex: "codex";
491
+ cursor: "cursor";
425
492
  gemini: "gemini";
493
+ goose: "goose";
494
+ opencode: "opencode";
426
495
  openrouter: "openrouter";
496
+ qwen: "qwen";
427
497
  }>;
428
498
  model: z.ZodString;
429
499
  tier: z.ZodEnum<{
@@ -444,9 +514,14 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
444
514
  }>;
445
515
  provider: z.ZodEnum<{
446
516
  claude: "claude";
517
+ cline: "cline";
447
518
  codex: "codex";
519
+ cursor: "cursor";
448
520
  gemini: "gemini";
521
+ goose: "goose";
522
+ opencode: "opencode";
449
523
  openrouter: "openrouter";
524
+ qwen: "qwen";
450
525
  }>;
451
526
  model: z.ZodString;
452
527
  by: z.ZodString;
@@ -17,7 +17,7 @@ import { limited, parseContract, versioned } from "./contract.js";
17
17
  export const ROUTE_PHASES = ["plan", "build", "repair", "review"];
18
18
  /** provider.ts's provider ids, restated (provider.ts sits above phase-routing.ts in the module graph) and pinned to
19
19
  * them by the test suite. */
20
- export const ROUTE_PROVIDERS = ["claude", "codex", "openrouter", "gemini"];
20
+ export const ROUTE_PROVIDERS = ["claude", "codex", "openrouter", "gemini", "opencode", "cursor", "cline", "qwen", "goose"];
21
21
  /** provider.ts's MODEL_ID, restated for the same reason: an exact model id is argv-safe — no leading dash, no
22
22
  * whitespace or control bytes. */
23
23
  export const MODEL_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
@@ -208,6 +208,8 @@ declare const STORE_COLUMN_VALUES: {
208
208
  subagent_state: "subagent_state";
209
209
  subagent_tools: "subagent_tools";
210
210
  subagent_undo: "subagent_undo";
211
+ sweep_cancel_rest: "sweep_cancel_rest";
212
+ sweep_file: "sweep_file";
211
213
  task_cancel: "task_cancel";
212
214
  task_resume: "task_resume";
213
215
  tool_add: "tool_add";
@@ -19,7 +19,7 @@ export declare function previewSetup(store: Store, repo: string, inputs: SetupIn
19
19
  fingerprint: string;
20
20
  command: string;
21
21
  seconds: number;
22
- provider: "claude" | "codex" | "gemini" | "openrouter";
22
+ provider: "claude" | "cline" | "codex" | "cursor" | "gemini" | "goose" | "opencode" | "openrouter" | "qwen";
23
23
  model: string;
24
24
  };
25
25
  export declare function approveSetup(store: Store, repo: string, inputs: SetupInputs, fingerprint: string, by: string, token: string, now: Date): {
@@ -1,5 +1,5 @@
1
1
  /** Shared setup views over the existing project configuration and approval doors. */
2
- import { html, joinHtml, postForm } from "./html.js";
2
+ import { html, joinHtml, postForm, reauth } from "./html.js";
3
3
  import { ASSISTANTS } from "./setup-guide.js";
4
4
  import { openRouterPicker } from "./openrouter-models.js";
5
5
  export function connectionWords(connection) {
@@ -24,5 +24,5 @@ export function controlSetupHtml(data) {
24
24
  /** `fields` may still carry the session's csrf (a caller's habit): postForm writes the one CSRF field. */
25
25
  export function setupPreviewHtml(inputs, fields) {
26
26
  const { csrf: _csrf, ...rest } = fields;
27
- return html `<h1>Approve project setup</h1><div class="card"><p>${fields.repo ?? ""}</p><p><strong>Builder:</strong> ${inputs.provider} · ${inputs.model}</p><p><strong>Preparation:</strong></p><pre class="recap">${inputs.command || "No preparation command"}</pre><p>Preparation timeout: ${inputs.seconds} seconds.</p><p>The builder default applies to new tasks. Preparation applies to subsequent runs, including already approved tasks. Existing signed task settings and verification rules remain in force.</p>${postForm("/control/setup-approve", html `<label>Your password<input type="password" name="token" autocomplete="current-password" required></label><button>Approve project setup</button>`, { hidden: { ...rest, ...inputs } })}</div>`;
27
+ return html `<h1>Approve project setup</h1><div class="card"><p>${fields.repo ?? ""}</p><p><strong>Builder:</strong> ${inputs.provider} · ${inputs.model}</p><p><strong>Preparation:</strong></p><pre class="recap">${inputs.command || "No preparation command"}</pre><p>Preparation timeout: ${inputs.seconds} seconds.</p><p>The builder default applies to new tasks. Preparation applies to subsequent runs, including already approved tasks. Existing signed task settings and verification rules remain in force.</p>${postForm("/control/setup-approve", html `${reauth(html `<label>Your password<input type="password" name="token" autocomplete="current-password" required></label>`)}<button>Approve project setup</button>`, { hidden: { ...rest, ...inputs } })}</div>`;
28
28
  }
@@ -180,13 +180,24 @@ export type LeadToolCall = {
180
180
  args: Record<string, unknown>;
181
181
  };
182
182
  /** A provider answer with tool calls allowed: text, calls, usage. */
183
+ /** `tokensIn` is ALL input tokens of the request; of them, `cacheWriteTokens` were written to the provider's prompt cache
184
+ * and `cacheReadTokens` read from it (absent: the provider reported none — never assumed zero for a cached request). */
183
185
  export type LeadProviderAnswer = {
184
186
  text: string;
185
187
  calls: LeadToolCall[];
186
188
  tokensIn: number;
187
189
  tokensOut: number;
188
190
  reportedCostMicrousd: number | null;
191
+ cacheWriteTokens?: number;
192
+ cacheReadTokens?: number;
189
193
  };
194
+ /** Anthropic prices a cache write at 1.25x and a cache read at 0.1x the input price (5-minute cache). */
195
+ export declare const CACHE_WRITE_RATIO = 1.25;
196
+ export declare const CACHE_READ_RATIO = 0.1;
197
+ /** The price a worst case reserves at while prompt caching is on: every input token as a cache write (the dearest). */
198
+ export declare function cacheWorstPrice(price: ModelPrice): ModelPrice;
199
+ /** A step's settlement with its cache categories: plain input, cache writes and cache reads each at their own rate. */
200
+ export declare function settleWithCache(price: ModelPrice, answer: Pick<LeadProviderAnswer, "tokensIn" | "tokensOut" | "cacheWriteTokens" | "cacheReadTokens">): number;
190
201
  /** One message of the lead's own history, provider-neutral. */
191
202
  export type LeadHistoryMessage = {
192
203
  role: "operator";
@@ -224,6 +235,11 @@ export declare function composeLeadRequest(args: {
224
235
  dataDocument: string;
225
236
  history: readonly LeadHistoryMessage[];
226
237
  tools: readonly LeadToolSchema[];
238
+ /** Anthropic prompt caching: breakpoints after the first `coreTools` tools (stable across replies), on the system
239
+ * block, and on the DATA message (stable across the steps of one reply). Absent: no cache annotations. */
240
+ cache?: {
241
+ coreTools: number;
242
+ };
227
243
  }): {
228
244
  url: string;
229
245
  headers: Record<string, string>;
package/dist/converse.js CHANGED
@@ -393,6 +393,20 @@ export function leadWorstCaseForPrice(price, baseBytes, caps = {}) {
393
393
  export function embeddedBytes(text) {
394
394
  return Buffer.byteLength(JSON.stringify(text), "utf8");
395
395
  }
396
+ /** Anthropic prices a cache write at 1.25x and a cache read at 0.1x the input price (5-minute cache). */
397
+ export const CACHE_WRITE_RATIO = 1.25;
398
+ export const CACHE_READ_RATIO = 0.1;
399
+ /** The price a worst case reserves at while prompt caching is on: every input token as a cache write (the dearest). */
400
+ export function cacheWorstPrice(price) {
401
+ return { inMicrousd: Math.ceil(price.inMicrousd * CACHE_WRITE_RATIO), outMicrousd: price.outMicrousd };
402
+ }
403
+ /** A step's settlement with its cache categories: plain input, cache writes and cache reads each at their own rate. */
404
+ export function settleWithCache(price, answer) {
405
+ const write = answer.cacheWriteTokens ?? 0, read = answer.cacheReadTokens ?? 0;
406
+ const plain = answer.tokensIn - write - read;
407
+ return plain * price.inMicrousd + write * Math.ceil(price.inMicrousd * CACHE_WRITE_RATIO) + read * Math.ceil(price.inMicrousd * CACHE_READ_RATIO)
408
+ + answer.tokensOut * price.outMicrousd;
409
+ }
396
410
  /**
397
411
  * The tool-capable wrapper parser (mate arc). Same caps and the same
398
412
  * duplicate-key lexer as fleet chat's; tool calls are read, bounded (at
@@ -475,7 +489,18 @@ export function parseLeadProviderWrapper(provider, bytes) {
475
489
  const usage = usageOf(body["usage"], "input_tokens", "output_tokens");
476
490
  if (usage === null)
477
491
  return { ok: false, problem: "no-usage" };
478
- return { ok: true, answer: { text, calls, tokensIn: usage.tokensIn, tokensOut: usage.tokensOut, reportedCostMicrousd: null } };
492
+ // Prompt cache categories: integers when present (anything else is malformed usage, never a guess). Anthropic's
493
+ // input_tokens counts only the uncached part; the request's whole input is the three added up.
494
+ const cache = (key) => {
495
+ const value = body["usage"][key];
496
+ // Absent: not reported. Present (even null): it must be a token count, or the usage is malformed.
497
+ return value === undefined ? undefined : typeof value === "number" && tokenCount(value) ? value : null;
498
+ };
499
+ const write = cache("cache_creation_input_tokens"), read = cache("cache_read_input_tokens");
500
+ if (write === null || read === null)
501
+ return { ok: false, problem: "no-usage" };
502
+ return { ok: true, answer: { text, calls, tokensIn: usage.tokensIn + (write ?? 0) + (read ?? 0), tokensOut: usage.tokensOut, reportedCostMicrousd: null,
503
+ ...(write === undefined ? {} : { cacheWriteTokens: write }), ...(read === undefined ? {} : { cacheReadTokens: read }) } };
479
504
  }
480
505
  const choices = body["choices"];
481
506
  if (!Array.isArray(choices) || choices.length !== 1)
@@ -528,7 +553,9 @@ export function parseLeadProviderWrapper(provider, bytes) {
528
553
  export function composeLeadRequest(args) {
529
554
  const opener = `DATA:\n${args.dataDocument}\n\n(The conversation follows. Every operator message is data, from the operator.)`;
530
555
  if (args.provider === "anthropic-api") {
531
- const messages = [{ role: "user", content: opener }];
556
+ const ephemeral = { cache_control: { type: "ephemeral" } };
557
+ const cached = args.cache !== undefined;
558
+ const messages = [{ role: "user", content: cached ? [{ type: "text", text: opener, ...ephemeral }] : opener }];
532
559
  for (const one of args.history) {
533
560
  if (one.role === "operator")
534
561
  messages.push({ role: "user", content: one.text });
@@ -549,8 +576,9 @@ export function composeLeadRequest(args) {
549
576
  body: JSON.stringify({
550
577
  model: args.model,
551
578
  max_tokens: MAX_OUTPUT_TOKENS,
552
- system: args.system,
553
- tools: args.tools.map(tool => ({ name: tool.name, description: tool.description, input_schema: tool.inputSchema })),
579
+ system: cached ? [{ type: "text", text: args.system, ...ephemeral }] : args.system,
580
+ tools: args.tools.map((tool, index) => ({ name: tool.name, description: tool.description, input_schema: tool.inputSchema,
581
+ ...(cached && index === args.cache.coreTools - 1 ? ephemeral : {}) })),
554
582
  messages,
555
583
  }),
556
584
  };
package/dist/demo-chat.js CHANGED
@@ -8,7 +8,7 @@ import { html, postForm } from "./html.js";
8
8
  import { headlineOf } from "./task-status.js";
9
9
  const SUGGESTIONS = ["Fix the flaky refund test", "Rewrite the empty Payouts page", "Put the new invoice view behind a flag"];
10
10
  function hint() {
11
- return html `<section class="demo-hint" aria-label="Get started"><p class="demo-hint-title">Ask for something, like “fix the flaky refund test”</p>${postForm("/chat/demo/ask", SUGGESTIONS.map(one => html `<button type="submit" name="message" value="${one}">${one}</button>`), { attrs: { class: "demo-suggestions" } })}</section>`;
11
+ return html `<section class="demo-hint" aria-label="Get started"><h2 class="demo-hint-title">What should the crew do?</h2><p class="demo-hint-sub">Ask for something, like “fix the flaky refund test”, or start from one of these.</p>${postForm("/chat/demo/ask", SUGGESTIONS.map(one => html `<button type="submit" name="message" value="${one}">${one}</button>`), { attrs: { class: "demo-suggestions" } })}</section>`;
12
12
  }
13
13
  function planCard(exchange) {
14
14
  const plan = exchange.plan;
@@ -55,7 +55,7 @@ export function demoThreadHtml(exchanges, resultOf) {
55
55
  return html `${exchanges.map(exchange => {
56
56
  const parts = [
57
57
  html `<div class="demo-said demo-you"><p>${exchange.asked}</p></div>`,
58
- html `<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>${exchange.reply}</p></div>`,
58
+ html `<div class="demo-said demo-lead"><p class="demo-who"><span class="demo-avatar" aria-hidden="true">L</span>Lead</p><p>${exchange.reply}</p></div>`,
59
59
  planCard(exchange),
60
60
  ];
61
61
  if (exchange.state === "working")
@@ -63,14 +63,14 @@ export function demoThreadHtml(exchanges, resultOf) {
63
63
  if (exchange.state === "ready" || exchange.state === "complete" || exchange.state === "sent-back")
64
64
  parts.push(resultCard(exchange, resultOf(exchange)));
65
65
  if (exchange.state === "complete") {
66
- parts.push(html `<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>Done. That's the whole loop: ask, approve, Ready, Complete. Ask for something else whenever you like.</p></div>`, html `<section class="card demo-handoff" data-demo-handoff aria-label="Your own project"><h2>Now try it on your own project</h2><p>In your repository's folder, run:</p><pre class="demo-command"><code>${START_COMMAND}</code></pre><p class="meta">It opens in your browser, already signed in.</p></section>`);
66
+ parts.push(html `<div class="demo-said demo-lead"><p class="demo-who"><span class="demo-avatar" aria-hidden="true">L</span>Lead</p><p>Done. That's the whole loop: ask, approve, Ready, Complete. Ask for something else whenever you like.</p></div>`, html `<section class="card demo-handoff" data-demo-handoff aria-label="Your own project"><h2>Now try it on your own project</h2><p>In your repository's folder, run:</p><pre class="demo-command"><code>${START_COMMAND}</code></pre><p class="meta">It opens in your browser, already signed in.</p></section>`);
67
67
  }
68
68
  return html `<article class="demo-turn" id="demo-${exchange.id}">${parts}</article>`;
69
69
  })}`;
70
70
  }
71
71
  export function demoChatHtml(input) {
72
72
  const working = input.exchanges.some(one => one.state === "working");
73
- return html `<div class="demo-chat">${input.problem === null ? "" : html `<p class="problem" role="alert">${input.problem}</p>`}<div id="demo-thread" class="demo-thread" aria-live="polite" data-version="${input.version}" data-working="${working ? "1" : "0"}">${demoThreadHtml(input.exchanges, input.resultOf)}</div>${postForm("/chat/demo/ask", html `<label for="demo-message" class="so-sr-only">Message the lead</label><textarea id="demo-message" name="message" rows="2" maxlength="500" placeholder="Ask the lead for a change" required></textarea><button type="submit" class="demo-primary">Send</button>`, { attrs: { class: "demo-composer" } })}</div>`;
73
+ return html `<div class="demo-chat${input.exchanges.length === 0 ? " demo-chat--empty" : ""}">${input.problem === null ? "" : html `<p class="problem" role="alert">${input.problem}</p>`}<div id="demo-thread" class="demo-thread" aria-live="polite" data-version="${input.version}" data-working="${working ? "1" : "0"}">${demoThreadHtml(input.exchanges, input.resultOf)}</div>${postForm("/chat/demo/ask", html `<label for="demo-message" class="so-sr-only">Message the lead</label><div class="demo-composer-card"><textarea id="demo-message" name="message" rows="1" maxlength="500" placeholder="Ask the lead for a change" required aria-describedby="demo-composer-hint"></textarea><div class="demo-composer-bar"><span id="demo-composer-hint">Enter to send · Shift + Enter for a new line</span><button type="submit" class="demo-primary demo-send" aria-label="Send"><svg aria-hidden="true" viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13V3M3.5 7.5 8 3l4.5 4.5"/></svg></button></div></div>`, { attrs: { class: "demo-composer" } })}</div>`;
74
74
  }
75
75
  /**
76
76
  * Keeps a running build live: polls while one is working and swaps the
package/dist/demo.js CHANGED
@@ -1044,14 +1044,14 @@ function seedDemoFlows(store, folder, now) {
1044
1044
  ["Parcel arrived crushed", "The box was crushed and two glasses inside are broken.", "Delivery problem", 0.91, "delivery", "Soon: the customer is waiting on it", true, 120],
1045
1045
  ["Where is my order and why was I billed?", "It hasn't arrived and there's a charge I don't recognise.", "Delivery problem", 0.58, "by-hand", "Soon: the customer is waiting on it", false, 45],
1046
1046
  ]) {
1047
- const card = store.addFlowCard({ flow: desk, title, description, stage: "sort", by: "Support form" }, at(minutes));
1047
+ const card = store.addFlowCard({ flow: desk, title, description, stage: "sort", by: "Support form", byKind: "automation" }, at(minutes));
1048
1048
  const confident = sure >= 0.8;
1049
1049
  step(card, "sort", "sort", at(minutes - 1), confident ? `${answer}, ${Math.round(sure * 100)}% sure.` : `Not sure: ${Math.round(sure * 100)}% ${answer}.`, `${confident ? `${answer}, ${Math.round(sure * 100)}% sure.` : `Not sure: ${Math.round(sure * 100)}% ${answer}.`} How urgent is it? ${urgency.split(":")[0]}.`, { decisionJson: sorted(answer, sure, to, urgency, refund) });
1050
1050
  move(card, to, at(minutes - 1));
1051
1051
  }
1052
1052
  const replies = store.createFlow({ repo, name: "Customer replies", definitionJson: template("email-replies"), by: "demo" }, at(200));
1053
1053
  const drafted = (title, description, draft, minutes) => {
1054
- const card = store.addFlowCard({ flow: replies, title, description, stage: "write", by: "Contact form" }, at(minutes));
1054
+ const card = store.addFlowCard({ flow: replies, title, description, stage: "write", by: "Contact form", byKind: "automation" }, at(minutes));
1055
1055
  step(card, "write", "draft", at(minutes - 1), `Drafted ${draft.split(/\s+/).length} words.`, draft, { log: `Claude (sonnet) · 4.2 s\n\n${draft}` });
1056
1056
  move(card, "check", at(minutes - 1));
1057
1057
  return card;
@@ -1,4 +1,5 @@
1
1
  import { activeCodingUpdateWork, installCodingUpdateGate, removeCodingUpdateGate } from "./coding-update.js";
2
+ import { activeTerminalUpdateWork, installTerminalUpdateGate, removeTerminalUpdateGate } from "./terminal-catalog.js";
2
3
  export const UPDATE_PAUSED = "Toolroll is updating. Current work can finish; new work will resume after the update. Open Update status in the desktop app.";
3
4
  const prefix = "so_desktop_update_";
4
5
  const rows = (db) => db.prepare("SELECT name, sql FROM sqlite_master WHERE type='trigger' AND name GLOB 'so_desktop_update_*' ORDER BY name").all();
@@ -36,6 +37,7 @@ export function installUpdateGate(db, id) {
36
37
  throw error;
37
38
  }
38
39
  installCodingUpdateGate(db, id);
40
+ installTerminalUpdateGate(db, id);
39
41
  }
40
42
  export function removeUpdateGate(db, id) {
41
43
  const wanted = [...statements(id), ...statements(id, true)];
@@ -45,6 +47,7 @@ export function removeUpdateGate(db, id) {
45
47
  if (existing.some(row => !owns(wanted, row.sql)))
46
48
  throw Error("This update does not own the admission pause. Nothing was cleared.");
47
49
  removeCodingUpdateGate(db, id);
50
+ removeTerminalUpdateGate(db, id);
48
51
  for (const row of existing)
49
52
  db.exec(`DROP TRIGGER "${String(row.name)}"`);
50
53
  db.exec("COMMIT");
@@ -67,6 +70,8 @@ export function activeUpdateWork(db) {
67
70
  conversations: count("SELECT count(*) n FROM mate_turn WHERE state IN ('queued','running')") + count("SELECT count(*) n FROM chat_turn WHERE state IN ('queued','running')"),
68
71
  stopping: count("SELECT count(*) n FROM run_stop WHERE settled_at IS NULL"),
69
72
  ...activeCodingUpdateWork(db),
73
+ // Live terminals in a console that is still running (terminal-catalog.ts).
74
+ ...activeTerminalUpdateWork(db),
70
75
  };
71
76
  }
72
77
  /** Last admission race closes in the same SQLite write transaction as the
@@ -77,6 +82,7 @@ export function freezeUpdateGate(db, id) {
77
82
  if (!updateGateOwned(db, id))
78
83
  throw Error("The update no longer owns admission.");
79
84
  installCodingUpdateGate(db, id);
85
+ installTerminalUpdateGate(db, id);
80
86
  const idle = Object.values(activeUpdateWork(db)).every(n => n === 0);
81
87
  if (idle) {
82
88
  for (const row of rows(db))
@@ -8,7 +8,7 @@ import { roomCommand } from "./chat-rooms.js";
8
8
  import { ChatState, ChatDeliveryError, chatHash, partContent, } from "./chat-delivery-state.js";
9
9
  import { readChatPart } from "./contracts/chat-content.js";
10
10
  import { readDiscordButtonToken, readDiscordComponentInteraction } from "./contracts/discord-callback.js";
11
- import { processChatEvent, planChatNotifications, channelAccess, chatObject as object, planRoomMessages, } from "./chat-delivery.js";
11
+ import { processChatEvent, planChatNotifications, channelAccess, chatObject as object, planRoomMessages, heldForWatch, } from "./chat-delivery.js";
12
12
  import { chatResultHref } from "./chat-controls.js";
13
13
  import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
14
14
  import { discordPlain, renderReply } from "./reply-shape.js";
@@ -379,6 +379,9 @@ export async function deliverDiscordPart(options) {
379
379
  fail_if_not_exists: false,
380
380
  };
381
381
  }
382
+ // The watch, at the last moment before the network (cockpit §3).
383
+ if (heldForWatch(state, store, row.id, event, binding.approver, content, now))
384
+ return true;
382
385
  const answer = await options.api(target ? "PATCH" : "POST", `/channels/${destination}/messages${target ? `/${target}` : ""}`, args, file);
383
386
  if (!discordId(answer.id) ||
384
387
  answer.channel_id !== destination ||
package/dist/dispatch.js CHANGED
@@ -15,6 +15,8 @@ import { REVIEW_TOKENS, resultStatusOf, reviewFactsOf } from "./workspace-ui.js"
15
15
  import { replacedWords } from "./task-status.js";
16
16
  import { failedAttemptSentence, latestFinishedAttempt } from "./needs-you.js";
17
17
  import { buildReviewOf } from "./review-switch.js";
18
+ import { grantStandsSql } from "./flow-unattended.js";
19
+ import { accountReachesSql } from "./project-access.js";
18
20
  import { BUILT_IN, parseCapabilityKey } from "./store.js";
19
21
  export const DEFAULT_MAX_OPEN_DECISIONS = 5;
20
22
  /**
@@ -75,13 +77,14 @@ export function scopeApprovedForDispatch(store, taskRef, now) {
75
77
  .prepare(`SELECT 1 AS hit FROM task_scope
76
78
  JOIN task_ref ON task_ref.id = ? AND task_scope.task_id = task_ref.external_id
77
79
  WHERE task_scope.approved_digest = task_scope.digest AND task_scope.approved_at IS NOT NULL
78
- AND (COALESCE(task_scope.approval_basis, 'password') <> 'mode'
79
- OR EXISTS (SELECT 1 FROM operating_mode om
80
+ AND (COALESCE(task_scope.approval_basis, 'password') NOT IN ('mode', 'grant')
81
+ OR (task_scope.approval_basis = 'mode' AND EXISTS (SELECT 1 FROM operating_mode om
80
82
  JOIN approver signer ON signer.name = om.signed_by
81
83
  WHERE om.repo = task_ref.repo AND om.revoked_at IS NULL
82
84
  AND om.absolute_expiry > ? AND om.digest = task_scope.mode_digest
83
- AND signer.revoked_at IS NULL AND signer.role = 'approver'))`)
84
- .get(taskRef, now.toISOString());
85
+ AND signer.revoked_at IS NULL AND signer.role = 'approver' AND ${accountReachesSql("signer", "task_ref.repo")}))
86
+ OR (task_scope.approval_basis = 'grant' AND ${grantStandsSql("task_scope.mode_digest", "?", "task_ref.repo")}))`)
87
+ .get(taskRef, now.toISOString(), now.toISOString());
85
88
  return row !== undefined;
86
89
  }
87
90
  /** The phase the ordinary dispatch loop would choose before it resolves an
@@ -0,0 +1,53 @@
1
+ import { type SupervisorRunner } from "./daemon.js";
2
+ /** Something that holds a credential a build could read, by name only, and what the owner does about it. */
3
+ export type DoctorFinding = {
4
+ where: string;
5
+ names: string[];
6
+ steps: string[];
7
+ };
8
+ /** Something the doctor could not read: never taken for clean. */
9
+ export type DoctorUnreadable = {
10
+ where: string;
11
+ problem: string;
12
+ steps: string[];
13
+ };
14
+ export type DoctorReport = {
15
+ ok: boolean;
16
+ checked: string[];
17
+ findings: DoctorFinding[];
18
+ unreadable: DoctorUnreadable[];
19
+ notes: string[];
20
+ };
21
+ export type DoctorDeps = {
22
+ platform?: NodeJS.Platform;
23
+ home?: string;
24
+ uid?: number;
25
+ /** Runs the read-only supervisor and process queries (launchctl, systemctl, ps). */
26
+ run?: SupervisorRunner;
27
+ /** Where Linux keeps its process files (a test's stand-in). */
28
+ procRoot?: string;
29
+ /** The doctor's own environment: the terminal a `toolroll up` would start from. */
30
+ env?: NodeJS.ProcessEnv;
31
+ };
32
+ /** The names among `pairs` that hold a credential: a provider key, a credential-looking name, or a key-shaped value.
33
+ * Values are only tested here and never leave. */
34
+ export declare function credentialNames(pairs: Iterable<readonly [string, string]>): string[];
35
+ /** A running process's environment names (never values) that hold a credential: macOS through `ps -E`, which reads
36
+ * KERN_PROCARGS2; Linux from /proc/<pid>/environ. */
37
+ export declare function processCredentialNames(pid: number, deps: {
38
+ platform: NodeJS.Platform;
39
+ run: SupervisorRunner;
40
+ procRoot?: string;
41
+ }): Promise<{
42
+ ok: true;
43
+ names: string[];
44
+ } | {
45
+ ok: false;
46
+ problem: string;
47
+ }>;
48
+ /** Audit what Toolroll's service holds. Reads only; the report names variables, never their values. */
49
+ export declare function auditServiceEnvironment(deps?: DoctorDeps): Promise<DoctorReport>;
50
+ /** The report as the owner reads it: one state, then each finding with its steps; detail after. */
51
+ export declare function doctorLines(report: DoctorReport): string[];
52
+ export declare const DOCTOR_HELP = "toolroll doctor [--json]\nChecks that Toolroll's service holds no API key, token or password a build could read: its service definitions, what `daemon install` writes, the environment its manager hands it, and its running process. Reads only: it names what it finds and the steps to fix it, and never changes a file or restarts anything.";
53
+ export declare function runDoctorCommand(argv: readonly string[], write: (line: string) => void, deps?: DoctorDeps): Promise<number>;