@quickchatai/quickreview 0.0.0-stage → 1.2.0

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