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