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/work-summary.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* State, decisions, approval, process custody and proof still belong to
|
|
3
3
|
* their existing owners. Action hints are navigation/proposal guidance,
|
|
4
4
|
* never credentials or permission to bypass an owning operation. */
|
|
5
|
+
import { heldFindingCount } from "./findings.js";
|
|
6
|
+
import { machineChecked } from "./machine-check.js";
|
|
5
7
|
import { resultCheckLevel } from "./result-follow-ups.js";
|
|
6
8
|
import { diagnoseTaskDispatch } from "./dispatch.js";
|
|
7
9
|
import { isAlive } from "./runner.js";
|
|
@@ -27,7 +29,7 @@ export function workDecisionAction(taskId, decision, principal) {
|
|
|
27
29
|
access: principal === "operator" ? "operator-control" : "proposal-only", retry: "refresh-before-acting" };
|
|
28
30
|
}
|
|
29
31
|
const PROPOSABLE = new Set(["answer-decision", "write-scope", "unhold"]);
|
|
30
|
-
const READ_ACTIONS = new Set(["open-result", "open-pr", "inspect-task", "inspect-run", "inspect-stop", "inspect-decisions"]);
|
|
32
|
+
const READ_ACTIONS = new Set(["open-result", "open-pr", "inspect-task", "inspect-findings", "inspect-run", "inspect-stop", "inspect-decisions"]);
|
|
31
33
|
/** Pure counterpart for callers already holding the same facts. */
|
|
32
34
|
export function workSummaryOf(facts, principal, resultDisplay) {
|
|
33
35
|
const status = workStatusOf(facts, resultDisplay);
|
|
@@ -47,6 +49,8 @@ export function workSummaryOf(facts, principal, resultDisplay) {
|
|
|
47
49
|
primary = make("resume-run", status.action.label, { runId: facts.control.run });
|
|
48
50
|
else if (status.token === "waiting-decision" && questionAction !== null)
|
|
49
51
|
primary = questionAction;
|
|
52
|
+
else if (status.token === "finding-held")
|
|
53
|
+
primary = make("inspect-findings", status.action.label);
|
|
50
54
|
else if (facts.dispatch?.code === "vanished-run")
|
|
51
55
|
primary = make("reconcile-run", "Check the unfinished attempt", { runId: facts.unfinishedRunId ?? null });
|
|
52
56
|
else if (status.action.kind === "open-result" || status.action.kind === "open-review")
|
|
@@ -94,9 +98,10 @@ export function taskWorkSummaryOf(store, taskId, now, access) {
|
|
|
94
98
|
const publication = result === null ? null : store.publicationForRun(result.id);
|
|
95
99
|
const facts = {
|
|
96
100
|
id: task.id, title: task.title, repo: ref.repo, state: task.state, updatedAt: task.updatedAt,
|
|
101
|
+
...(machineChecked(store, task.id) ? { machine: true } : {}),
|
|
97
102
|
dispatch: diagnoseTaskDispatch(store, taskId, now, access.dependencyRepos ?? null), liveRunId: live?.id ?? null,
|
|
98
103
|
unfinishedRunId: runs.find(one => one.outcome === null && one.role !== "reviewer")?.id ?? null,
|
|
99
|
-
control: taskControlOf(store, ref.id, now), openDecision: openWorkDecisionOf(store, ref.id, now),
|
|
104
|
+
control: taskControlOf(store, ref.id, now), openDecision: openWorkDecisionOf(store, ref.id, now), heldFindings: heldFindingCount(store, taskId),
|
|
100
105
|
result: result === null ? null : {
|
|
101
106
|
runId: result.id, role: result.role, outcome: result.outcome,
|
|
102
107
|
verdict: proof?.verdict ?? null, reasons: proof?.reasons ?? [], accepted: store.proofAcceptance(result.id) !== null,
|
package/dist/workspace-ui.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { type ChecksFact } from "./task-status.js";
|
|
|
26
26
|
import { type Html } from "./html.js";
|
|
27
27
|
/** The Work destination's views — shortcuts over the same rows, never a
|
|
28
28
|
* persisted state. All is the default. */
|
|
29
|
-
export type WorkView = "all" | "needs-you" | "running" | "queued" | "completed" | "archived";
|
|
29
|
+
export type WorkView = "all" | "needs-you" | "ready" | "running" | "queued" | "completed" | "archived";
|
|
30
30
|
export declare const WORK_VIEWS: readonly {
|
|
31
31
|
key: WorkView;
|
|
32
32
|
label: string;
|
|
@@ -106,7 +106,7 @@ export type NextAction = {
|
|
|
106
106
|
label: string;
|
|
107
107
|
/** Where the act lives — the page, not a URL, so every surface can
|
|
108
108
|
* build its own link (a task page anchors, a row links out). */
|
|
109
|
-
kind: "open-result" | "open-review" | "open-task" | "open-run" | "open-pr";
|
|
109
|
+
kind: "open-result" | "open-review" | "open-task" | "open-findings" | "open-run" | "open-pr";
|
|
110
110
|
};
|
|
111
111
|
export type DisplayStatus = {
|
|
112
112
|
/** The stable `data-work-status` token pages and tests key off. */
|
|
@@ -154,6 +154,8 @@ export declare function receiptPublicationWords(publication: PublicationFacts):
|
|
|
154
154
|
* already reads elsewhere. */
|
|
155
155
|
export type WorkFacts = {
|
|
156
156
|
id: string;
|
|
157
|
+
/** The machine runs this task's check on a prepared candidate; no agent runs (machine-check.ts). */
|
|
158
|
+
machine?: boolean;
|
|
157
159
|
title: string;
|
|
158
160
|
repo: string | null;
|
|
159
161
|
state: TaskState;
|
|
@@ -173,6 +175,8 @@ export type WorkFacts = {
|
|
|
173
175
|
question: string;
|
|
174
176
|
overdue: boolean;
|
|
175
177
|
} | null;
|
|
178
|
+
/** Findings of this task's family that used their rounds and wait on a person (findings.ts). */
|
|
179
|
+
heldFindings?: number;
|
|
176
180
|
};
|
|
177
181
|
export type WorkStatus = DisplayStatus & {
|
|
178
182
|
/** Which shortcut views list this row; `all` always does. */
|
|
@@ -233,7 +237,7 @@ export declare function lastErrorLineOf(log: string): {
|
|
|
233
237
|
text: string;
|
|
234
238
|
} | null;
|
|
235
239
|
/** The result page's own sentence when the decision is on it. */
|
|
236
|
-
export declare const RESULT_DECISION_SENTENCE = "Review the change, then accept it or
|
|
240
|
+
export declare const RESULT_DECISION_SENTENCE = "Review the change, then accept it or send it back.";
|
|
237
241
|
/** What the result page's Accept says, from facts it already shows: the
|
|
238
242
|
* recorded checks, the requirements not met, whether the saved proof reads,
|
|
239
243
|
* and what the button posts. "Accept and finish" only when everything is met
|
package/dist/workspace-ui.js
CHANGED
|
@@ -16,22 +16,25 @@
|
|
|
16
16
|
* observed" name observed publication records, and deployment is never
|
|
17
17
|
* claimed without one.
|
|
18
18
|
*/
|
|
19
|
+
import { lastIncludedWords } from './memory-use.js';
|
|
19
20
|
import { ACCEPT_NEEDS_REASON, FAILED_CHECK, cantAcceptYetOf, evidenceProblemOf } from "./result-acts.js";
|
|
20
21
|
export { ACCEPT_NEEDS_REASON, cantAcceptYetOf, evidenceProblemOf };
|
|
21
22
|
import { manualReviewOnly, plainReasonWords } from "./proof.js";
|
|
22
23
|
import { CHECK_COMMAND_CHANGED, CHECK_UNREADABLE, OPEN_RESULT, approvedCheckOf, approvedCheckStateOf, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
23
24
|
import { whenUtc } from "./when-html.js";
|
|
25
|
+
import { NEEDS } from "./needs-you.js";
|
|
24
26
|
import { html, postForm } from "./html.js";
|
|
25
27
|
export const WORK_VIEWS = [
|
|
26
28
|
{ key: "all", label: "All", hint: "Every task in view, most urgent first.", empty: "Nothing is in progress. Describe work in chat or add a task, and it appears here." },
|
|
27
29
|
{ key: "needs-you", label: "Needs you", hint: "Tasks waiting on an answer, approval, or your inspection.", empty: "Nothing needs you right now. Queued and running work continues on its own." },
|
|
30
|
+
{ key: "ready", label: "Ready", hint: "Results waiting for you to accept or send back.", empty: "Nothing is ready for review." },
|
|
28
31
|
{ key: "running", label: "Running", hint: "Attempts a builder owns right now.", empty: "Nothing is running right now. Approved tasks start when a builder with capacity is connected." },
|
|
29
32
|
{ key: "queued", label: "Queued", hint: "Tasks that start on their own: waiting for a builder, a dependency or a retry.", empty: "Nothing is queued." },
|
|
30
|
-
{ key: "completed", label: "Finished", hint: "Tasks the lead or
|
|
31
|
-
{ key: "archived", label: "Archived", hint: "Tasks
|
|
33
|
+
{ key: "completed", label: "Finished", hint: "Tasks the lead or you accepted. Recorded check results stay available.", empty: "Nothing has finished in this view yet." },
|
|
34
|
+
{ key: "archived", label: "Archived", hint: "Tasks archived by you or automatically. Their history and results are kept.", empty: "Nothing is archived." },
|
|
32
35
|
];
|
|
33
36
|
export function parseWorkView(raw) {
|
|
34
|
-
return raw === "needs-you" || raw === "running" || raw === "queued" || raw === "completed" || raw === "archived" ? raw : "all";
|
|
37
|
+
return raw === "needs-you" || raw === "ready" || raw === "running" || raw === "queued" || raw === "completed" || raw === "archived" ? raw : "all";
|
|
35
38
|
}
|
|
36
39
|
/** The review facts from the store's bounded retry projection — the same
|
|
37
40
|
* reading `diagnoseTaskDispatch` makes, so every surface starts from one
|
|
@@ -141,8 +144,8 @@ function machineVerdictWords(result, check) {
|
|
|
141
144
|
return `the approved check failed against it${exit === null ? "" : ` (exit ${exit})`}`;
|
|
142
145
|
}
|
|
143
146
|
if (problem === "mismatched")
|
|
144
|
-
return "its
|
|
145
|
-
return "its required
|
|
147
|
+
return "its saved files didn't match the record";
|
|
148
|
+
return "its required files were missing";
|
|
146
149
|
}
|
|
147
150
|
/** The approved check passed (task-status.ts approvedCheckStateOf): its own pass, or checks Off covered by a passing
|
|
148
151
|
* release check. A re-run underway keeps the pass it had. */
|
|
@@ -180,7 +183,7 @@ function storedResultStatusOf(result, publication) {
|
|
|
180
183
|
// Acceptance records a person's decision; it neither proves nor
|
|
181
184
|
// disproves anything about the checks. The machine's own verdict is
|
|
182
185
|
// restated as recorded.
|
|
183
|
-
detail: withPublication(`An approver accepted this result by hand, and
|
|
186
|
+
detail: withPublication(`An approver accepted this result by hand, and their reason says why. The checks are unchanged: ${machineVerdictWords(result, check)}.`),
|
|
184
187
|
tone: "done",
|
|
185
188
|
action: { label: "Open the recorded exception", kind: "open-review" },
|
|
186
189
|
};
|
|
@@ -228,16 +231,16 @@ function storedResultStatusOf(result, publication) {
|
|
|
228
231
|
// at adjudication; only a verdict-less no-change reads its two
|
|
229
232
|
// presence facts here.
|
|
230
233
|
return result.recordComplete === false && result.verdict === null
|
|
231
|
-
? { token: "record-incomplete", label: "No-change result, record incomplete", detail: "The build concluded nothing needed to change, but its handoff or
|
|
232
|
-
: { token: "no-change", label: "No changes were needed", detail: withPublication("The build concluded nothing needed to change; its handoff and
|
|
234
|
+
? { token: "record-incomplete", label: "No-change result, record incomplete", detail: "The build concluded nothing needed to change, but its handoff or saved diff is missing.", tone: "problem", action: { label: "Open the record", kind: "open-run" } }
|
|
235
|
+
: { token: "no-change", label: "No changes were needed", detail: withPublication("The build concluded nothing needed to change; its handoff and saved diff are on record."), tone: "done", action: { label: OPEN_RESULT, kind: "open-result" } };
|
|
233
236
|
}
|
|
234
237
|
if (result.verdict === null) {
|
|
235
238
|
return {
|
|
236
239
|
token: "verification-needed",
|
|
237
|
-
label: "
|
|
238
|
-
detail: withPublication("No
|
|
240
|
+
label: "No check result for this build yet",
|
|
241
|
+
detail: withPublication("No check result for this build yet. Run checks on it."),
|
|
239
242
|
tone: "problem",
|
|
240
|
-
action: { label: "
|
|
243
|
+
action: { label: "Run checks", kind: "open-review" },
|
|
241
244
|
};
|
|
242
245
|
}
|
|
243
246
|
// Any verdict short of a pass the check confirms: never "passed" when the check can't be read now.
|
|
@@ -250,8 +253,8 @@ function storedResultStatusOf(result, publication) {
|
|
|
250
253
|
if (result.verdict === "attested" && !passedNow(check)) {
|
|
251
254
|
return {
|
|
252
255
|
token: "agent-attested",
|
|
253
|
-
label: "
|
|
254
|
-
detail: withPublication("No
|
|
256
|
+
label: "Ready · no project check set up",
|
|
257
|
+
detail: withPublication("No project check is set up, so the checks listed are the agent's own report."),
|
|
255
258
|
tone: "neutral",
|
|
256
259
|
action: { label: OPEN_RESULT, kind: "open-result" },
|
|
257
260
|
};
|
|
@@ -322,11 +325,14 @@ const DISPATCH_ACTION_LABELS = {
|
|
|
322
325
|
"inspect-hold": "Review hold",
|
|
323
326
|
"repair-dependency": "Review required task",
|
|
324
327
|
"repair-capability": "Review missing requirement",
|
|
325
|
-
"start-worker": "
|
|
328
|
+
"start-worker": "Start the builder",
|
|
326
329
|
"retry-review": "Review retry options",
|
|
327
330
|
"resume-run": "Review pause",
|
|
328
331
|
};
|
|
329
332
|
export function dispatchActionLabel(dispatch) {
|
|
333
|
+
// One label everywhere for the builder (needs-you.ts NEEDS): Connect a builder, or Start the builder.
|
|
334
|
+
if (dispatch?.action === "start-worker")
|
|
335
|
+
return dispatch.code === "no-worker-registered" ? NEEDS["connect-builder"].action.label : NEEDS["start-builder"].action.label;
|
|
330
336
|
return dispatch?.action == null ? "View task details" : DISPATCH_ACTION_LABELS[dispatch.action];
|
|
331
337
|
}
|
|
332
338
|
/** Result tokens a done task can wear, as the shared stage they mean. A
|
|
@@ -376,7 +382,7 @@ export function reportMismatchesOf(reasons, criteria, changes) {
|
|
|
376
382
|
return [{ text: plainReasonWords(reason), ...place(review[1]), note: null, reason }];
|
|
377
383
|
const unsigned = UNSIGNED_NOTE.exec(reason);
|
|
378
384
|
if (unsigned !== null)
|
|
379
|
-
return [{ text: `The report's note ${unsigned[1]} is about ${unsigned[2]}, which isn't one of the
|
|
385
|
+
return [{ text: `The report's note ${unsigned[1]} is about ${unsigned[2]}, which isn't one of the approved requirements: ${unsigned[3]}`, path: null, lines: null, inChanges: null, note: Number(unsigned[1]), reason }];
|
|
380
386
|
if (FAILED_CHECK.test(reason))
|
|
381
387
|
return [];
|
|
382
388
|
return [{ text: sentenceOf(plainReasonWords(reason)), path: null, lines: null, inChanges: null, note: note === null ? null : Number(note), reason }];
|
|
@@ -419,7 +425,7 @@ export function lastErrorLineOf(log) {
|
|
|
419
425
|
return (stderr >= 0 ? pick(stderr + 1, lines.length) : null) ?? pick(0, stderr >= 0 ? stderr : lines.length);
|
|
420
426
|
}
|
|
421
427
|
/** The result page's own sentence when the decision is on it. */
|
|
422
|
-
export const RESULT_DECISION_SENTENCE = "Review the change, then accept it or
|
|
428
|
+
export const RESULT_DECISION_SENTENCE = "Review the change, then accept it or send it back.";
|
|
423
429
|
export function acceptWordsOf(facts) {
|
|
424
430
|
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
425
431
|
const parts = [
|
|
@@ -435,7 +441,7 @@ export function acceptWordsOf(facts) {
|
|
|
435
441
|
: facts.publishing === "pull-request" ? "Finishes the task. No pull request opens; you can open one from the task after."
|
|
436
442
|
: facts.publishing === "off" ? "Finishes the task. The branch stays; publishing isn't set up."
|
|
437
443
|
: "Finishes the task. Nothing is published.";
|
|
438
|
-
return { label: ready ? "Accept
|
|
444
|
+
return { label: ready ? "Accept" : "Accept without checks", ready, why: ready ? null : `${joined.charAt(0).toUpperCase()}${joined.slice(1)}.`, effect };
|
|
439
445
|
}
|
|
440
446
|
export function resultHeadlineOf(status) {
|
|
441
447
|
const read = RESULT_NEEDS_YOU.has(status.token) ? { stage: "needs-you", need: "other" }
|
|
@@ -454,8 +460,8 @@ export function workStatusOf(facts, resultDisplay) {
|
|
|
454
460
|
: stageOfCode(status.token, { needsPerson: status.views.includes("needs-you"), planning: facts.dispatch?.code === "running" && facts.dispatch.role === "planner", operatorHold: facts.dispatch?.action === "unhold" });
|
|
455
461
|
const stage = done && read.stage !== "needs-you" && read.stage !== "failed" ? "finished" : read.stage;
|
|
456
462
|
const level = facts.result?.checkLevel ?? null;
|
|
457
|
-
const shared = taskStatusOf({ stage, ...(read.need === undefined ? {} : { need: read.need }), reason: plainReasonOf(stage, status.token, status.detail),
|
|
458
|
-
report: facts.result?.role === "scout", checks: stage !== "finished" ? null : VERIFIED_RESULT.has(status.token) ? { status: "passed", exitCode: null, head: null, level }
|
|
463
|
+
const shared = taskStatusOf({ stage, ...(read.need === undefined ? {} : { need: read.need }), ...(read.wait === undefined ? {} : { wait: read.wait }), reason: plainReasonOf(stage, status.token, status.detail),
|
|
464
|
+
report: facts.result?.role === "scout", ...(facts.machine === true ? { machine: true } : {}), checks: stage !== "finished" ? null : VERIFIED_RESULT.has(status.token) ? { status: "passed", exitCode: null, head: null, level }
|
|
459
465
|
: level === "off" ? { status: "not-run", exitCode: null, head: null, level } : null });
|
|
460
466
|
return { ...status, label: shared.headline, tone: workToneOf(shared.headline), detail: stage === "finished" && level !== "off" ? status.detail : shared.sentence };
|
|
461
467
|
}
|
|
@@ -464,7 +470,8 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
464
470
|
const result = facts.state === "done" ? resultDisplay ?? resultStatusOf(facts.result, facts.publication) : null;
|
|
465
471
|
const running = facts.liveRunId !== null || (dispatch?.condition === "running" && !REVIEW_TOKENS.has(dispatch.code));
|
|
466
472
|
const question = (facts.state === "queued" || facts.state === "running" || facts.state === "done") ? facts.openDecision ?? null : null;
|
|
467
|
-
const
|
|
473
|
+
const held = (facts.heldFindings ?? 0) > 0 && facts.state !== "cancelled";
|
|
474
|
+
const needs = (dispatch !== null && !REVIEW_TOKENS.has(dispatch.code) && needsPerson(dispatch)) || question !== null || held;
|
|
468
475
|
const views = ["all"];
|
|
469
476
|
if (needs)
|
|
470
477
|
views.push("needs-you");
|
|
@@ -474,6 +481,10 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
474
481
|
views.push("queued");
|
|
475
482
|
if (facts.state === "done")
|
|
476
483
|
views.push("completed");
|
|
484
|
+
const findingWords = (behind) => ({
|
|
485
|
+
token: "finding-held", label: "Needs you", detail: NEEDS.finding.sentence({}), tone: "attention", action: { label: NEEDS.finding.action.label, kind: "open-findings" }, views, rank: 0,
|
|
486
|
+
...(behind === null ? {} : { diagnostics: [behind] }),
|
|
487
|
+
});
|
|
477
488
|
const dispatchWords = (tone, rank, action) => ({
|
|
478
489
|
token: dispatch?.code ?? "unknown",
|
|
479
490
|
label: dispatch?.summary ?? "Status unknown",
|
|
@@ -486,7 +497,7 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
486
497
|
// The control projection knows whether a stop has actually settled.
|
|
487
498
|
// A still-live claim alone cannot distinguish Running from Stopping.
|
|
488
499
|
if (facts.control?.kind === "stopping") {
|
|
489
|
-
return { token: "stopping", label: "Stopping…", detail: facts.control.detail ?? "The attempt is ending. Its work is preserved; resume becomes available after its processes exit.", tone: "attention", action: { label: "View stop details", kind: "open-task" }, views, rank:
|
|
500
|
+
return { token: "stopping", label: "Stopping…", detail: facts.control.detail ?? "The attempt is ending. Its work is preserved; resume becomes available after its processes exit.", tone: "attention", action: { label: "View stop details", kind: "open-task" }, views, rank: 1 };
|
|
490
501
|
}
|
|
491
502
|
if (facts.control?.kind === "paused") {
|
|
492
503
|
return { token: "stopped", label: "Paused", detail: "The stopped attempt's work is preserved. Resuming requires confirmation and checks the current approval again.", tone: "attention", action: { label: "Review pause", kind: "open-task" }, views, rank: 0 };
|
|
@@ -499,6 +510,8 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
499
510
|
tone: "attention", action: { label: "Answer question", kind: "open-task" }, views, rank: 0,
|
|
500
511
|
diagnostics: [{ token: result.token, label: result.label, detail: result.detail, tone: result.tone }] };
|
|
501
512
|
}
|
|
513
|
+
if (held && result.tone !== "problem")
|
|
514
|
+
return findingWords({ token: result.token, label: result.label, detail: result.detail, tone: result.tone });
|
|
502
515
|
return { ...result, views, rank: needs ? 0 : 3 };
|
|
503
516
|
}
|
|
504
517
|
if (facts.state === "cancelled") {
|
|
@@ -513,6 +526,8 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
513
526
|
}),
|
|
514
527
|
};
|
|
515
528
|
}
|
|
529
|
+
if (held && facts.state !== "failed")
|
|
530
|
+
return findingWords(dispatch === null ? null : { token: dispatch.code, label: dispatch.summary, detail: dispatch.detail, tone: running ? "live" : "attention" });
|
|
516
531
|
if (running) {
|
|
517
532
|
return dispatchWords("live", 1, { label: "Watch the build", kind: facts.liveRunId === null ? "open-task" : "open-run" });
|
|
518
533
|
}
|
|
@@ -529,7 +544,7 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
529
544
|
}
|
|
530
545
|
/** The counts each view tab wears — from the same rows the page lists. */
|
|
531
546
|
export function workCounts(rows) {
|
|
532
|
-
const counts = { all: 0, "needs-you": 0, running: 0, queued: 0, completed: 0, archived: 0 };
|
|
547
|
+
const counts = { all: 0, "needs-you": 0, ready: 0, running: 0, queued: 0, completed: 0, archived: 0 };
|
|
533
548
|
for (const row of rows)
|
|
534
549
|
for (const view of row.views)
|
|
535
550
|
counts[view] += 1;
|
|
@@ -557,7 +572,7 @@ export function learningHtml(view, csrf, canManage, source) {
|
|
|
557
572
|
const act = (action, label, lesson) => canManage && csrf ? postForm('/settings/learning/change', html `<button type="submit">${label}</button>`, { hidden: { ...base, action, ...(lesson ? { lesson: lesson.id, version: lesson.version, sha: lesson.sha } : {}) } }) : '';
|
|
558
573
|
const evidence = (list, run) => list.map(one => html `<li><a href="/r/${String(one.runId ?? run)}/evidence/${one.artifactId}">Source #${one.artifactId}</a><blockquote>${one.excerpt}</blockquote><code>${one.sha256}</code></li>`);
|
|
559
574
|
const lessons = view.lessons.filter(l => source === undefined || l.source === source);
|
|
560
|
-
const cards = lessons.map(l => html `<article class="card" data-lesson="${l.id}"><p><strong>${l.payload.observation}</strong></p><p>${l.payload.action}</p><p class="meta">${l.payload.kind === 'system' ? 'System suggestion · No change applied' : l.status === 'adopted' ? 'Adopted advice' : l.status === 'disabled' ? 'Disabled' : 'Proposed lesson'}</p><details><summary>Source and use</summary><p><a href="/r/${l.source}">Result #${l.source}</a> · <a href="/r/${l.reviewer}">Review #${l.reviewer}</a></p><p>${l.payload.paths.join(', ')} · ${l.payload.phases.join(', ')} · ${{ darwin: "macOS", win32: "Windows", linux: "Linux" }[l.payload.platform] ?? l.payload.platform}</p><ul>${evidence(l.payload.evidence, l.source)}</ul>${l.payload.kind !== 'project' ? '' : html `<p class="meta">${l.status === 'proposed' ? 'Save permits advisory reuse when enabled and applicable. It does not prove a remedy works.' : 'Disabling affects future runs. Active snapshots, code and approvals stay unchanged.'}</p>${l.status === 'proposed' ? act('adopt', 'Save lesson', l) : ''}`}</details>${l.payload.kind === 'project' && l.status === 'adopted' ? act('disable', 'Disable lesson', l) : ''}</article>`);
|
|
575
|
+
const cards = lessons.map(l => html `<article class="card" data-lesson="${l.id}"><p><strong>${l.payload.observation}</strong></p><p>${l.payload.action}</p><p class="meta">${lastIncludedWords(view.use.get(`lesson:${l.id}`))}</p><p class="meta">${l.payload.kind === 'system' ? 'System suggestion · No change applied' : l.status === 'adopted' ? 'Adopted advice' : l.status === 'disabled' ? 'Disabled' : 'Proposed lesson'}</p><details><summary>Source and use</summary><p><a href="/r/${l.source}">Result #${l.source}</a> · <a href="/r/${l.reviewer}">Review #${l.reviewer}</a></p><p>${l.payload.paths.join(', ')} · ${l.payload.phases.join(', ')} · ${{ darwin: "macOS", win32: "Windows", linux: "Linux" }[l.payload.platform] ?? l.payload.platform}</p><ul>${evidence(l.payload.evidence, l.source)}</ul>${l.payload.kind !== 'project' ? '' : html `<p class="meta">${l.status === 'proposed' ? 'Save permits advisory reuse when enabled and applicable. It does not prove a remedy works.' : 'Disabling affects future runs. Active snapshots, code and approvals stay unchanged.'}</p>${l.status === 'proposed' ? act('adopt', 'Save lesson', l) : ''}`}</details>${l.payload.kind === 'project' && l.status === 'adopted' ? act('disable', 'Disable lesson', l) : ''}</article>`);
|
|
561
576
|
if (source !== undefined)
|
|
562
577
|
return lessons.length ? html `<details class="learning result-learning"><summary>Learned from this task</summary>${cards}<a href="/settings/learning?repo=${encodeURIComponent(view.repo)}">Learning settings</a></details>` : html ``;
|
|
563
578
|
const history = view.events.map(ev => html `<article class="card" data-learning-event="${ev.action}"><p><strong>${{ assessment: { propose: 'Learning suggested', none: 'No lesson needed', unassessed: 'Learning not assessed', invalid: 'Learning assessment invalid' }[ev.after] ?? 'Learning not assessed', proposal: 'Suggestion recorded', adopt: 'Lesson adopted', disable: 'Lesson disabled', reset: 'Learning reset', enable: 'Reuse enabled', pause: 'Reuse paused', reuse: 'Run context saved', failure: 'Learning issue', capture: 'Capture finished' }[ev.action] ?? ev.action}</strong>${ev.action === "reuse" ? ` · ${ev.after}` : ""}</p><p class="meta">${whenUtc(ev.at)} · ${ev.actor}${ev.run === null ? '' : html ` · <a href="/r/${ev.run}">Run #${ev.run}</a>${ev.outcome === null ? "" : ` · ${ev.outcome}`}`}</p><details><summary>Details</summary><p>${ev.before} → ${ev.after}</p><p>${ev.reason}</p>${ev.lesson === null ? '' : html `<p>Lesson #${ev.lesson}</p>`}<ul>${evidence(ev.evidence, ev.run)}</ul>${ev.snapshot === null ? "" : html `<details><summary>Exact context</summary><pre>${ev.snapshot}</pre></details>`}</details></article>`);
|
package/dist/worktree.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export type LeaseResult = {
|
|
|
96
96
|
};
|
|
97
97
|
export type LeaseFailure = "held" | "dirty" | "git" | "unverified" | "unknown-runner" | "in-use";
|
|
98
98
|
/** Why storage retention left a checkout in place. */
|
|
99
|
-
export type KeptWhy = "has changes" | "has commits" | "unreadable" | "git refused";
|
|
99
|
+
export type KeptWhy = "has changes" | "has commits" | "not on its branch" | "unreadable" | "git refused";
|
|
100
100
|
/**
|
|
101
101
|
* The note a lease leaves in the checkout, naming the process holding it.
|
|
102
102
|
*
|
|
@@ -291,15 +291,24 @@ export declare class WorktreePool {
|
|
|
291
291
|
* or remote. Reads only, through `git` when given (teardown's hardened git).
|
|
292
292
|
*/
|
|
293
293
|
inspect(path: string, git?: Git): Promise<"clean" | KeptWhy>;
|
|
294
|
+
/**
|
|
295
|
+
* Whether a checkout a task may still come back to can go, beyond `inspect`: a later lease of its branch must make the
|
|
296
|
+
* same checkout, and every commit its task recorded must outlive it (git's garbage collection counts a worktree's HEAD
|
|
297
|
+
* and reflog; once they go only a branch holds a commit). So: the folder is the pool's own, the checkout belongs to its
|
|
298
|
+
* project by the plane's record (`boundDirs`, not its `.git` file), its HEAD is its recorded branch, every recorded
|
|
299
|
+
* `heads` commit is on a branch, and no ref lives only in this worktree. Reads only; anything unreadable keeps it.
|
|
300
|
+
*/
|
|
301
|
+
proveRecreatable(row: WorktreeRow, heads: readonly string[]): Promise<"clean" | KeptWhy>;
|
|
294
302
|
/**
|
|
295
303
|
* Keep storage in check: remove the working copies nobody needs any more. Of `rows`, a checkout goes when it is
|
|
296
304
|
* let go, nothing holds it, `wanted` says it may go (asked again right before it goes, so a task coming back or a
|
|
297
305
|
* lease since keeps it) and `inspect` finds it clean, then clean still right before it goes (git refuses an edit
|
|
298
306
|
* made after that). Only the working copy goes: its branch, and so every commit,
|
|
299
307
|
* stays, and a later lease of the same branch makes a new one. At most `max` go per pass. Every git command goes
|
|
300
|
-
* through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts).
|
|
308
|
+
* through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts). `proof` is asked last,
|
|
309
|
+
* inside the reservation, for what `inspect` doesn't cover (idle cleanup: `proveRecreatable`).
|
|
301
310
|
*/
|
|
302
|
-
prune(rows: readonly WorktreeRow[], wanted: (row: WorktreeRow, fresh: boolean) => boolean | Promise<boolean>, max?: number, git?: Git): Promise<{
|
|
311
|
+
prune(rows: readonly WorktreeRow[], wanted: (row: WorktreeRow, fresh: boolean) => boolean | Promise<boolean>, max?: number, git?: Git, proof?: (row: WorktreeRow) => Promise<"clean" | KeptWhy>): Promise<{
|
|
303
312
|
removed: WorktreeRow[];
|
|
304
313
|
kept: {
|
|
305
314
|
path: string;
|
package/dist/worktree.js
CHANGED
|
@@ -22,17 +22,19 @@
|
|
|
22
22
|
* synced folder like OneDrive or Dropbox, where a sync client racing an agent
|
|
23
23
|
* over `.git` internals corrupts both.
|
|
24
24
|
*/
|
|
25
|
+
import { INCIDENT_QUIET_MS, recentGitIncident } from "./git-incident.js";
|
|
25
26
|
import { createHash, randomBytes } from "node:crypto";
|
|
26
27
|
import { existsSync, lstatSync, readFileSync, writeFileSync, rmSync, realpathSync, mkdirSync, readdirSync, renameSync } from "node:fs";
|
|
27
28
|
import { hostname } from "node:os";
|
|
28
29
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
29
30
|
import { run } from "./exec.js";
|
|
30
31
|
import { currentBootId, provenDeadByBootChange } from "./boot-identity.js";
|
|
32
|
+
import { currentMachineId, isThisMachine } from "./machine-identity.js";
|
|
31
33
|
import { HANDOFF_PREFIX, MAILBOX_SUFFIX, looksLikeProtocolFile, readMailbox } from "./evidence.js";
|
|
32
34
|
import { parseHandoff } from "./decision.js";
|
|
33
35
|
import { diskBytes } from "./storage.js";
|
|
34
36
|
import { markNeverIndex } from "./never-index.js";
|
|
35
|
-
import { checkoutGit, teardownGit } from "./teardown-git.js";
|
|
37
|
+
import { checkoutGit, gitIncidentRecord, teardownGit } from "./teardown-git.js";
|
|
36
38
|
/**
|
|
37
39
|
* The note a lease leaves in the checkout, naming the process holding it.
|
|
38
40
|
*
|
|
@@ -55,7 +57,8 @@ const reservationPath = (path) => `${path}.occupancy`;
|
|
|
55
57
|
function reservation(path) {
|
|
56
58
|
try {
|
|
57
59
|
const note = JSON.parse(readFileSync(reservationPath(path), "utf8"));
|
|
58
|
-
if (Number.isSafeInteger(note.pid) && note.pid > 0 && typeof note.host === "string" && typeof note.token === "string"
|
|
60
|
+
if (Number.isSafeInteger(note.pid) && note.pid > 0 && typeof note.host === "string" && typeof note.token === "string"
|
|
61
|
+
&& (note.machineId === undefined || note.machineId === null || typeof note.machineId === "string"))
|
|
59
62
|
return note;
|
|
60
63
|
}
|
|
61
64
|
catch (error) {
|
|
@@ -66,9 +69,9 @@ function reservation(path) {
|
|
|
66
69
|
return { pid: 0, host: "unknown", bootId: null, token: "unknown" };
|
|
67
70
|
}
|
|
68
71
|
function reservationAlive(note) {
|
|
69
|
-
if (note.pid === 0 || note
|
|
72
|
+
if (note.pid === 0 || !isThisMachine(note))
|
|
70
73
|
return true;
|
|
71
|
-
if (note.bootId !== null && provenDeadByBootChange(note))
|
|
74
|
+
if (note.bootId !== null && provenDeadByBootChange(note, { host: note.host, bootId: currentBootId() }))
|
|
72
75
|
return false;
|
|
73
76
|
try {
|
|
74
77
|
process.kill(note.pid, 0);
|
|
@@ -81,7 +84,7 @@ function reservationAlive(note) {
|
|
|
81
84
|
/** A short store transaction arbitrates marker creation/recovery, never git, a scan or deletion.
|
|
82
85
|
* Atomic rename publishes a complete owner; a crash cannot leave a half-written reservation. */
|
|
83
86
|
function reserveCheckout(store, path) {
|
|
84
|
-
const note = { pid: process.pid, host: hostname(), bootId: currentBootId(), token: randomBytes(12).toString("hex") };
|
|
87
|
+
const note = { pid: process.pid, host: hostname(), bootId: currentBootId(), token: randomBytes(12).toString("hex"), machineId: currentMachineId() };
|
|
85
88
|
const file = reservationPath(path), temp = `${file}.${note.token}`;
|
|
86
89
|
const held = store.transact(() => {
|
|
87
90
|
const prior = reservation(path);
|
|
@@ -155,6 +158,18 @@ function safePoolPath(root, target) {
|
|
|
155
158
|
}
|
|
156
159
|
return true;
|
|
157
160
|
}
|
|
161
|
+
/** The checkout itself, strictly inside the pool as real paths, reached by real folders only: never a link out of it. */
|
|
162
|
+
function insidePool(root, path) {
|
|
163
|
+
if (!safePoolPath(root, path))
|
|
164
|
+
return false;
|
|
165
|
+
try {
|
|
166
|
+
const real = realpathSync(path), base = realpathSync(root);
|
|
167
|
+
return real !== base && real.startsWith(base + sep) && lstatSync(path).isDirectory();
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
158
173
|
function normalisePath(path) {
|
|
159
174
|
return path.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
160
175
|
}
|
|
@@ -178,12 +193,12 @@ export function recordWorktreeProcess(store, path, runner, pid, leaseEpoch) {
|
|
|
178
193
|
// verified boot change nothing of it survives, and the checkout is free
|
|
179
194
|
// without a PID probe that a reused pid could answer wrongly.
|
|
180
195
|
const bootId = currentBootId();
|
|
181
|
-
writeFileSync(join(path, MARKER), `${pid} ${runner} group ${bootId ?? "unknown"} ${hostname()}\n`, "utf8");
|
|
196
|
+
writeFileSync(join(path, MARKER), `${pid} ${runner} group ${bootId ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
|
|
182
197
|
}
|
|
183
198
|
/** Read-only occupancy witness shared with stop settlement. */
|
|
184
199
|
export function worktreeProcessOccupancy(path) {
|
|
185
200
|
const reserved = reservation(path);
|
|
186
|
-
if (reserved !== null && (reserved.pid !== process.pid || reserved
|
|
201
|
+
if (reserved !== null && (reserved.pid !== process.pid || !isThisMachine(reserved)) && reservationAlive(reserved))
|
|
187
202
|
return { held: true, by: reserved.pid };
|
|
188
203
|
const note = join(path, MARKER);
|
|
189
204
|
if (!existsSync(note))
|
|
@@ -193,7 +208,9 @@ export function worktreeProcessOccupancy(path) {
|
|
|
193
208
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
194
209
|
return { held: false };
|
|
195
210
|
const noteHost = parts[4];
|
|
196
|
-
|
|
211
|
+
// The sixth word (v150) is the writer's machine id; "unknown", or a note from before it, is matched by name.
|
|
212
|
+
const noteMachine = parts[5] === undefined || parts[5] === "unknown" ? null : parts[5];
|
|
213
|
+
if (noteHost !== undefined && !isThisMachine({ host: noteHost, machineId: noteMachine }))
|
|
197
214
|
return { held: true, by: pid };
|
|
198
215
|
if (pid === process.pid && parts[2] !== "group")
|
|
199
216
|
return { held: false };
|
|
@@ -201,7 +218,7 @@ export function worktreeProcessOccupancy(path) {
|
|
|
201
218
|
// that cannot exist any more; a note without a boot id (legacy) or a
|
|
202
219
|
// boot this host cannot verify keeps the probe below.
|
|
203
220
|
const noteBoot = parts[3];
|
|
204
|
-
if (noteBoot !== undefined && noteHost !== undefined && provenDeadByBootChange({ host: noteHost, bootId: noteBoot }))
|
|
221
|
+
if (noteBoot !== undefined && noteHost !== undefined && provenDeadByBootChange({ host: noteHost, bootId: noteBoot }, { host: noteHost, bootId: currentBootId() }))
|
|
205
222
|
return { held: false };
|
|
206
223
|
// A shell/provider can exit before its descendants. A recorded POSIX
|
|
207
224
|
// process group remains an owner until that whole group has gone.
|
|
@@ -650,7 +667,7 @@ export class WorktreePool {
|
|
|
650
667
|
/** Leave a note naming the process holding this checkout. */
|
|
651
668
|
mark(path, runner, pid = process.pid) {
|
|
652
669
|
try {
|
|
653
|
-
writeFileSync(join(path, MARKER), `${pid} ${runner} process ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
|
|
670
|
+
writeFileSync(join(path, MARKER), `${pid} ${runner} process ${currentBootId() ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
|
|
654
671
|
return true;
|
|
655
672
|
}
|
|
656
673
|
catch {
|
|
@@ -837,6 +854,9 @@ export class WorktreePool {
|
|
|
837
854
|
* or remote. Reads only, through `git` when given (teardown's hardened git).
|
|
838
855
|
*/
|
|
839
856
|
async inspect(path, git = this.teardown) {
|
|
857
|
+
// A checkout that had a git incident lately is left alone (git-incident.ts): nothing re-runs git there each pass.
|
|
858
|
+
if (quietAfterIncident(path))
|
|
859
|
+
return "unreadable";
|
|
840
860
|
const dirty = await this.isDirty(path, true, git);
|
|
841
861
|
if (dirty !== false)
|
|
842
862
|
return dirty === null ? "unreadable" : "has changes";
|
|
@@ -846,15 +866,51 @@ export class WorktreePool {
|
|
|
846
866
|
return "unreadable";
|
|
847
867
|
return own.stdout.trim() === "" ? "clean" : "has commits";
|
|
848
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Whether a checkout a task may still come back to can go, beyond `inspect`: a later lease of its branch must make the
|
|
871
|
+
* same checkout, and every commit its task recorded must outlive it (git's garbage collection counts a worktree's HEAD
|
|
872
|
+
* and reflog; once they go only a branch holds a commit). So: the folder is the pool's own, the checkout belongs to its
|
|
873
|
+
* project by the plane's record (`boundDirs`, not its `.git` file), its HEAD is its recorded branch, every recorded
|
|
874
|
+
* `heads` commit is on a branch, and no ref lives only in this worktree. Reads only; anything unreadable keeps it.
|
|
875
|
+
*/
|
|
876
|
+
async proveRecreatable(row, heads) {
|
|
877
|
+
if (quietAfterIncident(row.path))
|
|
878
|
+
return "unreadable";
|
|
879
|
+
if (row.branch === "unknown" || !insidePool(this.options.root, row.path) || heads.some(one => !/^[0-9a-f]{40,64}$/.test(one)))
|
|
880
|
+
return "unreadable";
|
|
881
|
+
const git = teardownGit(this.runner, { repo: row.repo });
|
|
882
|
+
const head = await git(row.path, [...READ_ONLY, "symbolic-ref", "-q", "HEAD"]);
|
|
883
|
+
if (head.code === 1)
|
|
884
|
+
return "not on its branch";
|
|
885
|
+
if (head.code !== 0)
|
|
886
|
+
return "unreadable";
|
|
887
|
+
if (head.stdout.trim() !== `refs/heads/${row.branch}`)
|
|
888
|
+
return "not on its branch";
|
|
889
|
+
const branch = await git(row.repo, [...READ_ONLY, "rev-parse", "--verify", "--quiet", `refs/heads/${row.branch}^{commit}`]);
|
|
890
|
+
if (branch.code !== 0)
|
|
891
|
+
return "unreadable";
|
|
892
|
+
if (heads.length > 0) {
|
|
893
|
+
const unheld = await git(row.repo, [...READ_ONLY, "rev-list", "-1", ...heads, "--not", "--branches"]);
|
|
894
|
+
if (unheld.code !== 0)
|
|
895
|
+
return "unreadable";
|
|
896
|
+
if (unheld.stdout.trim() !== "")
|
|
897
|
+
return "has commits";
|
|
898
|
+
}
|
|
899
|
+
const own = await git(row.path, [...READ_ONLY, "for-each-ref", "--count=1", "refs/worktree", "refs/bisect", "refs/rewritten"]);
|
|
900
|
+
if (own.code !== 0)
|
|
901
|
+
return "unreadable";
|
|
902
|
+
return own.stdout.trim() === "" ? "clean" : "has commits";
|
|
903
|
+
}
|
|
849
904
|
/**
|
|
850
905
|
* Keep storage in check: remove the working copies nobody needs any more. Of `rows`, a checkout goes when it is
|
|
851
906
|
* let go, nothing holds it, `wanted` says it may go (asked again right before it goes, so a task coming back or a
|
|
852
907
|
* lease since keeps it) and `inspect` finds it clean, then clean still right before it goes (git refuses an edit
|
|
853
908
|
* made after that). Only the working copy goes: its branch, and so every commit,
|
|
854
909
|
* stays, and a later lease of the same branch makes a new one. At most `max` go per pass. Every git command goes
|
|
855
|
-
* through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts).
|
|
910
|
+
* through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts). `proof` is asked last,
|
|
911
|
+
* inside the reservation, for what `inspect` doesn't cover (idle cleanup: `proveRecreatable`).
|
|
856
912
|
*/
|
|
857
|
-
async prune(rows, wanted, max = Infinity, git = this.teardown) {
|
|
913
|
+
async prune(rows, wanted, max = Infinity, git = this.teardown, proof) {
|
|
858
914
|
const removed = [];
|
|
859
915
|
const kept = [];
|
|
860
916
|
const idle = (row, releasedAt) => row !== null && row.runner === null && row.releasedAt === releasedAt && !this.inUse(row.path).held;
|
|
@@ -888,6 +944,11 @@ export class WorktreePool {
|
|
|
888
944
|
const allowed = wanted(now, true);
|
|
889
945
|
if (!(typeof allowed === "boolean" ? allowed : await allowed))
|
|
890
946
|
continue;
|
|
947
|
+
const proven = proof === undefined ? "clean" : await proof(now);
|
|
948
|
+
if (proven !== "clean") {
|
|
949
|
+
kept.push({ path: row.path, why: proven });
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
891
952
|
const gone = await this.removeCleanCopy(row, git);
|
|
892
953
|
if (gone !== "removed") {
|
|
893
954
|
kept.push({ path: row.path, why: gone });
|
|
@@ -931,7 +992,7 @@ export class WorktreePool {
|
|
|
931
992
|
return { ok: false, message: `${row.path} is in use` };
|
|
932
993
|
// Held while it slims: another process's lease sees this process in it.
|
|
933
994
|
try {
|
|
934
|
-
writeFileSync(note, `${process.pid} storage-retention slimming ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
|
|
995
|
+
writeFileSync(note, `${process.pid} storage-retention slimming ${currentBootId() ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
|
|
935
996
|
held = true;
|
|
936
997
|
}
|
|
937
998
|
catch {
|
|
@@ -989,7 +1050,7 @@ export class WorktreePool {
|
|
|
989
1050
|
// Held while it goes: another process's lease sees this process in it (the busy map is this process's only).
|
|
990
1051
|
const note = join(row.path, MARKER);
|
|
991
1052
|
try {
|
|
992
|
-
writeFileSync(note, `${process.pid} storage-retention removing ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
|
|
1053
|
+
writeFileSync(note, `${process.pid} storage-retention removing ${currentBootId() ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
|
|
993
1054
|
}
|
|
994
1055
|
catch {
|
|
995
1056
|
return false;
|
|
@@ -1065,3 +1126,19 @@ function firstLine(text) {
|
|
|
1065
1126
|
const [line = ""] = text.trim().split("\n");
|
|
1066
1127
|
return line;
|
|
1067
1128
|
}
|
|
1129
|
+
/** Whether a checkout had a git incident within INCIDENT_QUIET_MS (by its real path, as incidents record it). */
|
|
1130
|
+
function quietAfterIncident(path) {
|
|
1131
|
+
let real;
|
|
1132
|
+
try {
|
|
1133
|
+
real = realpathSync(path);
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
return recentGitIncident(gitIncidentRecord(), real, new Date(Date.now() - INCIDENT_QUIET_MS));
|
|
1140
|
+
}
|
|
1141
|
+
catch {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.95",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|
|
@@ -133,6 +133,7 @@
|
|
|
133
133
|
},
|
|
134
134
|
"dependencies": {
|
|
135
135
|
"@anthropic-ai/sandbox-runtime": "0.0.79",
|
|
136
|
+
"@json-render/core": "0.22.0",
|
|
136
137
|
"@slack/socket-mode": "3.1.1",
|
|
137
138
|
"@xterm/addon-serialize": "0.14.0",
|
|
138
139
|
"@xterm/headless": "6.0.0",
|