toolroll 0.9.86 → 0.9.88

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 (173) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/dist/acp-check.d.ts +9 -0
  3. package/dist/acp-check.js +40 -28
  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/backup-ui.d.ts +5 -4
  10. package/dist/backup-ui.js +65 -32
  11. package/dist/browser/terminal.css +1 -1
  12. package/dist/browser/terminal.js +57 -54
  13. package/dist/browser/workspace.css +2 -2
  14. package/dist/browser/workspace.js +35 -70
  15. package/dist/browser-crew.js +1 -1
  16. package/dist/browser-workspace.d.ts +329 -183
  17. package/dist/browser-workspace.js +10 -9
  18. package/dist/chat-controls.d.ts +1 -1
  19. package/dist/chat-controls.js +1 -1
  20. package/dist/chat-settings-page.d.ts +29 -9
  21. package/dist/chat-settings-page.js +59 -28
  22. package/dist/check-levels-ui.d.ts +2 -1
  23. package/dist/check-levels-ui.js +67 -58
  24. package/dist/contracts/cli.d.ts +43 -0
  25. package/dist/contracts/cli.js +4 -0
  26. package/dist/contracts/console-api.d.ts +1 -1
  27. package/dist/contracts/console-api.js +1 -1
  28. package/dist/credentials-ui.d.ts +5 -3
  29. package/dist/credentials-ui.js +58 -31
  30. package/dist/design/kit.css +722 -0
  31. package/dist/design/kit.d.ts +208 -0
  32. package/dist/design/kit.js +177 -0
  33. package/dist/design/tokens.css +31 -1
  34. package/dist/discord-settings.js +8 -4
  35. package/dist/export-ui.d.ts +6 -4
  36. package/dist/export-ui.js +18 -8
  37. package/dist/flow-changed.d.ts +14 -0
  38. package/dist/flow-changed.js +53 -0
  39. package/dist/flow-gallery-ui.d.ts +41 -4
  40. package/dist/flow-gallery-ui.js +95 -63
  41. package/dist/flow-starters-ui.d.ts +3 -9
  42. package/dist/flow-starters-ui.js +12 -23
  43. package/dist/flow-unattended-words.d.ts +20 -0
  44. package/dist/flow-unattended-words.js +24 -0
  45. package/dist/flow-unattended.d.ts +9 -5
  46. package/dist/flow-unattended.js +45 -23
  47. package/dist/flows-cli.js +7 -3
  48. package/dist/flows-ui.d.ts +12 -6
  49. package/dist/flows-ui.js +141 -30
  50. package/dist/integrations-ui.d.ts +6 -4
  51. package/dist/integrations-ui.js +34 -40
  52. package/dist/integrations.d.ts +2 -2
  53. package/dist/integrations.js +4 -4
  54. package/dist/kits-ui.d.ts +8 -10
  55. package/dist/kits-ui.js +29 -33
  56. package/dist/kits.js +1 -1
  57. package/dist/knowledge-ui.js +76 -19
  58. package/dist/ledger-view.d.ts +2 -0
  59. package/dist/ledger-view.js +41 -38
  60. package/dist/models-ui.d.ts +8 -3
  61. package/dist/models-ui.js +82 -59
  62. package/dist/monitoring-ui.d.ts +4 -2
  63. package/dist/monitoring-ui.js +44 -13
  64. package/dist/needs-you.d.ts +1 -1
  65. package/dist/needs-you.js +8 -0
  66. package/dist/operate-remote-arguments.js +1 -0
  67. package/dist/operate-remote.js +1 -1
  68. package/dist/operate.d.ts +1 -1
  69. package/dist/operate.js +38 -4
  70. package/dist/passwordless.d.ts +1 -1
  71. package/dist/passwordless.js +1 -1
  72. package/dist/people-audit-ui.d.ts +2 -1
  73. package/dist/people-audit-ui.js +25 -23
  74. package/dist/policy-ui.d.ts +3 -2
  75. package/dist/policy-ui.js +27 -21
  76. package/dist/policy.d.ts +3 -0
  77. package/dist/policy.js +14 -1
  78. package/dist/project-delete-ui.d.ts +4 -2
  79. package/dist/project-delete-ui.js +135 -126
  80. package/dist/project-knowledge.d.ts +10 -10
  81. package/dist/publish.d.ts +11 -0
  82. package/dist/publish.js +16 -3
  83. package/dist/pull-request-ui.d.ts +2 -1
  84. package/dist/pull-request-ui.js +39 -28
  85. package/dist/recipe-ui.d.ts +4 -2
  86. package/dist/recipe-ui.js +14 -4
  87. package/dist/release-adapters.d.ts +3 -0
  88. package/dist/release-adapters.js +22 -2
  89. package/dist/release-cli.js +81 -9
  90. package/dist/release.d.ts +23 -0
  91. package/dist/release.js +46 -4
  92. package/dist/releases.js +1 -1
  93. package/dist/request-budget-ui.d.ts +4 -2
  94. package/dist/request-budget-ui.js +30 -19
  95. package/dist/retention-ui.d.ts +4 -3
  96. package/dist/retention-ui.js +15 -12
  97. package/dist/scope.d.ts +3 -0
  98. package/dist/scope.js +6 -0
  99. package/dist/serve.d.ts +0 -1
  100. package/dist/serve.js +51 -7
  101. package/dist/server/agents-computers.d.ts +9 -0
  102. package/dist/server/agents-computers.js +67 -0
  103. package/dist/server/chat.js +16 -16
  104. package/dist/server/chrome.d.ts +1 -1
  105. package/dist/server/chrome.js +19 -9
  106. package/dist/server/console.css +24 -12
  107. package/dist/server/flows.js +64 -36
  108. package/dist/server/home.d.ts +35 -0
  109. package/dist/server/home.js +104 -0
  110. package/dist/server/http.d.ts +3 -0
  111. package/dist/server/http.js +5 -0
  112. package/dist/server/kit-standalone.d.ts +22 -0
  113. package/dist/server/kit-standalone.js +70 -0
  114. package/dist/server/kit.d.ts +419 -0
  115. package/dist/server/kit.js +369 -0
  116. package/dist/server/pages.d.ts +2 -1
  117. package/dist/server/pages.js +213 -174
  118. package/dist/server/people-tokens.js +26 -85
  119. package/dist/server/render-history.d.ts +51 -0
  120. package/dist/server/render-history.js +228 -0
  121. package/dist/server/render-new-task.d.ts +29 -0
  122. package/dist/server/render-new-task.js +96 -0
  123. package/dist/server/render-next.d.ts +63 -0
  124. package/dist/server/render-next.js +109 -0
  125. package/dist/server/render-pages.d.ts +22 -190
  126. package/dist/server/render-pages.js +123 -575
  127. package/dist/server/render-people.d.ts +24 -3
  128. package/dist/server/render-people.js +93 -72
  129. package/dist/server/render-projects.d.ts +103 -0
  130. package/dist/server/render-projects.js +247 -0
  131. package/dist/server/render-results.d.ts +5 -24
  132. package/dist/server/render-results.js +48 -185
  133. package/dist/server/render-settings.d.ts +5 -74
  134. package/dist/server/render-settings.js +102 -493
  135. package/dist/server/render-tasks.d.ts +35 -5
  136. package/dist/server/render-tasks.js +114 -85
  137. package/dist/server/route-table.js +20 -5
  138. package/dist/server/settings.js +276 -255
  139. package/dist/server/task-views.d.ts +8 -0
  140. package/dist/server/task-views.js +85 -2
  141. package/dist/server/tasks.d.ts +8 -0
  142. package/dist/server/tasks.js +95 -40
  143. package/dist/server/terminals.js +1 -1
  144. package/dist/server/welcome.d.ts +22 -0
  145. package/dist/server/welcome.js +69 -0
  146. package/dist/settings-groups-ui.d.ts +279 -0
  147. package/dist/settings-groups-ui.js +615 -0
  148. package/dist/settings-kit.d.ts +42 -6
  149. package/dist/settings-kit.js +69 -7
  150. package/dist/setup-guide.d.ts +2 -2
  151. package/dist/skills-ui.js +59 -7
  152. package/dist/slack-settings.js +12 -5
  153. package/dist/spend-ui.d.ts +45 -26
  154. package/dist/spend-ui.js +124 -56
  155. package/dist/sso-ui.d.ts +5 -2
  156. package/dist/sso-ui.js +63 -31
  157. package/dist/storage-ui.d.ts +3 -2
  158. package/dist/storage-ui.js +56 -50
  159. package/dist/store.d.ts +112 -3
  160. package/dist/store.js +292 -20
  161. package/dist/surface.js +5 -1
  162. package/dist/teams-settings.d.ts +2 -1
  163. package/dist/teams-settings.js +24 -41
  164. package/dist/telegram-settings.d.ts +0 -4
  165. package/dist/telegram-settings.js +52 -25
  166. package/dist/toolroll-update-ui.d.ts +8 -4
  167. package/dist/toolroll-update-ui.js +55 -45
  168. package/dist/tools-ui.d.ts +5 -3
  169. package/dist/tools-ui.js +107 -42
  170. package/dist/work-index.js +11 -7
  171. package/dist/workspace-ui.d.ts +1 -1
  172. package/dist/workspace-ui.js +7 -4
  173. 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;
@@ -693,9 +709,23 @@ export declare function qualityModeChoices(name: string, selected: QualityMode):
693
709
  * writes the field); kept for callers.
694
710
  */
695
711
  export declare function decisionOptionForms(decision: Decision, returnTo: string | null): Html;
696
- export declare function decisionPage(chrome: Chrome, decision: Decision, taskId: string, artifacts: Artifact[], who: Who, now: Date, returnTo?: string | null): Screen;
697
712
  export declare function taskOf(store: Store, decision: Decision): string;
698
713
  /** A release task's own card (release-once.md, "What people see"): the release it's approved for, the later commits
699
714
  * carried under it (on demand), what a later check must run again, the last nightly check of main, and one action to
700
715
  * stop carrying. A carried task says whose approval it was carried from. */
701
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;
@@ -8,7 +8,7 @@ import { writesWords } from "../writes-mode.js";
8
8
  import { checkAccessWords } from "../check-access.js";
9
9
  import { sandboxTermsWords } from "../project-sandbox.js";
10
10
  import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
11
- import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
11
+ import { html, htmlString, joinHtml, passwordlessRendering, postForm, replaceMarkup, reauth } from "../html.js";
12
12
  import { closedQuestionReason, closedQuestionWords } from "../decision.js";
13
13
  import {} from "../activity-line.js";
14
14
  import {} from "../agentconfig.js";
@@ -38,7 +38,7 @@ import { agentModelName } from "../model-names.js";
38
38
  import {} from "../provider.js";
39
39
  import { qualityModeTitle } from "../quality.js";
40
40
  import { evidenceShortenedWords } from "../result-review.js";
41
- import { acceptanceToLines, approvalOf, scopeAuthorityOf } from "../scope.js";
41
+ import { acceptanceToLines, approvalOf, freshUntil, scopeAuthorityOf } from "../scope.js";
42
42
  import {} from "../store.js";
43
43
  import { runCostWords } from "../summary.js";
44
44
  import {} from "../task-control.js";
@@ -230,7 +230,34 @@ function originParts(origin) {
230
230
  }
231
231
  return parts;
232
232
  }
233
+ /** The approval sheet: one form that restates every term it signs, the password, and Approve & start. */
233
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) {
234
261
  const { scope, route } = input;
235
262
  // A machine-drafted repair appends its brief to the signed goal; the row
236
263
  // shows the goal and one sentence says what the repair fixes. The whole
@@ -392,40 +419,27 @@ export function approvalSheetHtml(input) {
392
419
  "budget-microusd": scope.budgetMicrousd === null ? "none" : String(scope.budgetMicrousd),
393
420
  },
394
421
  });
395
- return joinHtml([
396
- postForm(input.action, joinHtml([
397
- html `<input type="text" name="username" autocomplete="username" class="visually-hidden" tabindex="-1" aria-hidden="true">`,
398
- html `<h2 class="approval-sheet-title">${input.deliverable === "report" ? "The investigation" : "The plan"}</h2>`,
399
- sentence === null ? "" : html `<p class="approval-revision">${sentence}</p>`,
400
- 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>`,
401
- input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "",
402
- originNote(input.origin) === null ? "" : html `<p class="approval-note" data-approval-origin>${originNote(input.origin)}</p>`,
403
- html `<dl class="approval-rows">`,
404
- row("Goal", html `<p class="approval-goal">${goalShown}</p>`),
405
- row("Changes", changes),
406
- row("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
407
- row("Done when", doneWhen),
408
- html `</dl>`,
409
- amendment,
410
- // The size, said once and plainly beside who builds: "Small change: fast model, no plan".
411
- projection === null ? "" : sizeLineHtml(projection),
412
- who.length === 0 ? "" : html `<p class="approval-who">${who.join(" · ")}</p>`,
413
- consent,
414
- html `<div class="approval-act" id="${input.surface === "task" ? "approval-confirm" : "chat-approval-confirm"}">`,
415
- 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>`)}`,
416
- input.freshSince == null ? html `<p class="approval-password-note" id="${passwordNote}">Your password signs this approval.</p>`
417
- : 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>`,
418
- html `<button type="submit" data-primary-action>${submit}</button></div>`,
419
- html `<p class="approval-after">${after}</p>`,
420
- 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>`,
421
- details,
422
- ]), {
423
- attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
424
- hidden: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
425
- returnTo: input.returnTo,
426
- }),
427
- 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>`,
428
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 };
429
443
  }
430
444
  /** Earlier versions of a task still in flight, in plain words: queued
431
445
  * while they only wait, running once one has started; null when none. */
@@ -1627,6 +1641,36 @@ export function taskBodyParts(data) {
1627
1641
  // assignment's one action): then it is shown open under the status, never folded.
1628
1642
  const approvalLeads = data.dispatch?.action === "approve-scope" ||
1629
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
+ };
1630
1674
  const approveForm = scope === null || approval.approved || data.plan === "requested"
1631
1675
  ? ""
1632
1676
  : data.revision !== null && data.revision !== undefined && "problem" in data.revision
@@ -1635,33 +1679,7 @@ export function taskBodyParts(data) {
1635
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>`
1636
1680
  : !consentDoor.open
1637
1681
  ? consentClosedHtml(task.id, consentDoor, "task")
1638
- : approvalSheetHtml({
1639
- surface: "task",
1640
- action: `${taskHref(task.id)}/approve`,
1641
- csrf: data.csrf,
1642
- nonce: data.nonce,
1643
- digest: data.approvalDigest ?? scope.digest,
1644
- freshSince: data.approvalFresh ?? null,
1645
- returnTo: null,
1646
- scope,
1647
- planDocument: data.plan === "drafted" ? data.planDocument : null,
1648
- planContract: data.plan === "drafted" ? data.planContract ?? null : null,
1649
- revision: data.revision == null || "problem" in data.revision ? null : data.revision,
1650
- revisionSourceHref: data.revision == null || "problem" in data.revision ? "" : `/r/${data.revision.sourceRun}`,
1651
- repairChain: data.repairChain ?? null,
1652
- route: data.route,
1653
- coordinator: data.coordinator ?? null,
1654
- origin: data.origin ?? null,
1655
- deliverable: data.deliverable ?? "branch",
1656
- rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
1657
- editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
1658
- notNowHref: "/work",
1659
- sticky: approvalLeads,
1660
- earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
1661
- edit: data.csrf === "" ? null
1662
- : { action: `${taskHref(task.id)}/scope`, draft: inlineScopeDraft ? data.scopeDraft ?? null : null, problem: inlineScopeDraft ? data.problem : null,
1663
- open: data.editPlan === true, stepsHref: data.planSha != null && data.planDocument !== null ? "#plan-edit" : null },
1664
- });
1682
+ : approvalSheetHtml(sheetInput);
1665
1683
  const scopeForm = joinHtml([
1666
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>`,
1667
1685
  postForm(`${taskHref(task.id)}/scope`, joinHtml([
@@ -2365,6 +2383,9 @@ export function taskBodyParts(data) {
2365
2383
  earlier,
2366
2384
  approval: data.archived != null ? "" : fragmentString(approvalHtml),
2367
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,
2368
2389
  more,
2369
2390
  confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
2370
2391
  ? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
@@ -2488,29 +2509,6 @@ export function decisionOptionForms(decision, returnTo) {
2488
2509
  : html `<details class="arm-danger"><summary>${option.label} — irreversible, tap to arm</summary>${inner}</details>`;
2489
2510
  }), "\n");
2490
2511
  }
2491
- export function decisionPage(chrome, decision, taskId, artifacts, who, now, returnTo = null) {
2492
- const options = decisionOptionForms(decision, returnTo);
2493
- const answered = decision.supersededReason !== null
2494
- ? html `<p class="meta">${closedQuestionReason(decision.supersededReason)}</p>`
2495
- : decision.state === "answered"
2496
- ? html `<div class="answered">Answered: <strong>${decision.choice ?? ""}</strong> by ${decision.answeredBy ?? ""}${decision.note === null ? "" : ` — ${decision.note}`}</div>`
2497
- : "";
2498
- const evidence = artifacts.length === 0
2499
- ? ""
2500
- : joinHtml([
2501
- html `<div class="evidence"><strong>Evidence</strong>`,
2502
- joinHtml(artifacts.map(artifact => html `<a href="/d/${decision.id}/evidence/${artifact.id}">${evidenceWords(artifact.kind)}${artifact.truncated ? " (truncated)" : ""} · ${artifact.bytesStored} bytes</a>`), "\n"),
2503
- html `</div>`,
2504
- ]);
2505
- return screen(`decide · ${taskId}`, joinHtml([
2506
- 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>`,
2507
- html `<div class="recap">${decision.recap}</div>`,
2508
- html `<div class="question">${decision.question}</div>`,
2509
- decision.state === "answered" ? answered : options,
2510
- evidence,
2511
- html `<p class="meta"><a href="${returnTo === null ? "/" : returnTo}">← ${returnTo === null ? "everything waiting" : "back to the task chat"}</a></p>`,
2512
- ], "\n"), { chrome });
2513
- }
2514
2512
  export function taskOf(store, decision) {
2515
2513
  const run = store.getRun(decision.run);
2516
2514
  return run === null ? "?" : store.externalIdFor(run.taskRef) ?? "?";
@@ -2538,3 +2536,34 @@ export function releaseCardHtml(taskId, release, canStop) {
2538
2536
  html `</div>`,
2539
2537
  ], "\n");
2540
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,25 +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 });
130
135
  // Put away old tasks (task-tidy.md): the preview reads only; putting away is an approver's step-up in a browser session.
131
136
  get("tasks", "work.put-away", "/work/put-away", { limited: "collection", ...free, callers: ["cookie"], ...approver("Only an approver can put tasks away.", "/work") });
132
137
  post("tasks", "work.put-away.apply", "/work/put-away", { scope: "step-up", limited: "collection", ...approver("Only an approver can put tasks away.", "/work") });
133
138
  get("tasks", "next", "/next", { limited: "collection" });
134
- get("pages", "morning", "/morning");
139
+ get("pages", "morning", "/morning", { limited: "collection", ...free }); // a redirect to / now
135
140
  get("pages", "workbench", "/workbench", free);
136
141
  get("tasks", "board", "/board", { limited: "collection" });
137
142
  get("tasks", "review", "/review", { limited: "collection" });
138
143
  get("settings", "spend", "/spend", { ...free, ...anyOperator("An instance operator sees spend.", "/") });
139
144
  get("settings", "settings.spend", "/settings/spend", { ...free, ...anyOperator("An instance operator sets spending limits.", "/settings") });
140
145
  get("pages", "ledger", "/ledger", { limited: "unscoped", ...free });
141
- get("pages", "activity", "/activity");
142
- 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
143
148
  get("settings", "mode", "/mode");
144
149
  get("people", "people.page", "/people", { limited: "unscoped", ...free });
145
- get("pages", "system", "/system");
150
+ get("pages", "system", "/system", free);
146
151
  // Where every "Connect a builder" leads: read-only, open to a project-limited person (the handler shows only a visible project).
147
152
  get("pages", "start-builder", "/start-builder", { limited: "unscoped", ...free });
148
153
  get("tasks", "tasks", "/tasks", { limited: "collection", ...free }); // a redirect to /work now: never sent to the project opener first
@@ -153,12 +158,17 @@ get("pages", "agents", "/agents", { limited: "unscoped", ...free });
153
158
  // Browser sessions only, like the transcript and the peek: no token (a lead's, a runner's, a remote one) reads live output.
154
159
  get("pages", "agents.live", "/agents/live", { limited: "unscoped", callers: ["cookie"], ...free });
155
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.
156
164
  get("pages", "fleet", "/fleet", free);
157
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 });
158
168
  get("tasks", "task.page", `^\\/t\\/${TASK}$`, { project: "task", limited: "resource", ...free, sample: "/t/one" });
159
169
  get("tasks", "task.evidence", `^\\/t\\/${TASK}\\/evidence$`, { project: "task", limited: "resource", ...free, sample: "/t/one/evidence" });
160
170
  get("pages", "ledger.export", "/ledger/export", { limited: "unscoped", ...free });
161
- get("tasks", "runs", "/runs", { limited: "collection" });
171
+ get("tasks", "runs", "/runs", { limited: "collection", ...free }); // a redirect to /agents now
162
172
  get("pages", "menu", "/menu", { limited: "collection", ...free });
163
173
  get("tasks", "run.page", `^\\/r\\/${ID}$`, { project: "run", limited: "resource", ...free, sample: "/r/1" });
164
174
  get("tasks", "run.evidence", `^\\/r\\/${ID}\\/evidence\\/${ID}$`, { project: "run", limited: "resource", ...free, sample: "/r/1/evidence/2" });
@@ -218,6 +228,11 @@ get("settings", "settings.knowledge", "/settings/knowledge", { limited: "unscope
218
228
  get("settings", "settings.learning", "/settings/learning", { limited: "unscoped", ...free });
219
229
  get("settings", "settings.telegram", "/settings/telegram", { limited: "self", ...free, ...approver("An approver can pair their own phone.", "/settings") });
220
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 });
221
236
  get("pages", "health", "/health", { ...free, ...anyOperator("An instance operator reads server health.", "/") });
222
237
  get("pages", "metrics", "/metrics", { ...free, ...anyOperator("An instance operator reads metrics.", "/") });
223
238
  get("chat", "push.key", "/push/key");