toolroll 0.9.65 → 0.9.68

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 (264) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +20 -2
  3. package/dist/acp-client.js +1 -3
  4. package/dist/agent-board.d.ts +0 -1
  5. package/dist/agent-board.js +0 -1
  6. package/dist/agent-fence.d.ts +69 -2
  7. package/dist/agent-fence.js +294 -11
  8. package/dist/agentconfig.js +4 -1
  9. package/dist/batch-checks.js +3 -2
  10. package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
  11. package/dist/browser/terminal.js +134 -23
  12. package/dist/browser/workspace.css +2 -2
  13. package/dist/browser/workspace.js +31 -29
  14. package/dist/builder.d.ts +4 -0
  15. package/dist/builder.js +56 -37
  16. package/dist/canonical-json.d.ts +14 -0
  17. package/dist/canonical-json.js +36 -0
  18. package/dist/chat-actions.js +1 -2
  19. package/dist/chat-admission.d.ts +19 -0
  20. package/dist/chat-admission.js +22 -0
  21. package/dist/chat-channel.js +1 -1
  22. package/dist/chat-delivery.js +0 -1
  23. package/dist/chat-follow.d.ts +17 -0
  24. package/dist/chat-follow.js +32 -0
  25. package/dist/chat-messages.js +0 -1
  26. package/dist/chat-settings-page.d.ts +23 -0
  27. package/dist/chat-settings-page.js +41 -0
  28. package/dist/claim.js +3 -6
  29. package/dist/claude-print.d.ts +12 -0
  30. package/dist/claude-print.js +34 -0
  31. package/dist/cli-http.d.ts +0 -7
  32. package/dist/cli-http.js +0 -21
  33. package/dist/cli.d.ts +3 -10
  34. package/dist/cli.js +15 -24
  35. package/dist/coding-provider.js +2 -1
  36. package/dist/coding-workspace.js +1 -1
  37. package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
  38. package/dist/connect-cli.js +331 -0
  39. package/dist/containment.d.ts +0 -2
  40. package/dist/containment.js +2 -6
  41. package/dist/contracts/chat-actions.d.ts +0 -1
  42. package/dist/contracts/chat-content.d.ts +0 -4
  43. package/dist/contracts/chat-content.js +0 -2
  44. package/dist/contracts/chat-tables.d.ts +0 -1
  45. package/dist/contracts/cli.d.ts +0 -62
  46. package/dist/contracts/cli.js +0 -4
  47. package/dist/contracts/coding-workspace.js +1 -1
  48. package/dist/contracts/console-api.d.ts +1 -0
  49. package/dist/contracts/console-api.js +1 -0
  50. package/dist/contracts/contract.js +1 -1
  51. package/dist/contracts/flow-card.d.ts +0 -1
  52. package/dist/contracts/flow-propose.d.ts +0 -1
  53. package/dist/contracts/flow-send.js +1 -1
  54. package/dist/contracts/gateway-tools.d.ts +0 -1
  55. package/dist/contracts/integration-metadata.js +1 -1
  56. package/dist/contracts/lead-tools.d.ts +0 -1
  57. package/dist/contracts/memory-pass.d.ts +0 -1
  58. package/dist/contracts/memory-pass.js +1 -1
  59. package/dist/contracts/plan.js +1 -1
  60. package/dist/contracts/project-knowledge.js +1 -1
  61. package/dist/contracts/project-skills.d.ts +0 -1
  62. package/dist/contracts/proof.js +1 -1
  63. package/dist/contracts/recipes.js +1 -1
  64. package/dist/contracts/scope.js +1 -1
  65. package/dist/contracts/scout-report.js +1 -1
  66. package/dist/contracts/teams-callback.d.ts +0 -1
  67. package/dist/converse.d.ts +0 -6
  68. package/dist/converse.js +0 -19
  69. package/dist/daemon.js +3 -2
  70. package/dist/decision.d.ts +1 -3
  71. package/dist/deprecations.d.ts +12 -4
  72. package/dist/deprecations.js +27 -4
  73. package/dist/desktop-update-recovery.js +1 -1
  74. package/dist/desktop-update.js +3 -2
  75. package/dist/discord-chat.js +4 -15
  76. package/dist/discord-settings.d.ts +2 -6
  77. package/dist/discord-settings.js +10 -38
  78. package/dist/discord.js +12 -23
  79. package/dist/evidence.d.ts +0 -1
  80. package/dist/evidence.js +0 -3
  81. package/dist/exec.d.ts +0 -9
  82. package/dist/exec.js +3 -81
  83. package/dist/flow-actions.js +0 -3
  84. package/dist/flow-draft.d.ts +1 -3
  85. package/dist/flow-draft.js +15 -28
  86. package/dist/flow-scripts.d.ts +1 -8
  87. package/dist/flow-scripts.js +0 -13
  88. package/dist/flow-send.js +1 -1
  89. package/dist/flow-share.d.ts +0 -2
  90. package/dist/flow-share.js +1 -6
  91. package/dist/flow-steps.js +1 -1
  92. package/dist/flow-triggers.d.ts +0 -1
  93. package/dist/flow-triggers.js +1 -2
  94. package/dist/flow-unattended.js +1 -12
  95. package/dist/flows.js +1 -1
  96. package/dist/health-cli.d.ts +2 -2
  97. package/dist/health-cli.js +1 -1
  98. package/dist/invoke.d.ts +18 -3
  99. package/dist/invoke.js +142 -17
  100. package/dist/is-record.d.ts +2 -0
  101. package/dist/is-record.js +2 -0
  102. package/dist/lead-doors.d.ts +1 -5
  103. package/dist/lead-doors.js +0 -4
  104. package/dist/lead-tools.js +3 -6
  105. package/dist/live-detail.d.ts +0 -1
  106. package/dist/live-detail.js +0 -6
  107. package/dist/live-terminal.d.ts +24 -13
  108. package/dist/live-terminal.js +81 -43
  109. package/dist/live.d.ts +0 -1
  110. package/dist/live.js +1 -2
  111. package/dist/mcp-core.js +0 -2
  112. package/dist/memory-pass.js +1 -1
  113. package/dist/model-catalog.js +1 -1
  114. package/dist/modes.js +1 -12
  115. package/dist/operate.d.ts +2 -0
  116. package/dist/operate.js +436 -745
  117. package/dist/password-bearer.d.ts +0 -2
  118. package/dist/password-bearer.js +0 -4
  119. package/dist/peek-cli.js +2 -1
  120. package/dist/peek.d.ts +2 -26
  121. package/dist/peek.js +2 -168
  122. package/dist/phase-routing.d.ts +0 -4
  123. package/dist/phase-routing.js +2 -21
  124. package/dist/planner-source.d.ts +2 -1
  125. package/dist/planner-source.js +2 -12
  126. package/dist/planner.js +2 -14
  127. package/dist/process-tree.d.ts +0 -2
  128. package/dist/process-tree.js +0 -10
  129. package/dist/project-delete-ui.d.ts +6 -0
  130. package/dist/project-delete-ui.js +20 -1
  131. package/dist/project-delete.d.ts +0 -2
  132. package/dist/project-delete.js +1 -3
  133. package/dist/project-learning.d.ts +0 -14
  134. package/dist/project-skills.d.ts +0 -1
  135. package/dist/project-skills.js +0 -14
  136. package/dist/project-tools.js +2 -10
  137. package/dist/provider.d.ts +0 -17
  138. package/dist/provider.js +0 -26
  139. package/dist/pulls.d.ts +0 -2
  140. package/dist/pulls.js +0 -9
  141. package/dist/release-adapters.d.ts +9 -0
  142. package/dist/release-adapters.js +81 -4
  143. package/dist/release.d.ts +7 -1
  144. package/dist/release.js +20 -7
  145. package/dist/remote-exec.d.ts +3 -2
  146. package/dist/remote-exec.js +3 -2
  147. package/dist/repos.d.ts +0 -5
  148. package/dist/repos.js +2 -10
  149. package/dist/repository-context.d.ts +0 -2
  150. package/dist/repository-context.js +0 -4
  151. package/dist/restart-certification.d.ts +0 -2
  152. package/dist/restart-certification.js +1 -5
  153. package/dist/result-follow-ups.d.ts +1 -4
  154. package/dist/result-follow-ups.js +4 -8
  155. package/dist/result-review.d.ts +0 -2
  156. package/dist/result-review.js +0 -2
  157. package/dist/retention.d.ts +0 -1
  158. package/dist/retention.js +0 -1
  159. package/dist/review-context.d.ts +0 -1
  160. package/dist/review-context.js +1 -12
  161. package/dist/reviewer.d.ts +0 -16
  162. package/dist/reviewer.js +0 -18
  163. package/dist/run-closeout.js +2 -1
  164. package/dist/scope.d.ts +13 -0
  165. package/dist/scope.js +11 -18
  166. package/dist/scout-browser.js +2 -1
  167. package/dist/serve.js +3 -2
  168. package/dist/server/chrome.d.ts +10 -1
  169. package/dist/server/chrome.js +45 -4
  170. package/dist/server/console.css +60 -137
  171. package/dist/server/flows.js +2 -2
  172. package/dist/server/http.d.ts +2 -0
  173. package/dist/server/http.js +5 -0
  174. package/dist/server/pages.js +2 -2
  175. package/dist/server/people-tokens.js +3 -4
  176. package/dist/server/remote.js +1 -6
  177. package/dist/server/render-chat.d.ts +9 -2
  178. package/dist/server/render-chat.js +53 -7
  179. package/dist/server/render-pages.d.ts +28 -3
  180. package/dist/server/render-pages.js +78 -9
  181. package/dist/server/render-results.d.ts +0 -6
  182. package/dist/server/render-results.js +3 -9
  183. package/dist/server/render-tasks.js +6 -6
  184. package/dist/server/route-table.d.ts +0 -4
  185. package/dist/server/route-table.js +1 -6
  186. package/dist/server/settings.js +43 -22
  187. package/dist/server/task-views.js +1 -1
  188. package/dist/server/tasks.js +10 -7
  189. package/dist/setup-guide.d.ts +2 -2
  190. package/dist/skills.d.ts +0 -6
  191. package/dist/skills.js +0 -8
  192. package/dist/slack-chat.d.ts +1 -7
  193. package/dist/slack-chat.js +5 -18
  194. package/dist/slack-settings.d.ts +2 -6
  195. package/dist/slack-settings.js +10 -38
  196. package/dist/slack.js +12 -23
  197. package/dist/sleep.d.ts +2 -0
  198. package/dist/sleep.js +2 -0
  199. package/dist/store.d.ts +11 -74
  200. package/dist/store.js +36 -179
  201. package/dist/structured-output.d.ts +0 -1
  202. package/dist/structured-output.js +0 -1
  203. package/dist/subagent-work.d.ts +0 -2
  204. package/dist/subagent-work.js +0 -2
  205. package/dist/subagents.d.ts +1 -3
  206. package/dist/subagents.js +20 -33
  207. package/dist/subscription-chat.d.ts +1 -3
  208. package/dist/subscription-chat.js +2 -3
  209. package/dist/task-sizing.d.ts +1 -4
  210. package/dist/task-sizing.js +3 -10
  211. package/dist/teams-chat.js +4 -11
  212. package/dist/teams.js +12 -19
  213. package/dist/teardown-git.d.ts +22 -0
  214. package/dist/teardown-git.js +136 -2
  215. package/dist/telegram-lead.js +2 -2
  216. package/dist/telegram.js +1 -1
  217. package/dist/token-notices.d.ts +0 -11
  218. package/dist/token-notices.js +1 -5
  219. package/dist/tool-broker.d.ts +12 -0
  220. package/dist/tool-broker.js +66 -2
  221. package/dist/tool-secret-store.d.ts +0 -3
  222. package/dist/tool-secret-store.js +2 -11
  223. package/dist/toolroll-update.d.ts +0 -2
  224. package/dist/toolroll-update.js +2 -2
  225. package/dist/unfenced-test-support.d.ts +2 -0
  226. package/dist/unfenced-test-support.js +11 -0
  227. package/dist/verification-evidence.d.ts +0 -1
  228. package/dist/verification-evidence.js +0 -1
  229. package/dist/webhooks.d.ts +0 -1
  230. package/dist/webhooks.js +0 -1
  231. package/dist/workspace-ui.d.ts +0 -8
  232. package/dist/workspace-ui.js +1 -6
  233. package/dist/writes-mode.d.ts +9 -0
  234. package/dist/writes-mode.js +8 -0
  235. package/package.json +5 -1
  236. package/dist/contracts/native-census.d.ts +0 -42
  237. package/dist/contracts/native-census.js +0 -36
  238. package/dist/process-recovery-anchor.d.ts +0 -75
  239. package/dist/process-recovery-anchor.js +0 -188
  240. package/dist/process-recovery-coalition.d.ts +0 -25
  241. package/dist/process-recovery-coalition.js +0 -105
  242. package/dist/process-recovery-eligibility.d.ts +0 -179
  243. package/dist/process-recovery-eligibility.js +0 -174
  244. package/dist/process-recovery-native.d.ts +0 -161
  245. package/dist/process-recovery-native.js +0 -595
  246. package/dist/process-recovery-provenance.d.ts +0 -163
  247. package/dist/process-recovery-provenance.js +0 -292
  248. package/dist/process-recovery-services.d.ts +0 -56
  249. package/dist/process-recovery-services.js +0 -191
  250. package/dist/process-recovery-settlement.d.ts +0 -32
  251. package/dist/process-recovery-settlement.js +0 -133
  252. package/dist/process-recovery.d.ts +0 -41
  253. package/dist/process-recovery.js +0 -91
  254. package/dist/session-brief.d.ts +0 -8
  255. package/dist/session-brief.js +0 -24
  256. package/dist/session-cli.d.ts +0 -42
  257. package/dist/session-cli.js +0 -271
  258. package/dist/session-contract.d.ts +0 -183
  259. package/dist/session-contract.js +0 -122
  260. package/dist/session-remote.d.ts +0 -19
  261. package/dist/session-remote.js +0 -86
  262. package/dist/session-service.d.ts +0 -22
  263. package/dist/session-service.js +0 -157
  264. package/dist/team-cli.js +0 -692
@@ -33,7 +33,6 @@ import { chatMoney, oneLineOf, relativeAge, safePrUrl, screen, sentenceCase, str
33
33
  import { chatResultHref, projectChatHref, taskChatHref, taskHref } from "./http.js";
34
34
  import {} from "./render-pages.js";
35
35
  import { completionReceiptCard } from "./render-results.js";
36
- import {} from "./render-settings.js";
37
36
  import { agentsAvailabilityHtml, agentsBadgesHtml, agentsStripHtml, agentsSummaryWords, agentsWhyHtml, approvalSheetHtml, checkProgressHtml, consentClosedHtml, consentDoorOf, decisionAnswerCard, dispatchHeadline, milestoneProgressHtml, planRevisionLedgerHtml, profileWords, sizeConsequence, taskControlDetailsHtml, taskStatusCard, taskViewSwitch } from "./render-tasks.js";
38
37
  /** The installation fact that the lead was turned on by default, once (its value: the provider). */
39
38
  export const LEAD_BY_DEFAULT_FACT = "lead-on-by-default";
@@ -260,8 +259,8 @@ export function chatHeading(copy, projectCount, showProjectToggle = true) {
260
259
  }
261
260
  /** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
262
261
  * no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
263
- * escaped before tags are introduced: bullets, numbered steps, bold, inline code and labelled http(s) links are
264
- * presentation only—never executable HTML. */
262
+ * escaped before tags are introduced: bullets, numbered steps, bold, inline code, fenced code blocks and labelled
263
+ * http(s) links are presentation only—never executable HTML. */
265
264
  export function renderChatText(raw, asked) {
266
265
  const inline = replyHtmlInline;
267
266
  const text = shapeReply(raw, { appOrigin: requestContext.getStore()?.appOrigins ?? null, ...(asked === undefined ? {} : { asked }) });
@@ -281,7 +280,31 @@ export function renderChatText(raw, asked) {
281
280
  out.push(list.tag === "ul" ? html `<ul>${list.items}</ul>` : html `<ol>${list.items}</ol>`);
282
281
  list = null;
283
282
  };
283
+ // A fenced block (shaping leaves it exactly as written) is shown as written: escaped, never parsed.
284
+ let fence = null;
285
+ const closeFence = () => {
286
+ const open = fence;
287
+ if (open === null)
288
+ return;
289
+ out.push(html `<pre class="chat-code"${open.lang === "" ? "" : html ` data-lang="${open.lang}"`}><code>${open.lines.join("\n")}</code></pre>`);
290
+ fence = null;
291
+ };
284
292
  for (const line of lines) {
293
+ const open = fence;
294
+ if (open !== null) {
295
+ if (/^\s{0,3}```\s*$/.test(line))
296
+ closeFence();
297
+ else
298
+ open.lines.push(line);
299
+ continue;
300
+ }
301
+ const fenceMark = /^\s{0,3}```\s*([A-Za-z0-9_+.#-]{0,24})\s*$/.exec(line);
302
+ if (fenceMark !== null) {
303
+ flushParagraph();
304
+ closeList();
305
+ fence = { lang: fenceMark[1], lines: [] };
306
+ continue;
307
+ }
285
308
  const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
286
309
  const numbered = /^\s*(\d{1,9})[.)]\s+(.+)$/.exec(line);
287
310
  if (bullet !== null || numbered !== null) {
@@ -304,10 +327,35 @@ export function renderChatText(raw, asked) {
304
327
  paragraph.push(line);
305
328
  }
306
329
  }
330
+ // An unclosed fence still shows its lines as code: the reply may have been cut.
307
331
  flushParagraph();
308
332
  closeList();
333
+ closeFence();
309
334
  return html `<div class="chat-copy">${out}</div>`;
310
335
  }
336
+ /** A line comment from the chat Workbench (src/browser/line-steer.tsx steerMessage): On "<file, as a JSON string>":<line>
337
+ * [ (removed line)] (run #<n>): <comment>, then a blank line and "> <the line>" (just ">" for a blank line, once the
338
+ * chat POST has trimmed the message), always last.
339
+ * The words the lead reads stay exactly as sent; the thread shows them in three parts: a small reference line, the quoted
340
+ * code as written, and the comment. Null for any other message, which keeps its own plain rendering. Everything is
341
+ * escaped by the html template. */
342
+ export function steerCommentHtml(text) {
343
+ const steer = /^On ("(?:[^"\\\n]|\\.){1,1200}"):(\d{1,9})( \(removed line\))? \(run #(\d{1,12})\): ([\s\S]+)\n\n>(?: ([^\n]*))?$/.exec(text);
344
+ if (steer === null)
345
+ return null;
346
+ // A blank line's quote ("> ") reaches the store as ">": the chat POST trims the message (src/server/chat.ts).
347
+ const [, quoted, line, removed, run, comment, code = ""] = steer;
348
+ let path;
349
+ try {
350
+ path = JSON.parse(quoted);
351
+ }
352
+ catch {
353
+ return null;
354
+ }
355
+ if (typeof path !== "string" || path === "" || /[\u0000-\u001f]/.test(path) || comment.trim() === "")
356
+ return null;
357
+ return html `<div class="chat-steer" data-steer-comment><p class="chat-steer-ref">${path}:${line}${removed === undefined ? "" : " · removed line"} · run #${run}</p>${code === "" ? "" : html `<pre class="chat-steer-code"><code>${code}</code></pre>`}<p class="chat-steer-note" style="white-space:pre-wrap">${comment}</p></div>`;
358
+ }
311
359
  export function chatActivity(activity) {
312
360
  return chatActivityDetailsHtml(activity);
313
361
  }
@@ -351,8 +399,6 @@ export function chatLimitsHtml(facts) {
351
399
  return html `<details class="chat-limits"><summary>Model &amp; limits<span class="meta">${facts.subscription ? "membership" : facts.provider}</span></summary><div class="chat-budget">${rows}</div></details>`;
352
400
  }
353
401
  export function chatPage(chrome, data) {
354
- const formFacts = { keyFacts: data.keyFacts, openrouterModels: data.openrouterModels, ...(data.liveModels === undefined ? {} : { liveModels: data.liveModels }), csrf: data.csrf,
355
- returnTo: data.focusTask === null ? "/chat" : taskChatHref(data.focusTask.id) };
356
402
  const parts = [
357
403
  data.focusTask === null
358
404
  ? chatHeading("", data.projects.length, data.enabled.ok)
@@ -803,7 +849,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
803
849
  return rows.messages.map(message => ({
804
850
  id: message.id, role: message.role, text: message.text,
805
851
  html: htmlString(message.role === 'operator'
806
- ? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
852
+ ? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
807
853
  : html `${renderChatText(message.text, askedBy.get(message.id))}${asked(message.turn)}`),
808
854
  activity: message.activity, createdAt: message.createdAt,
809
855
  ...(() => {
@@ -882,7 +928,7 @@ export function leadThreadHtml(data) {
882
928
  asked = message.text;
883
929
  // A message that waits its turn (the lead queue) says so, with Remove while it still waits — as in the React thread.
884
930
  const queued = data.queue?.get(message.id);
885
- thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
931
+ thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
886
932
  continue;
887
933
  }
888
934
  const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
@@ -146,14 +146,39 @@ export declare function systemPage(chrome: Chrome, data: {
146
146
  }[];
147
147
  now: Date;
148
148
  }): Screen;
149
+ /**
150
+ * A build's stage as one of three steps — set up, writing, checking and
151
+ * committing — read from the machine's own phase, never a percent. The
152
+ * steps only move forward: evidence and proof checks run both before and
153
+ * after the commit (builder.ts), so everything after the agent's turn is
154
+ * one step, and the stage words say which part. Null for runs that are not
155
+ * builds (planning, review, investigation): those have no honest steps.
156
+ */
157
+ export declare const BUILD_STEPS: readonly ["setting up", "writing", "checking and committing"];
158
+ export declare function buildStepOf(claim: NonNullable<BoardCard["claim"]>): number | null;
159
+ /**
160
+ * The workers a board's strip may show: not retired, bound to at least one
161
+ * project the server's ceiling admits (never a runner serving only projects
162
+ * this server must not speak about), and — on a one-project board — bound to
163
+ * that project.
164
+ */
165
+ export declare function boardWorkers(runners: readonly Runner[], scope: {
166
+ all: boolean;
167
+ project: string | null;
168
+ visible: (repo: string) => boolean;
169
+ }): Runner[];
149
170
  /**
150
171
  * The board: the pipeline as lanes, position as meaning — attention, then
151
- * queued, then waiting, then building, then recently done. Read-only by
152
- * construction (every card is a link, no forms, no nonces), which is what
153
- * makes it safe to re-render itself while somebody watches.
172
+ * queued, then waiting, then building, then recently done — under a strip
173
+ * of the workers doing it. Read-only by construction (every card is a link,
174
+ * no forms, no nonces), which is what makes it safe to re-render itself
175
+ * while somebody watches; a card that changes lane slides there
176
+ * (regionScript's animate swap, keyed by data-vt).
154
177
  */
155
178
  export declare function boardBody(data: {
156
179
  cards: BoardCard[];
180
+ /** The workers this board's work can go to (retired ones left out). */
181
+ workers: readonly Runner[];
157
182
  done: ReturnType<Store["listCompletedWorkScoped"]>;
158
183
  saturated: boolean;
159
184
  now: Date;
@@ -207,11 +207,74 @@ export function systemPage(chrome, data) {
207
207
  : html `<span class="meta">syncing normally</span>`}</p>`), "\n")}`,
208
208
  ], "\n"), { chrome, refreshSeconds: data.building.length > 0 ? 10 : 60 });
209
209
  }
210
+ /**
211
+ * A build's stage as one of three steps — set up, writing, checking and
212
+ * committing — read from the machine's own phase, never a percent. The
213
+ * steps only move forward: evidence and proof checks run both before and
214
+ * after the commit (builder.ts), so everything after the agent's turn is
215
+ * one step, and the stage words say which part. Null for runs that are not
216
+ * builds (planning, review, investigation): those have no honest steps.
217
+ */
218
+ export const BUILD_STEPS = ["setting up", "writing", "checking and committing"];
219
+ export function buildStepOf(claim) {
220
+ if (claim.role === "planner" || claim.role === "reviewer" || claim.role === "scout")
221
+ return null;
222
+ if (claim.model === null)
223
+ return 0;
224
+ if (claim.phase === null || claim.phase === "agent-running")
225
+ return 1;
226
+ return 2;
227
+ }
228
+ /** A clock that ticks in the page (boardTickScript) from the server's own start time; the text is the no-script answer. */
229
+ function tickingClock(startedIso, now) {
230
+ const minutes = Math.max(0, Math.round((now.getTime() - new Date(startedIso).getTime()) / 60_000));
231
+ return html `<time class="clock" datetime="${startedIso}" data-tick>${minutes}m</time>`;
232
+ }
233
+ /**
234
+ * The workers a board's strip may show: not retired, bound to at least one
235
+ * project the server's ceiling admits (never a runner serving only projects
236
+ * this server must not speak about), and — on a one-project board — bound to
237
+ * that project.
238
+ */
239
+ export function boardWorkers(runners, scope) {
240
+ return runners.filter(runner => runner.retiredAt === null &&
241
+ runner.repos.some(repo => scope.visible(repo)) &&
242
+ (scope.all || scope.project === null || runner.repos.includes(scope.project)));
243
+ }
244
+ /**
245
+ * The worker strip across the top of the board: every worker this board's
246
+ * work can go to, what it is building here, and its stage and clock. Only
247
+ * builds the board already shows are named — a worker busy in a project
248
+ * this reader cannot see reads "no build on this board", never "free".
249
+ */
250
+ function workerStrip(runners, cards, now) {
251
+ if (runners.length === 0)
252
+ return html `<p class="meta crew-empty">No workers yet — work waits in the queue until one joins.</p>`;
253
+ const tiles = runners.map(runner => {
254
+ const builds = cards
255
+ .filter(card => card.lane === "building" && card.claim !== null && card.claim.runner === runner.name)
256
+ .sort((a, b) => a.claim.claimedAt.localeCompare(b.claim.claimedAt));
257
+ const online = runnerAlive(runner, now);
258
+ const state = !online ? "offline" : builds.length > 0 ? "busy" : "idle";
259
+ const dot = state === "busy" ? html `<span class="dot dot-ok pulse"></span>` : html `<span class="dot dot-off"></span>`;
260
+ const jobs = builds.length > 0
261
+ ? builds.map(card => {
262
+ const claim = card.claim;
263
+ const words = claim.model === null ? "preparing workspace" : claim.phase === null ? "the agent is working" : phaseWords(claim.phase);
264
+ return html `<a class="worker-job" href="${card.href}"><span class="t">${card.title}</span><span class="sub"><span>${words}</span>${tickingClock(claim.claimedAt, now)}</span></a>`;
265
+ })
266
+ : html `<p class="worker-job idle">${online ? "no build on this board" : html `offline since ${whenTime(runner.heartbeatAt)}`}</p>`;
267
+ return html `<div class="worker worker-${state}" data-flash="${runner.name}" data-flash-value="${builds.map(card => card.taskId).join(" ")}"><div class="worker-head">${dot}<span class="name">${runner.name}</span><span class="kind">${runner.agents.join(" · ")}</span>${runner.capacity > 1 ? html `<span class="slots">${builds.length}/${runner.capacity}</span>` : null}</div>${jobs}</div>`;
268
+ });
269
+ return html `<section class="crew" aria-label="Workers">${tiles}</section>`;
270
+ }
210
271
  /**
211
272
  * The board: the pipeline as lanes, position as meaning — attention, then
212
- * queued, then waiting, then building, then recently done. Read-only by
213
- * construction (every card is a link, no forms, no nonces), which is what
214
- * makes it safe to re-render itself while somebody watches.
273
+ * queued, then waiting, then building, then recently done — under a strip
274
+ * of the workers doing it. Read-only by construction (every card is a link,
275
+ * no forms, no nonces), which is what makes it safe to re-render itself
276
+ * while somebody watches; a card that changes lane slides there
277
+ * (regionScript's animate swap, keyed by data-vt).
215
278
  */
216
279
  export function boardBody(data, ciRed) {
217
280
  const chip = (repo) => !data.all || repo === null ? null : html `<span class="badge">${projectName(repo)}</span>`;
@@ -261,7 +324,7 @@ export function boardBody(data, ciRed) {
261
324
  const kept = parts.filter((one) => one !== null);
262
325
  return kept.length === 0 ? null : html `<span class="chips">${joinHtml(kept, " ")}</span>`;
263
326
  };
264
- const plain = (card) => html `<a class="lane-card" href="${card.href}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
327
+ const plain = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
265
328
  ...(card.stalledSince === null ? [] : [["waiting", age(card.stalledSince)]]),
266
329
  ])}${chips([
267
330
  chip(card.repo),
@@ -279,7 +342,7 @@ export function boardBody(data, ciRed) {
279
342
  }
280
343
  return heads;
281
344
  })();
282
- const queuedCard = (card) => html `<a class="lane-card" href="${card.href}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
345
+ const queuedCard = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
283
346
  ["worker", card.assignedRunner === null ? "any free worker" : card.assignedRunner],
284
347
  ])}${chips([
285
348
  queuedHeads.get(`${card.assignedRunner ?? ""}|${card.repo ?? ""}`) === card.taskId
@@ -292,14 +355,19 @@ export function boardBody(data, ciRed) {
292
355
  const claim = card.claim;
293
356
  if (claim === null)
294
357
  return plain(card);
295
- const minutes = Math.max(1, Math.round((data.now.getTime() - new Date(claim.claimedAt).getTime()) / 60_000));
296
358
  const workspace = claim.worktree === null ? null : (claim.worktree.split("/").pop() ?? claim.worktree);
297
359
  // Chip copy: unknown phases show nothing rather than raw tokens.
298
360
  const phase = claim.phase === null ? undefined : PHASE_WORDS[claim.phase];
299
361
  // The live strip is the run's own phase and clock — never a percent:
300
362
  // a build has no honest progress figure, only a stage and an elapsed.
301
363
  const live = claim.model === null ? "preparing workspace" : (phase ?? "the agent is working");
302
- return html `<a class="lane-card building" href="${card.href}"><span class="id">${card.taskId}</span><span class="t"><span class="dot dot-ok pulse"></span>${card.title}</span><span class="live-line"><span class="stage">${live}</span><span class="clock">${minutes}m</span></span>${facts([
364
+ // The steps: where the build is among set up → writing → checking →
365
+ // committing, from its phase — a stage, never a percent.
366
+ const step = buildStepOf(claim);
367
+ const steps = step === null
368
+ ? null
369
+ : html `<span class="steps" role="img" aria-label="${`step ${step + 1} of ${BUILD_STEPS.length}: ${BUILD_STEPS[step]}`}">${BUILD_STEPS.map((_, index) => html `<i class="${index < step ? "on" : index === step ? "now" : ""}"></i>`)}</span>`;
370
+ return html `<a class="lane-card building" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t"><span class="dot dot-ok pulse"></span>${card.title}</span>${steps}<span class="live-line"><span class="stage">${live}</span>${tickingClock(claim.claimedAt, data.now)}</span>${facts([
303
371
  ["worker", claim.runner],
304
372
  ...(claim.model === null
305
373
  ? []
@@ -316,7 +384,7 @@ export function boardBody(data, ciRed) {
316
384
  const pr = row.prNumber === null
317
385
  ? null
318
386
  : html `<span class="badge badge-open">PR #${row.prNumber}</span>${ciRed(row.prNumber) ? html ` <span class="badge badge-failed">CI failing</span>` : null}`;
319
- return html `<a class="lane-card" href="${taskHref(row.taskId)}"><span class="id">${row.taskId}</span><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}${facts([
387
+ return html `<a class="lane-card" href="${taskHref(row.taskId)}" data-vt="${row.taskId}"><span class="id">${row.taskId}</span><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}${facts([
320
388
  ...(row.ranMinutes === null ? [] : [["ran", `${row.ranMinutes}m`]]),
321
389
  ["usage", runCostWords({ authMode: row.authMode, costUsd: row.costUsd, tokensIn: null, tokensOut: null })],
322
390
  ])}${chips([
@@ -347,7 +415,8 @@ export function boardBody(data, ciRed) {
347
415
  data.all ? null : html `<p class="meta board-view"><strong>State</strong> · <a href="/board?view=order">order →</a> <span class="meta">drag to reorder, or to reserve a task for one worker</span></p>`,
348
416
  deltaLine,
349
417
  toggle,
350
- html `<div class="board">`,
418
+ workerStrip(data.workers, data.cards, data.now),
419
+ html `<div class="board" data-now="${String(data.now.getTime())}">`,
351
420
  lane("attention", "needs you", "these wait for a person", plain),
352
421
  lane("queued", "queued", "starts when a worker is free", queuedCard),
353
422
  lane("waiting", "waiting", "paused until a time, another task, or a requirement is ready", plain),
@@ -95,9 +95,6 @@ export declare function withRefusal(href: string, code: keyof typeof RESULT_REFU
95
95
  /** The queue reads at most this many completed tasks — the store's own
96
96
  * page ceiling, admission bound before it. */
97
97
  export declare const REVIEW_QUEUE_CAP = 100;
98
- /** The one return road the comment endpoint honors besides its own run
99
- * page: the cockpit's selected-result link, exactly. */
100
- export declare const REVIEW_RETURN: RegExp;
101
98
  /** Review priority: a deterministic, LABELED presentation aid over facts
102
99
  * the done list already carries. Band 0 goes first; the words name why.
103
100
  * It is never a verdict — the stored proof verdict stays the authority,
@@ -287,9 +284,6 @@ export declare function reviewCockpitPage(chrome: Chrome, data: {
287
284
  user: string;
288
285
  now: Date;
289
286
  }): Screen;
290
- /** The selected result: intent → proof → changes → publication → acts,
291
- * one scan path, every fact labeled by its source. */
292
- export declare function reviewCockpitDetail(view: ReviewCockpitView, csrf: string, noted: boolean, canRetryReview?: boolean, tab?: ResultTab, user?: string): Html;
293
287
  /** The selected result's HTML and, for the rebuilt page, the same result
294
288
  * as data: every form and panel body is the one the HTML carries. */
295
289
  /** Where a result is shown: its own page (/review), or the Result tab of its task's page (docs/plans/one-page-task-and-agent.md),
@@ -238,9 +238,6 @@ export function withRefusal(href, code) {
238
238
  /** The queue reads at most this many completed tasks — the store's own
239
239
  * page ceiling, admission bound before it. */
240
240
  export const REVIEW_QUEUE_CAP = 100;
241
- /** The one return road the comment endpoint honors besides its own run
242
- * page: the cockpit's selected-result link, exactly. */
243
- export const REVIEW_RETURN = /^\/review\?result=[A-Za-z0-9._~%-]{1,200}$/;
244
241
  export const PRIORITY_LABELS = { 0: "needs action", 1: "review", 2: "no flags" };
245
242
  export function reviewPriorityOf(row) {
246
243
  if (row.assignment != null && (row.assignment.receipt?.runId ?? null) === row.runId) {
@@ -568,11 +565,6 @@ export function reviewCockpitPage(chrome, data) {
568
565
  functional: { script: reviewEvidenceScript() + (selected !== null && selected.detail !== null ? RESULT_REVIEW_SCRIPT : ""), fetches: false },
569
566
  });
570
567
  }
571
- /** The selected result: intent → proof → changes → publication → acts,
572
- * one scan path, every fact labeled by its source. */
573
- export function reviewCockpitDetail(view, csrf, noted, canRetryReview = false, tab = "summary", user = "") {
574
- return reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user).html;
575
- }
576
568
  export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user, host = { kind: "review" }) {
577
569
  const parts = [];
578
570
  const run = view.run;
@@ -1268,7 +1260,9 @@ export function reviewDiffHtml(patch, stat, runId, commentable, anchors = new Ma
1268
1260
  ? html `<span class="diff-annotate-space"></span>`
1269
1261
  : html `<button type="button" class="diff-annotate pick-line" data-path="${one.path}" data-line="${lineNumber}" data-side="${side}" aria-label="Annotate ${one.path}, ${side} line ${lineNumber}" title="Annotate this line">${annotateIcon}</button>`;
1270
1262
  const marker = line.kind === "addition" ? "+" : line.kind === "deletion" ? "−" : line.kind === "context" ? " " : "·";
1271
- return html `<div class="diff-line diff-${line.kind}">${annotate}<span class="diff-gutter">${line.oldLine ?? ""}</span><span class="diff-gutter">${line.newLine ?? ""}</span><code><b aria-hidden="true">${marker}</b>${line.text}</code></div>`;
1263
+ // Each real line names its file and line, so a comment (or a test) can find the exact row.
1264
+ const named = lineNumber === null || line.kind === "meta" ? "" : html ` data-diff-file="${one.path}" data-diff-line="${lineNumber}"`;
1265
+ return html `<div class="diff-line diff-${line.kind}"${named}>${annotate}<span class="diff-gutter">${line.oldLine ?? ""}</span><span class="diff-gutter">${line.newLine ?? ""}</span><code><b aria-hidden="true">${marker}</b>${line.text}</code></div>`;
1272
1266
  })}</div></section>`;
1273
1267
  });
1274
1268
  const anchor = anchors.get(one.path);
@@ -1,5 +1,6 @@
1
1
  /** Task pages: approval, agents, plans, controls, decisions and the task body. */
2
2
  import { networkWords } from "../egress.js";
3
+ import { writesWords } from "../writes-mode.js";
3
4
  import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
4
5
  import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
5
6
  import { closedQuestionReason, closedQuestionWords } from "../decision.js";
@@ -21,17 +22,16 @@ import { starterForWork } from "../flow-starters.js";
21
22
  import {} from "../gaps.js";
22
23
  import { readAuthModeStrict } from "../keys.js";
23
24
  import { failedAttemptSentence, retryNoteOf } from "../needs-you.js";
24
- import { agentsSummary, chosenWords, makesNoPlan, postureWords, projectRoute, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
25
+ import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
25
26
  import { parseExecutionPlanDocument } from "../plan.js";
26
27
  import {} from "../planner-source.js";
27
28
  import { projectName } from "../project.js";
28
29
  import { dispatchStatusToken, manualReviewOnly, plainReasonWords, reviewConflict } from "../proof.js";
29
30
  import { providerName } from "../provider-auth.js";
30
- import { PROVIDER_IDS } from "../provider.js";
31
+ import {} from "../provider.js";
31
32
  import { qualityModeTitle } from "../quality.js";
32
- import {} from "../result-evidence-readers.js";
33
33
  import { evidenceShortenedWords } from "../result-review.js";
34
- import { acceptanceToLines, approvalOf, chainFromJson, scopeAuthorityOf } from "../scope.js";
34
+ import { acceptanceToLines, approvalOf, scopeAuthorityOf } from "../scope.js";
35
35
  import {} from "../store.js";
36
36
  import { runCostWords } from "../summary.js";
37
37
  import {} from "../task-control.js";
@@ -43,7 +43,7 @@ import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SEN
43
43
  import { steerDestinationWords, steerStandingParts } from "../steer-status.js";
44
44
  import { reviewHref, taskChatHref, taskHref } from "./http.js";
45
45
  import { coordinatorProposalsSection } from "./render-chat.js";
46
- import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, terminalDiffCard, verificationExplanation, verificationRecovered } from "./render-results.js";
46
+ import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
47
47
  import {} from "./session.js";
48
48
  import { linkWords, originNote } from "../task-source.js";
49
49
  export function contractChangeHtml(change) {
@@ -541,7 +541,7 @@ export function profileWords(scope) {
541
541
  ? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside the OS fence; the agent runs any tool it chooses and nothing asks. No turn limit: stops after ${Math.round(profile.timeoutSeconds / 60)} min, or ${Math.round(profile.idleTimeoutSeconds / 60)} min silent; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min, or ${Math.round(profile.repairIdleTimeoutSeconds / 60)} min silent.</p>`
542
542
  : html `<p class="meta">Runs on <span class="mono">${profile.provider} · ${profile.model}</span> — ${profile.sandboxMode === "danger-full-access" ? "FULL permissions via --dangerously-bypass-approvals-and-sandbox; nothing asks" : "workspace-write sandbox"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}); repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`;
543
543
  // What the build may reach is part of what is approved (egress.ts): said on every approval form.
544
- return html `${base}<p class="meta" data-scope-network>${networkWords(profile.network ?? null)}</p>`;
544
+ return html `${base}<p class="meta" data-scope-network>${networkWords(profile.network ?? null)}</p><p class="meta" data-scope-writes>Writes: ${writesWords(profile.writes)}.</p>`;
545
545
  }
546
546
  export const ROLE_NOUN = { plan: "Planner", build: "Builder", repair: "Repair", review: "Reviewer" };
547
547
  export function agentsStandingWords(view) {
@@ -91,9 +91,5 @@ export interface RouteDeclaration {
91
91
  export declare const ROUTES: readonly RouteDeclaration[];
92
92
  /** The first row that declares this method and path, or null: an undeclared request never reaches a handler. */
93
93
  export declare function matchRoute(method: string, pathname: string, stage?: Stage): RouteDeclaration | null;
94
- /** True when some row declares this path for another method (a wrong-method request). */
95
- export declare function declaredForAnotherMethod(method: string, pathname: string, stage?: Stage): boolean;
96
94
  /** Refuses a malformed table at startup: duplicate ids, duplicate method+pattern rows, or a row shadowed by an earlier one for its own sample. */
97
95
  export declare function assertRouteTable(routes?: readonly RouteDeclaration[]): void;
98
- /** The effective policy of every row, as plain data — what the snapshot test compares. */
99
- export declare const routePolicySnapshot: () => Array<Omit<RouteDeclaration, "sample">>;
@@ -283,6 +283,7 @@ post("settings", "spend.budget", "/spend/budget", { scope: "step-up", ...operato
283
283
  post("settings", "settings.project-concurrency", "/settings/project/concurrency");
284
284
  post("settings", "settings.project-live-view", "/settings/project/live-view", { scope: "step-up", callers: ["cookie"] });
285
285
  post("settings", "settings.project-network", "/settings/project/network", { scope: "step-up", callers: ["cookie"] });
286
+ post("settings", "settings.project-writes", "/settings/project/writes", { scope: "step-up", callers: ["cookie"] });
286
287
  post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
287
288
  // Lead sweeps (docs/plans/lead-fan-out.md): the table, and cancelling what isn't ready. The handler shows a sweep only in
288
289
  // a project the reader can see, and acts only for an approver.
@@ -430,10 +431,6 @@ export function matchRoute(method, pathname, stage) {
430
431
  }
431
432
  return null;
432
433
  }
433
- /** True when some row declares this path for another method (a wrong-method request). */
434
- export function declaredForAnotherMethod(method, pathname, stage) {
435
- return compiled.some(({ route, test }) => (stage === undefined || route.stage === stage) && !methodMatches(route.method, method) && test.test(pathname));
436
- }
437
434
  /** Refuses a malformed table at startup: duplicate ids, duplicate method+pattern rows, or a row shadowed by an earlier one for its own sample. */
438
435
  export function assertRouteTable(routes = ROUTES) {
439
436
  const ids = new Set(), keys = new Set();
@@ -465,5 +462,3 @@ export function assertRouteTable(routes = ROUTES) {
465
462
  }
466
463
  }
467
464
  assertRouteTable();
468
- /** The effective policy of every row, as plain data — what the snapshot test compares. */
469
- export const routePolicySnapshot = () => ROUTES.map(({ sample: _sample, ...rest }) => ({ ...rest }));