@quickchatai/quickreview 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1443) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +385 -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 +1067 -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/muted-theme.js +5 -0
  62. package/dist/extensions/cli/muted-theme.js.map +1 -0
  63. package/dist/extensions/cli/onboarding.js +5 -0
  64. package/dist/extensions/cli/onboarding.js.map +1 -0
  65. package/dist/extensions/cli/setup.js +5 -0
  66. package/dist/extensions/cli/setup.js.map +1 -0
  67. package/dist/extensions/cli/tui-view.js +5 -0
  68. package/dist/extensions/cli/tui-view.js.map +1 -0
  69. package/dist/extensions/companion-guard.js +361 -0
  70. package/dist/extensions/companion-guard.js.map +1 -0
  71. package/dist/extensions/debug-round.js +204 -0
  72. package/dist/extensions/debug-round.js.map +1 -0
  73. package/dist/extensions/inject-bridge.js +603 -0
  74. package/dist/extensions/inject-bridge.js.map +1 -0
  75. package/dist/extensions/muted-theme.js +12 -0
  76. package/dist/extensions/muted-theme.js.map +1 -0
  77. package/dist/extensions/onboarding.js +175 -0
  78. package/dist/extensions/onboarding.js.map +1 -0
  79. package/dist/extensions/setup-replay.js +269 -0
  80. package/dist/extensions/setup-replay.js.map +1 -0
  81. package/dist/extensions/setup-scan.js +998 -0
  82. package/dist/extensions/setup-scan.js.map +1 -0
  83. package/dist/extensions/setup-triage.js +175 -0
  84. package/dist/extensions/setup-triage.js.map +1 -0
  85. package/dist/extensions/setup.js +1160 -0
  86. package/dist/extensions/setup.js.map +1 -0
  87. package/dist/extensions/tui-view.js +389 -0
  88. package/dist/extensions/tui-view.js.map +1 -0
  89. package/dist/extensions/vendor/openviking-pi/client.js +376 -0
  90. package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
  91. package/dist/extensions/vendor/openviking-pi/config.js +189 -0
  92. package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
  93. package/dist/extensions/vendor/openviking-pi/index.js +421 -0
  94. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
  95. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
  96. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
  97. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
  98. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
  99. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
  100. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
  101. package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
  102. package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
  103. package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
  104. package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
  105. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
  106. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
  107. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
  108. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
  109. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
  110. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
  111. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
  112. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
  113. package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
  114. package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
  115. package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
  116. package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
  117. package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
  118. package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
  119. package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
  120. package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
  121. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
  122. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
  123. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
  124. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
  125. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
  126. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
  127. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
  128. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
  129. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
  130. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
  131. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
  132. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
  133. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
  134. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
  135. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
  136. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
  137. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
  138. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
  139. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  140. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
  141. package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
  142. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
  143. package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
  144. package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
  145. package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
  146. package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
  147. package/dist/src/anomalies.js +483 -0
  148. package/dist/src/anomalies.js.map +1 -0
  149. package/dist/src/answerworker.js +180 -0
  150. package/dist/src/answerworker.js.map +1 -0
  151. package/dist/src/appstamp.js +68 -0
  152. package/dist/src/appstamp.js.map +1 -0
  153. package/dist/src/archive/admission.js +3 -0
  154. package/dist/src/archive/admission.js.map +1 -0
  155. package/dist/src/archive/archivedb.js +474 -0
  156. package/dist/src/archive/archivedb.js.map +1 -0
  157. package/dist/src/archive/archiver.js +1770 -0
  158. package/dist/src/archive/archiver.js.map +1 -0
  159. package/dist/src/archive/archivewrite.js +198 -0
  160. package/dist/src/archive/archivewrite.js.map +1 -0
  161. package/dist/src/archive/bundle.js +266 -0
  162. package/dist/src/archive/bundle.js.map +1 -0
  163. package/dist/src/archive/dbmove.js +638 -0
  164. package/dist/src/archive/dbmove.js.map +1 -0
  165. package/dist/src/archive/duplicatecounts.js +80 -0
  166. package/dist/src/archive/duplicatecounts.js.map +1 -0
  167. package/dist/src/archive/eligibility.js +113 -0
  168. package/dist/src/archive/eligibility.js.map +1 -0
  169. package/dist/src/archive/extract.js +288 -0
  170. package/dist/src/archive/extract.js.map +1 -0
  171. package/dist/src/archive/fsutil.js +330 -0
  172. package/dist/src/archive/fsutil.js.map +1 -0
  173. package/dist/src/archive/gzfile.js +277 -0
  174. package/dist/src/archive/gzfile.js.map +1 -0
  175. package/dist/src/archive/kinds.js +22 -0
  176. package/dist/src/archive/kinds.js.map +1 -0
  177. package/dist/src/archive/livecheckpoint.js +178 -0
  178. package/dist/src/archive/livecheckpoint.js.map +1 -0
  179. package/dist/src/archive/liveretention.js +145 -0
  180. package/dist/src/archive/liveretention.js.map +1 -0
  181. package/dist/src/archive/manifest.js +284 -0
  182. package/dist/src/archive/manifest.js.map +1 -0
  183. package/dist/src/archive/overseer.js +283 -0
  184. package/dist/src/archive/overseer.js.map +1 -0
  185. package/dist/src/archive/piglobal.js +129 -0
  186. package/dist/src/archive/piglobal.js.map +1 -0
  187. package/dist/src/archive/plan.js +398 -0
  188. package/dist/src/archive/plan.js.map +1 -0
  189. package/dist/src/archive/prarchive.js +831 -0
  190. package/dist/src/archive/prarchive.js.map +1 -0
  191. package/dist/src/archive/prfiles.js +303 -0
  192. package/dist/src/archive/prfiles.js.map +1 -0
  193. package/dist/src/archive/report.js +169 -0
  194. package/dist/src/archive/report.js.map +1 -0
  195. package/dist/src/archive/restore.js +276 -0
  196. package/dist/src/archive/restore.js.map +1 -0
  197. package/dist/src/archive/rows.js +106 -0
  198. package/dist/src/archive/rows.js.map +1 -0
  199. package/dist/src/archive/seal.js +480 -0
  200. package/dist/src/archive/seal.js.map +1 -0
  201. package/dist/src/archive/status.js +528 -0
  202. package/dist/src/archive/status.js.map +1 -0
  203. package/dist/src/archive/storepass.js +409 -0
  204. package/dist/src/archive/storepass.js.map +1 -0
  205. package/dist/src/backfill.js +217 -0
  206. package/dist/src/backfill.js.map +1 -0
  207. package/dist/src/bench/cost.js +87 -0
  208. package/dist/src/bench/exec.js +76 -0
  209. package/dist/src/bench/values.js +14 -0
  210. package/dist/src/botcontrol.js +127 -0
  211. package/dist/src/botcontrol.js.map +1 -0
  212. package/dist/src/botworker.js +805 -0
  213. package/dist/src/botworker.js.map +1 -0
  214. package/dist/src/capture.js +234 -0
  215. package/dist/src/capture.js.map +1 -0
  216. package/dist/src/cases.js +1491 -0
  217. package/dist/src/cases.js.map +1 -0
  218. package/dist/src/channelfs.js +27 -0
  219. package/dist/src/channelfs.js.map +1 -0
  220. package/dist/src/checklist.js +312 -0
  221. package/dist/src/checklist.js.map +1 -0
  222. package/dist/src/ciretry.js +91 -0
  223. package/dist/src/ciretry.js.map +1 -0
  224. package/dist/src/cli.js +1916 -0
  225. package/dist/src/cli.js.map +1 -0
  226. package/dist/src/cliops.js +333 -0
  227. package/dist/src/cliops.js.map +1 -0
  228. package/dist/src/clonedir.js +15 -0
  229. package/dist/src/clonedir.js.map +1 -0
  230. package/dist/src/commandeffects.js +43 -0
  231. package/dist/src/commandeffects.js.map +1 -0
  232. package/dist/src/commands.js +1312 -0
  233. package/dist/src/commands.js.map +1 -0
  234. package/dist/src/companioninbox.js +203 -0
  235. package/dist/src/companioninbox.js.map +1 -0
  236. package/dist/src/compare.js +673 -0
  237. package/dist/src/compare.js.map +1 -0
  238. package/dist/src/config.js +1027 -0
  239. package/dist/src/config.js.map +1 -0
  240. package/dist/src/configcheck.js +111 -0
  241. package/dist/src/configcheck.js.map +1 -0
  242. package/dist/src/coreext.js +43 -0
  243. package/dist/src/coreext.js.map +1 -0
  244. package/dist/src/daemon.js +929 -0
  245. package/dist/src/daemon.js.map +1 -0
  246. package/dist/src/debuground.js +731 -0
  247. package/dist/src/debuground.js.map +1 -0
  248. package/dist/src/debugroundpi.js +219 -0
  249. package/dist/src/debugroundpi.js.map +1 -0
  250. package/dist/src/diagnostics.js +14 -0
  251. package/dist/src/diagnostics.js.map +1 -0
  252. package/dist/src/digest.js +139 -0
  253. package/dist/src/digest.js.map +1 -0
  254. package/dist/src/dispatch.js +1726 -0
  255. package/dist/src/dispatch.js.map +1 -0
  256. package/dist/src/engine/answerguard.js +18 -0
  257. package/dist/src/engine/answerguard.js.map +1 -0
  258. package/dist/src/engine/botsdk.js +262 -0
  259. package/dist/src/engine/botsdk.js.map +1 -0
  260. package/dist/src/engine/caserunner-sdk.js +332 -0
  261. package/dist/src/engine/caserunner-sdk.js.map +1 -0
  262. package/dist/src/engine/debugpi.js +194 -0
  263. package/dist/src/engine/debugpi.js.map +1 -0
  264. package/dist/src/eod.js +473 -0
  265. package/dist/src/eod.js.map +1 -0
  266. package/dist/src/eodpinwatch.js +391 -0
  267. package/dist/src/eodpinwatch.js.map +1 -0
  268. package/dist/src/escalation.js +1009 -0
  269. package/dist/src/escalation.js.map +1 -0
  270. package/dist/src/eventloop.js +779 -0
  271. package/dist/src/eventloop.js.map +1 -0
  272. package/dist/src/execboundary.js +430 -0
  273. package/dist/src/execboundary.js.map +1 -0
  274. package/dist/src/feedback.js +915 -0
  275. package/dist/src/feedback.js.map +1 -0
  276. package/dist/src/findingscheck.js +200 -0
  277. package/dist/src/findingscheck.js.map +1 -0
  278. package/dist/src/fsdenied.js +26 -0
  279. package/dist/src/fsdenied.js.map +1 -0
  280. package/dist/src/ghread.js +181 -0
  281. package/dist/src/ghread.js.map +1 -0
  282. package/dist/src/github.js +1063 -0
  283. package/dist/src/github.js.map +1 -0
  284. package/dist/src/homes.js +103 -0
  285. package/dist/src/homes.js.map +1 -0
  286. package/dist/src/hostroute.js +24 -0
  287. package/dist/src/hostroute.js.map +1 -0
  288. package/dist/src/improvements.js +625 -0
  289. package/dist/src/improvements.js.map +1 -0
  290. package/dist/src/install.js +946 -0
  291. package/dist/src/install.js.map +1 -0
  292. package/dist/src/installkind.js +40 -0
  293. package/dist/src/installkind.js.map +1 -0
  294. package/dist/src/ipc/capability.js +286 -0
  295. package/dist/src/ipc/capability.js.map +1 -0
  296. package/dist/src/ipc/client.js +226 -0
  297. package/dist/src/ipc/client.js.map +1 -0
  298. package/dist/src/ipc/control.js +70 -0
  299. package/dist/src/ipc/control.js.map +1 -0
  300. package/dist/src/ipc/ghreadshim.js +77 -0
  301. package/dist/src/ipc/ghreadshim.js.map +1 -0
  302. package/dist/src/ipc/launcher.js +65 -0
  303. package/dist/src/ipc/launcher.js.map +1 -0
  304. package/dist/src/ipc/permits.js +293 -0
  305. package/dist/src/ipc/permits.js.map +1 -0
  306. package/dist/src/ipc/postshim.js +137 -0
  307. package/dist/src/ipc/postshim.js.map +1 -0
  308. package/dist/src/ipc/protocol.js +239 -0
  309. package/dist/src/ipc/protocol.js.map +1 -0
  310. package/dist/src/ipc/server.js +312 -0
  311. package/dist/src/ipc/server.js.map +1 -0
  312. package/dist/src/ipc/workerspec.js +148 -0
  313. package/dist/src/ipc/workerspec.js.map +1 -0
  314. package/dist/src/learner/learner.js +1272 -0
  315. package/dist/src/learner/learner.js.map +1 -0
  316. package/dist/src/learner/miner.js +948 -0
  317. package/dist/src/learner/miner.js.map +1 -0
  318. package/dist/src/learner/packet.js +216 -0
  319. package/dist/src/learner/packet.js.map +1 -0
  320. package/dist/src/learner/receipt.js +135 -0
  321. package/dist/src/learner/receipt.js.map +1 -0
  322. package/dist/src/learner/status.js +38 -0
  323. package/dist/src/learner/status.js.map +1 -0
  324. package/dist/src/ledgercheck.js +120 -0
  325. package/dist/src/ledgercheck.js.map +1 -0
  326. package/dist/src/lessonledger.js +451 -0
  327. package/dist/src/lessonledger.js.map +1 -0
  328. package/dist/src/lessonselect.js +639 -0
  329. package/dist/src/lessonselect.js.map +1 -0
  330. package/dist/src/lessonshadow.js +540 -0
  331. package/dist/src/lessonshadow.js.map +1 -0
  332. package/dist/src/levers.js +302 -0
  333. package/dist/src/levers.js.map +1 -0
  334. package/dist/src/live/projection.js +450 -0
  335. package/dist/src/live/projection.js.map +1 -0
  336. package/dist/src/live/publish.js +83 -0
  337. package/dist/src/live/publish.js.map +1 -0
  338. package/dist/src/live/read.js +60 -0
  339. package/dist/src/live/read.js.map +1 -0
  340. package/dist/src/livecli.js +288 -0
  341. package/dist/src/livecli.js.map +1 -0
  342. package/dist/src/loginhint.js +42 -0
  343. package/dist/src/loginhint.js.map +1 -0
  344. package/dist/src/loginrefresh/atomic.js +66 -0
  345. package/dist/src/loginrefresh/atomic.js.map +1 -0
  346. package/dist/src/loginrefresh/lock.js +93 -0
  347. package/dist/src/loginrefresh/lock.js.map +1 -0
  348. package/dist/src/loginrefresh/main.js +58 -0
  349. package/dist/src/loginrefresh/main.js.map +1 -0
  350. package/dist/src/loginrefresh/pirefresh.js +49 -0
  351. package/dist/src/loginrefresh/pirefresh.js.map +1 -0
  352. package/dist/src/loginrefresh/refresher.js +286 -0
  353. package/dist/src/loginrefresh/refresher.js.map +1 -0
  354. package/dist/src/loginrefresh/supervisor.js +253 -0
  355. package/dist/src/loginrefresh/supervisor.js.map +1 -0
  356. package/dist/src/loginrefresh/types.js +4 -0
  357. package/dist/src/loginrefresh/types.js.map +1 -0
  358. package/dist/src/memory/context.js +251 -0
  359. package/dist/src/memory/context.js.map +1 -0
  360. package/dist/src/memory/datafence.js +5 -0
  361. package/dist/src/memory/datafence.js.map +1 -0
  362. package/dist/src/memory/delivery.js +131 -0
  363. package/dist/src/memory/delivery.js.map +1 -0
  364. package/dist/src/memory/eventtags.js +81 -0
  365. package/dist/src/memory/eventtags.js.map +1 -0
  366. package/dist/src/memory/health.js +713 -0
  367. package/dist/src/memory/health.js.map +1 -0
  368. package/dist/src/memory/import.js +504 -0
  369. package/dist/src/memory/import.js.map +1 -0
  370. package/dist/src/memory/layout.js +252 -0
  371. package/dist/src/memory/layout.js.map +1 -0
  372. package/dist/src/memory/legacy-file.js +100 -0
  373. package/dist/src/memory/legacy-file.js.map +1 -0
  374. package/dist/src/memory/lessons.js +370 -0
  375. package/dist/src/memory/lessons.js.map +1 -0
  376. package/dist/src/memory/ovsession.js +449 -0
  377. package/dist/src/memory/ovsession.js.map +1 -0
  378. package/dist/src/memory/relayout.js +259 -0
  379. package/dist/src/memory/relayout.js.map +1 -0
  380. package/dist/src/memory/roundactivity.js +180 -0
  381. package/dist/src/memory/roundactivity.js.map +1 -0
  382. package/dist/src/memory/selection.js +690 -0
  383. package/dist/src/memory/selection.js.map +1 -0
  384. package/dist/src/memory/shadowstatus.js +69 -0
  385. package/dist/src/memory/shadowstatus.js.map +1 -0
  386. package/dist/src/memory/store.js +83 -0
  387. package/dist/src/memory/store.js.map +1 -0
  388. package/dist/src/memory/textidentity.js +8 -0
  389. package/dist/src/memory/textidentity.js.map +1 -0
  390. package/dist/src/metrics.js +773 -0
  391. package/dist/src/metrics.js.map +1 -0
  392. package/dist/src/missingconfig.js +8 -0
  393. package/dist/src/missingconfig.js.map +1 -0
  394. package/dist/src/npmverbs.js +226 -0
  395. package/dist/src/npmverbs.js.map +1 -0
  396. package/dist/src/onboard/choices.js +191 -0
  397. package/dist/src/onboard/choices.js.map +1 -0
  398. package/dist/src/onboard/engine.js +344 -0
  399. package/dist/src/onboard/engine.js.map +1 -0
  400. package/dist/src/onboard/launch.js +83 -0
  401. package/dist/src/onboard/launch.js.map +1 -0
  402. package/dist/src/onboard/steps/appslug.js +211 -0
  403. package/dist/src/onboard/steps/appslug.js.map +1 -0
  404. package/dist/src/onboard/steps/bringup.js +408 -0
  405. package/dist/src/onboard/steps/bringup.js.map +1 -0
  406. package/dist/src/onboard/steps/compose.js +343 -0
  407. package/dist/src/onboard/steps/compose.js.map +1 -0
  408. package/dist/src/onboard/steps/configdelivery.js +50 -0
  409. package/dist/src/onboard/steps/configdelivery.js.map +1 -0
  410. package/dist/src/onboard/steps/configfill.js +1148 -0
  411. package/dist/src/onboard/steps/configfill.js.map +1 -0
  412. package/dist/src/onboard/steps/deploysh.js +80 -0
  413. package/dist/src/onboard/steps/deploysh.js.map +1 -0
  414. package/dist/src/onboard/steps/embeddings.js +472 -0
  415. package/dist/src/onboard/steps/embeddings.js.map +1 -0
  416. package/dist/src/onboard/steps/escalation.js +571 -0
  417. package/dist/src/onboard/steps/escalation.js.map +1 -0
  418. package/dist/src/onboard/steps/githubapp.js +1092 -0
  419. package/dist/src/onboard/steps/githubapp.js.map +1 -0
  420. package/dist/src/onboard/steps/golive.js +506 -0
  421. package/dist/src/onboard/steps/golive.js.map +1 -0
  422. package/dist/src/onboard/steps/index.js +67 -0
  423. package/dist/src/onboard/steps/index.js.map +1 -0
  424. package/dist/src/onboard/steps/ingress.js +762 -0
  425. package/dist/src/onboard/steps/ingress.js.map +1 -0
  426. package/dist/src/onboard/steps/inventory.js +420 -0
  427. package/dist/src/onboard/steps/inventory.js.map +1 -0
  428. package/dist/src/onboard/steps/llmauth.js +893 -0
  429. package/dist/src/onboard/steps/llmauth.js.map +1 -0
  430. package/dist/src/onboard/steps/memory.js +1060 -0
  431. package/dist/src/onboard/steps/memory.js.map +1 -0
  432. package/dist/src/onboard/steps/memorykeys.js +43 -0
  433. package/dist/src/onboard/steps/memorykeys.js.map +1 -0
  434. package/dist/src/onboard/steps/preflight.js +172 -0
  435. package/dist/src/onboard/steps/preflight.js.map +1 -0
  436. package/dist/src/onboard/steps/proof.js +502 -0
  437. package/dist/src/onboard/steps/proof.js.map +1 -0
  438. package/dist/src/onboard/steps/ssh.js +300 -0
  439. package/dist/src/onboard/steps/ssh.js.map +1 -0
  440. package/dist/src/onboard/steps/topology.js +291 -0
  441. package/dist/src/onboard/steps/topology.js.map +1 -0
  442. package/dist/src/onboard/steps/transport.js +511 -0
  443. package/dist/src/onboard/steps/transport.js.map +1 -0
  444. package/dist/src/onboard/steps/verifyup.js +362 -0
  445. package/dist/src/onboard/steps/verifyup.js.map +1 -0
  446. package/dist/src/onboard/steps/vmdeploy.js +368 -0
  447. package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
  448. package/dist/src/onboard/toml.js +53 -0
  449. package/dist/src/onboard/toml.js.map +1 -0
  450. package/dist/src/opencli.js +508 -0
  451. package/dist/src/opencli.js.map +1 -0
  452. package/dist/src/openpr.js +457 -0
  453. package/dist/src/openpr.js.map +1 -0
  454. package/dist/src/openviking/client.js +821 -0
  455. package/dist/src/openviking/client.js.map +1 -0
  456. package/dist/src/openviking/context.js +218 -0
  457. package/dist/src/openviking/context.js.map +1 -0
  458. package/dist/src/openviking/embedder.js +457 -0
  459. package/dist/src/openviking/embedder.js.map +1 -0
  460. package/dist/src/openviking/gapsink.js +38 -0
  461. package/dist/src/openviking/gapsink.js.map +1 -0
  462. package/dist/src/openviking/health.js +69 -0
  463. package/dist/src/openviking/health.js.map +1 -0
  464. package/dist/src/openviking/index.js +24 -0
  465. package/dist/src/openviking/index.js.map +1 -0
  466. package/dist/src/openviking/lessonfind.js +54 -0
  467. package/dist/src/openviking/lessonfind.js.map +1 -0
  468. package/dist/src/openviking/probe.js +89 -0
  469. package/dist/src/openviking/probe.js.map +1 -0
  470. package/dist/src/openviking/proc.js +95 -0
  471. package/dist/src/openviking/proc.js.map +1 -0
  472. package/dist/src/openviking/provisionreport.js +144 -0
  473. package/dist/src/openviking/provisionreport.js.map +1 -0
  474. package/dist/src/openviking/reembed.js +317 -0
  475. package/dist/src/openviking/reembed.js.map +1 -0
  476. package/dist/src/openviking/relayout.js +153 -0
  477. package/dist/src/openviking/relayout.js.map +1 -0
  478. package/dist/src/openviking/sidecar-main.js +39 -0
  479. package/dist/src/openviking/sidecar-main.js.map +1 -0
  480. package/dist/src/openviking/sidecar.js +79 -0
  481. package/dist/src/openviking/sidecar.js.map +1 -0
  482. package/dist/src/openviking/store.js +1758 -0
  483. package/dist/src/openviking/store.js.map +1 -0
  484. package/dist/src/openviking/supervisor.js +422 -0
  485. package/dist/src/openviking/supervisor.js.map +1 -0
  486. package/dist/src/openviking/types.js +5 -0
  487. package/dist/src/openviking/types.js.map +1 -0
  488. package/dist/src/operator-recovery.js +172 -0
  489. package/dist/src/operator-recovery.js.map +1 -0
  490. package/dist/src/operatormessage.js +44 -0
  491. package/dist/src/operatormessage.js.map +1 -0
  492. package/dist/src/outcomes.js +1361 -0
  493. package/dist/src/outcomes.js.map +1 -0
  494. package/dist/src/piauthcopy.js +5 -0
  495. package/dist/src/piauthcopy.js.map +1 -0
  496. package/dist/src/picli.js +45 -0
  497. package/dist/src/picli.js.map +1 -0
  498. package/dist/src/picwd.js +25 -0
  499. package/dist/src/picwd.js.map +1 -0
  500. package/dist/src/pilogin.js +35 -0
  501. package/dist/src/pilogin.js.map +1 -0
  502. package/dist/src/pingpong.js +1028 -0
  503. package/dist/src/pingpong.js.map +1 -0
  504. package/dist/src/posting-journal.js +441 -0
  505. package/dist/src/posting-journal.js.map +1 -0
  506. package/dist/src/posting-outbox.js +662 -0
  507. package/dist/src/posting-outbox.js.map +1 -0
  508. package/dist/src/posting-receipt.js +203 -0
  509. package/dist/src/posting-receipt.js.map +1 -0
  510. package/dist/src/posting.js +348 -0
  511. package/dist/src/posting.js.map +1 -0
  512. package/dist/src/postops.js +637 -0
  513. package/dist/src/postops.js.map +1 -0
  514. package/dist/src/pr-activity.js +11 -0
  515. package/dist/src/pr-activity.js.map +1 -0
  516. package/dist/src/pr-author.js +5 -0
  517. package/dist/src/pr-author.js.map +1 -0
  518. package/dist/src/pr-conversation.js +355 -0
  519. package/dist/src/pr-conversation.js.map +1 -0
  520. package/dist/src/pr-session-lifecycle.js +542 -0
  521. package/dist/src/pr-session-lifecycle.js.map +1 -0
  522. package/dist/src/preflight.js +341 -0
  523. package/dist/src/preflight.js.map +1 -0
  524. package/dist/src/priorresolve.js +275 -0
  525. package/dist/src/priorresolve.js.map +1 -0
  526. package/dist/src/qcrbin.js +180 -0
  527. package/dist/src/qcrbin.js.map +1 -0
  528. package/dist/src/recheck.js +1398 -0
  529. package/dist/src/recheck.js.map +1 -0
  530. package/dist/src/reconcile.js +820 -0
  531. package/dist/src/reconcile.js.map +1 -0
  532. package/dist/src/recordedfindings.js +15 -0
  533. package/dist/src/recordedfindings.js.map +1 -0
  534. package/dist/src/regression.js +546 -0
  535. package/dist/src/regression.js.map +1 -0
  536. package/dist/src/replay.js +598 -0
  537. package/dist/src/replay.js.map +1 -0
  538. package/dist/src/requiredpasses.js +113 -0
  539. package/dist/src/requiredpasses.js.map +1 -0
  540. package/dist/src/resumecheck.js +168 -0
  541. package/dist/src/resumecheck.js.map +1 -0
  542. package/dist/src/reviewcli.js +671 -0
  543. package/dist/src/reviewcli.js.map +1 -0
  544. package/dist/src/reviewcontext.js +22 -0
  545. package/dist/src/reviewcontext.js.map +1 -0
  546. package/dist/src/reviewdetach.js +574 -0
  547. package/dist/src/reviewdetach.js.map +1 -0
  548. package/dist/src/reviewhistory.js +284 -0
  549. package/dist/src/reviewhistory.js.map +1 -0
  550. package/dist/src/reviewhome.js +26 -0
  551. package/dist/src/reviewhome.js.map +1 -0
  552. package/dist/src/reviewmemory.js +110 -0
  553. package/dist/src/reviewmemory.js.map +1 -0
  554. package/dist/src/reviewrendercli.js +104 -0
  555. package/dist/src/reviewrendercli.js.map +1 -0
  556. package/dist/src/round-work.js +20 -0
  557. package/dist/src/round-work.js.map +1 -0
  558. package/dist/src/runhost.js +72 -0
  559. package/dist/src/runhost.js.map +1 -0
  560. package/dist/src/scanauth.js +25 -0
  561. package/dist/src/scanauth.js.map +1 -0
  562. package/dist/src/scanbox.js +846 -0
  563. package/dist/src/scanbox.js.map +1 -0
  564. package/dist/src/scancli.js +1074 -0
  565. package/dist/src/scancli.js.map +1 -0
  566. package/dist/src/scandriverlog.js +30 -0
  567. package/dist/src/scandriverlog.js.map +1 -0
  568. package/dist/src/session-bridge.js +75 -0
  569. package/dist/src/session-bridge.js.map +1 -0
  570. package/dist/src/session-command-payload.js +29 -0
  571. package/dist/src/session-command-payload.js.map +1 -0
  572. package/dist/src/session-command-supervisor.js +939 -0
  573. package/dist/src/session-command-supervisor.js.map +1 -0
  574. package/dist/src/sessionjsonl.js +85 -0
  575. package/dist/src/sessionjsonl.js.map +1 -0
  576. package/dist/src/sessions.js +541 -0
  577. package/dist/src/sessions.js.map +1 -0
  578. package/dist/src/setup/adapters.js +330 -0
  579. package/dist/src/setup/adapters.js.map +1 -0
  580. package/dist/src/setup/baselines.js +64 -0
  581. package/dist/src/setup/baselines.js.map +1 -0
  582. package/dist/src/setup/caserunner.js +554 -0
  583. package/dist/src/setup/caserunner.js.map +1 -0
  584. package/dist/src/setup/configcommit.js +358 -0
  585. package/dist/src/setup/configcommit.js.map +1 -0
  586. package/dist/src/setup/engine.js +672 -0
  587. package/dist/src/setup/engine.js.map +1 -0
  588. package/dist/src/setup/facts.js +593 -0
  589. package/dist/src/setup/facts.js.map +1 -0
  590. package/dist/src/setup/findings.js +948 -0
  591. package/dist/src/setup/findings.js.map +1 -0
  592. package/dist/src/setup/history.js +338 -0
  593. package/dist/src/setup/history.js.map +1 -0
  594. package/dist/src/setup/interview.js +185 -0
  595. package/dist/src/setup/interview.js.map +1 -0
  596. package/dist/src/setup/ledger.js +207 -0
  597. package/dist/src/setup/ledger.js.map +1 -0
  598. package/dist/src/setup/pins.js +43 -0
  599. package/dist/src/setup/pins.js.map +1 -0
  600. package/dist/src/setup/portrait.js +432 -0
  601. package/dist/src/setup/portrait.js.map +1 -0
  602. package/dist/src/setup/presentation.js +9 -0
  603. package/dist/src/setup/presentation.js.map +1 -0
  604. package/dist/src/setup/profile.js +417 -0
  605. package/dist/src/setup/profile.js.map +1 -0
  606. package/dist/src/setup/replay.js +240 -0
  607. package/dist/src/setup/replay.js.map +1 -0
  608. package/dist/src/setup/replaybuild.js +311 -0
  609. package/dist/src/setup/replaybuild.js.map +1 -0
  610. package/dist/src/setup/replayrun.js +806 -0
  611. package/dist/src/setup/replayrun.js.map +1 -0
  612. package/dist/src/setup/replaysource.js +169 -0
  613. package/dist/src/setup/replaysource.js.map +1 -0
  614. package/dist/src/setup/retune.js +333 -0
  615. package/dist/src/setup/retune.js.map +1 -0
  616. package/dist/src/setup/retunelog.js +51 -0
  617. package/dist/src/setup/retunelog.js.map +1 -0
  618. package/dist/src/setup/scan.js +725 -0
  619. package/dist/src/setup/scan.js.map +1 -0
  620. package/dist/src/setup/scanbuild.js +133 -0
  621. package/dist/src/setup/scanbuild.js.map +1 -0
  622. package/dist/src/setup/scanfacts.js +96 -0
  623. package/dist/src/setup/scanfacts.js.map +1 -0
  624. package/dist/src/setup/scanlease.js +251 -0
  625. package/dist/src/setup/scanlease.js.map +1 -0
  626. package/dist/src/setup/scanreport.js +289 -0
  627. package/dist/src/setup/scanreport.js.map +1 -0
  628. package/dist/src/setup/scanrun.js +1733 -0
  629. package/dist/src/setup/scanrun.js.map +1 -0
  630. package/dist/src/setup/scantree.js +257 -0
  631. package/dist/src/setup/scantree.js.map +1 -0
  632. package/dist/src/setup/stage2.js +617 -0
  633. package/dist/src/setup/stage2.js.map +1 -0
  634. package/dist/src/setup/stage2request.js +259 -0
  635. package/dist/src/setup/stage2request.js.map +1 -0
  636. package/dist/src/setup/survey.js +307 -0
  637. package/dist/src/setup/survey.js.map +1 -0
  638. package/dist/src/setup/surveyrun.js +549 -0
  639. package/dist/src/setup/surveyrun.js.map +1 -0
  640. package/dist/src/setup/textsniff.js +89 -0
  641. package/dist/src/setup/textsniff.js.map +1 -0
  642. package/dist/src/setup/trackersink.js +395 -0
  643. package/dist/src/setup/trackersink.js.map +1 -0
  644. package/dist/src/setup/triage.js +636 -0
  645. package/dist/src/setup/triage.js.map +1 -0
  646. package/dist/src/setup/tuning.js +585 -0
  647. package/dist/src/setup/tuning.js.map +1 -0
  648. package/dist/src/setup/unitcut.js +437 -0
  649. package/dist/src/setup/unitcut.js.map +1 -0
  650. package/dist/src/singleflight.js +72 -0
  651. package/dist/src/singleflight.js.map +1 -0
  652. package/dist/src/slack/announce.js +725 -0
  653. package/dist/src/slack/announce.js.map +1 -0
  654. package/dist/src/slack/api.js +227 -0
  655. package/dist/src/slack/api.js.map +1 -0
  656. package/dist/src/slack/messages.js +158 -0
  657. package/dist/src/slack/messages.js.map +1 -0
  658. package/dist/src/slack/origin.js +550 -0
  659. package/dist/src/slack/origin.js.map +1 -0
  660. package/dist/src/slack/policy.js +225 -0
  661. package/dist/src/slack/policy.js.map +1 -0
  662. package/dist/src/slack/reply.js +153 -0
  663. package/dist/src/slack/reply.js.map +1 -0
  664. package/dist/src/slack/router.js +189 -0
  665. package/dist/src/slack/router.js.map +1 -0
  666. package/dist/src/slack/socket.js +323 -0
  667. package/dist/src/slack/socket.js.map +1 -0
  668. package/dist/src/slack/types.js +11 -0
  669. package/dist/src/slack/types.js.map +1 -0
  670. package/dist/src/sqlitehint.js +30 -0
  671. package/dist/src/sqlitehint.js.map +1 -0
  672. package/dist/src/ssh-observation.js +25 -0
  673. package/dist/src/ssh-observation.js.map +1 -0
  674. package/dist/src/state.js +5869 -0
  675. package/dist/src/state.js.map +1 -0
  676. package/dist/src/statemachine.js +220 -0
  677. package/dist/src/statemachine.js.map +1 -0
  678. package/dist/src/stubs.js +52 -0
  679. package/dist/src/stubs.js.map +1 -0
  680. package/dist/src/takeover.js +506 -0
  681. package/dist/src/takeover.js.map +1 -0
  682. package/dist/src/takeoverpi.js +249 -0
  683. package/dist/src/takeoverpi.js.map +1 -0
  684. package/dist/src/terminal-failure.js +26 -0
  685. package/dist/src/terminal-failure.js.map +1 -0
  686. package/dist/src/thread-resolution-refusal.js +4 -0
  687. package/dist/src/thread-resolution-refusal.js.map +1 -0
  688. package/dist/src/tmux.js +126 -0
  689. package/dist/src/tmux.js.map +1 -0
  690. package/dist/src/trackerid.js +43 -0
  691. package/dist/src/trackerid.js.map +1 -0
  692. package/dist/src/transcriptindex.js +512 -0
  693. package/dist/src/transcriptindex.js.map +1 -0
  694. package/dist/src/tui/actions.js +404 -0
  695. package/dist/src/tui/actions.js.map +1 -0
  696. package/dist/src/tui/alerts.js +523 -0
  697. package/dist/src/tui/alerts.js.map +1 -0
  698. package/dist/src/tui/app.js +1835 -0
  699. package/dist/src/tui/app.js.map +1 -0
  700. package/dist/src/tui/archiveops.js +164 -0
  701. package/dist/src/tui/archiveops.js.map +1 -0
  702. package/dist/src/tui/autofile.js +191 -0
  703. package/dist/src/tui/autofile.js.map +1 -0
  704. package/dist/src/tui/board.js +1143 -0
  705. package/dist/src/tui/board.js.map +1 -0
  706. package/dist/src/tui/candidates.js +699 -0
  707. package/dist/src/tui/candidates.js.map +1 -0
  708. package/dist/src/tui/cellwidth.js +151 -0
  709. package/dist/src/tui/cellwidth.js.map +1 -0
  710. package/dist/src/tui/changelog-store.js +404 -0
  711. package/dist/src/tui/changelog-store.js.map +1 -0
  712. package/dist/src/tui/chord.js +26 -0
  713. package/dist/src/tui/chord.js.map +1 -0
  714. package/dist/src/tui/clipboard.js +81 -0
  715. package/dist/src/tui/clipboard.js.map +1 -0
  716. package/dist/src/tui/cmdbar.js +253 -0
  717. package/dist/src/tui/cmdbar.js.map +1 -0
  718. package/dist/src/tui/companion.js +1275 -0
  719. package/dist/src/tui/companion.js.map +1 -0
  720. package/dist/src/tui/companionplan.js +200 -0
  721. package/dist/src/tui/companionplan.js.map +1 -0
  722. package/dist/src/tui/companiontab.js +61 -0
  723. package/dist/src/tui/companiontab.js.map +1 -0
  724. package/dist/src/tui/confighome.js +105 -0
  725. package/dist/src/tui/confighome.js.map +1 -0
  726. package/dist/src/tui/connect.js +44 -0
  727. package/dist/src/tui/connect.js.map +1 -0
  728. package/dist/src/tui/deploy-ceremony.js +75 -0
  729. package/dist/src/tui/deploy-ceremony.js.map +1 -0
  730. package/dist/src/tui/deploy-drain.js +93 -0
  731. package/dist/src/tui/deploy-drain.js.map +1 -0
  732. package/dist/src/tui/deploy-process.js +234 -0
  733. package/dist/src/tui/deploy-process.js.map +1 -0
  734. package/dist/src/tui/deploytab.js +1263 -0
  735. package/dist/src/tui/deploytab.js.map +1 -0
  736. package/dist/src/tui/design.js +111 -0
  737. package/dist/src/tui/design.js.map +1 -0
  738. package/dist/src/tui/digesttab.js +148 -0
  739. package/dist/src/tui/digesttab.js.map +1 -0
  740. package/dist/src/tui/emptystate.js +32 -0
  741. package/dist/src/tui/emptystate.js.map +1 -0
  742. package/dist/src/tui/evidence.js +790 -0
  743. package/dist/src/tui/evidence.js.map +1 -0
  744. package/dist/src/tui/feed.js +415 -0
  745. package/dist/src/tui/feed.js.map +1 -0
  746. package/dist/src/tui/feedbacktab.js +907 -0
  747. package/dist/src/tui/feedbacktab.js.map +1 -0
  748. package/dist/src/tui/filters.js +72 -0
  749. package/dist/src/tui/filters.js.map +1 -0
  750. package/dist/src/tui/ghread.js +272 -0
  751. package/dist/src/tui/ghread.js.map +1 -0
  752. package/dist/src/tui/historypull.js +61 -0
  753. package/dist/src/tui/historypull.js.map +1 -0
  754. package/dist/src/tui/inboxpull.js +197 -0
  755. package/dist/src/tui/inboxpull.js.map +1 -0
  756. package/dist/src/tui/index.js +1425 -0
  757. package/dist/src/tui/index.js.map +1 -0
  758. package/dist/src/tui/issuesink.js +206 -0
  759. package/dist/src/tui/issuesink.js.map +1 -0
  760. package/dist/src/tui/jsonfile.js +32 -0
  761. package/dist/src/tui/jsonfile.js.map +1 -0
  762. package/dist/src/tui/keys.js +97 -0
  763. package/dist/src/tui/keys.js.map +1 -0
  764. package/dist/src/tui/layout.js +70 -0
  765. package/dist/src/tui/layout.js.map +1 -0
  766. package/dist/src/tui/learning.js +469 -0
  767. package/dist/src/tui/learning.js.map +1 -0
  768. package/dist/src/tui/lessonswitch.js +103 -0
  769. package/dist/src/tui/lessonswitch.js.map +1 -0
  770. package/dist/src/tui/livecontrol.js +167 -0
  771. package/dist/src/tui/livecontrol.js.map +1 -0
  772. package/dist/src/tui/memforward.js +310 -0
  773. package/dist/src/tui/memforward.js.map +1 -0
  774. package/dist/src/tui/memincident.js +287 -0
  775. package/dist/src/tui/memincident.js.map +1 -0
  776. package/dist/src/tui/memory.js +485 -0
  777. package/dist/src/tui/memory.js.map +1 -0
  778. package/dist/src/tui/memtab.js +513 -0
  779. package/dist/src/tui/memtab.js.map +1 -0
  780. package/dist/src/tui/metricstab.js +979 -0
  781. package/dist/src/tui/metricstab.js.map +1 -0
  782. package/dist/src/tui/model.js +104 -0
  783. package/dist/src/tui/model.js.map +1 -0
  784. package/dist/src/tui/mouse.js +83 -0
  785. package/dist/src/tui/mouse.js.map +1 -0
  786. package/dist/src/tui/msg.js +196 -0
  787. package/dist/src/tui/msg.js.map +1 -0
  788. package/dist/src/tui/nativecompanion.js +321 -0
  789. package/dist/src/tui/nativecompanion.js.map +1 -0
  790. package/dist/src/tui/openurl.js +17 -0
  791. package/dist/src/tui/openurl.js.map +1 -0
  792. package/dist/src/tui/paint.js +501 -0
  793. package/dist/src/tui/paint.js.map +1 -0
  794. package/dist/src/tui/panes.js +74 -0
  795. package/dist/src/tui/panes.js.map +1 -0
  796. package/dist/src/tui/pipane.js +166 -0
  797. package/dist/src/tui/pipane.js.map +1 -0
  798. package/dist/src/tui/poll-hooks.js +11 -0
  799. package/dist/src/tui/poll-hooks.js.map +1 -0
  800. package/dist/src/tui/poll.js +113 -0
  801. package/dist/src/tui/poll.js.map +1 -0
  802. package/dist/src/tui/proposals.js +715 -0
  803. package/dist/src/tui/proposals.js.map +1 -0
  804. package/dist/src/tui/prpause.js +132 -0
  805. package/dist/src/tui/prpause.js.map +1 -0
  806. package/dist/src/tui/reload.js +351 -0
  807. package/dist/src/tui/reload.js.map +1 -0
  808. package/dist/src/tui/remote.js +312 -0
  809. package/dist/src/tui/remote.js.map +1 -0
  810. package/dist/src/tui/rollover.js +767 -0
  811. package/dist/src/tui/rollover.js.map +1 -0
  812. package/dist/src/tui/scantab.js +1053 -0
  813. package/dist/src/tui/scantab.js.map +1 -0
  814. package/dist/src/tui/setupentry.js +81 -0
  815. package/dist/src/tui/setupentry.js.map +1 -0
  816. package/dist/src/tui/sidebar.js +602 -0
  817. package/dist/src/tui/sidebar.js.map +1 -0
  818. package/dist/src/tui/sshtriage.js +393 -0
  819. package/dist/src/tui/sshtriage.js.map +1 -0
  820. package/dist/src/tui/startup.js +172 -0
  821. package/dist/src/tui/startup.js.map +1 -0
  822. package/dist/src/tui/tabs.js +41 -0
  823. package/dist/src/tui/tabs.js.map +1 -0
  824. package/dist/src/tui/termmirror.js +217 -0
  825. package/dist/src/tui/termmirror.js.map +1 -0
  826. package/dist/src/tui/theme.js +95 -0
  827. package/dist/src/tui/theme.js.map +1 -0
  828. package/dist/src/tui/ticketstab.js +224 -0
  829. package/dist/src/tui/ticketstab.js.map +1 -0
  830. package/dist/src/tui/tmuxsocket.js +101 -0
  831. package/dist/src/tui/tmuxsocket.js.map +1 -0
  832. package/dist/src/tui/trackertickets.js +77 -0
  833. package/dist/src/tui/trackertickets.js.map +1 -0
  834. package/dist/src/tui/unwedge.js +245 -0
  835. package/dist/src/tui/unwedge.js.map +1 -0
  836. package/dist/src/tui/viewer.js +479 -0
  837. package/dist/src/tui/viewer.js.map +1 -0
  838. package/dist/src/tui/viewerinput.js +125 -0
  839. package/dist/src/tui/viewerinput.js.map +1 -0
  840. package/dist/src/updatenotice.js +143 -0
  841. package/dist/src/updatenotice.js.map +1 -0
  842. package/dist/src/usage.js +1007 -0
  843. package/dist/src/usage.js.map +1 -0
  844. package/dist/src/verdicts.js +3848 -0
  845. package/dist/src/verdicts.js.map +1 -0
  846. package/dist/src/version.js +58 -0
  847. package/dist/src/version.js.map +1 -0
  848. package/dist/src/wakes.js +411 -0
  849. package/dist/src/wakes.js.map +1 -0
  850. package/dist/src/webhook.js +725 -0
  851. package/dist/src/webhook.js.map +1 -0
  852. package/dist/src/webhookheal.js +238 -0
  853. package/dist/src/webhookheal.js.map +1 -0
  854. package/dist/src/wire.js +1801 -0
  855. package/dist/src/wire.js.map +1 -0
  856. package/dist/src/worker-session-host.js +531 -0
  857. package/dist/src/worker-session-host.js.map +1 -0
  858. package/dist/src/workerbreaker.js +112 -0
  859. package/dist/src/workerbreaker.js.map +1 -0
  860. package/dist/src/workercontrol.js +49 -0
  861. package/dist/src/workercontrol.js.map +1 -0
  862. package/dist/src/workerlease.js +351 -0
  863. package/dist/src/workerlease.js.map +1 -0
  864. package/dist/src/workerliveness.js +84 -0
  865. package/dist/src/workerliveness.js.map +1 -0
  866. package/dist/src/workerlog.js +65 -0
  867. package/dist/src/workerlog.js.map +1 -0
  868. package/dist/src/workerruntime.js +10 -0
  869. package/dist/src/workerruntime.js.map +1 -0
  870. package/dist/src/workersupervisor.js +1206 -0
  871. package/dist/src/workersupervisor.js.map +1 -0
  872. package/dist/src/workerusage.js +23 -0
  873. package/dist/src/workerusage.js.map +1 -0
  874. package/dist/src/workerworld.js +332 -0
  875. package/dist/src/workerworld.js.map +1 -0
  876. package/dist/src/worktrees.js +984 -0
  877. package/dist/src/worktrees.js.map +1 -0
  878. package/docker/Dockerfile +284 -0
  879. package/docker/Dockerfile.dockerignore +35 -0
  880. package/docker/README.md +221 -0
  881. package/docker/companion/Dockerfile +139 -0
  882. package/docker/companion/agents/evidence.md +34 -0
  883. package/docker/companion/agents/explore.md +26 -0
  884. package/docker/companion/agents/pass-survey.md +49 -0
  885. package/docker/companion/agents/study.md +26 -0
  886. package/docker/companion/entrypoint.sh +343 -0
  887. package/docker/companion/memory-health.mjs +269 -0
  888. package/docker/entrypoint.sh +782 -0
  889. package/docker/memory/Dockerfile +191 -0
  890. package/docker/memory/Dockerfile.dockerignore +20 -0
  891. package/docker/memory/README.md +60 -0
  892. package/docker/memory/entrypoint.sh +163 -0
  893. package/docker/setid/qcr-setid.c +109 -0
  894. package/docker/tunnel/Dockerfile.cloudflared +51 -0
  895. package/docker/tunnel/Dockerfile.ngrok +40 -0
  896. package/docker/tunnel/sidecar.mjs +411 -0
  897. package/docker/vm/tmux.conf +26 -0
  898. package/docker-compose.yml +348 -0
  899. package/extensions/cli/bash-guard-ambient.ts +71 -0
  900. package/extensions/cli/bash-guard.ts +12 -0
  901. package/extensions/cli/companion-guard.ts +8 -0
  902. package/extensions/cli/debug-round.ts +9 -0
  903. package/extensions/cli/inject-bridge.ts +8 -0
  904. package/extensions/cli/ledger-pin.ts +13 -0
  905. package/extensions/cli/lens.ts +22 -0
  906. package/extensions/cli/muted-theme.ts +9 -0
  907. package/extensions/cli/onboarding.ts +8 -0
  908. package/extensions/cli/setup.ts +8 -0
  909. package/extensions/cli/tui-view.ts +8 -0
  910. package/extensions/companion-guard.ts +411 -0
  911. package/extensions/debug-round.ts +281 -0
  912. package/extensions/inject-bridge.ts +630 -0
  913. package/extensions/inline-bash.ts +102 -0
  914. package/extensions/muted-theme.json +68 -0
  915. package/extensions/muted-theme.ts +23 -0
  916. package/extensions/onboarding.ts +233 -0
  917. package/extensions/setup-replay.ts +336 -0
  918. package/extensions/setup-scan.ts +1236 -0
  919. package/extensions/setup-triage.ts +190 -0
  920. package/extensions/setup.ts +1428 -0
  921. package/extensions/tui-view.ts +489 -0
  922. package/extensions/vendor/openviking-pi/LICENSE +201 -0
  923. package/extensions/vendor/openviking-pi/NOTICE +68 -0
  924. package/extensions/vendor/openviking-pi/README.md +550 -0
  925. package/extensions/vendor/openviking-pi/client.ts +588 -0
  926. package/extensions/vendor/openviking-pi/config.json +28 -0
  927. package/extensions/vendor/openviking-pi/config.ts +271 -0
  928. package/extensions/vendor/openviking-pi/index.ts +523 -0
  929. package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
  930. package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
  931. package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
  932. package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
  933. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
  934. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
  935. package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
  936. package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
  937. package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
  938. package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
  939. package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
  940. package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
  941. package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
  942. package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
  943. package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
  944. package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
  945. package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
  946. package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
  947. package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
  948. package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
  949. package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
  950. package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
  951. package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
  952. package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
  953. package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
  954. package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
  955. package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
  956. package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
  957. package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
  958. package/extensions/vendor/openviking-pi/recall.ts +149 -0
  959. package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
  960. package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
  961. package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
  962. package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
  963. package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
  964. package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
  965. package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
  966. package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
  967. package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
  968. package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
  969. package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
  970. package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
  971. package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
  972. package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
  973. package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
  974. package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
  975. package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  976. package/extensions/vendor/openviking-pi/sync.ts +163 -0
  977. package/extensions/vendor/openviking-pi/takeover.ts +36 -0
  978. package/extensions/vendor/openviking-pi/tools.ts +479 -0
  979. package/extensions/vendor/openviking-pi/upstream.json +48 -0
  980. package/package.json +138 -4
  981. package/profiles/default/README.md +21 -0
  982. package/profiles/default/addenda/.gitkeep +0 -0
  983. package/profiles/default/profile.toml +17 -0
  984. package/profiles/example-django/README.md +21 -0
  985. package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
  986. package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
  987. package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
  988. package/profiles/example-django/addenda/40-orm-usage.md +19 -0
  989. package/profiles/example-django/addenda/50-declared-shape.md +10 -0
  990. package/profiles/example-django/addenda/60-config-placement.md +14 -0
  991. package/profiles/example-django/heuristics.toml +24 -0
  992. package/profiles/example-django/profile.toml +9 -0
  993. package/profiles/fast/README.md +24 -0
  994. package/profiles/fast/profile.toml +59 -0
  995. package/profiles/minimal/README.md +17 -0
  996. package/profiles/minimal/addenda/.gitkeep +0 -0
  997. package/profiles/minimal/profile.toml +8 -0
  998. package/profiles/snappy/README.md +24 -0
  999. package/profiles/snappy/addenda/10-race-as-question.md +6 -0
  1000. package/profiles/snappy/profile.toml +46 -0
  1001. package/prompts/companion-day-open.md +46 -0
  1002. package/prompts/companion-first-run.md +104 -0
  1003. package/prompts/companion.md +821 -0
  1004. package/prompts/conversation-system.md +117 -0
  1005. package/prompts/eod-sweep.md +120 -0
  1006. package/prompts/onboarding.md +74 -0
  1007. package/prompts/pass-common.md +52 -0
  1008. package/prompts/pass-quality-common.md +22 -0
  1009. package/prompts/round-first.md +874 -0
  1010. package/prompts/round-follow-up.md +991 -0
  1011. package/prompts/round-re-review.md +889 -0
  1012. package/prompts/round-recheck.md +196 -0
  1013. package/prompts/round-staleness.md +856 -0
  1014. package/schemas/review-result.v1.json +391 -0
  1015. package/schemas/scan-result.v1.json +421 -0
  1016. package/schemas/version.v1.json +57 -0
  1017. package/scripts/approve-pr.sh +16 -0
  1018. package/scripts/build-stamp.mjs +67 -0
  1019. package/scripts/commit-verdict.sh +155 -0
  1020. package/scripts/companion-e2e.sh +250 -0
  1021. package/scripts/companion-memory-forward.mjs +1966 -0
  1022. package/scripts/config-fingerprint.sh +28 -0
  1023. package/scripts/deploy-manifest.mjs +141 -0
  1024. package/scripts/deploy.sh +1435 -0
  1025. package/scripts/directory-lock.mjs +193 -0
  1026. package/scripts/heartbeat-fresh.mjs +54 -0
  1027. package/scripts/install-locator.sh +328 -0
  1028. package/scripts/install-wrapper.sh +76 -0
  1029. package/scripts/memory-search-check.mjs +98 -0
  1030. package/scripts/mint-token.sh +212 -0
  1031. package/scripts/openviking-embedder.py +392 -0
  1032. package/scripts/openviking-ov-conf.mjs +161 -0
  1033. package/scripts/openviking-provision.mjs +1122 -0
  1034. package/scripts/openviking-reembed.mjs +1154 -0
  1035. package/scripts/post-review.sh +16 -0
  1036. package/scripts/pr-checks.sh +256 -0
  1037. package/scripts/pr-context.py +174 -0
  1038. package/scripts/pr-context.sh +63 -0
  1039. package/scripts/pr-self.sh +144 -0
  1040. package/scripts/pr-threads.sh +145 -0
  1041. package/scripts/qcr +15 -0
  1042. package/scripts/qcr-host.sh +769 -0
  1043. package/scripts/render-review.sh +24 -0
  1044. package/scripts/reply-thread.sh +16 -0
  1045. package/scripts/required-passes.py +112 -0
  1046. package/scripts/smoke-companion.sh +211 -0
  1047. package/scripts/tui-deploy-runner.mjs +61 -0
  1048. package/scripts/verify-dist.mjs +201 -0
  1049. package/scripts/vm-ghcr-login.sh +188 -0
  1050. package/seeds/agents/pass-baseline.md +61 -0
  1051. package/seeds/agents/pass-contracts.md +30 -0
  1052. package/seeds/agents/pass-data-safety.md +54 -0
  1053. package/seeds/agents/pass-delivery.md +37 -0
  1054. package/seeds/agents/pass-prod-risk.md +24 -0
  1055. package/seeds/agents/pass-security.md +31 -0
  1056. package/seeds/agents/pass-statelessness.md +27 -0
  1057. package/seeds/agents/pass-ui.md +23 -0
  1058. package/seeds/agents/pass-validate.md +50 -0
  1059. package/seeds/prompts/conversation-system.md +117 -0
  1060. package/seeds/prompts/eod-sweep.md +120 -0
  1061. package/seeds/prompts/pass-common.md +52 -0
  1062. package/skills/companion/adjudicate/SKILL.md +247 -0
  1063. package/skills/companion/evidence/SKILL.md +150 -0
  1064. package/skills/companion/land/SKILL.md +200 -0
  1065. package/skills/companion/replay/SKILL.md +138 -0
  1066. package/skills/companion/setup/SKILL.md +167 -0
  1067. package/skills/companion/survey/SKILL.md +160 -0
  1068. package/skills/selfimprove/README.md +34 -0
  1069. package/skills/selfimprove/SKILL.md +127 -0
  1070. package/skills/selfimprove/agents/openai.yaml +2 -0
  1071. package/skills/selfimprove/commands/selfimprove.md +6 -0
  1072. package/skills/selfimprove/references/fix-loop.md +49 -0
  1073. package/skills/selfimprove/references/ledger.md +20 -0
  1074. package/skills/selfimprove/references/setup.md +40 -0
  1075. package/src/anomalies.ts +566 -0
  1076. package/src/answerworker.ts +251 -0
  1077. package/src/appstamp.ts +65 -0
  1078. package/src/archive/admission.ts +3 -0
  1079. package/src/archive/archivedb.ts +548 -0
  1080. package/src/archive/archiver.ts +1963 -0
  1081. package/src/archive/archivewrite.ts +279 -0
  1082. package/src/archive/bundle.ts +333 -0
  1083. package/src/archive/dbmove.ts +911 -0
  1084. package/src/archive/duplicatecounts.ts +98 -0
  1085. package/src/archive/eligibility.ts +181 -0
  1086. package/src/archive/extract.ts +342 -0
  1087. package/src/archive/fsutil.ts +415 -0
  1088. package/src/archive/gzfile.ts +370 -0
  1089. package/src/archive/kinds.ts +21 -0
  1090. package/src/archive/livecheckpoint.ts +217 -0
  1091. package/src/archive/liveretention.ts +180 -0
  1092. package/src/archive/manifest.ts +519 -0
  1093. package/src/archive/overseer.ts +336 -0
  1094. package/src/archive/piglobal.ts +174 -0
  1095. package/src/archive/plan.ts +612 -0
  1096. package/src/archive/prarchive.ts +984 -0
  1097. package/src/archive/prfiles.ts +406 -0
  1098. package/src/archive/report.ts +252 -0
  1099. package/src/archive/restore.ts +330 -0
  1100. package/src/archive/rows.ts +187 -0
  1101. package/src/archive/seal.ts +630 -0
  1102. package/src/archive/status.ts +719 -0
  1103. package/src/archive/storepass.ts +581 -0
  1104. package/src/backfill.ts +362 -0
  1105. package/src/bench/cost.ts +113 -0
  1106. package/src/bench/exec.ts +90 -0
  1107. package/src/bench/values.ts +13 -0
  1108. package/src/botcontrol.ts +176 -0
  1109. package/src/botworker.ts +953 -0
  1110. package/src/capture.ts +321 -0
  1111. package/src/cases.ts +1789 -0
  1112. package/src/channelfs.ts +26 -0
  1113. package/src/checklist.ts +506 -0
  1114. package/src/ciretry.ts +141 -0
  1115. package/src/cli.ts +2271 -0
  1116. package/src/cliops.ts +384 -0
  1117. package/src/clonedir.ts +15 -0
  1118. package/src/commandeffects.ts +57 -0
  1119. package/src/commands.ts +1678 -0
  1120. package/src/companioninbox.ts +239 -0
  1121. package/src/compare.ts +957 -0
  1122. package/src/config.ts +2050 -0
  1123. package/src/configcheck.ts +133 -0
  1124. package/src/coreext.ts +47 -0
  1125. package/src/daemon.ts +1302 -0
  1126. package/src/debuground.ts +927 -0
  1127. package/src/debugroundpi.ts +262 -0
  1128. package/src/diagnostics.ts +14 -0
  1129. package/src/digest.ts +183 -0
  1130. package/src/dispatch.ts +2250 -0
  1131. package/src/engine/answerguard.ts +27 -0
  1132. package/src/engine/botsdk.ts +336 -0
  1133. package/src/engine/caserunner-sdk.ts +423 -0
  1134. package/src/engine/debugpi.ts +235 -0
  1135. package/src/eod.ts +669 -0
  1136. package/src/eodpinwatch.ts +556 -0
  1137. package/src/escalation.ts +1253 -0
  1138. package/src/eventloop.ts +1045 -0
  1139. package/src/execboundary.ts +633 -0
  1140. package/src/feedback.ts +1162 -0
  1141. package/src/findingscheck.ts +298 -0
  1142. package/src/fsdenied.ts +24 -0
  1143. package/src/ghread.ts +242 -0
  1144. package/src/github.ts +1497 -0
  1145. package/src/homes.ts +175 -0
  1146. package/src/hostroute.ts +53 -0
  1147. package/src/improvements.ts +787 -0
  1148. package/src/install.ts +1103 -0
  1149. package/src/installkind.ts +50 -0
  1150. package/src/ipc/capability.ts +333 -0
  1151. package/src/ipc/client.ts +269 -0
  1152. package/src/ipc/control.ts +90 -0
  1153. package/src/ipc/ghreadshim.ts +94 -0
  1154. package/src/ipc/launcher.ts +65 -0
  1155. package/src/ipc/permits.ts +362 -0
  1156. package/src/ipc/postshim.ts +164 -0
  1157. package/src/ipc/protocol.ts +342 -0
  1158. package/src/ipc/server.ts +371 -0
  1159. package/src/ipc/workerspec.ts +218 -0
  1160. package/src/learner/learner.ts +1655 -0
  1161. package/src/learner/miner.ts +1290 -0
  1162. package/src/learner/packet.ts +340 -0
  1163. package/src/learner/receipt.ts +168 -0
  1164. package/src/learner/status.ts +107 -0
  1165. package/src/ledgercheck.ts +157 -0
  1166. package/src/lessonledger.ts +703 -0
  1167. package/src/lessonselect.ts +888 -0
  1168. package/src/lessonshadow.ts +810 -0
  1169. package/src/levers.ts +393 -0
  1170. package/src/live/projection.ts +491 -0
  1171. package/src/live/publish.ts +92 -0
  1172. package/src/live/read.ts +86 -0
  1173. package/src/livecli.ts +336 -0
  1174. package/src/loginhint.ts +56 -0
  1175. package/src/loginrefresh/atomic.ts +76 -0
  1176. package/src/loginrefresh/lock.ts +104 -0
  1177. package/src/loginrefresh/main.ts +59 -0
  1178. package/src/loginrefresh/pirefresh.ts +57 -0
  1179. package/src/loginrefresh/refresher.ts +326 -0
  1180. package/src/loginrefresh/supervisor.ts +273 -0
  1181. package/src/loginrefresh/types.ts +35 -0
  1182. package/src/memory/context.ts +385 -0
  1183. package/src/memory/datafence.ts +9 -0
  1184. package/src/memory/delivery.ts +171 -0
  1185. package/src/memory/eventtags.ts +80 -0
  1186. package/src/memory/health.ts +953 -0
  1187. package/src/memory/import.ts +664 -0
  1188. package/src/memory/layout.ts +261 -0
  1189. package/src/memory/legacy-file.ts +104 -0
  1190. package/src/memory/lessons.ts +447 -0
  1191. package/src/memory/ovsession.ts +601 -0
  1192. package/src/memory/relayout.ts +335 -0
  1193. package/src/memory/roundactivity.ts +209 -0
  1194. package/src/memory/selection.ts +989 -0
  1195. package/src/memory/shadowstatus.ts +125 -0
  1196. package/src/memory/store.ts +275 -0
  1197. package/src/memory/textidentity.ts +8 -0
  1198. package/src/metrics.ts +1056 -0
  1199. package/src/missingconfig.ts +7 -0
  1200. package/src/npmverbs.ts +264 -0
  1201. package/src/onboard/choices.ts +222 -0
  1202. package/src/onboard/engine.ts +518 -0
  1203. package/src/onboard/launch.ts +115 -0
  1204. package/src/onboard/steps/appslug.ts +265 -0
  1205. package/src/onboard/steps/bringup.ts +482 -0
  1206. package/src/onboard/steps/compose.ts +459 -0
  1207. package/src/onboard/steps/configdelivery.ts +58 -0
  1208. package/src/onboard/steps/configfill.ts +1352 -0
  1209. package/src/onboard/steps/deploysh.ts +105 -0
  1210. package/src/onboard/steps/embeddings.ts +550 -0
  1211. package/src/onboard/steps/escalation.ts +640 -0
  1212. package/src/onboard/steps/githubapp.ts +1414 -0
  1213. package/src/onboard/steps/golive.ts +577 -0
  1214. package/src/onboard/steps/index.ts +71 -0
  1215. package/src/onboard/steps/ingress.ts +907 -0
  1216. package/src/onboard/steps/inventory.ts +526 -0
  1217. package/src/onboard/steps/llmauth.ts +1095 -0
  1218. package/src/onboard/steps/memory.ts +1290 -0
  1219. package/src/onboard/steps/memorykeys.ts +85 -0
  1220. package/src/onboard/steps/preflight.ts +191 -0
  1221. package/src/onboard/steps/proof.ts +624 -0
  1222. package/src/onboard/steps/ssh.ts +377 -0
  1223. package/src/onboard/steps/topology.ts +337 -0
  1224. package/src/onboard/steps/transport.ts +665 -0
  1225. package/src/onboard/steps/verifyup.ts +448 -0
  1226. package/src/onboard/steps/vmdeploy.ts +442 -0
  1227. package/src/onboard/toml.ts +50 -0
  1228. package/src/opencli.ts +591 -0
  1229. package/src/openpr.ts +568 -0
  1230. package/src/openviking/client.ts +1131 -0
  1231. package/src/openviking/context.ts +263 -0
  1232. package/src/openviking/embedder.ts +516 -0
  1233. package/src/openviking/gapsink.ts +63 -0
  1234. package/src/openviking/health.ts +83 -0
  1235. package/src/openviking/index.ts +119 -0
  1236. package/src/openviking/lessonfind.ts +92 -0
  1237. package/src/openviking/probe.ts +129 -0
  1238. package/src/openviking/proc.ts +110 -0
  1239. package/src/openviking/provisionreport.ts +160 -0
  1240. package/src/openviking/reembed.ts +412 -0
  1241. package/src/openviking/relayout.ts +177 -0
  1242. package/src/openviking/sidecar-main.ts +38 -0
  1243. package/src/openviking/sidecar.ts +106 -0
  1244. package/src/openviking/store.ts +2025 -0
  1245. package/src/openviking/supervisor.ts +474 -0
  1246. package/src/openviking/types.ts +174 -0
  1247. package/src/operator-recovery.ts +237 -0
  1248. package/src/operatormessage.ts +52 -0
  1249. package/src/outcomes.ts +1670 -0
  1250. package/src/piauthcopy.ts +12 -0
  1251. package/src/picli.ts +49 -0
  1252. package/src/picwd.ts +25 -0
  1253. package/src/pilogin.ts +37 -0
  1254. package/src/pingpong.ts +1339 -0
  1255. package/src/posting-journal.ts +549 -0
  1256. package/src/posting-outbox.ts +802 -0
  1257. package/src/posting-receipt.ts +324 -0
  1258. package/src/posting.ts +603 -0
  1259. package/src/postops.ts +832 -0
  1260. package/src/pr-activity.ts +20 -0
  1261. package/src/pr-author.ts +6 -0
  1262. package/src/pr-conversation.ts +432 -0
  1263. package/src/pr-session-lifecycle.ts +631 -0
  1264. package/src/preflight.ts +391 -0
  1265. package/src/priorresolve.ts +343 -0
  1266. package/src/qcrbin.ts +186 -0
  1267. package/src/recheck.ts +1775 -0
  1268. package/src/reconcile.ts +1030 -0
  1269. package/src/recordedfindings.ts +27 -0
  1270. package/src/regression.ts +833 -0
  1271. package/src/replay.ts +816 -0
  1272. package/src/requiredpasses.ts +148 -0
  1273. package/src/resumecheck.ts +246 -0
  1274. package/src/reviewcli.ts +787 -0
  1275. package/src/reviewcontext.ts +35 -0
  1276. package/src/reviewdetach.ts +734 -0
  1277. package/src/reviewhistory.ts +427 -0
  1278. package/src/reviewhome.ts +36 -0
  1279. package/src/reviewmemory.ts +133 -0
  1280. package/src/reviewrendercli.ts +139 -0
  1281. package/src/round-work.ts +42 -0
  1282. package/src/runhost.ts +108 -0
  1283. package/src/scanauth.ts +36 -0
  1284. package/src/scanbox.ts +1081 -0
  1285. package/src/scancli.ts +1296 -0
  1286. package/src/scandriverlog.ts +32 -0
  1287. package/src/session-bridge.ts +139 -0
  1288. package/src/session-command-payload.ts +35 -0
  1289. package/src/session-command-supervisor.ts +1142 -0
  1290. package/src/sessionjsonl.ts +88 -0
  1291. package/src/sessions.ts +659 -0
  1292. package/src/setup/adapters.ts +335 -0
  1293. package/src/setup/baselines.ts +110 -0
  1294. package/src/setup/caserunner.ts +611 -0
  1295. package/src/setup/configcommit.ts +443 -0
  1296. package/src/setup/engine.ts +1001 -0
  1297. package/src/setup/facts.ts +627 -0
  1298. package/src/setup/findings.ts +1264 -0
  1299. package/src/setup/history.ts +443 -0
  1300. package/src/setup/interview.ts +243 -0
  1301. package/src/setup/ledger.ts +324 -0
  1302. package/src/setup/pins.ts +66 -0
  1303. package/src/setup/portrait.ts +635 -0
  1304. package/src/setup/presentation.ts +18 -0
  1305. package/src/setup/profile.ts +508 -0
  1306. package/src/setup/replay.ts +370 -0
  1307. package/src/setup/replaybuild.ts +392 -0
  1308. package/src/setup/replayrun.ts +1096 -0
  1309. package/src/setup/replaysource.ts +198 -0
  1310. package/src/setup/retune.ts +415 -0
  1311. package/src/setup/retunelog.ts +95 -0
  1312. package/src/setup/scan.ts +1033 -0
  1313. package/src/setup/scanbuild.ts +168 -0
  1314. package/src/setup/scanfacts.ts +107 -0
  1315. package/src/setup/scanlease.ts +297 -0
  1316. package/src/setup/scanreport.ts +373 -0
  1317. package/src/setup/scanrun.ts +2268 -0
  1318. package/src/setup/scantree.ts +259 -0
  1319. package/src/setup/stage2.ts +926 -0
  1320. package/src/setup/stage2request.ts +347 -0
  1321. package/src/setup/survey.ts +458 -0
  1322. package/src/setup/surveyrun.ts +758 -0
  1323. package/src/setup/textsniff.ts +95 -0
  1324. package/src/setup/trackersink.ts +433 -0
  1325. package/src/setup/triage.ts +768 -0
  1326. package/src/setup/tuning.ts +766 -0
  1327. package/src/setup/unitcut.ts +523 -0
  1328. package/src/singleflight.ts +86 -0
  1329. package/src/slack/announce.ts +911 -0
  1330. package/src/slack/api.ts +303 -0
  1331. package/src/slack/messages.ts +288 -0
  1332. package/src/slack/origin.ts +599 -0
  1333. package/src/slack/policy.ts +354 -0
  1334. package/src/slack/reply.ts +201 -0
  1335. package/src/slack/router.ts +285 -0
  1336. package/src/slack/socket.ts +384 -0
  1337. package/src/slack/types.ts +108 -0
  1338. package/src/sqlitehint.ts +32 -0
  1339. package/src/ssh-observation.ts +30 -0
  1340. package/src/state.ts +8243 -0
  1341. package/src/statemachine.ts +369 -0
  1342. package/src/stubs.ts +68 -0
  1343. package/src/takeover.ts +634 -0
  1344. package/src/takeoverpi.ts +293 -0
  1345. package/src/terminal-failure.ts +41 -0
  1346. package/src/thread-resolution-refusal.ts +2 -0
  1347. package/src/tmux.ts +153 -0
  1348. package/src/trackerid.ts +41 -0
  1349. package/src/transcriptindex.ts +665 -0
  1350. package/src/tui/actions.ts +484 -0
  1351. package/src/tui/alerts.ts +666 -0
  1352. package/src/tui/app.ts +1979 -0
  1353. package/src/tui/archiveops.ts +192 -0
  1354. package/src/tui/autofile.ts +233 -0
  1355. package/src/tui/board.ts +1432 -0
  1356. package/src/tui/candidates.ts +889 -0
  1357. package/src/tui/cellwidth.ts +148 -0
  1358. package/src/tui/changelog-store.ts +503 -0
  1359. package/src/tui/chord.ts +25 -0
  1360. package/src/tui/clipboard.ts +92 -0
  1361. package/src/tui/cmdbar.ts +296 -0
  1362. package/src/tui/companion.ts +1427 -0
  1363. package/src/tui/companionplan.ts +247 -0
  1364. package/src/tui/companiontab.ts +83 -0
  1365. package/src/tui/confighome.ts +155 -0
  1366. package/src/tui/connect.ts +68 -0
  1367. package/src/tui/deploy-ceremony.ts +104 -0
  1368. package/src/tui/deploy-drain.ts +129 -0
  1369. package/src/tui/deploy-process.ts +265 -0
  1370. package/src/tui/deploytab.ts +1523 -0
  1371. package/src/tui/design.ts +125 -0
  1372. package/src/tui/digesttab.ts +214 -0
  1373. package/src/tui/emptystate.ts +39 -0
  1374. package/src/tui/evidence.ts +999 -0
  1375. package/src/tui/feed.ts +604 -0
  1376. package/src/tui/feedbacktab.ts +1077 -0
  1377. package/src/tui/filters.ts +90 -0
  1378. package/src/tui/ghread.ts +321 -0
  1379. package/src/tui/historypull.ts +81 -0
  1380. package/src/tui/inboxpull.ts +236 -0
  1381. package/src/tui/index.ts +1565 -0
  1382. package/src/tui/issuesink.ts +238 -0
  1383. package/src/tui/jsonfile.ts +42 -0
  1384. package/src/tui/keys.ts +109 -0
  1385. package/src/tui/layout.ts +108 -0
  1386. package/src/tui/learning.ts +627 -0
  1387. package/src/tui/lessonswitch.ts +118 -0
  1388. package/src/tui/livecontrol.ts +228 -0
  1389. package/src/tui/memforward.ts +443 -0
  1390. package/src/tui/memincident.ts +363 -0
  1391. package/src/tui/memory.ts +620 -0
  1392. package/src/tui/memtab.ts +606 -0
  1393. package/src/tui/metricstab.ts +1177 -0
  1394. package/src/tui/model.ts +131 -0
  1395. package/src/tui/mouse.ts +98 -0
  1396. package/src/tui/msg.ts +268 -0
  1397. package/src/tui/nativecompanion.ts +417 -0
  1398. package/src/tui/openurl.ts +21 -0
  1399. package/src/tui/paint.ts +511 -0
  1400. package/src/tui/panes.ts +98 -0
  1401. package/src/tui/pipane.ts +219 -0
  1402. package/src/tui/poll-hooks.ts +12 -0
  1403. package/src/tui/poll.ts +147 -0
  1404. package/src/tui/proposals.ts +838 -0
  1405. package/src/tui/prpause.ts +150 -0
  1406. package/src/tui/reload.ts +436 -0
  1407. package/src/tui/remote.ts +413 -0
  1408. package/src/tui/rollover.ts +969 -0
  1409. package/src/tui/scantab.ts +1290 -0
  1410. package/src/tui/setupentry.ts +93 -0
  1411. package/src/tui/sidebar.ts +749 -0
  1412. package/src/tui/sshtriage.ts +510 -0
  1413. package/src/tui/startup.ts +231 -0
  1414. package/src/tui/tabs.ts +45 -0
  1415. package/src/tui/termmirror.ts +224 -0
  1416. package/src/tui/theme.ts +95 -0
  1417. package/src/tui/ticketstab.ts +310 -0
  1418. package/src/tui/tmuxsocket.ts +129 -0
  1419. package/src/tui/trackertickets.ts +84 -0
  1420. package/src/tui/unwedge.ts +286 -0
  1421. package/src/tui/viewer.ts +575 -0
  1422. package/src/tui/viewerinput.ts +173 -0
  1423. package/src/updatenotice.ts +178 -0
  1424. package/src/usage.ts +1154 -0
  1425. package/src/verdicts.ts +4689 -0
  1426. package/src/version.ts +80 -0
  1427. package/src/wakes.ts +476 -0
  1428. package/src/webhook.ts +942 -0
  1429. package/src/webhookheal.ts +276 -0
  1430. package/src/wire.ts +2153 -0
  1431. package/src/worker-session-host.ts +602 -0
  1432. package/src/workerbreaker.ts +156 -0
  1433. package/src/workercontrol.ts +104 -0
  1434. package/src/workerlease.ts +441 -0
  1435. package/src/workerliveness.ts +103 -0
  1436. package/src/workerlog.ts +79 -0
  1437. package/src/workerruntime.ts +30 -0
  1438. package/src/workersupervisor.ts +1538 -0
  1439. package/src/workerusage.ts +42 -0
  1440. package/src/workerworld.ts +427 -0
  1441. package/src/worktrees.ts +1138 -0
  1442. package/tests/fixtures/regression-cases.json +795 -0
  1443. package/tests/fixtures/seed-manifest.json +19 -0
@@ -0,0 +1,1565 @@
1
+ // `qcr tui [pr]` entry point. Wires the real ssh runner; the optional PR
2
+ // number opens the board with that PR selected.
3
+ import { join } from "node:path";
4
+ import { liveConfigPath, loadConfig, sandboxRoot, type Config } from "../config.js";
5
+ import type { TuiDriver } from "./actions.js";
6
+ import { App } from "./app.js";
7
+ import { powerActionFor, type DeployState } from "./deploytab.js";
8
+ import type { LearningTabs } from "./learning.js";
9
+ import type { MetricsView } from "./metricstab.js";
10
+ import { ensureSshMaster } from "./connect.js";
11
+ import { MOUSE_OFF, MOUSE_ON } from "./mouse.js";
12
+ import { isFreshHealth } from "./poll.js";
13
+ import { ALT_SCREEN_OFF, ALT_SCREEN_ON, CLEAR } from "./paint.js";
14
+ import { storeHealth } from "./memory.js";
15
+ import {
16
+ entryKey,
17
+ findMemoryEntry,
18
+ memoryOrder,
19
+ memoryRefUnread,
20
+ setTimelineFilter,
21
+ } from "./memtab.js";
22
+ import { refOf } from "../memory/store.js";
23
+ import { isTab } from "./tabs.js";
24
+ import { laptopCompanionConfig, laptopInstallEnv, laptopLifecycle } from "./companionplan.js";
25
+ import { TUI_COMPANION_ENV, TUI_COMPANION_TMUX_SOCKET, tmuxSocketRunner } from "./tmuxsocket.js";
26
+ import {
27
+ APP_BUILD_INFO,
28
+ BUILD_STAMP,
29
+ CORE_DIST,
30
+ HotReloader,
31
+ parseResumeArg,
32
+ readResumeState,
33
+ realReexec,
34
+ RELOAD_EXIT_CODE,
35
+ resumeStatePath,
36
+ } from "./reload.js";
37
+ import { killInFlightSsh, runnerFor, transportOf } from "./remote.js";
38
+ import { chainOnPoll } from "./poll-hooks.js";
39
+ import { deliverMessage } from "./msg.js";
40
+ import type { ConnectionDiagnosis } from "./sshtriage.js";
41
+
42
+ export { chainOnPoll } from "./poll-hooks.js";
43
+
44
+ /**
45
+ * `qcr tui <pr>` opens the board with that PR selected. `overseer` and `main`
46
+ * are refused with the place to go instead: the TUI never attaches to a VM
47
+ * session.
48
+ */
49
+ export function parseTarget(
50
+ arg: string | undefined,
51
+ ): { kind: "none" } | { kind: "pr"; pr: number } | { kind: "bad"; reason: string } {
52
+ if (arg === undefined || arg === "") return { kind: "none" };
53
+ if (/^\d+$/.test(arg)) return { kind: "pr", pr: Number(arg) };
54
+ if (arg === "overseer" || arg === "main") {
55
+ return {
56
+ kind: "bad",
57
+ reason: `'${arg}' is no longer a target — the TUI never attaches to the ${arg} session; open the board and ask the companion`,
58
+ };
59
+ }
60
+ return { kind: "bad", reason: `unknown target '${arg}' — expected a PR number` };
61
+ }
62
+
63
+ export const TUI_HELP = `qcr tui — live board and operator console for the review daemon (over ssh)
64
+
65
+ usage: qcr tui [<pr>] [--no-companion] [--no-reload]
66
+
67
+ qcr tui open the board
68
+ qcr tui 5012 open the board with PR 5012 selected (⏎ focuses its pi pane)
69
+
70
+ --no-companion do not run the local companion (container, ambient feed,
71
+ alert pipeline). The layout does not change: the column
72
+ is still on the right and says "companion off"
73
+ --no-reload do not re-exec when a new dist/ build lands
74
+
75
+ layout: ONE shape, always — the tab on the left, the companion column on the
76
+ right. Nothing goes full-screen and nothing collapses the column;
77
+ anything you would have attached to a VM session for, ask the
78
+ companion.
79
+
80
+ keys: 1…8 the eight surfaces — board · memory · digest · tickets · deploy ·
81
+ metrics · feedback · scan (docs/tui.md says what each is for; the bar at the
82
+ foot of the screen shows what the current frame answers to) ·
83
+ 2 memory: j/k select · [ ] older/newer day · e p x n edit/pin/retire/new ·
84
+ 3 digest: [ ] older/newer · 4 tickets: c actionable ⇄ all ·
85
+ 5 deploy: click ▶/■ buttons (D D deploy, S S stop/start) ·
86
+ 6 metrics: w/m/a window, # <pr> ⏎ for a PR timeline ·
87
+ 7 feedback: t focus · a archived · A/x accept/reject a proposal ·
88
+ R then y reverts a feedback-derived commit (local, never pushed) ·
89
+ 8 scan: [ ] unit · p promote · d dismiss · f/F filter · t tier · P plan (space
90
+ toggles a row, ⏎ approves, Esc back) · x stop · D delta re-scan ·
91
+ companion column on every tab: type any unclaimed key (or /) to talk to
92
+ it — every byte reaches pi · wheel over the column, or PgUp/PgDn while
93
+ focused, walks its scrollback ·
94
+ drag its left edge to resize · click it to take the keyboard ·
95
+ board: j/k select · ⏎ focus the PR's pi pane · m quick-message ·
96
+ g github · u unwedge · e events overlay · I set up the repo ·
97
+ Ctrl-q release focus · Ctrl-c ×3 hard-kill the TUI ·
98
+ s reconnect ssh (GCP passphrase, once) · r refresh ·
99
+ ? full key map · q quit
100
+
101
+ troubleshooting:
102
+ ✖ unreachable ssh to the VM failed. The board names WHICH of the
103
+ three usual causes it is and prints the exact command to
104
+ run — a key missing from the agent (ssh-add …), a wedged
105
+ shared master (ssh -O exit …), or a host that is simply
106
+ not answering. Nothing is run for you; s re-checks and
107
+ reports, and offers the one GCP passphrase prompt when a
108
+ key is what is missing
109
+ ▲ DATA STALE ssh works but status.json stopped updating — the
110
+ container is down or the daemon hung (qcr-side:
111
+ ssh <host> sudo docker ps / docker logs)
112
+ keys lost in pi the outer terminal must speak csi-u (kitty/ghostty/
113
+ wezterm/iterm2); running inside an outer tmux needs
114
+ extended-keys passthrough there too
115
+ `;
116
+
117
+ /** Flags the TUI owns; everything else is the (optional) deep-link target. */
118
+ export interface TuiArgs {
119
+ target: string | undefined;
120
+ companion: boolean;
121
+ reload: boolean;
122
+ resumeState: string | undefined;
123
+ help: boolean;
124
+ }
125
+
126
+ export function parseTuiArgs(args: string[]): TuiArgs {
127
+ const { stateFile, rest } = parseResumeArg(args);
128
+ const flags = new Set(rest.filter((a) => a.startsWith("-")));
129
+ return {
130
+ target: rest.find((a) => !a.startsWith("-")),
131
+ companion: !flags.has("--no-companion"),
132
+ reload: !flags.has("--no-reload"),
133
+ resumeState: stateFile,
134
+ help: flags.has("--help") || flags.has("-h"),
135
+ };
136
+ }
137
+
138
+ /**
139
+ * The companion action channel's executor, mapped onto the App's public
140
+ * surface plus the per-tab states the install hooks return. Exported so tests
141
+ * can drive a real App through `processTuiActions`; runTui wires it inside
142
+ * the companionUp block. Actions run instantly — no confirm gate (see
143
+ * src/tui/actions.ts header).
144
+ */
145
+ export function buildTuiDriver(
146
+ app: App,
147
+ learning: LearningTabs,
148
+ metricsView: MetricsView,
149
+ deployState: DeployState,
150
+ ): TuiDriver {
151
+ // One keyboard owner at a time: an action must never type into a focused
152
+ // pi pane or companion column (same rule the mouse/tab handlers follow).
153
+ // A busy App (operator input box open, terminal lent to the `s` passphrase
154
+ // prompt) is refused: a synthesized keystroke would land in the operator's
155
+ // draft or nowhere.
156
+ const prep = (): void => {
157
+ if (app.piFocused()) app.blurPiPane();
158
+ app.sidebar?.blur();
159
+ if (app.isBusy()) throw new Error("operator input open / terminal in use — action refused");
160
+ };
161
+ /** Press a deploy-tab run key twice (arm + confirm) and answer whether a
162
+ * run started: the tab refuses a press with a result on screen, a drain
163
+ * still owned, or a target not yet loaded, and a refused printable would
164
+ * otherwise fall through to the companion column while the action read as
165
+ * done. */
166
+ const startDeployRun = (key: "D" | "S", kind: "deploy" | "stop" | "start"): void => {
167
+ if (deployState.run)
168
+ throw new Error(
169
+ `deploy_tab ${kind} refused — ${deployState.run.running ? "a run is in flight" : "the previous result is still on screen (x closes it)"}`,
170
+ );
171
+ // Begin this action's own arm/confirm pair. An operator's earlier arm
172
+ // would make the first press confirm and the second reach the companion.
173
+ deployState.armedAt = undefined;
174
+ deployState.armedKind = undefined;
175
+ app.pressKey(key);
176
+ app.pressKey(key);
177
+ const started = (): string | undefined => deployState.run?.kind; // read after the presses
178
+ if (started() !== kind)
179
+ throw new Error(
180
+ `deploy_tab ${kind} refused by the tab${app.view.noticeLine ? ` — ${app.view.noticeLine}` : ""}`,
181
+ );
182
+ };
183
+ const driver: TuiDriver = {
184
+ switchTab: (tab) => {
185
+ prep();
186
+ app.tab = tab; // one vocabulary: the verb's tab name IS the App's (src/tui/tabs.ts)
187
+ app.repaint();
188
+ },
189
+ selectPr: (pr) => {
190
+ prep();
191
+ // public App API — selects + refreshes detail; a refusal leaves the tab
192
+ if (!app.selectPr(pr)) throw new Error(`#${pr} is not on the board`);
193
+ app.tab = "board";
194
+ app.repaint();
195
+ },
196
+ metricsWindow: (w) => {
197
+ prep();
198
+ app.tab = "metrics";
199
+ metricsView.drill = undefined;
200
+ metricsView.window = w === "7d" ? "d7" : w === "30d" ? "d30" : "all";
201
+ metricsView.scroll = 0;
202
+ app.repaint();
203
+ },
204
+ metricsPr: (pr) => {
205
+ prep();
206
+ app.tab = "metrics";
207
+ app.repaint();
208
+ // Drive the tab's own PR-entry state machine so the timeline FETCH runs
209
+ // (drillTo is closure-private): # opens entry, digits accumulate, ⏎ drills.
210
+ // The sequence starts from the dashboard: a half-typed operator entry
211
+ // would swallow the "#" and prefix our digits, and an open timeline
212
+ // answers to Esc only, so its "#" would reach the companion column.
213
+ metricsView.prEntry = undefined;
214
+ metricsView.drill = undefined;
215
+ metricsView.scroll = 0;
216
+ app.pressKey("#");
217
+ if (metricsView.prEntry !== "")
218
+ throw new Error(`metrics pr ${pr} refused — the metrics tab did not open its PR entry`);
219
+ for (const d of String(pr)) app.pressKey(d);
220
+ app.pressKey("\r");
221
+ },
222
+ digestDay: (date) => {
223
+ prep();
224
+ app.tab = "digest";
225
+ app.repaint();
226
+ // Page to the day through the tab's own [ / ] keys so content loads.
227
+ // files are date-named ASCENDING (parseDigestList sorts); back counts
228
+ // BACK from the newest file. An unlisted digest is a failed result —
229
+ // including when the listing itself has not happened or failed.
230
+ const files = learning.store.digest.files ?? [];
231
+ const i = files.findIndex((f) => f.startsWith(date));
232
+ if (i < 0) throw new Error(`no digest for ${date}`);
233
+ const target = files.length - 1 - i;
234
+ const delta = target - learning.store.digest.back;
235
+ for (let k = 0; k < Math.abs(delta); k++) app.pressKey(delta > 0 ? "[" : "]");
236
+ },
237
+ memoryView: (opts) => {
238
+ prep();
239
+ app.tab = "memory";
240
+ const mem = learning.memory;
241
+ if (opts.tag !== undefined) setTimelineFilter(mem, opts.tag);
242
+ if (opts.day !== undefined) {
243
+ // The day is a JUMP in the day-grouped timeline: land on its first
244
+ // entry. A day with no entry under the current filter is a failed
245
+ // result, not a silent no-op.
246
+ const ordered = memoryOrder(learning.store.entries, mem.filter);
247
+ const i = ordered.findIndex((e) => e.day === opts.day);
248
+ if (i < 0)
249
+ throw new Error(
250
+ `no memory entries on ${opts.day}${mem.filter ? ` under #${mem.filter}` : ""}`,
251
+ );
252
+ mem.sel = i;
253
+ mem.selKey = entryKey(ordered[i]!);
254
+ }
255
+ app.repaint();
256
+ },
257
+ memoryPin: async (ref, pin) => {
258
+ // The companion may pin or unpin an existing entry, and nothing else
259
+ // about memory rides this channel — the only op built here is `pin` or
260
+ // `unpin` (the companion boundary). A memory write, not a keystroke: no tab
261
+ // switch and no focus change (prep is for synthesized keys). It goes
262
+ // through the one store — the same verb the tab's `p` uses — against
263
+ // the entry as a new read finds it (the tab may never have been
264
+ // opened). That read lists every tree again (the TUI's store keeps no
265
+ // sweep cache; a file the listing shows unchanged may come from its
266
+ // per-file cache), and the write re-reads the file and refuses a stale
267
+ // hash — never applied blind.
268
+ const memory = learning.store.memory;
269
+ const entry = findMemoryEntry(await memory.read(), ref);
270
+ if (!entry) {
271
+ // Not found in a read that did not read everything is not "no such
272
+ // entry": answering that would publish a fact to the companion from
273
+ // no data.
274
+ const unread = memoryRefUnread(storeHealth(memory), ref);
275
+ if (unread)
276
+ throw new Error(
277
+ `memory not fully readable — '${ref}' may be there; nothing was written: ${unread}`,
278
+ );
279
+ throw new Error(`no memory entry '${ref}' — its id or URI, as memory lists it`);
280
+ }
281
+ if (entry.pinned === pin) return `${entry.id} is already ${pin ? "pinned" : "unpinned"}`;
282
+ const res = await memory.apply({
283
+ op: pin ? "pin" : "unpin",
284
+ ...refOf(entry),
285
+ });
286
+ learning.store.reloadMemory();
287
+ if (!res.ok) throw new Error(res.message);
288
+ return res.message;
289
+ },
290
+ setBoardFilter: (f) => {
291
+ prep();
292
+ app.tab = "board";
293
+ app.setBoardFilter(f);
294
+ app.repaint();
295
+ },
296
+ setMemoryFilter: (tag) => {
297
+ prep();
298
+ app.tab = "memory";
299
+ setTimelineFilter(learning.memory, tag);
300
+ app.repaint();
301
+ },
302
+ msgSession: (session, text) => deliverMessage(app, session, text),
303
+ // The companion's tier (rule 8): the same operator route the viewer's input
304
+ // line uses. A refusal or a failed write rejects, so the result file says
305
+ // so; an unconfirmed send resolves, and its label says to check first.
306
+ operatorCommand: async (command) => {
307
+ const send = app.hooks.operatorCommand;
308
+ if (!send) throw new Error("the operator command route is not wired in this TUI");
309
+ const out = await send(command, app);
310
+ if (out.state === "refused" || out.state === "failed") throw new Error(out.label);
311
+ return out.label;
312
+ },
313
+ deployTab: (action) => {
314
+ prep();
315
+ app.tab = "deploy";
316
+ app.repaint();
317
+ if (action === "abort") {
318
+ if (!deployState.run) throw new Error("deploy_tab abort refused — no run in flight");
319
+ app.pressKey("x"); // kills a running run (closes a finished log otherwise)
320
+ return;
321
+ }
322
+ if (action === "deploy") {
323
+ startDeployRun("D", "deploy"); // arm+confirm inside the window = instant
324
+ return;
325
+ }
326
+ // stop/start: the tab's S button computes its own kind from the observed
327
+ // container state; refuse a mismatched request rather than silently doing
328
+ // the opposite — and refuse outright when that state has not been read,
329
+ // so a stop request can never stop a live reviewer on data nobody
330
+ // fetched.
331
+ if (deployState.containerStatus === undefined) {
332
+ throw new Error(
333
+ `deploy_tab ${action} refused — the container's state has not been read yet`,
334
+ );
335
+ }
336
+ const kind = powerActionFor(deployState.containerStatus);
337
+ if (kind !== action)
338
+ throw new Error(
339
+ `deploy_tab ${action} refused — container is ${deployState.containerStatus}`,
340
+ );
341
+ startDeployRun("S", action);
342
+ },
343
+ };
344
+ return driver;
345
+ }
346
+
347
+ /**
348
+ * Injectable ends of the TUI process. Production passes none of them; the
349
+ * startup-contract test passes all four, so it can drive a real `runTui`
350
+ * against fake terminal streams without depending on the operator's
351
+ * `config.toml` and without the process exiting under vitest.
352
+ */
353
+ export interface RunTuiDeps {
354
+ out?: NodeJS.WriteStream;
355
+ input?: NodeJS.ReadStream;
356
+ config?: Config;
357
+ exit?: (code: number) => void;
358
+ }
359
+
360
+ /** First slice of the quit-time wait for an in-flight bring-up. */
361
+ /** Startup steps that exist only for the companion (the memory forward also serves the Memory tab). */
362
+ const COMPANION_ONLY_STEPS = [
363
+ "memory-namespaces",
364
+ "container",
365
+ "rollover",
366
+ "companion-pi",
367
+ ] as const;
368
+
369
+ const BRING_UP_QUIT_GRACE_MS = 250;
370
+
371
+ /** True if the promise settled inside `ms`. The timer is unref'd, so a slow
372
+ * bring-up can never be the thing that holds the process open. */
373
+ async function settledWithin(p: Promise<unknown>, ms: number): Promise<boolean> {
374
+ let timer: NodeJS.Timeout | undefined;
375
+ const timeout = new Promise<false>((resolve) => {
376
+ timer = setTimeout(() => resolve(false), ms);
377
+ timer.unref?.();
378
+ });
379
+ const settled = await Promise.race([p.then(() => true), timeout]);
380
+ if (timer) clearTimeout(timer);
381
+ return settled;
382
+ }
383
+
384
+ /**
385
+ * Clean quit stops the container (conversation + auth survive on volumes).
386
+ * The question is whether THIS start put a container up — true from the
387
+ * moment `ensureUp` returns, not only once the rollover and the pi spawn
388
+ * behind it finished: a quit whose bring-up grace ran out mid-rollover would
389
+ * otherwise leave a freshly started container running. A failed stop is
390
+ * swallowed — leaving it running is harmless, the next start adopts it.
391
+ */
392
+ export async function stopCompanionOnQuit(opts: {
393
+ containerStarted: boolean;
394
+ stop: () => Promise<void>;
395
+ write: (line: string) => void;
396
+ }): Promise<void> {
397
+ if (!opts.containerStarted) return;
398
+ opts.write("qcr tui: stopping the companion container…\n");
399
+ try {
400
+ await opts.stop();
401
+ } catch {
402
+ /* leaving it running is harmless: the next start adopts */
403
+ }
404
+ }
405
+
406
+ /**
407
+ * `qcr tui` — the whole startup, in two phases.
408
+ *
409
+ * Phase 0 is local only: parse, config, wire, and paint. Nothing external —
410
+ * no ssh, no docker, no network, no directory scan — may run before the first
411
+ * frame, and the frame it paints is the real layout with every unfetched value
412
+ * marked unknown. The companion column's width comes from config, the only
413
+ * layout-affecting fact, so nothing jumps when the container later arrives.
414
+ *
415
+ * Phase 1 is the bring-up, streaming in behind that frame and reporting into
416
+ * the companion column: connection triage, memory forward, namespaces,
417
+ * container, day rollover, pi. It never blocks the board, and a step that
418
+ * fails is a degraded mode, never a reason the TUI does not open.
419
+ */
420
+ export async function runTui(argv: string | string[] = [], deps: RunTuiDeps = {}): Promise<void> {
421
+ const args = parseTuiArgs(typeof argv === "string" ? [argv] : argv);
422
+ if (args.help) {
423
+ console.log(TUI_HELP);
424
+ return;
425
+ }
426
+ const target = parseTarget(args.target);
427
+ if (target.kind === "bad") {
428
+ console.error(`qcr tui: ${target.reason}`);
429
+ process.exitCode = 2;
430
+ return;
431
+ }
432
+ const out = deps.out ?? process.stdout;
433
+ const input = deps.input ?? process.stdin;
434
+ const exit = deps.exit ?? ((code: number) => process.exit(code));
435
+ if (!out.isTTY || !input.isTTY) {
436
+ console.error("qcr tui needs an interactive terminal");
437
+ process.exitCode = 2;
438
+ return;
439
+ }
440
+
441
+ // ---- phase 0: local only, ending in the first frame -----------------------
442
+
443
+ const loaded = deps.config ?? loadConfig();
444
+ // In the containerised TUI the companion is a container the host wrapper
445
+ // runs, reached through a shared tmux socket. Its channel dirs are the
446
+ // host's, named by the wrapper in env; nothing here can run docker.
447
+ const socketMode = process.env[TUI_COMPANION_ENV] === TUI_COMPANION_TMUX_SOCKET;
448
+ const full: Config = socketMode
449
+ ? { ...loaded, companion: laptopCompanionConfig(process.env, loaded.companion) }
450
+ : loaded;
451
+ const cfg = full.tui;
452
+ // One transport decision for every remote op — ssh to the VM, or the same
453
+ // commands against local docker on the laptop topology.
454
+ const runner = runnerFor(cfg);
455
+
456
+ const app = new App(cfg, runner, out, input);
457
+
458
+ // The terminal must come back whatever kills us — raw mode + alt screen
459
+ // surviving a crash is exactly the "only closing the terminal works" trap.
460
+ const restoreTerminal = (): void => {
461
+ try {
462
+ if (input.isTTY) input.setRawMode(false);
463
+ } catch {
464
+ /* stdin already gone */
465
+ }
466
+ out.write(MOUSE_OFF + ALT_SCREEN_OFF);
467
+ };
468
+ // A reload under the supervisor exits with RELOAD_EXIT_CODE so the next
469
+ // build takes over this terminal state — the alt screen, mouse reporting
470
+ // and raw mode stay exactly as they are. Restoring here would flash the
471
+ // shell between builds. Every other exit restores.
472
+ process.on("exit", (code) => {
473
+ if (code === RELOAD_EXIT_CODE) return;
474
+ restoreTerminal();
475
+ });
476
+ process.on("SIGINT", () => {
477
+ restoreTerminal();
478
+ exit(130);
479
+ });
480
+ process.on("SIGTERM", () => {
481
+ restoreTerminal();
482
+ exit(143);
483
+ });
484
+ process.on("uncaughtException", (err) => {
485
+ restoreTerminal();
486
+ console.error("qcr tui crashed:", err);
487
+ exit(1);
488
+ });
489
+
490
+ /** Suspend the TUI, run something on the real terminal, come back. */
491
+ const onRealTerminal = (fn: () => void): void => {
492
+ // Quit can land mid-flight (the operator presses `s`, then Ctrl-C twice
493
+ // while the probes run). The terminal is already restored by then, so
494
+ // going back into the alt screen would leave a blank alternate screen with
495
+ // no TUI behind it. Run the work plainly and leave the terminal alone.
496
+ if (app.isStopped()) {
497
+ fn();
498
+ return;
499
+ }
500
+ app.suspend();
501
+ try {
502
+ if (input.isTTY) input.setRawMode(false);
503
+ } catch {
504
+ /* keep going — fn may still work */
505
+ }
506
+ out.write(MOUSE_OFF + ALT_SCREEN_OFF);
507
+ try {
508
+ fn();
509
+ } finally {
510
+ // Deliberate, and NOT safe to "fix": this return also swallows a throw
511
+ // from fn(), which is what we want on the quit path — the alt screen is
512
+ // already gone, and restoring it to report an error would repaint over
513
+ // the shell the operator is back in. Restructuring so the return sits
514
+ // outside the finally would change that, and error propagation on quit
515
+ // is behaviour, not style.
516
+ // eslint-disable-next-line no-unsafe-finally
517
+ if (app.isStopped()) return; // quit happened while fn ran
518
+ if (input.isTTY) input.setRawMode(true);
519
+ out.write(ALT_SCREEN_ON + CLEAR + MOUSE_ON);
520
+ app.resume();
521
+ }
522
+ };
523
+ const { DurableDeployProcess } = await import("./deploy-process.js");
524
+ const durableDeploy = new DurableDeployProcess(sandboxRoot());
525
+ app.hooks.deployRun = (_theApp, runId, version, onData, onExit) =>
526
+ durableDeploy.startOrAttach(runId, version, onData, onExit);
527
+ app.view.repo = full.github.repo;
528
+ const { spawn } = await import("node:child_process");
529
+ const { openUrlDetached } = await import("./openurl.js");
530
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import("node:fs");
531
+ app.hooks.openUrl = (url) => openUrlDetached(url, (line) => app.notice(line));
532
+ const marker = join(sandboxRoot(), "state", "tui-help-shown");
533
+ app.hooks.helpSeen = {
534
+ get: () => existsSync(marker),
535
+ set: () => {
536
+ mkdirSync(join(sandboxRoot(), "state"), { recursive: true });
537
+ writeFileSync(marker, new Date().toISOString());
538
+ },
539
+ };
540
+
541
+ // The companion column is layout: its width depends on nothing but the
542
+ // terminal, so wiring it here — before anything asks docker whether the
543
+ // container is running — is what makes the first frame the real one. The
544
+ // sidebar is only `available()` once the container answers, and the
545
+ // container fills a column that was already the right size.
546
+ const { CompanionSidebar, restorePreferredSidebarWidth } = await import("./sidebar.js");
547
+ const { CompanionLifecycle, realCompanionFs, realDockerRunner, tmuxSocketPath } =
548
+ await import("./companion.js");
549
+ const { NativeCompanion, SelectingCompanion, nativeAgentDir, probeDocker, UNSANDBOXED_WARNING } =
550
+ await import("./nativecompanion.js");
551
+ const { configHome: companionConfigHome } = await import("../install.js");
552
+ const { MEMORY_OFF_NO_DOCKER } = await import("../memory/health.js");
553
+ // Is Docker answering here? Asked once, in the bring-up — never before the
554
+ // first frame — and shared by the companion's choice and the memory forward.
555
+ let dockerProbe: Promise<boolean> | undefined;
556
+ const dockerUp = (): Promise<boolean> =>
557
+ (dockerProbe ??= probeDocker(realDockerRunner).catch(() => false));
558
+ // Docker answering → the container companion; else the native one on this
559
+ // host, with the same guards and an "unsandboxed" warning
560
+ // (nativecompanion.ts). The containerised TUI keeps its wrapper-run
561
+ // container: it has no Docker of its own to ask.
562
+ const containerLifecycle = new CompanionLifecycle(full.companion, realDockerRunner);
563
+ const selecting = socketMode
564
+ ? undefined
565
+ : new SelectingCompanion(containerLifecycle, realDockerRunner, {
566
+ dockerUp,
567
+ makeNative: () =>
568
+ new NativeCompanion(containerLifecycle, {
569
+ appRoot: sandboxRoot(),
570
+ agentDir: nativeAgentDir(),
571
+ configHome: companionConfigHome(),
572
+ }),
573
+ onNative: () => {
574
+ app.sidebar?.setHeader({ unsandboxed: true });
575
+ app.notice(`companion: ${UNSANDBOXED_WARNING}`);
576
+ },
577
+ });
578
+ // The one seam the lifecycle, the column and the rollover reach the companion
579
+ // through: docker exec, the native host's tmux, or tmux -S over the shared
580
+ // socket.
581
+ const companionDocker = selecting
582
+ ? selecting.runner
583
+ : tmuxSocketRunner(tmuxSocketPath(full.companion), full.companion.container_name);
584
+ const companion: import("./nativecompanion.js").CompanionHost =
585
+ selecting ?? laptopLifecycle(process.env, companionDocker, loaded.companion, realCompanionFs);
586
+ // The companion's read-only GitHub token, minted here from the App
587
+ // credential this host holds and handed over as an env file in a dir the
588
+ // container mounts read-only. Refreshed while the TUI runs (it lives an
589
+ // hour); constructed now, first minted in the bring-up — it is network.
590
+ const { CompanionGithubToken, tokenOutcomeLine } = await import("./ghread.js");
591
+ const { companionGithubDir } = await import("../install.js");
592
+ const ghToken = new CompanionGithubToken({
593
+ dir: companionGithubDir(socketMode ? laptopInstallEnv(process.env) : undefined),
594
+ repo: full.github.repo,
595
+ secretsDir: full.github.secrets_dir,
596
+ });
597
+ let configMirrorTimer: NodeJS.Timeout | undefined;
598
+ const CONFIG_MIRROR_EVERY_MS = 30_000;
599
+ const companionOn = args.companion && full.companion.enabled;
600
+ let companionUp = false;
601
+ /** `ensureUp` returned: a container is running on this start's account,
602
+ * whether or not the rollover and pi spawn behind it ever finish. */
603
+ let companionContainerStarted = false;
604
+ let companionError: string | undefined;
605
+ // The column exists in every configuration: `--no-companion` and
606
+ // `[companion] enabled = false` decide what runs and what the column says,
607
+ // never whether the right half of the screen is there.
608
+ app.sidebar = new CompanionSidebar(full.companion, companionDocker, {
609
+ available: () => companionUp,
610
+ onUpdate: () => app.repaint(),
611
+ // pi's `/copy` inside the container lands in a tmux paste buffer; the
612
+ // column forwards it here, to the host clipboard.
613
+ onCopy: (text) => app.copyText(text, "the companion"),
614
+ });
615
+ if (!companionOn)
616
+ app.sidebar.setOff(args.companion ? "[companion] enabled = false" : "--no-companion");
617
+
618
+ // Bring-up progress goes in that column: it is empty at this moment and
619
+ // already the right shape, so it costs no new surface — and the lines are
620
+ // never written to the normal screen, where the alt screen's clear would
621
+ // erase them (a visible flicker). With the companion off the same column
622
+ // carries the skipped steps, and stays as the place that says why it is off.
623
+ const { StartupProgress } = await import("./startup.js");
624
+ const progress = new StartupProgress(undefined, {
625
+ onUpdate: (p) => {
626
+ // Kept after the bring-up settles on purpose: a `✗ companion container ·
627
+ // docker is not running` is the most useful thing the column can show,
628
+ // and clearing it would delete the diagnosis a second after it appeared.
629
+ // The hand-off is the sidebar's `mirrored` latch — the checklist yields
630
+ // to pi's first real capture and never comes back.
631
+ app.sidebar?.setStartup(p.lines());
632
+ app.repaint();
633
+ },
634
+ });
635
+ app.sidebar.setStartup(progress.lines());
636
+
637
+ // No attach hooks: the TUI never attaches to a VM session — see AppHooks.
638
+ const { installMessageHooks } = await import("./msg.js");
639
+ const { LiveControlSender, installLiveControlHooks } = await import("./livecontrol.js");
640
+ // One control sender for the viewer's input line and for `m` on a worker PR.
641
+ const control = new LiveControlSender(app.cfg, app.ssh);
642
+ installLiveControlHooks(app, control);
643
+ installMessageHooks(app, undefined, control);
644
+ const { installUnwedgeHooks } = await import("./unwedge.js");
645
+ installUnwedgeHooks(app);
646
+ // `I` asks the companion to run its setup skill — one inject into
647
+ // its own column (drained now, or at its next start) — and hands it the
648
+ // keyboard. The same ask `qcr setup` makes from a shell.
649
+ const { requestSetup, commitRequestedProfiles, commitLine } = await import("./setupentry.js");
650
+ const { companionInjectDir } = await import("./companion.js");
651
+ app.hooks.setup = (theApp) => {
652
+ try {
653
+ requestSetup({ injectDir: companionInjectDir(full.companion) });
654
+ } catch (err) {
655
+ theApp.notice(`setup: the request could not be written — ${String(err)}`);
656
+ return;
657
+ }
658
+ theApp.notice(
659
+ companionUp
660
+ ? "setup: asked the companion — it takes it from here"
661
+ : "setup: asked — the companion picks it up when it starts",
662
+ );
663
+ theApp.openCompanion();
664
+ };
665
+ // The profile commits setup asked for, carried out here, on the config
666
+ // mirror's timer — the container cannot see the config home's .git (the
667
+ // home holds secrets/, so it is never mounted whole).
668
+ const commitProfiles = (): void => {
669
+ for (const r of commitRequestedProfiles()) app.notice(commitLine(r));
670
+ };
671
+ // The four learning surfaces — [2] memory, [3] digest, [4] tickets,
672
+ // [7] feedback — over one store. Accept-verdict git actions run against
673
+ // this working copy (the canonical repo); the [sink] config decides where
674
+ // the defect auto-filer writes (wayfinder tracker, gated github command
675
+ // channel, or none) and which effort the ticket projection reads.
676
+ const { installLearningTabs } = await import("./learning.js");
677
+ const { execFile } = await import("node:child_process");
678
+ const gitRun = (cmd: string, args2: string[], stdin?: string): Promise<string> =>
679
+ new Promise((res, rej) => {
680
+ const child = execFile(cmd, args2, { cwd: sandboxRoot() }, (err, stdout, stderr) => {
681
+ if (err) rej(new Error(stderr.trim() || err.message));
682
+ else res(stdout);
683
+ });
684
+ if (stdin !== undefined) child.stdin?.end(stdin);
685
+ else child.stdin?.end();
686
+ });
687
+ // One memory store for the whole TUI: OpenViking on the VM, reached over
688
+ // the memory forward's loopback listener, holding both user keys. Every
689
+ // memory surface in the TUI shares this instance — a second one would mean
690
+ // a second sweep and a second health opinion.
691
+ const { tuiMemoryStore, tuiMemoryBaseUrl } = await import("./memory.js");
692
+ // The companion namespace's retired URIs, published into the state home
693
+ // the companion mounts — its recall excludes them.
694
+ const { MEMORY: COMPANION_MEMORY } = await import("./companion.js");
695
+ const { stateHome: memStateHome } = await import("../install.js");
696
+ const memStore = tuiMemoryStore({
697
+ retiredFiles: { companion: join(memStateHome(), COMPANION_MEMORY.retiredFile) },
698
+ });
699
+ // The TUI brings the memory forward up itself, so the Memory tab works
700
+ // without the operator installing the systemd unit first. It adopts a
701
+ // forward that is already listening (the unit, or another qcr tui) and stops
702
+ // only one it started — see memforward.ts for why that split matters.
703
+ // Constructed here, started in the bring-up: `ensure()` probes over HTTP.
704
+ const { MemoryForward, forwardUsable, tuiMemoryContainer } = await import("./memforward.js");
705
+ const memForward = new MemoryForward({
706
+ baseUrl: tuiMemoryBaseUrl(),
707
+ repoRoot: sandboxRoot(),
708
+ // The forward follows the same [tui] target knob — local mode reaches the
709
+ // daemon container's loopback engine via plain docker exec.
710
+ local: transportOf(cfg) === "local",
711
+ sudoDocker: cfg.sudo_docker,
712
+ sshHost: cfg.ssh_host,
713
+ // OpenViking's own container: the memory sidecar once the VM flips
714
+ // `[openviking] endpoint = "remote"`, else the daemon container.
715
+ container: tuiMemoryContainer(cfg),
716
+ // A local engine runs in Docker; with none here, memory is off — said
717
+ // once as the forward's state, never retried as a failure.
718
+ ...(!socketMode && transportOf(cfg) === "local"
719
+ ? {
720
+ offReason: async () => ((await dockerUp()) ? undefined : MEMORY_OFF_NO_DOCKER),
721
+ }
722
+ : {}),
723
+ log: (line) => app.notice(line),
724
+ });
725
+ const { buildIssueSink, ticketProjection } = await import("./issuesink.js");
726
+ const { readEffortTickets } = await import("./trackertickets.js");
727
+ const projection = ticketProjection(full);
728
+ const { RealDeployFetcher, installDeployHooks, powerSpawnArgv, realLocalRunner } =
729
+ await import("./deploytab.js");
730
+ const { FileDeployCeremonyStore } = await import("./deploy-ceremony.js");
731
+ const localRunner = realLocalRunner(sandboxRoot());
732
+ // The change register — every feedback-derived commit (identified by its
733
+ // `Ratified-in-session:` trailer), newest first, with a one-key revert on
734
+ // the feedback tab. It reads and writes the local checkout only, through
735
+ // the same read-only local-git seam the deploy tab uses; nothing here
736
+ // touches the VM, GitHub or the gate. Persisted under state/ so the list is
737
+ // on screen before git has been asked anything.
738
+ const { ChangeLog, ChangeRegisterStore } = await import("./changelog-store.js");
739
+ const { stateHome } = await import("../install.js");
740
+ const { PIN_PROPOSALS_FILE, parsePinProposals } = await import("../setup/pins.js");
741
+ const learning = installLearningTabs(app, {
742
+ memory: memStore,
743
+ // The entries setup proposed to pin, marked ☆ on the memory tab.
744
+ readPinProposals: () => {
745
+ try {
746
+ return parsePinProposals(readFileSync(join(stateHome(), PIN_PROPOSALS_FILE), "utf8"));
747
+ } catch (err) {
748
+ return (err as NodeJS.ErrnoException).code === "ENOENT"
749
+ ? { state: "absent" }
750
+ : { state: "unreadable", error: String(err) };
751
+ }
752
+ },
753
+ forward: memForward,
754
+ gitRun,
755
+ repoRoot: sandboxRoot(),
756
+ configPath: liveConfigPath(),
757
+ sink: buildIssueSink(full, app.ssh, memStore),
758
+ issueLabel: full.github.issue_label,
759
+ eod: { time: full.eod.time, timezone: full.eod.timezone },
760
+ changes: new ChangeLog(
761
+ new ChangeRegisterStore(join(sandboxRoot(), "state", "changes")),
762
+ localRunner,
763
+ ),
764
+ ...(projection
765
+ ? {
766
+ readTickets: () => readEffortTickets(projection.root, projection.effort),
767
+ ticketsEffort: projection.effort,
768
+ }
769
+ : {}),
770
+ });
771
+ app.hooks.powerRun = (_theApp, action, onData, onExit) => {
772
+ // docker stop/start on the VM (or local docker in local mode), streamed
773
+ // into the tab like a deploy.
774
+ const cmd = powerSpawnArgv(cfg, action);
775
+ const child = spawn(cmd[0]!, cmd.slice(1), { stdio: ["ignore", "pipe", "pipe"] });
776
+ onData(`$ ${cmd.join(" ")}\n`);
777
+ child.stdout.on("data", (b: Buffer) => onData(b.toString("utf8")));
778
+ child.stderr.on("data", (b: Buffer) => onData(b.toString("utf8")));
779
+ child.on("close", (code) => onExit(code));
780
+ child.on("error", (err) => {
781
+ onData(`\nspawn failed: ${String(err)}\n`);
782
+ onExit(1);
783
+ });
784
+ return {
785
+ kill: () => {
786
+ try {
787
+ child.kill("SIGTERM");
788
+ } catch {
789
+ /* already gone */
790
+ }
791
+ },
792
+ };
793
+ };
794
+
795
+ const deployState = installDeployHooks(
796
+ app,
797
+ new RealDeployFetcher(cfg, runner, localRunner),
798
+ undefined,
799
+ undefined,
800
+ new FileDeployCeremonyStore(join(sandboxRoot(), "state", "tui-deploy-ceremony.json")),
801
+ );
802
+ const { installMetricsHooks, localHeadProbe } = await import("./metricstab.js");
803
+ // The metrics OPERATIONS card answers "deploy needed?" through the same
804
+ // read-only local-git seam the deploy tab uses.
805
+ const metricsView = installMetricsHooks(app, undefined, localHeadProbe(localRunner));
806
+ // `[8] scan` — the scan report, read from the state home the
807
+ // companion writes. Its two request files (the stage-2 start, the stop)
808
+ // are answered by the companion's scan belt; promote, dismiss and delta go
809
+ // to the companion as the operator's asks, through the setup entry's inject
810
+ // channel.
811
+ const { installScanTab, ScanReportStore } = await import("./scantab.js");
812
+ const { writeCompanionAsk } = await import("./setupentry.js");
813
+ installScanTab(app, {
814
+ store: new ScanReportStore(stateHome()),
815
+ ask: (text, verb) => {
816
+ try {
817
+ writeCompanionAsk({
818
+ injectDir: companionInjectDir(full.companion),
819
+ suffix: `scan-${verb}`,
820
+ text,
821
+ });
822
+ } catch (err) {
823
+ return { ok: false, line: `✖ scan ${verb}: the ask could not be written — ${String(err)}` };
824
+ }
825
+ return {
826
+ ok: true,
827
+ line: companionUp
828
+ ? `scan ${verb}: asked the companion — the report updates when it has acted`
829
+ : `scan ${verb}: asked — the companion picks it up when it starts`,
830
+ };
831
+ },
832
+ companionUp: () =>
833
+ !companionOn || companionError !== undefined ? false : companionUp ? true : undefined,
834
+ });
835
+
836
+ // Finished-round evidence, copied down from the VM into a local store the
837
+ // companion reads read-only. Its dir is created in the bring-up,
838
+ // before the container starts — the mount source must exist first, or docker
839
+ // invents it root-owned and the copier can never write into it.
840
+ const {
841
+ EvidenceCopier,
842
+ EvidenceStore,
843
+ SWEEP_PROGRESS_FILE,
844
+ SweepProgressFile,
845
+ evidenceWhyIntervalMs,
846
+ } = await import("./evidence.js");
847
+ const evidence = new EvidenceStore(full.companion.evidence_dir);
848
+
849
+ // Ambient feed + alert pipeline + startup healing. All of it hangs
850
+ // off companionOn, so --no-companion really means "none of this runs" —
851
+ // the column itself is not part of "this": it is layout, and it is always
852
+ // there.
853
+ const { companionStarter, installCompanionTabHooks } = await import("./companiontab.js");
854
+
855
+ // The companion sidebar is the only companion surface. Mirror + send go
856
+ // through the same local docker seam the lifecycle uses; refresh piggybacks
857
+ // the app poll (the sidebar's own 500ms timer only runs focused/after-send).
858
+ chainOnPoll(app, (a) => void a.sidebar?.refresh());
859
+ // The header carries what the sidebar cannot know for itself: whose day it
860
+ // is (the companion rolls over on the ops-timezone boundary, so a stale day
861
+ // in the header is the first visible sign a rollover was missed) and whether
862
+ // memory is actually reachable — a companion talking with no memory behind
863
+ // it looks identical to a healthy one until you ask it something.
864
+ const { todayDate } = await import("./rollover.js");
865
+ chainOnPoll(app, (a) => {
866
+ const fwd = memForward.view();
867
+ // A native companion has no memory wiring: no Docker, no sidecar.
868
+ const noDocker =
869
+ selecting?.kind === "native" || (fwd.state === "off" && fwd.detail === MEMORY_OFF_NO_DOCKER);
870
+ a.sidebar?.setHeader({
871
+ day: todayDate(new Date(), process.env, full.ops.timezone),
872
+ memory: noDocker
873
+ ? "off"
874
+ : fwd.state === "failed"
875
+ ? "degraded"
876
+ : fwd.state === "off"
877
+ ? "off"
878
+ : "ok",
879
+ memoryNote: noDocker ? MEMORY_OFF_NO_DOCKER : undefined,
880
+ });
881
+ });
882
+
883
+ // The evidence copier rides the existing poll — no second scheduler,
884
+ // and deliberately not awaited. `sweep()` is re-entrant-safe (a sweep in
885
+ // flight makes the next call a no-op) and every remote read is killed at
886
+ // `evidence_timeout_s`, so a wedged ssh control master stalls the copier and
887
+ // nothing else: the poll keeps polling and the board keeps painting. Runs
888
+ // whether or not the companion is up — the record is worth keeping either
889
+ // way, and it is what the companion will read when it next starts.
890
+ //
891
+ // Its per-PR progress lives beside the TUI's other state files,
892
+ // so a re-exec (every local build) resumes instead of re-running `qcr why`
893
+ // for every PR, and its `why` execs are paced by `evidence_why_interval_s`
894
+ // and held while the board's last status shows a drain or a stale heartbeat.
895
+ const evidenceCopier = new EvidenceCopier(cfg, evidence, runner, {
896
+ sessionDir: full.pi.session_dir,
897
+ timeoutMs: Math.max(5, cfg.evidence_timeout_s) * 1000,
898
+ whyIntervalMs: evidenceWhyIntervalMs(cfg),
899
+ progress: new SweepProgressFile(join(sandboxRoot(), "state", SWEEP_PROGRESS_FILE)),
900
+ onNotice: (text) => app.notice(text),
901
+ });
902
+ chainOnPoll(app, (a) => {
903
+ void evidenceCopier.sweep(a.view.snapshot).catch(() => {
904
+ /* best-effort — a failed copy is retried on the next poll, never fatal */
905
+ });
906
+ });
907
+
908
+ // The durable candidate list behind the `survey` skill. Derived from what
909
+ // is already on this disk — the poll snapshot plus the evidence store's
910
+ // round manifests — so it costs no remote reads and rides the same poll. It lands
911
+ // in the evidence dir, which is already mounted `/evidence:ro`, so the
912
+ // companion reads `/evidence/candidates.json` over an existing channel and
913
+ // cannot write to it.
914
+ //
915
+ // Judged-ness comes back the other way, out of the `#adjudication` memory
916
+ // entries `adjudicate` already mandates — one record, one direction, and no
917
+ // second write path from the companion. It is read at a slow cadence because
918
+ // memory is an HTTP hop and the list only needs it to stop re-offering
919
+ // something settled.
920
+ const { CandidateStore, CandidateSweeper, adjudicationsFrom } = await import("./candidates.js");
921
+ const candidates = new CandidateStore(full.companion.evidence_dir);
922
+ let adjudicationsAt = 0;
923
+ let adjudicationsCache = new Map<number, string>();
924
+ const candidateSweeper = new CandidateSweeper(candidates, {
925
+ evidenceDir: evidence.dir,
926
+ onNotice: (text) => app.notice(text),
927
+ adjudications: async () => {
928
+ // 5 minutes: an adjudication is a human-scale event, and a stale map only
929
+ // re-offers something already judged for one more cycle.
930
+ if (Date.now() - adjudicationsAt < 300_000) return adjudicationsCache;
931
+ adjudicationsAt = Date.now();
932
+ adjudicationsCache = adjudicationsFrom(await memStore.read());
933
+ return adjudicationsCache;
934
+ },
935
+ });
936
+ chainOnPoll(app, (a) => {
937
+ void candidateSweeper.sweep(a.view.snapshot).catch(() => {
938
+ /* best-effort — the survey is rebuilt from scratch on the next poll */
939
+ });
940
+ });
941
+
942
+ // A reviewer-memory incident older than
943
+ // `[memory] incident_ticket_after_min` becomes one issue-tracker ticket
944
+ // (actor qcr-tui, deduped by the incident's start), read from status.json —
945
+ // never from memory, which is the thing that is down (the `#bug` filer reads
946
+ // memory, so it could never report this). Independent of the companion:
947
+ // the board banner and this ticket reach a person with the column off.
948
+ const { maybeFileCurationIncident, maybeFileMemoryIncident, newIncidentFilerMemo } =
949
+ await import("./memincident.js");
950
+ const incidentMemo = newIncidentFilerMemo();
951
+ const incidentTracker =
952
+ full.sink.kind === "wayfinder" && full.sink.root && full.sink.effort && full.sink.parent
953
+ ? { root: full.sink.root, effort: full.sink.effort, parent: full.sink.parent }
954
+ : undefined;
955
+ /** One incident filer's notices: a filing, a missing tracker, a failure —
956
+ * each distinct line once, so a tracker that keeps failing does not repaint
957
+ * the notice every poll. */
958
+ const incidentNotifier = (
959
+ what: string,
960
+ ): ((a: App, out: ReturnType<typeof maybeFileMemoryIncident>) => void) => {
961
+ let last = "";
962
+ return (a, out) => {
963
+ const line =
964
+ out.kind === "filed"
965
+ ? `${what} filed as ${out.ticketId} (${out.file})`
966
+ : out.kind === "no-tracker" || out.kind === "failed"
967
+ ? out.line
968
+ : "";
969
+ if (line !== "" && line !== last) {
970
+ last = line;
971
+ a.notice(line);
972
+ }
973
+ };
974
+ };
975
+ const incidentNotice = incidentNotifier("memory incident");
976
+ chainOnPoll(app, (a) => {
977
+ const out = maybeFileMemoryIncident(incidentTracker, a.view.snapshot?.memory, {
978
+ fresh: isFreshHealth(a.view.health),
979
+ now: new Date(),
980
+ afterMs: full.memory.incident_ticket_after_min * 60_000,
981
+ memo: incidentMemo,
982
+ });
983
+ incidentNotice(a, out);
984
+ });
985
+ // The learner's EOD curation that did not reach memory (no receipt, or
986
+ // lessons memory does not hold) — the same person path: one
987
+ // ticket per curation incident (its oldest unresolved failure) older than
988
+ // the same threshold, from status.json `memory.curation`.
989
+ const curationMemo = newIncidentFilerMemo();
990
+ const curationNotice = incidentNotifier("EOD curation incident");
991
+ chainOnPoll(app, (a) => {
992
+ const out = maybeFileCurationIncident(incidentTracker, a.view.snapshot?.memory, {
993
+ fresh: isFreshHealth(a.view.health),
994
+ now: new Date(),
995
+ afterMs: full.memory.incident_ticket_after_min * 60_000,
996
+ memo: curationMemo,
997
+ });
998
+ curationNotice(a, out);
999
+ });
1000
+
1001
+ // ---- the connection triage ------------------------------------------------
1002
+ //
1003
+ // The TUI does the three-way ssh triage behind the first frame:
1004
+ // `ensureSshMaster` before the alt screen would be both a network call
1005
+ // gating the first paint and, on a bad day, an interactive prompt. Nothing
1006
+ // here is self-healing by design; the TUI reports the cause and the exact
1007
+ // command, and `s` re-runs the check.
1008
+ const { diagnosisLine, triageConnection } = await import("./sshtriage.js");
1009
+ const applyDiagnosis = (d: ConnectionDiagnosis): void => {
1010
+ app.view.connection = d;
1011
+ if (d.kind === "ok" || d.kind === "local") progress.done("ssh", d.cause);
1012
+ else progress.fail("ssh", diagnosisLine(d));
1013
+ app.notice(d.kind === "ok" || d.kind === "local" ? `✓ ${d.cause}` : `✖ ${diagnosisLine(d)}`);
1014
+ app.repaint();
1015
+ };
1016
+ // `s` starts its own async chain, independent of the bring-up. Tracked for
1017
+ // the same reason: quit must not tear the terminal down underneath it.
1018
+ let pendingReconnect: Promise<void> = Promise.resolve();
1019
+ app.hooks.reconnect = () => {
1020
+ progress.begin("ssh", "re-checking");
1021
+ app.notice("checking the connection to the VM…");
1022
+ pendingReconnect = (async () => {
1023
+ let d = await triageConnection(cfg);
1024
+ // A missing key is the one class the operator can clear from here: `s`
1025
+ // is them asking for the single native passphrase prompt. A wedged
1026
+ // master or a dead host is reported with its command instead — prompting
1027
+ // against either would just hang.
1028
+ if (d.kind === "no-agent-key" || d.kind === "unknown") {
1029
+ onRealTerminal(() => {
1030
+ ensureSshMaster(cfg);
1031
+ });
1032
+ d = await triageConnection(cfg);
1033
+ }
1034
+ applyDiagnosis(d);
1035
+ // Awaited, not fired and forgotten: it is part of "re-run the check and
1036
+ // report the new outcome", and quit waits for this chain.
1037
+ await app.poller.tick();
1038
+ })().catch((err: unknown) =>
1039
+ app.notice(`✖ ssh check failed: ${String(err instanceof Error ? err.message : err)}`),
1040
+ );
1041
+ };
1042
+
1043
+ // ---- the bring-up: everything external, behind the first frame ------------
1044
+
1045
+ const startCompanion = async (): Promise<void> => {
1046
+ try {
1047
+ // Before the container, never after: the companion's pi extension picks
1048
+ // the user namespace it writes into by listing `viking://user/`, and
1049
+ // caches that choice for the life of the process. A namespace whose
1050
+ // directory does not exist yet is invisible to it, so it falls back to
1051
+ // `user/default` and every write is refused, reported only as "access
1052
+ // denied". Needs the forward up, so it is awaited here.
1053
+ progress.begin("memory-forward");
1054
+ const fwd = await memForward.ensure();
1055
+ if (fwd.state === "failed") progress.fail("memory-forward", fwd.detail);
1056
+ else progress.done("memory-forward", fwd.state === "off" ? fwd.detail : fwd.state);
1057
+ if (forwardUsable(fwd)) {
1058
+ progress.begin("memory-namespaces");
1059
+ const notes = await memStore.ensureNamespaces();
1060
+ for (const note of notes) app.notice(`memory: ${note}`);
1061
+ progress.done(
1062
+ "memory-namespaces",
1063
+ notes.length > 0 ? `${notes.length} note${notes.length === 1 ? "" : "s"}` : "ready",
1064
+ );
1065
+ } else {
1066
+ progress.skip("memory-namespaces", fwd.state === "off" ? fwd.detail : "forward down");
1067
+ }
1068
+ progress.begin("container");
1069
+ // Before the container, so a cold start finds the token already there.
1070
+ // Total: no credential, or GitHub down, is a note — never a failed start.
1071
+ const gh = await ghToken.refresh();
1072
+ progress.note("container", tokenOutcomeLine(gh));
1073
+ app.notice(`companion: ${tokenOutcomeLine(gh)}`);
1074
+ ghToken.start((o) => {
1075
+ if (o.kind === "failed") app.notice(`companion: ${tokenOutcomeLine(o)}`);
1076
+ });
1077
+ // The companion's read-only mirror of the live config — a copy, not a
1078
+ // file bind, so a config.toml replaced by rename is still seen (see
1079
+ // CONTAINER.configLive). Kept in step on the same timer as the token.
1080
+ const syncConfig = (): void => {
1081
+ const m = companion.syncConfigMirror();
1082
+ if (typeof m === "object")
1083
+ app.notice(`companion: config mirror NOT refreshed — ${m.failed}`);
1084
+ commitProfiles();
1085
+ };
1086
+ syncConfig();
1087
+ configMirrorTimer ??= setInterval(syncConfig, CONFIG_MIRROR_EVERY_MS);
1088
+ configMirrorTimer.unref();
1089
+ await companion.ensureUp((line) => {
1090
+ progress.note("container", line);
1091
+ app.notice(`companion: ${line}`);
1092
+ });
1093
+ companionContainerStarted = true;
1094
+ progress.done("container");
1095
+ // The container's memory network exists now: a forward we started
1096
+ // before it (a cold host) binds the bridge address it could not.
1097
+ await memForward.rebindMissing();
1098
+ // Daily rollover — strictly between "container is up" and
1099
+ // "pi is spawned": on the first start of a new ops day it archives
1100
+ // yesterday, kills the session and queues the opening context, so the
1101
+ // spawnPi below births the fresh conversation that reads it.
1102
+ progress.begin("rollover");
1103
+ const { CompanionRollover } = await import("./rollover.js");
1104
+ const { latestDigestHeadline } = await import("../digest.js");
1105
+ const rollover = new CompanionRollover(full.companion, companionDocker, {
1106
+ statePath: join(companionStateDir, "rollover.json"),
1107
+ policyStatePath,
1108
+ templatePath: join(sandboxRoot(), "prompts", "companion-day-open.md"),
1109
+ injectDir: companion.hostInjectDir(),
1110
+ localDigestHeadline: () => latestDigestHeadline(),
1111
+ ownerName: full.owner.name,
1112
+ timeZone: full.ops.timezone,
1113
+ // The first-run interview — the marker path is the setup wizard's
1114
+ // contract; the template makes it work without the wizard too.
1115
+ firstRunTemplatePath: join(sandboxRoot(), "prompts", "companion-first-run.md"),
1116
+ firstRunMarkerPath: join(companionStateDir, "first-run.json"),
1117
+ });
1118
+ const report = await rollover.run();
1119
+ if (report.deferred) {
1120
+ // The container could not be read, so the day is deliberately not
1121
+ // recorded and yesterday's conversation continues. Loud, because a
1122
+ // rollover that silently did not happen reads as one that did.
1123
+ app.notice(`companion: day rollover DEFERRED — ${report.deferred}`);
1124
+ } else if (report.stoodDown) {
1125
+ app.notice(`companion: ${report.stoodDown}`);
1126
+ } else if (report.verdict.kind === "rollover") {
1127
+ app.notice(
1128
+ `companion: new day ${report.verdict.today} — fresh conversation (${report.steps.length} steps)`,
1129
+ );
1130
+ }
1131
+ for (const warning of report.warnings ?? []) app.notice(`companion: ${warning}`);
1132
+ if (report.interviewFile) {
1133
+ app.notice(
1134
+ "companion: first start on this installation — greeting + calibration interview queued",
1135
+ );
1136
+ }
1137
+ progress.done(
1138
+ "rollover",
1139
+ report.deferred
1140
+ ? `deferred — ${report.deferred}`
1141
+ : report.stoodDown
1142
+ ? "rolled over by another start"
1143
+ : report.verdict.kind === "rollover"
1144
+ ? `new day ${report.verdict.today}`
1145
+ : "same day",
1146
+ );
1147
+ progress.begin("companion-pi");
1148
+ await companion.spawnPi();
1149
+ progress.done("companion-pi");
1150
+ companionUp = true;
1151
+ companionError = undefined;
1152
+ app.repaint();
1153
+ } catch (err) {
1154
+ companionError = String(err instanceof Error ? err.message : err);
1155
+ for (const key of ["memory-forward", ...COMPANION_ONLY_STEPS] as const) {
1156
+ const step = progress.steps().find((s) => s.key === key);
1157
+ if (step?.state === "running") progress.fail(key, companionError);
1158
+ else if (step?.state === "pending") progress.skip(key, "companion unavailable");
1159
+ }
1160
+ app.notice(`companion unavailable: ${companionError}`);
1161
+ }
1162
+ };
1163
+ const companionStateDir = join(sandboxRoot(), "state", "companion");
1164
+ const policyStatePath = join(companionStateDir, "alert-policy.json");
1165
+
1166
+ // Boot and retry share ONE start: the channels below are wired after the
1167
+ // first start that leaves the companion up, whichever path that was.
1168
+ const startAndWire = companionStarter({
1169
+ start: startCompanion,
1170
+ up: () => companionUp,
1171
+ wire: () => wireCompanionChannels(),
1172
+ });
1173
+ installCompanionTabHooks(app, {
1174
+ available: () => companionUp,
1175
+ failure: () => companionError,
1176
+ retry: companionOn ? startAndWire : undefined,
1177
+ });
1178
+
1179
+ /** Everything the companion feed / alert pipeline needs, once it is up. */
1180
+ const wireCompanionChannels = async (): Promise<void> => {
1181
+ const { FeedWriter, buildTuiView } = await import("./feed.js");
1182
+ const feed = new FeedWriter(full.companion.feed_dir);
1183
+
1184
+ // Publish the operator's view: the last painted frame plus the derived
1185
+ // state behind it. Two triggers, one throttle: the poll below (so it
1186
+ // keeps up with the daemon) and every repaint (so it keeps up with the
1187
+ // operator's keystrokes — "what is on their screen" is only true if it
1188
+ // tracks the screen). Files only: this never injects a companion turn,
1189
+ // and the companion reads it on demand through the `tui_*` tools.
1190
+ const publishView = (): void => {
1191
+ try {
1192
+ // One read of the frame for both: the view's screen metadata (rows,
1193
+ // chars, truncation) describes the text written beside it.
1194
+ const screen = app.screenText();
1195
+ feed.writeView(
1196
+ buildTuiView({
1197
+ now: new Date(),
1198
+ screen,
1199
+ tab: app.tab,
1200
+ snapshot: app.view.snapshot,
1201
+ health: app.view.health,
1202
+ selectedPr: app.currentPr(),
1203
+ panePr: app.view.piPr,
1204
+ paneFocused: app.view.piFocus === true,
1205
+ notice: app.view.noticeLine,
1206
+ deploy: deployState,
1207
+ tuiMemoryRequests: memStore.requestCounts(),
1208
+ }),
1209
+ screen,
1210
+ );
1211
+ } catch {
1212
+ // Publishing the view is a side effect of painting it — a failure here
1213
+ // must never surface on the notice line, let alone break the poll.
1214
+ }
1215
+ };
1216
+ app.afterRepaint = publishView;
1217
+ chainOnPoll(app, () => publishView());
1218
+
1219
+ chainOnPoll(app, (a) => {
1220
+ const snap = a.view.snapshot;
1221
+ if (!snap) return;
1222
+ try {
1223
+ feed.write(snap, a.rawEvents, a.view.health.kind);
1224
+ } catch (err) {
1225
+ a.notice(`✖ feed write failed: ${String(err instanceof Error ? err.message : err)}`);
1226
+ }
1227
+ });
1228
+
1229
+ const { AlertConsumer, AlertPolicy, filePolicyStore, notifySendRunner } =
1230
+ await import("./alerts.js");
1231
+ // Persisted per-cause memory: "already told you" survives a TUI restart
1232
+ // inside the same ops day, and resets with the day.
1233
+ const policy = new AlertPolicy(
1234
+ full.companion.cooldown_min,
1235
+ () => new Date(),
1236
+ filePolicyStore(policyStatePath),
1237
+ full.ops.timezone,
1238
+ );
1239
+ // No overseer inject: the overseer is retired (rule 8). An alert is the
1240
+ // status line, the badge and the desktop notification.
1241
+ const consumer = new AlertConsumer(full.companion.alerts_dir, policy, {
1242
+ notice: (text) => app.notice(text),
1243
+ badge: (badge) => app.setAlertBadge(badge),
1244
+ notify: notifySendRunner,
1245
+ });
1246
+ consumer.start(Math.max(1, cfg.poll_interval_s) * 1000);
1247
+ alerts = consumer;
1248
+
1249
+ // The bot's companion inbox (anomalies, heartbeat, login alerts, the EOD
1250
+ // sweep): pulled over the TUI's one runner into the companion's inject dir
1251
+ // and the alerts feed above, then acked back. Rule 8, src/tui/inboxpull.ts.
1252
+ const { InboxPuller } = await import("./inboxpull.js");
1253
+ const { companionInjectDir: injectDirOf } = await import("./companion.js");
1254
+ const inboxPuller = new InboxPuller(
1255
+ {
1256
+ cfg,
1257
+ ssh: runner,
1258
+ injectDir: injectDirOf(full.companion),
1259
+ alertsDir: full.companion.alerts_dir,
1260
+ },
1261
+ (line) => app.notice(line),
1262
+ );
1263
+ chainOnPoll(app, () => {
1264
+ void inboxPuller.tick().catch(() => {
1265
+ /* best-effort — never break the poll loop */
1266
+ });
1267
+ });
1268
+
1269
+ // Daemon-detected pi auth failures (status.json `pi_auth_expired`) ride
1270
+ // the same pipeline: on each poll, bridge a fresh flag into one alert
1271
+ // file; the consumer above does badge + notify + cooldown.
1272
+ const {
1273
+ maybeWritePiAuthAlert,
1274
+ maybeWriteMemoryAlert,
1275
+ maybeWriteCurationAlert,
1276
+ maybeWriteWorkerCrashAlerts,
1277
+ } = await import("./alerts.js");
1278
+ const piAuthMem = { lastWritten: "" };
1279
+ chainOnPoll(app, (a) => {
1280
+ try {
1281
+ maybeWritePiAuthAlert(
1282
+ full.companion.alerts_dir,
1283
+ a.view.snapshot?.pi_auth_expired,
1284
+ piAuthMem,
1285
+ );
1286
+ } catch {
1287
+ // alerting is best-effort; never break the poll loop
1288
+ }
1289
+ });
1290
+ // Reviewer memory refusing a key or down — one desktop notification per
1291
+ // transition, from status.json `memory` (never from OpenViking), and only
1292
+ // from a fresh snapshot.
1293
+ const memoryAlertMem = { lastWritten: "" };
1294
+ chainOnPoll(app, (a) => {
1295
+ try {
1296
+ maybeWriteMemoryAlert(
1297
+ full.companion.alerts_dir,
1298
+ a.view.snapshot?.memory,
1299
+ isFreshHealth(a.view.health),
1300
+ memoryAlertMem,
1301
+ );
1302
+ } catch {
1303
+ // alerting is best-effort; never break the poll loop
1304
+ }
1305
+ });
1306
+
1307
+ // The day's EOD curation did not reach memory (no receipt,
1308
+ // or a lesson refused / not found) — one notification per (state, day),
1309
+ // from status.json `memory.curation`, fresh snapshots only.
1310
+ const curationAlertMem = { lastWritten: "" };
1311
+ chainOnPoll(app, (a) => {
1312
+ try {
1313
+ maybeWriteCurationAlert(
1314
+ full.companion.alerts_dir,
1315
+ a.view.snapshot?.memory,
1316
+ isFreshHealth(a.view.health),
1317
+ curationAlertMem,
1318
+ );
1319
+ } catch {
1320
+ // alerting is best-effort; never break the poll loop
1321
+ }
1322
+ });
1323
+
1324
+ // A PR parked by the worker crash-loop breaker, or the bot drained by it
1325
+ // — one notification per park / per drain, from a fresh snapshot.
1326
+ const workerCrashMem = { written: new Set<string>() };
1327
+ chainOnPoll(app, (a) => {
1328
+ try {
1329
+ maybeWriteWorkerCrashAlerts(
1330
+ full.companion.alerts_dir,
1331
+ a.view.snapshot,
1332
+ isFreshHealth(a.view.health),
1333
+ workerCrashMem,
1334
+ );
1335
+ } catch {
1336
+ // alerting is best-effort; never break the poll loop
1337
+ }
1338
+ });
1339
+
1340
+ // No memory request-file channel here, and no memory feed: the companion
1341
+ // reaches OpenViking directly, over a host-owned forward, holding both
1342
+ // user keys. A second write path is exactly what must not exist — do not
1343
+ // add one as an "offline fallback" (the companion boundary).
1344
+
1345
+ // The companion → TUI action channel. Sweep discipline is the usual one
1346
+ // (poll cadence, busy flag so a slow sweep never stacks);
1347
+ // processTuiActions itself archives-before-execute and writes the result
1348
+ // files. Each executed action's trace lands on the notice line — actions
1349
+ // execute instantly, with no confirm gate.
1350
+ const { processTuiActions, parseCompanionCommand } = await import("./actions.js");
1351
+ const tuiActionsDir = join(full.companion.alerts_dir, "tui-actions");
1352
+ // The companion's commands/ (rule 8): under the state home it mounts rw,
1353
+ // where the seeded EOD sweep writes its receipt. Each file is one command
1354
+ // of the companion's tier, forwarded once down the operator route.
1355
+ const { COMPANION_COMMANDS_DIR } = await import("../install.js");
1356
+ const companionCommandsDir = join(companion.hostHomes().state, COMPANION_COMMANDS_DIR);
1357
+ const driver = buildTuiDriver(app, learning, metricsView, deployState);
1358
+ let tuiActBusy = false;
1359
+ chainOnPoll(app, (a) => {
1360
+ if (tuiActBusy) return;
1361
+ tuiActBusy = true;
1362
+ void processTuiActions(tuiActionsDir, driver)
1363
+ .then(async (done) => {
1364
+ for (const r of done)
1365
+ a.notice(r.trace ?? `companion tui-action ${r.file}: ✖ ${r.message}`);
1366
+ for (const r of await processTuiActions(
1367
+ companionCommandsDir,
1368
+ driver,
1369
+ parseCompanionCommand,
1370
+ ))
1371
+ a.notice(r.trace ?? `companion command ${r.file}: ✖ ${r.message}`);
1372
+ })
1373
+ .catch(() => {
1374
+ /* best-effort — never break the poll loop */
1375
+ })
1376
+ .finally(() => {
1377
+ tuiActBusy = false;
1378
+ });
1379
+ });
1380
+ };
1381
+
1382
+ let alerts: { stop(): void } | undefined;
1383
+
1384
+ /**
1385
+ * PHASE 1. Every external thing the TUI does at startup, in one place,
1386
+ * behind the first frame and reported into the companion column.
1387
+ */
1388
+ const bringUp = async (): Promise<void> => {
1389
+ // The tracker scan is a filesystem walk, so it waits for the frame too.
1390
+ learning.store.reloadTickets(); // total: an unreadable tracker is an UNKNOWN pane, never a failed start
1391
+ // The triage runs CONCURRENTLY with the companion: a wedged ssh master can
1392
+ // cost its probe seconds, and the companion has nothing to do with ssh.
1393
+ //
1394
+ // `begin` first, and it is not decoration: the probe chain is the slowest
1395
+ // thing here (a blackholed host costs its ConnectTimeout), so this is the
1396
+ // step most likely to need the overdue warning — and a step left `pending`
1397
+ // says "nobody has reached this yet", which would be a lie the moment the
1398
+ // probes start.
1399
+ progress.begin("ssh");
1400
+ const triaged = triageConnection(cfg)
1401
+ .then(applyDiagnosis)
1402
+ .catch((err: unknown) =>
1403
+ progress.fail("ssh", `probe failed: ${String(err instanceof Error ? err.message : err)}`),
1404
+ );
1405
+
1406
+ // The evidence store's dir must exist before the container mounts
1407
+ // it (docker would invent it root-owned), and the copier writes into it
1408
+ // whether or not the companion is up.
1409
+ try {
1410
+ evidence.ensure();
1411
+ } catch (err) {
1412
+ app.notice(
1413
+ `✖ evidence store ${evidence.dir}: ${String(err instanceof Error ? err.message : err)}`,
1414
+ );
1415
+ }
1416
+ if (companionOn) {
1417
+ await startAndWire();
1418
+ } else {
1419
+ for (const key of COMPANION_ONLY_STEPS) {
1420
+ progress.skip(key, "--no-companion");
1421
+ }
1422
+ // The Memory tab still needs the forward even with the companion off.
1423
+ progress.begin("memory-forward");
1424
+ const fwd = await memForward.ensure();
1425
+ if (fwd.state === "failed") progress.fail("memory-forward", fwd.detail);
1426
+ else progress.done("memory-forward", fwd.state === "off" ? fwd.detail : fwd.state);
1427
+ }
1428
+ await triaged;
1429
+ };
1430
+
1431
+ // The first poll is a step like any other: it begins when the poller does
1432
+ // (inside `run()` below) and completes on the first snapshot. Until then the
1433
+ // board says PRS (?) — an honest unknown, never a count of zero.
1434
+ progress.begin("poll");
1435
+ chainOnPoll(app, (a) => {
1436
+ const step = progress.steps().find((s) => s.key === "poll");
1437
+ if (a.view.snapshot) {
1438
+ if (step?.state !== "done") progress.done("poll", `${a.view.snapshot.prs.length} PRs`);
1439
+ return;
1440
+ }
1441
+ if (step?.state === "running" && a.poller.state.consecutiveFailures >= 2) {
1442
+ progress.fail(
1443
+ "poll",
1444
+ app.view.connection ? diagnosisLine(app.view.connection) : "status.json unreadable",
1445
+ );
1446
+ }
1447
+ });
1448
+
1449
+ // Restore a hot-reload snapshot before run(), so the first paint is already
1450
+ // on the right tab with the right selection.
1451
+ const resumed = args.resumeState ? readResumeState(args.resumeState) : undefined;
1452
+ if (resumed) {
1453
+ // A tab name this build does not know (e.g. from an older build) lands on
1454
+ // the board.
1455
+ if (isTab(resumed.tab)) app.tab = resumed.tab;
1456
+ app.view.eventsScroll = resumed.eventsScroll;
1457
+ app.view.eventsFocus = resumed.eventsFocus;
1458
+ if (app.sidebar && resumed.sidebarWidth !== undefined) {
1459
+ app.sidebar.preferredWidth = restorePreferredSidebarWidth(resumed.sidebarWidth);
1460
+ }
1461
+ }
1462
+
1463
+ // Hot-reload: watch our own dist/, the build stamp and (without acting) the
1464
+ // core's dist/, and re-exec when a whole build lands (reload.ts says why a
1465
+ // core write alone never reloads).
1466
+ const reloadState = resumeStatePath(sandboxRoot());
1467
+ const reloader = args.reload
1468
+ ? new HotReloader({
1469
+ distDir: join(sandboxRoot(), "dist"),
1470
+ buildStamp: join(sandboxRoot(), BUILD_STAMP),
1471
+ // A core-only change built without the stamp (a raw `tsc -b`, an
1472
+ // IDE): the core's dist/ only marks it, the app's clean build info
1473
+ // reloads (reload.ts).
1474
+ coreDistDir: join(sandboxRoot(), CORE_DIST),
1475
+ appBuildInfo: join(sandboxRoot(), APP_BUILD_INFO),
1476
+ stateFile: reloadState,
1477
+ snapshot: () => ({
1478
+ tab: app.tab,
1479
+ pr: app.currentPr(),
1480
+ eventsScroll: app.view.eventsScroll,
1481
+ eventsFocus: app.view.eventsFocus,
1482
+ sidebarWidth: app.sidebar?.preferredWidth,
1483
+ saved_at: "",
1484
+ }),
1485
+ // The forward must be released here, not in the cleanup after
1486
+ // `await running`. `realReexec` calls this teardown and then
1487
+ // spawnSync's the new build synchronously — `app.quit()` merely
1488
+ // resolves a promise, so that cleanup is a microtask which cannot run
1489
+ // until spawnSync returns, i.e. not until the child has exited. Left
1490
+ // to it, the old process would sit inside spawnSync holding :1933
1491
+ // with a FROZEN event loop: the new TUI finds the port taken, cannot
1492
+ // bind, and memory is dead for the rest of the session.
1493
+ // Same reasoning covers in-flight ssh: the per-call SIGKILL in
1494
+ // `realSshRunner` is a timer on the loop that spawnSync is about to
1495
+ // freeze, so a capture/poll/why still running at reload time is
1496
+ // orphaned forever. Against a wedged ControlMaster (an ssh config
1497
+ // block with no ServerAliveInterval lets a network blip hang the
1498
+ // master and every slave riding it) those children never exit on
1499
+ // their own either, and they stack up one generation per reload.
1500
+ // Kill them here, while the loop still turns.
1501
+ reexec: (file) =>
1502
+ realReexec(file, () => {
1503
+ killInFlightSsh();
1504
+ memForward.stop();
1505
+ app.quit();
1506
+ }),
1507
+ })
1508
+ : undefined;
1509
+
1510
+ // ---- the first frame ------------------------------------------------------
1511
+ //
1512
+ // Deep link: `qcr tui <pr>` is the board with that PR selected — nothing
1513
+ // opens over it.
1514
+ const initial = target.kind === "pr" ? target.pr : resumed?.pr;
1515
+ const running = app.run(initial);
1516
+ // Only now: a recursive dist watcher walks the tree, and the bring-up talks
1517
+ // to ssh, docker and OpenViking. Neither may precede the frame above.
1518
+ reloader?.start();
1519
+ // Kept, not dropped on the floor: quitting during the bring-up must not
1520
+ // leave it running against the teardown below, or `spawnPi()` could land
1521
+ // after `gracefulStop()` (a clean quit that leaves a freshly spawned
1522
+ // companion behind) and `memForward.stop()` could close a forward the
1523
+ // bring-up was still about to use.
1524
+ const bringUpDone = bringUp().catch(() => {
1525
+ /* every step already reports itself; a bring-up cannot fail the quit */
1526
+ });
1527
+ await running;
1528
+ // Let the bring-up finish before tearing down what it is still touching.
1529
+ // Bounded, and announced: this runs AFTER the terminal is restored, so a
1530
+ // silent wait here is a terminal that looks dead — the same reasoning the
1531
+ // companion stop below is written under.
1532
+ const inFlight = Promise.allSettled([bringUpDone, pendingReconnect]);
1533
+ if (!(await settledWithin(inFlight, BRING_UP_QUIT_GRACE_MS))) {
1534
+ out.write("qcr tui: waiting for startup to finish before shutting down…\n");
1535
+ await settledWithin(inFlight, BRING_UP_QUIT_GRACE_MS * 3);
1536
+ }
1537
+ reloader?.stop();
1538
+ progress.stop();
1539
+ alerts?.stop();
1540
+ ghToken.stop();
1541
+ if (configMirrorTimer) clearInterval(configMirrorTimer);
1542
+ // Closes only a forward THIS process bound; an adopted one (systemd unit,
1543
+ // another qcr tui) is left running on purpose — see memforward.ts.
1544
+ memForward.stop();
1545
+
1546
+ // Clean quit stops the container (conversation + auth survive on volumes).
1547
+ // A crash deliberately leaves it running — the next start adopts it.
1548
+ //
1549
+ // This runs after the terminal is restored, so every millisecond it takes is
1550
+ // a terminal that looks dead: board gone, prompt not back, nothing
1551
+ // responding. Say what is happening: a slow docker, or a container whose
1552
+ // PID 1 ignores SIGTERM (so `docker stop` runs to its full timeout), must
1553
+ // not look like a hang.
1554
+ await stopCompanionOnQuit({
1555
+ // The wrapper that ran the container stops it: nothing here can.
1556
+ containerStarted: !socketMode && (companionContainerStarted || companionUp),
1557
+ stop: () => companion.gracefulStop(),
1558
+ write: (line) => out.write(line),
1559
+ });
1560
+ // Exit explicitly. Anything still holding the event loop — a docker exec
1561
+ // that never returned, an ssh master, a socket — would otherwise keep the
1562
+ // process alive with the terminal already restored, which is exactly the
1563
+ // "TUI is gone but the shell never comes back" state.
1564
+ exit(0);
1565
+ }