toolroll 0.9.93 → 0.9.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +10 -0
- package/dist/agent-fence.js +6 -3
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.js +5 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.js +10 -0
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +32 -32
- package/dist/browser-crew.js +2 -0
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +50 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/build-review.js +2 -0
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +11 -2
- package/dist/builder.js +234 -101
- package/dist/card-catalog.d.ts +209 -0
- package/dist/card-catalog.js +133 -0
- package/dist/card-cli.d.ts +43 -0
- package/dist/card-cli.js +44 -0
- package/dist/card-render.d.ts +26 -0
- package/dist/card-render.js +223 -0
- package/dist/card-sources.d.ts +68 -0
- package/dist/card-sources.js +234 -0
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +106 -9
- package/dist/chat-actions.js +473 -46
- package/dist/chat-approval.d.ts +13 -0
- package/dist/chat-approval.js +39 -9
- package/dist/chat-card-part.d.ts +11 -0
- package/dist/chat-card-part.js +18 -0
- package/dist/chat-card.d.ts +46 -0
- package/dist/chat-card.js +99 -0
- package/dist/chat-channel.d.ts +2 -0
- package/dist/chat-channel.js +37 -14
- package/dist/chat-controls.d.ts +4 -0
- package/dist/chat-controls.js +4 -0
- package/dist/chat-decide.d.ts +10 -16
- package/dist/chat-decide.js +79 -62
- package/dist/chat-delivery.js +13 -4
- package/dist/chat-flow.d.ts +0 -27
- package/dist/chat-flow.js +31 -2
- package/dist/chat-quiet.js +4 -4
- package/dist/chat-task-actions.d.ts +3 -2
- package/dist/chat-task-actions.js +19 -5
- package/dist/chat-terms.d.ts +14 -2
- package/dist/chat-terms.js +35 -0
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +3 -1
- package/dist/check-facts.js +57 -11
- package/dist/check-failure.d.ts +24 -3
- package/dist/check-failure.js +89 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-repair.d.ts +47 -0
- package/dist/check-repair.js +240 -0
- package/dist/check-report.d.ts +2 -0
- package/dist/check-report.js +1 -1
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/claim.d.ts +6 -0
- package/dist/claim.js +13 -2
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +49 -30
- package/dist/cli.js +15 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +3 -1
- package/dist/coding-provider.js +12 -7
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.d.ts +1 -1
- package/dist/coding-workspace.js +8 -8
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +92 -0
- package/dist/contracts/chat-actions.js +20 -0
- package/dist/contracts/chat-card.d.ts +91 -0
- package/dist/contracts/chat-card.js +54 -0
- package/dist/contracts/chat-content.d.ts +191 -0
- package/dist/contracts/chat-content.js +4 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -3
- package/dist/contracts/console-api.js +12 -3
- package/dist/contracts/flow-propose.d.ts +8 -1
- package/dist/contracts/flow-propose.js +3 -1
- package/dist/contracts/flow-send.d.ts +126 -0
- package/dist/contracts/flow-send.js +3 -0
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +234 -0
- package/dist/contracts/lead-tools.js +25 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +18 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/discord-chat.js +14 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +25 -16
- package/dist/dispose.d.ts +19 -0
- package/dist/dispose.js +55 -9
- package/dist/easy-start.js +2 -3
- package/dist/eval-oracle.js +4 -1
- package/dist/evals.js +2 -1
- package/dist/evidence-pair.d.ts +53 -0
- package/dist/evidence-pair.js +128 -0
- package/dist/exhaustion.d.ts +17 -4
- package/dist/exhaustion.js +44 -4
- package/dist/export.js +1 -1
- package/dist/failing-first.d.ts +31 -0
- package/dist/failing-first.js +44 -0
- package/dist/fallback-schema.d.ts +8 -0
- package/dist/fallback-schema.js +40 -0
- package/dist/fallback.d.ts +138 -0
- package/dist/fallback.js +605 -0
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/findings.d.ts +224 -0
- package/dist/findings.js +449 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +75 -9
- package/dist/flaky.js +255 -0
- package/dist/fleet-schema.d.ts +17 -0
- package/dist/fleet-schema.js +135 -0
- package/dist/flow-browser.js +2 -2
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +9 -5
- package/dist/flow-result-card.d.ts +22 -0
- package/dist/flow-result-card.js +55 -0
- package/dist/flow-send.js +12 -9
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +2 -2
- package/dist/flow-unattended.d.ts +26 -0
- package/dist/flow-unattended.js +12 -3
- package/dist/flows-ui.js +10 -2
- package/dist/follow-through-brief.d.ts +12 -0
- package/dist/follow-through-brief.js +31 -0
- package/dist/follow-through-cli.d.ts +23 -0
- package/dist/follow-through-cli.js +153 -0
- package/dist/git-incident.d.ts +16 -0
- package/dist/git-incident.js +69 -6
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/handover-note.d.ts +33 -0
- package/dist/handover-note.js +106 -0
- package/dist/invoke.d.ts +25 -2
- package/dist/invoke.js +157 -58
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-cli.js +26 -24
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -2
- package/dist/lead-doors.d.ts +4 -0
- package/dist/lead-doors.js +2 -2
- package/dist/lead-progress.js +5 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +162 -34
- package/dist/lead.js +2 -2
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +16 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/modes.d.ts +6 -6
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +31 -9
- package/dist/needs-you.js +110 -41
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +7 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +565 -64
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/plan-login.d.ts +14 -0
- package/dist/plan-login.js +58 -0
- package/dist/planner.d.ts +1 -1
- package/dist/planner.js +4 -2
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +19 -2
- package/dist/project-delete-ui.js +29 -1
- package/dist/project-delete.js +14 -1
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/project-tools.d.ts +4 -4
- package/dist/project-tools.js +23 -7
- package/dist/proof.js +30 -5
- package/dist/protected-storage-look.d.ts +1 -0
- package/dist/protected-storage-look.js +13 -0
- package/dist/protected-storage.d.ts +70 -39
- package/dist/protected-storage.js +196 -147
- package/dist/provider-auth.js +1 -1
- package/dist/provider-capture.d.ts +34 -0
- package/dist/provider-capture.js +53 -0
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +18 -2
- package/dist/push.js +40 -16
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +76 -0
- package/dist/release-auto.js +13 -13
- package/dist/release-card.d.ts +1 -2
- package/dist/release-card.js +11 -0
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +49 -10
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +29 -22
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +20 -1
- package/dist/release.js +85 -9
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +27 -19
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +28 -4
- package/dist/scope.js +92 -28
- package/dist/scout.d.ts +1 -1
- package/dist/scout.js +2 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +26 -18
- package/dist/server/chat.js +16 -14
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +73 -0
- package/dist/server/guards.js +3 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +16 -3
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-follow-through.d.ts +11 -0
- package/dist/server/render-follow-through.js +68 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +2 -0
- package/dist/server/render-release.js +3 -3
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +68 -68
- package/dist/server/render-tasks.d.ts +16 -2
- package/dist/server/render-tasks.js +177 -141
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +48 -12
- package/dist/server/settings.js +166 -34
- package/dist/server/task-views.js +66 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +250 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.d.ts +1 -0
- package/dist/settings-groups-ui.js +4 -2
- package/dist/settings-kit.js +3 -2
- package/dist/settings-proofs.js +3 -1
- package/dist/slack-chat.js +14 -1
- package/dist/steer-mailbox.d.ts +8 -1
- package/dist/steer-mailbox.js +164 -6
- package/dist/stop-proof.js +6 -4
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +89 -7
- package/dist/store.js +386 -114
- package/dist/surface.js +67 -6
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.d.ts +2 -1
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +17 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +45 -49
- package/dist/teardown-git.d.ts +18 -0
- package/dist/teardown-git.js +105 -21
- package/dist/telegram-lead.js +23 -6
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/telegram.js +12 -1
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update.js +19 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +95 -66
- package/dist/work-summary.d.ts +1 -1
- package/dist/work-summary.js +7 -2
- package/dist/workspace-ui.d.ts +7 -3
- package/dist/workspace-ui.js +38 -23
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +91 -14
- package/package.json +2 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/chat-actions.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Shared, exact-state actions. Models may prepare; only the existing human
|
|
2
2
|
* confirmation door executes. Protected actions use a one-use review receipt. */
|
|
3
|
+
import { endHoldout, setCodeMapSwitch } from "./code-map-acts.js";
|
|
4
|
+
import { codeMapOn } from "./code-map-store.js";
|
|
3
5
|
import { dismissSuggestion, ruleById, stopTrial, suggestionsFor, suggestionWords, taskEvidenceWords, trialWords } from "./route-rules.js";
|
|
4
6
|
import { CHAT_ACTION_LOCAL_ACTS, currentLocalOwner } from "./local-owner.js";
|
|
5
7
|
import { fileTaskProposal } from "./proposal.js";
|
|
@@ -10,12 +12,24 @@ import { withActor } from "./actor.js";
|
|
|
10
12
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
11
13
|
import { publicChatText } from "./chat-display.js";
|
|
12
14
|
import { gateWords } from "./approval-policy.js";
|
|
15
|
+
import { checkLeadIdentity } from "./lead-identity.js";
|
|
16
|
+
import { hasForbiddenControls } from "./decision.js";
|
|
17
|
+
import { activeUnattendedGrant, candidatesDigestOf, FLOW_ACCEPTANCE, parseUnattendedAsk, revokeUnattended, sealUnattended, unattendedCandidates, unattendedSignProblem, untilWords, workReposOf } from "./flow-unattended.js";
|
|
18
|
+
import { UNATTENDED_RULE_WORDS, unattendedCoverWords, unattendedHeadWords, unattendedLimitWords } from "./flow-unattended-words.js";
|
|
19
|
+
import { CHECK_ACTION, checkFindingAt, cleanFindingText, familyInReach, FINDING_EXCERPT_LIMIT, FINDING_ROUND_LIMIT, FINDING_TEXT_LIMIT, findingLine, findingStandingWords, getFinding, resendFinding, REVIEW_ACTION, reviewFindingAt, sendCheckFinding, sendFinding, sendReviewFinding } from "./findings.js";
|
|
20
|
+
import { lessonNoteOf } from "./lesson-notes.js";
|
|
21
|
+
import { decideProposal, listProposals } from "./memory-pass.js";
|
|
22
|
+
import { authPauseOf, liftAuthPause } from "./provider-auth.js";
|
|
23
|
+
import { isProviderId } from "./provider.js";
|
|
24
|
+
import { approverStandsNow, familyRepoOf } from "./tidy.js";
|
|
25
|
+
import { chatApproveMode, effectiveChatApproval } from "./chat-approval.js";
|
|
26
|
+
import { cardTermsDigest, chatLimitProblem, scopeTermsText } from "./chat-terms.js";
|
|
13
27
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
|
-
import { leadTurnKeepsProposals, verifiedAuthor } from "./store.js";
|
|
28
|
+
import { archiveTaskFamily, leadTurnKeepsProposals, restoreTaskFamily, verifiedAuthor } from "./store.js";
|
|
15
29
|
import { isVerifiedApprover, reproveApprover, } from "./principal.js";
|
|
16
30
|
import { changeSkills, importSkill, skillsView, skillsVersion, testSkill, validateSkill, } from "./project-skills.js";
|
|
17
31
|
import { changeKnowledge, knowledgeVersion, knowledgeView, } from "./project-knowledge.js";
|
|
18
|
-
import { passwordlessSessionFor, approve, authenticateApprover, describeScope, scopeAuthorityOf, freshnessFor, localApprovalEligible, spendingOnlyOnSuccess, ssoNowAvailable, withFreshness, } from "./scope.js";
|
|
32
|
+
import { passwordlessSessionFor, approve, authenticateApprover, describeScope, scopeAuthorityOf, machineActing, freshnessFor, localApprovalEligible, spendingOnlyOnSuccess, ssoNowAvailable, withFreshness, } from "./scope.js";
|
|
19
33
|
import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
|
|
20
34
|
import { readAuthModeStrict } from "./keys.js";
|
|
21
35
|
import { acceptAndCompleteAsOperator, assignmentOf } from "./assignment.js";
|
|
@@ -30,7 +44,7 @@ import { assignFlowCard, commentOnFlowCard, flowPeople, mentionsIn, watchFlowCar
|
|
|
30
44
|
import { browserViewOf, liveBrowserGrant, revokeFlowBrowser, signFlowBrowser } from "./flow-browser.js";
|
|
31
45
|
import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger, takesDeliveries, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
|
|
32
46
|
import { describeSchedule, parseSchedule } from "./flow-schedule.js";
|
|
33
|
-
import { dirname } from "node:path";
|
|
47
|
+
import { basename, dirname } from "node:path";
|
|
34
48
|
import { handleOf, parseSoul, SOUL_CHARS, SUBAGENT_TEMPLATES, subagentLabel } from "./subagents.js";
|
|
35
49
|
import { createSubagentFrom, labelOf, nameOf, renamedSoul, saveSoul, setSubagentState } from "./subagent-admin.js";
|
|
36
50
|
import { cleanMemory, editMemory, forgetMemory, tellSubagent } from "./subagent-memory.js";
|
|
@@ -94,7 +108,7 @@ export const CHAT_ACTIONS = {
|
|
|
94
108
|
flow_starter: { label: "Switch on", protected: false, password: false },
|
|
95
109
|
// Lead fan-out (docs/plans/lead-fan-out.md): files N tasks under one brief and approves each one's scope — the
|
|
96
110
|
// person's ceremony (protected, with the password), N ordinary approvals of exactly the scopes shown.
|
|
97
|
-
sweep_file: { label: "Start sweep", protected: true, password:
|
|
111
|
+
sweep_file: { label: "Start sweep", protected: true, password: false },
|
|
98
112
|
// Stops every item of a sweep not yet ready; finished results stay. An ordinary act, like cancelling a task.
|
|
99
113
|
sweep_cancel_rest: { label: "Cancel the rest", protected: false, password: false },
|
|
100
114
|
flow_edit: { label: "Save flow", protected: false, password: false },
|
|
@@ -112,9 +126,9 @@ export const CHAT_ACTIONS = {
|
|
|
112
126
|
flow_trigger_pause: { label: "Pause trigger", protected: false, password: false },
|
|
113
127
|
flow_trigger_resume: { label: "Turn trigger on", protected: false, password: false },
|
|
114
128
|
flow_trigger_remove: { label: "Remove trigger", protected: false, password: false },
|
|
115
|
-
// Browser use widens a flow's sandbox (flow-browser.ts): the lead may only propose it; a person allows it with their
|
|
116
|
-
//
|
|
117
|
-
flow_browser: { label: "Allow browser", protected: true, password:
|
|
129
|
+
// Browser use widens a flow's sandbox (flow-browser.ts): the lead may only propose it; a person allows it with their own
|
|
130
|
+
// yes on a card that shows every term, in a chat they are signed in to or linked from. Turning it off lowers authority.
|
|
131
|
+
flow_browser: { label: "Allow browser", protected: true, password: false },
|
|
118
132
|
flow_browser_off: { label: "Turn off browser", protected: false, password: false },
|
|
119
133
|
subagent_create: { label: "Add subagent", protected: false, password: false },
|
|
120
134
|
subagent_soul: { label: "Update soul file", protected: false, password: false },
|
|
@@ -133,22 +147,50 @@ export const CHAT_ACTIONS = {
|
|
|
133
147
|
suggestion_not_now: { label: "Not now", protected: false, password: false },
|
|
134
148
|
// Stopping a trial only lowers: new tasks build on the configured model; approved ones keep theirs.
|
|
135
149
|
trial_stop: { label: "Stop trial", protected: false, password: false },
|
|
150
|
+
// The code map's switch and holdout (docs/plans/code-map.md §5): a person's yes on the card, re-proved in the write.
|
|
151
|
+
code_map_switch: { label: "Change code map", protected: false, password: false },
|
|
152
|
+
code_map_holdout_end: { label: "End holdout", protected: false, password: false },
|
|
153
|
+
// Chat and console parity (docs/plans/toolroll-cards.md): plain acts that lower authority or change no one's reach,
|
|
154
|
+
// each the console's own write. Ending a mode and turning a flow off its own only ever lower authority.
|
|
155
|
+
mode_revoke: { label: "End automatic approvals", protected: false, password: false },
|
|
156
|
+
flow_unattended_off: { label: "Stop running on its own", protected: false, password: false },
|
|
157
|
+
flow_archive: { label: "Archive flow", protected: false, password: false },
|
|
158
|
+
provider_resume: { label: "Resume agent", protected: false, password: false },
|
|
159
|
+
worker_note: { label: "Note on worker", protected: false, password: false },
|
|
160
|
+
lead_identity: { label: "Name your lead", protected: false, password: false },
|
|
161
|
+
task_archive: { label: "Put away task", protected: false, password: false },
|
|
162
|
+
task_restore: { label: "Restore task", protected: false, password: false },
|
|
163
|
+
incident_resolve: { label: "Resolve incident", protected: false, password: false },
|
|
164
|
+
memory_decide: { label: "Decide memory proposal", protected: false, password: false },
|
|
165
|
+
// Follow-through (findings.ts): not an approval. A person's yes to words shown on the card sends them to the agent.
|
|
166
|
+
finding_send: { label: "Send finding", protected: false, password: false },
|
|
167
|
+
finding_resend: { label: "Send finding back", protected: false, password: false },
|
|
168
|
+
// The rest of the approvals: a person's own yes on a card that shows every term.
|
|
169
|
+
task_reopen: { label: "Reopen task", protected: true, password: false },
|
|
170
|
+
revision_accept: { label: "Accept plan revision", protected: true, password: false },
|
|
171
|
+
revision_reject: { label: "Reject plan revision", protected: true, password: false },
|
|
172
|
+
flow_unattended: { label: "Let flow run on its own", protected: true, password: false },
|
|
136
173
|
// What the owner's own lead knows about them: theirs alone, in no project (lead-about.ts).
|
|
137
174
|
lead_about_you: { label: "Remember", protected: false, password: false },
|
|
138
|
-
scope_approve: { label: "Approve work", protected: true, password:
|
|
139
|
-
result_accept: { label: "Accept
|
|
175
|
+
scope_approve: { label: "Approve work", protected: true, password: false },
|
|
176
|
+
result_accept: { label: "Accept", protected: true, password: false },
|
|
140
177
|
task_cancel: { label: "Cancel task", protected: true, password: false },
|
|
141
|
-
task_resume: { label: "Resume task", protected: true, password:
|
|
178
|
+
task_resume: { label: "Resume task", protected: true, password: false },
|
|
142
179
|
};
|
|
143
180
|
// Every action has a schema (contracts/chat-actions.ts) and every schema an action: a mismatch fails to compile.
|
|
144
181
|
const SAME_ACTIONS = true;
|
|
145
182
|
void SAME_ACTIONS;
|
|
146
|
-
/**
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
183
|
+
/** Approvals (owner's decision, docs/plans/toolroll-cards.md): a person's own yes from their chat stands without a password.
|
|
184
|
+
* Only a person's tap in a linked chat, or their click in a signed-in browser, ever runs one. */
|
|
185
|
+
export const APPROVAL_ACTIONS = new Set(["scope_approve", "task_resume", "sweep_file", "flow_browser", "task_reopen", "revision_accept", "revision_reject", "flow_unattended"]);
|
|
186
|
+
/** Protected actions a linked chat may confirm behind its own explicit yes/cancel challenge instead of the review screen.
|
|
187
|
+
* Password actions and long or redacted terms never qualify: the card must show the exact terms. */
|
|
188
|
+
export const APPROVAL_TERM_CHARS = 3000;
|
|
189
|
+
export const CHALLENGE_ACTIONS = new Set(["result_accept", ...APPROVAL_ACTIONS]);
|
|
150
190
|
/** Actions about the person themselves, in no project: their card carries no project (repo ""). */
|
|
151
|
-
export const OWNER_ACTIONS = new Set(["lead_about_you"]);
|
|
191
|
+
export const OWNER_ACTIONS = new Set(["lead_about_you", "provider_resume", "worker_note", "lead_identity"]);
|
|
192
|
+
/** Actions the console refuses a project-limited account (route table: limited "deny"): the same here. */
|
|
193
|
+
const INSTANCE_ACTIONS = new Set(["mode_revoke", "flow_unattended_off", "flow_unattended", "provider_resume", "worker_note", "lead_identity", "incident_resolve", "memory_decide"]);
|
|
152
194
|
export function isChatAction(value) {
|
|
153
195
|
return typeof value === "string" && Object.hasOwn(CHAT_ACTIONS, value);
|
|
154
196
|
}
|
|
@@ -262,6 +304,14 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
262
304
|
const read = readChatActionRequest(operation, input);
|
|
263
305
|
if (!read.ok)
|
|
264
306
|
throw Error(`This action can't be read — ${read.issues.map(issue => issue.line).join("; ")}.`);
|
|
307
|
+
if (INSTANCE_ACTIONS.has(operation) && store.accountOf(who.name)?.projects !== null)
|
|
308
|
+
throw Error("That needs an account with access to every project, as in the console.");
|
|
309
|
+
if (operation === "provider_resume" || operation === "worker_note" || operation === "lead_identity") {
|
|
310
|
+
requireActor(store, who, "", operation);
|
|
311
|
+
const { request, terms, state, title } = instanceAction(store, who, operation, input);
|
|
312
|
+
const stamp = hash({ operation, request, repo: "", state, terms, actor: who.name, generation: who.generation, ceiling: who.ceilingDigest });
|
|
313
|
+
return { version: SHARED_ACTION_VERSION, operation, request, repo: "", title, terms, stamp, state };
|
|
314
|
+
}
|
|
265
315
|
if (operation === "lead_about_you") {
|
|
266
316
|
// The owner's own note: the card shows the line, and the line it replaces beside it.
|
|
267
317
|
const checked = checkAboutYouLine(input["line"]);
|
|
@@ -284,7 +334,7 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
284
334
|
const stamp = hash({ operation, request, repo: "", state, terms, actor: who.name, generation: who.generation, ceiling: who.ceilingDigest });
|
|
285
335
|
return { version: SHARED_ACTION_VERSION, operation, request, repo: "", title, terms, stamp, state };
|
|
286
336
|
}
|
|
287
|
-
const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("subagent_") || operation.startsWith("kit_") || operation.startsWith("sweep_") || operation.startsWith("suggestion_") || operation.startsWith("trial_")
|
|
337
|
+
const task = operation === "mode_revoke" || operation === "memory_decide" || operation === "incident_resolve" || operation === "finding_resend" || operation.startsWith("code_map_") || operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("subagent_") || operation.startsWith("kit_") || operation.startsWith("sweep_") || operation.startsWith("suggestion_") || operation.startsWith("trial_")
|
|
288
338
|
? null
|
|
289
339
|
: text(input, "task", 64);
|
|
290
340
|
const flowTarget = operation.startsWith("flow_") && operation !== "flow_create" && operation !== "flow_script_save" && operation !== "flow_starter" ? flowTargetOf(store, input) : null;
|
|
@@ -297,13 +347,21 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
297
347
|
if (operation === "subagent_answer" && (questionTarget === null || questionFlow === null))
|
|
298
348
|
throw Error("That question isn't open any more.");
|
|
299
349
|
const repo = operation === "sweep_cancel_rest" ? store.getSweep(integer(input, "sweep"))?.repo
|
|
300
|
-
:
|
|
350
|
+
: operation === "incident_resolve" ? incidentRepoOf(store, integer(input, "incident"))
|
|
351
|
+
: operation === "finding_resend" ? getFinding(store, integer(input, "finding"))?.repo
|
|
352
|
+
: flowTarget !== null ? flowTarget.flow.repo : subagentTarget !== null ? subagentTarget.repo : questionFlow !== null ? questionFlow.repo : task === null ? text(input, "repo", 4096) : store.lookupRef(task)?.repo;
|
|
301
353
|
if (!repo)
|
|
302
354
|
throw Error("Choose an available project.");
|
|
303
355
|
requireActor(store, who, repo);
|
|
304
356
|
const request = structuredClone(input), terms = [];
|
|
305
357
|
let state, title = CHAT_ACTIONS[operation].label;
|
|
306
|
-
if (
|
|
358
|
+
if (PARITY_ACTIONS.has(operation)) {
|
|
359
|
+
const made = parityAction(store, who, operation, input, repo, flowTarget?.flow ?? null, now, request, root);
|
|
360
|
+
state = made.state;
|
|
361
|
+
title = made.title;
|
|
362
|
+
terms.push(...made.terms);
|
|
363
|
+
}
|
|
364
|
+
else if (operation.startsWith("skill_")) {
|
|
307
365
|
const view = skillsView(store, repo, who.name);
|
|
308
366
|
state = {
|
|
309
367
|
identity: view.identity,
|
|
@@ -620,7 +678,9 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
620
678
|
const children = sweepChildScopes(store, who, repo, brief, cleaned.map(({ title, item }) => ({ title, item })), input["acceptance"], now);
|
|
621
679
|
state = { children: children.map(one => one.fingerprint) };
|
|
622
680
|
const tests = acceptance.map((one, index) => `Success test ${index + 1}${typeof one["id"] === "string" ? ` (${one["id"]})` : ""}: ${String(one["statement"] ?? "")}${Array.isArray(one["evidence"]) ? ` — shown by ${one["evidence"].map(String).join(", ")}` : ""}`);
|
|
623
|
-
terms.push(`${cleaned.length} tasks, each in its own worktree on its own branch, at most ${maxParallel} running at once, built directly (no separate planning step).`, `Shared rules for every item: ${brief}`, ...cleaned.map(one => `Item ${one.index + 1} — ${one.title}: ${one.item}`), `Each item's goal is the shared rules, then its own item, then: ${VERDICT_RULE}`, ...tests.map(line => `Every item — ${line}`), ...(children.every(one => one.runtime === children[0].runtime) ? [`Every item — ${children[0].runtime}`] : children.map((one, index) => `Item ${index + 1} — ${one.runtime}`)),
|
|
681
|
+
terms.push(`${cleaned.length} tasks, each in its own worktree on its own branch, at most ${maxParallel} running at once, built directly (no separate planning step).`, `Shared rules for every item: ${brief}`, ...cleaned.map(one => `Item ${one.index + 1} — ${one.title}: ${one.item}`), `Each item's goal is the shared rules, then its own item, then: ${VERDICT_RULE}`, ...tests.map(line => `Every item — ${line}`), ...(children.every(one => one.runtime === children[0].runtime) ? [`Every item — ${children[0].runtime}`] : children.map((one, index) => `Item ${index + 1} — ${one.runtime}`)),
|
|
682
|
+
// Each item's own terms, word for word: its yes is bound to this text.
|
|
683
|
+
...children.map((one, index) => `Item ${index + 1} in full:\n${one.terms}`), "Confirming files all of them and approves each one's scope exactly as shown. An item that needs a second person, or that the project's rules keep from its requester, is filed and waits for its own approval.", "Each ends with a verdict (fixed, not reproducible, blocked or done) and the sweep's table shows them as they land.");
|
|
624
684
|
}
|
|
625
685
|
else if (operation.startsWith("flow_")) {
|
|
626
686
|
const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
|
|
@@ -744,7 +804,9 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
744
804
|
// The approval the person reads and the one their password signs: the digest is re-derived when they confirm.
|
|
745
805
|
state = { flow: flow.id, revision: flow.revision, digest: view.digest };
|
|
746
806
|
title = `Allow browser use in ${flow.name}`;
|
|
747
|
-
terms.push(`Flow: ${flow.name} · project ${project}`,
|
|
807
|
+
terms.push(`Flow: ${flow.name} · project ${project}`,
|
|
808
|
+
// Each script by its short digest: the whole approval, every script's full digest included, is bound in the state.
|
|
809
|
+
...view.zones.map(zone => `${zone.title} runs ${zone.script} v${zone.version}\nScript ${zone.digest.slice(0, 12)}`), "Each run of these zones starts a headless Chromium.", ...view.terms);
|
|
748
810
|
}
|
|
749
811
|
}
|
|
750
812
|
else if (operation === "flow_trigger_pause" || operation === "flow_trigger_resume" || operation === "flow_trigger_remove") {
|
|
@@ -891,6 +953,17 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
891
953
|
title = "Stop trying a cheaper model";
|
|
892
954
|
terms.push(trialWords(store, trial), "New tasks build on the configured model again. Tasks a person already approved on the trial keep that model.");
|
|
893
955
|
}
|
|
956
|
+
else if (operation === "code_map_switch") {
|
|
957
|
+
const on = input["on"] === true;
|
|
958
|
+
state = { on: codeMapOn(store, repo) };
|
|
959
|
+
title = on ? "Turn the code map on" : "Turn the code map off";
|
|
960
|
+
terms.push(on ? "Toolroll keeps a map of main for this project, and builds get it." : "No new maps are made, and builds get none. The last map stays saved.");
|
|
961
|
+
}
|
|
962
|
+
else if (operation === "code_map_holdout_end") {
|
|
963
|
+
state = { holdout: true };
|
|
964
|
+
title = "End the code map holdout";
|
|
965
|
+
terms.push("No new builds are enrolled. Builds already enrolled keep their arm, so the results stay comparable.");
|
|
966
|
+
}
|
|
894
967
|
else if (operation.startsWith("decision_")) {
|
|
895
968
|
// The staleness fence: the newest decision id and the active count, so a
|
|
896
969
|
// card drafted before a subagent recorded or retired one is refused.
|
|
@@ -972,8 +1045,9 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
972
1045
|
if (!authority.ok)
|
|
973
1046
|
throw Error(authority.problem);
|
|
974
1047
|
state["authMode"] = authority.authMode;
|
|
975
|
-
terms
|
|
976
|
-
|
|
1048
|
+
// The whole terms word for word (the yes is bound to this text: chat-terms.ts cardShowsScope), then everything else.
|
|
1049
|
+
terms.push(scopeTermsText(scope), ...describeScope(scope, undefined, taskEvidenceWords(store, scope.taskId, now))
|
|
1050
|
+
.filter((line) => !/^\s*(reference|approved|goal|not this|touches)\s/.test(line))
|
|
977
1051
|
.map((line) => line
|
|
978
1052
|
.trimStart()
|
|
979
1053
|
.replace(/^why model\s+/, "Why this model: ")
|
|
@@ -1035,6 +1109,9 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
1035
1109
|
terms.push(`Limitation: ${caveat}`);
|
|
1036
1110
|
}
|
|
1037
1111
|
}
|
|
1112
|
+
// An approval keeps the digest of the exact words its card shows, from now on: the yes is to these (scope.ts approve).
|
|
1113
|
+
if (APPROVAL_ACTIONS.has(operation))
|
|
1114
|
+
state["shown"] = cardTermsDigest(terms.join("\n"));
|
|
1038
1115
|
const stamp = hash({
|
|
1039
1116
|
operation,
|
|
1040
1117
|
request,
|
|
@@ -1055,10 +1132,13 @@ export function sharedActionPayload(value) {
|
|
|
1055
1132
|
/** A shortened or redacted preview is not complete consent. This rule lives
|
|
1056
1133
|
* in the shared door, so a forged transport callback cannot bypass it. */
|
|
1057
1134
|
function sharedActionContentNeedsReview(action) {
|
|
1135
|
+
// An approval's card has room for longer terms. Its words are compared exactly: anything the card would hide or
|
|
1136
|
+
// rewrite (a long hex value, a path, a secret) sends the person to the review page. Toolroll's own digest is bound in
|
|
1137
|
+
// the state, never in the terms, and a commit is written short, so an ordinary approval shows every word as stored.
|
|
1138
|
+
const approval = APPROVAL_ACTIONS.has(action.operation), cap = approval ? APPROVAL_TERM_CHARS : 1200;
|
|
1058
1139
|
const content = [action.title, ...action.terms];
|
|
1059
|
-
return (action.terms.join("\n").length >
|
|
1060
|
-
content.some((value, index) => publicChatText(value, index === 0 ? 200 :
|
|
1061
|
-
value.replace(/\s+/g, " ").trim()));
|
|
1140
|
+
return (action.terms.join("\n").length > cap ||
|
|
1141
|
+
content.some((value, index) => publicChatText(value, index === 0 ? 200 : cap) !== value.replace(/\s+/g, " ").trim()));
|
|
1062
1142
|
}
|
|
1063
1143
|
export function sharedActionNeedsReview(action) {
|
|
1064
1144
|
return (CHAT_ACTIONS[action.operation].protected ||
|
|
@@ -1145,8 +1225,10 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1145
1225
|
if ((options.authority === "local-owner" || currentLocalOwner() !== null) && (!isChatAction(payload.operation) || CHAT_ACTION_LOCAL_ACTS[payload.operation] === null))
|
|
1146
1226
|
return refuse("refused", "Set a password to do this.");
|
|
1147
1227
|
let live;
|
|
1228
|
+
let drafted;
|
|
1148
1229
|
try {
|
|
1149
1230
|
const saved = savedActionContext(store, who, id, "confirming");
|
|
1231
|
+
drafted = saved;
|
|
1150
1232
|
if (hash(saved) !== hash(payload))
|
|
1151
1233
|
return refuse("refused", "This action does not match the saved proposal.");
|
|
1152
1234
|
live = prepareSharedAction(store, who, saved.operation, saved.request, options.root, now);
|
|
@@ -1160,31 +1242,24 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1160
1242
|
}
|
|
1161
1243
|
// The review nonce was minted against the saved proposal's stamp: it is checked against that, whoever reviews.
|
|
1162
1244
|
const savedStamp = payload.stamp;
|
|
1163
|
-
let personProved = null;
|
|
1164
1245
|
payload = live;
|
|
1165
1246
|
const config = CHAT_ACTIONS[live.operation], req = live.request;
|
|
1166
1247
|
const challenged = options.via !== "web" &&
|
|
1167
1248
|
options.via !== "cli" &&
|
|
1168
1249
|
options.confirm === true &&
|
|
1169
1250
|
sharedActionAllowsChallenge(payload);
|
|
1251
|
+
if (APPROVAL_ACTIONS.has(live.operation) && machineActing())
|
|
1252
|
+
return refuse("needs-confirm", "Only a person approves this.");
|
|
1253
|
+
// A lead's or remote token reaches the same web door: an approval needs a person's own session (or tap, above).
|
|
1254
|
+
if (APPROVAL_ACTIONS.has(live.operation) && options.via === "web" && options.authority !== "local-owner" && options.person !== true)
|
|
1255
|
+
return refuse("needs-confirm", "Approve this yourself, in your signed-in chat.");
|
|
1170
1256
|
if (sharedActionNeedsReview(payload) && !challenged) {
|
|
1171
1257
|
if (options.via !== "web" || !options.review || options.confirm !== true)
|
|
1172
1258
|
return refuse("needs-confirm", "Review the complete action in the secure confirmation screen.");
|
|
1173
1259
|
if (!store.consumeCeremonyNonce(nonceHash(options.review.nonce), who.name, "chat-action", id, savedStamp, now))
|
|
1174
1260
|
return refuse("needs-confirm", "This confirmation expired or was already used. Open the review again.");
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
const emptyApproval = payload.operation === "scope_approve" && options.review.password === "";
|
|
1178
|
-
const proved = config.password && !emptyApproval ? authenticateApprover(store, who.name, options.review.password, payload.repo) : null;
|
|
1179
|
-
// How the person was proved, for an approval that records it (browser use: flow-browser.ts refuses any but a person's).
|
|
1180
|
-
if (proved?.ok === true)
|
|
1181
|
-
personProved = proved.method;
|
|
1182
|
-
if (config.password &&
|
|
1183
|
-
(emptyApproval
|
|
1184
|
-
? !passwordlessSessionFor(store, who.name) && !ssoNowAvailable(who.name) && withFreshness("approve-standard", () => freshnessFor(who.name)) === null
|
|
1185
|
-
// Easy-start C5: a local owner's eligible approval takes no password (approve() proves it again in the seal).
|
|
1186
|
-
&& !(options.authority === "local-owner" && currentLocalOwner()?.act === "approve-plan" && typeof req["task"] === "string" && localApprovalEligible(store, who.name, req["task"]).ok)
|
|
1187
|
-
: proved?.ok !== true))
|
|
1261
|
+
const proved = config.password ? authenticateApprover(store, who.name, options.review.password, payload.repo) : null;
|
|
1262
|
+
if (config.password && proved?.ok !== true)
|
|
1188
1263
|
return refuse("needs-confirm", "Confirm with your password in the secure screen.");
|
|
1189
1264
|
}
|
|
1190
1265
|
try {
|
|
@@ -1193,6 +1268,10 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1193
1268
|
const act = () => store.savepoint(() => {
|
|
1194
1269
|
const task = typeof req["task"] === "string" ? req["task"] : null, repo = payload.repo, actor = who.name;
|
|
1195
1270
|
let taskId = task;
|
|
1271
|
+
if (LATE_APPROVALS.has(payload.operation))
|
|
1272
|
+
return runLateApproval(store, who, payload, repo => approvalBy(store, who, repo, now, options, drafted), approvalRoad(options), now);
|
|
1273
|
+
if (PARITY_ACTIONS.has(payload.operation) || OWNER_PARITY.has(payload.operation))
|
|
1274
|
+
return runParityAction(store, who, payload, now, options.root);
|
|
1196
1275
|
if (payload.operation === "lead_about_you") {
|
|
1197
1276
|
const next = withAboutYouLine(aboutYouOf(store, actor), String(req["line"]), Number(req["replaces"] ?? 0));
|
|
1198
1277
|
if (!next.ok)
|
|
@@ -1229,7 +1308,10 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1229
1308
|
// re-reads the flow, its zones and scripts in this transaction and refuses unless they are what the card showed.
|
|
1230
1309
|
const flow = Number(payload.state["flow"]);
|
|
1231
1310
|
// signFlowBrowser re-reads the account's generation and reach in its transaction (it may have narrowed since).
|
|
1232
|
-
const
|
|
1311
|
+
const by = approvalBy(store, who, repo, now, options, drafted);
|
|
1312
|
+
if (by.ok === false)
|
|
1313
|
+
throw Error(by.why);
|
|
1314
|
+
const signed = signFlowBrowser(store, { flow, signer: actor, proof: options.via === "web" ? "session" : "chat", sawDigest: String(payload.state["digest"] ?? ""), console: { generation: who.generation } }, now);
|
|
1233
1315
|
if (!signed.ok)
|
|
1234
1316
|
throw Error(signed.message);
|
|
1235
1317
|
return { ok: true, taskId: null, said: "Browser allowed. Its browser zones can start Chromium now.", href: `/flows/${flow}` };
|
|
@@ -1249,7 +1331,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1249
1331
|
return { ok: true, taskId: null, said, href: `/sweeps/${Number(req["sweep"])}` };
|
|
1250
1332
|
}
|
|
1251
1333
|
else if (payload.operation === "sweep_file") {
|
|
1252
|
-
const done = fileSweep(store, who, id, payload,
|
|
1334
|
+
const done = fileSweep(store, who, id, payload, approvalBy(store, who, payload.repo, now, options, drafted), now);
|
|
1253
1335
|
return { ok: true, taskId: null, said: done.said, href: done.href };
|
|
1254
1336
|
}
|
|
1255
1337
|
else if (payload.operation === "kit_setup") {
|
|
@@ -1282,6 +1364,13 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1282
1364
|
throw Error(done.problem);
|
|
1283
1365
|
return { ok: true, taskId: null, said: "Trial stopped. New tasks build on the configured model.", href: `/settings/models?scope=${encodeURIComponent(repo)}#models-suggested` };
|
|
1284
1366
|
}
|
|
1367
|
+
else if (payload.operation === "code_map_switch" || payload.operation === "code_map_holdout_end") {
|
|
1368
|
+
const door = currentLocalOwner() !== null ? { kind: "local-owner", actor } : { kind: "person", person: { actor: who.name, generation: who.generation, method: "session" } };
|
|
1369
|
+
const done = payload.operation === "code_map_switch" ? setCodeMapSwitch(store, door, repo, req["on"] === true, now) : endHoldout(store, door, repo, now);
|
|
1370
|
+
if (!done.ok)
|
|
1371
|
+
throw Error(done.problem);
|
|
1372
|
+
return { ok: true, taskId: null, said: payload.operation === "code_map_switch" ? `The code map is ${req["on"] === true ? "on" : "off"}.` : "The holdout has ended.", href: `/settings/project?repo=${encodeURIComponent(repo)}#code-map` };
|
|
1373
|
+
}
|
|
1285
1374
|
else if (payload.operation === "suggestion_not_now") {
|
|
1286
1375
|
// The rule door (route-store.ts proveRuleDoor) re-proves who says it: this computer's local owner only inside
|
|
1287
1376
|
// its `dismiss-suggestion` act, or the confirming person.
|
|
@@ -1313,7 +1402,16 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1313
1402
|
else if (payload.operation === "scope_approve") {
|
|
1314
1403
|
const scope = store.getScope(task);
|
|
1315
1404
|
// Cockpit B2: classified by the act, not the road — the same standard-approval freshness as /t/:id/approve.
|
|
1316
|
-
const
|
|
1405
|
+
const by = approvalBy(store, who, repo, now, options, drafted);
|
|
1406
|
+
if (by.ok === false)
|
|
1407
|
+
throw Error(by.why);
|
|
1408
|
+
const limited = chatLimitProblem(scope, by.limits, "your chat approval setting");
|
|
1409
|
+
if (limited !== null)
|
|
1410
|
+
throw Error(limited);
|
|
1411
|
+
// A local owner keeps its own road (approve proves it again in the seal); everyone else, their own yes from chat,
|
|
1412
|
+
// to the scope and the words the card was made with.
|
|
1413
|
+
const sawDigest = String(drafted.state["scope"]?.digest ?? "");
|
|
1414
|
+
const result = withFreshness("approve-standard", () => approve(store, task, actor, now, sawDigest, options.review?.password ?? "", {}, undefined, by.chat));
|
|
1317
1415
|
if (!result.ok) {
|
|
1318
1416
|
// A first yes on protected work is kept, with how its person was proved: committed, never rolled back.
|
|
1319
1417
|
// The card stays open (`awaiting`) for the second approver; the same person again changes nothing.
|
|
@@ -1340,11 +1438,14 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1340
1438
|
throw Error(`Task was not cancelled: ${result.reason}.`);
|
|
1341
1439
|
}
|
|
1342
1440
|
else if (payload.operation === "task_resume") {
|
|
1441
|
+
const by = approvalBy(store, who, repo, now, options, drafted);
|
|
1442
|
+
if (by.ok === false)
|
|
1443
|
+
throw Error(by.why);
|
|
1343
1444
|
const result = resumeTaskStop(store, {
|
|
1344
1445
|
taskId: task,
|
|
1345
1446
|
runId: Number(req["run"]),
|
|
1346
1447
|
by: verifiedAuthor(actor),
|
|
1347
|
-
via:
|
|
1448
|
+
via: options.via,
|
|
1348
1449
|
}, now);
|
|
1349
1450
|
if (!result.ok)
|
|
1350
1451
|
throw Error(result.detail);
|
|
@@ -1423,7 +1524,7 @@ function sweepChildScopeOf(store, taskId) {
|
|
|
1423
1524
|
// The fingerprint covers the scope's every field, not only what is rendered (a profile setting no sentence names, such
|
|
1424
1525
|
// as an agent's idle timeout, still makes the card stale): its exact terms with only identity left out.
|
|
1425
1526
|
const { taskId: _task, digest: _digest, approvedDigest: _approved, approvedBy: _by, approvedAt: _at, proposedAt: _proposed, approvalBasis: _basis, modeDigest: _mode, approvedProfile: _approvedProfile, approvedProfileJson: _approvedProfileJson, approvedChainJson: _approvedChain, ...terms } = scope;
|
|
1426
|
-
return { fingerprint: hash({ words, runtime, authMode: authority.authMode, profile: authority.profile, terms }), runtime, words };
|
|
1527
|
+
return { fingerprint: hash({ words, runtime, authMode: authority.authMode, profile: authority.profile, terms }), runtime, words, terms: scopeTermsText(scope) };
|
|
1427
1528
|
}
|
|
1428
1529
|
/** Every child as filing would make it, found in a savepoint that is always rolled back: prepare never writes. */
|
|
1429
1530
|
function sweepChildScopes(store, who, repo, brief, items, acceptance, now) {
|
|
@@ -1449,7 +1550,24 @@ function sweepChildScopes(store, who, repo, brief, items, acceptance, now) {
|
|
|
1449
1550
|
});
|
|
1450
1551
|
return found;
|
|
1451
1552
|
}
|
|
1452
|
-
|
|
1553
|
+
/** How this yes stands, and under whose limits: a person's own from chat, by their own chat setting, with the words and
|
|
1554
|
+
* digest the card was made with. A local owner's road has none: approve() proves it itself. */
|
|
1555
|
+
function approvalBy(store, who, repo, now, options, drafted) {
|
|
1556
|
+
if (options.authority === "local-owner")
|
|
1557
|
+
return { ok: true, limits: { fullAccess: true, capMicrousd: null } };
|
|
1558
|
+
if (options.via === "cli")
|
|
1559
|
+
return { ok: false, why: "Approve this in chat or in Toolroll." };
|
|
1560
|
+
const shown = { expect: String(drafted.state["shown"] ?? ""), terms: drafted.terms.join("\n") };
|
|
1561
|
+
if (options.via === "web") {
|
|
1562
|
+
const allowed = effectiveChatApproval(store, repo, who.name);
|
|
1563
|
+
return allowed.ok ? { ok: true, limits: allowed.limits, chat: { modeDigest: allowed.setting.digest, shown } } : { ok: false, why: allowed.why };
|
|
1564
|
+
}
|
|
1565
|
+
const allowed = chatApproveMode(store, repo, who.name, now, options.via);
|
|
1566
|
+
return allowed.ok ? { ok: true, limits: allowed.limits, chat: { modeDigest: allowed.digest, shown } } : allowed;
|
|
1567
|
+
}
|
|
1568
|
+
function fileSweep(store, who, proposal, payload, by, now) {
|
|
1569
|
+
if (by.ok === false)
|
|
1570
|
+
throw Error(by.why);
|
|
1453
1571
|
const req = payload.request;
|
|
1454
1572
|
const already = store.sweepOfProposal(proposal);
|
|
1455
1573
|
if (already !== null)
|
|
@@ -1472,7 +1590,9 @@ function fileSweep(store, who, proposal, payload, password, now) {
|
|
|
1472
1590
|
const waiting = [];
|
|
1473
1591
|
for (const one of filed) {
|
|
1474
1592
|
const scope = store.getScope(one.id);
|
|
1475
|
-
|
|
1593
|
+
// A person's own limits hold for each item: one past them is filed and waits for its own approval.
|
|
1594
|
+
const approved = scope === null || chatLimitProblem(scope, by.limits, "your chat approval setting") !== null ? null
|
|
1595
|
+
: withFreshness("approve-standard", () => approve(store, one.id, who.name, now, scope.digest, "", {}, undefined, by.chat));
|
|
1476
1596
|
if (approved === null || !approved.ok)
|
|
1477
1597
|
waiting.push(one.title);
|
|
1478
1598
|
}
|
|
@@ -1654,3 +1774,310 @@ function runFlowAction(store, payload, actor, repos, root, now) {
|
|
|
1654
1774
|
throw Error(acted.message);
|
|
1655
1775
|
return settle(card.flow, acted.said, card.id);
|
|
1656
1776
|
}
|
|
1777
|
+
// ---- chat and console parity (docs/plans/toolroll-cards.md) ------------------------------------------------------------
|
|
1778
|
+
const LATE_APPROVALS = new Set(["task_reopen", "revision_accept", "revision_reject", "flow_unattended"]);
|
|
1779
|
+
const PARITY_ACTIONS = new Set(["mode_revoke", "flow_unattended_off", "flow_archive", "task_archive", "task_restore", "incident_resolve", "memory_decide", "finding_send", "finding_resend", ...LATE_APPROVALS]);
|
|
1780
|
+
const OWNER_PARITY = new Set(["provider_resume", "worker_note", "lead_identity"]);
|
|
1781
|
+
function incidentRepoOf(store, id) {
|
|
1782
|
+
const open = store.openIncidents().find(one => one.id === id);
|
|
1783
|
+
return open === undefined ? null : store.lookupRef(open.taskId)?.repo ?? null;
|
|
1784
|
+
}
|
|
1785
|
+
function instanceAction(store, who, operation, input) {
|
|
1786
|
+
if (operation === "provider_resume") {
|
|
1787
|
+
const provider = text(input, "provider", 40);
|
|
1788
|
+
if (!isProviderId(provider))
|
|
1789
|
+
throw Error("Choose an agent provider.");
|
|
1790
|
+
const pause = authPauseOf(store, provider);
|
|
1791
|
+
if (pause === null)
|
|
1792
|
+
throw Error("That agent isn't paused.");
|
|
1793
|
+
// The pause is the whole installation's: every project with work waiting on it is named, and must be in reach here.
|
|
1794
|
+
const repos = [...new Set(store.handle.prepare(`SELECT t.repo AS repo FROM run r JOIN task_ref t ON t.id = r.task_ref WHERE r.provider = ? AND r.reason = 'auth-expired' AND r.finished_at >= ?
|
|
1795
|
+
UNION SELECT repo FROM task_ref WHERE auth_wait_pause = ?`).all(provider, pause.openedAt, pause.id).flatMap(row => row["repo"] === null ? [] : [String(row["repo"])]))].sort();
|
|
1796
|
+
if (repos.some(one => !who.repos.includes(one)))
|
|
1797
|
+
throw Error("That agent has work waiting in a project outside your access here.");
|
|
1798
|
+
return { request: { provider }, title: `Resume ${provider}`, state: { pause: pause.id, repos }, terms: [`Lifts the sign-in pause on ${provider} and sends its waiting tasks back to the queue. Existing approvals and holds still apply.`, ...(repos.length === 0 ? [] : [`Waiting work in: ${repos.map(one => basename(one)).join(", ")}`])] };
|
|
1799
|
+
}
|
|
1800
|
+
if (operation === "worker_note") {
|
|
1801
|
+
const runner = text(input, "runner", 120), note = String(input["note"] ?? "").trim();
|
|
1802
|
+
if (note.length > 200 || hasForbiddenControls(note))
|
|
1803
|
+
throw Error("A worker's note is at most 200 plain characters.");
|
|
1804
|
+
const worker = store.listRunners().find(one => one.name === runner && one.retiredAt === null);
|
|
1805
|
+
if (worker === undefined)
|
|
1806
|
+
throw Error("No such worker.");
|
|
1807
|
+
return { request: { runner, note }, title: `Note on ${runner}`, state: { runner }, terms: [note === "" ? "Clears this worker's note." : `Sets this worker's note to: ${note}`] };
|
|
1808
|
+
}
|
|
1809
|
+
const checked = checkLeadIdentity(String(input["name"] ?? ""), String(input["persona"] ?? ""));
|
|
1810
|
+
if (!checked.ok)
|
|
1811
|
+
throw Error(checked.message);
|
|
1812
|
+
return { request: { name: checked.identity.name, persona: checked.identity.persona }, title: `Call your lead ${checked.identity.name}`, state: {}, terms: [`Name: ${checked.identity.name}`, `Persona: ${checked.identity.persona}`] };
|
|
1813
|
+
}
|
|
1814
|
+
const REOPEN_REFUSED = {
|
|
1815
|
+
"unknown-task": "This task is not external work.", "not-latched": "The tracker never closed this: there is nothing to reopen.",
|
|
1816
|
+
"not-seen-open": "The tracker hasn't been seen open again since the close. Reopen it there first; the next sync notices.",
|
|
1817
|
+
claimed: "This task is being built right now.", held: "A hold stands: lift it first.", "question-open": "An unanswered question stands: answer or close it first.",
|
|
1818
|
+
"incident-open": "An unresolved incident stands: resolve it first.", "bad-state": "This task is not in a state reopen can take.",
|
|
1819
|
+
};
|
|
1820
|
+
/** The one plan revision waiting on a decision for this task, by its id or as the only one. */
|
|
1821
|
+
function waitingRevision(store, task, asked) {
|
|
1822
|
+
const ref = store.lookupRef(task);
|
|
1823
|
+
const waiting = ref === null ? [] : store.listPlanRevisions(ref.id).filter(one => one.status === "blocked");
|
|
1824
|
+
const found = asked === undefined ? (waiting.length === 1 ? waiting[0] : null) : waiting.find(one => one.id === asked) ?? null;
|
|
1825
|
+
if (found === null)
|
|
1826
|
+
throw Error("That plan revision isn't waiting on a decision.");
|
|
1827
|
+
return found;
|
|
1828
|
+
}
|
|
1829
|
+
/** A finding follows its task's family, so every project the family reaches must be in reach (findings.ts, one gate). */
|
|
1830
|
+
function familyWithin(store, who, task) {
|
|
1831
|
+
if (familyInReach(store, who, task) === null)
|
|
1832
|
+
throw Error("This task's work reaches a project outside your access here.");
|
|
1833
|
+
}
|
|
1834
|
+
function parityAction(store, who, operation, input, repo, flow, now, request, root) {
|
|
1835
|
+
switch (operation) {
|
|
1836
|
+
case "finding_send": {
|
|
1837
|
+
const task = String(input["task"]), row = store.getTask(task);
|
|
1838
|
+
if (row === null)
|
|
1839
|
+
throw Error("No such task.");
|
|
1840
|
+
familyWithin(store, who, task);
|
|
1841
|
+
const given = ["summary", "pointer", "run"].filter(key => input[key] !== undefined);
|
|
1842
|
+
if (given.length !== 1)
|
|
1843
|
+
throw Error("Send one thing: words in summary, a review finding's pointer, or a failed check's run.");
|
|
1844
|
+
const head = `Sends this to the agent on ${row.title}. It reaches a running build now, or the next attempt's brief, as data to check. The agent must accept it or reject it with a reason; a repeat counts as another round.`;
|
|
1845
|
+
if (input["pointer"] !== undefined) {
|
|
1846
|
+
const read = reviewFindingAt(store, task, text(input, "pointer", 40));
|
|
1847
|
+
if (!read.ok)
|
|
1848
|
+
throw Error(read.message);
|
|
1849
|
+
const place = read.finding.file === null ? [] : [`Where: ${read.finding.file}${read.finding.line === null ? "" : `:${read.finding.line}`}`];
|
|
1850
|
+
return { state: { task, pointer: input["pointer"], finding: read.finding }, title: "Send a review finding", terms: [head, `Finding: ${cleanFindingText(read.finding.scenario, FINDING_TEXT_LIMIT)}`, ...place, `Severity: ${read.finding.severity}`, `Asks: ${REVIEW_ACTION}`] };
|
|
1851
|
+
}
|
|
1852
|
+
if (input["run"] !== undefined) {
|
|
1853
|
+
if (root === undefined)
|
|
1854
|
+
throw Error("The check's log can't be read here.");
|
|
1855
|
+
const read = checkFindingAt(store, root, task, integer(input, "run"));
|
|
1856
|
+
if (!read.ok)
|
|
1857
|
+
throw Error(read.message);
|
|
1858
|
+
return { state: { task, run: input["run"], excerpt: read.excerpt, head: read.head }, title: "Send a failed check", terms: [head, `Finding: ${read.summary}`, `Log: ${cleanFindingText(read.excerpt, FINDING_EXCERPT_LIMIT)}`, `Asks: ${CHECK_ACTION}`] };
|
|
1859
|
+
}
|
|
1860
|
+
const summary = cleanFindingText(text(input, "summary", FINDING_TEXT_LIMIT), FINDING_TEXT_LIMIT);
|
|
1861
|
+
if (summary === "")
|
|
1862
|
+
throw Error("Say what the finding is.");
|
|
1863
|
+
const file = input["file"] === undefined ? null : cleanFindingText(text(input, "file", 300), 300) || null;
|
|
1864
|
+
const line = file === null || input["line"] === undefined ? null : integer(input, "line");
|
|
1865
|
+
for (const key of ["file", "line"])
|
|
1866
|
+
delete request[key];
|
|
1867
|
+
request["summary"] = summary;
|
|
1868
|
+
if (file !== null)
|
|
1869
|
+
request["file"] = file;
|
|
1870
|
+
if (line !== null)
|
|
1871
|
+
request["line"] = line;
|
|
1872
|
+
return { state: { task, updatedAt: row.updatedAt }, title: "Send a finding", terms: [head, `Finding: ${summary}`, ...(file === null ? [] : [`Where: ${file}${line === null ? "" : `:${line}`}`]), `Asks: ${REVIEW_ACTION}`] };
|
|
1873
|
+
}
|
|
1874
|
+
case "finding_resend": {
|
|
1875
|
+
const finding = getFinding(store, integer(input, "finding"));
|
|
1876
|
+
if (finding === null)
|
|
1877
|
+
throw Error("No such finding.");
|
|
1878
|
+
familyWithin(store, who, finding.taskId);
|
|
1879
|
+
if (finding.state === "open")
|
|
1880
|
+
throw Error("That finding is already with the agent.");
|
|
1881
|
+
if (finding.state === "closed")
|
|
1882
|
+
throw Error("That finding is closed.");
|
|
1883
|
+
if (finding.state === "with-person")
|
|
1884
|
+
throw Error("That finding has used its rounds: it needs your decision on the task.");
|
|
1885
|
+
const next = finding.rounds >= FINDING_ROUND_LIMIT ? "Its rounds are used, so this hands it to a person on the task." : `This is round ${finding.rounds + 1} of ${FINDING_ROUND_LIMIT}.`;
|
|
1886
|
+
return { state: { finding: finding.id, state: finding.state, rounds: finding.rounds, answer: finding.answer?.detail ?? null }, title: `Send finding #${finding.id} back`, terms: [`Finding: ${findingLine(finding)}`, `How it stands: ${findingStandingWords(finding)}`, next] };
|
|
1887
|
+
}
|
|
1888
|
+
case "task_reopen": {
|
|
1889
|
+
const task = String(input["task"]), row = store.getTask(task);
|
|
1890
|
+
if (row === null)
|
|
1891
|
+
throw Error("No such task.");
|
|
1892
|
+
if (store.mirrorByTask(task) === null)
|
|
1893
|
+
throw Error(REOPEN_REFUSED["unknown-task"]);
|
|
1894
|
+
return { state: { task, updatedAt: row.updatedAt, taskState: row.state }, title: `Reopen ${row.title}`, terms: ["Takes this outside work back up: the tracker shows it open again, and Toolroll may build it. Existing approvals and holds still apply."] };
|
|
1895
|
+
}
|
|
1896
|
+
case "revision_accept":
|
|
1897
|
+
case "revision_reject": {
|
|
1898
|
+
const task = String(input["task"]), revision = waitingRevision(store, task, input["revision"]);
|
|
1899
|
+
request["revision"] = revision.id;
|
|
1900
|
+
const artifact = store.getArtifact(revision.artifact);
|
|
1901
|
+
if (artifact === null || root === undefined)
|
|
1902
|
+
throw Error("The revised plan can't be read here.");
|
|
1903
|
+
const read = readVerifiedArtifact(root, artifact);
|
|
1904
|
+
if (!read.ok || artifact.truncated || artifact.redacted)
|
|
1905
|
+
throw Error("The revised plan couldn't be verified in full.");
|
|
1906
|
+
return { state: { task, revision: revision.id, artifact: artifact.sha256, authority: revision.authorityDigest }, title: `${operation === "revision_accept" ? "Accept" : "Reject"} revision ${revision.revision} of this plan`,
|
|
1907
|
+
terms: [`Why the builder asked: ${revision.reason}`, `Changes: ${revision.changedFields.join(", ") || "the plan only"}${revision.authorityKind === "authority-change" ? ". This changes what was approved." : "."}`, `Plan:\n${read.content.toString("utf8")}`] };
|
|
1908
|
+
}
|
|
1909
|
+
case "flow_unattended": {
|
|
1910
|
+
const asked = parseUnattendedAsk({ days: input["days"], budget: input["budget"], runs: input["runs"], include: input["include"] });
|
|
1911
|
+
if (!asked.ok)
|
|
1912
|
+
throw Error(asked.message);
|
|
1913
|
+
const problem = unattendedSignProblem(store, flow, who.name);
|
|
1914
|
+
if (problem !== null)
|
|
1915
|
+
throw Error(problem);
|
|
1916
|
+
const candidates = unattendedCandidates(store, flow.id), include = asked.ask.include ?? [];
|
|
1917
|
+
const covered = candidates.filter(one => one.optional === null || include.includes(one.id));
|
|
1918
|
+
if (include.some(id => !candidates.some(one => one.id === id)))
|
|
1919
|
+
throw Error("Name schedules from this flow's own list.");
|
|
1920
|
+
if (covered.length === 0)
|
|
1921
|
+
throw Error("Choose a schedule to include: this flow's schedules bring in outside content, so none is covered unless you pick it.");
|
|
1922
|
+
// Every project its work files in: the person must reach each, and each is named and checked at the yes.
|
|
1923
|
+
const repos = [...new Set([flow.repo, ...workReposOf(flow)])];
|
|
1924
|
+
if (repos.some(one => !who.repos.includes(one) || !store.accountCanAccess(who.name, one)))
|
|
1925
|
+
throw Error("This flow files work in a project outside your access here.");
|
|
1926
|
+
return { state: { flow: flow.id, revision: flow.revision, schedules: candidatesDigestOf(candidates), ask: asked.ask, repos }, title: `Let ${flow.name} run on its own`,
|
|
1927
|
+
terms: [...unattendedHeadWords(who.name, FLOW_ACCEPTANCE), `Works in: ${repos.map(one => basename(one)).join(", ")}`, unattendedCoverWords(covered), ...covered.flatMap(one => one.optional === null ? [] : [`${one.words}: ${one.optional}`]), ...unattendedLimitWords(asked.ask.budgetMicrousd, asked.ask.dailyRunCap), ...UNATTENDED_RULE_WORDS, `It ends after ${asked.ask.days} ${asked.ask.days === 1 ? "day" : "days"}.`] };
|
|
1928
|
+
}
|
|
1929
|
+
case "mode_revoke": {
|
|
1930
|
+
const mode = store.activeMode(repo, now);
|
|
1931
|
+
if (mode === null)
|
|
1932
|
+
throw Error("No automatic approvals are on for this project.");
|
|
1933
|
+
return { state: { mode: mode.id, digest: mode.digest }, title: "End automatic approvals", terms: [`Ends the ${mode.name} mode signed by ${mode.signedBy}. Every act goes back to its own approval.`] };
|
|
1934
|
+
}
|
|
1935
|
+
case "flow_unattended_off": {
|
|
1936
|
+
const grant = activeUnattendedGrant(store, flow.id, now);
|
|
1937
|
+
if (grant === null)
|
|
1938
|
+
throw Error(`${flow.name} isn't running on its own.`);
|
|
1939
|
+
return { state: { flow: flow.id, grant: grant.id }, title: `Stop ${flow.name} running on its own`, terms: ["New cards wait for a person again."] };
|
|
1940
|
+
}
|
|
1941
|
+
case "flow_archive":
|
|
1942
|
+
return { state: { flow: flow.id, revision: flow.revision }, title: `Archive ${flow.name}`, terms: ["Takes this flow off the list. Its triggers stop starting cards."] };
|
|
1943
|
+
case "task_archive": {
|
|
1944
|
+
const task = String(input["task"]), row = store.getTask(task), reason = text(input, "reason", 500, true).trim();
|
|
1945
|
+
if (row === null)
|
|
1946
|
+
throw Error("No such task.");
|
|
1947
|
+
if (reason === "")
|
|
1948
|
+
delete request["reason"];
|
|
1949
|
+
else
|
|
1950
|
+
request["reason"] = reason;
|
|
1951
|
+
return { state: { task, updatedAt: row.updatedAt, state: row.state }, title: `Put away ${row.title}`, terms: ["Takes this task out of every list. It stays saved and can be restored.", ...(reason === "" ? [] : [`Reason: ${reason}`])] };
|
|
1952
|
+
}
|
|
1953
|
+
case "task_restore": {
|
|
1954
|
+
const task = String(input["task"]), row = store.getTask(task);
|
|
1955
|
+
if (row === null)
|
|
1956
|
+
throw Error("No such task.");
|
|
1957
|
+
return { state: { task, updatedAt: row.updatedAt }, title: `Restore ${row.title}`, terms: ["Puts this task back in the lists."] };
|
|
1958
|
+
}
|
|
1959
|
+
case "incident_resolve": {
|
|
1960
|
+
const id = integer(input, "incident"), open = store.openIncidents().find(one => one.id === id);
|
|
1961
|
+
return { state: { incident: id }, title: `Resolve the incident on ${open.taskId}`, terms: ["Marks this incident resolved. Nothing else about the task changes."] };
|
|
1962
|
+
}
|
|
1963
|
+
case "memory_decide": {
|
|
1964
|
+
const id = integer(input, "proposal"), decision = input["decision"] === "accept" ? "accept" : "reject";
|
|
1965
|
+
const proposal = listProposals(store, repo).find(one => one.id === id);
|
|
1966
|
+
if (proposal === undefined)
|
|
1967
|
+
throw Error("That proposal isn't waiting any more.");
|
|
1968
|
+
// A lesson note is accepted in the person's own words (memory-pass.ts): never a model's, so never from a card.
|
|
1969
|
+
if (decision === "accept" && lessonNoteOf(store, id) !== null)
|
|
1970
|
+
throw Error("A lesson note is accepted in your own words: write it on the Knowledge page.");
|
|
1971
|
+
return { state: { proposal: id }, title: `${decision === "accept" ? "Accept" : "Reject"} a memory proposal`, terms: [proposal.title, ...(proposal.beforeText === null ? [] : [`Was: ${proposal.beforeText}`]), `Becomes: ${proposal.afterText}`, `Why: ${proposal.rationale}`, decision === "accept" ? "Later agent sessions in this project read this." : "Nothing is added to project memory."] };
|
|
1972
|
+
}
|
|
1973
|
+
default:
|
|
1974
|
+
throw Error("Choose an available action.");
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
/** Where a yes came from, in the words the other chat approvals' ledger lines use. */
|
|
1978
|
+
const approvalRoad = (options) => `via ${options.via}${options.binding === undefined ? "" : ` · chat binding #${options.binding}`}`;
|
|
1979
|
+
/** The approvals added last: the console's own write, after the same question every approval asks. */
|
|
1980
|
+
function runLateApproval(store, who, payload, byFor, road, now) {
|
|
1981
|
+
const req = payload.request, actor = who.name, by = byFor(payload.repo);
|
|
1982
|
+
// Rejecting lowers: it takes a person's yes, not their approving setting.
|
|
1983
|
+
if (by.ok === false && payload.operation !== "revision_reject")
|
|
1984
|
+
throw Error(by.why);
|
|
1985
|
+
if (payload.operation === "task_reopen") {
|
|
1986
|
+
const task = String(req["task"]), reopened = store.reopenMirror(task, actor, now);
|
|
1987
|
+
if (!reopened.ok)
|
|
1988
|
+
throw Error(REOPEN_REFUSED[reopened.reason] ?? "The task could not be reopened.");
|
|
1989
|
+
return { ok: true, taskId: task, said: "Reopened." };
|
|
1990
|
+
}
|
|
1991
|
+
if (payload.operation === "flow_unattended") {
|
|
1992
|
+
const ask = payload.state["ask"];
|
|
1993
|
+
// The person's own setting holds in every project this flow's work files in, not only its own.
|
|
1994
|
+
for (const repo of payload.state["repos"]) {
|
|
1995
|
+
const here = byFor(repo);
|
|
1996
|
+
if (here.ok === false)
|
|
1997
|
+
throw Error(here.why);
|
|
1998
|
+
if (here.limits.capMicrousd !== null && (ask.budgetMicrousd === null || ask.budgetMicrousd > here.limits.capMicrousd))
|
|
1999
|
+
throw Error("This has a higher attempt limit than your chat approval setting allows.");
|
|
2000
|
+
}
|
|
2001
|
+
const signed = sealUnattended(store, { flow: Number(req["flow"]), signer: actor, ask, sawRevision: Number(payload.state["revision"]), sawSchedules: String(payload.state["schedules"]), road }, now);
|
|
2002
|
+
if (!signed.ok)
|
|
2003
|
+
throw Error(signed.message);
|
|
2004
|
+
return { ok: true, taskId: null, said: `Runs on its own until ${untilWords(signed.grant.absoluteExpiry)}.`, href: `/flows/${Number(req["flow"])}` };
|
|
2005
|
+
}
|
|
2006
|
+
const task = String(req["task"]), revision = waitingRevision(store, task, req["revision"]), accept = payload.operation === "revision_accept";
|
|
2007
|
+
// A saved limit can't be read against a change of authority: that one opens in Toolroll.
|
|
2008
|
+
if (accept && by.ok && revision.authorityKind === "authority-change" && (!by.limits.fullAccess || by.limits.capMicrousd !== null))
|
|
2009
|
+
throw Error("This revision changes what was approved, beyond your chat approval limits. Open it in Toolroll.");
|
|
2010
|
+
if (!store.resolvePlanRevision(revision.id, accept ? "applied" : "rejected", actor, now))
|
|
2011
|
+
throw Error("That plan revision is no longer waiting on a decision.");
|
|
2012
|
+
store.releaseOwnedHold("revision", String(revision.id));
|
|
2013
|
+
return { ok: true, taskId: task, said: accept ? "Revision accepted." : "Revision rejected." };
|
|
2014
|
+
}
|
|
2015
|
+
/** Each runs the write the console's own handler calls, as this person, inside the confirm door's transaction. */
|
|
2016
|
+
function runParityAction(store, who, payload, now, root) {
|
|
2017
|
+
const req = payload.request, actor = who.name, repo = payload.repo;
|
|
2018
|
+
const done = (said, href, taskId = null) => ({ ok: true, taskId, said, ...(href === undefined ? {} : { href }) });
|
|
2019
|
+
switch (payload.operation) {
|
|
2020
|
+
case "finding_send":
|
|
2021
|
+
case "finding_resend": {
|
|
2022
|
+
const task = payload.operation === "finding_send" ? String(req["task"]) : getFinding(store, Number(req["finding"]))?.taskId ?? null;
|
|
2023
|
+
const sent = payload.operation === "finding_resend" ? resendFinding(store, Number(req["finding"]), now)
|
|
2024
|
+
: req["pointer"] !== undefined ? sendReviewFinding(store, task, String(req["pointer"]), actor, now)
|
|
2025
|
+
: req["run"] !== undefined ? sendCheckFinding(store, root ?? "", task, Number(req["run"]), actor, now)
|
|
2026
|
+
// The lead drafted these words and the person confirmed them: recorded as the lead's, sent by the person.
|
|
2027
|
+
: sendFinding(store, { taskId: task, source: "lead", by: actor, summary: String(req["summary"]), file: typeof req["file"] === "string" ? req["file"] : null, line: typeof req["line"] === "number" ? req["line"] : null }, now);
|
|
2028
|
+
if (!sent.ok)
|
|
2029
|
+
throw Error(sent.message);
|
|
2030
|
+
const said = sent.standing === "with-person" ? `Finding #${sent.id} needs a person's decision on the task.` : sent.standing === "already-sent" ? `Finding #${sent.id} was already sent.`
|
|
2031
|
+
: sent.standing === "running" ? `Finding #${sent.id} sent to the running build.` : `Finding #${sent.id} goes to the next attempt.`;
|
|
2032
|
+
return done(said, task === null ? undefined : `/t/${encodeURIComponent(task)}#findings`, task);
|
|
2033
|
+
}
|
|
2034
|
+
case "mode_revoke":
|
|
2035
|
+
if (!store.revokeMode(repo, actor, "operator", now))
|
|
2036
|
+
throw Error("That mode already ended.");
|
|
2037
|
+
return done("Automatic approvals are off. Every act goes back to its own approval.", "/mode");
|
|
2038
|
+
case "flow_unattended_off":
|
|
2039
|
+
if (!revokeUnattended(store, Number(req["flow"]), actor, now))
|
|
2040
|
+
throw Error("It wasn't running on its own.");
|
|
2041
|
+
return done("It no longer runs on its own. New cards wait for a person.", `/flows/${Number(req["flow"])}`);
|
|
2042
|
+
case "flow_archive":
|
|
2043
|
+
store.archiveFlow(Number(req["flow"]), actor, now);
|
|
2044
|
+
return done("Flow archived.", "/flows");
|
|
2045
|
+
case "task_archive":
|
|
2046
|
+
case "task_restore": {
|
|
2047
|
+
const task = String(req["task"]);
|
|
2048
|
+
const standing = (refs) => approverStandsNow(store, actor, familyRepoOf(store, refs));
|
|
2049
|
+
const made = payload.operation === "task_archive"
|
|
2050
|
+
? archiveTaskFamily(store, task, { by: actor, byKind: "person", rule: null, reason: String(req["reason"] ?? "").trim() || "Put away from chat" }, now, { guard: standing })
|
|
2051
|
+
: restoreTaskFamily(store, task, actor, now, standing);
|
|
2052
|
+
if (!made.ok)
|
|
2053
|
+
throw Error(made.message);
|
|
2054
|
+
return done(payload.operation === "task_archive" ? "Put away." : "Restored.", undefined, task);
|
|
2055
|
+
}
|
|
2056
|
+
case "incident_resolve":
|
|
2057
|
+
if (!store.resolveIncident(Number(req["incident"]), actor, now))
|
|
2058
|
+
throw Error("That incident was already resolved.");
|
|
2059
|
+
return done("Incident resolved.");
|
|
2060
|
+
case "memory_decide": {
|
|
2061
|
+
const decision = req["decision"] === "accept" ? "accept" : "reject";
|
|
2062
|
+
decideProposal(store, { repo, actor, id: Number(req["proposal"]), decision }, now);
|
|
2063
|
+
return done(decision === "accept" ? "Added to project memory." : "Rejected.", `/settings/knowledge?repo=${encodeURIComponent(repo)}`);
|
|
2064
|
+
}
|
|
2065
|
+
case "provider_resume": {
|
|
2066
|
+
const lifted = liftAuthPause(store, String(req["provider"]), "person", actor, now);
|
|
2067
|
+
if (lifted === null)
|
|
2068
|
+
throw Error("That agent isn't paused any more.");
|
|
2069
|
+
return done(`Resumed. ${lifted.resumed} ${lifted.resumed === 1 ? "task went" : "tasks went"} back to the queue.`);
|
|
2070
|
+
}
|
|
2071
|
+
case "worker_note": {
|
|
2072
|
+
const note = String(req["note"]);
|
|
2073
|
+
if (!store.setRunnerQueueNote(String(req["runner"]), note === "" ? null : note).ok)
|
|
2074
|
+
throw Error("No such worker.");
|
|
2075
|
+
return done("Note saved.", "/fleet");
|
|
2076
|
+
}
|
|
2077
|
+
case "lead_identity":
|
|
2078
|
+
store.setLeadConfig(actor, String(req["name"]), String(req["persona"]), now);
|
|
2079
|
+
return done(`Your lead is called ${String(req["name"])} now.`, "/settings/lead");
|
|
2080
|
+
default:
|
|
2081
|
+
throw Error("Choose an available action.");
|
|
2082
|
+
}
|
|
2083
|
+
}
|