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
@@ -0,0 +1,228 @@
1
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { LIVE_RETAIN_MS, legacyLiveLogPath, liveLogPath, liveWindowRecords } from "./live.js";
4
+ export const RUN_EFFICIENCY_VERSION = 3;
5
+ /** Budgets for one derivation: never more than the live file's own cap, and a bounded record count. */
6
+ export const EFFICIENCY_READ_BYTES = 4 * 1024 * 1024;
7
+ export const EFFICIENCY_RECORDS = 20_000;
8
+ const LONGEST = 5;
9
+ const num = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
10
+ const msBetween = (from, to) => {
11
+ if (from === null || to === null)
12
+ return null;
13
+ const ms = Date.parse(to) - Date.parse(from);
14
+ return Number.isFinite(ms) && ms >= 0 ? ms : null;
15
+ };
16
+ /** Tokens and cache from the provider's own usage report, read per provider (their counts mean different things). */
17
+ export function usageOf(run) {
18
+ let raw = {};
19
+ try {
20
+ const parsed = run.usageJson == null ? null : JSON.parse(run.usageJson);
21
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
22
+ raw = parsed;
23
+ }
24
+ catch {
25
+ raw = {};
26
+ }
27
+ const base = { provider: run.provider, model: run.model ?? null, out: num(raw["output_tokens"]) ?? run.tokensOut ?? null };
28
+ if (run.provider === "claude") {
29
+ // Claude's input_tokens leave out what was read from or written to the cache: the three add up to what it read, so
30
+ // all three must be reported before a total or a share is claimed (a missing one is unknown, never zero).
31
+ const input = num(raw["input_tokens"]), read = num(raw["cache_read_input_tokens"]), write = num(raw["cache_creation_input_tokens"]);
32
+ const total = input === null || read === null || write === null ? null : input + read + write;
33
+ return { ...base, in: total, cacheRead: read, cacheWrite: write, cacheShare: total === null || total === 0 ? null : read / total };
34
+ }
35
+ if (run.provider === "codex") {
36
+ // Codex's input_tokens already include what came from its cache: both reported, or no share.
37
+ const input = num(raw["input_tokens"]), read = num(raw["cached_input_tokens"]);
38
+ return { ...base, in: input ?? run.tokensIn ?? null, cacheRead: read, cacheWrite: null,
39
+ cacheShare: read === null || input === null || input === 0 || read > input ? null : read / input };
40
+ }
41
+ return { ...base, in: run.tokensIn ?? null, cacheRead: null, cacheWrite: null, cacheShare: null };
42
+ }
43
+ /** The live records' steps, bounded: counts by kind, failures, observed times and repeats (never their words). */
44
+ /** `measured`: the recorder saw this provider's steps start and end as they happened (Claude's stream does); for the
45
+ * others their times are partly made up (Codex reports a step when it completes, ACP agents may report late), so no
46
+ * duration is claimed for them. */
47
+ export function stepsOf(records, truncated, measured = true) {
48
+ let detail = "base";
49
+ const open = new Map();
50
+ const byKind = {};
51
+ const longest = [];
52
+ let total = 0, failed = 0, unknown = 0;
53
+ for (const record of records) {
54
+ if (record.k === "open")
55
+ detail = record.detail;
56
+ else if (record.k === "step") {
57
+ if (record.kind === "session" || record.kind === "message")
58
+ continue;
59
+ total++;
60
+ byKind[record.kind] = (byKind[record.kind] ?? 0) + 1;
61
+ open.set(record.id, { kind: record.kind, at: record.at, once: record.once === true });
62
+ }
63
+ else if (record.k === "done") {
64
+ const started = open.get(record.id);
65
+ if (started === undefined)
66
+ continue;
67
+ open.delete(record.id);
68
+ if (record.ok === false)
69
+ failed++;
70
+ // Not a measured time: a provider whose times the recorder can't see as they happen, a start and end made up
71
+ // together (`once`), a step the recorder closed itself when the run ended (outcome unknown), or no time between.
72
+ const ms = !measured || started.once || record.ok === null ? null : msBetween(started.at || null, record.at || null);
73
+ if (ms === null || ms === 0) {
74
+ unknown++;
75
+ continue;
76
+ }
77
+ longest.push({ kind: started.kind, ms });
78
+ longest.sort((a, b) => b.ms - a.ms);
79
+ if (longest.length > LONGEST)
80
+ longest.pop();
81
+ }
82
+ }
83
+ // Steps that never closed (the run ended, or the file was cut) have no measured time.
84
+ unknown += open.size;
85
+ // Repeats are never claimed: the step records keep a display form of each step (a file name, a host, a command with
86
+ // its shell wrapper taken off), so two different steps can read alike. Said, not guessed.
87
+ const repeatsReason = detail === "commands"
88
+ ? "Repeats can't be told apart: the step records keep a short form of each step, not its whole input."
89
+ : "Repeats can't be told apart: this project doesn't record step details.";
90
+ const repeats = null;
91
+ return { state: truncated === null ? "complete" : "truncated", reason: truncated, total, failed, unknownDuration: unknown, byKind, longest, repeats, repeatsReason };
92
+ }
93
+ const noSteps = (state, reason) => ({ state, reason, total: null, failed: null, unknownDuration: null, byKind: {}, longest: [], repeats: null, repeatsReason: null });
94
+ /** Read the run's live records once, strictly bounded: its own descriptor (no link followed, in a real live
95
+ * directory), measured once, and never more than the budget read whatever the file does meanwhile; then parsed line by
96
+ * line. A file over the budget, in the older text format, with lines that don't read, or cut or capped while the agent
97
+ * ran, says its steps are incomplete. Null when there is no step file at all. */
98
+ function readRecords(evidenceRoot, runId) {
99
+ try {
100
+ const dir = lstatSync(join(evidenceRoot, "live"));
101
+ if (!dir.isDirectory() || dir.isSymbolicLink())
102
+ return null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ let fd;
108
+ try {
109
+ fd = openSync(liveLogPath(evidenceRoot, runId), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
110
+ }
111
+ catch (error) {
112
+ if (error.code !== "ENOENT")
113
+ return { unreadable: "The step records couldn't be read." };
114
+ // No records file: an older run's plain text file has no step detail; with neither, there is nothing.
115
+ try {
116
+ lstatSync(legacyLiveLogPath(evidenceRoot, runId));
117
+ return { unreadable: "This run's steps were recorded in an older format that has no step detail." };
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ let text, short = false;
124
+ try {
125
+ const stat = fstatSync(fd);
126
+ if (!stat.isFile())
127
+ return { unreadable: "The step records aren't a plain file, so they weren't read." };
128
+ if (stat.size > EFFICIENCY_READ_BYTES)
129
+ return { unreadable: "The step records are larger than they can be read here." };
130
+ // At most what it measured, and never past the budget: growth after the measure is not read.
131
+ const buffer = Buffer.alloc(stat.size);
132
+ let at = 0;
133
+ while (at < buffer.length) {
134
+ const got = readSync(fd, buffer, at, buffer.length - at, at);
135
+ if (got <= 0)
136
+ break;
137
+ at += got;
138
+ }
139
+ // Less than it measured: the file changed under the read.
140
+ short = at < buffer.length;
141
+ text = buffer.subarray(0, at).toString("utf8");
142
+ }
143
+ catch {
144
+ return { unreadable: "The step records couldn't be read." };
145
+ }
146
+ finally {
147
+ closeSync(fd);
148
+ }
149
+ const lines = text.split("\n");
150
+ // The last line may be unfinished (the agent's final write): it is read when it parses, never counted as a loss.
151
+ const unfinished = lines.at(-1) ?? "";
152
+ const whole = lines.slice(0, -1).filter(line => line.trim() !== "");
153
+ if (whole.length > EFFICIENCY_RECORDS)
154
+ whole.length = EFFICIENCY_RECORDS;
155
+ const keys = { inKey: false };
156
+ // One record per line: a whole line that doesn't read is a record lost.
157
+ const read = liveWindowRecords({ text: whole.length === 0 ? "" : `${whole.join("\n")}\n`, format: "records" }, keys);
158
+ const lost = whole.length - read.length;
159
+ // A finished run's last line without its newline is read like any other; one that doesn't read is a record lost.
160
+ const tail = unfinished.trim() === "" ? [] : liveWindowRecords({ text: `${unfinished}\n`, format: "records" }, keys);
161
+ const records = [...read, ...tail];
162
+ const lostAll = lost + (unfinished.trim() !== "" && tail.length === 0 ? 1 : 0);
163
+ if (records.length === 0 && text.trim() !== "")
164
+ return { unreadable: "The step records couldn't be read." };
165
+ const truncated = short ? "the step records changed while they were read"
166
+ : lines.length - 1 > EFFICIENCY_RECORDS ? `only the first ${EFFICIENCY_RECORDS.toLocaleString("en-US")} records were read`
167
+ : records.some(one => one.k === "cap") ? "the step records reached their size limit while the agent ran"
168
+ : records.some(one => one.k === "cut") ? "a step record was too long to keep"
169
+ : lostAll > 0 ? `${lostAll} step record${lostAll === 1 ? "" : "s"} couldn't be read`
170
+ : null;
171
+ return { records, truncated };
172
+ }
173
+ /** Derive a finished run's How it ran from its records. */
174
+ export function deriveRunEfficiency(run, evidenceRoot, now) {
175
+ const finished = run.finishedAt;
176
+ const read = readRecords(evidenceRoot, run.id);
177
+ const steps = read !== null && "unreadable" in read ? noSteps("truncated", read.unreadable)
178
+ : read !== null ? stepsOf(read.records, read.truncated, run.provider === "claude")
179
+ : finished !== null && now.getTime() - Date.parse(finished) > LIVE_RETAIN_MS ? noSteps("expired", "Step details are kept for a day after a run; this one's have expired.")
180
+ : noSteps("absent", "No step records were kept for this run.");
181
+ return {
182
+ version: RUN_EFFICIENCY_VERSION,
183
+ time: { waitMs: msBetween(run.startedAt, run.providerStartedAt), agentMs: msBetween(run.providerStartedAt, finished) },
184
+ turns: run.agentTurns ?? null,
185
+ usage: usageOf(run),
186
+ steps,
187
+ };
188
+ }
189
+ /**
190
+ * A finished run's How it ran: the saved one when it was derived by this version, else derived now and saved (once:
191
+ * a row whose steps were complete or have expired is final). An unfinished run is derived but never saved.
192
+ */
193
+ export function runEfficiencyOf(store, evidenceRoot, runId, now) {
194
+ const run = store.getRun(runId);
195
+ if (run === null)
196
+ return null;
197
+ const usageJson = store.handle.prepare("SELECT usage_json FROM run WHERE id = ?").get(runId)?.["usage_json"];
198
+ const full = { ...run, usageJson: typeof usageJson === "string" ? usageJson : null };
199
+ if (run.finishedAt === null)
200
+ return deriveRunEfficiency(full, evidenceRoot, now);
201
+ let saved;
202
+ try {
203
+ saved = store.handle.prepare("SELECT parser_version, payload_json FROM run_efficiency WHERE run = ?").get(runId);
204
+ }
205
+ catch {
206
+ saved = undefined;
207
+ }
208
+ if (saved !== undefined && Number(saved["parser_version"]) === RUN_EFFICIENCY_VERSION) {
209
+ try {
210
+ return JSON.parse(String(saved["payload_json"]));
211
+ }
212
+ catch { /* derived again below */ }
213
+ }
214
+ const derived = deriveRunEfficiency(full, evidenceRoot, now);
215
+ const payload = JSON.stringify(derived);
216
+ // A row too large for its column (it can't be: the counts are bounded) is shown, never saved.
217
+ // Saving is a convenience: a store that can't take the row (read-only, busy) still shows what was derived.
218
+ if (payload.length <= 16_384) {
219
+ try {
220
+ store.handle.prepare(`INSERT INTO run_efficiency (run, parser_version, steps, steps_reason, payload_json, derived_at) VALUES (?, ?, ?, ?, ?, ?)
221
+ ON CONFLICT(run) DO UPDATE SET parser_version = excluded.parser_version, steps = excluded.steps, steps_reason = excluded.steps_reason,
222
+ payload_json = excluded.payload_json, derived_at = excluded.derived_at`)
223
+ .run(runId, RUN_EFFICIENCY_VERSION, derived.steps.state, derived.steps.reason, payload, now.toISOString());
224
+ }
225
+ catch { /* shown, not saved: derived again next time */ }
226
+ }
227
+ return derived;
228
+ }
package/dist/runner.d.ts CHANGED
@@ -19,6 +19,8 @@
19
19
  * again. Reaping on the second tells you a *machine* is gone, and everything
20
20
  * it was holding — claims and worktrees alike — needs recovering together.
21
21
  */
22
+ import { type AcpProviderId } from "./acp-agents.js";
23
+ import type { AcpAttestation } from "./store.js";
22
24
  import type { Store, Mutation } from "./store.js";
23
25
  import { type ProviderId } from "./provider.js";
24
26
  import type { ReadinessObservation } from "./phase-routing.js";
@@ -264,7 +266,9 @@ export type ReadinessProbe = (file: string, args: readonly string[], options?: {
264
266
  notFound: boolean;
265
267
  timedOut: boolean;
266
268
  }>;
267
- export declare function observeProviderReadiness(probe: ReadinessProbe, env?: Record<string, string | undefined>, providers?: readonly ProviderId[]): Promise<Omit<ReadinessObservation, "runner" | "observedAt">[]>;
269
+ export declare function observeProviderReadiness(probe: ReadinessProbe, env?: Record<string, string | undefined>, providers?: readonly ProviderId[],
270
+ /** An ACP agent's live check on this runner (store.acpAttestationFor), when the caller can read one. */
271
+ acpChecked?: (provider: AcpProviderId) => AcpAttestation | null): Promise<Omit<ReadinessObservation, "runner" | "observedAt">[]>;
268
272
  /**
269
273
  * Record a runner's observations under its OWN authenticated name: the
270
274
  * credential is verified and the rows written in one transaction, so a
package/dist/runner.js CHANGED
@@ -19,6 +19,9 @@
19
19
  * again. Reaping on the second tells you a *machine* is gone, and everything
20
20
  * it was holding — claims and worktrees alike — needs recovering together.
21
21
  */
22
+ import { isAcpProvider } from "./acp-agents.js";
23
+ import { verifyLaunch } from "./acp-attest.js";
24
+ import { inspectAcpAgent, inspectionWords } from "./acp-inspect.js";
22
25
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
23
26
  import { resolve } from "node:path";
24
27
  import { canonicalProject } from "./project.js";
@@ -363,8 +366,26 @@ function sameDigest(left, right) {
363
366
  return a.length === b.length && timingSafeEqual(a, b);
364
367
  }
365
368
  const READINESS_PROBE_TIMEOUT_MS = 5_000;
366
- export async function observeProviderReadiness(probe, env = process.env, providers = PROVIDER_IDS) {
369
+ export async function observeProviderReadiness(probe, env = process.env, providers = PROVIDER_IDS,
370
+ /** An ACP agent's live check on this runner (store.acpAttestationFor), when the caller can read one. */
371
+ acpChecked) {
367
372
  return Promise.all(providers.map(async (provider) => {
373
+ // An ACP agent is looked at, never run, outside the gateway (acp-inspect.ts). It is ready only once its
374
+ // conformance check passed on this computer; until then it says so.
375
+ if (isAcpProvider(provider)) {
376
+ const checked = acpChecked?.(provider) ?? null;
377
+ if (checked !== null) {
378
+ // Ready only while every file its check bound is still the one that passed.
379
+ const verified = verifyLaunch(checked.launch);
380
+ return verified.ok
381
+ ? { provider, state: "ready", reason: `${checked.agent.name} ${checked.agent.version}, checked ${checked.recordedAt.slice(0, 10)}`, probe: "version" }
382
+ : { provider, state: "unavailable", reason: `${verified.problem}`, probe: "version" };
383
+ }
384
+ const seen = inspectAcpAgent(provider, env);
385
+ return seen.found
386
+ ? { provider, state: "unavailable", reason: `${inspectionWords(seen)}; not checked on this computer — run \`toolroll agents check ${provider}\``, probe: "version" }
387
+ : { provider, state: "unavailable", reason: inspectionWords(seen), probe: "version" };
388
+ }
368
389
  const facts = inspectionOf(provider);
369
390
  const version = await probe(facts.binary, ["--version"], { timeoutMs: READINESS_PROBE_TIMEOUT_MS, omitEnv: ALL_CREDENTIAL_ENV });
370
391
  if (version.notFound) {
package/dist/scope.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { ACCEPTANCE_LIMITS, EVIDENCE_KINDS, type EvidenceKind } from "./contracts/acceptance-terms.js";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { isAcpProfile, type AcpProviderId } from "./acp-agents.js";
4
+ export { isAcpProfile };
5
+ import { type NetworkProfile } from "./egress.js";
3
6
  import { type EvidenceKind } from "./contracts/acceptance-terms.js";
4
7
  import { type AcceptanceCriterion, type AcceptanceProblem, type ScopeTerms } from "./contracts/scope.js";
5
8
  import type { Store, Mutation } from "./store.js";
@@ -42,6 +45,13 @@ export declare const GEMINI_LIMITS: {
42
45
  readonly timeoutSeconds: 1200;
43
46
  readonly repairTimeoutSeconds: 300;
44
47
  };
48
+ /** An ACP agent runs under two clocks at once: the whole attempt, and silence (no line from the agent). */
49
+ export declare const ACP_LIMITS: {
50
+ readonly timeoutSeconds: 3600;
51
+ readonly idleTimeoutSeconds: 900;
52
+ readonly repairTimeoutSeconds: 900;
53
+ readonly repairIdleTimeoutSeconds: 300;
54
+ };
45
55
  export type ClaudeProfile = {
46
56
  provider: "claude";
47
57
  /** The exact model string the argv carries. Never empty, never "default". */
@@ -61,6 +71,9 @@ export type ClaudeProfile = {
61
71
  /** Exact model for repairs, or the stable literal "inherit" (= the
62
72
  * build model, which is itself exact). */
63
73
  repairModel: string;
74
+ /** What the build may reach (egress.ts), resolved when the scope was written; absent on approvals sealed before
75
+ * network policies, which run narrowed (egress.ts legacyNetwork). */
76
+ network?: NetworkProfile;
64
77
  };
65
78
  export type CodexShapedProfile = {
66
79
  provider: "codex" | "openrouter";
@@ -77,6 +90,7 @@ export type CodexShapedProfile = {
77
90
  timeoutKind?: "idle";
78
91
  repairTimeoutSeconds: number;
79
92
  repairModel: string;
93
+ network?: NetworkProfile;
80
94
  };
81
95
  export type GeminiProfile = {
82
96
  provider: "gemini";
@@ -95,8 +109,40 @@ export type GeminiProfile = {
95
109
  timeoutKind?: "idle";
96
110
  repairTimeoutSeconds: number;
97
111
  repairModel: string;
112
+ network?: NetworkProfile;
113
+ };
114
+ /**
115
+ * An agent over the Agent Client Protocol (acp-agents.ts, docs/plans/acp-adapter.md).
116
+ * Full access inside the OS fence is the only posture: nothing else could be
117
+ * made true (an agent may run commands without asking). There is no turn
118
+ * limit; both clocks bound every attempt, and a repair has its own pair.
119
+ */
120
+ export type AcpProfile = {
121
+ provider: AcpProviderId;
122
+ /** Exact, and one the agent offered when it was checked. */
123
+ model: string;
124
+ permission: "full";
125
+ maxTurns: "unsupported";
126
+ repairMaxTurns: "unsupported";
127
+ timeoutSeconds: number;
128
+ idleTimeoutSeconds: number;
129
+ repairTimeoutSeconds: number;
130
+ repairIdleTimeoutSeconds: number;
131
+ repairModel: string;
132
+ network?: NetworkProfile;
133
+ };
134
+ export type ExecutionProfile = ClaudeProfile | CodexShapedProfile | GeminiProfile | AcpProfile;
135
+ /** Whether a profile's clock is a no-progress window (an ACP profile has both clocks; its overall one is a deadline). */
136
+ export declare function idleClock(profile: ExecutionProfile): boolean;
137
+ /**
138
+ * The watchdogs an attempt runs under, from its sealed profile and the overall bound the caller resolved: an ACP
139
+ * profile gets both clocks at once (the deadline and its silence window — a repair its own pair); the others one,
140
+ * a no-progress window when the profile says idle, else a deadline.
141
+ */
142
+ export declare function watchdogsOf(profile: ExecutionProfile, timeoutMs: number, repair?: boolean): {
143
+ timeoutMs?: number;
144
+ idleTimeoutMs?: number;
98
145
  };
99
- export type ExecutionProfile = ClaudeProfile | CodexShapedProfile | GeminiProfile;
100
146
  /** The durable unattended permission choices exposed by the console. The
101
147
  * provider-specific argv is still what gets sealed into an approval; this
102
148
  * small cross-provider type is only the operator-facing policy. */
@@ -258,7 +304,7 @@ export type Scope = SavedScopeTerms & {
258
304
  * 'mode' = sealed by the signer's live mode (modeDigest names it);
259
305
  * 'chat' = the owner's own two taps in their paired chat, under their chat-approval setting or a signed term
260
306
  * (modeDigest names that authority; the yes is the person's and doesn't lapse with it). */
261
- approvalBasis?: "password" | "mode" | "chat" | null;
307
+ approvalBasis?: "password" | "mode" | "chat" | "grant" | null;
262
308
  modeDigest?: string | null;
263
309
  /** v24 (optional so hand-built scopes in tests stay valid): the working
264
310
  * execution profile, its resolution state, and the immutable snapshot
@@ -461,6 +507,11 @@ export type ApproveResult = {
461
507
  ok: false;
462
508
  reason: "policy";
463
509
  message: string;
510
+ }
511
+ /** Cockpit B2: a remembered password can't approve these terms (protected, escalated, or a quarantined filing). */
512
+ | {
513
+ ok: false;
514
+ reason: "password-now";
464
515
  };
465
516
  /**
466
517
  * Whether this name-and-token pair is a person the store knows. Shared by
@@ -494,12 +545,114 @@ export declare function authenticateAccount(store: Store, by: string, secret: st
494
545
  * approval, chat-action review, the console's own ceremonies). A typed
495
546
  * password is always checked, and nothing else stands in.
496
547
  */
497
- export declare const freshIdentitySignIn: AsyncLocalStorage<{
498
- actor: string | null;
548
+ /**
549
+ * PASSWORD FRESHNESS (docs/plans/agents-cockpit.md B2), and the one place an empty password field is ever honoured.
550
+ *
551
+ * - An identity-provider re-check made **on request** (a step-up's re-authentication, never an ordinary sign-in) stands
552
+ * in for one password typed now, for ONE step-up of any kind, within five minutes: it is consumed by the act it
553
+ * proves (`sso-now`). Never reused across acts.
554
+ * - A password verified on this session, or that explicit re-check, stands in for fifteen minutes — but only inside an
555
+ * allowlisted act (`withFreshness`), only for this person, only on a direct local request (`fresh-password`,
556
+ * `fresh-sso`). Never extended by use.
557
+ * Every fact is read at the moment of the act (`FreshFacts` are live readers, not a snapshot), so a sign-out, a session
558
+ * revoked or rotated, a credential change or Forget made while a request was in flight is seen by it.
559
+ */
560
+ export type FreshOp = "approve-standard" | "chat-send" | "reply-question";
561
+ /** "session": Passwordless is on and this is the person's own live browser session (docs/plans/passwordless.md). */
562
+ /** "remote-token" / "lead-token": a machine credential stood in for the person (a remote run's API token, a lead's
563
+ * token) — fine for ordinary acts it is allowed, never for a credential act (it would mint a way in with no password). */
564
+ export type AuthMethod = "password-now" | "sso-now" | "fresh-password" | "fresh-sso" | "session" | "cli-login" | "remote-token" | "lead-token";
565
+ /**
566
+ * The local CLI's remembered login, marked for the one command that read it (operate.ts credentialsFrom): with
567
+ * Passwordless on, a proof made with it records "cli-login" — never a typed password — and no credential act accepts it.
568
+ * Scoped to that command's async chain; a browser request in the console's own process never carries it.
569
+ */
570
+ export declare const rememberedLogin: AsyncLocalStorage<{
571
+ login: {
572
+ name: string;
573
+ token: string;
574
+ } | null;
499
575
  }>;
500
- export declare const hasFreshIdentitySignIn: (by: string) => boolean;
501
- export declare function authenticateApprover(store: Store, by: string, token: string, repo?: string | null): {
576
+ export declare const FRESH_WINDOW_MS: number;
577
+ /** How long an explicit provider re-check stands in for one password typed now. */
578
+ export declare const SSO_NOW_MS: number;
579
+ export type FreshFacts = {
580
+ actor: string;
581
+ /** Straight from this computer: a loopback peer naming a loopback host, with no forwarding headers of any kind. */
582
+ local: boolean;
583
+ /** The request's route is on the freshness allowlist (route-table.ts `freshness: "allowlist"`): the table decides. */
584
+ allowlisted: boolean;
585
+ /** The session is still signed in, under the account's current credential generation, not revoked. */
586
+ live: () => boolean;
587
+ /** When a password was last verified on this session; when the provider last re-checked on request (window start);
588
+ * and an unconsumed re-check for one step-up. */
589
+ passwordAt: () => number | null;
590
+ ssoReauthAt: () => number | null;
591
+ ssoNow: () => number | null;
592
+ /** Spend the re-check; the answer puts it back (a refund) when the act it proved doesn't happen. */
593
+ consumeSsoNow: () => () => void;
594
+ /** Nothing before this counts (Forget). */
595
+ floor: () => number;
596
+ /** Record a verified password entry on this session (the console decides whether this request may start one). */
597
+ stamp: (at: number) => void;
598
+ };
599
+ export declare const freshFacts: AsyncLocalStorage<FreshFacts | null>;
600
+ /**
601
+ * Run an act a re-check may prove, spending the re-check only if the act happens: when it throws (a rolled-back
602
+ * transaction) or `failed` says it was refused, every re-check it spent is put back. ONLY for acts whose every effect
603
+ * is in one database transaction (an approval, a chat approval proposal, a chat turn opening): there, "refused" and
604
+ * "thrown" prove nothing happened. Every other step-up spends its re-check whatever comes of it (fail-closed: at worst
605
+ * the person confirms with their provider again). Synchronous acts only.
606
+ */
607
+ export declare function spendingOnlyOnSuccess<T>(act: () => T, failed: (result: T) => boolean): T;
608
+ /** Run an allowlisted act: within it, a fresh password or re-check may stand in for one typed now. */
609
+ export declare function withFreshness<T>(op: FreshOp, act: () => T): T;
610
+ /** The freshness this act may stand on now, or null: only inside an allowlisted act on an allowlisted route, for this
611
+ * person, on a direct local request, on a live session, within the window, after any Forget. `preview` asks it from a
612
+ * page (a GET) about the allowlisted act its form posts to — for words only, never to authorise. */
613
+ export declare function freshnessFor(by: string, now?: number, preview?: boolean): {
614
+ method: "fresh-password" | "fresh-sso";
615
+ at: number;
616
+ } | null;
617
+ /** Whether an explicit provider re-check is waiting to stand in for one step-up (the page says "Confirmed with …"). */
618
+ export declare function ssoNowAvailable(by: string, now?: number): boolean;
619
+ /**
620
+ * Proof that this person is here now, for any account step-up (an approver's or not): a password typed now, or an
621
+ * explicit provider re-check (consumed). The core of every console step-up; `authenticateApprover` adds standing.
622
+ */
623
+ export declare function proveAccountNow(store: Store, by: string, token: string, options?: {
624
+ credential?: boolean;
625
+ }): {
626
+ ok: true;
627
+ method: AuthMethod;
628
+ at: number;
629
+ } | {
630
+ ok: false;
631
+ reason: "no-approvers" | "not-an-approver";
632
+ };
633
+ /**
634
+ * A credential act (docs/plans/passwordless.md, the closed list: one that creates or widens a way in): only a password
635
+ * typed now, or a one-use identity-provider re-check, proves it — never a session, never a remembered password.
636
+ */
637
+ /** Whether an empty password would pass for `by` right now because Passwordless is on and this is their live browser
638
+ * session. Read only — the act itself proves it again inside `proveAccountNow`. */
639
+ export declare function passwordlessSessionFor(store: Store, by: string): boolean;
640
+ /** Whether a proof is one a credential act accepts: a password typed now, or a one-use identity-provider re-check. */
641
+ export declare const credentialProof: (method: AuthMethod) => boolean;
642
+ export declare function proveCredential(store: Store, by: string, token: string, repo?: string | null): {
502
643
  ok: true;
644
+ method: AuthMethod;
645
+ at: number;
646
+ } | {
647
+ ok: false;
648
+ reason: "no-approvers" | "not-an-approver";
649
+ };
650
+ export declare function authenticateApprover(store: Store, by: string, token: string, repo?: string | null, options?: {
651
+ credential?: boolean;
652
+ }): {
653
+ ok: true;
654
+ method: AuthMethod;
655
+ at: number;
503
656
  } | {
504
657
  ok: false;
505
658
  reason: "no-approvers" | "not-an-approver";
@@ -548,6 +701,18 @@ export declare function fileAndSealUnderMode(store: Store, input: ScopeInput & {
548
701
  detail?: string;
549
702
  };
550
703
  export declare function approve(store: Store, taskId: string, by: string, now: Date, sawDigest: string, token: string, mutation?: Mutation): ApproveResult;
704
+ /** The ledger act that records how an approval's person was proved (password-now, sso-now, fresh-password, fresh-sso). */
705
+ export declare const APPROVAL_PROOF = "approval proved";
706
+ /** The proof row's detail: when the person was proved, the generation, and the exact digest they said yes to. */
707
+ export declare const approvalProofDetail: (at: number, generation: number, digest: string) => string;
708
+ /** The proof of THIS approval: the approver's own row for these exact bytes (never another approval's words). */
709
+ export declare function approvalProofOf(store: Store, taskId: string, by: string, digest: string): {
710
+ method: string;
711
+ at: string;
712
+ } | null;
713
+ /** Whether a remembered password may approve these terms: one approver (no protected work), standard permissions
714
+ * (never escalated), and not filed by a coordinator, a lead or a scout (those keep their own password ceremony). */
715
+ export declare function freshlyApprovable(store: Store, taskId: string, scope: Scope, singleApprover: boolean): boolean;
551
716
  /**
552
717
  * Whether this task may be built.
553
718
  *