toolroll 0.9.85 → 0.9.87

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 (220) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/agent-fence.d.ts +6 -2
  3. package/dist/agent-fence.js +3 -2
  4. package/dist/agents-board.d.ts +12 -0
  5. package/dist/agents-board.js +26 -0
  6. package/dist/approval-rules-ui.d.ts +4 -1
  7. package/dist/approval-rules-ui.js +31 -15
  8. package/dist/assignment-ui.js +1 -1
  9. package/dist/assignment.js +4 -3
  10. package/dist/backup-ui.d.ts +5 -4
  11. package/dist/backup-ui.js +65 -32
  12. package/dist/browser/terminal.css +1 -1
  13. package/dist/browser/terminal.js +57 -54
  14. package/dist/browser/workspace.css +2 -2
  15. package/dist/browser/workspace.js +35 -68
  16. package/dist/browser-crew.js +1 -1
  17. package/dist/browser-workspace.d.ts +363 -177
  18. package/dist/browser-workspace.js +10 -9
  19. package/dist/chat-actions.d.ts +11 -1
  20. package/dist/chat-actions.js +53 -1
  21. package/dist/chat-channel.js +1 -0
  22. package/dist/chat-controls.d.ts +1 -1
  23. package/dist/chat-controls.js +1 -1
  24. package/dist/chat-quiet.js +3 -2
  25. package/dist/chat-settings-page.d.ts +29 -9
  26. package/dist/chat-settings-page.js +59 -28
  27. package/dist/check-levels-ui.d.ts +2 -1
  28. package/dist/check-levels-ui.js +67 -58
  29. package/dist/claim.d.ts +3 -0
  30. package/dist/claim.js +4 -1
  31. package/dist/contracts/chat-actions.d.ts +9 -0
  32. package/dist/contracts/chat-actions.js +3 -0
  33. package/dist/contracts/cli.d.ts +43 -0
  34. package/dist/contracts/cli.js +4 -0
  35. package/dist/contracts/console-api.d.ts +4 -1
  36. package/dist/contracts/console-api.js +4 -1
  37. package/dist/contracts/flow-propose.d.ts +4 -1
  38. package/dist/contracts/flow-propose.js +1 -1
  39. package/dist/contracts/flow.d.ts +41 -0
  40. package/dist/contracts/flow.js +4 -2
  41. package/dist/contracts/lead-tools.d.ts +21 -0
  42. package/dist/contracts/lead-tools.js +2 -0
  43. package/dist/contracts/store-json.d.ts +2 -0
  44. package/dist/contracts/trigger-payloads.d.ts +20 -0
  45. package/dist/contracts/trigger-payloads.js +4 -0
  46. package/dist/coordinator.js +1 -0
  47. package/dist/credentials-ui.d.ts +5 -3
  48. package/dist/credentials-ui.js +58 -31
  49. package/dist/deploy-gate.js +4 -0
  50. package/dist/design/kit.css +685 -0
  51. package/dist/design/kit.d.ts +208 -0
  52. package/dist/design/kit.js +177 -0
  53. package/dist/design/tokens.css +19 -1
  54. package/dist/discord-settings.js +8 -4
  55. package/dist/dispatch.d.ts +3 -0
  56. package/dist/dispatch.js +4 -1
  57. package/dist/export-ui.d.ts +6 -4
  58. package/dist/export-ui.js +18 -8
  59. package/dist/flow-browser.d.ts +103 -0
  60. package/dist/flow-browser.js +204 -0
  61. package/dist/flow-changed.d.ts +14 -0
  62. package/dist/flow-changed.js +53 -0
  63. package/dist/flow-code.d.ts +5 -1
  64. package/dist/flow-code.js +4 -1
  65. package/dist/flow-engine.js +16 -0
  66. package/dist/flow-gallery-ui.d.ts +41 -4
  67. package/dist/flow-gallery-ui.js +95 -63
  68. package/dist/flow-live.js +9 -1
  69. package/dist/flow-pull-request.js +15 -7
  70. package/dist/flow-share.js +3 -1
  71. package/dist/flow-starters-ui.d.ts +3 -9
  72. package/dist/flow-starters-ui.js +12 -23
  73. package/dist/flow-steps.d.ts +9 -1
  74. package/dist/flow-steps.js +55 -5
  75. package/dist/flow-triggers.js +55 -0
  76. package/dist/flow-unattended-words.d.ts +20 -0
  77. package/dist/flow-unattended-words.js +24 -0
  78. package/dist/flow-unattended.d.ts +9 -5
  79. package/dist/flow-unattended.js +45 -23
  80. package/dist/flows-cli.d.ts +19 -0
  81. package/dist/flows-cli.js +54 -5
  82. package/dist/flows-ui.d.ts +12 -6
  83. package/dist/flows-ui.js +152 -32
  84. package/dist/flows.js +5 -2
  85. package/dist/integrations-ui.d.ts +6 -4
  86. package/dist/integrations-ui.js +34 -40
  87. package/dist/integrations.d.ts +2 -2
  88. package/dist/integrations.js +4 -4
  89. package/dist/kits-ui.d.ts +8 -10
  90. package/dist/kits-ui.js +29 -33
  91. package/dist/kits.js +1 -1
  92. package/dist/knowledge-ui.js +76 -19
  93. package/dist/lead-tools.js +35 -6
  94. package/dist/lead-voice.js +16 -5
  95. package/dist/ledger-view.d.ts +2 -0
  96. package/dist/ledger-view.js +41 -38
  97. package/dist/models-ui.d.ts +8 -3
  98. package/dist/models-ui.js +82 -59
  99. package/dist/monitoring-ui.d.ts +4 -2
  100. package/dist/monitoring-ui.js +44 -13
  101. package/dist/needs-you.d.ts +1 -1
  102. package/dist/needs-you.js +8 -0
  103. package/dist/operate-remote-arguments.js +1 -0
  104. package/dist/operate-remote.js +1 -1
  105. package/dist/operate.d.ts +1 -1
  106. package/dist/operate.js +249 -6
  107. package/dist/passwordless.d.ts +1 -1
  108. package/dist/passwordless.js +1 -1
  109. package/dist/people-audit-ui.d.ts +2 -1
  110. package/dist/people-audit-ui.js +25 -23
  111. package/dist/policy-ui.d.ts +3 -2
  112. package/dist/policy-ui.js +27 -21
  113. package/dist/policy.d.ts +3 -0
  114. package/dist/policy.js +14 -1
  115. package/dist/project-delete-ui.d.ts +4 -2
  116. package/dist/project-delete-ui.js +135 -126
  117. package/dist/project-delete.js +7 -3
  118. package/dist/project-knowledge.d.ts +10 -10
  119. package/dist/publish.d.ts +11 -0
  120. package/dist/publish.js +16 -3
  121. package/dist/pull-request-flow.js +35 -4
  122. package/dist/pull-request-ui.d.ts +2 -1
  123. package/dist/pull-request-ui.js +39 -28
  124. package/dist/recipe-ui.d.ts +4 -2
  125. package/dist/recipe-ui.js +14 -4
  126. package/dist/release-adapters.d.ts +3 -0
  127. package/dist/release-adapters.js +22 -2
  128. package/dist/release-cli.js +81 -9
  129. package/dist/release.d.ts +21 -0
  130. package/dist/release.js +40 -4
  131. package/dist/releases.js +1 -1
  132. package/dist/request-budget-ui.d.ts +4 -2
  133. package/dist/request-budget-ui.js +30 -19
  134. package/dist/result-follow-ups.js +4 -0
  135. package/dist/retention-ui.d.ts +4 -3
  136. package/dist/retention-ui.js +15 -12
  137. package/dist/scope.d.ts +8 -0
  138. package/dist/scope.js +13 -0
  139. package/dist/serve.d.ts +0 -1
  140. package/dist/serve.js +51 -7
  141. package/dist/server/agents-computers.d.ts +9 -0
  142. package/dist/server/agents-computers.js +67 -0
  143. package/dist/server/chat.js +20 -18
  144. package/dist/server/chrome.d.ts +1 -1
  145. package/dist/server/chrome.js +19 -9
  146. package/dist/server/console.css +31 -2
  147. package/dist/server/flows.js +79 -36
  148. package/dist/server/home.d.ts +35 -0
  149. package/dist/server/home.js +104 -0
  150. package/dist/server/http.d.ts +3 -0
  151. package/dist/server/http.js +5 -0
  152. package/dist/server/kit-standalone.d.ts +22 -0
  153. package/dist/server/kit-standalone.js +69 -0
  154. package/dist/server/kit.d.ts +405 -0
  155. package/dist/server/kit.js +361 -0
  156. package/dist/server/pages.d.ts +2 -1
  157. package/dist/server/pages.js +213 -174
  158. package/dist/server/people-tokens.js +26 -85
  159. package/dist/server/render-history.d.ts +51 -0
  160. package/dist/server/render-history.js +228 -0
  161. package/dist/server/render-new-task.d.ts +29 -0
  162. package/dist/server/render-new-task.js +96 -0
  163. package/dist/server/render-next.d.ts +63 -0
  164. package/dist/server/render-next.js +109 -0
  165. package/dist/server/render-pages.d.ts +35 -190
  166. package/dist/server/render-pages.js +154 -563
  167. package/dist/server/render-people.d.ts +24 -3
  168. package/dist/server/render-people.js +93 -72
  169. package/dist/server/render-projects.d.ts +103 -0
  170. package/dist/server/render-projects.js +247 -0
  171. package/dist/server/render-results.d.ts +5 -24
  172. package/dist/server/render-results.js +48 -185
  173. package/dist/server/render-settings.d.ts +5 -74
  174. package/dist/server/render-settings.js +102 -493
  175. package/dist/server/render-tasks.d.ts +43 -5
  176. package/dist/server/render-tasks.js +131 -91
  177. package/dist/server/route-table.js +31 -5
  178. package/dist/server/settings.js +276 -255
  179. package/dist/server/task-views.d.ts +8 -0
  180. package/dist/server/task-views.js +90 -2
  181. package/dist/server/tasks.d.ts +18 -0
  182. package/dist/server/tasks.js +148 -41
  183. package/dist/server/terminals.js +1 -1
  184. package/dist/server/welcome.d.ts +22 -0
  185. package/dist/server/welcome.js +69 -0
  186. package/dist/settings-groups-ui.d.ts +279 -0
  187. package/dist/settings-groups-ui.js +615 -0
  188. package/dist/settings-kit.d.ts +42 -6
  189. package/dist/settings-kit.js +69 -7
  190. package/dist/settings-proofs.js +1 -0
  191. package/dist/setup-guide.d.ts +2 -2
  192. package/dist/skills-ui.js +59 -7
  193. package/dist/slack-settings.js +12 -5
  194. package/dist/spend-ui.d.ts +45 -26
  195. package/dist/spend-ui.js +124 -56
  196. package/dist/sso-ui.d.ts +5 -2
  197. package/dist/sso-ui.js +63 -31
  198. package/dist/storage-ui.d.ts +3 -2
  199. package/dist/storage-ui.js +56 -50
  200. package/dist/store.d.ts +182 -5
  201. package/dist/store.js +512 -38
  202. package/dist/surface.js +23 -3
  203. package/dist/task-archive.d.ts +10 -0
  204. package/dist/task-archive.js +16 -0
  205. package/dist/task-control.d.ts +1 -1
  206. package/dist/teams-settings.d.ts +2 -1
  207. package/dist/teams-settings.js +24 -41
  208. package/dist/telegram-settings.d.ts +0 -4
  209. package/dist/telegram-settings.js +52 -25
  210. package/dist/tidy.d.ts +130 -0
  211. package/dist/tidy.js +503 -0
  212. package/dist/toolroll-update-ui.d.ts +8 -4
  213. package/dist/toolroll-update-ui.js +55 -45
  214. package/dist/tools-ui.d.ts +5 -3
  215. package/dist/tools-ui.js +107 -42
  216. package/dist/work-index.d.ts +24 -0
  217. package/dist/work-index.js +56 -30
  218. package/dist/workspace-ui.d.ts +1 -1
  219. package/dist/workspace-ui.js +8 -4
  220. package/package.json +1 -1
@@ -17,12 +17,11 @@ import { type CriterionMatrixRow, type ProofVerdict } from "../proof.js";
17
17
  import { type Phase } from "../provider.js";
18
18
  import { type QualityMode } from "../quality.js";
19
19
  import { type AcceptanceCriterion, type Scope, type UnattendedPermissionMode } from "../scope.js";
20
- import { type Artifact, type CheckProgress, type CoordinatorProposal, type Decision, type ExternalMirror, type Hold, type Incident, type PlanRevisionKind, type PlanRevisionStatus, type Publication, type PublicationGrant, type RepairChainRow, type ReviewRetryState, type RevisionLineage, type Run, type SteerNote, type Store, type Task } from "../store.js";
20
+ import { type CheckProgress, type CoordinatorProposal, type Decision, type ExternalMirror, type Hold, type Incident, type PlanRevisionKind, type PlanRevisionStatus, type Publication, type PublicationGrant, type RepairChainRow, type ReviewRetryState, type RevisionLineage, type Run, type SteerNote, type Store, type Task } from "../store.js";
21
21
  import { type TaskControlView } from "../task-control.js";
22
22
  import { type DisplayStatus, type PublicationFacts, type ReviewFacts, type WorkStatus } from "../workspace-ui.js";
23
23
  import { type Chrome, type Screen } from "./chrome.js";
24
24
  import { type CompletionReceiptView, type EvidenceLinkMap, type TaskPullRequest } from "./render-results.js";
25
- import { type Who } from "./session.js";
26
25
  import { type TaskOrigin } from "../task-source.js";
27
26
  /** What the task and approval views show about a drafted plan's contract
28
27
  * (contract handoff, task 1). A problem is a named problem, never a blank. */
@@ -75,7 +74,7 @@ export declare function revisionSentence(revision: Extract<RevisionView, {
75
74
  /** A planner's amendment, said plainly and kept in view: approving binds
76
75
  * the amended terms, so this is never folded away. */
77
76
  export declare function approvalAmendmentHtml(view: PlanContractView | null | undefined): Html;
78
- export declare function approvalSheetHtml(input: {
77
+ export type ApprovalSheetInput = {
79
78
  surface: "task" | "chat";
80
79
  action: string;
81
80
  csrf: string;
@@ -120,7 +119,24 @@ export declare function approvalSheetHtml(input: {
120
119
  } | null;
121
120
  /** Cockpit B2: this person's password is remembered (when, ISO) and these terms may stand on it. */
122
121
  freshSince?: string | null;
123
- }): Html;
122
+ };
123
+ /** The approval sheet: one form that restates every term it signs, the password, and Approve & start. */
124
+ export declare function approvalSheetHtml(input: ApprovalSheetInput): Html;
125
+ /** The approval sheet's parts: what it says first, the signed rows, the in-place editor and its own form, the
126
+ * details fold, and the words the act and its outcome use. The task page (src/design/KIT.md) shows the rows in its
127
+ * Plan tab and the act in its approval box; the chat and /next keep the one sheet. */
128
+ export declare function approvalSheetParts(input: ApprovalSheetInput): {
129
+ lead: Html;
130
+ rows: Html;
131
+ editor: Html | null;
132
+ editorForm: Html | string;
133
+ details: Html;
134
+ submit: Html;
135
+ label: string;
136
+ after: Html;
137
+ allowing: string[];
138
+ passwordNote: string;
139
+ };
124
140
  /** Earlier versions of a task still in flight, in plain words: queued
125
141
  * while they only wait, running once one has started; null when none. */
126
142
  export declare function earlierVersionsWords(active: number, running: number): string | null;
@@ -438,6 +454,14 @@ export declare function taskBodyParts(data: {
438
454
  /** Markup the caller rendered (the version history). */
439
455
  history?: Html;
440
456
  versionLabel?: string | null;
457
+ /** Put away (v145): by whom and why; and whether the viewer can put it away or restore it (an approver's browser). */
458
+ archived?: {
459
+ by: string;
460
+ byKind: "person" | "tidy";
461
+ reason: string;
462
+ at: string;
463
+ } | null;
464
+ canPutAway?: boolean;
441
465
  status?: DisplayStatus;
442
466
  task: Task;
443
467
  /** Shared read-side lifecycle answer; the atomic claim still re-proves it. */
@@ -685,9 +709,23 @@ export declare function qualityModeChoices(name: string, selected: QualityMode):
685
709
  * writes the field); kept for callers.
686
710
  */
687
711
  export declare function decisionOptionForms(decision: Decision, returnTo: string | null): Html;
688
- export declare function decisionPage(chrome: Chrome, decision: Decision, taskId: string, artifacts: Artifact[], who: Who, now: Date, returnTo?: string | null): Screen;
689
712
  export declare function taskOf(store: Store, decision: Decision): string;
690
713
  /** A release task's own card (release-once.md, "What people see"): the release it's approved for, the later commits
691
714
  * carried under it (on demand), what a later check must run again, the last nightly check of main, and one action to
692
715
  * stop carrying. A carried task says whose approval it was carried from. */
693
716
  export declare function releaseCardHtml(taskId: string, release: ReleaseView | null, canStop: boolean): Html;
717
+ /** The task page's approval box (src/design/KIT.md): what it asks, the act's own form fields, and the facts the
718
+ * approve control reads to show a password, one click, or nothing. The rows it signs go to the Plan tab (`planHtml`).
719
+ * These decide only what the page shows: the approve route proves the person, the freshness and the digest again. */
720
+ export declare function approveKitOf(input: ApprovalSheetInput, freshSince: string | null): NonNullable<BrowserTaskView["approve"]>;
721
+ /** How an approved plan was approved, for the status box's terms line: by whom and how, or the rule that approved it. */
722
+ export declare function approvedKitOf(scope: Scope, approval: {
723
+ approved: true;
724
+ at: string;
725
+ by: string;
726
+ } | {
727
+ approved: false;
728
+ }, proof: {
729
+ method: string;
730
+ at: string;
731
+ } | null): NonNullable<BrowserTaskView["approved"]> | null;
@@ -1,4 +1,6 @@
1
1
  /** Task pages: approval, agents, plans, controls, decisions and the task body. */
2
+ import { archivedWords } from "../tidy.js";
3
+ import { whenUtc } from "../when-html.js";
2
4
  import { networkWords } from "../egress.js";
3
5
  import { releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
4
6
  import { RELEASE_CI_RUNNING_WORDS } from "../release-ci-hold.js";
@@ -6,7 +8,7 @@ import { writesWords } from "../writes-mode.js";
6
8
  import { checkAccessWords } from "../check-access.js";
7
9
  import { sandboxTermsWords } from "../project-sandbox.js";
8
10
  import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
9
- import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
11
+ import { html, htmlString, joinHtml, passwordlessRendering, postForm, replaceMarkup, reauth } from "../html.js";
10
12
  import { closedQuestionReason, closedQuestionWords } from "../decision.js";
11
13
  import {} from "../activity-line.js";
12
14
  import {} from "../agentconfig.js";
@@ -36,7 +38,7 @@ import { agentModelName } from "../model-names.js";
36
38
  import {} from "../provider.js";
37
39
  import { qualityModeTitle } from "../quality.js";
38
40
  import { evidenceShortenedWords } from "../result-review.js";
39
- import { acceptanceToLines, approvalOf, scopeAuthorityOf } from "../scope.js";
41
+ import { acceptanceToLines, approvalOf, freshUntil, scopeAuthorityOf } from "../scope.js";
40
42
  import {} from "../store.js";
41
43
  import { runCostWords } from "../summary.js";
42
44
  import {} from "../task-control.js";
@@ -228,7 +230,34 @@ function originParts(origin) {
228
230
  }
229
231
  return parts;
230
232
  }
233
+ /** The approval sheet: one form that restates every term it signs, the password, and Approve & start. */
231
234
  export function approvalSheetHtml(input) {
235
+ const parts = approvalSheetParts(input);
236
+ return joinHtml([
237
+ postForm(input.action, joinHtml([
238
+ html `<input type="text" name="username" autocomplete="username" class="visually-hidden" tabindex="-1" aria-hidden="true">`,
239
+ parts.lead,
240
+ parts.rows,
241
+ html `<div class="approval-act" id="${input.surface === "task" ? "approval-confirm" : "chat-approval-confirm"}">`,
242
+ 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="${parts.passwordNote}"></label>`)}`,
243
+ input.freshSince == null ? html `<p class="approval-password-note" id="${parts.passwordNote}">Your password signs this approval.</p>`
244
+ : html `<p class="approval-password-note" id="${parts.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>`,
245
+ html `<button type="submit" data-primary-action>${parts.submit}</button></div>`,
246
+ html `<p class="approval-after">${parts.after}</p>`,
247
+ html `<div class="approval-secondary">${parts.editor ?? html `<a class="approval-link" href="${input.editHref}">Edit plan</a>`}<a class="approval-link" href="${input.notNowHref}">Not now</a></div>`,
248
+ parts.details,
249
+ ]), {
250
+ attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
251
+ hidden: { nonce: input.nonce, digest: input.digest, ...(input.rebuild == null ? {} : { rebuild: String(input.rebuild) }) },
252
+ returnTo: input.returnTo,
253
+ }),
254
+ parts.editorForm,
255
+ ]);
256
+ }
257
+ /** The approval sheet's parts: what it says first, the signed rows, the in-place editor and its own form, the
258
+ * details fold, and the words the act and its outcome use. The task page (src/design/KIT.md) shows the rows in its
259
+ * Plan tab and the act in its approval box; the chat and /next keep the one sheet. */
260
+ export function approvalSheetParts(input) {
232
261
  const { scope, route } = input;
233
262
  // A machine-drafted repair appends its brief to the signed goal; the row
234
263
  // shows the goal and one sentence says what the repair fixes. The whole
@@ -390,40 +419,27 @@ export function approvalSheetHtml(input) {
390
419
  "budget-microusd": scope.budgetMicrousd === null ? "none" : String(scope.budgetMicrousd),
391
420
  },
392
421
  });
393
- return joinHtml([
394
- postForm(input.action, joinHtml([
395
- html `<input type="text" name="username" autocomplete="username" class="visually-hidden" tabindex="-1" aria-hidden="true">`,
396
- html `<h2 class="approval-sheet-title">${input.deliverable === "report" ? "The investigation" : "The plan"}</h2>`,
397
- sentence === null ? "" : html `<p class="approval-revision">${sentence}</p>`,
398
- 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>`,
399
- input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "",
400
- originNote(input.origin) === null ? "" : html `<p class="approval-note" data-approval-origin>${originNote(input.origin)}</p>`,
401
- html `<dl class="approval-rows">`,
402
- row("Goal", html `<p class="approval-goal">${goalShown}</p>`),
403
- row("Changes", changes),
404
- row("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
405
- row("Done when", doneWhen),
406
- html `</dl>`,
407
- amendment,
408
- // The size, said once and plainly beside who builds: "Small change: fast model, no plan".
409
- projection === null ? "" : sizeLineHtml(projection),
410
- who.length === 0 ? "" : html `<p class="approval-who">${who.join(" · ")}</p>`,
411
- consent,
412
- html `<div class="approval-act" id="${input.surface === "task" ? "approval-confirm" : "chat-approval-confirm"}">`,
413
- 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>`)}`,
414
- input.freshSince == null ? html `<p class="approval-password-note" id="${passwordNote}">Your password signs this approval.</p>`
415
- : 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>`,
416
- html `<button type="submit" data-primary-action>${submit}</button></div>`,
417
- html `<p class="approval-after">${after}</p>`,
418
- 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>`,
419
- details,
420
- ]), {
421
- attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
422
- hidden: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
423
- returnTo: input.returnTo,
424
- }),
425
- editorForm,
422
+ const lead = joinHtml([
423
+ html `<h2 class="approval-sheet-title">${input.deliverable === "report" ? "The investigation" : "The plan"}</h2>`,
424
+ sentence === null ? "" : html `<p class="approval-revision">${sentence}</p>`,
425
+ 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>`,
426
+ input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "",
427
+ originNote(input.origin) === null ? "" : html `<p class="approval-note" data-approval-origin>${originNote(input.origin)}</p>`,
426
428
  ]);
429
+ const rows = joinHtml([
430
+ html `<dl class="approval-rows">`,
431
+ row("Goal", html `<p class="approval-goal">${goalShown}</p>`),
432
+ row("Changes", changes),
433
+ row("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
434
+ row("Done when", doneWhen),
435
+ html `</dl>`,
436
+ amendment,
437
+ // The size, said once and plainly beside who builds: "Small change: fast model, no plan".
438
+ projection === null ? "" : sizeLineHtml(projection),
439
+ who.length === 0 ? "" : html `<p class="approval-who">${who.join(" · ")}</p>`,
440
+ consent,
441
+ ]);
442
+ return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and build again" : "Approve and start", after, allowing, passwordNote };
427
443
  }
428
444
  /** Earlier versions of a task still in flight, in plain words: queued
429
445
  * while they only wait, running once one has started; null when none. */
@@ -774,6 +790,8 @@ export function approveRefusalWords(reason) {
774
790
  return gateWords({ verdict: "refuse", reason });
775
791
  if (reason === "machine-proof")
776
792
  return "not approved: only a person can approve this. Approve it on the task page or from your paired chat";
793
+ if (reason === "archived")
794
+ return "not approved: this task is put away. Restore it first";
777
795
  if (reason === "unrouted")
778
796
  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";
779
797
  if (reason === "profile-unresolved")
@@ -1623,6 +1641,36 @@ export function taskBodyParts(data) {
1623
1641
  // assignment's one action): then it is shown open under the status, never folded.
1624
1642
  const approvalLeads = data.dispatch?.action === "approve-scope" ||
1625
1643
  (data.assignment?.primaryAction?.code === "approve-scope" && data.assignment.primaryAction.target.taskId === task.id);
1644
+ // The sheet's terms, when this person can approve them here: the task page's kit approval box (its Plan tab shows
1645
+ // the rows) and the legacy sheet both read these.
1646
+ const sheetInput = scope === null || approval.approved || data.plan === "requested" || (data.revision != null && "problem" in data.revision) ||
1647
+ scope.profileState === "unresolved" || !consentDoor.open ? null : {
1648
+ surface: "task",
1649
+ action: `${taskHref(task.id)}/approve`,
1650
+ csrf: data.csrf,
1651
+ nonce: data.nonce,
1652
+ digest: data.approvalDigest ?? scope.digest,
1653
+ freshSince: data.approvalFresh ?? null,
1654
+ returnTo: null,
1655
+ scope,
1656
+ planDocument: data.plan === "drafted" ? data.planDocument : null,
1657
+ planContract: data.plan === "drafted" ? data.planContract ?? null : null,
1658
+ revision: data.revision == null || "problem" in data.revision ? null : data.revision,
1659
+ revisionSourceHref: data.revision == null || "problem" in data.revision ? "" : `/r/${data.revision.sourceRun}`,
1660
+ repairChain: data.repairChain ?? null,
1661
+ route: data.route,
1662
+ coordinator: data.coordinator ?? null,
1663
+ origin: data.origin ?? null,
1664
+ deliverable: data.deliverable ?? "branch",
1665
+ rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
1666
+ editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
1667
+ notNowHref: "/work",
1668
+ sticky: approvalLeads,
1669
+ earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
1670
+ edit: data.csrf === "" ? null
1671
+ : { action: `${taskHref(task.id)}/scope`, draft: inlineScopeDraft ? data.scopeDraft ?? null : null, problem: inlineScopeDraft ? data.problem : null,
1672
+ open: data.editPlan === true, stepsHref: data.planSha != null && data.planDocument !== null ? "#plan-edit" : null },
1673
+ };
1626
1674
  const approveForm = scope === null || approval.approved || data.plan === "requested"
1627
1675
  ? ""
1628
1676
  : data.revision !== null && data.revision !== undefined && "problem" in data.revision
@@ -1631,33 +1679,7 @@ export function taskBodyParts(data) {
1631
1679
  ? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: its scope cannot be approved yet.</strong></p>${profileWords(scope)}<p class="ceremony-road"><a class="button-link" href="#scope">Edit the scope to fix it →</a></p></div>`
1632
1680
  : !consentDoor.open
1633
1681
  ? consentClosedHtml(task.id, consentDoor, "task")
1634
- : approvalSheetHtml({
1635
- surface: "task",
1636
- action: `${taskHref(task.id)}/approve`,
1637
- csrf: data.csrf,
1638
- nonce: data.nonce,
1639
- digest: data.approvalDigest ?? scope.digest,
1640
- freshSince: data.approvalFresh ?? null,
1641
- returnTo: null,
1642
- scope,
1643
- planDocument: data.plan === "drafted" ? data.planDocument : null,
1644
- planContract: data.plan === "drafted" ? data.planContract ?? null : null,
1645
- revision: data.revision == null || "problem" in data.revision ? null : data.revision,
1646
- revisionSourceHref: data.revision == null || "problem" in data.revision ? "" : `/r/${data.revision.sourceRun}`,
1647
- repairChain: data.repairChain ?? null,
1648
- route: data.route,
1649
- coordinator: data.coordinator ?? null,
1650
- origin: data.origin ?? null,
1651
- deliverable: data.deliverable ?? "branch",
1652
- rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
1653
- editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
1654
- notNowHref: "/work",
1655
- sticky: approvalLeads,
1656
- earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
1657
- edit: data.csrf === "" ? null
1658
- : { action: `${taskHref(task.id)}/scope`, draft: inlineScopeDraft ? data.scopeDraft ?? null : null, problem: inlineScopeDraft ? data.problem : null,
1659
- open: data.editPlan === true, stepsHref: data.planSha != null && data.planDocument !== null ? "#plan-edit" : null },
1660
- });
1682
+ : approvalSheetHtml(sheetInput);
1661
1683
  const scopeForm = joinHtml([
1662
1684
  html `<details${scope === null || scopeDraft !== undefined ? html ` open` : ""}><summary>${scope === null ? "Write the scope" : "Edit the scope"}${approval.approved ? " (editing voids the approval)" : ""}</summary>`,
1663
1685
  postForm(`${taskHref(task.id)}/scope`, joinHtml([
@@ -2068,8 +2090,11 @@ export function taskBodyParts(data) {
2068
2090
  // The title is bare; the receipt or shared task status leads once.
2069
2091
  html `<div class="task-title-row"><h1 class="task-main-title">${data.rootTitle ?? task.title}</h1>${data.csrf === "" ? "" : taskViewSwitch(data.rootId ?? task.id, "overview")}</div>`,
2070
2092
  data.versionLabel == null ? "" : html `<p class="meta">${data.versionLabel} · <a href="${taskHref(data.rootId ?? task.id)}">Current work</a></p>`,
2093
+ data.archived == null ? "" : html `<div class="task-archived" data-archived><p>${archivedWords(data.archived)} · ${whenUtc(data.archived.at)}</p>${data.canPutAway === true && data.csrf !== ""
2094
+ ? postForm(`${taskHref(task.id)}/restore`, html `<button type="submit" class="secondary">Restore</button>`, { hidden: { return: taskHref(task.id) }, attrs: { class: "inline" } }) : ""}</div>`,
2071
2095
  (data.history ?? ""),
2072
- statusHtml,
2096
+ // A put-away task's banner is its status: no Needs you card, no act, no approval form (each would be refused).
2097
+ data.archived == null ? statusHtml : "",
2073
2098
  controlHtml,
2074
2099
  previousResult === null ? "" : html `<details class="task-previous-result"><summary>${previousResult.title}</summary>${previousResult.html}</details>`,
2075
2100
  progressCard,
@@ -2078,7 +2103,7 @@ export function taskBodyParts(data) {
2078
2103
  mirrorCard,
2079
2104
  problemHtml,
2080
2105
  needsScopeCard,
2081
- approvalHtml,
2106
+ data.archived == null ? approvalHtml : "",
2082
2107
  html `<details class="task-status-details" id="task-diagnostics"${optionsOpen ? html ` open` : ""}><summary>Task options</summary>${optionsHtml}</details>`,
2083
2108
  // Evidence-first (M5.5): what needs you, then what happened — decisions
2084
2109
  // and incidents above the attempt ledger and spend, the mechanics
@@ -2209,7 +2234,8 @@ export function taskBodyParts(data) {
2209
2234
  // The Details panel: the same facts, grouped and said once.
2210
2235
  const lastRunForAgent = liveRun ?? familyRuns.filter(run => run.role !== "reviewer").sort((a, b) => b.id - a.id)[0];
2211
2236
  const work = [];
2212
- work.push({ label: "Status", parts: [data.assignment != null && assignmentOptions !== null ? assignmentCardOf(data.assignment, assignmentOptions).status.headline : status.label] });
2237
+ // A put-away task's one status is Archived (its banner says by whom and why).
2238
+ work.push({ label: "Status", parts: [data.archived != null ? "Archived" : data.assignment != null && assignmentOptions !== null ? assignmentCardOf(data.assignment, assignmentOptions).status.headline : status.label] });
2213
2239
  if (data.repo !== null)
2214
2240
  work.push({ label: "Project", parts: [projectName(data.repo)] });
2215
2241
  if (data.base != null)
@@ -2320,6 +2346,7 @@ export function taskBodyParts(data) {
2320
2346
  ...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
2321
2347
  ...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
2322
2348
  ...(data.csrf !== "" ? [{ group: "work", label: "Task options", kind: "goto", tab: "plan", target: "task-diagnostics" }] : []),
2349
+ ...(data.canPutAway === true && data.csrf !== "" && data.archived == null && !building ? [{ group: "work", label: "Put away", kind: "post", action: `${taskHref(task.id)}/archive`, fields: { return: taskHref(task.id) } }] : []),
2323
2350
  { group: "share", label: "Copy link", kind: "copy" },
2324
2351
  ...(facts.some(one => one.label === "Audit") ? [{ group: "share", label: "Evidence pack", kind: "link", href: `${taskHref(rootOf)}/evidence` }] : []),
2325
2352
  ...(data.repo !== null && data.scope !== null ? [{ group: "share", label: "Reuse as recipe", kind: "link", href: `/recipes/from-task?task=${encodeURIComponent(task.id)}` }] : []),
@@ -2336,10 +2363,12 @@ export function taskBodyParts(data) {
2336
2363
  { label: "Ask", href: taskChatHref(data.rootId ?? task.id), active: false },
2337
2364
  ],
2338
2365
  version: data.versionLabel == null ? null : { label: data.versionLabel, current: { label: "Current work", href: taskHref(data.rootId ?? task.id) } },
2339
- status: statusCard === null ? null : inPlaceChecks(failedCard ? { ...statusCard, action: null, status: { ...statusCard.status, sentence: data.failure?.line ?? statusCard.status.sentence } }
2366
+ archived: data.archived == null ? null : { words: archivedWords(data.archived), at: data.archived.at,
2367
+ restore: data.canPutAway === true && data.csrf !== "" ? { action: `${taskHref(task.id)}/restore`, returnTo: taskHref(task.id) } : null },
2368
+ status: statusCard === null || data.archived != null ? null : inPlaceChecks(failedCard ? { ...statusCard, action: null, status: { ...statusCard.status, sentence: data.failure?.line ?? statusCard.status.sentence } }
2340
2369
  : progress !== null || record !== null ? { ...statusCard, action: statusCard.action?.href === record?.href ? null : statusCard.action, status: { ...statusCard.status, sentence: progress?.line ?? statusCard.status.sentence } }
2341
2370
  : statusCard.action !== null && rebuild === null && bareSelfLink(statusCard.action.href) ? { ...statusCard, action: null } : statusCard),
2342
- statusHtml: htmlString(statusHtml),
2371
+ statusHtml: data.archived != null ? "" : htmlString(statusHtml),
2343
2372
  failure: failedCard && data.failure != null ? { line: data.failure.line, evidence: data.failure.evidence, suggestion: data.failure.suggestion, link: data.failure.link } : null,
2344
2373
  retry,
2345
2374
  runChecks,
@@ -2352,8 +2381,11 @@ export function taskBodyParts(data) {
2352
2381
  ...(data.csrf !== "" && (data.steerRecent?.length ?? 0) > 0 ? { steerRecent: data.steerRecent } : {}),
2353
2382
  record,
2354
2383
  earlier,
2355
- approval: fragmentString(approvalHtml),
2384
+ approval: data.archived != null ? "" : fragmentString(approvalHtml),
2356
2385
  approvalPlacement: approvalLeads ? "lead" : "plan",
2386
+ // The kit's approval box (src/design/KIT.md): the act, by the real rules, with the rows in the Plan tab.
2387
+ approve: data.archived != null || sheetInput === null || dependencyChoiceNeeded || data.csrf === "" ? null : approveKitOf(sheetInput, data.approvalFresh ?? null),
2388
+ approved: scope !== null && approval.approved ? approvedKitOf(scope, approval, data.approvalProof ?? null) : null,
2357
2389
  more,
2358
2390
  confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
2359
2391
  ? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
@@ -2477,29 +2509,6 @@ export function decisionOptionForms(decision, returnTo) {
2477
2509
  : html `<details class="arm-danger"><summary>${option.label} — irreversible, tap to arm</summary>${inner}</details>`;
2478
2510
  }), "\n");
2479
2511
  }
2480
- export function decisionPage(chrome, decision, taskId, artifacts, who, now, returnTo = null) {
2481
- const options = decisionOptionForms(decision, returnTo);
2482
- const answered = decision.supersededReason !== null
2483
- ? html `<p class="meta">${closedQuestionReason(decision.supersededReason)}</p>`
2484
- : decision.state === "answered"
2485
- ? html `<div class="answered">Answered: <strong>${decision.choice ?? ""}</strong> by ${decision.answeredBy ?? ""}${decision.note === null ? "" : ` — ${decision.note}`}</div>`
2486
- : "";
2487
- const evidence = artifacts.length === 0
2488
- ? ""
2489
- : joinHtml([
2490
- html `<div class="evidence"><strong>Evidence</strong>`,
2491
- joinHtml(artifacts.map(artifact => html `<a href="/d/${decision.id}/evidence/${artifact.id}">${evidenceWords(artifact.kind)}${artifact.truncated ? " (truncated)" : ""} · ${artifact.bytesStored} bytes</a>`), "\n"),
2492
- html `</div>`,
2493
- ]);
2494
- return screen(`decide · ${taskId}`, joinHtml([
2495
- html `<h1>${taskId} <span class="badge badge-${decision.state}">${decision.supersededReason !== null ? "closed" : decision.state}</span>${isOverdue(decision, now) ? html ` <span class="badge badge-overdue">Overdue</span>` : ""}${decision.deadline === null ? "" : html ` <span class="meta">deadline ${decision.deadline}</span>`}</h1>`,
2496
- html `<div class="recap">${decision.recap}</div>`,
2497
- html `<div class="question">${decision.question}</div>`,
2498
- decision.state === "answered" ? answered : options,
2499
- evidence,
2500
- html `<p class="meta"><a href="${returnTo === null ? "/" : returnTo}">← ${returnTo === null ? "everything waiting" : "back to the task chat"}</a></p>`,
2501
- ], "\n"), { chrome });
2502
- }
2503
2512
  export function taskOf(store, decision) {
2504
2513
  const run = store.getRun(decision.run);
2505
2514
  return run === null ? "?" : store.externalIdFor(run.taskRef) ?? "?";
@@ -2527,3 +2536,34 @@ export function releaseCardHtml(taskId, release, canStop) {
2527
2536
  html `</div>`,
2528
2537
  ], "\n");
2529
2538
  }
2539
+ /** The task page's approval box (src/design/KIT.md): what it asks, the act's own form fields, and the facts the
2540
+ * approve control reads to show a password, one click, or nothing. The rows it signs go to the Plan tab (`planHtml`).
2541
+ * These decide only what the page shows: the approve route proves the person, the freshness and the digest again. */
2542
+ export function approveKitOf(input, freshSince) {
2543
+ const parts = approvalSheetParts(input);
2544
+ const rebuild = input.rebuild ?? null;
2545
+ const after = rebuild !== null ? "builds it again to this plan" : input.deliverable === "report" ? "starts the investigation" : "starts the build";
2546
+ const allowing = parts.allowing.length === 0 ? "" : ` You’re allowing ${parts.allowing.join(", ")}.`;
2547
+ const until = freshSince === null ? null : freshUntil(freshSince);
2548
+ return {
2549
+ action: input.action,
2550
+ fields: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
2551
+ label: parts.label,
2552
+ heading: rebuild !== null ? "The plan changed" : input.deliverable === "report" ? "Investigation ready" : "Plan ready",
2553
+ text: `Approving signs plan version ${input.scope.digest.slice(0, 12)} and ${after}.${allowing}`,
2554
+ kind: "standard",
2555
+ facts: { passwordless: passwordlessRendering(), remembered: until !== null },
2556
+ rememberedUntil: until,
2557
+ planHtml: htmlString(joinHtml([parts.lead, parts.rows, parts.editor ?? "", parts.details, parts.editorForm])),
2558
+ };
2559
+ }
2560
+ /** How an approved plan was approved, for the status box's terms line: by whom and how, or the rule that approved it. */
2561
+ export function approvedKitOf(scope, approval, proof) {
2562
+ if (!approval.approved)
2563
+ return null;
2564
+ const basis = scope.approvalBasis ?? null;
2565
+ const rule = basis === "mode" ? "your operating mode for this project" : basis === "grant" ? "its flow, which runs on its own" : basis === "schedule" ? "the schedule you approved"
2566
+ : basis === "auto-green" || basis === "auto-check" ? "this project’s release approval (CI and the release check passed)" : basis === "carried" ? "the release approval it was carried under" : null;
2567
+ const how = approvalProofWords(proof).replace(/^ \(|\)$/g, "");
2568
+ return { by: approval.by, at: approval.at, automatic: rule !== null, rule, how: how === "" ? (rule === null ? "password typed" : "") : how, version: scope.digest.slice(0, 12) };
2569
+ }
@@ -124,22 +124,30 @@ get("pages", "projects.browse", "/projects/browse", free);
124
124
  get("pages", "projects.github", "/projects/github", { ...free, callers: ["cookie"], callerRefusal: { status: 403, type: "text/plain; charset=utf-8", body: "listing repositories feeds a browser session's act" },
125
125
  ...anyApprover("your login can watch — adding projects is an approver's act") });
126
126
  get("pages", "projects.page", "/projects", { limited: "unscoped", ...free });
127
+ // One project's reading page (/projects/<name>): reads only, like every GET (docs/plans/get-side-effects.md). It shows a
128
+ // project this account can see and never switches the session's project; its Open project button posts to
129
+ // /projects/select. The handler resolves the name among the account's visible projects; anything else is not found.
130
+ get("pages", "projects.project", "^\\/projects\\/[^/]+$", { limited: "unscoped", ...free, sample: "/projects/payments-api" });
127
131
  get("pages", "home", "/", { limited: "collection", ...free });
132
+ get("pages", "search", "/search", { limited: "collection", ...free });
128
133
  get("tasks", "inbox", "/inbox", { limited: "collection", ...free });
129
134
  get("tasks", "work", "/work", { limited: "collection", ...free });
135
+ // Put away old tasks (task-tidy.md): the preview reads only; putting away is an approver's step-up in a browser session.
136
+ get("tasks", "work.put-away", "/work/put-away", { limited: "collection", ...free, callers: ["cookie"], ...approver("Only an approver can put tasks away.", "/work") });
137
+ post("tasks", "work.put-away.apply", "/work/put-away", { scope: "step-up", limited: "collection", ...approver("Only an approver can put tasks away.", "/work") });
130
138
  get("tasks", "next", "/next", { limited: "collection" });
131
- get("pages", "morning", "/morning");
139
+ get("pages", "morning", "/morning", { limited: "collection", ...free }); // a redirect to / now
132
140
  get("pages", "workbench", "/workbench", free);
133
141
  get("tasks", "board", "/board", { limited: "collection" });
134
142
  get("tasks", "review", "/review", { limited: "collection" });
135
143
  get("settings", "spend", "/spend", { ...free, ...anyOperator("An instance operator sees spend.", "/") });
136
144
  get("settings", "settings.spend", "/settings/spend", { ...free, ...anyOperator("An instance operator sets spending limits.", "/settings") });
137
145
  get("pages", "ledger", "/ledger", { limited: "unscoped", ...free });
138
- get("pages", "activity", "/activity");
139
- get("tasks", "done", "/done", { limited: "collection" });
146
+ get("pages", "activity", "/activity", { limited: "collection", ...free }); // a redirect to / now (Home has the summary)
147
+ get("tasks", "done", "/done", { limited: "collection", ...free }); // a redirect to the Tasks list's Finished view now
140
148
  get("settings", "mode", "/mode");
141
149
  get("people", "people.page", "/people", { limited: "unscoped", ...free });
142
- get("pages", "system", "/system");
150
+ get("pages", "system", "/system", free);
143
151
  // Where every "Connect a builder" leads: read-only, open to a project-limited person (the handler shows only a visible project).
144
152
  get("pages", "start-builder", "/start-builder", { limited: "unscoped", ...free });
145
153
  get("tasks", "tasks", "/tasks", { limited: "collection", ...free }); // a redirect to /work now: never sent to the project opener first
@@ -150,12 +158,17 @@ get("pages", "agents", "/agents", { limited: "unscoped", ...free });
150
158
  // Browser sessions only, like the transcript and the peek: no token (a lead's, a runner's, a remote one) reads live output.
151
159
  get("pages", "agents.live", "/agents/live", { limited: "unscoped", callers: ["cookie"], ...free });
152
160
  get("pages", "agents.live.read", `^\\/agents\\/live\\/${ID}$`, { limited: "unscoped", callers: ["cookie"], ...free, sample: "/agents/live/1" });
161
+ // Terminals' address in the Agents area: a 302 to /term, admitted exactly as /term is (an operator's browser session).
162
+ get("pages", "agents.terminals", "/agents/terminals", TERM);
163
+ // Fleet and System are parts of Agents now: GET aliases to /agents#computers and /agents#models.
153
164
  get("pages", "fleet", "/fleet", free);
154
165
  get("tasks", "tasks.new", "/tasks/new", { limited: "collection", ...free });
166
+ // The first run's own page (Wave E): five guided steps, reads only; it leads Home once the first run is over.
167
+ get("tasks", "welcome", "/welcome", { limited: "collection", ...free });
155
168
  get("tasks", "task.page", `^\\/t\\/${TASK}$`, { project: "task", limited: "resource", ...free, sample: "/t/one" });
156
169
  get("tasks", "task.evidence", `^\\/t\\/${TASK}\\/evidence$`, { project: "task", limited: "resource", ...free, sample: "/t/one/evidence" });
157
170
  get("pages", "ledger.export", "/ledger/export", { limited: "unscoped", ...free });
158
- get("tasks", "runs", "/runs", { limited: "collection" });
171
+ get("tasks", "runs", "/runs", { limited: "collection", ...free }); // a redirect to /agents now
159
172
  get("pages", "menu", "/menu", { limited: "collection", ...free });
160
173
  get("tasks", "run.page", `^\\/r\\/${ID}$`, { project: "run", limited: "resource", ...free, sample: "/r/1" });
161
174
  get("tasks", "run.evidence", `^\\/r\\/${ID}\\/evidence\\/${ID}$`, { project: "run", limited: "resource", ...free, sample: "/r/1/evidence/2" });
@@ -215,6 +228,11 @@ get("settings", "settings.knowledge", "/settings/knowledge", { limited: "unscope
215
228
  get("settings", "settings.learning", "/settings/learning", { limited: "unscoped", ...free });
216
229
  get("settings", "settings.telegram", "/settings/telegram", { limited: "self", ...free, ...approver("An approver can pair their own phone.", "/settings") });
217
230
  get("settings", "settings.page", "/settings", { limited: "unscoped", ...free });
231
+ // The Settings groups (docs/plans/settings-revamp.md §3): one read-only page each, drawn by the overview's handler.
232
+ // Anyone signed in may open one; the page shows only the sections this person can use, and every form's own route
233
+ // keeps its own role and proof.
234
+ for (const one of ["general", "agents", "alerts", "projects", "access", "system"])
235
+ get("settings", `settings.group-${one}`, `/settings/${one}`, { limited: "unscoped", ...free });
218
236
  get("pages", "health", "/health", { ...free, ...anyOperator("An instance operator reads server health.", "/") });
219
237
  get("pages", "metrics", "/metrics", { ...free, ...anyOperator("An instance operator reads metrics.", "/") });
220
238
  get("chat", "push.key", "/push/key");
@@ -270,6 +288,11 @@ for (const one of ["linear-key", "hooks-address", "secrets"])
270
288
  // "Runs on its own": signing raises authority (the owner's password, a step-up); revoking is one click for any approver.
271
289
  post("flows", "flow.unattended", `^\\/flows\\/${FLOW}\\/unattended$`, { scope: "step-up", project: "flow", sample: "/flows/1/unattended", ...EDIT_FLOWS });
272
290
  post("flows", "flow.unattended-off", `^\\/flows\\/${FLOW}\\/unattended-off$`, { project: "flow", sample: "/flows/1/unattended-off", ...EDIT_FLOWS });
291
+ // Browser use in a flow's script zones (flow-browser.ts): allowing it widens the sandbox, so a person's own step-up from
292
+ // their browser, carrying the approval digest they read; turning it off lowers authority, one click. Browser only: a
293
+ // lead's or remote run's token never reaches either (the CLI says the same).
294
+ post("flows", "flow.browser", `^\\/flows\\/${FLOW}\\/browser$`, { scope: "step-up", project: "flow", sample: "/flows/1/browser", ...EDIT_FLOWS });
295
+ post("flows", "flow.browser-off", `^\\/flows\\/${FLOW}\\/browser-off$`, { callers: ["cookie"], project: "flow", limited: "resource", sample: "/flows/1/browser-off", ...EDIT_FLOWS });
273
296
  post("flows", "flow.triggers", `^\\/flows\\/${FLOW}\\/triggers$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers", ...EDIT_FLOWS });
274
297
  for (const one of ["move", "decide", "cancel", "comment", "assign", "watch"])
275
298
  post("flows", `flow.card.${one}`, `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/cards/2/${one}`, ...EDIT_FLOWS });
@@ -408,6 +431,9 @@ post("tasks", "task.act.merge", `^\\/t\\/${TASK}\\/merge$`, { project: "task", l
408
431
  post("tasks", "task.act.confirm-stopped", `^\\/t\\/${TASK}\\/confirm-stopped$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/confirm-stopped", ...approver("Only an approver can confirm a build stopped.") });
409
432
  post("tasks", "task.act.stop-carrying", `^\\/t\\/${TASK}\\/stop-carrying$`, { project: "task", limited: "resource", sample: "/t/one/stop-carrying", ...approver("Only an approver can stop carrying a release.") });
410
433
  post("tasks", "task.act.stop", `^\\/t\\/${TASK}\\/stop$`, { project: "task", limited: "resource", sample: "/t/one/stop" });
434
+ // Putting one task away and bringing it back (task-tidy.md): a person's act, so only a browser session.
435
+ post("tasks", "task.act.archive", `^\\/t\\/${TASK}\\/archive$`, { project: "task", limited: "resource", sample: "/t/one/archive", callers: ["cookie"], ...approver("Only an approver can put a task away.") });
436
+ post("tasks", "task.act.restore", `^\\/t\\/${TASK}\\/restore$`, { project: "task", limited: "resource", sample: "/t/one/restore", callers: ["cookie"], ...approver("Only an approver can restore a task.") });
411
437
  post("tasks", "task.act.pause", `^\\/t\\/${TASK}\\/pause$`, { project: "task", limited: "resource", sample: "/t/one/pause" });
412
438
  post("tasks", "task.act.resume-arm", `^\\/t\\/${TASK}\\/resume-arm$`, { project: "task", limited: "resource", sample: "/t/one/resume-arm" });
413
439
  post("tasks", "task.act.resume", `^\\/t\\/${TASK}\\/resume$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/resume" });