cloving 1.3.0 → 1.4.0
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/README.md +235 -206
- package/dist/src/agent-harness-shell.d.ts +2 -1
- package/dist/src/agent-harness-shell.js +8 -5
- package/dist/src/authority/command-wait-checks.d.ts +8 -0
- package/dist/src/authority/command-wait-checks.js +88 -0
- package/dist/src/authority/command-wait-results.d.ts +15 -0
- package/dist/src/authority/command-wait-results.js +73 -0
- package/dist/src/authority/command-wait-view.d.ts +21 -0
- package/dist/src/authority/command-wait-view.js +34 -0
- package/dist/src/authority/command-waits.d.ts +39 -0
- package/dist/src/authority/command-waits.js +97 -0
- package/dist/src/authority/commands.js +22 -1
- package/dist/src/authority/dispatch-decision.d.ts +8 -0
- package/dist/src/authority/dispatch-decision.js +26 -1
- package/dist/src/authority/effect-requests.js +6 -3
- package/dist/src/authority/effect-results.js +4 -2
- package/dist/src/authority/history-state.d.ts +2 -0
- package/dist/src/authority/history-state.js +13 -1
- package/dist/src/authority/instants.d.ts +1 -0
- package/dist/src/authority/instants.js +21 -0
- package/dist/src/authority/reduction-state.d.ts +2 -0
- package/dist/src/authority/reduction-state.js +5 -0
- package/dist/src/authority/reduction.js +24 -0
- package/dist/src/authority/status-input.d.ts +2 -0
- package/dist/src/authority/status.d.ts +2 -0
- package/dist/src/authority/status.js +1 -0
- package/dist/src/authority/stop-sequence.d.ts +1 -0
- package/dist/src/authority/stop-sequence.js +2 -1
- package/dist/src/authority/transitions.d.ts +6 -4
- package/dist/src/authority/transitions.js +7 -0
- package/dist/src/broker/actor/authority-queries.d.ts +2 -1
- package/dist/src/broker/actor/authority-queries.js +14 -5
- package/dist/src/broker/actor/authority-receipts.js +1 -1
- package/dist/src/broker/actor/authority-settings.js +2 -2
- package/dist/src/broker/actor/authority-upgrades.d.ts +19 -2
- package/dist/src/broker/actor/authority-upgrades.js +7 -2
- package/dist/src/broker/actor/delivery-intents.js +2 -0
- package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
- package/dist/src/broker/actor/effect-delivery.js +15 -2
- package/dist/src/broker/actor/effect-plan.js +11 -2
- package/dist/src/broker/actor/effect-queries.d.ts +4 -0
- package/dist/src/broker/actor/effect-queries.js +9 -1
- package/dist/src/broker/actor/effect-turn.js +2 -0
- package/dist/src/broker/actor/stop-plan.js +9 -3
- package/dist/src/broker/actor/stop-turn.d.ts +2 -0
- package/dist/src/broker/actor/stop-turn.js +24 -11
- package/dist/src/broker/actor/turn-draft.d.ts +1 -0
- package/dist/src/broker/actor/turn-draft.js +4 -0
- package/dist/src/broker/actor/turn-state.js +5 -1
- package/dist/src/broker/authority-admission.js +60 -0
- package/dist/src/broker/authority-restore.d.ts +2 -0
- package/dist/src/broker/authority-restore.js +35 -1
- package/dist/src/broker/command-wait-restore.d.ts +31 -0
- package/dist/src/broker/command-wait-restore.js +86 -0
- package/dist/src/broker/decision-restore.d.ts +1 -1
- package/dist/src/broker/decision-restore.js +1 -0
- package/dist/src/broker/effect-upgrade.d.ts +2 -0
- package/dist/src/broker/effect-upgrade.js +8 -2
- package/dist/src/cli/arguments.d.ts +13 -0
- package/dist/src/cli/arguments.js +42 -3
- package/dist/src/cli/chat-stores.d.ts +2 -0
- package/dist/src/cli/chat-stores.js +113 -0
- package/dist/src/cli/client.d.ts +4 -1
- package/dist/src/cli/client.js +9 -3
- package/dist/src/cli/command-requests.d.ts +17 -0
- package/dist/src/cli/command-requests.js +43 -0
- package/dist/src/cli/command-waits.d.ts +9 -0
- package/dist/src/cli/command-waits.js +51 -0
- package/dist/src/cli/commands.js +14 -41
- package/dist/src/cli/config-file.d.ts +2 -2
- package/dist/src/cli/config-file.js +4 -2
- package/dist/src/cli/doctor-workflows.d.ts +5 -2
- package/dist/src/cli/doctor-workflows.js +12 -7
- package/dist/src/cli/doctor.js +6 -9
- package/dist/src/cli/harness-probes.d.ts +2 -0
- package/dist/src/cli/harness-probes.js +1 -1
- package/dist/src/cli/harness-processes.d.ts +6 -0
- package/dist/src/cli/harness-processes.js +36 -0
- package/dist/src/cli/init-command.d.ts +9 -0
- package/dist/src/cli/init-command.js +52 -0
- package/dist/src/cli/init-prompt-command.d.ts +10 -0
- package/dist/src/cli/init-prompt-command.js +145 -0
- package/dist/src/cli/init-prompt.d.ts +35 -0
- package/dist/src/cli/init-prompt.js +74 -0
- package/dist/src/cli/init-selection.d.ts +13 -0
- package/dist/src/cli/init-selection.js +75 -0
- package/dist/src/cli/main.d.ts +2 -0
- package/dist/src/cli/main.js +37 -7
- package/dist/src/cli/mcp-server.d.ts +1 -0
- package/dist/src/cli/mcp-server.js +156 -0
- package/dist/src/cli/mcp-tools.d.ts +15 -0
- package/dist/src/cli/mcp-tools.js +80 -0
- package/dist/src/cli/render.d.ts +1 -1
- package/dist/src/cli/render.js +17 -30
- package/dist/src/cli/report-ci-jobs.d.ts +23 -0
- package/dist/src/cli/report-ci-jobs.js +103 -0
- package/dist/src/cli/report-ci-usage.d.ts +9 -0
- package/dist/src/cli/report-ci-usage.js +185 -0
- package/dist/src/cli/report-ci.d.ts +13 -0
- package/dist/src/cli/report-ci.js +74 -0
- package/dist/src/cli/report-closures.d.ts +6 -0
- package/dist/src/cli/report-closures.js +97 -0
- package/dist/src/cli/report-flow.d.ts +7 -0
- package/dist/src/cli/report-flow.js +122 -0
- package/dist/src/cli/report-github-reader.d.ts +29 -0
- package/dist/src/cli/report-github-reader.js +157 -0
- package/dist/src/cli/report-github.d.ts +15 -8
- package/dist/src/cli/report-github.js +131 -150
- package/dist/src/cli/report-operator.d.ts +3 -0
- package/dist/src/cli/report-operator.js +40 -0
- package/dist/src/cli/report-ready.d.ts +3 -0
- package/dist/src/cli/report-ready.js +96 -0
- package/dist/src/cli/report-text.d.ts +9 -0
- package/dist/src/cli/report-text.js +33 -0
- package/dist/src/cli/report.d.ts +1 -0
- package/dist/src/cli/report.js +104 -27
- package/dist/src/cli/runtime-arguments.d.ts +2 -1
- package/dist/src/cli/runtime-arguments.js +7 -2
- package/dist/src/cli/schedule-arguments.d.ts +14 -0
- package/dist/src/cli/schedule-arguments.js +26 -0
- package/dist/src/cli/schedule-render.d.ts +22 -0
- package/dist/src/cli/schedule-render.js +73 -0
- package/dist/src/cli/terminal-text.d.ts +3 -0
- package/dist/src/cli/terminal-text.js +28 -0
- package/dist/src/cli/usage-render.d.ts +1 -0
- package/dist/src/cli/usage-render.js +28 -1
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +50 -7
- package/dist/src/cli/watch-actions.js +2 -2
- package/dist/src/cli/watch-board-view.js +19 -2
- package/dist/src/cli/watch-board.d.ts +4 -2
- package/dist/src/cli/watch-board.js +53 -43
- package/dist/src/cli/watch-card-forms.d.ts +1 -1
- package/dist/src/cli/watch-card-forms.js +11 -10
- package/dist/src/cli/watch-client.d.ts +1 -1
- package/dist/src/cli/watch-client.js +2 -11
- package/dist/src/cli/watch-header-view.d.ts +0 -5
- package/dist/src/cli/watch-header-view.js +8 -20
- package/dist/src/cli/watch-model.d.ts +4 -1
- package/dist/src/cli/watch-model.js +17 -11
- package/dist/src/cli/watch-page.js +12 -14
- package/dist/src/cli/watch-phases.d.ts +12 -3
- package/dist/src/cli/watch-phases.js +68 -11
- package/dist/src/cli/watch-pr-board.js +1 -1
- package/dist/src/cli/watch-pr-command.d.ts +1 -0
- package/dist/src/cli/watch-pull-request.js +18 -8
- package/dist/src/cli/watch-reads.d.ts +2 -0
- package/dist/src/cli/watch-reads.js +17 -16
- package/dist/src/cli/watch-render.d.ts +14 -0
- package/dist/src/cli/watch-run-input.d.ts +0 -1
- package/dist/src/cli/watch-run-input.js +0 -13
- package/dist/src/cli/watch-server.js +6 -6
- package/dist/src/cli/watch-view.d.ts +0 -3
- package/dist/src/cli/watch-view.js +13 -13
- package/dist/src/cli.js +59 -5
- package/dist/src/command-wait.d.ts +4 -0
- package/dist/src/command-wait.js +45 -0
- package/dist/src/configuration-change.d.ts +7 -0
- package/dist/src/configuration-change.js +14 -0
- package/dist/src/configuration-schema.d.ts +9 -0
- package/dist/src/configuration-schema.js +6 -2
- package/dist/src/daemon/attachment-file.d.ts +6 -0
- package/dist/src/daemon/attachment-file.js +86 -0
- package/dist/src/daemon/attachments.d.ts +14 -0
- package/dist/src/daemon/attachments.js +17 -0
- package/dist/src/daemon/claude-recovery.d.ts +50 -0
- package/dist/src/daemon/claude-recovery.js +204 -0
- package/dist/src/daemon/claude-stream.d.ts +27 -0
- package/dist/src/daemon/claude-stream.js +63 -0
- package/dist/src/daemon/claude-turn.d.ts +11 -25
- package/dist/src/daemon/claude-turn.js +64 -81
- package/dist/src/daemon/claude-worker.d.ts +2 -1
- package/dist/src/daemon/claude-worker.js +124 -122
- package/dist/src/daemon/daemon-adapters.d.ts +2 -0
- package/dist/src/daemon/daemon-adapters.js +26 -2
- package/dist/src/daemon/daemon-loops.d.ts +3 -1
- package/dist/src/daemon/daemon-loops.js +3 -1
- package/dist/src/daemon/daemon.js +14 -6
- package/dist/src/daemon/github-attach.d.ts +2 -0
- package/dist/src/daemon/github-attach.js +149 -0
- package/dist/src/daemon/github-ci-filters.d.ts +6 -0
- package/dist/src/daemon/github-ci-filters.js +138 -0
- package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
- package/dist/src/daemon/github-ci-workflows.js +148 -0
- package/dist/src/daemon/github-ci.js +22 -4
- package/dist/src/daemon/github-issue-methods.d.ts +2 -0
- package/dist/src/daemon/github-issue-methods.js +37 -2
- package/dist/src/daemon/github-merge-call.d.ts +2 -1
- package/dist/src/daemon/github-merge-call.js +10 -3
- package/dist/src/daemon/github-merge.js +52 -24
- package/dist/src/daemon/github-methods.js +12 -2
- package/dist/src/daemon/github-operation.d.ts +4 -0
- package/dist/src/daemon/github-operation.js +6 -0
- package/dist/src/daemon/github-worker.d.ts +4 -0
- package/dist/src/daemon/github-worker.js +6 -0
- package/dist/src/daemon/issue-queue.d.ts +1 -1
- package/dist/src/daemon/issue-queue.js +16 -11
- package/dist/src/daemon/notify-events.d.ts +6 -0
- package/dist/src/daemon/notify-events.js +84 -0
- package/dist/src/daemon/notify-journal.js +1 -1
- package/dist/src/daemon/notify-message.d.ts +4 -1
- package/dist/src/daemon/notify-message.js +36 -0
- package/dist/src/daemon/notify.d.ts +1 -1
- package/dist/src/daemon/notify.js +5 -77
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/queue-status.d.ts +9 -3
- package/dist/src/daemon/queue-status.js +17 -4
- package/dist/src/daemon/resume-index-lock.d.ts +13 -0
- package/dist/src/daemon/resume-index-lock.js +160 -0
- package/dist/src/daemon/resume-worktree.d.ts +7 -0
- package/dist/src/daemon/resume-worktree.js +29 -0
- package/dist/src/daemon/retrying-turn.d.ts +2 -12
- package/dist/src/daemon/retrying-turn.js +98 -172
- package/dist/src/daemon/schedule-control.d.ts +26 -0
- package/dist/src/daemon/schedule-control.js +110 -0
- package/dist/src/daemon/schedule-journal.d.ts +2 -15
- package/dist/src/daemon/schedule-journal.js +67 -4
- package/dist/src/daemon/ticket-registration.d.ts +2 -7
- package/dist/src/daemon/ticket-registration.js +15 -81
- package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
- package/dist/src/daemon/turn-attempt-journal.js +136 -0
- package/dist/src/daemon/turn-attempts.d.ts +47 -0
- package/dist/src/daemon/turn-attempts.js +166 -0
- package/dist/src/daemon/turn-report.d.ts +8 -1
- package/dist/src/daemon/turn-report.js +39 -0
- package/dist/src/daemon/work-tree.d.ts +9 -0
- package/dist/src/daemon/work-tree.js +20 -0
- package/dist/src/daemon/worker-profile.d.ts +8 -0
- package/dist/src/daemon/worker-profile.js +40 -0
- package/dist/src/daemon/workflow-schedules.d.ts +3 -0
- package/dist/src/daemon/workflow-schedules.js +98 -45
- package/dist/src/daemon/worktree-processes.d.ts +9 -0
- package/dist/src/daemon/worktree-processes.js +151 -0
- package/dist/src/doctor-report.d.ts +3 -5
- package/dist/src/doctor-report.js +6 -13
- package/dist/src/harness-start.d.ts +15 -0
- package/dist/src/harness-start.js +37 -0
- package/dist/src/host/command-wait.d.ts +38 -0
- package/dist/src/host/command-wait.js +115 -0
- package/dist/src/host/start-delivery.d.ts +5 -7
- package/dist/src/host/start-delivery.js +3 -15
- package/dist/src/host/step-bridge-host.d.ts +2 -0
- package/dist/src/host/step-bridge-host.js +21 -0
- package/dist/src/operation-identity.d.ts +1 -0
- package/dist/src/operation-identity.js +4 -0
- package/dist/src/private-config.schema.json +40 -0
- package/dist/src/project-config.schema.json +40 -0
- package/dist/src/project-settings.d.ts +2 -2
- package/dist/src/project-settings.js +3 -1
- package/dist/src/queue-spend.d.ts +4 -0
- package/dist/src/queue-spend.js +21 -0
- package/dist/src/relay/relay-service.d.ts +3 -0
- package/dist/src/relay/relay-service.js +5 -3
- package/dist/src/report-ci.d.ts +164 -0
- package/dist/src/report-ci.js +32 -0
- package/dist/src/report-effects.d.ts +15 -0
- package/dist/src/report-effects.js +212 -0
- package/dist/src/report-flow-types.d.ts +38 -0
- package/dist/src/report-flow-types.js +7 -0
- package/dist/src/report-flow.d.ts +43 -0
- package/dist/src/report-flow.js +193 -0
- package/dist/src/report-operator.d.ts +67 -0
- package/dist/src/report-operator.js +25 -0
- package/dist/src/report-quality.d.ts +221 -0
- package/dist/src/report-quality.js +94 -0
- package/dist/src/report-ready.d.ts +23 -0
- package/dist/src/report-ready.js +117 -0
- package/dist/src/report-statistics.d.ts +2 -1
- package/dist/src/report-statistics.js +40 -4
- package/dist/src/report.d.ts +588 -47
- package/dist/src/report.js +156 -41
- package/dist/src/schedule-progress.d.ts +33 -0
- package/dist/src/schedule-progress.js +45 -0
- package/dist/src/service/active-tickets.js +6 -6
- package/dist/src/service/command-waits.d.ts +34 -0
- package/dist/src/service/command-waits.js +86 -0
- package/dist/src/service/expiry.js +2 -0
- package/dist/src/service/gateway.js +3 -1
- package/dist/src/service/queue-spend.d.ts +6 -0
- package/dist/src/service/queue-spend.js +68 -0
- package/dist/src/service/replies.d.ts +8 -1
- package/dist/src/service/replies.js +11 -0
- package/dist/src/service/run-handlers.js +22 -2
- package/dist/src/service/run-metadata.d.ts +15 -1
- package/dist/src/service/run-metadata.js +93 -8
- package/dist/src/service/service.d.ts +4 -0
- package/dist/src/service/service.js +8 -8
- package/dist/src/service/submission-handler.js +18 -4
- package/dist/src/service/worker-port.js +9 -1
- package/dist/src/service/worker-totals.d.ts +2 -0
- package/dist/src/service/worker-totals.js +1 -1
- package/dist/src/step-bridge.d.ts +20 -0
- package/dist/src/steps/command-wait.d.ts +12 -0
- package/dist/src/steps/command-wait.js +26 -0
- package/dist/src/steps/open-decision-wait.d.ts +2 -0
- package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
- package/dist/src/steps/review-by-person.js +4 -1
- package/dist/src/steps/wait-for-command.d.ts +3 -0
- package/dist/src/steps/wait-for-command.js +17 -0
- package/dist/src/steps/wait-for-decision.js +1 -1
- package/dist/src/storage/command-wait-rows.d.ts +30 -0
- package/dist/src/storage/command-wait-rows.js +96 -0
- package/dist/src/storage/decision-snapshots.d.ts +8 -1
- package/dist/src/storage/decision-snapshots.js +15 -1
- package/dist/src/storage/domain-changes.d.ts +2 -0
- package/dist/src/storage/domain-changes.js +3 -0
- package/dist/src/storage/domain-schema.d.ts +13 -1
- package/dist/src/storage/domain-schema.js +31 -4
- package/dist/src/storage/effect-schema.d.ts +1 -0
- package/dist/src/storage/effect-schema.js +5 -0
- package/dist/src/testing-requests.d.ts +15 -0
- package/dist/src/testing-requests.js +93 -0
- package/dist/src/testing.d.ts +4 -10
- package/dist/src/testing.js +33 -86
- package/dist/src/worker-usage.d.ts +1 -0
- package/dist/src/worker-usage.js +3 -0
- package/dist/src/workflow-candidate.d.ts +1 -0
- package/dist/src/workflow-candidate.js +12 -1
- package/dist/src/workflow-contracts.d.ts +1 -0
- package/dist/src/workflow-contracts.js +9 -2
- package/dist/src/workflow-discovery.d.ts +2 -13
- package/dist/src/workflow-discovery.js +1 -13
- package/dist/src/workflow-refusals.js +0 -1
- package/dist/src/workflows.d.ts +1 -1
- package/dist/src/workflows.js +1 -1
- package/dist/tools/red-green-command.d.ts +1 -0
- package/dist/tools/red-green-command.js +174 -0
- package/dist/tools/red-green-process.d.ts +10 -0
- package/dist/tools/red-green-process.js +59 -0
- package/dist/tools/self-host-processes.d.ts +2 -0
- package/dist/tools/self-host-processes.js +32 -13
- package/dist/tools/self-host-project.js +13 -78
- package/dist/tools/self-host-runtime.js +1 -1
- package/dist/tools/self-host.js +42 -10
- package/dist/tools/template-command.d.ts +1 -0
- package/dist/tools/template-command.js +160 -0
- package/dist/tools/template-copy.d.ts +11 -0
- package/dist/tools/template-copy.js +130 -0
- package/dist/tools/template-files.d.ts +17 -0
- package/dist/tools/template-files.js +74 -0
- package/dist/tools/workflow-bundles.js +1 -0
- package/dist/tools/workflow-candidate-counts.js +2 -2
- package/dist/tools/workflow-candidate-shape.d.ts +3 -0
- package/dist/tools/workflow-candidate-shape.js +90 -0
- package/dist/tools/workflow-candidate-source.js +15 -1
- package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
- package/dist/tools/workflow-candidate-syntax.js +24 -94
- package/dist/tools/workflow-command.js +1 -2
- package/dist/tools/workflow-folders.d.ts +2 -3
- package/dist/tools/workflow-folders.js +1 -1
- package/package.json +16 -8
- package/templates/develop/README.md +12 -0
- package/templates/develop/develop/flow-tests.ts +37 -0
- package/templates/develop/develop/pull.ts +5 -1
- package/templates/develop/develop/refusal-tests.ts +77 -0
- package/templates/develop/develop/turn.ts +2 -1
- package/templates/idea-to-issue/README.md +27 -0
- package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
- package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
- package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
- package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
- package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
- package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
- package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
- package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
- package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
- package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
- package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
- package/templates/idea-to-issue/idea-to-issue.ts +49 -0
- package/templates/idea-to-issue/template.json +81 -0
- package/templates/review-day/README.md +97 -0
- package/templates/review-day/review-day/critique.ts +58 -0
- package/templates/review-day/review-day/decision.ts +29 -0
- package/templates/review-day/review-day/facts.ts +69 -0
- package/templates/review-day/review-day/label.ts +29 -0
- package/templates/review-day/review-day/next.ts +28 -0
- package/templates/review-day/review-day/post.ts +27 -0
- package/templates/review-day/review-day/report.ts +98 -0
- package/templates/review-day/review-day/rules.ts +12 -0
- package/templates/review-day/review-day/scenarios.ts +76 -0
- package/templates/review-day/review-day/turn.ts +73 -0
- package/templates/review-day/review-day.declaration.json +16 -0
- package/templates/review-day/review-day.test.ts +362 -0
- package/templates/review-day/review-day.ts +37 -0
- package/templates/review-day/template.json +56 -0
- package/dist/src/cli/watch-start-view.d.ts +0 -18
- package/dist/src/cli/watch-start-view.js +0 -77
- package/dist/src/steps/decision-gate.d.ts +0 -3
- package/dist/workflows/develop-prd.d.ts +0 -7
- package/dist/workflows/develop-prd.js +0 -165
- package/dist/workflows/develop-ticket.d.ts +0 -9
- package/dist/workflows/develop-ticket.js +0 -207
- package/dist/workflows/issue-labels.d.ts +0 -16
- package/dist/workflows/issue-labels.js +0 -70
- package/dist/workflows/ticket-prompts.d.ts +0 -35
- package/dist/workflows/ticket-prompts.js +0 -147
|
@@ -11,23 +11,36 @@ function attemptOf(row) {
|
|
|
11
11
|
const closed = "NOT EXISTS (SELECT 1 FROM deliveries x WHERE x.operation_id = o.operation_id AND x.outcome IS NULL)";
|
|
12
12
|
const reported = "EXISTS (SELECT 1 FROM stopped_operations s WHERE s.operation_id = o.operation_id)";
|
|
13
13
|
const finalStatuses = "'refused', 'completed', 'failed', 'stopped', 'cancelled'";
|
|
14
|
+
const waitStopSequence = "(SELECT min(e.sequence) FROM command_waits w JOIN events e ON e.run_id = w.run_id WHERE w.check_operation = o.operation_id AND w.state != 'waiting' AND (e.type IN ('run.completed', 'run.failed', 'run.cancelled') OR (json_extract(e.payload, '$.waitName') = w.name AND (e.type = 'command-wait.expired' OR (e.type = 'command-wait.opened' AND round(unixepoch(e.recorded_at, 'subsec') * 1000) >= w.deadline)))))";
|
|
14
15
|
export async function stopping(connection, runId) {
|
|
15
|
-
const rows = await connection.all(`SELECT o.operation_id, o.kind, d.effect_id, coalesce(a.purpose, 'dispatch') AS purpose, d.outcome, d.staged_sequence, p.status, p.receipt, ${reported} AS stopped FROM outbox o JOIN deliveries d ON d.operation_id = o.operation_id LEFT JOIN dispatch_attempts a ON a.effect_id = d.effect_id LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = ? AND ((coalesce(p.status, '') NOT IN (${finalStatuses}) AND NOT ${reported}) OR NOT ${closed}) ORDER BY d.staged_sequence, d.rowid`, [runId]);
|
|
16
|
+
const rows = await connection.all(`SELECT o.operation_id, o.kind, d.effect_id, coalesce(a.purpose, 'dispatch') AS purpose, d.outcome, d.staged_sequence, p.status, p.receipt, ${reported} AS stopped, ${waitStopSequence} AS stop_sequence FROM outbox o JOIN deliveries d ON d.operation_id = o.operation_id LEFT JOIN dispatch_attempts a ON a.effect_id = d.effect_id LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = ? AND ((coalesce(p.status, '') NOT IN (${finalStatuses}) AND NOT ${reported}) OR NOT ${closed}) ORDER BY d.staged_sequence, d.rowid`, [runId]);
|
|
16
17
|
const grouped = Map.groupBy(rows, (row) => text(row, "operation_id"));
|
|
17
18
|
return {
|
|
18
|
-
subjects: [...grouped].map(([operationId, attempts]) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
subjects: [...grouped].map(([operationId, attempts]) => {
|
|
20
|
+
const stopSequence = optionalInteger(attempts[0], "stop_sequence");
|
|
21
|
+
return {
|
|
22
|
+
operationId,
|
|
23
|
+
kind: text(attempts[0], "kind"),
|
|
24
|
+
attempts: attempts.map(attemptOf),
|
|
25
|
+
status: optionalText(attempts[0], "status"),
|
|
26
|
+
receipt: optionalText(attempts[0], "receipt"),
|
|
27
|
+
stopped: integer(attempts[0], "stopped") === 1,
|
|
28
|
+
...(stopSequence === null ? {} : { stopSequence }),
|
|
29
|
+
};
|
|
30
|
+
}),
|
|
26
31
|
cancelledSequence: optionalInteger(await connection.get("SELECT max(sequence) AS sequence FROM events WHERE run_id = ? AND type = 'run.cancelled'", [runId]), "sequence"),
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
34
|
export function turnStopping(connection, scope) {
|
|
30
|
-
const
|
|
35
|
+
const endingWait = Object.values(scope.commandWaits ?? {}).some((wait) => wait.checkOperation !== null &&
|
|
36
|
+
(wait.state !== "waiting" ||
|
|
37
|
+
[
|
|
38
|
+
"expire-command-wait",
|
|
39
|
+
"open-command-wait",
|
|
40
|
+
"complete-run",
|
|
41
|
+
"fail-run",
|
|
42
|
+
].includes(scope.kind)));
|
|
43
|
+
const dropping = scope.status === "cancelled" || scope.kind === "cancel-run" || endingWait;
|
|
31
44
|
return dropping
|
|
32
45
|
? stopping(connection, scope.runId)
|
|
33
46
|
: Promise.resolve(noStopping);
|
|
@@ -37,6 +50,6 @@ const last = "(SELECT d.outcome FROM deliveries d WHERE d.operation_id = o.opera
|
|
|
37
50
|
const effectDue = `o.kind = 'protected-effect' AND p.status NOT IN (${finalStatuses}) AND (p.receipt IS NULL OR ?1 = 1 OR p.status IN ('recovering', 'blocked'))`;
|
|
38
51
|
const runDue = `o.kind != 'protected-effect' AND NOT ${reported} AND ((${sent} AND ?1 = 1) OR (NOT ${sent} AND ${last} NOT IN ('withheld', 'blocked', 'absent')))`;
|
|
39
52
|
export async function droppedRuns(connection, rule) {
|
|
40
|
-
const rows = await connection.all(`SELECT r.run_id FROM runs r WHERE
|
|
53
|
+
const rows = await connection.all(`SELECT r.run_id FROM runs r WHERE EXISTS (SELECT 1 FROM outbox o LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = r.run_id AND (r.status = 'cancelled' OR EXISTS (SELECT 1 FROM command_waits w WHERE w.run_id = r.run_id AND w.check_operation = o.operation_id AND w.state != 'waiting')) AND ${closed} AND ((${effectDue}) OR (${runDue}))) ORDER BY r.run_id LIMIT ?2`, [rule.stopSupported ? 1 : 0, rule.limit]);
|
|
41
54
|
return rows.map((row) => text(row, "run_id"));
|
|
42
55
|
}
|
|
@@ -11,6 +11,7 @@ export interface ChangeDraft {
|
|
|
11
11
|
readonly released: readonly Row[];
|
|
12
12
|
readonly closed: readonly Row[];
|
|
13
13
|
readonly gates: readonly Row[];
|
|
14
|
+
readonly waits: readonly Row[];
|
|
14
15
|
readonly details: readonly Row[];
|
|
15
16
|
readonly documents: readonly Row[];
|
|
16
17
|
readonly budgets: readonly Row[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { commandWaitRows } from "../command-wait-restore.js";
|
|
1
2
|
import { evidenceOf } from "../../authority/operations.js";
|
|
2
3
|
import { detailRecord, gateRecord } from "../decision-restore.js";
|
|
3
4
|
import { effectRows } from "../effect-restore.js";
|
|
@@ -23,6 +24,7 @@ export function unchangedAt(sequence, closed) {
|
|
|
23
24
|
released: [],
|
|
24
25
|
closed,
|
|
25
26
|
gates: [],
|
|
27
|
+
waits: [],
|
|
26
28
|
details: [],
|
|
27
29
|
documents: [],
|
|
28
30
|
...unchangedEffects,
|
|
@@ -78,6 +80,7 @@ export function draft(result, extra) {
|
|
|
78
80
|
closed,
|
|
79
81
|
documents,
|
|
80
82
|
...effectDraft(result, before),
|
|
83
|
+
waits: commandWaitRows(changedEntries(state.commandWaits ?? {}, before.commandWaits ?? {})).map(columns),
|
|
81
84
|
gates: changed(state.gates, before.gates).map((gate) => columns(gateRecord(gate))),
|
|
82
85
|
details: changed(state.details, before.details).map((details) => columns(detailRecord(details))),
|
|
83
86
|
previous: before.sequence,
|
|
@@ -127,6 +130,7 @@ export function merged(first, later) {
|
|
|
127
130
|
released: joined("released"),
|
|
128
131
|
closed: joined("closed"),
|
|
129
132
|
gates: joined("gates"),
|
|
133
|
+
waits: joined("waits"),
|
|
130
134
|
details: joined("details"),
|
|
131
135
|
documents: joined("documents"),
|
|
132
136
|
budgets: joined("budgets"),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readCommandWaits } from "../command-wait-restore.js";
|
|
1
2
|
import { parseCommand } from "../../authority/commands.js";
|
|
2
3
|
import { readExecutionManifest } from "../../execution-pin.js";
|
|
3
4
|
import { operationScope } from "../../operation-identity.js";
|
|
@@ -92,7 +93,7 @@ export async function recoverableIntents(connection) {
|
|
|
92
93
|
return [...intents, ...(await uncertainEffects(connection))];
|
|
93
94
|
}
|
|
94
95
|
async function uncertainEffects(connection) {
|
|
95
|
-
const rows = await connection.all("SELECT p.operation_id, p.run_id, r.workflow FROM operations p JOIN runs r ON r.run_id = p.run_id WHERE p.status IN ('recovering', 'blocked') AND r.status != 'cancelled' AND NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.operation_id = p.operation_id AND d.outcome IS NULL) ORDER BY p.requested_sequence LIMIT ?", [recoveryBatch]);
|
|
96
|
+
const rows = await connection.all("SELECT p.operation_id, p.run_id, r.workflow FROM operations p JOIN runs r ON r.run_id = p.run_id WHERE p.status IN ('recovering', 'blocked') AND r.status != 'cancelled' AND NOT EXISTS (SELECT 1 FROM command_waits w WHERE w.check_operation = p.operation_id AND w.state != 'waiting') AND NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.operation_id = p.operation_id AND d.outcome IS NULL) ORDER BY p.requested_sequence LIMIT ?", [recoveryBatch]);
|
|
96
97
|
const intents = [];
|
|
97
98
|
for (const row of rows)
|
|
98
99
|
intents.push(await pendingEffect(connection, {
|
|
@@ -161,8 +162,10 @@ async function turnState(connection, scope) {
|
|
|
161
162
|
sql: "SELECT operation_id, kind, run_id, created_sequence, released_sequence FROM outbox WHERE run_id = ? AND released_sequence IS NULL ORDER BY created_sequence, rowid",
|
|
162
163
|
parameters: [runId],
|
|
163
164
|
});
|
|
165
|
+
const waits = await readCommandWaits(connection, runId);
|
|
164
166
|
return {
|
|
165
167
|
state: {
|
|
168
|
+
...waits,
|
|
166
169
|
sequence,
|
|
167
170
|
runs: { ...near.runs, ...(stored ? { [runId]: stored } : {}) },
|
|
168
171
|
commands: recorded ? { [commandId]: recorded } : {},
|
|
@@ -178,6 +181,7 @@ async function turnState(connection, scope) {
|
|
|
178
181
|
runId,
|
|
179
182
|
status: stored?.status,
|
|
180
183
|
kind: scope.kind,
|
|
184
|
+
...waits,
|
|
181
185
|
}),
|
|
182
186
|
runId,
|
|
183
187
|
created: recorded
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { commandWaitKey } from "../authority/command-waits.js";
|
|
2
|
+
import { documentDigest } from "./decision-restore.js";
|
|
1
3
|
import { parseCommand } from "../authority/commands.js";
|
|
2
4
|
import { executionCapabilities, readExecutionManifest, resumePinned, } from "../execution-pin.js";
|
|
3
5
|
import { effectInputDigest } from "./effect-restore.js";
|
|
@@ -41,6 +43,61 @@ const commandEffect = (command, context) => {
|
|
|
41
43
|
return { kind: "rejected", reason: "command-not-declared" };
|
|
42
44
|
return unpinned(command, context);
|
|
43
45
|
};
|
|
46
|
+
function waitMatches(command, wait) {
|
|
47
|
+
return (command.target === wait.command &&
|
|
48
|
+
command.maximumWaitSeconds === wait.maximumWaitSeconds &&
|
|
49
|
+
command.pollEverySeconds === wait.pollEverySeconds);
|
|
50
|
+
}
|
|
51
|
+
const commandWait = (command, context) => {
|
|
52
|
+
const pin = context.state.runs[command.runId]?.workflow ?? "";
|
|
53
|
+
const declaration = context.definitions.get(pin);
|
|
54
|
+
const name = String(command.waitName);
|
|
55
|
+
const waits = declaration?.commandWaits ?? {};
|
|
56
|
+
const wait = Object.hasOwn(waits, name) ? waits[name] : undefined;
|
|
57
|
+
if (wait === undefined || !waitMatches(command, wait))
|
|
58
|
+
return { kind: "rejected", reason: "command-wait-not-declared" };
|
|
59
|
+
return unpinned(command, context);
|
|
60
|
+
};
|
|
61
|
+
function declaredWaitCheck(command, context) {
|
|
62
|
+
const pin = context.state.runs[command.runId]?.workflow ?? "";
|
|
63
|
+
const declarations = context.definitions.get(pin)?.commandWaits ?? {};
|
|
64
|
+
const name = String(command.waitName);
|
|
65
|
+
const declaration = Object.hasOwn(declarations, name)
|
|
66
|
+
? declarations[name]
|
|
67
|
+
: undefined;
|
|
68
|
+
const wait = context.state.commandWaits?.[commandWaitKey(command.runId, name)];
|
|
69
|
+
if (declaration === undefined || wait === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
return waitMatches({
|
|
72
|
+
...command,
|
|
73
|
+
target: wait.command,
|
|
74
|
+
maximumWaitSeconds: wait.maximumWaitSeconds,
|
|
75
|
+
pollEverySeconds: wait.pollEverySeconds,
|
|
76
|
+
}, declaration)
|
|
77
|
+
? wait
|
|
78
|
+
: undefined;
|
|
79
|
+
}
|
|
80
|
+
const commandWaitCheck = (command, context) => {
|
|
81
|
+
const wait = declaredWaitCheck(command, context);
|
|
82
|
+
if (wait === undefined)
|
|
83
|
+
return { kind: "rejected", reason: "command-wait-not-declared" };
|
|
84
|
+
const computed = effectInputDigest({
|
|
85
|
+
capability: "command",
|
|
86
|
+
method: `run:${wait.command}`,
|
|
87
|
+
target: wait.command,
|
|
88
|
+
payload: wait.inputDigest,
|
|
89
|
+
});
|
|
90
|
+
return command.inputDigest === computed
|
|
91
|
+
? unpinned(command, context)
|
|
92
|
+
: { kind: "rejected", reason: "input-digest-mismatch" };
|
|
93
|
+
};
|
|
94
|
+
const commandReport = (command, context) => {
|
|
95
|
+
const output = command.commandOutput;
|
|
96
|
+
if (output !== undefined &&
|
|
97
|
+
(command.worker !== "command" || documentDigest(output) !== command.result))
|
|
98
|
+
return { kind: "rejected", reason: "command-output-mismatch" };
|
|
99
|
+
return unpinned(command, context);
|
|
100
|
+
};
|
|
44
101
|
const handoffEffect = (command, context) => {
|
|
45
102
|
if (command.capability !== "handoff")
|
|
46
103
|
return unpinned(command, context);
|
|
@@ -67,6 +124,9 @@ const effectRequest = (command, context) => {
|
|
|
67
124
|
};
|
|
68
125
|
const rules = {
|
|
69
126
|
"request-effect": effectRequest,
|
|
127
|
+
"check-command-wait": commandWaitCheck,
|
|
128
|
+
"report-effect": commandReport,
|
|
129
|
+
"open-command-wait": commandWait,
|
|
70
130
|
"admit-run": admission,
|
|
71
131
|
"decide-gate": resumption,
|
|
72
132
|
"resume-run": resumption,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommandWaitRow } from "../storage/command-wait-rows.ts";
|
|
1
2
|
import type { AuthorityEvent, AuthorityState, EffectIntent, RunState } from "../authority/reducer.ts";
|
|
2
3
|
import type { DecisionRows, DocumentRow } from "../storage/decision-rows.ts";
|
|
3
4
|
import type { EffectRows } from "../storage/effect-rows.ts";
|
|
@@ -7,6 +8,7 @@ import type { ExecutionManifest } from "../workflow-admission.ts";
|
|
|
7
8
|
export type RestoredSnapshot = AuthoritySnapshot & Partial<DecisionRows> & {
|
|
8
9
|
readonly documents?: readonly DocumentRow[];
|
|
9
10
|
readonly effects?: EffectRows;
|
|
11
|
+
readonly waits?: readonly CommandWaitRow[];
|
|
10
12
|
};
|
|
11
13
|
export type Restoration = {
|
|
12
14
|
readonly kind: "restored";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { commandWaitState, commandWaitRows, commandOutputsIntact, } from "./command-wait-restore.js";
|
|
1
2
|
import { isDeepStrictEqual } from "node:util";
|
|
2
3
|
import { parseEvent } from "../authority/events.js";
|
|
3
4
|
import { rebuildHistory } from "../authority/history.js";
|
|
@@ -51,6 +52,7 @@ export function restore(snapshot) {
|
|
|
51
52
|
isDeepStrictEqual(Object.fromEntries(snapshot.runs.map((row) => [row.runId, row.sequence])), history.runSequences),
|
|
52
53
|
],
|
|
53
54
|
["definitions", definitionsIntact(definitions, state)],
|
|
55
|
+
...waitChecks(snapshot, { state, definitions, events }),
|
|
54
56
|
...decisionChecks(snapshot, state),
|
|
55
57
|
...(snapshot.effects === undefined
|
|
56
58
|
? []
|
|
@@ -68,6 +70,21 @@ export function restore(snapshot) {
|
|
|
68
70
|
};
|
|
69
71
|
return { kind: "restored", state, events, definitions };
|
|
70
72
|
}
|
|
73
|
+
function waitChecks(snapshot, context) {
|
|
74
|
+
const { state, definitions } = context;
|
|
75
|
+
return [
|
|
76
|
+
["command-outputs", commandOutputsIntact(context.events)],
|
|
77
|
+
["command-waits", commandWaitsIntact(definitions, state)],
|
|
78
|
+
...(snapshot.waits === undefined
|
|
79
|
+
? []
|
|
80
|
+
: [
|
|
81
|
+
[
|
|
82
|
+
"waits",
|
|
83
|
+
isDeepStrictEqual(snapshot.waits, commandWaitRows(state.commandWaits ?? {})),
|
|
84
|
+
],
|
|
85
|
+
]),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
71
88
|
function decisionChecks(snapshot, state) {
|
|
72
89
|
const { gates, details, documents } = snapshot;
|
|
73
90
|
if (gates === undefined || details === undefined || documents === undefined)
|
|
@@ -95,12 +112,29 @@ function definitionsIntact(definitions, state) {
|
|
|
95
112
|
const used = new Set(Object.values(state.runs).map((run) => run.workflow));
|
|
96
113
|
return ([...definitions].every(([pin, manifest]) => workflowPin(manifest) === pin && used.has(pin)) && [...used].every((pin) => definitions.has(pin)));
|
|
97
114
|
}
|
|
115
|
+
function commandWaitsIntact(definitions, state) {
|
|
116
|
+
return Object.values(state.commandWaits ?? {}).every((wait) => {
|
|
117
|
+
const pin = state.runs[wait.runId]?.workflow ?? "";
|
|
118
|
+
const waits = definitions.get(pin)?.commandWaits ?? {};
|
|
119
|
+
const permission = Object.hasOwn(waits, wait.name)
|
|
120
|
+
? waits[wait.name]
|
|
121
|
+
: undefined;
|
|
122
|
+
return (permission !== undefined &&
|
|
123
|
+
permission.command === wait.command &&
|
|
124
|
+
permission.maximumWaitSeconds === wait.maximumWaitSeconds &&
|
|
125
|
+
permission.pollEverySeconds === wait.pollEverySeconds);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
98
128
|
const projectionParts = ["sequence", "runs", "commands", "intents"];
|
|
99
129
|
function text(error) {
|
|
100
130
|
return String(Reflect.get(Object(error), "message"));
|
|
101
131
|
}
|
|
102
132
|
export function currentState(snapshot) {
|
|
103
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
...projectedState(snapshot),
|
|
135
|
+
...decisionState(snapshot),
|
|
136
|
+
...commandWaitState(snapshot.waits ?? []),
|
|
137
|
+
};
|
|
104
138
|
}
|
|
105
139
|
export function projectedState(snapshot) {
|
|
106
140
|
return {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Database } from "solid-objects";
|
|
2
|
+
import { type CommandWaitsState } from "../authority/command-waits.ts";
|
|
3
|
+
import type { AuthorityEvent } from "../authority/transitions.ts";
|
|
4
|
+
import type { DispatchRead } from "../authority/dispatch-decision.ts";
|
|
5
|
+
import type { AuthorityState } from "../authority/transitions.ts";
|
|
6
|
+
import { type CommandWaitRow } from "../storage/command-wait-rows.ts";
|
|
7
|
+
import type { SqlConnection } from "../storage/domain-rows.ts";
|
|
8
|
+
export declare function commandWaitRows(waits: CommandWaitsState): readonly CommandWaitRow[];
|
|
9
|
+
export declare function commandWaitState(rows: readonly CommandWaitRow[]): {
|
|
10
|
+
readonly commandWaits?: CommandWaitsState;
|
|
11
|
+
};
|
|
12
|
+
export declare function readCommandWaits(connection: SqlConnection, runId: string): Promise<{
|
|
13
|
+
readonly commandWaits?: CommandWaitsState;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function upgradeCommandWaits(database: Database, state: AuthorityState): Promise<void>;
|
|
16
|
+
export declare function upgradeCommandWaitChecks(database: Database): Promise<void>;
|
|
17
|
+
export declare function commandWaitDeadline(waits: CommandWaitsState, operationId: string): {
|
|
18
|
+
readonly commandDeadline?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function dispatchCommandWait(connection: SqlConnection, check: {
|
|
21
|
+
readonly runId: string;
|
|
22
|
+
readonly workflow: string;
|
|
23
|
+
readonly operationId: string;
|
|
24
|
+
}): Promise<Pick<DispatchRead, "commandWait">>;
|
|
25
|
+
export declare function commandOutputsIntact(events: readonly AuthorityEvent[]): boolean;
|
|
26
|
+
export declare function readCommandWaitDeadline(connection: SqlConnection, scope: {
|
|
27
|
+
readonly runId: string;
|
|
28
|
+
readonly operationId: string;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
readonly commandDeadline?: string;
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { commandWaitKey, } from "../authority/command-waits.js";
|
|
2
|
+
import { operationPhase } from "../operation-identity.js";
|
|
3
|
+
import { documentDigest } from "./decision-restore.js";
|
|
4
|
+
import { readExecutionManifest } from "../execution-pin.js";
|
|
5
|
+
import { text } from "../storage/domain-rows.js";
|
|
6
|
+
import { commandWaitStatements, loadCommandWaits, } from "../storage/command-wait-rows.js";
|
|
7
|
+
import { checkedVersion, waitingVersion, upgradeSchema, } from "../storage/domain-schema.js";
|
|
8
|
+
export function commandWaitRows(waits) {
|
|
9
|
+
return Object.values(waits).sort((left, right) => {
|
|
10
|
+
if (left.runId !== right.runId)
|
|
11
|
+
return left.runId < right.runId ? -1 : 1;
|
|
12
|
+
return left.name < right.name ? -1 : 1;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function commandWaitState(rows) {
|
|
16
|
+
const waits = {};
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const state = [
|
|
19
|
+
"waiting",
|
|
20
|
+
"timed-out",
|
|
21
|
+
"cancelled",
|
|
22
|
+
"completed",
|
|
23
|
+
"failed",
|
|
24
|
+
];
|
|
25
|
+
const kind = state.find((candidate) => candidate === row.state);
|
|
26
|
+
if (kind === undefined)
|
|
27
|
+
throw new Error("invalid-command-wait-state");
|
|
28
|
+
waits[commandWaitKey(row.runId, row.name)] = { ...row, state: kind };
|
|
29
|
+
}
|
|
30
|
+
return rows.length === 0 ? {} : { commandWaits: waits };
|
|
31
|
+
}
|
|
32
|
+
export async function readCommandWaits(connection, runId) {
|
|
33
|
+
return commandWaitState(await loadCommandWaits(connection, { runId }));
|
|
34
|
+
}
|
|
35
|
+
export async function upgradeCommandWaits(database, state) {
|
|
36
|
+
await database.transaction(async (connection) => {
|
|
37
|
+
await upgradeSchema(connection, waitingVersion);
|
|
38
|
+
for (const statement of commandWaitStatements(commandWaitRows(state.commandWaits ?? {}), { version: "opened" }))
|
|
39
|
+
await connection.run(statement.sql, statement.parameters);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export async function upgradeCommandWaitChecks(database) {
|
|
43
|
+
await database.transaction(async (connection) => {
|
|
44
|
+
await upgradeSchema(connection, checkedVersion);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function commandWaitDeadline(waits, operationId) {
|
|
48
|
+
const wait = Object.values(waits).find((wait) => wait.checkOperation === operationId);
|
|
49
|
+
return wait === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { commandDeadline: new Date(wait.deadline).toISOString() };
|
|
52
|
+
}
|
|
53
|
+
export async function dispatchCommandWait(connection, check) {
|
|
54
|
+
if (operationPhase(check.operationId) !== "command-wait")
|
|
55
|
+
return {};
|
|
56
|
+
const state = await readCommandWaits(connection, check.runId);
|
|
57
|
+
const wait = Object.values(state.commandWaits ?? {}).find((wait) => wait.checkOperation === check.operationId);
|
|
58
|
+
if (wait === undefined)
|
|
59
|
+
return {};
|
|
60
|
+
const row = await connection.get("SELECT manifest FROM definitions WHERE pin = ?", [check.workflow]);
|
|
61
|
+
const declarations = readExecutionManifest(JSON.parse(text(row, "manifest"))).commandWaits ?? {};
|
|
62
|
+
const permission = Object.hasOwn(declarations, wait.name)
|
|
63
|
+
? declarations[wait.name]
|
|
64
|
+
: undefined;
|
|
65
|
+
return { commandWait: { wait, declared: declaredWait(wait, permission) } };
|
|
66
|
+
}
|
|
67
|
+
function declaredWait(wait, permission) {
|
|
68
|
+
return (permission !== undefined &&
|
|
69
|
+
permission.command === wait.command &&
|
|
70
|
+
permission.maximumWaitSeconds === wait.maximumWaitSeconds &&
|
|
71
|
+
permission.pollEverySeconds === wait.pollEverySeconds);
|
|
72
|
+
}
|
|
73
|
+
export function commandOutputsIntact(events) {
|
|
74
|
+
return events.every((event) => {
|
|
75
|
+
const output = event.payload["commandOutput"];
|
|
76
|
+
return (output === undefined ||
|
|
77
|
+
(event.payload["worker"] === "command" &&
|
|
78
|
+
documentDigest(output) === event.payload["result"]));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function readCommandWaitDeadline(connection, scope) {
|
|
82
|
+
if (operationPhase(scope.operationId) !== "command-wait")
|
|
83
|
+
return {};
|
|
84
|
+
const state = await readCommandWaits(connection, scope.runId);
|
|
85
|
+
return commandWaitDeadline(state.commandWaits ?? {}, scope.operationId);
|
|
86
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { RunDetails } from "../authority/details.ts";
|
|
|
2
2
|
import { type GateState } from "../authority/gates.ts";
|
|
3
3
|
import type { AuthorityState } from "../authority/reducer.ts";
|
|
4
4
|
import type { DecisionRows, DetailRow, DocumentRow, GateRow } from "../storage/decision-rows.ts";
|
|
5
|
-
export type DecisionState = Pick<AuthorityState, "gates" | "details" | "questions">;
|
|
5
|
+
export type DecisionState = Pick<AuthorityState, "gates" | "details" | "questions" | "commandWaits">;
|
|
6
6
|
export declare function documentDigest(body: string): string;
|
|
7
7
|
export declare function gateRecord(gate: GateState): GateRow;
|
|
8
8
|
export declare function detailRecord(details: RunDetails): DetailRow;
|
|
@@ -141,6 +141,7 @@ export function decisionRows(state) {
|
|
|
141
141
|
}
|
|
142
142
|
export function referencedDigests(state) {
|
|
143
143
|
return [
|
|
144
|
+
...Object.values(state.commandWaits ?? {}).map((wait) => wait.inputDigest),
|
|
144
145
|
...Object.values(state.gates).flatMap((gate) => [
|
|
145
146
|
gate.prompt,
|
|
146
147
|
gate.outcome?.answer ?? null,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { Database } from "solid-objects";
|
|
2
2
|
import type { AuthorityState } from "../authority/reducer.ts";
|
|
3
3
|
export declare function upgradeEffects(database: Database, state: AuthorityState): Promise<void>;
|
|
4
|
+
export declare function upgradeSpendingIndexes(database: Database): Promise<void>;
|
|
5
|
+
export declare function upgradeEventIndexes(database: Database): Promise<void>;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { currentVersion, upgradeSchema } from "../storage/domain-schema.js";
|
|
1
|
+
import { currentVersion, effectedVersion, spendingVersion, upgradeSchema, } from "../storage/domain-schema.js";
|
|
2
2
|
import { persistEffects } from "../storage/effect-changes.js";
|
|
3
3
|
import { effectRows } from "./effect-restore.js";
|
|
4
4
|
export async function upgradeEffects(database, state) {
|
|
5
5
|
await database.transaction(async (connection) => {
|
|
6
|
-
await upgradeSchema(connection,
|
|
6
|
+
await upgradeSchema(connection, effectedVersion);
|
|
7
7
|
await persistEffects(connection, effectRows(state.effects, []));
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
+
export async function upgradeSpendingIndexes(database) {
|
|
11
|
+
await database.transaction((connection) => upgradeSchema(connection, spendingVersion));
|
|
12
|
+
}
|
|
13
|
+
export async function upgradeEventIndexes(database) {
|
|
14
|
+
await database.transaction((connection) => upgradeSchema(connection, currentVersion));
|
|
15
|
+
}
|
|
@@ -9,6 +9,19 @@ export type Invocation = {
|
|
|
9
9
|
readonly kind: "help";
|
|
10
10
|
} | {
|
|
11
11
|
readonly kind: "version";
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "mcp";
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: "mcp-help";
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "init-prompt";
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "init";
|
|
20
|
+
readonly harness: string | undefined;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: "init-prompt-help";
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "init-help";
|
|
12
25
|
} | {
|
|
13
26
|
readonly kind: "doctor-help";
|
|
14
27
|
} | {
|
|
@@ -26,6 +26,7 @@ const options = {
|
|
|
26
26
|
follow: { type: "boolean" },
|
|
27
27
|
since: { type: "string" },
|
|
28
28
|
repository: { type: "string" },
|
|
29
|
+
harness: { type: "string" },
|
|
29
30
|
};
|
|
30
31
|
const textOptions = ["--prompt", "--text"];
|
|
31
32
|
function knownFlag(token) {
|
|
@@ -83,8 +84,9 @@ function selectedInvocation(given) {
|
|
|
83
84
|
const [command, ...remaining] = positionals;
|
|
84
85
|
if (command === undefined)
|
|
85
86
|
return topLevel(values);
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const inspection = inspectionInvocation(command, { values, remaining });
|
|
88
|
+
if (inspection !== undefined)
|
|
89
|
+
return inspection;
|
|
88
90
|
if (command === "workflows" && remaining[0] === "validate")
|
|
89
91
|
return validation(values, remaining.slice(1));
|
|
90
92
|
const outcome = runtimeInvocation({
|
|
@@ -96,6 +98,43 @@ function selectedInvocation(given) {
|
|
|
96
98
|
return { kind: "usage-error", code: outcome, json };
|
|
97
99
|
return outcome;
|
|
98
100
|
}
|
|
101
|
+
function inspectionInvocation(command, input) {
|
|
102
|
+
const { values, remaining } = input;
|
|
103
|
+
if (command === "init") {
|
|
104
|
+
if (unexpected(values, ["harness", "help"]))
|
|
105
|
+
return { kind: "usage-error", code: "unexpected-option", json: false };
|
|
106
|
+
if (remaining.length > 0)
|
|
107
|
+
return { kind: "usage-error", code: "unexpected-argument", json: false };
|
|
108
|
+
return values.help
|
|
109
|
+
? { kind: "init-help" }
|
|
110
|
+
: { kind: "init", harness: values.harness };
|
|
111
|
+
}
|
|
112
|
+
if (command === "doctor")
|
|
113
|
+
return doctorInvocation(values, remaining);
|
|
114
|
+
if (command === "mcp")
|
|
115
|
+
return mcpInvocation(values, remaining);
|
|
116
|
+
if (command === "init-prompt")
|
|
117
|
+
return promptInvocation(values, remaining);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function mcpInvocation(values, remaining) {
|
|
121
|
+
if (unexpected(values, ["help"]))
|
|
122
|
+
return { kind: "usage-error", code: "unexpected-option", json: false };
|
|
123
|
+
if (remaining.length > 0)
|
|
124
|
+
return { kind: "usage-error", code: "unexpected-argument", json: false };
|
|
125
|
+
return { kind: values.help ? "mcp-help" : "mcp" };
|
|
126
|
+
}
|
|
127
|
+
function promptInvocation(values, remaining) {
|
|
128
|
+
if (unexpected(values, ["help"]))
|
|
129
|
+
return {
|
|
130
|
+
kind: "usage-error",
|
|
131
|
+
code: "unexpected-option",
|
|
132
|
+
json: values.json === true,
|
|
133
|
+
};
|
|
134
|
+
if (remaining.length > 0)
|
|
135
|
+
return { kind: "usage-error", code: "unexpected-argument", json: false };
|
|
136
|
+
return { kind: values.help ? "init-prompt-help" : "init-prompt" };
|
|
137
|
+
}
|
|
99
138
|
function doctorInvocation(values, remaining) {
|
|
100
139
|
const json = values.json === true;
|
|
101
140
|
if (unexpected(values, ["help", "json"]))
|
|
@@ -134,7 +173,7 @@ function knownCommand(positionals) {
|
|
|
134
173
|
return true;
|
|
135
174
|
if (command === "workflows")
|
|
136
175
|
return subcommand === "validate";
|
|
137
|
-
if (
|
|
176
|
+
if (["doctor", "init-prompt", "init", "mcp"].includes(command))
|
|
138
177
|
return true;
|
|
139
178
|
return isRuntimeCommand(command);
|
|
140
179
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { lstat, opendir } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
const knownStores = [
|
|
4
|
+
{
|
|
5
|
+
name: "Claude Code",
|
|
6
|
+
folder: ".claude/projects",
|
|
7
|
+
format: "JSONL",
|
|
8
|
+
documentation: "https://code.claude.com/docs/en/claude-directory",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "Codex",
|
|
12
|
+
folder: ".codex/sessions",
|
|
13
|
+
format: "JSONL, including zstd-compressed .jsonl.zst (read with zstd -dc)",
|
|
14
|
+
documentation: "https://learn.chatgpt.com/docs/security/sdk",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "Gemini CLI",
|
|
18
|
+
folder: ".gemini",
|
|
19
|
+
format: "JSON conversation history and checkpoints under tmp/<project>/",
|
|
20
|
+
documentation: "https://geminicli.com/docs/cli/checkpointing/",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "opencode",
|
|
24
|
+
folder: ".local/share/opencode",
|
|
25
|
+
format: "SQLite (opencode.db); older versions use JSON files under storage/",
|
|
26
|
+
documentation: "https://docs.opencode.ai/docs/troubleshooting/",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "grok",
|
|
30
|
+
folder: ".grok",
|
|
31
|
+
format: "JSONL ACP events in sessions/<encoded-cwd>/<session-id>/updates.jsonl; raw messages in chat_history.jsonl; JSON metadata",
|
|
32
|
+
documentation: "https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-pager/docs/user-guide/17-sessions.md",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "Cursor Agent",
|
|
36
|
+
folder: ".cursor",
|
|
37
|
+
format: "JSONL under projects/<project>/agent-transcripts/ (older transcripts may be text)",
|
|
38
|
+
documentation: "https://forum.cursor.com/t/accessing-the-full-agent-transcript-in-cursor/157311/5",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
async function metadata(path) {
|
|
42
|
+
return lstat(path).catch((error) => {
|
|
43
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
44
|
+
return null;
|
|
45
|
+
throw error;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function* storeFiles(path) {
|
|
49
|
+
const pending = [path];
|
|
50
|
+
for (let folder = pending.pop(); folder !== undefined; folder = pending.pop()) {
|
|
51
|
+
const directory = await opendir(folder);
|
|
52
|
+
for await (const entry of directory) {
|
|
53
|
+
const file = join(folder, entry.name);
|
|
54
|
+
if (entry.isDirectory())
|
|
55
|
+
pending.push(file);
|
|
56
|
+
if (!entry.isFile())
|
|
57
|
+
continue;
|
|
58
|
+
const info = await metadata(file);
|
|
59
|
+
if (info?.isFile())
|
|
60
|
+
yield info;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function measure(path) {
|
|
65
|
+
let fileCount = 0;
|
|
66
|
+
let totalBytes = 0;
|
|
67
|
+
let oldest = null;
|
|
68
|
+
let newest = null;
|
|
69
|
+
for await (const info of storeFiles(path)) {
|
|
70
|
+
fileCount += 1;
|
|
71
|
+
totalBytes += info.size;
|
|
72
|
+
oldest = Math.min(oldest ?? info.mtimeMs, info.mtimeMs);
|
|
73
|
+
newest = Math.max(newest ?? info.mtimeMs, info.mtimeMs);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
fileCount,
|
|
77
|
+
totalBytes,
|
|
78
|
+
oldestFileDate: oldest === null ? null : new Date(oldest).toISOString(),
|
|
79
|
+
newestFileDate: newest === null ? null : new Date(newest).toISOString(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function inspectStore(path) {
|
|
83
|
+
try {
|
|
84
|
+
if (!(await metadata(path))?.isDirectory())
|
|
85
|
+
return null;
|
|
86
|
+
return await measure(path);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
unavailable: error instanceof Error &&
|
|
91
|
+
"code" in error &&
|
|
92
|
+
typeof error.code === "string"
|
|
93
|
+
? error.code
|
|
94
|
+
: "scan-failed",
|
|
95
|
+
fileCount: null,
|
|
96
|
+
totalBytes: null,
|
|
97
|
+
oldestFileDate: null,
|
|
98
|
+
newestFileDate: null,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function chatStores(home) {
|
|
103
|
+
if (!home || !isAbsolute(home))
|
|
104
|
+
return [];
|
|
105
|
+
const stores = [];
|
|
106
|
+
for (const { folder, ...store } of knownStores) {
|
|
107
|
+
const path = join(home, folder);
|
|
108
|
+
const facts = await inspectStore(path);
|
|
109
|
+
if (facts)
|
|
110
|
+
stores.push({ ...store, path, ...facts });
|
|
111
|
+
}
|
|
112
|
+
return stores;
|
|
113
|
+
}
|