toolroll 0.9.63 → 0.9.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +33 -5
  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 +117 -2
  16. package/dist/agent-fence.js +439 -12
  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 +39 -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/batch-checks.js +3 -2
  32. package/dist/browser/THIRD_PARTY_NOTICES.txt +94 -0
  33. package/dist/browser/terminal.css +37 -0
  34. package/dist/browser/terminal.js +228 -0
  35. package/dist/browser/workspace.css +2 -2
  36. package/dist/browser/workspace.js +42 -38
  37. package/dist/browser-shell.js +6 -3
  38. package/dist/browser-workspace.d.ts +235 -1
  39. package/dist/browser-workspace.js +60 -0
  40. package/dist/builder.d.ts +10 -1
  41. package/dist/builder.js +87 -24
  42. package/dist/chat-actions.d.ts +13 -1
  43. package/dist/chat-actions.js +197 -15
  44. package/dist/chat-ask.d.ts +4 -0
  45. package/dist/chat-ask.js +14 -0
  46. package/dist/chat-channel.js +20 -0
  47. package/dist/chat-continuity.js +5 -4
  48. package/dist/chat-decide.d.ts +0 -14
  49. package/dist/chat-decide.js +4 -1
  50. package/dist/chat-delivery.d.ts +9 -0
  51. package/dist/chat-delivery.js +45 -6
  52. package/dist/chat-messages.d.ts +8 -0
  53. package/dist/chat-messages.js +36 -4
  54. package/dist/check-levels-ui.js +2 -2
  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 +19 -0
  59. package/dist/cli-help.d.ts +1 -1
  60. package/dist/cli-help.js +4 -1
  61. package/dist/cli.d.ts +3 -0
  62. package/dist/cli.js +21 -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/console-api.d.ts +13 -4
  71. package/dist/contracts/console-api.js +13 -4
  72. package/dist/contracts/flow-card.d.ts +4 -0
  73. package/dist/contracts/flow-card.js +3 -0
  74. package/dist/contracts/flow.d.ts +46 -0
  75. package/dist/contracts/flow.js +11 -2
  76. package/dist/contracts/lead-tools.d.ts +92 -0
  77. package/dist/contracts/lead-tools.js +13 -0
  78. package/dist/contracts/provider.d.ts +11 -1
  79. package/dist/contracts/provider.js +1 -1
  80. package/dist/contracts/route.d.ts +76 -1
  81. package/dist/contracts/route.js +1 -1
  82. package/dist/contracts/store-json.d.ts +2 -0
  83. package/dist/control-setup.d.ts +1 -1
  84. package/dist/control-ui.js +2 -2
  85. package/dist/converse.d.ts +16 -0
  86. package/dist/converse.js +32 -4
  87. package/dist/demo-chat.js +4 -4
  88. package/dist/demo.js +2 -2
  89. package/dist/desktop-update-gate.js +6 -0
  90. package/dist/desktop-update.d.ts +1 -1
  91. package/dist/discord-chat.js +4 -1
  92. package/dist/doctor.js +1 -1
  93. package/dist/egress-bridge.d.ts +17 -0
  94. package/dist/egress-bridge.js +111 -0
  95. package/dist/egress.d.ts +119 -0
  96. package/dist/egress.js +423 -0
  97. package/dist/exec.d.ts +78 -0
  98. package/dist/exec.js +367 -9
  99. package/dist/export-ui.js +2 -2
  100. package/dist/flow-engine.js +24 -5
  101. package/dist/flow-gallery-ui.js +2 -2
  102. package/dist/flow-gallery.js +27 -8
  103. package/dist/flow-share.js +16 -5
  104. package/dist/flow-triggers.d.ts +77 -8
  105. package/dist/flow-triggers.js +517 -70
  106. package/dist/flows-cli.js +8 -2
  107. package/dist/flows-ui.d.ts +0 -2
  108. package/dist/flows-ui.js +52 -4
  109. package/dist/flows.d.ts +4 -0
  110. package/dist/flows.js +9 -1
  111. package/dist/html.d.ts +9 -0
  112. package/dist/html.js +11 -0
  113. package/dist/invoke.d.ts +35 -0
  114. package/dist/invoke.js +347 -28
  115. package/dist/keys.d.ts +18 -14
  116. package/dist/keys.js +5 -0
  117. package/dist/lead-capabilities.js +3 -2
  118. package/dist/lead-contract.js +2 -0
  119. package/dist/lead-doors.d.ts +2 -0
  120. package/dist/lead-doors.js +7 -0
  121. package/dist/lead-follow.d.ts +2 -0
  122. package/dist/lead-follow.js +58 -2
  123. package/dist/lead-progress.js +1 -0
  124. package/dist/lead-queue-runtime.d.ts +46 -0
  125. package/dist/lead-queue-runtime.js +191 -0
  126. package/dist/lead-queue.d.ts +104 -0
  127. package/dist/lead-queue.js +187 -0
  128. package/dist/lead-sweep.d.ts +67 -0
  129. package/dist/lead-sweep.js +99 -0
  130. package/dist/lead-tools.d.ts +22 -0
  131. package/dist/lead-tools.js +152 -1
  132. package/dist/lead.d.ts +13 -5
  133. package/dist/lead.js +89 -29
  134. package/dist/limits-ui.d.ts +8 -1
  135. package/dist/limits-ui.js +26 -0
  136. package/dist/live-bus.d.ts +8 -0
  137. package/dist/live-bus.js +40 -4
  138. package/dist/live-detail.d.ts +27 -0
  139. package/dist/live-detail.js +57 -0
  140. package/dist/live-notes.d.ts +20 -0
  141. package/dist/live-notes.js +47 -0
  142. package/dist/live-records.d.ts +140 -0
  143. package/dist/live-records.js +935 -0
  144. package/dist/live-terminal.d.ts +269 -0
  145. package/dist/live-terminal.js +686 -0
  146. package/dist/live.d.ts +38 -27
  147. package/dist/live.js +142 -124
  148. package/dist/mcp-oauth.d.ts +6 -3
  149. package/dist/mcp-oauth.js +8 -4
  150. package/dist/memory-pass.js +2 -2
  151. package/dist/mobile-viewport.d.ts +5 -1
  152. package/dist/mobile-viewport.js +18 -2
  153. package/dist/model-catalog.js +4 -0
  154. package/dist/monitoring-ui.js +2 -2
  155. package/dist/needs-you.d.ts +1 -1
  156. package/dist/needs-you.js +11 -4
  157. package/dist/operate.d.ts +4 -0
  158. package/dist/operate.js +335 -19
  159. package/dist/passwordless.d.ts +23 -0
  160. package/dist/passwordless.js +26 -0
  161. package/dist/peek-cli.d.ts +12 -4
  162. package/dist/peek-cli.js +19 -11
  163. package/dist/phase-routing.js +10 -3
  164. package/dist/planner-source.d.ts +3 -0
  165. package/dist/planner-source.js +14 -0
  166. package/dist/planner.d.ts +2 -1
  167. package/dist/planner.js +7 -1
  168. package/dist/policy-ui.js +2 -2
  169. package/dist/policy.d.ts +1 -20
  170. package/dist/policy.js +29 -2
  171. package/dist/project-delete-ui.d.ts +18 -0
  172. package/dist/project-delete-ui.js +60 -9
  173. package/dist/project-delete.js +2 -2
  174. package/dist/project-memory.d.ts +3 -1
  175. package/dist/project-memory.js +57 -25
  176. package/dist/project-tools.d.ts +5 -3
  177. package/dist/project-tools.js +12 -9
  178. package/dist/provider-auth.d.ts +4 -3
  179. package/dist/provider-auth.js +5 -4
  180. package/dist/provider-connection.d.ts +2 -1
  181. package/dist/provider-connection.js +5 -0
  182. package/dist/provider.d.ts +25 -2
  183. package/dist/provider.js +63 -9
  184. package/dist/pull-request-ui.js +2 -2
  185. package/dist/push.js +1 -1
  186. package/dist/quick-start.d.ts +24 -0
  187. package/dist/quick-start.js +127 -0
  188. package/dist/release-adapters.d.ts +6 -0
  189. package/dist/release-adapters.js +70 -3
  190. package/dist/release.d.ts +10 -1
  191. package/dist/release.js +34 -15
  192. package/dist/request-budget-ui.js +2 -2
  193. package/dist/result-follow-ups.js +3 -2
  194. package/dist/result-review.d.ts +1 -1
  195. package/dist/result-review.js +9 -3
  196. package/dist/retention-ui.js +2 -2
  197. package/dist/retention.js +10 -1
  198. package/dist/run-efficiency.d.ts +73 -0
  199. package/dist/run-efficiency.js +228 -0
  200. package/dist/runner.d.ts +5 -1
  201. package/dist/runner.js +22 -1
  202. package/dist/scope.d.ts +183 -5
  203. package/dist/scope.js +256 -31
  204. package/dist/scout-net.d.ts +4 -1
  205. package/dist/scout-net.js +37 -12
  206. package/dist/scout.d.ts +20 -6
  207. package/dist/scout.js +27 -3
  208. package/dist/serve.js +248 -40
  209. package/dist/server/chat.d.ts +1 -1
  210. package/dist/server/chat.js +122 -31
  211. package/dist/server/chrome.d.ts +16 -2
  212. package/dist/server/chrome.js +63 -11
  213. package/dist/server/console.css +107 -42
  214. package/dist/server/flows.js +38 -8
  215. package/dist/server/guards.d.ts +1 -0
  216. package/dist/server/guards.js +23 -9
  217. package/dist/server/http.d.ts +7 -0
  218. package/dist/server/http.js +3 -2
  219. package/dist/server/live.d.ts +34 -1
  220. package/dist/server/live.js +39 -17
  221. package/dist/server/pages-assets.js +5 -0
  222. package/dist/server/pages.js +90 -13
  223. package/dist/server/people-tokens.d.ts +3 -0
  224. package/dist/server/people-tokens.js +55 -17
  225. package/dist/server/remote-hooks.js +12 -6
  226. package/dist/server/remote.d.ts +2 -1
  227. package/dist/server/remote.js +14 -5
  228. package/dist/server/render-chat.d.ts +12 -3
  229. package/dist/server/render-chat.js +47 -16
  230. package/dist/server/render-pages.d.ts +32 -4
  231. package/dist/server/render-pages.js +83 -13
  232. package/dist/server/render-people.d.ts +3 -2
  233. package/dist/server/render-people.js +3 -2
  234. package/dist/server/render-results.d.ts +18 -2
  235. package/dist/server/render-results.js +37 -28
  236. package/dist/server/render-settings.js +6 -5
  237. package/dist/server/render-tasks.d.ts +35 -10
  238. package/dist/server/render-tasks.js +111 -27
  239. package/dist/server/request-context.d.ts +2 -0
  240. package/dist/server/request-context.js +2 -1
  241. package/dist/server/route-table.d.ts +3 -0
  242. package/dist/server/route-table.js +47 -5
  243. package/dist/server/runtime.d.ts +33 -2
  244. package/dist/server/session.d.ts +25 -1
  245. package/dist/server/session.js +18 -4
  246. package/dist/server/settings.js +142 -10
  247. package/dist/server/sweeps.d.ts +4 -0
  248. package/dist/server/sweeps.js +58 -0
  249. package/dist/server/task-views.d.ts +2 -1
  250. package/dist/server/task-views.js +96 -6
  251. package/dist/server/tasks.js +327 -78
  252. package/dist/server/terminals.d.ts +31 -0
  253. package/dist/server/terminals.js +365 -0
  254. package/dist/setup-guide.js +3 -2
  255. package/dist/slack-chat.js +4 -1
  256. package/dist/spend-ui.js +3 -3
  257. package/dist/spend.js +2 -1
  258. package/dist/steer-capabilities.d.ts +32 -0
  259. package/dist/steer-capabilities.js +60 -0
  260. package/dist/steer-controls.d.ts +14 -0
  261. package/dist/steer-controls.js +36 -0
  262. package/dist/steer-status.js +2 -2
  263. package/dist/storage-ui.js +4 -4
  264. package/dist/store-baseline.d.ts +1 -1
  265. package/dist/store-baseline.js +1 -1
  266. package/dist/store.d.ts +440 -15
  267. package/dist/store.js +1072 -52
  268. package/dist/subagent-desk.js +1 -1
  269. package/dist/summary.js +3 -0
  270. package/dist/task-source.d.ts +58 -0
  271. package/dist/task-source.js +93 -0
  272. package/dist/team-leads.js +1 -1
  273. package/dist/teams-chat.js +4 -1
  274. package/dist/teardown-git.d.ts +22 -0
  275. package/dist/teardown-git.js +136 -2
  276. package/dist/telegram-lead.d.ts +3 -0
  277. package/dist/telegram-lead.js +44 -7
  278. package/dist/telegram.d.ts +11 -2
  279. package/dist/telegram.js +51 -17
  280. package/dist/terminal-catalog.d.ts +11 -0
  281. package/dist/terminal-catalog.js +129 -0
  282. package/dist/text-limits.d.ts +2 -0
  283. package/dist/text-limits.js +2 -0
  284. package/dist/tool-broker.d.ts +14 -0
  285. package/dist/tool-broker.js +67 -2
  286. package/dist/toolroll-update-ui.js +2 -2
  287. package/dist/toolroll-update.js +13 -3
  288. package/dist/tools-ui.js +2 -2
  289. package/dist/unfenced-test-support.d.ts +2 -0
  290. package/dist/unfenced-test-support.js +11 -0
  291. package/dist/webhook-limits.d.ts +23 -0
  292. package/dist/webhook-limits.js +30 -0
  293. package/dist/webhook-readers.d.ts +56 -0
  294. package/dist/webhook-readers.js +138 -0
  295. package/dist/webhook-signing.d.ts +27 -0
  296. package/dist/webhook-signing.js +119 -0
  297. package/dist/work-index.d.ts +29 -0
  298. package/dist/work-index.js +16 -2
  299. package/dist/workspace-revision.js +3 -1
  300. package/dist/writes-mode.d.ts +9 -0
  301. package/dist/writes-mode.js +8 -0
  302. package/package.json +15 -2
@@ -218,38 +218,63 @@ export function reindexProjectMemory(store, repo) {
218
218
  insert.run('decision', String(row['id']), repo, 'project', String(row['claim']), String(row['why']));
219
219
  });
220
220
  }
221
- /** Index conversation messages incrementally: private threads under their person, team conversations under their id and each project they cover. */
221
+ /** Index conversation messages incrementally: private threads under their person, team conversations under their id and each project they cover.
222
+ * A message still waiting for a turn is not indexed: it is indexed when a turn takes it (the admitted-turn pass below,
223
+ * with the words the turn took), so a withdrawn, removed or edited waiting message never reaches search. */
222
224
  export function reindexConversations(store) {
223
- const key = 'memory-search:messages';
225
+ const key = 'memory-search:messages', admittedKey = 'memory-search:admitted';
224
226
  const cursor = store.serviceCursor(key);
225
- const rows = store.handle.prepare('SELECT m.id,m.thread,m.role,m.text,t.approver FROM lead_message m JOIN lead_thread t ON t.id=m.thread WHERE m.id>? ORDER BY m.id LIMIT 500').all(cursor);
226
- if (rows.length === 0)
227
- return;
227
+ const rows = store.handle.prepare('SELECT m.id,m.thread,m.role,m.text,m.pending,t.approver FROM lead_message m JOIN lead_thread t ON t.id=m.thread WHERE m.id>? ORDER BY m.id LIMIT 500').all(cursor);
228
228
  const insert = store.handle.prepare('INSERT INTO memory_search(kind,ref,repo,scope,title,body) VALUES (?,?,?,?,?,?)');
229
229
  const teamOf = store.handle.prepare('SELECT id, projects_json FROM team_conversation WHERE thread=?');
230
+ const indexOne = (row) => {
231
+ const id = Number(row['id']);
232
+ // Indexed exactly as stored (the search proves a hit against the stored words); blank means nothing to index.
233
+ const text = String(row['text']);
234
+ if (!text.trim() || text.length > 20_000)
235
+ return;
236
+ const team = teamOf.get(Number(row['thread']));
237
+ if (team) {
238
+ let projects = [];
239
+ try {
240
+ projects = JSON.parse(String(team['projects_json']));
241
+ }
242
+ catch {
243
+ projects = [];
244
+ }
245
+ for (const repo of projects.length ? projects : [''])
246
+ insert.run('conversation', `message:${id}`, repo, `conversation:${String(team['id'])}`, `${row['role']} in a team conversation`, text);
247
+ }
248
+ else
249
+ insert.run('conversation', `message:${id}`, '', `approver:${String(row['approver'])}`, `${row['role']} in a private chat`, text);
250
+ };
230
251
  store.transact(() => {
231
252
  let last = cursor;
232
253
  for (const row of rows) {
233
254
  last = Number(row['id']);
234
- const text = String(row['text']).trim();
235
- if (!text || text.length > 20_000)
255
+ if (Number(row['pending']) === 1)
236
256
  continue;
237
- const team = teamOf.get(Number(row['thread']));
238
- if (team) {
239
- let projects = [];
240
- try {
241
- projects = JSON.parse(String(team['projects_json']));
242
- }
243
- catch {
244
- projects = [];
245
- }
246
- for (const repo of projects.length ? projects : [''])
247
- insert.run('conversation', `message:${last}`, repo, `conversation:${String(team['id'])}`, `${row['role']} in a team conversation`, text);
248
- }
249
- else
250
- insert.run('conversation', `message:${last}`, '', `approver:${String(row['approver'])}`, `${row['role']} in a private chat`, text);
257
+ indexOne(row);
258
+ }
259
+ if (rows.length > 0)
260
+ store.setServiceCursor(key, last, new Date());
261
+ // Messages that waited and were then admitted (a personal queue row or a team room message bound to a turn since the
262
+ // last pass): one the walk passed while it waited (at or below the cursor this pass started from) is indexed now,
263
+ // with the words its turn took. One past it is left to the walk, so nothing is indexed twice. Found by the turn
264
+ // indexes on both queues: only queued messages are visited, never every turn. (After an update from before v128, a
265
+ // team message already indexed when it was saved gains a second copy once; search counts each entry once.)
266
+ const turnCursor = store.serviceCursor(admittedKey);
267
+ const admitted = store.handle.prepare(`SELECT message, turn FROM (SELECT message, turn_id AS turn FROM lead_queue WHERE turn_id > ?
268
+ UNION ALL SELECT message, turn_id AS turn FROM team_message WHERE turn_id > ?) ORDER BY turn LIMIT 500`).all(turnCursor, turnCursor);
269
+ if (admitted.length === 0)
270
+ return;
271
+ const taken = store.handle.prepare("SELECT m.id,m.thread,m.role,m.text,t.approver FROM lead_message m JOIN lead_thread t ON t.id=m.thread WHERE m.id=? AND m.pending=0 AND m.id<=?");
272
+ for (const one of admitted) {
273
+ const row = taken.get(Number(one['message']), cursor);
274
+ if (row !== undefined)
275
+ indexOne(row);
251
276
  }
252
- store.setServiceCursor(key, last, new Date());
277
+ store.setServiceCursor(admittedKey, Number(admitted.at(-1)['turn']), new Date());
253
278
  });
254
279
  }
255
280
  const ftsQuery = (query) => {
@@ -269,11 +294,18 @@ export function searchMemory(store, args) {
269
294
  reindexProjectMemory(store, repo);
270
295
  const conversations = store.handle.prepare('SELECT conversation FROM team_participant WHERE account=? AND active=1').all(args.actor).map(row => `conversation:${String(row['conversation'])}`);
271
296
  const scopes = ['project', `approver:${args.actor}`, ...conversations];
297
+ const limit = Math.min(50, Math.max(1, args.limit ?? 12));
272
298
  const match = ftsQuery(query);
273
299
  if (!match)
274
300
  return [];
275
301
  const rows = store.handle.prepare(`SELECT kind, ref, repo, scope, title, snippet(memory_search, 5, '[', ']', '…', 24) AS snippet FROM memory_search
276
- WHERE memory_search MATCH ? AND scope IN (SELECT value FROM json_each(?)) AND (repo='' OR repo IN (SELECT value FROM json_each(?))) ORDER BY bm25(memory_search) LIMIT ?`)
277
- .all(match, JSON.stringify(scopes), JSON.stringify(repos), Math.min(50, Math.max(1, args.limit ?? 12)));
278
- return rows.map(row => ({ kind: row['kind'], ref: String(row['ref']), repo: String(row['repo']), title: String(row['title']).slice(0, 200), snippet: String(row['snippet']).slice(0, 400) }));
302
+ WHERE memory_search MATCH ? AND scope IN (SELECT value FROM json_each(?)) AND (repo='' OR repo IN (SELECT value FROM json_each(?)))
303
+ AND (kind<>'conversation' OR EXISTS (SELECT 1 FROM lead_message m WHERE m.id=CAST(substr(memory_search.ref, 9) AS INTEGER) AND m.pending=0
304
+ AND (m.text = memory_search.body OR trim(m.text, ' '||char(9)||char(10)||char(13)) = memory_search.body)))
305
+ ORDER BY bm25(memory_search) LIMIT ?`)
306
+ .all(match, JSON.stringify(scopes), JSON.stringify(repos), limit * 2);
307
+ // One hit per entry and place: a message indexed before it waited and again when its turn took it counts once.
308
+ const seen = new Set();
309
+ return rows.filter(row => { const key = `${row['kind']}\u0000${row['ref']}\u0000${row['repo']}\u0000${row['scope']}`; return !seen.has(key) && seen.add(key) !== undefined; }).slice(0, limit)
310
+ .map(row => ({ kind: row['kind'], ref: String(row['ref']), repo: String(row['repo']), title: String(row['title']).slice(0, 200), snippet: String(row['snippet']).slice(0, 400) }));
279
311
  }
@@ -1,6 +1,7 @@
1
1
  import type { Store, ToolActionInfo } from "./store.js";
2
2
  import { type BrokerOptions } from "./tool-broker.js";
3
3
  import type { FenceMethod } from "./agent-fence.js";
4
+ import type { ProviderId } from "./contracts/provider.js";
4
5
  import { type SecretStoreKind } from "./tool-secret-store.js";
5
6
  import { type ToolSecret, type ToolSpec } from "./contracts/integration-metadata.js";
6
7
  export type { ToolSecret, ToolSpec };
@@ -169,6 +170,7 @@ export type ToolLaunchArgs = {
169
170
  cleanup: () => void;
170
171
  privateDir?: string;
171
172
  serverPids?: () => number[];
173
+ brokerPort?: number;
172
174
  };
173
175
  /** How a launch's tools are offered: research steps' servers only their read-only actions. */
174
176
  export type ToolLaunchOptions = {
@@ -222,13 +224,13 @@ export type ToolContainment = {
222
224
  * read (OpenRouter uses the same harness); Claude's own file rules and Linux's bubblewrap don't stop it, and on Linux
223
225
  * hidepid hides nothing from the same user, so Linux never starts one.
224
226
  */
225
- export declare function secretToolRefusal(provider: "claude" | "codex" | "openrouter" | "gemini", containment: ToolContainment): string | null;
227
+ export declare function secretToolRefusal(provider: ProviderId, containment: ToolContainment): string | null;
226
228
  /**
227
229
  * One attempt's tools, ready for its provider, and recorded on the run:
228
230
  * what it launched with and what was left out and why. The caller spawns
229
231
  * with `argv`/`env` and calls `cleanup` when the process is done.
230
232
  */
231
- export declare function prepareRunTools(store: Pick<Store, "projectTools" | "getRun" | "refById" | "getScope" | "toolSealFor" | "recordRunTools" | "orgPolicy">, runId: number, provider: "claude" | "codex" | "openrouter" | "gemini", options: {
233
+ export declare function prepareRunTools(store: Pick<Store, "projectTools" | "getRun" | "refById" | "getScope" | "toolSealFor" | "recordRunTools" | "orgPolicy">, runId: number, provider: ProviderId, options: {
232
234
  home?: string;
233
235
  now: Date;
234
236
  containment: ToolContainment;
@@ -241,7 +243,7 @@ export declare function prepareRunTools(store: Pick<Store, "projectTools" | "get
241
243
  export declare class RunSignInError extends Error {
242
244
  }
243
245
  /** When a launch's tools cannot be prepared, it still gets none of anything else's: the same isolation with an empty list. */
244
- export declare function noToolsArgs(provider: "claude" | "codex" | "openrouter" | "gemini"): ToolLaunchArgs;
246
+ export declare function noToolsArgs(provider: ProviderId): ToolLaunchArgs;
245
247
  type ToolWrites = Pick<Store, "projectTools" | "addProjectTool" | "removeProjectTool" | "recordProjectToolTest">;
246
248
  /** Add one tool (and, for an import, the values it brings) to a project's builds. */
247
249
  export declare function addToolTo(store: ToolWrites, repo: string, input: ToolSpec, source: string, by: string, now: Date, options?: {
@@ -29,6 +29,7 @@ import { fileURLToPath } from "node:url";
29
29
  import { scanForSecrets } from "./evidence.js";
30
30
  import { namedPath } from "./names.js";
31
31
  import { startToolBroker } from "./tool-broker.js";
32
+ import { isAcpProvider } from "./acp-agents.js";
32
33
  import { SecretStoreError, destroyOwnFile, prepareSecretFolder, registerSecretFolder, pathIn, readOwnFile, readSealed, removeValue, secretBackend, secretFolder, secretStorage, serviceOf, withSecretLock, withSecretLockAsync, writeOwnFile, writeSealed } from "./tool-secret-store.js";
33
34
  import { ACCESS, GRANTED, REFRESH, mintRunToken, mintsRunTokens, oneAtATime, runScope, scopeList, signedIn } from "./oauth-run-token.js";
34
35
  import { HEADER_NAME, SECRET_NAME, TOOL_NAME as NAME, TOOL_SPEC_LIMITS as LIMITS, readToolSpec } from "./contracts/integration-metadata.js";
@@ -710,7 +711,7 @@ export async function claudeToolArgs(launch, options = {}) {
710
711
  omitEnv: [],
711
712
  cleanup,
712
713
  privateDir: dir,
713
- ...(broker === null ? {} : { serverPids: broker.pids }),
714
+ ...(broker === null ? {} : { serverPids: broker.pids, brokerPort: broker.port }),
714
715
  };
715
716
  }
716
717
  /** Where the tool launcher lives beside this module, built. */
@@ -749,7 +750,7 @@ export async function codexToolArgs(launch, options = {}) {
749
750
  if (enabled !== undefined)
750
751
  argv.push("-c", `${key}.enabled_tools=${tomlList(enabled)}`);
751
752
  }
752
- return { argv, env: {}, omitEnv: [], cleanup, privateDir: dir, serverPids: broker.pids };
753
+ return { argv, env: {}, omitEnv: [], cleanup, privateDir: dir, serverPids: broker.pids, brokerPort: broker.port };
753
754
  }
754
755
  /** Gemini: no way yet to hand it exactly these servers, so it gets none. */
755
756
  export const GEMINI_NO_TOOLS_ARGV = ["--allowed-mcp-server-names", "standing-orders-none"];
@@ -846,8 +847,9 @@ export function secretToolRefusal(provider, containment) {
846
847
  export async function prepareRunTools(store, runId, provider, options) {
847
848
  const launch = limitedTo(toolLaunchFor(store, runId, options.home), options.readOnly, options.withheld);
848
849
  // A local server with secrets starts only where the build can't read its environment; the rest are left out,
849
- // saying why, before the broker exists. Gemini starts none (below).
850
- const refusal = provider === "gemini" ? null : secretToolRefusal(provider, options.containment);
850
+ // saying why, before the broker exists. Gemini and ACP agents start none (below).
851
+ const toolless = provider === "gemini" || isAcpProvider(provider);
852
+ const refusal = toolless ? null : secretToolRefusal(provider, options.containment);
851
853
  if (refusal !== null) {
852
854
  launch.tools = launch.tools.filter(one => {
853
855
  if (one.spec.transport === "http" || Object.keys(one.values).length === 0)
@@ -856,14 +858,15 @@ export async function prepareRunTools(store, runId, provider, options) {
856
858
  return false;
857
859
  });
858
860
  }
859
- const revokes = provider === "gemini" ? [] : await signInRun(launch, { readOnly: options.readOnly !== undefined, now: options.now, ...(options.home === undefined ? {} : { home: options.home }), ...(options.fetcher === undefined ? {} : { fetcher: options.fetcher }) });
861
+ const revokes = toolless ? [] : await signInRun(launch, { readOnly: options.readOnly !== undefined, now: options.now, ...(options.home === undefined ? {} : { home: options.home }), ...(options.fetcher === undefined ? {} : { fetcher: options.fetcher }) });
860
862
  const revoke = () => Promise.all(revokes.splice(0).map(one => one())).then(() => undefined);
861
863
  const offered = { ...(options.readOnly === undefined ? {} : { enabledTools: options.readOnly }), ...(options.broker === undefined ? {} : { broker: options.broker }) };
862
864
  let launched;
863
865
  try {
864
866
  launched = provider === "claude" ? await claudeToolArgs(launch, offered)
865
867
  : provider === "codex" || provider === "openrouter" ? await codexToolArgs(launch, offered)
866
- : { argv: [...GEMINI_NO_TOOLS_ARGV], env: {}, omitEnv: [], cleanup: () => undefined };
868
+ : provider === "gemini" ? { argv: [...GEMINI_NO_TOOLS_ARGV], env: {}, omitEnv: [], cleanup: () => undefined }
869
+ : { argv: [], env: {}, omitEnv: [], cleanup: () => undefined };
867
870
  }
868
871
  catch (error) {
869
872
  // Tokens were made for a run that won't start: each is revoked, and the run with them.
@@ -874,13 +877,13 @@ export async function prepareRunTools(store, runId, provider, options) {
874
877
  }
875
878
  // The run's own sign-ins end with it: the broker closes first, so nothing uses a token as it is revoked.
876
879
  const prepared = revokes.length === 0 ? launched : { ...launched, cleanup: () => { launched.cleanup(); void revoke(); } };
877
- const skipped = provider === "gemini"
878
- ? [...launch.skipped, ...launch.tools.map(one => ({ name: one.spec.name, reason: "Gemini builds can't use project tools yet" }))]
880
+ const skipped = toolless
881
+ ? [...launch.skipped, ...launch.tools.map(one => ({ name: one.spec.name, reason: provider === "gemini" ? "Gemini builds can't use project tools yet" : "ACP agents can't use project tools yet" }))]
879
882
  : launch.skipped;
880
883
  try {
881
884
  store.recordRunTools(runId, JSON.stringify({
882
885
  provider,
883
- tools: provider === "gemini" ? [] : launch.tools.map(one => ({ name: one.spec.name, digest: one.digest })),
886
+ tools: toolless ? [] : launch.tools.map(one => ({ name: one.spec.name, digest: one.digest })),
884
887
  skipped,
885
888
  }), options.now);
886
889
  }
@@ -14,16 +14,17 @@
14
14
  */
15
15
  import type { Store } from "./store.js";
16
16
  import { type ProviderId } from "./provider.js";
17
+ import type { AuthMode } from "./keys.js";
17
18
  /** Every sign-in message's dedupe key starts here: `signin:<kind>:<provider>:<ordinal>:<person>`.
18
19
  * Not a lifecycle (`life:`) key: those are progress chatter, and an undelivered
19
20
  * one stays out of the attention list — this one needs a person. */
20
21
  export declare const SIGN_IN_KEY_PREFIX = "signin:";
21
- export declare const PROVIDER_AUTH_SCHEMA = "\nCREATE TABLE IF NOT EXISTS provider_auth_pause (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n ordinal INTEGER NOT NULL,\n auth_mode TEXT NOT NULL DEFAULT 'subscription' CHECK (auth_mode IN ('subscription', 'api-key')),\n opened_at TEXT NOT NULL,\n first_run INTEGER,\n task_ref INTEGER,\n runs INTEGER NOT NULL DEFAULT 1,\n -- 1 once a sign-in probe during this pause said the provider is signed out:\n -- only a probe that then sees it signed in again may lift the pause.\n probe_saw_out INTEGER NOT NULL DEFAULT 0,\n lifted_at TEXT,\n lifted_by TEXT,\n lifted_how TEXT CHECK (lifted_how IS NULL OR lifted_how IN ('run', 'probe', 'person')),\n resumed INTEGER,\n -- The last task let through as a trial while paused: a sign-in check can\n -- say \"logged in\" for a session that no longer works, so a real run decides.\n last_trial_at TEXT,\n UNIQUE (provider, ordinal)\n);\nCREATE UNIQUE INDEX IF NOT EXISTS provider_auth_pause_open ON provider_auth_pause (provider) WHERE lifted_at IS NULL;\n";
22
+ export declare const PROVIDER_AUTH_SCHEMA = "\nCREATE TABLE IF NOT EXISTS provider_auth_pause (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n ordinal INTEGER NOT NULL,\n auth_mode TEXT NOT NULL DEFAULT 'subscription' CHECK (auth_mode IN ('subscription', 'api-key', 'agent-login')),\n opened_at TEXT NOT NULL,\n first_run INTEGER,\n task_ref INTEGER,\n runs INTEGER NOT NULL DEFAULT 1,\n -- 1 once a sign-in probe during this pause said the provider is signed out:\n -- only a probe that then sees it signed in again may lift the pause.\n probe_saw_out INTEGER NOT NULL DEFAULT 0,\n lifted_at TEXT,\n lifted_by TEXT,\n lifted_how TEXT CHECK (lifted_how IS NULL OR lifted_how IN ('run', 'probe', 'person')),\n resumed INTEGER,\n -- The last task let through as a trial while paused: a sign-in check can\n -- say \"logged in\" for a session that no longer works, so a real run decides.\n last_trial_at TEXT,\n UNIQUE (provider, ordinal)\n);\nCREATE UNIQUE INDEX IF NOT EXISTS provider_auth_pause_open ON provider_auth_pause (provider) WHERE lifted_at IS NULL;\n";
22
23
  export type AuthPause = {
23
24
  id: number;
24
25
  provider: ProviderId;
25
26
  ordinal: number;
26
- authMode: "subscription" | "api-key";
27
+ authMode: AuthMode;
27
28
  openedAt: string;
28
29
  firstRun: number | null;
29
30
  taskRef: number | null;
@@ -63,7 +64,7 @@ export declare function openAuthPauses(store: Store): AuthPause[];
63
64
  */
64
65
  export declare function pauseForAuth(store: Store, args: {
65
66
  provider: string;
66
- authMode: "subscription" | "api-key" | null;
67
+ authMode: AuthMode | null;
67
68
  runId: number;
68
69
  taskRef: number;
69
70
  now: Date;
@@ -1,4 +1,5 @@
1
1
  import { PROVIDER_IDS } from "./provider.js";
2
+ import { withAcp } from "./acp-agents.js";
2
3
  /** Every sign-in message's dedupe key starts here: `signin:<kind>:<provider>:<ordinal>:<person>`.
3
4
  * Not a lifecycle (`life:`) key: those are progress chatter, and an undelivered
4
5
  * one stays out of the attention list — this one needs a person. */
@@ -8,7 +9,7 @@ CREATE TABLE IF NOT EXISTS provider_auth_pause (
8
9
  id INTEGER PRIMARY KEY AUTOINCREMENT,
9
10
  provider TEXT NOT NULL,
10
11
  ordinal INTEGER NOT NULL,
11
- auth_mode TEXT NOT NULL DEFAULT 'subscription' CHECK (auth_mode IN ('subscription', 'api-key')),
12
+ auth_mode TEXT NOT NULL DEFAULT 'subscription' CHECK (auth_mode IN ('subscription', 'api-key', 'agent-login')),
12
13
  opened_at TEXT NOT NULL,
13
14
  first_run INTEGER,
14
15
  task_ref INTEGER,
@@ -27,8 +28,8 @@ CREATE TABLE IF NOT EXISTS provider_auth_pause (
27
28
  );
28
29
  CREATE UNIQUE INDEX IF NOT EXISTS provider_auth_pause_open ON provider_auth_pause (provider) WHERE lifted_at IS NULL;
29
30
  `;
30
- const NAMES = { claude: "Claude", codex: "Codex", openrouter: "OpenRouter", gemini: "Gemini" };
31
- const SIGN_IN = { claude: "claude /login", codex: "codex login", gemini: "gemini", openrouter: "toolroll keys set openrouter" };
31
+ const NAMES = withAcp({ claude: "Claude", codex: "Codex", openrouter: "OpenRouter", gemini: "Gemini" }, agent => agent.name);
32
+ const SIGN_IN = withAcp({ claude: "claude /login", codex: "codex login", gemini: "gemini", openrouter: "toolroll keys set openrouter" }, agent => agent.signIn);
32
33
  export function providerName(provider) {
33
34
  return PROVIDER_IDS.includes(provider) ? NAMES[provider] : provider;
34
35
  }
@@ -57,7 +58,7 @@ function readPause(row) {
57
58
  id: Number(row["id"]),
58
59
  provider: String(row["provider"]),
59
60
  ordinal: Number(row["ordinal"]),
60
- authMode: row["auth_mode"] === "api-key" ? "api-key" : "subscription",
61
+ authMode: row["auth_mode"] === "api-key" ? "api-key" : row["auth_mode"] === "agent-login" ? "agent-login" : "subscription",
61
62
  openedAt: String(row["opened_at"]),
62
63
  firstRun: row["first_run"] == null ? null : Number(row["first_run"]),
63
64
  taskRef: row["task_ref"] == null ? null : Number(row["task_ref"]),
@@ -1,5 +1,6 @@
1
1
  import { run, type ExecResult } from "./exec.js";
2
2
  import { type ProviderId } from "./provider.js";
3
+ import { type KeyedProviderId } from "./acp-agents.js";
3
4
  import { type AuthMode, type KeyVerdict } from "./keys.js";
4
5
  export type ProviderConnection = {
5
6
  state: "connected" | "signed-out" | "not-installed" | "unverified" | "key-present" | "key-works" | "key-refused" | "missing-key";
@@ -18,5 +19,5 @@ export declare function createConnectionChecker(options?: {
18
19
  home?: string;
19
20
  clock?: () => Date;
20
21
  env?: NodeJS.ProcessEnv;
21
- verify?: (provider: ProviderId, key: string) => Promise<KeyVerdict>;
22
+ verify?: (provider: KeyedProviderId, key: string) => Promise<KeyVerdict>;
22
23
  }): ConnectionChecker;
@@ -2,6 +2,7 @@
2
2
  import { homedir } from "node:os";
3
3
  import { run } from "./exec.js";
4
4
  import { ALL_CREDENTIAL_ENV, inspectionOf } from "./provider.js";
5
+ import { isAcpProvider } from "./acp-agents.js";
5
6
  import { keyStatus, PROVIDER_KEY_ENV, readAuthMode, readProviderKey, verifyProviderKey } from "./keys.js";
6
7
  export function signInFacts(provider, result, now = new Date()) {
7
8
  const base = { mode: "subscription", checkedAt: now.toISOString() };
@@ -46,6 +47,10 @@ export function createConnectionChecker(options = {}) {
46
47
  const cache = new Map();
47
48
  const pending = new Map();
48
49
  return async (provider, fresh = false) => {
50
+ // An ACP agent is never run to ask how it is signed in (nothing executes it outside the gateway): what is known
51
+ // of it is its conformance check, read elsewhere. Here it is simply unverified.
52
+ if (isAcpProvider(provider))
53
+ return { state: "unverified", mode: "agent-login", checkedAt: clock().toISOString() };
49
54
  const mode = readAuthMode(provider, home);
50
55
  const keyFacts = keyStatus(provider, home);
51
56
  const ambient = !!(options.env ?? process.env)[PROVIDER_KEY_ENV[provider]];
@@ -175,6 +175,29 @@ export type ParsedEnvelope = {
175
175
  * failure terminal seen.
176
176
  */
177
177
  structuralTerminal: import("./exhaustion.js").StructuralTerminal | null;
178
+ /** What an ACP agent itself reported it used — kept on the run, never priced. Absent for every other dialect. */
179
+ agentReportedUsage?: string | null;
180
+ /** An ACP conversation's facts the conformance check reads (acp-client.ts AcpEnvelope): nothing else does. */
181
+ acp?: {
182
+ offered: string[];
183
+ exit: {
184
+ how: "eof" | "term" | "kill" | "self" | "error";
185
+ leftover: boolean;
186
+ timedOut: boolean;
187
+ } | null;
188
+ agent: {
189
+ name: string;
190
+ version: string;
191
+ } | null;
192
+ stop: string | null;
193
+ /** The conversation broke the protocol (acpParse's protocolError), or null. */
194
+ protocolError: string | null;
195
+ /** The plane's isolation probes for a check's first step (invoke.ts), when it ran them. */
196
+ isolation?: {
197
+ fileDenied: boolean;
198
+ envClear: boolean;
199
+ };
200
+ };
178
201
  };
179
202
  type Adapter = {
180
203
  binary: string;
@@ -266,13 +289,13 @@ export declare function adapterFor(provider: ProviderId): Adapter;
266
289
  */
267
290
  export type ProviderAudit = {
268
291
  /** How output reaches us — and therefore which signals can exist at all. */
269
- transport: "buffered-json" | "streaming-jsonl";
292
+ transport: "buffered-json" | "streaming-jsonl" | "acp-jsonrpc";
270
293
  /** Whether a later invocation can resume this provider's session. */
271
294
  resume: "native" | "none";
272
295
  /** The event whose absence on a failed run means "never initialized".
273
296
  * CAPABILITY metadata, not a per-run observation (arc 1 finding 16):
274
297
  * whether a given run actually saw it lives in ParsedEnvelope.initObserved. */
275
- initSignal: "thread.started" | "system-init" | "init-event" | "none";
298
+ initSignal: "thread.started" | "system-init" | "init-event" | "acp-session" | "none";
276
299
  /** How session identity is established: "announced" = read back from the
277
300
  * harness's own stream; "minted" = the PLANE chooses the id pre-spawn
278
301
  * (gemini --session-id) and the envelope must echo it (Phase 3 A5). */
package/dist/provider.js CHANGED
@@ -18,13 +18,16 @@
18
18
  * TOML-quoted deterministically, and the API key excluded from every
19
19
  * shell the model itself launches (Codex provider review, Q5).
20
20
  */
21
+ import { INTERPRETER_INJECTION_ENV } from "./acp-attest.js";
21
22
  import { claudeFenceSettings, codexFenceArgv } from "./agent-fence.js";
22
23
  import { claudeSteerHooks } from "./steer-mailbox.js";
23
24
  import {} from "./exec.js";
24
- import { runStreamJsonl, runClaudeStreamJsonl, runGeminiStreamJsonl, CLAUDE_COST_UNKNOWN } from "./exec.js";
25
+ import { runStreamJsonl, runClaudeStreamJsonl, runGeminiStreamJsonl, runAcpSession, CLAUDE_COST_UNKNOWN } from "./exec.js";
25
26
  import { scanForSecrets } from "./evidence.js";
26
27
  import { FINDINGS_MODEL_SCHEMA } from "./contracts/review-findings.js";
27
28
  import { REVIEW_OUTPUT_LIMITS } from "./structured-output.js";
29
+ import { ACP_AGENTS, isAcpProvider, withAcp } from "./acp-agents.js";
30
+ import { acpParse } from "./acp-client.js";
28
31
  import { agentSpecSchema, MODEL_ID, modelIdSchema, PROVIDER_ID_VALUES, providerIdSchema } from "./contracts/provider.js";
29
32
  export const PROVIDER_IDS = PROVIDER_ID_VALUES;
30
33
  export function isProviderId(value) {
@@ -787,12 +790,13 @@ export function safeDiagnostic(text) {
787
790
  * provider gets its OWN keys and nobody else's: S4 (live spike) confirmed
788
791
  * what inheritance makes true by construction — an agent's shell reads its
789
792
  * process env, so a foreign key in that env is a foreign key disclosed. */
790
- const CREDENTIAL_ENV = {
793
+ const CREDENTIAL_ENV = withAcp({
791
794
  claude: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"],
792
795
  codex: ["OPENAI_API_KEY", "CODEX_API_KEY"],
793
796
  openrouter: [OPENROUTER_ENV_KEY],
794
797
  gemini: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
795
- };
798
+ // An ACP agent signs in its own way; Toolroll holds no key for it, so it sheds every key above.
799
+ }, () => []);
796
800
  const foreignCredentialEnv = (own) => Object.entries(CREDENTIAL_ENV)
797
801
  .filter(([provider]) => provider !== own)
798
802
  .flatMap(([, keys]) => [...keys]);
@@ -800,7 +804,40 @@ const foreignCredentialEnv = (own) => Object.entries(CREDENTIAL_ENV)
800
804
  * since a feature check needs no key at all (Codex gemini verify,
801
805
  * finding 2). Agent spawns keep their own; probes keep none. */
802
806
  export const ALL_CREDENTIAL_ENV = Object.values(CREDENTIAL_ENV).flat();
803
- const ADAPTERS = {
807
+ /**
808
+ * An ACP agent's retained record (acp-client.ts) read into the gateway's
809
+ * envelope. Its own usage report is never priced: tokens and cost stay null.
810
+ */
811
+ function acpEnvelope(stdout) {
812
+ const read = acpParse(stdout);
813
+ return {
814
+ sessionId: read.sessionId,
815
+ protocolError: read.protocolError,
816
+ finalMessage: read.finalMessage,
817
+ tokensIn: null,
818
+ tokensOut: null,
819
+ costUsd: null,
820
+ usageRaw: null,
821
+ initObserved: read.initObserved,
822
+ promptConsumed: read.promptConsumed,
823
+ diagnostic: read.diagnostic === null ? null : safeDiagnostic(read.diagnostic),
824
+ structuralTerminal: read.terminal === null ? null : { failed: true, text: read.terminal.text === null ? null : safeDiagnostic(read.terminal.text), code: read.terminal.code.slice(0, 128) },
825
+ agentReportedUsage: read.reportedUsage,
826
+ acp: { offered: read.offered, exit: read.exit, agent: read.agent, stop: read.stop, protocolError: read.protocolError },
827
+ };
828
+ }
829
+ function acpAdapter(provider) {
830
+ return {
831
+ binary: ACP_AGENTS[provider].binary,
832
+ argv: () => [...ACP_AGENTS[provider].argv],
833
+ parse: acpEnvelope,
834
+ defaultRunner: runAcpSession,
835
+ // Every key Toolroll knows, and whatever would make the pinned interpreter load code beside the checked script.
836
+ extraOmitEnv: [...foreignCredentialEnv(provider), ...INTERPRETER_INJECTION_ENV],
837
+ clampTimeout: (_phase, requested) => requested,
838
+ };
839
+ }
840
+ const ADAPTERS = withAcp({
804
841
  claude: {
805
842
  binary: "claude",
806
843
  argv: claudeArgv,
@@ -852,7 +889,7 @@ const ADAPTERS = {
852
889
  extraOmitEnv: foreignCredentialEnv("gemini"),
853
890
  clampTimeout: (phase, requested) => Math.min(requested, GEMINI_TIMEOUT_CAP_MS[phase]),
854
891
  },
855
- };
892
+ }, (_agent, provider) => acpAdapter(provider));
856
893
  /**
857
894
  * The spawning surface. Imported by invoke.ts and NOWHERE else — the
858
895
  * architecture test reads imports, not string literals, because provider
@@ -861,7 +898,7 @@ const ADAPTERS = {
861
898
  export function adapterFor(provider) {
862
899
  return ADAPTERS[provider];
863
900
  }
864
- const AUDITS = {
901
+ const AUDITS = withAcp({
865
902
  claude: {
866
903
  transport: "streaming-jsonl",
867
904
  resume: "native",
@@ -923,8 +960,17 @@ const AUDITS = {
923
960
  "trusted-folder store (S2, live at 0.57.0: headless REFUSES untrusted directories; the plane grants --skip-trust per-invocation to its own leased worktrees)",
924
961
  ],
925
962
  },
926
- };
927
- export const MONEY_CAPABILITIES = {
963
+ }, agent => ({
964
+ // One ACP conversation per build: the prompt's answer is the only success (acp-client.ts), so exit 0 alone is never believed.
965
+ transport: "acp-jsonrpc",
966
+ resume: "none",
967
+ initSignal: "acp-session",
968
+ sessionIdentity: "announced",
969
+ terminalContract: "required",
970
+ isolation: { flag: agent.argv.filter(one => one.startsWith("--") && one !== "--acp").join(" ") || null, resumeSafe: null, enforced: agent.argv.some(one => one.startsWith("--") && one !== "--acp") },
971
+ configSurface: [...agent.configSurface],
972
+ }));
973
+ export const MONEY_CAPABILITIES = withAcp({
928
974
  claude: {
929
975
  incrementalUsage: true,
930
976
  nativeDollarCapFlag: "--max-budget-usd",
@@ -947,7 +993,12 @@ export const MONEY_CAPABILITIES = {
947
993
  // numbers cover that invocation only (conformance fixture j).
948
994
  usageSemantics: "per-invocation",
949
995
  },
950
- };
996
+ }, () => ({
997
+ // ACP carries no dollars Toolroll trusts: an agent's own usage report is kept apart, unpriced.
998
+ incrementalUsage: false,
999
+ nativeDollarCapFlag: null,
1000
+ usageSemantics: "per-invocation",
1001
+ }));
951
1002
  /**
952
1003
  * The fail-closed budget-flag probe (finding 24's amendment): resolve
953
1004
  * the EXACT executable that will spawn, read its version, and prove the
@@ -997,6 +1048,9 @@ export function validateSpec(spec) {
997
1048
  if (spec.provider === "gemini" && spec.model === null) {
998
1049
  return { ok: false, problem: "model: gemini needs an explicit model — the harness default drifts with its releases" };
999
1050
  }
1051
+ if (isAcpProvider(spec.provider) && spec.model === null) {
1052
+ return { ok: false, problem: `model: ${ACP_AGENTS[spec.provider].name} needs an explicit model — one it offered when it was checked` };
1053
+ }
1000
1054
  return { ok: true };
1001
1055
  }
1002
1056
  /**
@@ -1,11 +1,11 @@
1
1
  /** Settings → Projects → Pull requests: one project's pull-request setup, in plain words. Off shows what setup
2
2
  * found (or what to fix) and one Turn on; on shows where pull requests go and how they merge. Every change takes
3
3
  * the password; turning on restates the exact repository and branch that were checked. */
4
- import { html, postForm } from "./html.js";
4
+ import { html, postForm, reauth } from "./html.js";
5
5
  const METHOD_WORDS = { squash: "Squash and merge", merge: "Merge commit", rebase: "Rebase and merge" };
6
6
  export function pullRequestSettingsHtml(view) {
7
7
  const post = (act, body, hidden = {}) => postForm("/settings/pull-requests", body, { attrs: { class: "card pr-settings" }, hidden: { repo: view.repo, act, ...hidden } });
8
- const password = html `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`;
8
+ const password = html `${reauth(html `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`)}`;
9
9
  const notes = [
10
10
  view.said === null ? "" : html `<p class="notice" role="status">${view.said}</p>`,
11
11
  view.problem === null ? "" : html `<p class="problem" role="alert">${view.problem}</p>`,
package/dist/push.js CHANGED
@@ -259,7 +259,7 @@ export async function pushPass(store, options) {
259
259
  // Renew and re-fence immediately before the network (finding 12).
260
260
  if (!store.renewPushClaim(pair.id, owner, pair.claimGeneration, PUSH_CLAIM_TTL_MS, clock()))
261
261
  continue;
262
- const fenced = store.pushSendFence(pair.id, owner, pair.claimGeneration);
262
+ const fenced = store.pushSendFence(pair.id, owner, pair.claimGeneration, clock());
263
263
  if (fenced === null)
264
264
  continue; // resolved, retired, rotated, or lost — nothing to send
265
265
  const subscription = fenced.subscriptionRow;
@@ -0,0 +1,24 @@
1
+ import { type AttachSeams } from "./console-instance.js";
2
+ export type QuickStartSeams = {
3
+ databaseFile: string;
4
+ cwd: string;
5
+ env: Record<string, string | undefined>;
6
+ build: string;
7
+ /** The repository's top folder for a directory, or null outside one. */
8
+ findRepo: (cwd: string) => Promise<string | null>;
9
+ /** Whether this installation already admitted the repository. */
10
+ enrolled: (repo: string) => Promise<boolean>;
11
+ /** The ordinary admission (`repos add`): its registry lock and ceilings, from this CLI's own database. */
12
+ admit: (repo: string) => Promise<number>;
13
+ /** `up`, for this repository. */
14
+ startUp: () => Promise<number>;
15
+ openBrowser: (url: string) => void | Promise<void>;
16
+ terminal: boolean;
17
+ attach?: AttachSeams;
18
+ /** Tests: the agent check (default: the environment and the process's ancestors). */
19
+ underAgent?: boolean;
20
+ };
21
+ /** Whether argv is a bare `toolroll` (nothing, or only --json). */
22
+ export declare const isBareInvocation: (argv: readonly string[]) => boolean;
23
+ /** null: not this command (outside a repository) — the caller scans as before. */
24
+ export declare function runQuickStart(argv: readonly string[], write: (line: string) => void, seams: QuickStartSeams): Promise<number | null>;