toolroll 0.9.62 → 0.9.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +66 -8
  3. package/dist/acp-agents.d.ts +43 -0
  4. package/dist/acp-agents.js +62 -0
  5. package/dist/acp-attest.d.ts +44 -0
  6. package/dist/acp-attest.js +269 -0
  7. package/dist/acp-check.d.ts +28 -0
  8. package/dist/acp-check.js +410 -0
  9. package/dist/acp-client.d.ts +163 -0
  10. package/dist/acp-client.js +738 -0
  11. package/dist/acp-inspect.d.ts +23 -0
  12. package/dist/acp-inspect.js +109 -0
  13. package/dist/agent-board.d.ts +19 -6
  14. package/dist/agent-board.js +30 -9
  15. package/dist/agent-fence.d.ts +203 -10
  16. package/dist/agent-fence.js +623 -41
  17. package/dist/agent-pulse.js +9 -2
  18. package/dist/agent-state-words.d.ts +19 -0
  19. package/dist/agent-state-words.js +20 -0
  20. package/dist/agent-state.d.ts +65 -0
  21. package/dist/agent-state.js +229 -0
  22. package/dist/agentconfig.js +36 -13
  23. package/dist/agents-board.d.ts +34 -2
  24. package/dist/agents-board.js +110 -1
  25. package/dist/approval-rules-ui.js +2 -2
  26. package/dist/assignment-delivery.d.ts +14 -0
  27. package/dist/assignment-delivery.js +13 -0
  28. package/dist/assignment.js +13 -0
  29. package/dist/attest.js +7 -0
  30. package/dist/backup-ui.js +2 -2
  31. package/dist/browser/THIRD_PARTY_NOTICES.txt +48 -0
  32. package/dist/browser/terminal.css +37 -0
  33. package/dist/browser/terminal.js +117 -0
  34. package/dist/browser/workspace.css +2 -2
  35. package/dist/browser/workspace.js +42 -38
  36. package/dist/browser-shell.js +6 -3
  37. package/dist/browser-workspace.d.ts +263 -1
  38. package/dist/browser-workspace.js +60 -0
  39. package/dist/builder.d.ts +6 -1
  40. package/dist/builder.js +34 -13
  41. package/dist/chat-actions.d.ts +13 -1
  42. package/dist/chat-actions.js +199 -17
  43. package/dist/chat-ask.d.ts +4 -0
  44. package/dist/chat-ask.js +14 -0
  45. package/dist/chat-channel.js +20 -0
  46. package/dist/chat-continuity.js +5 -4
  47. package/dist/chat-decide.d.ts +0 -14
  48. package/dist/chat-decide.js +4 -1
  49. package/dist/chat-delivery.d.ts +9 -0
  50. package/dist/chat-delivery.js +45 -6
  51. package/dist/chat-messages.d.ts +8 -0
  52. package/dist/chat-messages.js +36 -4
  53. package/dist/check-levels-ui.js +2 -2
  54. package/dist/check-levels.js +3 -0
  55. package/dist/checkout-proof.d.ts +28 -0
  56. package/dist/checkout-proof.js +62 -0
  57. package/dist/claim.d.ts +12 -0
  58. package/dist/claim.js +20 -1
  59. package/dist/cli-help.d.ts +1 -1
  60. package/dist/cli-help.js +5 -1
  61. package/dist/cli.d.ts +3 -0
  62. package/dist/cli.js +24 -2
  63. package/dist/coding-ui.js +6 -4
  64. package/dist/console-instance.d.ts +124 -0
  65. package/dist/console-instance.js +337 -0
  66. package/dist/contracts/chat-actions.d.ts +16 -0
  67. package/dist/contracts/chat-actions.js +4 -0
  68. package/dist/contracts/chat-content.d.ts +19 -0
  69. package/dist/contracts/chat-content.js +6 -0
  70. package/dist/contracts/cli.d.ts +8 -0
  71. package/dist/contracts/cli.js +3 -1
  72. package/dist/contracts/console-api.d.ts +30 -4
  73. package/dist/contracts/console-api.js +13 -4
  74. package/dist/contracts/flow-card.d.ts +4 -0
  75. package/dist/contracts/flow-card.js +3 -0
  76. package/dist/contracts/flow.d.ts +46 -0
  77. package/dist/contracts/flow.js +11 -2
  78. package/dist/contracts/integration-metadata.d.ts +2 -1
  79. package/dist/contracts/integration-metadata.js +2 -1
  80. package/dist/contracts/lead-tools.d.ts +92 -0
  81. package/dist/contracts/lead-tools.js +13 -0
  82. package/dist/contracts/provider.d.ts +11 -1
  83. package/dist/contracts/provider.js +1 -1
  84. package/dist/contracts/route.d.ts +76 -1
  85. package/dist/contracts/route.js +1 -1
  86. package/dist/contracts/store-json.d.ts +2 -0
  87. package/dist/control-setup.d.ts +1 -1
  88. package/dist/control-ui.js +2 -2
  89. package/dist/converse.d.ts +16 -0
  90. package/dist/converse.js +32 -4
  91. package/dist/demo-chat.js +4 -4
  92. package/dist/demo.js +2 -2
  93. package/dist/desktop-update-gate.js +6 -0
  94. package/dist/discord-chat.js +4 -1
  95. package/dist/dispatch.js +7 -4
  96. package/dist/doctor.d.ts +53 -0
  97. package/dist/doctor.js +427 -0
  98. package/dist/egress-bridge.d.ts +17 -0
  99. package/dist/egress-bridge.js +111 -0
  100. package/dist/egress.d.ts +119 -0
  101. package/dist/egress.js +423 -0
  102. package/dist/evidence-pack.d.ts +1 -1
  103. package/dist/evidence-pack.js +2 -2
  104. package/dist/exec.d.ts +81 -0
  105. package/dist/exec.js +368 -9
  106. package/dist/export-ui.js +2 -2
  107. package/dist/flow-code.d.ts +2 -2
  108. package/dist/flow-code.js +4 -15
  109. package/dist/flow-engine.js +35 -11
  110. package/dist/flow-gallery-ui.js +2 -2
  111. package/dist/flow-gallery.js +27 -8
  112. package/dist/flow-share.js +16 -5
  113. package/dist/flow-triggers.d.ts +84 -11
  114. package/dist/flow-triggers.js +554 -87
  115. package/dist/flow-unattended-words.d.ts +28 -0
  116. package/dist/flow-unattended-words.js +50 -0
  117. package/dist/flow-unattended.d.ts +227 -0
  118. package/dist/flow-unattended.js +776 -0
  119. package/dist/flows-cli.d.ts +28 -1
  120. package/dist/flows-cli.js +63 -12
  121. package/dist/flows-ui.d.ts +2 -3
  122. package/dist/flows-ui.js +56 -5
  123. package/dist/flows.d.ts +4 -0
  124. package/dist/flows.js +9 -1
  125. package/dist/google-mail.d.ts +1 -0
  126. package/dist/google-mail.js +61 -18
  127. package/dist/html.d.ts +9 -0
  128. package/dist/html.js +11 -0
  129. package/dist/integrations-ui.d.ts +2 -1
  130. package/dist/integrations-ui.js +6 -3
  131. package/dist/integrations.d.ts +17 -3
  132. package/dist/integrations.js +38 -18
  133. package/dist/invoke.d.ts +17 -0
  134. package/dist/invoke.js +510 -182
  135. package/dist/keys.d.ts +18 -14
  136. package/dist/keys.js +5 -0
  137. package/dist/landed-cleanup.d.ts +3 -2
  138. package/dist/landed-cleanup.js +12 -5
  139. package/dist/lead-capabilities.js +3 -2
  140. package/dist/lead-contract.js +2 -0
  141. package/dist/lead-doors.d.ts +2 -0
  142. package/dist/lead-doors.js +7 -0
  143. package/dist/lead-follow.d.ts +2 -0
  144. package/dist/lead-follow.js +58 -2
  145. package/dist/lead-progress.js +1 -0
  146. package/dist/lead-queue-runtime.d.ts +46 -0
  147. package/dist/lead-queue-runtime.js +191 -0
  148. package/dist/lead-queue.d.ts +104 -0
  149. package/dist/lead-queue.js +187 -0
  150. package/dist/lead-sweep.d.ts +67 -0
  151. package/dist/lead-sweep.js +99 -0
  152. package/dist/lead-tools.d.ts +22 -0
  153. package/dist/lead-tools.js +163 -4
  154. package/dist/lead.d.ts +13 -5
  155. package/dist/lead.js +89 -29
  156. package/dist/limits-ui.d.ts +8 -1
  157. package/dist/limits-ui.js +26 -0
  158. package/dist/live-bus.d.ts +8 -0
  159. package/dist/live-bus.js +40 -4
  160. package/dist/live-detail.d.ts +27 -0
  161. package/dist/live-detail.js +57 -0
  162. package/dist/live-notes.d.ts +20 -0
  163. package/dist/live-notes.js +47 -0
  164. package/dist/live-records.d.ts +140 -0
  165. package/dist/live-records.js +935 -0
  166. package/dist/live-terminal.d.ts +258 -0
  167. package/dist/live-terminal.js +649 -0
  168. package/dist/live.d.ts +38 -27
  169. package/dist/live.js +142 -124
  170. package/dist/mcp-connect.d.ts +8 -7
  171. package/dist/mcp-connect.js +49 -51
  172. package/dist/mcp-oauth.d.ts +6 -3
  173. package/dist/mcp-oauth.js +8 -4
  174. package/dist/memory-pass.js +2 -2
  175. package/dist/mobile-viewport.d.ts +5 -1
  176. package/dist/mobile-viewport.js +18 -2
  177. package/dist/model-catalog.js +4 -0
  178. package/dist/monitoring-ui.js +2 -2
  179. package/dist/needs-you.d.ts +1 -1
  180. package/dist/needs-you.js +11 -4
  181. package/dist/oauth-run-token.d.ts +51 -0
  182. package/dist/oauth-run-token.js +127 -0
  183. package/dist/operate.d.ts +2 -0
  184. package/dist/operate.js +282 -18
  185. package/dist/passwordless.d.ts +23 -0
  186. package/dist/passwordless.js +26 -0
  187. package/dist/peek-cli.d.ts +12 -4
  188. package/dist/peek-cli.js +19 -11
  189. package/dist/phase-routing.js +10 -3
  190. package/dist/plan-auto.d.ts +11 -0
  191. package/dist/plan-auto.js +62 -11
  192. package/dist/planner-source.d.ts +3 -0
  193. package/dist/planner-source.js +14 -0
  194. package/dist/planner.d.ts +2 -1
  195. package/dist/planner.js +7 -1
  196. package/dist/policy-ui.js +2 -2
  197. package/dist/policy.d.ts +1 -20
  198. package/dist/policy.js +29 -2
  199. package/dist/process-recovery-eligibility.d.ts +1 -1
  200. package/dist/project-access.d.ts +3 -0
  201. package/dist/project-access.js +3 -0
  202. package/dist/project-delete-ui.d.ts +12 -0
  203. package/dist/project-delete-ui.js +41 -9
  204. package/dist/project-delete.d.ts +3 -1
  205. package/dist/project-delete.js +12 -6
  206. package/dist/project-file.d.ts +3 -0
  207. package/dist/project-file.js +16 -0
  208. package/dist/project-memory.d.ts +3 -1
  209. package/dist/project-memory.js +57 -25
  210. package/dist/project-tools.d.ts +103 -27
  211. package/dist/project-tools.js +399 -100
  212. package/dist/provider-auth.d.ts +4 -3
  213. package/dist/provider-auth.js +5 -4
  214. package/dist/provider-connection.d.ts +2 -1
  215. package/dist/provider-connection.js +5 -0
  216. package/dist/provider-home.d.ts +22 -0
  217. package/dist/provider-home.js +99 -0
  218. package/dist/provider.d.ts +42 -5
  219. package/dist/provider.js +97 -19
  220. package/dist/pull-request-ui.js +2 -2
  221. package/dist/push.js +1 -1
  222. package/dist/quick-start.d.ts +24 -0
  223. package/dist/quick-start.js +127 -0
  224. package/dist/release.d.ts +3 -0
  225. package/dist/release.js +14 -8
  226. package/dist/remote-main.d.ts +4 -0
  227. package/dist/remote-main.js +3 -3
  228. package/dist/request-budget-ui.js +2 -2
  229. package/dist/result-review.d.ts +1 -1
  230. package/dist/result-review.js +9 -3
  231. package/dist/retention-ui.js +2 -2
  232. package/dist/retention.js +10 -1
  233. package/dist/run-efficiency.d.ts +73 -0
  234. package/dist/run-efficiency.js +228 -0
  235. package/dist/runner.d.ts +5 -1
  236. package/dist/runner.js +22 -1
  237. package/dist/scope.d.ts +171 -6
  238. package/dist/scope.js +251 -31
  239. package/dist/scout-net.d.ts +4 -1
  240. package/dist/scout-net.js +37 -12
  241. package/dist/scout.d.ts +20 -6
  242. package/dist/scout.js +27 -3
  243. package/dist/serve.js +258 -43
  244. package/dist/server/chat.d.ts +1 -1
  245. package/dist/server/chat.js +122 -31
  246. package/dist/server/chrome.d.ts +6 -1
  247. package/dist/server/chrome.js +18 -7
  248. package/dist/server/console.css +52 -38
  249. package/dist/server/flows.js +65 -13
  250. package/dist/server/guards.d.ts +1 -0
  251. package/dist/server/guards.js +23 -9
  252. package/dist/server/http.d.ts +7 -0
  253. package/dist/server/http.js +3 -2
  254. package/dist/server/live.d.ts +34 -1
  255. package/dist/server/live.js +39 -17
  256. package/dist/server/pages-assets.js +5 -0
  257. package/dist/server/pages.js +90 -13
  258. package/dist/server/people-tokens.d.ts +3 -0
  259. package/dist/server/people-tokens.js +55 -17
  260. package/dist/server/remote-hooks.js +12 -6
  261. package/dist/server/remote.d.ts +2 -1
  262. package/dist/server/remote.js +14 -5
  263. package/dist/server/render-chat.d.ts +12 -3
  264. package/dist/server/render-chat.js +47 -16
  265. package/dist/server/render-pages.d.ts +4 -1
  266. package/dist/server/render-pages.js +5 -4
  267. package/dist/server/render-people.d.ts +3 -2
  268. package/dist/server/render-people.js +3 -2
  269. package/dist/server/render-results.d.ts +18 -2
  270. package/dist/server/render-results.js +37 -28
  271. package/dist/server/render-settings.js +6 -5
  272. package/dist/server/render-tasks.d.ts +35 -10
  273. package/dist/server/render-tasks.js +110 -27
  274. package/dist/server/request-context.d.ts +2 -0
  275. package/dist/server/request-context.js +2 -1
  276. package/dist/server/route-table.d.ts +3 -0
  277. package/dist/server/route-table.js +49 -5
  278. package/dist/server/runtime.d.ts +34 -3
  279. package/dist/server/session.d.ts +25 -1
  280. package/dist/server/session.js +18 -4
  281. package/dist/server/settings.js +132 -13
  282. package/dist/server/sweeps.d.ts +4 -0
  283. package/dist/server/sweeps.js +58 -0
  284. package/dist/server/task-views.d.ts +2 -1
  285. package/dist/server/task-views.js +96 -6
  286. package/dist/server/tasks.js +320 -74
  287. package/dist/server/terminals.d.ts +31 -0
  288. package/dist/server/terminals.js +365 -0
  289. package/dist/setup-guide.js +3 -2
  290. package/dist/slack-chat.js +4 -1
  291. package/dist/spend-ui.js +3 -3
  292. package/dist/spend.js +2 -1
  293. package/dist/steer-capabilities.d.ts +32 -0
  294. package/dist/steer-capabilities.js +60 -0
  295. package/dist/steer-controls.d.ts +14 -0
  296. package/dist/steer-controls.js +36 -0
  297. package/dist/steer-status.js +2 -2
  298. package/dist/storage-sweep-child.js +3 -1
  299. package/dist/storage-sweep-process.d.ts +12 -5
  300. package/dist/storage-sweep-process.js +23 -6
  301. package/dist/storage-ui.js +4 -4
  302. package/dist/store-baseline.d.ts +1 -1
  303. package/dist/store-baseline.js +1 -1
  304. package/dist/store.d.ts +466 -19
  305. package/dist/store.js +1185 -63
  306. package/dist/subagent-desk.js +1 -1
  307. package/dist/subagent-tools.d.ts +1 -1
  308. package/dist/summary.js +3 -0
  309. package/dist/surface.js +6 -4
  310. package/dist/task-source.d.ts +58 -0
  311. package/dist/task-source.js +93 -0
  312. package/dist/team-leads.js +1 -1
  313. package/dist/teams-chat.js +4 -1
  314. package/dist/teardown-git.d.ts +14 -0
  315. package/dist/teardown-git.js +119 -0
  316. package/dist/telegram-lead.d.ts +3 -0
  317. package/dist/telegram-lead.js +44 -7
  318. package/dist/telegram.d.ts +11 -2
  319. package/dist/telegram.js +51 -17
  320. package/dist/terminal-catalog.d.ts +11 -0
  321. package/dist/terminal-catalog.js +129 -0
  322. package/dist/text-limits.d.ts +2 -0
  323. package/dist/text-limits.js +2 -0
  324. package/dist/tool-broker.d.ts +39 -0
  325. package/dist/tool-broker.js +370 -0
  326. package/dist/tool-launcher.js +5 -4
  327. package/dist/tool-secret-store.d.ts +170 -0
  328. package/dist/tool-secret-store.js +834 -0
  329. package/dist/toolroll-update-ui.js +2 -2
  330. package/dist/toolroll-update.js +14 -3
  331. package/dist/tools-ui.d.ts +7 -1
  332. package/dist/tools-ui.js +19 -10
  333. package/dist/webhook-limits.d.ts +23 -0
  334. package/dist/webhook-limits.js +30 -0
  335. package/dist/webhook-readers.d.ts +56 -0
  336. package/dist/webhook-readers.js +138 -0
  337. package/dist/webhook-signing.d.ts +27 -0
  338. package/dist/webhook-signing.js +119 -0
  339. package/dist/work-index.d.ts +29 -0
  340. package/dist/work-index.js +21 -5
  341. package/dist/workspace-revision.js +3 -1
  342. package/dist/worktree.d.ts +10 -4
  343. package/dist/worktree.js +70 -29
  344. package/package.json +13 -2
  345. package/scripts/provider-canary.mjs +210 -31
@@ -1,10 +1,9 @@
1
- /** Task pages: approval, agents, plans, controls, decisions and the task body. */
2
1
  import { type Html } from "../html.js";
3
2
  import { type RunActivity } from "../activity-line.js";
4
3
  import { type AgentChoice } from "../agentconfig.js";
5
4
  import { type AssignmentSnapshot } from "../assignment.js";
6
5
  import { type TaskBaseView } from "../task-base.js";
7
- import { type BrowserTaskView } from "../browser-workspace.js";
6
+ import { type BrowserAgentControls, type BrowserTaskView } from "../browser-workspace.js";
8
7
  import { type FormView } from "../contracts/console-api.js";
9
8
  import { type DispatchDiagnosis } from "../dispatch.js";
10
9
  import { type ReportView } from "../evidence.js";
@@ -23,6 +22,7 @@ import { type DisplayStatus, type PublicationFacts, type ReviewFacts, type WorkS
23
22
  import { type Chrome, type Screen } from "./chrome.js";
24
23
  import { type CompletionReceiptView, type EvidenceLinkMap, type TaskPullRequest } from "./render-results.js";
25
24
  import { type Who } from "./session.js";
25
+ import { type TaskOrigin } from "../task-source.js";
26
26
  /** What the task and approval views show about a drafted plan's contract
27
27
  * (contract handoff, task 1). A problem is a named problem, never a blank. */
28
28
  export type PlanContractView = {
@@ -74,14 +74,6 @@ export declare function revisionSentence(revision: Extract<RevisionView, {
74
74
  /** A planner's amendment, said plainly and kept in view: approving binds
75
75
  * the amended terms, so this is never folded away. */
76
76
  export declare function approvalAmendmentHtml(view: PlanContractView | null | undefined): Html;
77
- /**
78
- * The approval sheet (approval critique, Oct 2): the plan open, in plain
79
- * rows — Goal, Changes, Won't touch, Done when — one line of who builds,
80
- * one Approve & start beside the password, and every remaining signed
81
- * term (lineage, routing, runtime limits, the seal) in one Details fold.
82
- * Presentation only: the same hidden fields, nonce, digest and password
83
- * post to the same route, and the task page and chat card share it.
84
- */
85
77
  export declare function approvalSheetHtml(input: {
86
78
  surface: "task" | "chat";
87
79
  action: string;
@@ -103,7 +95,11 @@ export declare function approvalSheetHtml(input: {
103
95
  label: string;
104
96
  filedAgo: string | null;
105
97
  } | null;
98
+ /** v126: where the work came from: an outside event's one line of context (never a term the approval agrees to). */
99
+ origin?: TaskOrigin | null;
106
100
  deliverable: "branch" | "report";
101
+ /** The plan was rewritten after this build: the yes builds again to it (the approve POST requeues). */
102
+ rebuild?: number | null;
107
103
  editHref: string;
108
104
  notNowHref: string;
109
105
  sticky: boolean;
@@ -121,6 +117,8 @@ export declare function approvalSheetHtml(input: {
121
117
  open: boolean;
122
118
  stepsHref: string | null;
123
119
  } | null;
120
+ /** Cockpit B2: this person's password is remembered (when, ISO) and these terms may stand on it. */
121
+ freshSince?: string | null;
124
122
  }): Html;
125
123
  /** Earlier versions of a task still in flight, in plain words: queued
126
124
  * while they only wait, running once one has started; null when none. */
@@ -233,6 +231,11 @@ export declare function agentsStripHtml(view: RouteView | null, taskId: string):
233
231
  /** A ceremony nonce as stored: the sha256 hex of the value the form carried. */
234
232
  export declare function nonceHashOf(value: string): string;
235
233
  /** The approve POST's refusal, in words a person can act on. */
234
+ /** " (password remembered from 10:42)" when the approval stood on a remembered password or sign-in; nothing otherwise. */
235
+ export declare function approvalProofWords(proof: {
236
+ method: string;
237
+ at: string;
238
+ } | null | undefined): string;
236
239
  export declare function approveRefusalWords(reason: string): string;
237
240
  /** The browser's one-click approval binds not only the signed scope but the
238
241
  * exact advisory plan revision shown beside it. The scope digest remains the
@@ -473,6 +476,8 @@ export declare function taskBodyParts(data: {
473
476
  /** Immutable filing provenance (v12) — the approver sees which door
474
477
  * filed this (console, cli, intake, template:<name>) at the yes. */
475
478
  filedVia?: string | null;
479
+ /** v126: where the work came from — the flow and the outside event — in words; null for everything else. */
480
+ origin?: TaskOrigin | null;
476
481
  /** Coordinator provenance when an agent filed this; null otherwise. */
477
482
  coordinator?: {
478
483
  label: string;
@@ -500,6 +505,23 @@ export declare function taskBodyParts(data: {
500
505
  activity?: RunActivity | null;
501
506
  /** Live steering: the Claude build that takes Pause right now, and its latest pause (null: none, or it continued). */
502
507
  livePause?: LivePauseFact | null;
508
+ /** The running attempt's composer facts (steer-controls.ts), for this reader. */
509
+ steerComposer?: {
510
+ controls: BrowserAgentControls;
511
+ steer: {
512
+ action: string;
513
+ run: number;
514
+ } | null;
515
+ } | null;
516
+ /** This task's latest attempts, newest first (a tab's unsent draft for one that ended is offered on). */
517
+ steerRecent?: number[];
518
+ /** Cockpit B2: when this person's remembered password was given, when it may approve these terms. */
519
+ approvalFresh?: string | null;
520
+ /** Cockpit B2: how the current approval's person was proved (approval proved ledger), when it was a remembered one. */
521
+ approvalProof?: {
522
+ method: string;
523
+ at: string;
524
+ } | null;
503
525
  /** Workers that are both alive and authorized for this task's project. */
504
526
  worker?: {
505
527
  answering: number;
@@ -583,6 +605,9 @@ export declare function taskBodyParts(data: {
583
605
  steering?: SteerNote[];
584
606
  /** A build's agent is running now and reads new notes when it next checks for them (v120). */
585
607
  steeringLive?: boolean;
608
+ /** The running build a note from this page is bound to (cockpit §4): the exact attempt, so a stale page's note is
609
+ * refused rather than filed for whatever runs later. Null: no build reads notes now (the note waits for the next). */
610
+ steeringRun?: number | null;
586
611
  /** The publication grant the publisher would act under — from
587
612
  * publicationGrantFor(repo) only; null when none, or no project. */
588
613
  grant?: PublicationGrant | null;
@@ -1,5 +1,7 @@
1
1
  /** Task pages: approval, agents, plans, controls, decisions and the task body. */
2
- import { html, htmlString, joinHtml, postForm, replaceMarkup } from "../html.js";
2
+ import { networkWords } from "../egress.js";
3
+ import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
4
+ import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
3
5
  import { closedQuestionReason, closedQuestionWords } from "../decision.js";
4
6
  import {} from "../activity-line.js";
5
7
  import {} from "../agentconfig.js";
@@ -43,6 +45,7 @@ import { reviewHref, taskChatHref, taskHref } from "./http.js";
43
45
  import { coordinatorProposalsSection } from "./render-chat.js";
44
46
  import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, terminalDiffCard, verificationExplanation, verificationRecovered } from "./render-results.js";
45
47
  import {} from "./session.js";
48
+ import { linkWords, originNote } from "../task-source.js";
46
49
  export function contractChangeHtml(change) {
47
50
  const tag = html `<span class="contract-change-kind contract-change-${change.kind}">${change.kind}</span>`;
48
51
  switch (change.field) {
@@ -147,7 +150,8 @@ export function permissionWordsOf(profile) {
147
150
  ? profile.permissionArgv === "bypassPermissions" ? "Full access" : "Auto permissions"
148
151
  : profile.provider === "gemini"
149
152
  ? profile.approvalArgv === "yolo" ? "Full access" : "Auto permissions"
150
- : profile.sandboxMode === "danger-full-access" ? "Full access" : "Workspace sandbox";
153
+ : isAcpProfile(profile) ? "Full access"
154
+ : profile.sandboxMode === "danger-full-access" ? "Full access" : "Workspace sandbox";
151
155
  }
152
156
  /** One plain sentence for a revision (approval critique, Oct 2):
153
157
  * "Fixes what build #9 missed: <criterion>." The notes, paths and lineage
@@ -200,6 +204,28 @@ const fragmentString = (fragment) => fragment === "" ? "" : htmlString(fragment)
200
204
  * Presentation only: the same hidden fields, nonce, digest and password
201
205
  * post to the same route, and the task page and chat card share it.
202
206
  */
207
+ /** v126: a task's origin as a line of words (the identity line). */
208
+ function originWords(origin) {
209
+ const flow = origin.flow === null ? null : origin.flow === "hidden" ? "from a flow" : origin.flow === "removed" ? "from a flow (removed)" : `from flow “${origin.flow.name}”`;
210
+ const source = origin.source === null ? null : `${origin.source.label} (${origin.source.proved})`;
211
+ return [flow, source].filter(one => one !== null).join(" · ");
212
+ }
213
+ /** v126: a task's origin as a fact: the flow, linked for those who may open it, and the outside event, linked to where
214
+ * it lives, with that link's host always shown. */
215
+ function originParts(origin) {
216
+ const parts = [];
217
+ if (origin.flow !== null)
218
+ parts.push(origin.flow === "hidden" ? "A flow" : origin.flow === "removed" ? "A flow (removed)" : { label: `Flow “${origin.flow.name}”`, href: origin.flow.href });
219
+ if (origin.source !== null) {
220
+ if (parts.length > 0)
221
+ parts.push(" · ");
222
+ // The link shows where it goes (host and path), beside what it is.
223
+ parts.push(origin.source.label, ` (${origin.source.proved})`);
224
+ if (origin.source.url !== null)
225
+ parts.push(" · ", { label: linkWords(origin.source.url), href: origin.source.url });
226
+ }
227
+ return parts;
228
+ }
203
229
  export function approvalSheetHtml(input) {
204
230
  const { scope, route } = input;
205
231
  // A machine-drafted repair appends its brief to the signed goal; the row
@@ -261,10 +287,13 @@ export function approvalSheetHtml(input) {
261
287
  allowing.length === 0 ? "" : html `<p class="approval-allowing" data-approval-allowing>You’re allowing: ${allowing.join(" · ")}</p>`,
262
288
  yoursToCheck.length === 0 ? "" : html `<p class="approval-you-check" data-approval-you-check>You’ll check: ${yoursToCheck.join("; ")}</p>`,
263
289
  ]);
264
- const after = input.deliverable === "report"
265
- ? html `An agent investigates without changing the repository. You'll hear when its report is ready.`
266
- : html `An agent starts in its own branch. You'll hear when it's ready to review.`;
267
- const submit = html `Approve & start`;
290
+ const rebuild = input.rebuild ?? null;
291
+ const after = rebuild !== null
292
+ ? html `Build #${rebuild} was made to the earlier plan. Approving builds it again to this one.`
293
+ : input.deliverable === "report"
294
+ ? html `An agent investigates without changing the repository. You'll hear when its report is ready.`
295
+ : html `An agent starts in its own branch. You'll hear when it's ready to review.`;
296
+ const submit = rebuild !== null ? html `Approve & build again` : html `Approve & start`;
268
297
  // Everything else the digest binds, one tap away.
269
298
  const detail = (title, body, attrs = html ``) => isEmptyHtml(body) ? html `` : html `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
270
299
  const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
@@ -366,6 +395,7 @@ export function approvalSheetHtml(input) {
366
395
  sentence === null ? "" : html `<p class="approval-revision">${sentence}</p>`,
367
396
  input.coordinator === null ? "" : html `<p class="approval-note">An agent filed this: <span class="mono">${input.coordinator.label}</span>${input.coordinator.filedAgo === null ? "" : html `, ${input.coordinator.filedAgo}`}. Nothing runs until you approve, and approving runs its request.</p>`,
368
397
  input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "",
398
+ originNote(input.origin) === null ? "" : html `<p class="approval-note" data-approval-origin>${originNote(input.origin)}</p>`,
369
399
  html `<dl class="approval-rows">`,
370
400
  row("Goal", html `<p class="approval-goal">${goalShown}</p>`),
371
401
  row("Changes", changes),
@@ -378,15 +408,16 @@ export function approvalSheetHtml(input) {
378
408
  who.length === 0 ? "" : html `<p class="approval-who">${who.join(" · ")}</p>`,
379
409
  consent,
380
410
  html `<div class="approval-act" id="${input.surface === "task" ? "approval-confirm" : "chat-approval-confirm"}">`,
381
- html `<label class="approval-password"><span class="visually-hidden">Your password</span><input type="password" name="token" autocomplete="current-password" placeholder="Your password" aria-describedby="${passwordNote}"></label>`,
382
- html `<p class="approval-password-note" id="${passwordNote}">Your password signs this approval.</p>`,
411
+ html `${reauth(html `<label class="approval-password"><span class="visually-hidden">Your password</span><input type="password" name="token" autocomplete="current-password" placeholder="Your password" aria-describedby="${passwordNote}"></label>`)}`,
412
+ input.freshSince == null ? html `<p class="approval-password-note" id="${passwordNote}">Your password signs this approval.</p>`
413
+ : html `<p class="approval-password-note" id="${passwordNote}" data-approval-fresh>Your password from ${whenTime(input.freshSince)} is remembered on this computer — leave this blank to approve with it, or type it again.</p>`,
383
414
  html `<button type="submit" data-primary-action>${submit}</button></div>`,
384
415
  html `<p class="approval-after">${after}</p>`,
385
416
  html `<div class="approval-secondary">${editor ?? html `<a class="approval-link" href="${input.editHref}">Edit plan</a>`}<a class="approval-link" href="${input.notNowHref}">Not now</a></div>`,
386
417
  details,
387
418
  ]), {
388
419
  attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
389
- hidden: { nonce: input.nonce, digest: input.digest },
420
+ hidden: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
390
421
  returnTo: input.returnTo,
391
422
  }),
392
423
  editorForm,
@@ -413,7 +444,8 @@ export function permissionModeOfProfile(profile) {
413
444
  ? profile.permissionArgv === "bypassPermissions" ? "bypassPermissions" : "auto"
414
445
  : profile.provider === "gemini"
415
446
  ? profile.approvalArgv === "yolo" ? "bypassPermissions" : "auto"
416
- : profile.sandboxMode === "danger-full-access" ? "bypassPermissions" : "auto";
447
+ : isAcpProfile(profile) ? "bypassPermissions"
448
+ : profile.sandboxMode === "danger-full-access" ? "bypassPermissions" : "auto";
417
449
  }
418
450
  /** Done when, from the in-place editor: each line keeps its requirement's
419
451
  * id by position, and its evidence and guidance while its words stand; a
@@ -505,8 +537,11 @@ export function profileWords(scope) {
505
537
  : "legacy acceptEdits; edits proceed, commands that ask are denied unattended"}, stops after ${profile.maxTurns} turns, ${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}; repairs on ${repair}, ${profile.repairMaxTurns} turns / ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`
506
538
  : profile.provider === "gemini"
507
539
  ? html `<p class="meta">Runs on <span class="mono">gemini · ${profile.model}</span> — ${profile.approvalArgv === "yolo" ? "Full access via --approval-mode yolo; every tool auto-approved" : "Auto via --approval-mode auto_edit; edits auto-approved, other tools refused"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}), spend reported in tokens only; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`
508
- : 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>`;
509
- return base;
540
+ : isAcpProfile(profile)
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
+ : 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
+ // 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>`;
510
545
  }
511
546
  export const ROLE_NOUN = { plan: "Planner", build: "Builder", repair: "Repair", review: "Reviewer" };
512
547
  export function agentsStandingWords(view) {
@@ -706,6 +741,21 @@ export function nonceHashOf(value) {
706
741
  return createHash("sha256").update(value).digest("hex");
707
742
  }
708
743
  /** The approve POST's refusal, in words a person can act on. */
744
+ /** " (password remembered from 10:42)" when the approval stood on a remembered password or sign-in; nothing otherwise. */
745
+ export function approvalProofWords(proof) {
746
+ if (proof == null)
747
+ return "";
748
+ if (proof.method === "fresh-password")
749
+ return ` (password remembered from ${whenText(proof.at)})`;
750
+ if (proof.method === "fresh-sso")
751
+ return ` (sign-in remembered from ${whenText(proof.at)})`;
752
+ if (proof.method === "session")
753
+ return " (passwordless: your signed-in session)";
754
+ if (proof.method === "cli-login")
755
+ return " (passwordless: this computer's remembered login)";
756
+ return "";
757
+ }
758
+ const whenText = (iso) => new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "2-digit" }).format(new Date(iso));
709
759
  export function approveRefusalWords(reason) {
710
760
  if (reason === "requester" || reason === "person-required")
711
761
  return gateWords({ verdict: "refuse", reason });
@@ -713,6 +763,8 @@ export function approveRefusalWords(reason) {
713
763
  return "not approved: this scope predates agent routing and its old approval no longer stands — edit and re-file the scope so it is routed under today’s agents, then approve it";
714
764
  if (reason === "profile-unresolved")
715
765
  return "not approved: the scope cannot name an exact agent for every role — fix the agent setup and re-file it";
766
+ if (reason === "password-now")
767
+ return "not approved: this needs your password typed now — protected work, raised permissions, or a plan an agent wrote can't use a remembered one";
716
768
  return `not approved: ${reason}`;
717
769
  }
718
770
  /** The browser's one-click approval binds not only the signed scope but the
@@ -897,7 +949,7 @@ export function resumeCeremonyPage(chrome, data) {
897
949
  data.gate === null ? "" : html `<p class="row"><strong>Before work starts:</strong> ${data.gate.summary} — ${data.gate.detail}</p>`,
898
950
  html `</div>`,
899
951
  postForm(`${taskHref(data.taskId)}/resume`, joinHtml([
900
- html `<label>Confirm with your password<input type="password" name="token" autocomplete="current-password"></label>`,
952
+ html `${reauth(html `<label>Confirm with your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
901
953
  html `<button type="submit">Resume task</button>`,
902
954
  ], "\n"), { attrs: { class: "card resume-form" }, returnTo: data.returnTo, hidden: { nonce: data.nonceValue, run: control.run } }),
903
955
  back,
@@ -1067,7 +1119,7 @@ export function planRevisionLedgerHtml(ledger, taskId, csrf) {
1067
1119
  : joinHtml([
1068
1120
  postForm(`${taskHref(taskId)}/accept-revision`, joinHtml([
1069
1121
  pendingRevision.authorityKind === "authority-change"
1070
- ? html `<input type="password" name="token" placeholder="approval password" required autocomplete="current-password">`
1122
+ ? html `${reauth(html `<input type="password" name="token" placeholder="approval password" required autocomplete="current-password">`)}`
1071
1123
  : "",
1072
1124
  html `<button type="submit">${pendingRevision.authorityKind === "authority-change" ? html `Approve changes &amp; continue` : html `Use revised plan &amp; continue`}</button>`,
1073
1125
  ]), { attrs: { class: "inline" }, hidden: { "revision-id": pendingRevision.id } }),
@@ -1399,7 +1451,7 @@ export function taskBodyParts(data) {
1399
1451
  const steerForm = data.csrf === "" || task.state === "done" || task.state === "cancelled"
1400
1452
  ? ""
1401
1453
  : joinHtml([
1402
- postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem"><button type="submit">Steer</button>`, { attrs: { class: "row" } }),
1454
+ postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem">${data.steeringRun == null ? "" : html `<input type="hidden" name="run" value="${String(data.steeringRun)}"><input type="hidden" name="key" value="${randomUUID()}"><input type="hidden" name="return" value="task">`}<button type="submit">${data.steeringRun == null ? "Save for the next attempt" : "Steer"}</button>`, { attrs: { class: "row" } }),
1403
1455
  html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved scope.</p>`,
1404
1456
  ]);
1405
1457
  const steeringCard = steerRows === "" && steerForm === "" ? "" : html `<h2>Steering</h2>${steerRows}${steerForm}`;
@@ -1425,7 +1477,7 @@ export function taskBodyParts(data) {
1425
1477
  html `<p><strong>Quality</strong> ${qualityModeTitle(scope.qualityMode ?? "default")}</p>`,
1426
1478
  acceptanceCeremonyHtml(scope.acceptance),
1427
1479
  approval.approved
1428
- ? html `<p class="meta scope-seal">Approved by ${approval.by} · ${whenTime(approval.at)} · <span class="seal">signs ${shortDigest(scope.digest)}</span><span class="so-sr-only"> — approval binds to this exact wording</span></p>`
1480
+ ? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)} · <span class="seal">signs ${shortDigest(scope.digest)}</span><span class="so-sr-only"> — approval binds to this exact wording</span></p>`
1429
1481
  : html `<p class="meta"><span class="seal">signs ${shortDigest(scope.digest)}</span> — approval binds to this exact wording</p><p class="meta">Not approved${approval.reason === "changed" ? " — approved once, then rewritten" : ""}</p>`,
1430
1482
  html `</div>`,
1431
1483
  ], "\n");
@@ -1533,6 +1585,10 @@ export function taskBodyParts(data) {
1533
1585
  const revisionInApproval = scope !== null && !approval.approved && data.plan !== "requested" &&
1534
1586
  scope.profileState !== "unresolved" && consentDoor.open && data.dispatch?.action !== "repair-dependency" &&
1535
1587
  (data.revision == null || !("problem" in data.revision));
1588
+ // The approval is the next step when dispatch waits on it, or when the plan was rewritten after a build (the
1589
+ // assignment's one action): then it is shown open under the status, never folded.
1590
+ const approvalLeads = data.dispatch?.action === "approve-scope" ||
1591
+ (data.assignment?.primaryAction?.code === "approve-scope" && data.assignment.primaryAction.target.taskId === task.id);
1536
1592
  const approveForm = scope === null || approval.approved || data.plan === "requested"
1537
1593
  ? ""
1538
1594
  : data.revision !== null && data.revision !== undefined && "problem" in data.revision
@@ -1547,6 +1603,7 @@ export function taskBodyParts(data) {
1547
1603
  csrf: data.csrf,
1548
1604
  nonce: data.nonce,
1549
1605
  digest: data.approvalDigest ?? scope.digest,
1606
+ freshSince: data.approvalFresh ?? null,
1550
1607
  returnTo: null,
1551
1608
  scope,
1552
1609
  planDocument: data.plan === "drafted" ? data.planDocument : null,
@@ -1556,10 +1613,12 @@ export function taskBodyParts(data) {
1556
1613
  repairChain: data.repairChain ?? null,
1557
1614
  route: data.route,
1558
1615
  coordinator: data.coordinator ?? null,
1616
+ origin: data.origin ?? null,
1559
1617
  deliverable: data.deliverable ?? "branch",
1618
+ rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
1560
1619
  editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
1561
1620
  notNowHref: "/work",
1562
- sticky: data.dispatch?.action === "approve-scope",
1621
+ sticky: approvalLeads,
1563
1622
  earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
1564
1623
  edit: data.csrf === "" ? null
1565
1624
  : { action: `${taskHref(task.id)}/scope`, draft: inlineScopeDraft ? data.scopeDraft ?? null : null, problem: inlineScopeDraft ? data.problem : null,
@@ -1711,7 +1770,7 @@ export function taskBodyParts(data) {
1711
1770
  const scopeRow = scope === null
1712
1771
  ? prop("scope", "none yet")
1713
1772
  : approval.approved
1714
- ? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by} · ${whenTime(approval.at)}`)
1773
+ ? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}`)
1715
1774
  : prop("scope", approval.reason === "changed" ? "rewritten since its approval — needs a new yes" : "not approved");
1716
1775
  const strikesRow = data.strikes > 0 ? prop("strikes", `${data.strikes} failed attempt(s)`) : "";
1717
1776
  const propsCard = html `<div class="card props">${workerRow}${queueRow}${scopeRow}${publishesRow}${economics}${strikesRow}</div>`;
@@ -1727,7 +1786,7 @@ export function taskBodyParts(data) {
1727
1786
  facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/board?view=order" }] });
1728
1787
  }
1729
1788
  facts.push(scope === null ? { label: "Scope", parts: ["none yet"] }
1730
- : approval.approved ? { label: "Approved scope", parts: [{ seal: `signs ${scope.digest.length <= 12 ? scope.digest : `${scope.digest.slice(0, 12)}…`}` }, ` · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }])] }
1789
+ : approval.approved ? { label: "Approved scope", parts: [{ seal: `signs ${scope.digest.length <= 12 ? scope.digest : `${scope.digest.slice(0, 12)}…`}` }, ` · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}${approvalProofWords(data.approvalProof)}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }])] }
1731
1790
  : { label: "Scope", parts: [approval.reason === "changed" ? "rewritten since its approval — needs a new yes" : "not approved"] });
1732
1791
  facts.push({ label: "Publishes as", parts: [publishesAs] });
1733
1792
  // v102: who asked for it, and what the project's approval rules need.
@@ -1893,20 +1952,22 @@ export function taskBodyParts(data) {
1893
1952
  // status and property rail, and approval provenance stays in the ceremony.
1894
1953
  const identity = html `<p class="meta task-identity">Task ID <span class="mono">${task.id}</span>${data.rootTitle !== undefined && data.rootTitle !== task.title ? ` · Execution: ${task.title}` : ""}${data.repo === null ? "" : ` · ${projectName(data.repo)}`}${data.coordinator !== null && data.coordinator !== undefined
1895
1954
  ? html ` · filed by <span class="mono">${data.coordinator.label}</span>${data.coordinator.filedAgo === null ? "" : ` ${data.coordinator.filedAgo}`}`
1896
- : data.filedVia === null || data.filedVia === undefined
1897
- ? ""
1898
- : ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Scout</span>` : ""}</p>`;
1955
+ : data.origin != null
1956
+ ? ` · ${originWords(data.origin)}`
1957
+ : data.filedVia === null || data.filedVia === undefined
1958
+ ? ""
1959
+ : ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Scout</span>` : ""}</p>`;
1899
1960
  // A pull request that can be merged (or opened) owns the one primary action; the result stays in its section.
1900
1961
  const pullRequestActs = data.csrf !== "" && (data.pullRequest?.view?.canMerge === true || (data.pullRequest?.view == null && data.pullRequest?.offer != null));
1901
1962
  const pullRequestFact = data.pullRequest?.fact != null && data.pullRequest.view?.runId === data.assignment?.receipt?.runId ? data.pullRequest.fact : undefined;
1902
- const assignmentOptions = data.assignment == null ? null : { workStatus: status, hideAction: (approveForm !== "" && data.dispatch?.action === "approve-scope") || pullRequestActs, problem: status.tone === "problem",
1963
+ const assignmentOptions = data.assignment == null ? null : { workStatus: status, hideAction: (approveForm !== "" && approvalLeads) || pullRequestActs, problem: status.tone === "problem",
1903
1964
  planning: data.dispatch?.code === "running" && data.dispatch.role === "planner", ...(pullRequestFact === undefined ? {} : { pullRequest: pullRequestFact }),
1904
1965
  diagnostics: [...(status.diagnostics ?? []), ...(status.tone === "problem" && status.detail !== data.assignment.detail ? [status] : [])] };
1905
1966
  // Say it once: a pull request that couldn't open, or merged, is the status
1906
1967
  // card's own row (its reason or merge commit under Details), not a second card.
1907
1968
  const pullRequestCard = data.pullRequest == null || (pullRequestFact !== undefined && (data.pullRequest.view?.state === "failed" || data.pullRequest.view?.state === "merged")) ? "" : pullRequestCardHtml(data.pullRequest, data.csrf);
1908
1969
  // The result takes over from the task status as soon as it is ready.
1909
- const statusHtml = html `${data.assignment != null && assignmentOptions !== null ? assignmentSummaryHtml(data.assignment, assignmentOptions) : receiptLeads ? completionReceiptCard(data.completion.receipt, task.id, "task", status) : taskStatusCard(status, task.id, data.dispatch ?? null, liveRunId, approveForm !== "" && data.dispatch?.action === "approve-scope")}${checkProgressHtml(data.checkProgress ?? null)}`;
1970
+ const statusHtml = html `${data.assignment != null && assignmentOptions !== null ? assignmentSummaryHtml(data.assignment, assignmentOptions) : receiptLeads ? completionReceiptCard(data.completion.receipt, task.id, "task", status) : taskStatusCard(status, task.id, data.dispatch ?? null, liveRunId, approveForm !== "" && approvalLeads)}${checkProgressHtml(data.checkProgress ?? null)}`;
1910
1971
  // The exact-run control (v52), directly under the scheduler's answer:
1911
1972
  // the one place a person stops or resumes THIS attempt.
1912
1973
  const controlHtml = taskControlDetailsHtml(data.control ?? { kind: "none" }, task.id, data.csrf, "task", false, false, data.livePause ?? null);
@@ -1939,7 +2000,7 @@ export function taskBodyParts(data) {
1939
2000
  return joinHtml([
1940
2001
  html `<div class="card"><p><strong>External work</strong> <span class="meta">${link} · ${state}</span></p>`,
1941
2002
  reopenable
1942
- ? postForm(`${taskHref(task.id)}/reopen`, html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password"><button type="submit">Reopen — the approved scope stands</button>`, { attrs: { class: "row" } })
2003
+ ? postForm(`${taskHref(task.id)}/reopen`, html `${reauth(html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password">`)}<button type="submit">Reopen — the approved scope stands</button>`, { attrs: { class: "row" } })
1943
2004
  : "",
1944
2005
  html `</div>`,
1945
2006
  ]);
@@ -1956,7 +2017,7 @@ export function taskBodyParts(data) {
1956
2017
  ? html `<div class="card"><p><strong>This task is waiting on you: an agent filed it, and it has no scope.</strong></p><p class="meta">Filed by <span class="mono">${data.coordinator.label}</span> — nothing plans, claims, or runs until you write a scope below and sign it. Your signature runs their request.</p></div>`
1957
2018
  : html `<div class="card task-scope-needed"><p><strong>No approved scope yet</strong></p><p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
1958
2019
  : "";
1959
- const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : data.dispatch?.action === "approve-scope"
2020
+ const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : approvalLeads
1960
2021
  ? html `<section class="task-plan-review" aria-label="Approve the plan">${approveForm}</section>`
1961
2022
  : html `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approveForm}</details>`;
1962
2023
  const optionsHtml = html `${identity}${dispatchStatus}${dependencyChoiceNeeded ? "" : actsBar}`;
@@ -2145,7 +2206,10 @@ export function taskBodyParts(data) {
2145
2206
  review.push({ label: "Publishes as", parts: [publishesAs] });
2146
2207
  const about = [];
2147
2208
  const filedFact = facts.find(one => one.label === "Filed by");
2148
- about.push(filedFact ?? { label: "Filed by", parts: [filedBy ?? (data.filedVia == null ? "Not recorded" : `via ${data.filedVia}`)] });
2209
+ about.push(filedFact ?? { label: "Filed by", parts: [filedBy ?? (data.origin != null ? "An automation" : data.filedVia == null ? "Not recorded" : `via ${data.filedVia}`)] });
2210
+ // v126: where it came from — the flow (linked only for those who can see it) and the outside event, with its host.
2211
+ if (data.origin != null)
2212
+ about.push({ label: "From", parts: originParts(data.origin) });
2149
2213
  about.push({ label: "Filed", parts: [{ at: rootFiled }] });
2150
2214
  about.push({ label: "Updated", parts: [{ at: task.updatedAt }] });
2151
2215
  for (const label of ["Audit", "This attempt", "Task total"]) {
@@ -2204,6 +2268,21 @@ export function taskBodyParts(data) {
2204
2268
  attempts: [...earlierStopped].sort((a, b) => a.id - b.id).map(run => ({ label: `${runNoun(run)[0].toUpperCase()}${runNoun(run).slice(1)} #${run.id}`, href: `/r/${run.id}`,
2205
2269
  text: run.outcome === null ? "Never finished." : run.outcome === "failed" ? failedAttemptSentence(run.reason) : run.reason === null ? null : (words => `${words.charAt(0).toUpperCase()}${words.slice(1)}.`)(reasonWords(run.reason)) })),
2206
2270
  };
2271
+ // ⋯ More actions (docs/plans/one-page-task-and-agent.md): each secondary act once, in one place — Work, Share & reuse,
2272
+ // then Danger last. A ceremony is never cloned here: its item goes to the tab that holds its one form.
2273
+ const sectionIds = new Set(sectionParts.map(one => one.id));
2274
+ const rootOf = data.rootId ?? task.id;
2275
+ const more = [
2276
+ ...(runChecks !== null && data.csrf !== "" ? [{ group: "work", label: "Run checks", kind: "post", action: runChecks.action, fields: { level: runChecks.level, return: runChecks.returnTo } }] : []),
2277
+ ...(sectionIds.has("scope") && data.csrf !== "" ? [{ group: "work", label: approval.approved || finished ? "Plan and agents" : "Edit plan", kind: "goto", tab: "plan", target: "scope" }] : []),
2278
+ ...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
2279
+ ...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
2280
+ ...(data.csrf !== "" ? [{ group: "work", label: "Task options", kind: "goto", tab: "plan", target: "task-diagnostics" }] : []),
2281
+ { group: "share", label: "Copy link", kind: "copy" },
2282
+ ...(facts.some(one => one.label === "Audit") ? [{ group: "share", label: "Evidence pack", kind: "link", href: `${taskHref(rootOf)}/evidence` }] : []),
2283
+ ...(data.repo !== null && data.scope !== null ? [{ group: "share", label: "Reuse as recipe", kind: "link", href: `/recipes/from-task?task=${encodeURIComponent(task.id)}` }] : []),
2284
+ ...(cancelForm !== "" ? [{ group: "danger", label: "Cancel task", kind: "goto", tab: "plan", target: "cancel-task" }] : []),
2285
+ ];
2207
2286
  const view = {
2208
2287
  kind: "task",
2209
2288
  id: task.id,
@@ -2227,9 +2306,13 @@ export function taskBodyParts(data) {
2227
2306
  activity: liveRun === undefined ? null : data.activity ?? null,
2228
2307
  stop,
2229
2308
  pause,
2309
+ steering: data.csrf !== "" ? data.steerComposer ?? null : null,
2310
+ ...(data.csrf !== "" && (data.steerRecent?.length ?? 0) > 0 ? { steerRecent: data.steerRecent } : {}),
2230
2311
  record,
2231
2312
  earlier,
2232
2313
  approval: fragmentString(approvalHtml),
2314
+ approvalPlacement: approvalLeads ? "lead" : "plan",
2315
+ more,
2233
2316
  confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
2234
2317
  ? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
2235
2318
  rebuild,
@@ -33,6 +33,8 @@ export type RequestFacts = {
33
33
  expiresAt: number;
34
34
  etag: string;
35
35
  };
36
+ /** Passwordless is on and this is a signed-in browser: ceremony password fields render as nothing (html.ts reauth). */
37
+ passwordless?: boolean;
36
38
  };
37
39
  /**
38
40
  * The request's own session facts, readable from anywhere below the
@@ -1,6 +1,6 @@
1
1
  /** What one console request knows about itself, readable anywhere below its handler. */
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
- import { provideFormToken } from "../html.js";
3
+ import { provideFormToken, providePasswordless } from "../html.js";
4
4
  /**
5
5
  * The request's own session facts, readable from anywhere below the
6
6
  * dispatcher without threading them through forty call sites: the csrf
@@ -10,6 +10,7 @@ import { provideFormToken } from "../html.js";
10
10
  */
11
11
  export const requestContext = new AsyncLocalStorage();
12
12
  provideFormToken(() => requestContext.getStore()?.csrf);
13
+ providePasswordless(() => requestContext.getStore()?.passwordless === true);
13
14
  /** Run a render outside a live request (a test, a background render) as if a browser session with this token asked. */
14
15
  export function withFormToken(csrf, render) {
15
16
  const facts = requestContext.getStore();
@@ -83,6 +83,9 @@ export interface RouteDeclaration {
83
83
  readonly roleRefusal?: RouteRefusal;
84
84
  /** The largest form body a POST here reads (else the console's default). */
85
85
  readonly bodyCap?: number;
86
+ /** Cockpit B2: whether a password remembered on this session may stand in here (`allowlist`), or never. Only the
87
+ * allowlisted acts run their check inside `withFreshness` (scope.ts); every other step-up is `never`. */
88
+ readonly freshness: "never" | "allowlist";
86
89
  }
87
90
  /** The sole route table, in match order. */
88
91
  export declare const ROUTES: readonly RouteDeclaration[];