toolroll 0.9.93 → 0.9.95
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.
- package/CHANGELOG.md +79 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +10 -0
- package/dist/agent-fence.js +6 -3
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.js +5 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.js +10 -0
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +32 -32
- package/dist/browser-crew.js +2 -0
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +50 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/build-review.js +2 -0
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +11 -2
- package/dist/builder.js +234 -101
- package/dist/card-catalog.d.ts +209 -0
- package/dist/card-catalog.js +133 -0
- package/dist/card-cli.d.ts +43 -0
- package/dist/card-cli.js +44 -0
- package/dist/card-render.d.ts +26 -0
- package/dist/card-render.js +223 -0
- package/dist/card-sources.d.ts +68 -0
- package/dist/card-sources.js +234 -0
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +106 -9
- package/dist/chat-actions.js +473 -46
- package/dist/chat-approval.d.ts +13 -0
- package/dist/chat-approval.js +39 -9
- package/dist/chat-card-part.d.ts +11 -0
- package/dist/chat-card-part.js +18 -0
- package/dist/chat-card.d.ts +46 -0
- package/dist/chat-card.js +99 -0
- package/dist/chat-channel.d.ts +2 -0
- package/dist/chat-channel.js +37 -14
- package/dist/chat-controls.d.ts +4 -0
- package/dist/chat-controls.js +4 -0
- package/dist/chat-decide.d.ts +10 -16
- package/dist/chat-decide.js +79 -62
- package/dist/chat-delivery.js +13 -4
- package/dist/chat-flow.d.ts +0 -27
- package/dist/chat-flow.js +31 -2
- package/dist/chat-quiet.js +4 -4
- package/dist/chat-task-actions.d.ts +3 -2
- package/dist/chat-task-actions.js +19 -5
- package/dist/chat-terms.d.ts +14 -2
- package/dist/chat-terms.js +35 -0
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +3 -1
- package/dist/check-facts.js +57 -11
- package/dist/check-failure.d.ts +24 -3
- package/dist/check-failure.js +89 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-repair.d.ts +47 -0
- package/dist/check-repair.js +240 -0
- package/dist/check-report.d.ts +2 -0
- package/dist/check-report.js +1 -1
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/claim.d.ts +6 -0
- package/dist/claim.js +13 -2
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +49 -30
- package/dist/cli.js +15 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +3 -1
- package/dist/coding-provider.js +12 -7
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.d.ts +1 -1
- package/dist/coding-workspace.js +8 -8
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +92 -0
- package/dist/contracts/chat-actions.js +20 -0
- package/dist/contracts/chat-card.d.ts +91 -0
- package/dist/contracts/chat-card.js +54 -0
- package/dist/contracts/chat-content.d.ts +191 -0
- package/dist/contracts/chat-content.js +4 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -3
- package/dist/contracts/console-api.js +12 -3
- package/dist/contracts/flow-propose.d.ts +8 -1
- package/dist/contracts/flow-propose.js +3 -1
- package/dist/contracts/flow-send.d.ts +126 -0
- package/dist/contracts/flow-send.js +3 -0
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +234 -0
- package/dist/contracts/lead-tools.js +25 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +18 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/discord-chat.js +14 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +25 -16
- package/dist/dispose.d.ts +19 -0
- package/dist/dispose.js +55 -9
- package/dist/easy-start.js +2 -3
- package/dist/eval-oracle.js +4 -1
- package/dist/evals.js +2 -1
- package/dist/evidence-pair.d.ts +53 -0
- package/dist/evidence-pair.js +128 -0
- package/dist/exhaustion.d.ts +17 -4
- package/dist/exhaustion.js +44 -4
- package/dist/export.js +1 -1
- package/dist/failing-first.d.ts +31 -0
- package/dist/failing-first.js +44 -0
- package/dist/fallback-schema.d.ts +8 -0
- package/dist/fallback-schema.js +40 -0
- package/dist/fallback.d.ts +138 -0
- package/dist/fallback.js +605 -0
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/findings.d.ts +224 -0
- package/dist/findings.js +449 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +75 -9
- package/dist/flaky.js +255 -0
- package/dist/fleet-schema.d.ts +17 -0
- package/dist/fleet-schema.js +135 -0
- package/dist/flow-browser.js +2 -2
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +9 -5
- package/dist/flow-result-card.d.ts +22 -0
- package/dist/flow-result-card.js +55 -0
- package/dist/flow-send.js +12 -9
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +2 -2
- package/dist/flow-unattended.d.ts +26 -0
- package/dist/flow-unattended.js +12 -3
- package/dist/flows-ui.js +10 -2
- package/dist/follow-through-brief.d.ts +12 -0
- package/dist/follow-through-brief.js +31 -0
- package/dist/follow-through-cli.d.ts +23 -0
- package/dist/follow-through-cli.js +153 -0
- package/dist/git-incident.d.ts +16 -0
- package/dist/git-incident.js +69 -6
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/handover-note.d.ts +33 -0
- package/dist/handover-note.js +106 -0
- package/dist/invoke.d.ts +25 -2
- package/dist/invoke.js +157 -58
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-cli.js +26 -24
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -2
- package/dist/lead-doors.d.ts +4 -0
- package/dist/lead-doors.js +2 -2
- package/dist/lead-progress.js +5 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +162 -34
- package/dist/lead.js +2 -2
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +16 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/modes.d.ts +6 -6
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +31 -9
- package/dist/needs-you.js +110 -41
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +7 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +565 -64
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/plan-login.d.ts +14 -0
- package/dist/plan-login.js +58 -0
- package/dist/planner.d.ts +1 -1
- package/dist/planner.js +4 -2
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +19 -2
- package/dist/project-delete-ui.js +29 -1
- package/dist/project-delete.js +14 -1
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/project-tools.d.ts +4 -4
- package/dist/project-tools.js +23 -7
- package/dist/proof.js +30 -5
- package/dist/protected-storage-look.d.ts +1 -0
- package/dist/protected-storage-look.js +13 -0
- package/dist/protected-storage.d.ts +70 -39
- package/dist/protected-storage.js +196 -147
- package/dist/provider-auth.js +1 -1
- package/dist/provider-capture.d.ts +34 -0
- package/dist/provider-capture.js +53 -0
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +18 -2
- package/dist/push.js +40 -16
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +76 -0
- package/dist/release-auto.js +13 -13
- package/dist/release-card.d.ts +1 -2
- package/dist/release-card.js +11 -0
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +49 -10
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +29 -22
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +20 -1
- package/dist/release.js +85 -9
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +27 -19
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +28 -4
- package/dist/scope.js +92 -28
- package/dist/scout.d.ts +1 -1
- package/dist/scout.js +2 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +26 -18
- package/dist/server/chat.js +16 -14
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +73 -0
- package/dist/server/guards.js +3 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +16 -3
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-follow-through.d.ts +11 -0
- package/dist/server/render-follow-through.js +68 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +2 -0
- package/dist/server/render-release.js +3 -3
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +68 -68
- package/dist/server/render-tasks.d.ts +16 -2
- package/dist/server/render-tasks.js +177 -141
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +48 -12
- package/dist/server/settings.js +166 -34
- package/dist/server/task-views.js +66 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +250 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.d.ts +1 -0
- package/dist/settings-groups-ui.js +4 -2
- package/dist/settings-kit.js +3 -2
- package/dist/settings-proofs.js +3 -1
- package/dist/slack-chat.js +14 -1
- package/dist/steer-mailbox.d.ts +8 -1
- package/dist/steer-mailbox.js +164 -6
- package/dist/stop-proof.js +6 -4
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +89 -7
- package/dist/store.js +386 -114
- package/dist/surface.js +67 -6
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.d.ts +2 -1
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +17 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +45 -49
- package/dist/teardown-git.d.ts +18 -0
- package/dist/teardown-git.js +105 -21
- package/dist/telegram-lead.js +23 -6
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/telegram.js +12 -1
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update.js +19 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +95 -66
- package/dist/work-summary.d.ts +1 -1
- package/dist/work-summary.js +7 -2
- package/dist/workspace-ui.d.ts +7 -3
- package/dist/workspace-ui.js +38 -23
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +91 -14
- package/package.json +2 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
2
|
import { archivedWords } from "../tidy.js";
|
|
3
|
+
import { btn } from "./kit.js";
|
|
3
4
|
import { whenUtc } from "../when-html.js";
|
|
4
5
|
import { networkWords } from "../egress.js";
|
|
5
|
-
import { releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
|
|
6
|
+
import { chainTermsLines, liveChainFromJson, releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
|
|
7
|
+
import { SUBSCRIPTION_CAPABLE } from "../keys.js";
|
|
6
8
|
import { RELEASE_CI_RUNNING_WORDS } from "../release-ci-hold.js";
|
|
7
9
|
import { writesWords } from "../writes-mode.js";
|
|
8
10
|
import { checkAccessWords } from "../check-access.js";
|
|
@@ -27,7 +29,8 @@ import { taskRoad } from "../first-run.js";
|
|
|
27
29
|
import { starterForWork } from "../flow-starters.js";
|
|
28
30
|
import {} from "../gaps.js";
|
|
29
31
|
import { readAuthModeStrict } from "../keys.js";
|
|
30
|
-
import { failedAttemptSentence, retryNoteOf, START_BUILDER_COMMAND, startBuilderHref } from "../needs-you.js";
|
|
32
|
+
import { failedAttemptSentence, retryNoteOf, START_BUILDER_COMMAND, startBuilderHref, STORAGE_HOLD_PREFIX } from "../needs-you.js";
|
|
33
|
+
import { planVersionWords } from "../plain-words.js";
|
|
31
34
|
import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
|
|
32
35
|
import { parseExecutionPlanDocument } from "../plan.js";
|
|
33
36
|
import {} from "../planner-source.js";
|
|
@@ -61,10 +64,10 @@ export function contractChangeHtml(change) {
|
|
|
61
64
|
case "outOfScope":
|
|
62
65
|
return html `<li>${tag} <strong>not this</strong>${change.before === null ? "" : html `<div class="contract-before">was: ${change.before}</div>`}${change.after === null ? html `<div class="contract-after">now: <em>no exclusions</em></div>` : html `<div class="contract-after">now: ${change.after}</div>`}</li>`;
|
|
63
66
|
case "touches":
|
|
64
|
-
return html `<li>${tag} <strong>
|
|
67
|
+
return html `<li>${tag} <strong>files</strong> <span class="mono">${change.path}</span></li>`;
|
|
65
68
|
case "acceptance": {
|
|
66
69
|
const criterion = (one) => one === null ? "" : html `${one.statement} <span class="meta">[requires: ${one.evidence.join(", ")}]</span>${one.how === null ? "" : html `<div class="meta">how: ${one.how}</div>`}`;
|
|
67
|
-
return html `<li>${tag} <strong>
|
|
70
|
+
return html `<li>${tag} <strong>requirement <code>${change.id}</code></strong>${change.kind === "changed" ? html ` <span class="meta">(${change.moved.join(", ")})</span>` : ""}${change.before === null ? "" : html `<div class="contract-before">${change.kind === "removed" ? "removed: " : "was: "}${criterion(change.before)}</div>`}${change.after === null ? "" : html `<div class="contract-after">${change.kind === "added" ? "added: " : "now: "}${criterion(change.after)}</div>`}</li>`;
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -78,25 +81,25 @@ export function contractChangeHtml(change) {
|
|
|
78
81
|
*/
|
|
79
82
|
export function planContractHtml(view, mode) {
|
|
80
83
|
if (view === null) {
|
|
81
|
-
return mode === "full" ? html `` : html `<p class="meta contract-note">
|
|
84
|
+
return mode === "full" ? html `` : html `<p class="meta contract-note">No record compares this plan with your request. Check the plan above against what you asked for before approving.</p>`;
|
|
82
85
|
}
|
|
83
86
|
if ("problem" in view) {
|
|
84
|
-
return html `<div class="contract-panel contract-problem"><p class="approval-label">
|
|
87
|
+
return html `<div class="contract-panel contract-problem"><p class="approval-label">Your request</p><p class="meta">${view.problem} · <a href="/r/${view.run}">Planner's record</a></p></div>`;
|
|
85
88
|
}
|
|
86
|
-
const stale = view.current ? "" : html `<p class="meta">The
|
|
89
|
+
const stale = view.current ? "" : html `<p class="meta">The plan was edited after the planner drafted it. The record below is the planner's draft.</p>`;
|
|
87
90
|
if (!view.filed) {
|
|
88
91
|
return mode === "full"
|
|
89
|
-
? html `<div class="contract-panel contract-drafted"><p class="approval-label">
|
|
90
|
-
: html `<p class="meta contract-note">
|
|
92
|
+
? html `<div class="contract-panel contract-drafted"><p class="approval-label">Your request</p><p class="meta">Nothing was filed before planning. The planner wrote this plan from the title and the project, so review every part as new.</p>${stale}</div>`
|
|
93
|
+
: html `<p class="meta contract-note">Nothing was filed before planning: every part above is the planner's.</p>`;
|
|
91
94
|
}
|
|
92
95
|
if (view.revision === true) {
|
|
93
96
|
// A send-back: the terms were copied from the previous version, and the planner updated them with the notes.
|
|
94
97
|
if (view.changes.length === 0) {
|
|
95
|
-
return html `<div class="contract-panel contract-preserved"><p class="approval-label">updated plan</p><p><strong>Same
|
|
98
|
+
return html `<div class="contract-panel contract-preserved"><p class="approval-label">updated plan</p><p><strong>Same plan as before</strong> <span class="meta">your notes fit the previous plan · <a href="/r/${view.run}">Planner's record</a></span></p>${stale}</div>`;
|
|
96
99
|
}
|
|
97
100
|
return joinHtml([
|
|
98
101
|
html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">updated plan</p>`,
|
|
99
|
-
html `<p><strong
|
|
102
|
+
html `<p><strong>The planner changed ${view.changes.length === 1 ? "1 thing" : `${view.changes.length} things`} from your notes:</strong> <span class="meta">approving accepts the updated plan ${mode === "full" ? "shown in the plan" : "above"} · <a href="/r/${view.run}">Planner's record</a></span></p>`,
|
|
100
103
|
view.amendment === null ? "" : html `<p class="recap contract-reason"><strong>Why:</strong> ${view.amendment}</p>`,
|
|
101
104
|
html `<ul class="recap contract-changes">${view.changes.map(contractChangeHtml)}</ul>`,
|
|
102
105
|
stale,
|
|
@@ -104,11 +107,11 @@ export function planContractHtml(view, mode) {
|
|
|
104
107
|
]);
|
|
105
108
|
}
|
|
106
109
|
if (view.changes.length === 0) {
|
|
107
|
-
return html `<div class="contract-panel contract-preserved"><p class="approval-label">
|
|
110
|
+
return html `<div class="contract-panel contract-preserved"><p class="approval-label">Your request</p><p><strong>Kept exactly</strong> <span class="meta">the plan keeps your goal, exclusions, files and requirements word for word · <a href="/r/${view.run}">Planner's record</a></span></p>${stale}</div>`;
|
|
108
111
|
}
|
|
109
112
|
return joinHtml([
|
|
110
|
-
html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">
|
|
111
|
-
html `<p><strong
|
|
113
|
+
html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">Your request · changes proposed</p>`,
|
|
114
|
+
html `<p><strong>The planner changed ${view.changes.length === 1 ? "1 thing" : `${view.changes.length} things`} from your request:</strong> <span class="meta">approving accepts the changed plan ${mode === "full" ? "shown in the plan" : "above"}, not your original words · <a href="/r/${view.run}">Planner's record</a></span></p>`,
|
|
112
115
|
view.amendment === null
|
|
113
116
|
? html `<p class="meta">The planner stated no reason for the amendment</p>`
|
|
114
117
|
: html `<p class="recap contract-reason"><strong>Why:</strong> ${view.amendment}</p>`,
|
|
@@ -165,7 +168,7 @@ export function revisionSentence(revision, acceptance) {
|
|
|
165
168
|
return `Fixes what ${build} missed: ${missed.map(clean).join("; ")}.`;
|
|
166
169
|
}
|
|
167
170
|
const notes = revision.comments.map(one => clean(one.note)).filter(one => one !== "");
|
|
168
|
-
return notes.length === 0 ? `
|
|
171
|
+
return notes.length === 0 ? `Sends back ${build}.` : `Fixes what ${build} missed: ${notes.join("; ")}.`;
|
|
169
172
|
}
|
|
170
173
|
/** A planner's amendment, said plainly and kept in view: approving binds
|
|
171
174
|
* the amended terms, so this is never folded away. */
|
|
@@ -242,11 +245,12 @@ export function approvalSheetHtml(input) {
|
|
|
242
245
|
input.localOwner != null ? html `` : 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
246
|
input.localOwner?.eligible === true ? html ``
|
|
244
247
|
: input.localOwner != null ? html `<p class="approval-password-note" id="${parts.passwordNote}">${input.localOwner.message} <a href="${input.localOwner.href}">${input.localOwner.action}</a></p>`
|
|
245
|
-
: input.freshSince == null ? html `<p class="approval-password-note" id="${parts.passwordNote}">
|
|
248
|
+
: input.freshSince == null ? html `<p class="approval-password-note" id="${parts.passwordNote}">Type your password to approve.</p>`
|
|
246
249
|
: 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>`,
|
|
247
250
|
html `<button type="submit" data-primary-action>${parts.submit}</button></div>`,
|
|
248
251
|
html `<p class="approval-after">${parts.after}</p>`,
|
|
249
|
-
|
|
252
|
+
// Approve, Request changes, Reject (docs/plans/plain-toolroll.md P1.3): Reject is the task's own Cancel, one tap away.
|
|
253
|
+
html `<div class="approval-secondary">${parts.editor ?? html `<a class="approval-link" href="${input.editHref}">Request changes</a>`}<a class="approval-link" href="${input.surface === "task" ? "#cancel-task" : input.notNowHref}">${input.surface === "task" ? "Reject" : "Not now"}</a></div>`,
|
|
250
254
|
parts.details,
|
|
251
255
|
]), {
|
|
252
256
|
attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
|
|
@@ -272,7 +276,7 @@ export function approvalSheetParts(input) {
|
|
|
272
276
|
const sentence = input.revision !== null && (repairText === null || input.revision.comments.length > 0) ? revisionSentence(input.revision, scope.acceptance)
|
|
273
277
|
: repairText === null ? null
|
|
274
278
|
: repairText[2] !== undefined ? `Fixes what ${builtBy} missed: ${statementsOf(repairText[2])}.`
|
|
275
|
-
: repairText[3] !== undefined ? `
|
|
279
|
+
: repairText[3] !== undefined ? `Shows what ${builtBy} couldn't show for: ${statementsOf(repairText[3])}. The saved code stays the same.`
|
|
276
280
|
: `Fixes the checks that failed in ${builtBy}.`;
|
|
277
281
|
const plan = input.planDocument === null ? null : parseExecutionPlanDocument(input.planDocument);
|
|
278
282
|
const milestones = plan !== null && plan.ok ? plan.document.milestones : [];
|
|
@@ -312,6 +316,14 @@ export function approvalSheetParts(input) {
|
|
|
312
316
|
if (allowed !== null)
|
|
313
317
|
allowing.push(allowed);
|
|
314
318
|
allowing.push(scope.budgetMicrousd === null ? "no attempt limit" : `up to ${money(scope.budgetMicrousd)} per attempt`);
|
|
319
|
+
// Fallback agents are terms: who takes over when an agent runs out, with any that spend money said before the yes.
|
|
320
|
+
const chain = chainTermsLines(scope);
|
|
321
|
+
const fallbacks = liveChainFromJson(scope.proposedChainJson ?? null)?.slice(1) ?? [];
|
|
322
|
+
if (fallbacks.length > 0) {
|
|
323
|
+
const paid = fallbacks.filter(one => one.authMode === "api-key" || SUBSCRIPTION_CAPABLE[one.profile.provider] !== true);
|
|
324
|
+
// Each agent named once, the paid one said where it stands in the order.
|
|
325
|
+
allowing.push(`if it runs out of its plan, the work moves to ${fallbacks.map(one => `${agentNameWords(one.profile.provider, one.profile.model)}${paid.includes(one) ? " on an API key, which spends money" : ""}`).join(", then ")}`);
|
|
326
|
+
}
|
|
315
327
|
const earlier = earlierVersionsWords(input.earlier.active, input.earlier.running);
|
|
316
328
|
if (earlier !== null)
|
|
317
329
|
allowing.push(earlier);
|
|
@@ -326,7 +338,7 @@ export function approvalSheetParts(input) {
|
|
|
326
338
|
: input.deliverable === "report"
|
|
327
339
|
? html `An agent investigates without changing the repository. You'll hear when its report is ready.`
|
|
328
340
|
: html `An agent starts in its own branch. You'll hear when it's ready to review.`;
|
|
329
|
-
const submit = rebuild !== null ? html `Approve
|
|
341
|
+
const submit = rebuild !== null ? html `Approve and retry` : html `Approve`;
|
|
330
342
|
// Everything else the digest binds, one tap away.
|
|
331
343
|
const detail = (title, body, attrs = html ``) => isEmptyHtml(body) ? html `` : html `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
|
|
332
344
|
const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
|
|
@@ -340,9 +352,9 @@ export function approvalSheetParts(input) {
|
|
|
340
352
|
const amendment = approvalAmendmentHtml(contract);
|
|
341
353
|
const contractDetail = input.planDocument === null || !isEmptyHtml(amendment) ? html ``
|
|
342
354
|
: contract === null || contract === undefined ? html `<p class="meta">No record compares this plan with what was filed. Check the rows above against your request.</p>`
|
|
343
|
-
: "problem" in contract ? html `<p class="meta">${contract.problem} · <a href="/r/${contract.run}">
|
|
355
|
+
: "problem" in contract ? html `<p class="meta">${contract.problem} · <a href="/r/${contract.run}">Planner's record</a></p>`
|
|
344
356
|
: !contract.filed && contract.revision !== true ? html `<p class="meta">Nothing was filed before planning, so every term is the planner's proposal.</p>`
|
|
345
|
-
: html `<p class="meta">${contract.revision === true ? "Same terms as the previous version." : "The plan keeps exactly what you filed."} <a href="/r/${contract.run}">
|
|
357
|
+
: html `<p class="meta">${contract.revision === true ? "Same terms as the previous version." : "The plan keeps exactly what you filed."} <a href="/r/${contract.run}">Planner's record</a></p>`;
|
|
346
358
|
const criteriaDetail = scope.acceptance.length === 0 ? html ``
|
|
347
359
|
: html `<ul class="approval-signed-criteria">${scope.acceptance.map(one => html `<li><code>${one.id}</code> ${one.statement} <span class="meta">shown by ${one.evidence.map(kind => EVIDENCE_PLAIN[kind] ?? kind).join(", ")}</span></li>`)}</ul>`;
|
|
348
360
|
const projection = route?.projection ?? null;
|
|
@@ -364,13 +376,13 @@ export function approvalSheetParts(input) {
|
|
|
364
376
|
const details = joinHtml([
|
|
365
377
|
html `<details class="approval-details"><summary>Plan details</summary>`,
|
|
366
378
|
revisionDetail,
|
|
367
|
-
repairText === null ? "" : detail("
|
|
379
|
+
repairText === null ? "" : detail("Full goal", html `<p>${scope.goal}</p>`),
|
|
368
380
|
detail("Plan record", contractDetail),
|
|
369
|
-
detail("
|
|
381
|
+
detail("Requirements", criteriaDetail),
|
|
370
382
|
detail("Why these agents", agentsDetail, html ` id="${input.surface === "task" ? "approval-agents" : "chat-approval-agents"}"`),
|
|
371
383
|
detail("Runtime limits", limits),
|
|
372
384
|
scope.candidate ? detail("Saved commit", html `<p><code class="approval-commit">${scope.candidate}</code></p>`) : "",
|
|
373
|
-
detail("
|
|
385
|
+
detail("Plan version", html `<p class="meta">Approving agrees to exactly this plan. <span class="seal mono" title="${planVersionWords(scope.digest)}">${planVersionWords(scope.digest)}</span></p>`),
|
|
374
386
|
html `</details>`,
|
|
375
387
|
]);
|
|
376
388
|
const passwordNote = input.surface === "task" ? "approval-password-note" : "chat-approval-password-note";
|
|
@@ -384,21 +396,25 @@ export function approvalSheetParts(input) {
|
|
|
384
396
|
const field = (name, value, rows, label, hint = "") => html `<label class="approval-field"><span class="approval-field-label">${label}</span>${hint === "" ? "" : html `<span class="approval-field-hint">${hint}</span>`}<textarea name="${name}" rows="${Math.min(10, Math.max(rows, value.split("\n").reduce((sum, line) => sum + Math.max(1, Math.ceil(line.length / 80)), 0)))}" form="${editForm}">${value}</textarea></label>`;
|
|
385
397
|
// Done when, one line per requirement in the signed order; the server
|
|
386
398
|
// matches them back by position (the digest guard keeps that order).
|
|
387
|
-
|
|
399
|
+
// Each requirement says how we'll know it is met (#19): a picker over the kinds a plan names, read back by position.
|
|
400
|
+
const howPicker = (name, chosen, label, planned) => html `<select name="${name}" aria-label="${label}" form="${editForm}" class="approval-how">${planned !== null && planned.length > 1 ? html `<option value="keep"${chosen === "keep" ? html ` selected` : ""}>${`As planned: ${planned.map(kind => EVIDENCE_PLAIN[kind] ?? kind).join(", ")}`}</option>` : ""}${HOW_WE_KNOW.map(kind => html `<option value="${kind}"${chosen === kind ? html ` selected` : ""}>${HOW_WE_KNOW_WORDS[kind]}</option>`)}</select>`;
|
|
401
|
+
const requirement = (name, value, label, how, placeholder = "") => html `<li><input type="text" name="${name}" value="${value}" aria-label="${label}"${placeholder === "" ? "" : html ` placeholder="${placeholder}"`} form="${editForm}">${how}</li>`;
|
|
388
402
|
const requirements = draft?.getAll("requirement") ?? scope.acceptance.map(one => one.statement);
|
|
403
|
+
const hows = draft?.getAll("requirement-how") ?? scope.acceptance.map(one => one.evidence.length === 1 ? one.evidence[0] : "keep");
|
|
389
404
|
const added = draft?.get("requirement-new") ?? "";
|
|
405
|
+
const addedHow = draft?.get("requirement-new-how") ?? "manual-review";
|
|
390
406
|
const editor = edit === null ? null : joinHtml([
|
|
391
407
|
html `<details class="approval-edit" id="plan-editor"${draft === null && !edit.open ? "" : html ` open`}>`,
|
|
392
|
-
html `<summary class="approval-link"><span class="approval-edit-open">
|
|
408
|
+
html `<summary class="approval-link"><span class="approval-edit-open">Request changes</span><span class="approval-edit-close">Cancel</span></summary>`,
|
|
393
409
|
html `<div class="approval-editor">`,
|
|
394
410
|
edit.problem === null ? "" : html `<p class="problem" role="alert">${edit.problem}</p>`,
|
|
395
411
|
field("goal", draft?.get("goal") ?? goalShown, 3, "Goal"),
|
|
396
412
|
field("touches", draft?.get("touches") ?? scope.touches.join("\n"), 2, "Changes", "Only in these paths, one per line. Leave empty for any file."),
|
|
397
413
|
field("not", draft?.get("not") ?? scope.outOfScope ?? "", 2, "Won’t touch"),
|
|
398
414
|
html `<fieldset class="approval-field"><legend class="approval-field-label">Done when</legend>`,
|
|
399
|
-
html `<span class="approval-field-hint">Clear a line to drop it.</span><ul class="approval-requirements">`,
|
|
400
|
-
requirements.map((one, index) => requirement("requirement", one, `Requirement ${index + 1}`)),
|
|
401
|
-
requirement("requirement-new", added, "Add a requirement", "Add a requirement"),
|
|
415
|
+
html `<span class="approval-field-hint">Clear a line to drop it. Beside each, how we'll know it is met.</span><ul class="approval-requirements">`,
|
|
416
|
+
requirements.map((one, index) => requirement("requirement", one, `Requirement ${index + 1}`, howPicker("requirement-how", hows[index] ?? "manual-review", `How we'll know requirement ${index + 1} is met`, scope.acceptance[index]?.evidence ?? null))),
|
|
417
|
+
requirement("requirement-new", added, "Add a requirement", howPicker("requirement-new-how", addedHow, "How we'll know the new requirement is met", null), "Add a requirement"),
|
|
402
418
|
html `</ul></fieldset>`,
|
|
403
419
|
html `<div class="approval-edit-act"><button type="submit" form="${editForm}">Save plan</button>`,
|
|
404
420
|
// A plan with written steps keeps its own editor for them.
|
|
@@ -434,6 +450,7 @@ export function approvalSheetParts(input) {
|
|
|
434
450
|
row("Changes", changes),
|
|
435
451
|
row("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
|
|
436
452
|
row("Done when", doneWhen),
|
|
453
|
+
chain.length === 0 ? "" : row("If it runs out", html `<p>${chain[0]}</p><ol class="approval-steps" data-approval-fallback>${chain.slice(1).map(one => html `<li>${one.replace(/^\d+\. /, "")}</li>`)}</ol>`),
|
|
437
454
|
html `</dl>`,
|
|
438
455
|
amendment,
|
|
439
456
|
// The size, said once and plainly beside who builds: "Small change: fast model, no plan".
|
|
@@ -442,7 +459,7 @@ export function approvalSheetParts(input) {
|
|
|
442
459
|
routeEvidenceHtml(route, "approval-evidence"),
|
|
443
460
|
consent,
|
|
444
461
|
]);
|
|
445
|
-
return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and
|
|
462
|
+
return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and retry" : "Approve", after, allowing, passwordNote };
|
|
446
463
|
}
|
|
447
464
|
/** Earlier versions of a task still in flight, in plain words: queued
|
|
448
465
|
* while they only wait, running once one has started; null when none. */
|
|
@@ -457,6 +474,9 @@ export class PermissionsWouldChange extends Error {
|
|
|
457
474
|
}
|
|
458
475
|
/** Evidence kinds in plain words, for the signed criteria in Details. */
|
|
459
476
|
export const EVIDENCE_PLAIN = { check: "the project check", screenshot: "screenshots", "changed-path": "changed files", "manual-review": "your own check" };
|
|
477
|
+
/** The kinds a requirement's "How we'll know" picker offers, in the order it lists them (#19). */
|
|
478
|
+
export const HOW_WE_KNOW = ["check", "screenshot", "changed-path", "manual-review"];
|
|
479
|
+
export const HOW_WE_KNOW_WORDS = { check: "The project check passes", screenshot: "A screenshot shows it", "changed-path": "The changed files show it", "manual-review": "You check it yourself" };
|
|
460
480
|
/** The permission choice a sealed profile carries, as the scope form names it. */
|
|
461
481
|
export function permissionModeOfProfile(profile) {
|
|
462
482
|
if (profile === null || profile === undefined)
|
|
@@ -471,30 +491,36 @@ export function permissionModeOfProfile(profile) {
|
|
|
471
491
|
/** Done when, from the in-place editor: each line keeps its requirement's
|
|
472
492
|
* id by position, and its evidence and guidance while its words stand; a
|
|
473
493
|
* rewritten one is yours to check, like an added one; an emptied one is
|
|
474
|
-
* dropped.
|
|
494
|
+
* dropped. A "How we'll know" choice beside a line (#19) names its one kind
|
|
495
|
+
* instead; "keep", or no choice at all (an older form), changes nothing. The
|
|
496
|
+
* saved plan is a new version either way: it needs approving like any edit. */
|
|
475
497
|
export function requirementsFromEditor(body, current) {
|
|
476
498
|
const used = new Set(current.map(one => one.id));
|
|
477
499
|
let next = 1;
|
|
478
500
|
while (used.has(`c${next}`))
|
|
479
501
|
next++;
|
|
480
502
|
const plain = (raw) => raw.replace(/\s+/g, " ").trim();
|
|
503
|
+
const hows = body.getAll("requirement-how");
|
|
504
|
+
const howOf = (raw) => raw !== undefined && HOW_WE_KNOW.includes(raw) ? raw : null;
|
|
481
505
|
const kept = body.getAll("requirement").slice(0, current.length).flatMap((raw, index) => {
|
|
482
506
|
const statement = plain(raw);
|
|
483
507
|
const criterion = current[index];
|
|
508
|
+
const how = howOf(hows[index]);
|
|
484
509
|
if (statement === "")
|
|
485
510
|
return [];
|
|
511
|
+
const evidence = how === null ? null : [how];
|
|
486
512
|
return statement === plain(criterion.statement)
|
|
487
|
-
? [{ id: criterion.id, statement: criterion.statement, evidence: [...criterion.evidence], how: criterion.how }]
|
|
488
|
-
: [{ id: criterion.id, statement, evidence: ["manual-review"], how: null }];
|
|
513
|
+
? [{ id: criterion.id, statement: criterion.statement, evidence: evidence ?? [...criterion.evidence], how: criterion.how }]
|
|
514
|
+
: [{ id: criterion.id, statement, evidence: evidence ?? ["manual-review"], how: null }];
|
|
489
515
|
});
|
|
490
516
|
const added = plain(body.get("requirement-new") ?? "");
|
|
491
|
-
return added === "" ? kept : [...kept, { id: `c${next}`, statement: added, evidence: ["manual-review"], how: null }];
|
|
517
|
+
return added === "" ? kept : [...kept, { id: `c${next}`, statement: added, evidence: [howOf(body.get("requirement-new-how") ?? undefined) ?? "manual-review"], how: null }];
|
|
492
518
|
}
|
|
493
519
|
export function consentDoorOf(scope, route) {
|
|
494
520
|
if (scope === null)
|
|
495
|
-
return { open: false, title: "There is no
|
|
521
|
+
return { open: false, title: "There is no plan to approve yet", why: "write the plan first", road: "scope" };
|
|
496
522
|
if (scope.profileState === "unresolved") {
|
|
497
|
-
return { open: false, title: "The agent setup isn’t ready yet", why: scope.unresolvedReason ?? "the
|
|
523
|
+
return { open: false, title: "The agent setup isn’t ready yet", why: scope.unresolvedReason ?? "the plan can't say exactly what would run", road: "scope" };
|
|
498
524
|
}
|
|
499
525
|
// THE SAME STRICT PROJECTION THE SEAL USES (v48 integrity): before any
|
|
500
526
|
// nonce, password field, or approve action renders, the stored scope
|
|
@@ -509,17 +535,17 @@ export function consentDoorOf(scope, route) {
|
|
|
509
535
|
return { open: false, title: "The provider’s auth mode can’t be read", why: `${authority.problem} — restate it, then approve`, road: "agents" };
|
|
510
536
|
}
|
|
511
537
|
if (authority.reason === "unrouted") {
|
|
512
|
-
return { open: false, title: "This
|
|
538
|
+
return { open: false, title: "This plan was made before agents were chosen per task", why: "Toolroll is choosing its agents, then you'll approve them. An approval now names exactly which agent plans, builds and fixes it: edit and save the plan to choose today’s agents, then approve it", road: "scope" };
|
|
513
539
|
}
|
|
514
|
-
return { open: false, title: "The agents on file can’t be read", why: `${authority.problem}
|
|
540
|
+
return { open: false, title: "The agents on file can’t be read", why: `${authority.problem}. Edit and save the plan to choose today’s agents again`, road: "scope" };
|
|
515
541
|
}
|
|
516
542
|
if (route === null || route === undefined)
|
|
517
543
|
return { open: true };
|
|
518
544
|
if (route.kind === "unreadable") {
|
|
519
|
-
return { open: false, title: "The agents on file can’t be read", why: `${route.problem}
|
|
545
|
+
return { open: false, title: "The agents on file can’t be read", why: `${route.problem}. Edit and save the plan to choose today’s agents again`, road: "scope" };
|
|
520
546
|
}
|
|
521
547
|
if (route.kind === "legacy") {
|
|
522
|
-
return { open: false, title: "This
|
|
548
|
+
return { open: false, title: "This plan was made before agents were chosen per task", why: "Toolroll is choosing its agents, then you'll approve them. An approval now names exactly which agent plans, builds and fixes it: edit and save the plan to choose today’s agents, then approve it", road: "scope" };
|
|
523
549
|
}
|
|
524
550
|
if (route.projection !== null && route.projection.problems.length > 0) {
|
|
525
551
|
return { open: false, title: "The agents on file can’t run", why: `${route.projection.problems.join("; ")} — change the agents, then approve`, road: "agents" };
|
|
@@ -530,7 +556,7 @@ export function consentDoorOf(scope, route) {
|
|
|
530
556
|
* the reason and the one act that opens it. */
|
|
531
557
|
export function consentClosedHtml(taskId, door, surface) {
|
|
532
558
|
const href = `${taskHref(taskId)}#${door.road === "agents" ? "agents" : "scope"}`;
|
|
533
|
-
const act = door.road === "agents" ? "Change the agents →" : "Edit
|
|
559
|
+
const act = door.road === "agents" ? "Change the agents →" : "Edit the plan →";
|
|
534
560
|
if (surface === "chat") {
|
|
535
561
|
return html `<section class="card chat-action-card consent-closed" id="task-chat-action"><span class="eyebrow">approval needs attention</span><h2>${door.title}</h2><p class="meta">${door.why}</p><a class="button-link" href="${href}">${act}</a></section>`;
|
|
536
562
|
}
|
|
@@ -541,12 +567,12 @@ export function consentClosedHtml(taskId, door, surface) {
|
|
|
541
567
|
}
|
|
542
568
|
export function profileWords(scope) {
|
|
543
569
|
if (scope.profileState === "unresolved") {
|
|
544
|
-
return html `<p class="meta"><strong>Filed but
|
|
570
|
+
return html `<p class="meta"><strong>Filed, but it can't be approved yet</strong> — ${scope.unresolvedReason ?? "the plan can't say exactly what would run"}. Edit the plan to fix it.</p>`;
|
|
545
571
|
}
|
|
546
572
|
const profile = scope.profile ?? null;
|
|
547
573
|
if (profile === null) {
|
|
548
574
|
return (scope.digestVersion ?? 1) < 2
|
|
549
|
-
? html `<p class="meta">Approved before
|
|
575
|
+
? html `<p class="meta">Approved before agents were chosen per task: it keeps the setup of that day</p>`
|
|
550
576
|
: html ``;
|
|
551
577
|
}
|
|
552
578
|
const repair = profile.repairModel === "inherit" ? "same model" : profile.repairModel;
|
|
@@ -559,7 +585,7 @@ export function profileWords(scope) {
|
|
|
559
585
|
: profile.provider === "gemini"
|
|
560
586
|
? html `<p class="meta">Runs on <span class="mono">gemini · ${profile.model}</span> — ${profile.approvalArgv === "yolo" ? "Full access via --approval-mode yolo; every tool auto-approved" : "Auto via --approval-mode auto_edit; edits auto-approved, other tools refused"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}), spend reported in tokens only; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`
|
|
561
587
|
: isAcpProfile(profile)
|
|
562
|
-
? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside
|
|
588
|
+
? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside Toolroll's sandbox; the agent runs any tool it chooses and nothing asks. No turn limit: stops after ${Math.round(profile.timeoutSeconds / 60)} min, or ${Math.round(profile.idleTimeoutSeconds / 60)} min silent; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min, or ${Math.round(profile.repairIdleTimeoutSeconds / 60)} min silent.</p>`
|
|
563
589
|
: html `<p class="meta">Runs on <span class="mono">${profile.provider} · ${profile.model}</span> — ${profile.sandboxMode === "danger-full-access" ? "FULL permissions via --dangerously-bypass-approvals-and-sandbox; nothing asks" : "workspace-write sandbox"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}); repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`;
|
|
564
590
|
// What the build may reach is part of what is approved (egress.ts): said on every approval form.
|
|
565
591
|
// Its sandbox terms (project-sandbox.ts): what it reads always, and any widening, part of what the yes agrees to.
|
|
@@ -748,7 +774,7 @@ export function agentsCardHtml(taskId, view, canEdit) {
|
|
|
748
774
|
routeEvidenceHtml(view),
|
|
749
775
|
p === null
|
|
750
776
|
? view.kind === "unreadable"
|
|
751
|
-
? html `<p class="agents-halted">Nothing runs for this task until its
|
|
777
|
+
? html `<p class="agents-halted">Nothing runs for this task until its plan is saved again and approved.</p>`
|
|
752
778
|
: ""
|
|
753
779
|
: joinHtml([
|
|
754
780
|
p.halted ? html `<p class="agents-halted">Paused: a provider these agents need is reported unavailable. Nothing else is used in its place — choose another agent below, or restore the provider and report readiness again.</p>` : "",
|
|
@@ -794,7 +820,7 @@ export function approvalProofWords(proof) {
|
|
|
794
820
|
if (proof.method === "chat")
|
|
795
821
|
return " (from your paired chat)";
|
|
796
822
|
if (proof.method === "carried")
|
|
797
|
-
return " (
|
|
823
|
+
return " (from the release approval)";
|
|
798
824
|
// Easy-start C5: this computer's owner approving attended work in local mode, with no password.
|
|
799
825
|
if (proof.method === "local-owner")
|
|
800
826
|
return " (on this computer, no password)";
|
|
@@ -807,11 +833,11 @@ export function approveRefusalWords(reason) {
|
|
|
807
833
|
if (reason === "machine-proof")
|
|
808
834
|
return "not approved: only a person can approve this. Approve it on the task page or from your paired chat";
|
|
809
835
|
if (reason === "archived")
|
|
810
|
-
return "not approved: this task is
|
|
836
|
+
return "not approved: this task is archived. Restore it first";
|
|
811
837
|
if (reason === "unrouted")
|
|
812
|
-
return "not approved: this
|
|
838
|
+
return "not approved: this plan was made before agents were chosen per task, so its old approval no longer stands. Edit and save the plan to choose today’s agents, then approve it";
|
|
813
839
|
if (reason === "profile-unresolved")
|
|
814
|
-
return "not approved: the
|
|
840
|
+
return "not approved: the plan can't name an exact agent for every role. Fix the agent setup, then save the plan again";
|
|
815
841
|
if (reason === "password-now")
|
|
816
842
|
return "not approved: this needs your password typed now — protected work, raised permissions, or a plan an agent wrote can't use a remembered one";
|
|
817
843
|
// A wrong password, someone who isn't an approver (or not for this project), a revoked login: one answer for all, so
|
|
@@ -941,7 +967,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
941
967
|
return html ``;
|
|
942
968
|
const guarded = csrf !== "" && !inert;
|
|
943
969
|
const runForm = (path, className, button) => postForm(`${taskHref(taskId)}/${path}`, button, { attrs: { class: className }, returnTo: surface, hidden: { run: control.run } });
|
|
944
|
-
const role = (word) => word.role === "planner" ? "plan attempt" : word.role === "scout" ? "
|
|
970
|
+
const role = (word) => word.role === "planner" ? "plan attempt" : word.role === "scout" ? "research attempt" : word.role === "reviewer" ? "review" : "build";
|
|
945
971
|
if (control.kind === "stop" && surface === "chat") {
|
|
946
972
|
return joinHtml([
|
|
947
973
|
html `<section class="card task-control" id="task-control" data-task-control="stop" data-control-run="${control.run}"><details class="chat-stop-confirm"><summary>Stop task</summary>`,
|
|
@@ -953,7 +979,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
953
979
|
if (control.kind === "stop") {
|
|
954
980
|
return joinHtml([
|
|
955
981
|
html `<section class="card task-control" id="task-control" data-task-control="stop" data-control-run="${control.run}" aria-label="stop this attempt">`,
|
|
956
|
-
html `<div class="task-control-copy"><details><summary>Stop details · ${role(control)} #${control.run}</summary><p class="meta">Stopping ends only this attempt's own processes. Its branch, uncommitted work,
|
|
982
|
+
html `<div class="task-control-copy"><details><summary>Stop details · ${role(control)} #${control.run}</summary><p class="meta">Stopping ends only this attempt's own processes. Its branch, uncommitted work, saved files and decisions are kept; other tasks keep running.</p></details></div>`,
|
|
957
983
|
stopElsewhere || !guarded || livePause === null || livePause.run !== control.run ? "" : pauseFormsHtml(taskId, livePause),
|
|
958
984
|
stopElsewhere ? "" : guarded
|
|
959
985
|
? runForm("stop", "inline task-control-form task-stop-form", html `<button type="submit" class="danger task-control-button">Stop</button>`)
|
|
@@ -974,7 +1000,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
974
1000
|
const settled = control.stop.settledAt === null ? "" : ` · settled ${stopWhen(control.stop.settledAt)} (${control.stop.settlement ?? "?"})`;
|
|
975
1001
|
const kept = control.stop.settlement === "finished"
|
|
976
1002
|
? html `The attempt reached its own ending (${control.outcome ?? "?"}) before the stop took effect; that outcome stands.`
|
|
977
|
-
: html `${control.committed ? "Its commit is on the branch as a reviewable artifact; a" : "A"}ny uncommitted work is preserved${control.worktree === null ? "" : html ` in <span class="mono">${control.worktree}</span>`}. Resuming
|
|
1003
|
+
: html `${control.committed ? "Its commit is on the branch as a reviewable artifact; a" : "A"}ny uncommitted work is preserved${control.worktree === null ? "" : html ` in <span class="mono">${control.worktree}</span>`}. Resuming checks the approved plan again and needs new checks — resuming approves nothing.`;
|
|
978
1004
|
return joinHtml([
|
|
979
1005
|
html `<section class="card task-control" id="task-control" data-task-control="paused" data-control-run="${control.run}" aria-label="paused attempt">`,
|
|
980
1006
|
html `<div class="task-control-copy"><span class="eyebrow">paused</span><strong>${role(control)} #${control.run} was stopped by <span class="mono">${control.stop.requestedBy}</span></strong>`,
|
|
@@ -991,23 +1017,19 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
991
1017
|
* does and does not do, the gate that would still keep work from
|
|
992
1018
|
* starting, and the password typed again. */
|
|
993
1019
|
export function resumeCeremonyPage(chrome, data) {
|
|
994
|
-
const back = html `<p class="meta"><a href="${data.returnTo === "chat" ? taskChatHref(data.taskId) : taskHref(data.taskId)}">Keep paused</a></p>`;
|
|
995
1020
|
const control = data.control;
|
|
1021
|
+
// One card, one button (docs/plans/plain-toolroll.md P1.3): the same post, nonce, run and password rule as before.
|
|
1022
|
+
const blocked = !data.approved ? "The plan isn't approved as it stands, so nothing builds until it is approved again."
|
|
1023
|
+
: data.gate === null ? null : `${data.gate.summary}: ${data.gate.detail}`;
|
|
996
1024
|
return screen("resume", joinHtml([
|
|
997
|
-
html `<h1>Resume
|
|
998
|
-
html `<div class="card resume-ceremony">`,
|
|
999
|
-
html `<p class="row">Lifts this stop’s hold. Other holds stay in place.</p>`,
|
|
1000
|
-
html `<p class="row">Continues saved work${control.committed ? " and its commit" : ""} under the current approved scope. The next attempt needs fresh evidence; the earlier handoff cannot count as a new result.</p>`,
|
|
1001
|
-
html `<p class="row">Resuming grants no new approval or publishing permission. Scope, budget, agents, verification, and review limits still apply.</p>`,
|
|
1002
|
-
html `<details><summary>Stop record and saved work</summary><p>Run #${control.run} was stopped by <span class="mono">${control.stop.requestedBy}</span> at ${stopWhen(control.stop.requestedAt)}${control.stop.settledAt === null ? "" : ` and settled ${stopWhen(control.stop.settledAt)} (${control.stop.settlement ?? "?"})`}.</p>${control.worktree === null ? "" : html `<p class="mono">${control.worktree}</p>`}</details>`,
|
|
1003
|
-
data.approved ? "" : html `<p class="row problem-words"><strong>The scope is not approved as it stands</strong> — resuming lifts the pause, but no worker spends until the scope is approved again</p>`,
|
|
1004
|
-
data.gate === null ? "" : html `<p class="row"><strong>Before work starts:</strong> ${data.gate.summary} — ${data.gate.detail}</p>`,
|
|
1005
|
-
html `</div>`,
|
|
1025
|
+
html `<h1>Resume build #${control.run}?</h1>`,
|
|
1006
1026
|
postForm(`${taskHref(data.taskId)}/resume`, joinHtml([
|
|
1007
|
-
html
|
|
1008
|
-
html `<
|
|
1027
|
+
html `<p class="resume-target"><strong>${data.taskTitle}</strong></p>`,
|
|
1028
|
+
html `<p class="row">It continues from the saved work${control.committed ? " and its commit" : ""}. Resuming approves nothing new.</p>`,
|
|
1029
|
+
blocked === null ? "" : html `<p class="row problem-words">${blocked}</p>`,
|
|
1030
|
+
html `${reauth(html `<label>Your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
1031
|
+
html `<button type="submit">Resume</button>`,
|
|
1009
1032
|
], "\n"), { attrs: { class: "card resume-form" }, returnTo: data.returnTo, hidden: { nonce: data.nonceValue, run: control.run } }),
|
|
1010
|
-
back,
|
|
1011
1033
|
], "\n"), { chrome });
|
|
1012
1034
|
}
|
|
1013
1035
|
export function taskViewSwitch(taskId, active) {
|
|
@@ -1032,6 +1054,7 @@ export function statusActionHref(status, taskId, runId, prUrl, repo) {
|
|
|
1032
1054
|
case "open-review": return repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId);
|
|
1033
1055
|
case "open-run": return runId === null ? taskHref(taskId) : `/r/${runId}`;
|
|
1034
1056
|
case "open-pr": return safePrUrl(prUrl) ?? (repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId));
|
|
1057
|
+
case "open-findings": return `${taskHref(taskId)}#findings`;
|
|
1035
1058
|
case "open-task":
|
|
1036
1059
|
default: return taskHref(taskId);
|
|
1037
1060
|
}
|
|
@@ -1041,7 +1064,7 @@ export function statusActionHref(status, taskId, runId, prUrl, repo) {
|
|
|
1041
1064
|
/** A not-yet-finished task's shared headline from its dispatch diagnosis (task-status.ts). */
|
|
1042
1065
|
export function dispatchHeadline(d) {
|
|
1043
1066
|
const read = stageOfDispatch(d);
|
|
1044
|
-
return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }) }).headline;
|
|
1067
|
+
return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }), ...(read.wait === undefined ? {} : { wait: read.wait }) }).headline;
|
|
1045
1068
|
}
|
|
1046
1069
|
export function statusLineHtml(status, extra = "") {
|
|
1047
1070
|
return html `<span class="status-line" data-work-status="${status.token}" data-tone="${status.tone}"><i class="status-dot" aria-hidden="true"></i><span class="status-label">${status.label}</span>${extra}</span>`;
|
|
@@ -1169,7 +1192,7 @@ export function planRevisionLedgerHtml(ledger, taskId, csrf) {
|
|
|
1169
1192
|
: joinHtml([
|
|
1170
1193
|
html `<div class="plan-revision-pending"><span class="eyebrow">decision needed</span><h3>The agent recommends a plan change</h3>`,
|
|
1171
1194
|
html `<p>${pendingRevision.reason}</p>`,
|
|
1172
|
-
html `<p class="meta">Revision ${pendingRevision.revision}${pendingRevision.authorityKind === "authority-change" ? ` changes ${pendingRevision.changedFields.map(changedAuthority).join(" and ")} from what you approved, so work is paused until you decide.` : " only changes the route, not the approved outcome."}${pendingRevision.evidenceLink === null ? "" : `
|
|
1195
|
+
html `<p class="meta">Revision ${pendingRevision.revision}${pendingRevision.authorityKind === "authority-change" ? ` changes ${pendingRevision.changedFields.map(changedAuthority).join(" and ")} from what you approved, so work is paused until you decide.` : " only changes the route, not the approved outcome."}${pendingRevision.evidenceLink === null ? "" : ` Why: ${pendingRevision.evidenceLink}.`}</p>`,
|
|
1173
1196
|
executionPlanHtml(pendingRevision.document, true),
|
|
1174
1197
|
csrf === "" || pendingRevision.id === null
|
|
1175
1198
|
? ""
|
|
@@ -1211,7 +1234,7 @@ export function executionPlanHtml(document, compact = false) {
|
|
|
1211
1234
|
html `<div class="execution-milestones"><span class="approval-label">milestones</span>${items(plan.milestones, true)}</div>`,
|
|
1212
1235
|
html `<div class="execution-support"><div><span class="approval-label">dependencies</span>${items(plan.dependencies)}</div>`,
|
|
1213
1236
|
html `<div><span class="approval-label">risks & mitigations</span>${items(plan.risks)}</div></div>`,
|
|
1214
|
-
html `<div class="execution-proof"><span class="approval-label">
|
|
1237
|
+
html `<div class="execution-proof"><span class="approval-label">how we'll know it's done</span>${items(plan.proof)}</div>`,
|
|
1215
1238
|
html `</div>`,
|
|
1216
1239
|
]);
|
|
1217
1240
|
}
|
|
@@ -1234,7 +1257,7 @@ export function revisionLineageWords(lineage) {
|
|
|
1234
1257
|
const chain = lineage.ancestors.length > 1 ? ` · lineage ${lineage.ancestors.join(" → ")}` : "";
|
|
1235
1258
|
words.push(`revises ${lineage.sourceTask}${lineage.sourceRun === null ? "" : ` (build #${lineage.sourceRun})`}${chain}`);
|
|
1236
1259
|
if (!lineage.sourceHadScope) {
|
|
1237
|
-
words.push("the
|
|
1260
|
+
words.push("the earlier version had no plan, so nothing was copied; the placeholder requirements wait for real ones");
|
|
1238
1261
|
}
|
|
1239
1262
|
else if (lineage.terms !== null) {
|
|
1240
1263
|
const terms = lineage.terms;
|
|
@@ -1318,7 +1341,7 @@ export function taskBodyParts(data) {
|
|
|
1318
1341
|
const peek = html `<p class="meta">What is changing right now</p><div id="run-peek"><p class="meta">Watching… the first look lands within 15 seconds</p></div><p class="meta" id="run-peek-stamp"></p>`;
|
|
1319
1342
|
const transcript = liveRun.provider !== "claude"
|
|
1320
1343
|
? html `<p class="meta">The live transcript needs the claude harness for now — this build runs on ${liveRun.provider}</p>`
|
|
1321
|
-
: html `<p class="meta">What the agent is saying
|
|
1344
|
+
: html `<p class="meta">What the agent is saying</p><pre id="live-transcript" class="mono" style="max-height:18rem;overflow:auto;white-space:pre-wrap"></pre><p class="meta" id="live-transcript-state"></p>`;
|
|
1322
1345
|
return html `<div class="card attempt-live" data-live-run="${liveRun.id}">${head}${peek}${transcript}${door}</div>`;
|
|
1323
1346
|
})();
|
|
1324
1347
|
// One truthful answer to the first question on a queued task: "will this
|
|
@@ -1338,9 +1361,9 @@ export function taskBodyParts(data) {
|
|
|
1338
1361
|
if (task.state !== "done") {
|
|
1339
1362
|
const diagnosis = data.dispatch ?? null;
|
|
1340
1363
|
if (diagnosis === null)
|
|
1341
|
-
return box("problem", "
|
|
1364
|
+
return box("problem", "Can't tell yet", "Reload this task to see whether it can start.", "unknown");
|
|
1342
1365
|
if (diagnosis.code === "running" && liveRun !== undefined) {
|
|
1343
|
-
return box("ok", diagnosis.summary, html `
|
|
1366
|
+
return box("ok", diagnosis.summary, html `Builder <span class="mono">${liveRun.runner}</span> is on <a href="/r/${liveRun.id}">build #${liveRun.id}</a>.`, diagnosis.code);
|
|
1344
1367
|
}
|
|
1345
1368
|
const blocker = diagnosis.blockerTaskId === null
|
|
1346
1369
|
? null
|
|
@@ -1348,11 +1371,11 @@ export function taskBodyParts(data) {
|
|
|
1348
1371
|
const action = (() => {
|
|
1349
1372
|
switch (diagnosis.action) {
|
|
1350
1373
|
case "start-worker": return "";
|
|
1351
|
-
case "write-scope": return html ` <a href="#scope">Write the
|
|
1374
|
+
case "write-scope": return html ` <a href="#scope">Write the plan</a> or use <strong>plan first</strong>.`;
|
|
1352
1375
|
case "select-agent": return html ` <a href="#scope">Choose an available provider and model</a>.`;
|
|
1353
|
-
case "approve-scope": return html ` <a href="#approve">Review
|
|
1376
|
+
case "approve-scope": return html ` <a href="#approve">Review the plan and approve it</a>.`;
|
|
1354
1377
|
case "answer-decision": return html ` <a href="#decisions">Answer the waiting question</a>.`;
|
|
1355
|
-
case "unhold": return html ` Use <strong>Remove hold</strong> when it can continue.`;
|
|
1378
|
+
case "unhold": return diagnosis.code === "storage-blocked" ? html ` Use <strong>Try again</strong> once it's allowed.` : html ` Use <strong>Remove hold</strong> when it can continue.`;
|
|
1356
1379
|
// The reason is the detail itself; Retry is the status card's own act, said once.
|
|
1357
1380
|
case "retry-task": return "";
|
|
1358
1381
|
case "repair-capability": return html ` <a href="/caps">Repair the requirement</a>.`;
|
|
@@ -1463,7 +1486,7 @@ export function taskBodyParts(data) {
|
|
|
1463
1486
|
// c2" — never pretending the machine always disagreed.
|
|
1464
1487
|
const machineNote = !reviewConflict(proof.proofMatrix, proof.machineVerdict, proof.proofVerdict)
|
|
1465
1488
|
? ""
|
|
1466
|
-
: html `<p class="meta">An independent review found
|
|
1489
|
+
: html `<p class="meta">An independent review found the report doesn't match the changes.</p>`;
|
|
1467
1490
|
const chainHtml = repairChainHtml(proof.repairChain);
|
|
1468
1491
|
// v51: semantic coverage sits beside the matrix, never inside the
|
|
1469
1492
|
// verdict word — what the independent reviewer settled, under the
|
|
@@ -1480,7 +1503,7 @@ export function taskBodyParts(data) {
|
|
|
1480
1503
|
// Damaged evidence (repair 2026-09-14): the box wears the receipt's
|
|
1481
1504
|
// own problem words — never "ok" with a verdict that predates the damage.
|
|
1482
1505
|
if (resultStatus.token === "evidence-damaged") {
|
|
1483
|
-
return withReview(box("problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${resultStatus.detail}${publicationWords}`, "evidence-damaged"), html `<div class="proof-review-actions"><a class="button-link" href="/r/${proof.runId}">Review
|
|
1506
|
+
return withReview(box("problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${resultStatus.detail}${publicationWords}`, "evidence-damaged"), html `<div class="proof-review-actions"><a class="button-link" href="/r/${proof.runId}">Review what doesn't match</a></div>`, proofDetails);
|
|
1484
1507
|
}
|
|
1485
1508
|
// A recorded pass the check read now doesn't stand behind (unreadable, failed since, or not read here) never
|
|
1486
1509
|
// reads "passed": the box says what holds now (task-status.ts approvedCheckOf).
|
|
@@ -1501,7 +1524,7 @@ export function taskBodyParts(data) {
|
|
|
1501
1524
|
const machineToken = proof.proofVerdict === "refuted" ? dispatchStatusToken("refuted") : dispatchStatusToken("short");
|
|
1502
1525
|
return withReview(box(accepted ? "ok" : "problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${verificationExplanation(proof.proofVerdict, proof.proofReasons)}${accepted ? html ` An approver accepted it by hand; that acceptance leaves the machine's verdict above unchanged.` : ""}${publicationWords}`, machineToken), html `<div class="proof-review-actions"><a class="button-link" href="${reviewHref(task.id)}">${accepted ? "Review the recorded exception" : resultStatus.action?.label ?? "Review evidence"}</a>${acceptForm}</div>`, proofDetails);
|
|
1503
1526
|
}
|
|
1504
|
-
return box("problem", "
|
|
1527
|
+
return box("problem", "Can't tell yet", "Reload this task to see whether it can start.", "unknown");
|
|
1505
1528
|
})();
|
|
1506
1529
|
// Operator steering (arc 1, v120): notes for the agent, each wearing the
|
|
1507
1530
|
// same words the CLI and the lead use — read (and when), waiting for the
|
|
@@ -1517,15 +1540,17 @@ export function taskBodyParts(data) {
|
|
|
1517
1540
|
? ""
|
|
1518
1541
|
: joinHtml([
|
|
1519
1542
|
postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem">${data.steeringRun == null ? "" : html `<input type="hidden" name="run" value="${String(data.steeringRun)}"><input type="hidden" name="key" value="${randomUUID()}"><input type="hidden" name="return" value="task">`}<button type="submit">${data.steeringRun == null ? "Save for the next attempt" : "Steer"}</button>`, { attrs: { class: "row" } }),
|
|
1520
|
-
html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved
|
|
1543
|
+
html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved plan.</p>`,
|
|
1521
1544
|
]);
|
|
1522
1545
|
const steeringCard = steerRows === "" && steerForm === "" ? "" : html `<h2>Steering</h2>${steerRows}${steerForm}`;
|
|
1523
1546
|
const holds = data.holds.length === 0
|
|
1524
1547
|
? ""
|
|
1525
1548
|
: joinHtml([
|
|
1526
1549
|
html `<h2>Holds</h2>`,
|
|
1527
|
-
joinHtml(data.holds.map(
|
|
1528
|
-
|
|
1550
|
+
joinHtml(data.holds.map(
|
|
1551
|
+
// The storage check's hold (dispose.ts holdForStorage) says what it waits for, in the check's own words.
|
|
1552
|
+
hold => html `<p class="row">${hold.ownerKind === "backoff" && hold.reason.startsWith(STORAGE_HOLD_PREFIX) ? `waiting for macOS permission — ${hold.reason.slice(STORAGE_HOLD_PREFIX.length)}` : `${holdOwnerWords(hold.ownerKind)} — ${hold.reason}`}${hold.until === null ? "" : html ` <span class="meta">until ${whenTime(hold.until)}</span>`}</p>`), "\n"),
|
|
1553
|
+
html `<p class="meta">Only your hold can be lifted here. Waits for a question, repeated failures or a retry clear on their own.</p>`,
|
|
1529
1554
|
]);
|
|
1530
1555
|
const approval = approvalOf(scope);
|
|
1531
1556
|
// A refused save from the sheet's in-place editor reopens that editor with
|
|
@@ -1533,22 +1558,22 @@ export function taskBodyParts(data) {
|
|
|
1533
1558
|
const inlineScopeDraft = data.scopeDraft?.has("requirement-new") === true;
|
|
1534
1559
|
const scopeDraft = inlineScopeDraft ? undefined : data.scopeDraft;
|
|
1535
1560
|
const scopeCard = scope === null
|
|
1536
|
-
? html `<p class="meta">No
|
|
1561
|
+
? html `<p class="meta">No plan yet. Nothing builds until one is approved.</p>`
|
|
1537
1562
|
: joinHtml([
|
|
1538
1563
|
html `<div class="card">`,
|
|
1539
1564
|
html `<p><strong>Goal</strong></p><p class="recap">${scope.goal}</p>`,
|
|
1540
1565
|
scope.outOfScope === null ? "" : html `<p><strong>Not this</strong></p><p class="recap">${scope.outOfScope}</p>`,
|
|
1541
|
-
scope.touches.length === 0 ? "" : html `<p class="scope-paths"><strong>
|
|
1566
|
+
scope.touches.length === 0 ? "" : html `<p class="scope-paths"><strong>Files</strong> ${scope.touches.join(", ")}</p>`,
|
|
1542
1567
|
html `<p><strong>Quality</strong> ${qualityModeTitle(scope.qualityMode ?? "default")}</p>`,
|
|
1543
1568
|
acceptanceCeremonyHtml(scope.acceptance),
|
|
1544
1569
|
scope.releaseTerms == null ? "" : html `<p><strong>Release</strong></p><p class="recap">${releaseTermsSentence(scope.releaseTerms)}</p>${data.releaseCiRunning === true ? html `<p class="meta">${RELEASE_CI_RUNNING_WORDS}</p>` : ""}${scope.releaseTerms.policy.carry ? html `<details><summary>What asks again</summary><ul>${joinHtml(releaseAsksAgainWords(scope.releaseTerms).map(line => html `<li>${line}</li>`), "")}</ul></details>` : ""}`,
|
|
1545
1570
|
approval.approved && data.autoRelease != null
|
|
1546
|
-
? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)}
|
|
1571
|
+
? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)}</p>${planVersionDetailsHtml(scope.digest)}`
|
|
1547
1572
|
: approval.approved && data.release?.kind === "carried"
|
|
1548
|
-
? html `<p class="meta scope-seal">
|
|
1573
|
+
? html `<p class="meta scope-seal">Approved with ${data.release.approver ?? "a person"}'s release approval · <a href="${taskHref(data.release.anchorTask)}">Release ${data.release.version}</a></p>${planVersionDetailsHtml(scope.digest)}`
|
|
1549
1574
|
: approval.approved
|
|
1550
|
-
? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}
|
|
1551
|
-
: html `<p class="meta"
|
|
1575
|
+
? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}</p>${planVersionDetailsHtml(scope.digest)}`
|
|
1576
|
+
: html `<p class="meta">${approval.reason === "changed" ? "Edited after approval" : "Not approved"}</p>${planVersionDetailsHtml(scope.digest)}`,
|
|
1552
1577
|
html `</div>`,
|
|
1553
1578
|
], "\n");
|
|
1554
1579
|
// The scout's report (mate arc §10): title, summary, the document inert,
|
|
@@ -1557,20 +1582,20 @@ export function taskBodyParts(data) {
|
|
|
1557
1582
|
// blank — the same rule as the revision brief.
|
|
1558
1583
|
const reportCard = data.report === null || data.report === undefined
|
|
1559
1584
|
? data.deliverable === "report"
|
|
1560
|
-
? html `<div class="card"><p><strong>
|
|
1585
|
+
? html `<div class="card"><p><strong>Research task</strong> <span class="meta">delivers a report, never a branch. A read-only session looks into the goal, and its report appears here when it finishes.</span></p></div>`
|
|
1561
1586
|
: ""
|
|
1562
1587
|
: !data.report.ok
|
|
1563
|
-
? html `<div class="card"><p><strong>The report</strong></p><p class="meta">${data.report.problem} · <a href="/r/${data.report.run}">
|
|
1588
|
+
? html `<div class="card"><p><strong>The report</strong></p><p class="meta">${data.report.problem} · <a href="/r/${data.report.run}">build #${data.report.run}</a></p></div>`
|
|
1564
1589
|
: joinHtml([
|
|
1565
1590
|
html `<div class="card report">`,
|
|
1566
|
-
html `<p><strong>${data.report.report.title}</strong> <span class="meta">the
|
|
1591
|
+
html `<p><strong>${data.report.report.title}</strong> <span class="meta">the research report · <a href="/r/${data.report.run}">build #${data.report.run}</a></span></p>`,
|
|
1567
1592
|
html `<p class="report-summary">${data.report.report.summary}</p>`,
|
|
1568
1593
|
reportItemsHtml(data.report.report.items, data.report.shots, data.report.run),
|
|
1569
1594
|
html `<pre class="recap plan-doc">${data.report.report.report}</pre>`,
|
|
1570
1595
|
...(data.report.report.followUps.length === 0
|
|
1571
1596
|
? []
|
|
1572
1597
|
: [
|
|
1573
|
-
html `<p><strong>Follow-ups the
|
|
1598
|
+
html `<p><strong>Follow-ups the research proposes</strong> <span class="meta">each files as a task in this project; its plan still needs your approval</span></p>`,
|
|
1574
1599
|
...data.report.report.followUps.map((one, index) => joinHtml([
|
|
1575
1600
|
html `<div class="follow-up"><p><strong>${one.title}</strong></p><p class="meta">${one.goal}</p>`,
|
|
1576
1601
|
data.csrf === "" || data.repo === null
|
|
@@ -1615,7 +1640,7 @@ export function taskBodyParts(data) {
|
|
|
1615
1640
|
: joinHtml([
|
|
1616
1641
|
approval.approved ? html `<details class="card planner-plan planner-plan-collapsed"><summary class="execution-plan-head">` : html `<section class="card planner-plan"><div class="execution-plan-head">`,
|
|
1617
1642
|
html `<div><span class="eyebrow">execution plan</span><h2>${approval.approved ? `${planMilestoneCount ?? "Full"} step${planMilestoneCount === 1 ? "" : "s"} · open to review` : "How the agent will tackle this"}</h2>`,
|
|
1618
|
-
approval.approved ? html `<p class="meta">The agent can adapt this route
|
|
1643
|
+
approval.approved ? html `<p class="meta">The agent can adapt this route as it learns more; your approved outcome stays fixed.</p>` : "",
|
|
1619
1644
|
html `</div><span class="plan-lock">${planStanding}</span>${approval.approved ? html `</summary><div class="planner-plan-body">` : html `</div>`}`,
|
|
1620
1645
|
executionPlanHtml(displayedPlanDocument ?? data.planDocument),
|
|
1621
1646
|
approval.approved || sheetStatesContract ? "" : planContractHtml(data.planContract ?? null, "full"),
|
|
@@ -1683,7 +1708,7 @@ export function taskBodyParts(data) {
|
|
|
1683
1708
|
deliverable: data.deliverable ?? "branch",
|
|
1684
1709
|
rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
|
|
1685
1710
|
editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
|
|
1686
|
-
notNowHref: "/
|
|
1711
|
+
notNowHref: "/tasks",
|
|
1687
1712
|
sticky: approvalLeads,
|
|
1688
1713
|
earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
|
|
1689
1714
|
edit: data.csrf === "" ? null
|
|
@@ -1693,21 +1718,21 @@ export function taskBodyParts(data) {
|
|
|
1693
1718
|
const approveForm = scope === null || approval.approved || data.plan === "requested"
|
|
1694
1719
|
? ""
|
|
1695
1720
|
: data.revision !== null && data.revision !== undefined && "problem" in data.revision
|
|
1696
|
-
? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: approval is blocked.</strong></p><p class="meta">${data.revision.problem}
|
|
1721
|
+
? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: approval is blocked.</strong></p><p class="meta">${data.revision.problem}. A revision can be approved only when its saved notes can be read.</p></div>`
|
|
1697
1722
|
: scope.profileState === "unresolved"
|
|
1698
|
-
? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: its
|
|
1723
|
+
? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: its plan can't be approved yet.</strong></p>${profileWords(scope)}<p class="ceremony-road"><a class="button-link" href="#scope">Edit the plan to fix it →</a></p></div>`
|
|
1699
1724
|
: !consentDoor.open
|
|
1700
1725
|
? consentClosedHtml(task.id, consentDoor, "task")
|
|
1701
1726
|
: approvalSheetHtml(sheetInput);
|
|
1702
1727
|
const scopeForm = joinHtml([
|
|
1703
|
-
html `<details${scope === null || scopeDraft !== undefined ? html ` open` : ""}><summary>${scope === null ? "Write the
|
|
1728
|
+
html `<details${scope === null || scopeDraft !== undefined ? html ` open` : ""}><summary>${scope === null ? "Write the plan" : "Edit the plan"}${approval.approved ? " (it asks for approval again)" : ""}</summary>`,
|
|
1704
1729
|
postForm(`${taskHref(task.id)}/scope`, joinHtml([
|
|
1705
1730
|
"",
|
|
1706
1731
|
scopeDraft === undefined || data.problem === null ? "" : html `<div class="problem" role="alert" id="scope-error">${data.problem}</div>`,
|
|
1707
1732
|
html `<label>Goal<textarea name="goal" rows="3"${scopeDraft === undefined ? "" : html ` autofocus aria-describedby="scope-error"`}>${scopeDraft?.get("goal") ?? scope?.goal ?? ""}</textarea></label>`,
|
|
1708
1733
|
html `<label>Not this<textarea name="not" rows="2">${scopeDraft?.get("not") ?? scope?.outOfScope ?? ""}</textarea></label>`,
|
|
1709
|
-
html `<label>
|
|
1710
|
-
html `<label>
|
|
1734
|
+
html `<label>Files <span class="meta">(one per line)</span><textarea name="touches" rows="2">${scopeDraft?.get("touches") ?? (scope?.touches ?? []).join("\n")}</textarea></label>`,
|
|
1735
|
+
html `<label>Done when <span class="meta">(required — one requirement per line: <code>what | how we'll know | note</code>; how we'll know is check, screenshot, changed-path or manual-review; ids number themselves)</span><textarea name="acceptance" rows="3" placeholder="The button opens the settings panel | screenshot">${scopeDraft?.get("acceptance") ?? acceptanceToLines(scope?.acceptance ?? []).join("\n")}</textarea></label>`,
|
|
1711
1736
|
(() => {
|
|
1712
1737
|
const defaults = data.spendDefaults ?? null;
|
|
1713
1738
|
const budgetPrefill = scope?.budgetMicrousd != null
|
|
@@ -1726,13 +1751,13 @@ export function taskBodyParts(data) {
|
|
|
1726
1751
|
? scope.profile.sandboxMode === "danger-full-access" ? "bypassPermissions" : "auto"
|
|
1727
1752
|
: null;
|
|
1728
1753
|
const selected = scopeDraft?.get("permission-mode") === "bypassPermissions" ? "bypassPermissions" : scopeDraft?.get("permission-mode") === "auto" ? "auto" : data.permissionMode ?? profileMode ?? data.permissionDefault ?? "auto";
|
|
1729
|
-
return html `<fieldset class="permission-field"><legend>Agent permissions</legend>${permissionModeChoices("permission-mode", selected)}<p class="meta permission-note">This
|
|
1754
|
+
return html `<fieldset class="permission-field"><legend>Agent permissions</legend>${permissionModeChoices("permission-mode", selected)}<p class="meta permission-note">This choice is part of the task's plan. Full access keeps permission prompts and sandbox limits from pausing agents that support it.</p></fieldset>`;
|
|
1730
1755
|
})(),
|
|
1731
1756
|
(() => {
|
|
1732
1757
|
const selected = scopeDraft?.get("quality-mode") === "strict" ? "strict" : scopeDraft?.get("quality-mode") === "default" ? "default" : scope?.qualityMode ?? data.qualityMode ?? data.qualityDefault ?? "default";
|
|
1733
|
-
return html `<fieldset class="permission-field"><legend>Quality</legend>${qualityModeChoices("quality-mode", selected)}<p class="meta permission-note">This choice is
|
|
1758
|
+
return html `<fieldset class="permission-field"><legend>Quality</legend>${qualityModeChoices("quality-mode", selected)}<p class="meta permission-note">This choice is part of the plan. Look at the saved work and the actual check results when it's ready. Publishing and deploying need their own approval.</p></fieldset>`;
|
|
1734
1759
|
})(),
|
|
1735
|
-
html `<button type="submit">Save
|
|
1760
|
+
html `<button type="submit">Save plan</button>`,
|
|
1736
1761
|
"",
|
|
1737
1762
|
], "\n"), { attrs: { class: "scope-editor" }, hidden: { sawDigest: scopeDraft?.get("sawDigest") ?? scope?.digest ?? "" } }),
|
|
1738
1763
|
html `</details>`,
|
|
@@ -1835,46 +1860,46 @@ export function taskBodyParts(data) {
|
|
|
1835
1860
|
// the scope's standing with its seal, publication, spend — the key
|
|
1836
1861
|
// facts a reader scans before anything else, in one row grammar.
|
|
1837
1862
|
const workerRow = liveRun !== undefined
|
|
1838
|
-
? prop("
|
|
1863
|
+
? prop("builder", html `${liveRun.runner} · <a href="/r/${liveRun.id}">build #${liveRun.id}</a> running`)
|
|
1839
1864
|
: data.runs[0] !== undefined
|
|
1840
1865
|
? prop("last attempt", html `<a href="/r/${data.runs[0].id}">${runNoun(data.runs[0])} #${data.runs[0].id}</a> · ${data.runs[0].role === "planner" && data.runs[0].reason === "plan-drafted" ? "planned" : data.runs[0].reason === "interrupted" ? "interrupted" : data.runs[0].id === liveHistoryRunId ? "running" : data.runs[0].outcome ?? "never finished"} · ${data.runs[0].runner}`)
|
|
1841
1866
|
: "";
|
|
1842
1867
|
const queueRow = data.position !== null && data.position !== undefined && task.state === "queued"
|
|
1843
|
-
? prop("queue", html `${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : html ` in ${data.position.column}'s queue`} · <a href="/
|
|
1868
|
+
? prop("queue", html `${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : html ` in ${data.position.column}'s queue`} · <a href="/tasks?view=order">reorder</a>`)
|
|
1844
1869
|
: "";
|
|
1845
1870
|
const scopeRow = scope === null
|
|
1846
|
-
? prop("
|
|
1871
|
+
? prop("plan", "none yet")
|
|
1847
1872
|
: approval.approved && data.autoRelease != null
|
|
1848
|
-
? prop("approved
|
|
1873
|
+
? prop("approved plan", html `${autoReleaseWords(data.autoRelease)}`)
|
|
1849
1874
|
: approval.approved && data.release?.kind === "carried"
|
|
1850
|
-
? prop("approved
|
|
1875
|
+
? prop("approved plan", html `approved with ${data.release.approver ?? "a person"}'s release approval · <a href="${taskHref(data.release.anchorTask)}">Release ${data.release.version}</a>`)
|
|
1851
1876
|
: approval.approved
|
|
1852
|
-
? prop("approved
|
|
1853
|
-
: prop("
|
|
1854
|
-
const strikesRow = data.strikes > 0 ? prop("
|
|
1877
|
+
? prop("approved plan", html `Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)} · ${qualityModeTitle(scope.qualityMode ?? "default")}`)
|
|
1878
|
+
: prop("plan", approval.reason === "changed" ? "Edited after approval: it needs a new approval" : "not approved");
|
|
1879
|
+
const strikesRow = data.strikes > 0 ? prop("failed attempts", `${data.strikes} in a row`) : "";
|
|
1855
1880
|
const propsCard = html `<div class="card props">${workerRow}${queueRow}${scopeRow}${publishesRow}${economics}${strikesRow}</div>`;
|
|
1856
1881
|
// The same facts for the rebuilt page, row for row.
|
|
1857
1882
|
const facts = [];
|
|
1858
1883
|
const lastRun = data.runs[0];
|
|
1859
1884
|
if (liveRun !== undefined)
|
|
1860
|
-
facts.push({ label: "
|
|
1885
|
+
facts.push({ label: "Builder", parts: [`${liveRun.runner} · `, { label: `build #${liveRun.id}`, href: `/r/${liveRun.id}` }, " running"] });
|
|
1861
1886
|
else if (lastRun !== undefined)
|
|
1862
1887
|
facts.push({ label: "Last attempt", parts: [{ label: `${runNoun(lastRun)} #${lastRun.id}`, href: `/r/${lastRun.id}` },
|
|
1863
1888
|
` · ${lastRun.role === "planner" && lastRun.reason === "plan-drafted" ? "planned" : lastRun.reason === "interrupted" ? "interrupted" : lastRun.id === liveHistoryRunId ? "running" : lastRun.outcome ?? "never finished"} · ${lastRun.runner}`] });
|
|
1864
1889
|
if (data.position !== null && data.position !== undefined && task.state === "queued") {
|
|
1865
|
-
facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/
|
|
1890
|
+
facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/tasks?view=order" }] });
|
|
1866
1891
|
}
|
|
1867
|
-
facts.push(scope === null ? { label: "
|
|
1868
|
-
: approval.approved && data.autoRelease != null ? { label: "Approved
|
|
1869
|
-
: approval.approved && data.release?.kind === "carried" ? { label: "Approved
|
|
1870
|
-
: approval.approved ? { label: "Approved
|
|
1871
|
-
: { label: "
|
|
1892
|
+
facts.push(scope === null ? { label: "Plan", parts: ["none yet"] }
|
|
1893
|
+
: approval.approved && data.autoRelease != null ? { label: "Approved plan", parts: [autoReleaseWords(data.autoRelease)] }
|
|
1894
|
+
: approval.approved && data.release?.kind === "carried" ? { label: "Approved plan", parts: [`Approved with ${data.release.approver ?? "a person"}'s release approval · `, { label: `Release ${data.release.version}`, href: taskHref(data.release.anchorTask) }] }
|
|
1895
|
+
: approval.approved ? { label: "Approved plan", parts: [`Approved by ${approval.by}${approvalProofWords(data.approvalProof)}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }]), ` · ${qualityModeTitle(scope.qualityMode ?? "default")}`] }
|
|
1896
|
+
: { label: "Plan", parts: [approval.reason === "changed" ? "Edited after approval: it needs a new approval" : "not approved"] });
|
|
1872
1897
|
if (data.release?.kind === "anchor")
|
|
1873
1898
|
facts.push({ label: "Release", parts: [`Approved for release ${data.release.version}`, ...(data.release.landed.length === 0 ? [] : [` · later commits: ${data.release.landed.map(one => one.sha.slice(0, 7)).join(", ")}`]), ...(data.release.carrying ? [] : [` · later commits ask again`])] });
|
|
1874
1899
|
facts.push({ label: "Publishes as", parts: [publishesAs] });
|
|
1875
1900
|
// v102: who asked for it, and what the project's approval rules need.
|
|
1876
1901
|
if (data.filer != null)
|
|
1877
|
-
facts.push({ label: "Filed by", parts: [data.filer.name === null ? "automation" : data.filer.kind === "coordinator" ? `${data.filer.name}, through
|
|
1902
|
+
facts.push({ label: "Filed by", parts: [data.filer.name === null ? "automation" : data.filer.kind === "coordinator" ? `${data.filer.name}, through your lead` : data.filer.name] });
|
|
1878
1903
|
if (data.approvalRules != null) {
|
|
1879
1904
|
const votes = data.approvalRules.votes;
|
|
1880
1905
|
facts.push({ label: "Approval rules", parts: [data.approvalRules.needsTwo && !approval.approved
|
|
@@ -1882,7 +1907,7 @@ export function taskBodyParts(data) {
|
|
|
1882
1907
|
: data.approvalRules.words] });
|
|
1883
1908
|
}
|
|
1884
1909
|
// v103: everything an auditor asks about this task, printable or as JSON.
|
|
1885
|
-
facts.push({ label: "Audit", parts: [{ label: "
|
|
1910
|
+
facts.push({ label: "Audit", parts: [{ label: "Audit record", href: `${taskHref(task.id)}/evidence` }] });
|
|
1886
1911
|
if (data.budgetHold != null)
|
|
1887
1912
|
facts.push({ label: "Budget", parts: [`${data.budgetHold} · `, { label: "Spend", href: "/spend" }] });
|
|
1888
1913
|
if (data.policyHold != null)
|
|
@@ -1967,7 +1992,7 @@ export function taskBodyParts(data) {
|
|
|
1967
1992
|
: postForm(`${taskHref(task.id)}/block`, html `<select name="on" aria-label="task to wait for">${candidates.map(one => html `<option value="${one.id}">${one.id} — ${one.title}</option>`)}</select><button type="submit">Wait for this task</button><span class="meta"> — this task starts only after it finishes</span>`, { attrs: { class: "row" } });
|
|
1968
1993
|
const waitsForCard = waitRows === "" && waitAdd === ""
|
|
1969
1994
|
? ""
|
|
1970
|
-
: html `<h2>Waits for</h2>${waitRows === "" ? html `<p class="meta">Nothing
|
|
1995
|
+
: html `<h2>Waits for</h2>${waitRows === "" ? html `<p class="meta">Nothing. It starts when a builder is free.</p>` : waitRows}${waitAdd}`;
|
|
1971
1996
|
// The acts bar (task page pass): every verb in one row under the title,
|
|
1972
1997
|
// the one that resolves this task's state first and primary — retry on a
|
|
1973
1998
|
// stalled task, build-next in the queue, plan-first with no scope. The
|
|
@@ -1979,13 +2004,13 @@ export function taskBodyParts(data) {
|
|
|
1979
2004
|
// A failed task's status card carries Retry itself (the same requeue); here it would be a second ink act.
|
|
1980
2005
|
const cardRetries = task.state === "failed" && data.assignment != null && data.csrf !== "";
|
|
1981
2006
|
const primaryAct = stopControlsActive || cardRetries ? null : stalled && !data.claimed
|
|
1982
|
-
? { html: act("requeue", "Retry, keeping the branch and workspace"), why: "
|
|
2007
|
+
? { html: act("requeue", "Retry, keeping the branch and workspace"), why: "Clears the repeated failures and queues the task again. The branch and workspace are kept.", whyClass: "retry" }
|
|
1983
2008
|
: canPlan
|
|
1984
|
-
? { html: act("plan", "plan first"), why: "plan first sends an agent to read the
|
|
2009
|
+
? { html: act("plan", "plan first"), why: "plan first sends an agent to read the project, ask you questions and propose a plan. Nothing builds until you approve it.", whyClass: "plan" }
|
|
1985
2010
|
: data.plan === "requested"
|
|
1986
2011
|
? null
|
|
1987
2012
|
: task.state === "queued" && !data.claimed && (data.position?.position ?? 1) > 1
|
|
1988
|
-
? { html: act("next", "build this next"), why: "moves it to the front of its queue
|
|
2013
|
+
? { html: act("next", "build this next"), why: "moves it to the front of its queue: the next free builder takes it first. It still needs approval.", whyClass: "next" }
|
|
1989
2014
|
: null;
|
|
1990
2015
|
const holdAct = postForm(`${taskHref(task.id)}/hold`, html `<input type="text" name="reason" class="inline" placeholder="reason (optional)" aria-label="hold reason"><button type="submit">Hold the next attempt</button>`, { attrs: { class: "inline act-hold" } });
|
|
1991
2016
|
const canHold = task.state === "queued" || task.state === "running" || task.state === "failed";
|
|
@@ -2000,7 +2025,9 @@ export function taskBodyParts(data) {
|
|
|
2000
2025
|
// to "plan first" adds a second, unnecessary decision at the exact
|
|
2001
2026
|
// moment the page should have one obvious action.
|
|
2002
2027
|
stopControlsActive || canPlan || !canHold ? "" : holdAct,
|
|
2003
|
-
data.holds.some(hold => hold.ownerKind === "operator") ? act("unhold", "Remove hold")
|
|
2028
|
+
data.holds.some(hold => hold.ownerKind === "operator") ? act("unhold", "Remove hold")
|
|
2029
|
+
// The storage check couldn't finish (dispose.ts holdForStorage): one tap tries again now.
|
|
2030
|
+
: data.holds.some(hold => hold.ownerKind === "backoff" && hold.ownerId === `storage:${hold.taskRef}`) ? act("unhold", "Try again") : "",
|
|
2004
2031
|
html `</div>`,
|
|
2005
2032
|
primaryAct === null ? "" : html `<p class="meta acts-why acts-why-${primaryAct.whyClass}">${primaryAct.why}</p>`,
|
|
2006
2033
|
data.claimed && !stopControlsActive
|
|
@@ -2039,7 +2066,7 @@ export function taskBodyParts(data) {
|
|
|
2039
2066
|
? ` · ${originWords(data.origin)}`
|
|
2040
2067
|
: data.filedVia === null || data.filedVia === undefined
|
|
2041
2068
|
? ""
|
|
2042
|
-
: ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">
|
|
2069
|
+
: ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Research</span>` : ""}</p>`;
|
|
2043
2070
|
// A pull request that can be merged (or opened) owns the one primary action; the result stays in its section.
|
|
2044
2071
|
const pullRequestActs = data.csrf !== "" && (data.pullRequest?.view?.canMerge === true || (data.pullRequest?.view == null && data.pullRequest?.offer != null));
|
|
2045
2072
|
const pullRequestFact = data.pullRequest?.fact != null && data.pullRequest.view?.runId === data.assignment?.receipt?.runId ? data.pullRequest.fact : undefined;
|
|
@@ -2083,7 +2110,7 @@ export function taskBodyParts(data) {
|
|
|
2083
2110
|
return joinHtml([
|
|
2084
2111
|
html `<div class="card"><p><strong>External work</strong> <span class="meta">${link} · ${state}</span></p>`,
|
|
2085
2112
|
reopenable
|
|
2086
|
-
? postForm(`${taskHref(task.id)}/reopen`, html `${reauth(html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password">`)}<button type="submit">Reopen — the approved
|
|
2113
|
+
? postForm(`${taskHref(task.id)}/reopen`, html `${reauth(html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password">`)}<button type="submit">Reopen — the approved plan stands</button>`, { attrs: { class: "row" } })
|
|
2087
2114
|
: "",
|
|
2088
2115
|
html `</div>`,
|
|
2089
2116
|
]);
|
|
@@ -2097,8 +2124,8 @@ export function taskBodyParts(data) {
|
|
|
2097
2124
|
// The quarantine speaks here too (round-2 finding 5): the planner
|
|
2098
2125
|
// is as fenced as the builder on a coordinator filing, so "plan
|
|
2099
2126
|
// first" would recommend a road that refuses.
|
|
2100
|
-
? html `<div class="card"><p><strong>This task is waiting on you: an agent filed it, and it has no
|
|
2101
|
-
: html `<div class="card task-scope-needed"><p><strong>No approved
|
|
2127
|
+
? html `<div class="card"><p><strong>This task is waiting on you: an agent filed it, and it has no plan.</strong></p><p class="meta">Filed by <span class="mono">${data.coordinator.label}</span> — nothing plans or runs until you write a plan below and approve it. Your approval runs their request.</p></div>`
|
|
2128
|
+
: html `<div class="card task-scope-needed"><p><strong>No approved plan yet</strong></p><p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
|
|
2102
2129
|
: "";
|
|
2103
2130
|
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : approvalLeads
|
|
2104
2131
|
? html `<section class="task-plan-review" aria-label="Approve the plan">${approveForm}</section>`
|
|
@@ -2135,9 +2162,9 @@ export function taskBodyParts(data) {
|
|
|
2135
2162
|
? ""
|
|
2136
2163
|
: section("proposals",
|
|
2137
2164
|
// Confirm and dismiss come back to this task.
|
|
2138
|
-
html `<h2>Proposed by
|
|
2165
|
+
html `<h2>Proposed by your lead</h2>${coordinatorProposalsSection(data.coordinatorProposals.rows, data.coordinatorProposals.decisions, data.coordinatorProposals.now, false, taskHref(data.task.id))}`, true, data.coordinatorProposals.rows.length),
|
|
2139
2166
|
section("decisions", decisions, true, data.decisions.length),
|
|
2140
|
-
section("
|
|
2167
|
+
section("repeated failures", incidents, true, data.incidents.length, "incidents"),
|
|
2141
2168
|
data.publication === null || data.publication === undefined
|
|
2142
2169
|
? ""
|
|
2143
2170
|
: html `<p class="row"><span class="meta">published</span> ${safePrUrl(data.publication.prUrl) === null ? html `<span class="mono">PR #${data.publication.prNumber ?? "?"}</span>` : html `<a href="${safePrUrl(data.publication.prUrl)}" class="mono">PR #${data.publication.prNumber ?? "?"}</a>`} <span class="meta">${data.publication.state}${data.publication.remoteState !== null ? ` · ${data.publication.remoteState.toLowerCase()} on GitHub` : ""}${data.publication.lastCheckState !== null
|
|
@@ -2147,10 +2174,14 @@ export function taskBodyParts(data) {
|
|
|
2147
2174
|
data.assignment == null ? section("attempts", runs, true, data.runs.length) : section("Build activity", data.runs.length === 0 ? "" : runRows, false, data.runs.length, "attempts"),
|
|
2148
2175
|
section("usage", spendCard, false),
|
|
2149
2176
|
section("steering", steeringCard, (data.steering ?? []).length > 0, (data.steering ?? []).length),
|
|
2150
|
-
section("
|
|
2177
|
+
data.followThrough == null ? "" : section("findings", data.followThrough.findings, data.followThrough.openFindings > 0, data.followThrough.findingCount),
|
|
2178
|
+
data.followThrough == null ? "" : section("failed checks", data.followThrough.repairs, true),
|
|
2179
|
+
data.followThrough == null ? "" : section("before and after", data.followThrough.evidence, false),
|
|
2180
|
+
data.followThrough == null ? "" : section("handover note", data.followThrough.handover, false),
|
|
2181
|
+
section("plan",
|
|
2151
2182
|
// The recipe road rides with the scope it reuses (UI polish
|
|
2152
2183
|
// 2026-09-13), off the title-to-action path.
|
|
2153
|
-
joinHtml([html `<h2>
|
|
2184
|
+
joinHtml([html `<h2>Plan</h2>`, scopeCard, releaseCardHtml(task.id, data.release ?? null, data.canStopRelease === true), html `<p class="meta">${data.repo !== null && data.scope !== null ? html `<a href="/recipes/from-task?task=${encodeURIComponent(task.id)}">Reuse this plan as a recipe →</a> · ` : ""}<a href="${taskHref(data.rootId ?? task.id)}/evidence">Audit record →</a></p>`, agentsCardHtml(task.id, data.route, data.canEditRoute === true), revisionInApproval ? "" : revisionCard, data.completion != null ? "" : repairChainHtml(data.repairChain ?? null), scopeForm], "\n"), scopeDraft !== undefined || (data.plan !== "requested" && approveForm === "" && !(scope === null && canPlan)), undefined, "scope"),
|
|
2154
2185
|
dependencyChoiceNeeded ? "" : section("waits for", waitsForCard, (data.waitsFor ?? []).length > 0, (data.waitsFor ?? []).length),
|
|
2155
2186
|
section("holds", holds, true, data.holds.length),
|
|
2156
2187
|
cancelAct,
|
|
@@ -2263,7 +2294,7 @@ export function taskBodyParts(data) {
|
|
|
2263
2294
|
: data.route?.legacy != null ? `${data.route.legacy.provider} · ${data.route.legacy.model}` : "The project's default"] });
|
|
2264
2295
|
if (scope !== null)
|
|
2265
2296
|
work.push({ label: "Checks level", parts: [qualityModeTitle(scope.qualityMode ?? "default")] });
|
|
2266
|
-
for (const label of ["
|
|
2297
|
+
for (const label of ["Builder", "Last attempt", "Queue", "Budget", "Policy", "Strikes"]) {
|
|
2267
2298
|
const fact = facts.find(one => one.label === label);
|
|
2268
2299
|
if (fact !== undefined)
|
|
2269
2300
|
work.push(fact);
|
|
@@ -2286,9 +2317,9 @@ export function taskBodyParts(data) {
|
|
|
2286
2317
|
links.push({ label: "Pull request", parts: [prHref === null ? `#${pr.prNumber ?? "?"}` : { label: `#${pr.prNumber ?? "?"}`, href: prHref }, ` · ${pr.label}`] });
|
|
2287
2318
|
}
|
|
2288
2319
|
const review = [];
|
|
2289
|
-
const approvedScope = facts.find(one => one.label === "Approved
|
|
2320
|
+
const approvedScope = facts.find(one => one.label === "Approved plan" || one.label === "Plan");
|
|
2290
2321
|
if (approvedScope !== undefined)
|
|
2291
|
-
review.push({ ...approvedScope, label: approvedScope.label === "Approved
|
|
2322
|
+
review.push({ ...approvedScope, label: approvedScope.label === "Approved plan" ? "Approvals" : "Plan" });
|
|
2292
2323
|
review.push({ label: "Who reviews", parts: [facts.find(one => one.label === "Approval rules")?.parts[0] ?? "Any approver in this project"] });
|
|
2293
2324
|
review.push({ label: "Publishes as", parts: [publishesAs] });
|
|
2294
2325
|
if (data.resultRecord != null)
|
|
@@ -2367,10 +2398,8 @@ export function taskBodyParts(data) {
|
|
|
2367
2398
|
...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
|
|
2368
2399
|
...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
|
|
2369
2400
|
...(data.csrf !== "" ? [{ group: "work", label: "Task options", kind: "goto", tab: "plan", target: "task-diagnostics" }] : []),
|
|
2370
|
-
...(data.canPutAway === true && data.csrf !== "" && data.archived == null && !building ? [{ group: "work", label: "
|
|
2401
|
+
...(data.canPutAway === true && data.csrf !== "" && data.archived == null && !building ? [{ group: "work", label: "Archive", kind: "post", action: `${taskHref(task.id)}/archive`, fields: { return: taskHref(task.id) } }] : []),
|
|
2371
2402
|
{ group: "share", label: "Copy link", kind: "copy" },
|
|
2372
|
-
...(facts.some(one => one.label === "Audit") ? [{ group: "share", label: "Evidence pack", kind: "link", href: `${taskHref(rootOf)}/evidence` }] : []),
|
|
2373
|
-
...(data.repo !== null && data.scope !== null ? [{ group: "share", label: "Reuse as recipe", kind: "link", href: `/recipes/from-task?task=${encodeURIComponent(task.id)}` }] : []),
|
|
2374
2403
|
...(cancelForm !== "" ? [{ group: "danger", label: "Cancel task", kind: "goto", tab: "plan", target: "cancel-task" }] : []),
|
|
2375
2404
|
];
|
|
2376
2405
|
const view = {
|
|
@@ -2408,12 +2437,15 @@ export function taskBodyParts(data) {
|
|
|
2408
2437
|
approve: data.archived != null || sheetInput === null || dependencyChoiceNeeded || data.csrf === "" ? null : approveKitOf(sheetInput, data.approvalFresh ?? null),
|
|
2409
2438
|
approved: scope !== null && approval.approved ? approvedKitOf(scope, approval, data.approvalProof ?? null) : null,
|
|
2410
2439
|
more,
|
|
2440
|
+
audit: facts.some(one => one.label === "Audit") ? { label: "Full record", href: `${taskHref(rootOf)}/evidence` } : null,
|
|
2411
2441
|
recheckStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "recheck-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
2412
2442
|
? { action: `${taskHref(data.assignment.rootId)}/recheck-stopped`, run: data.assignment.primaryAction.target.runId } : null,
|
|
2413
2443
|
rebuild,
|
|
2414
2444
|
// The plan, progress and plan changes are thread entries now; the rest still needs a person here.
|
|
2415
2445
|
lead: [
|
|
2416
2446
|
{ key: "history", html: data.history === undefined ? "" : htmlString(data.history) }, { key: "control", html: htmlString(stop === null ? controlHtml : taskControlDetailsHtml(data.control, task.id, data.csrf, "task", false, true)) }, { key: "problem", html: fragmentString(problemHtml) },
|
|
2447
|
+
{ key: "route-change", html: data.routeChange == null ? "" : htmlString(html `<div class="card"><p>${data.routeChange}</p></div>`) },
|
|
2448
|
+
{ key: "fast-grown", html: data.fastGrown == null || data.csrf === "" ? "" : htmlString(html `<div class="card"><p>${data.fastGrown}</p>${postForm(`${taskHref(task.id)}/keep-grown`, btn("Approve it", { variant: "primary", type: "submit" }))}</div>`) },
|
|
2417
2449
|
{ key: "pull-request", html: fragmentString(pullRequestCard) },
|
|
2418
2450
|
{ key: "needs-scope", html: fragmentString(needsScopeCard) },
|
|
2419
2451
|
{ key: "mirror", html: fragmentString(mirrorCard) }, { key: "attempt", html: fragmentString(attemptPanel) },
|
|
@@ -2541,7 +2573,7 @@ export function releaseCardHtml(taskId, release, canStop) {
|
|
|
2541
2573
|
if (release === null)
|
|
2542
2574
|
return html ``;
|
|
2543
2575
|
if (release.kind === "carried")
|
|
2544
|
-
return html `<div class="card" id="release"><p><strong>
|
|
2576
|
+
return html `<div class="card" id="release"><p><strong>Approved with ${release.approver ?? "a person"}'s release approval</strong></p><p class="meta"><a href="${taskHref(release.anchorTask)}">Release ${release.version}</a> · pull request #${release.pr}</p></div>`;
|
|
2545
2577
|
const until = new Intl.DateTimeFormat("en-US", { weekday: "short", hour: "numeric", minute: "2-digit" }).format(new Date(release.expiresAt));
|
|
2546
2578
|
const landed = release.landed.length === 0 ? html `<p class="meta">No later commits yet.</p>`
|
|
2547
2579
|
: html `<ul>${joinHtml(release.landed.map(one => html `<li><a href="${taskHref(one.task)}">${one.sha.slice(0, 7)}</a> · ${one.files.map(file => `${file.path} (+${file.added} −${file.deleted})`).join(", ")}</li>`), "")}</ul>`;
|
|
@@ -2553,7 +2585,7 @@ export function releaseCardHtml(taskId, release, canStop) {
|
|
|
2553
2585
|
release.obligations.length === 0 ? "" : html `<details><summary>Still to pass (${release.obligations.length})</summary><ul>${joinHtml(release.obligations.map(one => html `<li>${one}</li>`), "")}</ul></details>`,
|
|
2554
2586
|
release.nightly === null ? "" : html `<p class="meta">Nightly check of main: ${release.nightly.outcome ?? "running"} at ${release.nightly.sha.slice(0, 7)}${release.openMainFailures > 0 ? ` · ${release.openMainFailures} open failure${release.openMainFailures === 1 ? "" : "s"}` : ""}</p>`,
|
|
2555
2587
|
...release.githubNightly.map(line => html `<p class="meta">${line}</p>`),
|
|
2556
|
-
release.carrying && canStop ? postForm(`${taskHref(taskId)}/stop-carrying`, html `<button type="submit" class="secondary">
|
|
2588
|
+
release.carrying && canStop ? postForm(`${taskHref(taskId)}/stop-carrying`, html `<button type="submit" class="secondary">Ask again for later commits</button>`, { attrs: { class: "inline" } }) : "",
|
|
2557
2589
|
html `</div>`,
|
|
2558
2590
|
], "\n");
|
|
2559
2591
|
}
|
|
@@ -2571,7 +2603,7 @@ export function approveKitOf(input, freshSince) {
|
|
|
2571
2603
|
fields: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
|
|
2572
2604
|
label: parts.label,
|
|
2573
2605
|
heading: rebuild !== null ? "The plan changed" : input.deliverable === "report" ? "Investigation ready" : "Plan ready",
|
|
2574
|
-
text: `Approving
|
|
2606
|
+
text: `Approving ${after}.${allowing}`,
|
|
2575
2607
|
kind: "standard",
|
|
2576
2608
|
facts: { passwordless: passwordlessRendering() || input.localOwner?.eligible === true, remembered: until !== null },
|
|
2577
2609
|
rememberedUntil: until,
|
|
@@ -2586,7 +2618,11 @@ export function approvedKitOf(scope, approval, proof) {
|
|
|
2586
2618
|
return null;
|
|
2587
2619
|
const basis = scope.approvalBasis ?? null;
|
|
2588
2620
|
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"
|
|
2589
|
-
: basis === "auto-green" || basis === "auto-check" ? "this project’s release approval (CI and the release check passed)" : basis === "carried" ? "the release approval it
|
|
2621
|
+
: basis === "auto-green" || basis === "auto-check" ? "this project’s release approval (CI and the release check passed)" : basis === "carried" ? "the release approval it came with" : null;
|
|
2590
2622
|
const how = approvalProofWords(proof).replace(/^ \(|\)$/g, "");
|
|
2591
2623
|
return { by: approval.by, at: approval.at, automatic: rule !== null, rule, how: how === "" ? (rule === null ? "password typed" : "") : how, version: scope.digest.slice(0, 12) };
|
|
2592
2624
|
}
|
|
2625
|
+
/** The plan version, in Details: a short handle a person can compare, never on the approval line itself. */
|
|
2626
|
+
export function planVersionDetailsHtml(digest) {
|
|
2627
|
+
return html `<details class="scope-version"><summary class="meta">Details</summary><p class="meta"><span class="mono">${planVersionWords(digest)}</span></p></details>`;
|
|
2628
|
+
}
|