@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,1835 @@
1
+ import { boardBarHeight, boardKeys, overlayInputBox, piPaneSize, renderBoard, } from "./board.js";
2
+ import { barHitKey, NO_LIVE_KEYS, registeredKeys } from "./cmdbar.js";
3
+ import { decodeInputKey, decodeKey } from "./keys.js";
4
+ import { visiblePrs } from "./filters.js";
5
+ import { formatEvent, moveSelection, reselect } from "./model.js";
6
+ import { TripleCtrlC } from "./chord.js";
7
+ import { MOUSE_OFF, MOUSE_ON, MouseDecoder, hitTest } from "./mouse.js";
8
+ import { ALT_SCREEN_OFF, ALT_SCREEN_ON, CLEAR, Frame, accentFrameRect, cleanCopiedBlock, cloneFrame, composeColumn, fit, sliceDisplay, spliceDisplay, stripAnsi, } from "./paint.js";
9
+ import { ACCENT, RESET, SELECTION } from "./design.js";
10
+ import { ClipboardSink } from "./clipboard.js";
11
+ import { PiMirror } from "./pipane.js";
12
+ import { TranscriptViewer, paneSurface, surfaceSession } from "./viewer.js";
13
+ import { newViewerInput, viewerInputKey } from "./viewerinput.js";
14
+ import { PANE_ESCAPE, isEndKey, keysToTmux } from "./termmirror.js";
15
+ import { Poller } from "./poll.js";
16
+ import { parseRoundProgress } from "@quickchatai/pi-quickreview/src/round-progress.js";
17
+ import { prUrl } from "./theme.js";
18
+ import { paintSidebar, sidebarHit, sidebarResizeHit, sidebarWidth, } from "./sidebar.js";
19
+ import { eventsArgv, progressArgv, sendKeysArgv, whyArgv, } from "./remote.js";
20
+ /**
21
+ * Decoded verbs the BOARD owns. `handleInput` refuses them off the board —
22
+ * see the comment at the guard.
23
+ *
24
+ * The membership test is "is this key advertised by the board's command bar"
25
+ * (`boardKeys`, src/tui/board.ts), NOT "does it read the selection". Most
26
+ * members happen to read it, but the reason they are here is that they are
27
+ * the board's bar, they are unregistered elsewhere, and the board is where an
28
+ * operator looks for them. Getting that reason wrong is how the next verb
29
+ * ends up in or out by accident — a missing `unwedge` would let `u` from
30
+ * another tab unwedge a PR nobody was looking at.
31
+ */
32
+ const BOARD_ONLY_VERBS = new Set([
33
+ "attach",
34
+ "enter",
35
+ "message",
36
+ "github",
37
+ "events",
38
+ "unwedge",
39
+ ]);
40
+ /** The frame an UNWIRED tab paints: its notice on the first body row, at the
41
+ * inner width, so the companion column still composes beside it. */
42
+ function placeholderFrame(cols, rows, notice) {
43
+ const frame = new Frame(cols, rows);
44
+ frame.set(Math.min(1, Math.max(0, rows - 1)), fit(notice, cols));
45
+ return frame;
46
+ }
47
+ export class App {
48
+ cfg;
49
+ ssh;
50
+ out;
51
+ input;
52
+ now;
53
+ view = {
54
+ health: { kind: "unreachable", sinceS: Number.POSITIVE_INFINITY },
55
+ // UNKNOWN until a heartbeat proves one: a default of `true` would paint a
56
+ // DRY-RUN badge on a live installation.
57
+ dryRun: undefined,
58
+ events: [],
59
+ selected: 0,
60
+ eventsFocus: false,
61
+ eventsScroll: 0,
62
+ help: false,
63
+ };
64
+ tab = "board";
65
+ hooks;
66
+ /** The companion sidebar — a right-hand column on every tab.
67
+ * Wired by index.ts; absent in bare test harnesses (no column painted). */
68
+ sidebar;
69
+ poller;
70
+ /** Live mirror of the pi an operator is inside for the selected PR — its
71
+ * debug round or its takeover (board's lower region). */
72
+ pi;
73
+ /** The same region for a worker-hosted PR: its transcript, read-only. Which
74
+ * of the two a PR gets is `surfaceOf` — status.json's `runtime`, no toggle. */
75
+ viewer;
76
+ /** Raw event rows behind view.events — the ambient feed writes these
77
+ * verbatim, so the companion sees what the daemon logged, not our prose. */
78
+ rawEvents = [];
79
+ selectedPr;
80
+ detailSeq = 0;
81
+ progressSeq = 0;
82
+ eventsSeq = 0;
83
+ /** Stale-while-revalidate detail cache: paint the last known payload
84
+ * instantly on selection, refresh over ssh behind it. */
85
+ detailCache = new Map();
86
+ static DETAIL_TTL_MS = 60_000;
87
+ /** The newest `why` read started per PR (its detailSeq), until it settles.
88
+ * Only that read may write the PR's cache entry — an older one resolving
89
+ * late must not stamp its older payload fresh — and while it is pending a
90
+ * poll does not stack another. Invalidation drops it, so a read begun
91
+ * before the invalidation cannot repopulate the cache either. */
92
+ detailPending = new Map();
93
+ /** When a PR's last `why` read failed: a poll does not retry it sooner than
94
+ * DETAIL_RETRY_MS (the read reaches GitHub; a 3s retry loop against a rate
95
+ * limit is the wrong answer). Selection and `r` are not held by it. */
96
+ detailFailedAt = new Map();
97
+ static DETAIL_RETRY_MS = 15_000;
98
+ quitResolve;
99
+ suspended = false;
100
+ onData = (buf) => this.handleInput(buf);
101
+ onResize = () => this.repaint();
102
+ constructor(cfg, ssh, out = process.stdout, input = process.stdin, hooks = {}, now = () => new Date()) {
103
+ this.cfg = cfg;
104
+ this.ssh = ssh;
105
+ this.out = out;
106
+ this.input = input;
107
+ this.now = now;
108
+ this.hooks = { ...hooks, tabs: hooks.tabs ?? {} };
109
+ this.poller = new Poller(cfg, ssh, () => this.onPoll(), now);
110
+ this.pi = new PiMirror(cfg, ssh, () => {
111
+ this.syncPiPane();
112
+ // The session we were typing into went away — say so and hand the
113
+ // keyboard back rather than swallowing keystrokes into nothing.
114
+ const gone = this.view.pi?.session;
115
+ if (this.view.piFocus && this.view.pi?.missing && gone !== undefined)
116
+ this.blurPiPane(`pi session ${gone} is gone — keyboard back on the board`);
117
+ this.repaint();
118
+ }, 300, (pr) => this.sessionFor(pr));
119
+ this.viewer = new TranscriptViewer(cfg, ssh, () => {
120
+ this.syncPiPane();
121
+ this.repaint();
122
+ });
123
+ }
124
+ // ---- which surface the PR pane is -----------------------------------------
125
+ /** What the pane shows for `pr`, from the PR's `runtime` in status.json — a
126
+ * pi terminal (`debug`, `takeover`) or, for every other runtime, the
127
+ * transcript viewer. */
128
+ surfaceOf(pr) {
129
+ return paneSurface(this.view.snapshot?.prs.find((p) => p.number === pr)?.runtime, pr);
130
+ }
131
+ /** The pi terminal a pane types into and captures; undefined for the viewer
132
+ * (the bot runs no `pr-<n>` tmux session). */
133
+ sessionFor(pr) {
134
+ return surfaceSession(this.surfaceOf(pr));
135
+ }
136
+ /** The PR's `runtime` in status.json, as the daemon last reported it. */
137
+ runtimeOf(pr) {
138
+ return this.view.snapshot?.prs.find((p) => p.number === pr)?.runtime;
139
+ }
140
+ /** A round (or an answer) is running for the PR: the viewer's ⏎ steers it. */
141
+ roundRunning(pr) {
142
+ const runtime = this.runtimeOf(pr);
143
+ return runtime === "worker" || runtime === "answer";
144
+ }
145
+ /** The viewer's input line per PR: a draft survives moving to another PR. */
146
+ viewerInputs = new Map();
147
+ viewerInputOf(pr) {
148
+ return this.viewerInputs.get(pr) ?? newViewerInput();
149
+ }
150
+ viewerOn(pr) {
151
+ return pr !== undefined && this.surfaceOf(pr).kind === "viewer";
152
+ }
153
+ /** Read the PR pane's source now: the transcript logs or the tmux capture. */
154
+ refreshPane(pr) {
155
+ if (pr === undefined)
156
+ return Promise.resolve();
157
+ return this.viewerOn(pr) ? this.viewer.refresh(pr) : this.pi.refresh(pr);
158
+ }
159
+ /** Selection moved: read the PR pane's source behind a debounce. */
160
+ selectPane(pr) {
161
+ if (this.viewerOn(pr))
162
+ this.viewer.select(pr);
163
+ else
164
+ this.pi.select(pr);
165
+ }
166
+ paneOffset(pr) {
167
+ return this.viewerOn(pr) ? this.viewer.offsetFor(pr) : this.pi.offsetFor(pr);
168
+ }
169
+ setPaneOffset(pr, n) {
170
+ if (this.viewerOn(pr))
171
+ this.viewer.setOffset(pr, n);
172
+ else
173
+ this.pi.setOffset(pr, n);
174
+ }
175
+ // ---- the pi pane as an embedded terminal --------------------------------
176
+ /** The session the pane types into while focused (locked at focus time). */
177
+ piFocusPr;
178
+ /** Whether that pane was the viewer when it took the keyboard: a PR whose
179
+ * runtime flips surface under the operator's hands gets the keyboard back. */
180
+ piFocusViewer = false;
181
+ /** While focused the capture runs far faster than the poll tick, so typing
182
+ * echoes back at something close to terminal speed. */
183
+ piTimer;
184
+ static PI_FOCUS_POLL_MS = 400;
185
+ /** Hand the keyboard to the pi pane (click, or a wheel-driven focus). */
186
+ focusPiPane() {
187
+ const pr = this.panePr();
188
+ if (pr === undefined)
189
+ return;
190
+ if (this.view.piFocus && this.piFocusPr === pr)
191
+ return;
192
+ this.piFocusPr = pr;
193
+ this.piFocusViewer = this.viewerOn(pr);
194
+ this.view.piFocus = true;
195
+ this.sidebar?.blur(); // one keyboard owner at a time
196
+ this.syncPiPane();
197
+ void this.refreshPane(pr);
198
+ if (!this.piTimer) {
199
+ const t = setInterval(() => {
200
+ if (this.suspended || this.tab !== "board" || !this.view.piFocus)
201
+ return;
202
+ void this.refreshPane(this.piFocusPr);
203
+ }, App.PI_FOCUS_POLL_MS);
204
+ t.unref?.();
205
+ this.piTimer = t;
206
+ }
207
+ this.repaint();
208
+ }
209
+ /** Ctrl-q, a click outside the pane, or a dead session: back to the list. */
210
+ blurPiPane(notice) {
211
+ if (this.piTimer)
212
+ clearInterval(this.piTimer);
213
+ this.piTimer = undefined;
214
+ if (!this.view.piFocus)
215
+ return;
216
+ this.view.piFocus = false;
217
+ this.piFocusPr = undefined;
218
+ this.syncPiPane();
219
+ if (notice !== undefined)
220
+ this.view.noticeLine = notice;
221
+ this.selectPane(this.selectedPr); // follow resumes where the list is
222
+ }
223
+ /** True while the pane owns the keyboard (tests + hot-reload read this). */
224
+ piFocused() {
225
+ return this.view.piFocus === true;
226
+ }
227
+ /** Forward one raw stdin chunk into the focused session, then re-capture so
228
+ * the echo lands immediately instead of on the next tick. Typing also snaps
229
+ * the pane back to the live tail — you cannot type into history. */
230
+ sendKeysToPane(buf) {
231
+ const pr = this.piFocusPr;
232
+ if (pr === undefined)
233
+ return;
234
+ const groups = keysToTmux(buf);
235
+ if (groups.length === 0)
236
+ return;
237
+ if (this.paneOffset(pr) !== 0) {
238
+ this.setPaneOffset(pr, 0);
239
+ this.syncPiPane();
240
+ this.repaint();
241
+ }
242
+ // One queue for every keystroke into a pane: each chunk is its own ssh
243
+ // round trip, and unqueued a slow "a" lets the Enter typed after it reach
244
+ // tmux first and submit pi's input without it. For the same reason a
245
+ // chunk that did not arrive drops everything queued behind it — an Enter
246
+ // must never submit input with a hole in it — and says so; the next
247
+ // keystroke typed once the queue has drained starts afresh.
248
+ if (this.paneSendsQueued === 0)
249
+ this.paneSendDropped = false;
250
+ this.paneSendsQueued++;
251
+ this.paneSendTail = this.paneSendTail
252
+ .then(async () => {
253
+ for (const parts of groups) {
254
+ if (this.paneSendDropped)
255
+ return;
256
+ // Only a pi terminal takes keys. A PR whose debug round or takeover
257
+ // ended under the operator's hands has none: drop, and say so.
258
+ const session = this.sessionFor(pr);
259
+ if (session === undefined) {
260
+ this.paneSendDropped = true;
261
+ this.notice(`✖ #${pr}: no pi terminal any more — input dropped`);
262
+ return;
263
+ }
264
+ const res = await this.ssh(this.cfg.ssh_host, sendKeysArgv(this.cfg, session, parts), {
265
+ timeoutMs: 5_000,
266
+ });
267
+ if (!res.ok) {
268
+ this.paneSendDropped = true;
269
+ this.notice(`✖ ${session}: keys not delivered (${res.stderr.trim() || `exit ${res.code}`}) — input after them dropped`);
270
+ return;
271
+ }
272
+ }
273
+ })
274
+ .catch((err) => {
275
+ this.paneSendDropped = true;
276
+ this.notice(`✖ #${pr}: keys not delivered (${err instanceof Error ? err.message : String(err)}) — input after them dropped`);
277
+ })
278
+ .finally(() => {
279
+ this.paneSendsQueued--;
280
+ void this.pi.refresh(pr);
281
+ });
282
+ }
283
+ paneSendTail = Promise.resolve();
284
+ /** Sends queued and not yet finished. */
285
+ paneSendsQueued = 0;
286
+ /** A send in the current queue failed: the rest of it is not sent. */
287
+ paneSendDropped = false;
288
+ /** Wheel over the pane: move its scrollback window, re-capture with history.
289
+ * Wheeling back to the bottom returns it to the live tail. */
290
+ scrollPiPane(notches) {
291
+ const pr = this.panePr();
292
+ if (pr === undefined)
293
+ return;
294
+ if (this.viewerOn(pr))
295
+ this.viewer.scroll(pr, notches);
296
+ else
297
+ this.pi.scroll(pr, notches);
298
+ this.syncPiPane();
299
+ this.repaint();
300
+ void this.refreshPane(pr);
301
+ }
302
+ /** The PR whose session the pane is showing. While the pane has the keyboard
303
+ * this is LOCKED — a poll that reorders the list must never redirect the
304
+ * operator's typing to another PR's session. */
305
+ panePr() {
306
+ return this.view.piFocus ? this.piFocusPr : this.selectedPr;
307
+ }
308
+ /** Point the board's pane at whatever it should be showing right now (cache
309
+ * first, so switching back paints instantly). */
310
+ syncPiPane() {
311
+ const pr = this.panePr();
312
+ this.view.piPr = pr;
313
+ this.view.pi = this.viewerOn(pr) ? this.viewer.view(pr) : this.pi.view(pr);
314
+ // The input line exists on the viewer's pane only: a pi terminal gets
315
+ // none (the PR-pane rule).
316
+ this.view.viewerInput = this.viewerOn(pr)
317
+ ? {
318
+ ...this.viewerInputOf(pr),
319
+ pr,
320
+ running: this.roundRunning(pr),
321
+ focused: this.view.piFocus === true && this.piFocusPr === pr,
322
+ }
323
+ : undefined;
324
+ }
325
+ /** Selection moved: paint the cached mirror at once, capture behind it.
326
+ * A focused pane ignores this — follow is locked until Ctrl-q. */
327
+ followSelection() {
328
+ this.syncPiPane();
329
+ if (this.view.piFocus)
330
+ return;
331
+ this.selectPane(this.selectedPr);
332
+ }
333
+ // ---- lifecycle ---------------------------------------------------------
334
+ async run(initialSelection) {
335
+ if (initialSelection !== undefined)
336
+ this.selectedPr = initialSelection;
337
+ // From here the TUI owns the screen, and this is the FIRST thing that
338
+ // happens: nothing external — no docker call, no network call, no
339
+ // filesystem walk — runs before this paint.
340
+ //
341
+ // The first frame is already the final layout: the column's width is a
342
+ // function of the terminal alone (`sidebarWidth`), the sidebar is wired
343
+ // before this call, and the container merely fills a column that is
344
+ // already the right size. Everything slow streams in behind
345
+ // this frame and reports into that column (src/tui/startup.ts).
346
+ this.started = true;
347
+ this.out.write(ALT_SCREEN_ON + CLEAR + MOUSE_ON);
348
+ if (this.input.isTTY)
349
+ this.input.setRawMode(true);
350
+ this.input.resume();
351
+ this.input.on("data", this.onData);
352
+ this.out.on("resize", this.onResize);
353
+ if (this.hooks.helpSeen && !this.hooks.helpSeen.get()) {
354
+ this.view.help = true;
355
+ this.hooks.helpSeen.set();
356
+ }
357
+ this.repaint();
358
+ this.poller.start();
359
+ await new Promise((resolve) => (this.quitResolve = resolve));
360
+ this.teardown();
361
+ }
362
+ /** Set once teardown has restored the terminal. From then on nothing may
363
+ * write to the output stream: an async load that resolves after quit
364
+ * (memory/proposals fetches take seconds over ssh) would otherwise paint
365
+ * a full TUI frame onto the user's restored NORMAL screen — a ghost board
366
+ * that looks like the TUI is still running. */
367
+ stopped = false;
368
+ teardown() {
369
+ this.stopped = true;
370
+ this.poller.stop();
371
+ this.pi.stop();
372
+ this.viewer.stop();
373
+ this.sidebar?.stop();
374
+ if (this.piTimer)
375
+ clearInterval(this.piTimer);
376
+ this.piTimer = undefined;
377
+ this.input.off("data", this.onData);
378
+ this.out.off("resize", this.onResize);
379
+ if (this.input.isTTY)
380
+ this.input.setRawMode(false);
381
+ this.input.pause();
382
+ // MOUSE_OFF, leave the alt screen, THEN clear. The clear lands
383
+ // on the normal screen and is what makes the exit look clean rather than
384
+ // dropping the operator back onto whatever the alt screen was hiding; it
385
+ // also wipes any half-line a crash left behind. Order matters: clearing
386
+ // before ALT_SCREEN_OFF would clear the screen we are about to discard.
387
+ this.out.write(MOUSE_OFF + ALT_SCREEN_OFF + CLEAR);
388
+ }
389
+ quit() {
390
+ this.quitResolve?.();
391
+ }
392
+ /** The terminal has been restored and nothing may write to it again. Public
393
+ * so the orchestrator's real-terminal escape hatch can check it: re-entering
394
+ * the alt screen after teardown leaves the operator staring at a blank
395
+ * alternate screen with no TUI behind it — the trap this file exists to
396
+ * avoid, arrived at from the other end. */
397
+ isStopped() {
398
+ return this.stopped;
399
+ }
400
+ /** Ctrl-C taps, for the two-tap exit (distinct from the three-tap kill). */
401
+ ctrlCTaps = [];
402
+ static CTRL_C_EXIT_WINDOW_MS = 2_000;
403
+ /**
404
+ * Every route to "I want out", in one place.
405
+ *
406
+ * `q` asks first — quitting is cheap to redo but the confirmation is what
407
+ * stops a stray keystroke from dropping a board you were reading. Ctrl-C
408
+ * confirms by REPETITION instead: the second tap inside the window is the
409
+ * answer, so the reflex still works without a prompt in the way.
410
+ */
411
+ requestQuit(via) {
412
+ if (via === "ctrl-c") {
413
+ const t = this.now().getTime();
414
+ this.ctrlCTaps = [...this.ctrlCTaps.filter((x) => t - x <= App.CTRL_C_EXIT_WINDOW_MS), t];
415
+ if (this.ctrlCTaps.length >= 2) {
416
+ this.quit();
417
+ return;
418
+ }
419
+ this.notice("press Ctrl-C again to exit");
420
+ this.repaint();
421
+ return;
422
+ }
423
+ this.view.confirmQuit = true;
424
+ this.repaint();
425
+ }
426
+ /** A key while the quit prompt is up. Returns true when it consumed it. */
427
+ handleConfirmQuit(buf) {
428
+ if (!this.view.confirmQuit)
429
+ return false;
430
+ const raw = buf.toString("utf8");
431
+ // Only an explicit yes leaves. Anything else — including a second `q`,
432
+ // which is what a repeated keystroke looks like — cancels, because the
433
+ // prompt exists to catch exactly that.
434
+ if (raw === "y" || raw === "Y" || raw === "\r" || raw === "\n") {
435
+ this.view.confirmQuit = false;
436
+ this.quit();
437
+ return true;
438
+ }
439
+ this.view.confirmQuit = false;
440
+ this.repaint();
441
+ return true;
442
+ }
443
+ /** Injectable so tests never kill the runner. */
444
+ exiter = (code) => process.exit(code);
445
+ /** Triple-Ctrl-C hard kill: restore the terminal no matter what, then die.
446
+ * A hung ssh/pty must never trap the user in the alt screen — closing the
447
+ * terminal window cannot stay the only way out. */
448
+ die() {
449
+ try {
450
+ this.suspended = false; // teardown must run even while the terminal is lent out
451
+ this.teardown();
452
+ }
453
+ catch {
454
+ /* a half-restored terminal still beats a trapped one */
455
+ }
456
+ this.exiter(130);
457
+ }
458
+ /** Lend the terminal out: stop painting, swallow board keys, drop our mouse
459
+ * reporting. The ONE remaining borrower is `s` (index.ts `onRealTerminal`):
460
+ * the GCP key-passphrase prompt has to run on the real terminal, outside the
461
+ * alt screen. It is not a TUI surface and paints no layout of its own; the
462
+ * board comes back exactly as it was the moment ssh returns. */
463
+ suspend() {
464
+ this.suspended = true;
465
+ this.input.off("data", this.onData);
466
+ this.out.write(MOUSE_OFF);
467
+ }
468
+ resume() {
469
+ if (this.stopped)
470
+ return; // a borrower returning after quit must not repaint
471
+ this.suspended = false;
472
+ this.input.on("data", this.onData);
473
+ this.out.write(CLEAR + MOUSE_ON);
474
+ void this.refreshDetail();
475
+ void this.refreshProgress();
476
+ void this.refreshPane(this.panePr()); // the pane caught up while attached
477
+ this.repaint();
478
+ }
479
+ /** True while something other than the board owns the terminal or holds
480
+ * unsaved user input. Used to REFUSE a remote-driven action (index.ts), so
481
+ * it never fights the operator for the keyboard. The hot reload deliberately
482
+ * does NOT consult it — every build reloads (see reload.ts). */
483
+ isBusy() {
484
+ return (this.suspended ||
485
+ this.view.inputBox !== undefined ||
486
+ this.view.piFocus === true ||
487
+ this.sidebar?.focused === true);
488
+ }
489
+ /** The PR the board is pointing at (survives a hot-reload). */
490
+ currentPr() {
491
+ return this.selectedPr;
492
+ }
493
+ // ---- data --------------------------------------------------------------
494
+ onPoll() {
495
+ // A tick already in flight when the operator quits resolves AFTER
496
+ // teardown, and this method kicks off four more remote reads. They would
497
+ // spawn ssh against a TUI that no longer exists — the same family as the
498
+ // `stopped` guard on `repaint` (an async load resolving after quit must
499
+ // not touch the restored terminal), one level earlier: it must not start
500
+ // new work either.
501
+ if (this.stopped)
502
+ return;
503
+ const snap = this.poller.state.snapshot;
504
+ if (snap) {
505
+ // Invalidate cached details the snapshot proves stale (state/head moved).
506
+ for (const pr of snap.prs) {
507
+ const c = this.detailCache.get(pr.number);
508
+ if (c && (c.payload.head_sha !== pr.head_sha || c.payload.state !== pr.state))
509
+ this.invalidateDetail(pr.number);
510
+ }
511
+ this.view.snapshot = snap;
512
+ this.view.dryRun = snap.dry_run;
513
+ const sorted = visiblePrs(this.view);
514
+ this.view.selected = reselect(sorted, this.selectedPr, this.view.selected);
515
+ this.selectedPr = sorted[this.view.selected]?.number;
516
+ }
517
+ this.view.health = this.poller.health();
518
+ void this.refreshEvents();
519
+ if (this.detailWanted())
520
+ void this.refreshDetail();
521
+ // Live progress is a cheap host-volume read, separate from `why` because
522
+ // refreshing the enriched detail would call GitHub on every poll.
523
+ if (this.tab === "board" && !this.suspended)
524
+ void this.refreshProgress();
525
+ // A PR whose runtime flipped the pane's surface under a focused keyboard
526
+ // (a worker round ended, a debug round began) hands it back: the same
527
+ // keys would now mean something else.
528
+ if (this.view.piFocus &&
529
+ this.piFocusPr !== undefined &&
530
+ this.viewerOn(this.piFocusPr) !== this.piFocusViewer)
531
+ this.blurPiPane(`#${this.piFocusPr}'s pane changed what it shows — keyboard back on the board`);
532
+ // The pane only costs an ssh while the board is the visible tab.
533
+ this.syncPiPane();
534
+ if (this.tab === "board" && !this.suspended)
535
+ void this.refreshPane(this.panePr());
536
+ try {
537
+ this.hooks.onPoll?.(this);
538
+ }
539
+ catch {
540
+ /* a throwing wrapper must never kill the poll loop */
541
+ }
542
+ this.repaint();
543
+ }
544
+ async refreshEvents() {
545
+ const seq = ++this.eventsSeq;
546
+ const res = await this.ssh(this.cfg.ssh_host, eventsArgv(this.cfg, 50));
547
+ if (this.stopped || !res.ok || seq !== this.eventsSeq)
548
+ return; // a newer read superseded this one
549
+ try {
550
+ const parsed = JSON.parse(res.stdout);
551
+ this.rawEvents = parsed.events;
552
+ const lines = parsed.events.map(formatEvent).reverse(); // newest first
553
+ this.view.events = this.mergeNotices(lines);
554
+ try {
555
+ this.hooks.onPoll?.(this); // feed the companion the fresh tail too
556
+ }
557
+ catch {
558
+ /* a throwing wrapper must never kill the poll loop */
559
+ }
560
+ this.repaint();
561
+ }
562
+ catch {
563
+ /* keep the previous sidebar on parse failure */
564
+ }
565
+ }
566
+ /** Override point to splice extra rows (e.g. quick-message pending/delivered)
567
+ * into the events strip; the base passes lines through. */
568
+ mergeNotices(lines) {
569
+ return lines;
570
+ }
571
+ /** A poll refreshes the detail when the selection moved, or when the
572
+ * selected PR's cache entry is gone (invalidated) or past its TTL and no
573
+ * read for it is already in flight — a PR that stays selected must follow
574
+ * its new rounds without the operator moving away or pressing `r`. */
575
+ detailWanted() {
576
+ const pr = this.selectedPr;
577
+ if (pr === undefined)
578
+ return false;
579
+ // A read already in flight for it answers the question — stacking another
580
+ // each tick would bump detailSeq and starve a read slower than the poll.
581
+ if (this.detailPending.has(pr))
582
+ return false;
583
+ const failed = this.detailFailedAt.get(pr);
584
+ if (failed !== undefined && Date.now() - failed < App.DETAIL_RETRY_MS)
585
+ return false;
586
+ if (this.view.detail?.pr !== pr)
587
+ return true;
588
+ const c = this.detailCache.get(pr);
589
+ return !c || Date.now() - c.fetched_at >= App.DETAIL_TTL_MS;
590
+ }
591
+ /** Read the selected PR's detail. Served from cache at once; the fetch is
592
+ * skipped while the cache is fresh, unless `force` (the operator's `r`). */
593
+ async refreshDetail(opts = {}) {
594
+ const pr = this.selectedPr;
595
+ if (pr === undefined)
596
+ return;
597
+ const cached = this.detailCache.get(pr);
598
+ const now = Date.now();
599
+ if (cached) {
600
+ this.view.detail = cached.payload;
601
+ this.repaint();
602
+ if (!opts.force && now - cached.fetched_at < App.DETAIL_TTL_MS)
603
+ return;
604
+ }
605
+ const seq = ++this.detailSeq;
606
+ this.detailPending.set(pr, seq);
607
+ const res = await this.ssh(this.cfg.ssh_host, whyArgv(this.cfg, pr));
608
+ // Superseded (a newer read for this PR started) or invalidated meanwhile:
609
+ // this payload may be older than what the cache should hold.
610
+ const newest = this.detailPending.get(pr) === seq;
611
+ if (newest)
612
+ this.detailPending.delete(pr);
613
+ if (!res.ok) {
614
+ if (newest)
615
+ this.detailFailedAt.set(pr, Date.now());
616
+ return;
617
+ }
618
+ try {
619
+ const payload = JSON.parse(res.stdout);
620
+ if (newest)
621
+ this.detailFailedAt.delete(pr);
622
+ if (newest && payload.pr === pr)
623
+ this.detailCache.set(payload.pr, { payload, fetched_at: Date.now() });
624
+ if (seq !== this.detailSeq || payload.pr !== this.selectedPr)
625
+ return;
626
+ this.view.detail = payload;
627
+ this.repaint();
628
+ }
629
+ catch {
630
+ if (newest)
631
+ this.detailFailedAt.set(pr, Date.now());
632
+ /* stale/garbled detail keeps the loading line */
633
+ }
634
+ }
635
+ async refreshProgress() {
636
+ const pr = this.selectedPr;
637
+ if (pr === undefined)
638
+ return;
639
+ const seq = ++this.progressSeq;
640
+ const res = await this.ssh(this.cfg.ssh_host, progressArgv(this.cfg, pr), { timeoutMs: 3_000 });
641
+ if (seq !== this.progressSeq || pr !== this.selectedPr)
642
+ return;
643
+ if (!res.ok) {
644
+ // Keep a same-PR snapshot: its age becomes the visible SILENT signal.
645
+ // A different PR must never inherit it while its own read is pending.
646
+ if (this.view.progress?.pr !== pr)
647
+ this.view.progress = undefined;
648
+ this.repaint();
649
+ return;
650
+ }
651
+ const parsed = parseRoundProgress(res.stdout);
652
+ this.view.progress = parsed?.pr === pr ? parsed : undefined;
653
+ this.repaint();
654
+ }
655
+ /** Drop a PR's cached detail (a new verdict/round event makes it stale). */
656
+ invalidateDetail(pr) {
657
+ this.detailCache.delete(pr);
658
+ this.detailPending.delete(pr);
659
+ }
660
+ // ---- input -------------------------------------------------------------
661
+ /**
662
+ * Point the board at `pr` (the companion's `select_pr` action, a row click
663
+ * and the events overlay land here). Once the board has loaded, a PR it does not list
664
+ * (closed, or outside the filter) is refused with a notice and the
665
+ * selection stays where it is — the highlighted row, the detail and the
666
+ * mirrored session never point at different PRs. Before the first snapshot
667
+ * the request is kept (a `qcr tui <pr>` deep link), and the first poll
668
+ * settles it. False when refused.
669
+ */
670
+ selectPr(pr) {
671
+ const sorted = visiblePrs(this.view);
672
+ if (this.view.snapshot !== undefined && !sorted.some((p) => p.number === pr)) {
673
+ this.notice(`#${pr} is not on the board (closed, or outside the filter)`);
674
+ return false;
675
+ }
676
+ this.view.selected = reselect(sorted, pr, this.view.selected);
677
+ this.selectionMoved(pr);
678
+ return true;
679
+ }
680
+ /** The selection now points at `pr`: everything that shows the selected PR
681
+ * follows it — the progress panel (another PR's is never shown), the
682
+ * detail, and the mirrored session. */
683
+ selectionMoved(pr) {
684
+ if (pr !== this.selectedPr)
685
+ this.msgDraft = ""; // a cancelled draft belongs to the PR it was typed for
686
+ this.selectedPr = pr;
687
+ if (this.view.progress?.pr !== pr)
688
+ this.view.progress = undefined;
689
+ void this.refreshDetail();
690
+ void this.refreshProgress();
691
+ this.followSelection();
692
+ }
693
+ /** Move the events overlay's scroll, clamped to its lines. */
694
+ scrollEvents(delta) {
695
+ this.view.eventsScroll = moveSelection(this.view.eventsScroll, delta, this.view.events.length);
696
+ }
697
+ /** Narrow (or, with null, clear) the board. The selection stays on the
698
+ * same PR while it is still listed; otherwise it lands on the nearest row
699
+ * of the new list — and the row, the detail and the mirrored session move
700
+ * together, so the next key addresses the highlighted PR. */
701
+ setBoardFilter(filter) {
702
+ this.view.boardFilter = filter;
703
+ const sorted = visiblePrs(this.view);
704
+ this.view.selected = reselect(sorted, this.selectedPr, this.view.selected);
705
+ const pr = sorted[this.view.selected]?.number;
706
+ if (pr === this.selectedPr)
707
+ return;
708
+ this.selectionMoved(pr);
709
+ }
710
+ selectedSession() {
711
+ const pr = this.selectedPrNumber();
712
+ return pr === undefined ? undefined : `pr-${pr}`;
713
+ }
714
+ notice(text) {
715
+ this.view.noticeLine = text;
716
+ // Before the TUI owns the screen, a notice is startup progress and prints
717
+ // as a plain line — the companion can take minutes to build, and silence
718
+ // there looks like a hang, and painting a frame this early would flash.
719
+ // The alt screen hides these the
720
+ // moment run() begins, so they never linger.
721
+ if (!this.started) {
722
+ this.out.write(`${text}\n`);
723
+ return;
724
+ }
725
+ this.repaint();
726
+ }
727
+ /** The session the open input box targets; draft survives Esc. */
728
+ msgSession;
729
+ msgDraft = "";
730
+ /** When set, the input box belongs to a tab prompt (memory edits,
731
+ * proposal-reject reasons) — submit goes here, not to sendMessage. */
732
+ inputSubmit;
733
+ openMessageBox(session) {
734
+ this.msgSession = session;
735
+ this.inputSubmit = undefined;
736
+ const pr = /^pr-(\d+)$/.exec(session)?.[1];
737
+ this.view.inputBox = {
738
+ target: pr ? `#${pr} (a message to its session)` : session,
739
+ text: this.msgDraft,
740
+ };
741
+ this.repaint();
742
+ }
743
+ /** Generic one-line input over the same box UI. Esc cancels (no draft). */
744
+ promptInput(target, initial, onSubmit) {
745
+ this.inputSubmit = onSubmit;
746
+ this.view.inputBox = { target, text: initial };
747
+ this.repaint();
748
+ }
749
+ handleMessageBoxKey(buf) {
750
+ const box = this.view.inputBox;
751
+ const key = decodeInputKey(buf);
752
+ switch (key.kind) {
753
+ case "char":
754
+ box.text += key.ch;
755
+ break;
756
+ case "backspace":
757
+ box.text = box.text.slice(0, -1);
758
+ break;
759
+ case "alt-enter":
760
+ box.text += "\n";
761
+ break;
762
+ case "escape":
763
+ if (this.inputSubmit)
764
+ this.inputSubmit = undefined; // tab prompts keep no draft
765
+ else
766
+ this.msgDraft = box.text; // preserved until board navigation
767
+ this.view.inputBox = undefined;
768
+ break;
769
+ case "enter": {
770
+ const text = box.text.trim();
771
+ const submit = this.inputSubmit;
772
+ const session = this.msgSession;
773
+ this.view.inputBox = undefined;
774
+ this.inputSubmit = undefined;
775
+ if (submit) {
776
+ if (text.length > 0)
777
+ submit(text);
778
+ break;
779
+ }
780
+ this.msgDraft = "";
781
+ if (text.length > 0 && session) {
782
+ if (this.hooks.sendMessage)
783
+ void this.hooks.sendMessage(session, text, this);
784
+ else
785
+ this.notice(`✖ msg ${session} undelivered (sender not wired)`);
786
+ }
787
+ break;
788
+ }
789
+ case "quit":
790
+ this.requestQuit(buf.toString("utf8") === "\x03" ? "ctrl-c" : "key");
791
+ return;
792
+ default:
793
+ return;
794
+ }
795
+ this.repaint();
796
+ }
797
+ openGithub(pr) {
798
+ const n = pr ?? this.selectedPrNumber();
799
+ if (n === undefined || !this.view.repo)
800
+ return;
801
+ this.hooks.openUrl?.(prUrl(this.view.repo, n));
802
+ this.notice(`opened #${n} on GitHub`);
803
+ }
804
+ /** The selected row's PR number. Public because `selectedSession()` is the
805
+ * session-name view of the same thing and callers want either. */
806
+ selectedPrNumber() {
807
+ const sorted = visiblePrs(this.view);
808
+ return sorted[this.view.selected]?.number;
809
+ }
810
+ /** Synthesize a keypress through the normal input path — the command bar's
811
+ * buttons and any tab code that wants "exactly what the key would do". */
812
+ pressKey(key) {
813
+ this.handleInput(Buffer.from(key, "utf8"));
814
+ }
815
+ /** The installed surface under the current tab, if any (never the board). */
816
+ tabHooks() {
817
+ return this.tab === "board" ? undefined : this.hooks.tabs[this.tab];
818
+ }
819
+ /**
820
+ * What the current frame answers to: the bar the tab paints plus
821
+ * its inline keys. ONE computation feeds the bar's hit-test, the companion
822
+ * routing (`registeredKeys`) and the paint, so the three cannot disagree.
823
+ * An unwired tab answers to nothing but the globals.
824
+ */
825
+ liveKeys() {
826
+ if (this.tab === "board")
827
+ return boardKeys(this.view);
828
+ return this.tabHooks()?.keys(this) ?? NO_LIVE_KEYS;
829
+ }
830
+ /** Every printable the current frame claims — the companion column gets
831
+ * the rest. Public for the fences: a registered key must answer. */
832
+ registeredKeys() {
833
+ return registeredKeys(this.liveKeys());
834
+ }
835
+ handleMouse(ev) {
836
+ // The quit prompt is modal for the mouse as it is for the keyboard: a
837
+ // click is its "any other key" (cancel), and nothing under the prompt —
838
+ // a PR row, a GitHub link, the pi pane, the companion column, a tab's
839
+ // zones — ever sees it. Wheel, drag and release are swallowed.
840
+ if (this.view.confirmQuit) {
841
+ if (ev.kind === "click") {
842
+ this.view.confirmQuit = false;
843
+ this.repaint();
844
+ }
845
+ // A release swallowed here still ends any gesture begun before the
846
+ // prompt opened — never leave a seam resize armed behind it.
847
+ if (ev.kind === "release") {
848
+ this.sidebarResizeActive = false;
849
+ this.selAnchor = undefined;
850
+ }
851
+ return;
852
+ }
853
+ const cols = this.out.columns ?? 80;
854
+ // The sidebar's left seam is the ONE drag surface that is chrome rather
855
+ // than selectable content. Everywhere else drag and release belong to the
856
+ // rectangular copy.
857
+ if (this.sidebar && ev.kind === "click") {
858
+ const width = this.sidebarWidth(cols);
859
+ if (sidebarResizeHit(ev.x, cols, width)) {
860
+ this.sidebarResizeActive = true;
861
+ this.selAnchor = undefined;
862
+ this.selection = undefined;
863
+ this.repaint();
864
+ return;
865
+ }
866
+ }
867
+ if (this.sidebarResizeActive && ev.kind === "drag") {
868
+ this.sidebar?.resizeFromPointer(cols, ev.x);
869
+ this.repaint();
870
+ return;
871
+ }
872
+ if (this.sidebarResizeActive && ev.kind === "release") {
873
+ this.sidebarResizeActive = false;
874
+ this.notice(`sidebar ${this.sidebarWidth(cols)} cols`);
875
+ return;
876
+ }
877
+ // Selection owns every other drag and release. A drag is never
878
+ // a click, so nothing below sees it and no row is selected or button fired.
879
+ if (ev.kind === "drag") {
880
+ const anchor = this.selAnchor;
881
+ if (!anchor)
882
+ return;
883
+ // The first motion freezes the screen and fixes the pane the drag
884
+ // belongs to; every later motion only moves the far corner.
885
+ if (!this.selFrame) {
886
+ this.selFrame = this.baseFrame;
887
+ this.selPane = this.paneRectAt(anchor.x, anchor.y);
888
+ }
889
+ const p = this.selPane ?? { x1: 1, y1: 1, x2: cols, y2: this.out.rows ?? 24 };
890
+ const cx = (x) => Math.max(p.x1, Math.min(p.x2, x));
891
+ const cy = (y) => Math.max(p.y1, Math.min(p.y2, y));
892
+ this.selection = {
893
+ x1: cx(Math.min(anchor.x, ev.x)),
894
+ y1: cy(Math.min(anchor.y, ev.y)),
895
+ x2: cx(Math.max(anchor.x, ev.x)),
896
+ y2: cy(Math.max(anchor.y, ev.y)),
897
+ };
898
+ this.repaint();
899
+ return;
900
+ }
901
+ if (ev.kind === "release") {
902
+ // A press with no drag is a plain click and has already been handled;
903
+ // there is nothing to copy and the anchor just goes away.
904
+ if (this.selection)
905
+ this.copySelection();
906
+ else
907
+ this.selAnchor = undefined;
908
+ this.selFrame = undefined;
909
+ this.selPane = undefined;
910
+ return;
911
+ }
912
+ // A new press starts a possible selection and clears the last highlight.
913
+ if (ev.kind === "click") {
914
+ this.selAnchor = { x: ev.x, y: ev.y };
915
+ if (this.selection) {
916
+ this.selection = undefined;
917
+ this.repaint();
918
+ }
919
+ }
920
+ // The command bar answers first, on every tab: a click on one of its
921
+ // buttons IS that keypress, wherever it lands. EXCEPT while an input box
922
+ // is open — a synthesized key would be typed into the box, and [⏎]
923
+ // would SUBMIT the draft (a live memory edit or quick-message).
924
+ if (ev.kind === "click" && !this.view.inputBox) {
925
+ const rows = this.out.rows ?? 24;
926
+ // Hit-test at the width the bar was PAINTED at — the tab's inner width
927
+ // beside the companion column, not the terminal's. The bar wraps, so its
928
+ // row count depends on the width: tested at the full width, a click on
929
+ // the painted [r refresh] decoded as a nav slot one row up, and the
930
+ // phantom slots extended into the column (terminal x is inner-frame x
931
+ // because the tab is on the left).
932
+ const k = barHitKey(this.tab, ev.x, ev.y, this.innerWidth(cols), rows, this.liveKeys().bar);
933
+ if (k !== undefined) {
934
+ // A focused pi pane would swallow the synthesized key as text.
935
+ if (this.view.piFocus) {
936
+ this.blurPiPane();
937
+ this.repaint();
938
+ }
939
+ // So would a focused sidebar — and [⏎] would SUBMIT the draft. Blur
940
+ // first (same rule as the pane): a bar click IS that keypress.
941
+ if (this.sidebar?.focused) {
942
+ this.sidebar.blur();
943
+ this.repaint();
944
+ }
945
+ this.pressKey(k);
946
+ return;
947
+ }
948
+ // The companion column is fixed chrome on every tab: clicking anywhere
949
+ // in it takes the keyboard (its mirror IS the companion — there is no
950
+ // full-screen view to open, and no collapsed state to toggle).
951
+ if (this.sidebar) {
952
+ const width = this.sidebarWidth(cols);
953
+ const hit = width === 0 ? undefined : sidebarHit(ev.x, ev.y, cols, rows, width);
954
+ if (hit !== undefined) {
955
+ if (this.sidebar.available()) {
956
+ this.focusCompanion();
957
+ }
958
+ return;
959
+ }
960
+ // Zellij-style spatial focus: clicking a different pane moves focus
961
+ // there. Ctrl-q remains the keyboard-only escape hatch, not a tax on
962
+ // mouse users who already pointed at the main TUI.
963
+ if (this.sidebar.focused) {
964
+ this.sidebar.blur();
965
+ this.repaint();
966
+ }
967
+ }
968
+ }
969
+ // The wheel belongs to the companion column before any tab sees it. The
970
+ // column is chrome on EVERY tab, so a wheel notch that the tab dispatch
971
+ // below answers is a notch stolen from the surface the operator is
972
+ // actually reading: scrolling the conversation while the column has focus
973
+ // must never walk the board's selection (and drag the pi mirror away from
974
+ // the PR being read).
975
+ //
976
+ // Two ways it belongs here, and they compose: the pointer is over the
977
+ // column (focused or not — the same rule the pi pane already has), or the
978
+ // column holds the keyboard, wherever the pointer happens to rest. Focus
979
+ // means "this surface is the one I am working in"; the wheel is part of
980
+ // that, not an exception to it.
981
+ if (ev.kind === "wheel-up" || ev.kind === "wheel-down") {
982
+ if (this.sidebar && this.wheelBelongsToSidebar(ev.x, cols)) {
983
+ this.sidebar.scroll(ev.kind === "wheel-up" ? 1 : -1);
984
+ this.repaint();
985
+ return;
986
+ }
987
+ }
988
+ // The KEYS overlay covers the tab, the board included (see repaint): what
989
+ // it hides is not on screen, so a click must not reach it (a hidden [url]
990
+ // or row would fire unseen) and the wheel must not move a hidden
991
+ // selection. A click dismisses help, as any key does; the wheel is
992
+ // swallowed.
993
+ if (this.view.help) {
994
+ if (ev.kind === "click") {
995
+ this.view.help = false;
996
+ this.view.helpScroll = 0;
997
+ this.repaint();
998
+ }
999
+ return;
1000
+ }
1001
+ // The tab's input overlay owns its painted cells. Its renderer supplies
1002
+ // the bounds, so resize/column layout cannot expose hidden tab actions.
1003
+ if (this.view.inputBox) {
1004
+ const meta = this.lastFrame?.meta;
1005
+ const top = meta?.["inputBoxTop"];
1006
+ const bottom = meta?.["inputBoxBottom"];
1007
+ const width = meta?.["inputBoxCols"];
1008
+ if (top !== undefined &&
1009
+ bottom !== undefined &&
1010
+ width !== undefined &&
1011
+ ev.x >= 1 &&
1012
+ ev.x <= width &&
1013
+ ev.y >= top + 1 &&
1014
+ ev.y <= bottom + 1)
1015
+ return;
1016
+ }
1017
+ const tab = this.tabHooks();
1018
+ if (this.tab !== "board") {
1019
+ // An installed surface: clicks hit its own zones (rows select, buttons
1020
+ // act); the wheel is handed over as a key that says it was a wheel.
1021
+ if (ev.kind === "click")
1022
+ tab?.click?.(this, ev.x, ev.y);
1023
+ else
1024
+ tab?.key(this, { kind: ev.kind === "wheel-down" ? "down" : "up", wheel: true });
1025
+ return;
1026
+ }
1027
+ const zoneAt = (x, y) => this.lastFrame ? hitTest(this.lastFrame.zones, x, y) : undefined;
1028
+ if (ev.kind === "wheel-up" || ev.kind === "wheel-down") {
1029
+ const delta = ev.kind === "wheel-down" ? 1 : -1;
1030
+ // Pointer over the pi pane (focused or not) = that session's scrollback,
1031
+ // in place. Everything else keeps the wheel's board meaning.
1032
+ if (!this.view.eventsFocus && zoneAt(ev.x, ev.y)?.t === "pi-focus") {
1033
+ this.scrollPiPane(-delta);
1034
+ return;
1035
+ }
1036
+ // The events overlay owns the wheel while it is up; otherwise the wheel
1037
+ // moves the selection (and the mirror follows it).
1038
+ if (this.view.eventsFocus) {
1039
+ this.scrollEvents(delta);
1040
+ }
1041
+ else {
1042
+ const sorted = visiblePrs(this.view);
1043
+ this.view.selected = moveSelection(this.view.selected, delta, sorted.length);
1044
+ this.selectionMoved(sorted[this.view.selected]?.number);
1045
+ }
1046
+ this.repaint();
1047
+ return;
1048
+ }
1049
+ const action = zoneAt(ev.x, ev.y);
1050
+ // Any click outside the pane hands the keyboard back to the board — that
1051
+ // includes clicks on nothing in particular in the list/detail region.
1052
+ const paneWasFocused = this.view.piFocus;
1053
+ if (action?.t !== "pi-focus" && paneWasFocused) {
1054
+ this.blurPiPane();
1055
+ this.repaint();
1056
+ }
1057
+ if (!action)
1058
+ return;
1059
+ switch (action.t) {
1060
+ case "select-pr": {
1061
+ if (this.selectedPr === action.pr) {
1062
+ // Second click on the selected row = go to its pi, the same thing
1063
+ // ⏎ does. If the pane WAS focused, the click above already handed the
1064
+ // keyboard back, and that is the whole meaning of it.
1065
+ if (paneWasFocused)
1066
+ return;
1067
+ this.focusPiPane();
1068
+ this.repaint();
1069
+ return;
1070
+ }
1071
+ this.selectPr(action.pr);
1072
+ break;
1073
+ }
1074
+ case "open-github":
1075
+ this.openGithub(action.pr);
1076
+ return;
1077
+ case "event-jump":
1078
+ this.selectPr(action.pr);
1079
+ break;
1080
+ case "pi-focus":
1081
+ // Clicking the pane types into it in place — no full-screen takeover.
1082
+ this.focusPiPane();
1083
+ return;
1084
+ case "viewer-lens": {
1085
+ // A lens row of the progress panel, with the transcript viewer in the pane.
1086
+ const pr = this.selectedPr;
1087
+ if (!this.viewerOn(pr))
1088
+ return;
1089
+ if (this.viewer.selectLens(pr, action.lens, action.nth)) {
1090
+ this.syncPiPane();
1091
+ this.repaint();
1092
+ void this.viewer.refresh(pr);
1093
+ }
1094
+ else
1095
+ this.notice(`${action.lens}: no transcript yet — its lens has not started logging`);
1096
+ return;
1097
+ }
1098
+ case "companion":
1099
+ // The top-bar alert badge: hand the keyboard to the companion column.
1100
+ this.openCompanion();
1101
+ return;
1102
+ }
1103
+ this.repaint();
1104
+ }
1105
+ killChord = new TripleCtrlC();
1106
+ mouse = new MouseDecoder();
1107
+ handleInput(buf) {
1108
+ if (this.killChord.feed(buf)) {
1109
+ this.die();
1110
+ return;
1111
+ }
1112
+ if (this.suspended)
1113
+ return;
1114
+ const { events, rest } = this.mouse.feed(buf);
1115
+ for (const ev of events)
1116
+ this.handleMouse(ev);
1117
+ if (rest.length === 0)
1118
+ return;
1119
+ buf = rest;
1120
+ if (this.view.inputBox) {
1121
+ this.handleMessageBoxKey(buf);
1122
+ return;
1123
+ }
1124
+ // The quit prompt outranks every surface, focused sidebar included: it is
1125
+ // modal, and a keystroke meant for it must never reach a pi session.
1126
+ if (this.handleConfirmQuit(buf))
1127
+ return;
1128
+ const key = decodeKey(buf);
1129
+ if (this.view.help) {
1130
+ if ((key.kind === "down" || key.kind === "up") && key.arrow) {
1131
+ const max = this.lastFrame?.meta["helpMax"] ?? 0;
1132
+ const next = (this.view.helpScroll ?? 0) + (key.kind === "down" ? 1 : -1);
1133
+ this.view.helpScroll = Math.min(Math.max(0, next), max);
1134
+ }
1135
+ else {
1136
+ this.view.help = false;
1137
+ this.view.helpScroll = 0;
1138
+ }
1139
+ this.repaint();
1140
+ return;
1141
+ }
1142
+ // The pi pane owns the keyboard while it is focused: j/k/q/1-8/? and every
1143
+ // other board binding is just text as far as the session is concerned.
1144
+ // Ctrl-q — and only Ctrl-q — comes back to the board.
1145
+ if (this.tab === "board" && this.view.piFocus) {
1146
+ if (buf.length === 1 && buf[0] === PANE_ESCAPE) {
1147
+ this.blurPiPane();
1148
+ this.repaint();
1149
+ return;
1150
+ }
1151
+ // The transcript viewer is not a terminal: its keys are its own, and
1152
+ // nothing else goes anywhere.
1153
+ if (this.piFocusViewer && this.piFocusPr !== undefined) {
1154
+ this.handleViewerKey(buf, this.piFocusPr);
1155
+ return;
1156
+ }
1157
+ // End, while parked in scrollback, is the "back to live" key the header
1158
+ // advertises — it snaps the window to the tail instead of travelling.
1159
+ if (isEndKey(buf.toString("utf8")) && this.pi.offsetFor(this.piFocusPr) > 0) {
1160
+ this.pi.setOffset(this.piFocusPr, 0);
1161
+ this.syncPiPane();
1162
+ this.repaint();
1163
+ void this.pi.refresh(this.piFocusPr);
1164
+ return;
1165
+ }
1166
+ this.sendKeysToPane(buf);
1167
+ return;
1168
+ }
1169
+ // A focused sidebar owns the keyboard, exactly as the pi pane does above:
1170
+ // the companion is a live terminal, so j/k/1-8/? are just text to it.
1171
+ // Ctrl-q releases — the same chord, so there is ONE way out of every
1172
+ // embedded surface; Esc belongs to pi.
1173
+ if (this.sidebar?.focused) {
1174
+ this.handleSidebarKey(buf);
1175
+ return;
1176
+ }
1177
+ const raw = buf.toString("utf8");
1178
+ // `/` always focuses the companion, on every tab — and TRAVELS.
1179
+ // It is the key that opens pi's command menu, so swallowing it as a pure
1180
+ // focus key is the worst possible choice: you press `/`, the column takes
1181
+ // the keyboard, and the menu you actually wanted never opens. Seeding it
1182
+ // makes `/` behave like every other printable — focus, then reach pi.
1183
+ if (raw === "/" && this.sidebar?.available()) {
1184
+ this.sidebar.focus(raw);
1185
+ this.repaint();
1186
+ return;
1187
+ }
1188
+ // `\` is plain text for pi like any other unclaimed printable: the column
1189
+ // is always at its full width and the layout never changes shape.
1190
+ // The installed surface gets first crack at every key — including the
1191
+ // nav digits, which the metrics tab reads as TEXT inside its `# <pr> ⏎`
1192
+ // box (`keyChar` maps a tab kind back to its digit). Outside that box no
1193
+ // tab answers to a digit, so the hook returns false and the key falls
1194
+ // through to the switch below.
1195
+ if (this.tabHooks()?.key(this, key))
1196
+ return;
1197
+ // An unregistered printable is a message to the companion, not a dead
1198
+ // key: focus the sidebar seeded with it (paste seeds whole). Registered
1199
+ // means "this frame answers to it" — the set is derived from
1200
+ // the bar the tab is painting right now, never from a table.
1201
+ if (this.sidebar?.available() &&
1202
+ raw.length > 0 &&
1203
+ !raw.includes("\x1b") &&
1204
+ raw >= " " &&
1205
+ raw !== "\x7f" && // backspace (DEL) is an edit, never draft text
1206
+ raw !== "\b" &&
1207
+ !(raw.length === 1 && this.registeredKeys().has(raw))) {
1208
+ this.sidebar.focus(raw);
1209
+ this.repaint();
1210
+ return;
1211
+ }
1212
+ // The verbs below belong to the BOARD's command bar, so they only answer
1213
+ // on the board: off it, `a` would focus the board's pi pane behind your
1214
+ // back, and `u` would unwedge a PR nobody was looking at. A verb with no
1215
+ // meaning on the current surface must do nothing; the printable
1216
+ // spellings are unregistered off the board, so they reach the companion
1217
+ // column instead of dying here. The one exception is a live
1218
+ // alert, which owns ⏎ from wherever you are.
1219
+ if (this.tab !== "board" &&
1220
+ BOARD_ONLY_VERBS.has(key.kind) &&
1221
+ !(key.kind === "enter" && this.view.alertFocus))
1222
+ return;
1223
+ switch (key.kind) {
1224
+ case "quit":
1225
+ this.requestQuit(buf.toString("utf8") === "\x03" ? "ctrl-c" : "key");
1226
+ return;
1227
+ case "help":
1228
+ this.view.help = true;
1229
+ break;
1230
+ case "tab":
1231
+ this.tab = key.tab;
1232
+ break;
1233
+ case "up":
1234
+ case "down": {
1235
+ const delta = key.kind === "down" ? 1 : -1;
1236
+ if (this.view.eventsFocus) {
1237
+ this.scrollEvents(delta);
1238
+ break;
1239
+ }
1240
+ const sorted = visiblePrs(this.view);
1241
+ this.view.selected = moveSelection(this.view.selected, delta, sorted.length);
1242
+ this.msgDraft = ""; // board navigation drops a cancelled draft
1243
+ this.selectionMoved(sorted[this.view.selected]?.number);
1244
+ break;
1245
+ }
1246
+ case "escape":
1247
+ this.view.eventsFocus = false;
1248
+ this.view.alertFocus = false; // acknowledged: ⏎ goes back to the pi pane
1249
+ break;
1250
+ case "events":
1251
+ this.view.eventsFocus = !this.view.eventsFocus;
1252
+ this.view.eventsScroll = 0;
1253
+ break;
1254
+ case "refresh":
1255
+ void this.poller.tick();
1256
+ void this.refreshDetail({ force: true });
1257
+ void this.refreshPane(this.panePr());
1258
+ break;
1259
+ case "reconnect":
1260
+ if (this.hooks.reconnect)
1261
+ this.hooks.reconnect(this);
1262
+ else
1263
+ this.notice("reconnect not wired (test harness?)");
1264
+ void this.poller.tick();
1265
+ break;
1266
+ case "attach":
1267
+ case "enter": {
1268
+ // A live alert owns ⏎ until it is followed or dismissed — the point of
1269
+ // an alert is that the next thing you do is talk to the companion.
1270
+ if (key.kind === "enter" && this.view.alertFocus) {
1271
+ this.openCompanion();
1272
+ return;
1273
+ }
1274
+ // ⏎ on a PR row focuses the board's EMBEDDED pane. Nothing may take
1275
+ // the whole TUI (AGENTS.md invariant 10, the fixed layout). With no PR
1276
+ // selected there is nothing to focus, and nothing else to open.
1277
+ if (this.selectedPr !== undefined) {
1278
+ this.focusPiPane();
1279
+ this.repaint();
1280
+ }
1281
+ return;
1282
+ }
1283
+ case "github":
1284
+ this.openGithub();
1285
+ return;
1286
+ case "message": {
1287
+ const session = this.selectedSession();
1288
+ if (!session)
1289
+ break;
1290
+ if (this.hooks.message)
1291
+ this.hooks.message(session, this);
1292
+ else
1293
+ this.openMessageBox(session);
1294
+ break;
1295
+ }
1296
+ case "unwedge": {
1297
+ // Cancel the selected PR's running round and re-queue it. Safe
1298
+ // and recoverable by construction — the round re-dispatches through
1299
+ // the normal pump, nothing is posted and no GitHub state is touched —
1300
+ // so it acts immediately and reports through the events strip, the
1301
+ // same shape `m` uses. The harsher `restart-session` (which also kills
1302
+ // the conversation) is deliberately NOT bound to a key: it stays an
1303
+ // overseer command, where the extra deliberation is the point.
1304
+ const pr = this.selectedPrNumber();
1305
+ if (pr === undefined)
1306
+ break;
1307
+ if (this.hooks.unwedge)
1308
+ this.hooks.unwedge(pr, this);
1309
+ else
1310
+ this.notice("unwedge: not wired (no remote runner)");
1311
+ break;
1312
+ }
1313
+ case "older":
1314
+ case "newer":
1315
+ // `[ ]` step the viewer's streams — registered only while it is up.
1316
+ if (this.tab === "board" && this.viewerOn(this.selectedPr))
1317
+ this.stepViewer(this.selectedPr, "stream", key.kind === "older" ? -1 : 1);
1318
+ return;
1319
+ case "char":
1320
+ // `< >` step the viewer's rounds.
1321
+ if ((key.ch === "<" || key.ch === ">") &&
1322
+ this.tab === "board" &&
1323
+ this.viewerOn(this.selectedPr)) {
1324
+ this.stepViewer(this.selectedPr, "round", key.ch === "<" ? -1 : 1);
1325
+ return;
1326
+ }
1327
+ // F clears the board filter — the indicator promises it.
1328
+ if (key.ch === "F" && this.tab === "board" && this.view.boardFilter != null) {
1329
+ this.setBoardFilter(null);
1330
+ break;
1331
+ }
1332
+ // I starts setup — the companion takes it from there.
1333
+ if (key.ch === "I" && this.tab === "board") {
1334
+ if (this.hooks.setup)
1335
+ this.hooks.setup(this);
1336
+ else
1337
+ this.notice("setup: not wired (no companion in this TUI)");
1338
+ break;
1339
+ }
1340
+ return;
1341
+ default:
1342
+ return; // unknown keys repaint nothing
1343
+ }
1344
+ this.repaint();
1345
+ }
1346
+ /** Do what the input line asked: send a control, or say why not. */
1347
+ applyViewerEffect(pr, effect) {
1348
+ if (effect.t === "notice") {
1349
+ this.view.noticeLine = effect.text;
1350
+ return;
1351
+ }
1352
+ if (effect.t === "none")
1353
+ return;
1354
+ const req = effect.t === "abort"
1355
+ ? { kind: "abort-round", pr }
1356
+ : effect.kind === "message"
1357
+ ? { kind: "message", pr, text: effect.text, may_post: effect.may_post }
1358
+ : { kind: effect.kind, pr, text: effect.text };
1359
+ const send = this.hooks.liveControl;
1360
+ if (!send) {
1361
+ this.view.noticeLine = `✖ ${req.kind} #${pr} not sent (no control route wired)`;
1362
+ return;
1363
+ }
1364
+ void send(req, this).then(() => this.repaint());
1365
+ }
1366
+ /** Step the viewer's stream (`[ ]`) or round (`< >`) and read what it now shows. */
1367
+ stepViewer(pr, what, dir) {
1368
+ if (what === "stream")
1369
+ this.viewer.stepStream(pr, dir);
1370
+ else
1371
+ this.viewer.stepRound(pr, dir);
1372
+ this.syncPiPane();
1373
+ this.repaint();
1374
+ void this.viewer.refresh(pr);
1375
+ }
1376
+ /**
1377
+ * Keys while the transcript viewer holds the keyboard. It is not a terminal
1378
+ * — there is no pi behind it — so nothing is forwarded, and what it does not
1379
+ * answer to is dropped. Ctrl-q (handled by the caller) releases it.
1380
+ */
1381
+ handleViewerKey(buf, pr) {
1382
+ const raw = buf.toString("utf8");
1383
+ // The input line first: it takes text, ⏎, Alt-⏎, Alt-p and the abort
1384
+ // prompt. The navigation keys below are the line's only when it is empty.
1385
+ const typed = viewerInputKey(this.viewerInputOf(pr), raw, this.roundRunning(pr));
1386
+ if (typed.handled) {
1387
+ this.viewerInputs.set(pr, typed.state);
1388
+ this.applyViewerEffect(pr, typed.effect);
1389
+ this.syncPiPane();
1390
+ this.repaint();
1391
+ return;
1392
+ }
1393
+ if (raw === "[" || raw === "]")
1394
+ return this.stepViewer(pr, "stream", raw === "[" ? -1 : 1);
1395
+ if (raw === "<" || raw === ">")
1396
+ return this.stepViewer(pr, "round", raw === "<" ? -1 : 1);
1397
+ if (raw === "\x1b[5~")
1398
+ this.viewer.page(pr, 1);
1399
+ else if (raw === "\x1b[6~")
1400
+ this.viewer.page(pr, -1);
1401
+ else if (raw === "\x1b[A")
1402
+ this.viewer.setOffset(pr, this.viewer.offsetFor(pr) + 1);
1403
+ else if (raw === "\x1b[B")
1404
+ this.viewer.setOffset(pr, this.viewer.offsetFor(pr) - 1);
1405
+ else if (isEndKey(raw))
1406
+ this.viewer.setOffset(pr, 0);
1407
+ else
1408
+ return;
1409
+ this.syncPiPane();
1410
+ this.repaint();
1411
+ void this.viewer.refresh(pr);
1412
+ }
1413
+ /**
1414
+ * Go to the companion (the sidebar's rows, the alert badge, an alert-armed
1415
+ * ⏎). Following an alert clears its focus — it has been acted on.
1416
+ *
1417
+ * The companion is always on screen, at full width, so "open" means take
1418
+ * the keyboard. Nothing in the TUI covers the whole terminal, and the
1419
+ * column never collapses.
1420
+ */
1421
+ openCompanion() {
1422
+ this.view.alertFocus = false;
1423
+ if (this.sidebar?.available()) {
1424
+ this.focusCompanion();
1425
+ return;
1426
+ }
1427
+ // Down, but a failed startup can heal: hand it to the ensure hook, which
1428
+ // re-runs the startup and focuses on success (companiontab.ts).
1429
+ if (this.hooks.companionEnsure) {
1430
+ void this.hooks.companionEnsure(this);
1431
+ return;
1432
+ }
1433
+ this.notice("companion unavailable (started with --no-companion?)");
1434
+ }
1435
+ /** Give the companion column the keyboard — taking it from the PR pane
1436
+ * first: one keyboard owner, whoever asked (a retry that heals can land
1437
+ * here after the operator focused a pane). */
1438
+ focusCompanion() {
1439
+ if (!this.sidebar)
1440
+ return;
1441
+ if (this.view.piFocus)
1442
+ this.blurPiPane();
1443
+ this.sidebar.focus();
1444
+ this.repaint();
1445
+ }
1446
+ /**
1447
+ * Keys while the companion column has focus: RAW PASSTHROUGH to pi.
1448
+ *
1449
+ * The column is an embedded terminal, exactly like the board's pi pane, and
1450
+ * that is the whole point of running pi in tmux — `/` commands, the
1451
+ * completion menu, history, multi-line editing, Ctrl-C to interrupt a turn
1452
+ * are all pi's, and they only work if pi actually receives the bytes.
1453
+ *
1454
+ * DO NOT reintroduce a TUI-side draft line here: it would be a second, worse
1455
+ * input box in front of the real one, with no `/` commands and nothing
1456
+ * clickable. The rule is in
1457
+ * docs/tui.md; `tests/tui-sidebar.test.ts` pins it.
1458
+ *
1459
+ * Ctrl-q is the ONE byte that never travels — the same release chord the pi
1460
+ * pane uses, so there is one way out of every embedded surface. Everything
1461
+ * else, including Esc and Ctrl-C, belongs to pi.
1462
+ */
1463
+ handleSidebarKey(buf) {
1464
+ const sidebar = this.sidebar;
1465
+ if (buf.length === 1 && buf[0] === PANE_ESCAPE) {
1466
+ sidebar.blur();
1467
+ this.repaint();
1468
+ return;
1469
+ }
1470
+ // Scrollback is the MIRROR's, not pi's: pi cannot scroll a pane it does
1471
+ // not know is being mirrored. PgUp always enters scrollback; PgDn is the
1472
+ // mirror's only while parked (offset > 0), otherwise it reaches pi.
1473
+ const raw = buf.toString("utf8");
1474
+ if ((raw === "\x1b[5~" || raw === "\x1b[6~") && (raw === "\x1b[5~" || sidebar.offset > 0)) {
1475
+ sidebar.scroll(raw === "\x1b[5~" ? 1 : -1);
1476
+ this.repaint();
1477
+ return;
1478
+ }
1479
+ void sidebar
1480
+ .sendKeys(buf)
1481
+ .catch((err) => this.notice(`✖ ${String(err instanceof Error ? err.message : err)}`));
1482
+ }
1483
+ /** The alert that last armed `alertFocus`. A folded repeat keeps the
1484
+ * consumer's `latest` object, so it updates the counters without re-arming
1485
+ * an alert the operator already acknowledged. */
1486
+ armedAlert;
1487
+ /** Alert consumer → top bar. A NEW `alert`-tier alert also grabs ⏎ (see
1488
+ * handleInput); a folded repeat of it does not. */
1489
+ setAlertBadge(badge) {
1490
+ this.view.alerts = badge;
1491
+ if (badge.latest?.tier !== "alert") {
1492
+ this.view.alertFocus = false;
1493
+ this.armedAlert = undefined;
1494
+ }
1495
+ if (badge.latest?.tier === "alert" && badge.latest !== this.armedAlert) {
1496
+ this.armedAlert = badge.latest;
1497
+ this.view.alertFocus = true;
1498
+ }
1499
+ this.repaint();
1500
+ }
1501
+ // ---- paint -------------------------------------------------------------
1502
+ lastFrame;
1503
+ /**
1504
+ * The quit prompt, painted over the finished frame.
1505
+ *
1506
+ * Over the COMPOSED frame, not inside a renderer: it must appear on every
1507
+ * tab and over the companion column alike, and no tab should have to know
1508
+ * it exists. Three rows in the middle, so it cannot be mistaken for the
1509
+ * command bar's own `[q quit]` button.
1510
+ */
1511
+ paintQuitPrompt(frame, cols, rows) {
1512
+ if (!this.view.confirmQuit)
1513
+ return;
1514
+ const text = " Quit qcr tui? [y] yes [any other key] no ";
1515
+ const w = Math.max(2, Math.min(cols - 4, Math.max(text.length + 2, 24)));
1516
+ const x = Math.max(0, Math.floor((cols - w) / 2));
1517
+ const y = Math.max(0, Math.floor(rows / 2) - 1);
1518
+ const box = [`┌${"─".repeat(w - 2)}┐`, `│${fit(text, w - 2)}│`, `└${"─".repeat(w - 2)}┘`];
1519
+ for (let i = 0; i < box.length; i++) {
1520
+ const row = y + i;
1521
+ if (row < 0 || row >= rows)
1522
+ continue;
1523
+ const line = frame.lines[row] ?? "";
1524
+ // Splice by DISPLAY cells: the underlying row is full of SGR runs, and
1525
+ // slicing by code units would cut one in half and bleed colour.
1526
+ frame.set(row, spliceDisplay(line, x, box[i]));
1527
+ }
1528
+ }
1529
+ /**
1530
+ * Does this wheel notch belong to the companion column's scrollback?
1531
+ *
1532
+ * True when the pointer is inside the column, or when the column holds the
1533
+ * keyboard and the pointer is elsewhere.
1534
+ */
1535
+ wheelBelongsToSidebar(x, cols) {
1536
+ const sb = this.sidebar;
1537
+ if (!sb)
1538
+ return false;
1539
+ const width = this.sidebarWidth(cols);
1540
+ if (width === 0)
1541
+ return false;
1542
+ if (x > cols - width && x <= cols)
1543
+ return true;
1544
+ return sb.focused;
1545
+ }
1546
+ /**
1547
+ * Width the companion column takes on THIS paint.
1548
+ *
1549
+ * A pure function of the terminal width and the operator's seam drag —
1550
+ * never of config, docker or the container's state: the column is part of
1551
+ * the layout whenever a sidebar exists at all, so the first frame is the final shape and nothing that
1552
+ * happens later can move it. `0` only for a harness that built no sidebar.
1553
+ */
1554
+ sidebarWidth(cols) {
1555
+ if (!this.sidebar)
1556
+ return 0;
1557
+ return sidebarWidth(cols, { preferredWidth: this.sidebar.preferredWidth });
1558
+ }
1559
+ /** The tab's own width: everything left of the companion column, the
1560
+ * shared border cell included — the width every tab renders and lays its
1561
+ * command bar out at. */
1562
+ innerWidth(cols) {
1563
+ const sw = this.sidebarWidth(cols);
1564
+ return sw === 0 ? cols : cols - sw + 1;
1565
+ }
1566
+ /** Compose the companion column onto a tab's frame. The tab was rendered at
1567
+ * the REDUCED width, so this only ever appends — no coordinate inside the
1568
+ * frame moves, and the tab renderers stay ignorant of the sidebar. */
1569
+ withSidebar(inner, cols, rows) {
1570
+ const width = this.sidebarWidth(cols);
1571
+ if (width === 0 || !this.sidebar)
1572
+ return inner;
1573
+ // joinSeam: the tab's right border and the column's left border are the
1574
+ // same wall, so the inner frame gives up its last cell — which is why the
1575
+ // tab is rendered one column WIDER than `cols - width` (see repaint).
1576
+ return composeColumn(inner, paintSidebar(this.sidebar.view(), width, rows), width, true);
1577
+ }
1578
+ /** Set by run(): the TUI owns the screen and may paint. */
1579
+ started = false;
1580
+ // ---- mouse selection → clipboard -----------------------------------------
1581
+ //
1582
+ // Turning mouse reporting on takes the terminal's own selection away, so the
1583
+ // TUI owes one back. A selection is almost always made to copy it, so there
1584
+ // is no copy key and no copy mode: release IS the copy.
1585
+ //
1586
+ // RECTANGULAR, not linear. The screen is columns side by side — list next to
1587
+ // detail next to the companion — and a linear selection across a row would
1588
+ // splice text from panes the user never pointed at. A block keeps a drag
1589
+ // inside the pane it started in, which is what "select this bit of pi's
1590
+ // output" means beside the companion conversation.
1591
+ sidebarResizeActive = false;
1592
+ selAnchor;
1593
+ selection;
1594
+ /** The last composed frame WITHOUT the highlight — what a drag freezes. */
1595
+ baseFrame;
1596
+ /**
1597
+ * The screen as it was when the drag began, held until release.
1598
+ *
1599
+ * The mirrors refresh under the pointer several times a second while pi
1600
+ * streams: without the freeze the text would scroll out from under a
1601
+ * half-finished drag, and release would copy whatever had arrived in its
1602
+ * place rather than what the operator had highlighted.
1603
+ */
1604
+ selFrame;
1605
+ /** The pane the drag started in (1-based, inclusive); the block never leaves it. */
1606
+ selPane;
1607
+ /** Injectable so tests never touch the real clipboard. */
1608
+ clipboard = new ClipboardSink();
1609
+ /** The selected block as plain text, exactly as it appears on screen. */
1610
+ selectedText() {
1611
+ const sel = this.selection;
1612
+ const frame = this.selFrame ?? this.baseFrame ?? this.lastFrame;
1613
+ if (!sel || !frame)
1614
+ return "";
1615
+ const rows = [];
1616
+ for (let y = sel.y1; y <= sel.y2; y++) {
1617
+ const line = frame.lines[y - 1];
1618
+ if (line === undefined)
1619
+ continue;
1620
+ // BY DISPLAY CELL, not code unit: what is copied must be what was
1621
+ // highlighted, and a single wide glyph earlier in the row would put
1622
+ // those two out of step.
1623
+ rows.push(sliceDisplay(line, sel.x1 - 1, sel.x2));
1624
+ }
1625
+ return cleanCopiedBlock(rows);
1626
+ }
1627
+ /**
1628
+ * The pane under a point, as the content rectangle a selection may cover
1629
+ * (1-based, inclusive, borders excluded).
1630
+ *
1631
+ * A rectangular block already keeps a drag in its COLUMN band, but a drag
1632
+ * that wandered a cell too far picked up the neighbour's border and first
1633
+ * letters — the board's list beside its detail, the companion column beside
1634
+ * the tab. Each pane is known from the layout, not guessed from glyphs: the
1635
+ * companion column, and on the board the list, the detail and the pi pane
1636
+ * (`frame.meta`, written by renderBoard). Any other tab is one pane.
1637
+ */
1638
+ paneRectAt(x, y) {
1639
+ const frame = this.baseFrame;
1640
+ if (!frame)
1641
+ return undefined;
1642
+ const cols = frame.cols;
1643
+ const rows = frame.rows;
1644
+ const sw = this.sidebarWidth(cols);
1645
+ // The seam: with a column, the tab's right border IS the column's left one.
1646
+ const seam = sw === 0 ? cols : this.innerWidth(cols);
1647
+ if (sw > 0 && x > seam)
1648
+ return { x1: seam + 1, y1: 2, x2: cols - 1, y2: rows - 1 };
1649
+ const half = { x1: 2, y1: 1, x2: seam - 1, y2: rows };
1650
+ const m = frame.meta;
1651
+ if (this.tab !== "board" || this.view.help || m["listW"] === undefined)
1652
+ return half;
1653
+ const listW = m["listW"];
1654
+ const bodyRows = m["bodyRows"] ?? 0;
1655
+ const piTop = m["piTop"] ?? rows; // 0-based divider row
1656
+ const piRows = m["piRows"] ?? 0;
1657
+ // Body rows are 0-based 1..bodyRows: 1-based 2..bodyRows+1.
1658
+ if (y >= 2 && y <= bodyRows + 1) {
1659
+ return x <= listW + 1
1660
+ ? { x1: 2, y1: 2, x2: listW + 1, y2: bodyRows + 1 }
1661
+ : { x1: listW + 3, y1: 2, x2: half.x2, y2: bodyRows + 1 };
1662
+ }
1663
+ // The pi pane: its header row and mirror lines, below the divider.
1664
+ if (piRows > 0 && y >= piTop + 2 && y <= piTop + piRows) {
1665
+ return { x1: 2, y1: piTop + 2, x2: half.x2, y2: piTop + piRows };
1666
+ }
1667
+ return half;
1668
+ }
1669
+ /**
1670
+ * The WHOLE last painted frame as plain text: exactly what is on
1671
+ * screen, ANSI stripped, trailing blank rows dropped. Same source as
1672
+ * `selectedText()` (the last painted frame), for the same reason: what the
1673
+ * companion is shown must be what the operator is looking at, not a re-render of
1674
+ * the model behind it.
1675
+ *
1676
+ * `undefined` before the first frame is painted. Never a fetch: this is a
1677
+ * getter over state already painted.
1678
+ */
1679
+ screenText() {
1680
+ const frame = this.lastFrame;
1681
+ if (!frame)
1682
+ return undefined;
1683
+ const rows = frame.lines.map((line) => stripAnsi(line).replace(/\s+$/, ""));
1684
+ while (rows.length > 0 && rows[rows.length - 1] === "")
1685
+ rows.pop();
1686
+ return rows.join("\n");
1687
+ }
1688
+ /**
1689
+ * Called at the end of every repaint, so the published view tracks
1690
+ * keystrokes and not just the 3 s poll. Installed by index.ts; the callee
1691
+ * throttles. It must never repaint (that would recurse) and never throw.
1692
+ */
1693
+ afterRepaint;
1694
+ copySelection() {
1695
+ const text = this.selectedText();
1696
+ this.selAnchor = undefined;
1697
+ if (text.length === 0) {
1698
+ this.selection = undefined;
1699
+ this.repaint();
1700
+ return;
1701
+ }
1702
+ void this.copyText(text);
1703
+ // The highlight stays until the next click, so it is visible at the moment
1704
+ // the confirmation appears — proof of WHAT was copied, not just that
1705
+ // something was.
1706
+ this.repaint();
1707
+ }
1708
+ /**
1709
+ * Put `text` on the system clipboard and say so on the notice line — the
1710
+ * ONE route for every copy the TUI makes: a released drag, and a copy made
1711
+ * inside the companion's container (`from`, e.g. pi's `/copy`), which the
1712
+ * column hands over because nothing else could deliver it (sidebar.ts
1713
+ * `onCopy`).
1714
+ */
1715
+ async copyText(text, from) {
1716
+ if (text.length === 0)
1717
+ return true;
1718
+ const lines = text.split("\n").length;
1719
+ const what = `${text.length} chars${lines > 1 ? ` · ${lines} lines` : ""}`;
1720
+ try {
1721
+ const ok = await this.clipboard.write(text);
1722
+ this.notice(ok
1723
+ ? `✓ copied ${what}${from ? ` from ${from}` : ""}`
1724
+ : "✖ no clipboard tool (wl-copy / xclip / klipper)");
1725
+ return ok;
1726
+ }
1727
+ catch {
1728
+ this.notice("✖ clipboard write failed");
1729
+ return false;
1730
+ }
1731
+ }
1732
+ /** Paint the selection highlight over the finished frame. */
1733
+ paintSelection(frame, cols, rows) {
1734
+ const sel = this.selection;
1735
+ if (!sel)
1736
+ return;
1737
+ for (let y = sel.y1; y <= sel.y2; y++) {
1738
+ if (y < 1 || y > rows)
1739
+ continue;
1740
+ const line = frame.lines[y - 1];
1741
+ if (line === undefined)
1742
+ continue;
1743
+ const x1 = Math.max(1, Math.min(sel.x1, cols));
1744
+ const x2 = Math.max(1, Math.min(sel.x2, cols));
1745
+ const span = sliceDisplay(line, x1 - 1, x2);
1746
+ if (span.length === 0)
1747
+ continue;
1748
+ frame.set(y - 1, spliceDisplay(line, x1 - 1, `${SELECTION}${span}${RESET}`));
1749
+ }
1750
+ }
1751
+ /** Fire `afterRepaint` — never let a publisher's failure reach the paint
1752
+ * path. The screen is the product; publishing it is a side effect. */
1753
+ notifyRepaint() {
1754
+ try {
1755
+ this.afterRepaint?.();
1756
+ }
1757
+ catch {
1758
+ /* a failed publish is never worth a broken frame */
1759
+ }
1760
+ }
1761
+ repaint() {
1762
+ if (!this.started || this.suspended || this.stopped)
1763
+ return;
1764
+ const cols = this.out.columns ?? 80;
1765
+ const rows = this.out.rows ?? 24;
1766
+ // +1 when a column is shown: the tab and the column share one border, and
1767
+ // `withSidebar` drops the tab's last cell to join them (composeColumn's
1768
+ // joinSeam). Without the +1 every tab would lose a usable column.
1769
+ const sw = this.sidebarWidth(cols);
1770
+ // The companion runs in a detached tmux window, whose implicit 80×24 size
1771
+ // otherwise survives forever. Feed the exact painted rectangle back to
1772
+ // the mirror; its next refresh atomically resizes then captures the pane.
1773
+ this.sidebar?.setViewport(sw, rows);
1774
+ const inner = this.innerWidth(cols);
1775
+ const paint = (screen, fallback) => {
1776
+ // An unwired tab (a harness, or a hook that failed to install) paints
1777
+ // its one-line notice INSIDE the layout, never as a bare full-width
1778
+ // string: the column is part of every frame, wired tab or not.
1779
+ // Mid-drag the screen holds still (see selFrame): repaint the frozen
1780
+ // frame with the moving highlight, and nothing new underneath it.
1781
+ const frozen = this.selFrame;
1782
+ const frame = frozen
1783
+ ? cloneFrame(frozen)
1784
+ : this.withSidebar(screen ?? placeholderFrame(inner, rows, fallback), cols, rows);
1785
+ // Zellij-style spatial focus: exactly one pane owns the accent square.
1786
+ // The sidebar and board pi are terminals; otherwise the main TUI pane
1787
+ // owns focus. Colour existing frame glyphs only, never the content.
1788
+ const mainRight = sw === 0 ? frame.cols - 1 : inner - 1;
1789
+ if (frozen) {
1790
+ // already composed and accented
1791
+ }
1792
+ else if (sw > 0 && this.sidebar?.focused) {
1793
+ accentFrameRect(frame, inner - 1, 0, frame.cols - 1, rows - 1, ACCENT, RESET);
1794
+ }
1795
+ else if (this.tab === "board" && this.view.piFocus && frame.meta["piTop"] !== undefined) {
1796
+ accentFrameRect(frame, 0, frame.meta["piTop"], mainRight, rows - 1, ACCENT, RESET);
1797
+ }
1798
+ else {
1799
+ accentFrameRect(frame, 0, 0, mainRight, rows - 1, ACCENT, RESET);
1800
+ }
1801
+ if (!frozen)
1802
+ this.baseFrame = cloneFrame(frame);
1803
+ // Selection first, prompt second: the modal must sit ON TOP of a
1804
+ // highlight, never under it.
1805
+ this.paintSelection(frame, cols, rows);
1806
+ this.paintQuitPrompt(frame, cols, rows);
1807
+ this.lastFrame = frame;
1808
+ this.out.write(frame.render());
1809
+ this.notifyRepaint();
1810
+ };
1811
+ // `?` is a global key advertised on every tab's bar, and the KEYS overlay
1812
+ // (renderBoard's help branch) is the only thing that paints it — so while
1813
+ // help is up, every tab shows it. A tab screen here would leave the flag
1814
+ // set with nothing on screen, and the next keystroke silently eaten.
1815
+ if (this.tab !== "board" && !this.view.help) {
1816
+ const screen = this.tabHooks()?.screen(this, inner, rows);
1817
+ // A prompt the tab opened types into the input box: show it there.
1818
+ if (screen && this.view.inputBox)
1819
+ overlayInputBox(screen, this.view.inputBox);
1820
+ paint(screen, `${this.tab} tab not wired — [1] back to board`);
1821
+ return;
1822
+ }
1823
+ this.view.now = this.now();
1824
+ // Same reason as the sidebar above: a debug round's or a takeover's pi runs
1825
+ // in a detached tmux window stuck at 80×24, so without this it wraps at 80
1826
+ // columns however wide we paint the mirror. Fed here, applied by the next capture — and
1827
+ // sized against the bar rows THIS frame's board actually paints.
1828
+ const paneSize = piPaneSize(inner, rows, boardBarHeight(this.view, inner));
1829
+ this.pi.setViewport(paneSize);
1830
+ // The viewer keeps its last pane row for the input line.
1831
+ this.viewer.setViewport(paneSize && { ...paneSize, rows: Math.max(1, paneSize.rows - 1) });
1832
+ paint(renderBoard(this.view, inner, rows), "");
1833
+ }
1834
+ }
1835
+ //# sourceMappingURL=app.js.map