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
@@ -68,6 +68,16 @@ export declare const CHAT_ACTIONS: {
68
68
  readonly protected: false;
69
69
  readonly password: false;
70
70
  };
71
+ readonly sweep_file: {
72
+ readonly label: "Start sweep";
73
+ readonly protected: true;
74
+ readonly password: true;
75
+ };
76
+ readonly sweep_cancel_rest: {
77
+ readonly label: "Cancel the rest";
78
+ readonly protected: false;
79
+ readonly password: false;
80
+ };
71
81
  readonly flow_edit: {
72
82
  readonly label: "Save flow";
73
83
  readonly protected: false;
@@ -256,7 +266,7 @@ export declare function sharedActionReviewPath(id: number): string;
256
266
  export declare function mintSharedActionReview(store: Store, who: VerifiedApprover, id: number, root: string, now: Date): {
257
267
  payload: {
258
268
  version: 1;
259
- operation: "decision_record" | "decision_retire" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
269
+ operation: "decision_record" | "decision_retire" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
260
270
  request: {
261
271
  [x: string]: unknown;
262
272
  };
@@ -286,6 +296,8 @@ export declare function executeSharedAction(store: Store, who: VerifiedApprover,
286
296
  said: string;
287
297
  taskId: string | null;
288
298
  href?: string;
299
+ awaiting?: true;
300
+ already?: true;
289
301
  } | {
290
302
  ok: false;
291
303
  reason: "needs-confirm" | "stale" | "refused";
@@ -1,5 +1,9 @@
1
1
  /** Shared, exact-state actions. Models may prepare; only the existing human
2
2
  * confirmation door executes. Protected actions use a one-use review receipt. */
3
+ import { fileTaskProposal } from "./proposal.js";
4
+ import { heuristicSizing } from "./task-sizing.js";
5
+ import { cancelSweepRest, sweepOpen, sweepTable, VERDICT_RULE } from "./lead-sweep.js";
6
+ import { isAcpProfile } from "./acp-agents.js";
3
7
  import { withActor } from "./actor.js";
4
8
  import { TEXT_LIMITS } from "./text-limits.js";
5
9
  import { publicChatText } from "./chat-display.js";
@@ -9,7 +13,7 @@ import { leadTurnKeepsProposals, verifiedAuthor } from "./store.js";
9
13
  import { isVerifiedApprover, reproveApprover, } from "./principal.js";
10
14
  import { changeSkills, importSkill, skillsView, skillsVersion, testSkill, validateSkill, } from "./project-skills.js";
11
15
  import { changeKnowledge, knowledgeVersion, knowledgeView, } from "./project-knowledge.js";
12
- import { approve, authenticateApprover, describeScope, scopeAuthorityOf, } from "./scope.js";
16
+ import { passwordlessSessionFor, approve, authenticateApprover, describeScope, scopeAuthorityOf, freshnessFor, spendingOnlyOnSuccess, ssoNowAvailable, withFreshness, } from "./scope.js";
13
17
  import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
14
18
  import { readAuthModeStrict } from "./keys.js";
15
19
  import { parseProof } from "./proof.js";
@@ -86,6 +90,11 @@ export const CHAT_ACTIONS = {
86
90
  flow_create: { label: "Create flow", protected: false, password: false },
87
91
  // A starter flow: its zones and its trigger, switched on with one yes (flow-starters.ts).
88
92
  flow_starter: { label: "Switch on", protected: false, password: false },
93
+ // Lead fan-out (docs/plans/lead-fan-out.md): files N tasks under one brief and approves each one's scope — the
94
+ // person's ceremony (protected, with the password), N ordinary approvals of exactly the scopes shown.
95
+ sweep_file: { label: "Start sweep", protected: true, password: true },
96
+ // Stops every item of a sweep not yet ready; finished results stay. An ordinary act, like cancelling a task.
97
+ sweep_cancel_rest: { label: "Cancel the rest", protected: false, password: false },
89
98
  flow_edit: { label: "Save flow", protected: false, password: false },
90
99
  flow_card_add: { label: "Add card", protected: false, password: false },
91
100
  flow_card_move: { label: "Move card", protected: false, password: false },
@@ -175,10 +184,12 @@ function runtimeTerms(profile) {
175
184
  ? profile.approvalArgv === "yolo"
176
185
  ? "Full access: every tool is approved automatically (--approval-mode yolo)."
177
186
  : "Edits are approved automatically; other tools are refused."
178
- : profile.sandboxMode === "danger-full-access"
179
- ? "Full access: approvals and sandbox are bypassed (--dangerously-bypass-approvals-and-sandbox)."
180
- : "Commands use the workspace-write sandbox.";
181
- return `${profile.provider} · ${profile.model}\n${permission}\n${profile.timeoutSeconds} seconds ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}${profile.provider === "claude" ? `; at most ${profile.maxTurns} turns` : ""}. Repairs: ${profile.repairModel}, ${profile.repairTimeoutSeconds} seconds${profile.provider === "claude" ? `, at most ${profile.repairMaxTurns} turns` : ""}.`;
187
+ : isAcpProfile(profile)
188
+ ? "Full access inside the OS fence: the agent runs any tool it chooses, and nothing outside the fence is reachable."
189
+ : profile.sandboxMode === "danger-full-access"
190
+ ? "Full access: approvals and sandbox are bypassed (--dangerously-bypass-approvals-and-sandbox)."
191
+ : "Commands use the workspace-write sandbox.";
192
+ return `${profile.provider} · ${profile.model}\n${permission}\n${isAcpProfile(profile) ? `${profile.timeoutSeconds} seconds per attempt, or ${profile.idleTimeoutSeconds} seconds silent` : `${profile.timeoutSeconds} seconds ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}`}${profile.provider === "claude" ? `; at most ${profile.maxTurns} turns` : ""}. Repairs: ${profile.repairModel}, ${profile.repairTimeoutSeconds} seconds${profile.provider === "claude" ? `, at most ${profile.repairMaxTurns} turns` : ""}.`;
182
193
  }
183
194
  function resultOf(store, task, run, repo) {
184
195
  const ref = store.lookupRef(task);
@@ -263,7 +274,7 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
263
274
  const stamp = hash({ operation, request, repo: "", state, terms, actor: who.name, generation: who.generation, ceiling: who.ceilingDigest });
264
275
  return { version: SHARED_ACTION_VERSION, operation, request, repo: "", title, terms, stamp, state };
265
276
  }
266
- const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("subagent_") || operation.startsWith("kit_")
277
+ const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("subagent_") || operation.startsWith("kit_") || operation.startsWith("sweep_")
267
278
  ? null
268
279
  : text(input, "task", 64);
269
280
  const flowTarget = operation.startsWith("flow_") && operation !== "flow_create" && operation !== "flow_script_save" && operation !== "flow_starter" ? flowTargetOf(store, input) : null;
@@ -275,7 +286,8 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
275
286
  const questionFlow = questionTarget === null ? null : store.getFlow(store.getFlowCard(questionTarget.card)?.flow ?? -1);
276
287
  if (operation === "subagent_answer" && (questionTarget === null || questionFlow === null))
277
288
  throw Error("That question isn't open any more.");
278
- const repo = flowTarget !== null ? flowTarget.flow.repo : subagentTarget !== null ? subagentTarget.repo : questionFlow !== null ? questionFlow.repo : task === null ? text(input, "repo", 4096) : store.lookupRef(task)?.repo;
289
+ const repo = operation === "sweep_cancel_rest" ? store.getSweep(integer(input, "sweep"))?.repo
290
+ : flowTarget !== null ? flowTarget.flow.repo : subagentTarget !== null ? subagentTarget.repo : questionFlow !== null ? questionFlow.repo : task === null ? text(input, "repo", 4096) : store.lookupRef(task)?.repo;
279
291
  if (!repo)
280
292
  throw Error("Choose an available project.");
281
293
  requireActor(store, who, repo);
@@ -554,6 +566,50 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
554
566
  terms.push(question.question, `Your answer: ${[choice?.label, said].filter(Boolean).join(" — ")}`);
555
567
  }
556
568
  }
569
+ else if (operation === "sweep_cancel_rest") {
570
+ // Exactly the items open now: if any settles or starts in between, the card is stale and a fresh one is needed.
571
+ const table = sweepTable(store, integer(input, "sweep"), now, who.repos);
572
+ if (table === null || table.repo !== repo)
573
+ throw Error("That sweep isn't one of yours.");
574
+ // Exactly the items as they stand now — each one's active task (a revision, after one), state, running attempt and
575
+ // last change: any change makes this card stale, so nothing is acted on that wasn't shown.
576
+ const open = sweepOpen(store, table, now);
577
+ if (open.length === 0)
578
+ throw Error("Nothing in that sweep is still unfinished.");
579
+ state = { open };
580
+ title = `Cancel the rest of ${table.title}`;
581
+ const running = open.filter(one => one.run !== null);
582
+ terms.push(`Cancels ${open.length - running.length} unfinished item${open.length - running.length === 1 ? "" : "s"}${running.length === 0 ? "" : ` and stops ${running.length} running build${running.length === 1 ? "" : "s"}`}: ${open.map(one => `${one.label} (${one.task})`).join(", ")}.`, "Finished results stay.");
583
+ }
584
+ else if (operation === "sweep_file") {
585
+ // Everything the person approves is on this screen: the shared rules, every item, the one success test, the
586
+ // limit. Confirming files exactly these and approves exactly these scopes; anything changed is a new card.
587
+ const sweepTitle = text(input, "title", 200).trim(), brief = text(input, "brief", 8000).trim();
588
+ const items = Array.isArray(input["items"]) ? input["items"] : [];
589
+ if (items.length < 2 || items.length > 20)
590
+ throw Error("A sweep is 2 to 20 items. For one, propose a task.");
591
+ const cleaned = items.map((one, index) => ({ title: text(one, "title", 200).trim(), item: text(one, "item", 4000).trim(), index }));
592
+ const maxParallel = integer(input, "maxParallel");
593
+ if (maxParallel < 1 || maxParallel > 8)
594
+ throw Error("Run 1 to 8 at once.");
595
+ request["title"] = sweepTitle;
596
+ request["brief"] = brief;
597
+ request["items"] = cleaned.map(({ title, item }) => ({ title, item }));
598
+ const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
599
+ title = `Start a sweep in ${project}: ${sweepTitle}`;
600
+ // Every word each child's scope will hold is shown here (the security review: nothing approved off-screen): its
601
+ // goal is the shared rules, then its item, then the verdict line; its success test is the shared acceptance.
602
+ const acceptance = Array.isArray(input["acceptance"]) ? input["acceptance"] : [];
603
+ if (acceptance.length === 0)
604
+ throw Error("A sweep needs a success test every item shares.");
605
+ // Every child's scope exactly as filing would make it — its words AND its agent's permissions, limits and route —
606
+ // found by filing them in a savepoint that is always rolled back (prepare writes nothing). They are on this card and
607
+ // in its state: if a project default changes before confirm, the card is stale and nothing is filed or approved.
608
+ const children = sweepChildScopes(store, who, repo, brief, cleaned.map(({ title, item }) => ({ title, item })), input["acceptance"], now);
609
+ state = { children: children.map(one => one.fingerprint) };
610
+ const tests = acceptance.map((one, index) => `Success test ${index + 1}${typeof one["id"] === "string" ? ` (${one["id"]})` : ""}: ${String(one["statement"] ?? "")}${Array.isArray(one["evidence"]) ? ` — shown by ${one["evidence"].map(String).join(", ")}` : ""}`);
611
+ terms.push(`${cleaned.length} tasks, each in its own worktree on its own branch, at most ${maxParallel} running at once, built directly (no separate planning step).`, `Shared rules for every item: ${brief}`, ...cleaned.map(one => `Item ${one.index + 1} — ${one.title}: ${one.item}`), `Each item's goal is the shared rules, then its own item, then: ${VERDICT_RULE}`, ...tests.map(line => `Every item — ${line}`), ...(children.every(one => one.runtime === children[0].runtime) ? [`Every item — ${children[0].runtime}`] : children.map((one, index) => `Item ${index + 1} — ${one.runtime}`)), "Confirming files all of them and approves each one's scope exactly as shown. An item that needs a second person, or that the project's rules keep from its requester, is filed and waits for its own approval.", "Each ends with a verdict (fixed, not reproducible, blocked or done) and the sweep's table shows them as they land.");
612
+ }
557
613
  else if (operation.startsWith("flow_")) {
558
614
  const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
559
615
  if (operation === "flow_create") {
@@ -985,10 +1041,28 @@ function savedActionContext(store, who, id, state) {
985
1041
  requireActor(store, who, payload.repo, payload.operation);
986
1042
  return payload;
987
1043
  }
1044
+ /** An action's terms alone, without whose proposal it was (actor, generation, ceiling). */
1045
+ const termsStampOf = (action) => hash({ operation: action.operation, request: action.request, repo: action.repo, state: action.state, terms: action.terms });
1046
+ /**
1047
+ * Whether the live action is the saved one: the exact stamp; or, for a scope approval already holding another
1048
+ * approver's yes on these exact terms (awaiting the second), the same terms — each reviewer's own nonce and authority
1049
+ * are still bound to them individually.
1050
+ */
1051
+ function sameSharedAction(store, who, live, saved) {
1052
+ if (live.stamp === saved.stamp)
1053
+ return true;
1054
+ if (saved.operation !== "scope_approve" || termsStampOf(live) !== termsStampOf(saved))
1055
+ return false;
1056
+ const task = typeof saved.request["task"] === "string" ? saved.request["task"] : null;
1057
+ const scope = task === null ? null : store.getScope(task);
1058
+ if (scope === null)
1059
+ return false;
1060
+ return store.handle.prepare("SELECT 1 FROM scope_approval_vote WHERE task_id = ? AND digest = ? AND approver <> ? LIMIT 1").get(task, scope.digest, who.name) !== undefined;
1061
+ }
988
1062
  export function mintSharedActionReview(store, who, id, root, now) {
989
1063
  const payload = savedActionContext(store, who, id, "pending");
990
1064
  const live = prepareSharedAction(store, who, payload.operation, payload.request, root, now);
991
- if (live.stamp !== payload.stamp)
1065
+ if (!sameSharedAction(store, who, live, payload))
992
1066
  throw Error("This action changed. Ask for a fresh proposal.");
993
1067
  const nonce = randomBytes(24).toString("hex");
994
1068
  if (!store.mintCeremonyNonce({
@@ -1014,7 +1088,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1014
1088
  if (hash(saved) !== hash(payload))
1015
1089
  return refuse("refused", "This action does not match the saved proposal.");
1016
1090
  live = prepareSharedAction(store, who, saved.operation, saved.request, options.root, now);
1017
- if (live.stamp !== saved.stamp)
1091
+ if (!sameSharedAction(store, who, live, saved))
1018
1092
  return refuse("stale", "This action changed. Ask for a fresh proposal.");
1019
1093
  }
1020
1094
  catch (error) {
@@ -1022,6 +1096,8 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1022
1096
  ? error.message
1023
1097
  : "This action is no longer available.");
1024
1098
  }
1099
+ // The review nonce was minted against the saved proposal's stamp: it is checked against that, whoever reviews.
1100
+ const savedStamp = payload.stamp;
1025
1101
  payload = live;
1026
1102
  const config = CHAT_ACTIONS[live.operation], req = live.request;
1027
1103
  const challenged = options.via !== "web" &&
@@ -1031,14 +1107,21 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1031
1107
  if (sharedActionNeedsReview(payload) && !challenged) {
1032
1108
  if (options.via !== "web" || !options.review || options.confirm !== true)
1033
1109
  return refuse("needs-confirm", "Review the complete action in the secure confirmation screen.");
1034
- if (!store.consumeCeremonyNonce(nonceHash(options.review.nonce), who.name, "chat-action", id, payload.stamp, now))
1110
+ if (!store.consumeCeremonyNonce(nonceHash(options.review.nonce), who.name, "chat-action", id, savedStamp, now))
1035
1111
  return refuse("needs-confirm", "This confirmation expired or was already used. Open the review again.");
1112
+ // A scope approval proves its person inside approve() (one transaction with the seal, under the same freshness
1113
+ // rule as the task page); here an empty field is only looked at, never spent (an explicit re-check is one-use).
1114
+ const emptyApproval = payload.operation === "scope_approve" && options.review.password === "";
1036
1115
  if (config.password &&
1037
- !authenticateApprover(store, who.name, options.review.password, payload.repo).ok)
1116
+ (emptyApproval
1117
+ ? !passwordlessSessionFor(store, who.name) && !ssoNowAvailable(who.name) && withFreshness("approve-standard", () => freshnessFor(who.name)) === null
1118
+ : !authenticateApprover(store, who.name, options.review.password, payload.repo).ok))
1038
1119
  return refuse("needs-confirm", "Confirm with your password in the secure screen.");
1039
1120
  }
1040
1121
  try {
1041
- return store.savepoint(() => {
1122
+ // A scope approval's re-check (sso-now) is spent only if it happens: a refusal or a rolled-back savepoint puts it
1123
+ // back (its every effect is in this transaction). Other actions spend it whatever comes of them.
1124
+ const act = () => store.savepoint(() => {
1042
1125
  const task = typeof req["task"] === "string" ? req["task"] : null, repo = payload.repo, actor = who.name;
1043
1126
  let taskId = task;
1044
1127
  if (payload.operation === "lead_about_you") {
@@ -1076,6 +1159,14 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1076
1159
  const done = runFlowAction(store, payload, actor, who.repos, options.root, now);
1077
1160
  return { ok: true, taskId: null, said: done.said, href: done.href };
1078
1161
  }
1162
+ else if (payload.operation === "sweep_cancel_rest") {
1163
+ const said = cancelSweepRest(store, who.name, payload.state["open"], now, "web");
1164
+ return { ok: true, taskId: null, said, href: `/sweeps/${Number(req["sweep"])}` };
1165
+ }
1166
+ else if (payload.operation === "sweep_file") {
1167
+ const done = fileSweep(store, who, id, payload, options.review?.password ?? "", now);
1168
+ return { ok: true, taskId: null, said: done.said, href: done.href };
1169
+ }
1079
1170
  else if (payload.operation === "kit_setup") {
1080
1171
  const kit = kitOf(String(req["kit"]));
1081
1172
  const made = setUpKitNow(store, kit, repo, actor, now, null, true);
@@ -1117,11 +1208,14 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1117
1208
  }, now);
1118
1209
  else if (payload.operation === "scope_approve") {
1119
1210
  const scope = store.getScope(task);
1120
- const result = approve(store, task, actor, now, scope.digest, options.review.password);
1211
+ // Cockpit B2: classified by the act, not the road — the same standard-approval freshness as /t/:id/approve.
1212
+ const result = withFreshness("approve-standard", () => approve(store, task, actor, now, scope.digest, options.review.password));
1121
1213
  if (!result.ok) {
1122
- // v102: the project's approval rules speak for themselves.
1214
+ // A first yes on protected work is kept, with how its person was proved: committed, never rolled back.
1215
+ // The card stays open (`awaiting`) for the second approver; the same person again changes nothing.
1123
1216
  if (result.reason === "second-approver")
1124
- throw Error(gateWords({ verdict: "vote", have: result.have, need: 2, already: result.already }));
1217
+ return { ok: true, taskId, awaiting: true, ...(result.already ? { already: true } : {}), said: gateWords({ verdict: "vote", have: result.have, need: 2, already: result.already }) };
1218
+ // v102: the project's approval rules speak for themselves.
1125
1219
  if (result.reason === "requester" || result.reason === "person-required")
1126
1220
  throw Error(gateWords({ verdict: "refuse", reason: result.reason }));
1127
1221
  if (result.reason === "policy")
@@ -1187,6 +1281,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1187
1281
  : "Project knowledge saved.";
1188
1282
  return { ok: true, taskId, said };
1189
1283
  });
1284
+ return payload.operation === "scope_approve" ? spendingOnlyOnSuccess(act, result => !result.ok || ("already" in result && result.already === true)) : act();
1190
1285
  }
1191
1286
  catch (error) {
1192
1287
  return refuse("refused", error instanceof Error
@@ -1195,6 +1290,93 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1195
1290
  }
1196
1291
  }
1197
1292
  /** A confirmed subagent action (v92), through the same helpers the Subagents pages use. */
1293
+ /**
1294
+ * Lead fan-out: file every item (all of them or none — any refusal throws and the savepoint takes the whole sweep
1295
+ * back), link them to one sweep record, then approve each one's scope through the ordinary approval (its own digest,
1296
+ * its own rules). A child the approval refuses stays filed and unapproved; it is named, never retried.
1297
+ */
1298
+ /** One child as filing makes it (the shared brief, its item, the verdict rule; the shared acceptance; built directly). */
1299
+ function fileSweepChild(store, who, repo, brief, one, acceptance, now) {
1300
+ const goal = `${brief}\n\nThis item:\n${one.item}\n\n${VERDICT_RULE}`;
1301
+ return fileTaskProposal(store, {
1302
+ title: one.title, repo, goal, acceptance, planning: "skip",
1303
+ // Sized now, by the same heuristic filing would use: no background classifier is started — the card's dry run must
1304
+ // leave nothing running after its rollback, and the approved scope must be the one shown (Codex sweep r2).
1305
+ sizing: heuristicSizing({ title: one.title, goal, outOfScope: null }),
1306
+ filedVia: "mate", filedBy: { name: who.name, kind: "person" }, proposedVia: "mate", admittedRepos: [...who.repos],
1307
+ }, now);
1308
+ }
1309
+ /** A filed child's scope in the words an approval agrees to (no identity lines), its runtime terms, and their digest. */
1310
+ function sweepChildScopeOf(store, taskId) {
1311
+ const scope = store.getScope(taskId);
1312
+ if (scope === null)
1313
+ throw Error("A sweep item has no scope to show.");
1314
+ const authority = scopeAuthorityOf(scope, { authMode: readAuthModeStrict });
1315
+ if (!authority.ok)
1316
+ throw Error(authority.problem);
1317
+ const words = describeScope(scope).filter(line => !/^\s*(reference|approved)\s/.test(line)).map(line => line.trim());
1318
+ const runtime = `Agent permissions and limits: ${runtimeTerms(authority.profile)} ${authority.authMode === "api-key" ? "Uses your API key; spend is charged to that account." : authority.authMode === "subscription" ? "Uses your subscription login." : ""}`.trim();
1319
+ // The fingerprint covers the scope's every field, not only what is rendered (a profile setting no sentence names, such
1320
+ // as an agent's idle timeout, still makes the card stale): its exact terms with only identity left out.
1321
+ const { taskId: _task, digest: _digest, approvedDigest: _approved, approvedBy: _by, approvedAt: _at, proposedAt: _proposed, approvalBasis: _basis, modeDigest: _mode, approvedProfile: _approvedProfile, approvedProfileJson: _approvedProfileJson, approvedChainJson: _approvedChain, ...terms } = scope;
1322
+ return { fingerprint: hash({ words, runtime, authMode: authority.authMode, profile: authority.profile, terms }), runtime, words };
1323
+ }
1324
+ /** Every child as filing would make it, found in a savepoint that is always rolled back: prepare never writes. */
1325
+ function sweepChildScopes(store, who, repo, brief, items, acceptance, now) {
1326
+ const dryRun = new Error("sweep dry run");
1327
+ let found = [];
1328
+ // Inside one transaction (filing opens its own; transact flattens), a savepoint that always rolls back.
1329
+ store.transact(() => {
1330
+ try {
1331
+ store.savepoint(() => {
1332
+ found = items.map((one, index) => {
1333
+ const made = fileSweepChild(store, who, repo, brief, one, acceptance, now);
1334
+ if (!made.ok)
1335
+ throw Error(`Item ${index + 1} (${one.title}) can't be filed: ${made.message}`);
1336
+ return sweepChildScopeOf(store, made.id);
1337
+ });
1338
+ throw dryRun;
1339
+ });
1340
+ }
1341
+ catch (error) {
1342
+ if (error !== dryRun)
1343
+ throw error;
1344
+ }
1345
+ });
1346
+ return found;
1347
+ }
1348
+ function fileSweep(store, who, proposal, payload, password, now) {
1349
+ const req = payload.request;
1350
+ const already = store.sweepOfProposal(proposal);
1351
+ if (already !== null)
1352
+ return { said: "This sweep was already started.", href: `/sweeps/${already}` };
1353
+ const items = req["items"];
1354
+ const brief = String(req["brief"]);
1355
+ const sweep = store.createSweep({ owner: who.name, thread: store.getLeadProposal(proposal)?.thread ?? null, repo: payload.repo, title: String(req["title"]), brief,
1356
+ maxParallel: Number(req["maxParallel"]), proposal }, now);
1357
+ const filed = [];
1358
+ for (const [index, one] of items.entries()) {
1359
+ const made = fileSweepChild(store, who, payload.repo, brief, one, req["acceptance"], now);
1360
+ if (!made.ok)
1361
+ throw Error(`Item ${index + 1} (${one.title}) couldn't be filed: ${made.message}. Nothing was filed.`);
1362
+ // Each filed scope must be exactly the one the card showed (words, permissions, limits, route); else nothing is filed.
1363
+ if (sweepChildScopeOf(store, made.id).fingerprint !== payload.state["children"]?.[index])
1364
+ throw Error(`Item ${index + 1} (${one.title}) no longer matches what you reviewed (a project default changed). Nothing was filed; ask for a fresh card.`);
1365
+ store.addSweepTask(sweep, made.id, index, one.title);
1366
+ filed.push({ id: made.id, title: one.title });
1367
+ }
1368
+ const waiting = [];
1369
+ for (const one of filed) {
1370
+ const scope = store.getScope(one.id);
1371
+ const approved = scope === null ? null : withFreshness("approve-standard", () => approve(store, one.id, who.name, now, scope.digest, password));
1372
+ if (approved === null || !approved.ok)
1373
+ waiting.push(one.title);
1374
+ }
1375
+ const said = waiting.length === 0
1376
+ ? `Started: ${filed.length} tasks, at most ${Number(req["maxParallel"])} at once.`
1377
+ : `Started: ${filed.length - waiting.length} of ${filed.length} approved; ${waiting.length} wait for their own approval (${waiting.join(", ")}).`;
1378
+ return { said, href: `/sweeps/${sweep}` };
1379
+ }
1198
1380
  function runSubagentAction(store, payload, actor, now) {
1199
1381
  const req = payload.request;
1200
1382
  if (payload.operation === "subagent_create") {
@@ -1331,9 +1513,9 @@ function runFlowAction(store, payload, actor, repos, root, now) {
1331
1513
  if (payload.operation.startsWith("flow_trigger_")) {
1332
1514
  const trigger = store.getFlowTrigger(Number(req["trigger"]));
1333
1515
  if (payload.operation === "flow_trigger_remove")
1334
- removeFlowTrigger(store, trigger, now, configDirOf(store));
1516
+ removeFlowTrigger(store, trigger, now, configDirOf(store), actor);
1335
1517
  else
1336
- store.updateFlowTrigger(trigger.id, { state: payload.operation === "flow_trigger_pause" ? "paused" : "active" }, now);
1518
+ store.updateFlowTrigger(trigger.id, { state: payload.operation === "flow_trigger_pause" ? "paused" : "active" }, now, actor);
1337
1519
  return { said: payload.operation === "flow_trigger_remove" ? "Trigger removed." : payload.operation === "flow_trigger_pause" ? "Trigger paused." : "Trigger on again.", href: `/flows/${trigger.flow}` };
1338
1520
  }
1339
1521
  const card = store.getFlowCard(Number(req["card"]));
@@ -12,6 +12,10 @@
12
12
  */
13
13
  import type { ChatBinding, ChatEvent, ChatState } from "./chat-delivery-state.js";
14
14
  import { type LeadAsk, type Store } from "./store.js";
15
+ /** Whether a task (the execution shown, never only its family's root) moved past a lead's question asked at `since`:
16
+ * its plan was rewritten or approved, or a new attempt of it started. The options answered a state that is gone. A run
17
+ * that merely finishes, or a review beside it, answers nothing, so it never takes the question's buttons. */
18
+ export declare function taskMovedSince(store: Store, taskId: string, since: string): boolean;
15
19
  /** The question as text with its options, where no buttons are drawn. */
16
20
  export declare function chatAskText(ask: Pick<LeadAsk, "question" | "options">): string;
17
21
  /** The live buttons on one part, in the order they were minted: the options, then "Something else". None when the part
package/dist/chat-ask.js CHANGED
@@ -1,5 +1,19 @@
1
1
  import { chatHash, partContent, savedChatPart } from "./chat-delivery-state.js";
2
2
  import { LEAD_ASK_OTHER } from "./store.js";
3
+ /** Whether a task (the execution shown, never only its family's root) moved past a lead's question asked at `since`:
4
+ * its plan was rewritten or approved, or a new attempt of it started. The options answered a state that is gone. A run
5
+ * that merely finishes, or a review beside it, answers nothing, so it never takes the question's buttons. */
6
+ export function taskMovedSince(store, taskId, since) {
7
+ const at = new Date(since).getTime();
8
+ if (Number.isNaN(at))
9
+ return false;
10
+ const after = (iso) => iso != null && new Date(iso).getTime() > at;
11
+ const scope = store.getScope(taskId);
12
+ if (scope !== null && (after(scope.proposedAt) || after(scope.approvedAt)))
13
+ return true;
14
+ const ref = store.lookupRef(taskId);
15
+ return ref !== null && store.runsFor(ref.id).some(run => run.role !== "reviewer" && after(run.startedAt));
16
+ }
3
17
  /** The question as text with its options, where no buttons are drawn. */
4
18
  export function chatAskText(ask) {
5
19
  return [ask.question, ...ask.options.map(one => `• ${one}`), `• ${LEAD_ASK_OTHER}: say it in your own words`].join("\n");
@@ -509,6 +509,11 @@ export const CHAT_ACTION_PARITY = {
509
509
  how: "Read by the model during a phone turn over the enrolled ceiling.",
510
510
  gap: null,
511
511
  },
512
+ find_tools: {
513
+ support: "direct",
514
+ how: "Searches the lead's own other tools by what it wants to do; what it finds is offered to it from its next step in the same reply. Same on every channel: the shared engine runs it.",
515
+ gap: null,
516
+ },
512
517
  list_repos: {
513
518
  support: "direct",
514
519
  how: "Read during a turn; projects are r1..rN in enrollment order, as on the console.",
@@ -583,6 +588,21 @@ export const CHAT_ACTION_PARITY = {
583
588
  how: "Card with Confirm/Dismiss through confirmLeadProposal (filed as a lead proposal, via telegram). The confirmed card links Review & start for the filed task while its scope waits; under a signed automatic mode it says the scope is approved and links the task.",
584
589
  gap: "Under manual approval the password step happens in the console, reached from the card's button.",
585
590
  },
591
+ propose_sweep: {
592
+ support: "direct",
593
+ how: "A protected shared action (sweep_file): the card lists every item, the shared rules and the success test; confirming files them all and approves each scope.",
594
+ gap: "Starting a sweep approves work, so it opens the secure review in the console (the password, or Passwordless).",
595
+ },
596
+ get_sweep: {
597
+ support: "direct",
598
+ how: "Reads a sweep's table (each item's state, verdict and result) from the local database through the shared engine.",
599
+ gap: null,
600
+ },
601
+ propose_sweep_cancel: {
602
+ support: "direct",
603
+ how: "An ordinary shared action (sweep_cancel_rest): Confirm cancels every item not yet ready; finished results stay.",
604
+ gap: null,
605
+ },
586
606
  propose_scope: {
587
607
  support: "direct",
588
608
  how: "Confirm rewrites the scope through the shared door; the confirmed card links Review & start for the exact task.",
@@ -63,7 +63,7 @@ export const CHAT_CONTINUITY_SCRIPT = String.raw `
63
63
  var carry=JSON.parse(sessionStorage.getItem(carryKey)||'null');sessionStorage.removeItem(carryKey);
64
64
  if(!draft&&carry&&user&&carry.owner===user&&carry.task===task&&typeof carry.text==='string'&&carry.text.length<=2000&&typeof carry.at==='number'&&carry.at>Date.now()-86400000){box.value=carry.text;draft={text:carry.text,request:request.value,submitted:false,at:Date.now()};save();}
65
65
  }catch(e){storage=false;}
66
- function buttons(){document.querySelectorAll('form[action="/chat"] button[type="submit"]').forEach(function(button){button.disabled=busy||sending||stale||offline;});}
66
+ function buttons(){document.querySelectorAll('form[action="/chat"] button[type="submit"]').forEach(function(button){button.disabled=sending||stale||offline;});}
67
67
  function showReconnect(on){if(reconnect)reconnect.hidden=!on;}
68
68
  function lostAuthority(){stale=true;buttons();showReconnect(true);say('Sign in again to reconnect. Your draft stays in this tab.');}
69
69
  function changed(){stale=true;buttons();showReconnect(true);say('This conversation changed or ended. Your draft stays in this tab; reconnect to continue.');}
@@ -97,7 +97,8 @@ export const CHAT_CONTINUITY_SCRIPT = String.raw `
97
97
  if(!(one instanceof HTMLFormElement))return;
98
98
  var action=one.getAttribute('action')||'';
99
99
  if(action==='/chat'){
100
- if(busy||sending||stale||offline){event.preventDefault();return;}
100
+ // A reply in progress no longer holds the composer: a message sent now waits its turn (the lead queue).
101
+ if(sending||stale||offline){event.preventDefault();return;}
101
102
  bind(one);
102
103
  if(one===form){
103
104
  if(!box.value.trim()){event.preventDefault();return;}
@@ -125,7 +126,7 @@ export const CHAT_CONTINUITY_SCRIPT = String.raw `
125
126
  var data=got.data||{};
126
127
  if(data.ok===true){
127
128
  if(String(data.session)!==session){changed();return;}
128
- say('Sent. Waiting for the reply…');later(300);return;
129
+ say(data.queued===true?'Queued. It goes to the lead after the reply in progress.':'Sent. Waiting for the reply…');later(300);return;
129
130
  }
130
131
  // Refused, so nothing was received: the draft stays, editable and
131
132
  // resendable under its own key; the words are the server's.
@@ -302,7 +303,7 @@ export const CHAT_CONTINUITY_SCRIPT = String.raw `
302
303
  }
303
304
  settleLive();
304
305
  busy=data.pending;buttons();
305
- say(busy?'Reply in progress. You can draft your next message or come back later.':sent?'Message not confirmed. Check the conversation before retrying.':'');
306
+ say(busy?'Reply in progress. A message you send now waits its turn.':sent?'Message not confirmed. Check the conversation before retrying.':'');
306
307
  later(busy?2500:5000);
307
308
  }catch(e){say('Connection lost. Reconnecting… Your existing work is not cancelled.');later(10000);}
308
309
  finally{polling=false;}
@@ -1,17 +1,3 @@
1
- /** Decisions finish in the chat app. A pushed card about a result, a failed task, a plan waiting for approval or a
2
- * pull request ready to merge carries buttons that act in place: Accept and finish, Request changes, Retry,
3
- * Approve & start, Merge. Every act that changes something asks twice ("Accept and finish 'X'? [Yes] [Cancel]"),
4
- * and each button is one opaque token bound to the exact result, receipt, plan or commit the card showed: a card a
5
- * newer result, a finished task or a changed plan has overtaken says so and acts on nothing.
6
- *
7
- * Approving a plan or merging needs the password, which never goes in chat, so they act in chat only where the owner
8
- * turned approving from chat on (their lasting setting, chat-approval.ts, or a signed mode's chatApprove term), for
9
- * their own chat; otherwise, and for a plan that widens permissions, exceeds the limit or touches protected paths, the
10
- * card keeps its link.
11
- *
12
- * The same in every chat app: Telegram's bridge and the shared engine Slack, Discord and Teams run on (chat-delivery.ts)
13
- * offer, mint and answer these through the same tokens (the `channel` column). Every act runs through the door the
14
- * console uses. */
15
1
  import { DECIDE_ACTS } from "./contracts/chat-callback-rows.js";
16
2
  import { type ChatApprovalLimits } from "./chat-approval.js";
17
3
  import { type VerifiedApprover } from "./principal.js";
@@ -12,6 +12,7 @@
12
12
  * The same in every chat app: Telegram's bridge and the shared engine Slack, Discord and Teams run on (chat-delivery.ts)
13
13
  * offer, mint and answer these through the same tokens (the `channel` column). Every act runs through the door the
14
14
  * console uses. */
15
+ import { isAcpProfile } from "./acp-agents.js";
15
16
  import { DECIDE_ACTS, readDecideActionRow } from "./contracts/chat-callback-rows.js";
16
17
  import { createHash, randomBytes } from "node:crypto";
17
18
  import { acceptAndCompleteAsOperator, assignmentOf, owedAcceptance } from "./assignment.js";
@@ -81,13 +82,15 @@ export function permissionPlainWords(profile) {
81
82
  }
82
83
  if (profile.provider === "gemini")
83
84
  return profile.approvalArgv === "yolo" ? full : "file edits only; other tools are refused";
85
+ if (isAcpProfile(profile))
86
+ return "full access inside the OS fence, nothing asks first";
84
87
  return profile.sandboxMode === "danger-full-access" ? full : "file edits and commands inside the project only";
85
88
  }
86
89
  function fullAccess(profile) {
87
90
  if (profile === null || profile === undefined)
88
91
  return false;
89
92
  return profile.provider === "claude" ? profile.permissionArgv === "bypassPermissions"
90
- : profile.provider === "gemini" ? profile.approvalArgv === "yolo" : profile.sandboxMode === "danger-full-access";
93
+ : profile.provider === "gemini" ? profile.approvalArgv === "yolo" : isAcpProfile(profile) ? true : profile.sandboxMode === "danger-full-access";
91
94
  }
92
95
  const money = (micro) => `$${(micro / 1_000_000).toFixed(2)}`;
93
96
  /** A plan this person's chat may approve now: the term, the plan's exact bytes, and none of the reasons it must open
@@ -3,6 +3,15 @@ import { ChatState, type ChatBinding, type ChatContent, type ChatEvent, type Cha
3
3
  import { type Store } from "./store.js";
4
4
  import type { SubscriptionLeadRunner } from "./subscription-chat.js";
5
5
  import { type ChatMerge } from "./chat-decide.js";
6
+ /**
7
+ * The watch at a chat app's final fence (cockpit §3, R11), right before the network: a notice about an agent its
8
+ * person has open now waits, as a held part, until the watch ends; then it is delivered as usual. The subject is the
9
+ * part's own run, or its task's live build. Only a part planned with `watch` (never a security alert or the digest)
10
+ * can be held. True: held, so nothing is sent now.
11
+ */
12
+ export declare function heldForWatch(state: ChatState, store: Store, part: number, event: {
13
+ kind: string;
14
+ }, approver: string, content: ChatContent, now: Date): boolean;
6
15
  export declare const chatObject: (v: unknown) => Record<string, unknown>;
7
16
  export type ChatDeliveryOptions = {
8
17
  store: Store;