cloving 1.1.0 → 1.3.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 +584 -144
- package/assets/watch-fonts/fira-code-OFL.md +93 -0
- package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-OFL.md +93 -0
- package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
- package/assets/watch-fonts/manifest.json +112 -0
- package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
- package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- package/dist/src/agent-harness-shell.d.ts +12 -0
- package/dist/src/agent-harness-shell.js +90 -0
- package/dist/src/agent-harness-versions.d.ts +8 -0
- package/dist/src/agent-harness-versions.js +49 -0
- package/dist/src/agent-harnesses.d.ts +4 -0
- package/dist/src/agent-harnesses.js +13 -0
- package/dist/src/authority/commands.d.ts +2 -0
- package/dist/src/authority/commands.js +49 -8
- package/dist/src/authority/dispatch-decision.d.ts +1 -0
- package/dist/src/authority/dispatch-decision.js +7 -4
- package/dist/src/authority/events.js +2 -2
- package/dist/src/authority/gate-reduction.d.ts +1 -0
- package/dist/src/authority/gate-reduction.js +18 -3
- package/dist/src/authority/gates.d.ts +2 -1
- package/dist/src/authority/gates.js +5 -1
- package/dist/src/authority/handoff-chain.d.ts +24 -0
- package/dist/src/authority/handoff-chain.js +46 -0
- package/dist/src/authority/operations.js +1 -1
- package/dist/src/authority/reconciliation.d.ts +1 -0
- package/dist/src/authority/reconciliation.js +18 -11
- package/dist/src/authority/reduction.d.ts +1 -1
- package/dist/src/authority/reduction.js +6 -1
- package/dist/src/authority/status-input.d.ts +1 -1
- package/dist/src/authority/status.d.ts +1 -0
- package/dist/src/authority/status.js +27 -3
- package/dist/src/authority/transitions.d.ts +5 -5
- package/dist/src/authority/transitions.js +9 -0
- package/dist/src/broker/actor/authority-delivery.js +1 -0
- package/dist/src/broker/actor/authority-queries.d.ts +11 -1
- package/dist/src/broker/actor/authority-queries.js +67 -3
- package/dist/src/broker/actor/authority-receipts.js +11 -1
- package/dist/src/broker/actor/authority-session.js +2 -2
- package/dist/src/broker/actor/authority-settings.js +4 -0
- package/dist/src/broker/actor/delivery-intents.d.ts +3 -1
- package/dist/src/broker/actor/delivery-intents.js +4 -4
- package/dist/src/broker/actor/effect-delivery.js +13 -1
- package/dist/src/broker/actor/effect-queries.d.ts +1 -0
- package/dist/src/broker/actor/effect-queries.js +7 -0
- package/dist/src/broker/actor/pin-check.js +1 -1
- package/dist/src/broker/actor/stop-plan.js +1 -1
- package/dist/src/broker/actor/turn-state.js +1 -1
- package/dist/src/broker/authority-admission.js +26 -5
- package/dist/src/broker/authority-restore.js +1 -0
- package/dist/src/broker/decision-restore.js +4 -4
- package/dist/src/broker/socket-address.d.ts +3 -1
- package/dist/src/broker/socket-address.js +2 -2
- package/dist/src/cli/arguments.d.ts +11 -13
- package/dist/src/cli/arguments.js +37 -107
- package/dist/src/cli/command-effects.d.ts +7 -0
- package/dist/src/cli/command-effects.js +39 -0
- package/dist/src/cli/commands.js +11 -4
- package/dist/src/cli/config-arguments.d.ts +11 -0
- package/dist/src/cli/config-arguments.js +59 -0
- package/dist/src/cli/config-command.d.ts +2 -0
- package/dist/src/cli/config-command.js +105 -0
- package/dist/src/cli/config-file.d.ts +3 -0
- package/dist/src/cli/config-file.js +50 -0
- package/dist/src/cli/config-lock.d.ts +7 -0
- package/dist/src/cli/config-lock.js +61 -0
- package/dist/src/cli/config-notice.d.ts +5 -0
- package/dist/src/cli/config-notice.js +46 -0
- package/dist/src/cli/doctor-github.d.ts +9 -0
- package/dist/src/cli/doctor-github.js +138 -0
- package/dist/src/cli/doctor-harness.d.ts +13 -0
- package/dist/src/cli/doctor-harness.js +242 -0
- package/dist/src/cli/doctor-local.d.ts +18 -0
- package/dist/src/cli/doctor-local.js +77 -0
- package/dist/src/cli/doctor-runtime.d.ts +15 -0
- package/dist/src/cli/doctor-runtime.js +176 -0
- package/dist/src/cli/doctor-service.d.ts +1 -0
- package/dist/src/cli/doctor-service.js +11 -0
- package/dist/src/cli/doctor-workflows.d.ts +3 -0
- package/dist/src/cli/doctor-workflows.js +45 -0
- package/dist/src/cli/doctor.d.ts +15 -0
- package/dist/src/cli/doctor.js +190 -0
- package/dist/src/cli/github-ci-progress.d.ts +8 -0
- package/dist/src/cli/github-ci-progress.js +83 -0
- package/dist/src/cli/github-retry.d.ts +6 -0
- package/dist/src/cli/github-retry.js +47 -0
- package/dist/src/cli/harness-probes.d.ts +22 -0
- package/dist/src/cli/harness-probes.js +183 -0
- package/dist/src/cli/main.d.ts +7 -1
- package/dist/src/cli/main.js +39 -61
- package/dist/src/cli/phone-arguments.d.ts +14 -0
- package/dist/src/cli/phone-arguments.js +37 -0
- package/dist/src/cli/phone-client.d.ts +2 -0
- package/dist/src/cli/phone-client.js +32 -0
- package/dist/src/cli/phone-command.d.ts +13 -0
- package/dist/src/cli/phone-command.js +101 -0
- package/dist/src/cli/probe-command.d.ts +36 -0
- package/dist/src/cli/probe-command.js +59 -0
- package/dist/src/cli/project-settings.d.ts +4 -0
- package/dist/src/cli/project-settings.js +33 -0
- package/dist/src/cli/render.d.ts +3 -1
- package/dist/src/cli/render.js +102 -39
- package/dist/src/cli/report-github.d.ts +18 -0
- package/dist/src/cli/report-github.js +200 -0
- package/dist/src/cli/report.d.ts +17 -0
- package/dist/src/cli/report.js +192 -0
- package/dist/src/cli/repository-arguments.d.ts +5 -0
- package/dist/src/cli/repository-arguments.js +34 -0
- package/dist/src/cli/runtime-arguments.d.ts +4 -0
- package/dist/src/cli/runtime-arguments.js +15 -0
- package/dist/src/cli/runtime-directory.d.ts +6 -2
- package/dist/src/cli/runtime-directory.js +72 -14
- package/dist/src/cli/toolchain.d.ts +2 -0
- package/dist/src/cli/toolchain.js +22 -2
- package/dist/src/cli/usage-render.d.ts +5 -0
- package/dist/src/cli/usage-render.js +40 -1
- package/dist/src/cli/usage.d.ts +3 -3
- package/dist/src/cli/usage.js +74 -45
- package/dist/src/cli/watch-actions.d.ts +2 -0
- package/dist/src/cli/watch-actions.js +4 -4
- package/dist/src/cli/watch-board-view.d.ts +12 -1
- package/dist/src/cli/watch-board-view.js +64 -12
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +61 -16
- package/dist/src/cli/watch-card-forms.js +2 -2
- package/dist/src/cli/watch-client.d.ts +1 -1
- package/dist/src/cli/watch-client.js +12 -3
- package/dist/src/cli/watch-header-view.d.ts +27 -0
- package/dist/src/cli/watch-header-view.js +116 -0
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +49 -59
- package/dist/src/cli/watch-page.js +67 -14
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-phone-client.d.ts +1 -0
- package/dist/src/cli/watch-phone-client.js +84 -0
- package/dist/src/cli/watch-phone-view.d.ts +56 -0
- package/dist/src/cli/watch-phone-view.js +113 -0
- package/dist/src/cli/watch-phone.d.ts +5 -0
- package/dist/src/cli/watch-phone.js +53 -0
- package/dist/src/cli/watch-pr-command.d.ts +5 -2
- package/dist/src/cli/watch-pr-command.js +41 -4
- package/dist/src/cli/watch-pr-identity.d.ts +14 -2
- package/dist/src/cli/watch-pr-identity.js +25 -0
- package/dist/src/cli/watch-pr-observer.d.ts +1 -0
- package/dist/src/cli/watch-pr-observer.js +48 -6
- package/dist/src/cli/watch-pull-request.d.ts +12 -0
- package/dist/src/cli/watch-pull-request.js +134 -0
- package/dist/src/cli/watch-reads.d.ts +7 -1
- package/dist/src/cli/watch-reads.js +42 -20
- package/dist/src/cli/watch-render.d.ts +23 -2
- package/dist/src/cli/watch-render.js +2 -1
- package/dist/src/cli/watch-server.js +56 -7
- package/dist/src/cli/watch-turn.d.ts +4 -1
- package/dist/src/cli/watch-turn.js +38 -2
- package/dist/src/cli/watch-usage.js +5 -1
- package/dist/src/cli/watch-view.d.ts +10 -17
- package/dist/src/cli/watch-view.js +33 -77
- package/dist/src/cli.js +106 -23
- package/dist/src/command-settings.d.ts +16 -0
- package/dist/src/command-settings.js +42 -0
- package/dist/src/configuration-change.d.ts +11 -0
- package/dist/src/configuration-change.js +62 -0
- package/dist/src/configuration-display.d.ts +21 -0
- package/dist/src/configuration-display.js +69 -0
- package/dist/src/configuration-schema.d.ts +49 -0
- package/dist/src/configuration-schema.js +159 -0
- package/dist/src/daemon/claude-turn.d.ts +20 -5
- package/dist/src/daemon/claude-turn.js +116 -53
- package/dist/src/daemon/claude-worker.d.ts +3 -5
- package/dist/src/daemon/claude-worker.js +64 -80
- package/dist/src/daemon/command-journal.d.ts +5 -0
- package/dist/src/daemon/command-journal.js +50 -0
- package/dist/src/daemon/command-process.d.ts +28 -0
- package/dist/src/daemon/command-process.js +164 -0
- package/dist/src/daemon/command-worker.d.ts +19 -0
- package/dist/src/daemon/command-worker.js +182 -0
- package/dist/src/daemon/cursor-stream.d.ts +4 -0
- package/dist/src/daemon/cursor-stream.js +154 -0
- package/dist/src/daemon/daemon-adapters.d.ts +58 -0
- package/dist/src/daemon/daemon-adapters.js +102 -0
- package/dist/src/daemon/daemon-loops.d.ts +14 -0
- package/dist/src/daemon/daemon-loops.js +39 -0
- package/dist/src/daemon/daemon.d.ts +3 -30
- package/dist/src/daemon/daemon.js +132 -110
- package/dist/src/daemon/declared-input.d.ts +3 -0
- package/dist/src/daemon/declared-input.js +9 -0
- package/dist/src/daemon/delivery-router.js +7 -1
- package/dist/src/daemon/gemini-stream.d.ts +4 -0
- package/dist/src/daemon/gemini-stream.js +129 -0
- package/dist/src/daemon/github-calls.d.ts +4 -0
- package/dist/src/daemon/github-calls.js +16 -7
- package/dist/src/daemon/github-ci-rerun.d.ts +26 -0
- package/dist/src/daemon/github-ci-rerun.js +92 -0
- package/dist/src/daemon/github-ci.d.ts +6 -0
- package/dist/src/daemon/github-ci.js +251 -0
- package/dist/src/daemon/github-merge-call.d.ts +16 -0
- package/dist/src/daemon/github-merge-call.js +73 -0
- package/dist/src/daemon/github-merge.d.ts +3 -5
- package/dist/src/daemon/github-merge.js +59 -98
- package/dist/src/daemon/github-methods.d.ts +28 -0
- package/dist/src/daemon/github-methods.js +90 -16
- package/dist/src/daemon/github-operation.d.ts +3 -0
- package/dist/src/daemon/github-retry.d.ts +6 -0
- package/dist/src/daemon/github-retry.js +13 -0
- package/dist/src/daemon/github-ticket-status.js +27 -1
- package/dist/src/daemon/github-worker.js +2 -1
- package/dist/src/daemon/handoff-worker.d.ts +10 -0
- package/dist/src/daemon/handoff-worker.js +232 -0
- package/dist/src/daemon/issue-queue.d.ts +2 -0
- package/dist/src/daemon/issue-queue.js +92 -63
- package/dist/src/daemon/notify-journal.d.ts +18 -0
- package/dist/src/daemon/notify-journal.js +108 -0
- package/dist/src/daemon/notify-message.js +2 -1
- package/dist/src/daemon/notify.js +177 -37
- package/dist/src/daemon/opencode-stream.d.ts +5 -0
- package/dist/src/daemon/opencode-stream.js +87 -0
- package/dist/src/daemon/queue-status.d.ts +4 -0
- package/dist/src/daemon/queue-status.js +38 -0
- package/dist/src/daemon/retry-worktree.d.ts +18 -0
- package/dist/src/daemon/retry-worktree.js +139 -0
- package/dist/src/daemon/retrying-turn.d.ts +5 -0
- package/dist/src/daemon/retrying-turn.js +145 -21
- package/dist/src/daemon/schedule-journal.d.ts +19 -0
- package/dist/src/daemon/schedule-journal.js +57 -0
- package/dist/src/daemon/text-output.d.ts +18 -0
- package/dist/src/daemon/text-output.js +44 -0
- package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
- package/dist/src/daemon/ticket-dependency-waits.js +60 -0
- package/dist/src/daemon/ticket-registration.d.ts +10 -8
- package/dist/src/daemon/ticket-registration.js +146 -47
- package/dist/src/daemon/ticket-source.d.ts +4 -1
- package/dist/src/daemon/ticket-source.js +8 -2
- package/dist/src/daemon/turn-command.d.ts +10 -0
- package/dist/src/daemon/turn-command.js +33 -0
- package/dist/src/daemon/turn-report.d.ts +18 -0
- package/dist/src/daemon/turn-report.js +79 -0
- package/dist/src/daemon/work-tree.d.ts +2 -1
- package/dist/src/daemon/work-tree.js +15 -12
- package/dist/src/daemon/workflow-generations.d.ts +32 -0
- package/dist/src/daemon/workflow-generations.js +57 -0
- package/dist/src/daemon/workflow-replay.d.ts +16 -0
- package/dist/src/daemon/workflow-replay.js +58 -0
- package/dist/src/daemon/workflow-schedules.d.ts +27 -0
- package/dist/src/daemon/workflow-schedules.js +185 -0
- package/dist/src/decision-visibility.d.ts +2 -0
- package/dist/src/decision-visibility.js +10 -0
- package/dist/src/decision-wait.d.ts +1 -0
- package/dist/src/decision-wait.js +3 -0
- package/dist/src/delivery-backoff.d.ts +1 -0
- package/dist/src/delivery-backoff.js +3 -0
- package/dist/src/doctor-report.d.ts +24 -0
- package/dist/src/doctor-report.js +70 -0
- package/dist/src/execution-pin.d.ts +9 -0
- package/dist/src/execution-pin.js +86 -2
- package/dist/src/grok-result.d.ts +12 -0
- package/dist/src/grok-result.js +61 -0
- package/dist/src/harness-defaults.d.ts +35 -0
- package/dist/src/harness-defaults.js +121 -0
- package/dist/src/host/decision-resume.d.ts +35 -0
- package/dist/src/host/decision-resume.js +86 -0
- package/dist/src/host/gate-bridge.d.ts +48 -0
- package/dist/src/host/gate-bridge.js +124 -0
- package/dist/src/host/run-wait.d.ts +26 -0
- package/dist/src/host/run-wait.js +37 -0
- package/dist/src/host/start-delivery.d.ts +3 -2
- package/dist/src/host/start-delivery.js +11 -4
- package/dist/src/host/step-bridge-host.d.ts +6 -19
- package/dist/src/host/step-bridge-host.js +57 -55
- package/dist/src/host/world-reconciliation.d.ts +1 -0
- package/dist/src/host/world-reconciliation.js +3 -0
- package/dist/src/host/world-runs.js +5 -0
- package/dist/src/issue-identity.d.ts +6 -0
- package/dist/src/issue-identity.js +23 -0
- package/dist/src/merge-request.d.ts +19 -0
- package/dist/src/merge-request.js +32 -0
- package/dist/src/phone/canonical-json.d.ts +1 -0
- package/dist/src/phone/canonical-json.js +16 -0
- package/dist/src/phone/phone-link.d.ts +7 -0
- package/dist/src/phone/phone-link.js +21 -0
- package/dist/src/phone/qr-data.d.ts +8 -0
- package/dist/src/phone/qr-data.js +133 -0
- package/dist/src/phone/qr-matrix.d.ts +6 -0
- package/dist/src/phone/qr-matrix.js +175 -0
- package/dist/src/phone/qr-penalty.d.ts +3 -0
- package/dist/src/phone/qr-penalty.js +67 -0
- package/dist/src/phone/qr-picture.d.ts +5 -0
- package/dist/src/phone/qr-picture.js +24 -0
- package/dist/src/private-config.schema.json +263 -0
- package/dist/src/project-config.schema.json +65 -0
- package/dist/src/project-settings.d.ts +19 -0
- package/dist/src/project-settings.js +131 -0
- package/dist/src/relay/answer-check.d.ts +24 -0
- package/dist/src/relay/answer-check.js +145 -0
- package/dist/src/relay/answer-inbox.d.ts +15 -0
- package/dist/src/relay/answer-inbox.js +173 -0
- package/dist/src/relay/daemon-keys.d.ts +16 -0
- package/dist/src/relay/daemon-keys.js +75 -0
- package/dist/src/relay/decision-check.d.ts +3 -0
- package/dist/src/relay/decision-check.js +77 -0
- package/dist/src/relay/decision-close.d.ts +6 -0
- package/dist/src/relay/decision-close.js +72 -0
- package/dist/src/relay/decision-envelopes.d.ts +29 -0
- package/dist/src/relay/decision-envelopes.js +93 -0
- package/dist/src/relay/decision-watch.d.ts +14 -0
- package/dist/src/relay/decision-watch.js +68 -0
- package/dist/src/relay/envelope-post.d.ts +8 -0
- package/dist/src/relay/envelope-post.js +71 -0
- package/dist/src/relay/envelope.d.ts +28 -0
- package/dist/src/relay/envelope.js +53 -0
- package/dist/src/relay/hpke.d.ts +25 -0
- package/dist/src/relay/hpke.js +108 -0
- package/dist/src/relay/pairing-checks.d.ts +42 -0
- package/dist/src/relay/pairing-checks.js +172 -0
- package/dist/src/relay/pairing-follow.d.ts +8 -0
- package/dist/src/relay/pairing-follow.js +95 -0
- package/dist/src/relay/pairing-session.d.ts +29 -0
- package/dist/src/relay/pairing-session.js +87 -0
- package/dist/src/relay/phone-delivery.d.ts +51 -0
- package/dist/src/relay/phone-delivery.js +139 -0
- package/dist/src/relay/phone-devices.d.ts +36 -0
- package/dist/src/relay/phone-devices.js +46 -0
- package/dist/src/relay/phone-inbox.d.ts +18 -0
- package/dist/src/relay/phone-inbox.js +17 -0
- package/dist/src/relay/phone-operations.d.ts +18 -0
- package/dist/src/relay/phone-operations.js +121 -0
- package/dist/src/relay/phone-outbox.d.ts +21 -0
- package/dist/src/relay/phone-outbox.js +25 -0
- package/dist/src/relay/phone-socket.d.ts +6 -0
- package/dist/src/relay/phone-socket.js +63 -0
- package/dist/src/relay/relay-control.d.ts +7 -0
- package/dist/src/relay/relay-control.js +24 -0
- package/dist/src/relay/relay-files.d.ts +2 -0
- package/dist/src/relay/relay-files.js +23 -0
- package/dist/src/relay/relay-heartbeat.d.ts +17 -0
- package/dist/src/relay/relay-heartbeat.js +81 -0
- package/dist/src/relay/relay-http.d.ts +14 -0
- package/dist/src/relay/relay-http.js +63 -0
- package/dist/src/relay/relay-registration.d.ts +15 -0
- package/dist/src/relay/relay-registration.js +63 -0
- package/dist/src/relay/relay-service.d.ts +25 -0
- package/dist/src/relay/relay-service.js +216 -0
- package/dist/src/relay/relay-store.d.ts +48 -0
- package/dist/src/relay/relay-store.js +131 -0
- package/dist/src/relay/sealed-post.d.ts +25 -0
- package/dist/src/relay/sealed-post.js +52 -0
- package/dist/src/relay.d.ts +1 -0
- package/dist/src/relay.js +8 -0
- package/dist/src/report-statistics.d.ts +50 -0
- package/dist/src/report-statistics.js +70 -0
- package/dist/src/report.d.ts +171 -0
- package/dist/src/report.js +119 -0
- package/dist/src/repository-identity.d.ts +4 -0
- package/dist/src/repository-identity.js +4 -0
- package/dist/src/review-subject.d.ts +41 -0
- package/dist/src/review-subject.js +91 -0
- package/dist/src/run-wait.d.ts +23 -0
- package/dist/src/run-wait.js +20 -0
- package/dist/src/service/active-tickets.d.ts +4 -0
- package/dist/src/service/active-tickets.js +49 -0
- package/dist/src/service/command-effects.d.ts +4 -0
- package/dist/src/service/command-effects.js +16 -0
- package/dist/src/service/decision-handler.d.ts +4 -0
- package/dist/src/service/decision-handler.js +39 -4
- package/dist/src/service/gateway.js +18 -3
- package/dist/src/service/queue-runs.d.ts +3 -0
- package/dist/src/service/queue-runs.js +39 -0
- package/dist/src/service/registry.d.ts +22 -3
- package/dist/src/service/registry.js +51 -8
- package/dist/src/service/replies.d.ts +10 -0
- package/dist/src/service/replies.js +12 -0
- package/dist/src/service/run-handlers.js +45 -54
- package/dist/src/service/run-metadata.d.ts +8 -0
- package/dist/src/service/run-metadata.js +53 -0
- package/dist/src/service/run-waits.d.ts +3 -0
- package/dist/src/service/run-waits.js +41 -0
- package/dist/src/service/service.d.ts +21 -2
- package/dist/src/service/service.js +23 -5
- package/dist/src/service/submission-handler.js +70 -19
- package/dist/src/service/worker-port.d.ts +1 -0
- package/dist/src/service/worker-totals.js +10 -1
- package/dist/src/step-bridge.d.ts +32 -0
- package/dist/src/steps/decision-gate.d.ts +2 -1
- package/dist/src/steps/decision-gate.js +7 -0
- package/dist/src/steps/handoff.d.ts +2 -0
- package/dist/src/steps/handoff.js +13 -0
- package/dist/src/steps/merge-by-person.d.ts +2 -0
- package/dist/src/steps/merge-by-person.js +18 -0
- package/dist/src/steps/merge-when-green.d.ts +2 -0
- package/dist/src/steps/merge-when-green.js +62 -0
- package/dist/src/steps/review-by-agent.d.ts +5 -0
- package/dist/src/steps/review-by-agent.js +124 -0
- package/dist/src/steps/review-by-person.d.ts +4 -0
- package/dist/src/steps/review-by-person.js +16 -0
- package/dist/src/steps/review-subject.d.ts +2 -0
- package/dist/src/steps/review-subject.js +11 -0
- package/dist/src/steps/run-command.d.ts +2 -0
- package/dist/src/steps/run-command.js +15 -0
- package/dist/src/steps/sleep.d.ts +3 -0
- package/dist/src/steps/sleep.js +19 -0
- package/dist/src/steps/wait-for-decision.d.ts +2 -0
- package/dist/src/steps/wait-for-decision.js +71 -0
- package/dist/src/steps/wait-for-run.d.ts +5 -0
- package/dist/src/steps/wait-for-run.js +9 -0
- package/dist/src/storage/decision-snapshots.d.ts +1 -0
- package/dist/src/storage/decision-snapshots.js +7 -1
- package/dist/src/submission.d.ts +4 -0
- package/dist/src/submission.js +6 -2
- package/dist/src/testing.d.ts +29 -0
- package/dist/src/testing.js +264 -0
- package/dist/src/ticket-dependencies.d.ts +1 -0
- package/dist/src/ticket-dependencies.js +15 -0
- package/dist/src/ticket-status-stream.d.ts +9 -0
- package/dist/src/ticket-status-stream.js +94 -0
- package/dist/src/ticket-status.js +7 -4
- package/dist/src/token-prices.d.ts +14 -4
- package/dist/src/token-prices.js +56 -11
- package/dist/src/turn-profile.d.ts +1 -0
- package/dist/src/turn-profile.js +8 -1
- package/dist/src/worker-usage.d.ts +25 -0
- package/dist/src/worker-usage.js +58 -1
- package/dist/src/workflow-admission.d.ts +6 -0
- package/dist/src/workflow-candidate.d.ts +7 -2
- package/dist/src/workflow-candidate.js +48 -5
- package/dist/src/workflow-contracts.d.ts +2 -0
- package/dist/src/workflow-contracts.js +8 -3
- package/dist/src/workflow-declaration.d.ts +31 -2
- package/dist/src/workflow-declaration.js +140 -16
- package/dist/src/workflow-discovery.d.ts +19 -0
- package/dist/src/workflow-discovery.js +36 -0
- package/dist/src/workflow-refusals.d.ts +6 -0
- package/dist/src/workflow-refusals.js +42 -0
- package/dist/src/workflow-schedule.d.ts +22 -0
- package/dist/src/workflow-schedule.js +53 -0
- package/dist/src/workflows.d.ts +12 -1
- package/dist/src/workflows.js +9 -0
- package/dist/tools/admit-workflow.d.ts +14 -0
- package/dist/tools/admit-workflow.js +107 -0
- package/dist/tools/doctor-adoption.d.ts +1 -0
- package/dist/tools/doctor-adoption.js +11 -0
- package/dist/tools/project-configuration.d.ts +4 -0
- package/dist/tools/project-configuration.js +47 -0
- package/dist/tools/self-host-processes.d.ts +1 -0
- package/dist/tools/self-host-processes.js +33 -22
- package/dist/tools/self-host-project.d.ts +21 -0
- package/dist/tools/self-host-project.js +224 -0
- package/dist/tools/self-host-runtime.d.ts +16 -0
- package/dist/tools/self-host-runtime.js +60 -0
- package/dist/tools/self-host-workflows.d.ts +1 -0
- package/dist/tools/self-host-workflows.js +36 -1
- package/dist/tools/self-host.js +106 -135
- package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
- package/dist/tools/workflow-bundle-dependencies.js +93 -0
- package/dist/tools/workflow-bundles.d.ts +20 -0
- package/dist/tools/workflow-bundles.js +173 -0
- package/dist/tools/workflow-candidate-counts.d.ts +12 -0
- package/dist/tools/workflow-candidate-counts.js +266 -0
- package/dist/tools/workflow-candidate-fields.d.ts +5 -0
- package/dist/tools/workflow-candidate-fields.js +24 -0
- package/dist/tools/workflow-candidate-helper-counts.d.ts +10 -0
- package/dist/tools/workflow-candidate-helper-counts.js +129 -0
- package/dist/tools/workflow-candidate-program.d.ts +13 -0
- package/dist/tools/workflow-candidate-program.js +106 -0
- package/dist/tools/workflow-candidate-source.d.ts +6 -0
- package/dist/tools/workflow-candidate-source.js +169 -0
- package/dist/tools/workflow-candidate-syntax.d.ts +21 -0
- package/dist/tools/workflow-candidate-syntax.js +263 -0
- package/dist/tools/workflow-command.d.ts +1 -0
- package/dist/tools/workflow-command.js +143 -0
- package/dist/tools/workflow-folder-capture.d.ts +18 -0
- package/dist/tools/workflow-folder-capture.js +261 -0
- package/dist/tools/workflow-folders.d.ts +49 -0
- package/dist/tools/workflow-folders.js +203 -0
- package/dist/tools/workflow-source-effects.d.ts +10 -0
- package/dist/tools/workflow-source-effects.js +213 -0
- package/dist/tools/workflow-test-hook.d.ts +7 -0
- package/dist/tools/workflow-test-hook.js +16 -0
- package/dist/tools/workflow-test-loader.d.ts +1 -0
- package/dist/tools/workflow-test-loader.js +10 -0
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.js +6 -1
- package/dist/workflows/ticket-prompts.js +17 -3
- package/package.json +66 -6
- package/templates/develop/README.md +78 -0
- package/templates/develop/develop/accept.ts +11 -0
- package/templates/develop/develop/flow-tests.ts +357 -0
- package/templates/develop/develop/local.ts +40 -0
- package/templates/develop/develop/option-tests.ts +174 -0
- package/templates/develop/develop/partial-choice.ts +29 -0
- package/templates/develop/develop/partial.ts +28 -0
- package/templates/develop/develop/pull.ts +152 -0
- package/templates/develop/develop/queue-partial.ts +39 -0
- package/templates/develop/develop/refusal-tests.ts +174 -0
- package/templates/develop/develop/report.ts +156 -0
- package/templates/develop/develop/round.ts +40 -0
- package/templates/develop/develop/scenarios.ts +87 -0
- package/templates/develop/develop/turn.ts +67 -0
- package/templates/develop/develop.declaration.json +24 -0
- package/templates/develop/develop.test.ts +287 -0
- package/templates/develop/develop.ts +30 -0
- package/templates/develop/template.json +153 -0
- package/dist/src/chats/analysis-batches.d.ts +0 -24
- package/dist/src/chats/analysis-batches.js +0 -83
- package/dist/src/chats/analysis-contract.d.ts +0 -80
- package/dist/src/chats/analysis-contract.js +0 -16
- package/dist/src/chats/analysis-data.d.ts +0 -3
- package/dist/src/chats/analysis-data.js +0 -85
- package/dist/src/chats/analysis-input.d.ts +0 -12
- package/dist/src/chats/analysis-input.js +0 -83
- package/dist/src/chats/analysis-links.d.ts +0 -2
- package/dist/src/chats/analysis-links.js +0 -25
- package/dist/src/chats/analysis-prompt.d.ts +0 -10
- package/dist/src/chats/analysis-prompt.js +0 -16
- package/dist/src/chats/analysis-proposal.d.ts +0 -2
- package/dist/src/chats/analysis-proposal.js +0 -32
- package/dist/src/chats/analysis-provenance.d.ts +0 -4
- package/dist/src/chats/analysis-provenance.js +0 -99
- package/dist/src/chats/analysis-shape.d.ts +0 -3
- package/dist/src/chats/analysis-shape.js +0 -228
- package/dist/src/chats/analysis.d.ts +0 -3
- package/dist/src/chats/analysis.js +0 -42
- package/dist/src/chats/corpus-input.d.ts +0 -5
- package/dist/src/chats/corpus-input.js +0 -288
- package/dist/src/chats/destination.d.ts +0 -4
- package/dist/src/chats/destination.js +0 -96
- package/dist/src/chats/import.d.ts +0 -15
- package/dist/src/chats/import.js +0 -243
- package/dist/src/chats/input-file.d.ts +0 -3
- package/dist/src/chats/input-file.js +0 -32
- package/dist/src/chats/normalize.d.ts +0 -29
- package/dist/src/chats/normalize.js +0 -155
- package/dist/src/chats/previous-import.d.ts +0 -5
- package/dist/src/chats/previous-import.js +0 -34
- package/dist/src/chats/reader.d.ts +0 -21
- package/dist/src/chats/reader.js +0 -118
- package/dist/src/chats/redact-import.d.ts +0 -14
- package/dist/src/chats/redact-import.js +0 -141
- package/dist/src/chats/redaction.d.ts +0 -41
- package/dist/src/chats/redaction.js +0 -161
- package/dist/src/chats/revision-input.d.ts +0 -3
- package/dist/src/chats/revision-input.js +0 -180
- package/dist/src/chats/revision.d.ts +0 -48
- package/dist/src/chats/revision.js +0 -94
- package/dist/src/chats/selection.d.ts +0 -25
- package/dist/src/chats/selection.js +0 -109
- package/dist/src/chats/source.d.ts +0 -23
- package/dist/src/chats/source.js +0 -135
- package/dist/src/chats/structure.d.ts +0 -57
- package/dist/src/chats/structure.js +0 -216
- package/dist/src/daemon/analysis-launch.d.ts +0 -5
- package/dist/src/daemon/analysis-launch.js +0 -81
- package/dist/src/daemon/analysis-token.d.ts +0 -6
- package/dist/src/daemon/analysis-token.js +0 -88
- package/dist/src/daemon/analysis-worker.d.ts +0 -22
- package/dist/src/daemon/analysis-worker.js +0 -219
- package/dist/src/daemon/chats-files.d.ts +0 -15
- package/dist/src/daemon/chats-files.js +0 -113
- package/dist/src/daemon/chats-worker.d.ts +0 -12
- package/dist/src/daemon/chats-worker.js +0 -200
- package/dist/workflows/analyze-chats.d.ts +0 -10
- package/dist/workflows/analyze-chats.declaration.json +0 -9
- package/dist/workflows/analyze-chats.js +0 -73
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Actor } from "solid-objects";
|
|
2
|
+
export declare const outboxIdentity = "phone";
|
|
3
|
+
export type OpenDecision = {
|
|
4
|
+
readonly decisionId: string;
|
|
5
|
+
readonly runId: string;
|
|
6
|
+
};
|
|
7
|
+
export declare class PhoneOutbox extends Actor {
|
|
8
|
+
static readonly actorType = "PhoneOutbox";
|
|
9
|
+
decisions: OpenDecision[];
|
|
10
|
+
retired: string[];
|
|
11
|
+
track(decision: OpenDecision): boolean;
|
|
12
|
+
retire({ decisionId }: {
|
|
13
|
+
readonly decisionId: string;
|
|
14
|
+
}): void;
|
|
15
|
+
forget({ decisionId }: {
|
|
16
|
+
readonly decisionId: string;
|
|
17
|
+
}): void;
|
|
18
|
+
untrack({ decisionId }: {
|
|
19
|
+
readonly decisionId: string;
|
|
20
|
+
}): boolean;
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Actor } from "solid-objects";
|
|
2
|
+
export const outboxIdentity = "phone";
|
|
3
|
+
export class PhoneOutbox extends Actor {
|
|
4
|
+
static actorType = "PhoneOutbox";
|
|
5
|
+
decisions = [];
|
|
6
|
+
retired = [];
|
|
7
|
+
track(decision) {
|
|
8
|
+
if (this.decisions.some((item) => item.decisionId === decision.decisionId))
|
|
9
|
+
return false;
|
|
10
|
+
this.decisions = [...this.decisions, decision];
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
retire({ decisionId }) {
|
|
14
|
+
if (!this.retired.includes(decisionId))
|
|
15
|
+
this.retired = [...this.retired, decisionId];
|
|
16
|
+
}
|
|
17
|
+
forget({ decisionId }) {
|
|
18
|
+
this.retired = this.retired.filter((item) => item !== decisionId);
|
|
19
|
+
}
|
|
20
|
+
untrack({ decisionId }) {
|
|
21
|
+
const known = this.decisions.some((item) => item.decisionId === decisionId);
|
|
22
|
+
this.decisions = this.decisions.filter((item) => item.decisionId !== decisionId);
|
|
23
|
+
return known;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Reply, type ServiceRequest } from "../protocol.ts";
|
|
2
|
+
export type PhoneHandler = (request: ServiceRequest) => Promise<Reply>;
|
|
3
|
+
export interface PhoneSocket {
|
|
4
|
+
readonly close: () => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function listenPhoneSocket(path: string, handle: PhoneHandler): Promise<PhoneSocket>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { chmod, rm } from "node:fs/promises";
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
import { maximumFrameBytes, parseRequest, replyLine, } from "../protocol.js";
|
|
4
|
+
function failed(error) {
|
|
5
|
+
return {
|
|
6
|
+
kind: "error",
|
|
7
|
+
error: {
|
|
8
|
+
code: "phone-relay-failed",
|
|
9
|
+
detail: String(Reflect.get(Object(error), "message")),
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function served(socket, handle) {
|
|
14
|
+
let buffered = "";
|
|
15
|
+
socket.setEncoding("utf8");
|
|
16
|
+
socket.on("error", () => undefined);
|
|
17
|
+
socket.on("data", (data) => {
|
|
18
|
+
buffered += data;
|
|
19
|
+
const end = buffered.indexOf("\n");
|
|
20
|
+
if (end < 0 && Buffer.byteLength(buffered) <= maximumFrameBytes)
|
|
21
|
+
return;
|
|
22
|
+
socket.removeAllListeners("data");
|
|
23
|
+
const parsed = parseRequest(end < 0 ? "" : buffered.slice(0, end));
|
|
24
|
+
const answer = (reply) => {
|
|
25
|
+
socket.end(replyLine(reply));
|
|
26
|
+
};
|
|
27
|
+
const replied = parsed.kind === "error"
|
|
28
|
+
? Promise.resolve(parsed)
|
|
29
|
+
: handle(parsed.request);
|
|
30
|
+
replied.then(answer, (error) => {
|
|
31
|
+
answer(failed(error));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function listenPhoneSocket(path, handle) {
|
|
36
|
+
await rm(path, { force: true });
|
|
37
|
+
const sockets = new Set();
|
|
38
|
+
const server = net.createServer((socket) => {
|
|
39
|
+
sockets.add(socket);
|
|
40
|
+
socket.once("close", () => sockets.delete(socket));
|
|
41
|
+
served(socket, handle);
|
|
42
|
+
});
|
|
43
|
+
await new Promise((ready, refuse) => {
|
|
44
|
+
server.once("error", refuse);
|
|
45
|
+
server.listen(path, () => {
|
|
46
|
+
server.off("error", refuse);
|
|
47
|
+
ready();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
await chmod(path, 0o600);
|
|
51
|
+
return {
|
|
52
|
+
close: async () => {
|
|
53
|
+
const closed = new Promise((settle) => {
|
|
54
|
+
server.close(() => {
|
|
55
|
+
settle();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
for (const socket of sockets)
|
|
59
|
+
socket.destroy();
|
|
60
|
+
await closed;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Fields, Reply, ServiceRequest } from "../protocol.ts";
|
|
2
|
+
export declare function relayControl(runtimeDirectory: string): (request: ServiceRequest) => Promise<Reply>;
|
|
3
|
+
export declare function relayEvents(runtimeDirectory: string, stream: {
|
|
4
|
+
readonly after: number;
|
|
5
|
+
readonly onEvent: (event: Fields) => void;
|
|
6
|
+
readonly stopped: Promise<void>;
|
|
7
|
+
}): Promise<Reply>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { follow, send } from "../cli/client.js";
|
|
2
|
+
const allowed = new Set([
|
|
3
|
+
"questions",
|
|
4
|
+
"status",
|
|
5
|
+
"events",
|
|
6
|
+
"decision",
|
|
7
|
+
]);
|
|
8
|
+
export function relayControl(runtimeDirectory) {
|
|
9
|
+
return (request) => {
|
|
10
|
+
if (!allowed.has(request.operation))
|
|
11
|
+
return Promise.reject(new Error(`relay-operation-refused: The phone relay may not send ${request.operation}.`));
|
|
12
|
+
return send(runtimeDirectory, request);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function relayEvents(runtimeDirectory, stream) {
|
|
16
|
+
return follow(runtimeDirectory, {
|
|
17
|
+
request: {
|
|
18
|
+
operation: "events",
|
|
19
|
+
parameters: { after: stream.after, follow: true },
|
|
20
|
+
},
|
|
21
|
+
onEvent: stream.onEvent,
|
|
22
|
+
signal: stream.stopped,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export async function readOptional(path) {
|
|
5
|
+
return readFile(path, "utf8").catch((error) => {
|
|
6
|
+
if (Reflect.get(Object(error), "code") === "ENOENT")
|
|
7
|
+
return undefined;
|
|
8
|
+
throw error;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export async function writePrivate(path, value) {
|
|
12
|
+
const temporary = join(path, "..", `.${randomUUID()}.tmp`);
|
|
13
|
+
try {
|
|
14
|
+
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
15
|
+
mode: 0o600,
|
|
16
|
+
flag: "wx",
|
|
17
|
+
});
|
|
18
|
+
await rename(temporary, path);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await rm(temporary, { force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RelayStore } from "./relay-store.ts";
|
|
2
|
+
export interface HeartbeatTiming {
|
|
3
|
+
readonly intervalMilliseconds: number;
|
|
4
|
+
readonly firstBackoffMilliseconds: number;
|
|
5
|
+
readonly longestBackoffMilliseconds: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const heartbeatTiming: HeartbeatTiming;
|
|
8
|
+
export interface HeartbeatContext {
|
|
9
|
+
readonly phoneDirectory: string;
|
|
10
|
+
readonly store: RelayStore;
|
|
11
|
+
readonly lost: () => void;
|
|
12
|
+
readonly timing: HeartbeatTiming;
|
|
13
|
+
readonly wait: (milliseconds: number, signal: AbortSignal) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export type BeatOutcome = "beat" | "failed" | "lost" | "replaced" | "unregistered";
|
|
16
|
+
export declare function heartbeat(context: HeartbeatContext, signal?: AbortSignal): Promise<BeatOutcome>;
|
|
17
|
+
export declare function runHeartbeats(context: HeartbeatContext, signal: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { relayRequest } from "./relay-http.js";
|
|
4
|
+
import { readLink } from "./relay-registration.js";
|
|
5
|
+
export const heartbeatTiming = {
|
|
6
|
+
intervalMilliseconds: 60_000,
|
|
7
|
+
firstBackoffMilliseconds: 5000,
|
|
8
|
+
longestBackoffMilliseconds: 300_000,
|
|
9
|
+
};
|
|
10
|
+
function revokedDevices(body) {
|
|
11
|
+
const devices = typeof body === "object" && body !== null
|
|
12
|
+
? Reflect.get(body, "devices")
|
|
13
|
+
: undefined;
|
|
14
|
+
if (!Array.isArray(devices))
|
|
15
|
+
return [];
|
|
16
|
+
return devices.flatMap((device) => {
|
|
17
|
+
const deviceId = Reflect.get(Object(device), "device_id");
|
|
18
|
+
const revokedAt = Reflect.get(Object(device), "revoked_at");
|
|
19
|
+
return typeof deviceId === "string" && typeof revokedAt === "string"
|
|
20
|
+
? [deviceId]
|
|
21
|
+
: [];
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function applyRevocations(context, call) {
|
|
25
|
+
const { link, signal } = call;
|
|
26
|
+
if (signal.aborted)
|
|
27
|
+
return;
|
|
28
|
+
const reply = await relayRequest(link.origin, {
|
|
29
|
+
method: "GET",
|
|
30
|
+
path: "/api/v1/relay/devices",
|
|
31
|
+
token: link.token,
|
|
32
|
+
signal,
|
|
33
|
+
});
|
|
34
|
+
if (reply.status !== 200)
|
|
35
|
+
return;
|
|
36
|
+
const known = new Set((await context.store.devices())
|
|
37
|
+
.filter((device) => device.removedAt === null)
|
|
38
|
+
.map((device) => device.deviceId));
|
|
39
|
+
for (const deviceId of revokedDevices(reply.body))
|
|
40
|
+
if (known.has(deviceId))
|
|
41
|
+
await context.store.remove(deviceId);
|
|
42
|
+
}
|
|
43
|
+
async function lostRegistration(context, link) {
|
|
44
|
+
const current = await readLink(context.phoneDirectory);
|
|
45
|
+
if (current?.token !== link.token)
|
|
46
|
+
return "replaced";
|
|
47
|
+
await context.store.removeAll();
|
|
48
|
+
await rm(join(context.phoneDirectory, "link.json"), { force: true });
|
|
49
|
+
context.lost();
|
|
50
|
+
return "lost";
|
|
51
|
+
}
|
|
52
|
+
export async function heartbeat(context, signal = new AbortController().signal) {
|
|
53
|
+
const link = await readLink(context.phoneDirectory);
|
|
54
|
+
if (link === undefined)
|
|
55
|
+
return "unregistered";
|
|
56
|
+
const reply = await relayRequest(link.origin, {
|
|
57
|
+
method: "PUT",
|
|
58
|
+
path: "/api/v1/relay/daemon/heartbeat",
|
|
59
|
+
token: link.token,
|
|
60
|
+
signal,
|
|
61
|
+
}).catch(() => undefined);
|
|
62
|
+
if (reply?.status === 401)
|
|
63
|
+
return lostRegistration(context, link);
|
|
64
|
+
if (reply === undefined || reply.status >= 300)
|
|
65
|
+
return "failed";
|
|
66
|
+
await applyRevocations(context, { link, signal }).catch(() => undefined);
|
|
67
|
+
return "beat";
|
|
68
|
+
}
|
|
69
|
+
function pause(timing, failures) {
|
|
70
|
+
if (failures === 0)
|
|
71
|
+
return timing.intervalMilliseconds;
|
|
72
|
+
return Math.min(timing.firstBackoffMilliseconds * 2 ** (failures - 1), timing.longestBackoffMilliseconds);
|
|
73
|
+
}
|
|
74
|
+
export async function runHeartbeats(context, signal) {
|
|
75
|
+
let failures = 0;
|
|
76
|
+
while (!signal.aborted) {
|
|
77
|
+
const outcome = await heartbeat(context, signal).catch(() => "failed");
|
|
78
|
+
failures = outcome === "failed" ? failures + 1 : 0;
|
|
79
|
+
await context.wait(pause(context.timing, failures), signal);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface RelayCall {
|
|
2
|
+
readonly method: "GET" | "POST" | "PUT" | "DELETE";
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly token?: string;
|
|
5
|
+
readonly body?: unknown;
|
|
6
|
+
readonly timeoutMilliseconds?: number;
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
export interface RelayReply {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly body: unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare function relayOrigin(value: string | undefined): string;
|
|
14
|
+
export declare function relayRequest(origin: string, call: RelayCall): Promise<RelayReply>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
export function relayOrigin(value) {
|
|
4
|
+
const given = value ?? "https://cloving.ai";
|
|
5
|
+
const url = URL.parse(given);
|
|
6
|
+
const local = url?.protocol === "http:" && url.hostname === "127.0.0.1";
|
|
7
|
+
if (url === null ||
|
|
8
|
+
(url.protocol !== "https:" && !local) ||
|
|
9
|
+
url.origin !== given ||
|
|
10
|
+
given.length > 161)
|
|
11
|
+
throw new Error(`relay-origin-invalid: CLOVING_RELAY_ORIGIN must be an https origin of at most 161 characters, such as https://cloving.ai.`);
|
|
12
|
+
return url.origin;
|
|
13
|
+
}
|
|
14
|
+
function parsed(text) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function headers(call, payload) {
|
|
23
|
+
return {
|
|
24
|
+
accept: "application/json",
|
|
25
|
+
...(payload === undefined ? {} : { "content-type": "application/json" }),
|
|
26
|
+
...(call.token === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { authorization: `Bearer ${call.token}` }),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function relayRequest(origin, call) {
|
|
32
|
+
const url = new URL(call.path, origin);
|
|
33
|
+
const transport = url.protocol === "https:" ? https : http;
|
|
34
|
+
const payload = call.body === undefined ? undefined : JSON.stringify(call.body);
|
|
35
|
+
return new Promise((settle, reject) => {
|
|
36
|
+
const request = transport.request(url, {
|
|
37
|
+
method: call.method,
|
|
38
|
+
headers: headers(call, payload),
|
|
39
|
+
timeout: call.timeoutMilliseconds ?? 30_000,
|
|
40
|
+
...(call.signal === undefined ? {} : { signal: call.signal }),
|
|
41
|
+
}, (response) => {
|
|
42
|
+
let text = "";
|
|
43
|
+
response.setEncoding("utf8");
|
|
44
|
+
response.on("data", (chunk) => {
|
|
45
|
+
text += chunk;
|
|
46
|
+
});
|
|
47
|
+
response.once("end", () => {
|
|
48
|
+
settle({ status: response.statusCode ?? 0, body: parsed(text) });
|
|
49
|
+
});
|
|
50
|
+
const incomplete = () => {
|
|
51
|
+
if (!response.complete)
|
|
52
|
+
reject(new Error("relay-response-incomplete: cloving.ai closed the answer before its end."));
|
|
53
|
+
};
|
|
54
|
+
response.once("error", incomplete);
|
|
55
|
+
response.once("close", incomplete);
|
|
56
|
+
});
|
|
57
|
+
request.once("timeout", () => {
|
|
58
|
+
request.destroy(new Error("relay-timeout: cloving.ai did not answer."));
|
|
59
|
+
});
|
|
60
|
+
request.once("error", reject);
|
|
61
|
+
request.end(payload);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type PhoneLink } from "../phone/phone-link.ts";
|
|
2
|
+
import { type DaemonKeys } from "./daemon-keys.ts";
|
|
3
|
+
export interface RegistrationContext {
|
|
4
|
+
readonly phoneDirectory: string;
|
|
5
|
+
readonly origin: string;
|
|
6
|
+
readonly computerName: string;
|
|
7
|
+
}
|
|
8
|
+
export interface Registration {
|
|
9
|
+
readonly link: PhoneLink;
|
|
10
|
+
readonly keys: DaemonKeys;
|
|
11
|
+
}
|
|
12
|
+
export declare function readLink(phoneDirectory: string): Promise<PhoneLink | undefined>;
|
|
13
|
+
export declare function storedRegistration(phoneDirectory: string): Promise<Registration | undefined>;
|
|
14
|
+
export declare function forgetRegistration(phoneDirectory: string): Promise<void>;
|
|
15
|
+
export declare function registration(context: RegistrationContext): Promise<Registration>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parsePhoneLink } from "../phone/phone-link.js";
|
|
4
|
+
import { newDaemonKeys, parseDaemonKeys, } from "./daemon-keys.js";
|
|
5
|
+
import { readOptional, writePrivate } from "./relay-files.js";
|
|
6
|
+
import { relayRequest } from "./relay-http.js";
|
|
7
|
+
export function readLink(phoneDirectory) {
|
|
8
|
+
return readOptional(join(phoneDirectory, "link.json")).then(parsePhoneLink);
|
|
9
|
+
}
|
|
10
|
+
function readKeys(phoneDirectory) {
|
|
11
|
+
return readOptional(join(phoneDirectory, "daemon-keys.json")).then(parseDaemonKeys);
|
|
12
|
+
}
|
|
13
|
+
export async function storedRegistration(phoneDirectory) {
|
|
14
|
+
const link = await readLink(phoneDirectory);
|
|
15
|
+
const keys = await readKeys(phoneDirectory);
|
|
16
|
+
if (link === undefined || keys === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
return { link, keys };
|
|
19
|
+
}
|
|
20
|
+
export async function forgetRegistration(phoneDirectory) {
|
|
21
|
+
await rm(join(phoneDirectory, "link.json"), { force: true });
|
|
22
|
+
}
|
|
23
|
+
function text(body, name) {
|
|
24
|
+
const value = typeof body === "object" && body !== null
|
|
25
|
+
? Reflect.get(body, name)
|
|
26
|
+
: undefined;
|
|
27
|
+
if (typeof value !== "string" || value === "")
|
|
28
|
+
throw new Error(`registration-invalid: cloving.ai sent no ${name}.`);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
async function registered(context, keys) {
|
|
32
|
+
const reply = await relayRequest(context.origin, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
path: "/api/v1/relay/daemons",
|
|
35
|
+
body: {
|
|
36
|
+
name: context.computerName,
|
|
37
|
+
agreement_public_key: keys.agreementPublicKey,
|
|
38
|
+
signing_public_key: keys.signingPublicKey,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (reply.status !== 201)
|
|
42
|
+
throw new Error(`registration-failed: cloving.ai answered ${String(reply.status)}.`);
|
|
43
|
+
const link = {
|
|
44
|
+
daemonId: text(reply.body, "daemon_id"),
|
|
45
|
+
origin: context.origin,
|
|
46
|
+
token: text(reply.body, "token"),
|
|
47
|
+
registeredAt: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
await writePrivate(join(context.phoneDirectory, "link.json"), link);
|
|
50
|
+
return { link, keys };
|
|
51
|
+
}
|
|
52
|
+
export async function registration(context) {
|
|
53
|
+
const link = await readLink(context.phoneDirectory);
|
|
54
|
+
const stored = await readKeys(context.phoneDirectory);
|
|
55
|
+
if (link !== undefined &&
|
|
56
|
+
stored !== undefined &&
|
|
57
|
+
link.origin === context.origin)
|
|
58
|
+
return { link, keys: stored };
|
|
59
|
+
const keys = stored ?? newDaemonKeys();
|
|
60
|
+
if (stored === undefined)
|
|
61
|
+
await writePrivate(join(context.phoneDirectory, "daemon-keys.json"), keys);
|
|
62
|
+
return registered(context, keys);
|
|
63
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type InboxTiming } from "./answer-inbox.ts";
|
|
2
|
+
import { type WatchTiming } from "./decision-watch.ts";
|
|
3
|
+
import { type HeartbeatTiming } from "./relay-heartbeat.ts";
|
|
4
|
+
export interface RelayPaths {
|
|
5
|
+
readonly runtimeDirectory: string;
|
|
6
|
+
readonly phoneDirectory: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RelayOptions extends RelayPaths {
|
|
9
|
+
readonly origin?: string;
|
|
10
|
+
readonly computerName?: string;
|
|
11
|
+
readonly secret?: () => Buffer;
|
|
12
|
+
readonly heartbeatTiming?: HeartbeatTiming;
|
|
13
|
+
readonly watchTiming?: WatchTiming;
|
|
14
|
+
readonly inboxTiming?: InboxTiming;
|
|
15
|
+
}
|
|
16
|
+
export interface Relay {
|
|
17
|
+
readonly close: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function computerName(name: string): string;
|
|
20
|
+
export declare function startRelay(options: RelayOptions): Promise<Relay>;
|
|
21
|
+
export interface RelayProgram {
|
|
22
|
+
readonly stopped: Promise<unknown>;
|
|
23
|
+
readonly error: (line: string) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function runRelay(environment: Readonly<Record<string, string | undefined>>, program: RelayProgram): Promise<number>;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
import { hostname } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { inboxTiming, runInbox } from "./answer-inbox.js";
|
|
7
|
+
import { watchDecisions, watchTiming, } from "./decision-watch.js";
|
|
8
|
+
import { followPairing } from "./pairing-follow.js";
|
|
9
|
+
import { pairingSessions, RegistrationEnded, } from "./pairing-session.js";
|
|
10
|
+
import { pairAccept, pairDeny, phoneRemove, phoneReset, result, } from "./phone-operations.js";
|
|
11
|
+
import { listenPhoneSocket } from "./phone-socket.js";
|
|
12
|
+
import { heartbeatTiming, runHeartbeats, } from "./relay-heartbeat.js";
|
|
13
|
+
import { relayControl } from "./relay-control.js";
|
|
14
|
+
import { relayOrigin } from "./relay-http.js";
|
|
15
|
+
import { forgetRegistration, readLink, registration, } from "./relay-registration.js";
|
|
16
|
+
import { openRelayStore } from "./relay-store.js";
|
|
17
|
+
function unknownOperation(request) {
|
|
18
|
+
return {
|
|
19
|
+
kind: "error",
|
|
20
|
+
error: {
|
|
21
|
+
code: "unknown-operation",
|
|
22
|
+
detail: `The phone relay has no operation ${request.operation}.`,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function pairingView(open) {
|
|
27
|
+
if (open === undefined)
|
|
28
|
+
return null;
|
|
29
|
+
const request = open.request;
|
|
30
|
+
return {
|
|
31
|
+
pairingId: open.pairingId,
|
|
32
|
+
expiresAt: open.expiresAt,
|
|
33
|
+
stage: open.stage,
|
|
34
|
+
request: request === undefined
|
|
35
|
+
? null
|
|
36
|
+
: {
|
|
37
|
+
deviceId: request.deviceId,
|
|
38
|
+
name: request.name,
|
|
39
|
+
platform: request.platform,
|
|
40
|
+
code: request.code,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function phoneState(relay) {
|
|
45
|
+
const link = await readLink(relay.context.phoneDirectory);
|
|
46
|
+
return result({
|
|
47
|
+
registered: link !== undefined,
|
|
48
|
+
daemonId: link?.daemonId ?? null,
|
|
49
|
+
origin: link?.origin ?? null,
|
|
50
|
+
pairing: pairingView(relay.sessions.current()),
|
|
51
|
+
refusal: relay.sessions.refusal(),
|
|
52
|
+
reason: relay.registration.lost ? "registration-lost" : null,
|
|
53
|
+
devices: (await relay.store.devices()).map((device) => ({
|
|
54
|
+
deviceId: device.deviceId,
|
|
55
|
+
name: device.name,
|
|
56
|
+
platform: device.platform,
|
|
57
|
+
pairedAt: device.pairedAt,
|
|
58
|
+
state: device.state,
|
|
59
|
+
removedAt: device.removedAt,
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function opened(relay) {
|
|
64
|
+
const startedIn = relay.sessions.generation();
|
|
65
|
+
try {
|
|
66
|
+
return await relay.sessions.start(await registration(relay.context), startedIn);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (!(error instanceof RegistrationEnded))
|
|
70
|
+
throw error;
|
|
71
|
+
await relay.store.removeAll();
|
|
72
|
+
await forgetRegistration(relay.context.phoneDirectory);
|
|
73
|
+
return relay.sessions.start(await registration(relay.context), startedIn);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const starts = new WeakMap();
|
|
77
|
+
function sharedStart(relay) {
|
|
78
|
+
const running = starts.get(relay);
|
|
79
|
+
if (running !== undefined)
|
|
80
|
+
return running;
|
|
81
|
+
const starting = opened(relay).finally(() => starts.delete(relay));
|
|
82
|
+
starts.set(relay, starting);
|
|
83
|
+
return starting;
|
|
84
|
+
}
|
|
85
|
+
async function pairStart(relay) {
|
|
86
|
+
const open = relay.sessions.current() ?? (await sharedStart(relay));
|
|
87
|
+
relay.registration.lost = false;
|
|
88
|
+
return result({ link: open.link, expiresAt: open.expiresAt });
|
|
89
|
+
}
|
|
90
|
+
function pairCancel(relay) {
|
|
91
|
+
relay.sessions.cancel();
|
|
92
|
+
return Promise.resolve(result({ pairing: null }));
|
|
93
|
+
}
|
|
94
|
+
async function resetAfterStart(relay) {
|
|
95
|
+
relay.sessions.cancel();
|
|
96
|
+
await starts.get(relay)?.catch(() => undefined);
|
|
97
|
+
return phoneReset(relay);
|
|
98
|
+
}
|
|
99
|
+
const operations = {
|
|
100
|
+
"phone-state": phoneState,
|
|
101
|
+
"phone-pair-start": pairStart,
|
|
102
|
+
"phone-pair-cancel": pairCancel,
|
|
103
|
+
"phone-pair-accept": pairAccept,
|
|
104
|
+
"phone-pair-deny": pairDeny,
|
|
105
|
+
"phone-remove": phoneRemove,
|
|
106
|
+
"phone-reset": resetAfterStart,
|
|
107
|
+
};
|
|
108
|
+
export function computerName(name) {
|
|
109
|
+
return name.replace(/\.local$/, "").slice(0, 64);
|
|
110
|
+
}
|
|
111
|
+
function deliveringStore(options, opened) {
|
|
112
|
+
const stop = new AbortController();
|
|
113
|
+
const reading = new AbortController();
|
|
114
|
+
const running = opened.run(stop.signal);
|
|
115
|
+
const context = {
|
|
116
|
+
runtimeDirectory: options.runtimeDirectory,
|
|
117
|
+
phoneDirectory: options.phoneDirectory,
|
|
118
|
+
store: opened,
|
|
119
|
+
control: relayControl(options.runtimeDirectory),
|
|
120
|
+
now: () => Date.now(),
|
|
121
|
+
};
|
|
122
|
+
const watch = watchDecisions(context, options.watchTiming ?? watchTiming);
|
|
123
|
+
const answers = runInbox(context, {
|
|
124
|
+
signal: reading.signal,
|
|
125
|
+
timing: options.inboxTiming ?? inboxTiming,
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
store: {
|
|
129
|
+
...opened,
|
|
130
|
+
connect: async (deviceId) => {
|
|
131
|
+
const found = await opened.connect(deviceId);
|
|
132
|
+
watch.wake();
|
|
133
|
+
return found;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
close: async () => {
|
|
137
|
+
reading.abort();
|
|
138
|
+
await answers;
|
|
139
|
+
await watch.close();
|
|
140
|
+
stop.abort();
|
|
141
|
+
await running;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export async function startRelay(options) {
|
|
146
|
+
await mkdir(options.runtimeDirectory, { recursive: true, mode: 0o700 });
|
|
147
|
+
await mkdir(options.phoneDirectory, { recursive: true, mode: 0o700 });
|
|
148
|
+
const opened = await openRelayStore(options.phoneDirectory);
|
|
149
|
+
const delivering = deliveringStore(options, opened);
|
|
150
|
+
const { store } = delivering;
|
|
151
|
+
const follower = {
|
|
152
|
+
follow: () => Promise.resolve(),
|
|
153
|
+
};
|
|
154
|
+
const relay = {
|
|
155
|
+
context: {
|
|
156
|
+
phoneDirectory: options.phoneDirectory,
|
|
157
|
+
origin: options.origin ?? relayOrigin(undefined),
|
|
158
|
+
computerName: options.computerName ?? computerName(hostname()),
|
|
159
|
+
},
|
|
160
|
+
store,
|
|
161
|
+
sessions: pairingSessions({
|
|
162
|
+
secret: options.secret ?? (() => randomBytes(16)),
|
|
163
|
+
follow: (open) => follower.follow(open),
|
|
164
|
+
}),
|
|
165
|
+
registration: { lost: false },
|
|
166
|
+
};
|
|
167
|
+
follower.follow = (open) => followPairing(relay, open);
|
|
168
|
+
const socket = await listenPhoneSocket(join(options.runtimeDirectory, "phone.sock"), (request) => {
|
|
169
|
+
const operation = operations[request.operation];
|
|
170
|
+
return operation === undefined
|
|
171
|
+
? Promise.resolve(unknownOperation(request))
|
|
172
|
+
: operation(relay, request.parameters);
|
|
173
|
+
});
|
|
174
|
+
const stop = new AbortController();
|
|
175
|
+
const beating = runHeartbeats({
|
|
176
|
+
phoneDirectory: options.phoneDirectory,
|
|
177
|
+
store,
|
|
178
|
+
timing: options.heartbeatTiming ?? heartbeatTiming,
|
|
179
|
+
lost: () => {
|
|
180
|
+
relay.registration.lost = true;
|
|
181
|
+
relay.sessions.cancel();
|
|
182
|
+
},
|
|
183
|
+
wait: (milliseconds, signal) => delay(milliseconds, undefined, { signal }).catch(() => undefined),
|
|
184
|
+
}, stop.signal);
|
|
185
|
+
return {
|
|
186
|
+
close: async () => {
|
|
187
|
+
stop.abort();
|
|
188
|
+
relay.sessions.cancel();
|
|
189
|
+
await beating;
|
|
190
|
+
await socket.close();
|
|
191
|
+
await delivering.close();
|
|
192
|
+
await opened.close();
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export async function runRelay(environment, program) {
|
|
197
|
+
const runtimeDirectory = environment["CLOVING_RUNTIME_DIR"];
|
|
198
|
+
if (runtimeDirectory === undefined || runtimeDirectory === "") {
|
|
199
|
+
program.error("runtime-directory-unknown: The phone relay needs CLOVING_RUNTIME_DIR.\n");
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const relay = await startRelay({
|
|
204
|
+
runtimeDirectory,
|
|
205
|
+
phoneDirectory: join(dirname(runtimeDirectory), "state", "phone"),
|
|
206
|
+
origin: relayOrigin(environment["CLOVING_RELAY_ORIGIN"]),
|
|
207
|
+
});
|
|
208
|
+
await program.stopped;
|
|
209
|
+
await relay.close();
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
program.error(`phone-relay-failed: ${String(Reflect.get(Object(error), "message"))}\n`);
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
}
|