@quickchatai/quickreview 1.2.0 → 1.3.2

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 (350) hide show
  1. package/README.md +116 -53
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +25 -7
  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/selfimprove.js +18 -0
  11. package/dist/extensions/cli/selfimprove.js.map +1 -0
  12. package/dist/extensions/home.js +350 -0
  13. package/dist/extensions/home.js.map +1 -0
  14. package/dist/extensions/login-route.js +1 -1
  15. package/dist/extensions/login-route.js.map +1 -1
  16. package/dist/extensions/onboarding.js +8 -7
  17. package/dist/extensions/onboarding.js.map +1 -1
  18. package/dist/extensions/selfimprove.js +49 -0
  19. package/dist/extensions/selfimprove.js.map +1 -0
  20. package/dist/extensions/setup-scan.js +23 -592
  21. package/dist/extensions/setup-scan.js.map +1 -1
  22. package/dist/extensions/setup.js +2 -47
  23. package/dist/extensions/setup.js.map +1 -1
  24. package/dist/extensions/vendor/openviking-pi/index.js +4 -0
  25. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -1
  26. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +3 -1
  27. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -1
  28. package/dist/extensions/vendor/openviking-pi/sync.js +81 -0
  29. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -1
  30. package/dist/src/archive/restore.js +18 -15
  31. package/dist/src/archive/restore.js.map +1 -1
  32. package/dist/src/botworker.js +13 -2
  33. package/dist/src/botworker.js.map +1 -1
  34. package/dist/src/channelfs.js +3 -26
  35. package/dist/src/channelfs.js.map +1 -1
  36. package/dist/src/cli.js +14 -50
  37. package/dist/src/cli.js.map +1 -1
  38. package/dist/src/config.js +16 -0
  39. package/dist/src/config.js.map +1 -1
  40. package/dist/src/daemon.js +33 -4
  41. package/dist/src/daemon.js.map +1 -1
  42. package/dist/src/debuground.js +1 -0
  43. package/dist/src/debuground.js.map +1 -1
  44. package/dist/src/debugroundpi.js +1 -0
  45. package/dist/src/debugroundpi.js.map +1 -1
  46. package/dist/src/engine/caserunner-sdk.js +15 -251
  47. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  48. package/dist/src/home/adapters.js +349 -0
  49. package/dist/src/home/adapters.js.map +1 -0
  50. package/dist/src/home/advanced.js +318 -0
  51. package/dist/src/home/advanced.js.map +1 -0
  52. package/dist/src/home/advancedtools.js +57 -0
  53. package/dist/src/home/advancedtools.js.map +1 -0
  54. package/dist/src/home/autoreview.js +325 -0
  55. package/dist/src/home/autoreview.js.map +1 -0
  56. package/dist/src/home/daemonstatus.js +32 -0
  57. package/dist/src/home/daemonstatus.js.map +1 -0
  58. package/dist/src/home/devicelogin.js +61 -0
  59. package/dist/src/home/devicelogin.js.map +1 -0
  60. package/dist/src/home/launch.js +202 -0
  61. package/dist/src/home/launch.js.map +1 -0
  62. package/dist/src/home/loginowner.js +205 -0
  63. package/dist/src/home/loginowner.js.map +1 -0
  64. package/dist/src/home/memory.js +122 -0
  65. package/dist/src/home/memory.js.map +1 -0
  66. package/dist/src/home/model.js +811 -0
  67. package/dist/src/home/model.js.map +1 -0
  68. package/dist/src/home/panel.js +520 -0
  69. package/dist/src/home/panel.js.map +1 -0
  70. package/dist/src/home/ports.js +19 -0
  71. package/dist/src/home/ports.js.map +1 -0
  72. package/dist/src/home/review.js +163 -0
  73. package/dist/src/home/review.js.map +1 -0
  74. package/dist/src/home/reviews.js +478 -0
  75. package/dist/src/home/reviews.js.map +1 -0
  76. package/dist/src/home/setup.js +507 -0
  77. package/dist/src/home/setup.js.map +1 -0
  78. package/dist/src/home/setupadapters.js +214 -0
  79. package/dist/src/home/setupadapters.js.map +1 -0
  80. package/dist/src/home/setupcard.js +199 -0
  81. package/dist/src/home/setupcard.js.map +1 -0
  82. package/dist/src/home/setuptools.js +45 -0
  83. package/dist/src/home/setuptools.js.map +1 -0
  84. package/dist/src/home/spend.js +160 -0
  85. package/dist/src/home/spend.js.map +1 -0
  86. package/dist/src/home/switches.js +209 -0
  87. package/dist/src/home/switches.js.map +1 -0
  88. package/dist/src/home/switchon.js +439 -0
  89. package/dist/src/home/switchon.js.map +1 -0
  90. package/dist/src/home/toolnames.js +41 -0
  91. package/dist/src/home/toolnames.js.map +1 -0
  92. package/dist/src/home/tune.js +341 -0
  93. package/dist/src/home/tune.js.map +1 -0
  94. package/dist/src/home/words.js +7 -0
  95. package/dist/src/home/words.js.map +1 -0
  96. package/dist/src/homes.js +14 -0
  97. package/dist/src/homes.js.map +1 -1
  98. package/dist/src/install.js +1 -1
  99. package/dist/src/install.js.map +1 -1
  100. package/dist/src/ipc/workerspec.js +8 -0
  101. package/dist/src/ipc/workerspec.js.map +1 -1
  102. package/dist/src/ledgercheck.js +39 -10
  103. package/dist/src/ledgercheck.js.map +1 -1
  104. package/dist/src/live/publish.js +1 -1
  105. package/dist/src/live/publish.js.map +1 -1
  106. package/dist/src/livecli.js +1 -1
  107. package/dist/src/livecli.js.map +1 -1
  108. package/dist/src/loginrefresh/handover.js +121 -0
  109. package/dist/src/loginrefresh/handover.js.map +1 -0
  110. package/dist/src/loginrefresh/lock.js +54 -3
  111. package/dist/src/loginrefresh/lock.js.map +1 -1
  112. package/dist/src/loginrefresh/refresher.js +33 -5
  113. package/dist/src/loginrefresh/refresher.js.map +1 -1
  114. package/dist/src/loginrefresh/supervisor.js +18 -1
  115. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  116. package/dist/src/npmverbs.js +42 -12
  117. package/dist/src/npmverbs.js.map +1 -1
  118. package/dist/src/onboard/choices.js +6 -3
  119. package/dist/src/onboard/choices.js.map +1 -1
  120. package/dist/src/onboard/engine.js +33 -292
  121. package/dist/src/onboard/engine.js.map +1 -1
  122. package/dist/src/onboard/steps/bringup.js +3 -2
  123. package/dist/src/onboard/steps/bringup.js.map +1 -1
  124. package/dist/src/onboard/steps/configfill.js +18 -7
  125. package/dist/src/onboard/steps/configfill.js.map +1 -1
  126. package/dist/src/onboard/steps/loginshare.js +213 -0
  127. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  128. package/dist/src/onboard/steps/memory.js +14 -2
  129. package/dist/src/onboard/steps/memory.js.map +1 -1
  130. package/dist/src/onboard/steps/repoconfig.js +72 -0
  131. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  132. package/dist/src/onboard/toolnames.js +2 -2
  133. package/dist/src/onboard/toolnames.js.map +1 -1
  134. package/dist/src/piagentdir.js +13 -0
  135. package/dist/src/piagentdir.js.map +1 -0
  136. package/dist/src/pilogin.js +31 -16
  137. package/dist/src/pilogin.js.map +1 -1
  138. package/dist/src/qcrbin.js +16 -11
  139. package/dist/src/qcrbin.js.map +1 -1
  140. package/dist/src/reviewargs.js +122 -0
  141. package/dist/src/reviewargs.js.map +1 -0
  142. package/dist/src/reviewcli.js +7 -161
  143. package/dist/src/reviewcli.js.map +1 -1
  144. package/dist/src/reviewdetach.js +76 -11
  145. package/dist/src/reviewdetach.js.map +1 -1
  146. package/dist/src/scancli.js +39 -691
  147. package/dist/src/scancli.js.map +1 -1
  148. package/dist/src/selfimprove.js +66 -0
  149. package/dist/src/selfimprove.js.map +1 -0
  150. package/dist/src/session-command-supervisor.js +38 -9
  151. package/dist/src/session-command-supervisor.js.map +1 -1
  152. package/dist/src/setup/adapters.js +5 -223
  153. package/dist/src/setup/adapters.js.map +1 -1
  154. package/dist/src/setup/caserunner.js +15 -249
  155. package/dist/src/setup/caserunner.js.map +1 -1
  156. package/dist/src/setup/engine.js +32 -341
  157. package/dist/src/setup/engine.js.map +1 -1
  158. package/dist/src/setup/facts.js +3 -592
  159. package/dist/src/setup/facts.js.map +1 -1
  160. package/dist/src/setup/findings.js +3 -947
  161. package/dist/src/setup/findings.js.map +1 -1
  162. package/dist/src/setup/presentation.js +3 -8
  163. package/dist/src/setup/presentation.js.map +1 -1
  164. package/dist/src/setup/scan.js +3 -724
  165. package/dist/src/setup/scan.js.map +1 -1
  166. package/dist/src/setup/scanbuild.js +9 -92
  167. package/dist/src/setup/scanbuild.js.map +1 -1
  168. package/dist/src/setup/scanlease.js +3 -250
  169. package/dist/src/setup/scanlease.js.map +1 -1
  170. package/dist/src/setup/scanreport.js +3 -288
  171. package/dist/src/setup/scanreport.js.map +1 -1
  172. package/dist/src/setup/scanrun.js +3 -1732
  173. package/dist/src/setup/scanrun.js.map +1 -1
  174. package/dist/src/setup/scantree.js +3 -256
  175. package/dist/src/setup/scantree.js.map +1 -1
  176. package/dist/src/setup/stage2request.js +3 -258
  177. package/dist/src/setup/stage2request.js.map +1 -1
  178. package/dist/src/setup/unitcut.js +3 -436
  179. package/dist/src/setup/unitcut.js.map +1 -1
  180. package/dist/src/state.js +75 -9
  181. package/dist/src/state.js.map +1 -1
  182. package/dist/src/takeover.js +1 -0
  183. package/dist/src/takeover.js.map +1 -1
  184. package/dist/src/takeoverpi.js +2 -1
  185. package/dist/src/takeoverpi.js.map +1 -1
  186. package/dist/src/tui/alerts.js +4 -1
  187. package/dist/src/tui/alerts.js.map +1 -1
  188. package/dist/src/tui/board.js +2 -2
  189. package/dist/src/tui/board.js.map +1 -1
  190. package/dist/src/tui/companion.js +2 -1
  191. package/dist/src/tui/companion.js.map +1 -1
  192. package/dist/src/tui/deploytab.js +2 -2
  193. package/dist/src/tui/deploytab.js.map +1 -1
  194. package/dist/src/tui/index.js +9 -6
  195. package/dist/src/tui/index.js.map +1 -1
  196. package/dist/src/tui/memory.js +3 -16
  197. package/dist/src/tui/memory.js.map +1 -1
  198. package/dist/src/tui/memoryurl.js +22 -0
  199. package/dist/src/tui/memoryurl.js.map +1 -0
  200. package/dist/src/tui/remote.js +1 -1
  201. package/dist/src/tui/remote.js.map +1 -1
  202. package/dist/src/tui/route.js +2 -2
  203. package/dist/src/tui/route.js.map +1 -1
  204. package/dist/src/tui/setupstate.js +8 -5
  205. package/dist/src/tui/setupstate.js.map +1 -1
  206. package/dist/src/tui/sshtriage.js +1 -1
  207. package/dist/src/tui/sshtriage.js.map +1 -1
  208. package/dist/src/verdicts.js +12 -0
  209. package/dist/src/verdicts.js.map +1 -1
  210. package/dist/src/wire.js +9 -4
  211. package/dist/src/wire.js.map +1 -1
  212. package/dist/src/workerbreaker.js +32 -5
  213. package/dist/src/workerbreaker.js.map +1 -1
  214. package/dist/src/workerworld.js +4 -0
  215. package/dist/src/workerworld.js.map +1 -1
  216. package/docker/README.md +1 -1
  217. package/docker/entrypoint.sh +40 -6
  218. package/docker-compose.yml +16 -1
  219. package/extensions/cli/home.ts +14 -0
  220. package/extensions/cli/selfimprove.ts +18 -0
  221. package/extensions/home.ts +484 -0
  222. package/extensions/login-route.ts +1 -1
  223. package/extensions/onboarding.ts +8 -7
  224. package/extensions/selfimprove.ts +65 -0
  225. package/extensions/setup-scan.ts +79 -766
  226. package/extensions/setup.ts +14 -70
  227. package/extensions/vendor/openviking-pi/NOTICE +5 -0
  228. package/extensions/vendor/openviking-pi/README.md +39 -5
  229. package/extensions/vendor/openviking-pi/index.ts +4 -0
  230. package/extensions/vendor/openviking-pi/patches/0014-one-turn-sync-in-flight.patch +239 -0
  231. package/extensions/vendor/openviking-pi/qcr-hooks.ts +3 -1
  232. package/extensions/vendor/openviking-pi/sync.ts +80 -0
  233. package/extensions/vendor/openviking-pi/upstream.json +2 -1
  234. package/package.json +6 -4
  235. package/prompts/onboarding.md +8 -7
  236. package/schemas/review-result.v1.json +60 -0
  237. package/scripts/deploy.sh +8 -6
  238. package/scripts/qcr-host.sh +28 -5
  239. package/scripts/vm-ghcr-login.sh +7 -4
  240. package/src/archive/restore.ts +18 -21
  241. package/src/botworker.ts +13 -2
  242. package/src/channelfs.ts +3 -26
  243. package/src/cli.ts +13 -54
  244. package/src/config.ts +38 -0
  245. package/src/daemon.ts +34 -4
  246. package/src/debuground.ts +3 -0
  247. package/src/debugroundpi.ts +1 -0
  248. package/src/engine/caserunner-sdk.ts +29 -314
  249. package/src/home/adapters.ts +418 -0
  250. package/src/home/advanced.ts +421 -0
  251. package/src/home/advancedtools.ts +96 -0
  252. package/src/home/autoreview.ts +385 -0
  253. package/src/home/daemonstatus.ts +55 -0
  254. package/src/home/devicelogin.ts +107 -0
  255. package/src/home/launch.ts +257 -0
  256. package/src/home/loginowner.ts +248 -0
  257. package/src/home/memory.ts +176 -0
  258. package/src/home/model.ts +965 -0
  259. package/src/home/panel.ts +666 -0
  260. package/src/home/ports.ts +338 -0
  261. package/src/home/review.ts +206 -0
  262. package/src/home/reviews.ts +565 -0
  263. package/src/home/setup.ts +689 -0
  264. package/src/home/setupadapters.ts +217 -0
  265. package/src/home/setupcard.ts +288 -0
  266. package/src/home/setuptools.ts +57 -0
  267. package/src/home/spend.ts +194 -0
  268. package/src/home/switches.ts +293 -0
  269. package/src/home/switchon.ts +554 -0
  270. package/src/home/toolnames.ts +46 -0
  271. package/src/home/tune.ts +405 -0
  272. package/src/home/words.ts +8 -0
  273. package/src/homes.ts +17 -0
  274. package/src/install.ts +1 -1
  275. package/src/ipc/workerspec.ts +15 -0
  276. package/src/ledgercheck.ts +45 -9
  277. package/src/live/publish.ts +4 -1
  278. package/src/livecli.ts +6 -1
  279. package/src/loginrefresh/handover.ts +138 -0
  280. package/src/loginrefresh/lock.ts +72 -3
  281. package/src/loginrefresh/refresher.ts +34 -5
  282. package/src/loginrefresh/supervisor.ts +25 -2
  283. package/src/npmverbs.ts +51 -11
  284. package/src/onboard/choices.ts +6 -3
  285. package/src/onboard/engine.ts +64 -434
  286. package/src/onboard/steps/bringup.ts +3 -2
  287. package/src/onboard/steps/configfill.ts +23 -7
  288. package/src/onboard/steps/loginshare.ts +258 -0
  289. package/src/onboard/steps/memory.ts +18 -2
  290. package/src/onboard/steps/repoconfig.ts +85 -0
  291. package/src/onboard/toolnames.ts +2 -2
  292. package/src/piagentdir.ts +12 -0
  293. package/src/pilogin.ts +32 -16
  294. package/src/qcrbin.ts +16 -11
  295. package/src/reviewargs.ts +119 -0
  296. package/src/reviewcli.ts +6 -167
  297. package/src/reviewdetach.ts +107 -13
  298. package/src/scancli.ts +75 -826
  299. package/src/selfimprove.ts +90 -0
  300. package/src/session-command-supervisor.ts +41 -8
  301. package/src/setup/adapters.ts +13 -221
  302. package/src/setup/caserunner.ts +34 -273
  303. package/src/setup/engine.ts +62 -496
  304. package/src/setup/facts.ts +3 -627
  305. package/src/setup/findings.ts +3 -1264
  306. package/src/setup/presentation.ts +3 -18
  307. package/src/setup/scan.ts +3 -1033
  308. package/src/setup/scanbuild.ts +20 -114
  309. package/src/setup/scanlease.ts +3 -297
  310. package/src/setup/scanreport.ts +3 -373
  311. package/src/setup/scanrun.ts +3 -2268
  312. package/src/setup/scantree.ts +3 -259
  313. package/src/setup/stage2request.ts +3 -347
  314. package/src/setup/unitcut.ts +3 -523
  315. package/src/state.ts +126 -11
  316. package/src/takeover.ts +5 -0
  317. package/src/takeoverpi.ts +3 -2
  318. package/src/tui/alerts.ts +9 -2
  319. package/src/tui/board.ts +2 -2
  320. package/src/tui/companion.ts +2 -1
  321. package/src/tui/deploytab.ts +2 -2
  322. package/src/tui/index.ts +9 -6
  323. package/src/tui/memory.ts +3 -15
  324. package/src/tui/memoryurl.ts +22 -0
  325. package/src/tui/remote.ts +1 -1
  326. package/src/tui/route.ts +2 -2
  327. package/src/tui/setupstate.ts +20 -16
  328. package/src/tui/sshtriage.ts +1 -1
  329. package/src/verdicts.ts +13 -0
  330. package/src/wire.ts +9 -4
  331. package/src/workerbreaker.ts +41 -5
  332. package/src/workerworld.ts +9 -1
  333. package/dist/src/live/projection.js +0 -450
  334. package/dist/src/live/projection.js.map +0 -1
  335. package/dist/src/live/read.js +0 -60
  336. package/dist/src/live/read.js.map +0 -1
  337. package/dist/src/onboard/launch.js +0 -75
  338. package/dist/src/onboard/launch.js.map +0 -1
  339. package/dist/src/setup/ledger.js +0 -207
  340. package/dist/src/setup/ledger.js.map +0 -1
  341. package/dist/src/setup/stage2.js +0 -617
  342. package/dist/src/setup/stage2.js.map +0 -1
  343. package/dist/src/setup/textsniff.js +0 -89
  344. package/dist/src/setup/textsniff.js.map +0 -1
  345. package/src/live/projection.ts +0 -491
  346. package/src/live/read.ts +0 -86
  347. package/src/onboard/launch.ts +0 -103
  348. package/src/setup/ledger.ts +0 -324
  349. package/src/setup/stage2.ts +0 -926
  350. package/src/setup/textsniff.ts +0 -95
@@ -15,49 +15,38 @@ import {
15
15
  existsSync,
16
16
  mkdirSync,
17
17
  mkdtempSync,
18
- readdirSync,
19
18
  readFileSync,
20
19
  realpathSync,
21
20
  rmSync,
22
- statSync,
23
21
  writeFileSync,
24
22
  } from "node:fs";
25
23
  import { homedir, tmpdir } from "node:os";
26
- import { dirname, join, posix, resolve } from "node:path";
27
- import { fileURLToPath } from "node:url";
28
- import { GITHUB_TOKEN_ENVS } from "@quickchatai/pi-quickreview/src/envstrip.js";
24
+ import { dirname, join, resolve } from "node:path";
29
25
  import { PI_PACKAGE, piCliPath } from "../picli.js";
26
+ import {
27
+ STDERR_TAIL_CHARS,
28
+ TRANSCRIPT_RUNS_KEPT,
29
+ caseReadout as readoutIn,
30
+ leafEnv as leafEnvOf,
31
+ parseAgentFile,
32
+ pruneTranscripts,
33
+ resolvedReadPath as readPathIn,
34
+ stderrExcerpt,
35
+ transcriptFile,
36
+ type AgentFile,
37
+ } from "@quickchatai/pi-quickreview/src/scan/caseread.js";
30
38
  import type { CaseOutcome, CaseRunner, CaseSpec } from "./engine.js";
31
39
 
32
- /** An agent file of the companion set: `name`/`tools`/`model` frontmatter
33
- * and the system prompt below it — the native subagent loader's format. */
34
- export interface AgentFile {
35
- name: string;
36
- tools: string[];
37
- model: string | null;
38
- systemPrompt: string;
39
- }
40
-
41
- export function parseAgentFile(text: string): AgentFile | null {
42
- const m = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text.replace(/\r\n/g, "\n"));
43
- if (!m) return null;
44
- const fields: Record<string, string> = {};
45
- for (const line of m[1]!.split("\n")) {
46
- const kv = /^([A-Za-z_][\w-]*):\s*(.*)$/.exec(line);
47
- if (kv) fields[kv[1]!] = kv[2]!.trim();
48
- }
49
- if (!fields.name) return null;
50
- return {
51
- name: fields.name,
52
- tools: (fields.tools ?? "")
53
- .replace(/^\[|\]$/g, "")
54
- .split(",")
55
- .map((t) => t.trim())
56
- .filter(Boolean),
57
- model: fields.model || null,
58
- systemPrompt: m[2]!.trim(),
59
- };
60
- }
40
+ export { parseAgentFile, type AgentFile } from "@quickchatai/pi-quickreview/src/scan/caseread.js";
41
+ export {
42
+ FILE_READ_TOOLS,
43
+ TRANSCRIPT_RUNS_KEPT,
44
+ pruneTranscripts,
45
+ readPathOf,
46
+ readSucceeded,
47
+ stderrExcerpt,
48
+ transcriptFile,
49
+ } from "@quickchatai/pi-quickreview/src/scan/caseread.js";
61
50
 
62
51
  /** The spawn this runner needs — `child_process.spawn`'s shape, narrowed so
63
52
  * tests hand in a fake child. */
@@ -137,254 +126,26 @@ export function piInvocation(
137
126
  return { command: host.execPath, prefix: [(host.piCli ?? piCliPath)()] };
138
127
  }
139
128
 
140
- /** The tools whose `path` argument names content the model read. `find` and
141
- * `ls` list names and read no content, so they are not reads. */
142
- export const FILE_READ_TOOLS: readonly string[] = ["read", "grep"];
143
-
144
- /** The path a `tool_execution_start` event says the model asked to read, or
145
- * null. An attempt, not a read: `piCaseRunner` credits it only once the
146
- * call's `tool_execution_end` reports success (`readSucceeded`).
147
- * VERIFY-ON-PIN (read from 0.99.1's dist: the JSON mode passes agent events
148
- * through verbatim — `tool_execution_start` carries `toolName` and `args`;
149
- * `read` and `grep` both take `path`): the event and argument spelling. */
150
- export function readPathOf(event: {
151
- type?: string;
152
- toolName?: unknown;
153
- args?: unknown;
154
- }): string | null {
155
- if (event.type !== "tool_execution_start" || typeof event.toolName !== "string") return null;
156
- if (!FILE_READ_TOOLS.includes(event.toolName)) return null;
157
- const path = (event.args as { path?: unknown } | null | undefined)?.path;
158
- return typeof path === "string" && path.trim() !== "" ? path : null;
159
- }
160
-
161
- /** The id of a `tool_execution_end` that finished WITHOUT an error, or null.
162
- * A failed read (a missing file, an offset past its end) or a grep that
163
- * could not run read nothing, so its start credits no file.
164
- * VERIFY-ON-PIN (0.99.1's agent-core `tool_execution_end` carries
165
- * `toolCallId` and `isError`; the same pair is in the JSON-mode transcripts
166
- * kept by a scan at 1.0.0): the event spelling. */
167
- export function readSucceeded(event: {
168
- type?: string;
169
- toolCallId?: unknown;
170
- isError?: unknown;
171
- }): string | null {
172
- if (event.type !== "tool_execution_end" || typeof event.toolCallId !== "string") return null;
173
- return event.isError === false ? event.toolCallId : null;
174
- }
175
-
176
- /** The file a read of `raw` opened, for a child running in `cwd` — pi's own
177
- * resolution (`resolveToCwd`): one leading `@` stripped, `~` expanded, a
178
- * relative path taken against the child's cwd, `.`/`..` collapsed. The scan
179
- * judges what the lens opened, not how it spelled it: the child runs in the
180
- * app root, not the scanned tree, so a relative `api/x.py` opened the app
181
- * root's copy — another repository, or this checkout at HEAD — and must not
182
- * pass for a read of the tree.
183
- * VERIFY-ON-PIN (read from 0.99.1's dist/core/tools/path-utils.js
184
- * `resolveToCwd` → utils/paths `resolvePath` with `stripAtPrefix` and
185
- * `normalizeUnicodeSpaces`, `file://` URLs included): the macOS
186
- * filename variants its `resolveReadPath` also tries are not modelled — they
187
- * only differ for a path that does not exist as spelled. */
129
+ /** The file a read of `raw` opened, for a child running in `cwd`
130
+ * (caseread.ts `resolvedReadPath`), `~` taken as this process's home unless
131
+ * told otherwise. */
188
132
  export function resolvedReadPath(raw: string, cwd: string, home: string = homedir()): string {
189
- // pi's `normalizePath` order: unicode spaces → ASCII space, one `@`
190
- // stripped, `~` expanded, a `file://` URL taken as its path.
191
- let p = raw.replace(/[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g, " ");
192
- if (p.startsWith("@")) p = p.slice(1);
193
- if (p === "~") p = home;
194
- else if (p.startsWith("~/")) p = `${home}${p.slice(1)}`;
195
- else if (/^file:\/\//.test(p)) {
196
- try {
197
- p = fileURLToPath(p);
198
- } catch {
199
- // pi would throw on it and open nothing; left as spelled, it resolves
200
- // to no focus file and is never credited.
201
- }
202
- }
203
- return posix.resolve(cwd, p);
133
+ return readPathIn(raw, cwd, home);
204
134
  }
205
135
 
206
- /** What one case's event stream says, fed event by event: the last assistant
207
- * text, pi's own per-message cost summed, how the model stopped, and the files
208
- * its `read`/`grep` calls opened. One reader for both hosts of a leaf — the
209
- * child process (`piCaseRunner`) and the in-process session
210
- * (src/engine/caserunner-sdk.ts) emit the same events. */
136
+ /** What one case's event stream says (caseread.ts `caseReadout`), a read's
137
+ * `~` taken as this process's home. */
211
138
  export function caseReadout(
212
139
  cwd: string,
213
140
  onCost?: (spentSoFar: number) => void,
214
- ): {
215
- feed(event: unknown): void;
216
- readonly final: string;
217
- readonly failure: string | null;
218
- readonly reads: string[];
219
- /** The summed cost; null when no message priced itself. */
220
- spent(): number | null;
221
- } {
222
- let final = "";
223
- let cost = 0;
224
- let priced = false;
225
- let failure: string | null = null;
226
- const reads: string[] = [];
227
- // Started reads by call id, credited only when the call succeeds.
228
- const attempted = new Map<string, string>();
229
- return {
230
- feed(raw: unknown): void {
231
- if (typeof raw !== "object" || raw === null) return;
232
- const event = raw as {
233
- type?: string;
234
- message?: Record<string, unknown>;
235
- toolName?: unknown;
236
- toolCallId?: unknown;
237
- args?: unknown;
238
- isError?: unknown;
239
- };
240
- const read = readPathOf(event);
241
- if (read !== null && typeof event.toolCallId === "string")
242
- attempted.set(event.toolCallId, resolvedReadPath(read, cwd));
243
- const done = readSucceeded(event);
244
- if (done !== null && attempted.has(done)) {
245
- reads.push(attempted.get(done)!);
246
- attempted.delete(done);
247
- }
248
- const msg = event.message;
249
- if (event.type !== "message_end" || !msg || msg.role !== "assistant") return;
250
- const usage = msg.usage as { cost?: { total?: unknown } } | undefined;
251
- if (typeof usage?.cost?.total === "number") {
252
- cost += usage.cost.total;
253
- priced = true;
254
- try {
255
- onCost?.(cost);
256
- } catch {
257
- // A listener's fault never ends the case.
258
- }
259
- }
260
- const parts = Array.isArray(msg.content)
261
- ? (msg.content as Array<{ type?: string; text?: string }>)
262
- : [];
263
- const text = parts
264
- .filter((p) => p.type === "text" && typeof p.text === "string")
265
- .map((p) => p.text)
266
- .join("\n");
267
- if (text.trim() !== "") final = text;
268
- if (msg.stopReason === "error" || msg.stopReason === "aborted") {
269
- failure =
270
- typeof msg.errorMessage === "string"
271
- ? msg.errorMessage
272
- : `the model stopped: ${String(msg.stopReason)}`;
273
- } else {
274
- failure = null;
275
- }
276
- },
277
- get final(): string {
278
- return final;
279
- },
280
- get failure(): string | null {
281
- return failure;
282
- },
283
- get reads(): string[] {
284
- return reads;
285
- },
286
- spent(): number | null {
287
- return priced ? cost : null;
288
- },
289
- };
141
+ ): ReturnType<typeof readoutIn> {
142
+ return readoutIn(cwd, homedir(), onCost);
290
143
  }
291
144
 
292
- /** What a leaf's pi process may inherit, by exact name. The leaf's model runs
293
- * bash, so the process env is allowlisted rather than copied: no GitHub
294
- * credential (packages/pi-plugin/src/envstrip.ts) and nothing else of the driver's reaches it. */
295
- const LEAF_ENV_NAMES: ReadonlySet<string> = new Set([
296
- "PATH",
297
- "HOME",
298
- "USER",
299
- "LOGNAME",
300
- "SHELL",
301
- "TERM",
302
- "TZ",
303
- "LANG",
304
- "TMPDIR",
305
- "TMP",
306
- "TEMP",
307
- "SSL_CERT_FILE",
308
- "SSL_CERT_DIR",
309
- "NODE_EXTRA_CA_CERTS",
310
- "HTTP_PROXY",
311
- "HTTPS_PROXY",
312
- "NO_PROXY",
313
- "ALL_PROXY",
314
- "http_proxy",
315
- "https_proxy",
316
- "no_proxy",
317
- "all_proxy",
318
- ]);
319
-
320
- /** …and by prefix or shape: locale (`LC_*`), XDG dirs, pi's own settings
321
- * (`PI_*`, among them `PI_CODING_AGENT_DIR`), bash-guard's locators
322
- * (`QCR_*`: install, config home, sandbox root, replay flag) and the model
323
- * providers' key variables. */
324
- const LEAF_ENV_PATTERNS: readonly RegExp[] = [
325
- /^LC_[A-Z_]+$/,
326
- /^XDG_[A-Z_]+$/,
327
- /^PI_[A-Z0-9_]+$/,
328
- /^QCR_[A-Z0-9_]+$/,
329
- /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*_API_KEY$/,
330
- ];
331
-
332
- /** The env one leaf's pi starts with: `source` (the driver's) filtered to the
333
- * allowlist, the GitHub token envs always dropped, and `QCR_GITHUB_READ_ENV`
334
- * (the path of the file holding the read token) with them. */
145
+ /** The env one leaf's pi starts with (caseread.ts `leafEnv`): `source`, this
146
+ * process's env unless told otherwise, filtered to the allowlist. */
335
147
  export function leafEnv(source: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
336
- const env: NodeJS.ProcessEnv = {};
337
- for (const [name, value] of Object.entries(source)) {
338
- if (value === undefined) continue;
339
- if (LEAF_ENV_NAMES.has(name) || LEAF_ENV_PATTERNS.some((re) => re.test(name)))
340
- env[name] = value;
341
- }
342
- for (const k of GITHUB_TOKEN_ENVS) delete env[k];
343
- delete env["QCR_GITHUB_READ_ENV"];
344
- return env;
345
- }
346
-
347
- /** The stderr a failed case's reason is taken from: its last characters. */
348
- const STDERR_TAIL_CHARS = 4000;
349
-
350
- /** The last three lines of the last `STDERR_TAIL_CHARS` characters of a failed
351
- * leaf's stderr, joined with " / " — the excerpt a failure reason quotes. */
352
- export function stderrExcerpt(stderr: string): string {
353
- return stderr.slice(-STDERR_TAIL_CHARS).trim().split("\n").slice(-3).join(" / ");
354
- }
355
-
356
- /** Transcript directories kept: the newest by modification time. */
357
- export const TRANSCRIPT_RUNS_KEPT = 20;
358
-
359
- /** Where a case's transcript lands under `dir`, relative to it: the case id's
360
- * first segment (the run) is the directory, the rest of it the file name —
361
- * `r3/row-2#1/validate` → `r3/row-2_1.validate.jsonl`. Every segment is
362
- * reduced to `[A-Za-z0-9._-]` and none may be `.` or `..`, so an id never
363
- * names a path outside `dir`. Pure. */
364
- export function transcriptFile(id: string): string {
365
- const seg = (s: string): string => {
366
- const t = s.replace(/[^A-Za-z0-9._-]/g, "_");
367
- return t === "" || /^\.+$/.test(t) ? t.replace(/\./g, "_") || "_" : t;
368
- };
369
- const parts = id.split("/").map(seg);
370
- if (parts.length === 1) return `${parts[0]}.jsonl`;
371
- return `${parts[0]}/${parts.slice(1).join(".")}.jsonl`;
372
- }
373
-
374
- /** Remove all but the newest `keep` run directories under `dir` (by
375
- * modification time, never `except`). Best effort: a transcript is evidence,
376
- * not state, and a failure here never touches a case. */
377
- export function pruneTranscripts(dir: string, keep: number, except: string): void {
378
- try {
379
- const runs = readdirSync(dir, { withFileTypes: true })
380
- .filter((d) => d.isDirectory() && d.name !== except)
381
- .map((d) => ({ name: d.name, at: statSync(join(dir, d.name)).mtimeMs }))
382
- .sort((a, b) => b.at - a.at || b.name.localeCompare(a.name));
383
- for (const r of runs.slice(Math.max(0, keep - 1)))
384
- rmSync(join(dir, r.name), { recursive: true, force: true });
385
- } catch {
386
- // Evidence only: nothing to undo.
387
- }
148
+ return leafEnvOf(source);
388
149
  }
389
150
 
390
151
  /**