toolroll 0.9.92 → 0.9.94
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 +87 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- 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 -3
- package/dist/assignment.js +16 -3
- 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 +2 -2
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +2 -2
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +48 -7
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +73 -25
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +126 -0
- package/dist/check-facts.js +318 -0
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -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-report.d.ts +7 -0
- package/dist/check-report.js +4 -3
- 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/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +16 -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 +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- 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 +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -5
- package/dist/contracts/console-api.js +12 -5
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -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/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -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/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/evals.js +2 -1
- 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/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 +83 -0
- package/dist/flaky.js +264 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- 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-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- 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 +11 -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/needs-you.d.ts +32 -13
- package/dist/needs-you.js +108 -49
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +540 -70
- 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/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +13 -2
- 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/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +26 -3
- package/dist/push.js +55 -15
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +87 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +26 -17
- package/dist/release-card.d.ts +74 -6
- package/dist/release-card.js +179 -23
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +38 -23
- 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 +50 -1
- package/dist/release.js +118 -11
- 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 +29 -21
- 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 +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +4 -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 +2 -2
- 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-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 +19 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +2 -2
- package/dist/server/render-results.js +71 -73
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +156 -139
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +4 -1
- package/dist/server/route-table.js +52 -13
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +17 -6
- package/dist/server/tasks.js +273 -338
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +295 -0
- 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 +148 -34
- package/dist/store.js +626 -177
- package/dist/surface.js +46 -5
- 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.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +36 -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 +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- 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-ui.js +2 -2
- package/dist/toolroll-update.js +18 -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 +82 -70
- package/dist/work-summary.d.ts +2 -2
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
|
@@ -1,5 +1,6 @@
|
|
|
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
6
|
import { releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
|
|
@@ -28,6 +29,7 @@ import { starterForWork } from "../flow-starters.js";
|
|
|
28
29
|
import {} from "../gaps.js";
|
|
29
30
|
import { readAuthModeStrict } from "../keys.js";
|
|
30
31
|
import { failedAttemptSentence, retryNoteOf, START_BUILDER_COMMAND, startBuilderHref } from "../needs-you.js";
|
|
32
|
+
import { planVersionWords } from "../plain-words.js";
|
|
31
33
|
import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
|
|
32
34
|
import { parseExecutionPlanDocument } from "../plan.js";
|
|
33
35
|
import {} from "../planner-source.js";
|
|
@@ -50,7 +52,7 @@ import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SEN
|
|
|
50
52
|
import { steerDestinationWords, steerStandingParts } from "../steer-status.js";
|
|
51
53
|
import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
52
54
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
53
|
-
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords,
|
|
55
|
+
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
54
56
|
import {} from "./session.js";
|
|
55
57
|
import { linkWords, originNote, requestName, requestWords } from "../task-source.js";
|
|
56
58
|
export function contractChangeHtml(change) {
|
|
@@ -61,10 +63,10 @@ export function contractChangeHtml(change) {
|
|
|
61
63
|
case "outOfScope":
|
|
62
64
|
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
65
|
case "touches":
|
|
64
|
-
return html `<li>${tag} <strong>
|
|
66
|
+
return html `<li>${tag} <strong>files</strong> <span class="mono">${change.path}</span></li>`;
|
|
65
67
|
case "acceptance": {
|
|
66
68
|
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>
|
|
69
|
+
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
70
|
}
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -78,25 +80,25 @@ export function contractChangeHtml(change) {
|
|
|
78
80
|
*/
|
|
79
81
|
export function planContractHtml(view, mode) {
|
|
80
82
|
if (view === null) {
|
|
81
|
-
return mode === "full" ? html `` : html `<p class="meta contract-note">
|
|
83
|
+
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
84
|
}
|
|
83
85
|
if ("problem" in view) {
|
|
84
|
-
return html `<div class="contract-panel contract-problem"><p class="approval-label">
|
|
86
|
+
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
87
|
}
|
|
86
|
-
const stale = view.current ? "" : html `<p class="meta">The
|
|
88
|
+
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
89
|
if (!view.filed) {
|
|
88
90
|
return mode === "full"
|
|
89
|
-
? html `<div class="contract-panel contract-drafted"><p class="approval-label">
|
|
90
|
-
: html `<p class="meta contract-note">
|
|
91
|
+
? 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>`
|
|
92
|
+
: html `<p class="meta contract-note">Nothing was filed before planning: every part above is the planner's.</p>`;
|
|
91
93
|
}
|
|
92
94
|
if (view.revision === true) {
|
|
93
95
|
// A send-back: the terms were copied from the previous version, and the planner updated them with the notes.
|
|
94
96
|
if (view.changes.length === 0) {
|
|
95
|
-
return html `<div class="contract-panel contract-preserved"><p class="approval-label">updated plan</p><p><strong>Same
|
|
97
|
+
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
98
|
}
|
|
97
99
|
return joinHtml([
|
|
98
100
|
html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">updated plan</p>`,
|
|
99
|
-
html `<p><strong
|
|
101
|
+
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
102
|
view.amendment === null ? "" : html `<p class="recap contract-reason"><strong>Why:</strong> ${view.amendment}</p>`,
|
|
101
103
|
html `<ul class="recap contract-changes">${view.changes.map(contractChangeHtml)}</ul>`,
|
|
102
104
|
stale,
|
|
@@ -104,11 +106,11 @@ export function planContractHtml(view, mode) {
|
|
|
104
106
|
]);
|
|
105
107
|
}
|
|
106
108
|
if (view.changes.length === 0) {
|
|
107
|
-
return html `<div class="contract-panel contract-preserved"><p class="approval-label">
|
|
109
|
+
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
110
|
}
|
|
109
111
|
return joinHtml([
|
|
110
|
-
html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">
|
|
111
|
-
html `<p><strong
|
|
112
|
+
html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">Your request · changes proposed</p>`,
|
|
113
|
+
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
114
|
view.amendment === null
|
|
113
115
|
? html `<p class="meta">The planner stated no reason for the amendment</p>`
|
|
114
116
|
: html `<p class="recap contract-reason"><strong>Why:</strong> ${view.amendment}</p>`,
|
|
@@ -165,7 +167,7 @@ export function revisionSentence(revision, acceptance) {
|
|
|
165
167
|
return `Fixes what ${build} missed: ${missed.map(clean).join("; ")}.`;
|
|
166
168
|
}
|
|
167
169
|
const notes = revision.comments.map(one => clean(one.note)).filter(one => one !== "");
|
|
168
|
-
return notes.length === 0 ? `
|
|
170
|
+
return notes.length === 0 ? `Sends back ${build}.` : `Fixes what ${build} missed: ${notes.join("; ")}.`;
|
|
169
171
|
}
|
|
170
172
|
/** A planner's amendment, said plainly and kept in view: approving binds
|
|
171
173
|
* the amended terms, so this is never folded away. */
|
|
@@ -242,11 +244,12 @@ export function approvalSheetHtml(input) {
|
|
|
242
244
|
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
245
|
input.localOwner?.eligible === true ? html ``
|
|
244
246
|
: 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}">
|
|
247
|
+
: input.freshSince == null ? html `<p class="approval-password-note" id="${parts.passwordNote}">Type your password to approve.</p>`
|
|
246
248
|
: 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
249
|
html `<button type="submit" data-primary-action>${parts.submit}</button></div>`,
|
|
248
250
|
html `<p class="approval-after">${parts.after}</p>`,
|
|
249
|
-
|
|
251
|
+
// Approve, Request changes, Reject (docs/plans/plain-toolroll.md P1.3): Reject is the task's own Cancel, one tap away.
|
|
252
|
+
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
253
|
parts.details,
|
|
251
254
|
]), {
|
|
252
255
|
attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
|
|
@@ -272,7 +275,7 @@ export function approvalSheetParts(input) {
|
|
|
272
275
|
const sentence = input.revision !== null && (repairText === null || input.revision.comments.length > 0) ? revisionSentence(input.revision, scope.acceptance)
|
|
273
276
|
: repairText === null ? null
|
|
274
277
|
: repairText[2] !== undefined ? `Fixes what ${builtBy} missed: ${statementsOf(repairText[2])}.`
|
|
275
|
-
: repairText[3] !== undefined ? `
|
|
278
|
+
: repairText[3] !== undefined ? `Shows what ${builtBy} couldn't show for: ${statementsOf(repairText[3])}. The saved code stays the same.`
|
|
276
279
|
: `Fixes the checks that failed in ${builtBy}.`;
|
|
277
280
|
const plan = input.planDocument === null ? null : parseExecutionPlanDocument(input.planDocument);
|
|
278
281
|
const milestones = plan !== null && plan.ok ? plan.document.milestones : [];
|
|
@@ -326,7 +329,7 @@ export function approvalSheetParts(input) {
|
|
|
326
329
|
: input.deliverable === "report"
|
|
327
330
|
? html `An agent investigates without changing the repository. You'll hear when its report is ready.`
|
|
328
331
|
: html `An agent starts in its own branch. You'll hear when it's ready to review.`;
|
|
329
|
-
const submit = rebuild !== null ? html `Approve
|
|
332
|
+
const submit = rebuild !== null ? html `Approve and retry` : html `Approve`;
|
|
330
333
|
// Everything else the digest binds, one tap away.
|
|
331
334
|
const detail = (title, body, attrs = html ``) => isEmptyHtml(body) ? html `` : html `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
|
|
332
335
|
const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
|
|
@@ -340,9 +343,9 @@ export function approvalSheetParts(input) {
|
|
|
340
343
|
const amendment = approvalAmendmentHtml(contract);
|
|
341
344
|
const contractDetail = input.planDocument === null || !isEmptyHtml(amendment) ? html ``
|
|
342
345
|
: 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}">
|
|
346
|
+
: "problem" in contract ? html `<p class="meta">${contract.problem} · <a href="/r/${contract.run}">Planner's record</a></p>`
|
|
344
347
|
: !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}">
|
|
348
|
+
: 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
349
|
const criteriaDetail = scope.acceptance.length === 0 ? html ``
|
|
347
350
|
: 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
351
|
const projection = route?.projection ?? null;
|
|
@@ -364,13 +367,13 @@ export function approvalSheetParts(input) {
|
|
|
364
367
|
const details = joinHtml([
|
|
365
368
|
html `<details class="approval-details"><summary>Plan details</summary>`,
|
|
366
369
|
revisionDetail,
|
|
367
|
-
repairText === null ? "" : detail("
|
|
370
|
+
repairText === null ? "" : detail("Full goal", html `<p>${scope.goal}</p>`),
|
|
368
371
|
detail("Plan record", contractDetail),
|
|
369
|
-
detail("
|
|
372
|
+
detail("Requirements", criteriaDetail),
|
|
370
373
|
detail("Why these agents", agentsDetail, html ` id="${input.surface === "task" ? "approval-agents" : "chat-approval-agents"}"`),
|
|
371
374
|
detail("Runtime limits", limits),
|
|
372
375
|
scope.candidate ? detail("Saved commit", html `<p><code class="approval-commit">${scope.candidate}</code></p>`) : "",
|
|
373
|
-
detail("
|
|
376
|
+
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
377
|
html `</details>`,
|
|
375
378
|
]);
|
|
376
379
|
const passwordNote = input.surface === "task" ? "approval-password-note" : "chat-approval-password-note";
|
|
@@ -384,21 +387,25 @@ export function approvalSheetParts(input) {
|
|
|
384
387
|
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
388
|
// Done when, one line per requirement in the signed order; the server
|
|
386
389
|
// matches them back by position (the digest guard keeps that order).
|
|
387
|
-
|
|
390
|
+
// Each requirement says how we'll know it is met (#19): a picker over the kinds a plan names, read back by position.
|
|
391
|
+
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>`;
|
|
392
|
+
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
393
|
const requirements = draft?.getAll("requirement") ?? scope.acceptance.map(one => one.statement);
|
|
394
|
+
const hows = draft?.getAll("requirement-how") ?? scope.acceptance.map(one => one.evidence.length === 1 ? one.evidence[0] : "keep");
|
|
389
395
|
const added = draft?.get("requirement-new") ?? "";
|
|
396
|
+
const addedHow = draft?.get("requirement-new-how") ?? "manual-review";
|
|
390
397
|
const editor = edit === null ? null : joinHtml([
|
|
391
398
|
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">
|
|
399
|
+
html `<summary class="approval-link"><span class="approval-edit-open">Request changes</span><span class="approval-edit-close">Cancel</span></summary>`,
|
|
393
400
|
html `<div class="approval-editor">`,
|
|
394
401
|
edit.problem === null ? "" : html `<p class="problem" role="alert">${edit.problem}</p>`,
|
|
395
402
|
field("goal", draft?.get("goal") ?? goalShown, 3, "Goal"),
|
|
396
403
|
field("touches", draft?.get("touches") ?? scope.touches.join("\n"), 2, "Changes", "Only in these paths, one per line. Leave empty for any file."),
|
|
397
404
|
field("not", draft?.get("not") ?? scope.outOfScope ?? "", 2, "Won’t touch"),
|
|
398
405
|
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"),
|
|
406
|
+
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">`,
|
|
407
|
+
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))),
|
|
408
|
+
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
409
|
html `</ul></fieldset>`,
|
|
403
410
|
html `<div class="approval-edit-act"><button type="submit" form="${editForm}">Save plan</button>`,
|
|
404
411
|
// A plan with written steps keeps its own editor for them.
|
|
@@ -442,7 +449,7 @@ export function approvalSheetParts(input) {
|
|
|
442
449
|
routeEvidenceHtml(route, "approval-evidence"),
|
|
443
450
|
consent,
|
|
444
451
|
]);
|
|
445
|
-
return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and
|
|
452
|
+
return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and retry" : "Approve", after, allowing, passwordNote };
|
|
446
453
|
}
|
|
447
454
|
/** Earlier versions of a task still in flight, in plain words: queued
|
|
448
455
|
* while they only wait, running once one has started; null when none. */
|
|
@@ -457,6 +464,9 @@ export class PermissionsWouldChange extends Error {
|
|
|
457
464
|
}
|
|
458
465
|
/** Evidence kinds in plain words, for the signed criteria in Details. */
|
|
459
466
|
export const EVIDENCE_PLAIN = { check: "the project check", screenshot: "screenshots", "changed-path": "changed files", "manual-review": "your own check" };
|
|
467
|
+
/** The kinds a requirement's "How we'll know" picker offers, in the order it lists them (#19). */
|
|
468
|
+
export const HOW_WE_KNOW = ["check", "screenshot", "changed-path", "manual-review"];
|
|
469
|
+
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
470
|
/** The permission choice a sealed profile carries, as the scope form names it. */
|
|
461
471
|
export function permissionModeOfProfile(profile) {
|
|
462
472
|
if (profile === null || profile === undefined)
|
|
@@ -471,30 +481,36 @@ export function permissionModeOfProfile(profile) {
|
|
|
471
481
|
/** Done when, from the in-place editor: each line keeps its requirement's
|
|
472
482
|
* id by position, and its evidence and guidance while its words stand; a
|
|
473
483
|
* rewritten one is yours to check, like an added one; an emptied one is
|
|
474
|
-
* dropped.
|
|
484
|
+
* dropped. A "How we'll know" choice beside a line (#19) names its one kind
|
|
485
|
+
* instead; "keep", or no choice at all (an older form), changes nothing. The
|
|
486
|
+
* saved plan is a new version either way: it needs approving like any edit. */
|
|
475
487
|
export function requirementsFromEditor(body, current) {
|
|
476
488
|
const used = new Set(current.map(one => one.id));
|
|
477
489
|
let next = 1;
|
|
478
490
|
while (used.has(`c${next}`))
|
|
479
491
|
next++;
|
|
480
492
|
const plain = (raw) => raw.replace(/\s+/g, " ").trim();
|
|
493
|
+
const hows = body.getAll("requirement-how");
|
|
494
|
+
const howOf = (raw) => raw !== undefined && HOW_WE_KNOW.includes(raw) ? raw : null;
|
|
481
495
|
const kept = body.getAll("requirement").slice(0, current.length).flatMap((raw, index) => {
|
|
482
496
|
const statement = plain(raw);
|
|
483
497
|
const criterion = current[index];
|
|
498
|
+
const how = howOf(hows[index]);
|
|
484
499
|
if (statement === "")
|
|
485
500
|
return [];
|
|
501
|
+
const evidence = how === null ? null : [how];
|
|
486
502
|
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 }];
|
|
503
|
+
? [{ id: criterion.id, statement: criterion.statement, evidence: evidence ?? [...criterion.evidence], how: criterion.how }]
|
|
504
|
+
: [{ id: criterion.id, statement, evidence: evidence ?? ["manual-review"], how: null }];
|
|
489
505
|
});
|
|
490
506
|
const added = plain(body.get("requirement-new") ?? "");
|
|
491
|
-
return added === "" ? kept : [...kept, { id: `c${next}`, statement: added, evidence: ["manual-review"], how: null }];
|
|
507
|
+
return added === "" ? kept : [...kept, { id: `c${next}`, statement: added, evidence: [howOf(body.get("requirement-new-how") ?? undefined) ?? "manual-review"], how: null }];
|
|
492
508
|
}
|
|
493
509
|
export function consentDoorOf(scope, route) {
|
|
494
510
|
if (scope === null)
|
|
495
|
-
return { open: false, title: "There is no
|
|
511
|
+
return { open: false, title: "There is no plan to approve yet", why: "write the plan first", road: "scope" };
|
|
496
512
|
if (scope.profileState === "unresolved") {
|
|
497
|
-
return { open: false, title: "The agent setup isn’t ready yet", why: scope.unresolvedReason ?? "the
|
|
513
|
+
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
514
|
}
|
|
499
515
|
// THE SAME STRICT PROJECTION THE SEAL USES (v48 integrity): before any
|
|
500
516
|
// nonce, password field, or approve action renders, the stored scope
|
|
@@ -509,17 +525,17 @@ export function consentDoorOf(scope, route) {
|
|
|
509
525
|
return { open: false, title: "The provider’s auth mode can’t be read", why: `${authority.problem} — restate it, then approve`, road: "agents" };
|
|
510
526
|
}
|
|
511
527
|
if (authority.reason === "unrouted") {
|
|
512
|
-
return { open: false, title: "This
|
|
528
|
+
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
529
|
}
|
|
514
|
-
return { open: false, title: "The agents on file can’t be read", why: `${authority.problem}
|
|
530
|
+
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
531
|
}
|
|
516
532
|
if (route === null || route === undefined)
|
|
517
533
|
return { open: true };
|
|
518
534
|
if (route.kind === "unreadable") {
|
|
519
|
-
return { open: false, title: "The agents on file can’t be read", why: `${route.problem}
|
|
535
|
+
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
536
|
}
|
|
521
537
|
if (route.kind === "legacy") {
|
|
522
|
-
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" };
|
|
523
539
|
}
|
|
524
540
|
if (route.projection !== null && route.projection.problems.length > 0) {
|
|
525
541
|
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 +546,7 @@ export function consentDoorOf(scope, route) {
|
|
|
530
546
|
* the reason and the one act that opens it. */
|
|
531
547
|
export function consentClosedHtml(taskId, door, surface) {
|
|
532
548
|
const href = `${taskHref(taskId)}#${door.road === "agents" ? "agents" : "scope"}`;
|
|
533
|
-
const act = door.road === "agents" ? "Change the agents →" : "Edit
|
|
549
|
+
const act = door.road === "agents" ? "Change the agents →" : "Edit the plan →";
|
|
534
550
|
if (surface === "chat") {
|
|
535
551
|
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
552
|
}
|
|
@@ -541,12 +557,12 @@ export function consentClosedHtml(taskId, door, surface) {
|
|
|
541
557
|
}
|
|
542
558
|
export function profileWords(scope) {
|
|
543
559
|
if (scope.profileState === "unresolved") {
|
|
544
|
-
return html `<p class="meta"><strong>Filed but
|
|
560
|
+
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
561
|
}
|
|
546
562
|
const profile = scope.profile ?? null;
|
|
547
563
|
if (profile === null) {
|
|
548
564
|
return (scope.digestVersion ?? 1) < 2
|
|
549
|
-
? html `<p class="meta">Approved before
|
|
565
|
+
? html `<p class="meta">Approved before agents were chosen per task: it keeps the setup of that day</p>`
|
|
550
566
|
: html ``;
|
|
551
567
|
}
|
|
552
568
|
const repair = profile.repairModel === "inherit" ? "same model" : profile.repairModel;
|
|
@@ -559,7 +575,7 @@ export function profileWords(scope) {
|
|
|
559
575
|
: profile.provider === "gemini"
|
|
560
576
|
? 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
577
|
: 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
|
|
578
|
+
? 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
579
|
: 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
580
|
// What the build may reach is part of what is approved (egress.ts): said on every approval form.
|
|
565
581
|
// Its sandbox terms (project-sandbox.ts): what it reads always, and any widening, part of what the yes agrees to.
|
|
@@ -748,7 +764,7 @@ export function agentsCardHtml(taskId, view, canEdit) {
|
|
|
748
764
|
routeEvidenceHtml(view),
|
|
749
765
|
p === null
|
|
750
766
|
? view.kind === "unreadable"
|
|
751
|
-
? html `<p class="agents-halted">Nothing runs for this task until its
|
|
767
|
+
? html `<p class="agents-halted">Nothing runs for this task until its plan is saved again and approved.</p>`
|
|
752
768
|
: ""
|
|
753
769
|
: joinHtml([
|
|
754
770
|
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 +810,7 @@ export function approvalProofWords(proof) {
|
|
|
794
810
|
if (proof.method === "chat")
|
|
795
811
|
return " (from your paired chat)";
|
|
796
812
|
if (proof.method === "carried")
|
|
797
|
-
return " (
|
|
813
|
+
return " (from the release approval)";
|
|
798
814
|
// Easy-start C5: this computer's owner approving attended work in local mode, with no password.
|
|
799
815
|
if (proof.method === "local-owner")
|
|
800
816
|
return " (on this computer, no password)";
|
|
@@ -807,11 +823,11 @@ export function approveRefusalWords(reason) {
|
|
|
807
823
|
if (reason === "machine-proof")
|
|
808
824
|
return "not approved: only a person can approve this. Approve it on the task page or from your paired chat";
|
|
809
825
|
if (reason === "archived")
|
|
810
|
-
return "not approved: this task is
|
|
826
|
+
return "not approved: this task is archived. Restore it first";
|
|
811
827
|
if (reason === "unrouted")
|
|
812
|
-
return "not approved: this
|
|
828
|
+
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
829
|
if (reason === "profile-unresolved")
|
|
814
|
-
return "not approved: the
|
|
830
|
+
return "not approved: the plan can't name an exact agent for every role. Fix the agent setup, then save the plan again";
|
|
815
831
|
if (reason === "password-now")
|
|
816
832
|
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
833
|
// A wrong password, someone who isn't an approver (or not for this project), a revoked login: one answer for all, so
|
|
@@ -941,7 +957,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
941
957
|
return html ``;
|
|
942
958
|
const guarded = csrf !== "" && !inert;
|
|
943
959
|
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" ? "
|
|
960
|
+
const role = (word) => word.role === "planner" ? "plan attempt" : word.role === "scout" ? "research attempt" : word.role === "reviewer" ? "review" : "build";
|
|
945
961
|
if (control.kind === "stop" && surface === "chat") {
|
|
946
962
|
return joinHtml([
|
|
947
963
|
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 +969,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
953
969
|
if (control.kind === "stop") {
|
|
954
970
|
return joinHtml([
|
|
955
971
|
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,
|
|
972
|
+
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
973
|
stopElsewhere || !guarded || livePause === null || livePause.run !== control.run ? "" : pauseFormsHtml(taskId, livePause),
|
|
958
974
|
stopElsewhere ? "" : guarded
|
|
959
975
|
? runForm("stop", "inline task-control-form task-stop-form", html `<button type="submit" class="danger task-control-button">Stop</button>`)
|
|
@@ -974,7 +990,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
974
990
|
const settled = control.stop.settledAt === null ? "" : ` · settled ${stopWhen(control.stop.settledAt)} (${control.stop.settlement ?? "?"})`;
|
|
975
991
|
const kept = control.stop.settlement === "finished"
|
|
976
992
|
? 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
|
|
993
|
+
: 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
994
|
return joinHtml([
|
|
979
995
|
html `<section class="card task-control" id="task-control" data-task-control="paused" data-control-run="${control.run}" aria-label="paused attempt">`,
|
|
980
996
|
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 +1007,19 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
|
|
|
991
1007
|
* does and does not do, the gate that would still keep work from
|
|
992
1008
|
* starting, and the password typed again. */
|
|
993
1009
|
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
1010
|
const control = data.control;
|
|
1011
|
+
// One card, one button (docs/plans/plain-toolroll.md P1.3): the same post, nonce, run and password rule as before.
|
|
1012
|
+
const blocked = !data.approved ? "The plan isn't approved as it stands, so nothing builds until it is approved again."
|
|
1013
|
+
: data.gate === null ? null : `${data.gate.summary}: ${data.gate.detail}`;
|
|
996
1014
|
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>`,
|
|
1015
|
+
html `<h1>Resume build #${control.run}?</h1>`,
|
|
1006
1016
|
postForm(`${taskHref(data.taskId)}/resume`, joinHtml([
|
|
1007
|
-
html
|
|
1008
|
-
html `<
|
|
1017
|
+
html `<p class="resume-target"><strong>${data.taskTitle}</strong></p>`,
|
|
1018
|
+
html `<p class="row">It continues from the saved work${control.committed ? " and its commit" : ""}. Resuming approves nothing new.</p>`,
|
|
1019
|
+
blocked === null ? "" : html `<p class="row problem-words">${blocked}</p>`,
|
|
1020
|
+
html `${reauth(html `<label>Your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
1021
|
+
html `<button type="submit">Resume</button>`,
|
|
1009
1022
|
], "\n"), { attrs: { class: "card resume-form" }, returnTo: data.returnTo, hidden: { nonce: data.nonceValue, run: control.run } }),
|
|
1010
|
-
back,
|
|
1011
1023
|
], "\n"), { chrome });
|
|
1012
1024
|
}
|
|
1013
1025
|
export function taskViewSwitch(taskId, active) {
|
|
@@ -1041,7 +1053,7 @@ export function statusActionHref(status, taskId, runId, prUrl, repo) {
|
|
|
1041
1053
|
/** A not-yet-finished task's shared headline from its dispatch diagnosis (task-status.ts). */
|
|
1042
1054
|
export function dispatchHeadline(d) {
|
|
1043
1055
|
const read = stageOfDispatch(d);
|
|
1044
|
-
return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }) }).headline;
|
|
1056
|
+
return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }), ...(read.wait === undefined ? {} : { wait: read.wait }) }).headline;
|
|
1045
1057
|
}
|
|
1046
1058
|
export function statusLineHtml(status, extra = "") {
|
|
1047
1059
|
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 +1181,7 @@ export function planRevisionLedgerHtml(ledger, taskId, csrf) {
|
|
|
1169
1181
|
: joinHtml([
|
|
1170
1182
|
html `<div class="plan-revision-pending"><span class="eyebrow">decision needed</span><h3>The agent recommends a plan change</h3>`,
|
|
1171
1183
|
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 ? "" : `
|
|
1184
|
+
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
1185
|
executionPlanHtml(pendingRevision.document, true),
|
|
1174
1186
|
csrf === "" || pendingRevision.id === null
|
|
1175
1187
|
? ""
|
|
@@ -1211,7 +1223,7 @@ export function executionPlanHtml(document, compact = false) {
|
|
|
1211
1223
|
html `<div class="execution-milestones"><span class="approval-label">milestones</span>${items(plan.milestones, true)}</div>`,
|
|
1212
1224
|
html `<div class="execution-support"><div><span class="approval-label">dependencies</span>${items(plan.dependencies)}</div>`,
|
|
1213
1225
|
html `<div><span class="approval-label">risks & mitigations</span>${items(plan.risks)}</div></div>`,
|
|
1214
|
-
html `<div class="execution-proof"><span class="approval-label">
|
|
1226
|
+
html `<div class="execution-proof"><span class="approval-label">how we'll know it's done</span>${items(plan.proof)}</div>`,
|
|
1215
1227
|
html `</div>`,
|
|
1216
1228
|
]);
|
|
1217
1229
|
}
|
|
@@ -1234,7 +1246,7 @@ export function revisionLineageWords(lineage) {
|
|
|
1234
1246
|
const chain = lineage.ancestors.length > 1 ? ` · lineage ${lineage.ancestors.join(" → ")}` : "";
|
|
1235
1247
|
words.push(`revises ${lineage.sourceTask}${lineage.sourceRun === null ? "" : ` (build #${lineage.sourceRun})`}${chain}`);
|
|
1236
1248
|
if (!lineage.sourceHadScope) {
|
|
1237
|
-
words.push("the
|
|
1249
|
+
words.push("the earlier version had no plan, so nothing was copied; the placeholder requirements wait for real ones");
|
|
1238
1250
|
}
|
|
1239
1251
|
else if (lineage.terms !== null) {
|
|
1240
1252
|
const terms = lineage.terms;
|
|
@@ -1318,7 +1330,7 @@ export function taskBodyParts(data) {
|
|
|
1318
1330
|
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
1331
|
const transcript = liveRun.provider !== "claude"
|
|
1320
1332
|
? 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
|
|
1333
|
+
: 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
1334
|
return html `<div class="card attempt-live" data-live-run="${liveRun.id}">${head}${peek}${transcript}${door}</div>`;
|
|
1323
1335
|
})();
|
|
1324
1336
|
// One truthful answer to the first question on a queued task: "will this
|
|
@@ -1338,9 +1350,9 @@ export function taskBodyParts(data) {
|
|
|
1338
1350
|
if (task.state !== "done") {
|
|
1339
1351
|
const diagnosis = data.dispatch ?? null;
|
|
1340
1352
|
if (diagnosis === null)
|
|
1341
|
-
return box("problem", "
|
|
1353
|
+
return box("problem", "Can't tell yet", "Reload this task to see whether it can start.", "unknown");
|
|
1342
1354
|
if (diagnosis.code === "running" && liveRun !== undefined) {
|
|
1343
|
-
return box("ok", diagnosis.summary, html `
|
|
1355
|
+
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
1356
|
}
|
|
1345
1357
|
const blocker = diagnosis.blockerTaskId === null
|
|
1346
1358
|
? null
|
|
@@ -1348,9 +1360,9 @@ export function taskBodyParts(data) {
|
|
|
1348
1360
|
const action = (() => {
|
|
1349
1361
|
switch (diagnosis.action) {
|
|
1350
1362
|
case "start-worker": return "";
|
|
1351
|
-
case "write-scope": return html ` <a href="#scope">Write the
|
|
1363
|
+
case "write-scope": return html ` <a href="#scope">Write the plan</a> or use <strong>plan first</strong>.`;
|
|
1352
1364
|
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
|
|
1365
|
+
case "approve-scope": return html ` <a href="#approve">Review the plan and approve it</a>.`;
|
|
1354
1366
|
case "answer-decision": return html ` <a href="#decisions">Answer the waiting question</a>.`;
|
|
1355
1367
|
case "unhold": return html ` Use <strong>Remove hold</strong> when it can continue.`;
|
|
1356
1368
|
// The reason is the detail itself; Retry is the status card's own act, said once.
|
|
@@ -1463,7 +1475,7 @@ export function taskBodyParts(data) {
|
|
|
1463
1475
|
// c2" — never pretending the machine always disagreed.
|
|
1464
1476
|
const machineNote = !reviewConflict(proof.proofMatrix, proof.machineVerdict, proof.proofVerdict)
|
|
1465
1477
|
? ""
|
|
1466
|
-
: html `<p class="meta">An independent review found
|
|
1478
|
+
: html `<p class="meta">An independent review found the report doesn't match the changes.</p>`;
|
|
1467
1479
|
const chainHtml = repairChainHtml(proof.repairChain);
|
|
1468
1480
|
// v51: semantic coverage sits beside the matrix, never inside the
|
|
1469
1481
|
// verdict word — what the independent reviewer settled, under the
|
|
@@ -1480,7 +1492,7 @@ export function taskBodyParts(data) {
|
|
|
1480
1492
|
// Damaged evidence (repair 2026-09-14): the box wears the receipt's
|
|
1481
1493
|
// own problem words — never "ok" with a verdict that predates the damage.
|
|
1482
1494
|
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
|
|
1495
|
+
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
1496
|
}
|
|
1485
1497
|
// A recorded pass the check read now doesn't stand behind (unreadable, failed since, or not read here) never
|
|
1486
1498
|
// reads "passed": the box says what holds now (task-status.ts approvedCheckOf).
|
|
@@ -1501,7 +1513,7 @@ export function taskBodyParts(data) {
|
|
|
1501
1513
|
const machineToken = proof.proofVerdict === "refuted" ? dispatchStatusToken("refuted") : dispatchStatusToken("short");
|
|
1502
1514
|
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
1515
|
}
|
|
1504
|
-
return box("problem", "
|
|
1516
|
+
return box("problem", "Can't tell yet", "Reload this task to see whether it can start.", "unknown");
|
|
1505
1517
|
})();
|
|
1506
1518
|
// Operator steering (arc 1, v120): notes for the agent, each wearing the
|
|
1507
1519
|
// same words the CLI and the lead use — read (and when), waiting for the
|
|
@@ -1517,7 +1529,7 @@ export function taskBodyParts(data) {
|
|
|
1517
1529
|
? ""
|
|
1518
1530
|
: joinHtml([
|
|
1519
1531
|
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
|
|
1532
|
+
html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved plan.</p>`,
|
|
1521
1533
|
]);
|
|
1522
1534
|
const steeringCard = steerRows === "" && steerForm === "" ? "" : html `<h2>Steering</h2>${steerRows}${steerForm}`;
|
|
1523
1535
|
const holds = data.holds.length === 0
|
|
@@ -1525,7 +1537,7 @@ export function taskBodyParts(data) {
|
|
|
1525
1537
|
: joinHtml([
|
|
1526
1538
|
html `<h2>Holds</h2>`,
|
|
1527
1539
|
joinHtml(data.holds.map(hold => html `<p class="row">${holdOwnerWords(hold.ownerKind)} — ${hold.reason}${hold.until === null ? "" : html ` <span class="meta">until ${whenTime(hold.until)}</span>`}</p>`), "\n"),
|
|
1528
|
-
html `<p class="meta">Only your hold can be lifted here
|
|
1540
|
+
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
1541
|
]);
|
|
1530
1542
|
const approval = approvalOf(scope);
|
|
1531
1543
|
// A refused save from the sheet's in-place editor reopens that editor with
|
|
@@ -1533,22 +1545,22 @@ export function taskBodyParts(data) {
|
|
|
1533
1545
|
const inlineScopeDraft = data.scopeDraft?.has("requirement-new") === true;
|
|
1534
1546
|
const scopeDraft = inlineScopeDraft ? undefined : data.scopeDraft;
|
|
1535
1547
|
const scopeCard = scope === null
|
|
1536
|
-
? html `<p class="meta">No
|
|
1548
|
+
? html `<p class="meta">No plan yet. Nothing builds until one is approved.</p>`
|
|
1537
1549
|
: joinHtml([
|
|
1538
1550
|
html `<div class="card">`,
|
|
1539
1551
|
html `<p><strong>Goal</strong></p><p class="recap">${scope.goal}</p>`,
|
|
1540
1552
|
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>
|
|
1553
|
+
scope.touches.length === 0 ? "" : html `<p class="scope-paths"><strong>Files</strong> ${scope.touches.join(", ")}</p>`,
|
|
1542
1554
|
html `<p><strong>Quality</strong> ${qualityModeTitle(scope.qualityMode ?? "default")}</p>`,
|
|
1543
1555
|
acceptanceCeremonyHtml(scope.acceptance),
|
|
1544
1556
|
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
1557
|
approval.approved && data.autoRelease != null
|
|
1546
|
-
? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)}
|
|
1558
|
+
? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)}</p>${planVersionDetailsHtml(scope.digest)}`
|
|
1547
1559
|
: approval.approved && data.release?.kind === "carried"
|
|
1548
|
-
? html `<p class="meta scope-seal">
|
|
1560
|
+
? 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
1561
|
: approval.approved
|
|
1550
|
-
? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}
|
|
1551
|
-
: html `<p class="meta"
|
|
1562
|
+
? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}</p>${planVersionDetailsHtml(scope.digest)}`
|
|
1563
|
+
: html `<p class="meta">${approval.reason === "changed" ? "Edited after approval" : "Not approved"}</p>${planVersionDetailsHtml(scope.digest)}`,
|
|
1552
1564
|
html `</div>`,
|
|
1553
1565
|
], "\n");
|
|
1554
1566
|
// The scout's report (mate arc §10): title, summary, the document inert,
|
|
@@ -1557,20 +1569,20 @@ export function taskBodyParts(data) {
|
|
|
1557
1569
|
// blank — the same rule as the revision brief.
|
|
1558
1570
|
const reportCard = data.report === null || data.report === undefined
|
|
1559
1571
|
? data.deliverable === "report"
|
|
1560
|
-
? html `<div class="card"><p><strong>
|
|
1572
|
+
? 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
1573
|
: ""
|
|
1562
1574
|
: !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}">
|
|
1575
|
+
? 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
1576
|
: joinHtml([
|
|
1565
1577
|
html `<div class="card report">`,
|
|
1566
|
-
html `<p><strong>${data.report.report.title}</strong> <span class="meta">the
|
|
1578
|
+
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
1579
|
html `<p class="report-summary">${data.report.report.summary}</p>`,
|
|
1568
1580
|
reportItemsHtml(data.report.report.items, data.report.shots, data.report.run),
|
|
1569
1581
|
html `<pre class="recap plan-doc">${data.report.report.report}</pre>`,
|
|
1570
1582
|
...(data.report.report.followUps.length === 0
|
|
1571
1583
|
? []
|
|
1572
1584
|
: [
|
|
1573
|
-
html `<p><strong>Follow-ups the
|
|
1585
|
+
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
1586
|
...data.report.report.followUps.map((one, index) => joinHtml([
|
|
1575
1587
|
html `<div class="follow-up"><p><strong>${one.title}</strong></p><p class="meta">${one.goal}</p>`,
|
|
1576
1588
|
data.csrf === "" || data.repo === null
|
|
@@ -1615,7 +1627,7 @@ export function taskBodyParts(data) {
|
|
|
1615
1627
|
: joinHtml([
|
|
1616
1628
|
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
1629
|
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
|
|
1630
|
+
approval.approved ? html `<p class="meta">The agent can adapt this route as it learns more; your approved outcome stays fixed.</p>` : "",
|
|
1619
1631
|
html `</div><span class="plan-lock">${planStanding}</span>${approval.approved ? html `</summary><div class="planner-plan-body">` : html `</div>`}`,
|
|
1620
1632
|
executionPlanHtml(displayedPlanDocument ?? data.planDocument),
|
|
1621
1633
|
approval.approved || sheetStatesContract ? "" : planContractHtml(data.planContract ?? null, "full"),
|
|
@@ -1683,7 +1695,7 @@ export function taskBodyParts(data) {
|
|
|
1683
1695
|
deliverable: data.deliverable ?? "branch",
|
|
1684
1696
|
rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
|
|
1685
1697
|
editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
|
|
1686
|
-
notNowHref: "/
|
|
1698
|
+
notNowHref: "/tasks",
|
|
1687
1699
|
sticky: approvalLeads,
|
|
1688
1700
|
earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
|
|
1689
1701
|
edit: data.csrf === "" ? null
|
|
@@ -1693,21 +1705,21 @@ export function taskBodyParts(data) {
|
|
|
1693
1705
|
const approveForm = scope === null || approval.approved || data.plan === "requested"
|
|
1694
1706
|
? ""
|
|
1695
1707
|
: 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}
|
|
1708
|
+
? 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
1709
|
: scope.profileState === "unresolved"
|
|
1698
|
-
? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: its
|
|
1710
|
+
? 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
1711
|
: !consentDoor.open
|
|
1700
1712
|
? consentClosedHtml(task.id, consentDoor, "task")
|
|
1701
1713
|
: approvalSheetHtml(sheetInput);
|
|
1702
1714
|
const scopeForm = joinHtml([
|
|
1703
|
-
html `<details${scope === null || scopeDraft !== undefined ? html ` open` : ""}><summary>${scope === null ? "Write the
|
|
1715
|
+
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
1716
|
postForm(`${taskHref(task.id)}/scope`, joinHtml([
|
|
1705
1717
|
"",
|
|
1706
1718
|
scopeDraft === undefined || data.problem === null ? "" : html `<div class="problem" role="alert" id="scope-error">${data.problem}</div>`,
|
|
1707
1719
|
html `<label>Goal<textarea name="goal" rows="3"${scopeDraft === undefined ? "" : html ` autofocus aria-describedby="scope-error"`}>${scopeDraft?.get("goal") ?? scope?.goal ?? ""}</textarea></label>`,
|
|
1708
1720
|
html `<label>Not this<textarea name="not" rows="2">${scopeDraft?.get("not") ?? scope?.outOfScope ?? ""}</textarea></label>`,
|
|
1709
|
-
html `<label>
|
|
1710
|
-
html `<label>
|
|
1721
|
+
html `<label>Files <span class="meta">(one per line)</span><textarea name="touches" rows="2">${scopeDraft?.get("touches") ?? (scope?.touches ?? []).join("\n")}</textarea></label>`,
|
|
1722
|
+
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
1723
|
(() => {
|
|
1712
1724
|
const defaults = data.spendDefaults ?? null;
|
|
1713
1725
|
const budgetPrefill = scope?.budgetMicrousd != null
|
|
@@ -1726,13 +1738,13 @@ export function taskBodyParts(data) {
|
|
|
1726
1738
|
? scope.profile.sandboxMode === "danger-full-access" ? "bypassPermissions" : "auto"
|
|
1727
1739
|
: null;
|
|
1728
1740
|
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
|
|
1741
|
+
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
1742
|
})(),
|
|
1731
1743
|
(() => {
|
|
1732
1744
|
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
|
|
1745
|
+
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
1746
|
})(),
|
|
1735
|
-
html `<button type="submit">Save
|
|
1747
|
+
html `<button type="submit">Save plan</button>`,
|
|
1736
1748
|
"",
|
|
1737
1749
|
], "\n"), { attrs: { class: "scope-editor" }, hidden: { sawDigest: scopeDraft?.get("sawDigest") ?? scope?.digest ?? "" } }),
|
|
1738
1750
|
html `</details>`,
|
|
@@ -1835,46 +1847,46 @@ export function taskBodyParts(data) {
|
|
|
1835
1847
|
// the scope's standing with its seal, publication, spend — the key
|
|
1836
1848
|
// facts a reader scans before anything else, in one row grammar.
|
|
1837
1849
|
const workerRow = liveRun !== undefined
|
|
1838
|
-
? prop("
|
|
1850
|
+
? prop("builder", html `${liveRun.runner} · <a href="/r/${liveRun.id}">build #${liveRun.id}</a> running`)
|
|
1839
1851
|
: data.runs[0] !== undefined
|
|
1840
1852
|
? 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
1853
|
: "";
|
|
1842
1854
|
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="/
|
|
1855
|
+
? 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
1856
|
: "";
|
|
1845
1857
|
const scopeRow = scope === null
|
|
1846
|
-
? prop("
|
|
1858
|
+
? prop("plan", "none yet")
|
|
1847
1859
|
: approval.approved && data.autoRelease != null
|
|
1848
|
-
? prop("approved
|
|
1860
|
+
? prop("approved plan", html `${autoReleaseWords(data.autoRelease)}`)
|
|
1849
1861
|
: approval.approved && data.release?.kind === "carried"
|
|
1850
|
-
? prop("approved
|
|
1862
|
+
? 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
1863
|
: approval.approved
|
|
1852
|
-
? prop("approved
|
|
1853
|
-
: prop("
|
|
1854
|
-
const strikesRow = data.strikes > 0 ? prop("
|
|
1864
|
+
? prop("approved plan", html `Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)} · ${qualityModeTitle(scope.qualityMode ?? "default")}`)
|
|
1865
|
+
: prop("plan", approval.reason === "changed" ? "Edited after approval: it needs a new approval" : "not approved");
|
|
1866
|
+
const strikesRow = data.strikes > 0 ? prop("failed attempts", `${data.strikes} in a row`) : "";
|
|
1855
1867
|
const propsCard = html `<div class="card props">${workerRow}${queueRow}${scopeRow}${publishesRow}${economics}${strikesRow}</div>`;
|
|
1856
1868
|
// The same facts for the rebuilt page, row for row.
|
|
1857
1869
|
const facts = [];
|
|
1858
1870
|
const lastRun = data.runs[0];
|
|
1859
1871
|
if (liveRun !== undefined)
|
|
1860
|
-
facts.push({ label: "
|
|
1872
|
+
facts.push({ label: "Builder", parts: [`${liveRun.runner} · `, { label: `build #${liveRun.id}`, href: `/r/${liveRun.id}` }, " running"] });
|
|
1861
1873
|
else if (lastRun !== undefined)
|
|
1862
1874
|
facts.push({ label: "Last attempt", parts: [{ label: `${runNoun(lastRun)} #${lastRun.id}`, href: `/r/${lastRun.id}` },
|
|
1863
1875
|
` · ${lastRun.role === "planner" && lastRun.reason === "plan-drafted" ? "planned" : lastRun.reason === "interrupted" ? "interrupted" : lastRun.id === liveHistoryRunId ? "running" : lastRun.outcome ?? "never finished"} · ${lastRun.runner}`] });
|
|
1864
1876
|
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: "/
|
|
1877
|
+
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
1878
|
}
|
|
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: "
|
|
1879
|
+
facts.push(scope === null ? { label: "Plan", parts: ["none yet"] }
|
|
1880
|
+
: approval.approved && data.autoRelease != null ? { label: "Approved plan", parts: [autoReleaseWords(data.autoRelease)] }
|
|
1881
|
+
: 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) }] }
|
|
1882
|
+
: approval.approved ? { label: "Approved plan", parts: [`Approved by ${approval.by}${approvalProofWords(data.approvalProof)}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }]), ` · ${qualityModeTitle(scope.qualityMode ?? "default")}`] }
|
|
1883
|
+
: { label: "Plan", parts: [approval.reason === "changed" ? "Edited after approval: it needs a new approval" : "not approved"] });
|
|
1872
1884
|
if (data.release?.kind === "anchor")
|
|
1873
1885
|
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
1886
|
facts.push({ label: "Publishes as", parts: [publishesAs] });
|
|
1875
1887
|
// v102: who asked for it, and what the project's approval rules need.
|
|
1876
1888
|
if (data.filer != null)
|
|
1877
|
-
facts.push({ label: "Filed by", parts: [data.filer.name === null ? "automation" : data.filer.kind === "coordinator" ? `${data.filer.name}, through
|
|
1889
|
+
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
1890
|
if (data.approvalRules != null) {
|
|
1879
1891
|
const votes = data.approvalRules.votes;
|
|
1880
1892
|
facts.push({ label: "Approval rules", parts: [data.approvalRules.needsTwo && !approval.approved
|
|
@@ -1882,7 +1894,7 @@ export function taskBodyParts(data) {
|
|
|
1882
1894
|
: data.approvalRules.words] });
|
|
1883
1895
|
}
|
|
1884
1896
|
// v103: everything an auditor asks about this task, printable or as JSON.
|
|
1885
|
-
facts.push({ label: "Audit", parts: [{ label: "
|
|
1897
|
+
facts.push({ label: "Audit", parts: [{ label: "Audit record", href: `${taskHref(task.id)}/evidence` }] });
|
|
1886
1898
|
if (data.budgetHold != null)
|
|
1887
1899
|
facts.push({ label: "Budget", parts: [`${data.budgetHold} · `, { label: "Spend", href: "/spend" }] });
|
|
1888
1900
|
if (data.policyHold != null)
|
|
@@ -1967,7 +1979,7 @@ export function taskBodyParts(data) {
|
|
|
1967
1979
|
: 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
1980
|
const waitsForCard = waitRows === "" && waitAdd === ""
|
|
1969
1981
|
? ""
|
|
1970
|
-
: html `<h2>Waits for</h2>${waitRows === "" ? html `<p class="meta">Nothing
|
|
1982
|
+
: html `<h2>Waits for</h2>${waitRows === "" ? html `<p class="meta">Nothing. It starts when a builder is free.</p>` : waitRows}${waitAdd}`;
|
|
1971
1983
|
// The acts bar (task page pass): every verb in one row under the title,
|
|
1972
1984
|
// the one that resolves this task's state first and primary — retry on a
|
|
1973
1985
|
// stalled task, build-next in the queue, plan-first with no scope. The
|
|
@@ -1979,13 +1991,13 @@ export function taskBodyParts(data) {
|
|
|
1979
1991
|
// A failed task's status card carries Retry itself (the same requeue); here it would be a second ink act.
|
|
1980
1992
|
const cardRetries = task.state === "failed" && data.assignment != null && data.csrf !== "";
|
|
1981
1993
|
const primaryAct = stopControlsActive || cardRetries ? null : stalled && !data.claimed
|
|
1982
|
-
? { html: act("requeue", "Retry, keeping the branch and workspace"), why: "
|
|
1994
|
+
? { 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
1995
|
: canPlan
|
|
1984
|
-
? { html: act("plan", "plan first"), why: "plan first sends an agent to read the
|
|
1996
|
+
? { 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
1997
|
: data.plan === "requested"
|
|
1986
1998
|
? null
|
|
1987
1999
|
: 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
|
|
2000
|
+
? { 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
2001
|
: null;
|
|
1990
2002
|
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
2003
|
const canHold = task.state === "queued" || task.state === "running" || task.state === "failed";
|
|
@@ -2039,7 +2051,7 @@ export function taskBodyParts(data) {
|
|
|
2039
2051
|
? ` · ${originWords(data.origin)}`
|
|
2040
2052
|
: data.filedVia === null || data.filedVia === undefined
|
|
2041
2053
|
? ""
|
|
2042
|
-
: ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">
|
|
2054
|
+
: ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Research</span>` : ""}</p>`;
|
|
2043
2055
|
// A pull request that can be merged (or opened) owns the one primary action; the result stays in its section.
|
|
2044
2056
|
const pullRequestActs = data.csrf !== "" && (data.pullRequest?.view?.canMerge === true || (data.pullRequest?.view == null && data.pullRequest?.offer != null));
|
|
2045
2057
|
const pullRequestFact = data.pullRequest?.fact != null && data.pullRequest.view?.runId === data.assignment?.receipt?.runId ? data.pullRequest.fact : undefined;
|
|
@@ -2083,7 +2095,7 @@ export function taskBodyParts(data) {
|
|
|
2083
2095
|
return joinHtml([
|
|
2084
2096
|
html `<div class="card"><p><strong>External work</strong> <span class="meta">${link} · ${state}</span></p>`,
|
|
2085
2097
|
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
|
|
2098
|
+
? 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
2099
|
: "",
|
|
2088
2100
|
html `</div>`,
|
|
2089
2101
|
]);
|
|
@@ -2097,8 +2109,8 @@ export function taskBodyParts(data) {
|
|
|
2097
2109
|
// The quarantine speaks here too (round-2 finding 5): the planner
|
|
2098
2110
|
// is as fenced as the builder on a coordinator filing, so "plan
|
|
2099
2111
|
// 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
|
|
2112
|
+
? 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>`
|
|
2113
|
+
: 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
2114
|
: "";
|
|
2103
2115
|
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : approvalLeads
|
|
2104
2116
|
? html `<section class="task-plan-review" aria-label="Approve the plan">${approveForm}</section>`
|
|
@@ -2135,9 +2147,9 @@ export function taskBodyParts(data) {
|
|
|
2135
2147
|
? ""
|
|
2136
2148
|
: section("proposals",
|
|
2137
2149
|
// Confirm and dismiss come back to this task.
|
|
2138
|
-
html `<h2>Proposed by
|
|
2150
|
+
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
2151
|
section("decisions", decisions, true, data.decisions.length),
|
|
2140
|
-
section("
|
|
2152
|
+
section("repeated failures", incidents, true, data.incidents.length, "incidents"),
|
|
2141
2153
|
data.publication === null || data.publication === undefined
|
|
2142
2154
|
? ""
|
|
2143
2155
|
: 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 +2159,10 @@ export function taskBodyParts(data) {
|
|
|
2147
2159
|
data.assignment == null ? section("attempts", runs, true, data.runs.length) : section("Build activity", data.runs.length === 0 ? "" : runRows, false, data.runs.length, "attempts"),
|
|
2148
2160
|
section("usage", spendCard, false),
|
|
2149
2161
|
section("steering", steeringCard, (data.steering ?? []).length > 0, (data.steering ?? []).length),
|
|
2150
|
-
section("
|
|
2162
|
+
section("plan",
|
|
2151
2163
|
// The recipe road rides with the scope it reuses (UI polish
|
|
2152
2164
|
// 2026-09-13), off the title-to-action path.
|
|
2153
|
-
joinHtml([html `<h2>
|
|
2165
|
+
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
2166
|
dependencyChoiceNeeded ? "" : section("waits for", waitsForCard, (data.waitsFor ?? []).length > 0, (data.waitsFor ?? []).length),
|
|
2155
2167
|
section("holds", holds, true, data.holds.length),
|
|
2156
2168
|
cancelAct,
|
|
@@ -2263,7 +2275,7 @@ export function taskBodyParts(data) {
|
|
|
2263
2275
|
: data.route?.legacy != null ? `${data.route.legacy.provider} · ${data.route.legacy.model}` : "The project's default"] });
|
|
2264
2276
|
if (scope !== null)
|
|
2265
2277
|
work.push({ label: "Checks level", parts: [qualityModeTitle(scope.qualityMode ?? "default")] });
|
|
2266
|
-
for (const label of ["
|
|
2278
|
+
for (const label of ["Builder", "Last attempt", "Queue", "Budget", "Policy", "Strikes"]) {
|
|
2267
2279
|
const fact = facts.find(one => one.label === label);
|
|
2268
2280
|
if (fact !== undefined)
|
|
2269
2281
|
work.push(fact);
|
|
@@ -2286,9 +2298,9 @@ export function taskBodyParts(data) {
|
|
|
2286
2298
|
links.push({ label: "Pull request", parts: [prHref === null ? `#${pr.prNumber ?? "?"}` : { label: `#${pr.prNumber ?? "?"}`, href: prHref }, ` · ${pr.label}`] });
|
|
2287
2299
|
}
|
|
2288
2300
|
const review = [];
|
|
2289
|
-
const approvedScope = facts.find(one => one.label === "Approved
|
|
2301
|
+
const approvedScope = facts.find(one => one.label === "Approved plan" || one.label === "Plan");
|
|
2290
2302
|
if (approvedScope !== undefined)
|
|
2291
|
-
review.push({ ...approvedScope, label: approvedScope.label === "Approved
|
|
2303
|
+
review.push({ ...approvedScope, label: approvedScope.label === "Approved plan" ? "Approvals" : "Plan" });
|
|
2292
2304
|
review.push({ label: "Who reviews", parts: [facts.find(one => one.label === "Approval rules")?.parts[0] ?? "Any approver in this project"] });
|
|
2293
2305
|
review.push({ label: "Publishes as", parts: [publishesAs] });
|
|
2294
2306
|
if (data.resultRecord != null)
|
|
@@ -2367,10 +2379,8 @@ export function taskBodyParts(data) {
|
|
|
2367
2379
|
...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
|
|
2368
2380
|
...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
|
|
2369
2381
|
...(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: "
|
|
2382
|
+
...(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
2383
|
{ 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
2384
|
...(cancelForm !== "" ? [{ group: "danger", label: "Cancel task", kind: "goto", tab: "plan", target: "cancel-task" }] : []),
|
|
2375
2385
|
];
|
|
2376
2386
|
const view = {
|
|
@@ -2408,12 +2418,15 @@ export function taskBodyParts(data) {
|
|
|
2408
2418
|
approve: data.archived != null || sheetInput === null || dependencyChoiceNeeded || data.csrf === "" ? null : approveKitOf(sheetInput, data.approvalFresh ?? null),
|
|
2409
2419
|
approved: scope !== null && approval.approved ? approvedKitOf(scope, approval, data.approvalProof ?? null) : null,
|
|
2410
2420
|
more,
|
|
2411
|
-
|
|
2412
|
-
|
|
2421
|
+
audit: facts.some(one => one.label === "Audit") ? { label: "Full record", href: `${taskHref(rootOf)}/evidence` } : null,
|
|
2422
|
+
recheckStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "recheck-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
2423
|
+
? { action: `${taskHref(data.assignment.rootId)}/recheck-stopped`, run: data.assignment.primaryAction.target.runId } : null,
|
|
2413
2424
|
rebuild,
|
|
2414
2425
|
// The plan, progress and plan changes are thread entries now; the rest still needs a person here.
|
|
2415
2426
|
lead: [
|
|
2416
2427
|
{ 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) },
|
|
2428
|
+
{ key: "route-change", html: data.routeChange == null ? "" : htmlString(html `<div class="card"><p>${data.routeChange}</p></div>`) },
|
|
2429
|
+
{ 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
2430
|
{ key: "pull-request", html: fragmentString(pullRequestCard) },
|
|
2418
2431
|
{ key: "needs-scope", html: fragmentString(needsScopeCard) },
|
|
2419
2432
|
{ key: "mirror", html: fragmentString(mirrorCard) }, { key: "attempt", html: fragmentString(attemptPanel) },
|
|
@@ -2541,7 +2554,7 @@ export function releaseCardHtml(taskId, release, canStop) {
|
|
|
2541
2554
|
if (release === null)
|
|
2542
2555
|
return html ``;
|
|
2543
2556
|
if (release.kind === "carried")
|
|
2544
|
-
return html `<div class="card" id="release"><p><strong>
|
|
2557
|
+
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
2558
|
const until = new Intl.DateTimeFormat("en-US", { weekday: "short", hour: "numeric", minute: "2-digit" }).format(new Date(release.expiresAt));
|
|
2546
2559
|
const landed = release.landed.length === 0 ? html `<p class="meta">No later commits yet.</p>`
|
|
2547
2560
|
: 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 +2566,7 @@ export function releaseCardHtml(taskId, release, canStop) {
|
|
|
2553
2566
|
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
2567
|
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
2568
|
...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">
|
|
2569
|
+
release.carrying && canStop ? postForm(`${taskHref(taskId)}/stop-carrying`, html `<button type="submit" class="secondary">Ask again for later commits</button>`, { attrs: { class: "inline" } }) : "",
|
|
2557
2570
|
html `</div>`,
|
|
2558
2571
|
], "\n");
|
|
2559
2572
|
}
|
|
@@ -2571,7 +2584,7 @@ export function approveKitOf(input, freshSince) {
|
|
|
2571
2584
|
fields: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
|
|
2572
2585
|
label: parts.label,
|
|
2573
2586
|
heading: rebuild !== null ? "The plan changed" : input.deliverable === "report" ? "Investigation ready" : "Plan ready",
|
|
2574
|
-
text: `Approving
|
|
2587
|
+
text: `Approving ${after}.${allowing}`,
|
|
2575
2588
|
kind: "standard",
|
|
2576
2589
|
facts: { passwordless: passwordlessRendering() || input.localOwner?.eligible === true, remembered: until !== null },
|
|
2577
2590
|
rememberedUntil: until,
|
|
@@ -2586,7 +2599,11 @@ export function approvedKitOf(scope, approval, proof) {
|
|
|
2586
2599
|
return null;
|
|
2587
2600
|
const basis = scope.approvalBasis ?? null;
|
|
2588
2601
|
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
|
|
2602
|
+
: 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
2603
|
const how = approvalProofWords(proof).replace(/^ \(|\)$/g, "");
|
|
2591
2604
|
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
2605
|
}
|
|
2606
|
+
/** The plan version, in Details: a short handle a person can compare, never on the approval line itself. */
|
|
2607
|
+
export function planVersionDetailsHtml(digest) {
|
|
2608
|
+
return html `<details class="scope-version"><summary class="meta">Details</summary><p class="meta"><span class="mono">${planVersionWords(digest)}</span></p></details>`;
|
|
2609
|
+
}
|