@quickchatai/quickreview 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1443) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +385 -2
  3. package/THIRD-PARTY-NOTICES.md +134 -0
  4. package/agents/pass-abstraction.md +64 -0
  5. package/agents/pass-ask-human.md +134 -0
  6. package/agents/pass-backcompat.md +67 -0
  7. package/agents/pass-baseline.md +61 -0
  8. package/agents/pass-behaviour-delta.md +64 -0
  9. package/agents/pass-bounds.md +36 -0
  10. package/agents/pass-claims.md +31 -0
  11. package/agents/pass-concurrency.md +168 -0
  12. package/agents/pass-consistency.md +57 -0
  13. package/agents/pass-contracts.md +30 -0
  14. package/agents/pass-data-safety.md +54 -0
  15. package/agents/pass-deferred.md +35 -0
  16. package/agents/pass-delivery.md +37 -0
  17. package/agents/pass-duplication.md +60 -0
  18. package/agents/pass-intent.md +63 -0
  19. package/agents/pass-lint-conformance.md +46 -0
  20. package/agents/pass-maintainability.md +67 -0
  21. package/agents/pass-placement.md +68 -0
  22. package/agents/pass-prod-risk.md +24 -0
  23. package/agents/pass-rollout.md +32 -0
  24. package/agents/pass-roundtrip.md +46 -0
  25. package/agents/pass-security.md +31 -0
  26. package/agents/pass-simplicity.md +68 -0
  27. package/agents/pass-statelessness.md +27 -0
  28. package/agents/pass-style.md +37 -0
  29. package/agents/pass-sweep.md +57 -0
  30. package/agents/pass-tests.md +58 -0
  31. package/agents/pass-ui.md +23 -0
  32. package/agents/pass-validate.md +50 -0
  33. package/agents/pass-widened.md +46 -0
  34. package/agents/scan/pass-scan.md +86 -0
  35. package/bin/qcr.js +11 -0
  36. package/config.example.toml +1067 -0
  37. package/deploy/ansible/README.md +356 -0
  38. package/deploy/ansible/group_vars/all.yml +212 -0
  39. package/deploy/ansible/inventory.example.ini +25 -0
  40. package/deploy/ansible/playbook.yml +360 -0
  41. package/deploy/collect-secrets.sh +289 -0
  42. package/deploy/collect-slack-secrets.mjs +1190 -0
  43. package/deploy/github-app-manifest.json +31 -0
  44. package/deploy/manifest.schema.json +75 -0
  45. package/deploy/qcr-update.sh +803 -0
  46. package/deploy/reviewer/AGENTS.override.md +12 -0
  47. package/dist/extensions/cli/bash-guard-ambient.js +42 -0
  48. package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
  49. package/dist/extensions/cli/bash-guard.js +5 -0
  50. package/dist/extensions/cli/bash-guard.js.map +1 -0
  51. package/dist/extensions/cli/companion-guard.js +5 -0
  52. package/dist/extensions/cli/companion-guard.js.map +1 -0
  53. package/dist/extensions/cli/debug-round.js +8 -0
  54. package/dist/extensions/cli/debug-round.js.map +1 -0
  55. package/dist/extensions/cli/inject-bridge.js +5 -0
  56. package/dist/extensions/cli/inject-bridge.js.map +1 -0
  57. package/dist/extensions/cli/ledger-pin.js +5 -0
  58. package/dist/extensions/cli/ledger-pin.js.map +1 -0
  59. package/dist/extensions/cli/lens.js +13 -0
  60. package/dist/extensions/cli/lens.js.map +1 -0
  61. package/dist/extensions/cli/muted-theme.js +5 -0
  62. package/dist/extensions/cli/muted-theme.js.map +1 -0
  63. package/dist/extensions/cli/onboarding.js +5 -0
  64. package/dist/extensions/cli/onboarding.js.map +1 -0
  65. package/dist/extensions/cli/setup.js +5 -0
  66. package/dist/extensions/cli/setup.js.map +1 -0
  67. package/dist/extensions/cli/tui-view.js +5 -0
  68. package/dist/extensions/cli/tui-view.js.map +1 -0
  69. package/dist/extensions/companion-guard.js +361 -0
  70. package/dist/extensions/companion-guard.js.map +1 -0
  71. package/dist/extensions/debug-round.js +204 -0
  72. package/dist/extensions/debug-round.js.map +1 -0
  73. package/dist/extensions/inject-bridge.js +603 -0
  74. package/dist/extensions/inject-bridge.js.map +1 -0
  75. package/dist/extensions/muted-theme.js +12 -0
  76. package/dist/extensions/muted-theme.js.map +1 -0
  77. package/dist/extensions/onboarding.js +175 -0
  78. package/dist/extensions/onboarding.js.map +1 -0
  79. package/dist/extensions/setup-replay.js +269 -0
  80. package/dist/extensions/setup-replay.js.map +1 -0
  81. package/dist/extensions/setup-scan.js +998 -0
  82. package/dist/extensions/setup-scan.js.map +1 -0
  83. package/dist/extensions/setup-triage.js +175 -0
  84. package/dist/extensions/setup-triage.js.map +1 -0
  85. package/dist/extensions/setup.js +1160 -0
  86. package/dist/extensions/setup.js.map +1 -0
  87. package/dist/extensions/tui-view.js +389 -0
  88. package/dist/extensions/tui-view.js.map +1 -0
  89. package/dist/extensions/vendor/openviking-pi/client.js +376 -0
  90. package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
  91. package/dist/extensions/vendor/openviking-pi/config.js +189 -0
  92. package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
  93. package/dist/extensions/vendor/openviking-pi/index.js +421 -0
  94. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
  95. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
  96. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
  97. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
  98. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
  99. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
  100. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
  101. package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
  102. package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
  103. package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
  104. package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
  105. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
  106. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
  107. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
  108. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
  109. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
  110. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
  111. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
  112. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
  113. package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
  114. package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
  115. package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
  116. package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
  117. package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
  118. package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
  119. package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
  120. package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
  121. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
  122. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
  123. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
  124. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
  125. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
  126. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
  127. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
  128. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
  129. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
  130. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
  131. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
  132. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
  133. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
  134. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
  135. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
  136. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
  137. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
  138. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
  139. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  140. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
  141. package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
  142. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
  143. package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
  144. package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
  145. package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
  146. package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
  147. package/dist/src/anomalies.js +483 -0
  148. package/dist/src/anomalies.js.map +1 -0
  149. package/dist/src/answerworker.js +180 -0
  150. package/dist/src/answerworker.js.map +1 -0
  151. package/dist/src/appstamp.js +68 -0
  152. package/dist/src/appstamp.js.map +1 -0
  153. package/dist/src/archive/admission.js +3 -0
  154. package/dist/src/archive/admission.js.map +1 -0
  155. package/dist/src/archive/archivedb.js +474 -0
  156. package/dist/src/archive/archivedb.js.map +1 -0
  157. package/dist/src/archive/archiver.js +1770 -0
  158. package/dist/src/archive/archiver.js.map +1 -0
  159. package/dist/src/archive/archivewrite.js +198 -0
  160. package/dist/src/archive/archivewrite.js.map +1 -0
  161. package/dist/src/archive/bundle.js +266 -0
  162. package/dist/src/archive/bundle.js.map +1 -0
  163. package/dist/src/archive/dbmove.js +638 -0
  164. package/dist/src/archive/dbmove.js.map +1 -0
  165. package/dist/src/archive/duplicatecounts.js +80 -0
  166. package/dist/src/archive/duplicatecounts.js.map +1 -0
  167. package/dist/src/archive/eligibility.js +113 -0
  168. package/dist/src/archive/eligibility.js.map +1 -0
  169. package/dist/src/archive/extract.js +288 -0
  170. package/dist/src/archive/extract.js.map +1 -0
  171. package/dist/src/archive/fsutil.js +330 -0
  172. package/dist/src/archive/fsutil.js.map +1 -0
  173. package/dist/src/archive/gzfile.js +277 -0
  174. package/dist/src/archive/gzfile.js.map +1 -0
  175. package/dist/src/archive/kinds.js +22 -0
  176. package/dist/src/archive/kinds.js.map +1 -0
  177. package/dist/src/archive/livecheckpoint.js +178 -0
  178. package/dist/src/archive/livecheckpoint.js.map +1 -0
  179. package/dist/src/archive/liveretention.js +145 -0
  180. package/dist/src/archive/liveretention.js.map +1 -0
  181. package/dist/src/archive/manifest.js +284 -0
  182. package/dist/src/archive/manifest.js.map +1 -0
  183. package/dist/src/archive/overseer.js +283 -0
  184. package/dist/src/archive/overseer.js.map +1 -0
  185. package/dist/src/archive/piglobal.js +129 -0
  186. package/dist/src/archive/piglobal.js.map +1 -0
  187. package/dist/src/archive/plan.js +398 -0
  188. package/dist/src/archive/plan.js.map +1 -0
  189. package/dist/src/archive/prarchive.js +831 -0
  190. package/dist/src/archive/prarchive.js.map +1 -0
  191. package/dist/src/archive/prfiles.js +303 -0
  192. package/dist/src/archive/prfiles.js.map +1 -0
  193. package/dist/src/archive/report.js +169 -0
  194. package/dist/src/archive/report.js.map +1 -0
  195. package/dist/src/archive/restore.js +276 -0
  196. package/dist/src/archive/restore.js.map +1 -0
  197. package/dist/src/archive/rows.js +106 -0
  198. package/dist/src/archive/rows.js.map +1 -0
  199. package/dist/src/archive/seal.js +480 -0
  200. package/dist/src/archive/seal.js.map +1 -0
  201. package/dist/src/archive/status.js +528 -0
  202. package/dist/src/archive/status.js.map +1 -0
  203. package/dist/src/archive/storepass.js +409 -0
  204. package/dist/src/archive/storepass.js.map +1 -0
  205. package/dist/src/backfill.js +217 -0
  206. package/dist/src/backfill.js.map +1 -0
  207. package/dist/src/bench/cost.js +87 -0
  208. package/dist/src/bench/exec.js +76 -0
  209. package/dist/src/bench/values.js +14 -0
  210. package/dist/src/botcontrol.js +127 -0
  211. package/dist/src/botcontrol.js.map +1 -0
  212. package/dist/src/botworker.js +805 -0
  213. package/dist/src/botworker.js.map +1 -0
  214. package/dist/src/capture.js +234 -0
  215. package/dist/src/capture.js.map +1 -0
  216. package/dist/src/cases.js +1491 -0
  217. package/dist/src/cases.js.map +1 -0
  218. package/dist/src/channelfs.js +27 -0
  219. package/dist/src/channelfs.js.map +1 -0
  220. package/dist/src/checklist.js +312 -0
  221. package/dist/src/checklist.js.map +1 -0
  222. package/dist/src/ciretry.js +91 -0
  223. package/dist/src/ciretry.js.map +1 -0
  224. package/dist/src/cli.js +1916 -0
  225. package/dist/src/cli.js.map +1 -0
  226. package/dist/src/cliops.js +333 -0
  227. package/dist/src/cliops.js.map +1 -0
  228. package/dist/src/clonedir.js +15 -0
  229. package/dist/src/clonedir.js.map +1 -0
  230. package/dist/src/commandeffects.js +43 -0
  231. package/dist/src/commandeffects.js.map +1 -0
  232. package/dist/src/commands.js +1312 -0
  233. package/dist/src/commands.js.map +1 -0
  234. package/dist/src/companioninbox.js +203 -0
  235. package/dist/src/companioninbox.js.map +1 -0
  236. package/dist/src/compare.js +673 -0
  237. package/dist/src/compare.js.map +1 -0
  238. package/dist/src/config.js +1027 -0
  239. package/dist/src/config.js.map +1 -0
  240. package/dist/src/configcheck.js +111 -0
  241. package/dist/src/configcheck.js.map +1 -0
  242. package/dist/src/coreext.js +43 -0
  243. package/dist/src/coreext.js.map +1 -0
  244. package/dist/src/daemon.js +929 -0
  245. package/dist/src/daemon.js.map +1 -0
  246. package/dist/src/debuground.js +731 -0
  247. package/dist/src/debuground.js.map +1 -0
  248. package/dist/src/debugroundpi.js +219 -0
  249. package/dist/src/debugroundpi.js.map +1 -0
  250. package/dist/src/diagnostics.js +14 -0
  251. package/dist/src/diagnostics.js.map +1 -0
  252. package/dist/src/digest.js +139 -0
  253. package/dist/src/digest.js.map +1 -0
  254. package/dist/src/dispatch.js +1726 -0
  255. package/dist/src/dispatch.js.map +1 -0
  256. package/dist/src/engine/answerguard.js +18 -0
  257. package/dist/src/engine/answerguard.js.map +1 -0
  258. package/dist/src/engine/botsdk.js +262 -0
  259. package/dist/src/engine/botsdk.js.map +1 -0
  260. package/dist/src/engine/caserunner-sdk.js +332 -0
  261. package/dist/src/engine/caserunner-sdk.js.map +1 -0
  262. package/dist/src/engine/debugpi.js +194 -0
  263. package/dist/src/engine/debugpi.js.map +1 -0
  264. package/dist/src/eod.js +473 -0
  265. package/dist/src/eod.js.map +1 -0
  266. package/dist/src/eodpinwatch.js +391 -0
  267. package/dist/src/eodpinwatch.js.map +1 -0
  268. package/dist/src/escalation.js +1009 -0
  269. package/dist/src/escalation.js.map +1 -0
  270. package/dist/src/eventloop.js +779 -0
  271. package/dist/src/eventloop.js.map +1 -0
  272. package/dist/src/execboundary.js +430 -0
  273. package/dist/src/execboundary.js.map +1 -0
  274. package/dist/src/feedback.js +915 -0
  275. package/dist/src/feedback.js.map +1 -0
  276. package/dist/src/findingscheck.js +200 -0
  277. package/dist/src/findingscheck.js.map +1 -0
  278. package/dist/src/fsdenied.js +26 -0
  279. package/dist/src/fsdenied.js.map +1 -0
  280. package/dist/src/ghread.js +181 -0
  281. package/dist/src/ghread.js.map +1 -0
  282. package/dist/src/github.js +1063 -0
  283. package/dist/src/github.js.map +1 -0
  284. package/dist/src/homes.js +103 -0
  285. package/dist/src/homes.js.map +1 -0
  286. package/dist/src/hostroute.js +24 -0
  287. package/dist/src/hostroute.js.map +1 -0
  288. package/dist/src/improvements.js +625 -0
  289. package/dist/src/improvements.js.map +1 -0
  290. package/dist/src/install.js +946 -0
  291. package/dist/src/install.js.map +1 -0
  292. package/dist/src/installkind.js +40 -0
  293. package/dist/src/installkind.js.map +1 -0
  294. package/dist/src/ipc/capability.js +286 -0
  295. package/dist/src/ipc/capability.js.map +1 -0
  296. package/dist/src/ipc/client.js +226 -0
  297. package/dist/src/ipc/client.js.map +1 -0
  298. package/dist/src/ipc/control.js +70 -0
  299. package/dist/src/ipc/control.js.map +1 -0
  300. package/dist/src/ipc/ghreadshim.js +77 -0
  301. package/dist/src/ipc/ghreadshim.js.map +1 -0
  302. package/dist/src/ipc/launcher.js +65 -0
  303. package/dist/src/ipc/launcher.js.map +1 -0
  304. package/dist/src/ipc/permits.js +293 -0
  305. package/dist/src/ipc/permits.js.map +1 -0
  306. package/dist/src/ipc/postshim.js +137 -0
  307. package/dist/src/ipc/postshim.js.map +1 -0
  308. package/dist/src/ipc/protocol.js +239 -0
  309. package/dist/src/ipc/protocol.js.map +1 -0
  310. package/dist/src/ipc/server.js +312 -0
  311. package/dist/src/ipc/server.js.map +1 -0
  312. package/dist/src/ipc/workerspec.js +148 -0
  313. package/dist/src/ipc/workerspec.js.map +1 -0
  314. package/dist/src/learner/learner.js +1272 -0
  315. package/dist/src/learner/learner.js.map +1 -0
  316. package/dist/src/learner/miner.js +948 -0
  317. package/dist/src/learner/miner.js.map +1 -0
  318. package/dist/src/learner/packet.js +216 -0
  319. package/dist/src/learner/packet.js.map +1 -0
  320. package/dist/src/learner/receipt.js +135 -0
  321. package/dist/src/learner/receipt.js.map +1 -0
  322. package/dist/src/learner/status.js +38 -0
  323. package/dist/src/learner/status.js.map +1 -0
  324. package/dist/src/ledgercheck.js +120 -0
  325. package/dist/src/ledgercheck.js.map +1 -0
  326. package/dist/src/lessonledger.js +451 -0
  327. package/dist/src/lessonledger.js.map +1 -0
  328. package/dist/src/lessonselect.js +639 -0
  329. package/dist/src/lessonselect.js.map +1 -0
  330. package/dist/src/lessonshadow.js +540 -0
  331. package/dist/src/lessonshadow.js.map +1 -0
  332. package/dist/src/levers.js +302 -0
  333. package/dist/src/levers.js.map +1 -0
  334. package/dist/src/live/projection.js +450 -0
  335. package/dist/src/live/projection.js.map +1 -0
  336. package/dist/src/live/publish.js +83 -0
  337. package/dist/src/live/publish.js.map +1 -0
  338. package/dist/src/live/read.js +60 -0
  339. package/dist/src/live/read.js.map +1 -0
  340. package/dist/src/livecli.js +288 -0
  341. package/dist/src/livecli.js.map +1 -0
  342. package/dist/src/loginhint.js +42 -0
  343. package/dist/src/loginhint.js.map +1 -0
  344. package/dist/src/loginrefresh/atomic.js +66 -0
  345. package/dist/src/loginrefresh/atomic.js.map +1 -0
  346. package/dist/src/loginrefresh/lock.js +93 -0
  347. package/dist/src/loginrefresh/lock.js.map +1 -0
  348. package/dist/src/loginrefresh/main.js +58 -0
  349. package/dist/src/loginrefresh/main.js.map +1 -0
  350. package/dist/src/loginrefresh/pirefresh.js +49 -0
  351. package/dist/src/loginrefresh/pirefresh.js.map +1 -0
  352. package/dist/src/loginrefresh/refresher.js +286 -0
  353. package/dist/src/loginrefresh/refresher.js.map +1 -0
  354. package/dist/src/loginrefresh/supervisor.js +253 -0
  355. package/dist/src/loginrefresh/supervisor.js.map +1 -0
  356. package/dist/src/loginrefresh/types.js +4 -0
  357. package/dist/src/loginrefresh/types.js.map +1 -0
  358. package/dist/src/memory/context.js +251 -0
  359. package/dist/src/memory/context.js.map +1 -0
  360. package/dist/src/memory/datafence.js +5 -0
  361. package/dist/src/memory/datafence.js.map +1 -0
  362. package/dist/src/memory/delivery.js +131 -0
  363. package/dist/src/memory/delivery.js.map +1 -0
  364. package/dist/src/memory/eventtags.js +81 -0
  365. package/dist/src/memory/eventtags.js.map +1 -0
  366. package/dist/src/memory/health.js +713 -0
  367. package/dist/src/memory/health.js.map +1 -0
  368. package/dist/src/memory/import.js +504 -0
  369. package/dist/src/memory/import.js.map +1 -0
  370. package/dist/src/memory/layout.js +252 -0
  371. package/dist/src/memory/layout.js.map +1 -0
  372. package/dist/src/memory/legacy-file.js +100 -0
  373. package/dist/src/memory/legacy-file.js.map +1 -0
  374. package/dist/src/memory/lessons.js +370 -0
  375. package/dist/src/memory/lessons.js.map +1 -0
  376. package/dist/src/memory/ovsession.js +449 -0
  377. package/dist/src/memory/ovsession.js.map +1 -0
  378. package/dist/src/memory/relayout.js +259 -0
  379. package/dist/src/memory/relayout.js.map +1 -0
  380. package/dist/src/memory/roundactivity.js +180 -0
  381. package/dist/src/memory/roundactivity.js.map +1 -0
  382. package/dist/src/memory/selection.js +690 -0
  383. package/dist/src/memory/selection.js.map +1 -0
  384. package/dist/src/memory/shadowstatus.js +69 -0
  385. package/dist/src/memory/shadowstatus.js.map +1 -0
  386. package/dist/src/memory/store.js +83 -0
  387. package/dist/src/memory/store.js.map +1 -0
  388. package/dist/src/memory/textidentity.js +8 -0
  389. package/dist/src/memory/textidentity.js.map +1 -0
  390. package/dist/src/metrics.js +773 -0
  391. package/dist/src/metrics.js.map +1 -0
  392. package/dist/src/missingconfig.js +8 -0
  393. package/dist/src/missingconfig.js.map +1 -0
  394. package/dist/src/npmverbs.js +226 -0
  395. package/dist/src/npmverbs.js.map +1 -0
  396. package/dist/src/onboard/choices.js +191 -0
  397. package/dist/src/onboard/choices.js.map +1 -0
  398. package/dist/src/onboard/engine.js +344 -0
  399. package/dist/src/onboard/engine.js.map +1 -0
  400. package/dist/src/onboard/launch.js +83 -0
  401. package/dist/src/onboard/launch.js.map +1 -0
  402. package/dist/src/onboard/steps/appslug.js +211 -0
  403. package/dist/src/onboard/steps/appslug.js.map +1 -0
  404. package/dist/src/onboard/steps/bringup.js +408 -0
  405. package/dist/src/onboard/steps/bringup.js.map +1 -0
  406. package/dist/src/onboard/steps/compose.js +343 -0
  407. package/dist/src/onboard/steps/compose.js.map +1 -0
  408. package/dist/src/onboard/steps/configdelivery.js +50 -0
  409. package/dist/src/onboard/steps/configdelivery.js.map +1 -0
  410. package/dist/src/onboard/steps/configfill.js +1148 -0
  411. package/dist/src/onboard/steps/configfill.js.map +1 -0
  412. package/dist/src/onboard/steps/deploysh.js +80 -0
  413. package/dist/src/onboard/steps/deploysh.js.map +1 -0
  414. package/dist/src/onboard/steps/embeddings.js +472 -0
  415. package/dist/src/onboard/steps/embeddings.js.map +1 -0
  416. package/dist/src/onboard/steps/escalation.js +571 -0
  417. package/dist/src/onboard/steps/escalation.js.map +1 -0
  418. package/dist/src/onboard/steps/githubapp.js +1092 -0
  419. package/dist/src/onboard/steps/githubapp.js.map +1 -0
  420. package/dist/src/onboard/steps/golive.js +506 -0
  421. package/dist/src/onboard/steps/golive.js.map +1 -0
  422. package/dist/src/onboard/steps/index.js +67 -0
  423. package/dist/src/onboard/steps/index.js.map +1 -0
  424. package/dist/src/onboard/steps/ingress.js +762 -0
  425. package/dist/src/onboard/steps/ingress.js.map +1 -0
  426. package/dist/src/onboard/steps/inventory.js +420 -0
  427. package/dist/src/onboard/steps/inventory.js.map +1 -0
  428. package/dist/src/onboard/steps/llmauth.js +893 -0
  429. package/dist/src/onboard/steps/llmauth.js.map +1 -0
  430. package/dist/src/onboard/steps/memory.js +1060 -0
  431. package/dist/src/onboard/steps/memory.js.map +1 -0
  432. package/dist/src/onboard/steps/memorykeys.js +43 -0
  433. package/dist/src/onboard/steps/memorykeys.js.map +1 -0
  434. package/dist/src/onboard/steps/preflight.js +172 -0
  435. package/dist/src/onboard/steps/preflight.js.map +1 -0
  436. package/dist/src/onboard/steps/proof.js +502 -0
  437. package/dist/src/onboard/steps/proof.js.map +1 -0
  438. package/dist/src/onboard/steps/ssh.js +300 -0
  439. package/dist/src/onboard/steps/ssh.js.map +1 -0
  440. package/dist/src/onboard/steps/topology.js +291 -0
  441. package/dist/src/onboard/steps/topology.js.map +1 -0
  442. package/dist/src/onboard/steps/transport.js +511 -0
  443. package/dist/src/onboard/steps/transport.js.map +1 -0
  444. package/dist/src/onboard/steps/verifyup.js +362 -0
  445. package/dist/src/onboard/steps/verifyup.js.map +1 -0
  446. package/dist/src/onboard/steps/vmdeploy.js +368 -0
  447. package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
  448. package/dist/src/onboard/toml.js +53 -0
  449. package/dist/src/onboard/toml.js.map +1 -0
  450. package/dist/src/opencli.js +508 -0
  451. package/dist/src/opencli.js.map +1 -0
  452. package/dist/src/openpr.js +457 -0
  453. package/dist/src/openpr.js.map +1 -0
  454. package/dist/src/openviking/client.js +821 -0
  455. package/dist/src/openviking/client.js.map +1 -0
  456. package/dist/src/openviking/context.js +218 -0
  457. package/dist/src/openviking/context.js.map +1 -0
  458. package/dist/src/openviking/embedder.js +457 -0
  459. package/dist/src/openviking/embedder.js.map +1 -0
  460. package/dist/src/openviking/gapsink.js +38 -0
  461. package/dist/src/openviking/gapsink.js.map +1 -0
  462. package/dist/src/openviking/health.js +69 -0
  463. package/dist/src/openviking/health.js.map +1 -0
  464. package/dist/src/openviking/index.js +24 -0
  465. package/dist/src/openviking/index.js.map +1 -0
  466. package/dist/src/openviking/lessonfind.js +54 -0
  467. package/dist/src/openviking/lessonfind.js.map +1 -0
  468. package/dist/src/openviking/probe.js +89 -0
  469. package/dist/src/openviking/probe.js.map +1 -0
  470. package/dist/src/openviking/proc.js +95 -0
  471. package/dist/src/openviking/proc.js.map +1 -0
  472. package/dist/src/openviking/provisionreport.js +144 -0
  473. package/dist/src/openviking/provisionreport.js.map +1 -0
  474. package/dist/src/openviking/reembed.js +317 -0
  475. package/dist/src/openviking/reembed.js.map +1 -0
  476. package/dist/src/openviking/relayout.js +153 -0
  477. package/dist/src/openviking/relayout.js.map +1 -0
  478. package/dist/src/openviking/sidecar-main.js +39 -0
  479. package/dist/src/openviking/sidecar-main.js.map +1 -0
  480. package/dist/src/openviking/sidecar.js +79 -0
  481. package/dist/src/openviking/sidecar.js.map +1 -0
  482. package/dist/src/openviking/store.js +1758 -0
  483. package/dist/src/openviking/store.js.map +1 -0
  484. package/dist/src/openviking/supervisor.js +422 -0
  485. package/dist/src/openviking/supervisor.js.map +1 -0
  486. package/dist/src/openviking/types.js +5 -0
  487. package/dist/src/openviking/types.js.map +1 -0
  488. package/dist/src/operator-recovery.js +172 -0
  489. package/dist/src/operator-recovery.js.map +1 -0
  490. package/dist/src/operatormessage.js +44 -0
  491. package/dist/src/operatormessage.js.map +1 -0
  492. package/dist/src/outcomes.js +1361 -0
  493. package/dist/src/outcomes.js.map +1 -0
  494. package/dist/src/piauthcopy.js +5 -0
  495. package/dist/src/piauthcopy.js.map +1 -0
  496. package/dist/src/picli.js +45 -0
  497. package/dist/src/picli.js.map +1 -0
  498. package/dist/src/picwd.js +25 -0
  499. package/dist/src/picwd.js.map +1 -0
  500. package/dist/src/pilogin.js +35 -0
  501. package/dist/src/pilogin.js.map +1 -0
  502. package/dist/src/pingpong.js +1028 -0
  503. package/dist/src/pingpong.js.map +1 -0
  504. package/dist/src/posting-journal.js +441 -0
  505. package/dist/src/posting-journal.js.map +1 -0
  506. package/dist/src/posting-outbox.js +662 -0
  507. package/dist/src/posting-outbox.js.map +1 -0
  508. package/dist/src/posting-receipt.js +203 -0
  509. package/dist/src/posting-receipt.js.map +1 -0
  510. package/dist/src/posting.js +348 -0
  511. package/dist/src/posting.js.map +1 -0
  512. package/dist/src/postops.js +637 -0
  513. package/dist/src/postops.js.map +1 -0
  514. package/dist/src/pr-activity.js +11 -0
  515. package/dist/src/pr-activity.js.map +1 -0
  516. package/dist/src/pr-author.js +5 -0
  517. package/dist/src/pr-author.js.map +1 -0
  518. package/dist/src/pr-conversation.js +355 -0
  519. package/dist/src/pr-conversation.js.map +1 -0
  520. package/dist/src/pr-session-lifecycle.js +542 -0
  521. package/dist/src/pr-session-lifecycle.js.map +1 -0
  522. package/dist/src/preflight.js +341 -0
  523. package/dist/src/preflight.js.map +1 -0
  524. package/dist/src/priorresolve.js +275 -0
  525. package/dist/src/priorresolve.js.map +1 -0
  526. package/dist/src/qcrbin.js +180 -0
  527. package/dist/src/qcrbin.js.map +1 -0
  528. package/dist/src/recheck.js +1398 -0
  529. package/dist/src/recheck.js.map +1 -0
  530. package/dist/src/reconcile.js +820 -0
  531. package/dist/src/reconcile.js.map +1 -0
  532. package/dist/src/recordedfindings.js +15 -0
  533. package/dist/src/recordedfindings.js.map +1 -0
  534. package/dist/src/regression.js +546 -0
  535. package/dist/src/regression.js.map +1 -0
  536. package/dist/src/replay.js +598 -0
  537. package/dist/src/replay.js.map +1 -0
  538. package/dist/src/requiredpasses.js +113 -0
  539. package/dist/src/requiredpasses.js.map +1 -0
  540. package/dist/src/resumecheck.js +168 -0
  541. package/dist/src/resumecheck.js.map +1 -0
  542. package/dist/src/reviewcli.js +671 -0
  543. package/dist/src/reviewcli.js.map +1 -0
  544. package/dist/src/reviewcontext.js +22 -0
  545. package/dist/src/reviewcontext.js.map +1 -0
  546. package/dist/src/reviewdetach.js +574 -0
  547. package/dist/src/reviewdetach.js.map +1 -0
  548. package/dist/src/reviewhistory.js +284 -0
  549. package/dist/src/reviewhistory.js.map +1 -0
  550. package/dist/src/reviewhome.js +26 -0
  551. package/dist/src/reviewhome.js.map +1 -0
  552. package/dist/src/reviewmemory.js +110 -0
  553. package/dist/src/reviewmemory.js.map +1 -0
  554. package/dist/src/reviewrendercli.js +104 -0
  555. package/dist/src/reviewrendercli.js.map +1 -0
  556. package/dist/src/round-work.js +20 -0
  557. package/dist/src/round-work.js.map +1 -0
  558. package/dist/src/runhost.js +72 -0
  559. package/dist/src/runhost.js.map +1 -0
  560. package/dist/src/scanauth.js +25 -0
  561. package/dist/src/scanauth.js.map +1 -0
  562. package/dist/src/scanbox.js +846 -0
  563. package/dist/src/scanbox.js.map +1 -0
  564. package/dist/src/scancli.js +1074 -0
  565. package/dist/src/scancli.js.map +1 -0
  566. package/dist/src/scandriverlog.js +30 -0
  567. package/dist/src/scandriverlog.js.map +1 -0
  568. package/dist/src/session-bridge.js +75 -0
  569. package/dist/src/session-bridge.js.map +1 -0
  570. package/dist/src/session-command-payload.js +29 -0
  571. package/dist/src/session-command-payload.js.map +1 -0
  572. package/dist/src/session-command-supervisor.js +939 -0
  573. package/dist/src/session-command-supervisor.js.map +1 -0
  574. package/dist/src/sessionjsonl.js +85 -0
  575. package/dist/src/sessionjsonl.js.map +1 -0
  576. package/dist/src/sessions.js +541 -0
  577. package/dist/src/sessions.js.map +1 -0
  578. package/dist/src/setup/adapters.js +330 -0
  579. package/dist/src/setup/adapters.js.map +1 -0
  580. package/dist/src/setup/baselines.js +64 -0
  581. package/dist/src/setup/baselines.js.map +1 -0
  582. package/dist/src/setup/caserunner.js +554 -0
  583. package/dist/src/setup/caserunner.js.map +1 -0
  584. package/dist/src/setup/configcommit.js +358 -0
  585. package/dist/src/setup/configcommit.js.map +1 -0
  586. package/dist/src/setup/engine.js +672 -0
  587. package/dist/src/setup/engine.js.map +1 -0
  588. package/dist/src/setup/facts.js +593 -0
  589. package/dist/src/setup/facts.js.map +1 -0
  590. package/dist/src/setup/findings.js +948 -0
  591. package/dist/src/setup/findings.js.map +1 -0
  592. package/dist/src/setup/history.js +338 -0
  593. package/dist/src/setup/history.js.map +1 -0
  594. package/dist/src/setup/interview.js +185 -0
  595. package/dist/src/setup/interview.js.map +1 -0
  596. package/dist/src/setup/ledger.js +207 -0
  597. package/dist/src/setup/ledger.js.map +1 -0
  598. package/dist/src/setup/pins.js +43 -0
  599. package/dist/src/setup/pins.js.map +1 -0
  600. package/dist/src/setup/portrait.js +432 -0
  601. package/dist/src/setup/portrait.js.map +1 -0
  602. package/dist/src/setup/presentation.js +9 -0
  603. package/dist/src/setup/presentation.js.map +1 -0
  604. package/dist/src/setup/profile.js +417 -0
  605. package/dist/src/setup/profile.js.map +1 -0
  606. package/dist/src/setup/replay.js +240 -0
  607. package/dist/src/setup/replay.js.map +1 -0
  608. package/dist/src/setup/replaybuild.js +311 -0
  609. package/dist/src/setup/replaybuild.js.map +1 -0
  610. package/dist/src/setup/replayrun.js +806 -0
  611. package/dist/src/setup/replayrun.js.map +1 -0
  612. package/dist/src/setup/replaysource.js +169 -0
  613. package/dist/src/setup/replaysource.js.map +1 -0
  614. package/dist/src/setup/retune.js +333 -0
  615. package/dist/src/setup/retune.js.map +1 -0
  616. package/dist/src/setup/retunelog.js +51 -0
  617. package/dist/src/setup/retunelog.js.map +1 -0
  618. package/dist/src/setup/scan.js +725 -0
  619. package/dist/src/setup/scan.js.map +1 -0
  620. package/dist/src/setup/scanbuild.js +133 -0
  621. package/dist/src/setup/scanbuild.js.map +1 -0
  622. package/dist/src/setup/scanfacts.js +96 -0
  623. package/dist/src/setup/scanfacts.js.map +1 -0
  624. package/dist/src/setup/scanlease.js +251 -0
  625. package/dist/src/setup/scanlease.js.map +1 -0
  626. package/dist/src/setup/scanreport.js +289 -0
  627. package/dist/src/setup/scanreport.js.map +1 -0
  628. package/dist/src/setup/scanrun.js +1733 -0
  629. package/dist/src/setup/scanrun.js.map +1 -0
  630. package/dist/src/setup/scantree.js +257 -0
  631. package/dist/src/setup/scantree.js.map +1 -0
  632. package/dist/src/setup/stage2.js +617 -0
  633. package/dist/src/setup/stage2.js.map +1 -0
  634. package/dist/src/setup/stage2request.js +259 -0
  635. package/dist/src/setup/stage2request.js.map +1 -0
  636. package/dist/src/setup/survey.js +307 -0
  637. package/dist/src/setup/survey.js.map +1 -0
  638. package/dist/src/setup/surveyrun.js +549 -0
  639. package/dist/src/setup/surveyrun.js.map +1 -0
  640. package/dist/src/setup/textsniff.js +89 -0
  641. package/dist/src/setup/textsniff.js.map +1 -0
  642. package/dist/src/setup/trackersink.js +395 -0
  643. package/dist/src/setup/trackersink.js.map +1 -0
  644. package/dist/src/setup/triage.js +636 -0
  645. package/dist/src/setup/triage.js.map +1 -0
  646. package/dist/src/setup/tuning.js +585 -0
  647. package/dist/src/setup/tuning.js.map +1 -0
  648. package/dist/src/setup/unitcut.js +437 -0
  649. package/dist/src/setup/unitcut.js.map +1 -0
  650. package/dist/src/singleflight.js +72 -0
  651. package/dist/src/singleflight.js.map +1 -0
  652. package/dist/src/slack/announce.js +725 -0
  653. package/dist/src/slack/announce.js.map +1 -0
  654. package/dist/src/slack/api.js +227 -0
  655. package/dist/src/slack/api.js.map +1 -0
  656. package/dist/src/slack/messages.js +158 -0
  657. package/dist/src/slack/messages.js.map +1 -0
  658. package/dist/src/slack/origin.js +550 -0
  659. package/dist/src/slack/origin.js.map +1 -0
  660. package/dist/src/slack/policy.js +225 -0
  661. package/dist/src/slack/policy.js.map +1 -0
  662. package/dist/src/slack/reply.js +153 -0
  663. package/dist/src/slack/reply.js.map +1 -0
  664. package/dist/src/slack/router.js +189 -0
  665. package/dist/src/slack/router.js.map +1 -0
  666. package/dist/src/slack/socket.js +323 -0
  667. package/dist/src/slack/socket.js.map +1 -0
  668. package/dist/src/slack/types.js +11 -0
  669. package/dist/src/slack/types.js.map +1 -0
  670. package/dist/src/sqlitehint.js +30 -0
  671. package/dist/src/sqlitehint.js.map +1 -0
  672. package/dist/src/ssh-observation.js +25 -0
  673. package/dist/src/ssh-observation.js.map +1 -0
  674. package/dist/src/state.js +5869 -0
  675. package/dist/src/state.js.map +1 -0
  676. package/dist/src/statemachine.js +220 -0
  677. package/dist/src/statemachine.js.map +1 -0
  678. package/dist/src/stubs.js +52 -0
  679. package/dist/src/stubs.js.map +1 -0
  680. package/dist/src/takeover.js +506 -0
  681. package/dist/src/takeover.js.map +1 -0
  682. package/dist/src/takeoverpi.js +249 -0
  683. package/dist/src/takeoverpi.js.map +1 -0
  684. package/dist/src/terminal-failure.js +26 -0
  685. package/dist/src/terminal-failure.js.map +1 -0
  686. package/dist/src/thread-resolution-refusal.js +4 -0
  687. package/dist/src/thread-resolution-refusal.js.map +1 -0
  688. package/dist/src/tmux.js +126 -0
  689. package/dist/src/tmux.js.map +1 -0
  690. package/dist/src/trackerid.js +43 -0
  691. package/dist/src/trackerid.js.map +1 -0
  692. package/dist/src/transcriptindex.js +512 -0
  693. package/dist/src/transcriptindex.js.map +1 -0
  694. package/dist/src/tui/actions.js +404 -0
  695. package/dist/src/tui/actions.js.map +1 -0
  696. package/dist/src/tui/alerts.js +523 -0
  697. package/dist/src/tui/alerts.js.map +1 -0
  698. package/dist/src/tui/app.js +1835 -0
  699. package/dist/src/tui/app.js.map +1 -0
  700. package/dist/src/tui/archiveops.js +164 -0
  701. package/dist/src/tui/archiveops.js.map +1 -0
  702. package/dist/src/tui/autofile.js +191 -0
  703. package/dist/src/tui/autofile.js.map +1 -0
  704. package/dist/src/tui/board.js +1143 -0
  705. package/dist/src/tui/board.js.map +1 -0
  706. package/dist/src/tui/candidates.js +699 -0
  707. package/dist/src/tui/candidates.js.map +1 -0
  708. package/dist/src/tui/cellwidth.js +151 -0
  709. package/dist/src/tui/cellwidth.js.map +1 -0
  710. package/dist/src/tui/changelog-store.js +404 -0
  711. package/dist/src/tui/changelog-store.js.map +1 -0
  712. package/dist/src/tui/chord.js +26 -0
  713. package/dist/src/tui/chord.js.map +1 -0
  714. package/dist/src/tui/clipboard.js +81 -0
  715. package/dist/src/tui/clipboard.js.map +1 -0
  716. package/dist/src/tui/cmdbar.js +253 -0
  717. package/dist/src/tui/cmdbar.js.map +1 -0
  718. package/dist/src/tui/companion.js +1275 -0
  719. package/dist/src/tui/companion.js.map +1 -0
  720. package/dist/src/tui/companionplan.js +200 -0
  721. package/dist/src/tui/companionplan.js.map +1 -0
  722. package/dist/src/tui/companiontab.js +61 -0
  723. package/dist/src/tui/companiontab.js.map +1 -0
  724. package/dist/src/tui/confighome.js +105 -0
  725. package/dist/src/tui/confighome.js.map +1 -0
  726. package/dist/src/tui/connect.js +44 -0
  727. package/dist/src/tui/connect.js.map +1 -0
  728. package/dist/src/tui/deploy-ceremony.js +75 -0
  729. package/dist/src/tui/deploy-ceremony.js.map +1 -0
  730. package/dist/src/tui/deploy-drain.js +93 -0
  731. package/dist/src/tui/deploy-drain.js.map +1 -0
  732. package/dist/src/tui/deploy-process.js +234 -0
  733. package/dist/src/tui/deploy-process.js.map +1 -0
  734. package/dist/src/tui/deploytab.js +1263 -0
  735. package/dist/src/tui/deploytab.js.map +1 -0
  736. package/dist/src/tui/design.js +111 -0
  737. package/dist/src/tui/design.js.map +1 -0
  738. package/dist/src/tui/digesttab.js +148 -0
  739. package/dist/src/tui/digesttab.js.map +1 -0
  740. package/dist/src/tui/emptystate.js +32 -0
  741. package/dist/src/tui/emptystate.js.map +1 -0
  742. package/dist/src/tui/evidence.js +790 -0
  743. package/dist/src/tui/evidence.js.map +1 -0
  744. package/dist/src/tui/feed.js +415 -0
  745. package/dist/src/tui/feed.js.map +1 -0
  746. package/dist/src/tui/feedbacktab.js +907 -0
  747. package/dist/src/tui/feedbacktab.js.map +1 -0
  748. package/dist/src/tui/filters.js +72 -0
  749. package/dist/src/tui/filters.js.map +1 -0
  750. package/dist/src/tui/ghread.js +272 -0
  751. package/dist/src/tui/ghread.js.map +1 -0
  752. package/dist/src/tui/historypull.js +61 -0
  753. package/dist/src/tui/historypull.js.map +1 -0
  754. package/dist/src/tui/inboxpull.js +197 -0
  755. package/dist/src/tui/inboxpull.js.map +1 -0
  756. package/dist/src/tui/index.js +1425 -0
  757. package/dist/src/tui/index.js.map +1 -0
  758. package/dist/src/tui/issuesink.js +206 -0
  759. package/dist/src/tui/issuesink.js.map +1 -0
  760. package/dist/src/tui/jsonfile.js +32 -0
  761. package/dist/src/tui/jsonfile.js.map +1 -0
  762. package/dist/src/tui/keys.js +97 -0
  763. package/dist/src/tui/keys.js.map +1 -0
  764. package/dist/src/tui/layout.js +70 -0
  765. package/dist/src/tui/layout.js.map +1 -0
  766. package/dist/src/tui/learning.js +469 -0
  767. package/dist/src/tui/learning.js.map +1 -0
  768. package/dist/src/tui/lessonswitch.js +103 -0
  769. package/dist/src/tui/lessonswitch.js.map +1 -0
  770. package/dist/src/tui/livecontrol.js +167 -0
  771. package/dist/src/tui/livecontrol.js.map +1 -0
  772. package/dist/src/tui/memforward.js +310 -0
  773. package/dist/src/tui/memforward.js.map +1 -0
  774. package/dist/src/tui/memincident.js +287 -0
  775. package/dist/src/tui/memincident.js.map +1 -0
  776. package/dist/src/tui/memory.js +485 -0
  777. package/dist/src/tui/memory.js.map +1 -0
  778. package/dist/src/tui/memtab.js +513 -0
  779. package/dist/src/tui/memtab.js.map +1 -0
  780. package/dist/src/tui/metricstab.js +979 -0
  781. package/dist/src/tui/metricstab.js.map +1 -0
  782. package/dist/src/tui/model.js +104 -0
  783. package/dist/src/tui/model.js.map +1 -0
  784. package/dist/src/tui/mouse.js +83 -0
  785. package/dist/src/tui/mouse.js.map +1 -0
  786. package/dist/src/tui/msg.js +196 -0
  787. package/dist/src/tui/msg.js.map +1 -0
  788. package/dist/src/tui/nativecompanion.js +321 -0
  789. package/dist/src/tui/nativecompanion.js.map +1 -0
  790. package/dist/src/tui/openurl.js +17 -0
  791. package/dist/src/tui/openurl.js.map +1 -0
  792. package/dist/src/tui/paint.js +501 -0
  793. package/dist/src/tui/paint.js.map +1 -0
  794. package/dist/src/tui/panes.js +74 -0
  795. package/dist/src/tui/panes.js.map +1 -0
  796. package/dist/src/tui/pipane.js +166 -0
  797. package/dist/src/tui/pipane.js.map +1 -0
  798. package/dist/src/tui/poll-hooks.js +11 -0
  799. package/dist/src/tui/poll-hooks.js.map +1 -0
  800. package/dist/src/tui/poll.js +113 -0
  801. package/dist/src/tui/poll.js.map +1 -0
  802. package/dist/src/tui/proposals.js +715 -0
  803. package/dist/src/tui/proposals.js.map +1 -0
  804. package/dist/src/tui/prpause.js +132 -0
  805. package/dist/src/tui/prpause.js.map +1 -0
  806. package/dist/src/tui/reload.js +351 -0
  807. package/dist/src/tui/reload.js.map +1 -0
  808. package/dist/src/tui/remote.js +312 -0
  809. package/dist/src/tui/remote.js.map +1 -0
  810. package/dist/src/tui/rollover.js +767 -0
  811. package/dist/src/tui/rollover.js.map +1 -0
  812. package/dist/src/tui/scantab.js +1053 -0
  813. package/dist/src/tui/scantab.js.map +1 -0
  814. package/dist/src/tui/setupentry.js +81 -0
  815. package/dist/src/tui/setupentry.js.map +1 -0
  816. package/dist/src/tui/sidebar.js +602 -0
  817. package/dist/src/tui/sidebar.js.map +1 -0
  818. package/dist/src/tui/sshtriage.js +393 -0
  819. package/dist/src/tui/sshtriage.js.map +1 -0
  820. package/dist/src/tui/startup.js +172 -0
  821. package/dist/src/tui/startup.js.map +1 -0
  822. package/dist/src/tui/tabs.js +41 -0
  823. package/dist/src/tui/tabs.js.map +1 -0
  824. package/dist/src/tui/termmirror.js +217 -0
  825. package/dist/src/tui/termmirror.js.map +1 -0
  826. package/dist/src/tui/theme.js +95 -0
  827. package/dist/src/tui/theme.js.map +1 -0
  828. package/dist/src/tui/ticketstab.js +224 -0
  829. package/dist/src/tui/ticketstab.js.map +1 -0
  830. package/dist/src/tui/tmuxsocket.js +101 -0
  831. package/dist/src/tui/tmuxsocket.js.map +1 -0
  832. package/dist/src/tui/trackertickets.js +77 -0
  833. package/dist/src/tui/trackertickets.js.map +1 -0
  834. package/dist/src/tui/unwedge.js +245 -0
  835. package/dist/src/tui/unwedge.js.map +1 -0
  836. package/dist/src/tui/viewer.js +479 -0
  837. package/dist/src/tui/viewer.js.map +1 -0
  838. package/dist/src/tui/viewerinput.js +125 -0
  839. package/dist/src/tui/viewerinput.js.map +1 -0
  840. package/dist/src/updatenotice.js +143 -0
  841. package/dist/src/updatenotice.js.map +1 -0
  842. package/dist/src/usage.js +1007 -0
  843. package/dist/src/usage.js.map +1 -0
  844. package/dist/src/verdicts.js +3848 -0
  845. package/dist/src/verdicts.js.map +1 -0
  846. package/dist/src/version.js +58 -0
  847. package/dist/src/version.js.map +1 -0
  848. package/dist/src/wakes.js +411 -0
  849. package/dist/src/wakes.js.map +1 -0
  850. package/dist/src/webhook.js +725 -0
  851. package/dist/src/webhook.js.map +1 -0
  852. package/dist/src/webhookheal.js +238 -0
  853. package/dist/src/webhookheal.js.map +1 -0
  854. package/dist/src/wire.js +1801 -0
  855. package/dist/src/wire.js.map +1 -0
  856. package/dist/src/worker-session-host.js +531 -0
  857. package/dist/src/worker-session-host.js.map +1 -0
  858. package/dist/src/workerbreaker.js +112 -0
  859. package/dist/src/workerbreaker.js.map +1 -0
  860. package/dist/src/workercontrol.js +49 -0
  861. package/dist/src/workercontrol.js.map +1 -0
  862. package/dist/src/workerlease.js +351 -0
  863. package/dist/src/workerlease.js.map +1 -0
  864. package/dist/src/workerliveness.js +84 -0
  865. package/dist/src/workerliveness.js.map +1 -0
  866. package/dist/src/workerlog.js +65 -0
  867. package/dist/src/workerlog.js.map +1 -0
  868. package/dist/src/workerruntime.js +10 -0
  869. package/dist/src/workerruntime.js.map +1 -0
  870. package/dist/src/workersupervisor.js +1206 -0
  871. package/dist/src/workersupervisor.js.map +1 -0
  872. package/dist/src/workerusage.js +23 -0
  873. package/dist/src/workerusage.js.map +1 -0
  874. package/dist/src/workerworld.js +332 -0
  875. package/dist/src/workerworld.js.map +1 -0
  876. package/dist/src/worktrees.js +984 -0
  877. package/dist/src/worktrees.js.map +1 -0
  878. package/docker/Dockerfile +284 -0
  879. package/docker/Dockerfile.dockerignore +35 -0
  880. package/docker/README.md +221 -0
  881. package/docker/companion/Dockerfile +139 -0
  882. package/docker/companion/agents/evidence.md +34 -0
  883. package/docker/companion/agents/explore.md +26 -0
  884. package/docker/companion/agents/pass-survey.md +49 -0
  885. package/docker/companion/agents/study.md +26 -0
  886. package/docker/companion/entrypoint.sh +343 -0
  887. package/docker/companion/memory-health.mjs +269 -0
  888. package/docker/entrypoint.sh +782 -0
  889. package/docker/memory/Dockerfile +191 -0
  890. package/docker/memory/Dockerfile.dockerignore +20 -0
  891. package/docker/memory/README.md +60 -0
  892. package/docker/memory/entrypoint.sh +163 -0
  893. package/docker/setid/qcr-setid.c +109 -0
  894. package/docker/tunnel/Dockerfile.cloudflared +51 -0
  895. package/docker/tunnel/Dockerfile.ngrok +40 -0
  896. package/docker/tunnel/sidecar.mjs +411 -0
  897. package/docker/vm/tmux.conf +26 -0
  898. package/docker-compose.yml +348 -0
  899. package/extensions/cli/bash-guard-ambient.ts +71 -0
  900. package/extensions/cli/bash-guard.ts +12 -0
  901. package/extensions/cli/companion-guard.ts +8 -0
  902. package/extensions/cli/debug-round.ts +9 -0
  903. package/extensions/cli/inject-bridge.ts +8 -0
  904. package/extensions/cli/ledger-pin.ts +13 -0
  905. package/extensions/cli/lens.ts +22 -0
  906. package/extensions/cli/muted-theme.ts +9 -0
  907. package/extensions/cli/onboarding.ts +8 -0
  908. package/extensions/cli/setup.ts +8 -0
  909. package/extensions/cli/tui-view.ts +8 -0
  910. package/extensions/companion-guard.ts +411 -0
  911. package/extensions/debug-round.ts +281 -0
  912. package/extensions/inject-bridge.ts +630 -0
  913. package/extensions/inline-bash.ts +102 -0
  914. package/extensions/muted-theme.json +68 -0
  915. package/extensions/muted-theme.ts +23 -0
  916. package/extensions/onboarding.ts +233 -0
  917. package/extensions/setup-replay.ts +336 -0
  918. package/extensions/setup-scan.ts +1236 -0
  919. package/extensions/setup-triage.ts +190 -0
  920. package/extensions/setup.ts +1428 -0
  921. package/extensions/tui-view.ts +489 -0
  922. package/extensions/vendor/openviking-pi/LICENSE +201 -0
  923. package/extensions/vendor/openviking-pi/NOTICE +68 -0
  924. package/extensions/vendor/openviking-pi/README.md +550 -0
  925. package/extensions/vendor/openviking-pi/client.ts +588 -0
  926. package/extensions/vendor/openviking-pi/config.json +28 -0
  927. package/extensions/vendor/openviking-pi/config.ts +271 -0
  928. package/extensions/vendor/openviking-pi/index.ts +523 -0
  929. package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
  930. package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
  931. package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
  932. package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
  933. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
  934. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
  935. package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
  936. package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
  937. package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
  938. package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
  939. package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
  940. package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
  941. package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
  942. package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
  943. package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
  944. package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
  945. package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
  946. package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
  947. package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
  948. package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
  949. package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
  950. package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
  951. package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
  952. package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
  953. package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
  954. package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
  955. package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
  956. package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
  957. package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
  958. package/extensions/vendor/openviking-pi/recall.ts +149 -0
  959. package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
  960. package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
  961. package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
  962. package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
  963. package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
  964. package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
  965. package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
  966. package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
  967. package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
  968. package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
  969. package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
  970. package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
  971. package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
  972. package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
  973. package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
  974. package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
  975. package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  976. package/extensions/vendor/openviking-pi/sync.ts +163 -0
  977. package/extensions/vendor/openviking-pi/takeover.ts +36 -0
  978. package/extensions/vendor/openviking-pi/tools.ts +479 -0
  979. package/extensions/vendor/openviking-pi/upstream.json +48 -0
  980. package/package.json +138 -4
  981. package/profiles/default/README.md +21 -0
  982. package/profiles/default/addenda/.gitkeep +0 -0
  983. package/profiles/default/profile.toml +17 -0
  984. package/profiles/example-django/README.md +21 -0
  985. package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
  986. package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
  987. package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
  988. package/profiles/example-django/addenda/40-orm-usage.md +19 -0
  989. package/profiles/example-django/addenda/50-declared-shape.md +10 -0
  990. package/profiles/example-django/addenda/60-config-placement.md +14 -0
  991. package/profiles/example-django/heuristics.toml +24 -0
  992. package/profiles/example-django/profile.toml +9 -0
  993. package/profiles/fast/README.md +24 -0
  994. package/profiles/fast/profile.toml +59 -0
  995. package/profiles/minimal/README.md +17 -0
  996. package/profiles/minimal/addenda/.gitkeep +0 -0
  997. package/profiles/minimal/profile.toml +8 -0
  998. package/profiles/snappy/README.md +24 -0
  999. package/profiles/snappy/addenda/10-race-as-question.md +6 -0
  1000. package/profiles/snappy/profile.toml +46 -0
  1001. package/prompts/companion-day-open.md +46 -0
  1002. package/prompts/companion-first-run.md +104 -0
  1003. package/prompts/companion.md +821 -0
  1004. package/prompts/conversation-system.md +117 -0
  1005. package/prompts/eod-sweep.md +120 -0
  1006. package/prompts/onboarding.md +74 -0
  1007. package/prompts/pass-common.md +52 -0
  1008. package/prompts/pass-quality-common.md +22 -0
  1009. package/prompts/round-first.md +874 -0
  1010. package/prompts/round-follow-up.md +991 -0
  1011. package/prompts/round-re-review.md +889 -0
  1012. package/prompts/round-recheck.md +196 -0
  1013. package/prompts/round-staleness.md +856 -0
  1014. package/schemas/review-result.v1.json +391 -0
  1015. package/schemas/scan-result.v1.json +421 -0
  1016. package/schemas/version.v1.json +57 -0
  1017. package/scripts/approve-pr.sh +16 -0
  1018. package/scripts/build-stamp.mjs +67 -0
  1019. package/scripts/commit-verdict.sh +155 -0
  1020. package/scripts/companion-e2e.sh +250 -0
  1021. package/scripts/companion-memory-forward.mjs +1966 -0
  1022. package/scripts/config-fingerprint.sh +28 -0
  1023. package/scripts/deploy-manifest.mjs +141 -0
  1024. package/scripts/deploy.sh +1435 -0
  1025. package/scripts/directory-lock.mjs +193 -0
  1026. package/scripts/heartbeat-fresh.mjs +54 -0
  1027. package/scripts/install-locator.sh +328 -0
  1028. package/scripts/install-wrapper.sh +76 -0
  1029. package/scripts/memory-search-check.mjs +98 -0
  1030. package/scripts/mint-token.sh +212 -0
  1031. package/scripts/openviking-embedder.py +392 -0
  1032. package/scripts/openviking-ov-conf.mjs +161 -0
  1033. package/scripts/openviking-provision.mjs +1122 -0
  1034. package/scripts/openviking-reembed.mjs +1154 -0
  1035. package/scripts/post-review.sh +16 -0
  1036. package/scripts/pr-checks.sh +256 -0
  1037. package/scripts/pr-context.py +174 -0
  1038. package/scripts/pr-context.sh +63 -0
  1039. package/scripts/pr-self.sh +144 -0
  1040. package/scripts/pr-threads.sh +145 -0
  1041. package/scripts/qcr +15 -0
  1042. package/scripts/qcr-host.sh +769 -0
  1043. package/scripts/render-review.sh +24 -0
  1044. package/scripts/reply-thread.sh +16 -0
  1045. package/scripts/required-passes.py +112 -0
  1046. package/scripts/smoke-companion.sh +211 -0
  1047. package/scripts/tui-deploy-runner.mjs +61 -0
  1048. package/scripts/verify-dist.mjs +201 -0
  1049. package/scripts/vm-ghcr-login.sh +188 -0
  1050. package/seeds/agents/pass-baseline.md +61 -0
  1051. package/seeds/agents/pass-contracts.md +30 -0
  1052. package/seeds/agents/pass-data-safety.md +54 -0
  1053. package/seeds/agents/pass-delivery.md +37 -0
  1054. package/seeds/agents/pass-prod-risk.md +24 -0
  1055. package/seeds/agents/pass-security.md +31 -0
  1056. package/seeds/agents/pass-statelessness.md +27 -0
  1057. package/seeds/agents/pass-ui.md +23 -0
  1058. package/seeds/agents/pass-validate.md +50 -0
  1059. package/seeds/prompts/conversation-system.md +117 -0
  1060. package/seeds/prompts/eod-sweep.md +120 -0
  1061. package/seeds/prompts/pass-common.md +52 -0
  1062. package/skills/companion/adjudicate/SKILL.md +247 -0
  1063. package/skills/companion/evidence/SKILL.md +150 -0
  1064. package/skills/companion/land/SKILL.md +200 -0
  1065. package/skills/companion/replay/SKILL.md +138 -0
  1066. package/skills/companion/setup/SKILL.md +167 -0
  1067. package/skills/companion/survey/SKILL.md +160 -0
  1068. package/skills/selfimprove/README.md +34 -0
  1069. package/skills/selfimprove/SKILL.md +127 -0
  1070. package/skills/selfimprove/agents/openai.yaml +2 -0
  1071. package/skills/selfimprove/commands/selfimprove.md +6 -0
  1072. package/skills/selfimprove/references/fix-loop.md +49 -0
  1073. package/skills/selfimprove/references/ledger.md +20 -0
  1074. package/skills/selfimprove/references/setup.md +40 -0
  1075. package/src/anomalies.ts +566 -0
  1076. package/src/answerworker.ts +251 -0
  1077. package/src/appstamp.ts +65 -0
  1078. package/src/archive/admission.ts +3 -0
  1079. package/src/archive/archivedb.ts +548 -0
  1080. package/src/archive/archiver.ts +1963 -0
  1081. package/src/archive/archivewrite.ts +279 -0
  1082. package/src/archive/bundle.ts +333 -0
  1083. package/src/archive/dbmove.ts +911 -0
  1084. package/src/archive/duplicatecounts.ts +98 -0
  1085. package/src/archive/eligibility.ts +181 -0
  1086. package/src/archive/extract.ts +342 -0
  1087. package/src/archive/fsutil.ts +415 -0
  1088. package/src/archive/gzfile.ts +370 -0
  1089. package/src/archive/kinds.ts +21 -0
  1090. package/src/archive/livecheckpoint.ts +217 -0
  1091. package/src/archive/liveretention.ts +180 -0
  1092. package/src/archive/manifest.ts +519 -0
  1093. package/src/archive/overseer.ts +336 -0
  1094. package/src/archive/piglobal.ts +174 -0
  1095. package/src/archive/plan.ts +612 -0
  1096. package/src/archive/prarchive.ts +984 -0
  1097. package/src/archive/prfiles.ts +406 -0
  1098. package/src/archive/report.ts +252 -0
  1099. package/src/archive/restore.ts +330 -0
  1100. package/src/archive/rows.ts +187 -0
  1101. package/src/archive/seal.ts +630 -0
  1102. package/src/archive/status.ts +719 -0
  1103. package/src/archive/storepass.ts +581 -0
  1104. package/src/backfill.ts +362 -0
  1105. package/src/bench/cost.ts +113 -0
  1106. package/src/bench/exec.ts +90 -0
  1107. package/src/bench/values.ts +13 -0
  1108. package/src/botcontrol.ts +176 -0
  1109. package/src/botworker.ts +953 -0
  1110. package/src/capture.ts +321 -0
  1111. package/src/cases.ts +1789 -0
  1112. package/src/channelfs.ts +26 -0
  1113. package/src/checklist.ts +506 -0
  1114. package/src/ciretry.ts +141 -0
  1115. package/src/cli.ts +2271 -0
  1116. package/src/cliops.ts +384 -0
  1117. package/src/clonedir.ts +15 -0
  1118. package/src/commandeffects.ts +57 -0
  1119. package/src/commands.ts +1678 -0
  1120. package/src/companioninbox.ts +239 -0
  1121. package/src/compare.ts +957 -0
  1122. package/src/config.ts +2050 -0
  1123. package/src/configcheck.ts +133 -0
  1124. package/src/coreext.ts +47 -0
  1125. package/src/daemon.ts +1302 -0
  1126. package/src/debuground.ts +927 -0
  1127. package/src/debugroundpi.ts +262 -0
  1128. package/src/diagnostics.ts +14 -0
  1129. package/src/digest.ts +183 -0
  1130. package/src/dispatch.ts +2250 -0
  1131. package/src/engine/answerguard.ts +27 -0
  1132. package/src/engine/botsdk.ts +336 -0
  1133. package/src/engine/caserunner-sdk.ts +423 -0
  1134. package/src/engine/debugpi.ts +235 -0
  1135. package/src/eod.ts +669 -0
  1136. package/src/eodpinwatch.ts +556 -0
  1137. package/src/escalation.ts +1253 -0
  1138. package/src/eventloop.ts +1045 -0
  1139. package/src/execboundary.ts +633 -0
  1140. package/src/feedback.ts +1162 -0
  1141. package/src/findingscheck.ts +298 -0
  1142. package/src/fsdenied.ts +24 -0
  1143. package/src/ghread.ts +242 -0
  1144. package/src/github.ts +1497 -0
  1145. package/src/homes.ts +175 -0
  1146. package/src/hostroute.ts +53 -0
  1147. package/src/improvements.ts +787 -0
  1148. package/src/install.ts +1103 -0
  1149. package/src/installkind.ts +50 -0
  1150. package/src/ipc/capability.ts +333 -0
  1151. package/src/ipc/client.ts +269 -0
  1152. package/src/ipc/control.ts +90 -0
  1153. package/src/ipc/ghreadshim.ts +94 -0
  1154. package/src/ipc/launcher.ts +65 -0
  1155. package/src/ipc/permits.ts +362 -0
  1156. package/src/ipc/postshim.ts +164 -0
  1157. package/src/ipc/protocol.ts +342 -0
  1158. package/src/ipc/server.ts +371 -0
  1159. package/src/ipc/workerspec.ts +218 -0
  1160. package/src/learner/learner.ts +1655 -0
  1161. package/src/learner/miner.ts +1290 -0
  1162. package/src/learner/packet.ts +340 -0
  1163. package/src/learner/receipt.ts +168 -0
  1164. package/src/learner/status.ts +107 -0
  1165. package/src/ledgercheck.ts +157 -0
  1166. package/src/lessonledger.ts +703 -0
  1167. package/src/lessonselect.ts +888 -0
  1168. package/src/lessonshadow.ts +810 -0
  1169. package/src/levers.ts +393 -0
  1170. package/src/live/projection.ts +491 -0
  1171. package/src/live/publish.ts +92 -0
  1172. package/src/live/read.ts +86 -0
  1173. package/src/livecli.ts +336 -0
  1174. package/src/loginhint.ts +56 -0
  1175. package/src/loginrefresh/atomic.ts +76 -0
  1176. package/src/loginrefresh/lock.ts +104 -0
  1177. package/src/loginrefresh/main.ts +59 -0
  1178. package/src/loginrefresh/pirefresh.ts +57 -0
  1179. package/src/loginrefresh/refresher.ts +326 -0
  1180. package/src/loginrefresh/supervisor.ts +273 -0
  1181. package/src/loginrefresh/types.ts +35 -0
  1182. package/src/memory/context.ts +385 -0
  1183. package/src/memory/datafence.ts +9 -0
  1184. package/src/memory/delivery.ts +171 -0
  1185. package/src/memory/eventtags.ts +80 -0
  1186. package/src/memory/health.ts +953 -0
  1187. package/src/memory/import.ts +664 -0
  1188. package/src/memory/layout.ts +261 -0
  1189. package/src/memory/legacy-file.ts +104 -0
  1190. package/src/memory/lessons.ts +447 -0
  1191. package/src/memory/ovsession.ts +601 -0
  1192. package/src/memory/relayout.ts +335 -0
  1193. package/src/memory/roundactivity.ts +209 -0
  1194. package/src/memory/selection.ts +989 -0
  1195. package/src/memory/shadowstatus.ts +125 -0
  1196. package/src/memory/store.ts +275 -0
  1197. package/src/memory/textidentity.ts +8 -0
  1198. package/src/metrics.ts +1056 -0
  1199. package/src/missingconfig.ts +7 -0
  1200. package/src/npmverbs.ts +264 -0
  1201. package/src/onboard/choices.ts +222 -0
  1202. package/src/onboard/engine.ts +518 -0
  1203. package/src/onboard/launch.ts +115 -0
  1204. package/src/onboard/steps/appslug.ts +265 -0
  1205. package/src/onboard/steps/bringup.ts +482 -0
  1206. package/src/onboard/steps/compose.ts +459 -0
  1207. package/src/onboard/steps/configdelivery.ts +58 -0
  1208. package/src/onboard/steps/configfill.ts +1352 -0
  1209. package/src/onboard/steps/deploysh.ts +105 -0
  1210. package/src/onboard/steps/embeddings.ts +550 -0
  1211. package/src/onboard/steps/escalation.ts +640 -0
  1212. package/src/onboard/steps/githubapp.ts +1414 -0
  1213. package/src/onboard/steps/golive.ts +577 -0
  1214. package/src/onboard/steps/index.ts +71 -0
  1215. package/src/onboard/steps/ingress.ts +907 -0
  1216. package/src/onboard/steps/inventory.ts +526 -0
  1217. package/src/onboard/steps/llmauth.ts +1095 -0
  1218. package/src/onboard/steps/memory.ts +1290 -0
  1219. package/src/onboard/steps/memorykeys.ts +85 -0
  1220. package/src/onboard/steps/preflight.ts +191 -0
  1221. package/src/onboard/steps/proof.ts +624 -0
  1222. package/src/onboard/steps/ssh.ts +377 -0
  1223. package/src/onboard/steps/topology.ts +337 -0
  1224. package/src/onboard/steps/transport.ts +665 -0
  1225. package/src/onboard/steps/verifyup.ts +448 -0
  1226. package/src/onboard/steps/vmdeploy.ts +442 -0
  1227. package/src/onboard/toml.ts +50 -0
  1228. package/src/opencli.ts +591 -0
  1229. package/src/openpr.ts +568 -0
  1230. package/src/openviking/client.ts +1131 -0
  1231. package/src/openviking/context.ts +263 -0
  1232. package/src/openviking/embedder.ts +516 -0
  1233. package/src/openviking/gapsink.ts +63 -0
  1234. package/src/openviking/health.ts +83 -0
  1235. package/src/openviking/index.ts +119 -0
  1236. package/src/openviking/lessonfind.ts +92 -0
  1237. package/src/openviking/probe.ts +129 -0
  1238. package/src/openviking/proc.ts +110 -0
  1239. package/src/openviking/provisionreport.ts +160 -0
  1240. package/src/openviking/reembed.ts +412 -0
  1241. package/src/openviking/relayout.ts +177 -0
  1242. package/src/openviking/sidecar-main.ts +38 -0
  1243. package/src/openviking/sidecar.ts +106 -0
  1244. package/src/openviking/store.ts +2025 -0
  1245. package/src/openviking/supervisor.ts +474 -0
  1246. package/src/openviking/types.ts +174 -0
  1247. package/src/operator-recovery.ts +237 -0
  1248. package/src/operatormessage.ts +52 -0
  1249. package/src/outcomes.ts +1670 -0
  1250. package/src/piauthcopy.ts +12 -0
  1251. package/src/picli.ts +49 -0
  1252. package/src/picwd.ts +25 -0
  1253. package/src/pilogin.ts +37 -0
  1254. package/src/pingpong.ts +1339 -0
  1255. package/src/posting-journal.ts +549 -0
  1256. package/src/posting-outbox.ts +802 -0
  1257. package/src/posting-receipt.ts +324 -0
  1258. package/src/posting.ts +603 -0
  1259. package/src/postops.ts +832 -0
  1260. package/src/pr-activity.ts +20 -0
  1261. package/src/pr-author.ts +6 -0
  1262. package/src/pr-conversation.ts +432 -0
  1263. package/src/pr-session-lifecycle.ts +631 -0
  1264. package/src/preflight.ts +391 -0
  1265. package/src/priorresolve.ts +343 -0
  1266. package/src/qcrbin.ts +186 -0
  1267. package/src/recheck.ts +1775 -0
  1268. package/src/reconcile.ts +1030 -0
  1269. package/src/recordedfindings.ts +27 -0
  1270. package/src/regression.ts +833 -0
  1271. package/src/replay.ts +816 -0
  1272. package/src/requiredpasses.ts +148 -0
  1273. package/src/resumecheck.ts +246 -0
  1274. package/src/reviewcli.ts +787 -0
  1275. package/src/reviewcontext.ts +35 -0
  1276. package/src/reviewdetach.ts +734 -0
  1277. package/src/reviewhistory.ts +427 -0
  1278. package/src/reviewhome.ts +36 -0
  1279. package/src/reviewmemory.ts +133 -0
  1280. package/src/reviewrendercli.ts +139 -0
  1281. package/src/round-work.ts +42 -0
  1282. package/src/runhost.ts +108 -0
  1283. package/src/scanauth.ts +36 -0
  1284. package/src/scanbox.ts +1081 -0
  1285. package/src/scancli.ts +1296 -0
  1286. package/src/scandriverlog.ts +32 -0
  1287. package/src/session-bridge.ts +139 -0
  1288. package/src/session-command-payload.ts +35 -0
  1289. package/src/session-command-supervisor.ts +1142 -0
  1290. package/src/sessionjsonl.ts +88 -0
  1291. package/src/sessions.ts +659 -0
  1292. package/src/setup/adapters.ts +335 -0
  1293. package/src/setup/baselines.ts +110 -0
  1294. package/src/setup/caserunner.ts +611 -0
  1295. package/src/setup/configcommit.ts +443 -0
  1296. package/src/setup/engine.ts +1001 -0
  1297. package/src/setup/facts.ts +627 -0
  1298. package/src/setup/findings.ts +1264 -0
  1299. package/src/setup/history.ts +443 -0
  1300. package/src/setup/interview.ts +243 -0
  1301. package/src/setup/ledger.ts +324 -0
  1302. package/src/setup/pins.ts +66 -0
  1303. package/src/setup/portrait.ts +635 -0
  1304. package/src/setup/presentation.ts +18 -0
  1305. package/src/setup/profile.ts +508 -0
  1306. package/src/setup/replay.ts +370 -0
  1307. package/src/setup/replaybuild.ts +392 -0
  1308. package/src/setup/replayrun.ts +1096 -0
  1309. package/src/setup/replaysource.ts +198 -0
  1310. package/src/setup/retune.ts +415 -0
  1311. package/src/setup/retunelog.ts +95 -0
  1312. package/src/setup/scan.ts +1033 -0
  1313. package/src/setup/scanbuild.ts +168 -0
  1314. package/src/setup/scanfacts.ts +107 -0
  1315. package/src/setup/scanlease.ts +297 -0
  1316. package/src/setup/scanreport.ts +373 -0
  1317. package/src/setup/scanrun.ts +2268 -0
  1318. package/src/setup/scantree.ts +259 -0
  1319. package/src/setup/stage2.ts +926 -0
  1320. package/src/setup/stage2request.ts +347 -0
  1321. package/src/setup/survey.ts +458 -0
  1322. package/src/setup/surveyrun.ts +758 -0
  1323. package/src/setup/textsniff.ts +95 -0
  1324. package/src/setup/trackersink.ts +433 -0
  1325. package/src/setup/triage.ts +768 -0
  1326. package/src/setup/tuning.ts +766 -0
  1327. package/src/setup/unitcut.ts +523 -0
  1328. package/src/singleflight.ts +86 -0
  1329. package/src/slack/announce.ts +911 -0
  1330. package/src/slack/api.ts +303 -0
  1331. package/src/slack/messages.ts +288 -0
  1332. package/src/slack/origin.ts +599 -0
  1333. package/src/slack/policy.ts +354 -0
  1334. package/src/slack/reply.ts +201 -0
  1335. package/src/slack/router.ts +285 -0
  1336. package/src/slack/socket.ts +384 -0
  1337. package/src/slack/types.ts +108 -0
  1338. package/src/sqlitehint.ts +32 -0
  1339. package/src/ssh-observation.ts +30 -0
  1340. package/src/state.ts +8243 -0
  1341. package/src/statemachine.ts +369 -0
  1342. package/src/stubs.ts +68 -0
  1343. package/src/takeover.ts +634 -0
  1344. package/src/takeoverpi.ts +293 -0
  1345. package/src/terminal-failure.ts +41 -0
  1346. package/src/thread-resolution-refusal.ts +2 -0
  1347. package/src/tmux.ts +153 -0
  1348. package/src/trackerid.ts +41 -0
  1349. package/src/transcriptindex.ts +665 -0
  1350. package/src/tui/actions.ts +484 -0
  1351. package/src/tui/alerts.ts +666 -0
  1352. package/src/tui/app.ts +1979 -0
  1353. package/src/tui/archiveops.ts +192 -0
  1354. package/src/tui/autofile.ts +233 -0
  1355. package/src/tui/board.ts +1432 -0
  1356. package/src/tui/candidates.ts +889 -0
  1357. package/src/tui/cellwidth.ts +148 -0
  1358. package/src/tui/changelog-store.ts +503 -0
  1359. package/src/tui/chord.ts +25 -0
  1360. package/src/tui/clipboard.ts +92 -0
  1361. package/src/tui/cmdbar.ts +296 -0
  1362. package/src/tui/companion.ts +1427 -0
  1363. package/src/tui/companionplan.ts +247 -0
  1364. package/src/tui/companiontab.ts +83 -0
  1365. package/src/tui/confighome.ts +155 -0
  1366. package/src/tui/connect.ts +68 -0
  1367. package/src/tui/deploy-ceremony.ts +104 -0
  1368. package/src/tui/deploy-drain.ts +129 -0
  1369. package/src/tui/deploy-process.ts +265 -0
  1370. package/src/tui/deploytab.ts +1523 -0
  1371. package/src/tui/design.ts +125 -0
  1372. package/src/tui/digesttab.ts +214 -0
  1373. package/src/tui/emptystate.ts +39 -0
  1374. package/src/tui/evidence.ts +999 -0
  1375. package/src/tui/feed.ts +604 -0
  1376. package/src/tui/feedbacktab.ts +1077 -0
  1377. package/src/tui/filters.ts +90 -0
  1378. package/src/tui/ghread.ts +321 -0
  1379. package/src/tui/historypull.ts +81 -0
  1380. package/src/tui/inboxpull.ts +236 -0
  1381. package/src/tui/index.ts +1565 -0
  1382. package/src/tui/issuesink.ts +238 -0
  1383. package/src/tui/jsonfile.ts +42 -0
  1384. package/src/tui/keys.ts +109 -0
  1385. package/src/tui/layout.ts +108 -0
  1386. package/src/tui/learning.ts +627 -0
  1387. package/src/tui/lessonswitch.ts +118 -0
  1388. package/src/tui/livecontrol.ts +228 -0
  1389. package/src/tui/memforward.ts +443 -0
  1390. package/src/tui/memincident.ts +363 -0
  1391. package/src/tui/memory.ts +620 -0
  1392. package/src/tui/memtab.ts +606 -0
  1393. package/src/tui/metricstab.ts +1177 -0
  1394. package/src/tui/model.ts +131 -0
  1395. package/src/tui/mouse.ts +98 -0
  1396. package/src/tui/msg.ts +268 -0
  1397. package/src/tui/nativecompanion.ts +417 -0
  1398. package/src/tui/openurl.ts +21 -0
  1399. package/src/tui/paint.ts +511 -0
  1400. package/src/tui/panes.ts +98 -0
  1401. package/src/tui/pipane.ts +219 -0
  1402. package/src/tui/poll-hooks.ts +12 -0
  1403. package/src/tui/poll.ts +147 -0
  1404. package/src/tui/proposals.ts +838 -0
  1405. package/src/tui/prpause.ts +150 -0
  1406. package/src/tui/reload.ts +436 -0
  1407. package/src/tui/remote.ts +413 -0
  1408. package/src/tui/rollover.ts +969 -0
  1409. package/src/tui/scantab.ts +1290 -0
  1410. package/src/tui/setupentry.ts +93 -0
  1411. package/src/tui/sidebar.ts +749 -0
  1412. package/src/tui/sshtriage.ts +510 -0
  1413. package/src/tui/startup.ts +231 -0
  1414. package/src/tui/tabs.ts +45 -0
  1415. package/src/tui/termmirror.ts +224 -0
  1416. package/src/tui/theme.ts +95 -0
  1417. package/src/tui/ticketstab.ts +310 -0
  1418. package/src/tui/tmuxsocket.ts +129 -0
  1419. package/src/tui/trackertickets.ts +84 -0
  1420. package/src/tui/unwedge.ts +286 -0
  1421. package/src/tui/viewer.ts +575 -0
  1422. package/src/tui/viewerinput.ts +173 -0
  1423. package/src/updatenotice.ts +178 -0
  1424. package/src/usage.ts +1154 -0
  1425. package/src/verdicts.ts +4689 -0
  1426. package/src/version.ts +80 -0
  1427. package/src/wakes.ts +476 -0
  1428. package/src/webhook.ts +942 -0
  1429. package/src/webhookheal.ts +276 -0
  1430. package/src/wire.ts +2153 -0
  1431. package/src/worker-session-host.ts +602 -0
  1432. package/src/workerbreaker.ts +156 -0
  1433. package/src/workercontrol.ts +104 -0
  1434. package/src/workerlease.ts +441 -0
  1435. package/src/workerliveness.ts +103 -0
  1436. package/src/workerlog.ts +79 -0
  1437. package/src/workerruntime.ts +30 -0
  1438. package/src/workersupervisor.ts +1538 -0
  1439. package/src/workerusage.ts +42 -0
  1440. package/src/workerworld.ts +427 -0
  1441. package/src/worktrees.ts +1138 -0
  1442. package/tests/fixtures/regression-cases.json +795 -0
  1443. package/tests/fixtures/seed-manifest.json +19 -0
@@ -0,0 +1,1427 @@
1
+ // Local companion container lifecycle.
2
+ //
3
+ // The shape mirrors remote.ts: command construction is pure so tests can assert
4
+ // the exact argv, execution goes through one injectable runner, and nothing
5
+ // here ever kills anything the user did not explicitly ask to stop.
6
+ //
7
+ // Lifecycle contract:
8
+ // `qcr tui` start → adopt a running container, else `docker start`, else
9
+ // `docker run` it. Quit → `docker stop` (conversation, pihome auth and
10
+ // the pi home survive on volumes; next start is a fast resume). TUI crash →
11
+ // the container keeps running and the next start adopts it. A second
12
+ // concurrent TUI attaches to the same container, never spawns a twin:
13
+ // ensureUp() and spawnPi() are both idempotent. A container goes away only
14
+ // on the explicit `qcr companion down`, or when ensureUp() recreates one
15
+ // whose mounts or DNS drifted from runArgv() or that can no longer start —
16
+ // the conversation and the pi home survive both on their volumes.
17
+ import { spawn } from "node:child_process";
18
+ import {
19
+ cpSync,
20
+ existsSync,
21
+ mkdirSync,
22
+ mkdtempSync,
23
+ readFileSync,
24
+ rmSync,
25
+ statSync,
26
+ } from "node:fs";
27
+ import { homedir, tmpdir } from "node:os";
28
+ import { isAbsolute, join } from "node:path";
29
+ import {
30
+ companionConfigMirrorDir,
31
+ CONFIG_FILE,
32
+ companionGithubDir,
33
+ configHome,
34
+ KEYS_FILE,
35
+ homeProfileDirs,
36
+ legacySecretsDir,
37
+ locateConfig,
38
+ locateKeysFile,
39
+ PORTRAIT_DIR,
40
+ PROFILES_DIR,
41
+ stateHome,
42
+ type InstallEnv,
43
+ } from "../install.js";
44
+ import type { CompanionConfig } from "../config.js";
45
+ import { writeAtomic } from "../channelfs.js";
46
+ import { appStamp } from "../appstamp.js";
47
+ import { TOKEN_FILE } from "./ghread.js";
48
+
49
+ export interface DockerResult {
50
+ ok: boolean;
51
+ stdout: string;
52
+ stderr: string;
53
+ code: number | null;
54
+ }
55
+
56
+ /** Runs `docker <argv…>`. Injectable — tests never spawn docker. */
57
+ export type DockerRunner = (argv: string[], opts?: { timeoutMs?: number }) => Promise<DockerResult>;
58
+
59
+ export interface ContainerStatus {
60
+ exists: boolean;
61
+ running: boolean;
62
+ /** `dest -> source` for every mount the container was created with; empty
63
+ * when it does not exist or docker could not be asked. */
64
+ mounts: Map<string, string>;
65
+ /** Set when docker could not be asked (daemon down, permission denied):
66
+ * `exists`/`running` are then false by default, not by observation. Absent
67
+ * when the inspect succeeded or docker said the container does not exist. */
68
+ unknown?: string;
69
+ }
70
+
71
+ /** The one word `qcr companion status` prints — "unknown — <why>" when docker
72
+ * could not be asked, so a failed inspect never reads as a confirmed absence. */
73
+ export function companionStateWord(st: ContainerStatus): string {
74
+ if (st.unknown !== undefined) return `unknown — docker inspect failed: ${st.unknown}`;
75
+ return st.running ? "running" : st.exists ? "stopped" : "absent";
76
+ }
77
+
78
+ /**
79
+ * The companion's resolvers, pinned at `docker run`. Without this, docker's
80
+ * embedded DNS forwards to whatever upstream the host had at container
81
+ * creation — and that upstream is network-specific, so the first network
82
+ * change after a long-lived start leaves every lookup dead (pi: "Error: fetch
83
+ * failed" on all model calls). Public resolvers are identical on every
84
+ * network, and the companion needs no LAN names: it talks to public endpoints
85
+ * and to the memory bridge by IP.
86
+ */
87
+ export const PUBLIC_DNS = ["1.1.1.1", "8.8.8.8"];
88
+
89
+ /** The filesystem facts the lifecycle needs. Injectable so argv tests stay
90
+ * hermetic; the real one is plain node:fs. */
91
+ export interface CompanionFs {
92
+ exists(path: string): boolean;
93
+ /** File text, or undefined when it cannot be read. */
94
+ read(path: string): string | undefined;
95
+ /** Unix mode bits of a file, or undefined. */
96
+ mode(path: string): number | undefined;
97
+ }
98
+
99
+ export const realCompanionFs: CompanionFs = {
100
+ exists: (p) => existsSync(p),
101
+ read: (p) => {
102
+ try {
103
+ return readFileSync(p, "utf8");
104
+ } catch {
105
+ return undefined;
106
+ }
107
+ },
108
+ mode: (p) => {
109
+ try {
110
+ return statSync(p).mode & 0o777;
111
+ } catch {
112
+ return undefined;
113
+ }
114
+ },
115
+ };
116
+
117
+ /** In-container paths. Fixed by the image (docker/Dockerfile, run in its
118
+ * `companion` mode), so they are constants rather than knobs — the host side
119
+ * of each is configured. */
120
+ export const CONTAINER = {
121
+ /** The release image's fixed HOME (`ENV HOME` in docker/Dockerfile),
122
+ * writable by whatever uid the container runs as. */
123
+ home: "/home/qcr",
124
+ piHome: "/home/qcr/.pi",
125
+ /** The image's app root: the companion's extensions, its system prompt and
126
+ * its agent set are loaded from here, never from the /workspace
127
+ * mount, so a change to them lands with `qcr companion rebuild`. */
128
+ app: "/opt/qcr",
129
+ /** The owner's checkout, read-write: the repo the companion works on and
130
+ * commits to. It is the working directory, and the data it is about — not
131
+ * where its code comes from. */
132
+ workspace: "/workspace",
133
+ tracker: "/tracker",
134
+ /** The tracker's protocol docs, read-only. `/tracker` carries the
135
+ * issues; the doc that says how to write them lives one level up on the
136
+ * host, outside every other mount. */
137
+ trackerDocs: "/tracker-docs",
138
+ /** The reviewed repo, read-only. The companion reads the code a
139
+ * review is about; `:ro` is the whole guarantee that it can never touch that
140
+ * repo's `master`. Mounted only when the host checkout exists — a missing
141
+ * one leaves the companion under-equipped, never broken. A generic path:
142
+ * the container path is not the place a repo's name belongs. */
143
+ reviewedRepo: "/reviewed-repo",
144
+ /** The install's config home, in part: `profiles/` read-write — setup
145
+ * writes and re-tunes the repo profile there — `portrait/` read-write (the
146
+ * portrait narrative), and a mirror of the live `config.toml` read-only.
147
+ * Deliberately not the whole home: it holds
148
+ * `secrets/` (the App's private key, which can write to GitHub), and the
149
+ * companion's reach into GitHub is the read-only token below and nothing
150
+ * else. Mounting the two pieces rather than masking a subdirectory is what
151
+ * keeps a `secrets/` created later out as well. */
152
+ configHome: "/qcr-config",
153
+ /** Inside `configHome`: the profiles dir (`:rw`) and a read-only mirror of
154
+ * the live config (`configLive`, a directory mount). The live config is not
155
+ * bind-mounted as a file: a file bind pins the inode, and every writer of
156
+ * config.toml (the onboarding wizard, most editors) replaces it by rename —
157
+ * the companion would read the old text until the container was recreated,
158
+ * and no drift check can see it (the source path never changes). The host
159
+ * copies the located config into the mirror dir instead
160
+ * (`syncConfigMirror`), and a rename inside a mounted dir is visible.
161
+ * Spelled out so the in-container paths are literals — nothing here builds
162
+ * a host config path (the locator does). */
163
+ configLive: "/qcr-config/live",
164
+ configFile: "/qcr-config/live/config.toml",
165
+ profiles: "/qcr-config/profiles",
166
+ /** Inside `configHome`: the repo portrait's narrative (`:rw`) —
167
+ * setup writes it there, keeping the one it replaced. */
168
+ portrait: "/qcr-config/portrait",
169
+ /** The install's state home, read-write: the survey, the scan
170
+ * ledger, findings and the portrait facts — PC-only, never shipped. */
171
+ stateHome: "/qcr-state",
172
+ /** The down-scoped read-only GitHub token, as an env file the host re-mints
173
+ * before it expires (`src/tui/ghread.ts`). `:ro` — the file is the
174
+ * host's to write; the token itself cannot write to GitHub either way. */
175
+ githubRead: "/github-read",
176
+ /** Finished-round evidence, read-only. The TUI holds the VM access and
177
+ * copies each finished round's artefacts down here; the companion reads
178
+ * plain files. That is the whole of the transport — it grants no ssh, no
179
+ * credential and no network reach (the companion boundary).
180
+ * `:ro` because this is a record: read history, never rewrite it. */
181
+ evidence: "/evidence",
182
+ feed: "/feed",
183
+ alerts: "/alerts",
184
+ /** The shared tmux-socket volume (`tmux-socket` mode): the companion's
185
+ * tmux server listens inside it, and the containerised TUI mounts the same
186
+ * volume to drive that server with `tmux -S`. The image creates the dir
187
+ * 1777, so a fresh named volume is writable by whatever uid runs. */
188
+ tmuxDir: "/run/qcr-tmux",
189
+ } as const;
190
+
191
+ /** How the companion's tmux server is reached.
192
+ *
193
+ * docker `docker exec <container> tmux …`, the server on tmux's
194
+ * default socket inside the container. The clone route, where
195
+ * this process can run docker.
196
+ * tmux-socket the server listens on a socket in a named volume that the
197
+ * containerised TUI shares, so the TUI drives it with
198
+ * `tmux -S <socket>` and holds no docker.sock. The container
199
+ * itself is run by the host wrapper (scripts/qcr-host.sh),
200
+ * from the argv `qcr companion plan` prints. */
201
+ export type CompanionMode = "docker" | "tmux-socket";
202
+
203
+ /** The named volume holding the companion's tmux socket. */
204
+ export function tmuxVolume(cfg: Pick<CompanionConfig, "container_name">): string {
205
+ return `${cfg.container_name}-tmux`;
206
+ }
207
+
208
+ /** The companion's tmux server socket, the same path in both containers. */
209
+ export function tmuxSocketPath(cfg: Pick<CompanionConfig, "session">): string {
210
+ return `${CONTAINER.tmuxDir}/${cfg.session}.sock`;
211
+ }
212
+
213
+ /** Set on the companion container in `tmux-socket` mode: the socket its
214
+ * entrypoint starts the tmux server on (docker/entrypoint.sh). */
215
+ export const TMUX_SOCKET_ENV = "QCR_TMUX_SOCKET";
216
+
217
+ /** The companion's ssh access to the VM (the companion boundary: full ssh, so
218
+ * it may deploy). Exactly three host
219
+ * sources, each read-only and each only when the host has it: the ssh agent's
220
+ * socket, the ssh config and `known_hosts` — the shape the containerised TUI
221
+ * gets. Never `~/.ssh` whole, never a private key file, never
222
+ * `docker.sock`: the keys stay in the host's agent. */
223
+ export const SSH = {
224
+ agent: "/run/qcr-ssh-agent",
225
+ config: `${CONTAINER.home}/.ssh/config`,
226
+ knownHosts: `${CONTAINER.home}/.ssh/known_hosts`,
227
+ } as const;
228
+
229
+ /** In `tmux-socket` mode this process sees none of the host's files, so the
230
+ * host wrapper checks the three ssh sources itself and names the present ones
231
+ * in these env vars (scripts/qcr-host.sh); the planner mounts what they name. */
232
+ export const HOST_SSH_ENV = {
233
+ agent: "QCR_HOST_SSH_AUTH_SOCK",
234
+ config: "QCR_HOST_SSH_CONFIG",
235
+ knownHosts: "QCR_HOST_SSH_KNOWN_HOSTS",
236
+ } as const;
237
+
238
+ /** The host's ssh sources the companion is given, present ones only. */
239
+ export interface SshSources {
240
+ agent?: string;
241
+ config?: string;
242
+ knownHosts?: string;
243
+ }
244
+
245
+ /** An absolute single-line path, else undefined. */
246
+ function hostPath(v: string | undefined): string | undefined {
247
+ const t = (v ?? "").trim();
248
+ return t !== "" && isAbsolute(t) && !/[\n\r]/.test(t) ? t : undefined;
249
+ }
250
+
251
+ /** How long a containerised TUI waits for the wrapper-run companion's tmux
252
+ * server (the wrapper starts the container just before it starts the TUI). */
253
+ const WRAPPER_START_WAIT_MS = 20_000;
254
+
255
+ export type EnsureOutcome = "adopted" | "started" | "created" | "recreated";
256
+
257
+ /** The vendored patched OpenViking pi extension, relative to the app root. */
258
+ const VENDOR_EXTENSION = "extensions/vendor/openviking-pi/index.ts";
259
+
260
+ // ---- the memory forward -----------------------------------------------------
261
+
262
+ /**
263
+ * Constants of the host-owned memory path. Memory never rides ssh:
264
+ * a systemd --user forward on the owner's PC listens on a docker bridge the
265
+ * companion is attached to and relays to OpenViking inside the VM container.
266
+ *
267
+ * The subnet is fixed so the gateway address is knowable before the network
268
+ * exists; `resolveMemoryWiring()` still asks docker for the real gateway and
269
+ * only falls back to this constant.
270
+ */
271
+ export const MEMORY = {
272
+ /** User-defined docker bridge shared by the forward and the companion. */
273
+ network: "qcr-memory",
274
+ subnet: "198.51.100.0/24",
275
+ gateway: "198.51.100.1",
276
+ /** Same port the server listens on in the VM container ([openviking] port). */
277
+ port: 1933,
278
+ /** The OpenViking user whose memory the companion owns — becomes the
279
+ * `X-OpenViking-User` header on every request it makes. */
280
+ user: "companion",
281
+ /** Peer id the patched extension reports (`X-OpenViking-Actor-Peer`);
282
+ * verified to override the path-derived default. Same
283
+ * identity as `user` for the companion — one surface, one actor. */
284
+ peerId: "companion",
285
+ /** The vendored patched OpenViking pi extension, inside the image. */
286
+ extension: `${CONTAINER.app}/${VENDOR_EXTENSION}`,
287
+ /** The companion namespace's retired URIs, relative to the state home.
288
+ * The TUI's memory store publishes them there on the host
289
+ * (no daemon sweeps the companion's tree); the extension's per-turn recall
290
+ * reads them from the mount (`QCR_OV_RETIRED_FILE`, set in `piEnvPrefix`)
291
+ * and sends them as `exclude_uris`, so a retired companion memory is never
292
+ * recalled — not only one whose own text still opens with the tag. */
293
+ retiredFile: "openviking/companion-retired-uris.json",
294
+ } as const;
295
+
296
+ /** The companion's lens tools (`lens`, `lens_wave`; packages/pi-plugin/extensions/lens.ts), which
297
+ * stand where pi's example subagent extension used to. They run a lens pass as
298
+ * an SDK session of the companion's own pi process, over its own login and
299
+ * reusing pi's HTTP dispatcher, and read their agents from the agents dir
300
+ * under pi's agent dir, which the entrypoint links at the companion agent set
301
+ * (explore/evidence/study, docker/companion/agents in the image). A lens
302
+ * session is never given a lens tool (leaves, rule 9), and there is no cwd or
303
+ * scope knob to pin: the tool takes `{agent, task}` and nothing else. */
304
+ export const LENS = {
305
+ extension: `${CONTAINER.app}/extensions/cli/lens.ts`,
306
+ /** The companion's process-wide pass cap: 8, because a survey or scan fans
307
+ * out one case per unit. Rides `QCR_SUBAGENT_MAX_CONCURRENCY` on the pi
308
+ * command line (the name the retired subagents glue gave it, which
309
+ * the lens tool and extensions/setup.ts still read). Pinned by
310
+ * tests/tui-companion.test.ts. */
311
+ maxConcurrency: 8,
312
+ } as const;
313
+
314
+ export type ConfigMirrorOutcome =
315
+ "copied" | "unchanged" | "removed" | "absent" | { failed: string };
316
+
317
+ /** Mirror `source` (the host's live config, or undefined when there is none)
318
+ * into `dir/config.toml`. Pure fs, total — see `syncConfigMirror`. */
319
+ export function mirrorConfigFile(source: string | undefined, dir: string): ConfigMirrorOutcome {
320
+ const target = join(dir, CONFIG_FILE);
321
+ try {
322
+ if (source === undefined) {
323
+ if (!existsSync(target)) return "absent";
324
+ rmSync(target, { force: true });
325
+ return "removed";
326
+ }
327
+ const text = readFileSync(source, "utf8");
328
+ let current: string | undefined;
329
+ try {
330
+ current = readFileSync(target, "utf8");
331
+ } catch {
332
+ current = undefined;
333
+ }
334
+ if (current === text) return "unchanged";
335
+ writeAtomic(target, text);
336
+ return "copied";
337
+ } catch (err) {
338
+ return { failed: err instanceof Error ? err.message : String(err) };
339
+ }
340
+ }
341
+
342
+ /** The three env names the keys file must define. Values are never read into a
343
+ * log line or an argv — docker reads the file itself via `--env-file`. */
344
+ export const KEY_NAMES = [
345
+ "OPENVIKING_API_KEY",
346
+ "OPENVIKING_COMPANION_KEY",
347
+ "OPENVIKING_REVIEWER_KEY",
348
+ ] as const;
349
+
350
+ export interface MemoryWiring {
351
+ /** Docker network to attach the container to; "" = memory-less degraded. */
352
+ network: string;
353
+ /** Base URL the container reaches OpenViking on; "" = memory-less. */
354
+ endpoint: string;
355
+ /** Host path of the two-key env file, when it is readable. */
356
+ keysFile?: string;
357
+ /** Human notes worth showing on the status line (never secret values). */
358
+ notes: string[];
359
+ }
360
+
361
+ /**
362
+ * Non-secret memory env + the key file, for a container on `wiring`.
363
+ *
364
+ * The two USER keys ride `--env-file`, so the values never appear in this
365
+ * process's argv (and therefore never in `ps`). docker reads the file
366
+ * itself. Never switch these to `--env NAME=<value>`. The companion and the
367
+ * scan sandbox (src/scanbox.ts) both wire memory with it.
368
+ */
369
+ export function memoryEnvArgv(wiring: MemoryWiring): string[] {
370
+ const argv: string[] = [];
371
+ if (wiring.endpoint !== "") {
372
+ argv.push(
373
+ // Read straight out of the vendored extension's credential resolver
374
+ // (shared/credentials.mjs): OPENVIKING_URL wins, OPENVIKING_BASE_URL is
375
+ // its documented alias, and the user/peer headers are what select the
376
+ // namespace a request addresses.
377
+ "--env",
378
+ `OPENVIKING_URL=${wiring.endpoint}`,
379
+ "--env",
380
+ `OPENVIKING_BASE_URL=${wiring.endpoint}`,
381
+ "--env",
382
+ `OPENVIKING_USER=${MEMORY.user}`,
383
+ "--env",
384
+ `OPENVIKING_PEER_ID=${MEMORY.peerId}`,
385
+ // Ours: the in-container health watcher (docker/companion/memory-health.mjs).
386
+ "--env",
387
+ `QCR_MEMORY_ENDPOINT=${wiring.endpoint}`,
388
+ "--env",
389
+ `QCR_MEMORY_ALERTS_DIR=${CONTAINER.alerts}`,
390
+ );
391
+ }
392
+ if (wiring.keysFile) argv.push("--env-file", wiring.keysFile);
393
+ return argv;
394
+ }
395
+
396
+ export function memoryEndpoint(gateway: string, port: number = MEMORY.port): string {
397
+ return `http://${gateway}:${port}`;
398
+ }
399
+
400
+ /** Host path of the OpenViking key file, decided by the install locator:
401
+ * `QCR_OPENVIKING_KEYS`, else the operator home
402
+ * (`$XDG_CONFIG_HOME/qcr-operator/<install>/`, outside every dir a container
403
+ * mounts). 0600, and handed to the container by `--env-file`, so no value
404
+ * reaches an argv. */
405
+ export function keysFilePath(
406
+ env: NodeJS.ProcessEnv = process.env,
407
+ home: string = homedir(),
408
+ ): string {
409
+ return locateKeysFile({ env: { ...env, HOME: home } }).path;
410
+ }
411
+
412
+ /** Which of KEY_NAMES the file defines — names only, values never leave here. */
413
+ export function keyNamesIn(text: string): string[] {
414
+ const found = new Set<string>();
415
+ for (const line of text.split("\n")) {
416
+ const m = /^\s*(?:export\s+)?([A-Z0-9_]+)\s*=/.exec(line);
417
+ if (m && (KEY_NAMES as readonly string[]).includes(m[1]!)) found.add(m[1]!);
418
+ }
419
+ return [...found];
420
+ }
421
+
422
+ /**
423
+ * Lines docker's `--env-file` parser would choke on.
424
+ *
425
+ * This matters more than it looks: `docker run --env-file` rejects a file with
426
+ * `export FOO=…`, `FOO = bar` or a bare word, and a rejected `docker run` is a
427
+ * companion that does not start. Memory must never be able to do that, so a
428
+ * file we cannot vouch for is left out of the argv (degraded, not fatal).
429
+ */
430
+ export function envFileProblems(text: string): string[] {
431
+ const problems: string[] = [];
432
+ text.split("\n").forEach((line, i) => {
433
+ const trimmed = line.trim();
434
+ if (trimmed === "" || trimmed.startsWith("#")) return;
435
+ if (!/^[A-Za-z_][A-Za-z0-9_]*=/.test(line)) problems.push(`line ${i + 1} is not NAME=value`);
436
+ });
437
+ return problems;
438
+ }
439
+
440
+ /** Gateway address out of `docker network inspect -f …` output, or "". */
441
+ export function parseGateway(stdout: string): string {
442
+ const line = stdout.trim().split("\n").pop() ?? "";
443
+ return /^\d{1,3}(\.\d{1,3}){3}$/.test(line.trim()) ? line.trim() : "";
444
+ }
445
+
446
+ /** Host side of the companion's inject dir (`/workspace/inject/<session>`):
447
+ * the rollover's day-open and `qcr setup`'s request land here. */
448
+ export function companionInjectDir(cfg: Pick<CompanionConfig, "repo_dir" | "session">): string {
449
+ return `${cfg.repo_dir}/inject/${cfg.session}`;
450
+ }
451
+
452
+ /** Set on the companion's pi: code that runs there (the setup belt, a
453
+ * `qcr scan` the companion runs) knows it is inside the companion container. */
454
+ export const IN_COMPANION_ENV = "QCR_IN_COMPANION";
455
+
456
+ /** The release image's mode word for the companion container
457
+ * (docker/entrypoint.sh): provision pi, start the tmux server, stay alive. */
458
+ export const COMPANION_MODE = "companion";
459
+
460
+ /** Why the companion image cannot be built from `ctx`, or null: the build
461
+ * compiles the app itself, so all it needs is the release Dockerfile. */
462
+ export function imageBuildProblem(ctx: string, exists: (p: string) => boolean): string | null {
463
+ const dockerfile = join(ctx, "docker", "Dockerfile");
464
+ return exists(dockerfile)
465
+ ? null
466
+ : `the companion image is built from the release Dockerfile, and ${dockerfile} is not there — is ${ctx} a qcr checkout?`;
467
+ }
468
+
469
+ /** `docker build` of the companion image: the release image
470
+ * (docker/Dockerfile, which compiles the app in its own build stage) from the
471
+ * app root `ctx`, as `image`, stamped. Builds an image and nothing else: no
472
+ * container is touched. No uid is baked in: the container is started as the
473
+ * host user (`hostUser`). */
474
+ export function appImageBuildArgv(image: string, ctx: string, stamp: string): string[] {
475
+ return [
476
+ "build",
477
+ "-t",
478
+ image,
479
+ "-f",
480
+ join(ctx, "docker", "Dockerfile"),
481
+ "--build-arg",
482
+ `BUILD_SHA=${stamp}`,
483
+ ctx,
484
+ ];
485
+ }
486
+
487
+ /** The paths of the release image's build context (docker/Dockerfile): the
488
+ * Dockerfile and its own ignore file, then every `!path` that ignore file
489
+ * admits. Only BuildKit reads a per-Dockerfile ignore file; the legacy
490
+ * builder (a Docker without buildx) reads the root .dockerignore, which is the
491
+ * scan sandbox's narrower allowlist, so the context is assembled from this
492
+ * list instead of relying on either — as bench/aacr/bench.sh does. */
493
+ export function releaseContextPaths(ignoreText: string): string[] {
494
+ const admitted = ignoreText
495
+ .split("\n")
496
+ .map((l) => l.trim())
497
+ .filter((l) => l.startsWith("!"))
498
+ .map((l) => l.slice(1).replace(/^\/+/, ""))
499
+ .filter((l) => l !== "");
500
+ return ["docker/Dockerfile", "docker/Dockerfile.dockerignore", ...admitted];
501
+ }
502
+
503
+ /** Copy the release image's context out of `ctx` into a fresh temp dir
504
+ * (node_modules, .git and tsbuildinfo files left out); the caller removes it. */
505
+ export function assembleReleaseContext(ctx: string): string {
506
+ const out = mkdtempSync(join(tmpdir(), "qcr-companion-ctx-"));
507
+ const ignore = readFileSync(join(ctx, "docker", "Dockerfile.dockerignore"), "utf8");
508
+ for (const rel of releaseContextPaths(ignore)) {
509
+ const from = join(ctx, rel);
510
+ if (!existsSync(from)) continue;
511
+ cpSync(from, join(out, rel), {
512
+ recursive: true,
513
+ filter: (src) => !/(^|\/)(node_modules|\.git)(\/|$)|\.tsbuildinfo$/.test(src),
514
+ });
515
+ }
516
+ return out;
517
+ }
518
+
519
+ /** `uid:gid` the companion container runs as: the invoking host user, so the
520
+ * host bind mounts it writes (/workspace, /tracker, /feed, /alerts) stay
521
+ * writable and its files stay yours. The release image bakes no user. */
522
+ export function hostUser(): string {
523
+ const uid = typeof process.getuid === "function" ? process.getuid() : 1000;
524
+ const gid = typeof process.getgid === "function" ? process.getgid() : 1000;
525
+ return `${uid}:${gid}`;
526
+ }
527
+
528
+ export class CompanionLifecycle {
529
+ /** Resolved by ensureUp() before any `docker run`; the constants are the
530
+ * safe standing answer so runArgv() stays pure and callable in tests. */
531
+ private wiring: MemoryWiring = {
532
+ network: MEMORY.network,
533
+ endpoint: memoryEndpoint(MEMORY.gateway),
534
+ notes: [],
535
+ };
536
+
537
+ constructor(
538
+ readonly cfg: CompanionConfig,
539
+ private readonly docker: DockerRunner,
540
+ private readonly env: NodeJS.ProcessEnv = process.env,
541
+ /** Host dirs are created before a cold run so docker does not invent them
542
+ * root-owned. Injectable so tests touch no filesystem. */
543
+ private readonly ensureDirs: (paths: string[]) => void = (paths) => {
544
+ for (const p of paths) mkdirSync(p, { recursive: true });
545
+ },
546
+ /** Filesystem seam: the keys file and the vendored extension are looked up,
547
+ * never assumed. Injectable so argv assertions need no real files. */
548
+ private readonly fs: CompanionFs = realCompanionFs,
549
+ /** The build stamp of the app under the build context (src/appstamp.ts).
550
+ * Injectable so the build argv is assertable without hashing a tree. */
551
+ private readonly stampOf: (root: string) => string = appStamp,
552
+ /** How the tmux server is reached — see `CompanionMode`. */
553
+ readonly mode: CompanionMode = "docker",
554
+ /** Builds the image's context from the app root (`assembleReleaseContext`);
555
+ * injectable so the build argv is assertable without copying a tree. */
556
+ private readonly assembleContext: (ctx: string) => string = assembleReleaseContext,
557
+ ) {}
558
+
559
+ /** How long `ensureUp` waits, in `tmux-socket` mode, for the wrapper-run
560
+ * companion's tmux server. A field so tests need not wait it out. */
561
+ wrapperStartWaitMs = WRAPPER_START_WAIT_MS;
562
+
563
+ /** The shared socket in `tmux-socket` mode, else undefined. */
564
+ get socketPath(): string | undefined {
565
+ return this.mode === "tmux-socket" ? tmuxSocketPath(this.cfg) : undefined;
566
+ }
567
+
568
+ /** `tmux <argv…>`, on the shared socket when there is one. */
569
+ private tmuxArgv(...args: string[]): string[] {
570
+ const socket = this.socketPath;
571
+ return socket === undefined ? ["tmux", ...args] : ["tmux", "-S", socket, ...args];
572
+ }
573
+
574
+ /** Start without memory: no network, no endpoint env (the laptop route, where
575
+ * the memory sidecar is opt-in). Pure; for the argv a host wrapper runs. */
576
+ withoutMemory(): this {
577
+ this.wiring = { network: "", endpoint: "", notes: [] };
578
+ return this;
579
+ }
580
+
581
+ /** The memory path this instance will wire (or has wired). */
582
+ get memory(): MemoryWiring {
583
+ return this.wiring;
584
+ }
585
+
586
+ // ---- pure argv ----------------------------------------------------------
587
+
588
+ /** `docker run` for a cold start: every mount the companion needs,
589
+ * the three ssh sources among them (`SSH`, rule 8). Memory still rides the
590
+ * docker network the host-owned forward listens on, never ssh. */
591
+ runArgv(): string[] {
592
+ const argv = [
593
+ "run",
594
+ "--detach",
595
+ "--name",
596
+ this.cfg.container_name,
597
+ "--hostname",
598
+ this.cfg.container_name,
599
+ ];
600
+ // Memory-less is a legitimate mode: no network, no endpoint env, container
601
+ // still starts. It must never be able to wedge the companion.
602
+ if (this.wiring.network !== "") argv.push("--network", this.wiring.network);
603
+ // Network-roaming survival: see PUBLIC_DNS.
604
+ for (const ip of PUBLIC_DNS) argv.push("--dns", ip);
605
+ // The host user (the image bakes none), and the checkout as the working
606
+ // directory: pi reads the repo's own context files from there, and the
607
+ // image's WORKDIR is its app root, whose reviewer stub is not the
608
+ // companion's to load.
609
+ argv.push("--user", hostUser(), "--workdir", CONTAINER.workspace);
610
+ // The wrapper-run companion is hardened like every other container
611
+ // the wrapper starts; nothing it does needs a capability.
612
+ if (this.socketPath !== undefined)
613
+ argv.push("--init", "--cap-drop", "ALL", "--security-opt", "no-new-privileges");
614
+ for (const m of this.mountSpec())
615
+ argv.push("--volume", `${m.source}:${m.dest}${m.mode ? `:${m.mode}` : ""}`);
616
+ argv.push(...this.memoryEnvArgv());
617
+ // pi's default model, set by the entrypoint on every start.
618
+ argv.push("--env", `QCR_PI_DEFAULT_MODEL=${this.cfg.model}`);
619
+ if (this.socketPath !== undefined) argv.push("--env", `${TMUX_SOCKET_ENV}=${this.socketPath}`);
620
+ // The agent is reached by its mounted socket; no key is in the container.
621
+ if (this.sshSources().agent !== undefined) argv.push("--env", `SSH_AUTH_SOCK=${SSH.agent}`);
622
+ // The release image, in its companion mode (docker/entrypoint.sh).
623
+ argv.push(this.cfg.image, COMPANION_MODE);
624
+ return argv;
625
+ }
626
+
627
+ /** Non-secret memory env + the key file, over this instance's wiring
628
+ * (the free `memoryEnvArgv`). */
629
+ memoryEnvArgv(): string[] {
630
+ return memoryEnvArgv(this.wiring);
631
+ }
632
+
633
+ /**
634
+ * The pi argv inside the companion's tmux session.
635
+ *
636
+ * Every extension, the theme and the system prompt are the IMAGE's
637
+ * (`CONTAINER.app`): the companion runs the code it was built with, and
638
+ * the checkout at /workspace is the repo it works on, not where its hands
639
+ * come from.
640
+ *
641
+ * VERIFY-ON-PIN, every flag (same list as sessions.ts buildPiCommand):
642
+ * `--model` is documented; `--session-dir` is documented for RPC mode only
643
+ * and assumed to apply to the interactive TUI; `--system-prompt` and
644
+ * `--name` are not documented at all — the fallback for `--system-prompt` is
645
+ * writing the body to <pi home>/.pi/SYSTEM.md, which is reachable on the
646
+ * pihome volume without an image rebuild.
647
+ *
648
+ * The session dir lives on the pihome volume so the conversation survives
649
+ * `docker stop` (that is what makes quit-then-restart a resume rather than a
650
+ * fresh start), and so daily rollover can archive "the newest .jsonl".
651
+ */
652
+ piArgv(): string[] {
653
+ const ext = `${CONTAINER.app}/extensions`;
654
+ return [
655
+ `${CONTAINER.app}/node_modules/.bin/pi`,
656
+ // --approve: pre-trust project-local .pi resources for this run. Without
657
+ // it a cwd carrying .pi/ hits pi's interactive "Trust project folder?"
658
+ // modal and the session stalls forever. Same flag sessions.ts passes
659
+ // (the reviewer never blocks on interactive pi input).
660
+ "--approve",
661
+ // The vendored patched OpenViking extension — the companion's memory
662
+ // hands. It rides the image, so it is always there; with no memory
663
+ // wiring it finds no endpoint and stays idle (the companion never wedges).
664
+ "--extension",
665
+ MEMORY.extension,
666
+ // Ambient /feed state is deliberately not an extension: one would turn
667
+ // every meaningful poll into a synthetic user message in the operator's
668
+ // active conversation. The model reads the bounded feed files on demand
669
+ // instead.
670
+ "--extension",
671
+ `${ext}/cli/muted-theme.ts`,
672
+ "--theme",
673
+ `${ext}/muted-theme.json`,
674
+ "--extension",
675
+ // Day-open channel: the daemon's inject bridge, unmodified. It
676
+ // drains its directory on session_start before it starts watching, so
677
+ // the rollover can queue the opening message before pi is spawned and
678
+ // the delivery is race-free. QCR_INJECT_DIR (below) points it at the
679
+ // companion's own dir. VERIFY-ON-PIN: `--extension <path>` again.
680
+ `${ext}/cli/inject-bridge.ts`,
681
+ "--extension",
682
+ // The read-only `tui_*` tools over what the TUI published to
683
+ // /feed (screen.txt + view.json). Reads only — it holds no credential and
684
+ // cannot fetch; see the header of extensions/tui-view.ts for why that is
685
+ // structural and not a promise. QCR_FEED_DIR (below) points it at the
686
+ // same dir the FeedWriter writes. VERIFY-ON-PIN: `--extension <path>`.
687
+ `${ext}/cli/tui-view.ts`,
688
+ "--extension",
689
+ // Our lens tools (`lens`, `lens_wave`) at cap 8 — see LENS and
690
+ // packages/pi-plugin/extensions/lens.ts. First-party and always in the image, so it loads
691
+ // unconditionally like inject-bridge; its lens sessions are SDK
692
+ // sessions of this pi, over pi's own dispatcher. The cap rides
693
+ // QCR_SUBAGENT_MAX_CONCURRENCY in piEnvPrefix (LENS.maxConcurrency).
694
+ LENS.extension,
695
+ "--extension",
696
+ // The companion's bright lines (frozen seeds, no-GitHub,
697
+ // no-discard in the shared checkout) as tool_call vetoes — see the
698
+ // header of extensions/companion-guard.ts. Companion only; the VM has
699
+ // the gate.
700
+ `${ext}/cli/companion-guard.ts`,
701
+ "--extension",
702
+ // The setup & scan tool belt — the deterministic SetupEngine
703
+ // (repo facts, the unit cut) as `setup_*` tools. Reads the reviewed
704
+ // checkout (`:ro`) and writes only the state home; see the header of
705
+ // extensions/setup.ts. Unconditional like every other extension:
706
+ // a missing checkout is a refusal from the tools, never a pi that
707
+ // won't start.
708
+ `${ext}/cli/setup.ts`,
709
+ "--extension",
710
+ // `!{cmd}` inline expansion in typed prompts — vendored
711
+ // verbatim from the pinned pi package's shipped examples (see its
712
+ // header; byte-identical at 0.84.1 and 0.84.4). QoL for the operator
713
+ // typing into the mirrored terminal.
714
+ `${ext}/inline-bash.ts`,
715
+ "--model",
716
+ this.cfg.model,
717
+ "--session-dir",
718
+ companionSessionDir(this.cfg.session),
719
+ "--system-prompt",
720
+ `${CONTAINER.app}/prompts/companion.md`,
721
+ "--name",
722
+ this.cfg.session,
723
+ ];
724
+ }
725
+
726
+ /** QCR_* routing vars. They ride the pi command line, never the client env:
727
+ * tmux panes inherit the tmux server's environment. */
728
+ piEnvPrefix(): string[] {
729
+ return [
730
+ "env",
731
+ `QCR_ROOT=${CONTAINER.workspace}`,
732
+ `QCR_SESSION=${this.cfg.session}`,
733
+ // inject-bridge resolves its session name from this one.
734
+ `QCR_SESSION_NAME=${this.cfg.session}`,
735
+ `QCR_INJECT_DIR=${CONTAINER.workspace}/inject/${this.cfg.session}`,
736
+ `QCR_FEED_DIR=${CONTAINER.feed}`,
737
+ `QCR_ALERTS_DIR=${CONTAINER.alerts}`,
738
+ `QCR_TRACKER_DIR=${CONTAINER.tracker}`,
739
+ // Where the install's homes and the token file sit in here.
740
+ // The locator (src/install.ts) honours QCR_CONFIG_HOME/QCR_STATE_HOME
741
+ // as whole-home overrides, so code run in the container resolves the
742
+ // mounted homes — profiles written by setup land on the host, not in
743
+ // the container user's ~/.config. QCR_CONFIG always names the mirror:
744
+ // when the host has no config the file is simply absent, which the
745
+ // reader reports — never a silent fall back to /workspace/config.toml.
746
+ `QCR_CONFIG_HOME=${CONTAINER.configHome}`,
747
+ `QCR_CONFIG=${CONTAINER.configFile}`,
748
+ `QCR_STATE_HOME=${CONTAINER.stateHome}`,
749
+ // Where the setup tools read the reviewed checkout.
750
+ `QCR_REVIEWED_REPO=${CONTAINER.reviewedRepo}`,
751
+ `QCR_GITHUB_READ_ENV=${CONTAINER.githubRead}/${TOKEN_FILE}`,
752
+ // Survey and scan fan out one case per unit — see LENS.
753
+ `QCR_SUBAGENT_MAX_CONCURRENCY=${LENS.maxConcurrency}`,
754
+ // The lens tool builds its SDK host over pi's own agent dir and refuses
755
+ // a relative or absent one (packages/pi-plugin/extensions/lens.ts); name the one pi uses.
756
+ `PI_CODING_AGENT_DIR=${CONTAINER.piHome}/agent`,
757
+ // The retired URIs the recall excludes — see MEMORY.retiredFile.
758
+ `QCR_OV_RETIRED_FILE=${CONTAINER.stateHome}/${MEMORY.retiredFile}`,
759
+ // What a `qcr scan` run here reads to default its sandbox to the host.
760
+ `${IN_COMPANION_ENV}=1`,
761
+ ];
762
+ }
763
+
764
+ /**
765
+ * Host side of the install-home mounts, from the install locator over this
766
+ * lifecycle's env (so a test's temp XDG homes are what it mounts):
767
+ * `<config home>/profiles` and `<config home>/portrait`, the live config
768
+ * file when one exists (the source of the mirror — never itself mounted),
769
+ * the mirror dir, the state home, and the token dir. Never the config home
770
+ * as a whole — see `CONTAINER.configHome` for why `secrets/` must stay out.
771
+ */
772
+ hostHomes(): {
773
+ profiles: string;
774
+ portrait: string;
775
+ configFile?: string;
776
+ configMirror: string;
777
+ state: string;
778
+ github: string;
779
+ } {
780
+ const env = this.installEnv();
781
+ const located = locateConfig({ env });
782
+ return {
783
+ profiles: join(configHome(env), PROFILES_DIR),
784
+ portrait: join(configHome(env), PORTRAIT_DIR),
785
+ configFile: this.fs.exists(located.path) ? located.path : undefined,
786
+ configMirror: companionConfigMirrorDir(env),
787
+ state: stateHome(env),
788
+ github: companionGithubDir(env),
789
+ };
790
+ }
791
+
792
+ /**
793
+ * Copy the host's live config into the mirror dir the companion mounts
794
+ * (see `CONTAINER.configLive`), or remove the mirror when the host has none
795
+ * — an absent file is the honest answer, a stale one is not. Atomic (tmp +
796
+ * rename inside the mounted dir) and a no-op when nothing changed. Total:
797
+ * a failure is an outcome, never a throw.
798
+ */
799
+ syncConfigMirror(): ConfigMirrorOutcome {
800
+ const homes = this.hostHomes();
801
+ return mirrorConfigFile(homes.configFile, homes.configMirror);
802
+ }
803
+
804
+ private installEnv(): InstallEnv {
805
+ return { ...this.env, HOME: this.env.HOME || homedir() };
806
+ }
807
+
808
+ /** `docker exec … tmux new-session -d -s companion env QCR_… pi …`. */
809
+ spawnArgv(): string[] {
810
+ return [
811
+ "exec",
812
+ this.cfg.container_name,
813
+ ...this.tmuxArgv("new-session", "-d", "-s", this.cfg.session),
814
+ ...this.piEnvPrefix(),
815
+ ...this.piArgv(),
816
+ ];
817
+ }
818
+
819
+ /** Local attach — plain `docker exec -it`, no ssh (this box owns the
820
+ * container). Returned argv is everything after the `docker` binary. */
821
+ attachArgv(): string[] {
822
+ return [
823
+ "exec",
824
+ "-it",
825
+ this.cfg.container_name,
826
+ ...this.tmuxArgv("attach", "-t", this.cfg.session),
827
+ ];
828
+ }
829
+
830
+ /** Host side of `/workspace/inject/<session>` — where the rollover drops the
831
+ * day-open message (the repo is bind-mounted, so no docker exec needed). */
832
+ hostInjectDir(): string {
833
+ return companionInjectDir(this.cfg);
834
+ }
835
+
836
+ /**
837
+ * Is the reviewed repo checked out on this host?
838
+ *
839
+ * The mount is conditional on this, in `runArgv()` and in `expectedMounts()`,
840
+ * so the two never disagree: present ⇒ the container must carry it (absence
841
+ * is drift, and drift recreates); absent ⇒ nothing is expected and a stale
842
+ * mount is still drift. `docker run` would happily invent an empty host dir
843
+ * for a missing bind source, which is worse than no mount: the companion
844
+ * would read an empty tree and conclude the code says nothing.
845
+ */
846
+ reviewedRepoPresent(): boolean {
847
+ return this.cfg.reviewed_repo_dir !== "" && this.fs.exists(this.cfg.reviewed_repo_dir);
848
+ }
849
+
850
+ // ---- state --------------------------------------------------------------
851
+
852
+ async status(): Promise<ContainerStatus> {
853
+ // One inspect answers both questions: is it up, and what was it created
854
+ // with. Line 1 is the running flag; every later line is `dest=source` for
855
+ // one mount. Keeping it to a single call means the drift check
856
+ // costs nothing on the start path.
857
+ const res = await this.docker([
858
+ "inspect",
859
+ "--format",
860
+ // `.Name` for a named volume, `.Source` for a bind. Docker reports a
861
+ // volume's Source as its on-disk _data path, which never equals the
862
+ // volume name the run argv asks for — comparing Source would make the
863
+ // drift check fire on every start and recreate the container each time.
864
+ "{{.State.Running}}\n{{range .Mounts}}{{.Destination}}={{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}}\n{{end}}",
865
+ this.cfg.container_name,
866
+ ]);
867
+ if (!res.ok) {
868
+ const none = { exists: false, running: false, mounts: new Map<string, string>() };
869
+ return /no such (object|container)/i.test(res.stderr)
870
+ ? none
871
+ : { ...none, unknown: errText(res) };
872
+ }
873
+ const [first = "", ...rest] = res.stdout.split("\n");
874
+ const mounts = new Map<string, string>();
875
+ for (const line of rest) {
876
+ const eq = line.indexOf("=");
877
+ if (eq > 0) mounts.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());
878
+ }
879
+ return { exists: true, running: first.trim() === "true", mounts };
880
+ }
881
+
882
+ /**
883
+ * The DNS servers the container was created with, or undefined when docker
884
+ * could not be asked (no opinion — never drift). Its own inspect call on
885
+ * purpose: on docker 29, referencing `.HostConfig` in the same template as
886
+ * the mounts' `{{if .Name}}` makes execution fall back to the raw JSON map,
887
+ * where a bind mount has no "Name" key and the whole inspect errors. Do not
888
+ * fold this back into status().
889
+ */
890
+ async createdDns(): Promise<string[] | undefined> {
891
+ const res = await this.docker([
892
+ "inspect",
893
+ "--format",
894
+ "{{range .HostConfig.Dns}}{{.}}\n{{end}}",
895
+ this.cfg.container_name,
896
+ ]);
897
+ if (!res.ok) return undefined;
898
+ return res.stdout
899
+ .split("\n")
900
+ .map((s) => s.trim())
901
+ .filter((s) => s !== "");
902
+ }
903
+
904
+ /** True when the companion tmux session is already live in the container. */
905
+ async piRunning(): Promise<boolean> {
906
+ const res = await this.docker([
907
+ "exec",
908
+ this.cfg.container_name,
909
+ ...this.tmuxArgv("has-session", "-t", this.cfg.session),
910
+ ]);
911
+ return res.ok;
912
+ }
913
+
914
+ /** Is the companion's tmux server answering on its socket? `show-options`
915
+ * never starts a server, so asking cannot birth one in the wrong container. */
916
+ async serverUp(): Promise<boolean> {
917
+ const res = await this.docker([
918
+ "exec",
919
+ this.cfg.container_name,
920
+ ...this.tmuxArgv("show-options", "-s", "exit-empty"),
921
+ ]);
922
+ return res.ok;
923
+ }
924
+
925
+ // ---- transitions --------------------------------------------------------
926
+
927
+ /** True when the companion image exists locally. */
928
+ async imageExists(): Promise<boolean> {
929
+ const res = await this.docker(["image", "inspect", this.cfg.image]);
930
+ return res.ok;
931
+ }
932
+
933
+ /** Remove the container, running or not. Volumes (pihome: OAuth, the
934
+ * conversation) survive — this replaces the container, never its memory. */
935
+ async removeContainer(): Promise<void> {
936
+ const st = await this.status();
937
+ if (st.unknown) throw new Error(`container status unknown: ${st.unknown}`);
938
+ if (!st.exists) return;
939
+ await this.forceRemove();
940
+ }
941
+
942
+ /** `docker rm -f` the container; a refusal throws with docker's reason. */
943
+ private async forceRemove(): Promise<void> {
944
+ const res = await this.docker(["rm", "-f", this.cfg.container_name]);
945
+ if (!res.ok) throw new Error(`docker rm ${this.cfg.container_name} failed: ${errText(res)}`);
946
+ }
947
+
948
+ /** Throws when the image cannot be built (`imageBuildProblem`) — asked
949
+ * before anything is removed for a rebuild. */
950
+ checkBuildable(): void {
951
+ const problem = imageBuildProblem(this.cfg.build_context, (p) => this.fs.exists(p));
952
+ if (problem !== null) throw new Error(problem);
953
+ }
954
+
955
+ /** Build the companion image (idempotent, cached) from the app root, with
956
+ * the release Dockerfile (docker/Dockerfile): the image compiles the app
957
+ * itself, so nothing needs building on the host first, and it is stamped
958
+ * with the checkout's build stamp. The companion's extensions come from this
959
+ * image, so a change to them lands only with a rebuild. */
960
+ async buildImage(onProgress?: (line: string) => void): Promise<void> {
961
+ this.checkBuildable();
962
+ const ctx = this.cfg.build_context;
963
+ onProgress?.(`building ${this.cfg.image} image (first run — takes a few minutes)…`);
964
+ // The stamp is the checkout's; the build reads an assembled copy of the
965
+ // release context, so it works on the legacy builder too.
966
+ const stamp = this.stampOf(ctx);
967
+ const built = this.assembleContext(ctx);
968
+ let res;
969
+ try {
970
+ res = await this.docker(appImageBuildArgv(this.cfg.image, built, stamp), {
971
+ timeoutMs: 1_800_000,
972
+ });
973
+ } finally {
974
+ if (built !== ctx) rmSync(built, { recursive: true, force: true });
975
+ }
976
+ if (!res.ok) throw new Error(`docker build ${this.cfg.image} failed: ${errText(res)}`);
977
+ onProgress?.(`${this.cfg.image} image built`);
978
+ }
979
+
980
+ // ---- memory wiring --------------------------------------------------------
981
+
982
+ /**
983
+ * Work out how this container will reach memory, without ever failing.
984
+ *
985
+ * The forward (scripts/companion-memory-forward.mjs, run by systemd on the
986
+ * host) owns the network; the lifecycle only joins it. When the network is
987
+ * absent it is created here anyway — idempotent, same fixed subnet — so the
988
+ * container's wiring is stable and memory starts working the moment the
989
+ * forward comes up, rather than needing a container recreate. If even that
990
+ * fails (a subnet clash, docker unhappy) the wiring degrades to memory-less
991
+ * and the companion starts regardless.
992
+ */
993
+ async resolveMemoryWiring(onProgress?: (line: string) => void): Promise<MemoryWiring> {
994
+ const notes: string[] = [];
995
+ let gateway = await this.networkGateway();
996
+ if (gateway === "") {
997
+ await this.docker(["network", "create", "--subnet", MEMORY.subnet, MEMORY.network]);
998
+ gateway = await this.networkGateway();
999
+ if (gateway !== "") notes.push(`created docker network ${MEMORY.network}`);
1000
+ }
1001
+ if (gateway === "") {
1002
+ notes.push(`docker network ${MEMORY.network} unavailable — companion starts without memory`);
1003
+ this.wiring = { network: "", endpoint: "", notes };
1004
+ for (const n of notes) onProgress?.(n);
1005
+ return this.wiring;
1006
+ }
1007
+ const keysFile = keysFilePath(this.env);
1008
+ const keysText = this.fs.read(keysFile);
1009
+ let usableKeys: string | undefined;
1010
+ if (keysText === undefined) {
1011
+ notes.push(
1012
+ `no OpenViking keys at ${keysFile} — memory reads/writes will be refused (see docs/companion-memory-access.md)`,
1013
+ );
1014
+ // The old-place copy is never read; say so when it is the reason.
1015
+ const stranded = `${legacySecretsDir({ ...this.env, HOME: homedir() })}/${KEYS_FILE}`;
1016
+ if (this.fs.exists(stranded))
1017
+ notes.push(
1018
+ `${stranded} is no longer read — \`qcr install migrate\` moves it to ${keysFile}`,
1019
+ );
1020
+ } else {
1021
+ const names = keyNamesIn(keysText);
1022
+ const missing = KEY_NAMES.filter((n) => !names.includes(n));
1023
+ if (missing.length > 0) notes.push(`${keysFile} is missing ${missing.join(", ")}`);
1024
+ const mode = this.fs.mode(keysFile);
1025
+ if (mode !== undefined && (mode & 0o077) !== 0)
1026
+ notes.push(`${keysFile} is group/world readable — chmod 600 it`);
1027
+ const bad = envFileProblems(keysText);
1028
+ if (bad.length > 0) {
1029
+ // Passing it anyway would make `docker run` refuse and the companion
1030
+ // never start — memory is never allowed to cost that.
1031
+ notes.push(
1032
+ `${keysFile} is not a docker env-file (${bad[0]}) — starting without memory keys`,
1033
+ );
1034
+ } else {
1035
+ usableKeys = keysFile;
1036
+ }
1037
+ }
1038
+ this.wiring = {
1039
+ network: MEMORY.network,
1040
+ endpoint: memoryEndpoint(gateway),
1041
+ keysFile: usableKeys,
1042
+ notes,
1043
+ };
1044
+ for (const n of notes) onProgress?.(n);
1045
+ return this.wiring;
1046
+ }
1047
+
1048
+ private async networkGateway(): Promise<string> {
1049
+ const res = await this.docker([
1050
+ "network",
1051
+ "inspect",
1052
+ "--format",
1053
+ "{{range .IPAM.Config}}{{.Gateway}}{{end}}",
1054
+ MEMORY.network,
1055
+ ]);
1056
+ return res.ok ? parseGateway(res.stdout) : "";
1057
+ }
1058
+
1059
+ /** Attach an already-running container to the memory network. Idempotent —
1060
+ * "already exists" is the normal answer and never an error here. */
1061
+ async attachToMemoryNetwork(): Promise<boolean> {
1062
+ if (this.wiring.network === "") return false;
1063
+ const res = await this.docker([
1064
+ "network",
1065
+ "connect",
1066
+ this.wiring.network,
1067
+ this.cfg.container_name,
1068
+ ]);
1069
+ return res.ok;
1070
+ }
1071
+
1072
+ /**
1073
+ * Bind mounts are frozen at creation, so a container that predates a mount
1074
+ * change keeps the old set forever — `docker start` on it silently resurrects
1075
+ * the old boundary. That is not cosmetic: a stale container can carry a
1076
+ * mount the boundary does not grant (an `~/.ssh` whole, a key file, a dead
1077
+ * agent socket) — the companion boundary means its mounts to be structural.
1078
+ *
1079
+ * So: compare what the container has against what `runArgv()` asks for, and
1080
+ * report the drift. Named volumes are compared by name, binds by host path.
1081
+ * Extra mounts count as drift (an `~/.ssh` whole is that case); an unreadable inspect
1082
+ * reports nothing.
1083
+ */
1084
+ mountDrift(actual: Map<string, string>): string[] {
1085
+ if (actual.size === 0) return [];
1086
+ const drift: string[] = [];
1087
+ // An agent socket moves with every login and differs between shells: a
1088
+ // container on another agent socket that is still live keeps its ssh, so
1089
+ // a TUI started from another shell never recreates a running companion.
1090
+ // A dead one is drift like any other.
1091
+ const liveAgent = (dest: string, have: string | undefined): boolean =>
1092
+ dest === SSH.agent && have !== undefined && this.fs.exists(have);
1093
+ for (const [dest, source] of this.expectedMounts()) {
1094
+ const have = actual.get(dest);
1095
+ if (have === undefined) drift.push(`missing ${dest}`);
1096
+ else if (have !== source && !liveAgent(dest, have))
1097
+ drift.push(`${dest} points at ${have}, expected ${source}`);
1098
+ }
1099
+ for (const [dest, have] of actual) {
1100
+ if (!this.expectedMounts().has(dest) && !liveAgent(dest, have)) drift.push(`stale ${dest}`);
1101
+ }
1102
+ return drift;
1103
+ }
1104
+
1105
+ /**
1106
+ * DNS is frozen at creation exactly like the mounts, so a container created
1107
+ * before PUBLIC_DNS was pinned keeps forwarding lookups to a
1108
+ * network-specific upstream forever (see PUBLIC_DNS).
1109
+ * Same remedy as mount drift: recreate. Consulted only after status()
1110
+ * yielded a mount set (see ensureUp) so an unreadable inspect stays
1111
+ * "no opinion", never a recreate.
1112
+ */
1113
+ dnsDrift(actual: string[]): string[] {
1114
+ return actual.join(",") === PUBLIC_DNS.join(",")
1115
+ ? []
1116
+ : [`dns is [${actual.join(", ")}], expected [${PUBLIC_DNS.join(", ")}]`];
1117
+ }
1118
+
1119
+ /**
1120
+ * Every mount the container is created with, in `runArgv()` order — the
1121
+ * one list `runArgv()` turns into `--volume` flags and `expectedMounts()`
1122
+ * (the drift check) reads, so the two can never disagree.
1123
+ */
1124
+ mountSpec(): Array<{ dest: string; source: string; mode?: "ro" | "rw" }> {
1125
+ const spec: Array<{ dest: string; source: string; mode?: "ro" | "rw" }> = [
1126
+ { dest: CONTAINER.piHome, source: this.cfg.pihome_volume },
1127
+ { dest: CONTAINER.workspace, source: this.cfg.repo_dir, mode: "rw" },
1128
+ ];
1129
+ // The tmux socket volume, shared with the containerised TUI. The only
1130
+ // thing in it is the socket; it is no ssh agent and no docker.sock.
1131
+ if (this.socketPath !== undefined)
1132
+ spec.push({ dest: CONTAINER.tmuxDir, source: tmuxVolume(this.cfg), mode: "rw" });
1133
+ // The tracker and its protocol docs are optional: mounted only when the
1134
+ // operator configured them ([companion] tracker_dir / tracker_docs_dir).
1135
+ if (this.cfg.tracker_dir !== "")
1136
+ spec.push({ dest: CONTAINER.tracker, source: this.cfg.tracker_dir, mode: "rw" });
1137
+ if (this.cfg.tracker_docs_dir !== "")
1138
+ spec.push({ dest: CONTAINER.trackerDocs, source: this.cfg.tracker_docs_dir, mode: "ro" });
1139
+ // The reviewed repo, read-only and only when it is really there — see
1140
+ // reviewedRepoPresent().
1141
+ if (this.reviewedRepoPresent())
1142
+ spec.push({ dest: CONTAINER.reviewedRepo, source: this.cfg.reviewed_repo_dir, mode: "ro" });
1143
+ // The evidence store. Created by the TUI before the container starts
1144
+ // (`ensureDirs`), so unlike the reviewed repo it is never absent and the
1145
+ // mount is unconditional — an empty store is a companion that has seen no
1146
+ // rounds yet, which is honest; a missing one would have docker invent a
1147
+ // root-owned dir the copier could not then write.
1148
+ spec.push(
1149
+ { dest: CONTAINER.evidence, source: this.cfg.evidence_dir, mode: "ro" },
1150
+ { dest: CONTAINER.feed, source: this.cfg.feed_dir, mode: "rw" },
1151
+ { dest: CONTAINER.alerts, source: this.cfg.alerts_dir, mode: "rw" },
1152
+ );
1153
+ // The install's homes (profiles/, portrait/, state), the config mirror
1154
+ // and the read-only GitHub token. All directories, created before a cold
1155
+ // run (`ensureDirs`), so unconditional like the evidence store.
1156
+ const homes = this.hostHomes();
1157
+ spec.push(
1158
+ { dest: CONTAINER.profiles, source: homes.profiles, mode: "rw" },
1159
+ { dest: CONTAINER.portrait, source: homes.portrait, mode: "rw" },
1160
+ { dest: CONTAINER.configLive, source: homes.configMirror, mode: "ro" },
1161
+ { dest: CONTAINER.stateHome, source: homes.state, mode: "rw" },
1162
+ { dest: CONTAINER.githubRead, source: homes.github, mode: "ro" },
1163
+ );
1164
+ // The locator also searches beside an explicit config file. Keep that
1165
+ // fallback beside the mirrored config without mounting its secret home.
1166
+ const adjacent = homeProfileDirs(this.installEnv())[1];
1167
+ if (adjacent && adjacent !== homes.profiles && this.fs.exists(adjacent))
1168
+ spec.push({ dest: `${CONTAINER.configLive}/profiles`, source: adjacent, mode: "rw" });
1169
+ // The VM access (rule 8): exactly these three, read-only, present ones
1170
+ // only. An agent socket moves with every login, so a new path is mount
1171
+ // drift and the container is recreated onto it, unless the previous
1172
+ // socket still exists (mountDrift keeps the live agent's mount).
1173
+ const ssh = this.sshSources();
1174
+ if (ssh.agent !== undefined) spec.push({ dest: SSH.agent, source: ssh.agent, mode: "ro" });
1175
+ if (ssh.config !== undefined) spec.push({ dest: SSH.config, source: ssh.config, mode: "ro" });
1176
+ if (ssh.knownHosts !== undefined)
1177
+ spec.push({ dest: SSH.knownHosts, source: ssh.knownHosts, mode: "ro" });
1178
+ return spec;
1179
+ }
1180
+
1181
+ /**
1182
+ * The host's ssh sources the companion is given (`SSH`): the agent socket
1183
+ * `SSH_AUTH_SOCK` names, and `config` and `known_hosts` under the
1184
+ * configured `ssh_dir` — each only when it exists. In `tmux-socket` mode
1185
+ * this process cannot see the host, so the wrapper names them
1186
+ * (`HOST_SSH_ENV`).
1187
+ */
1188
+ sshSources(): SshSources {
1189
+ const out: SshSources = {};
1190
+ const set = (key: keyof SshSources, path: string | undefined): void => {
1191
+ if (path !== undefined) out[key] = path;
1192
+ };
1193
+ if (this.socketPath !== undefined) {
1194
+ set("agent", hostPath(this.env[HOST_SSH_ENV.agent]));
1195
+ set("config", hostPath(this.env[HOST_SSH_ENV.config]));
1196
+ set("knownHosts", hostPath(this.env[HOST_SSH_ENV.knownHosts]));
1197
+ return out;
1198
+ }
1199
+ const present = (path: string | undefined): string | undefined =>
1200
+ path !== undefined && this.fs.exists(path) ? path : undefined;
1201
+ const dir = hostPath(this.cfg.ssh_dir);
1202
+ set("agent", present(hostPath(this.env["SSH_AUTH_SOCK"])));
1203
+ set("config", present(dir === undefined ? undefined : join(dir, "config")));
1204
+ set("knownHosts", present(dir === undefined ? undefined : join(dir, "known_hosts")));
1205
+ return out;
1206
+ }
1207
+
1208
+ /** `dest -> source` for every mount `runArgv()` declares. */
1209
+ expectedMounts(): Map<string, string> {
1210
+ return new Map(this.mountSpec().map((m) => [m.dest, m.source]));
1211
+ }
1212
+
1213
+ /** Idempotent: running → adopt; stopped → start; absent → run. */
1214
+ async ensureUp(onProgress?: (line: string) => void): Promise<EnsureOutcome> {
1215
+ if (this.socketPath !== undefined) return this.adoptWrapperRun(onProgress);
1216
+ await this.resolveMemoryWiring(onProgress);
1217
+ // Say which of the two worlds this start is in, either way. A missing
1218
+ // checkout is a normal, non-fatal state — but a companion silently reading
1219
+ // no reviewed source at all must never be left implicit.
1220
+ onProgress?.(
1221
+ this.reviewedRepoPresent()
1222
+ ? `reviewed repo ${this.cfg.reviewed_repo_dir} → ${CONTAINER.reviewedRepo} (read-only)`
1223
+ : `no reviewed repo at ${this.cfg.reviewed_repo_dir} — companion starts without it ([companion] reviewed_repo_dir)`,
1224
+ );
1225
+ let st = await this.status();
1226
+ let recreating = false;
1227
+ // Mount drift is repaired by recreation and nothing else. Do it before the
1228
+ // adopt/start branches, so a stale container never survives a TUI start.
1229
+ // The conversation is on the pihome volume and survives the rm.
1230
+ if (st.exists) {
1231
+ // DNS drift is only worth asking about when the mounts came back clean
1232
+ // (drifted mounts already force the recreate) and status() actually
1233
+ // yielded a mount set: a real companion container always reports its
1234
+ // mounts, so an empty set means the answer was unreadable — no opinion,
1235
+ // never a recreate. An unreadable createdDns() is no opinion too.
1236
+ const mountIssues = this.mountDrift(st.mounts);
1237
+ const dns =
1238
+ mountIssues.length === 0 && st.mounts.size > 0 ? await this.createdDns() : undefined;
1239
+ const drift = [...mountIssues, ...(dns === undefined ? [] : this.dnsDrift(dns))];
1240
+ if (drift.length > 0) {
1241
+ onProgress?.(`companion mounts drifted (${drift.join("; ")}) — recreating`);
1242
+ await this.forceRemove();
1243
+ st = { exists: false, running: false, mounts: new Map() };
1244
+ recreating = true;
1245
+ }
1246
+ }
1247
+ if (st.running) {
1248
+ // A container created before the memory forward existed is still on the
1249
+ // old network; connecting is live and needs no recreate.
1250
+ await this.attachToMemoryNetwork();
1251
+ return "adopted";
1252
+ }
1253
+ if (st.exists) {
1254
+ const started = await this.docker(["start", this.cfg.container_name]);
1255
+ if (started.ok) {
1256
+ // Same repair as adopting a running container: one created before
1257
+ // the memory network existed joins it now.
1258
+ await this.attachToMemoryNetwork();
1259
+ return "started";
1260
+ }
1261
+ // A stopped container can be permanently unstartable: bind-mount
1262
+ // sources are recorded at creation, and a source that no longer exists
1263
+ // makes `docker start` fail forever. Remove the husk and recreate with
1264
+ // the current environment; named volumes (pihome auth, conversation)
1265
+ // survive the rm.
1266
+ onProgress?.(`companion container unstartable (${firstLine(errText(started))}) — recreating`);
1267
+ await this.forceRemove();
1268
+ }
1269
+ // Self-healing first run: build the image rather than failing on its absence.
1270
+ if (!(await this.imageExists())) await this.buildImage(onProgress);
1271
+ const homes = this.hostHomes();
1272
+ this.ensureDirs([
1273
+ this.cfg.feed_dir,
1274
+ this.cfg.alerts_dir,
1275
+ this.cfg.evidence_dir,
1276
+ homes.profiles,
1277
+ homes.portrait,
1278
+ homes.configMirror,
1279
+ homes.state,
1280
+ homes.github,
1281
+ ]);
1282
+ const created = await this.docker(this.runArgv(), { timeoutMs: 120_000 });
1283
+ if (!created.ok) throw new Error(`docker run ${this.cfg.image} failed: ${errText(created)}`);
1284
+ return st.exists || recreating ? "recreated" : "created";
1285
+ }
1286
+
1287
+ /**
1288
+ * `tmux-socket` mode: the host wrapper started (or adopted) the companion
1289
+ * container before this process existed, and this one has no docker, so the
1290
+ * start is only a wait for the tmux server to answer on the shared socket.
1291
+ * A server that never answers is the wrapper's failure to report, not ours
1292
+ * to repair.
1293
+ */
1294
+ private async adoptWrapperRun(onProgress?: (line: string) => void): Promise<EnsureOutcome> {
1295
+ onProgress?.(`companion container run by the qcr wrapper — tmux socket ${this.socketPath}`);
1296
+ if (await this.waitRunning(this.wrapperStartWaitMs, 250)) return "adopted";
1297
+ throw new Error(
1298
+ `the companion's tmux server is not answering on ${this.socketPath} — the qcr wrapper starts the companion container before the TUI (is ${this.cfg.container_name} running?)`,
1299
+ );
1300
+ }
1301
+
1302
+ /**
1303
+ * Wait until the container is actually running.
1304
+ *
1305
+ * `docker run --detach` returns once the container is created, and a
1306
+ * `docker exec` an instant later can still lose the race with "container is
1307
+ * not running", typically right after a recreate. Bounded and total: if
1308
+ * it never comes up, the caller's own error is the better message.
1309
+ */
1310
+ async waitRunning(timeoutMs = 5_000, stepMs = 100): Promise<boolean> {
1311
+ const deadline = Date.now() + timeoutMs;
1312
+ for (;;) {
1313
+ if (this.socketPath !== undefined) {
1314
+ // No docker to ask: the server answering is "running".
1315
+ if (await this.serverUp()) return true;
1316
+ if (Date.now() >= deadline) return false;
1317
+ await new Promise((r) => setTimeout(r, stepMs));
1318
+ continue;
1319
+ }
1320
+ const st = await this.status();
1321
+ if (st.running) return true;
1322
+ // Gone rather than starting: waiting cannot help, and the caller's own
1323
+ // failure is a better message than a five-second stall.
1324
+ if (!st.exists) return false;
1325
+ if (Date.now() >= deadline) return false;
1326
+ await new Promise((r) => setTimeout(r, stepMs));
1327
+ }
1328
+ }
1329
+
1330
+ /** Idempotent: a live companion session is left strictly alone (a second
1331
+ * TUI attaches to it). Returns true when this call birthed the session. */
1332
+ async spawnPi(): Promise<boolean> {
1333
+ if (await this.piRunning()) return false;
1334
+ let res = await this.docker(this.spawnArgv());
1335
+ // `docker run --detach` returns at create, not at running, so an exec an
1336
+ // instant after a recreate can lose the race with "container is not
1337
+ // running", which would report the companion unavailable for the whole
1338
+ // session. Wait for it once and try again;
1339
+ // anything else is a real failure and is raised as one.
1340
+ if (!res.ok && /is not running|tmux server is not up/i.test(errText(res))) {
1341
+ await this.waitRunning();
1342
+ res = await this.docker(this.spawnArgv());
1343
+ }
1344
+ // Another TUI may have created the session between the check above and
1345
+ // this spawn (tmux refuses a duplicate): that session is the one to adopt.
1346
+ if (!res.ok && (await this.piRunning())) return false;
1347
+ if (!res.ok) throw new Error(`spawning the companion pi session failed: ${errText(res)}`);
1348
+ return true;
1349
+ }
1350
+
1351
+ /** Graceful `docker stop` — SIGTERM to the entrypoint; volumes persist.
1352
+ * A container docker could not be asked about is not a stopped one: that
1353
+ * throws rather than return as if the stop had happened. */
1354
+ async gracefulStop(): Promise<void> {
1355
+ // The wrapper that ran the container stops it, after the TUI container
1356
+ // has exited; this process cannot, and must not pretend to.
1357
+ if (this.socketPath !== undefined) return;
1358
+ const st = await this.status();
1359
+ if (st.unknown !== undefined)
1360
+ throw new Error(
1361
+ `cannot tell whether ${this.cfg.container_name} is running (docker inspect failed: ${st.unknown}) — nothing was stopped`,
1362
+ );
1363
+ if (!st.running) return;
1364
+ const res = await this.docker(["stop", this.cfg.container_name], { timeoutMs: 60_000 });
1365
+ if (!res.ok) throw new Error(`docker stop ${this.cfg.container_name} failed: ${errText(res)}`);
1366
+ }
1367
+ }
1368
+
1369
+ /** One-line cause from a failed docker call: the last line of stderr, else
1370
+ * of stdout, else the exit status. */
1371
+ export function dockerFailureLine(r: DockerResult): string {
1372
+ return (r.stderr.trim() || r.stdout.trim() || `exit ${r.code}`).split("\n").slice(-1)[0]!;
1373
+ }
1374
+
1375
+ const errText = dockerFailureLine;
1376
+
1377
+ /** The companion's pi session dir inside the container — where `piArgv` points
1378
+ * pi and where the day rollover looks for the conversation. */
1379
+ export function companionSessionDir(session: string): string {
1380
+ return `${CONTAINER.piHome}/agent/sessions/${session}`;
1381
+ }
1382
+
1383
+ function firstLine(s: string): string {
1384
+ return s.split("\n", 1)[0] ?? "";
1385
+ }
1386
+
1387
+ export const realDockerRunner: DockerRunner = (argv, opts) => runCommand("docker", argv, opts);
1388
+
1389
+ /** Run `bin argv…` and collect its output. A spawn failure (no such binary,
1390
+ * no permission) or the timeout is said in `stderr`, so the caller's
1391
+ * one-line cause names it instead of "exit null". */
1392
+ export function runCommand(
1393
+ bin: string,
1394
+ argv: string[],
1395
+ opts?: { timeoutMs?: number },
1396
+ ): Promise<DockerResult> {
1397
+ return new Promise((resolvePromise) => {
1398
+ const child = spawn(bin, argv, { stdio: ["ignore", "pipe", "pipe"] });
1399
+ let stdout = "";
1400
+ let stderr = "";
1401
+ let settled = false;
1402
+ const settle = (ok: boolean, code: number | null): void => {
1403
+ if (settled) return;
1404
+ settled = true;
1405
+ clearTimeout(timer);
1406
+ resolvePromise({ ok, stdout, stderr, code });
1407
+ };
1408
+ const timeoutMs = opts?.timeoutMs ?? 20_000;
1409
+ const timer = setTimeout(() => {
1410
+ child.kill("SIGKILL");
1411
+ stderr += `${stderr === "" || stderr.endsWith("\n") ? "" : "\n"}${bin} timed out after ${timeoutMs} ms`;
1412
+ settle(false, null);
1413
+ }, timeoutMs);
1414
+ // Decoded by the stream, never per chunk: a multi-byte character split
1415
+ // across two reads (a `─` in a large capture, any non-ASCII in a long
1416
+ // copied buffer) otherwise becomes two U+FFFD.
1417
+ child.stdout.setEncoding("utf8");
1418
+ child.stderr.setEncoding("utf8");
1419
+ child.stdout.on("data", (d: string) => (stdout += d));
1420
+ child.stderr.on("data", (d: string) => (stderr += d));
1421
+ child.on("error", (err) => {
1422
+ stderr += `${stderr === "" || stderr.endsWith("\n") ? "" : "\n"}${bin}: ${err.message}`;
1423
+ settle(false, null);
1424
+ });
1425
+ child.on("close", (code) => settle(code === 0, code));
1426
+ });
1427
+ }