@quickchatai/quickreview 1.1.0 → 1.3.1

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 (471) hide show
  1. package/README.md +137 -57
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +60 -14
  4. package/deploy/ansible/README.md +20 -12
  5. package/deploy/ansible/inventory.example.ini +1 -1
  6. package/deploy/collect-secrets.sh +1 -1
  7. package/deploy/qcr-update.sh +1 -1
  8. package/dist/extensions/cli/home.js +14 -0
  9. package/dist/extensions/cli/home.js.map +1 -0
  10. package/dist/extensions/cli/login-route.js +5 -0
  11. package/dist/extensions/cli/login-route.js.map +1 -0
  12. package/dist/extensions/cli/selfimprove.js +18 -0
  13. package/dist/extensions/cli/selfimprove.js.map +1 -0
  14. package/dist/extensions/home.js +350 -0
  15. package/dist/extensions/home.js.map +1 -0
  16. package/dist/extensions/login-route.js +180 -0
  17. package/dist/extensions/login-route.js.map +1 -0
  18. package/dist/extensions/onboarding.js +35 -11
  19. package/dist/extensions/onboarding.js.map +1 -1
  20. package/dist/extensions/selfimprove.js +49 -0
  21. package/dist/extensions/selfimprove.js.map +1 -0
  22. package/dist/extensions/setup-scan.js +23 -592
  23. package/dist/extensions/setup-scan.js.map +1 -1
  24. package/dist/extensions/setup.js +2 -47
  25. package/dist/extensions/setup.js.map +1 -1
  26. package/dist/src/archive/restore.js +18 -15
  27. package/dist/src/archive/restore.js.map +1 -1
  28. package/dist/src/botworker.js +13 -2
  29. package/dist/src/botworker.js.map +1 -1
  30. package/dist/src/channelfs.js +3 -26
  31. package/dist/src/channelfs.js.map +1 -1
  32. package/dist/src/ciretry.js +2 -1
  33. package/dist/src/ciretry.js.map +1 -1
  34. package/dist/src/cli.js +54 -61
  35. package/dist/src/cli.js.map +1 -1
  36. package/dist/src/cliops.js +3 -2
  37. package/dist/src/cliops.js.map +1 -1
  38. package/dist/src/commands.js +25 -2
  39. package/dist/src/commands.js.map +1 -1
  40. package/dist/src/config.js +32 -2
  41. package/dist/src/config.js.map +1 -1
  42. package/dist/src/daemon.js +50 -8
  43. package/dist/src/daemon.js.map +1 -1
  44. package/dist/src/debuground.js +1 -0
  45. package/dist/src/debuground.js.map +1 -1
  46. package/dist/src/debugroundpi.js +6 -1
  47. package/dist/src/debugroundpi.js.map +1 -1
  48. package/dist/src/dispatch.js +5 -4
  49. package/dist/src/dispatch.js.map +1 -1
  50. package/dist/src/engine/caserunner-sdk.js +15 -251
  51. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  52. package/dist/src/engine/debugpi.js +17 -5
  53. package/dist/src/engine/debugpi.js.map +1 -1
  54. package/dist/src/execboundary.js +10 -3
  55. package/dist/src/execboundary.js.map +1 -1
  56. package/dist/src/github.js +51 -16
  57. package/dist/src/github.js.map +1 -1
  58. package/dist/src/githubtoken.js +346 -0
  59. package/dist/src/githubtoken.js.map +1 -0
  60. package/dist/src/home/adapters.js +349 -0
  61. package/dist/src/home/adapters.js.map +1 -0
  62. package/dist/src/home/advanced.js +318 -0
  63. package/dist/src/home/advanced.js.map +1 -0
  64. package/dist/src/home/advancedtools.js +57 -0
  65. package/dist/src/home/advancedtools.js.map +1 -0
  66. package/dist/src/home/autoreview.js +325 -0
  67. package/dist/src/home/autoreview.js.map +1 -0
  68. package/dist/src/home/daemonstatus.js +32 -0
  69. package/dist/src/home/daemonstatus.js.map +1 -0
  70. package/dist/src/home/devicelogin.js +61 -0
  71. package/dist/src/home/devicelogin.js.map +1 -0
  72. package/dist/src/home/launch.js +202 -0
  73. package/dist/src/home/launch.js.map +1 -0
  74. package/dist/src/home/loginowner.js +205 -0
  75. package/dist/src/home/loginowner.js.map +1 -0
  76. package/dist/src/home/memory.js +122 -0
  77. package/dist/src/home/memory.js.map +1 -0
  78. package/dist/src/home/model.js +811 -0
  79. package/dist/src/home/model.js.map +1 -0
  80. package/dist/src/home/panel.js +520 -0
  81. package/dist/src/home/panel.js.map +1 -0
  82. package/dist/src/home/ports.js +19 -0
  83. package/dist/src/home/ports.js.map +1 -0
  84. package/dist/src/home/review.js +163 -0
  85. package/dist/src/home/review.js.map +1 -0
  86. package/dist/src/home/reviews.js +478 -0
  87. package/dist/src/home/reviews.js.map +1 -0
  88. package/dist/src/home/setup.js +507 -0
  89. package/dist/src/home/setup.js.map +1 -0
  90. package/dist/src/home/setupadapters.js +214 -0
  91. package/dist/src/home/setupadapters.js.map +1 -0
  92. package/dist/src/home/setupcard.js +199 -0
  93. package/dist/src/home/setupcard.js.map +1 -0
  94. package/dist/src/home/setuptools.js +45 -0
  95. package/dist/src/home/setuptools.js.map +1 -0
  96. package/dist/src/home/spend.js +160 -0
  97. package/dist/src/home/spend.js.map +1 -0
  98. package/dist/src/home/switches.js +209 -0
  99. package/dist/src/home/switches.js.map +1 -0
  100. package/dist/src/home/switchon.js +439 -0
  101. package/dist/src/home/switchon.js.map +1 -0
  102. package/dist/src/home/toolnames.js +41 -0
  103. package/dist/src/home/toolnames.js.map +1 -0
  104. package/dist/src/home/tune.js +341 -0
  105. package/dist/src/home/tune.js.map +1 -0
  106. package/dist/src/home/words.js +7 -0
  107. package/dist/src/home/words.js.map +1 -0
  108. package/dist/src/homes.js +14 -0
  109. package/dist/src/homes.js.map +1 -1
  110. package/dist/src/install.js +1 -1
  111. package/dist/src/install.js.map +1 -1
  112. package/dist/src/ipc/workerspec.js +8 -0
  113. package/dist/src/ipc/workerspec.js.map +1 -1
  114. package/dist/src/live/publish.js +1 -1
  115. package/dist/src/live/publish.js.map +1 -1
  116. package/dist/src/livecli.js +1 -1
  117. package/dist/src/livecli.js.map +1 -1
  118. package/dist/src/loginhint.js +8 -3
  119. package/dist/src/loginhint.js.map +1 -1
  120. package/dist/src/loginrefresh/handover.js +121 -0
  121. package/dist/src/loginrefresh/handover.js.map +1 -0
  122. package/dist/src/loginrefresh/lock.js +54 -3
  123. package/dist/src/loginrefresh/lock.js.map +1 -1
  124. package/dist/src/loginrefresh/pirefresh.js +3 -0
  125. package/dist/src/loginrefresh/pirefresh.js.map +1 -1
  126. package/dist/src/loginrefresh/refresher.js +33 -5
  127. package/dist/src/loginrefresh/refresher.js.map +1 -1
  128. package/dist/src/loginrefresh/supervisor.js +18 -1
  129. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  130. package/dist/src/npmverbs.js +46 -14
  131. package/dist/src/npmverbs.js.map +1 -1
  132. package/dist/src/onboard/choices.js +35 -4
  133. package/dist/src/onboard/choices.js.map +1 -1
  134. package/dist/src/onboard/engine.js +32 -306
  135. package/dist/src/onboard/engine.js.map +1 -1
  136. package/dist/src/onboard/pipin.js +22 -0
  137. package/dist/src/onboard/pipin.js.map +1 -0
  138. package/dist/src/onboard/stepforward.js +41 -0
  139. package/dist/src/onboard/stepforward.js.map +1 -0
  140. package/dist/src/onboard/steps/appslug.js +4 -1
  141. package/dist/src/onboard/steps/appslug.js.map +1 -1
  142. package/dist/src/onboard/steps/bringup.js +3 -2
  143. package/dist/src/onboard/steps/bringup.js.map +1 -1
  144. package/dist/src/onboard/steps/compose.js +29 -0
  145. package/dist/src/onboard/steps/compose.js.map +1 -1
  146. package/dist/src/onboard/steps/configfill.js +120 -39
  147. package/dist/src/onboard/steps/configfill.js.map +1 -1
  148. package/dist/src/onboard/steps/githubaccount.js +595 -0
  149. package/dist/src/onboard/steps/githubaccount.js.map +1 -0
  150. package/dist/src/onboard/steps/githubapp.js +65 -7
  151. package/dist/src/onboard/steps/githubapp.js.map +1 -1
  152. package/dist/src/onboard/steps/golive.js +2 -0
  153. package/dist/src/onboard/steps/golive.js.map +1 -1
  154. package/dist/src/onboard/steps/index.js +11 -0
  155. package/dist/src/onboard/steps/index.js.map +1 -1
  156. package/dist/src/onboard/steps/ingress.js +80 -25
  157. package/dist/src/onboard/steps/ingress.js.map +1 -1
  158. package/dist/src/onboard/steps/llmauth.js +103 -30
  159. package/dist/src/onboard/steps/llmauth.js.map +1 -1
  160. package/dist/src/onboard/steps/loginshare.js +213 -0
  161. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  162. package/dist/src/onboard/steps/memory.js +67 -3
  163. package/dist/src/onboard/steps/memory.js.map +1 -1
  164. package/dist/src/onboard/steps/proof.js +366 -56
  165. package/dist/src/onboard/steps/proof.js.map +1 -1
  166. package/dist/src/onboard/steps/repoconfig.js +72 -0
  167. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  168. package/dist/src/onboard/steps/topology.js +6 -6
  169. package/dist/src/onboard/steps/topology.js.map +1 -1
  170. package/dist/src/onboard/steps/transport.js +9 -1
  171. package/dist/src/onboard/steps/transport.js.map +1 -1
  172. package/dist/src/onboard/steps/tuiroute.js +102 -0
  173. package/dist/src/onboard/steps/tuiroute.js.map +1 -0
  174. package/dist/src/onboard/steps/verifyup.js +17 -1
  175. package/dist/src/onboard/steps/verifyup.js.map +1 -1
  176. package/dist/src/onboard/toolnames.js +30 -0
  177. package/dist/src/onboard/toolnames.js.map +1 -0
  178. package/dist/src/operatorwrite.js +23 -0
  179. package/dist/src/operatorwrite.js.map +1 -0
  180. package/dist/src/outcomes.js +4 -1
  181. package/dist/src/outcomes.js.map +1 -1
  182. package/dist/src/piagentdir.js +13 -0
  183. package/dist/src/piagentdir.js.map +1 -0
  184. package/dist/src/pilogin.js +31 -16
  185. package/dist/src/pilogin.js.map +1 -1
  186. package/dist/src/posting-journal.js +2 -1
  187. package/dist/src/posting-journal.js.map +1 -1
  188. package/dist/src/posting-outbox.js +6 -2
  189. package/dist/src/posting-outbox.js.map +1 -1
  190. package/dist/src/posting-receipt.js +14 -1
  191. package/dist/src/posting-receipt.js.map +1 -1
  192. package/dist/src/qcrbin.js +16 -11
  193. package/dist/src/qcrbin.js.map +1 -1
  194. package/dist/src/recheck.js +2 -1
  195. package/dist/src/recheck.js.map +1 -1
  196. package/dist/src/reconcile.js +22 -2
  197. package/dist/src/reconcile.js.map +1 -1
  198. package/dist/src/reviewargs.js +122 -0
  199. package/dist/src/reviewargs.js.map +1 -0
  200. package/dist/src/reviewcli.js +7 -161
  201. package/dist/src/reviewcli.js.map +1 -1
  202. package/dist/src/reviewdetach.js +76 -11
  203. package/dist/src/reviewdetach.js.map +1 -1
  204. package/dist/src/reviewhistory.js +32 -6
  205. package/dist/src/reviewhistory.js.map +1 -1
  206. package/dist/src/reviewnow.js +45 -0
  207. package/dist/src/reviewnow.js.map +1 -0
  208. package/dist/src/scancli.js +39 -691
  209. package/dist/src/scancli.js.map +1 -1
  210. package/dist/src/selfimprove.js +66 -0
  211. package/dist/src/selfimprove.js.map +1 -0
  212. package/dist/src/setup/adapters.js +5 -223
  213. package/dist/src/setup/adapters.js.map +1 -1
  214. package/dist/src/setup/caserunner.js +15 -249
  215. package/dist/src/setup/caserunner.js.map +1 -1
  216. package/dist/src/setup/engine.js +32 -341
  217. package/dist/src/setup/engine.js.map +1 -1
  218. package/dist/src/setup/facts.js +3 -592
  219. package/dist/src/setup/facts.js.map +1 -1
  220. package/dist/src/setup/findings.js +3 -947
  221. package/dist/src/setup/findings.js.map +1 -1
  222. package/dist/src/setup/presentation.js +3 -8
  223. package/dist/src/setup/presentation.js.map +1 -1
  224. package/dist/src/setup/scan.js +3 -724
  225. package/dist/src/setup/scan.js.map +1 -1
  226. package/dist/src/setup/scanbuild.js +9 -92
  227. package/dist/src/setup/scanbuild.js.map +1 -1
  228. package/dist/src/setup/scanlease.js +3 -250
  229. package/dist/src/setup/scanlease.js.map +1 -1
  230. package/dist/src/setup/scanreport.js +3 -288
  231. package/dist/src/setup/scanreport.js.map +1 -1
  232. package/dist/src/setup/scanrun.js +3 -1732
  233. package/dist/src/setup/scanrun.js.map +1 -1
  234. package/dist/src/setup/scantree.js +3 -256
  235. package/dist/src/setup/scantree.js.map +1 -1
  236. package/dist/src/setup/stage2request.js +3 -258
  237. package/dist/src/setup/stage2request.js.map +1 -1
  238. package/dist/src/setup/unitcut.js +3 -436
  239. package/dist/src/setup/unitcut.js.map +1 -1
  240. package/dist/src/state.js +2 -0
  241. package/dist/src/state.js.map +1 -1
  242. package/dist/src/statemachine.js +4 -1
  243. package/dist/src/statemachine.js.map +1 -1
  244. package/dist/src/takeover.js +1 -0
  245. package/dist/src/takeover.js.map +1 -1
  246. package/dist/src/takeoverpi.js +15 -1
  247. package/dist/src/takeoverpi.js.map +1 -1
  248. package/dist/src/tui/app.js +49 -0
  249. package/dist/src/tui/app.js.map +1 -1
  250. package/dist/src/tui/board.js +88 -12
  251. package/dist/src/tui/board.js.map +1 -1
  252. package/dist/src/tui/cmdbar.js +4 -1
  253. package/dist/src/tui/cmdbar.js.map +1 -1
  254. package/dist/src/tui/companion.js +76 -11
  255. package/dist/src/tui/companion.js.map +1 -1
  256. package/dist/src/tui/connect.js +7 -1
  257. package/dist/src/tui/connect.js.map +1 -1
  258. package/dist/src/tui/deploytab.js +2 -2
  259. package/dist/src/tui/deploytab.js.map +1 -1
  260. package/dist/src/tui/filters.js +5 -2
  261. package/dist/src/tui/filters.js.map +1 -1
  262. package/dist/src/tui/ghread.js +61 -5
  263. package/dist/src/tui/ghread.js.map +1 -1
  264. package/dist/src/tui/index.js +109 -20
  265. package/dist/src/tui/index.js.map +1 -1
  266. package/dist/src/tui/memforward.js +39 -2
  267. package/dist/src/tui/memforward.js.map +1 -1
  268. package/dist/src/tui/memory.js +3 -16
  269. package/dist/src/tui/memory.js.map +1 -1
  270. package/dist/src/tui/memoryurl.js +22 -0
  271. package/dist/src/tui/memoryurl.js.map +1 -0
  272. package/dist/src/tui/model.js +25 -0
  273. package/dist/src/tui/model.js.map +1 -1
  274. package/dist/src/tui/prpause.js +7 -0
  275. package/dist/src/tui/prpause.js.map +1 -1
  276. package/dist/src/tui/remote.js +7 -1
  277. package/dist/src/tui/remote.js.map +1 -1
  278. package/dist/src/tui/route.js +143 -0
  279. package/dist/src/tui/route.js.map +1 -0
  280. package/dist/src/tui/setupstate.js +32 -0
  281. package/dist/src/tui/setupstate.js.map +1 -0
  282. package/dist/src/tui/sshtriage.js +102 -5
  283. package/dist/src/tui/sshtriage.js.map +1 -1
  284. package/dist/src/tui/theme.js +2 -1
  285. package/dist/src/tui/theme.js.map +1 -1
  286. package/dist/src/tui/unwedge.js +2 -12
  287. package/dist/src/tui/unwedge.js.map +1 -1
  288. package/dist/src/verdicts.js +17 -5
  289. package/dist/src/verdicts.js.map +1 -1
  290. package/dist/src/version.js +1 -1
  291. package/dist/src/version.js.map +1 -1
  292. package/dist/src/webhook.js +12 -1
  293. package/dist/src/webhook.js.map +1 -1
  294. package/dist/src/wire.js +34 -8
  295. package/dist/src/wire.js.map +1 -1
  296. package/dist/src/workerworld.js +11 -1
  297. package/dist/src/workerworld.js.map +1 -1
  298. package/docker/README.md +1 -1
  299. package/docker/companion/entrypoint.sh +20 -1
  300. package/docker/entrypoint.sh +57 -6
  301. package/docker-compose.yml +16 -1
  302. package/extensions/cli/home.ts +14 -0
  303. package/extensions/cli/login-route.ts +10 -0
  304. package/extensions/cli/selfimprove.ts +18 -0
  305. package/extensions/home.ts +484 -0
  306. package/extensions/login-route.ts +220 -0
  307. package/extensions/onboarding.ts +41 -11
  308. package/extensions/selfimprove.ts +65 -0
  309. package/extensions/setup-scan.ts +79 -766
  310. package/extensions/setup.ts +14 -70
  311. package/package.json +6 -4
  312. package/prompts/onboarding.md +58 -18
  313. package/schemas/review-result.v1.json +60 -0
  314. package/scripts/deploy.sh +8 -6
  315. package/scripts/mint-token.sh +25 -4
  316. package/scripts/qcr-host.sh +28 -5
  317. package/scripts/vm-ghcr-login.sh +7 -4
  318. package/src/archive/restore.ts +18 -21
  319. package/src/botworker.ts +13 -2
  320. package/src/channelfs.ts +3 -26
  321. package/src/ciretry.ts +2 -1
  322. package/src/cli.ts +59 -67
  323. package/src/cliops.ts +3 -2
  324. package/src/commands.ts +28 -2
  325. package/src/config.ts +62 -4
  326. package/src/daemon.ts +58 -7
  327. package/src/debuground.ts +3 -0
  328. package/src/debugroundpi.ts +12 -1
  329. package/src/dispatch.ts +5 -4
  330. package/src/engine/caserunner-sdk.ts +29 -314
  331. package/src/engine/debugpi.ts +17 -5
  332. package/src/execboundary.ts +16 -4
  333. package/src/github.ts +57 -15
  334. package/src/githubtoken.ts +435 -0
  335. package/src/home/adapters.ts +418 -0
  336. package/src/home/advanced.ts +421 -0
  337. package/src/home/advancedtools.ts +96 -0
  338. package/src/home/autoreview.ts +385 -0
  339. package/src/home/daemonstatus.ts +55 -0
  340. package/src/home/devicelogin.ts +107 -0
  341. package/src/home/launch.ts +257 -0
  342. package/src/home/loginowner.ts +248 -0
  343. package/src/home/memory.ts +176 -0
  344. package/src/home/model.ts +965 -0
  345. package/src/home/panel.ts +666 -0
  346. package/src/home/ports.ts +338 -0
  347. package/src/home/review.ts +206 -0
  348. package/src/home/reviews.ts +565 -0
  349. package/src/home/setup.ts +689 -0
  350. package/src/home/setupadapters.ts +217 -0
  351. package/src/home/setupcard.ts +288 -0
  352. package/src/home/setuptools.ts +57 -0
  353. package/src/home/spend.ts +194 -0
  354. package/src/home/switches.ts +293 -0
  355. package/src/home/switchon.ts +554 -0
  356. package/src/home/toolnames.ts +46 -0
  357. package/src/home/tune.ts +405 -0
  358. package/src/home/words.ts +8 -0
  359. package/src/homes.ts +17 -0
  360. package/src/install.ts +1 -1
  361. package/src/ipc/workerspec.ts +15 -0
  362. package/src/live/publish.ts +4 -1
  363. package/src/livecli.ts +6 -1
  364. package/src/loginhint.ts +8 -3
  365. package/src/loginrefresh/handover.ts +138 -0
  366. package/src/loginrefresh/lock.ts +72 -3
  367. package/src/loginrefresh/pirefresh.ts +3 -0
  368. package/src/loginrefresh/refresher.ts +34 -5
  369. package/src/loginrefresh/supervisor.ts +25 -2
  370. package/src/npmverbs.ts +55 -13
  371. package/src/onboard/choices.ts +40 -4
  372. package/src/onboard/engine.ts +70 -421
  373. package/src/onboard/pipin.ts +31 -0
  374. package/src/onboard/stepforward.ts +61 -0
  375. package/src/onboard/steps/appslug.ts +4 -1
  376. package/src/onboard/steps/bringup.ts +3 -2
  377. package/src/onboard/steps/compose.ts +31 -0
  378. package/src/onboard/steps/configfill.ts +144 -40
  379. package/src/onboard/steps/githubaccount.ts +716 -0
  380. package/src/onboard/steps/githubapp.ts +92 -8
  381. package/src/onboard/steps/golive.ts +2 -0
  382. package/src/onboard/steps/index.ts +11 -0
  383. package/src/onboard/steps/ingress.ts +98 -31
  384. package/src/onboard/steps/llmauth.ts +114 -29
  385. package/src/onboard/steps/loginshare.ts +258 -0
  386. package/src/onboard/steps/memory.ts +76 -3
  387. package/src/onboard/steps/proof.ts +473 -55
  388. package/src/onboard/steps/repoconfig.ts +85 -0
  389. package/src/onboard/steps/topology.ts +7 -6
  390. package/src/onboard/steps/transport.ts +15 -0
  391. package/src/onboard/steps/tuiroute.ts +119 -0
  392. package/src/onboard/steps/verifyup.ts +20 -1
  393. package/src/onboard/toolnames.ts +34 -0
  394. package/src/operatorwrite.ts +25 -0
  395. package/src/outcomes.ts +8 -1
  396. package/src/piagentdir.ts +12 -0
  397. package/src/pilogin.ts +32 -16
  398. package/src/posting-journal.ts +6 -1
  399. package/src/posting-outbox.ts +10 -2
  400. package/src/posting-receipt.ts +17 -0
  401. package/src/qcrbin.ts +16 -11
  402. package/src/recheck.ts +2 -1
  403. package/src/reconcile.ts +29 -3
  404. package/src/reviewargs.ts +119 -0
  405. package/src/reviewcli.ts +6 -167
  406. package/src/reviewdetach.ts +107 -13
  407. package/src/reviewhistory.ts +20 -6
  408. package/src/reviewnow.ts +76 -0
  409. package/src/scancli.ts +75 -826
  410. package/src/selfimprove.ts +90 -0
  411. package/src/setup/adapters.ts +13 -221
  412. package/src/setup/caserunner.ts +34 -273
  413. package/src/setup/engine.ts +62 -496
  414. package/src/setup/facts.ts +3 -627
  415. package/src/setup/findings.ts +3 -1264
  416. package/src/setup/presentation.ts +3 -18
  417. package/src/setup/scan.ts +3 -1033
  418. package/src/setup/scanbuild.ts +20 -114
  419. package/src/setup/scanlease.ts +3 -297
  420. package/src/setup/scanreport.ts +3 -373
  421. package/src/setup/scanrun.ts +3 -2268
  422. package/src/setup/scantree.ts +3 -259
  423. package/src/setup/stage2request.ts +3 -347
  424. package/src/setup/unitcut.ts +3 -523
  425. package/src/state.ts +43 -0
  426. package/src/statemachine.ts +4 -1
  427. package/src/takeover.ts +5 -0
  428. package/src/takeoverpi.ts +16 -2
  429. package/src/tui/app.ts +47 -0
  430. package/src/tui/board.ts +111 -14
  431. package/src/tui/cmdbar.ts +4 -1
  432. package/src/tui/companion.ts +80 -11
  433. package/src/tui/connect.ts +7 -1
  434. package/src/tui/deploytab.ts +2 -2
  435. package/src/tui/filters.ts +7 -3
  436. package/src/tui/ghread.ts +74 -7
  437. package/src/tui/index.ts +117 -23
  438. package/src/tui/memforward.ts +52 -3
  439. package/src/tui/memory.ts +3 -15
  440. package/src/tui/memoryurl.ts +22 -0
  441. package/src/tui/model.ts +36 -2
  442. package/src/tui/prpause.ts +11 -2
  443. package/src/tui/remote.ts +9 -1
  444. package/src/tui/route.ts +173 -0
  445. package/src/tui/setupstate.ts +56 -0
  446. package/src/tui/sshtriage.ts +127 -6
  447. package/src/tui/theme.ts +2 -1
  448. package/src/tui/unwedge.ts +2 -14
  449. package/src/verdicts.ts +23 -5
  450. package/src/version.ts +1 -1
  451. package/src/webhook.ts +10 -1
  452. package/src/wire.ts +42 -9
  453. package/src/workerworld.ts +16 -2
  454. package/dist/src/live/projection.js +0 -450
  455. package/dist/src/live/projection.js.map +0 -1
  456. package/dist/src/live/read.js +0 -60
  457. package/dist/src/live/read.js.map +0 -1
  458. package/dist/src/onboard/launch.js +0 -83
  459. package/dist/src/onboard/launch.js.map +0 -1
  460. package/dist/src/setup/ledger.js +0 -207
  461. package/dist/src/setup/ledger.js.map +0 -1
  462. package/dist/src/setup/stage2.js +0 -617
  463. package/dist/src/setup/stage2.js.map +0 -1
  464. package/dist/src/setup/textsniff.js +0 -89
  465. package/dist/src/setup/textsniff.js.map +0 -1
  466. package/src/live/projection.ts +0 -491
  467. package/src/live/read.ts +0 -86
  468. package/src/onboard/launch.ts +0 -115
  469. package/src/setup/ledger.ts +0 -324
  470. package/src/setup/stage2.ts +0 -926
  471. package/src/setup/textsniff.ts +0 -95
@@ -0,0 +1,811 @@
1
+ import { KEEP_IT_HERE, postAnswer } from "@quickchatai/pi-quickreview/src/plugin/messages.js";
2
+ import { PENDING, known, unknown, } from "./ports.js";
3
+ import { attachedRun, foldStatus, plannedRun, quoteRun, reviewMessage, runReview, } from "./review.js";
4
+ import { switchAutoReview, switchMemory } from "./switches.js";
5
+ import { money, plural } from "./words.js";
6
+ /** After a re-login, auto-review's own "login expired" is taken as old news
7
+ * for this long: its status lags the refresher's by a poll or two. */
8
+ export const RELOGIN_GRACE_MS = 5 * 60_000;
9
+ /** How long the cursor rests on a PR before the home prices it. */
10
+ export const PRICE_REST_MS = 500;
11
+ /** How often a running review's status is read. */
12
+ export const STATUS_POLL_MS = 2000;
13
+ /** "about $1.33, stops at $2.66", or what is not known of it. */
14
+ export function priceWords(est, cap) {
15
+ if (est === null)
16
+ return "price unknown";
17
+ return `about ${money(est)}${cap === null ? ", no hard stop" : `, stops at ${money(cap)}`}`;
18
+ }
19
+ const noTimer = () => () => { };
20
+ /** The provider of a `provider/id` model. */
21
+ export function providerOf(model) {
22
+ return model.split("/")[0] ?? model;
23
+ }
24
+ /** The provider as a person names it. */
25
+ export function providerLabel(provider) {
26
+ return provider === "openai-codex" || provider === "openai" ? "OpenAI" : provider;
27
+ }
28
+ export function pullUrl(repo, pr) {
29
+ return `https://github.com/${repo}/pull/${pr}`;
30
+ }
31
+ /** A short age: 5m, 3h, 2d. */
32
+ export function ago(thenMs, nowMs) {
33
+ if (!Number.isFinite(thenMs))
34
+ return "?";
35
+ const m = Math.max(0, (nowMs - thenMs) / 60_000);
36
+ return m < 60
37
+ ? `${Math.round(m)}m`
38
+ : m < 1440
39
+ ? `${Math.round(m / 60)}h`
40
+ : `${Math.round(m / 1440)}d`;
41
+ }
42
+ const message = (err) => (err instanceof Error ? err.message : String(err));
43
+ /** A port's answer; a port that throws instead of answering is unknown. */
44
+ async function safe(read) {
45
+ try {
46
+ return await read();
47
+ }
48
+ catch (err) {
49
+ return unknown(message(err));
50
+ }
51
+ }
52
+ /** An action port's outcome; one that throws is a refusal with its message. */
53
+ async function attempt(act) {
54
+ try {
55
+ return await act();
56
+ }
57
+ catch (err) {
58
+ return { ok: false, why: message(err) };
59
+ }
60
+ }
61
+ const later = (a, b) => a === undefined ? b : b === undefined ? a : b.at > a.at ? b : a;
62
+ const newestFirst = (a, b) => b.number - a.number;
63
+ const valueOf = (s) => (s.kind === "known" ? s.value : undefined);
64
+ const nullable = (s) => (s.kind === "known" ? s.value : null);
65
+ export class HomeModel {
66
+ o;
67
+ state = {
68
+ login: PENDING,
69
+ github: PENDING,
70
+ repo: PENDING,
71
+ pulls: PENDING,
72
+ reviews: PENDING,
73
+ autoRounds: PENDING,
74
+ autoReview: PENDING,
75
+ trigger: null,
76
+ memory: PENDING,
77
+ spend: PENDING,
78
+ loginHealth: null,
79
+ };
80
+ /** Slots a switch shows its own state in while it runs (src/home/switches.ts):
81
+ * a refresh does not overwrite them until released. */
82
+ held = new Set();
83
+ /** The selected pull request's number. */
84
+ selected = null;
85
+ notice = null;
86
+ tools;
87
+ provider;
88
+ inFlight = null;
89
+ greeted = false;
90
+ pullsWarned = false;
91
+ /** The latest run per PR this home started or re-attached to. */
92
+ runs = new Map();
93
+ /** Quotes by PR, each for the head it priced. */
94
+ quotes = new Map();
95
+ /** The pending rest timer's cancel. */
96
+ pricer = null;
97
+ /** The one background preview in flight. */
98
+ pricing = null;
99
+ timers = new Set();
100
+ /** PRs whose review the person stopped before it had a run id. */
101
+ stopping = new Set();
102
+ attachedTo = null;
103
+ disposed = false;
104
+ /** pi refused the chat for its login: the error it showed. */
105
+ chatLoginError = null;
106
+ /** When the person last signed in again, ms since the epoch. */
107
+ reloggedAt = null;
108
+ after;
109
+ constructor(o) {
110
+ this.o = o;
111
+ this.tools = o.tools;
112
+ this.provider = providerOf(o.model);
113
+ this.after = o.after ?? noTimer;
114
+ if (o.adapters.autoReview.trigger)
115
+ this.state.trigger = PENDING;
116
+ if (o.adapters.loginOwner)
117
+ this.state.loginHealth = PENDING;
118
+ }
119
+ /** The session ended: no timer fires and no preview runs. Runs go on. */
120
+ dispose() {
121
+ this.disposed = true;
122
+ this.pricer?.();
123
+ this.pricer = null;
124
+ this.pricing?.stop.abort();
125
+ this.pricing = null;
126
+ for (const cancel of this.timers)
127
+ cancel();
128
+ this.timers.clear();
129
+ }
130
+ changed() {
131
+ this.o.onChange?.();
132
+ }
133
+ now() {
134
+ return this.o.adapters.clock.now();
135
+ }
136
+ /** The provider the chat's model runs on: the one login that counts. */
137
+ chatProvider() {
138
+ return this.provider;
139
+ }
140
+ /** pi chose another model (at start, or the person's /model). */
141
+ setChatModel(provider) {
142
+ if (provider === undefined || provider === this.provider)
143
+ return;
144
+ this.provider = provider;
145
+ this.changed();
146
+ }
147
+ /** Signed in for the chat's provider: true, false, or null when the auth
148
+ * file could not be read. A login under another provider is not one. */
149
+ signedIn() {
150
+ const l = this.state.login;
151
+ return l.kind === "known" ? l.value.includes(this.provider) : null;
152
+ }
153
+ // ---- one login per box (src/home/loginowner.ts) -------------------------
154
+ /** pi refused the chat for its login (extensions/home.ts reads its error). */
155
+ loginFailed(why) {
156
+ if (this.chatLoginError === why)
157
+ return;
158
+ this.chatLoginError = why;
159
+ this.changed();
160
+ }
161
+ /** pi answered the chat: the login works. */
162
+ chatAnswered() {
163
+ if (this.chatLoginError === null)
164
+ return;
165
+ this.chatLoginError = null;
166
+ this.changed();
167
+ }
168
+ /** The person signed in again: what said the old login failed is past. */
169
+ signedInAgain() {
170
+ this.chatLoginError = null;
171
+ this.reloggedAt = this.now();
172
+ this.changed();
173
+ }
174
+ /** Why the login no longer works ("login expired"), or null while nothing
175
+ * says so. */
176
+ loginExpired() {
177
+ if (this.chatLoginError !== null)
178
+ return "login expired";
179
+ const h = this.state.loginHealth;
180
+ if (h?.kind === "known" &&
181
+ h.value.expired !== undefined &&
182
+ !(this.reloggedAt !== null && (h.value.checkedAt ?? 0) < this.reloggedAt))
183
+ return h.value.expired;
184
+ const a = this.state.autoReview;
185
+ if (a.kind === "known" &&
186
+ a.value.state === "paused-login" &&
187
+ a.value.cause === "login expired" &&
188
+ !(this.reloggedAt !== null && this.now() - this.reloggedAt < RELOGIN_GRACE_MS))
189
+ return "login expired";
190
+ return null;
191
+ }
192
+ /** Who refreshes the login now; null when that is not known. */
193
+ refreshedBy() {
194
+ const h = this.state.loginHealth;
195
+ return h?.kind === "known" ? h.value.refresher : null;
196
+ }
197
+ /** The setup steps a read showed to be needed: the checklist's gaps. */
198
+ gaps() {
199
+ return this.o.gaps?.() ?? [];
200
+ }
201
+ /** Is anything still being read? */
202
+ busy() {
203
+ return (this.inFlight !== null ||
204
+ this.pricing !== null ||
205
+ [...this.runs.keys()].some((pr) => this.isRunning(pr)));
206
+ }
207
+ repo() {
208
+ const r = valueOf(this.state.repo);
209
+ return typeof r === "string" ? r : null;
210
+ }
211
+ /** The lists, or null when the open pull requests are not known. */
212
+ lists() {
213
+ const pulls = valueOf(this.state.pulls);
214
+ if (pulls === undefined)
215
+ return null;
216
+ const sorted = [...pulls].sort(newestFirst);
217
+ if (valueOf(this.state.reviews) === undefined)
218
+ return { toReview: sorted, reviewed: null };
219
+ return {
220
+ toReview: sorted.filter((p) => this.reviewOf(p.number) === undefined),
221
+ reviewed: sorted.flatMap((p) => {
222
+ const review = this.reviewOf(p.number);
223
+ return review ? [{ pull: p, review }] : [];
224
+ }),
225
+ };
226
+ }
227
+ /** Why auto-review's half of the Reviewed list is unknown; null when known. */
228
+ autoUnknown() {
229
+ const a = this.state.autoRounds;
230
+ return a.kind === "unknown" ? a.why : null;
231
+ }
232
+ /** Every listed pull request, in the panel's order: To review, then Reviewed. */
233
+ ordered() {
234
+ const l = this.lists();
235
+ return l ? [...l.toReview, ...(l.reviewed ?? []).map((r) => r.pull)] : [];
236
+ }
237
+ selectedPull() {
238
+ return this.ordered().find((p) => p.number === this.selected);
239
+ }
240
+ /** The latest review of `pr`: this home's finished run, the person's own
241
+ * reviews, or auto-review's, whichever finished last. */
242
+ reviewOf(pr) {
243
+ const run = this.runs.get(pr);
244
+ const mine = run?.stage === "done" ? runReview(run) : undefined;
245
+ return later(later(valueOf(this.state.reviews)?.get(pr), valueOf(this.state.autoRounds)?.get(pr)), mine);
246
+ }
247
+ /** Is a review of `pr` under way (pricing, starting or running)? */
248
+ isRunning(pr) {
249
+ const r = this.runs.get(pr);
250
+ return r !== undefined && r.stage !== "done" && r.stage !== "failed";
251
+ }
252
+ /** The PR's price, for its head: pending while it is being priced, null
253
+ * before it was asked for. */
254
+ quoteOf(p) {
255
+ const e = this.quotes.get(p.number);
256
+ return e !== undefined && e.sha === p.headSha ? e.q : null;
257
+ }
258
+ say(text, kind = "info") {
259
+ this.notice = { text, kind };
260
+ this.changed();
261
+ }
262
+ // ---- the actions ------------------------------------------------------
263
+ /** Read every source again. A refresh already running is joined. */
264
+ refresh() {
265
+ this.inFlight ??= this.detect().finally(() => {
266
+ this.inFlight = null;
267
+ this.changed();
268
+ });
269
+ return this.inFlight;
270
+ }
271
+ async detect() {
272
+ const a = this.o.adapters;
273
+ const land = (key) => (v) => {
274
+ if (this.held.has(key))
275
+ return;
276
+ this.state[key] = v;
277
+ this.changed();
278
+ };
279
+ const side = Promise.all([
280
+ safe(() => a.login.providers()).then(land("login")),
281
+ safe(() => a.github.user()).then(land("github")),
282
+ safe(() => a.autoReview.status()).then(land("autoReview")),
283
+ safe(() => a.memory.status()).then(land("memory")),
284
+ a.autoReview.trigger
285
+ ? safe(() => a.autoReview.trigger()).then(land("trigger"))
286
+ : Promise.resolve(),
287
+ safe(() => a.spend.totals()).then(land("spend")),
288
+ a.loginOwner
289
+ ? safe(() => a.loginOwner.health()).then(land("loginHealth"))
290
+ : Promise.resolve(),
291
+ ]);
292
+ const repo = await safe(() => a.repo.repo());
293
+ land("repo")(repo);
294
+ if (repo.kind === "unknown") {
295
+ const why = `can't read the repository from your qcr config: ${repo.why}`;
296
+ this.state.pulls = unknown(why);
297
+ this.state.reviews = unknown(why);
298
+ this.state.autoRounds = unknown(why);
299
+ }
300
+ else if (repo.value === null) {
301
+ this.state.pulls = unknown("no repository is chosen");
302
+ this.state.reviews = unknown("no repository is chosen");
303
+ this.state.autoRounds = unknown("no repository is chosen");
304
+ }
305
+ else {
306
+ const name = repo.value;
307
+ const [pulls, reviews, autoRounds] = await Promise.all([
308
+ safe(() => a.github.openPulls(name)),
309
+ safe(() => a.reviews.latest(name)),
310
+ safe(() => a.reviews.autoRounds(name)),
311
+ this.attach(name),
312
+ ]);
313
+ this.state.pulls = pulls;
314
+ this.state.reviews = reviews;
315
+ this.state.autoRounds = autoRounds;
316
+ }
317
+ this.keepSelection();
318
+ await side;
319
+ // the checklist never throws (a step's detect that does is a missing)
320
+ await this.o.onRead?.();
321
+ this.greet();
322
+ return this.summary();
323
+ }
324
+ /** The selection survives a refresh while its PR is listed; else the first row. */
325
+ keepSelection() {
326
+ const rows = this.ordered();
327
+ if (!rows.some((p) => p.number === this.selected))
328
+ this.selected = rows[0]?.number ?? null;
329
+ this.rested();
330
+ }
331
+ /** The first notice, once everything has been read. */
332
+ greet() {
333
+ const repo = this.repo();
334
+ if (this.state.pulls.kind === "unknown" && repo !== null) {
335
+ this.pullsWarned = true;
336
+ this.say(`Couldn't read ${repo}'s pull requests: ${this.state.pulls.why}.`, "warn");
337
+ return;
338
+ }
339
+ if (this.pullsWarned) {
340
+ // the list came back: the warning about it no longer holds
341
+ this.pullsWarned = false;
342
+ const open = this.ordered().length;
343
+ this.say(`${open} open pull request${open === 1 ? "" : "s"}.`, "ok");
344
+ return;
345
+ }
346
+ if (this.greeted)
347
+ return;
348
+ this.greeted = true;
349
+ const gaps = this.gaps();
350
+ if (gaps.length > 0) {
351
+ this.say("Not set up yet: the steps above say what is missing.", "warn");
352
+ return;
353
+ }
354
+ const user = nullable(this.state.github);
355
+ const found = [
356
+ this.signedIn() ? `your ${providerLabel(this.provider)} login` : null,
357
+ user ? `GitHub @${user}` : null,
358
+ repo ? `${repo} from your qcr config` : null,
359
+ ].filter((x) => x !== null);
360
+ const open = this.ordered().length;
361
+ this.say(found.length === 3
362
+ ? `Found ${found[0]}, ${found[1]} and ${found[2]}: you're set. ${open} open pull request${open === 1 ? "" : "s"}.`
363
+ : `${open} open pull request${open === 1 ? "" : "s"}.`, "ok");
364
+ }
365
+ summary() {
366
+ const l = this.lists();
367
+ if (l === null) {
368
+ const p = this.state.pulls;
369
+ return p.kind === "unknown" ? `pull requests unknown: ${p.why}` : "still reading";
370
+ }
371
+ return `${l.toReview.length} to review, ${l.reviewed === null ? "reviewed unknown" : `${l.reviewed.length} reviewed`}`;
372
+ }
373
+ /** Move the selection by `by` rows through both lists. */
374
+ move(by) {
375
+ const rows = this.ordered();
376
+ if (rows.length === 0)
377
+ return "there is no pull request to move to";
378
+ const at = rows.findIndex((p) => p.number === this.selected);
379
+ const next = rows[Math.max(0, Math.min(rows.length - 1, (at < 0 ? 0 : at) + Math.trunc(by)))];
380
+ this.selected = next.number;
381
+ this.rested();
382
+ this.changed();
383
+ return `selected #${next.number} ${next.title}`;
384
+ }
385
+ /** Select one listed pull request (a hover or a click does this). */
386
+ select(pr) {
387
+ const p = this.ordered().find((x) => x.number === pr);
388
+ if (!p) {
389
+ const repo = this.repo();
390
+ return `#${pr} is not listed: it is not an open pull request${repo ? ` in ${repo}` : ""}, or the list is not known`;
391
+ }
392
+ if (this.selected !== pr) {
393
+ this.selected = pr;
394
+ this.rested();
395
+ this.changed();
396
+ }
397
+ return `selected #${p.number} ${p.title}`;
398
+ }
399
+ /** Open a pull request (by default the selected one) in the browser. */
400
+ async openPr(pr) {
401
+ const repo = this.repo();
402
+ if (repo === null) {
403
+ const r = this.state.repo;
404
+ return r.kind === "unknown"
405
+ ? `the repository is unknown (${r.why}), so there is nothing to open`
406
+ : "no repository is chosen, so there is nothing to open";
407
+ }
408
+ const n = pr ?? this.selected;
409
+ if (n === null)
410
+ return "no pull request is selected";
411
+ const url = pullUrl(repo, n);
412
+ const r = await this.o.adapters.github
413
+ .openInBrowser(repo, n)
414
+ .catch((err) => ({ ok: false, why: message(err) }));
415
+ if (r.ok) {
416
+ this.say(`Opened #${n} in your browser.`, "ok");
417
+ return `opened ${url} in the browser`;
418
+ }
419
+ this.say(`Couldn't open a browser (${r.why}). The pull request is at ${url}`, "warn");
420
+ return `couldn't open a browser (${r.why}); the pull request is at ${url}`;
421
+ }
422
+ // ---- the review button ------------------------------------------------
423
+ /** A PR's price for the model: null until it is quoted. */
424
+ priceOf(p) {
425
+ const q = this.quoteOf(p);
426
+ if (q === null || q.kind === "pending")
427
+ return null;
428
+ return q.kind === "known"
429
+ ? { estimate_usd: q.value.estimateUsd, cap_usd: q.value.capUsd }
430
+ : { unknown_because: q.why };
431
+ }
432
+ pullOf(pr) {
433
+ return this.ordered().find((p) => p.number === pr);
434
+ }
435
+ /** The cursor came to rest on a PR: price it in a moment, unless its head
436
+ * is priced already. A rest on another PR first cancels this one's. */
437
+ rested() {
438
+ this.pricer?.();
439
+ this.pricer = null;
440
+ const p = this.selectedPull();
441
+ if (this.disposed || !p || this.quoteOf(p) !== null || this.isRunning(p.number))
442
+ return;
443
+ this.pricer = this.after(PRICE_REST_MS, () => {
444
+ this.pricer = null;
445
+ void this.priceSelected();
446
+ });
447
+ }
448
+ /** Give way: the background preview in flight stops, and its PR is left
449
+ * unpriced (the next rest on it prices it again). */
450
+ yieldPricing() {
451
+ const p = this.pricing;
452
+ if (p === null)
453
+ return;
454
+ p.stop.abort();
455
+ this.pricing = null;
456
+ if (this.quotes.get(p.pr)?.q.kind === "pending")
457
+ this.quotes.delete(p.pr);
458
+ }
459
+ /** Price the PR the cursor rests on: one preview at a time. */
460
+ async priceSelected() {
461
+ const repo = this.repo();
462
+ const p = this.selectedPull();
463
+ if (repo === null || !p || this.quoteOf(p) !== null || this.isRunning(p.number))
464
+ return;
465
+ this.yieldPricing();
466
+ const stop = new AbortController();
467
+ this.pricing = { pr: p.number, stop };
468
+ this.quotes.set(p.number, { sha: p.headSha, q: PENDING });
469
+ this.changed();
470
+ const r = await attempt(() => this.o.adapters.reviews.preview(repo, p.number, { signal: stop.signal }));
471
+ if (this.pricing?.stop === stop)
472
+ this.pricing = null;
473
+ if (stop.signal.aborted)
474
+ return;
475
+ this.quotes.set(p.number, { sha: p.headSha, q: r.ok ? known(r.quote) : unknown(r.why) });
476
+ this.changed();
477
+ }
478
+ /** ⏎ (or alt+r): review the selected PR, or show its review when it has
479
+ * one and none is running. */
480
+ enter() {
481
+ const p = this.selectedPull();
482
+ if (p && !this.isRunning(p.number) && this.reviewOf(p.number))
483
+ return Promise.resolve(this.showReview(p.number));
484
+ return this.review();
485
+ }
486
+ /** Refused before anything starts: why the review button cannot act. */
487
+ cannotAct(pr) {
488
+ const repo = this.repo();
489
+ if (repo === null) {
490
+ const r = this.state.repo;
491
+ return r.kind === "unknown"
492
+ ? `the repository is unknown (${r.why}), so there is nothing to review`
493
+ : "no repository is chosen, so there is nothing to review";
494
+ }
495
+ const gaps = this.gaps();
496
+ if (gaps.length > 0)
497
+ return `finish setup first: ${gaps.join(", ")}`;
498
+ const n = pr ?? this.selected;
499
+ if (n === null)
500
+ return "no pull request is selected";
501
+ return (this.pullOf(n) ??
502
+ `#${n} is not listed: it is not an open pull request in ${repo}, or the list is not known`);
503
+ }
504
+ /** Review a pull request (by default the selected one) in the background:
505
+ * preview it, start a detached run with the preview's key, follow it. The
506
+ * run writes its report to a file and never posts by itself. */
507
+ async review(pr) {
508
+ const target = this.cannotAct(pr);
509
+ if (typeof target === "string")
510
+ return target;
511
+ const repo = this.repo();
512
+ const n = target.number;
513
+ const running = this.runs.get(n);
514
+ if (running && this.isRunning(n))
515
+ return `#${n} is already being reviewed${running.runId ? ` (run ${running.runId})` : ""}`;
516
+ const run = plannedRun(n, this.now());
517
+ this.runs.set(n, run);
518
+ this.stopping.delete(n);
519
+ this.selected = n;
520
+ this.pricer?.();
521
+ this.pricer = null;
522
+ this.yieldPricing();
523
+ this.say(`Pricing the review of #${n}…`);
524
+ const fail = (why) => {
525
+ run.stage = "failed";
526
+ run.error = why;
527
+ run.ended = this.now();
528
+ this.say(`#${n} was not reviewed: ${why}. It can be reviewed again.`, "warn");
529
+ return `the review of #${n} did not start: ${why}`;
530
+ };
531
+ const reviews = this.o.adapters.reviews;
532
+ const pre = await attempt(() => reviews.preview(repo, n));
533
+ this.quotes.set(n, { sha: target.headSha, q: pre.ok ? known(pre.quote) : unknown(pre.why) });
534
+ if (!pre.ok)
535
+ return fail(`the preview failed: ${pre.why}`);
536
+ if (this.stopping.delete(n))
537
+ return fail("you stopped it");
538
+ quoteRun(run, pre.quote);
539
+ run.now = "starting the review";
540
+ this.changed();
541
+ const st = await attempt(() => reviews.start(repo, n, pre.quote.key));
542
+ if (!st.ok)
543
+ return fail(st.why);
544
+ run.runId = st.runId;
545
+ run.report = st.report;
546
+ run.now = "fetching the pull request";
547
+ this.say(`Reviewing #${n} in the background — ${priceWords(run.est, run.cap)}. Keep chatting.`);
548
+ if (this.stopping.delete(n))
549
+ void this.stopReview(n);
550
+ this.follow(run);
551
+ return `the review of #${n} started (run ${st.runId}, ${priceWords(run.est, run.cap)}); it writes its report to ${st.report} and posts nothing by itself`;
552
+ }
553
+ /** Poll a run's status until it is over. */
554
+ follow(run) {
555
+ if (this.disposed)
556
+ return;
557
+ const cancel = this.after(STATUS_POLL_MS, () => {
558
+ this.timers.delete(cancel);
559
+ void this.poll(run);
560
+ });
561
+ this.timers.add(cancel);
562
+ }
563
+ async poll(run) {
564
+ if (this.disposed || run.runId === undefined)
565
+ return;
566
+ const id = run.runId;
567
+ const s = await safe(() => this.o.adapters.reviews.status(id));
568
+ if (this.disposed || this.runs.get(run.pr) !== run)
569
+ return;
570
+ if (s.kind === "unknown") {
571
+ run.now = `can't read its status: ${s.why}`;
572
+ this.changed();
573
+ return this.follow(run);
574
+ }
575
+ const over = foldStatus(run, s.value, this.now());
576
+ this.changed();
577
+ if (!over)
578
+ return this.follow(run);
579
+ await this.finished(run);
580
+ }
581
+ /** A run this home followed is over: say how, then the posting rule. */
582
+ async finished(run) {
583
+ const n = run.pr;
584
+ if (run.stage === "failed") {
585
+ this.say(`#${n} was not reviewed: ${run.error ?? "unknown"}. It can be reviewed again.`, "warn");
586
+ return;
587
+ }
588
+ const high = run.findings.filter((f) => f.sev === "high").length;
589
+ this.selected = n;
590
+ this.say(`#${n} reviewed — ${plural(run.findings.length, "finding")}${high ? `, ${high} high` : ""}. ${"⏎"} shows them.`, "ok");
591
+ await this.offerPost(run);
592
+ }
593
+ /** After a finished review the person's posting rule applies: never, always,
594
+ * or ask with "No, keep it here" first. Nobody to ask is a no. */
595
+ async offerPost(run) {
596
+ const file = run.postFile;
597
+ const repo = this.repo();
598
+ if (file === undefined || repo === null)
599
+ return;
600
+ const rule = this.o.postingRule?.() ?? "ask";
601
+ const n = run.pr;
602
+ if (rule === "never")
603
+ return;
604
+ if (rule === "always") {
605
+ this.say(`Posting the review of #${n} to GitHub, as your posting rule says…`);
606
+ return this.postNow(n, file);
607
+ }
608
+ const ask = this.o.ask;
609
+ if (ask === undefined)
610
+ return;
611
+ // the review first, so the question is about something the person has read
612
+ this.showReview(n);
613
+ const dry = await safe(() => this.o.adapters.reviews.dryRun());
614
+ const yes = dry.kind === "known"
615
+ ? postAnswer(dry.value)
616
+ : "Yes, post it as a comment-only review (your install's dry_run decides whether it is sent)";
617
+ let picked;
618
+ try {
619
+ picked = await ask(`Post the review of #${n} (${repo}) on GitHub as a comment-only review?`, [
620
+ KEEP_IT_HERE,
621
+ yes,
622
+ ]);
623
+ }
624
+ catch {
625
+ picked = undefined;
626
+ }
627
+ if (picked !== yes) {
628
+ this.say(`The review of #${n} stays here: nothing was posted.`);
629
+ return;
630
+ }
631
+ await this.postNow(n, file);
632
+ }
633
+ async postNow(n, file) {
634
+ const r = await attempt(() => this.o.adapters.reviews.post(file));
635
+ this.say(r.ok ? r.line : `The review of #${n} was not posted: ${r.why}`, r.ok ? "ok" : "warn");
636
+ }
637
+ /** Put a reviewed PR's findings in the chat as a message. */
638
+ showReview(pr) {
639
+ const n = pr ?? this.selected;
640
+ if (n === null)
641
+ return "no pull request is selected";
642
+ const review = this.reviewOf(n);
643
+ if (!review)
644
+ return `#${n} has no review yet`;
645
+ const title = this.pullOf(n)?.title ?? "";
646
+ const content = reviewMessage({ pr: n, title, review });
647
+ if (!this.o.send)
648
+ return content;
649
+ this.o.send({
650
+ content,
651
+ details: { repo: this.repo(), pr: n, by: review.by, findings: review.list ?? null },
652
+ });
653
+ return `the review of #${n} is in the chat`;
654
+ }
655
+ /** Stop the review of a pull request (by default the selected one). The PR
656
+ * is reviewable again once its status says it stopped. */
657
+ async stopReview(pr) {
658
+ const n = pr ?? this.selected;
659
+ if (n === null)
660
+ return "no pull request is selected";
661
+ const run = this.runs.get(n);
662
+ if (!run || !this.isRunning(n))
663
+ return `no review of #${n} is running`;
664
+ if (run.runId === undefined) {
665
+ // still pricing or starting: it stops as soon as it has a run
666
+ this.stopping.add(n);
667
+ this.say(`Stopping the review of #${n}…`);
668
+ return `the review of #${n} stops as soon as it starts`;
669
+ }
670
+ const r = await attempt(() => this.o.adapters.reviews.stop(run.runId));
671
+ if (!r.ok) {
672
+ this.say(`Couldn't stop the review of #${n}: ${r.why}`, "warn");
673
+ return `couldn't stop the review of #${n}: ${r.why}`;
674
+ }
675
+ this.say(`Stopping the review of #${n}…`);
676
+ return `stopped the review of #${n} (run ${run.runId})`;
677
+ }
678
+ /** On opening (and on a new repository): the registry's runs of `repo`.
679
+ * A running one is followed, a finished or failed one shown; nothing is
680
+ * asked about a run that ended while the home was closed. */
681
+ async attach(repo) {
682
+ if (this.attachedTo === repo)
683
+ return;
684
+ this.attachedTo = repo;
685
+ const r = await safe(() => this.o.adapters.reviews.runs(repo));
686
+ if (r.kind === "unknown" || this.disposed)
687
+ return;
688
+ const latest = new Map();
689
+ for (const s of r.value)
690
+ if (s.pr !== null && s.repo === repo)
691
+ latest.set(s.pr, s);
692
+ for (const [pr, s] of latest) {
693
+ if (this.runs.has(pr))
694
+ continue;
695
+ const run = attachedRun(s);
696
+ foldStatus(run, s, this.now());
697
+ this.runs.set(pr, run);
698
+ if (s.state === "running")
699
+ this.follow(run);
700
+ }
701
+ this.changed();
702
+ }
703
+ // ---- the switches (src/home/switches.ts) ------------------------------
704
+ /** Can the home switch auto-review / memory, or only show them? */
705
+ canSwitch(what) {
706
+ return this.o.adapters[what].control !== undefined;
707
+ }
708
+ /** Memory's switch, when the home has one. */
709
+ memoryControl() {
710
+ return this.o.adapters.memory.control;
711
+ }
712
+ /** The trigger label, when known. */
713
+ trigger() {
714
+ const t = this.state.trigger;
715
+ return t?.kind === "known" ? t.value : null;
716
+ }
717
+ switchHost() {
718
+ const hold = (slot, v) => {
719
+ this.state[slot] = v;
720
+ this.held.add(slot);
721
+ this.changed();
722
+ };
723
+ return {
724
+ autoReview: () => this.state.autoReview,
725
+ memory: () => this.state.memory,
726
+ trigger: () => this.trigger(),
727
+ repo: () => this.repo(),
728
+ selected: () => this.selected,
729
+ say: (text, kind) => this.say(text, kind),
730
+ hold,
731
+ release: (slot) => {
732
+ this.held.delete(slot);
733
+ },
734
+ refresh: () => this.refresh(),
735
+ };
736
+ }
737
+ /** Switch auto-review on or off: every question asked through `ask`. */
738
+ switchAutoReview(ask) {
739
+ return switchAutoReview(this.switchHost(), this.o.adapters, ask);
740
+ }
741
+ /** Switch memory on or off: its own switch, auto-review untouched. */
742
+ switchMemory(ask) {
743
+ return switchMemory(this.switchHost(), this.o.adapters, ask);
744
+ }
745
+ /** The login's owner and health, for the model: null when unknown. */
746
+ loginOwnerStatus(h) {
747
+ const expired = this.loginExpired();
748
+ return {
749
+ refreshed_by: this.refreshedBy(),
750
+ expired: expired !== null ? true : h.kind === "known" ? false : null,
751
+ ...(h.kind === "unknown" ? { refresher_unknown_because: h.why } : {}),
752
+ };
753
+ }
754
+ /** What the panel shows, for the model: an unknown source is null, never
755
+ * absent and never zero. */
756
+ status() {
757
+ const l = this.lists();
758
+ const why = (s) => (s.kind === "unknown" ? s.why : null);
759
+ const auto = this.state.autoReview;
760
+ return {
761
+ repo: nullable(this.state.repo),
762
+ login: {
763
+ provider: this.provider,
764
+ signed_in: this.signedIn(),
765
+ ...(why(this.state.login) ? { unknown_because: why(this.state.login) } : {}),
766
+ ...(this.state.loginHealth ? this.loginOwnerStatus(this.state.loginHealth) : {}),
767
+ },
768
+ github: {
769
+ user: nullable(this.state.github),
770
+ signed_in: this.state.github.kind === "known" ? this.state.github.value !== null : null,
771
+ },
772
+ tools: this.tools,
773
+ setup_needed: this.gaps(),
774
+ auto_review: auto.kind === "known" ? auto.value : null,
775
+ ...(auto.kind === "unknown" ? { auto_review_unknown_because: auto.why } : {}),
776
+ ...(this.state.trigger
777
+ ? { auto_review_picks_up: { trigger_label: this.trigger(), review_requests: true } }
778
+ : {}),
779
+ memory: nullable(this.state.memory),
780
+ spend: nullable(this.state.spend),
781
+ to_review: l ? l.toReview.map((p) => ({ ...row(p), price: this.priceOf(p) })) : null,
782
+ reviewed: l?.reviewed ? l.reviewed.map((r) => ({ ...row(r.pull), review: r.review })) : null,
783
+ ...(l?.reviewed && this.autoUnknown() !== null
784
+ ? { auto_reviews_unknown_because: this.autoUnknown() }
785
+ : {}),
786
+ reviewing: [...this.runs.values()]
787
+ .filter((r) => this.isRunning(r.pr))
788
+ .map((r) => ({
789
+ number: r.pr,
790
+ run_id: r.runId ?? null,
791
+ stage: r.stage,
792
+ estimate_usd: r.est,
793
+ cap_usd: r.cap,
794
+ lenses: r.lenses.map((x) => ({ name: x.name, state: x.state, tools: x.tools })),
795
+ })),
796
+ ...(why(this.state.pulls) ? { pull_requests_unknown_because: why(this.state.pulls) } : {}),
797
+ selected: this.selected,
798
+ };
799
+ }
800
+ }
801
+ function row(p) {
802
+ return {
803
+ number: p.number,
804
+ title: p.title,
805
+ author: p.author,
806
+ updated_at: p.updatedAt,
807
+ draft: p.isDraft,
808
+ branch: p.headRefName,
809
+ };
810
+ }
811
+ //# sourceMappingURL=model.js.map