toolroll 0.9.93 → 0.9.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +10 -0
- package/dist/agent-fence.js +6 -3
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.js +5 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.js +10 -0
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +32 -32
- package/dist/browser-crew.js +2 -0
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +50 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/build-review.js +2 -0
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +11 -2
- package/dist/builder.js +234 -101
- package/dist/card-catalog.d.ts +209 -0
- package/dist/card-catalog.js +133 -0
- package/dist/card-cli.d.ts +43 -0
- package/dist/card-cli.js +44 -0
- package/dist/card-render.d.ts +26 -0
- package/dist/card-render.js +223 -0
- package/dist/card-sources.d.ts +68 -0
- package/dist/card-sources.js +234 -0
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +106 -9
- package/dist/chat-actions.js +473 -46
- package/dist/chat-approval.d.ts +13 -0
- package/dist/chat-approval.js +39 -9
- package/dist/chat-card-part.d.ts +11 -0
- package/dist/chat-card-part.js +18 -0
- package/dist/chat-card.d.ts +46 -0
- package/dist/chat-card.js +99 -0
- package/dist/chat-channel.d.ts +2 -0
- package/dist/chat-channel.js +37 -14
- package/dist/chat-controls.d.ts +4 -0
- package/dist/chat-controls.js +4 -0
- package/dist/chat-decide.d.ts +10 -16
- package/dist/chat-decide.js +79 -62
- package/dist/chat-delivery.js +13 -4
- package/dist/chat-flow.d.ts +0 -27
- package/dist/chat-flow.js +31 -2
- package/dist/chat-quiet.js +4 -4
- package/dist/chat-task-actions.d.ts +3 -2
- package/dist/chat-task-actions.js +19 -5
- package/dist/chat-terms.d.ts +14 -2
- package/dist/chat-terms.js +35 -0
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +3 -1
- package/dist/check-facts.js +57 -11
- package/dist/check-failure.d.ts +24 -3
- package/dist/check-failure.js +89 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-repair.d.ts +47 -0
- package/dist/check-repair.js +240 -0
- package/dist/check-report.d.ts +2 -0
- package/dist/check-report.js +1 -1
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/claim.d.ts +6 -0
- package/dist/claim.js +13 -2
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +49 -30
- package/dist/cli.js +15 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +3 -1
- package/dist/coding-provider.js +12 -7
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.d.ts +1 -1
- package/dist/coding-workspace.js +8 -8
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +92 -0
- package/dist/contracts/chat-actions.js +20 -0
- package/dist/contracts/chat-card.d.ts +91 -0
- package/dist/contracts/chat-card.js +54 -0
- package/dist/contracts/chat-content.d.ts +191 -0
- package/dist/contracts/chat-content.js +4 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -3
- package/dist/contracts/console-api.js +12 -3
- package/dist/contracts/flow-propose.d.ts +8 -1
- package/dist/contracts/flow-propose.js +3 -1
- package/dist/contracts/flow-send.d.ts +126 -0
- package/dist/contracts/flow-send.js +3 -0
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +234 -0
- package/dist/contracts/lead-tools.js +25 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +18 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/discord-chat.js +14 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +25 -16
- package/dist/dispose.d.ts +19 -0
- package/dist/dispose.js +55 -9
- package/dist/easy-start.js +2 -3
- package/dist/eval-oracle.js +4 -1
- package/dist/evals.js +2 -1
- package/dist/evidence-pair.d.ts +53 -0
- package/dist/evidence-pair.js +128 -0
- package/dist/exhaustion.d.ts +17 -4
- package/dist/exhaustion.js +44 -4
- package/dist/export.js +1 -1
- package/dist/failing-first.d.ts +31 -0
- package/dist/failing-first.js +44 -0
- package/dist/fallback-schema.d.ts +8 -0
- package/dist/fallback-schema.js +40 -0
- package/dist/fallback.d.ts +138 -0
- package/dist/fallback.js +605 -0
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/findings.d.ts +224 -0
- package/dist/findings.js +449 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +75 -9
- package/dist/flaky.js +255 -0
- package/dist/fleet-schema.d.ts +17 -0
- package/dist/fleet-schema.js +135 -0
- package/dist/flow-browser.js +2 -2
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +9 -5
- package/dist/flow-result-card.d.ts +22 -0
- package/dist/flow-result-card.js +55 -0
- package/dist/flow-send.js +12 -9
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +2 -2
- package/dist/flow-unattended.d.ts +26 -0
- package/dist/flow-unattended.js +12 -3
- package/dist/flows-ui.js +10 -2
- package/dist/follow-through-brief.d.ts +12 -0
- package/dist/follow-through-brief.js +31 -0
- package/dist/follow-through-cli.d.ts +23 -0
- package/dist/follow-through-cli.js +153 -0
- package/dist/git-incident.d.ts +16 -0
- package/dist/git-incident.js +69 -6
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/handover-note.d.ts +33 -0
- package/dist/handover-note.js +106 -0
- package/dist/invoke.d.ts +25 -2
- package/dist/invoke.js +157 -58
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-cli.js +26 -24
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -2
- package/dist/lead-doors.d.ts +4 -0
- package/dist/lead-doors.js +2 -2
- package/dist/lead-progress.js +5 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +162 -34
- package/dist/lead.js +2 -2
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +16 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/modes.d.ts +6 -6
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +31 -9
- package/dist/needs-you.js +110 -41
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +7 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +565 -64
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/plan-login.d.ts +14 -0
- package/dist/plan-login.js +58 -0
- package/dist/planner.d.ts +1 -1
- package/dist/planner.js +4 -2
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +19 -2
- package/dist/project-delete-ui.js +29 -1
- package/dist/project-delete.js +14 -1
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/project-tools.d.ts +4 -4
- package/dist/project-tools.js +23 -7
- package/dist/proof.js +30 -5
- package/dist/protected-storage-look.d.ts +1 -0
- package/dist/protected-storage-look.js +13 -0
- package/dist/protected-storage.d.ts +70 -39
- package/dist/protected-storage.js +196 -147
- package/dist/provider-auth.js +1 -1
- package/dist/provider-capture.d.ts +34 -0
- package/dist/provider-capture.js +53 -0
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +18 -2
- package/dist/push.js +40 -16
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +76 -0
- package/dist/release-auto.js +13 -13
- package/dist/release-card.d.ts +1 -2
- package/dist/release-card.js +11 -0
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +49 -10
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +29 -22
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +20 -1
- package/dist/release.js +85 -9
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +27 -19
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +28 -4
- package/dist/scope.js +92 -28
- package/dist/scout.d.ts +1 -1
- package/dist/scout.js +2 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +26 -18
- package/dist/server/chat.js +16 -14
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +73 -0
- package/dist/server/guards.js +3 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +16 -3
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-follow-through.d.ts +11 -0
- package/dist/server/render-follow-through.js +68 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +2 -0
- package/dist/server/render-release.js +3 -3
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +68 -68
- package/dist/server/render-tasks.d.ts +16 -2
- package/dist/server/render-tasks.js +177 -141
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +48 -12
- package/dist/server/settings.js +166 -34
- package/dist/server/task-views.js +66 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +250 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.d.ts +1 -0
- package/dist/settings-groups-ui.js +4 -2
- package/dist/settings-kit.js +3 -2
- package/dist/settings-proofs.js +3 -1
- package/dist/slack-chat.js +14 -1
- package/dist/steer-mailbox.d.ts +8 -1
- package/dist/steer-mailbox.js +164 -6
- package/dist/stop-proof.js +6 -4
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +89 -7
- package/dist/store.js +386 -114
- package/dist/surface.js +67 -6
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.d.ts +2 -1
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +17 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +45 -49
- package/dist/teardown-git.d.ts +18 -0
- package/dist/teardown-git.js +105 -21
- package/dist/telegram-lead.js +23 -6
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/telegram.js +12 -1
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update.js +19 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +95 -66
- package/dist/work-summary.d.ts +1 -1
- package/dist/work-summary.js +7 -2
- package/dist/workspace-ui.d.ts +7 -3
- package/dist/workspace-ui.js +38 -23
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +91 -14
- package/package.json +2 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/server/tasks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { titleWithin } from "../task-text.js";
|
|
2
2
|
import { parseFeedbackImage, sameFeedbackImage } from "../result-review.js";
|
|
3
|
-
import { archiveTaskFamily, dropFeedbackImage, releaseAuthorityOfTask, restoreTaskFamily, revokeRelease, runLiveCommands } from "../store.js";
|
|
3
|
+
import { archiveTaskFamily, dropFeedbackImage, releaseAuthorityOfTask, restoreTaskFamily, revokeRelease, runCheckStamp, runLiveCommands } from "../store.js";
|
|
4
|
+
import { ranWords } from "../check-stamp.js";
|
|
4
5
|
import { applyTidy, approverStandsNow, familyRepoOf, releaseFactsOf, tidyDays, tidyDigest, tidyPreview, TIDY_DEFAULT_DAYS } from "../tidy.js";
|
|
5
6
|
import { steerCapabilityOf } from "../steer-capabilities.js";
|
|
6
7
|
import { canReveal, localCheckoutOf, revealInFinder } from "../checkout-proof.js";
|
|
@@ -17,7 +18,7 @@ import { DEPRECATED_PAGE } from "../deprecations.js";
|
|
|
17
18
|
/** tasks handlers, moved without changing their route bodies. */
|
|
18
19
|
import { html, htmlString, joinHtml, postForm, textHtml } from "../html.js";
|
|
19
20
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
20
|
-
import { lstatSync, realpathSync } from "node:fs";
|
|
21
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
21
22
|
import {} from "node:http";
|
|
22
23
|
import { withActor } from "../actor.js";
|
|
23
24
|
import { gateWords } from "../approval-policy.js";
|
|
@@ -32,8 +33,14 @@ import { checkResponse, CONSOLE_FORMS, readForm } from "../contracts/console-api
|
|
|
32
33
|
import { hasForbiddenControls, validateNote } from "../decision.js";
|
|
33
34
|
import { builderReadyFor, diagnoseTaskDispatch, firstRunBuilderRepo } from "../dispatch.js";
|
|
34
35
|
import { firstTaskSuggestions, withBuilderStep } from "../first-run.js";
|
|
35
|
-
import { newTaskPage } from "./render-new-task.js";
|
|
36
|
-
import {
|
|
36
|
+
import { fastStartPage, fastTermsRows, importPage, newTaskPage } from "./render-new-task.js";
|
|
37
|
+
import { fileGithubItem, readGithubItem } from "../github-import.js";
|
|
38
|
+
import { allowAutoPublish, autoPublishHeld } from "../release-auto.js";
|
|
39
|
+
import { startCheckRepair } from "../check-repair.js";
|
|
40
|
+
import { askFailingFirst, dropFailingFirst, rerunEvidence, saveEvidencePair } from "../evidence-pair.js";
|
|
41
|
+
import { closeFinding, familyInWebReach, familyRootOf, getFinding, NOT_ON_TASK, resendFinding, runOnTask, sendFinding, sendReviewFinding } from "../findings.js";
|
|
42
|
+
import { labelProviderCapture, providerCapture } from "../provider-capture.js";
|
|
43
|
+
import { fastStartBuild, keepGrownChange, prepareFastStart } from "../fast-start.js";
|
|
37
44
|
import { evidencePack } from "../evidence-pack.js";
|
|
38
45
|
import { decisionPage, evidenceRecordPage, runPage } from "./render-history.js";
|
|
39
46
|
import { readVerifiedArtifact, readVerifiedReport, writeEvidenceFile } from "../evidence.js";
|
|
@@ -72,8 +79,9 @@ import { WorkIndexCursorError, workIndexPage } from "../work-index.js";
|
|
|
72
79
|
import { buildProgressOf, parseWorkView } from "../workspace-ui.js";
|
|
73
80
|
import { requestContext } from "./request-context.js";
|
|
74
81
|
import { boardTickScript, oneLineOf, QUEUE_VIEW, refuse, regionScript, screen, transcriptScript, whenTime } from "./chrome.js";
|
|
82
|
+
import { retiredRedirect } from "./retired-routes.js";
|
|
75
83
|
import { chatReturnWithSaid, matchTaskPath, redirect, respond, reviewHref, safeChatReturn, safeReturn, SAFETY, taskChatHref, taskHref } from "./http.js";
|
|
76
|
-
import { putAwayPage, boardBody, boardWorkers,
|
|
84
|
+
import { putAwayPage, boardBody, boardWorkers, QUEUE_FRONT, queueBody, queueScript, TASK_STATES, workPage } from "./render-pages.js";
|
|
77
85
|
import { runEfficiencyOf } from "../run-efficiency.js";
|
|
78
86
|
import { timingFor } from "../latency-timeline.js";
|
|
79
87
|
import { ACCEPT_ANYWAY_NEEDS_REASON, editorFileHref, proofBundleView, rankReviewQueue, RESULT_REFUSALS, REVIEW_QUEUE_CAP, reviewCockpitDetailParts, reviewCockpitPage, reviewPriorityOf, runFactsFragment, runOutcomeBadge, withRefusal } from "./render-results.js";
|
|
@@ -94,7 +102,7 @@ export function createTasksHandlers(runtime) {
|
|
|
94
102
|
const showCode = (part, posted = false) => async (ctx) => {
|
|
95
103
|
const { url, who, response, project } = ctx;
|
|
96
104
|
if (who.via !== 'cookie' || !codingActorAllowed({ name: who.name, generation: who.session.generation }))
|
|
97
|
-
return refuse(response, who, 403, 'Coding sessions require installation-wide operator access. You can manage project tasks in Work.', '/
|
|
105
|
+
return refuse(response, who, 403, 'Coding sessions require installation-wide operator access. You can manage project tasks in Work.', '/tasks');
|
|
98
106
|
const actor = { name: who.name, generation: who.session.generation };
|
|
99
107
|
const sessionId = part === "workspace" || part === "unknown" ? null : /^\/code\/([a-f0-9]{32})/.exec(url.pathname)[1];
|
|
100
108
|
const read = part === "state" || part === "changes" ? part : null;
|
|
@@ -135,7 +143,7 @@ export function createTasksHandlers(runtime) {
|
|
|
135
143
|
if (!codingActorAllowed(actor))
|
|
136
144
|
return read !== null
|
|
137
145
|
? respond(response, 403, 'application/json', JSON.stringify({ ok: false, error: 'Your access changed. Sign in again.' }))
|
|
138
|
-
: refuse(response, who, 403, 'Your access changed. Sign in again.', '/
|
|
146
|
+
: refuse(response, who, 403, 'Your access changed. Sign in again.', '/tasks');
|
|
139
147
|
const message = error instanceof Error ? error.message : 'The coding workspace is unavailable.';
|
|
140
148
|
if (part === 'ship')
|
|
141
149
|
return sendScreen(response, 409, screen('Review for shipping', html `<h1>Prepare this result</h1><p>${message}</p><a href="/code/${sessionId}">Back to coding</a>`, { chrome: chromeFor(project, 'code') }));
|
|
@@ -144,82 +152,6 @@ export function createTasksHandlers(runtime) {
|
|
|
144
152
|
return sendScreen(response, 404, screen('Coding session unavailable', html `<h1>Session unavailable</h1><p>${message}</p><a href="/code">Open coding workspace</a>`, { chrome: chromeFor(project, 'code') }));
|
|
145
153
|
}
|
|
146
154
|
};
|
|
147
|
-
async function showInbox(ctx) {
|
|
148
|
-
const { url, who, request, response, now, project } = ctx;
|
|
149
|
-
// With no project open in scoped mode this is the ROLL-UP inbox:
|
|
150
|
-
// admission binds inside the bounded queries, every row's repo is
|
|
151
|
-
// re-proved here, and rows render as links only (Codex roll-up
|
|
152
|
-
// review, findings 6–8). Gaps stay per-project — they are derived
|
|
153
|
-
// against one repo's capabilities and roll up dishonestly.
|
|
154
|
-
const rollup = project === null && !unscopedMode;
|
|
155
|
-
const admission = rollup ? admissionList() : null;
|
|
156
|
-
const cancelled = store
|
|
157
|
-
.listCancelledBlockersScoped(project, 10, admission)
|
|
158
|
-
.filter(one => visible(one.repo) && visible(one.blockerRepo));
|
|
159
|
-
const inboxData = {
|
|
160
|
-
csrf: who.via === "cookie" ? who.session.csrf : "",
|
|
161
|
-
revision: who.via === "cookie" ? who.session.projectRevision : 0,
|
|
162
|
-
rollup,
|
|
163
|
-
interactive: project !== null,
|
|
164
|
-
decisions: store.listDecisionsScoped(project).filter(one => visible(one.repo)).slice(0, 10),
|
|
165
|
-
// Each waiting scope wears its consent door (v48 authority repair): a scope whose
|
|
166
|
-
// approval is closed — unreadable route, a route that cannot run,
|
|
167
|
-
// a pre-routing row whose approval lapsed — reads as needing
|
|
168
|
-
// attention, never as something to approve.
|
|
169
|
-
approvals: store.scopesAwaitingApproval(project, 10, admission).filter(one => visible(one.repo)).map(one => {
|
|
170
|
-
const ref = store.lookupRef(one.taskId);
|
|
171
|
-
const scope = store.getScope(one.taskId);
|
|
172
|
-
const door = consentDoorOf(scope, routeViewOf(one.taskId, ref, scope, now, who));
|
|
173
|
-
return { ...one, closed: door.open ? null : door.title };
|
|
174
|
-
}),
|
|
175
|
-
requeueables: store.listRequeueablesScoped(project, now, 10, admission).filter(one => visible(one.repo)),
|
|
176
|
-
needsVerification: store
|
|
177
|
-
.listCompletedWorkScoped(project, 10, admission)
|
|
178
|
-
.filter(one => visible(one.repo) && (one.proofVerdict === "short" || one.proofVerdict === "refuted") && !one.proofAccepted &&
|
|
179
|
-
assignmentOf(store, one.taskId, now, workAccess(), evidenceRoot) === null)
|
|
180
|
-
.map(one => ({
|
|
181
|
-
taskId: one.taskId,
|
|
182
|
-
title: one.title,
|
|
183
|
-
verdict: one.proofVerdict,
|
|
184
|
-
repo: one.repo,
|
|
185
|
-
matrix: one.proofMatrix,
|
|
186
|
-
repairChain: one.runId === null ? null : store.repairChainFor(one.runId),
|
|
187
|
-
})),
|
|
188
|
-
cancelledBlockers: cancelled,
|
|
189
|
-
gaps: project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0).slice(0, 10),
|
|
190
|
-
// The builder step speaks for the same project as Chat's (firstRunBuilderRepo), as dispatch decides.
|
|
191
|
-
wizard: withBuilderStep(firstRunStepsNow(now), builderReadyFor(store, firstRunBuilderRepo(store, project, visible, managedRepos()), now)),
|
|
192
|
-
worker: (() => {
|
|
193
|
-
// The one fact the inbox must never hide (install review): with
|
|
194
|
-
// no worker answering, nothing here will ever build, and every
|
|
195
|
-
// approval below is a promise nobody is there to keep.
|
|
196
|
-
const runners = store.listRunners().filter(one => one.retiredAt === null && (!restricted() || one.repos.some(visible)));
|
|
197
|
-
const answering = runners.filter(one => runnerAlive(one, now));
|
|
198
|
-
const lastHeard = runners.map(one => one.heartbeatAt).sort().at(-1) ?? null;
|
|
199
|
-
return { answering: answering.length, registered: runners.length, lastHeard };
|
|
200
|
-
})(),
|
|
201
|
-
now,
|
|
202
|
-
// Console v2: results ready to review and work running now, from the same admitted work index.
|
|
203
|
-
...(() => {
|
|
204
|
-
const row = (one) => ({ taskId: one.rootId, title: one.title, detail: one.status.detail, repo: one.repo });
|
|
205
|
-
const needsYou = workIndexPage(store, now, workAccess(), { view: "needs-you", limit: 40, project, root: evidenceRoot });
|
|
206
|
-
const runningNow = workIndexPage(store, now, workAccess(), { view: "running", limit: 40, project, root: evidenceRoot });
|
|
207
|
-
return { ready: needsYou.items.filter(one => one.assignmentState === "ready-to-check").map(row), running: runningNow.items.map(row) };
|
|
208
|
-
})(),
|
|
209
|
-
};
|
|
210
|
-
// Which tabs hold something new since this browser last looked (the dots on a phone).
|
|
211
|
-
const tab = parseInboxTab(url.searchParams.get("tab")) ?? "all";
|
|
212
|
-
const prints = inboxFingerprints(inboxData);
|
|
213
|
-
const seenWords = /(?:^|;\s*)so-inbox-seen=([0-9a-f]{8}(?:\.[0-9a-f]{8}){3})(?:;|$)/.exec(request.headers.cookie ?? "")?.[1]?.split(".") ?? null;
|
|
214
|
-
const order = ["needs-you", "ready", "running", "all"];
|
|
215
|
-
const seen = Object.fromEntries(order.map((one, index) => [one, seenWords?.[index] ?? ""]));
|
|
216
|
-
const unread = order.filter(one => seenWords !== null && seen[one] !== prints[one]);
|
|
217
|
-
for (const one of order)
|
|
218
|
-
if (tab === "all" || one === tab)
|
|
219
|
-
seen[one] = prints[one];
|
|
220
|
-
response.setHeader("Set-Cookie", `so-inbox-seen=${order.map(one => seen[one] || prints[one]).join(".")}; SameSite=Lax; Path=/; Max-Age=31536000`);
|
|
221
|
-
return sendScreen(response, 200, inboxPage(chromeFor(project, "inbox"), { ...inboxData, tab, unread }));
|
|
222
|
-
}
|
|
223
155
|
async function showWork(ctx) {
|
|
224
156
|
const { url, who, response, now, project } = ctx;
|
|
225
157
|
// The Work destination (workspace package 1): every task in view as
|
|
@@ -232,7 +164,7 @@ export function createTasksHandlers(runtime) {
|
|
|
232
164
|
// The stored-state filter the old task list had (`?state=`): an unknown state is refused, as it was there.
|
|
233
165
|
const wantedState = url.searchParams.get("state");
|
|
234
166
|
if (wantedState !== null && !TASK_STATES.includes(wantedState))
|
|
235
|
-
return refuse(response, who, 400, "no such state", "/
|
|
167
|
+
return refuse(response, who, 400, "no such state", "/tasks");
|
|
236
168
|
const state = wantedState;
|
|
237
169
|
const rollup = project === null && !unscopedMode;
|
|
238
170
|
let work;
|
|
@@ -253,7 +185,7 @@ export function createTasksHandlers(runtime) {
|
|
|
253
185
|
}
|
|
254
186
|
catch (error) {
|
|
255
187
|
if (error instanceof WorkIndexCursorError)
|
|
256
|
-
return refuse(response, who, 400, 'This task page has expired. Open the first page.', '/
|
|
188
|
+
return refuse(response, who, 400, 'This task page has expired. Open the first page.', '/tasks');
|
|
257
189
|
throw error;
|
|
258
190
|
}
|
|
259
191
|
// A failed row says what its latest attempt missed, as its task page does (a stop reason already reads so), from
|
|
@@ -305,179 +237,13 @@ export function createTasksHandlers(runtime) {
|
|
|
305
237
|
});
|
|
306
238
|
// One project's Tasks dock that project's own conversation (v77).
|
|
307
239
|
const projectThread = url.searchParams.has('project') && project !== null && managedRepos().includes(project) ? project : null;
|
|
308
|
-
const docked = projectThread === null ? null : dockedConversation(who, null, projectThread, now, `/
|
|
240
|
+
const docked = projectThread === null ? null : dockedConversation(who, null, projectThread, now, `/tasks?project=${encodeURIComponent(projectThread)}`);
|
|
309
241
|
if (docked !== null)
|
|
310
242
|
page.workspace = { ...page.workspace, conversation: docked, pageHtml: page.body };
|
|
311
243
|
// Without a docked chat the list reads itself: 10 s while any of its tasks is building, else 30 s.
|
|
312
244
|
page.refreshSeconds = work.totals.running > 0 ? 10 : 30;
|
|
313
245
|
return sendScreen(response, 200, page);
|
|
314
246
|
}
|
|
315
|
-
async function showNext(ctx) {
|
|
316
|
-
const { url, who, response, now, project } = ctx;
|
|
317
|
-
// Triage: everything waiting on a person, one at a time, hardest-
|
|
318
|
-
// blocked first — oldest question, then plans and scopes to approve,
|
|
319
|
-
// then stalled work to retry, then requirement gaps. `skip` is a
|
|
320
|
-
// bounded, session-free cursor of keys the operator set aside; every
|
|
321
|
-
// act 303s back here, which is what makes it a flow and not a list.
|
|
322
|
-
const skipped = new Set((url.searchParams.get("skip") ?? "").split(",").filter(one => /^[darg]:[A-Za-z0-9._:-]{1,80}$/.test(one)).slice(0, 20));
|
|
323
|
-
const decisions = store.listDecisionsScoped(project).filter(one => one.state !== "answered");
|
|
324
|
-
const approvals = store.scopesAwaitingApproval(project, 20);
|
|
325
|
-
const requeueables = store.listRequeueablesScoped(project, now, 20);
|
|
326
|
-
const gaps = project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0);
|
|
327
|
-
const queue = [
|
|
328
|
-
...decisions.map(decision => ({ key: `d:${decision.id}`, kind: "decision", decision })),
|
|
329
|
-
...approvals.map(approval => ({ key: `a:${approval.taskId}`, kind: "approval", approval })),
|
|
330
|
-
...requeueables.map(stalled => ({ key: `r:${stalled.taskId}`, kind: "requeue", stalled })),
|
|
331
|
-
...gaps.map(gap => ({ key: `g:${gap.key.replace(/[^A-Za-z0-9._:-]/g, "_")}`, kind: "gap", gap })),
|
|
332
|
-
];
|
|
333
|
-
const remaining = queue.filter(one => !skipped.has(one.key));
|
|
334
|
-
const item = remaining[0] ?? null;
|
|
335
|
-
const csrf = who.via === "cookie" ? who.session.csrf : "";
|
|
336
|
-
if (item !== null && item.kind === "approval") {
|
|
337
|
-
// The card restates the digest-bound terms, so the nonce may be
|
|
338
|
-
// minted here — same rule as the task screen, same binding.
|
|
339
|
-
const ref = store.lookupRef(item.approval.taskId);
|
|
340
|
-
const scope = store.getScope(item.approval.taskId);
|
|
341
|
-
const planView = ref !== null && ref.plan === "drafted" ? planViewOf(ref.id) : null;
|
|
342
|
-
const approvalDigest = approvalFormDigest(item.approval.digest, planView?.sha256 ?? null);
|
|
343
|
-
// The same agents block the task page and chat sign under (v48),
|
|
344
|
-
// and the same consent door: closed, it mints no nonce.
|
|
345
|
-
const route = routeViewOf(item.approval.taskId, ref, scope, now, who);
|
|
346
|
-
const nonce = who.via === "cookie" && consentDoorOf(scope, route).open ? mintApprovalNonce(who.name, item.approval.taskId, approvalDigest) : "";
|
|
347
|
-
return sendScreen(response, 200, nextPage(chromeFor(project, "inbox"), {
|
|
348
|
-
item, scope,
|
|
349
|
-
planDocument: planView?.document ?? null,
|
|
350
|
-
planContract: ref === null || planView === null ? null : planContractViewOf(ref.id, scope),
|
|
351
|
-
approvalDigest,
|
|
352
|
-
deliverable: ref?.deliverable ?? "branch",
|
|
353
|
-
route,
|
|
354
|
-
origin: originOf(store, item.approval.taskId, repo => store.accountCanAccess(who.name, repo)),
|
|
355
|
-
// Whether a remembered password may approve these exact terms: the task page's own preview (task-views.ts
|
|
356
|
-
// approvalFresh), read without minting a second nonce. Display only; the approve POST proves it again.
|
|
357
|
-
remembered: nonce !== "" && runtime.taskViewData(item.approval.taskId, who, null, { mintNonce: false })?.approvalFresh != null,
|
|
358
|
-
csrf, nonce, remaining: remaining.length, skipped: [...skipped], now,
|
|
359
|
-
}));
|
|
360
|
-
}
|
|
361
|
-
return sendScreen(response, 200, nextPage(chromeFor(project, "inbox"), {
|
|
362
|
-
item, scope: null, planDocument: null, planContract: null, csrf, nonce: "",
|
|
363
|
-
approvalDigest: null, route: null,
|
|
364
|
-
remaining: remaining.length, skipped: [...skipped], now,
|
|
365
|
-
}));
|
|
366
|
-
}
|
|
367
|
-
async function showBoard(ctx) {
|
|
368
|
-
const { url, who, response, now, project } = ctx;
|
|
369
|
-
// scope=all is the rolled-up view: every project this server was
|
|
370
|
-
// allowed to serve, on one board. The ceiling still rules row by row
|
|
371
|
-
// (rowVisible, the same predicate as every list) — a repo outside
|
|
372
|
-
// the server's configuration never renders a card, whatever the
|
|
373
|
-
// database holds. Unplaced work (repo NULL) appears: it dispatches
|
|
374
|
-
// anywhere, so every board honestly owns it.
|
|
375
|
-
const all = url.searchParams.get("scope") === "all";
|
|
376
|
-
// Roll-up admission happens BEFORE the query limit (Codex round 2,
|
|
377
|
-
// finding 11) — and root ceilings enumerate too, through the STORED
|
|
378
|
-
// repos that pass the ceiling (attended review, finding 3); the
|
|
379
|
-
// per-row visible() re-check below stays either way.
|
|
380
|
-
const admission = all ? admissionList() : null;
|
|
381
|
-
const snapshot = store.boardScoped(all ? null : project, now, 200, admission);
|
|
382
|
-
const admitted = all
|
|
383
|
-
? snapshot.tasks.filter(facts => facts.repo === null || visible(facts.repo))
|
|
384
|
-
: snapshot.tasks;
|
|
385
|
-
const done = all
|
|
386
|
-
? snapshot.done.filter(row => row.repo === null || visible(row.repo))
|
|
387
|
-
: snapshot.done;
|
|
388
|
-
// A blocker may live in a repo this server must not speak about —
|
|
389
|
-
// redact its state before the pure classifier composes a sentence
|
|
390
|
-
// from it (Codex round 2, finding 12). The dependency's NAME stays:
|
|
391
|
-
// the edge belongs to the visible task; the other project's live
|
|
392
|
-
// status does not.
|
|
393
|
-
const cards = admitted.map(facts => classify(facts.blockerRepo !== null && !visible(facts.blockerRepo)
|
|
394
|
-
? { ...facts, blockerState: null }
|
|
395
|
-
: facts, now));
|
|
396
|
-
// "Since you last looked": what concluded between this session's
|
|
397
|
-
// previous full board read and now. Fragment polls never move the
|
|
398
|
-
// anchor — an open tab is not a person looking.
|
|
399
|
-
let delta = null;
|
|
400
|
-
if (who.via === "cookie" && url.searchParams.get("fragment") !== "1") {
|
|
401
|
-
const prev = who.session.sawBoardAt;
|
|
402
|
-
if (prev !== null && now.getTime() - prev > 5 * 60_000) {
|
|
403
|
-
const sinceIso = new Date(prev).toISOString();
|
|
404
|
-
const runs = store.runsSinceScoped(sinceIso, all ? null : project).filter(one => all ? one.taskId !== "" : true);
|
|
405
|
-
delta = {
|
|
406
|
-
agoMinutes: Math.round((now.getTime() - prev) / 60_000),
|
|
407
|
-
built: runs.filter(one => one.outcome === "built" || one.outcome === "no-change").length,
|
|
408
|
-
failed: runs.filter(one => one.outcome === "failed").length,
|
|
409
|
-
questions: store.listDecisionsScoped(all ? null : project).filter(one => one.createdAt >= sinceIso && one.state !== "answered").length,
|
|
410
|
-
};
|
|
411
|
-
if (delta.built === 0 && delta.failed === 0 && delta.questions === 0)
|
|
412
|
-
delta = null;
|
|
413
|
-
}
|
|
414
|
-
who.session.sawBoardAt = now.getTime();
|
|
415
|
-
}
|
|
416
|
-
// A completed task whose proof is short or refuted and not yet
|
|
417
|
-
// accepted (Priority 2) reads "needs verification", not done — the
|
|
418
|
-
// board's once-and-only-once rule holds because this split is the
|
|
419
|
-
// ONE place a done row becomes either lane; the done-lane render
|
|
420
|
-
// below never sees the rows filtered out here.
|
|
421
|
-
const unverifiedDone = done.filter(row => (row.proofVerdict === "short" || row.proofVerdict === "refuted") && !row.proofAccepted);
|
|
422
|
-
const verifiedDone = done.filter(row => !unverifiedDone.includes(row));
|
|
423
|
-
const unverifiedCards = unverifiedDone.map(row => {
|
|
424
|
-
// v40: the SAME chip gains one more word when a repair chain
|
|
425
|
-
// exists for this row's own run — never a second card.
|
|
426
|
-
const chain = row.runId === null ? null : store.repairChainFor(row.runId);
|
|
427
|
-
const repairChain = chain === null
|
|
428
|
-
? null
|
|
429
|
-
: {
|
|
430
|
-
attempt: chain.attempt,
|
|
431
|
-
outcome: chain.outcome,
|
|
432
|
-
approved: chain.draftTask !== null && (store.getScope(chain.draftTask)?.approvedAt ?? null) !== null,
|
|
433
|
-
};
|
|
434
|
-
return attentionCardForUnverifiedDone({
|
|
435
|
-
taskId: row.taskId,
|
|
436
|
-
title: row.title,
|
|
437
|
-
repo: row.repo,
|
|
438
|
-
completedAt: row.completedAt,
|
|
439
|
-
proofVerdict: row.proofVerdict,
|
|
440
|
-
proofMatrix: row.proofMatrix,
|
|
441
|
-
repairChain,
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
// Project-level Needs you (evals-nightly.md, part A3): GitHub's nightly lane failures and the plane's own nightly
|
|
445
|
-
// check of main, for the projects this board shows.
|
|
446
|
-
const nightlyCards = nightlyNeedsYou(store, repo => visible(repo) && (all || project === null || repo === project)).map(need => ({
|
|
447
|
-
lane: "attention", taskId: need.key, title: need.title, repo: need.repo, reason: need.reason, href: need.href, claim: null,
|
|
448
|
-
stalledSince: need.since, attempt: null, overdue: false, priority: 0, assignedRunner: null,
|
|
449
|
-
}));
|
|
450
|
-
const laneCards = [...cards, ...unverifiedCards, ...nightlyCards];
|
|
451
|
-
// The worker strip: the workers this board's work can go to — bound to
|
|
452
|
-
// this project, or (rolled up) to any project the ceiling admits.
|
|
453
|
-
const workers = boardWorkers(store.listRunners(), { all, project, visible });
|
|
454
|
-
const body = boardBody({ cards: laneCards, workers, done: verifiedDone, saturated: snapshot.saturated, now, all, project, delta }, pr => store.ciFailureObserved(pr));
|
|
455
|
-
if (url.searchParams.get("fragment") === "1") {
|
|
456
|
-
// The live region alone — the in-page swapper's diet. Same auth,
|
|
457
|
-
// same ceiling, no shell, no scripts (finding 2).
|
|
458
|
-
return respond(response, 200, "text/html; charset=utf-8", body);
|
|
459
|
-
}
|
|
460
|
-
if (url.searchParams.get("view") === "order") {
|
|
461
|
-
// The board's ORDER view (operator request): the same screen, flipped
|
|
462
|
-
// to dispatch order with the queue's drag handles. Reordering and
|
|
463
|
-
// reserving are scheduling, not authority, so this is the one view
|
|
464
|
-
// where a drag does anything; the state view stays a view.
|
|
465
|
-
const csrf = who.via === "cookie" ? who.session.csrf : "";
|
|
466
|
-
const revision = who.via === "cookie" ? who.session.projectRevision : 0;
|
|
467
|
-
const region = queueRegionFor(project, csrf, revision, now);
|
|
468
|
-
return sendScreen(response, 200, screen("board", joinHtml([
|
|
469
|
-
html `<h1>Board</h1>`,
|
|
470
|
-
html `<p class="meta board-view"><a href="/board">state</a> \u00b7 <strong>order</strong> <span class="meta">\u2014 drag to reorder, or onto a worker to reserve; the state view is where cards move on their own</span></p>`,
|
|
471
|
-
html `<div id="queue-region">${region}</div>`,
|
|
472
|
-
html `<p class="meta" id="queue-region-stamp"></p>`,
|
|
473
|
-
], "\n"), { chrome: chromeFor(project, "board"), functional: { script: queueScript(), fetches: true } }));
|
|
474
|
-
}
|
|
475
|
-
const regionBody = html `<div id="board-region">${body}</div><p class="meta" id="board-region-stamp"></p>`;
|
|
476
|
-
return sendScreen(response, 200, screen("board", regionBody, {
|
|
477
|
-
chrome: chromeFor(project, "board", undefined, all ? "board-all" : undefined),
|
|
478
|
-
functional: { script: regionScript("board-region", "1", undefined, { animate: true }) + boardTickScript(), fetches: true },
|
|
479
|
-
}));
|
|
480
|
-
}
|
|
481
247
|
/** A completed (or attempted) row as the review list ranks it: its proof reasons, CI standing, and the assignment
|
|
482
248
|
* when that assignment's receipt is this very row. */
|
|
483
249
|
function reviewRowOf(row, now) {
|
|
@@ -574,9 +340,9 @@ export function createTasksHandlers(runtime) {
|
|
|
574
340
|
const selectedRow = wantedId === null ? ranked[0] ?? null : chosen;
|
|
575
341
|
// An exact link to a result this person can't see (or that never existed) reads the same either way.
|
|
576
342
|
if (expectedRun !== null && wantedId !== null && chosen === null)
|
|
577
|
-
return refuse(response, who, 404, "No such result in your projects.", "/
|
|
343
|
+
return refuse(response, who, 404, "No such result in your projects.", "/tasks");
|
|
578
344
|
if (expectedRun !== null && (!/^[1-9]\d*$/.test(expectedRun) || selectedRow?.runId !== Number(expectedRun))) {
|
|
579
|
-
return sendScreen(response, 409, screen("Result changed", html `<h1>Result changed</h1><p>This acceptance link no longer matches the current result. Review the current task before accepting.</p><p class="refusal-back"><a class="button-link" href="/
|
|
345
|
+
return sendScreen(response, 409, screen("Result changed", html `<h1>Result changed</h1><p>This acceptance link no longer matches the current result. Review the current task before accepting.</p><p class="refusal-back"><a class="button-link" href="/tasks?view=ready">Review results</a></p>`, { chrome: chromeFor(project, "runs") }));
|
|
580
346
|
}
|
|
581
347
|
const selected = picked !== null ? picked.selected : selectedRow === null ? null : reviewCockpitViewOf(selectedRow, who, now);
|
|
582
348
|
// Opening a run's own result page is viewing it: the shell says so with a keyed POST /agents/seen (easy-start N1:
|
|
@@ -607,39 +373,59 @@ export function createTasksHandlers(runtime) {
|
|
|
607
373
|
reviewPage.workspace = { ...reviewPage.workspace, conversation: reviewDocked, pageHtml: reviewPage.body };
|
|
608
374
|
return sendScreen(response, 200, reviewPage);
|
|
609
375
|
}
|
|
610
|
-
/** Finished tasks live in the Tasks list's Finished view now (UI revamp wave B): the old address keeps answering, as a
|
|
611
|
-
* GET alias, with a named project carried along. */
|
|
612
|
-
async function showDone(ctx) {
|
|
613
|
-
const { url, response } = ctx;
|
|
614
|
-
const named = url.searchParams.get("project");
|
|
615
|
-
return redirectGet(response, `/work?view=completed${named === null || named === "" ? "" : `&project=${encodeURIComponent(named)}`}`);
|
|
616
|
-
}
|
|
617
376
|
/** The old task list, retired into /work (docs/plans/reui-polish.md slice 7): its address keeps answering, as a
|
|
618
377
|
* redirect, so links and bookmarks still land. `state` carries over (an unknown one is still refused); `template`
|
|
619
378
|
* goes to the add page, which now pre-fills it. Anything else is dropped. */
|
|
379
|
+
/** Tasks (docs/plans/plain-toolroll.md P1.4): the one list, with List, Board, Ready, Archived and an every-project
|
|
380
|
+
* scope (`scope=all`). `view=board` is the Board, `view=order` the queue's order, `view=done` Finished. */
|
|
620
381
|
async function showTasks(ctx) {
|
|
621
|
-
const { url
|
|
622
|
-
const wanted = url.searchParams.get("state");
|
|
623
|
-
if (wanted !== null && !TASK_STATES.includes(wanted))
|
|
624
|
-
return refuse(response, who, 400, "no such state", "/work");
|
|
382
|
+
const { url } = ctx;
|
|
625
383
|
const template = url.searchParams.get("template");
|
|
626
384
|
if (template !== null)
|
|
627
|
-
return redirect(response, `/tasks/new?template=${encodeURIComponent(template)}`);
|
|
628
|
-
|
|
385
|
+
return redirect(ctx.response, `/tasks/new?template=${encodeURIComponent(template)}`);
|
|
386
|
+
const view = url.searchParams.get("view");
|
|
387
|
+
if (view === "order")
|
|
388
|
+
return showOrder(ctx);
|
|
389
|
+
// The list reads its own keys: Board is its layout, every project its roll-up, Finished its completed view.
|
|
390
|
+
const read = new URL(url.href);
|
|
391
|
+
if (view === "board") {
|
|
392
|
+
read.searchParams.delete("view");
|
|
393
|
+
read.searchParams.set("layout", "board");
|
|
394
|
+
}
|
|
395
|
+
if (view === "done")
|
|
396
|
+
read.searchParams.set("view", "completed");
|
|
397
|
+
if (read.searchParams.get("scope") === "all") {
|
|
398
|
+
read.searchParams.delete("scope");
|
|
399
|
+
read.searchParams.set("all", "1");
|
|
400
|
+
}
|
|
401
|
+
return showWork({ ...ctx, url: read });
|
|
629
402
|
}
|
|
630
|
-
|
|
403
|
+
/** The queue's order view, under Tasks: dispatch order with drag handles (scheduling, never authority). */
|
|
404
|
+
async function showOrder(ctx) {
|
|
631
405
|
const { url, who, response, project } = ctx;
|
|
632
406
|
const csrf = who.via === "cookie" ? who.session.csrf : "";
|
|
633
407
|
const revision = who.via === "cookie" ? who.session.projectRevision : 0;
|
|
634
|
-
|
|
635
|
-
// in THIS project, per worker (the capacity is global), so the header
|
|
636
|
-
// names both and never a ratio.
|
|
637
|
-
if (url.searchParams.get("fragment") === "1") {
|
|
408
|
+
if (url.searchParams.get("fragment") === "1")
|
|
638
409
|
return respond(response, 200, "text/html; charset=utf-8", queueRegionFor(project, csrf, revision, clock()));
|
|
410
|
+
const region = queueRegionFor(project, csrf, revision, clock());
|
|
411
|
+
return sendScreen(response, 200, screen("order", joinHtml([
|
|
412
|
+
html `<h1>Order the queue</h1>`,
|
|
413
|
+
html `<p class="meta board-view"><a href="/tasks?view=board">Board</a> · <strong>order</strong> <span class="meta">— drag to reorder, or onto a builder to keep it for that builder</span></p>`,
|
|
414
|
+
html `<div id="queue-region">${region}</div>`,
|
|
415
|
+
html `<p class="meta" id="queue-region-stamp"></p>`,
|
|
416
|
+
], "\n"), { chrome: chromeFor(project, "work"), functional: { script: queueScript(), fetches: true } }));
|
|
417
|
+
}
|
|
418
|
+
/** /n/<number>: a push's task link, by the task's stored number, never its title. It opens the task's page only
|
|
419
|
+
* inside the viewer's own projects; anything else is the same not-found as an unknown number. */
|
|
420
|
+
async function showNumberedTask(ctx) {
|
|
421
|
+
const { url, who, response } = ctx;
|
|
422
|
+
const number = Number(url.pathname.slice("/n/".length));
|
|
423
|
+
const ref = Number.isSafeInteger(number) ? store.refForId(number) : null;
|
|
424
|
+
const task = ref === null || ref.backend !== "built-in" ? null : ref.externalId;
|
|
425
|
+
if (task === null || ref === null || ref.repo === null || !visible(ref.repo) || !store.accountCanAccess(who.name, ref.repo)) {
|
|
426
|
+
return refuse(response, who, 404, "There's no page at this address.", "/tasks");
|
|
639
427
|
}
|
|
640
|
-
|
|
641
|
-
// keeps answering, as a redirect, so nothing anyone bookmarked 404s.
|
|
642
|
-
return redirect(response, QUEUE_VIEW);
|
|
428
|
+
return redirectGet(response, taskHref(task));
|
|
643
429
|
}
|
|
644
430
|
async function showNewTask(ctx) {
|
|
645
431
|
const picked = (() => { const name = ctx.url.searchParams.get("template"); return name === null ? null : templateByName(name); })();
|
|
@@ -657,7 +443,7 @@ export function createTasksHandlers(runtime) {
|
|
|
657
443
|
return noPage(who, response);
|
|
658
444
|
const family = familyOf(task.taskId);
|
|
659
445
|
if (family === null)
|
|
660
|
-
return refuse(response, who, 404, "no such task", "/
|
|
446
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
661
447
|
const version = url.searchParams.get("version");
|
|
662
448
|
if (version !== null && !family.versions.some(one => one.id === version))
|
|
663
449
|
return refuse(response, who, 404, "That version is not available for this task.", taskHref(family.root.id));
|
|
@@ -709,12 +495,12 @@ export function createTasksHandlers(runtime) {
|
|
|
709
495
|
const access = workAccess();
|
|
710
496
|
const family = store.taskFamilyOf(evidence.taskId, access.repos, access.includeUnplaced);
|
|
711
497
|
if (family === null || !visible(family.root.repo))
|
|
712
|
-
return refuse(response, who, 404, "no such task", "/
|
|
498
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
713
499
|
if (evidence.taskId !== family.root.id)
|
|
714
500
|
return redirect(response, `${taskHref(family.root.id)}/evidence${url.search}`);
|
|
715
501
|
const pack = evidencePack(store, family.root.id, access, who.name, now, evidenceRoot);
|
|
716
502
|
if (pack === null)
|
|
717
|
-
return refuse(response, who, 404, "no such task", "/
|
|
503
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
718
504
|
if (url.searchParams.get("format") === "json") {
|
|
719
505
|
response.writeHead(200, { "content-type": "application/json; charset=utf-8", "content-disposition": `attachment; filename="evidence-${family.root.id.replace(/[^A-Za-z0-9._-]/g, "_")}.json"`,
|
|
720
506
|
"cache-control": "no-store", "x-content-type-options": "nosniff" });
|
|
@@ -860,7 +646,9 @@ export function createTasksHandlers(runtime) {
|
|
|
860
646
|
who.via === "cookie" &&
|
|
861
647
|
who.session.editorLinks === true &&
|
|
862
648
|
// A reviewer run (v29) never had a checkout — no files to open.
|
|
863
|
-
found.worktree !== null
|
|
649
|
+
found.worktree !== null &&
|
|
650
|
+
// Idle cleanup removes the folder and a later run makes it again: no link to a folder that isn't there.
|
|
651
|
+
existsSync(found.worktree)
|
|
864
652
|
? { worktree: found.worktree }
|
|
865
653
|
: null, options.editorLinks !== undefined &&
|
|
866
654
|
options.localRunner !== undefined &&
|
|
@@ -1067,6 +855,30 @@ export function createTasksHandlers(runtime) {
|
|
|
1067
855
|
}
|
|
1068
856
|
// One filing door for every surface (Codex adoption review, finding 7).
|
|
1069
857
|
const after = (body.get("after") ?? "").trim();
|
|
858
|
+
// From an issue or pull request (github-import.ts): read and quoted first; filing is the second press, on what was read.
|
|
859
|
+
const fromLink = (body.get("from") ?? "").trim();
|
|
860
|
+
if (fromLink !== "") {
|
|
861
|
+
if (who.via !== "cookie" || repo === "")
|
|
862
|
+
return newTaskScreen(ctx, 400, "Choose the project this issue belongs to.", draft);
|
|
863
|
+
if (body.get("from-read") !== "1") {
|
|
864
|
+
const read = await readGithubItem(store, { url: fromLink, repo });
|
|
865
|
+
if (!read.ok)
|
|
866
|
+
return newTaskScreen(ctx, 400, read.message, draft);
|
|
867
|
+
return sendScreen(response, 200, importPage(chromeFor(project ?? null, "tasks"), { repo, url: read.item.url, title: read.item.title, body: read.item.body, kind: read.item.kind, number: read.item.number, head: read.item.head }));
|
|
868
|
+
}
|
|
869
|
+
const filed = await fileGithubItem(store, { url: fromLink, repo, actor: who.name, shownHead: body.get("from-head") ?? null }, now);
|
|
870
|
+
if (!filed.ok)
|
|
871
|
+
return newTaskScreen(ctx, filed.reason === "head-moved" ? 409 : 400, filed.message, draft);
|
|
872
|
+
return redirect(response, taskHref(filed.taskId));
|
|
873
|
+
}
|
|
874
|
+
// Small work a person starts themselves: their words become the exact terms, and Build is their yes (fast-start.ts).
|
|
875
|
+
// Anything else, or any doubt, is the ordinary filing below. `plain` is "Plan it first" from that form.
|
|
876
|
+
if (who.via === "cookie" && body.get("plain") !== "1" && repo !== "" && goal === "" && notThis === "" && touchesGiven.length === 0 && !scout && id === "" && after === ""
|
|
877
|
+
&& (qualityMode === null || qualityMode === "default") && (permissionMode === null || permissionMode === "auto")) {
|
|
878
|
+
const fast = await prepareFastStart(store, { actor: who.name, repo, title: typed.trim(), via: "console", ...(permissionMode === null ? {} : { permissionMode }), password: fastPasswordFacts(who) }, now);
|
|
879
|
+
if (fast.eligible)
|
|
880
|
+
return sendScreen(response, 200, fastStartPage(chromeFor(project ?? null, "tasks"), { title: typed.trim(), repo, preview: fast.preview, digest: fast.digest, terms: fastTermsRows(store, fast.scope, repo) }));
|
|
881
|
+
}
|
|
1070
882
|
let chainProblem = null;
|
|
1071
883
|
const made = store.transact(() => {
|
|
1072
884
|
// A project folder's identity changed while this request waited (a root or link now leads elsewhere): what was
|
|
@@ -1246,7 +1058,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1246
1058
|
const id = Number(answer[1]);
|
|
1247
1059
|
const requestedReturn = body.get("return");
|
|
1248
1060
|
const decisionBack = requestedReturn === "next"
|
|
1249
|
-
? "/
|
|
1061
|
+
? "/"
|
|
1250
1062
|
: requestedReturn === null
|
|
1251
1063
|
? `/d/${id}`
|
|
1252
1064
|
: safeChatReturn(requestedReturn);
|
|
@@ -1300,7 +1112,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1300
1112
|
const back = taskHref(act.taskId);
|
|
1301
1113
|
const ref = store.lookupRef(act.taskId);
|
|
1302
1114
|
if (ref === null || !visible(ref.repo))
|
|
1303
|
-
return refuse(response, who, 404, "no such task", "/
|
|
1115
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
1304
1116
|
const named = (body.get("run") ?? "").trim();
|
|
1305
1117
|
const run = /^[0-9]{1,15}$/.test(named) ? store.getRun(Number(named)) : null;
|
|
1306
1118
|
const runTask = run === null ? null : store.externalIdFor(run.taskRef);
|
|
@@ -1335,7 +1147,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1335
1147
|
return taskScreen(response, who, act.taskId, "The demo merges nothing.", 403);
|
|
1336
1148
|
const ref = store.lookupRef(act.taskId);
|
|
1337
1149
|
if (ref === null || !visible(ref.repo))
|
|
1338
|
-
return refuse(response, who, 404, "no such task", "/
|
|
1150
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
1339
1151
|
const named = (body.get("run") ?? "").trim();
|
|
1340
1152
|
const run = /^[0-9]{1,15}$/.test(named) ? store.getRun(Number(named)) : null;
|
|
1341
1153
|
const runTask = run === null ? null : store.externalIdFor(run.taskRef);
|
|
@@ -1697,7 +1509,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1697
1509
|
const resolved = store.resolveIncident(id, who.name, now);
|
|
1698
1510
|
if (!resolved)
|
|
1699
1511
|
return refuse(response, who, 409, "already resolved, or never open");
|
|
1700
|
-
const back = body.get("return") === "inbox" ? "/
|
|
1512
|
+
const back = body.get("return") === "inbox" ? "/" : openRow === undefined ? "/" : taskHref(openRow.taskId);
|
|
1701
1513
|
return redirect(response, back);
|
|
1702
1514
|
}
|
|
1703
1515
|
/** The compact recent-runs list for the master pane. */
|
|
@@ -1823,10 +1635,10 @@ export function createTasksHandlers(runtime) {
|
|
|
1823
1635
|
function taskMutation(response, who, taskId, verb, posted, now) {
|
|
1824
1636
|
const ref = store.lookupRef(taskId);
|
|
1825
1637
|
if (ref === null || store.getTask(taskId) === null) {
|
|
1826
|
-
return refuse(response, who, 404, "no such task", "/
|
|
1638
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
1827
1639
|
}
|
|
1828
1640
|
if (!visible(ref.repo)) {
|
|
1829
|
-
return refuse(response, who, 404, "no such task", "/
|
|
1641
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
1830
1642
|
}
|
|
1831
1643
|
switch (verb) {
|
|
1832
1644
|
case "steer": {
|
|
@@ -2091,7 +1903,7 @@ export function createTasksHandlers(runtime) {
|
|
|
2091
1903
|
return taskScreen(response, who, taskId, "Retried. The note wasn't saved; add it under Steering.", 409);
|
|
2092
1904
|
}
|
|
2093
1905
|
// Allow-listed return only — never an arbitrary URL from the form.
|
|
2094
|
-
return redirect(response, body.get("return") === "inbox"
|
|
1906
|
+
return redirect(response, body.get("return") === "inbox" || body.get("return") === "next" ? "/" : taskHref(taskId));
|
|
2095
1907
|
}
|
|
2096
1908
|
case "follow-up": {
|
|
2097
1909
|
const body = readForm(posted, CONSOLE_FORMS.taskFollowUp);
|
|
@@ -2459,7 +2271,7 @@ export function createTasksHandlers(runtime) {
|
|
|
2459
2271
|
const body = readForm(posted, CONSOLE_FORMS.taskApprove);
|
|
2460
2272
|
const requestedReturn = body.get("return");
|
|
2461
2273
|
const approvalBack = requestedReturn === "next"
|
|
2462
|
-
? "/
|
|
2274
|
+
? "/"
|
|
2463
2275
|
: requestedReturn === null
|
|
2464
2276
|
? taskHref(taskId)
|
|
2465
2277
|
: safeChatReturn(requestedReturn);
|
|
@@ -2960,6 +2772,8 @@ export function createTasksHandlers(runtime) {
|
|
|
2960
2772
|
timing: timingFor(store, run.id, row.taskId),
|
|
2961
2773
|
// How far its checks reached (check-access.ts), display only.
|
|
2962
2774
|
checkAccess: store.runCheckAccess(run.id),
|
|
2775
|
+
// How its checks actually ran, by what was stamped at launch: said only for a result whose checks ran.
|
|
2776
|
+
checkRan: store.artifactsFor(run.id).some(one => one.kind === "check-log") ? ranWords(runCheckStamp(store, run.id)) : null,
|
|
2963
2777
|
reviewRetry: store.reviewRetryStateOf(run.id),
|
|
2964
2778
|
review: reviewFactsFor(run.id),
|
|
2965
2779
|
notes: store.notesForRun(run.id),
|
|
@@ -3119,14 +2933,17 @@ export function createTasksHandlers(runtime) {
|
|
|
3119
2933
|
"code.ship-preview": showCode("ship", true),
|
|
3120
2934
|
"code.other": showCode("unknown"),
|
|
3121
2935
|
// Retired into Tasks and the task page (src/design/KIT.md): each old address still answers, as a redirect.
|
|
3122
|
-
|
|
3123
|
-
"
|
|
3124
|
-
"
|
|
3125
|
-
"
|
|
3126
|
-
"
|
|
3127
|
-
|
|
2936
|
+
// Retired pages (docs/plans/plain-toolroll.md P1.4): permanent redirects, no page handler of their own.
|
|
2937
|
+
"inbox": async (ctx) => retiredRedirect("inbox", ctx.url, ctx.response),
|
|
2938
|
+
"work": async (ctx) => retiredRedirect("work", ctx.url, ctx.response),
|
|
2939
|
+
"next": async (ctx) => retiredRedirect("next", ctx.url, ctx.response),
|
|
2940
|
+
"board": async (ctx) => retiredRedirect("board", ctx.url, ctx.response),
|
|
2941
|
+
// A result keeps its own page; the bare list moved to Tasks' Ready view.
|
|
2942
|
+
"review": async (ctx) => ctx.url.searchParams.has("result") ? showReview(ctx) : retiredRedirect("review", ctx.url, ctx.response),
|
|
2943
|
+
"done": async (ctx) => retiredRedirect("done", ctx.url, ctx.response),
|
|
3128
2944
|
"tasks": showTasks,
|
|
3129
|
-
"queue":
|
|
2945
|
+
"queue": async (ctx) => retiredRedirect("queue", ctx.url, ctx.response),
|
|
2946
|
+
"n.task": showNumberedTask,
|
|
3130
2947
|
"tasks.new": showNewTask,
|
|
3131
2948
|
"task.page": showTask,
|
|
3132
2949
|
"task.evidence": showTaskEvidence,
|
|
@@ -3147,6 +2964,9 @@ export function createTasksHandlers(runtime) {
|
|
|
3147
2964
|
"code.answer-send": codeAct("answer"),
|
|
3148
2965
|
"code.act-other": codeAct("unknown"),
|
|
3149
2966
|
"tasks.add": addTask,
|
|
2967
|
+
"tasks.fast": buildNow,
|
|
2968
|
+
"task.act.keep-grown": keepGrown,
|
|
2969
|
+
"task.act.follow-through": followThroughAct,
|
|
3150
2970
|
"queue.move": queueMove,
|
|
3151
2971
|
"queue.note": queueNote,
|
|
3152
2972
|
"decision.answer": answerDecision,
|
|
@@ -3208,7 +3028,115 @@ export function createTasksHandlers(runtime) {
|
|
|
3208
3028
|
const startsOn = draft?.values?.get("repo") ?? project ?? chrome.projects?.[0]?.path ?? null;
|
|
3209
3029
|
const found = startsOn === null ? null : runtime.firstTasks.get(startsOn)?.found ?? null;
|
|
3210
3030
|
const ideas = found ?? (runtime.firstRunStepsNow(ctx.now) !== null && !store.hasAnyWork() ? firstTaskSuggestions({ issues: [], todos: [] }) : []);
|
|
3211
|
-
return sendScreen(response, status, newTaskPage(chrome, project, revision, problem, chainable, store.permissionDefault().mode, store.qualityDefault().mode, chrome.projects ?? [], draft, text, ideas));
|
|
3031
|
+
return sendScreen(response, status, newTaskPage(chrome, project, revision, problem, chainable, store.permissionDefault().mode, store.qualityDefault().mode, chrome.projects ?? [], draft, text, ideas, ctx.url.searchParams.get("from")?.slice(0, 300) ?? null));
|
|
3032
|
+
}
|
|
3033
|
+
/** What the password rule reads for this request (approval-password.ts), the same facts the approve forms read. */
|
|
3034
|
+
function fastPasswordFacts(who) {
|
|
3035
|
+
return { passwordless: passwordlessSessionFor(store, who.name), localOwner: who.via === "cookie" && who.authority === "local-owner",
|
|
3036
|
+
freshAt: withFreshness("approve-standard", () => freshnessFor(who.name, Date.now(), true))?.at ?? null, returnTo: "/tasks/new" };
|
|
3037
|
+
}
|
|
3038
|
+
/** Build: the fast start's one write. Every check is made again inside fastStartBuild's transaction. */
|
|
3039
|
+
async function buildNow(ctx) {
|
|
3040
|
+
const { who, response, now, posted } = ctx;
|
|
3041
|
+
const body = readForm(posted, CONSOLE_FORMS.tasksFast);
|
|
3042
|
+
if (who.via !== "cookie")
|
|
3043
|
+
return refuse(response, who, 403, "Only a person starts a build this way.", "/tasks/new");
|
|
3044
|
+
const preview = body.get("preview") ?? "";
|
|
3045
|
+
const row = store.handle.prepare("SELECT repo, title FROM fast_preview WHERE id = ?").get(preview);
|
|
3046
|
+
const title = row === undefined ? "" : String(row["title"]);
|
|
3047
|
+
if (row !== undefined && !visible(String(row["repo"])))
|
|
3048
|
+
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
3049
|
+
const built = spendingOnlyOnSuccess(() => withFreshness("approve-standard", () => fastStartBuild(store, { preview, digest: body.get("digest") ?? "", actor: who.name, via: "console", password: fastPasswordFacts(who),
|
|
3050
|
+
approveNow: (taskId, sawDigest, fast) => approveScope(store, taskId, who.name, now, sawDigest, "", {}, fast) }, now)), result => !result.ok);
|
|
3051
|
+
if (!built.ok)
|
|
3052
|
+
return newTaskScreen(ctx, built.reason === "changed" ? 409 : 403, built.message, { title, goal: "", not: "", touches: "", acceptance: "" });
|
|
3053
|
+
const actionContext = requestContext.getStore();
|
|
3054
|
+
if (actionContext !== undefined)
|
|
3055
|
+
actionContext.createdTask = built.taskId;
|
|
3056
|
+
return redirect(response, taskHref(built.taskId));
|
|
3057
|
+
}
|
|
3058
|
+
/** "Approve it to keep it": a fast start that grew gets a plan for its exact commit, on the ordinary approval. It seals nothing. */
|
|
3059
|
+
async function keepGrown(ctx) {
|
|
3060
|
+
const { url, who, response, now, posted } = ctx;
|
|
3061
|
+
readForm(posted, CONSOLE_FORMS.keepGrown);
|
|
3062
|
+
const act = matchTaskPath(url.pathname, "/keep-grown$");
|
|
3063
|
+
if (act === null)
|
|
3064
|
+
return noPage(who, response);
|
|
3065
|
+
const kept = keepGrownChange(store, act.taskId, now);
|
|
3066
|
+
if (!kept.ok)
|
|
3067
|
+
return taskScreen(response, who, act.taskId, kept.message, 409);
|
|
3068
|
+
return redirect(response, taskHref(act.taskId));
|
|
3069
|
+
}
|
|
3070
|
+
/** Findings, a failed check's repair and evidence (render-follow-through.ts). Nothing here is an approval: a repair is a
|
|
3071
|
+
* draft, and its task page asks for the plan's approval as any other. */
|
|
3072
|
+
async function followThroughAct(ctx) {
|
|
3073
|
+
const { url, who, response, now, posted } = ctx;
|
|
3074
|
+
const body = readForm(posted, CONSOLE_FORMS.followThrough);
|
|
3075
|
+
const act = matchTaskPath(url.pathname, "/follow-through$");
|
|
3076
|
+
if (act === null)
|
|
3077
|
+
return noPage(who, response);
|
|
3078
|
+
const { taskId } = act;
|
|
3079
|
+
// Chat's gate, here too (findings.ts familyInReach): every project in this task's family, not only this page's.
|
|
3080
|
+
if (familyInWebReach(store, who, taskId, visible) === null)
|
|
3081
|
+
return refuse(response, who, 403, "That task is outside your access.", "/tasks");
|
|
3082
|
+
const number = (name) => Number(body.get(name) ?? "");
|
|
3083
|
+
const back = () => redirect(response, `${taskHref(taskId)}#findings`);
|
|
3084
|
+
const refused = (message) => taskScreen(response, who, taskId, message, 409);
|
|
3085
|
+
/** A finding or a run named by a form must belong to this task's family: this page's project was the one admitted. */
|
|
3086
|
+
const ownFinding = () => { const found = getFinding(store, number("finding")); return found !== null && found.familyRoot === familyRootOf(store, taskId) ? found : null; };
|
|
3087
|
+
const ownRun = () => runOnTask(store, number("run"), taskId);
|
|
3088
|
+
switch (body.get("act")) {
|
|
3089
|
+
case "send": {
|
|
3090
|
+
const line = body.get("line") ?? "";
|
|
3091
|
+
const pointer = body.get("pointer") ?? "";
|
|
3092
|
+
const sent = pointer !== "" ? sendReviewFinding(store, taskId, pointer, who.name, now)
|
|
3093
|
+
: sendFinding(store, { taskId, source: "person", by: who.name, summary: body.get("note") ?? "", file: body.get("file") || null, line: line === "" ? null : Number(line) }, now);
|
|
3094
|
+
return sent.ok ? back() : refused(sent.message);
|
|
3095
|
+
}
|
|
3096
|
+
case "resend": {
|
|
3097
|
+
if (ownFinding() === null)
|
|
3098
|
+
return refused("That finding isn't on this task.");
|
|
3099
|
+
const sent = resendFinding(store, number("finding"), now);
|
|
3100
|
+
return sent.ok ? back() : refused(sent.message);
|
|
3101
|
+
}
|
|
3102
|
+
case "close": {
|
|
3103
|
+
if (ownFinding() === null)
|
|
3104
|
+
return refused("That finding isn't on this task.");
|
|
3105
|
+
const closed = closeFinding(store, number("finding"), who.name, body.get("note") ?? "", now);
|
|
3106
|
+
return closed.ok ? back() : refused(closed.message);
|
|
3107
|
+
}
|
|
3108
|
+
case "repair": {
|
|
3109
|
+
if (!ownRun())
|
|
3110
|
+
return refused(NOT_ON_TASK);
|
|
3111
|
+
const started = startCheckRepair(store, evidenceRoot, number("run"), now);
|
|
3112
|
+
return started.ok ? redirect(response, taskHref(started.draft)) : refused(started.message);
|
|
3113
|
+
}
|
|
3114
|
+
case "save-evidence": {
|
|
3115
|
+
if (!ownRun())
|
|
3116
|
+
return refused(NOT_ON_TASK);
|
|
3117
|
+
const saved = saveEvidencePair(store, evidenceRoot, { taskId, runId: number("run"), by: who.name }, now);
|
|
3118
|
+
return saved.ok ? back() : refused(saved.message);
|
|
3119
|
+
}
|
|
3120
|
+
case "rerun-evidence": {
|
|
3121
|
+
const asked = rerunEvidence(store, taskId, who.name, now);
|
|
3122
|
+
return asked.ok ? back() : refused(asked.message);
|
|
3123
|
+
}
|
|
3124
|
+
case "failing-first-on":
|
|
3125
|
+
askFailingFirst(store, taskId, who.name, now);
|
|
3126
|
+
return back();
|
|
3127
|
+
case "failing-first-off":
|
|
3128
|
+
dropFailingFirst(store, taskId);
|
|
3129
|
+
return back();
|
|
3130
|
+
case "mark": {
|
|
3131
|
+
const capture = providerCapture(store, number("capture"));
|
|
3132
|
+
const label = body.get("label");
|
|
3133
|
+
if (capture === null || !runOnTask(store, capture.run, taskId) || (label !== "exhausted" && label !== "not-exhausted"))
|
|
3134
|
+
return refused("That capture isn't on this task.");
|
|
3135
|
+
labelProviderCapture(store, capture.id, label, who.name);
|
|
3136
|
+
return back();
|
|
3137
|
+
}
|
|
3138
|
+
default: return refused("Reload the page and try again.");
|
|
3139
|
+
}
|
|
3212
3140
|
}
|
|
3213
3141
|
return { registrations, runListPane, peekFragment, taskMutation, queueRegionFor, ciFailingFor, completedRowFor, attemptRowFor, resultRowOf, reviewCockpitViewOf, decisionEvidence, runEvidence, sendArtifact, peekGuards, liveRunIds };
|
|
3214
3142
|
}
|
|
@@ -3221,7 +3149,7 @@ export function createTidyHandlers(runtime) {
|
|
|
3221
3149
|
const { url, who, response, now, project } = ctx;
|
|
3222
3150
|
const days = tidyDays(url.searchParams.get("days"));
|
|
3223
3151
|
if (days === null)
|
|
3224
|
-
return refuse(response, who, 400, "Choose a number of days from 1 to 365.", "/
|
|
3152
|
+
return refuse(response, who, 400, "Choose a number of days from 1 to 365.", "/tasks/archive");
|
|
3225
3153
|
const releases = releaseFactsOf(store);
|
|
3226
3154
|
const items = tidyPreview(store, now, { releases, days, access: workAccess() });
|
|
3227
3155
|
return sendScreen(response, 200, putAwayPage(chromeFor(project, "work"), { items, days, digest: tidyDigest(items, days), now,
|
|
@@ -3231,22 +3159,22 @@ export function createTidyHandlers(runtime) {
|
|
|
3231
3159
|
const { who, response, now, posted } = ctx;
|
|
3232
3160
|
const body = readForm(posted, CONSOLE_FORMS.tasksPutAway);
|
|
3233
3161
|
const days = tidyDays(body.get("days"));
|
|
3234
|
-
const back = (problem) => redirect(response, `/
|
|
3162
|
+
const back = (problem) => redirect(response, `/tasks/archive?${new URLSearchParams({ ...(days === null || days === TIDY_DEFAULT_DAYS ? {} : { days: String(days) }), problem })}`);
|
|
3235
3163
|
if (days === null)
|
|
3236
3164
|
return back("Choose a number of days from 1 to 365.");
|
|
3237
3165
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
3238
|
-
return back("Enter your Toolroll password to
|
|
3166
|
+
return back("Enter your Toolroll password to archive these.");
|
|
3239
3167
|
const done = applyTidy(store, now, { by: who.name, days, digest: body.get("digest") ?? "", only: body.getAll("task"), releases: releaseFactsOf(store), access: workAccess() });
|
|
3240
3168
|
if (!done.ok)
|
|
3241
3169
|
return back(done.message);
|
|
3242
3170
|
bustBadge();
|
|
3243
|
-
return redirect(response, "/
|
|
3171
|
+
return redirect(response, "/tasks?view=archived");
|
|
3244
3172
|
}
|
|
3245
3173
|
const oneTask = (kind) => async (ctx) => {
|
|
3246
3174
|
const { url, who, response, now, posted } = ctx;
|
|
3247
3175
|
const act = matchTaskPath(url.pathname, `/${kind}$`);
|
|
3248
3176
|
if (act === null)
|
|
3249
|
-
return refuse(response, who, 404, "There's no page at this address.", "/
|
|
3177
|
+
return refuse(response, who, 404, "There's no page at this address.", "/tasks");
|
|
3250
3178
|
const body = readForm(posted, kind === "archive" ? CONSOLE_FORMS.taskArchive : CONSOLE_FORMS.taskRestore);
|
|
3251
3179
|
const standing = (refs) => approverStandsNow(store, who.name, familyRepoOf(store, refs));
|
|
3252
3180
|
const done = kind === "archive"
|
|
@@ -3258,30 +3186,14 @@ export function createTidyHandlers(runtime) {
|
|
|
3258
3186
|
const to = safeReturn(body.get("return"));
|
|
3259
3187
|
return redirect(response, to === "/" ? taskHref(act.taskId) : to);
|
|
3260
3188
|
};
|
|
3261
|
-
return { "
|
|
3189
|
+
return { "tasks.archive": showPutAway, "work.put-away": async (ctx) => retiredRedirect("work.put-away", ctx.url, ctx.response),
|
|
3190
|
+
"work.put-away.apply": applyPutAway, "task.act.archive": oneTask("archive"), "task.act.restore": oneTask("restore") };
|
|
3262
3191
|
}
|
|
3263
3192
|
/** A retired page's GET alias: a plain 302 (the shared 303 helper belongs to POST landings), as pages.ts morningAlias. */
|
|
3264
3193
|
function redirectGet(response, to) {
|
|
3265
3194
|
response.writeHead(302, { ...SAFETY, Location: to });
|
|
3266
3195
|
response.end();
|
|
3267
3196
|
}
|
|
3268
|
-
/**
|
|
3269
|
-
* Where a retired Tasks address goes now (wave A, src/design/KIT.md): Done is Finished. `board` names where the old
|
|
3270
|
-
* Board's state view goes once it is retired (Tasks' Board view; its Order view and its live fragment stay). Null: this
|
|
3271
|
-
* address still answers itself.
|
|
3272
|
-
*/
|
|
3273
|
-
export function retiredTasksAddress(route, url) {
|
|
3274
|
-
// (The Board keeps its own page for now: Tasks' Board view is where the app links; see the wave A report.)
|
|
3275
|
-
const project = url.searchParams.get("project");
|
|
3276
|
-
const keep = project === null || url.searchParams.getAll("project").length !== 1 ? "" : `project=${encodeURIComponent(project)}&`;
|
|
3277
|
-
switch (route) {
|
|
3278
|
-
case "done": return `/work?${keep}view=completed`;
|
|
3279
|
-
case "board":
|
|
3280
|
-
if (url.searchParams.get("view") === "order" || url.searchParams.get("fragment") === "1")
|
|
3281
|
-
return null;
|
|
3282
|
-
return url.searchParams.get("scope") === "all" ? "/work?all=1&layout=board" : `/work?${keep}layout=board`;
|
|
3283
|
-
}
|
|
3284
|
-
}
|
|
3285
3197
|
/** How many cards a Board column shows; the column's count covers them all and its "View all" opens the list. */
|
|
3286
3198
|
export const BOARD_COLUMN_LIMIT = 40;
|
|
3287
3199
|
/**
|
|
@@ -3336,7 +3248,7 @@ export function createAgentSignalHandlers(runtime) {
|
|
|
3336
3248
|
* unchanged approve() (release-approval.ts approveReleaseCard).
|
|
3337
3249
|
*/
|
|
3338
3250
|
export function createReleaseSheetHandlers(runtime) {
|
|
3339
|
-
const { store, sendScreen, chromeFor, visible, mintApprovalNonce, clock } = runtime;
|
|
3251
|
+
const { store, sendScreen, chromeFor, visible, mintApprovalNonce, clock, authenticateApprover } = runtime;
|
|
3340
3252
|
const missing = (who, response) => refuse(response, who, 404, "There's no release waiting for approval at this address.", "/work");
|
|
3341
3253
|
/** The sheet, with a problem said where it happened. */
|
|
3342
3254
|
function sheetScreen(ctx, taskId, problem, status) {
|
|
@@ -3361,7 +3273,7 @@ export function createReleaseSheetHandlers(runtime) {
|
|
|
3361
3273
|
const gate = store.approvalGate(taskId, who.name, "person");
|
|
3362
3274
|
return freshlyApprovable(store, taskId, card.scope, gate.verdict === "seal" && !gate.protectedWork);
|
|
3363
3275
|
})();
|
|
3364
|
-
return sendScreen(response, status, releaseSheetPage(chromeFor(project ?? ref.repo, "work"), { card, nonce, facts: approvalFactsNow({ remembered }), local, problem }));
|
|
3276
|
+
return sendScreen(response, status, releaseSheetPage(chromeFor(project ?? ref.repo, "work"), { card, nonce, facts: approvalFactsNow({ remembered }), local, problem, held: who.role === "approver" ? autoPublishHeld(store, taskId) : null }));
|
|
3365
3277
|
}
|
|
3366
3278
|
async function showSheet(ctx) {
|
|
3367
3279
|
const task = matchTaskPath(ctx.url.pathname, "/release$");
|
|
@@ -3394,5 +3306,22 @@ export function createReleaseSheetHandlers(runtime) {
|
|
|
3394
3306
|
return sheetScreen(ctx, taskId, refused.message, 403);
|
|
3395
3307
|
return sheetScreen(ctx, taskId, approveRefusalWords(refused.reason), refused.reason === "changed" || refused.reason === "unrouted" ? 409 : 403);
|
|
3396
3308
|
}
|
|
3397
|
-
|
|
3309
|
+
/** A held automatic release goes on: a person only, proved now. The watch then resumes it (release-next.ts). */
|
|
3310
|
+
async function goOn(ctx) {
|
|
3311
|
+
const { who, url, posted, now, response } = ctx;
|
|
3312
|
+
const body = readForm(posted, CONSOLE_FORMS.releaseGoOn);
|
|
3313
|
+
const task = matchTaskPath(url.pathname, "/release/go-on$");
|
|
3314
|
+
if (task === null || who.via !== "cookie")
|
|
3315
|
+
return missing(who, response);
|
|
3316
|
+
const ref = store.lookupRef(task.taskId);
|
|
3317
|
+
if (ref === null || !visible(ref.repo))
|
|
3318
|
+
return missing(who, response);
|
|
3319
|
+
if (!authenticateApprover(who, body.get("password") ?? "", ref.repo).ok)
|
|
3320
|
+
return sheetScreen(ctx, task.taskId, "Enter your Toolroll password to let this release go on.", 403);
|
|
3321
|
+
const allowed = allowAutoPublish(store, { taskId: task.taskId, by: who.name, now });
|
|
3322
|
+
if (!allowed.ok)
|
|
3323
|
+
return sheetScreen(ctx, task.taskId, allowed.why, 409);
|
|
3324
|
+
return redirect(response, releaseSheetHref(task.taskId));
|
|
3325
|
+
}
|
|
3326
|
+
return { routes: { "task.release": showSheet, "task.release.go-on": goOn, "release.locate": locate }, sheetScreen, approveFromSheet };
|
|
3398
3327
|
}
|