toolroll 0.9.92 → 0.9.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -3
- package/dist/assignment.js +16 -3
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +2 -2
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +48 -7
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +73 -25
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +126 -0
- package/dist/check-facts.js +318 -0
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-report.d.ts +7 -0
- package/dist/check-report.js +4 -3
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +16 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -5
- package/dist/contracts/console-api.js +12 -5
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/evals.js +2 -1
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +83 -0
- package/dist/flaky.js +264 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/needs-you.d.ts +32 -13
- package/dist/needs-you.js +108 -49
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +540 -70
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +13 -2
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +26 -3
- package/dist/push.js +55 -15
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +87 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +26 -17
- package/dist/release-card.d.ts +74 -6
- package/dist/release-card.js +179 -23
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +38 -23
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +50 -1
- package/dist/release.js +118 -11
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +29 -21
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +4 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +19 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +2 -2
- package/dist/server/render-results.js +71 -73
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +156 -139
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +4 -1
- package/dist/server/route-table.js +52 -13
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +17 -6
- package/dist/server/tasks.js +273 -338
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +295 -0
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +148 -34
- package/dist/store.js +626 -177
- package/dist/surface.js +46 -5
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +36 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +18 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +82 -70
- package/dist/work-summary.d.ts +2 -2
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/operate.js
CHANGED
|
@@ -21,7 +21,7 @@ import { parseProtectedPaths } from "./approval-policy.js";
|
|
|
21
21
|
import { rulesSummary } from "./approval-rules-ui.js";
|
|
22
22
|
import { evidencePack, exportDay, ledgerExportChunks, standaloneEvidenceHtml } from "./evidence-pack.js";
|
|
23
23
|
import { matchesOutsideCheckpoint } from "./ledger-chain.js";
|
|
24
|
-
import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, parseCleanup, storageReport } from "./storage.js";
|
|
24
|
+
import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, idleCleanupWords, idleWords, parseCleanup, parseIdle, storageReport } from "./storage.js";
|
|
25
25
|
import { removeStaleTestTemp, tempRoots, testTempFolders } from "./test-temp.js";
|
|
26
26
|
import { storageSweepSupervisor } from "./storage-sweep-process.js";
|
|
27
27
|
import { idleStorageSweep, sweepSchedule, sweepScheduleWords } from "./storage-sweep-schedule.js";
|
|
@@ -33,6 +33,7 @@ import { personProof } from "./person-proof.js";
|
|
|
33
33
|
import { RemoteMains } from "./remote-main.js";
|
|
34
34
|
import { checkoutPlan, cleanCheckouts, discardCheckout, finishedCheckouts, slimKeptCheckouts, whyWords } from "./checkout-cleanup.js";
|
|
35
35
|
import { closeOutCheckouts, closeOutRuns, stoppedWords } from "./run-closeout.js";
|
|
36
|
+
import { proveFinishedRunsStopped, proveRunStopped } from "./stop-proof.js";
|
|
36
37
|
import { MIN_DAYS, RETENTION_KINDS, countWords, dailyRetention, isRetentionKind, lastSweepAt, parsePeriod, periodLabel, periodWords, retentionPlan, sweepWords } from "./retention.js";
|
|
37
38
|
import { checkPolicy, parseList, policyParts } from "./policy.js";
|
|
38
39
|
import { billingOf, budgetHoldWords, budgetLabel, budgetStates, monthNamed, monthOf, spendItems, subagentNames, usd as spendUsd } from "./spend.js";
|
|
@@ -63,7 +64,12 @@ import { sendSubagentSummaries } from "./subagent-admin.js";
|
|
|
63
64
|
import { runRequestedUndos, sendSubagentWeeklies } from "./subagent-week.js";
|
|
64
65
|
import { refreshConnections } from "./mcp-connect.js";
|
|
65
66
|
import { releaseCheckPurposeFor, releaseTermsFor } from "./release-carry.js";
|
|
67
|
+
import { approveReleaseCard, journalMatchesPlane, releaseApprovalCard, releaseCardLines, releaseTaskOf, waitingJournals } from "./release-approval.js";
|
|
68
|
+
import { journalFile } from "./release.js";
|
|
66
69
|
import { refreshTrustedMains, settleReleaseChecks } from "./nightly-check.js";
|
|
70
|
+
import { refreshCodeMaps } from "./code-map-build.js";
|
|
71
|
+
import { sweepDeliveries } from "./code-map-admission.js";
|
|
72
|
+
import { runMapCommand } from "./code-map-cli.js";
|
|
67
73
|
import { githubNightlyRepos, readGithubNightlies } from "./nightly-github.js";
|
|
68
74
|
import { exec as releaseExec, githubReads } from "./release-adapters.js";
|
|
69
75
|
import { releaseTools } from "./release-git.js";
|
|
@@ -73,11 +79,17 @@ import { followTeams } from "./teams.js";
|
|
|
73
79
|
import { followSlack } from "./slack.js";
|
|
74
80
|
import { validateScopeText } from "./task-text.js";
|
|
75
81
|
import { runMemoryCommand } from "./memory-cli.js";
|
|
82
|
+
import { runFlakyCommand } from "./flaky-cli.js";
|
|
83
|
+
import { backfillMemoryUse } from "./memory-use.js";
|
|
84
|
+
import { scheduledTidy } from "./memory-tidy.js";
|
|
85
|
+
import { CI_READ_EVERY_MS, ciCursorKey, ciRepositoriesOf, observeCiAttempts } from "./flaky-ci.js";
|
|
76
86
|
import { runKnowledgeApply, runKnowledgeCommand } from "./knowledge-cli.js";
|
|
77
87
|
import { FLOWS_VALUE_FLAGS, runFlowsCommand } from "./flows-cli.js";
|
|
78
88
|
import { runAssignmentCommand } from "./assignment-adapters.js";
|
|
79
89
|
import { applyProjectProfile, runProjectCommand } from "./project-cli.js";
|
|
80
90
|
import { pullRequestLines, runTaskMergeCommand, runTaskOutcomeCommand } from "./task-outcome-cli.js";
|
|
91
|
+
import { withTaskAlias } from "./task-aliases.js";
|
|
92
|
+
import { checklistLines, mergeChecklistOf } from "./merge-checklist.js";
|
|
81
93
|
import { taskReviewBrief, renderReviewBrief } from "./task-review-brief.js";
|
|
82
94
|
/**
|
|
83
95
|
* The commands that actually move work: authoring tasks, and the claim loop.
|
|
@@ -109,10 +121,10 @@ import { homedir, hostname } from "node:os";
|
|
|
109
121
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
110
122
|
import { releaseStalledUpdate, waitingUpdate } from "./toolroll-update.js";
|
|
111
123
|
import { dur } from "./design/kit.js";
|
|
112
|
-
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";
|
|
124
|
+
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";
|
|
113
125
|
import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
114
126
|
import { claimInstance, clearInstance, openInstanceSocket } from "./console-instance.js";
|
|
115
|
-
import { backgroundOff, backgroundOn, backgroundShow, laptopStatus, laptopStop } from "./laptop-background.js";
|
|
127
|
+
import { backgroundOff, backgroundOn, backgroundShow, laptopStatus, laptopStop, restartOwnBuilder } from "./laptop-background.js";
|
|
116
128
|
import { authorizePlanUnderMode } from "./plan-auto.js";
|
|
117
129
|
import { ghDispatchAdapter, mirrorTaskId, syncPass } from "./sync.js";
|
|
118
130
|
import { LIVE_FILE_BOUND, LIVE_RETAIN_MS, sweepLiveLogs } from "./live.js";
|
|
@@ -182,7 +194,7 @@ function planContractLines(store, evidenceRoot, taskId) {
|
|
|
182
194
|
...describeContractChanges(record.changes).map(line => ` ${line}`),
|
|
183
195
|
];
|
|
184
196
|
}
|
|
185
|
-
import { verdictWords as proofVerdictWords, matrixWords, semanticCoverage, coverageWords } from "./proof.js";
|
|
197
|
+
import { verdictWords as proofVerdictWords, matrixWords, plainReasonWords, semanticCoverage, coverageWords } from "./proof.js";
|
|
186
198
|
import { probeRepo } from "./probe.js";
|
|
187
199
|
import { diagnoseTaskDispatch, diagnosisIsDispatchable, scopeApprovedForDispatch, } from "./dispatch.js";
|
|
188
200
|
import { createDecisionServer } from "./serve.js";
|
|
@@ -234,6 +246,17 @@ import { resolvePhaseAgent, resolveScopeProfile, resolveRouteCandidates, routeOf
|
|
|
234
246
|
import { applyModelChoices, stateDigest } from "./model-choices.js";
|
|
235
247
|
import { isTaskSize, legOf, projectRoute, routeDigestOf, routeWords, TASK_SIZES, PHASES as ROUTE_PHASES } from "./phase-routing.js";
|
|
236
248
|
import { observeProviderReadiness, reportProviderReadinessAuthed } from "./runner.js";
|
|
249
|
+
import { retryVanishedWhenStopped } from "./vanished-retry.js";
|
|
250
|
+
import { requestMissingRechecks } from "./check-only.js";
|
|
251
|
+
import { recordBuilderWish } from "./builder-restart.js";
|
|
252
|
+
import { expireFastStarts, fastRefusalWords, fastStartBuild, keepGrownChange, prepareFastStart } from "./fast-start.js";
|
|
253
|
+
import { fastTermsLines } from "./fast-terms.js";
|
|
254
|
+
import { resumeAllowedReleases } from "./release-next.js";
|
|
255
|
+
import { fileGithubItem, readGithubItem } from "./github-import.js";
|
|
256
|
+
import { runRelease } from "./release.js";
|
|
257
|
+
import { releaseAdapters } from "./release-adapters.js";
|
|
258
|
+
import { routeChangeOf, routeLegacyPlans } from "./route-change.js";
|
|
259
|
+
import { passwordlessOn } from "./passwordless.js";
|
|
237
260
|
import { parseDemoUrl, projectDemoUrl, saveProjectDemo } from "./project-demo.js";
|
|
238
261
|
import { effectiveConcurrency, maySlotTake, parseProjectConcurrency, PROJECT_CONCURRENCY_DEFAULT, ProjectPasses, projectConcurrency, saveProjectConcurrency, savedProjectConcurrency } from "./project-concurrency.js";
|
|
239
262
|
import { activeWebhookTargets, webhookPass, LEGACY_WEBHOOK_WARNING, loadWebhookTargets, effectivePrimary, isMessagingChannel, loadConsoleUrl, phoneOrigin, readUpConsoleUrl, saveConsoleUrl, savePrimary, UP_CONSOLE_FILE } from "./webhooks.js";
|
|
@@ -317,10 +340,16 @@ export const OPERATE_HELP = `toolroll — operating the queue
|
|
|
317
340
|
the project's own demo or dev server, the one non-public address a
|
|
318
341
|
report's scout may screenshot
|
|
319
342
|
toolroll task evidence <id> the task's evidence pack as JSON (--html for a printable page; --out <file>)
|
|
343
|
+
toolroll map [<project>] [--json] a project's code map, made by Toolroll from main: areas and how they use each other
|
|
344
|
+
toolroll map area <id> | file <path> | search <words> [--cursor <c>]
|
|
345
|
+
one area, one file, or files and exported names; --cursor reads the next page of the same version
|
|
346
|
+
toolroll map refresh ask for a check of main now (an approver)
|
|
320
347
|
toolroll storage where the disk goes: database, build checkouts, releases, evidence, leftover test temp folders
|
|
321
348
|
toolroll storage clean [--yes] preview removing finished tasks' clean checkouts (their branches stay) and test temp folders older than a day; --yes removes them
|
|
322
349
|
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)
|
|
323
|
-
toolroll storage cleanup <when> remove a finished task's clean
|
|
350
|
+
toolroll storage cleanup <when> remove a finished task's clean folder: finished|2d|7d|never (instance operator)
|
|
351
|
+
toolroll storage cleanup idle <days>
|
|
352
|
+
remove an idle task's clean folder: 7|14|30|never (instance operator)
|
|
324
353
|
toolroll storage discard <path> --yes throw away a checkout kept for its changes; its branch stays
|
|
325
354
|
toolroll integrations [--json] which integrations work: Connected, Not set up or Broken, and what to do (--saved: no new checks)
|
|
326
355
|
toolroll monitoring where the audit stream and traces go, and how each destination is doing
|
|
@@ -685,10 +714,10 @@ Exit codes
|
|
|
685
714
|
* (surface.ts) is tested for exact equality against them.
|
|
686
715
|
*/
|
|
687
716
|
export const TASK_ACTIONS = [
|
|
688
|
-
"add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
|
|
717
|
+
"add", "new", "keep", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
|
|
689
718
|
"reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan",
|
|
690
719
|
"review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask",
|
|
691
|
-
"checks", "add-tests", "archive", "restore", "tidy", "ship",
|
|
720
|
+
"checks", "add-tests", "archive", "restore", "tidy", "ship", "ready",
|
|
692
721
|
];
|
|
693
722
|
export const PUBLISH_ACTIONS = ["setup", "grant", "revoke", "status", "unblock", "rearm", "merge", "refire"];
|
|
694
723
|
export const CONFIG_ACTIONS = ["show", "set", "clear"];
|
|
@@ -703,11 +732,11 @@ export const KEYS_ACTIONS = ["status", "set", "clear", "verify", "auth"];
|
|
|
703
732
|
/** Flags whose value is a secret someone types or pastes: taken as the next argument even when it starts with "--". */
|
|
704
733
|
export const SECRET_VALUE_FLAGS = new Set(["token", "password"]);
|
|
705
734
|
export const OPERATE_VALUE_FLAGS = new Set([
|
|
706
|
-
"key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend",
|
|
735
|
+
"key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend", "build", "from",
|
|
707
736
|
"allow", "selector", "paths", "credentials", "repo", "token", "capacity",
|
|
708
737
|
"goal", "not", "touches", "acceptance", "candidate", "by", "digest", "as", "branch", "pool", "base", "model", "turns",
|
|
709
738
|
"max", "cap", "probe", "kind", "expires", "cmd", "since", "repair-model",
|
|
710
|
-
"choose", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
|
|
739
|
+
"choose", "choice", "claim", "revision", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
|
|
711
740
|
"for", "tick-every", "bridge-every", "reconcile-every", "incarnation",
|
|
712
741
|
"say", "ceiling-usd", "cap-usd",
|
|
713
742
|
// evals: the cases a diagnostic campaign runs, its k, an attempt's world and what its builds may not read, and the
|
|
@@ -751,8 +780,14 @@ export const OPERATE_VALUE_FLAGS = new Set([
|
|
|
751
780
|
"read-deny", "mach",
|
|
752
781
|
// task scope --release <version>:<pr>:<branch>: a release scope's terms (release-once.md).
|
|
753
782
|
"release",
|
|
783
|
+
// task accept --despite "<reason>": acceptance with a reason, never a completion.
|
|
784
|
+
"despite",
|
|
754
785
|
]);
|
|
755
786
|
export const OPERATE_BOOLEAN_FLAGS = new Set([
|
|
787
|
+
// task accept --finish: the person's Accept, exactly task complete's exact-receipt completion.
|
|
788
|
+
"finish",
|
|
789
|
+
// task new --preview: the terminal UI's own, the exact terms with nothing started.
|
|
790
|
+
"preview",
|
|
756
791
|
// evals run: a report-only campaign; keep each attempt's world for a look afterwards.
|
|
757
792
|
"diagnostic", "keep",
|
|
758
793
|
// task scope --release ... --no-carry: every later commit asks again.
|
|
@@ -844,6 +879,9 @@ export function parseOperateArgs(argv, ownValues = new Set()) {
|
|
|
844
879
|
}
|
|
845
880
|
/** Route a native command and return its exit code. Its password source cannot be selected by an HTTP request. */
|
|
846
881
|
export async function runOperate(command, argv, write, options = {}) {
|
|
882
|
+
// People's names for task commands (task-aliases.ts) are the commands they name, before any rule reads the command.
|
|
883
|
+
if (command === "task")
|
|
884
|
+
argv = withTaskAlias(["task", ...argv]).slice(1);
|
|
847
885
|
// Each command gets its own remembered-login holder (Passwordless: "cli-login" is this command's, never the next one's).
|
|
848
886
|
return withPasswordSource(CLI_PASSWORD_SOURCE, () => rememberedLogin.run({ login: null }, () => runOperateLocal(command, argv, write, options)));
|
|
849
887
|
}
|
|
@@ -1059,6 +1097,8 @@ export async function runOperateAs(argv, opts) {
|
|
|
1059
1097
|
const source = opts.source === "mcp" ? "mcp" : "api";
|
|
1060
1098
|
const options = opts.options ?? {};
|
|
1061
1099
|
const now = options.now ?? new Date();
|
|
1100
|
+
// People's names for task commands are the commands they name, before the remote policy reads the command.
|
|
1101
|
+
argv = [...withTaskAlias(argv)];
|
|
1062
1102
|
const [root = "", ...rest] = argv;
|
|
1063
1103
|
const json = rest.includes("--json");
|
|
1064
1104
|
let invocation = root;
|
|
@@ -1215,6 +1255,9 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1215
1255
|
switch (command) {
|
|
1216
1256
|
case "status":
|
|
1217
1257
|
return statusCommand(positional, flags, context);
|
|
1258
|
+
// `toolroll release approve` (cli.ts routes only this verb here; every other `release` form is release-cli.ts).
|
|
1259
|
+
case "release":
|
|
1260
|
+
return releaseApproveCommand(positional, flags, context);
|
|
1218
1261
|
case "evals":
|
|
1219
1262
|
return evalsCommand(positional, flags, context);
|
|
1220
1263
|
case "ready":
|
|
@@ -1299,6 +1342,8 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1299
1342
|
const verified = acting === null || lead ? null : authenticateApprover(context.store, acting.name, acting.token, repo === undefined ? null : resolve(repo));
|
|
1300
1343
|
return runKnowledgeApply(flags, { ...context, now: context.clock(), actor: verified !== null && verified.ok ? acting.name : null });
|
|
1301
1344
|
}
|
|
1345
|
+
case "map":
|
|
1346
|
+
return mapCommand(positional, flags, context);
|
|
1302
1347
|
case "models": {
|
|
1303
1348
|
// A local owner's lowering act (models turn-off / dismiss, through the terminal UI) asks for no credential: the
|
|
1304
1349
|
// rule door re-checks the owner and its exact act itself.
|
|
@@ -1319,10 +1364,13 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1319
1364
|
const acting = await askCredentials(flags, context);
|
|
1320
1365
|
if (memoryWriteVerb(positional) && acting !== null && parseLeadToken(acting.token) !== null)
|
|
1321
1366
|
return fail(context.write, context.json, `memory ${positional[0]}`, "unauthenticated", MEMORY_WRITE_REFUSAL, EXIT.refused);
|
|
1322
|
-
|
|
1323
|
-
const
|
|
1367
|
+
// The laptop's local owner: only the rows LOCAL_ALLOW names reach here, and each writer checks the exact act.
|
|
1368
|
+
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)));
|
|
1369
|
+
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
1324
1370
|
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 }) });
|
|
1325
1371
|
}
|
|
1372
|
+
case "flaky":
|
|
1373
|
+
return flakyCommand(positional, flags, context);
|
|
1326
1374
|
case "claim":
|
|
1327
1375
|
return claimCommand(positional, flags, context);
|
|
1328
1376
|
case "heartbeat":
|
|
@@ -1758,7 +1806,7 @@ async function runnerCommand(positional, flags, context) {
|
|
|
1758
1806
|
// at the mint. A caller flag never grants what the mint did not.
|
|
1759
1807
|
const acting = await askCredentials(flags, context);
|
|
1760
1808
|
if (acting === null) {
|
|
1761
|
-
return fail(write, json, "runner register", "usage", "`runner register <name> --repo <path> --as <you> --token <t>` —
|
|
1809
|
+
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);
|
|
1762
1810
|
}
|
|
1763
1811
|
const authenticated = credentialApprover(store, acting.name, acting.token);
|
|
1764
1812
|
if (!authenticated.ok)
|
|
@@ -1858,7 +1906,7 @@ async function runnerCommand(positional, flags, context) {
|
|
|
1858
1906
|
const acting = await askCredentials(flags, context);
|
|
1859
1907
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
1860
1908
|
if (acting === null || !verified?.ok)
|
|
1861
|
-
return refuseApprover(context, "runner retire", verified, "`runner retire <name> --as <you> --token <t>` —
|
|
1909
|
+
return refuseApprover(context, "runner retire", verified, "`runner retire <name> --as <you> --token <t>` — retiring a builder is an approver's act", name);
|
|
1862
1910
|
const retired = store.retireRunner(name, now, mutationFrom(flags, now));
|
|
1863
1911
|
if (!retired)
|
|
1864
1912
|
return fail(write, json, "runner retire", "unknown", `no runner \`${name}\``, EXIT.refused);
|
|
@@ -1874,7 +1922,7 @@ async function runnerCommand(positional, flags, context) {
|
|
|
1874
1922
|
// stated authority, never accumulation nobody can read back.
|
|
1875
1923
|
const acting = await askCredentials(flags, context);
|
|
1876
1924
|
if (acting === null) {
|
|
1877
|
-
return fail(write, json, "runner bind", "usage", "`runner bind <name> --repo <path> --as <you> --token <t>` —
|
|
1925
|
+
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);
|
|
1878
1926
|
}
|
|
1879
1927
|
const authenticated = credentialApprover(store, acting.name, acting.token);
|
|
1880
1928
|
if (!authenticated.ok)
|
|
@@ -1954,7 +2002,7 @@ async function coordinatorCommand(positional, flags, context) {
|
|
|
1954
2002
|
}
|
|
1955
2003
|
const acting = await askCredentials(flags, context);
|
|
1956
2004
|
if (acting === null) {
|
|
1957
|
-
return fail(write, json, "coordinator mint", "usage", "`coordinator mint <name> --repo <path> --as <you> --token <t>` — minting
|
|
2005
|
+
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);
|
|
1958
2006
|
}
|
|
1959
2007
|
const authenticated = credentialApprover(store, acting.name, acting.token);
|
|
1960
2008
|
if (!authenticated.ok)
|
|
@@ -2001,7 +2049,7 @@ async function coordinatorCommand(positional, flags, context) {
|
|
|
2001
2049
|
const acting = await askCredentials(flags, context);
|
|
2002
2050
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
2003
2051
|
if (acting === null || !verified?.ok)
|
|
2004
|
-
return refuseApprover(context, "coordinator revoke", verified, "`coordinator revoke <cid> --as <you> --token <t>` — revoking is an
|
|
2052
|
+
return refuseApprover(context, "coordinator revoke", verified, "`coordinator revoke <cid> --as <you> --token <t>` — revoking is an approver's act", name);
|
|
2005
2053
|
const revoked = revokeCoordinator(store, name, acting.name, now);
|
|
2006
2054
|
if (!revoked.ok) {
|
|
2007
2055
|
const said = revoked.reason === "unknown" ? `no coordinator \`${name}\` — \`coordinator list\` shows cids` : "already revoked";
|
|
@@ -3576,7 +3624,16 @@ async function reconcileCommand(flags, context) {
|
|
|
3576
3624
|
// A witness reserved for a spawn that never made a process (a crash or a
|
|
3577
3625
|
// thrown spawn) settles once its run's process groups are proven gone.
|
|
3578
3626
|
store.settleUnspawnedWitnesses(clock());
|
|
3627
|
+
// What no record proves stopped, Toolroll proves itself from this computer's processes (stop-proof.ts): nobody is
|
|
3628
|
+
// asked to confirm a build stopped, so a release waiting on its check moves on by itself.
|
|
3629
|
+
await proveFinishedRunsStopped(store, clock);
|
|
3579
3630
|
store.settleQuiescentStops(clock());
|
|
3631
|
+
// A vanished build whose old process is now proven stopped gets its retry decision (vanished-retry.ts).
|
|
3632
|
+
retryVanishedWhenStopped(store, clock());
|
|
3633
|
+
// A fast start that didn't start in time goes back to the ordinary Approve plan (fast-start.ts).
|
|
3634
|
+
expireFastStarts(store, clock());
|
|
3635
|
+
// A plan from before agent routing gets its route and one Approve (route-change.ts).
|
|
3636
|
+
routeLegacyPlans(store, clock());
|
|
3580
3637
|
// An update whose updater ended while letting work finish must not leave new work paused.
|
|
3581
3638
|
try {
|
|
3582
3639
|
releaseStalledUpdate(dirname(context.databaseFile), clock());
|
|
@@ -6457,6 +6514,11 @@ async function runWatchLoop(args) {
|
|
|
6457
6514
|
const batched = await runBatchChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
|
|
6458
6515
|
if (batched > 0)
|
|
6459
6516
|
progress(`watch: settled ${batched} batch-checked result(s)`);
|
|
6517
|
+
// A held release a person let go on resumes by itself, in the background of this pass (release-next.ts).
|
|
6518
|
+
if (context.agentRunner === undefined)
|
|
6519
|
+
void resumeHeldReleases(store, dirname(context.databaseFile), context.clock());
|
|
6520
|
+
// A result whose verdict went missing asks for its one automatic recheck (check-only.ts).
|
|
6521
|
+
requestMissingRechecks(store, context.evidenceRoot, context.clock());
|
|
6460
6522
|
const ran = await runWaitingChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
|
|
6461
6523
|
if (ran > 0)
|
|
6462
6524
|
progress(`watch: ran ${ran} follow-up check(s)`);
|
|
@@ -6469,11 +6531,27 @@ async function runWatchLoop(args) {
|
|
|
6469
6531
|
// all. A test's injected agent runner means a test: no real gh.
|
|
6470
6532
|
if (context.agentRunner === undefined)
|
|
6471
6533
|
await refreshGithubNightlies(store, context.clock);
|
|
6534
|
+
// The flaky tests list's GitHub rows (flaky-ci.ts): read-only, each project at most every 30 minutes.
|
|
6535
|
+
if (context.agentRunner === undefined)
|
|
6536
|
+
await refreshLearning(store, context.clock);
|
|
6472
6537
|
// A finished check may send a build's card down its failure path: now, not on the next beat.
|
|
6473
6538
|
if (ran + batched > 0)
|
|
6474
6539
|
moveCards(store, repo, context.clock(), context.evidenceRoot);
|
|
6475
6540
|
},
|
|
6476
6541
|
});
|
|
6542
|
+
// The code map (docs/plans/code-map.md §2): its own beat, so a slow remote holds nothing else. A test's injected agent
|
|
6543
|
+
// runner means a test: no real fetch.
|
|
6544
|
+
const codeMapChecks = startMaintenance({
|
|
6545
|
+
intervalMs: 60_000,
|
|
6546
|
+
shouldStop: stopping,
|
|
6547
|
+
onError: error => progress(`watch: a code map check failed — ${describe(error)}; it tries again`),
|
|
6548
|
+
run: async () => {
|
|
6549
|
+
if (store.isDemo() || context.agentRunner !== undefined)
|
|
6550
|
+
return;
|
|
6551
|
+
await refreshCodeMaps(store, { scratch: join(context.evidenceRoot, ".code-map"), clock: context.clock });
|
|
6552
|
+
sweepDeliveries(store);
|
|
6553
|
+
},
|
|
6554
|
+
});
|
|
6477
6555
|
// Flows on their own cadence (flow-cadence.ts), not between builds: while builds hold every slot of this
|
|
6478
6556
|
// project, a card whose step finished still moves within one beat. Moves run apart from triggers, replies
|
|
6479
6557
|
// and steps, so a long check script doesn't hold them either. Neither starts a build: a card entering a
|
|
@@ -6753,6 +6831,7 @@ async function runWatchLoop(args) {
|
|
|
6753
6831
|
await Promise.all(lanes);
|
|
6754
6832
|
await maintenance.stop();
|
|
6755
6833
|
await followUpChecks.stop();
|
|
6834
|
+
await codeMapChecks.stop();
|
|
6756
6835
|
await flowMoves.stop();
|
|
6757
6836
|
await flowSteps.stop();
|
|
6758
6837
|
await modelWatch.stop();
|
|
@@ -7427,6 +7506,8 @@ async function upCommand(flags, context) {
|
|
|
7427
7506
|
retireRunnerIfCurrent(store, runnerName, runnerToken, clock());
|
|
7428
7507
|
return fail(write, json, "up", "runner-alive", "The worker process record could not be saved; nothing started", EXIT.refused);
|
|
7429
7508
|
}
|
|
7509
|
+
// What a person asked for, and what ran: an automatic restart acts on exactly this (builder-restart.ts).
|
|
7510
|
+
recordBuilderWish(store, "running", "up", packageVersion(), clock());
|
|
7430
7511
|
doorAnswer = answer;
|
|
7431
7512
|
break;
|
|
7432
7513
|
}
|
|
@@ -8599,6 +8680,9 @@ async function statusCommand(positional, flags, context) {
|
|
|
8599
8680
|
// This installation's console and background service (easy-start D), beside its updates and integrations: a person
|
|
8600
8681
|
// limited to some projects returned above without it; an all-project caller reads what a local one does.
|
|
8601
8682
|
const laptop = { laptop: await laptopStatus(context.store, context.databaseFile, context.laptopSeams) };
|
|
8683
|
+
// Nothing else on this computer is left to notice a builder that died: a look at status brings it back when it may.
|
|
8684
|
+
if (!laptop.laptop.running && laptop.laptop.background === "on" && !machineActing())
|
|
8685
|
+
await restartOwnBuilder(context.store, context.databaseFile, context.laptopSeams, context.clock()).catch(() => null);
|
|
8602
8686
|
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])]);
|
|
8603
8687
|
}
|
|
8604
8688
|
function unsentRepliesLine(unsent) {
|
|
@@ -8743,10 +8827,16 @@ function taskAction(action, rest, flags, context) {
|
|
|
8743
8827
|
demo: context.store.isDemo(), credentials: () => askCredentials(flags, context), ...(context.publishExec === undefined ? {} : { exec: context.publishExec }) });
|
|
8744
8828
|
case "add":
|
|
8745
8829
|
return addTask(rest, flags, context);
|
|
8830
|
+
case "new":
|
|
8831
|
+
return newTaskCommand(rest, flags, context);
|
|
8832
|
+
case "keep":
|
|
8833
|
+
return keepGrownCommand(rest, flags, context);
|
|
8746
8834
|
case "checks":
|
|
8747
8835
|
return runTaskChecks(rest, flags, context);
|
|
8748
8836
|
case "ship":
|
|
8749
8837
|
return taskShipCommand(rest, context);
|
|
8838
|
+
case "ready":
|
|
8839
|
+
return taskReadyCommand(rest, context);
|
|
8750
8840
|
case "add-tests":
|
|
8751
8841
|
return addTestsTask(rest, flags, context);
|
|
8752
8842
|
case "list":
|
|
@@ -9323,8 +9413,8 @@ function showTask(positional, context) {
|
|
|
9323
9413
|
...(task.state !== "done" || detail.proofVerdict === null
|
|
9324
9414
|
? []
|
|
9325
9415
|
: [
|
|
9326
|
-
`
|
|
9327
|
-
...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.join("
|
|
9416
|
+
` checked: ${proofVerdictWords(current?.receipt?.runId === latestFinished?.id ? current?.readiness?.verdict ?? detail.proofVerdict : detail.proofVerdict, detail.proofReasons).word}${detail.proofAccepted ? " (accepted)" : ""}`,
|
|
9417
|
+
...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.map(plainReasonWords).join(" ")}`] : []),
|
|
9328
9418
|
...matrixWords(detail.proofMatrix),
|
|
9329
9419
|
...(detail.semanticCoverage === null ? [] : coverageWords(detail.semanticCoverage).map(line => ` ${line}`)),
|
|
9330
9420
|
]),
|
|
@@ -9332,11 +9422,11 @@ function showTask(positional, context) {
|
|
|
9332
9422
|
? []
|
|
9333
9423
|
: detail.report.ok
|
|
9334
9424
|
? [
|
|
9335
|
-
` report: ${detail.report.report.title} (
|
|
9425
|
+
` report: ${detail.report.report.title} (build #${detail.report.run})`,
|
|
9336
9426
|
` ${detail.report.report.summary}`,
|
|
9337
9427
|
...detail.report.report.followUps.map((one, index) => ` follow-up ${index + 1}: ${one.title}`),
|
|
9338
9428
|
]
|
|
9339
|
-
: [` report: ${detail.report.problem} (
|
|
9429
|
+
: [` report: ${detail.report.problem} (build #${detail.report.run})`]),
|
|
9340
9430
|
...pullRequestLines(detail.pullRequest),
|
|
9341
9431
|
...(base === null ? [] : [` base: ${baseStandingWords(base)}`]),
|
|
9342
9432
|
...(detail.blockedBy.length > 0 ? [` waits for ${detail.blockedBy.join(", ")}`] : []),
|
|
@@ -9369,7 +9459,7 @@ function showTask(positional, context) {
|
|
|
9369
9459
|
...detail.runs
|
|
9370
9460
|
.filter(one => one.route !== null)
|
|
9371
9461
|
.slice(0, 8)
|
|
9372
|
-
.map(one => `
|
|
9462
|
+
.map(one => ` build #${one.id} ${one.role.padEnd(8)} ${one.route.provider}${one.route.model === null ? "" : ` · ${one.route.model}`} [${one.route.chosen}]`),
|
|
9373
9463
|
]);
|
|
9374
9464
|
}
|
|
9375
9465
|
/** The review status lines `task show` prints (v50): the state word, the
|
|
@@ -9409,47 +9499,72 @@ function taskRouteStandingLines(id, routed) {
|
|
|
9409
9499
|
if (routed === null)
|
|
9410
9500
|
return [];
|
|
9411
9501
|
if (routed.kind === "route") {
|
|
9412
|
-
return [` agents ${routed.source === "approved" ? "
|
|
9502
|
+
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"}`];
|
|
9413
9503
|
}
|
|
9414
9504
|
if (routed.kind === "legacy") {
|
|
9415
9505
|
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`];
|
|
9416
9506
|
}
|
|
9417
|
-
return [` agents UNREADABLE — ${routed.problem}`, ` nothing runs for ${id} until its
|
|
9507
|
+
return [` agents UNREADABLE — ${routed.problem}`, ` nothing runs for ${id} until its plan is saved again and approved`];
|
|
9418
9508
|
}
|
|
9419
9509
|
/**
|
|
9420
|
-
* `task accept <id
|
|
9421
|
-
*
|
|
9422
|
-
*
|
|
9423
|
-
*
|
|
9510
|
+
* `task accept <id>`: every call names what it means (docs/plans/plain-toolroll.md P1.1), so the same words never
|
|
9511
|
+
* quietly start meaning something else.
|
|
9512
|
+
* - `--finish` or `--digest <receipt>` is the person's Accept: exactly `task complete`'s exact-receipt completion, with
|
|
9513
|
+
* the same rule that `--json` and scoped credentials must pass `--digest`.
|
|
9514
|
+
* - `--despite "<reason>"` (`--note` is its alias) is acceptance only: an explicit acceptance of a short or refuted
|
|
9515
|
+
* result with a reason, credentialed like approving a plan. It never completes, and never combines with the above.
|
|
9516
|
+
* - A bare `task accept <id>` refuses, for good, naming both.
|
|
9424
9517
|
*/
|
|
9425
9518
|
async function acceptTaskProof(positional, flags, context) {
|
|
9426
9519
|
const { store, write, json, now } = context;
|
|
9520
|
+
const finishing = flags.has("finish") || flags.has("digest");
|
|
9521
|
+
const despiteFlag = flags.has("despite") ? flags.get("despite") : flags.get("note");
|
|
9522
|
+
const despiting = flags.has("despite") || flags.has("note");
|
|
9523
|
+
if (flags.has("despite") && flags.has("note"))
|
|
9524
|
+
return fail(write, json, "task accept", "usage", "Use --despite \"<reason>\" once (--note is the same flag).", EXIT.usage);
|
|
9525
|
+
if (finishing && despiting) {
|
|
9526
|
+
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);
|
|
9527
|
+
}
|
|
9528
|
+
if (!finishing && !despiting) {
|
|
9529
|
+
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);
|
|
9530
|
+
}
|
|
9531
|
+
if (finishing) {
|
|
9532
|
+
// The person's Accept: `task complete`, unchanged, under this command's name.
|
|
9533
|
+
if (flags.get("finish") !== undefined && flags.get("finish") !== true)
|
|
9534
|
+
return fail(write, json, "task accept", "usage", "--finish takes no value.", EXIT.usage);
|
|
9535
|
+
const rest = new Map(flags);
|
|
9536
|
+
rest.delete("finish");
|
|
9537
|
+
return runTaskOutcomeCommand("complete", positional, rest, { ...context, command: "task accept", operator: async () => {
|
|
9538
|
+
const acting = await askCredentials(flags, context);
|
|
9539
|
+
if (acting === null)
|
|
9540
|
+
return null;
|
|
9541
|
+
const authenticated = verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
|
|
9542
|
+
return authenticated.ok ? authenticated.who : null;
|
|
9543
|
+
} });
|
|
9544
|
+
}
|
|
9427
9545
|
const id = positional[0];
|
|
9428
9546
|
if (id === undefined)
|
|
9429
|
-
return fail(write, json, "task accept", "usage", "`toolroll task accept <id>
|
|
9547
|
+
return fail(write, json, "task accept", "usage", "`toolroll task accept <id> --despite \"<reason>\" --as <you> --token <t>`", EXIT.usage);
|
|
9548
|
+
if (typeof despiteFlag !== "string" || despiteFlag.trim() === "")
|
|
9549
|
+
return fail(write, json, "task accept", "usage", "--despite needs the reason you accept it anyway: `--despite \"<reason>\"`.", EXIT.usage);
|
|
9550
|
+
const validated = validateNote(despiteFlag);
|
|
9551
|
+
if (!validated.ok)
|
|
9552
|
+
return fail(write, json, "task accept", "invalid", validated.problem, EXIT.usage);
|
|
9430
9553
|
const task = store.getTask(id);
|
|
9431
9554
|
if (task === null)
|
|
9432
9555
|
return fail(write, json, "task accept", "unknown-task", `no task \`${id}\``, EXIT.refused);
|
|
9433
9556
|
const acting = await askCredentials(flags, context);
|
|
9434
9557
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
9435
9558
|
if (acting === null || !verified?.ok)
|
|
9436
|
-
return refuseApprover(context, "task accept", verified, "accepting
|
|
9559
|
+
return refuseApprover(context, "task accept", verified, "accepting a result with a reason is a person's act — it takes `--as <you> --token <t>`", id);
|
|
9437
9560
|
const ref = store.refFor(BUILT_IN, id);
|
|
9438
9561
|
// v40 fix: a reviewer run is never the attempt whose proof is accepted.
|
|
9439
9562
|
const latest = store.runsFor(ref.id).find(one => one.finishedAt !== null && (one.role === "builder" || one.role === "scout"));
|
|
9440
9563
|
if (latest === undefined)
|
|
9441
9564
|
return fail(write, json, "task accept", "no-run", `${id} has no finished attempt to accept`, EXIT.refused);
|
|
9442
|
-
|
|
9443
|
-
let note = null;
|
|
9444
|
-
if (rawNote !== undefined && rawNote.trim() !== "") {
|
|
9445
|
-
const validated = validateNote(rawNote);
|
|
9446
|
-
if (!validated.ok)
|
|
9447
|
-
return fail(write, json, "task accept", "invalid", validated.problem, EXIT.usage);
|
|
9448
|
-
note = validated.note;
|
|
9449
|
-
}
|
|
9450
|
-
store.acceptProof(latest.id, acting.name, note, now);
|
|
9565
|
+
store.acceptProof(latest.id, acting.name, validated.note, now);
|
|
9451
9566
|
return succeed(write, json, "task accept", { id, run: latest.id, acceptedBy: acting.name }, () => [
|
|
9452
|
-
`Accepted
|
|
9567
|
+
`Accepted ${id}'s build #${latest.id} with your reason, ${acting.name}. It isn't finished: \`toolroll task accept ${id} --finish\` finishes it.`,
|
|
9453
9568
|
]);
|
|
9454
9569
|
}
|
|
9455
9570
|
/**
|
|
@@ -9721,7 +9836,7 @@ async function pauseTask(positional, flags, context) {
|
|
|
9721
9836
|
return fail(write, json, "task pause", "usage", "`toolroll task pause <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
|
|
9722
9837
|
const acting = await askCredentials(flags, context);
|
|
9723
9838
|
if (acting === null)
|
|
9724
|
-
return fail(write, json, "task pause", "usage", "pausing takes `--as <you> --token <t>` — it is an
|
|
9839
|
+
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);
|
|
9725
9840
|
const authed = authenticateApprover(store, acting.name, acting.token);
|
|
9726
9841
|
if (!authed.ok)
|
|
9727
9842
|
return fail(write, json, "task pause", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
|
|
@@ -9739,7 +9854,7 @@ async function continueTask(positional, flags, context) {
|
|
|
9739
9854
|
return fail(write, json, "task continue", "usage", "`toolroll task continue <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
|
|
9740
9855
|
const acting = await askCredentials(flags, context);
|
|
9741
9856
|
if (acting === null)
|
|
9742
|
-
return fail(write, json, "task continue", "usage", "resuming takes `--as <you> --token <t>` — it is an
|
|
9857
|
+
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);
|
|
9743
9858
|
const authed = authenticateApprover(store, acting.name, acting.token);
|
|
9744
9859
|
if (!authed.ok)
|
|
9745
9860
|
return fail(write, json, "task continue", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
|
|
@@ -10274,6 +10389,11 @@ async function approveTask(positional, flags, context) {
|
|
|
10274
10389
|
interactive() && !json) {
|
|
10275
10390
|
write(`Approving lets a builder work on ${id}, within exactly this:`);
|
|
10276
10391
|
write("");
|
|
10392
|
+
const changed = routeChangeOf(store, id);
|
|
10393
|
+
if (changed !== null) {
|
|
10394
|
+
write(changed);
|
|
10395
|
+
write("");
|
|
10396
|
+
}
|
|
10277
10397
|
for (const line of describeScope(scope))
|
|
10278
10398
|
write(line);
|
|
10279
10399
|
for (const line of planContractLines(store, context.evidenceRoot, id))
|
|
@@ -10301,6 +10421,11 @@ async function approveTask(positional, flags, context) {
|
|
|
10301
10421
|
}
|
|
10302
10422
|
write(`Would approve this, and let a builder work on ${id}:`);
|
|
10303
10423
|
write("");
|
|
10424
|
+
const changed = routeChangeOf(store, id);
|
|
10425
|
+
if (changed !== null) {
|
|
10426
|
+
write(changed);
|
|
10427
|
+
write("");
|
|
10428
|
+
}
|
|
10304
10429
|
for (const line of describeScope(scope))
|
|
10305
10430
|
write(line);
|
|
10306
10431
|
for (const line of planContractLines(store, context.evidenceRoot, id))
|
|
@@ -10737,7 +10862,7 @@ async function stopTaskCommand(positional, flags, context) {
|
|
|
10737
10862
|
const acting = await askCredentials(flags, context);
|
|
10738
10863
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
10739
10864
|
if (acting === null || !verified?.ok)
|
|
10740
|
-
return refuseApprover(context, "task stop", verified, "stopping takes `--as <you> --token <t>` — it is an
|
|
10865
|
+
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);
|
|
10741
10866
|
const asked = requestTaskStop(store, { taskId: id, runId, by: acting.name, via: "cli" }, clock());
|
|
10742
10867
|
if (!asked.ok)
|
|
10743
10868
|
return fail(write, json, "task stop", asked.reason, asked.detail, EXIT.refused, { run: runId });
|
|
@@ -10790,43 +10915,47 @@ async function resumeTaskCommand(positional, flags, context) {
|
|
|
10790
10915
|
]);
|
|
10791
10916
|
}
|
|
10792
10917
|
/**
|
|
10793
|
-
* `toolroll run settle <id> --
|
|
10794
|
-
*
|
|
10795
|
-
*
|
|
10796
|
-
*
|
|
10797
|
-
*
|
|
10918
|
+
* `toolroll run settle <id> --as <you> --token <t>`: have Toolroll check again whether a finished run stopped. Nobody
|
|
10919
|
+
* vouches that a build stopped (a person can't see whether a process exited): this runs Toolroll's own look at this
|
|
10920
|
+
* computer's processes (stop-proof.ts), the one the worker makes on its own, and settles the run when none of them is
|
|
10921
|
+
* the run's. A leftover proven the run's is stopped; anything else is left alone. `--why` is still accepted from older
|
|
10922
|
+
* scripts and ignored.
|
|
10798
10923
|
*/
|
|
10799
10924
|
async function runCommand(positional, flags, context) {
|
|
10800
10925
|
const { store, write, json, clock } = context;
|
|
10801
10926
|
const [action, id, ...extra] = positional;
|
|
10802
10927
|
const command = "run settle";
|
|
10803
|
-
const usage = "`toolroll run settle <run-id
|
|
10928
|
+
const usage = "`toolroll run settle <run-id>`";
|
|
10804
10929
|
if (action !== "settle")
|
|
10805
10930
|
return fail(write, json, "run", "usage", usage, EXIT.usage);
|
|
10806
10931
|
const runId = Number(id ?? "");
|
|
10807
|
-
const why = text(flags, "why")?.trim();
|
|
10808
10932
|
if (id === undefined || extra.length > 0 || !/^[1-9]\d*$/.test(id) || !Number.isSafeInteger(runId))
|
|
10809
10933
|
return fail(write, json, command, "usage", usage, EXIT.usage);
|
|
10810
|
-
if (why === undefined || why === "" || why.length > 300 || /[\x00-\x1f\x7f]/.test(why))
|
|
10811
|
-
return fail(write, json, command, "usage", "say why in one line with --why (at most 300 characters) — it is kept in the ledger", EXIT.usage);
|
|
10812
10934
|
const run = store.getRun(runId);
|
|
10813
10935
|
if (run === null)
|
|
10814
10936
|
return fail(write, json, command, "unknown-run", `no run #${runId}`, EXIT.refused);
|
|
10815
10937
|
const acting = await askCredentials(flags, context);
|
|
10816
10938
|
if (acting === null)
|
|
10817
|
-
return fail(write, json, command, "usage", "
|
|
10939
|
+
return fail(write, json, command, "usage", "checking a run again takes `--as <you> --token <t>` (or your sign-in from `toolroll up`)", EXIT.usage);
|
|
10818
10940
|
const authenticated = authenticateApprover(store, acting.name, acting.token);
|
|
10819
10941
|
const repo = store.refById(run.taskRef)?.repo ?? null;
|
|
10820
10942
|
if (!authenticated.ok || !store.accountCanAccess(acting.name, repo))
|
|
10821
10943
|
return fail(write, json, command, "not-an-approver", "that is not an approver for this run's project, or the token does not match", EXIT.refused);
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10944
|
+
if (run.outcome === null)
|
|
10945
|
+
return fail(write, json, command, "still-running", `run #${runId} is still open`, EXIT.refused, { run: runId });
|
|
10946
|
+
const proved = await proveRunStopped(store, runId, clock);
|
|
10947
|
+
const stopped = store.stopQuiescenceFact(runId);
|
|
10948
|
+
if (stopped === null) {
|
|
10949
|
+
return succeed(write, json, command, { run: runId, outcome: proved.outcome === "skipped" ? "already-stopped" : "stopped", stoppedProcesses: proved.stopped ?? 0 }, () => [
|
|
10950
|
+
proved.outcome === "skipped" ? `Run #${runId} had already stopped; nothing changed.`
|
|
10951
|
+
: `Run #${runId} stopped: Toolroll checked this computer's processes and none of them is the run's${proved.stopped ? ` (it stopped ${proved.stopped} the run left running)` : ""}. The check is in the ledger.`,
|
|
10952
|
+
]);
|
|
10953
|
+
}
|
|
10954
|
+
const reason = proved.outcome === "blocked" ? "unproven" : stopped.kind === "alive" ? "alive" : "waiting";
|
|
10955
|
+
const words = proved.outcome === "blocked" ? `Run #${proved.run} ${proved.words}.`
|
|
10956
|
+
: stopped.kind === "alive" ? `Something of run #${stopped.run} is still running; Toolroll stops what is proven the run's on its own.`
|
|
10957
|
+
: `Toolroll is still making sure run #${stopped.run} stopped. This clears on its own.`;
|
|
10958
|
+
return fail(write, json, command, reason, words, EXIT.refused, { run: runId });
|
|
10830
10959
|
}
|
|
10831
10960
|
// ---- shared ---------------------------------------------------------------
|
|
10832
10961
|
/**
|
|
@@ -11422,9 +11551,11 @@ async function storageCommand(positional, flags, context) {
|
|
|
11422
11551
|
};
|
|
11423
11552
|
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);
|
|
11424
11553
|
const summary = (plan) => [
|
|
11425
|
-
`
|
|
11426
|
-
plan.go.length === 0 ? "A clean up now would free nothing." : `A clean up now would free about ${bytesWords(plan.freeBytes)} (${plan.go.length}
|
|
11427
|
-
`A finished task's clean
|
|
11554
|
+
`Task folders: ${plan.count}, ${bytesWords(plan.totalBytes)}. ${plan.waitingReview} waiting for review, ${plan.withChanges} with uncommitted changes.`,
|
|
11555
|
+
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`,
|
|
11556
|
+
`A finished task's clean folder is removed ${cleanupWords(plan.cleanup)}; its branch stays.`,
|
|
11557
|
+
idleCleanupWords(plan),
|
|
11558
|
+
...uncommittedLines(plan),
|
|
11428
11559
|
];
|
|
11429
11560
|
const itemLine = (one) => ` ${bytesWords(one.bytes).padStart(8)} ${one.path}${one.why === null ? "" : ` (${whyWords(one)})`}`;
|
|
11430
11561
|
// Test temp folders the tests, journeys and scripts left (a killed run, an older version): those nothing touched for a day go.
|
|
@@ -11477,16 +11608,28 @@ async function storageCommand(positional, flags, context) {
|
|
|
11477
11608
|
if (action === "cleanup") {
|
|
11478
11609
|
if (rest.length === 0) {
|
|
11479
11610
|
const cleanup = store.checkoutCleanup();
|
|
11480
|
-
|
|
11611
|
+
const idle = checkoutIdle(store);
|
|
11612
|
+
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.`]);
|
|
11613
|
+
}
|
|
11614
|
+
const usage = `Use storage cleanup <${CLEANUP_CHOICES.map(one => one.value).join("|")}> or storage cleanup idle <7|14|30|never>.`;
|
|
11615
|
+
if (rest[0] === "idle") {
|
|
11616
|
+
const idle = rest.length === 2 ? parseIdle(rest[1]) : undefined;
|
|
11617
|
+
if (idle === undefined)
|
|
11618
|
+
return fail(context.write, context.json, command, "usage", usage, EXIT.usage);
|
|
11619
|
+
const actor = await operator();
|
|
11620
|
+
if (actor === null)
|
|
11621
|
+
return refused("sets idle cleanup");
|
|
11622
|
+
const saved = setCheckoutIdle(store, idle, actor, context.clock());
|
|
11623
|
+
return succeed(context.write, context.json, command, { idle: saved }, () => [`Saved. A task's clean folder is removed ${idleWords(saved)}; its branch stays.`]);
|
|
11481
11624
|
}
|
|
11482
11625
|
const cleanup = rest.length === 1 ? parseCleanup(rest[0]) : undefined;
|
|
11483
11626
|
if (cleanup === undefined)
|
|
11484
|
-
return fail(context.write, context.json, command, "usage",
|
|
11627
|
+
return fail(context.write, context.json, command, "usage", usage, EXIT.usage);
|
|
11485
11628
|
const actor = await operator();
|
|
11486
11629
|
if (actor === null)
|
|
11487
11630
|
return refused("sets checkout cleanup");
|
|
11488
11631
|
const saved = store.setCheckoutCleanup(cleanup, actor, context.clock());
|
|
11489
|
-
return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean
|
|
11632
|
+
return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean folder is removed ${cleanupWords(saved)}; its branch stays.`]);
|
|
11490
11633
|
}
|
|
11491
11634
|
if (action === "clean" && flags.has("landed")) {
|
|
11492
11635
|
if (rest.length > 0 || flags.get("landed") !== true)
|
|
@@ -11506,7 +11649,7 @@ async function storageCommand(positional, flags, context) {
|
|
|
11506
11649
|
const sharedBytes = sharedGo.reduce((sum, one) => sum + one.bytes, 0);
|
|
11507
11650
|
const anything = plan.go.length > 0 || temp.stale > 0 || sharedGo.length > 0;
|
|
11508
11651
|
return succeed(context.write, context.json, command, { preview: true, go: plan.go, stay: plan.stay, freeBytes: plan.freeBytes, testTemp: temp, sharedGo, sharedFreeBytes: sharedBytes }, () => [
|
|
11509
|
-
...(plan.go.length === 0 ? [anything ? "No
|
|
11652
|
+
...(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)]),
|
|
11510
11653
|
...(sharedGo.length === 0 ? [] : [`Would remove ${sharedGo.length} shared dependency install${sharedGo.length === 1 ? "" : "s"} no checkout uses, about ${bytesWords(sharedBytes)}:`, ...sharedGo.map(sharedLine)]),
|
|
11511
11654
|
...(plan.stay.length === 0 ? [] : [`Stays (${plan.stay.length}):`, ...plan.stay.map(itemLine)]),
|
|
11512
11655
|
...(temp.stale === 0 ? [] : [`Would remove ${temp.stale} test temp folder${temp.stale === 1 ? "" : "s"} older than a day, about ${bytesWords(temp.staleBytes)}.`]),
|
|
@@ -11541,7 +11684,7 @@ async function storageCommand(positional, flags, context) {
|
|
|
11541
11684
|
];
|
|
11542
11685
|
saveSweep(store, { at: context.clock().toISOString(), source: "manual", actor, parts });
|
|
11543
11686
|
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 }, () => [
|
|
11544
|
-
done.removed.length === 0 ? (swept.removed.length === 0 && sharedRemoved.length === 0 ? "Nothing to clean up." : "No
|
|
11687
|
+
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.`,
|
|
11545
11688
|
...done.removed.map(itemLine),
|
|
11546
11689
|
...(sharedRemoved.length === 0 ? [] : [`Removed ${sharedRemoved.length} shared dependency install${sharedRemoved.length === 1 ? "" : "s"} no checkout used, about ${bytesWords(sharedFreed)}.`, ...sharedRemoved.map(sharedLine)]),
|
|
11547
11690
|
...done.kept.map(one => ` kept ${one.path} (${whyWords({ why: one.why })})`),
|
|
@@ -11619,7 +11762,8 @@ async function landedCommandWith(remotes, yes, operator, refused, pool, context)
|
|
|
11619
11762
|
]);
|
|
11620
11763
|
}
|
|
11621
11764
|
function checkoutSummary(plan) {
|
|
11622
|
-
return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup
|
|
11765
|
+
return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup,
|
|
11766
|
+
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 })) };
|
|
11623
11767
|
}
|
|
11624
11768
|
/** `review [show] | on | off --repo <path>`: the project's one automatic review
|
|
11625
11769
|
* of finished builds. Changing it takes an approver of that project; the
|
|
@@ -13298,6 +13442,8 @@ async function backgroundCommand(positional, flags, context) {
|
|
|
13298
13442
|
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."]);
|
|
13299
13443
|
}
|
|
13300
13444
|
const on = await backgroundOn(context.store, context.databaseFile, port, seams);
|
|
13445
|
+
if (on.ok)
|
|
13446
|
+
recordBuilderWish(context.store, "running", "background on", packageVersion(), context.clock());
|
|
13301
13447
|
if (!on.ok) {
|
|
13302
13448
|
const code = ["agent", "conflict", "declined", "running-elsewhere", "unsupported", "console-unproven", "invalid-unit", "unreadable"].includes(on.reason) ? EXIT.refused : EXIT.failed;
|
|
13303
13449
|
if (json) {
|
|
@@ -13461,8 +13607,332 @@ export function upLaptopPortCandidates(store, fromSeam) {
|
|
|
13461
13607
|
}
|
|
13462
13608
|
/** The memory verbs that change project memory (memory-cli.ts), and `propose`, which files the proposals a person then
|
|
13463
13609
|
* applies: a person's act, never the lead's. The reads stay as they are. */
|
|
13464
|
-
const MEMORY_WRITE_VERBS = new Set(["decide", "retire", "apply", "propose"]);
|
|
13610
|
+
const MEMORY_WRITE_VERBS = new Set(["decide", "retire", "apply", "propose", "tidy", "restore", "unmerge"]);
|
|
13465
13611
|
function memoryWriteVerb(positional) {
|
|
13466
13612
|
return MEMORY_WRITE_VERBS.has(positional[0] ?? "");
|
|
13467
13613
|
}
|
|
13468
13614
|
const MEMORY_WRITE_REFUSAL = "Project memory is changed by a person with their own sign-in, not the lead. The lead proposes decisions in chat for you to confirm.";
|
|
13615
|
+
/**
|
|
13616
|
+
* `toolroll release approve [<branch>] [--repo <path>]` (docs/plans/trusted-release-reports.md, slice A, A8): approve
|
|
13617
|
+
* the release waiting for a person from this terminal, without copying a task id or a digest. The release journals are
|
|
13618
|
+
* discovery only, read strictly; the plane's sealed release identity decides which release this is. The card prints
|
|
13619
|
+
* control-free; the person is asked through `askCredentials` (or its remembered login, under its existing conditions);
|
|
13620
|
+
* the card is built again AFTER that prompt and anything changed refuses; then the shared card wrapper hands the
|
|
13621
|
+
* snapshot's scope digest to the unchanged approve(). `--json` never prompts.
|
|
13622
|
+
*/
|
|
13623
|
+
async function releaseApproveCommand(positional, flags, context) {
|
|
13624
|
+
const { store, write, json } = context;
|
|
13625
|
+
const command = "release approve";
|
|
13626
|
+
const usage = "`toolroll release approve [<branch>] [--repo <path>]` (with --json: --as <you> --token <password> --digest <card digest>)";
|
|
13627
|
+
const branchFlag = text(flags, "branch");
|
|
13628
|
+
if (positional[0] !== "approve" || positional.length > 2 || (branchFlag !== undefined && positional.length > 1))
|
|
13629
|
+
return fail(write, json, command, "usage", usage, EXIT.usage);
|
|
13630
|
+
const branch = branchFlag ?? positional[1];
|
|
13631
|
+
if (branch !== undefined && (!/^[A-Za-z0-9._/-]{1,255}$/.test(branch) || branch.startsWith("-") || branch.includes("..")))
|
|
13632
|
+
return fail(write, json, command, "usage", `${branch} is not a branch name this command takes.`, EXIT.usage);
|
|
13633
|
+
const repoFlag = text(flags, "repo");
|
|
13634
|
+
const repo = repoFlag === undefined ? null : resolve(repoFlag);
|
|
13635
|
+
const stateDir = dirname(context.databaseFile);
|
|
13636
|
+
const { journals, unreadable } = waitingJournals(stateDir, name => journalFile(stateDir, name));
|
|
13637
|
+
// The exact branch asked for, never another that shares its file name (feature/foo and feature_foo).
|
|
13638
|
+
const named = branch === undefined ? journals : journals.filter(one => one.branch === branch);
|
|
13639
|
+
if (branch !== undefined && named.length === 0 && unreadable.includes(basename(journalFile(stateDir, branch)))) {
|
|
13640
|
+
return fail(write, json, command, "unreadable", `${branch}'s release journal can't be read as one; nothing was approved. Run \`toolroll release ${branch}\` again.`, EXIT.refused);
|
|
13641
|
+
}
|
|
13642
|
+
const matches = [], refusals = [];
|
|
13643
|
+
for (const one of named) {
|
|
13644
|
+
if (releaseTaskOf(store, one.taskId) !== null && approvalOf(store.getScope(one.taskId)).approved)
|
|
13645
|
+
continue;
|
|
13646
|
+
const proved = journalMatchesPlane(store, one, repo);
|
|
13647
|
+
if (proved.ok)
|
|
13648
|
+
matches.push(one);
|
|
13649
|
+
else
|
|
13650
|
+
refusals.push(proved.why);
|
|
13651
|
+
}
|
|
13652
|
+
if (matches.length === 0) {
|
|
13653
|
+
const why = refusals.length > 0 ? `${refusals[0]}; nothing was approved` : branch === undefined ? "No release is waiting for your approval" : `No release of ${branch} is waiting for your approval`;
|
|
13654
|
+
return fail(write, json, command, refusals.length > 0 ? "mismatch" : "none", `${why}.`, EXIT.refused);
|
|
13655
|
+
}
|
|
13656
|
+
if (matches.length > 1) {
|
|
13657
|
+
return fail(write, json, command, "ambiguous", `More than one release is waiting: ${matches.map(one => `${one.branch} (${one.version})`).join(", ")}. Name the branch: \`toolroll release approve <branch>\`.`, EXIT.refused, { releases: matches.map(one => ({ branch: one.branch, version: one.version, task: one.taskId })) });
|
|
13658
|
+
}
|
|
13659
|
+
const chosen = matches[0];
|
|
13660
|
+
const built = releaseApprovalCard(store, chosen.taskId);
|
|
13661
|
+
if (!built.ok)
|
|
13662
|
+
return fail(write, json, command, "none", built.why, EXIT.refused);
|
|
13663
|
+
const shown = built.card;
|
|
13664
|
+
const cardData = { task: shown.taskId, version: shown.seal.identity.packageVersion, branch: shown.seal.identity.branch, candidate: shown.seal.identity.approvedSha, digest: shown.digest, ci: shown.ci?.state ?? null };
|
|
13665
|
+
if (shown.approved !== null)
|
|
13666
|
+
return fail(write, json, command, "approved", `Release ${cardData.version} is already approved by ${shown.approved.by}.`, EXIT.refused, { release: cardData });
|
|
13667
|
+
if (shown.refusal !== null) {
|
|
13668
|
+
if (!json)
|
|
13669
|
+
for (const line of releaseCardLines(shown))
|
|
13670
|
+
write(line);
|
|
13671
|
+
return fail(write, json, command, "refused", `Can't approve now: ${shown.refusal}`, EXIT.refused, { release: cardData });
|
|
13672
|
+
}
|
|
13673
|
+
let name, token;
|
|
13674
|
+
if (json) {
|
|
13675
|
+
const given = credentialsFrom(flags, context);
|
|
13676
|
+
if (given.name === undefined || given.token === undefined || given.name === "" || given.token === "")
|
|
13677
|
+
return fail(write, json, command, "credentials", "Approving a release needs who is agreeing: --as <you> --token <password>. Nothing was approved.", EXIT.usage, { release: cardData });
|
|
13678
|
+
if (text(flags, "digest") !== shown.digest)
|
|
13679
|
+
return fail(write, json, command, "unconfirmed", "Name the card you read: --digest <card digest>. Nothing was approved.", EXIT.refused, { release: cardData });
|
|
13680
|
+
name = given.name;
|
|
13681
|
+
token = given.token;
|
|
13682
|
+
}
|
|
13683
|
+
else {
|
|
13684
|
+
for (const line of releaseCardLines(shown))
|
|
13685
|
+
write(line);
|
|
13686
|
+
write("");
|
|
13687
|
+
if (interactive()) {
|
|
13688
|
+
if (!await confirm(`Approve release ${cardData.version}?`)) {
|
|
13689
|
+
write("Nothing approved.");
|
|
13690
|
+
return EXIT.refused;
|
|
13691
|
+
}
|
|
13692
|
+
}
|
|
13693
|
+
else if (!flags.has("yes") || text(flags, "digest") !== shown.digest) {
|
|
13694
|
+
write(`Nothing has been approved. Agree to exactly this card with: toolroll release approve ${chosen.branch} --yes --digest ${shown.digest} --as <you> --token <your password>`);
|
|
13695
|
+
return EXIT.refused;
|
|
13696
|
+
}
|
|
13697
|
+
const acting = await askCredentials(flags, context);
|
|
13698
|
+
if (acting === null)
|
|
13699
|
+
return fail(write, json, command, "credentials", "Approving a release needs who is agreeing. Nothing was approved.", EXIT.usage);
|
|
13700
|
+
name = acting.name;
|
|
13701
|
+
token = acting.token;
|
|
13702
|
+
}
|
|
13703
|
+
// After the prompt: the card again, from the plane. A change refuses; it is never approved in place of what was shown.
|
|
13704
|
+
const again = releaseApprovalCard(store, chosen.taskId);
|
|
13705
|
+
if (!again.ok || again.card.digest !== shown.digest)
|
|
13706
|
+
return fail(write, json, command, "changed", "This release changed while you were being asked; nothing was approved. Run the command again to read it.", EXIT.refused);
|
|
13707
|
+
const now = context.clock();
|
|
13708
|
+
const answer = approveReleaseCard(store, { taskId: chosen.taskId, cardDigest: shown.digest, approveNow: scopeDigest => approve(store, chosen.taskId, name, now, scopeDigest, token, mutationFrom(flags, now)) });
|
|
13709
|
+
if (!answer.ok) {
|
|
13710
|
+
if (answer.reason !== "approve")
|
|
13711
|
+
return fail(write, json, command, answer.reason, `${answer.message} Nothing was approved.`, EXIT.refused);
|
|
13712
|
+
const refused = answer.result;
|
|
13713
|
+
if (refused.reason === "policy")
|
|
13714
|
+
return fail(write, json, command, "policy", `${chosen.taskId}: ${refused.message}`, EXIT.refused);
|
|
13715
|
+
return fail(write, json, command, refused.reason, describeApproveFailure(refused.reason, chosen.taskId), EXIT.refused);
|
|
13716
|
+
}
|
|
13717
|
+
return succeed(write, json, command, { release: cardData }, () => [
|
|
13718
|
+
`Approved release ${cardData.version} (${cardData.candidate.slice(0, 7)}). The release check runs next; it deploys, merges and publishes once GitHub CI and the check pass.`,
|
|
13719
|
+
...(shown.stopped === null ? [] : [`The release isn't running: run \`toolroll release ${chosen.branch}\` to go on.`]),
|
|
13720
|
+
]);
|
|
13721
|
+
}
|
|
13722
|
+
/**
|
|
13723
|
+
* `toolroll task ready <task>` (read-only; docs/plans/plain-toolroll.md P1.6): "Can I merge?" — the task's checklist
|
|
13724
|
+
* from the records the task page reads (merge-checklist.ts), each blocker with the existing act that clears it. A task
|
|
13725
|
+
* outside the caller's projects is no task here.
|
|
13726
|
+
*/
|
|
13727
|
+
function taskReadyCommand(positional, context) {
|
|
13728
|
+
const { store, write, json } = context;
|
|
13729
|
+
const command = "task ready";
|
|
13730
|
+
const id = positional[0];
|
|
13731
|
+
if (id === undefined || positional.length > 1)
|
|
13732
|
+
return fail(write, json, command, "usage", "`toolroll task ready <task>`", EXIT.usage);
|
|
13733
|
+
const lens = context.principal?.lens ?? null;
|
|
13734
|
+
const ref = store.lookupRef(id);
|
|
13735
|
+
if (ref === null || store.getTask(id) === null || (lens !== null && (ref.repo === null || !lens.includes(ref.repo))))
|
|
13736
|
+
return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
|
|
13737
|
+
const now = (context.clock ?? (() => context.now))();
|
|
13738
|
+
const list = mergeChecklistOf(store, id, now, { principal: "operator", repos: lens, includeUnplaced: lens === null, dependencyRepos: lens }, context.evidenceRoot);
|
|
13739
|
+
if (list === null)
|
|
13740
|
+
return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
|
|
13741
|
+
return succeed(write, json, command, list, () => checklistLines(list));
|
|
13742
|
+
}
|
|
13743
|
+
/** The folders with uncommitted changes: `toolroll storage` lists them so a person can decide. Never removed automatically. */
|
|
13744
|
+
function uncommittedLines(plan) {
|
|
13745
|
+
const changed = plan.stay.filter(one => one.why === "has changes");
|
|
13746
|
+
if (changed.length === 0)
|
|
13747
|
+
return [];
|
|
13748
|
+
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):`,
|
|
13749
|
+
...changed.map(one => ` ${bytesWords(one.bytes).padStart(8)} ${one.path}${one.taskId === null ? "" : ` (${one.taskId})`}`)];
|
|
13750
|
+
}
|
|
13751
|
+
/** `toolroll map` (code-map-cli.ts): who is asking, and which projects they can open. A read needs no sign-in on this
|
|
13752
|
+
* computer's own database, as `knowledge search`; a sign-in limits it to that person's projects. */
|
|
13753
|
+
async function mapCommand(positional, flags, context) {
|
|
13754
|
+
const { store } = context;
|
|
13755
|
+
const local = currentLocalOwner();
|
|
13756
|
+
const { name, token } = credentialsFrom(flags, context);
|
|
13757
|
+
let actor = null;
|
|
13758
|
+
let repos = null;
|
|
13759
|
+
const all = [...new Set([...store.knownRepos(), ...store.listProjects().map(one => one.path)])];
|
|
13760
|
+
if (local !== null) {
|
|
13761
|
+
actor = local.account;
|
|
13762
|
+
repos = all.filter(repo => store.accountCanAccess(local.account, repo));
|
|
13763
|
+
}
|
|
13764
|
+
else if (name !== undefined && token !== undefined) {
|
|
13765
|
+
if (!authenticateAccount(store, name, token).ok)
|
|
13766
|
+
return fail(context.write, context.json, "map", "unauthenticated", "Sign in again: that login doesn't work here.", EXIT.refused);
|
|
13767
|
+
const remote = activeRemote();
|
|
13768
|
+
actor = name;
|
|
13769
|
+
repos = all.filter(repo => store.accountCanAccess(name, repo) && (remote === null || remote.allows(repo)));
|
|
13770
|
+
}
|
|
13771
|
+
else
|
|
13772
|
+
repos = all;
|
|
13773
|
+
return runMapCommand(positional, flags, { store, write: context.write, json: context.json, now: context.clock(), actor, repos,
|
|
13774
|
+
approver: repo => local === null && name !== undefined && token !== undefined && authenticateApprover(store, name, token, repo).ok,
|
|
13775
|
+
door: repo => {
|
|
13776
|
+
if (local !== null)
|
|
13777
|
+
return { kind: "local-owner", actor: local.account };
|
|
13778
|
+
if (name === undefined || token === undefined)
|
|
13779
|
+
return null;
|
|
13780
|
+
// The credential generation the proof is checked against, read before it, as `models` does.
|
|
13781
|
+
const generation = store.accountOf(name)?.generation ?? -1;
|
|
13782
|
+
const proved = authenticateApprover(store, name, token, repo);
|
|
13783
|
+
return proved.ok ? { kind: "person", person: { actor: name, generation, method: proved.method } } : null;
|
|
13784
|
+
} });
|
|
13785
|
+
}
|
|
13786
|
+
// ---- `toolroll task new` (docs/plans/plain-toolroll.md P2.4) -----------------------------------------------------------
|
|
13787
|
+
/**
|
|
13788
|
+
* Start a task from a person's own terminal: their words become the exact terms, printed; "Build now?" is their yes.
|
|
13789
|
+
* Filing and approving are one step (fast-start.ts), for small work a person approves without typing a password here;
|
|
13790
|
+
* anything else is the ordinary `task add`, then Approve plan. Never for a script or an agent: `--json`, no terminal, a
|
|
13791
|
+
* token or a remote run all refuse. The terminal UI drives it as the local owner, with --preview and --build.
|
|
13792
|
+
*/
|
|
13793
|
+
async function newTaskCommand(positional, flags, context) {
|
|
13794
|
+
const { store, write, json } = context;
|
|
13795
|
+
const clock = context.clock ?? (() => new Date());
|
|
13796
|
+
const command = "task new";
|
|
13797
|
+
const local = currentLocalOwner();
|
|
13798
|
+
if (context.principal !== undefined || machineActing())
|
|
13799
|
+
return fail(write, json, command, "person-only", "Only a person starts a build this way. File it with `task add`.", EXIT.refused);
|
|
13800
|
+
if (local === null && (json || !personAtTerminal(context)))
|
|
13801
|
+
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);
|
|
13802
|
+
const { name, token } = credentialsFrom(flags, context);
|
|
13803
|
+
const passwordFree = token !== undefined && (local !== null || passwordlessOn(store));
|
|
13804
|
+
const facts = { passwordless: local === null && passwordFree, localOwner: local !== null, freshAt: null, returnTo: "/" };
|
|
13805
|
+
const buildNow = (preview, digest) => fastStartBuild(store, { preview, digest, actor: name ?? "", via: local === null ? "cli" : "terminal-ui", password: facts,
|
|
13806
|
+
approveNow: (taskId, sawDigest, fast) => approve(store, taskId, name ?? "", clock(), sawDigest, token ?? "", {}, fast) }, clock());
|
|
13807
|
+
const answerBuild = (built) => built.ok
|
|
13808
|
+
? succeed(write, json, command, { task: built.taskId }, () => [`Building ${built.taskId}. A builder starts it next.`])
|
|
13809
|
+
: fail(write, json, command, built.reason, built.message, EXIT.refused);
|
|
13810
|
+
const pressed = text(flags, "build");
|
|
13811
|
+
if (pressed !== undefined) {
|
|
13812
|
+
if (local === null)
|
|
13813
|
+
return fail(write, json, command, "person-only", "--build is the terminal UI's own.", EXIT.refused);
|
|
13814
|
+
return answerBuild(buildNow(pressed, text(flags, "digest") ?? ""));
|
|
13815
|
+
}
|
|
13816
|
+
const fromLink = text(flags, "from");
|
|
13817
|
+
if (fromLink !== undefined)
|
|
13818
|
+
return importTaskCommand(fromLink, flags, context, name);
|
|
13819
|
+
const words = positional.join(" ").trim();
|
|
13820
|
+
const repo = text(flags, "repo");
|
|
13821
|
+
if (words === "" || repo === undefined)
|
|
13822
|
+
return fail(write, json, command, "usage", "`toolroll task new \"<what should change>\" --repo <project>`", EXIT.usage);
|
|
13823
|
+
const placed = canonicalProject(repo) ?? resolve(repo);
|
|
13824
|
+
const prepared = name === undefined || token === undefined ? { eligible: false, reason: "password" }
|
|
13825
|
+
: await prepareFastStart(store, { actor: name, repo: placed, title: words, via: local === null ? "cli" : "terminal-ui", password: facts }, clock());
|
|
13826
|
+
const terms = prepared.eligible ? fastTermsLines(store, prepared.scope, placed) : [];
|
|
13827
|
+
if (flags.has("preview")) {
|
|
13828
|
+
if (local === null)
|
|
13829
|
+
return fail(write, json, command, "person-only", "--preview is the terminal UI's own.", EXIT.refused);
|
|
13830
|
+
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) }, () => []);
|
|
13831
|
+
}
|
|
13832
|
+
if (!prepared.eligible) {
|
|
13833
|
+
write(`${fastRefusalWords(prepared.reason)} Filing it for a plan.`);
|
|
13834
|
+
return addTask([words], flags, context);
|
|
13835
|
+
}
|
|
13836
|
+
write("Build approves exactly this:");
|
|
13837
|
+
write("");
|
|
13838
|
+
for (const line of terms)
|
|
13839
|
+
write(` ${line.label}: ${line.words}`);
|
|
13840
|
+
write("");
|
|
13841
|
+
if (!(await (context.easySeams?.confirm ?? confirm)("Build now?"))) {
|
|
13842
|
+
write("Nothing started. File it for a plan with `toolroll task add`.");
|
|
13843
|
+
return EXIT.refused;
|
|
13844
|
+
}
|
|
13845
|
+
return answerBuild(buildNow(prepared.preview, prepared.digest));
|
|
13846
|
+
}
|
|
13847
|
+
/** `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`. */
|
|
13848
|
+
async function keepGrownCommand(positional, flags, context) {
|
|
13849
|
+
const { store, write, json, clock } = context;
|
|
13850
|
+
const id = positional[0];
|
|
13851
|
+
if (id === undefined)
|
|
13852
|
+
return fail(write, json, "task keep", "usage", "`toolroll task keep <id> --as <you> --token <t>`", EXIT.usage);
|
|
13853
|
+
const acting = await askCredentials(flags, context);
|
|
13854
|
+
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
13855
|
+
if (acting === null || !verified?.ok)
|
|
13856
|
+
return refuseApprover(context, "task keep", verified, "keeping a change takes `--as <you> --token <t>`", id);
|
|
13857
|
+
const kept = keepGrownChange(store, id, clock());
|
|
13858
|
+
if (!kept.ok)
|
|
13859
|
+
return fail(write, json, "task keep", "refused", kept.message, EXIT.refused);
|
|
13860
|
+
return succeed(write, json, "task keep", { id, digest: kept.digest }, () => [`Its plan now names the exact commit. Approve it with \`toolroll task approve ${id}\`.`]);
|
|
13861
|
+
}
|
|
13862
|
+
/** One resume at a time per process; a release's own steps can take many minutes. */
|
|
13863
|
+
let resumingReleases = false;
|
|
13864
|
+
async function resumeHeldReleases(store, stateDir, now) {
|
|
13865
|
+
if (resumingReleases)
|
|
13866
|
+
return;
|
|
13867
|
+
resumingReleases = true;
|
|
13868
|
+
try {
|
|
13869
|
+
await resumeAllowedReleases(store, stateDir, journal => runRelease({ branch: journal.branch, checkout: journal.checkout, stateDir, limits: {} }, releaseAdapters({ checkout: journal.checkout, say: () => { } })), now);
|
|
13870
|
+
}
|
|
13871
|
+
catch { /* the release's own journal says where it stopped; the next person act tries again */ }
|
|
13872
|
+
finally {
|
|
13873
|
+
resumingReleases = false;
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
/** `task new --from <url>`: read and quote an issue or pull request, then, on a person's yes, file it for the ordinary approval. */
|
|
13877
|
+
async function importTaskCommand(url, flags, context, actor) {
|
|
13878
|
+
const { store, write, json } = context;
|
|
13879
|
+
const clock = context.clock ?? (() => new Date());
|
|
13880
|
+
const command = "task new";
|
|
13881
|
+
const repo = text(flags, "repo");
|
|
13882
|
+
if (repo === undefined)
|
|
13883
|
+
return fail(write, json, command, "usage", "`toolroll task new --from <url> --repo <project>`", EXIT.usage);
|
|
13884
|
+
const placed = canonicalProject(repo) ?? resolve(repo);
|
|
13885
|
+
const read = await readGithubItem(store, { url, repo: placed }, context.easySeams?.github ?? {});
|
|
13886
|
+
if (!read.ok)
|
|
13887
|
+
return fail(write, json, command, read.reason, read.message, EXIT.refused);
|
|
13888
|
+
write(`${read.item.kind === "pull" ? "Pull request" : "Issue"} #${read.item.number}, written outside Toolroll:`);
|
|
13889
|
+
write(` > ${read.item.title}`);
|
|
13890
|
+
for (const line of read.item.body.split(/\r?\n/))
|
|
13891
|
+
write(` > ${line}`);
|
|
13892
|
+
write("");
|
|
13893
|
+
if (!(await (context.easySeams?.confirm ?? confirm)("Add this task? You approve its plan before anything builds."))) {
|
|
13894
|
+
write("Nothing filed.");
|
|
13895
|
+
return EXIT.refused;
|
|
13896
|
+
}
|
|
13897
|
+
const filed = await fileGithubItem(store, { url, repo: placed, actor: actor ?? "cli", shownHead: read.item.head }, clock(), context.easySeams?.github ?? {});
|
|
13898
|
+
if (!filed.ok)
|
|
13899
|
+
return fail(write, json, command, filed.reason, filed.message, EXIT.refused);
|
|
13900
|
+
return succeed(write, json, command, { task: filed.taskId }, () => [`Filed ${filed.taskId}. Its plan is ready for you to approve.`]);
|
|
13901
|
+
}
|
|
13902
|
+
/** `toolroll flaky list|show`: reads only, for the remembered local login (or --as/--token) and its projects. */
|
|
13903
|
+
async function flakyCommand(positional, flags, context) {
|
|
13904
|
+
const acting = await askCredentials(flags, context);
|
|
13905
|
+
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)));
|
|
13906
|
+
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
13907
|
+
return runFlakyCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [] });
|
|
13908
|
+
}
|
|
13909
|
+
/** The watch loop's background learning pass (docs/plans/learning-loops.md): advisory reads, each project at most every
|
|
13910
|
+
* 30 minutes through the plane's own gh. A failure here never reaches the loop: it is tried on the next pass. */
|
|
13911
|
+
export async function refreshLearning(store, now, reads) {
|
|
13912
|
+
try {
|
|
13913
|
+
backfillMemoryUse(store, now());
|
|
13914
|
+
}
|
|
13915
|
+
catch { /* advisory: the next pass tries again */ }
|
|
13916
|
+
// Project memory's clean-up proposals: deterministic, a quiet project at most weekly. Nothing changes until a person accepts.
|
|
13917
|
+
for (const repo of store.knownRepos()) {
|
|
13918
|
+
try {
|
|
13919
|
+
scheduledTidy(store, repo, now());
|
|
13920
|
+
}
|
|
13921
|
+
catch { /* the next pass tries again */ }
|
|
13922
|
+
}
|
|
13923
|
+
for (const repo of store.knownRepos()) {
|
|
13924
|
+
try {
|
|
13925
|
+
const key = ciCursorKey(repo), at = now();
|
|
13926
|
+
if (at.getTime() - store.serviceCursor(key) < CI_READ_EVERY_MS || ciRepositoriesOf(store, repo).length === 0)
|
|
13927
|
+
continue;
|
|
13928
|
+
store.setServiceCursor(key, at.getTime(), at);
|
|
13929
|
+
await observeCiAttempts(store, repo, reads ?? githubReads(releaseExec, releaseTools().gh, { retry: true }), at);
|
|
13930
|
+
}
|
|
13931
|
+
catch { /* advisory: the next pass tries again */ }
|
|
13932
|
+
}
|
|
13933
|
+
}
|
|
13934
|
+
/** The laptop's local owner as a reader: its name and its projects. Never a proof: every write proves its own act. */
|
|
13935
|
+
function localReader(context) {
|
|
13936
|
+
const local = currentLocalOwner();
|
|
13937
|
+
return local === null ? null : { name: local.account, repos: context.store.knownRepos().filter(repo => context.store.accountCanAccess(local.account, repo)) };
|
|
13938
|
+
}
|