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/operate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runCardCommand } from "./card-cli.js";
|
|
1
2
|
import { mintSecret } from "./secret-mint.js";
|
|
2
3
|
import { ACP_AGENTS, ACP_PROVIDER_IDS, isAcpProvider, isKeyedProvider } from "./acp-agents.js";
|
|
3
4
|
import { inspectAcpAgent } from "./acp-inspect.js";
|
|
@@ -21,7 +22,7 @@ import { parseProtectedPaths } from "./approval-policy.js";
|
|
|
21
22
|
import { rulesSummary } from "./approval-rules-ui.js";
|
|
22
23
|
import { evidencePack, exportDay, ledgerExportChunks, standaloneEvidenceHtml } from "./evidence-pack.js";
|
|
23
24
|
import { matchesOutsideCheckpoint } from "./ledger-chain.js";
|
|
24
|
-
import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, parseCleanup, storageReport } from "./storage.js";
|
|
25
|
+
import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, idleCleanupWords, idleWords, parseCleanup, parseIdle, storageReport } from "./storage.js";
|
|
25
26
|
import { removeStaleTestTemp, tempRoots, testTempFolders } from "./test-temp.js";
|
|
26
27
|
import { storageSweepSupervisor } from "./storage-sweep-process.js";
|
|
27
28
|
import { idleStorageSweep, sweepSchedule, sweepScheduleWords } from "./storage-sweep-schedule.js";
|
|
@@ -67,6 +68,9 @@ import { releaseCheckPurposeFor, releaseTermsFor } from "./release-carry.js";
|
|
|
67
68
|
import { approveReleaseCard, journalMatchesPlane, releaseApprovalCard, releaseCardLines, releaseTaskOf, waitingJournals } from "./release-approval.js";
|
|
68
69
|
import { journalFile } from "./release.js";
|
|
69
70
|
import { refreshTrustedMains, settleReleaseChecks } from "./nightly-check.js";
|
|
71
|
+
import { refreshCodeMaps } from "./code-map-build.js";
|
|
72
|
+
import { sweepDeliveries } from "./code-map-admission.js";
|
|
73
|
+
import { runMapCommand } from "./code-map-cli.js";
|
|
70
74
|
import { githubNightlyRepos, readGithubNightlies } from "./nightly-github.js";
|
|
71
75
|
import { exec as releaseExec, githubReads } from "./release-adapters.js";
|
|
72
76
|
import { releaseTools } from "./release-git.js";
|
|
@@ -76,11 +80,17 @@ import { followTeams } from "./teams.js";
|
|
|
76
80
|
import { followSlack } from "./slack.js";
|
|
77
81
|
import { validateScopeText } from "./task-text.js";
|
|
78
82
|
import { runMemoryCommand } from "./memory-cli.js";
|
|
83
|
+
import { runFlakyCommand } from "./flaky-cli.js";
|
|
84
|
+
import { backfillMemoryUse } from "./memory-use.js";
|
|
85
|
+
import { scheduledTidy } from "./memory-tidy.js";
|
|
86
|
+
import { CI_READ_EVERY_MS, ciCursorKey, ciRepositoriesOf, observeCiAttempts } from "./flaky-ci.js";
|
|
79
87
|
import { runKnowledgeApply, runKnowledgeCommand } from "./knowledge-cli.js";
|
|
80
88
|
import { FLOWS_VALUE_FLAGS, runFlowsCommand } from "./flows-cli.js";
|
|
81
89
|
import { runAssignmentCommand } from "./assignment-adapters.js";
|
|
82
90
|
import { applyProjectProfile, runProjectCommand } from "./project-cli.js";
|
|
83
91
|
import { pullRequestLines, runTaskMergeCommand, runTaskOutcomeCommand } from "./task-outcome-cli.js";
|
|
92
|
+
import { withTaskAlias } from "./task-aliases.js";
|
|
93
|
+
import { checklistLines, mergeChecklistOf } from "./merge-checklist.js";
|
|
84
94
|
import { taskReviewBrief, renderReviewBrief } from "./task-review-brief.js";
|
|
85
95
|
/**
|
|
86
96
|
* The commands that actually move work: authoring tasks, and the claim loop.
|
|
@@ -112,10 +122,10 @@ import { homedir, hostname } from "node:os";
|
|
|
112
122
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
113
123
|
import { releaseStalledUpdate, waitingUpdate } from "./toolroll-update.js";
|
|
114
124
|
import { dur } from "./design/kit.js";
|
|
115
|
-
import { openStore, openStoreNoMigrate, initLocalMode, enrollOwnerPassword, installationCreatedBy, laptopPortOf, recordLaptopPort, localModeOf, localOwnerBinding, archiveTaskFamily, restoreTaskFamily, shipChecksForTask, elapsedSeconds, CONCURRENT_WRITER_WAIT_MS, isDatabaseBusy, databasePath, BUILT_IN, parseCapabilityKey, verifiedAuthor, isDigestTime, predecessorWatches, RESULT_SCREENSHOTS, lifecycleOf, reportLifecycle, beatLifecycle, } from "./store.js";
|
|
125
|
+
import { checkoutIdle, setCheckoutIdle, openStore, openStoreNoMigrate, initLocalMode, enrollOwnerPassword, installationCreatedBy, laptopPortOf, recordLaptopPort, localModeOf, localOwnerBinding, archiveTaskFamily, restoreTaskFamily, shipChecksForTask, elapsedSeconds, CONCURRENT_WRITER_WAIT_MS, isDatabaseBusy, databasePath, BUILT_IN, parseCapabilityKey, verifiedAuthor, isDigestTime, predecessorWatches, RESULT_SCREENSHOTS, lifecycleOf, reportLifecycle, beatLifecycle, } from "./store.js";
|
|
116
126
|
import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
117
127
|
import { claimInstance, clearInstance, openInstanceSocket } from "./console-instance.js";
|
|
118
|
-
import { backgroundOff, backgroundOn, backgroundShow, laptopStatus, laptopStop } from "./laptop-background.js";
|
|
128
|
+
import { backgroundOff, backgroundOn, backgroundShow, laptopStatus, laptopStop, restartOwnBuilder } from "./laptop-background.js";
|
|
119
129
|
import { authorizePlanUnderMode } from "./plan-auto.js";
|
|
120
130
|
import { ghDispatchAdapter, mirrorTaskId, syncPass } from "./sync.js";
|
|
121
131
|
import { LIVE_FILE_BOUND, LIVE_RETAIN_MS, sweepLiveLogs } from "./live.js";
|
|
@@ -185,7 +195,7 @@ function planContractLines(store, evidenceRoot, taskId) {
|
|
|
185
195
|
...describeContractChanges(record.changes).map(line => ` ${line}`),
|
|
186
196
|
];
|
|
187
197
|
}
|
|
188
|
-
import { verdictWords as proofVerdictWords, matrixWords, semanticCoverage, coverageWords } from "./proof.js";
|
|
198
|
+
import { verdictWords as proofVerdictWords, matrixWords, plainReasonWords, semanticCoverage, coverageWords } from "./proof.js";
|
|
189
199
|
import { probeRepo } from "./probe.js";
|
|
190
200
|
import { diagnoseTaskDispatch, diagnosisIsDispatchable, scopeApprovedForDispatch, } from "./dispatch.js";
|
|
191
201
|
import { createDecisionServer } from "./serve.js";
|
|
@@ -233,10 +243,24 @@ import { createScheduledFlow, describeSchedule, parseSchedule } from "./flow-sch
|
|
|
233
243
|
import { fileTaskProposal, validateTaskText } from "./proposal.js";
|
|
234
244
|
import { TEMPLATES, templateByName } from "./templates.js";
|
|
235
245
|
import { isDirectChatProvider, isSubscriptionChatProvider, priceOf, PRICED_MODELS } from "./converse.js";
|
|
246
|
+
import { clearFallbackChain, dispatchEntryOf, entryMovesOnWords, entryWords, fallbackSettingOf, parseEntry, reconcileWalks, setFallbackChain, setPaidGrant } from "./fallback.js";
|
|
236
247
|
import { resolvePhaseAgent, resolveScopeProfile, resolveRouteCandidates, routeOfTask, INSTALLATION_SCOPE } from "./agentconfig.js";
|
|
237
248
|
import { applyModelChoices, stateDigest } from "./model-choices.js";
|
|
238
249
|
import { isTaskSize, legOf, projectRoute, routeDigestOf, routeWords, TASK_SIZES, PHASES as ROUTE_PHASES } from "./phase-routing.js";
|
|
239
250
|
import { observeProviderReadiness, reportProviderReadinessAuthed } from "./runner.js";
|
|
251
|
+
import { retryVanishedWhenStopped } from "./vanished-retry.js";
|
|
252
|
+
import { requestMissingRechecks } from "./check-only.js";
|
|
253
|
+
import { checkRepairOfRun, resumeParkedRepairs, startCheckRepair } from "./check-repair.js";
|
|
254
|
+
import { capturesCommand, evidenceCommand, failingFirstCommand, findingCommand, handoverCommand } from "./follow-through-cli.js";
|
|
255
|
+
import { recordBuilderWish } from "./builder-restart.js";
|
|
256
|
+
import { expireFastStarts, fastRefusalWords, fastStartBuild, keepGrownChange, prepareFastStart } from "./fast-start.js";
|
|
257
|
+
import { fastTermsLines } from "./fast-terms.js";
|
|
258
|
+
import { resumeAllowedReleases } from "./release-next.js";
|
|
259
|
+
import { fileGithubItem, readGithubItem } from "./github-import.js";
|
|
260
|
+
import { runRelease } from "./release.js";
|
|
261
|
+
import { releaseAdapters } from "./release-adapters.js";
|
|
262
|
+
import { routeChangeOf, routeLegacyPlans } from "./route-change.js";
|
|
263
|
+
import { passwordlessOn } from "./passwordless.js";
|
|
240
264
|
import { parseDemoUrl, projectDemoUrl, saveProjectDemo } from "./project-demo.js";
|
|
241
265
|
import { effectiveConcurrency, maySlotTake, parseProjectConcurrency, PROJECT_CONCURRENCY_DEFAULT, ProjectPasses, projectConcurrency, saveProjectConcurrency, savedProjectConcurrency } from "./project-concurrency.js";
|
|
242
266
|
import { activeWebhookTargets, webhookPass, LEGACY_WEBHOOK_WARNING, loadWebhookTargets, effectivePrimary, isMessagingChannel, loadConsoleUrl, phoneOrigin, readUpConsoleUrl, saveConsoleUrl, savePrimary, UP_CONSOLE_FILE } from "./webhooks.js";
|
|
@@ -301,6 +325,8 @@ export const OPERATE_HELP = `toolroll — operating the queue
|
|
|
301
325
|
toolroll project activity --json read the current project supervision report
|
|
302
326
|
toolroll project rules --repo <p> a project's approval rules; an instance operator changes them with
|
|
303
327
|
--not-requester on|off, --protect none|project, --protect-paths "a/**,b/**"
|
|
328
|
+
toolroll project fallback --repo <p> [--agents provider:model:plan|api-key,…] [--clear] [--allow-paid|--no-paid]
|
|
329
|
+
Which agents take over when one runs out of its plan. New tasks seal the chain; a paid agent also needs --allow-paid.
|
|
304
330
|
toolroll project network --repo <p> [--allow h,…] [--remove h,…] [--open|--allowlist] [--local any|none]
|
|
305
331
|
what the project's builds may reach on the network; an approver changes it
|
|
306
332
|
(new scopes seal it; approved ones keep theirs)
|
|
@@ -320,10 +346,16 @@ export const OPERATE_HELP = `toolroll — operating the queue
|
|
|
320
346
|
the project's own demo or dev server, the one non-public address a
|
|
321
347
|
report's scout may screenshot
|
|
322
348
|
toolroll task evidence <id> the task's evidence pack as JSON (--html for a printable page; --out <file>)
|
|
349
|
+
toolroll map [<project>] [--json] a project's code map, made by Toolroll from main: areas and how they use each other
|
|
350
|
+
toolroll map area <id> | file <path> | search <words> [--cursor <c>]
|
|
351
|
+
one area, one file, or files and exported names; --cursor reads the next page of the same version
|
|
352
|
+
toolroll map refresh ask for a check of main now (an approver)
|
|
323
353
|
toolroll storage where the disk goes: database, build checkouts, releases, evidence, leftover test temp folders
|
|
324
354
|
toolroll storage clean [--yes] preview removing finished tasks' clean checkouts (their branches stay) and test temp folders older than a day; --yes removes them
|
|
325
355
|
toolroll storage clean --landed [--yes] preview work that landed in main over a week ago: marks its results complete, removes their clean checkouts and local branches; --yes does it (instance operator)
|
|
326
|
-
toolroll storage cleanup <when> remove a finished task's clean
|
|
356
|
+
toolroll storage cleanup <when> remove a finished task's clean folder: finished|2d|7d|never (instance operator)
|
|
357
|
+
toolroll storage cleanup idle <days>
|
|
358
|
+
remove an idle task's clean folder: 7|14|30|never (instance operator)
|
|
327
359
|
toolroll storage discard <path> --yes throw away a checkout kept for its changes; its branch stays
|
|
328
360
|
toolroll integrations [--json] which integrations work: Connected, Not set up or Broken, and what to do (--saved: no new checks)
|
|
329
361
|
toolroll monitoring where the audit stream and traces go, and how each destination is doing
|
|
@@ -688,10 +720,10 @@ Exit codes
|
|
|
688
720
|
* (surface.ts) is tested for exact equality against them.
|
|
689
721
|
*/
|
|
690
722
|
export const TASK_ACTIONS = [
|
|
691
|
-
"add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
|
|
723
|
+
"add", "new", "keep", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
|
|
692
724
|
"reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan",
|
|
693
725
|
"review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask",
|
|
694
|
-
"checks", "add-tests", "archive", "restore", "tidy", "ship",
|
|
726
|
+
"checks", "add-tests", "archive", "restore", "tidy", "ship", "ready", "handover", "before-after", "failing-first",
|
|
695
727
|
];
|
|
696
728
|
export const PUBLISH_ACTIONS = ["setup", "grant", "revoke", "status", "unblock", "rearm", "merge", "refire"];
|
|
697
729
|
export const CONFIG_ACTIONS = ["show", "set", "clear"];
|
|
@@ -706,11 +738,11 @@ export const KEYS_ACTIONS = ["status", "set", "clear", "verify", "auth"];
|
|
|
706
738
|
/** Flags whose value is a secret someone types or pastes: taken as the next argument even when it starts with "--". */
|
|
707
739
|
export const SECRET_VALUE_FLAGS = new Set(["token", "password"]);
|
|
708
740
|
export const OPERATE_VALUE_FLAGS = new Set([
|
|
709
|
-
"key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend",
|
|
710
|
-
"allow", "selector", "paths", "credentials", "repo", "token", "capacity",
|
|
741
|
+
"key", "db", "runner", "ttl", "state", "flow", "on", "reason", "until", "id", "backend", "build", "from",
|
|
742
|
+
"allow", "selector", "paths", "credentials", "repo", "token", "capacity", "agents",
|
|
711
743
|
"goal", "not", "touches", "acceptance", "candidate", "by", "digest", "as", "branch", "pool", "base", "model", "turns",
|
|
712
744
|
"max", "cap", "probe", "kind", "expires", "cmd", "since", "repair-model",
|
|
713
|
-
"choose", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
|
|
745
|
+
"choose", "choice", "claim", "revision", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
|
|
714
746
|
"for", "tick-every", "bridge-every", "reconcile-every", "incarnation",
|
|
715
747
|
"say", "ceiling-usd", "cap-usd",
|
|
716
748
|
// evals: the cases a diagnostic campaign runs, its k, an attempt's world and what its builds may not read, and the
|
|
@@ -730,7 +762,7 @@ export const OPERATE_VALUE_FLAGS = new Set([
|
|
|
730
762
|
"command", "timeout-seconds", "setup-digest", "stop-grace", "title", "name", "every", "lines",
|
|
731
763
|
"label", "reviewers", "limit", "role", "key-file", "weekly-usd", "daily-turns", "per-hour", "token-file", "budget-usd", "build-usd", "sync-max-age", "merge-method",
|
|
732
764
|
"phase", "tier", "clear-phase", "size", "risky",
|
|
733
|
-
"run", "containment", "agent",
|
|
765
|
+
"run", "containment", "agent", "path", "line", "action", "from-review", "from-check", "task",
|
|
734
766
|
// onboard: the starter flows to switch on.
|
|
735
767
|
"starter",
|
|
736
768
|
// flows: templates, steps, scripts and cards.
|
|
@@ -754,8 +786,14 @@ export const OPERATE_VALUE_FLAGS = new Set([
|
|
|
754
786
|
"read-deny", "mach",
|
|
755
787
|
// task scope --release <version>:<pr>:<branch>: a release scope's terms (release-once.md).
|
|
756
788
|
"release",
|
|
789
|
+
// task accept --despite "<reason>": acceptance with a reason, never a completion.
|
|
790
|
+
"despite",
|
|
757
791
|
]);
|
|
758
792
|
export const OPERATE_BOOLEAN_FLAGS = new Set([
|
|
793
|
+
// task accept --finish: the person's Accept, exactly task complete's exact-receipt completion.
|
|
794
|
+
"finish",
|
|
795
|
+
// task new --preview: the terminal UI's own, the exact terms with nothing started.
|
|
796
|
+
"preview",
|
|
759
797
|
// evals run: a report-only campaign; keep each attempt's world for a look afterwards.
|
|
760
798
|
"diagnostic", "keep",
|
|
761
799
|
// task scope --release ... --no-carry: every later commit asks again.
|
|
@@ -767,6 +805,8 @@ export const OPERATE_BOOLEAN_FLAGS = new Set([
|
|
|
767
805
|
"html", "csv", "alerts-only",
|
|
768
806
|
// project network: what its builds may reach (--open | --allowlist).
|
|
769
807
|
"open", "allowlist",
|
|
808
|
+
// project fallback: whether paid fallback agents may spend.
|
|
809
|
+
"allow-paid", "no-paid",
|
|
770
810
|
// v105: the full export.
|
|
771
811
|
"zip",
|
|
772
812
|
// Settings → Integrations: the last checks, without checking again.
|
|
@@ -847,6 +887,9 @@ export function parseOperateArgs(argv, ownValues = new Set()) {
|
|
|
847
887
|
}
|
|
848
888
|
/** Route a native command and return its exit code. Its password source cannot be selected by an HTTP request. */
|
|
849
889
|
export async function runOperate(command, argv, write, options = {}) {
|
|
890
|
+
// People's names for task commands (task-aliases.ts) are the commands they name, before any rule reads the command.
|
|
891
|
+
if (command === "task")
|
|
892
|
+
argv = withTaskAlias(["task", ...argv]).slice(1);
|
|
850
893
|
// Each command gets its own remembered-login holder (Passwordless: "cli-login" is this command's, never the next one's).
|
|
851
894
|
return withPasswordSource(CLI_PASSWORD_SOURCE, () => rememberedLogin.run({ login: null }, () => runOperateLocal(command, argv, write, options)));
|
|
852
895
|
}
|
|
@@ -1062,6 +1105,8 @@ export async function runOperateAs(argv, opts) {
|
|
|
1062
1105
|
const source = opts.source === "mcp" ? "mcp" : "api";
|
|
1063
1106
|
const options = opts.options ?? {};
|
|
1064
1107
|
const now = options.now ?? new Date();
|
|
1108
|
+
// People's names for task commands are the commands they name, before the remote policy reads the command.
|
|
1109
|
+
argv = [...withTaskAlias(argv)];
|
|
1065
1110
|
const [root = "", ...rest] = argv;
|
|
1066
1111
|
const json = rest.includes("--json");
|
|
1067
1112
|
let invocation = root;
|
|
@@ -1277,6 +1322,8 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1277
1322
|
return projectConcurrencyCommand(positional, flags, context);
|
|
1278
1323
|
if (positional[0] === "network")
|
|
1279
1324
|
return projectNetworkCommand(positional, flags, context);
|
|
1325
|
+
if (positional[0] === "fallback")
|
|
1326
|
+
return projectFallbackCommand(positional, flags, context);
|
|
1280
1327
|
if (positional[0] === "writes")
|
|
1281
1328
|
return projectWritesCommand(positional, flags, context);
|
|
1282
1329
|
if (positional[0] === "check-access")
|
|
@@ -1305,6 +1352,8 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1305
1352
|
const verified = acting === null || lead ? null : authenticateApprover(context.store, acting.name, acting.token, repo === undefined ? null : resolve(repo));
|
|
1306
1353
|
return runKnowledgeApply(flags, { ...context, now: context.clock(), actor: verified !== null && verified.ok ? acting.name : null });
|
|
1307
1354
|
}
|
|
1355
|
+
case "map":
|
|
1356
|
+
return mapCommand(positional, flags, context);
|
|
1308
1357
|
case "models": {
|
|
1309
1358
|
// A local owner's lowering act (models turn-off / dismiss, through the terminal UI) asks for no credential: the
|
|
1310
1359
|
// rule door re-checks the owner and its exact act itself.
|
|
@@ -1325,10 +1374,19 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1325
1374
|
const acting = await askCredentials(flags, context);
|
|
1326
1375
|
if (memoryWriteVerb(positional) && acting !== null && parseLeadToken(acting.token) !== null)
|
|
1327
1376
|
return fail(context.write, context.json, `memory ${positional[0]}`, "unauthenticated", MEMORY_WRITE_REFUSAL, EXIT.refused);
|
|
1328
|
-
|
|
1329
|
-
const
|
|
1377
|
+
// The laptop's local owner: only the rows LOCAL_ALLOW names reach here, and each writer checks the exact act.
|
|
1378
|
+
const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
|
|
1379
|
+
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
1330
1380
|
return runMemoryCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], configDir: dirname(context.databaseFile), ...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }) });
|
|
1331
1381
|
}
|
|
1382
|
+
case "card":
|
|
1383
|
+
return cardCommand(positional, flags, context);
|
|
1384
|
+
case "flaky":
|
|
1385
|
+
return flakyCommand(positional, flags, context);
|
|
1386
|
+
case "finding":
|
|
1387
|
+
return findingCommand(positional, flags, followThroughContext(context, flags));
|
|
1388
|
+
case "captures":
|
|
1389
|
+
return capturesCommand(positional, followThroughContext(context, flags));
|
|
1332
1390
|
case "claim":
|
|
1333
1391
|
return claimCommand(positional, flags, context);
|
|
1334
1392
|
case "heartbeat":
|
|
@@ -1764,7 +1822,7 @@ async function runnerCommand(positional, flags, context) {
|
|
|
1764
1822
|
// at the mint. A caller flag never grants what the mint did not.
|
|
1765
1823
|
const acting = await askCredentials(flags, context);
|
|
1766
1824
|
if (acting === null) {
|
|
1767
|
-
return fail(write, json, "runner register", "usage", "`runner register <name> --repo <path> --as <you> --token <t>` —
|
|
1825
|
+
return fail(write, json, "runner register", "usage", "`runner register <name> --repo <path> --as <you> --token <t>` — adding a builder is an approver's act", EXIT.usage);
|
|
1768
1826
|
}
|
|
1769
1827
|
const authenticated = credentialApprover(store, acting.name, acting.token);
|
|
1770
1828
|
if (!authenticated.ok)
|
|
@@ -1864,7 +1922,7 @@ async function runnerCommand(positional, flags, context) {
|
|
|
1864
1922
|
const acting = await askCredentials(flags, context);
|
|
1865
1923
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
1866
1924
|
if (acting === null || !verified?.ok)
|
|
1867
|
-
return refuseApprover(context, "runner retire", verified, "`runner retire <name> --as <you> --token <t>` —
|
|
1925
|
+
return refuseApprover(context, "runner retire", verified, "`runner retire <name> --as <you> --token <t>` — retiring a builder is an approver's act", name);
|
|
1868
1926
|
const retired = store.retireRunner(name, now, mutationFrom(flags, now));
|
|
1869
1927
|
if (!retired)
|
|
1870
1928
|
return fail(write, json, "runner retire", "unknown", `no runner \`${name}\``, EXIT.refused);
|
|
@@ -1880,7 +1938,7 @@ async function runnerCommand(positional, flags, context) {
|
|
|
1880
1938
|
// stated authority, never accumulation nobody can read back.
|
|
1881
1939
|
const acting = await askCredentials(flags, context);
|
|
1882
1940
|
if (acting === null) {
|
|
1883
|
-
return fail(write, json, "runner bind", "usage", "`runner bind <name> --repo <path> --as <you> --token <t>` —
|
|
1941
|
+
return fail(write, json, "runner bind", "usage", "`runner bind <name> --repo <path> --as <you> --token <t>` — choosing a builder's projects is an approver's act", EXIT.usage);
|
|
1884
1942
|
}
|
|
1885
1943
|
const authenticated = credentialApprover(store, acting.name, acting.token);
|
|
1886
1944
|
if (!authenticated.ok)
|
|
@@ -1960,7 +2018,7 @@ async function coordinatorCommand(positional, flags, context) {
|
|
|
1960
2018
|
}
|
|
1961
2019
|
const acting = await askCredentials(flags, context);
|
|
1962
2020
|
if (acting === null) {
|
|
1963
|
-
return fail(write, json, "coordinator mint", "usage", "`coordinator mint <name> --repo <path> --as <you> --token <t>` — minting
|
|
2021
|
+
return fail(write, json, "coordinator mint", "usage", "`coordinator mint <name> --repo <path> --as <you> --token <t>` — minting your lead's credential is an approver's act", EXIT.usage);
|
|
1964
2022
|
}
|
|
1965
2023
|
const authenticated = credentialApprover(store, acting.name, acting.token);
|
|
1966
2024
|
if (!authenticated.ok)
|
|
@@ -2007,7 +2065,7 @@ async function coordinatorCommand(positional, flags, context) {
|
|
|
2007
2065
|
const acting = await askCredentials(flags, context);
|
|
2008
2066
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
2009
2067
|
if (acting === null || !verified?.ok)
|
|
2010
|
-
return refuseApprover(context, "coordinator revoke", verified, "`coordinator revoke <cid> --as <you> --token <t>` — revoking is an
|
|
2068
|
+
return refuseApprover(context, "coordinator revoke", verified, "`coordinator revoke <cid> --as <you> --token <t>` — revoking is an approver's act", name);
|
|
2011
2069
|
const revoked = revokeCoordinator(store, name, acting.name, now);
|
|
2012
2070
|
if (!revoked.ok) {
|
|
2013
2071
|
const said = revoked.reason === "unknown" ? `no coordinator \`${name}\` — \`coordinator list\` shows cids` : "already revoked";
|
|
@@ -2535,6 +2593,8 @@ async function tickCommand(flags, context) {
|
|
|
2535
2593
|
const ready = store.listReady(clock(), runner);
|
|
2536
2594
|
const considered = ready.length;
|
|
2537
2595
|
const dispatched = [];
|
|
2596
|
+
// Every walk settled before anything dispatches: a crash anywhere leaves a state this pass closes the same way.
|
|
2597
|
+
reconcileWalks(store, clock());
|
|
2538
2598
|
// The stale-scan (dispatch v3, finding 20): mirrors the courtesy filter
|
|
2539
2599
|
// kept out of ready are REPORTED here, typed, with a paged episode —
|
|
2540
2600
|
// an undispatakable tracker item is a 9am fact, not a silent absence.
|
|
@@ -2627,7 +2687,15 @@ async function tickCommand(flags, context) {
|
|
|
2627
2687
|
}
|
|
2628
2688
|
}
|
|
2629
2689
|
const planLeg = route === null ? null : legOf(route.route, "plan");
|
|
2630
|
-
|
|
2690
|
+
// A task whose agent ran out runs as the next approved entry: every gate below is keyed to that entry (fallback.ts).
|
|
2691
|
+
const fallback = route !== null && route.source === "approved" && !wantsPlan && !wantsScout ? dispatchEntryOf(store, ref.id, id, clock()) : null;
|
|
2692
|
+
if (fallback !== null && "problem" in fallback) {
|
|
2693
|
+
dispatched.push({ id, outcome: "skipped", reason: "agent-config", detail: `${id}: ${fallback.problem}` });
|
|
2694
|
+
continue;
|
|
2695
|
+
}
|
|
2696
|
+
const sealedBuildLeg = route !== null && route.source === "approved"
|
|
2697
|
+
? fallback !== null ? { provider: fallback.entry.profile.provider, model: fallback.entry.profile.model, chosen: "fallback" } : legOf(route.route, "build")
|
|
2698
|
+
: null;
|
|
2631
2699
|
const planFlagged = planProvider !== undefined || planModel !== undefined || providerFlag !== undefined || model !== undefined;
|
|
2632
2700
|
if (wantsPlan && planLeg !== null && planFlagged) {
|
|
2633
2701
|
// Pass flags cannot contradict the task's plan leg: a flag that names
|
|
@@ -2762,7 +2830,7 @@ async function tickCommand(flags, context) {
|
|
|
2762
2830
|
// existing approval refusals own that road, and attestation must
|
|
2763
2831
|
// never mask them. The gateway re-checks before spawn; this skip only
|
|
2764
2832
|
// keeps the normal road cheap.
|
|
2765
|
-
const skipProviders = wantsPlan
|
|
2833
|
+
const skipProviders = wantsPlan || fallback !== null
|
|
2766
2834
|
? [spec.provider]
|
|
2767
2835
|
: [store.getScope(id)?.approvedProfile?.provider].filter((one) => one !== null && one !== undefined);
|
|
2768
2836
|
let unattestedLane = null;
|
|
@@ -2799,7 +2867,7 @@ async function tickCommand(flags, context) {
|
|
|
2799
2867
|
}
|
|
2800
2868
|
// Sprint 8: the organisation policy before the claim (build() looks again before spawn, and lowers).
|
|
2801
2869
|
{
|
|
2802
|
-
const sealed = wantsPlan || wantsScout ? null : store.getScope(id)?.approvedProfile ?? null;
|
|
2870
|
+
const sealed = wantsPlan || wantsScout ? null : fallback?.entry.profile ?? store.getScope(id)?.approvedProfile ?? null;
|
|
2803
2871
|
const refused = policyHold([sealed !== null ? { profile: sealed }
|
|
2804
2872
|
: { provider: spec.provider, model: spec.model, ...(wantsPlan ? { session: "planning" } : wantsScout ? { session: "scouting" } : {}) }]);
|
|
2805
2873
|
if (refused !== null) {
|
|
@@ -2813,7 +2881,8 @@ async function tickCommand(flags, context) {
|
|
|
2813
2881
|
// Each provider as it bills now.
|
|
2814
2882
|
// An ACP agent bills as its declaration for the exact model it would run on this runner.
|
|
2815
2883
|
const sealedModel = store.getScope(id)?.approvedProfile?.model ?? null;
|
|
2816
|
-
|
|
2884
|
+
// A paid fallback entry is a dollar spend whatever its provider's current setting says.
|
|
2885
|
+
const budgeted = budgetHold(ref.id, fallback?.paid === true ? [{ provider: spec.provider, billing: "api-key" }] : [...new Set([spec.provider, ...skipProviders])].map(provider => isAcpProvider(provider) ? store.agentFor(provider, provider === spec.provider ? spec.model ?? sealedModel : sealedModel, runner) : store.agentsFor([provider])[0]));
|
|
2817
2886
|
if (budgeted.over !== null) {
|
|
2818
2887
|
dispatched.push({ id, outcome: "skipped", reason: "budget", detail: budgetWords(budgeted) });
|
|
2819
2888
|
continue;
|
|
@@ -2837,6 +2906,7 @@ async function tickCommand(flags, context) {
|
|
|
2837
2906
|
repo,
|
|
2838
2907
|
provider: spec.provider,
|
|
2839
2908
|
...(spec.model === null ? {} : { model: spec.model }),
|
|
2909
|
+
...(fallback === null ? {} : { authMode: fallback.entry.authMode }),
|
|
2840
2910
|
...(text(flags, "incarnation") === undefined ? {} : { incarnation: text(flags, "incarnation") }),
|
|
2841
2911
|
...(text(flags, "max-open-decisions") === undefined
|
|
2842
2912
|
? {}
|
|
@@ -3434,12 +3504,16 @@ async function tickCommand(flags, context) {
|
|
|
3434
3504
|
reason: disposition.sealed
|
|
3435
3505
|
? disposition.disposition === "auth-expired"
|
|
3436
3506
|
? "auth-expired — requeued, sign-in needed"
|
|
3437
|
-
:
|
|
3507
|
+
: disposition.disposition === "fallback"
|
|
3508
|
+
? `out of plan — moved to the next approved agent`
|
|
3509
|
+
: `${disposition.failureClass}${disposition.disposition === "backoff" ? ` — retry ${disposition.strikes}/3` : disposition.disposition === "stalled" ? " — stalled" : ""}`
|
|
3438
3510
|
: "fenced",
|
|
3439
3511
|
...(result.ok ? {} : { detail: result.message.slice(0, 200) }),
|
|
3440
3512
|
worktree: leased.worktree.path,
|
|
3441
3513
|
});
|
|
3442
|
-
|
|
3514
|
+
// Moving on isn't this pass breaking: the task is queued for its next agent.
|
|
3515
|
+
if (!(disposition.sealed && disposition.disposition === "fallback"))
|
|
3516
|
+
broke++;
|
|
3443
3517
|
break;
|
|
3444
3518
|
case "recorded":
|
|
3445
3519
|
// The standalone-only arm; unreachable under the tick policy.
|
|
@@ -3586,6 +3660,12 @@ async function reconcileCommand(flags, context) {
|
|
|
3586
3660
|
// asked to confirm a build stopped, so a release waiting on its check moves on by itself.
|
|
3587
3661
|
await proveFinishedRunsStopped(store, clock);
|
|
3588
3662
|
store.settleQuiescentStops(clock());
|
|
3663
|
+
// A vanished build whose old process is now proven stopped gets its retry decision (vanished-retry.ts).
|
|
3664
|
+
retryVanishedWhenStopped(store, clock());
|
|
3665
|
+
// A fast start that didn't start in time goes back to the ordinary Approve plan (fast-start.ts).
|
|
3666
|
+
expireFastStarts(store, clock());
|
|
3667
|
+
// A plan from before agent routing gets its route and one Approve (route-change.ts).
|
|
3668
|
+
routeLegacyPlans(store, clock());
|
|
3589
3669
|
// An update whose updater ended while letting work finish must not leave new work paused.
|
|
3590
3670
|
try {
|
|
3591
3671
|
releaseStalledUpdate(dirname(context.databaseFile), clock());
|
|
@@ -4956,7 +5036,7 @@ async function modeCommand(positional, flags, context) {
|
|
|
4956
5036
|
if (acting === null)
|
|
4957
5037
|
return fail(write, json, "mode", "usage", "signing or revoking a mode takes `--as <you> --token <t>`", EXIT.usage);
|
|
4958
5038
|
// Signing a mode that lets a chat app approve is a credential act; any other mode signing or revoking is ordinary.
|
|
4959
|
-
const authed = (flag(flags, "chat-approve") ? credentialApprover(store, acting.name, acting.token) : authenticateApprover(store, acting.name, acting.token));
|
|
5039
|
+
const authed = (flag(flags, "chat-approve") || flag(flags, "repair-auto") ? credentialApprover(store, acting.name, acting.token) : authenticateApprover(store, acting.name, acting.token));
|
|
4960
5040
|
if (!authed.ok)
|
|
4961
5041
|
return fail(write, json, "mode", authed.reason, "that is not an active approver, or the token does not match", EXIT.refused);
|
|
4962
5042
|
if (action === "revoke") {
|
|
@@ -4984,8 +5064,18 @@ async function modeCommand(positional, flags, context) {
|
|
|
4984
5064
|
terms.autoApproveFiling = true;
|
|
4985
5065
|
if (flag(flags, "plan-auto"))
|
|
4986
5066
|
terms.planAuto = true;
|
|
4987
|
-
if (["review-auto", "review-retry-auto"
|
|
4988
|
-
return fail(write, json, "mode set", "invalid", "Separate model review
|
|
5067
|
+
if (["review-auto", "review-retry-auto"].some(name => flags.has(name)))
|
|
5068
|
+
return fail(write, json, "mode set", "invalid", "Separate model review has been removed; finished work goes to the lead or user.", EXIT.refused);
|
|
5069
|
+
// Repairing failed checks by themselves: never a preset default, only these flags, with a limit you sign.
|
|
5070
|
+
if (flags.has("repair-max-attempts") && !flag(flags, "repair-auto"))
|
|
5071
|
+
return fail(write, json, "mode set", "invalid", "--repair-max-attempts needs --repair-auto", EXIT.refused);
|
|
5072
|
+
if (flag(flags, "repair-auto")) {
|
|
5073
|
+
const limit = Number(text(flags, "repair-max-attempts") ?? 2);
|
|
5074
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 3)
|
|
5075
|
+
return fail(write, json, "mode set", "invalid", "--repair-max-attempts is 1, 2 or 3", EXIT.refused);
|
|
5076
|
+
terms.repairAuto = true;
|
|
5077
|
+
terms.repairMaxAttempts = limit;
|
|
5078
|
+
}
|
|
4989
5079
|
if (terms.planAuto && !terms.autoApproveFiling)
|
|
4990
5080
|
return fail(write, json, "mode set", "invalid", "--plan-auto requires automatic filing approval", EXIT.refused);
|
|
4991
5081
|
// Approving plans and merges from the signer's paired chat: never a preset default, only this flag.
|
|
@@ -6466,6 +6556,13 @@ async function runWatchLoop(args) {
|
|
|
6466
6556
|
const batched = await runBatchChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
|
|
6467
6557
|
if (batched > 0)
|
|
6468
6558
|
progress(`watch: settled ${batched} batch-checked result(s)`);
|
|
6559
|
+
// A held release a person let go on resumes by itself, in the background of this pass (release-next.ts).
|
|
6560
|
+
if (context.agentRunner === undefined)
|
|
6561
|
+
void resumeHeldReleases(store, dirname(context.databaseFile), context.clock());
|
|
6562
|
+
// A result whose verdict went missing asks for its one automatic recheck (check-only.ts).
|
|
6563
|
+
requestMissingRechecks(store, context.evidenceRoot, context.clock());
|
|
6564
|
+
// A failed check that was parked takes its next step once its task is free again (check-repair.ts).
|
|
6565
|
+
resumeParkedRepairs(store, context.evidenceRoot, context.clock());
|
|
6469
6566
|
const ran = await runWaitingChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
|
|
6470
6567
|
if (ran > 0)
|
|
6471
6568
|
progress(`watch: ran ${ran} follow-up check(s)`);
|
|
@@ -6478,11 +6575,27 @@ async function runWatchLoop(args) {
|
|
|
6478
6575
|
// all. A test's injected agent runner means a test: no real gh.
|
|
6479
6576
|
if (context.agentRunner === undefined)
|
|
6480
6577
|
await refreshGithubNightlies(store, context.clock);
|
|
6578
|
+
// The flaky tests list's GitHub rows (flaky-ci.ts): read-only, each project at most every 30 minutes.
|
|
6579
|
+
if (context.agentRunner === undefined)
|
|
6580
|
+
await refreshLearning(store, context.clock);
|
|
6481
6581
|
// A finished check may send a build's card down its failure path: now, not on the next beat.
|
|
6482
6582
|
if (ran + batched > 0)
|
|
6483
6583
|
moveCards(store, repo, context.clock(), context.evidenceRoot);
|
|
6484
6584
|
},
|
|
6485
6585
|
});
|
|
6586
|
+
// The code map (docs/plans/code-map.md §2): its own beat, so a slow remote holds nothing else. A test's injected agent
|
|
6587
|
+
// runner means a test: no real fetch.
|
|
6588
|
+
const codeMapChecks = startMaintenance({
|
|
6589
|
+
intervalMs: 60_000,
|
|
6590
|
+
shouldStop: stopping,
|
|
6591
|
+
onError: error => progress(`watch: a code map check failed — ${describe(error)}; it tries again`),
|
|
6592
|
+
run: async () => {
|
|
6593
|
+
if (store.isDemo() || context.agentRunner !== undefined)
|
|
6594
|
+
return;
|
|
6595
|
+
await refreshCodeMaps(store, { scratch: join(context.evidenceRoot, ".code-map"), clock: context.clock });
|
|
6596
|
+
sweepDeliveries(store);
|
|
6597
|
+
},
|
|
6598
|
+
});
|
|
6486
6599
|
// Flows on their own cadence (flow-cadence.ts), not between builds: while builds hold every slot of this
|
|
6487
6600
|
// project, a card whose step finished still moves within one beat. Moves run apart from triggers, replies
|
|
6488
6601
|
// and steps, so a long check script doesn't hold them either. Neither starts a build: a card entering a
|
|
@@ -6762,6 +6875,7 @@ async function runWatchLoop(args) {
|
|
|
6762
6875
|
await Promise.all(lanes);
|
|
6763
6876
|
await maintenance.stop();
|
|
6764
6877
|
await followUpChecks.stop();
|
|
6878
|
+
await codeMapChecks.stop();
|
|
6765
6879
|
await flowMoves.stop();
|
|
6766
6880
|
await flowSteps.stop();
|
|
6767
6881
|
await modelWatch.stop();
|
|
@@ -7436,6 +7550,8 @@ async function upCommand(flags, context) {
|
|
|
7436
7550
|
retireRunnerIfCurrent(store, runnerName, runnerToken, clock());
|
|
7437
7551
|
return fail(write, json, "up", "runner-alive", "The worker process record could not be saved; nothing started", EXIT.refused);
|
|
7438
7552
|
}
|
|
7553
|
+
// What a person asked for, and what ran: an automatic restart acts on exactly this (builder-restart.ts).
|
|
7554
|
+
recordBuilderWish(store, "running", "up", packageVersion(), clock());
|
|
7439
7555
|
doorAnswer = answer;
|
|
7440
7556
|
break;
|
|
7441
7557
|
}
|
|
@@ -8608,6 +8724,9 @@ async function statusCommand(positional, flags, context) {
|
|
|
8608
8724
|
// This installation's console and background service (easy-start D), beside its updates and integrations: a person
|
|
8609
8725
|
// limited to some projects returned above without it; an all-project caller reads what a local one does.
|
|
8610
8726
|
const laptop = { laptop: await laptopStatus(context.store, context.databaseFile, context.laptopSeams) };
|
|
8727
|
+
// Nothing else on this computer is left to notice a builder that died: a look at status brings it back when it may.
|
|
8728
|
+
if (!laptop.laptop.running && laptop.laptop.background === "on" && !machineActing())
|
|
8729
|
+
await restartOwnBuilder(context.store, context.databaseFile, context.laptopSeams, context.clock()).catch(() => null);
|
|
8611
8730
|
return succeed(context.write, context.json, command, { ...status, projects, ...laptop, ...update, ...integrations, ...updateWaiting, ...unsentReplies, ...(legacyWebhookWarning === null ? {} : { legacyWebhookWarning }) }, () => [...(waiting === null ? [] : [waiting.words]), ...(unsentLine === null ? [] : [unsentLine]), ...renderInstallationStatus(status), ...(projectsLine === null ? [] : [projectsLine]), ...(line === null ? [] : [line]), ...(brokenLine === null ? [] : [brokenLine]), ...(legacyWebhookWarning === null ? [] : [legacyWebhookWarning])]);
|
|
8612
8731
|
}
|
|
8613
8732
|
function unsentRepliesLine(unsent) {
|
|
@@ -8752,10 +8871,16 @@ function taskAction(action, rest, flags, context) {
|
|
|
8752
8871
|
demo: context.store.isDemo(), credentials: () => askCredentials(flags, context), ...(context.publishExec === undefined ? {} : { exec: context.publishExec }) });
|
|
8753
8872
|
case "add":
|
|
8754
8873
|
return addTask(rest, flags, context);
|
|
8874
|
+
case "new":
|
|
8875
|
+
return newTaskCommand(rest, flags, context);
|
|
8876
|
+
case "keep":
|
|
8877
|
+
return keepGrownCommand(rest, flags, context);
|
|
8755
8878
|
case "checks":
|
|
8756
8879
|
return runTaskChecks(rest, flags, context);
|
|
8757
8880
|
case "ship":
|
|
8758
8881
|
return taskShipCommand(rest, context);
|
|
8882
|
+
case "ready":
|
|
8883
|
+
return taskReadyCommand(rest, context);
|
|
8759
8884
|
case "add-tests":
|
|
8760
8885
|
return addTestsTask(rest, flags, context);
|
|
8761
8886
|
case "list":
|
|
@@ -8810,6 +8935,12 @@ function taskAction(action, rest, flags, context) {
|
|
|
8810
8935
|
return requeueTask(rest, flags, context);
|
|
8811
8936
|
case "regate":
|
|
8812
8937
|
return regateTaskCommand(rest, flags, context);
|
|
8938
|
+
case "handover":
|
|
8939
|
+
return handoverCommand(rest, followThroughContext(context, flags));
|
|
8940
|
+
case "before-after":
|
|
8941
|
+
return evidenceCommand(rest, flags, followThroughContext(context, flags));
|
|
8942
|
+
case "failing-first":
|
|
8943
|
+
return failingFirstCommand(rest, followThroughContext(context, flags));
|
|
8813
8944
|
case "plan":
|
|
8814
8945
|
return planTaskCommand(rest, flags, context);
|
|
8815
8946
|
case "review":
|
|
@@ -9332,8 +9463,8 @@ function showTask(positional, context) {
|
|
|
9332
9463
|
...(task.state !== "done" || detail.proofVerdict === null
|
|
9333
9464
|
? []
|
|
9334
9465
|
: [
|
|
9335
|
-
`
|
|
9336
|
-
...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.join("
|
|
9466
|
+
` checked: ${proofVerdictWords(current?.receipt?.runId === latestFinished?.id ? current?.readiness?.verdict ?? detail.proofVerdict : detail.proofVerdict, detail.proofReasons).word}${detail.proofAccepted ? " (accepted)" : ""}`,
|
|
9467
|
+
...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.map(plainReasonWords).join(" ")}`] : []),
|
|
9337
9468
|
...matrixWords(detail.proofMatrix),
|
|
9338
9469
|
...(detail.semanticCoverage === null ? [] : coverageWords(detail.semanticCoverage).map(line => ` ${line}`)),
|
|
9339
9470
|
]),
|
|
@@ -9341,11 +9472,11 @@ function showTask(positional, context) {
|
|
|
9341
9472
|
? []
|
|
9342
9473
|
: detail.report.ok
|
|
9343
9474
|
? [
|
|
9344
|
-
` report: ${detail.report.report.title} (
|
|
9475
|
+
` report: ${detail.report.report.title} (build #${detail.report.run})`,
|
|
9345
9476
|
` ${detail.report.report.summary}`,
|
|
9346
9477
|
...detail.report.report.followUps.map((one, index) => ` follow-up ${index + 1}: ${one.title}`),
|
|
9347
9478
|
]
|
|
9348
|
-
: [` report: ${detail.report.problem} (
|
|
9479
|
+
: [` report: ${detail.report.problem} (build #${detail.report.run})`]),
|
|
9349
9480
|
...pullRequestLines(detail.pullRequest),
|
|
9350
9481
|
...(base === null ? [] : [` base: ${baseStandingWords(base)}`]),
|
|
9351
9482
|
...(detail.blockedBy.length > 0 ? [` waits for ${detail.blockedBy.join(", ")}`] : []),
|
|
@@ -9378,7 +9509,7 @@ function showTask(positional, context) {
|
|
|
9378
9509
|
...detail.runs
|
|
9379
9510
|
.filter(one => one.route !== null)
|
|
9380
9511
|
.slice(0, 8)
|
|
9381
|
-
.map(one => `
|
|
9512
|
+
.map(one => ` build #${one.id} ${one.role.padEnd(8)} ${one.route.provider}${one.route.model === null ? "" : ` · ${one.route.model}`} [${one.route.chosen}]`),
|
|
9382
9513
|
]);
|
|
9383
9514
|
}
|
|
9384
9515
|
/** The review status lines `task show` prints (v50): the state word, the
|
|
@@ -9418,47 +9549,72 @@ function taskRouteStandingLines(id, routed) {
|
|
|
9418
9549
|
if (routed === null)
|
|
9419
9550
|
return [];
|
|
9420
9551
|
if (routed.kind === "route") {
|
|
9421
|
-
return [` agents ${routed.source === "approved" ? "
|
|
9552
|
+
return [` agents ${routed.source === "approved" ? "approved with the plan — a configuration change never moves them" : routed.source === "proposed" ? "proposed — approving the plan approves them" : "recommended now — no plan filed yet"}`];
|
|
9422
9553
|
}
|
|
9423
9554
|
if (routed.kind === "legacy") {
|
|
9424
9555
|
return [` agents approved before agent routing existed — ${routed.profile.provider} · ${routed.profile.model} builds and repairs (repair model ${routed.profile.repairModel === "inherit" ? routed.profile.model : routed.profile.repairModel}); the planner and reviewer resolve from configuration at run time`];
|
|
9425
9556
|
}
|
|
9426
|
-
return [` agents UNREADABLE — ${routed.problem}`, ` nothing runs for ${id} until its
|
|
9557
|
+
return [` agents UNREADABLE — ${routed.problem}`, ` nothing runs for ${id} until its plan is saved again and approved`];
|
|
9427
9558
|
}
|
|
9428
9559
|
/**
|
|
9429
|
-
* `task accept <id
|
|
9430
|
-
*
|
|
9431
|
-
*
|
|
9432
|
-
*
|
|
9560
|
+
* `task accept <id>`: every call names what it means (docs/plans/plain-toolroll.md P1.1), so the same words never
|
|
9561
|
+
* quietly start meaning something else.
|
|
9562
|
+
* - `--finish` or `--digest <receipt>` is the person's Accept: exactly `task complete`'s exact-receipt completion, with
|
|
9563
|
+
* the same rule that `--json` and scoped credentials must pass `--digest`.
|
|
9564
|
+
* - `--despite "<reason>"` (`--note` is its alias) is acceptance only: an explicit acceptance of a short or refuted
|
|
9565
|
+
* result with a reason, credentialed like approving a plan. It never completes, and never combines with the above.
|
|
9566
|
+
* - A bare `task accept <id>` refuses, for good, naming both.
|
|
9433
9567
|
*/
|
|
9434
9568
|
async function acceptTaskProof(positional, flags, context) {
|
|
9435
9569
|
const { store, write, json, now } = context;
|
|
9570
|
+
const finishing = flags.has("finish") || flags.has("digest");
|
|
9571
|
+
const despiteFlag = flags.has("despite") ? flags.get("despite") : flags.get("note");
|
|
9572
|
+
const despiting = flags.has("despite") || flags.has("note");
|
|
9573
|
+
if (flags.has("despite") && flags.has("note"))
|
|
9574
|
+
return fail(write, json, "task accept", "usage", "Use --despite \"<reason>\" once (--note is the same flag).", EXIT.usage);
|
|
9575
|
+
if (finishing && despiting) {
|
|
9576
|
+
return fail(write, json, "task accept", "usage", "--despite accepts a result with a reason and never finishes the task; --finish and --digest finish it. Use one: first accept with --despite, then finish with --finish.", EXIT.usage);
|
|
9577
|
+
}
|
|
9578
|
+
if (!finishing && !despiting) {
|
|
9579
|
+
return fail(write, json, "task accept", "usage", "Say what accepting means: `toolroll task accept <id> --finish` accepts the result and finishes the task, or `--despite \"<reason>\"` accepts it with a reason without finishing.", EXIT.usage);
|
|
9580
|
+
}
|
|
9581
|
+
if (finishing) {
|
|
9582
|
+
// The person's Accept: `task complete`, unchanged, under this command's name.
|
|
9583
|
+
if (flags.get("finish") !== undefined && flags.get("finish") !== true)
|
|
9584
|
+
return fail(write, json, "task accept", "usage", "--finish takes no value.", EXIT.usage);
|
|
9585
|
+
const rest = new Map(flags);
|
|
9586
|
+
rest.delete("finish");
|
|
9587
|
+
return runTaskOutcomeCommand("complete", positional, rest, { ...context, command: "task accept", operator: async () => {
|
|
9588
|
+
const acting = await askCredentials(flags, context);
|
|
9589
|
+
if (acting === null)
|
|
9590
|
+
return null;
|
|
9591
|
+
const authenticated = verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
|
|
9592
|
+
return authenticated.ok ? authenticated.who : null;
|
|
9593
|
+
} });
|
|
9594
|
+
}
|
|
9436
9595
|
const id = positional[0];
|
|
9437
9596
|
if (id === undefined)
|
|
9438
|
-
return fail(write, json, "task accept", "usage", "`toolroll task accept <id>
|
|
9597
|
+
return fail(write, json, "task accept", "usage", "`toolroll task accept <id> --despite \"<reason>\" --as <you> --token <t>`", EXIT.usage);
|
|
9598
|
+
if (typeof despiteFlag !== "string" || despiteFlag.trim() === "")
|
|
9599
|
+
return fail(write, json, "task accept", "usage", "--despite needs the reason you accept it anyway: `--despite \"<reason>\"`.", EXIT.usage);
|
|
9600
|
+
const validated = validateNote(despiteFlag);
|
|
9601
|
+
if (!validated.ok)
|
|
9602
|
+
return fail(write, json, "task accept", "invalid", validated.problem, EXIT.usage);
|
|
9439
9603
|
const task = store.getTask(id);
|
|
9440
9604
|
if (task === null)
|
|
9441
9605
|
return fail(write, json, "task accept", "unknown-task", `no task \`${id}\``, EXIT.refused);
|
|
9442
9606
|
const acting = await askCredentials(flags, context);
|
|
9443
9607
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
9444
9608
|
if (acting === null || !verified?.ok)
|
|
9445
|
-
return refuseApprover(context, "task accept", verified, "accepting
|
|
9609
|
+
return refuseApprover(context, "task accept", verified, "accepting a result with a reason is a person's act — it takes `--as <you> --token <t>`", id);
|
|
9446
9610
|
const ref = store.refFor(BUILT_IN, id);
|
|
9447
9611
|
// v40 fix: a reviewer run is never the attempt whose proof is accepted.
|
|
9448
9612
|
const latest = store.runsFor(ref.id).find(one => one.finishedAt !== null && (one.role === "builder" || one.role === "scout"));
|
|
9449
9613
|
if (latest === undefined)
|
|
9450
9614
|
return fail(write, json, "task accept", "no-run", `${id} has no finished attempt to accept`, EXIT.refused);
|
|
9451
|
-
|
|
9452
|
-
let note = null;
|
|
9453
|
-
if (rawNote !== undefined && rawNote.trim() !== "") {
|
|
9454
|
-
const validated = validateNote(rawNote);
|
|
9455
|
-
if (!validated.ok)
|
|
9456
|
-
return fail(write, json, "task accept", "invalid", validated.problem, EXIT.usage);
|
|
9457
|
-
note = validated.note;
|
|
9458
|
-
}
|
|
9459
|
-
store.acceptProof(latest.id, acting.name, note, now);
|
|
9615
|
+
store.acceptProof(latest.id, acting.name, validated.note, now);
|
|
9460
9616
|
return succeed(write, json, "task accept", { id, run: latest.id, acceptedBy: acting.name }, () => [
|
|
9461
|
-
`Accepted
|
|
9617
|
+
`Accepted ${id}'s build #${latest.id} with your reason, ${acting.name}. It isn't finished: \`toolroll task accept ${id} --finish\` finishes it.`,
|
|
9462
9618
|
]);
|
|
9463
9619
|
}
|
|
9464
9620
|
/**
|
|
@@ -9479,9 +9635,18 @@ async function repairTaskCommand(positional, flags, context) {
|
|
|
9479
9635
|
const acting = await askCredentials(flags, context);
|
|
9480
9636
|
if (acting === null)
|
|
9481
9637
|
return fail(write, json, "task repair", "usage", "Evidence collection requires --as and --token.", EXIT.usage);
|
|
9482
|
-
|
|
9638
|
+
// A failed check names its project: the password proves the person, and their access to the run they named.
|
|
9639
|
+
const failed = checkRepairOfRun(store, runId);
|
|
9640
|
+
const authenticated = authenticateApprover(store, acting.name, acting.token, failed?.repo);
|
|
9483
9641
|
if (!authenticated.ok)
|
|
9484
9642
|
return fail(write, json, "task repair", authenticated.reason, describeApproveFailure(authenticated.reason, String(runId)), EXIT.refused);
|
|
9643
|
+
// A failed check waiting for a person: this draft is what they asked for, and its approval below is the normal one.
|
|
9644
|
+
if (failed !== null) {
|
|
9645
|
+
const started = startCheckRepair(store, context.evidenceRoot, runId, clock());
|
|
9646
|
+
if (!started.ok)
|
|
9647
|
+
return fail(write, json, "task repair", "refused", started.message, EXIT.refused);
|
|
9648
|
+
chain = store.repairChainFor(runId);
|
|
9649
|
+
}
|
|
9485
9650
|
const run = store.getRun(runId), proof = store.proofVerdictFor(runId);
|
|
9486
9651
|
const repo = run && store.refById(run.taskRef)?.repo;
|
|
9487
9652
|
if (repo && repo === repoFrom(flags) && proof?.matrix.some(row => row.assessment && row.review?.judgement === "cannot-tell") &&
|
|
@@ -9730,7 +9895,7 @@ async function pauseTask(positional, flags, context) {
|
|
|
9730
9895
|
return fail(write, json, "task pause", "usage", "`toolroll task pause <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
|
|
9731
9896
|
const acting = await askCredentials(flags, context);
|
|
9732
9897
|
if (acting === null)
|
|
9733
|
-
return fail(write, json, "task pause", "usage", "pausing takes `--as <you> --token <t>` — it is an
|
|
9898
|
+
return fail(write, json, "task pause", "usage", "pausing takes `--as <you> --token <t>` — it is an approver's act and is recorded against your name", EXIT.usage);
|
|
9734
9899
|
const authed = authenticateApprover(store, acting.name, acting.token);
|
|
9735
9900
|
if (!authed.ok)
|
|
9736
9901
|
return fail(write, json, "task pause", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
|
|
@@ -9748,7 +9913,7 @@ async function continueTask(positional, flags, context) {
|
|
|
9748
9913
|
return fail(write, json, "task continue", "usage", "`toolroll task continue <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
|
|
9749
9914
|
const acting = await askCredentials(flags, context);
|
|
9750
9915
|
if (acting === null)
|
|
9751
|
-
return fail(write, json, "task continue", "usage", "resuming takes `--as <you> --token <t>` — it is an
|
|
9916
|
+
return fail(write, json, "task continue", "usage", "resuming takes `--as <you> --token <t>` — it is an approver's act and is recorded against your name", EXIT.usage);
|
|
9752
9917
|
const authed = authenticateApprover(store, acting.name, acting.token);
|
|
9753
9918
|
if (!authed.ok)
|
|
9754
9919
|
return fail(write, json, "task continue", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
|
|
@@ -10283,6 +10448,11 @@ async function approveTask(positional, flags, context) {
|
|
|
10283
10448
|
interactive() && !json) {
|
|
10284
10449
|
write(`Approving lets a builder work on ${id}, within exactly this:`);
|
|
10285
10450
|
write("");
|
|
10451
|
+
const changed = routeChangeOf(store, id);
|
|
10452
|
+
if (changed !== null) {
|
|
10453
|
+
write(changed);
|
|
10454
|
+
write("");
|
|
10455
|
+
}
|
|
10286
10456
|
for (const line of describeScope(scope))
|
|
10287
10457
|
write(line);
|
|
10288
10458
|
for (const line of planContractLines(store, context.evidenceRoot, id))
|
|
@@ -10310,6 +10480,11 @@ async function approveTask(positional, flags, context) {
|
|
|
10310
10480
|
}
|
|
10311
10481
|
write(`Would approve this, and let a builder work on ${id}:`);
|
|
10312
10482
|
write("");
|
|
10483
|
+
const changed = routeChangeOf(store, id);
|
|
10484
|
+
if (changed !== null) {
|
|
10485
|
+
write(changed);
|
|
10486
|
+
write("");
|
|
10487
|
+
}
|
|
10313
10488
|
for (const line of describeScope(scope))
|
|
10314
10489
|
write(line);
|
|
10315
10490
|
for (const line of planContractLines(store, context.evidenceRoot, id))
|
|
@@ -10746,7 +10921,7 @@ async function stopTaskCommand(positional, flags, context) {
|
|
|
10746
10921
|
const acting = await askCredentials(flags, context);
|
|
10747
10922
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
10748
10923
|
if (acting === null || !verified?.ok)
|
|
10749
|
-
return refuseApprover(context, "task stop", verified, "stopping takes `--as <you> --token <t>` — it is an
|
|
10924
|
+
return refuseApprover(context, "task stop", verified, "stopping takes `--as <you> --token <t>` — it is an approver's act and is recorded against your name", id);
|
|
10750
10925
|
const asked = requestTaskStop(store, { taskId: id, runId, by: acting.name, via: "cli" }, clock());
|
|
10751
10926
|
if (!asked.ok)
|
|
10752
10927
|
return fail(write, json, "task stop", asked.reason, asked.detail, EXIT.refused, { run: runId });
|
|
@@ -11435,9 +11610,11 @@ async function storageCommand(positional, flags, context) {
|
|
|
11435
11610
|
};
|
|
11436
11611
|
const refused = (what) => fail(context.write, context.json, command, "refused", `An instance operator ${what}: pass --as and --token (or use the remembered login).`, EXIT.refused);
|
|
11437
11612
|
const summary = (plan) => [
|
|
11438
|
-
`
|
|
11439
|
-
plan.go.length === 0 ? "A clean up now would free nothing." : `A clean up now would free about ${bytesWords(plan.freeBytes)} (${plan.go.length}
|
|
11440
|
-
`A finished task's clean
|
|
11613
|
+
`Task folders: ${plan.count}, ${bytesWords(plan.totalBytes)}. ${plan.waitingReview} waiting for review, ${plan.withChanges} with uncommitted changes.`,
|
|
11614
|
+
plan.go.length === 0 ? "A clean up now would free nothing." : `A clean up now would free about ${bytesWords(plan.freeBytes)} (${plan.go.length} task folder${plan.go.length === 1 ? "" : "s"}): toolroll storage clean`,
|
|
11615
|
+
`A finished task's clean folder is removed ${cleanupWords(plan.cleanup)}; its branch stays.`,
|
|
11616
|
+
idleCleanupWords(plan),
|
|
11617
|
+
...uncommittedLines(plan),
|
|
11441
11618
|
];
|
|
11442
11619
|
const itemLine = (one) => ` ${bytesWords(one.bytes).padStart(8)} ${one.path}${one.why === null ? "" : ` (${whyWords(one)})`}`;
|
|
11443
11620
|
// Test temp folders the tests, journeys and scripts left (a killed run, an older version): those nothing touched for a day go.
|
|
@@ -11490,16 +11667,28 @@ async function storageCommand(positional, flags, context) {
|
|
|
11490
11667
|
if (action === "cleanup") {
|
|
11491
11668
|
if (rest.length === 0) {
|
|
11492
11669
|
const cleanup = store.checkoutCleanup();
|
|
11493
|
-
|
|
11670
|
+
const idle = checkoutIdle(store);
|
|
11671
|
+
return succeed(context.write, context.json, command, { cleanup, idle }, () => [`A finished task's clean folder is removed ${cleanupWords(cleanup)}; its branch stays.`, `A task's clean folder is removed ${idleWords(idle)}; its branch stays.`]);
|
|
11672
|
+
}
|
|
11673
|
+
const usage = `Use storage cleanup <${CLEANUP_CHOICES.map(one => one.value).join("|")}> or storage cleanup idle <7|14|30|never>.`;
|
|
11674
|
+
if (rest[0] === "idle") {
|
|
11675
|
+
const idle = rest.length === 2 ? parseIdle(rest[1]) : undefined;
|
|
11676
|
+
if (idle === undefined)
|
|
11677
|
+
return fail(context.write, context.json, command, "usage", usage, EXIT.usage);
|
|
11678
|
+
const actor = await operator();
|
|
11679
|
+
if (actor === null)
|
|
11680
|
+
return refused("sets idle cleanup");
|
|
11681
|
+
const saved = setCheckoutIdle(store, idle, actor, context.clock());
|
|
11682
|
+
return succeed(context.write, context.json, command, { idle: saved }, () => [`Saved. A task's clean folder is removed ${idleWords(saved)}; its branch stays.`]);
|
|
11494
11683
|
}
|
|
11495
11684
|
const cleanup = rest.length === 1 ? parseCleanup(rest[0]) : undefined;
|
|
11496
11685
|
if (cleanup === undefined)
|
|
11497
|
-
return fail(context.write, context.json, command, "usage",
|
|
11686
|
+
return fail(context.write, context.json, command, "usage", usage, EXIT.usage);
|
|
11498
11687
|
const actor = await operator();
|
|
11499
11688
|
if (actor === null)
|
|
11500
11689
|
return refused("sets checkout cleanup");
|
|
11501
11690
|
const saved = store.setCheckoutCleanup(cleanup, actor, context.clock());
|
|
11502
|
-
return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean
|
|
11691
|
+
return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean folder is removed ${cleanupWords(saved)}; its branch stays.`]);
|
|
11503
11692
|
}
|
|
11504
11693
|
if (action === "clean" && flags.has("landed")) {
|
|
11505
11694
|
if (rest.length > 0 || flags.get("landed") !== true)
|
|
@@ -11519,7 +11708,7 @@ async function storageCommand(positional, flags, context) {
|
|
|
11519
11708
|
const sharedBytes = sharedGo.reduce((sum, one) => sum + one.bytes, 0);
|
|
11520
11709
|
const anything = plan.go.length > 0 || temp.stale > 0 || sharedGo.length > 0;
|
|
11521
11710
|
return succeed(context.write, context.json, command, { preview: true, go: plan.go, stay: plan.stay, freeBytes: plan.freeBytes, testTemp: temp, sharedGo, sharedFreeBytes: sharedBytes }, () => [
|
|
11522
|
-
...(plan.go.length === 0 ? [anything ? "No
|
|
11711
|
+
...(plan.go.length === 0 ? [anything ? "No task folders to clean up." : "Nothing to clean up."] : [`Would remove ${plan.go.length} task folder${plan.go.length === 1 ? "" : "s"}, freeing about ${bytesWords(plan.freeBytes)} (their branches stay):`, ...plan.go.map(itemLine)]),
|
|
11523
11712
|
...(sharedGo.length === 0 ? [] : [`Would remove ${sharedGo.length} shared dependency install${sharedGo.length === 1 ? "" : "s"} no checkout uses, about ${bytesWords(sharedBytes)}:`, ...sharedGo.map(sharedLine)]),
|
|
11524
11713
|
...(plan.stay.length === 0 ? [] : [`Stays (${plan.stay.length}):`, ...plan.stay.map(itemLine)]),
|
|
11525
11714
|
...(temp.stale === 0 ? [] : [`Would remove ${temp.stale} test temp folder${temp.stale === 1 ? "" : "s"} older than a day, about ${bytesWords(temp.staleBytes)}.`]),
|
|
@@ -11554,7 +11743,7 @@ async function storageCommand(positional, flags, context) {
|
|
|
11554
11743
|
];
|
|
11555
11744
|
saveSweep(store, { at: context.clock().toISOString(), source: "manual", actor, parts });
|
|
11556
11745
|
return succeed(context.write, context.json, command, { removed: done.removed, kept: done.kept, freedBytes: done.freed, testTempRemoved: swept.removed.length, testTempFailed: swept.failed, sharedRemoved, sharedFreedBytes: sharedFreed }, () => [
|
|
11557
|
-
done.removed.length === 0 ? (swept.removed.length === 0 && sharedRemoved.length === 0 ? "Nothing to clean up." : "No
|
|
11746
|
+
done.removed.length === 0 ? (swept.removed.length === 0 && sharedRemoved.length === 0 ? "Nothing to clean up." : "No task folders to clean up.") : `Removed ${done.removed.length} task folder${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}; their branches stay.`,
|
|
11558
11747
|
...done.removed.map(itemLine),
|
|
11559
11748
|
...(sharedRemoved.length === 0 ? [] : [`Removed ${sharedRemoved.length} shared dependency install${sharedRemoved.length === 1 ? "" : "s"} no checkout used, about ${bytesWords(sharedFreed)}.`, ...sharedRemoved.map(sharedLine)]),
|
|
11560
11749
|
...done.kept.map(one => ` kept ${one.path} (${whyWords({ why: one.why })})`),
|
|
@@ -11632,7 +11821,8 @@ async function landedCommandWith(remotes, yes, operator, refused, pool, context)
|
|
|
11632
11821
|
]);
|
|
11633
11822
|
}
|
|
11634
11823
|
function checkoutSummary(plan) {
|
|
11635
|
-
return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup
|
|
11824
|
+
return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup,
|
|
11825
|
+
idle: plan.idle, idleCount: plan.idleCount, idleBytes: plan.idleBytes, uncommitted: plan.stay.filter(one => one.why === "has changes").map(one => ({ path: one.path, taskId: one.taskId, bytes: one.bytes })) };
|
|
11636
11826
|
}
|
|
11637
11827
|
/** `review [show] | on | off --repo <path>`: the project's one automatic review
|
|
11638
11828
|
* of finished builds. Changing it takes an approver of that project; the
|
|
@@ -13311,6 +13501,8 @@ async function backgroundCommand(positional, flags, context) {
|
|
|
13311
13501
|
return succeed(write, json, command, off, () => [off.action === "already-off" ? "Toolroll wasn't set to run in the background." : "Toolroll no longer runs in the background. Start it with `toolroll` when you need it."]);
|
|
13312
13502
|
}
|
|
13313
13503
|
const on = await backgroundOn(context.store, context.databaseFile, port, seams);
|
|
13504
|
+
if (on.ok)
|
|
13505
|
+
recordBuilderWish(context.store, "running", "background on", packageVersion(), context.clock());
|
|
13314
13506
|
if (!on.ok) {
|
|
13315
13507
|
const code = ["agent", "conflict", "declined", "running-elsewhere", "unsupported", "console-unproven", "invalid-unit", "unreadable"].includes(on.reason) ? EXIT.refused : EXIT.failed;
|
|
13316
13508
|
if (json) {
|
|
@@ -13474,7 +13666,7 @@ export function upLaptopPortCandidates(store, fromSeam) {
|
|
|
13474
13666
|
}
|
|
13475
13667
|
/** The memory verbs that change project memory (memory-cli.ts), and `propose`, which files the proposals a person then
|
|
13476
13668
|
* applies: a person's act, never the lead's. The reads stay as they are. */
|
|
13477
|
-
const MEMORY_WRITE_VERBS = new Set(["decide", "retire", "apply", "propose"]);
|
|
13669
|
+
const MEMORY_WRITE_VERBS = new Set(["decide", "retire", "apply", "propose", "tidy", "restore", "unmerge"]);
|
|
13478
13670
|
function memoryWriteVerb(positional) {
|
|
13479
13671
|
return MEMORY_WRITE_VERBS.has(positional[0] ?? "");
|
|
13480
13672
|
}
|
|
@@ -13586,3 +13778,312 @@ async function releaseApproveCommand(positional, flags, context) {
|
|
|
13586
13778
|
...(shown.stopped === null ? [] : [`The release isn't running: run \`toolroll release ${chosen.branch}\` to go on.`]),
|
|
13587
13779
|
]);
|
|
13588
13780
|
}
|
|
13781
|
+
/**
|
|
13782
|
+
* `toolroll task ready <task>` (read-only; docs/plans/plain-toolroll.md P1.6): "Can I merge?" — the task's checklist
|
|
13783
|
+
* from the records the task page reads (merge-checklist.ts), each blocker with the existing act that clears it. A task
|
|
13784
|
+
* outside the caller's projects is no task here.
|
|
13785
|
+
*/
|
|
13786
|
+
function taskReadyCommand(positional, context) {
|
|
13787
|
+
const { store, write, json } = context;
|
|
13788
|
+
const command = "task ready";
|
|
13789
|
+
const id = positional[0];
|
|
13790
|
+
if (id === undefined || positional.length > 1)
|
|
13791
|
+
return fail(write, json, command, "usage", "`toolroll task ready <task>`", EXIT.usage);
|
|
13792
|
+
const lens = context.principal?.lens ?? null;
|
|
13793
|
+
const ref = store.lookupRef(id);
|
|
13794
|
+
if (ref === null || store.getTask(id) === null || (lens !== null && (ref.repo === null || !lens.includes(ref.repo))))
|
|
13795
|
+
return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
|
|
13796
|
+
const now = (context.clock ?? (() => context.now))();
|
|
13797
|
+
const list = mergeChecklistOf(store, id, now, { principal: "operator", repos: lens, includeUnplaced: lens === null, dependencyRepos: lens }, context.evidenceRoot);
|
|
13798
|
+
if (list === null)
|
|
13799
|
+
return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
|
|
13800
|
+
return succeed(write, json, command, list, () => checklistLines(list));
|
|
13801
|
+
}
|
|
13802
|
+
/** The folders with uncommitted changes: `toolroll storage` lists them so a person can decide. Never removed automatically. */
|
|
13803
|
+
function uncommittedLines(plan) {
|
|
13804
|
+
const changed = plan.stay.filter(one => one.why === "has changes");
|
|
13805
|
+
if (changed.length === 0)
|
|
13806
|
+
return [];
|
|
13807
|
+
return [`${changed.length === 1 ? "This folder has" : "These folders have"} uncommitted changes and stay${changed.length === 1 ? "s" : ""} until you decide (toolroll storage discard <path> --yes throws one away):`,
|
|
13808
|
+
...changed.map(one => ` ${bytesWords(one.bytes).padStart(8)} ${one.path}${one.taskId === null ? "" : ` (${one.taskId})`}`)];
|
|
13809
|
+
}
|
|
13810
|
+
/** `toolroll map` (code-map-cli.ts): who is asking, and which projects they can open. A read needs no sign-in on this
|
|
13811
|
+
* computer's own database, as `knowledge search`; a sign-in limits it to that person's projects. */
|
|
13812
|
+
async function mapCommand(positional, flags, context) {
|
|
13813
|
+
const { store } = context;
|
|
13814
|
+
const local = currentLocalOwner();
|
|
13815
|
+
const { name, token } = credentialsFrom(flags, context);
|
|
13816
|
+
let actor = null;
|
|
13817
|
+
let repos = null;
|
|
13818
|
+
const all = [...new Set([...store.knownRepos(), ...store.listProjects().map(one => one.path)])];
|
|
13819
|
+
if (local !== null) {
|
|
13820
|
+
actor = local.account;
|
|
13821
|
+
repos = all.filter(repo => store.accountCanAccess(local.account, repo));
|
|
13822
|
+
}
|
|
13823
|
+
else if (name !== undefined && token !== undefined) {
|
|
13824
|
+
if (!authenticateAccount(store, name, token).ok)
|
|
13825
|
+
return fail(context.write, context.json, "map", "unauthenticated", "Sign in again: that login doesn't work here.", EXIT.refused);
|
|
13826
|
+
const remote = activeRemote();
|
|
13827
|
+
actor = name;
|
|
13828
|
+
repos = all.filter(repo => store.accountCanAccess(name, repo) && (remote === null || remote.allows(repo)));
|
|
13829
|
+
}
|
|
13830
|
+
else
|
|
13831
|
+
repos = all;
|
|
13832
|
+
return runMapCommand(positional, flags, { store, write: context.write, json: context.json, now: context.clock(), actor, repos,
|
|
13833
|
+
approver: repo => local === null && name !== undefined && token !== undefined && authenticateApprover(store, name, token, repo).ok,
|
|
13834
|
+
door: repo => {
|
|
13835
|
+
if (local !== null)
|
|
13836
|
+
return { kind: "local-owner", actor: local.account };
|
|
13837
|
+
if (name === undefined || token === undefined)
|
|
13838
|
+
return null;
|
|
13839
|
+
// The credential generation the proof is checked against, read before it, as `models` does.
|
|
13840
|
+
const generation = store.accountOf(name)?.generation ?? -1;
|
|
13841
|
+
const proved = authenticateApprover(store, name, token, repo);
|
|
13842
|
+
return proved.ok ? { kind: "person", person: { actor: name, generation, method: proved.method } } : null;
|
|
13843
|
+
} });
|
|
13844
|
+
}
|
|
13845
|
+
// ---- `toolroll task new` (docs/plans/plain-toolroll.md P2.4) -----------------------------------------------------------
|
|
13846
|
+
/**
|
|
13847
|
+
* Start a task from a person's own terminal: their words become the exact terms, printed; "Build now?" is their yes.
|
|
13848
|
+
* Filing and approving are one step (fast-start.ts), for small work a person approves without typing a password here;
|
|
13849
|
+
* anything else is the ordinary `task add`, then Approve plan. Never for a script or an agent: `--json`, no terminal, a
|
|
13850
|
+
* token or a remote run all refuse. The terminal UI drives it as the local owner, with --preview and --build.
|
|
13851
|
+
*/
|
|
13852
|
+
async function newTaskCommand(positional, flags, context) {
|
|
13853
|
+
const { store, write, json } = context;
|
|
13854
|
+
const clock = context.clock ?? (() => new Date());
|
|
13855
|
+
const command = "task new";
|
|
13856
|
+
const local = currentLocalOwner();
|
|
13857
|
+
if (context.principal !== undefined || machineActing())
|
|
13858
|
+
return fail(write, json, command, "person-only", "Only a person starts a build this way. File it with `task add`.", EXIT.refused);
|
|
13859
|
+
if (local === null && (json || !personAtTerminal(context)))
|
|
13860
|
+
return fail(write, json, command, "person-only", "`task new` asks a person at a terminal. Use `task add` from a script or an agent.", EXIT.refused);
|
|
13861
|
+
const { name, token } = credentialsFrom(flags, context);
|
|
13862
|
+
const passwordFree = token !== undefined && (local !== null || passwordlessOn(store));
|
|
13863
|
+
const facts = { passwordless: local === null && passwordFree, localOwner: local !== null, freshAt: null, returnTo: "/" };
|
|
13864
|
+
const buildNow = (preview, digest) => fastStartBuild(store, { preview, digest, actor: name ?? "", via: local === null ? "cli" : "terminal-ui", password: facts,
|
|
13865
|
+
approveNow: (taskId, sawDigest, fast) => approve(store, taskId, name ?? "", clock(), sawDigest, token ?? "", {}, fast) }, clock());
|
|
13866
|
+
const answerBuild = (built) => built.ok
|
|
13867
|
+
? succeed(write, json, command, { task: built.taskId }, () => [`Building ${built.taskId}. A builder starts it next.`])
|
|
13868
|
+
: fail(write, json, command, built.reason, built.message, EXIT.refused);
|
|
13869
|
+
const pressed = text(flags, "build");
|
|
13870
|
+
if (pressed !== undefined) {
|
|
13871
|
+
if (local === null)
|
|
13872
|
+
return fail(write, json, command, "person-only", "--build is the terminal UI's own.", EXIT.refused);
|
|
13873
|
+
return answerBuild(buildNow(pressed, text(flags, "digest") ?? ""));
|
|
13874
|
+
}
|
|
13875
|
+
const fromLink = text(flags, "from");
|
|
13876
|
+
if (fromLink !== undefined)
|
|
13877
|
+
return importTaskCommand(fromLink, flags, context, name);
|
|
13878
|
+
const words = positional.join(" ").trim();
|
|
13879
|
+
const repo = text(flags, "repo");
|
|
13880
|
+
if (words === "" || repo === undefined)
|
|
13881
|
+
return fail(write, json, command, "usage", "`toolroll task new \"<what should change>\" --repo <project>`", EXIT.usage);
|
|
13882
|
+
const placed = canonicalProject(repo) ?? resolve(repo);
|
|
13883
|
+
const prepared = name === undefined || token === undefined ? { eligible: false, reason: "password" }
|
|
13884
|
+
: await prepareFastStart(store, { actor: name, repo: placed, title: words, via: local === null ? "cli" : "terminal-ui", password: facts }, clock());
|
|
13885
|
+
const terms = prepared.eligible ? fastTermsLines(store, prepared.scope, placed) : [];
|
|
13886
|
+
if (flags.has("preview")) {
|
|
13887
|
+
if (local === null)
|
|
13888
|
+
return fail(write, json, command, "person-only", "--preview is the terminal UI's own.", EXIT.refused);
|
|
13889
|
+
return succeed(write, json, command, prepared.eligible ? { eligible: true, preview: prepared.preview, digest: prepared.digest, terms } : { eligible: false, reason: prepared.reason, message: fastRefusalWords(prepared.reason) }, () => []);
|
|
13890
|
+
}
|
|
13891
|
+
if (!prepared.eligible) {
|
|
13892
|
+
write(`${fastRefusalWords(prepared.reason)} Filing it for a plan.`);
|
|
13893
|
+
return addTask([words], flags, context);
|
|
13894
|
+
}
|
|
13895
|
+
write("Build approves exactly this:");
|
|
13896
|
+
write("");
|
|
13897
|
+
for (const line of terms)
|
|
13898
|
+
write(` ${line.label}: ${line.words}`);
|
|
13899
|
+
write("");
|
|
13900
|
+
if (!(await (context.easySeams?.confirm ?? confirm)("Build now?"))) {
|
|
13901
|
+
write("Nothing started. File it for a plan with `toolroll task add`.");
|
|
13902
|
+
return EXIT.refused;
|
|
13903
|
+
}
|
|
13904
|
+
return answerBuild(buildNow(prepared.preview, prepared.digest));
|
|
13905
|
+
}
|
|
13906
|
+
/** `toolroll task keep <id>`: "Approve it to keep it", a plan for the exact commit a fast start grew into. Approve it with `task approve`. */
|
|
13907
|
+
async function keepGrownCommand(positional, flags, context) {
|
|
13908
|
+
const { store, write, json, clock } = context;
|
|
13909
|
+
const id = positional[0];
|
|
13910
|
+
if (id === undefined)
|
|
13911
|
+
return fail(write, json, "task keep", "usage", "`toolroll task keep <id> --as <you> --token <t>`", EXIT.usage);
|
|
13912
|
+
const acting = await askCredentials(flags, context);
|
|
13913
|
+
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
13914
|
+
if (acting === null || !verified?.ok)
|
|
13915
|
+
return refuseApprover(context, "task keep", verified, "keeping a change takes `--as <you> --token <t>`", id);
|
|
13916
|
+
const kept = keepGrownChange(store, id, clock());
|
|
13917
|
+
if (!kept.ok)
|
|
13918
|
+
return fail(write, json, "task keep", "refused", kept.message, EXIT.refused);
|
|
13919
|
+
return succeed(write, json, "task keep", { id, digest: kept.digest }, () => [`Its plan now names the exact commit. Approve it with \`toolroll task approve ${id}\`.`]);
|
|
13920
|
+
}
|
|
13921
|
+
/** One resume at a time per process; a release's own steps can take many minutes. */
|
|
13922
|
+
let resumingReleases = false;
|
|
13923
|
+
async function resumeHeldReleases(store, stateDir, now) {
|
|
13924
|
+
if (resumingReleases)
|
|
13925
|
+
return;
|
|
13926
|
+
resumingReleases = true;
|
|
13927
|
+
try {
|
|
13928
|
+
await resumeAllowedReleases(store, stateDir, journal => runRelease({ branch: journal.branch, checkout: journal.checkout, stateDir, limits: {} }, releaseAdapters({ checkout: journal.checkout, say: () => { } })), now);
|
|
13929
|
+
}
|
|
13930
|
+
catch { /* the release's own journal says where it stopped; the next person act tries again */ }
|
|
13931
|
+
finally {
|
|
13932
|
+
resumingReleases = false;
|
|
13933
|
+
}
|
|
13934
|
+
}
|
|
13935
|
+
/** `task new --from <url>`: read and quote an issue or pull request, then, on a person's yes, file it for the ordinary approval. */
|
|
13936
|
+
async function importTaskCommand(url, flags, context, actor) {
|
|
13937
|
+
const { store, write, json } = context;
|
|
13938
|
+
const clock = context.clock ?? (() => new Date());
|
|
13939
|
+
const command = "task new";
|
|
13940
|
+
const repo = text(flags, "repo");
|
|
13941
|
+
if (repo === undefined)
|
|
13942
|
+
return fail(write, json, command, "usage", "`toolroll task new --from <url> --repo <project>`", EXIT.usage);
|
|
13943
|
+
const placed = canonicalProject(repo) ?? resolve(repo);
|
|
13944
|
+
const read = await readGithubItem(store, { url, repo: placed }, context.easySeams?.github ?? {});
|
|
13945
|
+
if (!read.ok)
|
|
13946
|
+
return fail(write, json, command, read.reason, read.message, EXIT.refused);
|
|
13947
|
+
write(`${read.item.kind === "pull" ? "Pull request" : "Issue"} #${read.item.number}, written outside Toolroll:`);
|
|
13948
|
+
write(` > ${read.item.title}`);
|
|
13949
|
+
for (const line of read.item.body.split(/\r?\n/))
|
|
13950
|
+
write(` > ${line}`);
|
|
13951
|
+
write("");
|
|
13952
|
+
if (!(await (context.easySeams?.confirm ?? confirm)("Add this task? You approve its plan before anything builds."))) {
|
|
13953
|
+
write("Nothing filed.");
|
|
13954
|
+
return EXIT.refused;
|
|
13955
|
+
}
|
|
13956
|
+
const filed = await fileGithubItem(store, { url, repo: placed, actor: actor ?? "cli", shownHead: read.item.head }, clock(), context.easySeams?.github ?? {});
|
|
13957
|
+
if (!filed.ok)
|
|
13958
|
+
return fail(write, json, command, filed.reason, filed.message, EXIT.refused);
|
|
13959
|
+
return succeed(write, json, command, { task: filed.taskId }, () => [`Filed ${filed.taskId}. Its plan is ready for you to approve.`]);
|
|
13960
|
+
}
|
|
13961
|
+
/** `toolroll flaky list|show`: reads only, for the remembered local login (or --as/--token) and its projects. */
|
|
13962
|
+
async function flakyCommand(positional, flags, context) {
|
|
13963
|
+
const acting = await askCredentials(flags, context);
|
|
13964
|
+
const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
|
|
13965
|
+
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
13966
|
+
return runFlakyCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [] });
|
|
13967
|
+
}
|
|
13968
|
+
/** The watch loop's background learning pass (docs/plans/learning-loops.md): advisory reads, each project at most every
|
|
13969
|
+
* 30 minutes through the plane's own gh. A failure here never reaches the loop: it is tried on the next pass. */
|
|
13970
|
+
export async function refreshLearning(store, now, reads) {
|
|
13971
|
+
try {
|
|
13972
|
+
backfillMemoryUse(store, now());
|
|
13973
|
+
}
|
|
13974
|
+
catch { /* advisory: the next pass tries again */ }
|
|
13975
|
+
// Project memory's clean-up proposals: deterministic, a quiet project at most weekly. Nothing changes until a person accepts.
|
|
13976
|
+
for (const repo of store.knownRepos()) {
|
|
13977
|
+
try {
|
|
13978
|
+
scheduledTidy(store, repo, now());
|
|
13979
|
+
}
|
|
13980
|
+
catch { /* the next pass tries again */ }
|
|
13981
|
+
}
|
|
13982
|
+
for (const repo of store.knownRepos()) {
|
|
13983
|
+
try {
|
|
13984
|
+
const key = ciCursorKey(repo), at = now();
|
|
13985
|
+
if (at.getTime() - store.serviceCursor(key) < CI_READ_EVERY_MS || ciRepositoriesOf(store, repo).length === 0)
|
|
13986
|
+
continue;
|
|
13987
|
+
store.setServiceCursor(key, at.getTime(), at);
|
|
13988
|
+
await observeCiAttempts(store, repo, reads ?? githubReads(releaseExec, releaseTools().gh, { retry: true }), at);
|
|
13989
|
+
}
|
|
13990
|
+
catch { /* advisory: the next pass tries again */ }
|
|
13991
|
+
}
|
|
13992
|
+
}
|
|
13993
|
+
/** `toolroll card <source>`: reads only, for the remembered local login (or --as/--token) and its projects. */
|
|
13994
|
+
async function cardCommand(positional, flags, context) {
|
|
13995
|
+
const acting = await askCredentials(flags, context);
|
|
13996
|
+
const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
|
|
13997
|
+
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
13998
|
+
return runCardCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], evidenceRoot: context.evidenceRoot,
|
|
13999
|
+
ok: (result, lines) => succeed(context.write, context.json, "card", result, lines),
|
|
14000
|
+
no: (reason, message) => fail(context.write, context.json, "card", reason, message, reason === "usage" ? EXIT.usage : EXIT.refused) });
|
|
14001
|
+
}
|
|
14002
|
+
/** The laptop's local owner as a reader: its name and its projects. Never a proof: every write proves its own act. */
|
|
14003
|
+
function localReader(context) {
|
|
14004
|
+
const local = currentLocalOwner();
|
|
14005
|
+
return local === null ? null : { name: local.account, repos: context.store.knownRepos().filter(repo => context.store.accountCanAccess(local.account, repo)) };
|
|
14006
|
+
}
|
|
14007
|
+
/** What the follow-through commands need from this file: its envelope, and a verified person (follow-through-cli.ts). */
|
|
14008
|
+
function followThroughContext(context, flags) {
|
|
14009
|
+
const { store, write, json, clock, evidenceRoot } = context;
|
|
14010
|
+
return {
|
|
14011
|
+
store, evidenceRoot, now: clock(),
|
|
14012
|
+
ok: (command, result, lines) => succeed(write, json, command, result, lines),
|
|
14013
|
+
no: (command, reason, message) => fail(write, json, command, reason, message, reason === "usage" ? EXIT.usage : EXIT.refused),
|
|
14014
|
+
prove: async (command, repo) => {
|
|
14015
|
+
const acting = await askCredentials(flags, context);
|
|
14016
|
+
if (acting === null) {
|
|
14017
|
+
fail(write, json, command, "usage", "this takes `--as <you> --token <t>`", EXIT.usage);
|
|
14018
|
+
return null;
|
|
14019
|
+
}
|
|
14020
|
+
const authed = authenticateApprover(store, acting.name, acting.token, repo);
|
|
14021
|
+
if (!authed.ok) {
|
|
14022
|
+
fail(write, json, command, "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
|
|
14023
|
+
return null;
|
|
14024
|
+
}
|
|
14025
|
+
return acting.name;
|
|
14026
|
+
},
|
|
14027
|
+
};
|
|
14028
|
+
}
|
|
14029
|
+
/**
|
|
14030
|
+
* `project fallback --repo <p> [--agents a,b,c] [--clear] [--allow-paid|--no-paid]`: which agents take over when one runs
|
|
14031
|
+
* out of its plan (fallback.ts). New tasks seal the chain; approved ones keep theirs. Changing it is an approver's act
|
|
14032
|
+
* with a password typed now, in the ledger. A lead's token never gets past the store's own check.
|
|
14033
|
+
*/
|
|
14034
|
+
async function projectFallbackCommand(positional, flags, context) {
|
|
14035
|
+
const command = "project fallback";
|
|
14036
|
+
const allowed = new Set(["repo", "agents", "clear", "allow-paid", "no-paid", "as", "token", "token-file", "token-env", "db", "json"]);
|
|
14037
|
+
for (const name of flags.keys())
|
|
14038
|
+
if (!allowed.has(name))
|
|
14039
|
+
return fail(context.write, context.json, command, "usage", `--${name} is not a project fallback option.`, EXIT.usage);
|
|
14040
|
+
const repoFlag = text(flags, "repo");
|
|
14041
|
+
const agents = text(flags, "agents");
|
|
14042
|
+
const clearing = flags.has("clear"), allowing = flags.has("allow-paid"), withdrawing = flags.has("no-paid");
|
|
14043
|
+
if (positional.length > 1 || repoFlag === undefined || (clearing && agents !== undefined) || (allowing && withdrawing))
|
|
14044
|
+
return fail(context.write, context.json, command, "usage", "Use project fallback --repo <project path> [--agents provider:model:plan|api-key,…] [--clear] [--allow-paid|--no-paid].", EXIT.usage);
|
|
14045
|
+
const repo = await knownProject(context, repoFlag, { listed: true, canonical: true });
|
|
14046
|
+
if (repo === undefined)
|
|
14047
|
+
return fail(context.write, context.json, command, "not-found", "That isn't a project Toolroll knows.", EXIT.refused);
|
|
14048
|
+
const words = () => {
|
|
14049
|
+
const setting = fallbackSettingOf(context.store, repo);
|
|
14050
|
+
if (setting === null)
|
|
14051
|
+
return [`${repo}: no fallback agents. A task stops when its agent runs out of its plan.`];
|
|
14052
|
+
return [`${repo}: when an agent runs out of its plan, the work moves on to:`,
|
|
14053
|
+
...setting.entries.map((one, at) => ` ${at + 1}. ${entryWords(one)}. ${entryMovesOnWords(one)}.`),
|
|
14054
|
+
setting.paidGrant === null ? "Paid fallback: not allowed." : `Paid fallback: allowed by ${setting.paidGrant.by}.`];
|
|
14055
|
+
};
|
|
14056
|
+
const shown = () => { const setting = fallbackSettingOf(context.store, repo); return { repo, agents: setting?.entries ?? [], paidAllowed: setting?.paidGrant != null }; };
|
|
14057
|
+
if (agents === undefined && !clearing && !allowing && !withdrawing)
|
|
14058
|
+
return succeed(context.write, context.json, command, shown(), words);
|
|
14059
|
+
const parsed = agents === undefined ? [] : agents.split(",").map(one => parseEntry(one.trim()));
|
|
14060
|
+
const bad = parsed.find((one) => typeof one === "string");
|
|
14061
|
+
if (bad !== undefined)
|
|
14062
|
+
return fail(context.write, context.json, command, "usage", bad, EXIT.usage);
|
|
14063
|
+
const acting = await askCredentials(flags, context);
|
|
14064
|
+
const verified = acting === null ? null : credentialApprover(context.store, acting.name, acting.token, repo);
|
|
14065
|
+
if (acting === null || !verified?.ok || !context.store.accountCanAccess(acting.name, repo))
|
|
14066
|
+
return fail(context.write, context.json, command, "refused", "An approver for this project changes its fallback agents, with their password: pass --as and --token.", EXIT.refused);
|
|
14067
|
+
const now = context.clock();
|
|
14068
|
+
const done = context.store.transact(() => {
|
|
14069
|
+
if (clearing) {
|
|
14070
|
+
const one = clearFallbackChain(context.store, repo, acting.name, now);
|
|
14071
|
+
if (!one.ok)
|
|
14072
|
+
return one;
|
|
14073
|
+
}
|
|
14074
|
+
if (agents !== undefined) {
|
|
14075
|
+
const one = setFallbackChain(context.store, repo, parsed, acting.name, now);
|
|
14076
|
+
if (!one.ok)
|
|
14077
|
+
return one;
|
|
14078
|
+
}
|
|
14079
|
+
if (allowing || withdrawing) {
|
|
14080
|
+
const one = setPaidGrant(context.store, repo, allowing, acting.name, now);
|
|
14081
|
+
if (!one.ok)
|
|
14082
|
+
return one;
|
|
14083
|
+
}
|
|
14084
|
+
return { ok: true };
|
|
14085
|
+
});
|
|
14086
|
+
if (!done.ok)
|
|
14087
|
+
return fail(context.write, context.json, command, "refused", done.problem, EXIT.refused);
|
|
14088
|
+
return succeed(context.write, context.json, command, shown(), () => [...words(), "Tasks scoped from now on use this; tasks already approved keep the chain they were approved with."]);
|
|
14089
|
+
}
|