@quickchatai/quickreview 0.0.0-stage → 1.1.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/LICENSE +23 -0
- package/README.md +385 -2
- package/THIRD-PARTY-NOTICES.md +134 -0
- package/agents/pass-abstraction.md +64 -0
- package/agents/pass-ask-human.md +134 -0
- package/agents/pass-backcompat.md +67 -0
- package/agents/pass-baseline.md +61 -0
- package/agents/pass-behaviour-delta.md +64 -0
- package/agents/pass-bounds.md +36 -0
- package/agents/pass-claims.md +31 -0
- package/agents/pass-concurrency.md +168 -0
- package/agents/pass-consistency.md +57 -0
- package/agents/pass-contracts.md +30 -0
- package/agents/pass-data-safety.md +54 -0
- package/agents/pass-deferred.md +35 -0
- package/agents/pass-delivery.md +37 -0
- package/agents/pass-duplication.md +60 -0
- package/agents/pass-intent.md +63 -0
- package/agents/pass-lint-conformance.md +46 -0
- package/agents/pass-maintainability.md +67 -0
- package/agents/pass-placement.md +68 -0
- package/agents/pass-prod-risk.md +24 -0
- package/agents/pass-rollout.md +32 -0
- package/agents/pass-roundtrip.md +46 -0
- package/agents/pass-security.md +31 -0
- package/agents/pass-simplicity.md +68 -0
- package/agents/pass-statelessness.md +27 -0
- package/agents/pass-style.md +37 -0
- package/agents/pass-sweep.md +57 -0
- package/agents/pass-tests.md +58 -0
- package/agents/pass-ui.md +23 -0
- package/agents/pass-validate.md +50 -0
- package/agents/pass-widened.md +46 -0
- package/agents/scan/pass-scan.md +86 -0
- package/bin/qcr.js +11 -0
- package/config.example.toml +1067 -0
- package/deploy/ansible/README.md +356 -0
- package/deploy/ansible/group_vars/all.yml +212 -0
- package/deploy/ansible/inventory.example.ini +25 -0
- package/deploy/ansible/playbook.yml +360 -0
- package/deploy/collect-secrets.sh +289 -0
- package/deploy/collect-slack-secrets.mjs +1190 -0
- package/deploy/github-app-manifest.json +31 -0
- package/deploy/manifest.schema.json +75 -0
- package/deploy/qcr-update.sh +803 -0
- package/deploy/reviewer/AGENTS.override.md +12 -0
- package/dist/extensions/cli/bash-guard-ambient.js +42 -0
- package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
- package/dist/extensions/cli/bash-guard.js +5 -0
- package/dist/extensions/cli/bash-guard.js.map +1 -0
- package/dist/extensions/cli/companion-guard.js +5 -0
- package/dist/extensions/cli/companion-guard.js.map +1 -0
- package/dist/extensions/cli/debug-round.js +8 -0
- package/dist/extensions/cli/debug-round.js.map +1 -0
- package/dist/extensions/cli/inject-bridge.js +5 -0
- package/dist/extensions/cli/inject-bridge.js.map +1 -0
- package/dist/extensions/cli/ledger-pin.js +5 -0
- package/dist/extensions/cli/ledger-pin.js.map +1 -0
- package/dist/extensions/cli/lens.js +13 -0
- package/dist/extensions/cli/lens.js.map +1 -0
- package/dist/extensions/cli/muted-theme.js +5 -0
- package/dist/extensions/cli/muted-theme.js.map +1 -0
- package/dist/extensions/cli/onboarding.js +5 -0
- package/dist/extensions/cli/onboarding.js.map +1 -0
- package/dist/extensions/cli/setup.js +5 -0
- package/dist/extensions/cli/setup.js.map +1 -0
- package/dist/extensions/cli/tui-view.js +5 -0
- package/dist/extensions/cli/tui-view.js.map +1 -0
- package/dist/extensions/companion-guard.js +361 -0
- package/dist/extensions/companion-guard.js.map +1 -0
- package/dist/extensions/debug-round.js +204 -0
- package/dist/extensions/debug-round.js.map +1 -0
- package/dist/extensions/inject-bridge.js +603 -0
- package/dist/extensions/inject-bridge.js.map +1 -0
- package/dist/extensions/muted-theme.js +12 -0
- package/dist/extensions/muted-theme.js.map +1 -0
- package/dist/extensions/onboarding.js +175 -0
- package/dist/extensions/onboarding.js.map +1 -0
- package/dist/extensions/setup-replay.js +269 -0
- package/dist/extensions/setup-replay.js.map +1 -0
- package/dist/extensions/setup-scan.js +998 -0
- package/dist/extensions/setup-scan.js.map +1 -0
- package/dist/extensions/setup-triage.js +175 -0
- package/dist/extensions/setup-triage.js.map +1 -0
- package/dist/extensions/setup.js +1160 -0
- package/dist/extensions/setup.js.map +1 -0
- package/dist/extensions/tui-view.js +389 -0
- package/dist/extensions/tui-view.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/client.js +376 -0
- package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/config.js +189 -0
- package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/index.js +421 -0
- package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
- package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
- package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
- package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
- package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
- package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
- package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
- package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
- package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
- package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
- package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
- package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
- package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
- package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
- package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
- package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
- package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
- package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
- package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
- package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
- package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
- package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
- package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
- package/dist/src/anomalies.js +483 -0
- package/dist/src/anomalies.js.map +1 -0
- package/dist/src/answerworker.js +180 -0
- package/dist/src/answerworker.js.map +1 -0
- package/dist/src/appstamp.js +68 -0
- package/dist/src/appstamp.js.map +1 -0
- package/dist/src/archive/admission.js +3 -0
- package/dist/src/archive/admission.js.map +1 -0
- package/dist/src/archive/archivedb.js +474 -0
- package/dist/src/archive/archivedb.js.map +1 -0
- package/dist/src/archive/archiver.js +1770 -0
- package/dist/src/archive/archiver.js.map +1 -0
- package/dist/src/archive/archivewrite.js +198 -0
- package/dist/src/archive/archivewrite.js.map +1 -0
- package/dist/src/archive/bundle.js +266 -0
- package/dist/src/archive/bundle.js.map +1 -0
- package/dist/src/archive/dbmove.js +638 -0
- package/dist/src/archive/dbmove.js.map +1 -0
- package/dist/src/archive/duplicatecounts.js +80 -0
- package/dist/src/archive/duplicatecounts.js.map +1 -0
- package/dist/src/archive/eligibility.js +113 -0
- package/dist/src/archive/eligibility.js.map +1 -0
- package/dist/src/archive/extract.js +288 -0
- package/dist/src/archive/extract.js.map +1 -0
- package/dist/src/archive/fsutil.js +330 -0
- package/dist/src/archive/fsutil.js.map +1 -0
- package/dist/src/archive/gzfile.js +277 -0
- package/dist/src/archive/gzfile.js.map +1 -0
- package/dist/src/archive/kinds.js +22 -0
- package/dist/src/archive/kinds.js.map +1 -0
- package/dist/src/archive/livecheckpoint.js +178 -0
- package/dist/src/archive/livecheckpoint.js.map +1 -0
- package/dist/src/archive/liveretention.js +145 -0
- package/dist/src/archive/liveretention.js.map +1 -0
- package/dist/src/archive/manifest.js +284 -0
- package/dist/src/archive/manifest.js.map +1 -0
- package/dist/src/archive/overseer.js +283 -0
- package/dist/src/archive/overseer.js.map +1 -0
- package/dist/src/archive/piglobal.js +129 -0
- package/dist/src/archive/piglobal.js.map +1 -0
- package/dist/src/archive/plan.js +398 -0
- package/dist/src/archive/plan.js.map +1 -0
- package/dist/src/archive/prarchive.js +831 -0
- package/dist/src/archive/prarchive.js.map +1 -0
- package/dist/src/archive/prfiles.js +303 -0
- package/dist/src/archive/prfiles.js.map +1 -0
- package/dist/src/archive/report.js +169 -0
- package/dist/src/archive/report.js.map +1 -0
- package/dist/src/archive/restore.js +276 -0
- package/dist/src/archive/restore.js.map +1 -0
- package/dist/src/archive/rows.js +106 -0
- package/dist/src/archive/rows.js.map +1 -0
- package/dist/src/archive/seal.js +480 -0
- package/dist/src/archive/seal.js.map +1 -0
- package/dist/src/archive/status.js +528 -0
- package/dist/src/archive/status.js.map +1 -0
- package/dist/src/archive/storepass.js +409 -0
- package/dist/src/archive/storepass.js.map +1 -0
- package/dist/src/backfill.js +217 -0
- package/dist/src/backfill.js.map +1 -0
- package/dist/src/bench/cost.js +87 -0
- package/dist/src/bench/exec.js +76 -0
- package/dist/src/bench/values.js +14 -0
- package/dist/src/botcontrol.js +127 -0
- package/dist/src/botcontrol.js.map +1 -0
- package/dist/src/botworker.js +805 -0
- package/dist/src/botworker.js.map +1 -0
- package/dist/src/capture.js +234 -0
- package/dist/src/capture.js.map +1 -0
- package/dist/src/cases.js +1491 -0
- package/dist/src/cases.js.map +1 -0
- package/dist/src/channelfs.js +27 -0
- package/dist/src/channelfs.js.map +1 -0
- package/dist/src/checklist.js +312 -0
- package/dist/src/checklist.js.map +1 -0
- package/dist/src/ciretry.js +91 -0
- package/dist/src/ciretry.js.map +1 -0
- package/dist/src/cli.js +1916 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/cliops.js +333 -0
- package/dist/src/cliops.js.map +1 -0
- package/dist/src/clonedir.js +15 -0
- package/dist/src/clonedir.js.map +1 -0
- package/dist/src/commandeffects.js +43 -0
- package/dist/src/commandeffects.js.map +1 -0
- package/dist/src/commands.js +1312 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/companioninbox.js +203 -0
- package/dist/src/companioninbox.js.map +1 -0
- package/dist/src/compare.js +673 -0
- package/dist/src/compare.js.map +1 -0
- package/dist/src/config.js +1027 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/configcheck.js +111 -0
- package/dist/src/configcheck.js.map +1 -0
- package/dist/src/coreext.js +43 -0
- package/dist/src/coreext.js.map +1 -0
- package/dist/src/daemon.js +929 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/debuground.js +731 -0
- package/dist/src/debuground.js.map +1 -0
- package/dist/src/debugroundpi.js +219 -0
- package/dist/src/debugroundpi.js.map +1 -0
- package/dist/src/diagnostics.js +14 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/digest.js +139 -0
- package/dist/src/digest.js.map +1 -0
- package/dist/src/dispatch.js +1726 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/engine/answerguard.js +18 -0
- package/dist/src/engine/answerguard.js.map +1 -0
- package/dist/src/engine/botsdk.js +262 -0
- package/dist/src/engine/botsdk.js.map +1 -0
- package/dist/src/engine/caserunner-sdk.js +332 -0
- package/dist/src/engine/caserunner-sdk.js.map +1 -0
- package/dist/src/engine/debugpi.js +194 -0
- package/dist/src/engine/debugpi.js.map +1 -0
- package/dist/src/eod.js +473 -0
- package/dist/src/eod.js.map +1 -0
- package/dist/src/eodpinwatch.js +391 -0
- package/dist/src/eodpinwatch.js.map +1 -0
- package/dist/src/escalation.js +1009 -0
- package/dist/src/escalation.js.map +1 -0
- package/dist/src/eventloop.js +779 -0
- package/dist/src/eventloop.js.map +1 -0
- package/dist/src/execboundary.js +430 -0
- package/dist/src/execboundary.js.map +1 -0
- package/dist/src/feedback.js +915 -0
- package/dist/src/feedback.js.map +1 -0
- package/dist/src/findingscheck.js +200 -0
- package/dist/src/findingscheck.js.map +1 -0
- package/dist/src/fsdenied.js +26 -0
- package/dist/src/fsdenied.js.map +1 -0
- package/dist/src/ghread.js +181 -0
- package/dist/src/ghread.js.map +1 -0
- package/dist/src/github.js +1063 -0
- package/dist/src/github.js.map +1 -0
- package/dist/src/homes.js +103 -0
- package/dist/src/homes.js.map +1 -0
- package/dist/src/hostroute.js +24 -0
- package/dist/src/hostroute.js.map +1 -0
- package/dist/src/improvements.js +625 -0
- package/dist/src/improvements.js.map +1 -0
- package/dist/src/install.js +946 -0
- package/dist/src/install.js.map +1 -0
- package/dist/src/installkind.js +40 -0
- package/dist/src/installkind.js.map +1 -0
- package/dist/src/ipc/capability.js +286 -0
- package/dist/src/ipc/capability.js.map +1 -0
- package/dist/src/ipc/client.js +226 -0
- package/dist/src/ipc/client.js.map +1 -0
- package/dist/src/ipc/control.js +70 -0
- package/dist/src/ipc/control.js.map +1 -0
- package/dist/src/ipc/ghreadshim.js +77 -0
- package/dist/src/ipc/ghreadshim.js.map +1 -0
- package/dist/src/ipc/launcher.js +65 -0
- package/dist/src/ipc/launcher.js.map +1 -0
- package/dist/src/ipc/permits.js +293 -0
- package/dist/src/ipc/permits.js.map +1 -0
- package/dist/src/ipc/postshim.js +137 -0
- package/dist/src/ipc/postshim.js.map +1 -0
- package/dist/src/ipc/protocol.js +239 -0
- package/dist/src/ipc/protocol.js.map +1 -0
- package/dist/src/ipc/server.js +312 -0
- package/dist/src/ipc/server.js.map +1 -0
- package/dist/src/ipc/workerspec.js +148 -0
- package/dist/src/ipc/workerspec.js.map +1 -0
- package/dist/src/learner/learner.js +1272 -0
- package/dist/src/learner/learner.js.map +1 -0
- package/dist/src/learner/miner.js +948 -0
- package/dist/src/learner/miner.js.map +1 -0
- package/dist/src/learner/packet.js +216 -0
- package/dist/src/learner/packet.js.map +1 -0
- package/dist/src/learner/receipt.js +135 -0
- package/dist/src/learner/receipt.js.map +1 -0
- package/dist/src/learner/status.js +38 -0
- package/dist/src/learner/status.js.map +1 -0
- package/dist/src/ledgercheck.js +120 -0
- package/dist/src/ledgercheck.js.map +1 -0
- package/dist/src/lessonledger.js +451 -0
- package/dist/src/lessonledger.js.map +1 -0
- package/dist/src/lessonselect.js +639 -0
- package/dist/src/lessonselect.js.map +1 -0
- package/dist/src/lessonshadow.js +540 -0
- package/dist/src/lessonshadow.js.map +1 -0
- package/dist/src/levers.js +302 -0
- package/dist/src/levers.js.map +1 -0
- package/dist/src/live/projection.js +450 -0
- package/dist/src/live/projection.js.map +1 -0
- package/dist/src/live/publish.js +83 -0
- package/dist/src/live/publish.js.map +1 -0
- package/dist/src/live/read.js +60 -0
- package/dist/src/live/read.js.map +1 -0
- package/dist/src/livecli.js +288 -0
- package/dist/src/livecli.js.map +1 -0
- package/dist/src/loginhint.js +42 -0
- package/dist/src/loginhint.js.map +1 -0
- package/dist/src/loginrefresh/atomic.js +66 -0
- package/dist/src/loginrefresh/atomic.js.map +1 -0
- package/dist/src/loginrefresh/lock.js +93 -0
- package/dist/src/loginrefresh/lock.js.map +1 -0
- package/dist/src/loginrefresh/main.js +58 -0
- package/dist/src/loginrefresh/main.js.map +1 -0
- package/dist/src/loginrefresh/pirefresh.js +49 -0
- package/dist/src/loginrefresh/pirefresh.js.map +1 -0
- package/dist/src/loginrefresh/refresher.js +286 -0
- package/dist/src/loginrefresh/refresher.js.map +1 -0
- package/dist/src/loginrefresh/supervisor.js +253 -0
- package/dist/src/loginrefresh/supervisor.js.map +1 -0
- package/dist/src/loginrefresh/types.js +4 -0
- package/dist/src/loginrefresh/types.js.map +1 -0
- package/dist/src/memory/context.js +251 -0
- package/dist/src/memory/context.js.map +1 -0
- package/dist/src/memory/datafence.js +5 -0
- package/dist/src/memory/datafence.js.map +1 -0
- package/dist/src/memory/delivery.js +131 -0
- package/dist/src/memory/delivery.js.map +1 -0
- package/dist/src/memory/eventtags.js +81 -0
- package/dist/src/memory/eventtags.js.map +1 -0
- package/dist/src/memory/health.js +713 -0
- package/dist/src/memory/health.js.map +1 -0
- package/dist/src/memory/import.js +504 -0
- package/dist/src/memory/import.js.map +1 -0
- package/dist/src/memory/layout.js +252 -0
- package/dist/src/memory/layout.js.map +1 -0
- package/dist/src/memory/legacy-file.js +100 -0
- package/dist/src/memory/legacy-file.js.map +1 -0
- package/dist/src/memory/lessons.js +370 -0
- package/dist/src/memory/lessons.js.map +1 -0
- package/dist/src/memory/ovsession.js +449 -0
- package/dist/src/memory/ovsession.js.map +1 -0
- package/dist/src/memory/relayout.js +259 -0
- package/dist/src/memory/relayout.js.map +1 -0
- package/dist/src/memory/roundactivity.js +180 -0
- package/dist/src/memory/roundactivity.js.map +1 -0
- package/dist/src/memory/selection.js +690 -0
- package/dist/src/memory/selection.js.map +1 -0
- package/dist/src/memory/shadowstatus.js +69 -0
- package/dist/src/memory/shadowstatus.js.map +1 -0
- package/dist/src/memory/store.js +83 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/memory/textidentity.js +8 -0
- package/dist/src/memory/textidentity.js.map +1 -0
- package/dist/src/metrics.js +773 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/missingconfig.js +8 -0
- package/dist/src/missingconfig.js.map +1 -0
- package/dist/src/npmverbs.js +226 -0
- package/dist/src/npmverbs.js.map +1 -0
- package/dist/src/onboard/choices.js +191 -0
- package/dist/src/onboard/choices.js.map +1 -0
- package/dist/src/onboard/engine.js +344 -0
- package/dist/src/onboard/engine.js.map +1 -0
- package/dist/src/onboard/launch.js +83 -0
- package/dist/src/onboard/launch.js.map +1 -0
- package/dist/src/onboard/steps/appslug.js +211 -0
- package/dist/src/onboard/steps/appslug.js.map +1 -0
- package/dist/src/onboard/steps/bringup.js +408 -0
- package/dist/src/onboard/steps/bringup.js.map +1 -0
- package/dist/src/onboard/steps/compose.js +343 -0
- package/dist/src/onboard/steps/compose.js.map +1 -0
- package/dist/src/onboard/steps/configdelivery.js +50 -0
- package/dist/src/onboard/steps/configdelivery.js.map +1 -0
- package/dist/src/onboard/steps/configfill.js +1148 -0
- package/dist/src/onboard/steps/configfill.js.map +1 -0
- package/dist/src/onboard/steps/deploysh.js +80 -0
- package/dist/src/onboard/steps/deploysh.js.map +1 -0
- package/dist/src/onboard/steps/embeddings.js +472 -0
- package/dist/src/onboard/steps/embeddings.js.map +1 -0
- package/dist/src/onboard/steps/escalation.js +571 -0
- package/dist/src/onboard/steps/escalation.js.map +1 -0
- package/dist/src/onboard/steps/githubapp.js +1092 -0
- package/dist/src/onboard/steps/githubapp.js.map +1 -0
- package/dist/src/onboard/steps/golive.js +506 -0
- package/dist/src/onboard/steps/golive.js.map +1 -0
- package/dist/src/onboard/steps/index.js +67 -0
- package/dist/src/onboard/steps/index.js.map +1 -0
- package/dist/src/onboard/steps/ingress.js +762 -0
- package/dist/src/onboard/steps/ingress.js.map +1 -0
- package/dist/src/onboard/steps/inventory.js +420 -0
- package/dist/src/onboard/steps/inventory.js.map +1 -0
- package/dist/src/onboard/steps/llmauth.js +893 -0
- package/dist/src/onboard/steps/llmauth.js.map +1 -0
- package/dist/src/onboard/steps/memory.js +1060 -0
- package/dist/src/onboard/steps/memory.js.map +1 -0
- package/dist/src/onboard/steps/memorykeys.js +43 -0
- package/dist/src/onboard/steps/memorykeys.js.map +1 -0
- package/dist/src/onboard/steps/preflight.js +172 -0
- package/dist/src/onboard/steps/preflight.js.map +1 -0
- package/dist/src/onboard/steps/proof.js +502 -0
- package/dist/src/onboard/steps/proof.js.map +1 -0
- package/dist/src/onboard/steps/ssh.js +300 -0
- package/dist/src/onboard/steps/ssh.js.map +1 -0
- package/dist/src/onboard/steps/topology.js +291 -0
- package/dist/src/onboard/steps/topology.js.map +1 -0
- package/dist/src/onboard/steps/transport.js +511 -0
- package/dist/src/onboard/steps/transport.js.map +1 -0
- package/dist/src/onboard/steps/verifyup.js +362 -0
- package/dist/src/onboard/steps/verifyup.js.map +1 -0
- package/dist/src/onboard/steps/vmdeploy.js +368 -0
- package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
- package/dist/src/onboard/toml.js +53 -0
- package/dist/src/onboard/toml.js.map +1 -0
- package/dist/src/opencli.js +508 -0
- package/dist/src/opencli.js.map +1 -0
- package/dist/src/openpr.js +457 -0
- package/dist/src/openpr.js.map +1 -0
- package/dist/src/openviking/client.js +821 -0
- package/dist/src/openviking/client.js.map +1 -0
- package/dist/src/openviking/context.js +218 -0
- package/dist/src/openviking/context.js.map +1 -0
- package/dist/src/openviking/embedder.js +457 -0
- package/dist/src/openviking/embedder.js.map +1 -0
- package/dist/src/openviking/gapsink.js +38 -0
- package/dist/src/openviking/gapsink.js.map +1 -0
- package/dist/src/openviking/health.js +69 -0
- package/dist/src/openviking/health.js.map +1 -0
- package/dist/src/openviking/index.js +24 -0
- package/dist/src/openviking/index.js.map +1 -0
- package/dist/src/openviking/lessonfind.js +54 -0
- package/dist/src/openviking/lessonfind.js.map +1 -0
- package/dist/src/openviking/probe.js +89 -0
- package/dist/src/openviking/probe.js.map +1 -0
- package/dist/src/openviking/proc.js +95 -0
- package/dist/src/openviking/proc.js.map +1 -0
- package/dist/src/openviking/provisionreport.js +144 -0
- package/dist/src/openviking/provisionreport.js.map +1 -0
- package/dist/src/openviking/reembed.js +317 -0
- package/dist/src/openviking/reembed.js.map +1 -0
- package/dist/src/openviking/relayout.js +153 -0
- package/dist/src/openviking/relayout.js.map +1 -0
- package/dist/src/openviking/sidecar-main.js +39 -0
- package/dist/src/openviking/sidecar-main.js.map +1 -0
- package/dist/src/openviking/sidecar.js +79 -0
- package/dist/src/openviking/sidecar.js.map +1 -0
- package/dist/src/openviking/store.js +1758 -0
- package/dist/src/openviking/store.js.map +1 -0
- package/dist/src/openviking/supervisor.js +422 -0
- package/dist/src/openviking/supervisor.js.map +1 -0
- package/dist/src/openviking/types.js +5 -0
- package/dist/src/openviking/types.js.map +1 -0
- package/dist/src/operator-recovery.js +172 -0
- package/dist/src/operator-recovery.js.map +1 -0
- package/dist/src/operatormessage.js +44 -0
- package/dist/src/operatormessage.js.map +1 -0
- package/dist/src/outcomes.js +1361 -0
- package/dist/src/outcomes.js.map +1 -0
- package/dist/src/piauthcopy.js +5 -0
- package/dist/src/piauthcopy.js.map +1 -0
- package/dist/src/picli.js +45 -0
- package/dist/src/picli.js.map +1 -0
- package/dist/src/picwd.js +25 -0
- package/dist/src/picwd.js.map +1 -0
- package/dist/src/pilogin.js +35 -0
- package/dist/src/pilogin.js.map +1 -0
- package/dist/src/pingpong.js +1028 -0
- package/dist/src/pingpong.js.map +1 -0
- package/dist/src/posting-journal.js +441 -0
- package/dist/src/posting-journal.js.map +1 -0
- package/dist/src/posting-outbox.js +662 -0
- package/dist/src/posting-outbox.js.map +1 -0
- package/dist/src/posting-receipt.js +203 -0
- package/dist/src/posting-receipt.js.map +1 -0
- package/dist/src/posting.js +348 -0
- package/dist/src/posting.js.map +1 -0
- package/dist/src/postops.js +637 -0
- package/dist/src/postops.js.map +1 -0
- package/dist/src/pr-activity.js +11 -0
- package/dist/src/pr-activity.js.map +1 -0
- package/dist/src/pr-author.js +5 -0
- package/dist/src/pr-author.js.map +1 -0
- package/dist/src/pr-conversation.js +355 -0
- package/dist/src/pr-conversation.js.map +1 -0
- package/dist/src/pr-session-lifecycle.js +542 -0
- package/dist/src/pr-session-lifecycle.js.map +1 -0
- package/dist/src/preflight.js +341 -0
- package/dist/src/preflight.js.map +1 -0
- package/dist/src/priorresolve.js +275 -0
- package/dist/src/priorresolve.js.map +1 -0
- package/dist/src/qcrbin.js +180 -0
- package/dist/src/qcrbin.js.map +1 -0
- package/dist/src/recheck.js +1398 -0
- package/dist/src/recheck.js.map +1 -0
- package/dist/src/reconcile.js +820 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/recordedfindings.js +15 -0
- package/dist/src/recordedfindings.js.map +1 -0
- package/dist/src/regression.js +546 -0
- package/dist/src/regression.js.map +1 -0
- package/dist/src/replay.js +598 -0
- package/dist/src/replay.js.map +1 -0
- package/dist/src/requiredpasses.js +113 -0
- package/dist/src/requiredpasses.js.map +1 -0
- package/dist/src/resumecheck.js +168 -0
- package/dist/src/resumecheck.js.map +1 -0
- package/dist/src/reviewcli.js +671 -0
- package/dist/src/reviewcli.js.map +1 -0
- package/dist/src/reviewcontext.js +22 -0
- package/dist/src/reviewcontext.js.map +1 -0
- package/dist/src/reviewdetach.js +574 -0
- package/dist/src/reviewdetach.js.map +1 -0
- package/dist/src/reviewhistory.js +284 -0
- package/dist/src/reviewhistory.js.map +1 -0
- package/dist/src/reviewhome.js +26 -0
- package/dist/src/reviewhome.js.map +1 -0
- package/dist/src/reviewmemory.js +110 -0
- package/dist/src/reviewmemory.js.map +1 -0
- package/dist/src/reviewrendercli.js +104 -0
- package/dist/src/reviewrendercli.js.map +1 -0
- package/dist/src/round-work.js +20 -0
- package/dist/src/round-work.js.map +1 -0
- package/dist/src/runhost.js +72 -0
- package/dist/src/runhost.js.map +1 -0
- package/dist/src/scanauth.js +25 -0
- package/dist/src/scanauth.js.map +1 -0
- package/dist/src/scanbox.js +846 -0
- package/dist/src/scanbox.js.map +1 -0
- package/dist/src/scancli.js +1074 -0
- package/dist/src/scancli.js.map +1 -0
- package/dist/src/scandriverlog.js +30 -0
- package/dist/src/scandriverlog.js.map +1 -0
- package/dist/src/session-bridge.js +75 -0
- package/dist/src/session-bridge.js.map +1 -0
- package/dist/src/session-command-payload.js +29 -0
- package/dist/src/session-command-payload.js.map +1 -0
- package/dist/src/session-command-supervisor.js +939 -0
- package/dist/src/session-command-supervisor.js.map +1 -0
- package/dist/src/sessionjsonl.js +85 -0
- package/dist/src/sessionjsonl.js.map +1 -0
- package/dist/src/sessions.js +541 -0
- package/dist/src/sessions.js.map +1 -0
- package/dist/src/setup/adapters.js +330 -0
- package/dist/src/setup/adapters.js.map +1 -0
- package/dist/src/setup/baselines.js +64 -0
- package/dist/src/setup/baselines.js.map +1 -0
- package/dist/src/setup/caserunner.js +554 -0
- package/dist/src/setup/caserunner.js.map +1 -0
- package/dist/src/setup/configcommit.js +358 -0
- package/dist/src/setup/configcommit.js.map +1 -0
- package/dist/src/setup/engine.js +672 -0
- package/dist/src/setup/engine.js.map +1 -0
- package/dist/src/setup/facts.js +593 -0
- package/dist/src/setup/facts.js.map +1 -0
- package/dist/src/setup/findings.js +948 -0
- package/dist/src/setup/findings.js.map +1 -0
- package/dist/src/setup/history.js +338 -0
- package/dist/src/setup/history.js.map +1 -0
- package/dist/src/setup/interview.js +185 -0
- package/dist/src/setup/interview.js.map +1 -0
- package/dist/src/setup/ledger.js +207 -0
- package/dist/src/setup/ledger.js.map +1 -0
- package/dist/src/setup/pins.js +43 -0
- package/dist/src/setup/pins.js.map +1 -0
- package/dist/src/setup/portrait.js +432 -0
- package/dist/src/setup/portrait.js.map +1 -0
- package/dist/src/setup/presentation.js +9 -0
- package/dist/src/setup/presentation.js.map +1 -0
- package/dist/src/setup/profile.js +417 -0
- package/dist/src/setup/profile.js.map +1 -0
- package/dist/src/setup/replay.js +240 -0
- package/dist/src/setup/replay.js.map +1 -0
- package/dist/src/setup/replaybuild.js +311 -0
- package/dist/src/setup/replaybuild.js.map +1 -0
- package/dist/src/setup/replayrun.js +806 -0
- package/dist/src/setup/replayrun.js.map +1 -0
- package/dist/src/setup/replaysource.js +169 -0
- package/dist/src/setup/replaysource.js.map +1 -0
- package/dist/src/setup/retune.js +333 -0
- package/dist/src/setup/retune.js.map +1 -0
- package/dist/src/setup/retunelog.js +51 -0
- package/dist/src/setup/retunelog.js.map +1 -0
- package/dist/src/setup/scan.js +725 -0
- package/dist/src/setup/scan.js.map +1 -0
- package/dist/src/setup/scanbuild.js +133 -0
- package/dist/src/setup/scanbuild.js.map +1 -0
- package/dist/src/setup/scanfacts.js +96 -0
- package/dist/src/setup/scanfacts.js.map +1 -0
- package/dist/src/setup/scanlease.js +251 -0
- package/dist/src/setup/scanlease.js.map +1 -0
- package/dist/src/setup/scanreport.js +289 -0
- package/dist/src/setup/scanreport.js.map +1 -0
- package/dist/src/setup/scanrun.js +1733 -0
- package/dist/src/setup/scanrun.js.map +1 -0
- package/dist/src/setup/scantree.js +257 -0
- package/dist/src/setup/scantree.js.map +1 -0
- package/dist/src/setup/stage2.js +617 -0
- package/dist/src/setup/stage2.js.map +1 -0
- package/dist/src/setup/stage2request.js +259 -0
- package/dist/src/setup/stage2request.js.map +1 -0
- package/dist/src/setup/survey.js +307 -0
- package/dist/src/setup/survey.js.map +1 -0
- package/dist/src/setup/surveyrun.js +549 -0
- package/dist/src/setup/surveyrun.js.map +1 -0
- package/dist/src/setup/textsniff.js +89 -0
- package/dist/src/setup/textsniff.js.map +1 -0
- package/dist/src/setup/trackersink.js +395 -0
- package/dist/src/setup/trackersink.js.map +1 -0
- package/dist/src/setup/triage.js +636 -0
- package/dist/src/setup/triage.js.map +1 -0
- package/dist/src/setup/tuning.js +585 -0
- package/dist/src/setup/tuning.js.map +1 -0
- package/dist/src/setup/unitcut.js +437 -0
- package/dist/src/setup/unitcut.js.map +1 -0
- package/dist/src/singleflight.js +72 -0
- package/dist/src/singleflight.js.map +1 -0
- package/dist/src/slack/announce.js +725 -0
- package/dist/src/slack/announce.js.map +1 -0
- package/dist/src/slack/api.js +227 -0
- package/dist/src/slack/api.js.map +1 -0
- package/dist/src/slack/messages.js +158 -0
- package/dist/src/slack/messages.js.map +1 -0
- package/dist/src/slack/origin.js +550 -0
- package/dist/src/slack/origin.js.map +1 -0
- package/dist/src/slack/policy.js +225 -0
- package/dist/src/slack/policy.js.map +1 -0
- package/dist/src/slack/reply.js +153 -0
- package/dist/src/slack/reply.js.map +1 -0
- package/dist/src/slack/router.js +189 -0
- package/dist/src/slack/router.js.map +1 -0
- package/dist/src/slack/socket.js +323 -0
- package/dist/src/slack/socket.js.map +1 -0
- package/dist/src/slack/types.js +11 -0
- package/dist/src/slack/types.js.map +1 -0
- package/dist/src/sqlitehint.js +30 -0
- package/dist/src/sqlitehint.js.map +1 -0
- package/dist/src/ssh-observation.js +25 -0
- package/dist/src/ssh-observation.js.map +1 -0
- package/dist/src/state.js +5869 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/statemachine.js +220 -0
- package/dist/src/statemachine.js.map +1 -0
- package/dist/src/stubs.js +52 -0
- package/dist/src/stubs.js.map +1 -0
- package/dist/src/takeover.js +506 -0
- package/dist/src/takeover.js.map +1 -0
- package/dist/src/takeoverpi.js +249 -0
- package/dist/src/takeoverpi.js.map +1 -0
- package/dist/src/terminal-failure.js +26 -0
- package/dist/src/terminal-failure.js.map +1 -0
- package/dist/src/thread-resolution-refusal.js +4 -0
- package/dist/src/thread-resolution-refusal.js.map +1 -0
- package/dist/src/tmux.js +126 -0
- package/dist/src/tmux.js.map +1 -0
- package/dist/src/trackerid.js +43 -0
- package/dist/src/trackerid.js.map +1 -0
- package/dist/src/transcriptindex.js +512 -0
- package/dist/src/transcriptindex.js.map +1 -0
- package/dist/src/tui/actions.js +404 -0
- package/dist/src/tui/actions.js.map +1 -0
- package/dist/src/tui/alerts.js +523 -0
- package/dist/src/tui/alerts.js.map +1 -0
- package/dist/src/tui/app.js +1835 -0
- package/dist/src/tui/app.js.map +1 -0
- package/dist/src/tui/archiveops.js +164 -0
- package/dist/src/tui/archiveops.js.map +1 -0
- package/dist/src/tui/autofile.js +191 -0
- package/dist/src/tui/autofile.js.map +1 -0
- package/dist/src/tui/board.js +1143 -0
- package/dist/src/tui/board.js.map +1 -0
- package/dist/src/tui/candidates.js +699 -0
- package/dist/src/tui/candidates.js.map +1 -0
- package/dist/src/tui/cellwidth.js +151 -0
- package/dist/src/tui/cellwidth.js.map +1 -0
- package/dist/src/tui/changelog-store.js +404 -0
- package/dist/src/tui/changelog-store.js.map +1 -0
- package/dist/src/tui/chord.js +26 -0
- package/dist/src/tui/chord.js.map +1 -0
- package/dist/src/tui/clipboard.js +81 -0
- package/dist/src/tui/clipboard.js.map +1 -0
- package/dist/src/tui/cmdbar.js +253 -0
- package/dist/src/tui/cmdbar.js.map +1 -0
- package/dist/src/tui/companion.js +1275 -0
- package/dist/src/tui/companion.js.map +1 -0
- package/dist/src/tui/companionplan.js +200 -0
- package/dist/src/tui/companionplan.js.map +1 -0
- package/dist/src/tui/companiontab.js +61 -0
- package/dist/src/tui/companiontab.js.map +1 -0
- package/dist/src/tui/confighome.js +105 -0
- package/dist/src/tui/confighome.js.map +1 -0
- package/dist/src/tui/connect.js +44 -0
- package/dist/src/tui/connect.js.map +1 -0
- package/dist/src/tui/deploy-ceremony.js +75 -0
- package/dist/src/tui/deploy-ceremony.js.map +1 -0
- package/dist/src/tui/deploy-drain.js +93 -0
- package/dist/src/tui/deploy-drain.js.map +1 -0
- package/dist/src/tui/deploy-process.js +234 -0
- package/dist/src/tui/deploy-process.js.map +1 -0
- package/dist/src/tui/deploytab.js +1263 -0
- package/dist/src/tui/deploytab.js.map +1 -0
- package/dist/src/tui/design.js +111 -0
- package/dist/src/tui/design.js.map +1 -0
- package/dist/src/tui/digesttab.js +148 -0
- package/dist/src/tui/digesttab.js.map +1 -0
- package/dist/src/tui/emptystate.js +32 -0
- package/dist/src/tui/emptystate.js.map +1 -0
- package/dist/src/tui/evidence.js +790 -0
- package/dist/src/tui/evidence.js.map +1 -0
- package/dist/src/tui/feed.js +415 -0
- package/dist/src/tui/feed.js.map +1 -0
- package/dist/src/tui/feedbacktab.js +907 -0
- package/dist/src/tui/feedbacktab.js.map +1 -0
- package/dist/src/tui/filters.js +72 -0
- package/dist/src/tui/filters.js.map +1 -0
- package/dist/src/tui/ghread.js +272 -0
- package/dist/src/tui/ghread.js.map +1 -0
- package/dist/src/tui/historypull.js +61 -0
- package/dist/src/tui/historypull.js.map +1 -0
- package/dist/src/tui/inboxpull.js +197 -0
- package/dist/src/tui/inboxpull.js.map +1 -0
- package/dist/src/tui/index.js +1425 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/issuesink.js +206 -0
- package/dist/src/tui/issuesink.js.map +1 -0
- package/dist/src/tui/jsonfile.js +32 -0
- package/dist/src/tui/jsonfile.js.map +1 -0
- package/dist/src/tui/keys.js +97 -0
- package/dist/src/tui/keys.js.map +1 -0
- package/dist/src/tui/layout.js +70 -0
- package/dist/src/tui/layout.js.map +1 -0
- package/dist/src/tui/learning.js +469 -0
- package/dist/src/tui/learning.js.map +1 -0
- package/dist/src/tui/lessonswitch.js +103 -0
- package/dist/src/tui/lessonswitch.js.map +1 -0
- package/dist/src/tui/livecontrol.js +167 -0
- package/dist/src/tui/livecontrol.js.map +1 -0
- package/dist/src/tui/memforward.js +310 -0
- package/dist/src/tui/memforward.js.map +1 -0
- package/dist/src/tui/memincident.js +287 -0
- package/dist/src/tui/memincident.js.map +1 -0
- package/dist/src/tui/memory.js +485 -0
- package/dist/src/tui/memory.js.map +1 -0
- package/dist/src/tui/memtab.js +513 -0
- package/dist/src/tui/memtab.js.map +1 -0
- package/dist/src/tui/metricstab.js +979 -0
- package/dist/src/tui/metricstab.js.map +1 -0
- package/dist/src/tui/model.js +104 -0
- package/dist/src/tui/model.js.map +1 -0
- package/dist/src/tui/mouse.js +83 -0
- package/dist/src/tui/mouse.js.map +1 -0
- package/dist/src/tui/msg.js +196 -0
- package/dist/src/tui/msg.js.map +1 -0
- package/dist/src/tui/nativecompanion.js +321 -0
- package/dist/src/tui/nativecompanion.js.map +1 -0
- package/dist/src/tui/openurl.js +17 -0
- package/dist/src/tui/openurl.js.map +1 -0
- package/dist/src/tui/paint.js +501 -0
- package/dist/src/tui/paint.js.map +1 -0
- package/dist/src/tui/panes.js +74 -0
- package/dist/src/tui/panes.js.map +1 -0
- package/dist/src/tui/pipane.js +166 -0
- package/dist/src/tui/pipane.js.map +1 -0
- package/dist/src/tui/poll-hooks.js +11 -0
- package/dist/src/tui/poll-hooks.js.map +1 -0
- package/dist/src/tui/poll.js +113 -0
- package/dist/src/tui/poll.js.map +1 -0
- package/dist/src/tui/proposals.js +715 -0
- package/dist/src/tui/proposals.js.map +1 -0
- package/dist/src/tui/prpause.js +132 -0
- package/dist/src/tui/prpause.js.map +1 -0
- package/dist/src/tui/reload.js +351 -0
- package/dist/src/tui/reload.js.map +1 -0
- package/dist/src/tui/remote.js +312 -0
- package/dist/src/tui/remote.js.map +1 -0
- package/dist/src/tui/rollover.js +767 -0
- package/dist/src/tui/rollover.js.map +1 -0
- package/dist/src/tui/scantab.js +1053 -0
- package/dist/src/tui/scantab.js.map +1 -0
- package/dist/src/tui/setupentry.js +81 -0
- package/dist/src/tui/setupentry.js.map +1 -0
- package/dist/src/tui/sidebar.js +602 -0
- package/dist/src/tui/sidebar.js.map +1 -0
- package/dist/src/tui/sshtriage.js +393 -0
- package/dist/src/tui/sshtriage.js.map +1 -0
- package/dist/src/tui/startup.js +172 -0
- package/dist/src/tui/startup.js.map +1 -0
- package/dist/src/tui/tabs.js +41 -0
- package/dist/src/tui/tabs.js.map +1 -0
- package/dist/src/tui/termmirror.js +217 -0
- package/dist/src/tui/termmirror.js.map +1 -0
- package/dist/src/tui/theme.js +95 -0
- package/dist/src/tui/theme.js.map +1 -0
- package/dist/src/tui/ticketstab.js +224 -0
- package/dist/src/tui/ticketstab.js.map +1 -0
- package/dist/src/tui/tmuxsocket.js +101 -0
- package/dist/src/tui/tmuxsocket.js.map +1 -0
- package/dist/src/tui/trackertickets.js +77 -0
- package/dist/src/tui/trackertickets.js.map +1 -0
- package/dist/src/tui/unwedge.js +245 -0
- package/dist/src/tui/unwedge.js.map +1 -0
- package/dist/src/tui/viewer.js +479 -0
- package/dist/src/tui/viewer.js.map +1 -0
- package/dist/src/tui/viewerinput.js +125 -0
- package/dist/src/tui/viewerinput.js.map +1 -0
- package/dist/src/updatenotice.js +143 -0
- package/dist/src/updatenotice.js.map +1 -0
- package/dist/src/usage.js +1007 -0
- package/dist/src/usage.js.map +1 -0
- package/dist/src/verdicts.js +3848 -0
- package/dist/src/verdicts.js.map +1 -0
- package/dist/src/version.js +58 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/wakes.js +411 -0
- package/dist/src/wakes.js.map +1 -0
- package/dist/src/webhook.js +725 -0
- package/dist/src/webhook.js.map +1 -0
- package/dist/src/webhookheal.js +238 -0
- package/dist/src/webhookheal.js.map +1 -0
- package/dist/src/wire.js +1801 -0
- package/dist/src/wire.js.map +1 -0
- package/dist/src/worker-session-host.js +531 -0
- package/dist/src/worker-session-host.js.map +1 -0
- package/dist/src/workerbreaker.js +112 -0
- package/dist/src/workerbreaker.js.map +1 -0
- package/dist/src/workercontrol.js +49 -0
- package/dist/src/workercontrol.js.map +1 -0
- package/dist/src/workerlease.js +351 -0
- package/dist/src/workerlease.js.map +1 -0
- package/dist/src/workerliveness.js +84 -0
- package/dist/src/workerliveness.js.map +1 -0
- package/dist/src/workerlog.js +65 -0
- package/dist/src/workerlog.js.map +1 -0
- package/dist/src/workerruntime.js +10 -0
- package/dist/src/workerruntime.js.map +1 -0
- package/dist/src/workersupervisor.js +1206 -0
- package/dist/src/workersupervisor.js.map +1 -0
- package/dist/src/workerusage.js +23 -0
- package/dist/src/workerusage.js.map +1 -0
- package/dist/src/workerworld.js +332 -0
- package/dist/src/workerworld.js.map +1 -0
- package/dist/src/worktrees.js +984 -0
- package/dist/src/worktrees.js.map +1 -0
- package/docker/Dockerfile +284 -0
- package/docker/Dockerfile.dockerignore +35 -0
- package/docker/README.md +221 -0
- package/docker/companion/Dockerfile +139 -0
- package/docker/companion/agents/evidence.md +34 -0
- package/docker/companion/agents/explore.md +26 -0
- package/docker/companion/agents/pass-survey.md +49 -0
- package/docker/companion/agents/study.md +26 -0
- package/docker/companion/entrypoint.sh +343 -0
- package/docker/companion/memory-health.mjs +269 -0
- package/docker/entrypoint.sh +782 -0
- package/docker/memory/Dockerfile +191 -0
- package/docker/memory/Dockerfile.dockerignore +20 -0
- package/docker/memory/README.md +60 -0
- package/docker/memory/entrypoint.sh +163 -0
- package/docker/setid/qcr-setid.c +109 -0
- package/docker/tunnel/Dockerfile.cloudflared +51 -0
- package/docker/tunnel/Dockerfile.ngrok +40 -0
- package/docker/tunnel/sidecar.mjs +411 -0
- package/docker/vm/tmux.conf +26 -0
- package/docker-compose.yml +348 -0
- package/extensions/cli/bash-guard-ambient.ts +71 -0
- package/extensions/cli/bash-guard.ts +12 -0
- package/extensions/cli/companion-guard.ts +8 -0
- package/extensions/cli/debug-round.ts +9 -0
- package/extensions/cli/inject-bridge.ts +8 -0
- package/extensions/cli/ledger-pin.ts +13 -0
- package/extensions/cli/lens.ts +22 -0
- package/extensions/cli/muted-theme.ts +9 -0
- package/extensions/cli/onboarding.ts +8 -0
- package/extensions/cli/setup.ts +8 -0
- package/extensions/cli/tui-view.ts +8 -0
- package/extensions/companion-guard.ts +411 -0
- package/extensions/debug-round.ts +281 -0
- package/extensions/inject-bridge.ts +630 -0
- package/extensions/inline-bash.ts +102 -0
- package/extensions/muted-theme.json +68 -0
- package/extensions/muted-theme.ts +23 -0
- package/extensions/onboarding.ts +233 -0
- package/extensions/setup-replay.ts +336 -0
- package/extensions/setup-scan.ts +1236 -0
- package/extensions/setup-triage.ts +190 -0
- package/extensions/setup.ts +1428 -0
- package/extensions/tui-view.ts +489 -0
- package/extensions/vendor/openviking-pi/LICENSE +201 -0
- package/extensions/vendor/openviking-pi/NOTICE +68 -0
- package/extensions/vendor/openviking-pi/README.md +550 -0
- package/extensions/vendor/openviking-pi/client.ts +588 -0
- package/extensions/vendor/openviking-pi/config.json +28 -0
- package/extensions/vendor/openviking-pi/config.ts +271 -0
- package/extensions/vendor/openviking-pi/index.ts +523 -0
- package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
- package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
- package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
- package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
- package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
- package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
- package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
- package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
- package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
- package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
- package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
- package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
- package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
- package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
- package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
- package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
- package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
- package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
- package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
- package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
- package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
- package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
- package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
- package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
- package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
- package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
- package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
- package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
- package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
- package/extensions/vendor/openviking-pi/recall.ts +149 -0
- package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
- package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
- package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
- package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
- package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
- package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
- package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
- package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
- package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
- package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
- package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
- package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
- package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
- package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
- package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
- package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
- package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
- package/extensions/vendor/openviking-pi/sync.ts +163 -0
- package/extensions/vendor/openviking-pi/takeover.ts +36 -0
- package/extensions/vendor/openviking-pi/tools.ts +479 -0
- package/extensions/vendor/openviking-pi/upstream.json +48 -0
- package/package.json +138 -4
- package/profiles/default/README.md +21 -0
- package/profiles/default/addenda/.gitkeep +0 -0
- package/profiles/default/profile.toml +17 -0
- package/profiles/example-django/README.md +21 -0
- package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
- package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
- package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
- package/profiles/example-django/addenda/40-orm-usage.md +19 -0
- package/profiles/example-django/addenda/50-declared-shape.md +10 -0
- package/profiles/example-django/addenda/60-config-placement.md +14 -0
- package/profiles/example-django/heuristics.toml +24 -0
- package/profiles/example-django/profile.toml +9 -0
- package/profiles/fast/README.md +24 -0
- package/profiles/fast/profile.toml +59 -0
- package/profiles/minimal/README.md +17 -0
- package/profiles/minimal/addenda/.gitkeep +0 -0
- package/profiles/minimal/profile.toml +8 -0
- package/profiles/snappy/README.md +24 -0
- package/profiles/snappy/addenda/10-race-as-question.md +6 -0
- package/profiles/snappy/profile.toml +46 -0
- package/prompts/companion-day-open.md +46 -0
- package/prompts/companion-first-run.md +104 -0
- package/prompts/companion.md +821 -0
- package/prompts/conversation-system.md +117 -0
- package/prompts/eod-sweep.md +120 -0
- package/prompts/onboarding.md +74 -0
- package/prompts/pass-common.md +52 -0
- package/prompts/pass-quality-common.md +22 -0
- package/prompts/round-first.md +874 -0
- package/prompts/round-follow-up.md +991 -0
- package/prompts/round-re-review.md +889 -0
- package/prompts/round-recheck.md +196 -0
- package/prompts/round-staleness.md +856 -0
- package/schemas/review-result.v1.json +391 -0
- package/schemas/scan-result.v1.json +421 -0
- package/schemas/version.v1.json +57 -0
- package/scripts/approve-pr.sh +16 -0
- package/scripts/build-stamp.mjs +67 -0
- package/scripts/commit-verdict.sh +155 -0
- package/scripts/companion-e2e.sh +250 -0
- package/scripts/companion-memory-forward.mjs +1966 -0
- package/scripts/config-fingerprint.sh +28 -0
- package/scripts/deploy-manifest.mjs +141 -0
- package/scripts/deploy.sh +1435 -0
- package/scripts/directory-lock.mjs +193 -0
- package/scripts/heartbeat-fresh.mjs +54 -0
- package/scripts/install-locator.sh +328 -0
- package/scripts/install-wrapper.sh +76 -0
- package/scripts/memory-search-check.mjs +98 -0
- package/scripts/mint-token.sh +212 -0
- package/scripts/openviking-embedder.py +392 -0
- package/scripts/openviking-ov-conf.mjs +161 -0
- package/scripts/openviking-provision.mjs +1122 -0
- package/scripts/openviking-reembed.mjs +1154 -0
- package/scripts/post-review.sh +16 -0
- package/scripts/pr-checks.sh +256 -0
- package/scripts/pr-context.py +174 -0
- package/scripts/pr-context.sh +63 -0
- package/scripts/pr-self.sh +144 -0
- package/scripts/pr-threads.sh +145 -0
- package/scripts/qcr +15 -0
- package/scripts/qcr-host.sh +769 -0
- package/scripts/render-review.sh +24 -0
- package/scripts/reply-thread.sh +16 -0
- package/scripts/required-passes.py +112 -0
- package/scripts/smoke-companion.sh +211 -0
- package/scripts/tui-deploy-runner.mjs +61 -0
- package/scripts/verify-dist.mjs +201 -0
- package/scripts/vm-ghcr-login.sh +188 -0
- package/seeds/agents/pass-baseline.md +61 -0
- package/seeds/agents/pass-contracts.md +30 -0
- package/seeds/agents/pass-data-safety.md +54 -0
- package/seeds/agents/pass-delivery.md +37 -0
- package/seeds/agents/pass-prod-risk.md +24 -0
- package/seeds/agents/pass-security.md +31 -0
- package/seeds/agents/pass-statelessness.md +27 -0
- package/seeds/agents/pass-ui.md +23 -0
- package/seeds/agents/pass-validate.md +50 -0
- package/seeds/prompts/conversation-system.md +117 -0
- package/seeds/prompts/eod-sweep.md +120 -0
- package/seeds/prompts/pass-common.md +52 -0
- package/skills/companion/adjudicate/SKILL.md +247 -0
- package/skills/companion/evidence/SKILL.md +150 -0
- package/skills/companion/land/SKILL.md +200 -0
- package/skills/companion/replay/SKILL.md +138 -0
- package/skills/companion/setup/SKILL.md +167 -0
- package/skills/companion/survey/SKILL.md +160 -0
- package/skills/selfimprove/README.md +34 -0
- package/skills/selfimprove/SKILL.md +127 -0
- package/skills/selfimprove/agents/openai.yaml +2 -0
- package/skills/selfimprove/commands/selfimprove.md +6 -0
- package/skills/selfimprove/references/fix-loop.md +49 -0
- package/skills/selfimprove/references/ledger.md +20 -0
- package/skills/selfimprove/references/setup.md +40 -0
- package/src/anomalies.ts +566 -0
- package/src/answerworker.ts +251 -0
- package/src/appstamp.ts +65 -0
- package/src/archive/admission.ts +3 -0
- package/src/archive/archivedb.ts +548 -0
- package/src/archive/archiver.ts +1963 -0
- package/src/archive/archivewrite.ts +279 -0
- package/src/archive/bundle.ts +333 -0
- package/src/archive/dbmove.ts +911 -0
- package/src/archive/duplicatecounts.ts +98 -0
- package/src/archive/eligibility.ts +181 -0
- package/src/archive/extract.ts +342 -0
- package/src/archive/fsutil.ts +415 -0
- package/src/archive/gzfile.ts +370 -0
- package/src/archive/kinds.ts +21 -0
- package/src/archive/livecheckpoint.ts +217 -0
- package/src/archive/liveretention.ts +180 -0
- package/src/archive/manifest.ts +519 -0
- package/src/archive/overseer.ts +336 -0
- package/src/archive/piglobal.ts +174 -0
- package/src/archive/plan.ts +612 -0
- package/src/archive/prarchive.ts +984 -0
- package/src/archive/prfiles.ts +406 -0
- package/src/archive/report.ts +252 -0
- package/src/archive/restore.ts +330 -0
- package/src/archive/rows.ts +187 -0
- package/src/archive/seal.ts +630 -0
- package/src/archive/status.ts +719 -0
- package/src/archive/storepass.ts +581 -0
- package/src/backfill.ts +362 -0
- package/src/bench/cost.ts +113 -0
- package/src/bench/exec.ts +90 -0
- package/src/bench/values.ts +13 -0
- package/src/botcontrol.ts +176 -0
- package/src/botworker.ts +953 -0
- package/src/capture.ts +321 -0
- package/src/cases.ts +1789 -0
- package/src/channelfs.ts +26 -0
- package/src/checklist.ts +506 -0
- package/src/ciretry.ts +141 -0
- package/src/cli.ts +2271 -0
- package/src/cliops.ts +384 -0
- package/src/clonedir.ts +15 -0
- package/src/commandeffects.ts +57 -0
- package/src/commands.ts +1678 -0
- package/src/companioninbox.ts +239 -0
- package/src/compare.ts +957 -0
- package/src/config.ts +2050 -0
- package/src/configcheck.ts +133 -0
- package/src/coreext.ts +47 -0
- package/src/daemon.ts +1302 -0
- package/src/debuground.ts +927 -0
- package/src/debugroundpi.ts +262 -0
- package/src/diagnostics.ts +14 -0
- package/src/digest.ts +183 -0
- package/src/dispatch.ts +2250 -0
- package/src/engine/answerguard.ts +27 -0
- package/src/engine/botsdk.ts +336 -0
- package/src/engine/caserunner-sdk.ts +423 -0
- package/src/engine/debugpi.ts +235 -0
- package/src/eod.ts +669 -0
- package/src/eodpinwatch.ts +556 -0
- package/src/escalation.ts +1253 -0
- package/src/eventloop.ts +1045 -0
- package/src/execboundary.ts +633 -0
- package/src/feedback.ts +1162 -0
- package/src/findingscheck.ts +298 -0
- package/src/fsdenied.ts +24 -0
- package/src/ghread.ts +242 -0
- package/src/github.ts +1497 -0
- package/src/homes.ts +175 -0
- package/src/hostroute.ts +53 -0
- package/src/improvements.ts +787 -0
- package/src/install.ts +1103 -0
- package/src/installkind.ts +50 -0
- package/src/ipc/capability.ts +333 -0
- package/src/ipc/client.ts +269 -0
- package/src/ipc/control.ts +90 -0
- package/src/ipc/ghreadshim.ts +94 -0
- package/src/ipc/launcher.ts +65 -0
- package/src/ipc/permits.ts +362 -0
- package/src/ipc/postshim.ts +164 -0
- package/src/ipc/protocol.ts +342 -0
- package/src/ipc/server.ts +371 -0
- package/src/ipc/workerspec.ts +218 -0
- package/src/learner/learner.ts +1655 -0
- package/src/learner/miner.ts +1290 -0
- package/src/learner/packet.ts +340 -0
- package/src/learner/receipt.ts +168 -0
- package/src/learner/status.ts +107 -0
- package/src/ledgercheck.ts +157 -0
- package/src/lessonledger.ts +703 -0
- package/src/lessonselect.ts +888 -0
- package/src/lessonshadow.ts +810 -0
- package/src/levers.ts +393 -0
- package/src/live/projection.ts +491 -0
- package/src/live/publish.ts +92 -0
- package/src/live/read.ts +86 -0
- package/src/livecli.ts +336 -0
- package/src/loginhint.ts +56 -0
- package/src/loginrefresh/atomic.ts +76 -0
- package/src/loginrefresh/lock.ts +104 -0
- package/src/loginrefresh/main.ts +59 -0
- package/src/loginrefresh/pirefresh.ts +57 -0
- package/src/loginrefresh/refresher.ts +326 -0
- package/src/loginrefresh/supervisor.ts +273 -0
- package/src/loginrefresh/types.ts +35 -0
- package/src/memory/context.ts +385 -0
- package/src/memory/datafence.ts +9 -0
- package/src/memory/delivery.ts +171 -0
- package/src/memory/eventtags.ts +80 -0
- package/src/memory/health.ts +953 -0
- package/src/memory/import.ts +664 -0
- package/src/memory/layout.ts +261 -0
- package/src/memory/legacy-file.ts +104 -0
- package/src/memory/lessons.ts +447 -0
- package/src/memory/ovsession.ts +601 -0
- package/src/memory/relayout.ts +335 -0
- package/src/memory/roundactivity.ts +209 -0
- package/src/memory/selection.ts +989 -0
- package/src/memory/shadowstatus.ts +125 -0
- package/src/memory/store.ts +275 -0
- package/src/memory/textidentity.ts +8 -0
- package/src/metrics.ts +1056 -0
- package/src/missingconfig.ts +7 -0
- package/src/npmverbs.ts +264 -0
- package/src/onboard/choices.ts +222 -0
- package/src/onboard/engine.ts +518 -0
- package/src/onboard/launch.ts +115 -0
- package/src/onboard/steps/appslug.ts +265 -0
- package/src/onboard/steps/bringup.ts +482 -0
- package/src/onboard/steps/compose.ts +459 -0
- package/src/onboard/steps/configdelivery.ts +58 -0
- package/src/onboard/steps/configfill.ts +1352 -0
- package/src/onboard/steps/deploysh.ts +105 -0
- package/src/onboard/steps/embeddings.ts +550 -0
- package/src/onboard/steps/escalation.ts +640 -0
- package/src/onboard/steps/githubapp.ts +1414 -0
- package/src/onboard/steps/golive.ts +577 -0
- package/src/onboard/steps/index.ts +71 -0
- package/src/onboard/steps/ingress.ts +907 -0
- package/src/onboard/steps/inventory.ts +526 -0
- package/src/onboard/steps/llmauth.ts +1095 -0
- package/src/onboard/steps/memory.ts +1290 -0
- package/src/onboard/steps/memorykeys.ts +85 -0
- package/src/onboard/steps/preflight.ts +191 -0
- package/src/onboard/steps/proof.ts +624 -0
- package/src/onboard/steps/ssh.ts +377 -0
- package/src/onboard/steps/topology.ts +337 -0
- package/src/onboard/steps/transport.ts +665 -0
- package/src/onboard/steps/verifyup.ts +448 -0
- package/src/onboard/steps/vmdeploy.ts +442 -0
- package/src/onboard/toml.ts +50 -0
- package/src/opencli.ts +591 -0
- package/src/openpr.ts +568 -0
- package/src/openviking/client.ts +1131 -0
- package/src/openviking/context.ts +263 -0
- package/src/openviking/embedder.ts +516 -0
- package/src/openviking/gapsink.ts +63 -0
- package/src/openviking/health.ts +83 -0
- package/src/openviking/index.ts +119 -0
- package/src/openviking/lessonfind.ts +92 -0
- package/src/openviking/probe.ts +129 -0
- package/src/openviking/proc.ts +110 -0
- package/src/openviking/provisionreport.ts +160 -0
- package/src/openviking/reembed.ts +412 -0
- package/src/openviking/relayout.ts +177 -0
- package/src/openviking/sidecar-main.ts +38 -0
- package/src/openviking/sidecar.ts +106 -0
- package/src/openviking/store.ts +2025 -0
- package/src/openviking/supervisor.ts +474 -0
- package/src/openviking/types.ts +174 -0
- package/src/operator-recovery.ts +237 -0
- package/src/operatormessage.ts +52 -0
- package/src/outcomes.ts +1670 -0
- package/src/piauthcopy.ts +12 -0
- package/src/picli.ts +49 -0
- package/src/picwd.ts +25 -0
- package/src/pilogin.ts +37 -0
- package/src/pingpong.ts +1339 -0
- package/src/posting-journal.ts +549 -0
- package/src/posting-outbox.ts +802 -0
- package/src/posting-receipt.ts +324 -0
- package/src/posting.ts +603 -0
- package/src/postops.ts +832 -0
- package/src/pr-activity.ts +20 -0
- package/src/pr-author.ts +6 -0
- package/src/pr-conversation.ts +432 -0
- package/src/pr-session-lifecycle.ts +631 -0
- package/src/preflight.ts +391 -0
- package/src/priorresolve.ts +343 -0
- package/src/qcrbin.ts +186 -0
- package/src/recheck.ts +1775 -0
- package/src/reconcile.ts +1030 -0
- package/src/recordedfindings.ts +27 -0
- package/src/regression.ts +833 -0
- package/src/replay.ts +816 -0
- package/src/requiredpasses.ts +148 -0
- package/src/resumecheck.ts +246 -0
- package/src/reviewcli.ts +787 -0
- package/src/reviewcontext.ts +35 -0
- package/src/reviewdetach.ts +734 -0
- package/src/reviewhistory.ts +427 -0
- package/src/reviewhome.ts +36 -0
- package/src/reviewmemory.ts +133 -0
- package/src/reviewrendercli.ts +139 -0
- package/src/round-work.ts +42 -0
- package/src/runhost.ts +108 -0
- package/src/scanauth.ts +36 -0
- package/src/scanbox.ts +1081 -0
- package/src/scancli.ts +1296 -0
- package/src/scandriverlog.ts +32 -0
- package/src/session-bridge.ts +139 -0
- package/src/session-command-payload.ts +35 -0
- package/src/session-command-supervisor.ts +1142 -0
- package/src/sessionjsonl.ts +88 -0
- package/src/sessions.ts +659 -0
- package/src/setup/adapters.ts +335 -0
- package/src/setup/baselines.ts +110 -0
- package/src/setup/caserunner.ts +611 -0
- package/src/setup/configcommit.ts +443 -0
- package/src/setup/engine.ts +1001 -0
- package/src/setup/facts.ts +627 -0
- package/src/setup/findings.ts +1264 -0
- package/src/setup/history.ts +443 -0
- package/src/setup/interview.ts +243 -0
- package/src/setup/ledger.ts +324 -0
- package/src/setup/pins.ts +66 -0
- package/src/setup/portrait.ts +635 -0
- package/src/setup/presentation.ts +18 -0
- package/src/setup/profile.ts +508 -0
- package/src/setup/replay.ts +370 -0
- package/src/setup/replaybuild.ts +392 -0
- package/src/setup/replayrun.ts +1096 -0
- package/src/setup/replaysource.ts +198 -0
- package/src/setup/retune.ts +415 -0
- package/src/setup/retunelog.ts +95 -0
- package/src/setup/scan.ts +1033 -0
- package/src/setup/scanbuild.ts +168 -0
- package/src/setup/scanfacts.ts +107 -0
- package/src/setup/scanlease.ts +297 -0
- package/src/setup/scanreport.ts +373 -0
- package/src/setup/scanrun.ts +2268 -0
- package/src/setup/scantree.ts +259 -0
- package/src/setup/stage2.ts +926 -0
- package/src/setup/stage2request.ts +347 -0
- package/src/setup/survey.ts +458 -0
- package/src/setup/surveyrun.ts +758 -0
- package/src/setup/textsniff.ts +95 -0
- package/src/setup/trackersink.ts +433 -0
- package/src/setup/triage.ts +768 -0
- package/src/setup/tuning.ts +766 -0
- package/src/setup/unitcut.ts +523 -0
- package/src/singleflight.ts +86 -0
- package/src/slack/announce.ts +911 -0
- package/src/slack/api.ts +303 -0
- package/src/slack/messages.ts +288 -0
- package/src/slack/origin.ts +599 -0
- package/src/slack/policy.ts +354 -0
- package/src/slack/reply.ts +201 -0
- package/src/slack/router.ts +285 -0
- package/src/slack/socket.ts +384 -0
- package/src/slack/types.ts +108 -0
- package/src/sqlitehint.ts +32 -0
- package/src/ssh-observation.ts +30 -0
- package/src/state.ts +8243 -0
- package/src/statemachine.ts +369 -0
- package/src/stubs.ts +68 -0
- package/src/takeover.ts +634 -0
- package/src/takeoverpi.ts +293 -0
- package/src/terminal-failure.ts +41 -0
- package/src/thread-resolution-refusal.ts +2 -0
- package/src/tmux.ts +153 -0
- package/src/trackerid.ts +41 -0
- package/src/transcriptindex.ts +665 -0
- package/src/tui/actions.ts +484 -0
- package/src/tui/alerts.ts +666 -0
- package/src/tui/app.ts +1979 -0
- package/src/tui/archiveops.ts +192 -0
- package/src/tui/autofile.ts +233 -0
- package/src/tui/board.ts +1432 -0
- package/src/tui/candidates.ts +889 -0
- package/src/tui/cellwidth.ts +148 -0
- package/src/tui/changelog-store.ts +503 -0
- package/src/tui/chord.ts +25 -0
- package/src/tui/clipboard.ts +92 -0
- package/src/tui/cmdbar.ts +296 -0
- package/src/tui/companion.ts +1427 -0
- package/src/tui/companionplan.ts +247 -0
- package/src/tui/companiontab.ts +83 -0
- package/src/tui/confighome.ts +155 -0
- package/src/tui/connect.ts +68 -0
- package/src/tui/deploy-ceremony.ts +104 -0
- package/src/tui/deploy-drain.ts +129 -0
- package/src/tui/deploy-process.ts +265 -0
- package/src/tui/deploytab.ts +1523 -0
- package/src/tui/design.ts +125 -0
- package/src/tui/digesttab.ts +214 -0
- package/src/tui/emptystate.ts +39 -0
- package/src/tui/evidence.ts +999 -0
- package/src/tui/feed.ts +604 -0
- package/src/tui/feedbacktab.ts +1077 -0
- package/src/tui/filters.ts +90 -0
- package/src/tui/ghread.ts +321 -0
- package/src/tui/historypull.ts +81 -0
- package/src/tui/inboxpull.ts +236 -0
- package/src/tui/index.ts +1565 -0
- package/src/tui/issuesink.ts +238 -0
- package/src/tui/jsonfile.ts +42 -0
- package/src/tui/keys.ts +109 -0
- package/src/tui/layout.ts +108 -0
- package/src/tui/learning.ts +627 -0
- package/src/tui/lessonswitch.ts +118 -0
- package/src/tui/livecontrol.ts +228 -0
- package/src/tui/memforward.ts +443 -0
- package/src/tui/memincident.ts +363 -0
- package/src/tui/memory.ts +620 -0
- package/src/tui/memtab.ts +606 -0
- package/src/tui/metricstab.ts +1177 -0
- package/src/tui/model.ts +131 -0
- package/src/tui/mouse.ts +98 -0
- package/src/tui/msg.ts +268 -0
- package/src/tui/nativecompanion.ts +417 -0
- package/src/tui/openurl.ts +21 -0
- package/src/tui/paint.ts +511 -0
- package/src/tui/panes.ts +98 -0
- package/src/tui/pipane.ts +219 -0
- package/src/tui/poll-hooks.ts +12 -0
- package/src/tui/poll.ts +147 -0
- package/src/tui/proposals.ts +838 -0
- package/src/tui/prpause.ts +150 -0
- package/src/tui/reload.ts +436 -0
- package/src/tui/remote.ts +413 -0
- package/src/tui/rollover.ts +969 -0
- package/src/tui/scantab.ts +1290 -0
- package/src/tui/setupentry.ts +93 -0
- package/src/tui/sidebar.ts +749 -0
- package/src/tui/sshtriage.ts +510 -0
- package/src/tui/startup.ts +231 -0
- package/src/tui/tabs.ts +45 -0
- package/src/tui/termmirror.ts +224 -0
- package/src/tui/theme.ts +95 -0
- package/src/tui/ticketstab.ts +310 -0
- package/src/tui/tmuxsocket.ts +129 -0
- package/src/tui/trackertickets.ts +84 -0
- package/src/tui/unwedge.ts +286 -0
- package/src/tui/viewer.ts +575 -0
- package/src/tui/viewerinput.ts +173 -0
- package/src/updatenotice.ts +178 -0
- package/src/usage.ts +1154 -0
- package/src/verdicts.ts +4689 -0
- package/src/version.ts +80 -0
- package/src/wakes.ts +476 -0
- package/src/webhook.ts +942 -0
- package/src/webhookheal.ts +276 -0
- package/src/wire.ts +2153 -0
- package/src/worker-session-host.ts +602 -0
- package/src/workerbreaker.ts +156 -0
- package/src/workercontrol.ts +104 -0
- package/src/workerlease.ts +441 -0
- package/src/workerliveness.ts +103 -0
- package/src/workerlog.ts +79 -0
- package/src/workerruntime.ts +30 -0
- package/src/workersupervisor.ts +1538 -0
- package/src/workerusage.ts +42 -0
- package/src/workerworld.ts +427 -0
- package/src/worktrees.ts +1138 -0
- package/tests/fixtures/regression-cases.json +795 -0
- package/tests/fixtures/seed-manifest.json +19 -0
package/src/wire.ts
ADDED
|
@@ -0,0 +1,2153 @@
|
|
|
1
|
+
// Production assembly of quickreviewd: createDaemon() with the real pi session
|
|
2
|
+
// manager, the round-template renderer, the agents build step, the EOD timer,
|
|
3
|
+
// the overseer command executor, and the weekly overseer rotation. `npm run
|
|
4
|
+
// daemon` runs this; createDaemon() alone (src/daemon.ts) stays the
|
|
5
|
+
// stub-wired, test-friendly core.
|
|
6
|
+
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
fsSecretReader,
|
|
11
|
+
ingressHostnameFile,
|
|
12
|
+
liveConfigPath,
|
|
13
|
+
loadConfig,
|
|
14
|
+
openVikingBaseUrl,
|
|
15
|
+
sandboxRoot,
|
|
16
|
+
slackTokenPath,
|
|
17
|
+
validateSlackConfig,
|
|
18
|
+
type Config,
|
|
19
|
+
type SecretReader,
|
|
20
|
+
} from "./config.js";
|
|
21
|
+
import {
|
|
22
|
+
CommandWatcher,
|
|
23
|
+
processOperatorSeal,
|
|
24
|
+
type CommandDeps,
|
|
25
|
+
type CommandWatcherOptions,
|
|
26
|
+
} from "./commands.js";
|
|
27
|
+
import { drainLever, pauseLever, resumeLever, type LeverDeps } from "./levers.js";
|
|
28
|
+
import { CaseWriter } from "./cases.js";
|
|
29
|
+
import { Archiver } from "./archive/archiver.js";
|
|
30
|
+
import { piGlobalSessionsDir } from "./archive/piglobal.js";
|
|
31
|
+
import { runDirOf, toolUidSplit } from "./ipc/capability.js";
|
|
32
|
+
import { PermitBroker } from "./ipc/permits.js";
|
|
33
|
+
import { LoginRefreshSupervisor } from "./loginrefresh/supervisor.js";
|
|
34
|
+
import { pendingArchiveStatus } from "./archive/status.js";
|
|
35
|
+
import { Learner, learnerKnobsOf } from "./learner/learner.js";
|
|
36
|
+
import { renderNoPacketSection } from "./learner/packet.js";
|
|
37
|
+
import type { CurationReceipt } from "./learner/receipt.js";
|
|
38
|
+
import { LessonLedger } from "./lessonledger.js";
|
|
39
|
+
import { RoundLessonSelector, type RoundLessonHook } from "./lessonselect.js";
|
|
40
|
+
import { lessonDeliveryStatus } from "./lessonshadow.js";
|
|
41
|
+
import { RoundMemoryActivity } from "./memory/roundactivity.js";
|
|
42
|
+
import type { LessonDeliveryStatus } from "./memory/shadowstatus.js";
|
|
43
|
+
import { lessonFindClient, openVikingLessonEngine } from "./openviking/lessonfind.js";
|
|
44
|
+
import { createDaemon, type Daemon } from "./daemon.js";
|
|
45
|
+
import { reviewsInFlight } from "./dispatch.js";
|
|
46
|
+
import { eventLoopPolicy } from "./eventloop.js";
|
|
47
|
+
import { digestWritten, eodDigestContext, latestDigestHeadline } from "./digest.js";
|
|
48
|
+
import { startHeartbeat, type HeartbeatTimer } from "./anomalies.js";
|
|
49
|
+
import { CompanionInbox, type InboxWriter } from "./companioninbox.js";
|
|
50
|
+
import { opsDay, promptReader, startEodTimer, type EodTimer } from "./eod.js";
|
|
51
|
+
import { MutationGate, type MutationIntent } from "@quickchatai/pi-quickreview/src/gate.js";
|
|
52
|
+
import { GitHubAdapter } from "./github.js";
|
|
53
|
+
import { priorSidecarPath } from "@quickchatai/pi-quickreview/src/priorfindings.js";
|
|
54
|
+
import { completeRecordedFindings } from "./recordedfindings.js";
|
|
55
|
+
import { makePostingGuard } from "@quickchatai/pi-quickreview/src/guards.js";
|
|
56
|
+
import { classifyCi, ciBlocksApproval } from "@quickchatai/pi-quickreview/src/cigate.js";
|
|
57
|
+
import { TokenMinter } from "@quickchatai/pi-quickreview/src/githubAuth.js";
|
|
58
|
+
import {
|
|
59
|
+
FeedbackEngine,
|
|
60
|
+
FeedbackMemoryPump,
|
|
61
|
+
dayScopedRebuild,
|
|
62
|
+
feedbackDayAddendum,
|
|
63
|
+
ledgerFeedbackSignal,
|
|
64
|
+
type FeedbackEvent,
|
|
65
|
+
} from "./feedback.js";
|
|
66
|
+
import {
|
|
67
|
+
PASS_ADDENDA,
|
|
68
|
+
buildAgents,
|
|
69
|
+
configAddenda,
|
|
70
|
+
lessonAddendaFor,
|
|
71
|
+
identityVars,
|
|
72
|
+
} from "@quickchatai/pi-quickreview/src/include.js";
|
|
73
|
+
import { ecosystemFor } from "@quickchatai/pi-quickreview/src/ecosystem.js";
|
|
74
|
+
import { lensTuningAddenda, roundAgentModels } from "@quickchatai/pi-quickreview/src/lenstuning.js";
|
|
75
|
+
import {
|
|
76
|
+
applyProfileOverlays,
|
|
77
|
+
loadProfileAddenda,
|
|
78
|
+
loadProfileEcosystem,
|
|
79
|
+
loadProfileHeuristics,
|
|
80
|
+
} from "@quickchatai/pi-quickreview/src/profiles.js";
|
|
81
|
+
import { postingPolicyAddenda } from "@quickchatai/pi-quickreview/src/postingpolicy.js";
|
|
82
|
+
import {
|
|
83
|
+
prepareDependencySources,
|
|
84
|
+
renderDependencyNote,
|
|
85
|
+
} from "@quickchatai/pi-quickreview/src/depsources.js";
|
|
86
|
+
import { fileImprovementPr, fileIssue, gitRunner, type FileOutcome } from "./improvements.js";
|
|
87
|
+
import { homeProfileLookup } from "./install.js";
|
|
88
|
+
import { hostFacts, type HostFacts, type RunHostStatus } from "./runhost.js";
|
|
89
|
+
import {
|
|
90
|
+
EMBEDDER_GATE_MAX_WAIT_MS,
|
|
91
|
+
LocalEmbedderSupervisor,
|
|
92
|
+
OpenVikingClient,
|
|
93
|
+
OpenVikingSupervisor,
|
|
94
|
+
ReembedDriver,
|
|
95
|
+
keyFromFile,
|
|
96
|
+
memoryServing,
|
|
97
|
+
openVikingContextAssembler,
|
|
98
|
+
openVikingStore,
|
|
99
|
+
type OvRequestRecord,
|
|
100
|
+
} from "./openviking/index.js";
|
|
101
|
+
import {
|
|
102
|
+
OV_REVIEWER_KEY_SECRET,
|
|
103
|
+
keyChangeWatcher,
|
|
104
|
+
openVikingSessionLifecycle,
|
|
105
|
+
type OvSessionLifecycle,
|
|
106
|
+
readOvcliKey,
|
|
107
|
+
readRetiredUrisFile,
|
|
108
|
+
writeRecallHint,
|
|
109
|
+
writeRetiredUris,
|
|
110
|
+
} from "./memory/ovsession.js";
|
|
111
|
+
import type { ContextAssembler, RoundMemory, RoundMemorySeed } from "./memory/context.js";
|
|
112
|
+
import { MEMORY_LAYOUT_NOTE, ROUND_SEARCH_AREAS } from "./memory/layout.js";
|
|
113
|
+
import {
|
|
114
|
+
MemoryHealthMonitor,
|
|
115
|
+
disabledMemoryStatus,
|
|
116
|
+
pendingMemoryStatus,
|
|
117
|
+
type CaseWritesStatus,
|
|
118
|
+
type MemoryStatus,
|
|
119
|
+
} from "./memory/health.js";
|
|
120
|
+
import { credentialProbe } from "./openviking/probe.js";
|
|
121
|
+
import {
|
|
122
|
+
provisionReportPath,
|
|
123
|
+
raiseProvisionWake,
|
|
124
|
+
readProvisionReport,
|
|
125
|
+
} from "./openviking/provisionreport.js";
|
|
126
|
+
import { countedGapSink } from "./openviking/gapsink.js";
|
|
127
|
+
import { recoverPrForOperator } from "./operator-recovery.js";
|
|
128
|
+
import { createWorkerLiveness, inFlightPrs } from "./workerliveness.js";
|
|
129
|
+
import { resolveTerminalFailure } from "./terminal-failure.js";
|
|
130
|
+
import type { MemoryStore } from "./memory/store.js";
|
|
131
|
+
import { readPinnedFromSweep, startEodPinWatch, type PinnedSweepSource } from "./eodpinwatch.js";
|
|
132
|
+
import { reconcileOrphanedRounds } from "./reconcile.js";
|
|
133
|
+
import { PiSessionManager, prSessionName } from "./sessions.js";
|
|
134
|
+
import { SlackApiClient } from "./slack/api.js";
|
|
135
|
+
import { makeAnnouncer, type SlackAnnouncer } from "./slack/announce.js";
|
|
136
|
+
import { clearOriginRecords, OriginStore } from "./slack/origin.js";
|
|
137
|
+
import { makeSlackReply } from "./slack/reply.js";
|
|
138
|
+
import { SlackRouter } from "./slack/router.js";
|
|
139
|
+
import { SlackSocketClient } from "./slack/socket.js";
|
|
140
|
+
import type { SlackTransport } from "./slack/types.js";
|
|
141
|
+
import { StateStore, type RoundRow } from "./state.js";
|
|
142
|
+
import { deliverablePosting } from "./posting-outbox.js";
|
|
143
|
+
import { readPostingReceipt } from "./posting-receipt.js";
|
|
144
|
+
import type { RecheckOrder } from "./recheck.js";
|
|
145
|
+
import {
|
|
146
|
+
RECHECK_TEMPLATE_FILE,
|
|
147
|
+
renderRecheckPrompt,
|
|
148
|
+
renderRoundPrompt,
|
|
149
|
+
templateFileForOrder,
|
|
150
|
+
} from "@quickchatai/pi-quickreview/src/templates.js";
|
|
151
|
+
import { assertOrderFitsHost } from "@quickchatai/pi-quickreview/src/launchvocab.js";
|
|
152
|
+
import { nodeExec } from "./tmux.js";
|
|
153
|
+
import type { RoundOrder, SessionHooks } from "@quickchatai/pi-quickreview/src/types.js";
|
|
154
|
+
import { versionInfo } from "./version.js";
|
|
155
|
+
import { WebhookHealer } from "./webhookheal.js";
|
|
156
|
+
import { buildWorkerWorld } from "./workerworld.js";
|
|
157
|
+
|
|
158
|
+
export interface ProductionParts {
|
|
159
|
+
cfg: Config;
|
|
160
|
+
root: string;
|
|
161
|
+
minter: TokenMinter;
|
|
162
|
+
gate: MutationGate;
|
|
163
|
+
adapter: GitHubAdapter;
|
|
164
|
+
/** The tmux manager: it serves what has not moved to workers yet (the overseer) and
|
|
165
|
+
* lends the worker world its worktrees, session environment and path helpers. */
|
|
166
|
+
sessions: PiSessionManager;
|
|
167
|
+
/** The PR's raw-notes tier, which the worker host creates and deletes. */
|
|
168
|
+
ovSessions?: OvSessionLifecycle;
|
|
169
|
+
/** Late-bound observer for the raw-tier client's requests:
|
|
170
|
+
* buildParts builds that client before the memory health monitor exists,
|
|
171
|
+
* so createProductionDaemon points `sink` at the monitor once it does. */
|
|
172
|
+
memoryRequests?: { sink: ((record: OvRequestRecord) => void) | null };
|
|
173
|
+
/** Configured memory whose optional boot migration could not complete. */
|
|
174
|
+
memoryBootFailure?: string;
|
|
175
|
+
/** Where the daemon runs (src/runhost.ts): logged at start, and status.json
|
|
176
|
+
* `run_host`. Absent: not asked (tests, the CLI's own parts). */
|
|
177
|
+
runHost?: RunHostStatus;
|
|
178
|
+
/** Memory configured on but switched off for this host — no Docker for
|
|
179
|
+
* the sidecar outside the image. The config's `openviking.enabled` is
|
|
180
|
+
* already false; this is the reason the status block says. */
|
|
181
|
+
memoryOff?: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A configured engine refused before it could open partial migrated state. */
|
|
185
|
+
export function migrationMemoryStatus(reason: string, at: Date = new Date()): MemoryStatus {
|
|
186
|
+
return {
|
|
187
|
+
...pendingMemoryStatus(at),
|
|
188
|
+
state: "down",
|
|
189
|
+
down_cause: "unanswered",
|
|
190
|
+
last_error: {
|
|
191
|
+
at: at.toISOString(),
|
|
192
|
+
op: "boot",
|
|
193
|
+
status: 0,
|
|
194
|
+
code: "migration_incomplete",
|
|
195
|
+
message: reason,
|
|
196
|
+
},
|
|
197
|
+
serving: { ok: false, since: at.toISOString(), reason },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function buildParts(
|
|
202
|
+
root = sandboxRoot(),
|
|
203
|
+
loaded = loadConfig(),
|
|
204
|
+
/** The entry point's view of its host (`hostFacts`); absent, nothing about
|
|
205
|
+
* the host changes the config. */
|
|
206
|
+
host?: HostFacts,
|
|
207
|
+
): ProductionParts {
|
|
208
|
+
// The profile's per-lens tuning overlays the parsed config once, here, so
|
|
209
|
+
// every consumer downstream (the plan, the agents build, the round row)
|
|
210
|
+
// reads the one resolved `cfg.lenses` — the profile wins over
|
|
211
|
+
// config.toml, the preset and the defaults. A malformed `[lenses]` table
|
|
212
|
+
// fails the boot loudly, like every other profile loader.
|
|
213
|
+
// The profile's `[posting]` policy rides the same overlay (packages/pi-plugin/src/postingpolicy.ts).
|
|
214
|
+
const overlaid = applyProfileOverlays(root, loaded, homeProfileLookup());
|
|
215
|
+
// A failed optional boot migration leaves its destination incomplete. No
|
|
216
|
+
// client, raw session or supervisor may open it until the next clean boot.
|
|
217
|
+
// Memory switched off for this host (no Docker outside the image) is the
|
|
218
|
+
// same clean off: no client, no supervisor, no probe to fail.
|
|
219
|
+
const memoryOff = overlaid.openviking.enabled ? host?.memoryOff : undefined;
|
|
220
|
+
const cfg =
|
|
221
|
+
process.env.QCR_OPENVIKING_BOOT_DISABLED === "1" || memoryOff !== undefined
|
|
222
|
+
? { ...overlaid, openviking: { ...overlaid.openviking, enabled: false } }
|
|
223
|
+
: overlaid;
|
|
224
|
+
const memoryBootFailure =
|
|
225
|
+
process.env.QCR_OPENVIKING_BOOT_DISABLED === "1" && overlaid.openviking.enabled
|
|
226
|
+
? "OpenViking state migration is incomplete; memory is unavailable for this boot — original files were retained; retry after correcting storage and restarting"
|
|
227
|
+
: undefined;
|
|
228
|
+
const minter = new TokenMinter(cfg.github.secrets_dir);
|
|
229
|
+
const gate: MutationGate = new MutationGate(
|
|
230
|
+
cfg.dry_run,
|
|
231
|
+
join(root, "state", "dryrun"),
|
|
232
|
+
(intent: MutationIntent) => adapter.executeMutation(intent),
|
|
233
|
+
);
|
|
234
|
+
const adapter: GitHubAdapter = new GitHubAdapter(cfg, minter, gate);
|
|
235
|
+
// The PR's raw memory tier: the daemon owns create/delete of the
|
|
236
|
+
// OpenViking session a PR's pi attaches to. Its own client, on the reviewer
|
|
237
|
+
// user key — never ADMIN/ROOT. Both directions are total, so a store that is
|
|
238
|
+
// down or unkeyed costs the raw tier and never a session.
|
|
239
|
+
const memoryRequests: NonNullable<ProductionParts["memoryRequests"]> = { sink: null };
|
|
240
|
+
const ovSessions = cfg.openviking.enabled
|
|
241
|
+
? openVikingSessionLifecycle(
|
|
242
|
+
new OpenVikingClient({
|
|
243
|
+
...ovConnection(cfg.openviking),
|
|
244
|
+
apiKey: keyFromFile(join(cfg.github.secrets_dir, OV_REVIEWER_KEY_SECRET)),
|
|
245
|
+
// Counted with the daemon's other requests.
|
|
246
|
+
onRequest: (r) => memoryRequests.sink?.(r),
|
|
247
|
+
}),
|
|
248
|
+
// The lifecycle logs a failed delete.
|
|
249
|
+
{ log: (msg) => console.log(`[quickreviewd] ${msg}`) },
|
|
250
|
+
)
|
|
251
|
+
: undefined;
|
|
252
|
+
const sessions = new PiSessionManager({
|
|
253
|
+
cfg,
|
|
254
|
+
root,
|
|
255
|
+
ovSessions,
|
|
256
|
+
configPath: liveConfigPath(),
|
|
257
|
+
});
|
|
258
|
+
return {
|
|
259
|
+
cfg,
|
|
260
|
+
root,
|
|
261
|
+
minter,
|
|
262
|
+
gate,
|
|
263
|
+
adapter,
|
|
264
|
+
sessions,
|
|
265
|
+
...(ovSessions ? { ovSessions } : {}),
|
|
266
|
+
memoryRequests,
|
|
267
|
+
...(memoryBootFailure ? { memoryBootFailure } : {}),
|
|
268
|
+
...(host ? { runHost: host.runHost } : {}),
|
|
269
|
+
...(memoryOff !== undefined && memoryBootFailure === undefined ? { memoryOff } : {}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Exit-code checked, cwd mandatory: a runner that ignored git's exit or
|
|
274
|
+
// defaulted to the daemon's own cwd (a repo with no remote) would leave the
|
|
275
|
+
// improvement channel unable to push without saying so.
|
|
276
|
+
const gitRun = gitRunner(nodeExec);
|
|
277
|
+
|
|
278
|
+
/** The status.json a previous run left, or undefined (first boot, unreadable). */
|
|
279
|
+
function readPreviousStatus(path: string): string | undefined {
|
|
280
|
+
try {
|
|
281
|
+
return readFileSync(path, "utf8");
|
|
282
|
+
} catch {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** package.json version for the `daemon.up` line; never worth failing a boot. */
|
|
288
|
+
function packageVersion(root: string): string | undefined {
|
|
289
|
+
const version = versionInfo(root).image.version;
|
|
290
|
+
return version === "unknown" ? undefined : version;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ---- the Slack concierge ---------------------------------------------------
|
|
294
|
+
//
|
|
295
|
+
// Everything Slack is assembled here and nowhere else, because this is the one
|
|
296
|
+
// module allowed to read the two tokens off disk. Three properties the shape
|
|
297
|
+
// enforces rather than documents:
|
|
298
|
+
//
|
|
299
|
+
// * `[slack] enabled = false` ⇒ this function returns before it touches a
|
|
300
|
+
// secret reader, a socket or a clock. Nothing is constructed, so nothing
|
|
301
|
+
// can post.
|
|
302
|
+
// * A missing token, a malformed [slack] block or an auth.test that cannot
|
|
303
|
+
// name us is a hard boot error. A bot that cannot identify itself cannot
|
|
304
|
+
// filter its own echoes — running anyway is a loop risk, not a degraded
|
|
305
|
+
// mode.
|
|
306
|
+
// * Socket Mode is the only inbound transport. There is no HTTP route here
|
|
307
|
+
// and none in src/webhook.ts (tests/slack-no-route.test.ts makes that
|
|
308
|
+
// structural).
|
|
309
|
+
|
|
310
|
+
/** How often the duplicate-consumer check reads `hello.num_connections`. The
|
|
311
|
+
* socket exposes the count, not an event; `noteConnections` only fires on a
|
|
312
|
+
* CHANGE, so polling it is equivalent to hooking the frame. */
|
|
313
|
+
const SLACK_CONNECTIONS_TICK_MS = 60_000;
|
|
314
|
+
|
|
315
|
+
export interface SlackWiringOptions {
|
|
316
|
+
cfg: Config;
|
|
317
|
+
root: string;
|
|
318
|
+
store: StateStore;
|
|
319
|
+
/** The daemon's command handlers; the Slack dir gets these plus slackReply. */
|
|
320
|
+
commandDeps: CommandDeps;
|
|
321
|
+
/** The companion inbox — where ops signals go (the connection-count
|
|
322
|
+
* anomaly). Never the channel: v1 ships ops causes off. */
|
|
323
|
+
writeInbox: InboxWriter;
|
|
324
|
+
log: (msg: string, extra?: unknown) => void;
|
|
325
|
+
version?: string;
|
|
326
|
+
now?: () => Date;
|
|
327
|
+
/** Test seam: how a token file is read. Default is the shared fs reader. */
|
|
328
|
+
readSecret?: SecretReader;
|
|
329
|
+
/** Test seam: how the transport is built from the two tokens. `log` is the
|
|
330
|
+
* wiring's own sink, so the socket's connection diagnostics reach it. */
|
|
331
|
+
makeTransport?: (opts: {
|
|
332
|
+
cfg: Config;
|
|
333
|
+
botToken: string;
|
|
334
|
+
appToken: string;
|
|
335
|
+
log: (msg: string, extra?: unknown) => void;
|
|
336
|
+
}) => SlackTransport;
|
|
337
|
+
/** The previous run's `state/status.json`, read before this boot wrote its
|
|
338
|
+
* own first snapshot — the downtime `daemon.up` reports is measured from
|
|
339
|
+
* its heartbeat. Absent ⇒ the file is read at announce time. */
|
|
340
|
+
previousStatus?: string;
|
|
341
|
+
/**
|
|
342
|
+
* The origin store, when the caller already owns one. `createProductionDaemon`
|
|
343
|
+
* builds it before the daemon so the belt reads the overseer's accepted
|
|
344
|
+
* wakes. Omitted ⇒ built here (tests).
|
|
345
|
+
*/
|
|
346
|
+
origins?: OriginStore;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** The live Slack subsystem, as `onStop` needs to see it. */
|
|
350
|
+
export interface SlackRuntime {
|
|
351
|
+
announcer: SlackAnnouncer;
|
|
352
|
+
router: SlackRouter;
|
|
353
|
+
origins: OriginStore;
|
|
354
|
+
transport: SlackTransport;
|
|
355
|
+
/** The tier-checked `commands/slack/` watcher. */
|
|
356
|
+
commands: CommandWatcher;
|
|
357
|
+
/** `CommandDeps.slackReply` — also handed to the legacy-dir watcher. */
|
|
358
|
+
slackReply: NonNullable<CommandDeps["slackReply"]>;
|
|
359
|
+
stop(reason: string): Promise<void>;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The real transport: Web API out, Socket Mode in, composed into one object. */
|
|
363
|
+
export function defaultTransport(opts: {
|
|
364
|
+
cfg: Config;
|
|
365
|
+
botToken: string;
|
|
366
|
+
appToken: string;
|
|
367
|
+
log: (msg: string, extra?: unknown) => void;
|
|
368
|
+
}): SlackTransport {
|
|
369
|
+
const { cfg, log } = opts;
|
|
370
|
+
// Both transports log to the daemon log: an auth failure, a reconnect, a
|
|
371
|
+
// ping timeout or a dropped post is what an operator reads to tell why the
|
|
372
|
+
// concierge went quiet.
|
|
373
|
+
const api = new SlackApiClient({
|
|
374
|
+
botToken: opts.botToken,
|
|
375
|
+
postMinIntervalMs: cfg.slack.post_min_interval_ms,
|
|
376
|
+
userCacheTtlSec: cfg.slack.name_cache_ttl_s,
|
|
377
|
+
log: (message, meta) => log(`slack: ${message}`, meta),
|
|
378
|
+
});
|
|
379
|
+
const socket = new SlackSocketClient({
|
|
380
|
+
appToken: opts.appToken,
|
|
381
|
+
backoffMs: cfg.slack.reconnect_backoff_ms,
|
|
382
|
+
serverPingTimeoutMs: cfg.slack.server_ping_timeout_ms,
|
|
383
|
+
log: (message, meta) => log(`slack: ${message}`, meta),
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
postMessage: (a) => api.postMessage(a),
|
|
387
|
+
updateMessage: (a) => api.updateMessage(a),
|
|
388
|
+
usersInfo: (id) => api.usersInfo(id),
|
|
389
|
+
authTest: () => api.authTest(),
|
|
390
|
+
pinsAdd: (a) => api.pinsAdd(a),
|
|
391
|
+
startSocket: (h) => socket.startSocket(h),
|
|
392
|
+
stopSocket: () => socket.stopSocket(),
|
|
393
|
+
get numConnections(): number {
|
|
394
|
+
return socket.numConnections;
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Build + start the concierge. Returns undefined when Slack is off — callers
|
|
401
|
+
* pass that straight through, which is why nothing downstream needs a flag.
|
|
402
|
+
*/
|
|
403
|
+
export async function startSlack(opts: SlackWiringOptions): Promise<SlackRuntime | undefined> {
|
|
404
|
+
const { cfg, root, store, log } = opts;
|
|
405
|
+
if (!cfg.slack.enabled) return undefined;
|
|
406
|
+
const now = opts.now ?? (() => new Date());
|
|
407
|
+
const readSecret = opts.readSecret ?? fsSecretReader;
|
|
408
|
+
|
|
409
|
+
// Fail loud, at boot, on a missing token or a half-filled [slack] block.
|
|
410
|
+
validateSlackConfig(cfg, { readSecret });
|
|
411
|
+
const botToken = readSecret(slackTokenPath(cfg, "bot"));
|
|
412
|
+
const appToken = readSecret(slackTokenPath(cfg, "app"));
|
|
413
|
+
if (!botToken || !appToken)
|
|
414
|
+
throw new Error("slack: token files vanished between validation and read");
|
|
415
|
+
|
|
416
|
+
const transport = (opts.makeTransport ?? defaultTransport)({ cfg, botToken, appToken, log });
|
|
417
|
+
|
|
418
|
+
// Who are we? Resolved once, and required: the router's echo filter is built
|
|
419
|
+
// on this identity, and a bot that cannot recognise its own messages will
|
|
420
|
+
// answer itself forever.
|
|
421
|
+
const identity = await transport.authTest();
|
|
422
|
+
if (!identity) {
|
|
423
|
+
throw new Error(
|
|
424
|
+
"slack: auth.test failed — refusing to start (a bot that cannot identify itself will answer its own echoes)",
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
log(`slack: connected as ${identity.user_id} in ${cfg.slack.channel}`);
|
|
428
|
+
|
|
429
|
+
const origins = opts.origins ?? new OriginStore(join(root, "state", "slack", "origin"), now);
|
|
430
|
+
const announcer = makeAnnouncer({
|
|
431
|
+
cfg,
|
|
432
|
+
store,
|
|
433
|
+
slack: transport,
|
|
434
|
+
now,
|
|
435
|
+
log,
|
|
436
|
+
version: opts.version,
|
|
437
|
+
statusPath: join(root, "state", "status.json"),
|
|
438
|
+
...(opts.previousStatus !== undefined ? { readStatus: () => opts.previousStatus } : {}),
|
|
439
|
+
onAnomalySignal: (message) => {
|
|
440
|
+
// Durable anomaly record beside the inbox item.
|
|
441
|
+
store.logAnomaly("slack_connections", null, null, message);
|
|
442
|
+
return opts.writeInbox(message, "anomaly");
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
if (!announcer) throw new Error("slack: announcer not built despite enabled = true");
|
|
446
|
+
|
|
447
|
+
const router = new SlackRouter({
|
|
448
|
+
cfg,
|
|
449
|
+
store,
|
|
450
|
+
identity,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const slackReply = makeSlackReply({ cfg, store, api: transport, now, log: (line) => log(line) });
|
|
454
|
+
const commands = new CommandWatcher({
|
|
455
|
+
dir: join(root, "commands", "slack"),
|
|
456
|
+
scope: "slack",
|
|
457
|
+
tier: { dir: "slack", currentOrigin: () => origins.latest() },
|
|
458
|
+
deps: { ...opts.commandDeps, slackReply },
|
|
459
|
+
store,
|
|
460
|
+
log: (line) => log(line),
|
|
461
|
+
});
|
|
462
|
+
await commands.start();
|
|
463
|
+
|
|
464
|
+
await transport.startSocket((event) => {
|
|
465
|
+
void router
|
|
466
|
+
.handle(event)
|
|
467
|
+
.catch((err) => log(`slack: router threw (event ${event.event_id} dropped)`, err));
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const timers: Array<ReturnType<typeof setInterval>> = [];
|
|
471
|
+
const every = (ms: number, fn: () => void): void => {
|
|
472
|
+
const t = setInterval(fn, ms);
|
|
473
|
+
t.unref?.();
|
|
474
|
+
timers.push(t);
|
|
475
|
+
};
|
|
476
|
+
// Half the grace window, so a PR is noticed roughly when it goes overdue.
|
|
477
|
+
every(Math.max(1, Math.round(cfg.slack.policy.pickup_grace_min / 2)) * 60_000, () => {
|
|
478
|
+
void announcer
|
|
479
|
+
.announceNotPickedUp()
|
|
480
|
+
.catch((err) => log("slack: not-picked-up sweep failed", err));
|
|
481
|
+
});
|
|
482
|
+
// Every timer body is its own try/catch: a throw inside setInterval is an
|
|
483
|
+
// uncaught exception, and a duplicate-consumer probe must never kill the daemon.
|
|
484
|
+
every(SLACK_CONNECTIONS_TICK_MS, () => {
|
|
485
|
+
try {
|
|
486
|
+
announcer.noteConnections(transport.numConnections);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
log("slack: connections probe failed", err);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
await announcer.daemonUp();
|
|
493
|
+
announcer.startHeartbeat();
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
announcer,
|
|
497
|
+
router,
|
|
498
|
+
origins,
|
|
499
|
+
transport,
|
|
500
|
+
commands,
|
|
501
|
+
slackReply,
|
|
502
|
+
async stop(reason: string): Promise<void> {
|
|
503
|
+
for (const t of timers) clearInterval(t);
|
|
504
|
+
timers.length = 0;
|
|
505
|
+
announcer.stopHeartbeat();
|
|
506
|
+
commands.stop();
|
|
507
|
+
// The store is still open here (onStop runs before teardown), so the
|
|
508
|
+
// post is ledgered; `daemonStopping` is bounded internally, so awaiting
|
|
509
|
+
// it can never hold the exit open.
|
|
510
|
+
await announcer.daemonStopping(reason);
|
|
511
|
+
await transport.stopSocket().catch((err) => log("slack: socket stop failed", err));
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* The local Slack belt both daemon-side watchers install (`commands/` and
|
|
518
|
+
* `commands/operator/`): with Slack live, a file is refused while the turn
|
|
519
|
+
* being served was caused by a non-owner Slack message — read through
|
|
520
|
+
* `latestAny()`, which only a daemon wake's `local` origin supersedes, and
|
|
521
|
+
* `slack_reply` is executable there. Slack off ⇒ no tier gate at all and the
|
|
522
|
+
* handlers as given.
|
|
523
|
+
*/
|
|
524
|
+
function localSlackBelt(
|
|
525
|
+
slack: SlackRuntime | null,
|
|
526
|
+
deps: CommandDeps,
|
|
527
|
+
): Pick<CommandWatcherOptions, "tier" | "deps"> {
|
|
528
|
+
return slack
|
|
529
|
+
? {
|
|
530
|
+
tier: { dir: "local", currentOrigin: () => slack.origins.latestAny() },
|
|
531
|
+
deps: { ...deps, slackReply: slack.slackReply },
|
|
532
|
+
}
|
|
533
|
+
: { deps };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* The legacy `commands/` watcher. Two things change when Slack is live, and
|
|
538
|
+
* only those two: `slack_reply` becomes executable from this dir as well, and
|
|
539
|
+
* the belt gate is installed — when the turn being served was caused by a
|
|
540
|
+
* non-owner Slack message, the legacy dir refuses, so a tier-checked command
|
|
541
|
+
* cannot slip past by being written to the wrong place. Slack off ⇒ no tier
|
|
542
|
+
* gate at all.
|
|
543
|
+
*
|
|
544
|
+
* The gate reads `latestAny()`, not `latest()`: the belt must not lift merely
|
|
545
|
+
* because a turn ran past the 15-minute TTL (text in the channel can ask for
|
|
546
|
+
* exactly that — "take your time"). Slack inbound turns are retired and no
|
|
547
|
+
* wake stamps an origin (see `slackOriginStore`), so the belt stays lifted.
|
|
548
|
+
*/
|
|
549
|
+
export function buildLocalCommandWatcher(opts: {
|
|
550
|
+
root: string;
|
|
551
|
+
deps: CommandDeps;
|
|
552
|
+
store: StateStore;
|
|
553
|
+
slack: SlackRuntime | null;
|
|
554
|
+
log: (line: string) => void;
|
|
555
|
+
}): CommandWatcher {
|
|
556
|
+
const { slack } = opts;
|
|
557
|
+
return new CommandWatcher({
|
|
558
|
+
dir: join(opts.root, "commands"),
|
|
559
|
+
...localSlackBelt(slack, opts.deps),
|
|
560
|
+
store: opts.store,
|
|
561
|
+
log: opts.log,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The operator's command watcher: `commands/operator/`, the directory
|
|
567
|
+
* `qcr drain`, the board's `u` and the deploy tab write as root.
|
|
568
|
+
* Same vocabulary, same handlers (`slack_reply` included while Slack is
|
|
569
|
+
* live); the difference is the issuer the watcher stamps, and only the real
|
|
570
|
+
* process seal decides it. The same local Slack
|
|
571
|
+
* belt as `commands/` is installed, and binds only files the seal does not
|
|
572
|
+
* vouch for: the overseer cannot write into a sealed directory, but the
|
|
573
|
+
* parent is the daemon's, so it can make or swap in an unsealed one — and a
|
|
574
|
+
* file there is a session's, so it must be refused exactly where `commands/`
|
|
575
|
+
* would refuse it. The daemon never creates the directory — see
|
|
576
|
+
* `CommandWatcherOptions`.
|
|
577
|
+
*/
|
|
578
|
+
export function buildOperatorCommandWatcher(opts: {
|
|
579
|
+
root: string;
|
|
580
|
+
deps: CommandDeps;
|
|
581
|
+
store: StateStore;
|
|
582
|
+
slack: SlackRuntime | null;
|
|
583
|
+
log: (line: string) => void;
|
|
584
|
+
}): CommandWatcher {
|
|
585
|
+
const { slack } = opts;
|
|
586
|
+
return new CommandWatcher({
|
|
587
|
+
dir: join(opts.root, "commands", "operator"),
|
|
588
|
+
operator: processOperatorSeal(),
|
|
589
|
+
...localSlackBelt(slack, opts.deps),
|
|
590
|
+
store: opts.store,
|
|
591
|
+
log: opts.log,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Where the passive auth watch looks. PR sessions are spawned
|
|
597
|
+
* with `--session-dir <root>/<pi.session_dir>/<session>`, so THAT is where
|
|
598
|
+
* their expired-token errors land; pi's default store alone would miss every
|
|
599
|
+
* reviewer session. The default store under the container user's home (the
|
|
600
|
+
* pihome volume) stays covered for anything spawned without an explicit dir.
|
|
601
|
+
*/
|
|
602
|
+
export function piAuthWatchDirs(sessions: Pick<PiSessionManager, "sessionsRoot">): string[] {
|
|
603
|
+
// pi's default global store — the same dir the archiver's pi-global store
|
|
604
|
+
// moves day-old files out of: one definition, `piGlobalSessionsDir`.
|
|
605
|
+
return [sessions.sessionsRoot(), piGlobalSessionsDir()];
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Where the `eod-curation-receipt` command lands. With no learner on this
|
|
610
|
+
* daemon (memory switched off) the receipt is refused, with the reason —
|
|
611
|
+
* which is what the sweep prompt's "off" no-packet section
|
|
612
|
+
* (`renderNoPacketSection`) tells the curator: "the daemon would refuse
|
|
613
|
+
* one". tests/eod-seed-coherence.test.ts holds the two together.
|
|
614
|
+
*/
|
|
615
|
+
export function routeCurationReceipt(
|
|
616
|
+
learner: Pick<Learner, "applyReceipt"> | null,
|
|
617
|
+
receipt: CurationReceipt,
|
|
618
|
+
): FileOutcome {
|
|
619
|
+
if (!learner)
|
|
620
|
+
return {
|
|
621
|
+
ok: false,
|
|
622
|
+
reason: "the learner is not running on this daemon ([openviking] enabled = false)",
|
|
623
|
+
};
|
|
624
|
+
return learner.applyReceipt(receipt);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* The daemon's round-memory seed, or none when memory is switched off: a
|
|
629
|
+
* round with no seed renders memory-less without a read, so a deliberate off
|
|
630
|
+
* never reads as degraded memory in the prompt or the round's record.
|
|
631
|
+
*/
|
|
632
|
+
export function roundMemorySeed(
|
|
633
|
+
enabled: boolean,
|
|
634
|
+
seed: RoundMemorySeed,
|
|
635
|
+
): { memory?: RoundMemorySeed } {
|
|
636
|
+
return enabled ? { memory: seed } : {};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** Where and as whom every daemon-side OpenViking client connects — the
|
|
640
|
+
* credential, the observers and the client instance stay each caller's own. */
|
|
641
|
+
function ovConnection(ov: Config["openviking"]): {
|
|
642
|
+
baseUrl: string;
|
|
643
|
+
user: string;
|
|
644
|
+
account: string;
|
|
645
|
+
timeoutMs: number;
|
|
646
|
+
} {
|
|
647
|
+
return {
|
|
648
|
+
baseUrl: openVikingBaseUrl(ov),
|
|
649
|
+
user: ov.user,
|
|
650
|
+
account: ov.account,
|
|
651
|
+
timeoutMs: ov.request_timeout_ms,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* The startup orphan sweep's posting debt. Two rounds are no phantom:
|
|
657
|
+
* - one whose outbox job is still pending owes its review, and the outbox
|
|
658
|
+
* delivers it with no session. Only a job the outbox will actually process
|
|
659
|
+
* counts — live mode, this repo (`outboxProcesses`): a pending dry-run
|
|
660
|
+
* snapshot left over a flip to live is never delivered, so keeping its
|
|
661
|
+
* round would hold it until the operator's `u`. A dry run never owes one;
|
|
662
|
+
* - one whose review is already on GitHub (a `confirmed` receipt for exactly
|
|
663
|
+
* this round and posting key) but whose verdict never landed: its stage
|
|
664
|
+
* limit settles it from the receipt, where a cancel and requeue would run
|
|
665
|
+
* the lenses again for a review already posted.
|
|
666
|
+
*/
|
|
667
|
+
export function orphanPostingOwed(
|
|
668
|
+
receiptDir: string,
|
|
669
|
+
dryRun: boolean,
|
|
670
|
+
repo: string,
|
|
671
|
+
): (round: Pick<RoundRow, "pr" | "round_no" | "posting_key">) => boolean {
|
|
672
|
+
return (round) => {
|
|
673
|
+
if (round.posting_key === null) return false;
|
|
674
|
+
const receipt = readPostingReceipt(receiptDir, round.pr, round.round_no);
|
|
675
|
+
if (
|
|
676
|
+
receipt?.status === "confirmed" &&
|
|
677
|
+
receipt.posting_key === round.posting_key &&
|
|
678
|
+
receipt.pr === round.pr &&
|
|
679
|
+
receipt.round_no === round.round_no
|
|
680
|
+
)
|
|
681
|
+
return true;
|
|
682
|
+
return (
|
|
683
|
+
!dryRun && deliverablePosting(receiptDir, round.pr, round.round_no, round.posting_key, repo)
|
|
684
|
+
);
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* The daemon's origin store. It has no accepted-wake view, so the newest
|
|
690
|
+
* record is in force at once.
|
|
691
|
+
*
|
|
692
|
+
* Slack inbound turns are retired, so no record can describe a live turn any
|
|
693
|
+
* more, and no daemon wake stamps a superseding `local` one. A guest's record
|
|
694
|
+
* left by the previous run would therefore hold the legacy-dir belt on for
|
|
695
|
+
* good: the records are dropped at boot, before the store reads them.
|
|
696
|
+
*/
|
|
697
|
+
export function slackOriginStore(root: string, now?: () => Date): OriginStore {
|
|
698
|
+
clearOriginRecords(join(root, "state", "slack", "origin"));
|
|
699
|
+
return new OriginStore(join(root, "state", "slack", "origin"), now);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export function createProductionDaemon(parts: ProductionParts = buildParts()): Daemon {
|
|
703
|
+
const { cfg, root, gate, adapter, sessions } = parts;
|
|
704
|
+
const memoryBootAt = new Date();
|
|
705
|
+
const log = (msg: string, extra?: unknown) => console.log(`[quickreviewd] ${msg}`, extra ?? "");
|
|
706
|
+
// The last run's heartbeat, read before start() writes this boot's first
|
|
707
|
+
// status.json over it: the Slack `daemon.up` downtime is measured from it.
|
|
708
|
+
const previousStatus = readPreviousStatus(join(root, "state", "status.json"));
|
|
709
|
+
|
|
710
|
+
// The daemon's one memory store, built in onStart once secrets are readable.
|
|
711
|
+
// Everything that needs it is late-bound through this holder: the round
|
|
712
|
+
// assembler and the EOD pinned-area watchdog are constructed before it
|
|
713
|
+
// exists, and both degrade to "no memory" rather than to an error.
|
|
714
|
+
let memoryStore: MemoryStore | null = null;
|
|
715
|
+
/** The same store, as the sweep that keeps what it could not read (the
|
|
716
|
+
* pinned watchdog must tell an outage from an empty pinned area). */
|
|
717
|
+
let memorySweep: PinnedSweepSource | null = null;
|
|
718
|
+
let memoryAssembler: ContextAssembler | null = null;
|
|
719
|
+
const notBuiltYet = {
|
|
720
|
+
status: "unreadable",
|
|
721
|
+
reason: "memory store not built yet (daemon still starting)",
|
|
722
|
+
} as const;
|
|
723
|
+
const lateAssembler: ContextAssembler = {
|
|
724
|
+
pinnedHead: () =>
|
|
725
|
+
memoryAssembler ? memoryAssembler.pinnedHead() : Promise.resolve(notBuiltYet),
|
|
726
|
+
contextBlock: (q, o) =>
|
|
727
|
+
memoryAssembler ? memoryAssembler.contextBlock(q, o) : Promise.resolve(notBuiltYet),
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// The profile-selected ecosystem seam, loaded once at assembly (a
|
|
731
|
+
// malformed profile.toml fails the boot loudly). It decides the tier-forcing
|
|
732
|
+
// migration gate, the round pre-check and the prompt's migration-gate prose;
|
|
733
|
+
// "none" (no profile, no file, no key) runs none of that machinery.
|
|
734
|
+
const ecosystem = ecosystemFor(
|
|
735
|
+
loadProfileEcosystem(root, cfg.review.profile, homeProfileLookup()),
|
|
736
|
+
);
|
|
737
|
+
log(`profile ${cfg.review.profile || "(none)"}: ecosystem ${ecosystem.name}`);
|
|
738
|
+
|
|
739
|
+
// The identity variable map every prompt render and agent build
|
|
740
|
+
// resolves {{owner_name}}/{{repo}}/{{bot_login}} against.
|
|
741
|
+
const identity = identityVars(cfg);
|
|
742
|
+
// The prompt is bound to the host's lens tools (packages/pi-plugin/src/launchvocab.ts). The worker
|
|
743
|
+
// host's sessions have `lens` and `lens_wave`; the tmux host's have `subagent`
|
|
744
|
+
// and `launch_plan`. Dispatch writes the host's names into every order it builds
|
|
745
|
+
// (`SessionHooks.lensRuntime`), whatever `[engine] runtime`, `[pi] lens_runtime` or
|
|
746
|
+
// `engine_wave` say, and this refuses an order written for the other pair.
|
|
747
|
+
// The central lens permit broker (src/ipc/permits.ts): one count of lens
|
|
748
|
+
// sessions across every worker. The WorkerSupervisor serves its ops on the worker
|
|
749
|
+
// socket and status.json shows it.
|
|
750
|
+
const lensPermits = new PermitBroker({
|
|
751
|
+
ceiling: cfg.daemon.lens_permits,
|
|
752
|
+
staggerMs: cfg.daemon.lens_grant_stagger_ms,
|
|
753
|
+
log: (m) => log(m),
|
|
754
|
+
});
|
|
755
|
+
// The daemon's one store, built here so the worker world (the host the daemon's
|
|
756
|
+
// state machine speaks to) can exist before the daemon does.
|
|
757
|
+
const store = new StateStore(join(root, "state", "state.db"));
|
|
758
|
+
// The bot login's single refresher (src/loginrefresh/), spawned by
|
|
759
|
+
// path so this process imports neither pi nor src/engine/. Only built when
|
|
760
|
+
// `[login_refresh] enabled`.
|
|
761
|
+
let loginRefresh: LoginRefreshSupervisor | null = null;
|
|
762
|
+
// Workers are the bot's ONLY host: the IPC server,
|
|
763
|
+
// the supervisor and its spec builder, the leases and the controls, assembled in
|
|
764
|
+
// src/workerworld.ts. No config key picks the tmux route, and nothing births a
|
|
765
|
+
// `pr-<n>` tmux session any more; `sessions` serves paths, worktrees and the replay harness.
|
|
766
|
+
const agentDir = process.env["PI_CODING_AGENT_DIR"]?.trim() || join(homedir(), ".pi", "agent");
|
|
767
|
+
const workerWorld = buildWorkerWorld({
|
|
768
|
+
cfg,
|
|
769
|
+
root,
|
|
770
|
+
store,
|
|
771
|
+
adapter,
|
|
772
|
+
reader: adapter,
|
|
773
|
+
// The entrypoint names the tool uid (docker/entrypoint.sh): the model's
|
|
774
|
+
// shims then dial the socket as another uid of this group.
|
|
775
|
+
toolUid: toolUidSplit(),
|
|
776
|
+
sessions,
|
|
777
|
+
...(parts.ovSessions ? { ovSessions: parts.ovSessions } : {}),
|
|
778
|
+
permits: lensPermits,
|
|
779
|
+
agentDir,
|
|
780
|
+
pump: () => daemon.pump(),
|
|
781
|
+
loginGate: {
|
|
782
|
+
paused: () => loginRefresh?.paused() ?? false,
|
|
783
|
+
whenResumed: (ms) => loginRefresh?.whenResumed(ms) ?? Promise.resolve(true),
|
|
784
|
+
},
|
|
785
|
+
log,
|
|
786
|
+
});
|
|
787
|
+
const botHost: SessionHooks = workerWorld.host;
|
|
788
|
+
const renderPrompt = (order: RoundOrder, memory: RoundMemory): string => {
|
|
789
|
+
assertOrderFitsHost(botHost, order);
|
|
790
|
+
return renderRoundPrompt(
|
|
791
|
+
readFileSync(join(root, "prompts", templateFileForOrder(order)), "utf8"),
|
|
792
|
+
order,
|
|
793
|
+
memory,
|
|
794
|
+
cfg.owner.name,
|
|
795
|
+
ecosystem,
|
|
796
|
+
identity,
|
|
797
|
+
);
|
|
798
|
+
};
|
|
799
|
+
// The recheck's own template — a nudge, not a round order.
|
|
800
|
+
const renderRecheck = (order: RecheckOrder): string =>
|
|
801
|
+
renderRecheckPrompt(
|
|
802
|
+
readFileSync(join(root, "prompts", RECHECK_TEMPLATE_FILE), "utf8"),
|
|
803
|
+
order,
|
|
804
|
+
identity,
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
let eod: EodTimer | null = null;
|
|
808
|
+
let pinWatch: ReturnType<typeof startEodPinWatch> | null = null;
|
|
809
|
+
let heartbeat: HeartbeatTimer | null = null;
|
|
810
|
+
let commands: CommandWatcher | null = null;
|
|
811
|
+
let operatorCommands: CommandWatcher | null = null;
|
|
812
|
+
let slack: SlackRuntime | null = null;
|
|
813
|
+
// onStart's own intervals (the overseer rotation, the agents day check),
|
|
814
|
+
// cleared at stop like every other timer.
|
|
815
|
+
const intervals: Array<ReturnType<typeof setInterval>> = [];
|
|
816
|
+
// The [feedback] engine, built in onStart (it needs the memory store). A
|
|
817
|
+
// signal arriving in the boot gap between the webhook binding and onStart
|
|
818
|
+
// is ledgered, never dropped: its row starts `pending`, the backfill pump
|
|
819
|
+
// writes its memory entry, and the agents rebuild once the engine exists so
|
|
820
|
+
// a same-day [feedback] comment still reaches the validator.
|
|
821
|
+
let feedback: FeedbackEngine | null = null;
|
|
822
|
+
let feedbackLedgeredEarly = false;
|
|
823
|
+
// The [feedback] ledger's backfill pump — writes what memory still owes
|
|
824
|
+
// while memory health reads healthy, and wakes `memory_write_backlog` when
|
|
825
|
+
// an owed signal ages past the threshold.
|
|
826
|
+
let feedbackPump: FeedbackMemoryPump | null = null;
|
|
827
|
+
// The finding-case writer — one document per posted finding a human
|
|
828
|
+
// answered or whose fate is known, into reviewer memory's `finding-cases/`,
|
|
829
|
+
// through the same store; backfills `[memory] cases_backfill_days`. Writes
|
|
830
|
+
// only while memory health reads healthy.
|
|
831
|
+
let caseWriter: CaseWriter | null = null;
|
|
832
|
+
// Is a review round reading memory right now? Dispatch opens a window
|
|
833
|
+
// around each round's seed (through to its lesson selection), the selector
|
|
834
|
+
// one around each prefetch, and the case writer starts no batch while any
|
|
835
|
+
// is open — every case is a multi-second embedding on the one-thread
|
|
836
|
+
// embedder those reads wait on.
|
|
837
|
+
const roundActivity = new RoundMemoryActivity();
|
|
838
|
+
// The learner — mines the human outcomes into lesson candidates at
|
|
839
|
+
// EOD − `lessons_mine_lead_min`, hands the EOD sweep its outcomes packet
|
|
840
|
+
// (`dayContext`), takes the `eod-curation-receipt`, checks every adoption
|
|
841
|
+
// against memory, and wakes `eod_curation_missing` /
|
|
842
|
+
// `eod_lessons_not_persisted`. Only where memory is switched on: with it off
|
|
843
|
+
// there is nowhere a lesson could land.
|
|
844
|
+
let learner: Learner | null = null;
|
|
845
|
+
// The closed-PR archiver — daemon bookkeeping on its own timer (never a
|
|
846
|
+
// reconcile step), started beside the case writer and the learner whatever
|
|
847
|
+
// memory does. It starts no work before the first healthy reconcile plus
|
|
848
|
+
// `[archive] start_delay_min`, and holds while draining, stalled, starved or
|
|
849
|
+
// short of disk. Every tick plans (status.json `archive` says what a pass
|
|
850
|
+
// would move); in mode `on` it also restores reopened PRs, moves the old
|
|
851
|
+
// reader-less event rows and webhook deliveries into archive/db/, moves
|
|
852
|
+
// closed PRs' files into archive/pr-<n>/ and seals finished months.
|
|
853
|
+
let archiver: Archiver | null = null;
|
|
854
|
+
// The lesson shadow ledger — every round's selection through OpenViking
|
|
855
|
+
// (bounded, `[memory] delivery`), the marks a delivered dont-raise lesson
|
|
856
|
+
// would have left on its posted findings, and the operator's kill switch.
|
|
857
|
+
// Built in onStart (it needs the state store); the selector only where
|
|
858
|
+
// memory is on. Under `shadow` nothing here changes a prompt; under `on`
|
|
859
|
+
// dispatch awaits `select` beside the seed and renders the round's lessons,
|
|
860
|
+
// and `repoFacts` reaches the recheck nudge and the ping-pong wake.
|
|
861
|
+
let lessonLedger: LessonLedger | null = null;
|
|
862
|
+
let lessonSelector: RoundLessonSelector | null = null;
|
|
863
|
+
const lateLessons: RoundLessonHook = {
|
|
864
|
+
start: (order, files) => lessonSelector?.start(order, files),
|
|
865
|
+
delivering: () => lessonSelector?.delivering() ?? false,
|
|
866
|
+
select: (order, files, opts) =>
|
|
867
|
+
lessonSelector?.select(order, files, opts) ?? Promise.resolve(null),
|
|
868
|
+
repoFacts: (q) => lessonSelector?.repoFacts(q) ?? Promise.resolve(""),
|
|
869
|
+
// A round discarded before its handoff: its selection record goes (with
|
|
870
|
+
// no selector nothing was ever recorded).
|
|
871
|
+
discard: (roundId) => lessonSelector?.discard(roundId),
|
|
872
|
+
};
|
|
873
|
+
/** The status headline, recomputed at most once a minute (the heartbeat
|
|
874
|
+
* runs far more often, and the report reads several tables). */
|
|
875
|
+
let lessonStatusCache: { at: number; value: LessonDeliveryStatus | null } | null = null;
|
|
876
|
+
// The OpenViking server, supervised as a child of this process. Started
|
|
877
|
+
// first in onStart so it warms up while the agents build — but never
|
|
878
|
+
// awaited: there is deliberately no boot ordering, and rounds run
|
|
879
|
+
// memory-less until `/ready` passes.
|
|
880
|
+
let openviking: OpenVikingSupervisor | null = null;
|
|
881
|
+
// The local English embedder the server embeds through — a second
|
|
882
|
+
// supervised child, started first; the server's first start waits (bounded)
|
|
883
|
+
// for it to serve. Off unless the rendered ov.conf routes to it.
|
|
884
|
+
let localEmbedder: LocalEmbedderSupervisor | null = null;
|
|
885
|
+
// The embedder migration's daemon half — resumes the re-embed of reviewer
|
|
886
|
+
// memory the entrypoint's `prepare` journalled, once /ready passes.
|
|
887
|
+
// Timer-armed, never awaited; its block rides inside the heartbeat's
|
|
888
|
+
// `openviking` entry.
|
|
889
|
+
let reembed: ReembedDriver | null = null;
|
|
890
|
+
// Worker liveness: the archiver, the startup orphan sweep and operator
|
|
891
|
+
// recovery read the supervisor's lease files, never a tmux listing.
|
|
892
|
+
const workerLive = createWorkerLiveness({ runDir: runDirOf(join(root, "state")) });
|
|
893
|
+
// The webhook-URL self-heal. Only ever constructed when [ingress]
|
|
894
|
+
// shape = "tunnel-file".
|
|
895
|
+
let webhookHeal: WebhookHealer | null = null;
|
|
896
|
+
// Reviewer-memory health — the authenticated probe of each credential in
|
|
897
|
+
// use, fed by the daemon's own requests and round seeds, and the one source
|
|
898
|
+
// of the status.json `memory` block every person-facing surface reads.
|
|
899
|
+
// Built in onStart (it needs the store and the server's config); until then
|
|
900
|
+
// the block reads `unknown` — never healthy, never off.
|
|
901
|
+
let memoryHealth: MemoryHealthMonitor | null = null;
|
|
902
|
+
/** Why memory is NOT monitored on this daemon; null while still deciding. */
|
|
903
|
+
let memoryUnmonitored: string | null = null;
|
|
904
|
+
if (parts.memoryRequests) parts.memoryRequests.sink = (r) => memoryHealth?.recordRequest(r);
|
|
905
|
+
|
|
906
|
+
// The origin store the Slack belt reads. Slack inbound turns are retired, so
|
|
907
|
+
// nothing writes an origin any more.
|
|
908
|
+
const origins = slackOriginStore(root);
|
|
909
|
+
|
|
910
|
+
// The companion inbox: everything that used to wake the overseer (anomalies,
|
|
911
|
+
// the heartbeat, authwatch's and the login refresher's alerts, the EOD sweep,
|
|
912
|
+
// the self-requeue notice, the Slack connection signal) is an item here.
|
|
913
|
+
const inbox = new CompanionInbox({ root, log });
|
|
914
|
+
const writeInbox = inbox.writer();
|
|
915
|
+
|
|
916
|
+
// The profile's lens-heuristics extension, loaded once here so a
|
|
917
|
+
// malformed heuristics.toml fails the assembly loudly (same contract as the
|
|
918
|
+
// addenda pack below) and every dispatch selects with the same predicates.
|
|
919
|
+
const profileHeuristics = loadProfileHeuristics(root, cfg.review.profile, homeProfileLookup());
|
|
920
|
+
|
|
921
|
+
// The gate's loud line (app-self-config executions) goes to the
|
|
922
|
+
// daemon log; the default console fallback covers any unwired gate.
|
|
923
|
+
gate.setLog(log);
|
|
924
|
+
|
|
925
|
+
const daemon = createDaemon({
|
|
926
|
+
cfg,
|
|
927
|
+
root,
|
|
928
|
+
store,
|
|
929
|
+
adapter,
|
|
930
|
+
...(parts.runHost ? { runHost: parts.runHost } : {}),
|
|
931
|
+
postingDelivery: {
|
|
932
|
+
createReview: async (pr, payload) => {
|
|
933
|
+
// An approval queued by approve-pr.sh keeps that route's green-CI
|
|
934
|
+
// precondition. Do not silently turn an old approval verdict into a
|
|
935
|
+
// COMMENT that its session never marked approval_withheld.
|
|
936
|
+
if (payload.event === "APPROVE" && cfg.guards.block_approve_on_red_ci) {
|
|
937
|
+
try {
|
|
938
|
+
if (
|
|
939
|
+
ciBlocksApproval(classifyCi(await adapter.listChecks(payload.commit_id)), {
|
|
940
|
+
noChecksIsRed: cfg.guards.no_checks_is_red,
|
|
941
|
+
})
|
|
942
|
+
)
|
|
943
|
+
return { executed: false, blocked: "ci-not-green" };
|
|
944
|
+
} catch {
|
|
945
|
+
return { executed: false, blocked: "pr-unreadable" };
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return adapter.createReview(pr, payload);
|
|
949
|
+
},
|
|
950
|
+
listReviewsFull: (pr) => adapter.listReviewsFull(pr),
|
|
951
|
+
// The deferred-posting retry: straight through the gate, whose guards
|
|
952
|
+
// bind as on any review (guard 4 softens a red-CI approval to a
|
|
953
|
+
// COMMENT there); only guard 5 is exempt, via `postingRetry`.
|
|
954
|
+
createDeferredReview: (pr, payload, postingKey) =>
|
|
955
|
+
adapter.createReview(pr, payload, { postingRetry: postingKey }),
|
|
956
|
+
},
|
|
957
|
+
hooks: botHost,
|
|
958
|
+
writeInbox,
|
|
959
|
+
renderPrompt,
|
|
960
|
+
ecosystem,
|
|
961
|
+
heuristics: profileHeuristics,
|
|
962
|
+
// Pinned head + one budgeted context block per round. Past the
|
|
963
|
+
// deadline the round dispatches memory-less and logs the gap — memory
|
|
964
|
+
// never blocks or delays a round. Memory switched off
|
|
965
|
+
// (`[openviking] enabled = false`) seeds nothing: no read, no
|
|
966
|
+
// "memory unavailable" note, nothing counted as degraded.
|
|
967
|
+
...roundMemorySeed(cfg.openviking.enabled, {
|
|
968
|
+
assembler: lateAssembler,
|
|
969
|
+
maxTokens: cfg.memory.context_tokens,
|
|
970
|
+
timeoutMs: cfg.memory.context_timeout_ms,
|
|
971
|
+
// Every seed, after the round was built, feeds memory health — a seed
|
|
972
|
+
// that timed out reaches no client observer, so this is the only place
|
|
973
|
+
// it is seen.
|
|
974
|
+
onSeeded: (mem: RoundMemory) =>
|
|
975
|
+
memoryHealth?.recordRoundSeed({
|
|
976
|
+
degraded: mem.degraded,
|
|
977
|
+
reasons: mem.gaps.map((g) => `${g.part}: ${g.reason}`),
|
|
978
|
+
}),
|
|
979
|
+
// The PR session's own recall (vendored openviking-pi patch 0008)
|
|
980
|
+
// searches with the round's query and the round's areas, not the round
|
|
981
|
+
// order.
|
|
982
|
+
publishQuery: (pr: number, query: string) => {
|
|
983
|
+
writeRecallHint(
|
|
984
|
+
sessions.ovRecallHintPath(prSessionName(pr)),
|
|
985
|
+
{ query, areas: ROUND_SEARCH_AREAS },
|
|
986
|
+
{ log: (msg) => log(msg) },
|
|
987
|
+
);
|
|
988
|
+
},
|
|
989
|
+
// The round's memory window — the case writer starts no
|
|
990
|
+
// batch from the seed until the lesson selection has taken over.
|
|
991
|
+
activity: roundActivity,
|
|
992
|
+
}),
|
|
993
|
+
// The round's lesson selection (shadow ledger; awaited by dispatch only
|
|
994
|
+
// under `delivery = "on"`); and the matcher, told when a round's findings
|
|
995
|
+
// record lands.
|
|
996
|
+
lessons: lateLessons,
|
|
997
|
+
onFindingsRecorded: (target) =>
|
|
998
|
+
completeRecordedFindings(
|
|
999
|
+
{
|
|
1000
|
+
store: daemon.store,
|
|
1001
|
+
ledger: lessonLedger,
|
|
1002
|
+
adapter,
|
|
1003
|
+
sidecarPath: (n, k) => priorSidecarPath(join(root, "state"), n, k),
|
|
1004
|
+
log,
|
|
1005
|
+
},
|
|
1006
|
+
target,
|
|
1007
|
+
),
|
|
1008
|
+
renderRecheckPrompt: renderRecheck,
|
|
1009
|
+
// Conflict resolutions are never trivial: git-level auto-merge comparison
|
|
1010
|
+
// in the base clone names the hand-resolved files.
|
|
1011
|
+
// A degraded merge-verify is logged with its reason here (the failed git
|
|
1012
|
+
// command and its stderr when one failed); the callers record the
|
|
1013
|
+
// degradation itself.
|
|
1014
|
+
resolvedFilesInMerges: async (from, to, pr) => {
|
|
1015
|
+
const res = await sessions.worktrees.resolvedFilesInMerges(from, to, pr);
|
|
1016
|
+
if (res.degraded)
|
|
1017
|
+
log(
|
|
1018
|
+
`merge-verify degraded${pr !== undefined ? ` #${pr}` : ""} ${from.slice(0, 7)}→${to.slice(0, 7)}: ${res.reason ?? "unknown"}`,
|
|
1019
|
+
);
|
|
1020
|
+
return res;
|
|
1021
|
+
},
|
|
1022
|
+
// Outcome capture: did the code at a posted finding's anchor
|
|
1023
|
+
// change? Read-only git in the same base clone, no fetch, memoised.
|
|
1024
|
+
outcomeGit: sessions.worktrees,
|
|
1025
|
+
// One network fetch per round, before the order is built: the PR head and
|
|
1026
|
+
// the branch it targets, together. Everything downstream (prompt, session,
|
|
1027
|
+
// passes) then works from the shas this returns.
|
|
1028
|
+
prepareWorktree: (pr, baseRef, expectedHead) =>
|
|
1029
|
+
sessions.worktrees.ensureWorktree(pr, { baseRef, expectedHead }),
|
|
1030
|
+
// Follow-up rounds: the PR-owned change since the last reviewed
|
|
1031
|
+
// head (the reviewed head replayed onto the round's merge base, read-only
|
|
1032
|
+
// git in the base clone), and the pin that keeps a reviewed head
|
|
1033
|
+
// replayable after a force-push.
|
|
1034
|
+
prOwnedDelta: (q) => sessions.worktrees.prOwnedDelta(q),
|
|
1035
|
+
pinReviewedHead: (pr, sha) => sessions.worktrees.pinReviewedHead(pr, sha),
|
|
1036
|
+
// Third-party sources at the PR's locked versions, unpacked into a cache
|
|
1037
|
+
// on the state volume for the passes to read (`[review]
|
|
1038
|
+
// dependency_sources`). Download and unpack only;
|
|
1039
|
+
// total — a dead registry yields an empty note.
|
|
1040
|
+
dependencySources: async (worktreePath, files) =>
|
|
1041
|
+
renderDependencyNote(
|
|
1042
|
+
await prepareDependencySources(worktreePath, files, {
|
|
1043
|
+
cacheDir: join(root, "state", "depcache"),
|
|
1044
|
+
log: (line) => log(`deps: ${line}`),
|
|
1045
|
+
}),
|
|
1046
|
+
),
|
|
1047
|
+
// Migration-number pre-check input: the base branch's migrations
|
|
1048
|
+
// at the sha the round pinned. Local git in the just-fetched base clone.
|
|
1049
|
+
listBaseMigrations: (baseSha) => sessions.worktrees.listMigrationFiles(baseSha),
|
|
1050
|
+
// Escalation-mention pick: most recent human author of the
|
|
1051
|
+
// touched first-party directive files at the round's merge base, read from
|
|
1052
|
+
// the base clone; the daemon's own bot identity never qualifies.
|
|
1053
|
+
directiveAuthor: (sha, paths) =>
|
|
1054
|
+
sessions.worktrees.lastHumanAuthor(sha, paths, {
|
|
1055
|
+
botLogins: [cfg.github.bot_login, cfg.github.app_slug],
|
|
1056
|
+
}),
|
|
1057
|
+
log,
|
|
1058
|
+
// Lazy on purpose: the Slack subsystem is built in onStart, after the
|
|
1059
|
+
// store exists. Off ⇒ this resolves undefined forever and dispatch/verdicts
|
|
1060
|
+
// never see an announcer.
|
|
1061
|
+
announce: () => slack?.announcer,
|
|
1062
|
+
digestHeadline: () => latestDigestHeadline(join(root, "digest")),
|
|
1063
|
+
// Passive pi auth-failure watch: the JSONLs pi actually writes.
|
|
1064
|
+
piSessionsDir: piAuthWatchDirs(sessions),
|
|
1065
|
+
// The memory engine's heartbeat block — a cached, synchronous
|
|
1066
|
+
// read of the supervisor's last health + /proc sample, plus the reembed
|
|
1067
|
+
// journal (one small synchronous file read per heartbeat) and the local
|
|
1068
|
+
// embedder's status.
|
|
1069
|
+
openvikingStatus: () => {
|
|
1070
|
+
const status = openviking?.status();
|
|
1071
|
+
if (!status) return status;
|
|
1072
|
+
return {
|
|
1073
|
+
...status,
|
|
1074
|
+
...(reembed ? { reembed: reembed.status() } : {}),
|
|
1075
|
+
...(localEmbedder ? { embedder: localEmbedder.status() } : {}),
|
|
1076
|
+
};
|
|
1077
|
+
},
|
|
1078
|
+
// The archiver's block — a cached synchronous read. Before
|
|
1079
|
+
// onStart builds it the mode is known and nothing else: `unknown`, a
|
|
1080
|
+
// null backlog, never 0.
|
|
1081
|
+
archiveStatus: () => (archiver ? archiver.status() : pendingArchiveStatus(cfg.archive.mode)),
|
|
1082
|
+
// The login refresher's block — a cached synchronous read; absent when the
|
|
1083
|
+
// refresher is not configured.
|
|
1084
|
+
loginRefreshStatus: () => loginRefresh?.status(),
|
|
1085
|
+
// The lens permit broker's use and queue.
|
|
1086
|
+
permitStatus: () => lensPermits.status(),
|
|
1087
|
+
// What hosts each PR's session, for status.json's per-PR `runtime`.
|
|
1088
|
+
runtimeOf: workerWorld.runtimeOf,
|
|
1089
|
+
// Whether memory works, beside whether the server is up — and what memory
|
|
1090
|
+
// still owes the [feedback] ledger.
|
|
1091
|
+
memoryStatus: (): MemoryStatus => {
|
|
1092
|
+
const base = memoryHealth
|
|
1093
|
+
? memoryHealth.status()
|
|
1094
|
+
: parts.memoryBootFailure
|
|
1095
|
+
? migrationMemoryStatus(parts.memoryBootFailure, memoryBootAt)
|
|
1096
|
+
: memoryUnmonitored !== null
|
|
1097
|
+
? disabledMemoryStatus(memoryUnmonitored)
|
|
1098
|
+
: pendingMemoryStatus();
|
|
1099
|
+
// The finding-case ledger, when a case writer runs (the block is a
|
|
1100
|
+
// fresh object each call). Unread is unknown (null), never
|
|
1101
|
+
// "nothing owed".
|
|
1102
|
+
if (caseWriter) {
|
|
1103
|
+
let cases: CaseWritesStatus | null;
|
|
1104
|
+
try {
|
|
1105
|
+
cases = caseWriter.status();
|
|
1106
|
+
} catch (err) {
|
|
1107
|
+
log("memory status: could not read the finding-case ledger", err);
|
|
1108
|
+
cases = null;
|
|
1109
|
+
}
|
|
1110
|
+
base.case_writes = cases;
|
|
1111
|
+
}
|
|
1112
|
+
// The lesson shadow ledger's headline. Unread is unknown (null), never
|
|
1113
|
+
// "nothing happened".
|
|
1114
|
+
if (lessonLedger) {
|
|
1115
|
+
const nowMs = Date.now();
|
|
1116
|
+
if (!lessonStatusCache || nowMs - lessonStatusCache.at >= 60_000) {
|
|
1117
|
+
let value: LessonDeliveryStatus | null;
|
|
1118
|
+
try {
|
|
1119
|
+
value = lessonDeliveryStatus(lessonLedger, cfg.memory.delivery, {
|
|
1120
|
+
selecting: lessonSelector !== null,
|
|
1121
|
+
});
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
log("memory status: could not read the lesson ledger", err);
|
|
1124
|
+
value = null;
|
|
1125
|
+
}
|
|
1126
|
+
lessonStatusCache = { at: nowMs, value };
|
|
1127
|
+
}
|
|
1128
|
+
base.lessons = lessonStatusCache.value;
|
|
1129
|
+
}
|
|
1130
|
+
// The day's EOD curation and the lessons' balance. Unread is unknown
|
|
1131
|
+
// (null), never "all well".
|
|
1132
|
+
if (learner) {
|
|
1133
|
+
try {
|
|
1134
|
+
base.curation = learner.status();
|
|
1135
|
+
} catch (err) {
|
|
1136
|
+
log("memory status: could not read the learner's ledger", err);
|
|
1137
|
+
base.curation = null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
const b = daemon.store.feedbackMemoryBacklog();
|
|
1142
|
+
return {
|
|
1143
|
+
...base,
|
|
1144
|
+
feedback_writes: {
|
|
1145
|
+
pending: b.pending,
|
|
1146
|
+
failed: b.failed,
|
|
1147
|
+
oldest_pending_at: b.oldest_pending_at,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
// Unread is unknown, never "nothing owed".
|
|
1152
|
+
log("memory status: could not read the [feedback] memory backlog", err);
|
|
1153
|
+
return { ...base, feedback_writes: null };
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
// The [feedback] tuning loop.
|
|
1158
|
+
onFeedback: async (e: FeedbackEvent) => {
|
|
1159
|
+
if (!feedback) {
|
|
1160
|
+
// Before the engine exists the signal is ledgered, never dropped: its
|
|
1161
|
+
// row waits `pending` for the backfill pump.
|
|
1162
|
+
const res = ledgerFeedbackSignal(daemon.store, e, {
|
|
1163
|
+
timezone: cfg.ops.timezone,
|
|
1164
|
+
memoryEnabled: cfg.openviking.enabled,
|
|
1165
|
+
});
|
|
1166
|
+
if (res.result === "recorded") feedbackLedgeredEarly = true;
|
|
1167
|
+
log(
|
|
1168
|
+
`feedback: signal before engine ready — ledgered (${res.result}); memory is written by the backfill`,
|
|
1169
|
+
e.kind,
|
|
1170
|
+
);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
await feedback.ingest(e);
|
|
1174
|
+
},
|
|
1175
|
+
|
|
1176
|
+
async onStart(d) {
|
|
1177
|
+
// OpenViking first and unawaited: the server takes seconds to become
|
|
1178
|
+
// ready, so it starts warming while everything below builds. A missing
|
|
1179
|
+
// binary (any dev PC) is reported once and never retried; a crash is
|
|
1180
|
+
// respawned on a capped backoff ladder, forever.
|
|
1181
|
+
//
|
|
1182
|
+
// The local English embedder starts before it — the server's /ready
|
|
1183
|
+
// probes it — and the server's first start waits for it
|
|
1184
|
+
// to serve, bounded (EMBEDDER_GATE_MAX_WAIT_MS). Neither is awaited here.
|
|
1185
|
+
localEmbedder = new LocalEmbedderSupervisor({ cfg: cfg.openviking, repoRoot: root, log });
|
|
1186
|
+
localEmbedder.start();
|
|
1187
|
+
const embedderForGate = localEmbedder;
|
|
1188
|
+
openviking = new OpenVikingSupervisor({
|
|
1189
|
+
cfg: cfg.openviking,
|
|
1190
|
+
log,
|
|
1191
|
+
startGate: {
|
|
1192
|
+
open: () => embedderForGate.gateOpen(),
|
|
1193
|
+
maxWaitMs: EMBEDDER_GATE_MAX_WAIT_MS,
|
|
1194
|
+
what: "the local embedder",
|
|
1195
|
+
},
|
|
1196
|
+
// The reviewer's memory root does not exist on a fresh server until
|
|
1197
|
+
// something creates it. The TUI does this for the companion's sake;
|
|
1198
|
+
// the daemon does it here for its own, each time the server comes up,
|
|
1199
|
+
// so the round path's first sweep lists a real (empty) tree instead
|
|
1200
|
+
// of leaning on how the server spells a missing one.
|
|
1201
|
+
onReady: () => {
|
|
1202
|
+
// Memory health re-measures at once rather than at its next tick:
|
|
1203
|
+
// a server that just came up should read healthy (or say why not)
|
|
1204
|
+
// in seconds, not minutes.
|
|
1205
|
+
void memoryHealth?.probeNow();
|
|
1206
|
+
const store = memoryStore;
|
|
1207
|
+
if (!store?.ensureWritable) return;
|
|
1208
|
+
void store.ensureWritable().then(
|
|
1209
|
+
(res) =>
|
|
1210
|
+
log(
|
|
1211
|
+
`openviking ready: memory root ${res.ok ? "ready" : "NOT ready"} — ${res.message}`,
|
|
1212
|
+
),
|
|
1213
|
+
(err) => log("openviking ready: memory root check failed", err),
|
|
1214
|
+
);
|
|
1215
|
+
},
|
|
1216
|
+
});
|
|
1217
|
+
openviking.start();
|
|
1218
|
+
const supervisor = openviking;
|
|
1219
|
+
// Under the memory sidecar (endpoint = remote) the store, its journal
|
|
1220
|
+
// and the re-embed migration live in the sidecar, which runs its own
|
|
1221
|
+
// driver at its boot: this daemon has neither the journal nor ov.conf.
|
|
1222
|
+
if (cfg.openviking.endpoint === "local") {
|
|
1223
|
+
reembed = new ReembedDriver({
|
|
1224
|
+
cfg: cfg.openviking,
|
|
1225
|
+
repoRoot: root,
|
|
1226
|
+
keyPath: join(cfg.github.secrets_dir, OV_REVIEWER_KEY_SECRET),
|
|
1227
|
+
ready: () => supervisor.status().ready,
|
|
1228
|
+
log,
|
|
1229
|
+
});
|
|
1230
|
+
reembed.start();
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Lens agents: expand {{include:...}} + per-pass model overrides into
|
|
1234
|
+
// the build dir (state/agents) the native subagent extension discovers
|
|
1235
|
+
// through its "user" scope — the container entrypoint symlinks
|
|
1236
|
+
// ~/.pi/agent/agents at it.
|
|
1237
|
+
//
|
|
1238
|
+
// The build composes in the same-day feedback addendum when today
|
|
1239
|
+
// carries [feedback] comments — validator-scoped, demotion-only,
|
|
1240
|
+
// regenerated on every ingest and expired on ops-day change (the
|
|
1241
|
+
// 5-minute day check below), so a mid-day restart picks today's
|
|
1242
|
+
// feedback up and yesterday's never leaks into a new day.
|
|
1243
|
+
// The repo-profile addenda pack is loaded once at assembly so a
|
|
1244
|
+
// malformed pack fails the boot loudly (never a silent skip) and every
|
|
1245
|
+
// rebuild composes the same bytes. Composition order is fixed in the
|
|
1246
|
+
// `addenda` list below, with the same-day feedback addendum last.
|
|
1247
|
+
const profileAddenda = loadProfileAddenda(root, cfg.review.profile, homeProfileLookup());
|
|
1248
|
+
if (cfg.review.profile !== "") {
|
|
1249
|
+
log(`profile ${cfg.review.profile}: ${profileAddenda.length} addenda loaded`);
|
|
1250
|
+
const tuned = Object.keys(cfg.lenses ?? {});
|
|
1251
|
+
if (tuned.length) log(`profile ${cfg.review.profile}: lens tuning for ${tuned.join(", ")}`);
|
|
1252
|
+
}
|
|
1253
|
+
// The day is marked built only once the build LANDS (dayScopedRebuild):
|
|
1254
|
+
// a throw on the day-change tick is retried by the next tick instead of
|
|
1255
|
+
// leaving yesterday's feedback addendum in force all day.
|
|
1256
|
+
// The lesson ledger holds the operator's kill switch,
|
|
1257
|
+
// which decides (with `[memory] delivery`) whether the validator's
|
|
1258
|
+
// lesson addendum is composed — so it exists before the first build.
|
|
1259
|
+
lessonLedger = new LessonLedger(d.store);
|
|
1260
|
+
const agentsDay = dayScopedRebuild(
|
|
1261
|
+
() => opsDay(cfg.ops.timezone),
|
|
1262
|
+
(buildDay) => {
|
|
1263
|
+
const fb = feedbackDayAddendum(d.store.feedbackCommentsForDay(buildDay), buildDay);
|
|
1264
|
+
const lessonAdd = lessonAddendaFor(cfg.memory.delivery, lessonLedger);
|
|
1265
|
+
const built = buildAgents(join(root, "agents"), join(root, "state", "agents"), {
|
|
1266
|
+
root,
|
|
1267
|
+
tiers: cfg.models.tiers,
|
|
1268
|
+
riskPasses: cfg.models.risk_passes,
|
|
1269
|
+
validateTier: cfg.models.validate_tier,
|
|
1270
|
+
vars: identity,
|
|
1271
|
+
// Ecosystem-gated addenda follow the profile's selection.
|
|
1272
|
+
ecosystem: ecosystem.name,
|
|
1273
|
+
// A lens's profile `effort` rides the per-variant hook;
|
|
1274
|
+
// `[models.pass_tiers]` pins move a lens's tier on top of it.
|
|
1275
|
+
models: roundAgentModels(cfg),
|
|
1276
|
+
addenda: [
|
|
1277
|
+
...PASS_ADDENDA,
|
|
1278
|
+
...configAddenda(cfg.review),
|
|
1279
|
+
...profileAddenda,
|
|
1280
|
+
// The profile's `emphasis` / `max_severity`, compiled.
|
|
1281
|
+
...lensTuningAddenda(cfg.lenses),
|
|
1282
|
+
// The profile's `[posting]` policy, compiled for the validator.
|
|
1283
|
+
...postingPolicyAddenda(cfg.posting),
|
|
1284
|
+
// The validator's lesson authority, beside the
|
|
1285
|
+
// same-day feedback addendum (never in place of it) — only
|
|
1286
|
+
// while `[memory] delivery = "on"` and the kill switch is on.
|
|
1287
|
+
...lessonAdd,
|
|
1288
|
+
...(fb ? [fb] : []),
|
|
1289
|
+
],
|
|
1290
|
+
});
|
|
1291
|
+
log(
|
|
1292
|
+
`built ${built.length} pass agents into state/agents${lessonAdd.length ? " (+ lesson addendum)" : ""}${fb ? " (+ same-day feedback addendum)" : ""}`,
|
|
1293
|
+
);
|
|
1294
|
+
},
|
|
1295
|
+
);
|
|
1296
|
+
const rebuildAgents = agentsDay.rebuild;
|
|
1297
|
+
rebuildAgents();
|
|
1298
|
+
// Reviewer memory: the one daemon-side `MemoryStore`, over the
|
|
1299
|
+
// supervised OpenViking server on loopback. The reviewer user key is
|
|
1300
|
+
// read from the secrets mount lazily (`keyFromFile` re-checks on a TTL),
|
|
1301
|
+
// so a key that lands after boot is picked up without a restart — and an
|
|
1302
|
+
// absent key degrades exactly like a dead server, which is the correct
|
|
1303
|
+
// behaviour on a dev PC. Never an ADMIN or ROOT key.
|
|
1304
|
+
// One key source for the client, the health probe and the key-change
|
|
1305
|
+
// watcher: it re-stats the file every TTL and the client drops its
|
|
1306
|
+
// cache on a 401, so a rotated key is re-read rather than a dead one
|
|
1307
|
+
// held forever.
|
|
1308
|
+
const reviewerKey = keyFromFile(join(cfg.github.secrets_dir, OV_REVIEWER_KEY_SECRET));
|
|
1309
|
+
// The counted `openviking_gap`: every gap counted into the memory
|
|
1310
|
+
// block, and a quiet anomaly row at most once an hour per op:code that
|
|
1311
|
+
// carries the running count (src/openviking/gapsink.ts).
|
|
1312
|
+
const countedGap = countedGapSink({
|
|
1313
|
+
record: (gap) => memoryHealth?.recordGap(gap),
|
|
1314
|
+
logAnomaly: (kind, pr, roundNo, detail) => d.store.logAnomaly(kind, pr, roundNo, detail),
|
|
1315
|
+
log,
|
|
1316
|
+
});
|
|
1317
|
+
const memoryClient = new OpenVikingClient({
|
|
1318
|
+
...ovConnection(cfg.openviking),
|
|
1319
|
+
apiKey: reviewerKey,
|
|
1320
|
+
log,
|
|
1321
|
+
onGap: countedGap,
|
|
1322
|
+
onRequest: (r) => memoryHealth?.recordRequest(r),
|
|
1323
|
+
});
|
|
1324
|
+
const memory = openVikingStore(memoryClient, {
|
|
1325
|
+
defaultCategory: cfg.openviking.default_category,
|
|
1326
|
+
cacheTtlMs: cfg.openviking.read_cache_ttl_ms,
|
|
1327
|
+
log,
|
|
1328
|
+
// The sessions' own recall (vendored openviking-pi patch 0007) excludes
|
|
1329
|
+
// retired entries by this list, published beside their ovcli.conf
|
|
1330
|
+
// whenever the store's sweep sees it change.
|
|
1331
|
+
onRetired: (uris) =>
|
|
1332
|
+
writeRetiredUris(sessions.ovRetiredPath(), uris, {
|
|
1333
|
+
log: (msg) => log(msg),
|
|
1334
|
+
}),
|
|
1335
|
+
// …starting from the list the last daemon published, so a first sweep
|
|
1336
|
+
// that cannot read everything (the server still warming up) adds to
|
|
1337
|
+
// the sessions' exclusions and never wipes them.
|
|
1338
|
+
initialRetired: readRetiredUrisFile(sessions.ovRetiredPath()),
|
|
1339
|
+
});
|
|
1340
|
+
feedback = new FeedbackEngine({
|
|
1341
|
+
store: d.store,
|
|
1342
|
+
timezone: cfg.ops.timezone,
|
|
1343
|
+
rebuildAgents,
|
|
1344
|
+
memory,
|
|
1345
|
+
// Memory switched off: signals are ledgered `skipped`, never owed.
|
|
1346
|
+
memoryEnabled: cfg.openviking.enabled,
|
|
1347
|
+
log,
|
|
1348
|
+
});
|
|
1349
|
+
// Same-day [feedback] comments ledgered in the boot gap reach the
|
|
1350
|
+
// validator's addendum now (the boot build above may predate them).
|
|
1351
|
+
if (feedbackLedgeredEarly) {
|
|
1352
|
+
try {
|
|
1353
|
+
rebuildAgents();
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
log("feedback: agents rebuild for boot-gap signals failed", err);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
// Memory switched off leaves the holders empty: the round seeds
|
|
1359
|
+
// nothing (above) and the EOD pinned-area watchdog is not built, so a
|
|
1360
|
+
// deliberate off never reads as an outage.
|
|
1361
|
+
if (cfg.openviking.enabled) {
|
|
1362
|
+
memoryStore = memory;
|
|
1363
|
+
memorySweep = memory;
|
|
1364
|
+
memoryAssembler = openVikingContextAssembler({
|
|
1365
|
+
client: memoryClient,
|
|
1366
|
+
store: memory,
|
|
1367
|
+
// Without quotas the server balances the block across every scope it
|
|
1368
|
+
// holds and a round gets about one memory entry beside two built-in
|
|
1369
|
+
// scope blurbs. The quotas name the corpus;
|
|
1370
|
+
// `context_tokens` stays the budget. See [memory] in config.toml.
|
|
1371
|
+
quotas: cfg.memory.context_quotas,
|
|
1372
|
+
searchTimeoutMs: cfg.openviking.search_timeout_ms,
|
|
1373
|
+
log,
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// The lesson shadow ledger and the round's selection. The
|
|
1378
|
+
// ledger (and so the operator's kill switch) always; the selector only
|
|
1379
|
+
// where memory is on — with it off there is nothing to select from.
|
|
1380
|
+
// (Built before the first agents build — the kill switch decides the
|
|
1381
|
+
// validator's lesson addendum; this keeps that same ledger.)
|
|
1382
|
+
lessonLedger = lessonLedger ?? new LessonLedger(d.store);
|
|
1383
|
+
if (cfg.openviking.enabled && cfg.memory.delivery !== "off") {
|
|
1384
|
+
// Its own client — no health observer, no counted gap — so a find
|
|
1385
|
+
// the round's budget aborts never reads as memory failing (the
|
|
1386
|
+
// health window would otherwise call a slow-but-working server
|
|
1387
|
+
// degraded and pause the case writer, the pump and the learner).
|
|
1388
|
+
const lessonEngine = openVikingLessonEngine(
|
|
1389
|
+
lessonFindClient({
|
|
1390
|
+
...ovConnection(cfg.openviking),
|
|
1391
|
+
apiKey: reviewerKey,
|
|
1392
|
+
log,
|
|
1393
|
+
}),
|
|
1394
|
+
memory,
|
|
1395
|
+
);
|
|
1396
|
+
lessonSelector = new RoundLessonSelector({
|
|
1397
|
+
ledger: lessonLedger,
|
|
1398
|
+
mode: () => cfg.memory.delivery,
|
|
1399
|
+
engine: () => lessonEngine,
|
|
1400
|
+
budgetMs: cfg.memory.delivery_budget_ms,
|
|
1401
|
+
// The budget follows the mode — `shadow` runs after the
|
|
1402
|
+
// handoff and blocks nothing, so it gets its own, looser one.
|
|
1403
|
+
shadowBudgetMs: cfg.memory.shadow_budget_ms,
|
|
1404
|
+
activity: roundActivity,
|
|
1405
|
+
repo: cfg.github.repo,
|
|
1406
|
+
log,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
log(
|
|
1410
|
+
`lessons: [memory] delivery = ${cfg.memory.delivery}` +
|
|
1411
|
+
(cfg.memory.delivery === "on"
|
|
1412
|
+
? lessonSelector
|
|
1413
|
+
? " — DELIVERING: each round awaits its selection beside the memory seed (≤ " +
|
|
1414
|
+
`${cfg.memory.delivery_budget_ms} ms); lessons reach the lens task files, the lessons files of the validator and the lenses a session adds, the main session's index and past-review cases, and the recheck/ping-pong repo facts; the validator carries the lesson-shape addendum` +
|
|
1415
|
+
(cfg.review.task_files
|
|
1416
|
+
? ""
|
|
1417
|
+
: " — but `[review] task_files = false`: the planned lenses have no task file, so THEY get no lessons")
|
|
1418
|
+
: " — but memory is off ([openviking] enabled = false): nothing to select, nothing delivered"
|
|
1419
|
+
: cfg.memory.delivery === "shadow"
|
|
1420
|
+
? ` — selection and the shadow ledger only (after each round's handoff, ≤ ${cfg.memory.shadow_budget_ms} ms); no prompt, task file or agent changes`
|
|
1421
|
+
: " — nothing selected, nothing recorded") +
|
|
1422
|
+
(lessonLedger.lessonsOff() ? "; the operator's kill switch is OFF" : ""),
|
|
1423
|
+
);
|
|
1424
|
+
|
|
1425
|
+
// Reviewer-memory health. "Not monitored" is reserved for memory
|
|
1426
|
+
// switched off — `[openviking] enabled = false`, which is what
|
|
1427
|
+
// declining memory in onboarding writes. Memory configured on whose
|
|
1428
|
+
// server cannot run (a binary missing after a rebuild, a venv path
|
|
1429
|
+
// drifted from `[openviking] bin`) is monitored: its probes go
|
|
1430
|
+
// unreachable and it reads `down` after `[memory] down_after_min`,
|
|
1431
|
+
// because that is an outage, not a choice, and must not be painted
|
|
1432
|
+
// like a deliberate off.
|
|
1433
|
+
if (!cfg.openviking.enabled) {
|
|
1434
|
+
memoryUnmonitored = parts.memoryOff ?? "[openviking] enabled = false";
|
|
1435
|
+
if (parts.memoryOff)
|
|
1436
|
+
log(
|
|
1437
|
+
`${parts.memoryOff}: the memory engine runs as the Docker sidecar outside the release image, and no Docker answers here — memory stays off for this boot`,
|
|
1438
|
+
);
|
|
1439
|
+
} else {
|
|
1440
|
+
// A rotated daemon key reaches the sessions too: their ovcli.conf is
|
|
1441
|
+
// rewritten the moment the key changes, not at the next session birth.
|
|
1442
|
+
const watchKey = keyChangeWatcher(() => {
|
|
1443
|
+
const res = sessions.provisionOvCredentials();
|
|
1444
|
+
log(
|
|
1445
|
+
`openviking: the reviewer key changed — sessions' ovcli.conf ${res.written ? "rewritten" : "NOT rewritten"}${res.keyed ? "" : ` (${res.reason || "no key"})`}`,
|
|
1446
|
+
);
|
|
1447
|
+
d.store.logEvent("openviking_key_changed", null, {
|
|
1448
|
+
ovcli_written: res.written,
|
|
1449
|
+
keyed: res.keyed,
|
|
1450
|
+
reason: res.reason,
|
|
1451
|
+
});
|
|
1452
|
+
// A failed rewrite stays owed: the next probe tick tries again.
|
|
1453
|
+
return res.written;
|
|
1454
|
+
});
|
|
1455
|
+
const probeBase = {
|
|
1456
|
+
...ovConnection(cfg.openviking),
|
|
1457
|
+
};
|
|
1458
|
+
memoryHealth = new MemoryHealthMonitor({
|
|
1459
|
+
probes: {
|
|
1460
|
+
daemon: credentialProbe({
|
|
1461
|
+
...probeBase,
|
|
1462
|
+
key: () => ({ state: "key", key: reviewerKey() ?? "" }),
|
|
1463
|
+
}),
|
|
1464
|
+
sessions: credentialProbe({
|
|
1465
|
+
...probeBase,
|
|
1466
|
+
key: () => readOvcliKey(sessions.ovcliConfPath()),
|
|
1467
|
+
}),
|
|
1468
|
+
},
|
|
1469
|
+
beforeProbe: () => {
|
|
1470
|
+
watchKey(reviewerKey());
|
|
1471
|
+
},
|
|
1472
|
+
wake: (kind, detail, message) => d.store.logAnomaly(kind, null, null, detail, message),
|
|
1473
|
+
draining: () => d.store.drainState().draining,
|
|
1474
|
+
// Whether memory serves search — the server's /ready (it runs an
|
|
1475
|
+
// embedding probe) and the local embedder. The fs/ls probe needs no
|
|
1476
|
+
// embedding, so without this a dead embedder would read healthy
|
|
1477
|
+
// while every search failed. Cached, synchronous reads.
|
|
1478
|
+
serving: () =>
|
|
1479
|
+
memoryServing(
|
|
1480
|
+
cfg.openviking.embedding_provider,
|
|
1481
|
+
openviking?.status(),
|
|
1482
|
+
localEmbedder?.status(),
|
|
1483
|
+
),
|
|
1484
|
+
incidents: {
|
|
1485
|
+
load: () => d.store.memoryIncident(),
|
|
1486
|
+
save: (incident) => d.store.recordMemoryIncident(incident),
|
|
1487
|
+
},
|
|
1488
|
+
where: {
|
|
1489
|
+
daemon: `secrets/${OV_REVIEWER_KEY_SECRET}`,
|
|
1490
|
+
sessions: "ovcli.conf in the pi HOME",
|
|
1491
|
+
},
|
|
1492
|
+
log,
|
|
1493
|
+
// Floors: a mistyped 0 must not become a tight probe loop or a
|
|
1494
|
+
// wake on every probe.
|
|
1495
|
+
intervalMs: Math.max(10, cfg.memory.probe_interval_s) * 1000,
|
|
1496
|
+
downAfterMs: Math.max(1, cfg.memory.down_after_min) * 60_000,
|
|
1497
|
+
bootGraceMs: Math.max(0, cfg.memory.boot_grace_min) * 60_000,
|
|
1498
|
+
rewakeMs: Math.max(1, cfg.memory.rewake_h) * 3_600_000,
|
|
1499
|
+
});
|
|
1500
|
+
// This boot's provisioning report (the entrypoint ran the script
|
|
1501
|
+
// before us): into the block, and one wake per report when it says
|
|
1502
|
+
// anything is wrong — keyed by the report's fingerprint, so a daemon
|
|
1503
|
+
// restart inside the same container does not repeat it.
|
|
1504
|
+
const prov = readProvisionReport(provisionReportPath(root));
|
|
1505
|
+
memoryHealth.setProvision(prov);
|
|
1506
|
+
raiseProvisionWake(d.store, prov);
|
|
1507
|
+
memoryHealth.start();
|
|
1508
|
+
|
|
1509
|
+
// The backfill pump. It writes owed [feedback] signals through the
|
|
1510
|
+
// same `note` op on the same store — a retry on the one route, never
|
|
1511
|
+
// a second channel — only while health reads healthy, and wakes
|
|
1512
|
+
// `memory_write_backlog` past the threshold. Legacy rows (older than the
|
|
1513
|
+
// store's legacy cutoff, written under random names) are checked against a complete sweep
|
|
1514
|
+
// first (an entry whose write timed out may have landed after all).
|
|
1515
|
+
const health = memoryHealth;
|
|
1516
|
+
feedbackPump = new FeedbackMemoryPump({
|
|
1517
|
+
store: d.store,
|
|
1518
|
+
memory,
|
|
1519
|
+
healthy: () => health.currentState() === "healthy",
|
|
1520
|
+
healthState: () => health.currentState(),
|
|
1521
|
+
// Retired entries count as "landed": a legacy signal whose entry was
|
|
1522
|
+
// retired since — by the operator, or by the
|
|
1523
|
+
// relayout — was written once and must not be written again into
|
|
1524
|
+
// events/. (A relayout MOVE leaves the copy live in events/, so it
|
|
1525
|
+
// is found among the live entries.)
|
|
1526
|
+
readEntries: async () => {
|
|
1527
|
+
const sweep = await memory.sweep();
|
|
1528
|
+
return {
|
|
1529
|
+
entries: [...sweep.entries, ...sweep.retired],
|
|
1530
|
+
complete: sweep.unread.length === 0,
|
|
1531
|
+
};
|
|
1532
|
+
},
|
|
1533
|
+
wake: (kind, detail, message) => d.store.logAnomaly(kind, null, null, detail, message),
|
|
1534
|
+
log,
|
|
1535
|
+
intervalMs: Math.max(10, cfg.memory.backfill_interval_s) * 1000,
|
|
1536
|
+
backlogAfterMs: Math.max(1, cfg.memory.write_backlog_after_min) * 60_000,
|
|
1537
|
+
rewakeMs: Math.max(1, cfg.memory.rewake_h) * 3_600_000,
|
|
1538
|
+
});
|
|
1539
|
+
feedbackPump.start();
|
|
1540
|
+
|
|
1541
|
+
// Finding cases: the learner's context. Rendered from state.db
|
|
1542
|
+
// (round_findings, the outcome capture's tables, the human replies,
|
|
1543
|
+
// the feedback ledger) and written through the same store's
|
|
1544
|
+
// `putDocuments` — one batch-write under `finding-cases/` a tick, every case
|
|
1545
|
+
// under its own precondition — only while memory reads healthy. Its
|
|
1546
|
+
// backfill reads a PR's inline comments through the adapter (a read:
|
|
1547
|
+
// never the gate) and runs the outcome capture for PRs nothing
|
|
1548
|
+
// captured. Never in a round's search scope; only the daemon's lesson
|
|
1549
|
+
// selection reads cases.
|
|
1550
|
+
caseWriter = new CaseWriter({
|
|
1551
|
+
store: d.store,
|
|
1552
|
+
outcomes: d.outcomes,
|
|
1553
|
+
memory,
|
|
1554
|
+
repo: cfg.github.repo,
|
|
1555
|
+
botLogin: cfg.github.bot_login,
|
|
1556
|
+
healthy: () => health.currentState() === "healthy",
|
|
1557
|
+
// No case batch starts while a review round reads memory (its seed,
|
|
1558
|
+
// its lesson selection); every batch sent reports its embedding
|
|
1559
|
+
// tail there, which a seed that starts inside it waits out (within
|
|
1560
|
+
// its own budget).
|
|
1561
|
+
rounds: roundActivity,
|
|
1562
|
+
embedTailMsPerCase: cfg.memory.case_embed_tail_ms,
|
|
1563
|
+
// While a review round runs or is queued to dispatch,
|
|
1564
|
+
// the backfill, the late activity and history cases wait.
|
|
1565
|
+
reviews: () => reviewsInFlight(d.store),
|
|
1566
|
+
read: adapter,
|
|
1567
|
+
log,
|
|
1568
|
+
intervalMs: Math.max(10, cfg.memory.cases_interval_s) * 1000,
|
|
1569
|
+
backfillDays: Math.max(0, cfg.memory.cases_backfill_days),
|
|
1570
|
+
});
|
|
1571
|
+
caseWriter.start();
|
|
1572
|
+
|
|
1573
|
+
// The learner: state.db and the base clone's git in,
|
|
1574
|
+
// candidates and the EOD packet out; it reads the lessons in force
|
|
1575
|
+
// through the same store (only while memory reads healthy) and never
|
|
1576
|
+
// writes memory — the curator does, with `viking_write`'s typed
|
|
1577
|
+
// `lesson` (vendored patch 0010).
|
|
1578
|
+
learner = new Learner({
|
|
1579
|
+
store: d.store,
|
|
1580
|
+
root,
|
|
1581
|
+
eodTime: cfg.eod.time,
|
|
1582
|
+
timeZone: cfg.eod.timezone,
|
|
1583
|
+
botLogin: cfg.github.bot_login,
|
|
1584
|
+
baseBranch: cfg.github.base_branch,
|
|
1585
|
+
config: learnerKnobsOf(cfg.memory),
|
|
1586
|
+
git: sessions.worktrees,
|
|
1587
|
+
lessons: memory,
|
|
1588
|
+
healthy: () => health.currentState() === "healthy",
|
|
1589
|
+
wake: (kind, detail, message) => d.store.logAnomaly(kind, null, null, detail, message),
|
|
1590
|
+
log,
|
|
1591
|
+
});
|
|
1592
|
+
learner.start();
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// The archiver. Its eligibility asks which workers run (leases the
|
|
1596
|
+
// process table confirms; unknown holds, never "none") and the session
|
|
1597
|
+
// manager which births are in flight; its holds read the daemon's own
|
|
1598
|
+
// load watch.
|
|
1599
|
+
archiver = new Archiver({
|
|
1600
|
+
store: d.store,
|
|
1601
|
+
cfg: cfg.archive,
|
|
1602
|
+
root,
|
|
1603
|
+
sessionsRoot: sessions.sessionsRoot(),
|
|
1604
|
+
firstHealthyReconcileAt: () => d.firstHealthyReconcileAt(),
|
|
1605
|
+
load: () => d.load.status(),
|
|
1606
|
+
starvedRatio: eventLoopPolicy(cfg.daemon).starvedRatio,
|
|
1607
|
+
liveSessions: async () => workerLive.liveSessions(),
|
|
1608
|
+
birthInFlight: (pr) => workerWorld.host.birthInFlight(pr),
|
|
1609
|
+
// The stores that belong to no PR. The overseer is gone, so its
|
|
1610
|
+
// transcripts have no current file to protect and no birth record
|
|
1611
|
+
// (unknown = nothing moves); pi's global store lies under the
|
|
1612
|
+
// container user's home.
|
|
1613
|
+
piGlobalDir: piGlobalSessionsDir(),
|
|
1614
|
+
log,
|
|
1615
|
+
});
|
|
1616
|
+
archiver.start();
|
|
1617
|
+
if (cfg.login_refresh.enabled) {
|
|
1618
|
+
loginRefresh = new LoginRefreshSupervisor({
|
|
1619
|
+
repoRoot: root,
|
|
1620
|
+
agentDir,
|
|
1621
|
+
marginMin: cfg.login_refresh.margin_min,
|
|
1622
|
+
log,
|
|
1623
|
+
// One anomaly per failure episode, through the one durable funnel.
|
|
1624
|
+
raiseAnomaly: (detail) => d.store.logAnomaly("login_refresh_failed", null, null, detail),
|
|
1625
|
+
});
|
|
1626
|
+
loginRefresh.start();
|
|
1627
|
+
}
|
|
1628
|
+
// A reopened PR's archive comes back before its session is born:
|
|
1629
|
+
// both birth paths await this, bounded, and never refuse the birth.
|
|
1630
|
+
const archiveRef = archiver;
|
|
1631
|
+
workerWorld.host.setBeforeBirth((pr) => archiveRef.beforeBirth(pr));
|
|
1632
|
+
|
|
1633
|
+
// The worker world comes up before anything asks which workers run: the socket,
|
|
1634
|
+
// then whatever a previous daemon left reaped and requeued (a worker's round is
|
|
1635
|
+
// resumed from its JSONL by a fresh one), then the timers. Nothing is adopted.
|
|
1636
|
+
await workerWorld.start();
|
|
1637
|
+
|
|
1638
|
+
// Sweep stale worktrees. No tmux session is adopted: a PR with no worker
|
|
1639
|
+
// is idle with its JSONL, and one a worker still runs for keeps its tree.
|
|
1640
|
+
const active = d.store
|
|
1641
|
+
.allPrs()
|
|
1642
|
+
.filter((p) => p.state !== "closed")
|
|
1643
|
+
.map((p) => p.number);
|
|
1644
|
+
const inFlight = inFlightPrs(d.store, workerLive);
|
|
1645
|
+
const pre = await sessions.preflight([...active, ...inFlight]);
|
|
1646
|
+
log(
|
|
1647
|
+
`preflight: workers=${inFlight.join(",") || "none"} swept-worktrees=${pre.sweptWorktrees.join(",") || "none"}`,
|
|
1648
|
+
);
|
|
1649
|
+
|
|
1650
|
+
// Rounds still marked running whose worker did not survive the restart
|
|
1651
|
+
// are phantoms: cancel them, requeue the ones whose work was truly lost.
|
|
1652
|
+
// A round whose finished review the outbox still owes GitHub is not one,
|
|
1653
|
+
// nor is one whose round command still waits for the next worker.
|
|
1654
|
+
const orphans = reconcileOrphanedRounds(d.store, inFlight, {
|
|
1655
|
+
log,
|
|
1656
|
+
postingOwed: orphanPostingOwed(
|
|
1657
|
+
join(root, "state", "posting-receipts"),
|
|
1658
|
+
cfg.dry_run,
|
|
1659
|
+
cfg.github.repo,
|
|
1660
|
+
),
|
|
1661
|
+
});
|
|
1662
|
+
if (orphans.cancelled.length) {
|
|
1663
|
+
log(
|
|
1664
|
+
`startup: cancelled orphaned rounds on #${orphans.cancelled.join(", #")}; requeued ${orphans.requeued.length}`,
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// Overseer command files → validated execution (whitelist + caps).
|
|
1669
|
+
const leverDeps: LeverDeps = {
|
|
1670
|
+
store: d.store,
|
|
1671
|
+
levers: cfg.levers,
|
|
1672
|
+
timezone: cfg.ops.timezone,
|
|
1673
|
+
};
|
|
1674
|
+
const commandDeps: CommandDeps = {
|
|
1675
|
+
async rerunRound(pr) {
|
|
1676
|
+
return recoverPrForOperator(
|
|
1677
|
+
{ store: d.store, pump: () => d.pump(), workerRuns: (n) => workerLive.runs(n) },
|
|
1678
|
+
pr,
|
|
1679
|
+
"rerun-round",
|
|
1680
|
+
);
|
|
1681
|
+
},
|
|
1682
|
+
async restartSession(target, deliveryKey) {
|
|
1683
|
+
return recoverPrForOperator(
|
|
1684
|
+
{
|
|
1685
|
+
store: d.store,
|
|
1686
|
+
deliveryKey,
|
|
1687
|
+
pump: () => d.pump(),
|
|
1688
|
+
// Returns only once the worker is reaped; one that outlives the kill
|
|
1689
|
+
// throws, which leaves ownership unchanged.
|
|
1690
|
+
killSession: (pr) => workerWorld.host.killSession(pr),
|
|
1691
|
+
workerRuns: (pr) => workerLive.runs(pr),
|
|
1692
|
+
// A fresh worker resumes the newest JSONL: rotate it away first.
|
|
1693
|
+
rotateSession: (pr) => workerWorld.host.rotateSession(pr),
|
|
1694
|
+
},
|
|
1695
|
+
target,
|
|
1696
|
+
"restart-session",
|
|
1697
|
+
);
|
|
1698
|
+
},
|
|
1699
|
+
resolveTerminalFailure(payload) {
|
|
1700
|
+
return resolveTerminalFailure(
|
|
1701
|
+
{
|
|
1702
|
+
store: d.store,
|
|
1703
|
+
getPrDisposition: (pr) => adapter.getPrDisposition(pr),
|
|
1704
|
+
},
|
|
1705
|
+
payload,
|
|
1706
|
+
);
|
|
1707
|
+
},
|
|
1708
|
+
// The operator's round controls on a worker-hosted PR: steer, follow-up,
|
|
1709
|
+
// abort, takeover, debug-round, release and the idle-PR message.
|
|
1710
|
+
operatorControl: workerWorld.operatorControl,
|
|
1711
|
+
// The two bounded levers: who is asking comes from the
|
|
1712
|
+
// watcher's channel, and a session's hold is capped, budgeted and
|
|
1713
|
+
// expiring in src/levers.ts — an operator's is not.
|
|
1714
|
+
pausePr(pr, reason, issuer, forMin, deliveryKey) {
|
|
1715
|
+
return pauseLever({ ...leverDeps, deliveryKey }, pr, reason, issuer, forMin);
|
|
1716
|
+
},
|
|
1717
|
+
resumePr(pr, issuer) {
|
|
1718
|
+
return resumeLever(leverDeps, pr, issuer, () => d.pump());
|
|
1719
|
+
},
|
|
1720
|
+
flagOwner(message) {
|
|
1721
|
+
const flags = join(root, "state", "flags");
|
|
1722
|
+
mkdirSync(flags, { recursive: true });
|
|
1723
|
+
// owner.log; older entries live on in the legacy owner-legacy.log,
|
|
1724
|
+
// which nothing writes.
|
|
1725
|
+
appendFileSync(join(flags, "owner.log"), `${new Date().toISOString()} ${message}\n`);
|
|
1726
|
+
d.store.logEvent("flag_owner", null, { message });
|
|
1727
|
+
return { ok: true };
|
|
1728
|
+
},
|
|
1729
|
+
fileImprovementPr(payload, deliveryKey) {
|
|
1730
|
+
return fileImprovementPr(payload, {
|
|
1731
|
+
deliveryKey,
|
|
1732
|
+
cfg,
|
|
1733
|
+
adapter,
|
|
1734
|
+
gate,
|
|
1735
|
+
store: d.store,
|
|
1736
|
+
run: gitRun,
|
|
1737
|
+
// The reviewed repo's base clone — the one tree with `origin`
|
|
1738
|
+
// pointing at the reviewed repo and the session's branch in it.
|
|
1739
|
+
ensureRepoDir: () => sessions.worktrees.ensureBaseClone(),
|
|
1740
|
+
});
|
|
1741
|
+
},
|
|
1742
|
+
fileIssue(payload, deliveryKey) {
|
|
1743
|
+
return fileIssue(payload, { cfg, adapter, gate, store: d.store, deliveryKey });
|
|
1744
|
+
},
|
|
1745
|
+
retryThreadDecision(commentId) {
|
|
1746
|
+
return d.retryThreadDecision(commentId);
|
|
1747
|
+
},
|
|
1748
|
+
// The operator's lesson kill switch — reached only for a command the
|
|
1749
|
+
// sealed commands/operator/ channel delivered (commands.ts refuses
|
|
1750
|
+
// every other issuer, twice). State in state.db.
|
|
1751
|
+
lessonSwitch(change, file) {
|
|
1752
|
+
if (!lessonLedger)
|
|
1753
|
+
return { ok: false, reason: "the lesson ledger is not built on this daemon yet" };
|
|
1754
|
+
const r = lessonLedger.apply(change, file);
|
|
1755
|
+
lessonStatusCache = null;
|
|
1756
|
+
// The validator's lesson addendum is composed only
|
|
1757
|
+
// while the kill switch is on — so flipping it rebuilds the agents,
|
|
1758
|
+
// the way a [feedback] comment does for its addendum. Suppressing
|
|
1759
|
+
// one lesson needs no rebuild: the selector reads it every round.
|
|
1760
|
+
// A rebuild that throws leaves the build stale: the 5-minute agents
|
|
1761
|
+
// tick retries it, and the answer says so rather than reading as
|
|
1762
|
+
// a validator already rebuilt.
|
|
1763
|
+
let rebuildFailed = false;
|
|
1764
|
+
if (r.changed && (change.action === "off" || change.action === "on")) {
|
|
1765
|
+
try {
|
|
1766
|
+
rebuildAgents();
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
rebuildFailed = true;
|
|
1769
|
+
log(
|
|
1770
|
+
"lessons: agents rebuild after the kill switch flipped failed — the agents tick retries it",
|
|
1771
|
+
err,
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return {
|
|
1776
|
+
ok: true,
|
|
1777
|
+
changed: r.changed,
|
|
1778
|
+
...(rebuildFailed
|
|
1779
|
+
? { agents_rebuild: "failed — the 5-minute agents tick retries it" }
|
|
1780
|
+
: {}),
|
|
1781
|
+
lessons_off: r.lessons_off,
|
|
1782
|
+
suppressed: r.suppressed.length,
|
|
1783
|
+
...("uri" in change ? { uri: change.uri } : {}),
|
|
1784
|
+
};
|
|
1785
|
+
},
|
|
1786
|
+
// The operator's archive verbs — reached only for a
|
|
1787
|
+
// command the sealed commands/operator/ channel delivered (commands.ts
|
|
1788
|
+
// refuses every other issuer, twice). Each runs under the archiver's
|
|
1789
|
+
// strict mutex and answers within OPERATOR_WAIT_MS; `archiver` is read
|
|
1790
|
+
// at call time, never captured at wiring.
|
|
1791
|
+
archiveRun() {
|
|
1792
|
+
if (!archiver)
|
|
1793
|
+
return { ok: false, reason: "the archiver is not built on this daemon yet" };
|
|
1794
|
+
return archiver.operatorRun();
|
|
1795
|
+
},
|
|
1796
|
+
archiveRestore(pr) {
|
|
1797
|
+
if (!archiver)
|
|
1798
|
+
return { ok: false, reason: "the archiver is not built on this daemon yet" };
|
|
1799
|
+
return archiver.operatorRestore(pr);
|
|
1800
|
+
},
|
|
1801
|
+
// The EOD curator's receipt for the day's outcomes packet.
|
|
1802
|
+
// `learner` is read at call time, never captured at wiring.
|
|
1803
|
+
curationReceipt(receipt) {
|
|
1804
|
+
return routeCurationReceipt(learner, receipt);
|
|
1805
|
+
},
|
|
1806
|
+
// Deploy-window drain. On = the webhook answers 200 and drops, the
|
|
1807
|
+
// pump starts nothing, recheck/ping-pong/EOD hold — in-flight rounds
|
|
1808
|
+
// finish naturally. Off is the manual lift; the automatic one is the
|
|
1809
|
+
// next boot's first healthy reconcile pass (daemon.ts) — except for
|
|
1810
|
+
// the operator's HOLD, which only an explicit off lifts. Never touches
|
|
1811
|
+
// GitHub, never goes near the gate.
|
|
1812
|
+
drain(on, issuer, forMin, deliveryKey, hold) {
|
|
1813
|
+
return drainLever(
|
|
1814
|
+
{ ...leverDeps, deliveryKey },
|
|
1815
|
+
on,
|
|
1816
|
+
issuer,
|
|
1817
|
+
forMin,
|
|
1818
|
+
async () => {
|
|
1819
|
+
d.wakeThreadDecisions();
|
|
1820
|
+
await d.pump();
|
|
1821
|
+
},
|
|
1822
|
+
hold === true,
|
|
1823
|
+
);
|
|
1824
|
+
},
|
|
1825
|
+
};
|
|
1826
|
+
// The Slack concierge. Off by default; when on it owns its own
|
|
1827
|
+
// tier-checked command dir, the socket, and every unprompted post. It is
|
|
1828
|
+
// started before the legacy watcher because that watcher borrows two
|
|
1829
|
+
// things from it: the reply handler and the origin lookup.
|
|
1830
|
+
slack =
|
|
1831
|
+
(await startSlack({
|
|
1832
|
+
cfg,
|
|
1833
|
+
root,
|
|
1834
|
+
store: d.store,
|
|
1835
|
+
commandDeps,
|
|
1836
|
+
writeInbox,
|
|
1837
|
+
origins,
|
|
1838
|
+
log,
|
|
1839
|
+
version: packageVersion(root),
|
|
1840
|
+
...(previousStatus !== undefined ? { previousStatus } : {}),
|
|
1841
|
+
})) ?? null;
|
|
1842
|
+
if (slack) log("slack: concierge live (socket mode; commands/slack tier-checked)");
|
|
1843
|
+
|
|
1844
|
+
commands = buildLocalCommandWatcher({
|
|
1845
|
+
root,
|
|
1846
|
+
deps: commandDeps,
|
|
1847
|
+
store: d.store,
|
|
1848
|
+
slack,
|
|
1849
|
+
log: (l) => log(l),
|
|
1850
|
+
});
|
|
1851
|
+
await commands.start();
|
|
1852
|
+
operatorCommands = buildOperatorCommandWatcher({
|
|
1853
|
+
root,
|
|
1854
|
+
deps: commandDeps,
|
|
1855
|
+
store: d.store,
|
|
1856
|
+
slack,
|
|
1857
|
+
log: (l) => log(l),
|
|
1858
|
+
});
|
|
1859
|
+
await operatorCommands.start();
|
|
1860
|
+
|
|
1861
|
+
// Webhook-URL self-heal: only under the tunnel-file ingress shape. The
|
|
1862
|
+
// gated PATCH rides the app-self-config class, so it executes even in
|
|
1863
|
+
// dry-run (AGENTS.md invariant 6) — loudly.
|
|
1864
|
+
if (cfg.ingress.shape === "tunnel-file") {
|
|
1865
|
+
webhookHeal = new WebhookHealer({
|
|
1866
|
+
hostnameFile: ingressHostnameFile(cfg, root),
|
|
1867
|
+
readConfig: () => adapter.getAppWebhookConfig(),
|
|
1868
|
+
updateUrl: (newUrl, oldUrl) => adapter.updateOwnWebhookUrl(newUrl, oldUrl),
|
|
1869
|
+
logEvent: (kind, payload) => d.store.logEvent(kind, null, payload),
|
|
1870
|
+
anomaly: (detail) => d.store.logAnomaly("webhook_heal", null, null, detail, detail),
|
|
1871
|
+
log,
|
|
1872
|
+
debounceMs: cfg.ingress.debounce_ms,
|
|
1873
|
+
sweepIntervalMs: cfg.ingress.sweep_interval_s * 1000,
|
|
1874
|
+
});
|
|
1875
|
+
webhookHeal.start();
|
|
1876
|
+
log(`webhookheal: watching ${ingressHostnameFile(cfg, root)} (ingress shape tunnel-file)`);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// Daemon-clock EOD ([eod]-timezone wall time; pi schedulers deliberately unused).
|
|
1880
|
+
// Curation must never touch the owner's pinned entries. The watchdog
|
|
1881
|
+
// fingerprints the pinned area around the sweep and raises an anomaly on
|
|
1882
|
+
// any change. It wraps the inject rather than hooking inside
|
|
1883
|
+
// the timer, so a broken watchdog can never skip the sweep.
|
|
1884
|
+
const sweepSource = memorySweep;
|
|
1885
|
+
pinWatch =
|
|
1886
|
+
cfg.eod.pinned_watchdog && sweepSource
|
|
1887
|
+
? startEodPinWatch({
|
|
1888
|
+
readPinned: readPinnedFromSweep(sweepSource),
|
|
1889
|
+
logAnomaly: (kind, pr, reason, detail, injectMessage) =>
|
|
1890
|
+
d.store.logAnomaly(kind, pr, reason, detail, injectMessage),
|
|
1891
|
+
timeZone: cfg.eod.timezone,
|
|
1892
|
+
digestReady: (date) => digestWritten(date, join(root, "digest")),
|
|
1893
|
+
windowMin: cfg.eod.pinned_watchdog_window_min,
|
|
1894
|
+
log,
|
|
1895
|
+
})
|
|
1896
|
+
: null;
|
|
1897
|
+
// The sweep is one inbox item per day: the companion's TUI pulls it and
|
|
1898
|
+
// runs it, so with the companion off it runs late, never not at all.
|
|
1899
|
+
const injectEod = inbox.eodWriter();
|
|
1900
|
+
eod = startEodTimer({
|
|
1901
|
+
timeHHMM: cfg.eod.time,
|
|
1902
|
+
timeZone: cfg.eod.timezone,
|
|
1903
|
+
readPrompt: promptReader(join(root, "prompts", "eod-sweep.md")),
|
|
1904
|
+
identity,
|
|
1905
|
+
// The day's cancellation summary rides the sweep prompt so the
|
|
1906
|
+
// digest actually mentions cancellations (grouped, loud first, "none"
|
|
1907
|
+
// explicit) — and the learner's outcomes packet, built here, at the
|
|
1908
|
+
// fire (a mine first when the day's has not run) — whose pointer the
|
|
1909
|
+
// digest carries and whose curation instructions render after the
|
|
1910
|
+
// seed. A learner that throws costs the packet, never the sweep.
|
|
1911
|
+
dayContext: async (date) => {
|
|
1912
|
+
const cancelled = eodDigestContext(d.store, date, cfg.eod.timezone);
|
|
1913
|
+
if (!learner) return cancelled;
|
|
1914
|
+
try {
|
|
1915
|
+
const prep = await learner.prepareEod(date);
|
|
1916
|
+
return { lines: `${cancelled}\n${prep.lines}`, curation: prep.curation };
|
|
1917
|
+
} catch (err) {
|
|
1918
|
+
log(`learner: the outcomes packet for ${date} failed`, err);
|
|
1919
|
+
return cancelled;
|
|
1920
|
+
}
|
|
1921
|
+
},
|
|
1922
|
+
onContextMissing: (date, why) =>
|
|
1923
|
+
log(`EOD sweep for ${date}: its day context is missing — it goes out without it`, why),
|
|
1924
|
+
// The curator learns the memory layout here — the seed is frozen and
|
|
1925
|
+
// names only some areas.
|
|
1926
|
+
...(cfg.openviking.enabled ? { memoryLayout: MEMORY_LAYOUT_NOTE } : {}),
|
|
1927
|
+
// The seed starts §1 from "Outcomes to curate" and closes it with the
|
|
1928
|
+
// receipt, so a sweep the packet did not reach says why in that
|
|
1929
|
+
// section instead of dropping it: no learner on this daemon, or a
|
|
1930
|
+
// packet that failed or missed the wait.
|
|
1931
|
+
noCuration: (date) => renderNoPacketSection(date, learner ? "unavailable" : "off"),
|
|
1932
|
+
// Deploy-window drain: the sweep does not fire into a board being
|
|
1933
|
+
// drained for a deploy — it is deferred: the day is persisted in
|
|
1934
|
+
// state.db and fires once the drain lifts, across a restart too.
|
|
1935
|
+
shouldHold: () => d.store.drainState().draining,
|
|
1936
|
+
// One sweep at a time: while the pinned-area watch window of the
|
|
1937
|
+
// last sweep is open, the next day (a deferred one above all — they
|
|
1938
|
+
// queue back to back after a drain) waits behind it, so each window
|
|
1939
|
+
// judges its own day's work under its own date.
|
|
1940
|
+
sweepInFlight: () => pinWatch?.isOpen() === true,
|
|
1941
|
+
deferred: {
|
|
1942
|
+
load: () => d.store.eodDeferredDates(),
|
|
1943
|
+
save: (dates) => d.store.recordEodDeferred(dates),
|
|
1944
|
+
},
|
|
1945
|
+
onDeferredStoreFailed: (op, dates, err) =>
|
|
1946
|
+
log(
|
|
1947
|
+
`EOD owed-day ${op} failed${dates ? ` (owed: ${dates.join(", ") || "none"})` : ""} — scheduling continues in memory`,
|
|
1948
|
+
err,
|
|
1949
|
+
),
|
|
1950
|
+
onHeld: (date, why) => {
|
|
1951
|
+
d.store.logEvent(
|
|
1952
|
+
why === "draining" ? "eod_deferred_draining" : "eod_deferred_behind_sweep",
|
|
1953
|
+
null,
|
|
1954
|
+
{ date },
|
|
1955
|
+
);
|
|
1956
|
+
log(
|
|
1957
|
+
why === "draining"
|
|
1958
|
+
? `EOD sweep for ${date} deferred — draining; it fires once the drain lifts`
|
|
1959
|
+
: `EOD sweep for ${date} deferred — an earlier sweep is still in flight; it fires after`,
|
|
1960
|
+
);
|
|
1961
|
+
},
|
|
1962
|
+
onDeferredFire: (date) => {
|
|
1963
|
+
d.store.logEvent("eod_deferred_fired", null, { date });
|
|
1964
|
+
// An owed day — deferred behind a drain or an earlier sweep, or one
|
|
1965
|
+
// whose fire a restart interrupted — fires only once neither holds;
|
|
1966
|
+
// the timer does not say which owed it.
|
|
1967
|
+
log(
|
|
1968
|
+
`EOD sweep for ${date} firing — owed from a deferral or an interrupted fire; no drain or sweep holds it now`,
|
|
1969
|
+
);
|
|
1970
|
+
},
|
|
1971
|
+
writeInbox: pinWatch ? pinWatch.wrapInject(injectEod) : injectEod,
|
|
1972
|
+
onFire: (date) => {
|
|
1973
|
+
log(`EOD sweep written to the companion inbox for ${date}`);
|
|
1974
|
+
// The receipt for the day's packet is owed from now.
|
|
1975
|
+
learner?.eodFired(date);
|
|
1976
|
+
},
|
|
1977
|
+
onError: (err) => log("EOD inbox write failed (retried with backoff)", err),
|
|
1978
|
+
// Three retries failed too: the day's curation did not happen.
|
|
1979
|
+
onInjectFailed: (date, err, attempts) => {
|
|
1980
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
1981
|
+
d.store.logAnomaly(
|
|
1982
|
+
"eod_inject_failed",
|
|
1983
|
+
null,
|
|
1984
|
+
null,
|
|
1985
|
+
`EOD sweep for ${date} not written to the inbox after ${attempts} attempts: ${why.slice(0, 200)}`,
|
|
1986
|
+
[
|
|
1987
|
+
`EOD INBOX WRITE FAILED — the EOD sweep for ${date} could not be written to the companion inbox (${attempts} attempts, backed off).`,
|
|
1988
|
+
`Last error: ${why.slice(0, 300)}`,
|
|
1989
|
+
"That day's curation did not run: no digest, no lessons. Check the companion inbox directory and the state volume's free space, then re-run the sweep by hand if the day matters.",
|
|
1990
|
+
].join("\n"),
|
|
1991
|
+
);
|
|
1992
|
+
},
|
|
1993
|
+
});
|
|
1994
|
+
log(`EOD timer armed for ${eod.nextFire().toISOString()}`);
|
|
1995
|
+
|
|
1996
|
+
// Daily heartbeat inbox item — same wall-clock machinery,
|
|
1997
|
+
// morning slot. At most one boot catch-up if the daemon was down at the
|
|
1998
|
+
// hour; the fired-date lives in the store, so restarts never double-fire.
|
|
1999
|
+
heartbeat = startHeartbeat({
|
|
2000
|
+
timeHHMM: cfg.eod.heartbeat_time,
|
|
2001
|
+
timeZone: cfg.eod.timezone,
|
|
2002
|
+
lastFired: () => d.store.lastHeartbeatDate(),
|
|
2003
|
+
recordFired: (date) => d.store.recordHeartbeat(date),
|
|
2004
|
+
writeInbox: inbox.heartbeatWriter(),
|
|
2005
|
+
log,
|
|
2006
|
+
onError: (err) => log("heartbeat injection failed", err),
|
|
2007
|
+
});
|
|
2008
|
+
log(`heartbeat armed for ${heartbeat.nextFire().toISOString()}`);
|
|
2009
|
+
|
|
2010
|
+
// Expire a stale same-day feedback addendum promptly on the
|
|
2011
|
+
// ops-day change — a 5-minute string compare, so yesterday's items
|
|
2012
|
+
// bite at most minutes into a new day, not an hour — and retries a
|
|
2013
|
+
// rebuild that threw (a stale build) on the same day.
|
|
2014
|
+
const dayCheck = setInterval(() => {
|
|
2015
|
+
try {
|
|
2016
|
+
agentsDay.tick();
|
|
2017
|
+
} catch (err) {
|
|
2018
|
+
log("feedback: day-change agents rebuild failed", err);
|
|
2019
|
+
}
|
|
2020
|
+
}, 300_000);
|
|
2021
|
+
dayCheck.unref?.();
|
|
2022
|
+
intervals.push(dayCheck);
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
async onStop(reason = "shutdown") {
|
|
2026
|
+
memoryHealth?.stop();
|
|
2027
|
+
feedbackPump?.stop();
|
|
2028
|
+
caseWriter?.stop();
|
|
2029
|
+
learner?.stop();
|
|
2030
|
+
archiver?.stop();
|
|
2031
|
+
// Workers end and their commands requeue; the socket closes.
|
|
2032
|
+
try {
|
|
2033
|
+
await workerWorld.stop();
|
|
2034
|
+
} catch (err) {
|
|
2035
|
+
log("workers: shutdown failed", err);
|
|
2036
|
+
}
|
|
2037
|
+
if (loginRefresh) {
|
|
2038
|
+
try {
|
|
2039
|
+
await loginRefresh.stop();
|
|
2040
|
+
} catch (err) {
|
|
2041
|
+
log("login refresh: shutdown failed", err);
|
|
2042
|
+
}
|
|
2043
|
+
loginRefresh = null;
|
|
2044
|
+
}
|
|
2045
|
+
// The lesson selections in flight. A shadow one is cut now and records
|
|
2046
|
+
// nothing (`stop`): its budget — `shadow_budget_ms`, up to 20 s — does
|
|
2047
|
+
// not fit docker's 10 s stop grace beside the server's own 5 s, and a
|
|
2048
|
+
// record cut short by our stop would read as the engine's. An awaited
|
|
2049
|
+
// one (`on`) is not cut: it settles within `delivery_budget_ms`
|
|
2050
|
+
// (≤ 2 s); let it record before the server and the store go. The wait
|
|
2051
|
+
// follows that budget, never the shadow one.
|
|
2052
|
+
if (lessonSelector) {
|
|
2053
|
+
const selector = lessonSelector;
|
|
2054
|
+
lessonSelector = null;
|
|
2055
|
+
selector.stop();
|
|
2056
|
+
await Promise.race([
|
|
2057
|
+
selector.idle(),
|
|
2058
|
+
new Promise<void>((r) => setTimeout(r, cfg.memory.delivery_budget_ms + 500).unref?.()),
|
|
2059
|
+
]);
|
|
2060
|
+
}
|
|
2061
|
+
for (const t of intervals) clearInterval(t);
|
|
2062
|
+
intervals.length = 0;
|
|
2063
|
+
eod?.stop();
|
|
2064
|
+
// Its open window polls the pinned area; drop it before the server goes.
|
|
2065
|
+
pinWatch?.stop();
|
|
2066
|
+
heartbeat?.stop();
|
|
2067
|
+
commands?.stop();
|
|
2068
|
+
operatorCommands?.stop();
|
|
2069
|
+
webhookHeal?.stop();
|
|
2070
|
+
webhookHeal = null;
|
|
2071
|
+
reembed?.stop();
|
|
2072
|
+
reembed = null;
|
|
2073
|
+
// SIGTERM then SIGKILL after a bounded grace — a memory engine must not
|
|
2074
|
+
// be able to hold the daemon's exit open.
|
|
2075
|
+
if (openviking) {
|
|
2076
|
+
try {
|
|
2077
|
+
await openviking.stop();
|
|
2078
|
+
} catch (err) {
|
|
2079
|
+
log("openviking: shutdown failed", err);
|
|
2080
|
+
}
|
|
2081
|
+
openviking = null;
|
|
2082
|
+
}
|
|
2083
|
+
// After the server: it may still be vectorizing on its way down.
|
|
2084
|
+
if (localEmbedder) {
|
|
2085
|
+
try {
|
|
2086
|
+
await localEmbedder.stop();
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
log("openviking: local embedder shutdown failed", err);
|
|
2089
|
+
}
|
|
2090
|
+
localEmbedder = null;
|
|
2091
|
+
}
|
|
2092
|
+
// Runs before the store closes, so the goodbye is ledgered; bounded by
|
|
2093
|
+
// the announcer itself, so it can never delay the exit.
|
|
2094
|
+
if (slack) {
|
|
2095
|
+
try {
|
|
2096
|
+
await slack.stop(reason);
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
log("slack: shutdown failed", err);
|
|
2099
|
+
}
|
|
2100
|
+
slack = null;
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
// Posting-time guards live on the gate, so every posting route in
|
|
2106
|
+
// the daemon — session review, approval path, migration pre-check comment —
|
|
2107
|
+
// passes through them. They need the store, which only exists once the
|
|
2108
|
+
// daemon has been built; hence the setter here rather than in buildParts().
|
|
2109
|
+
gate.setGuard(
|
|
2110
|
+
makePostingGuard({
|
|
2111
|
+
store: daemon.store,
|
|
2112
|
+
adapter,
|
|
2113
|
+
botLogin: cfg.github.bot_login,
|
|
2114
|
+
guards: cfg.guards,
|
|
2115
|
+
log,
|
|
2116
|
+
}),
|
|
2117
|
+
);
|
|
2118
|
+
|
|
2119
|
+
return daemon;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
async function main(): Promise<void> {
|
|
2123
|
+
// Where this daemon runs, asked once before anything is assembled: outside
|
|
2124
|
+
// the release image it warns, and with no Docker its memory is off.
|
|
2125
|
+
const root = sandboxRoot();
|
|
2126
|
+
const loaded = loadConfig();
|
|
2127
|
+
const daemon = createProductionDaemon(
|
|
2128
|
+
buildParts(root, loaded, hostFacts(root, loaded.openviking.enabled)),
|
|
2129
|
+
);
|
|
2130
|
+
const shutdown = (sig: string) => {
|
|
2131
|
+
void (async () => {
|
|
2132
|
+
console.log(`[quickreviewd] ${sig} — shutting down`);
|
|
2133
|
+
await daemon.stop(sig);
|
|
2134
|
+
process.exit(0);
|
|
2135
|
+
})();
|
|
2136
|
+
};
|
|
2137
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
2138
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
2139
|
+
try {
|
|
2140
|
+
await daemon.start();
|
|
2141
|
+
} catch (err) {
|
|
2142
|
+
// A failed boot (onStart is fatal — daemon.ts) exits non-zero after an
|
|
2143
|
+
// orderly stop, so the container's restart policy retries it rather than
|
|
2144
|
+
// leaving a half-assembled daemon serving reviews without its watchers.
|
|
2145
|
+
console.error("[quickreviewd] startup failed — exiting", err);
|
|
2146
|
+
await daemon.stop("startup failed").catch(() => {});
|
|
2147
|
+
process.exit(1);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
if (process.argv[1]?.endsWith("wire.js")) {
|
|
2152
|
+
void main();
|
|
2153
|
+
}
|