@prestyj/cli 5.11.0 → 5.13.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 (466) hide show
  1. package/README.md +46 -36
  2. package/assets/skills/bulletproof/SKILL.md +158 -0
  3. package/assets/skills/bulletproof/references/agent-surface.md +82 -0
  4. package/assets/skills/bulletproof/references/audit-protocol.md +147 -0
  5. package/assets/skills/bulletproof/references/platform-playbooks.md +176 -0
  6. package/assets/skills/bulletproof/references/provenance.md +45 -0
  7. package/assets/skills/bulletproof/references/secure-defaults.md +108 -0
  8. package/assets/skills/bulletproof/references/supply-chain.md +76 -0
  9. package/assets/skills/bulletproof/references/threat-landscape.md +74 -0
  10. package/assets/skills/bulletproof/references/verification.md +65 -0
  11. package/assets/skills/compliance-guard/SKILL.md +251 -0
  12. package/assets/skills/compliance-guard/references/artifacts.md +186 -0
  13. package/assets/skills/compliance-guard/references/eu-uk.md +151 -0
  14. package/assets/skills/compliance-guard/references/exposure-triage.md +161 -0
  15. package/assets/skills/compliance-guard/references/lawsuit-vectors.md +141 -0
  16. package/assets/skills/compliance-guard/references/provenance.md +42 -0
  17. package/assets/skills/compliance-guard/references/sector-gates.md +173 -0
  18. package/assets/skills/compliance-guard/references/security-baseline.md +130 -0
  19. package/assets/skills/compliance-guard/references/trigger-map.md +163 -0
  20. package/assets/skills/compliance-guard/references/us.md +164 -0
  21. package/assets/skills/durable/SKILL.md +111 -0
  22. package/assets/skills/durable/references/backups-and-runtime.md +86 -0
  23. package/assets/skills/durable/references/migrations-and-schema.md +79 -0
  24. package/assets/skills/lean/SKILL.md +110 -0
  25. package/assets/skills/lean/references/memory-and-processes.md +98 -0
  26. package/assets/skills/lean/references/playbooks.md +107 -0
  27. package/dist/app-sidecar-auth-broadcast.test.js +111 -10
  28. package/dist/app-sidecar-auth-broadcast.test.js.map +1 -1
  29. package/dist/app-sidecar-sessions.test.js +1 -1
  30. package/dist/app-sidecar.js +210 -72
  31. package/dist/app-sidecar.js.map +1 -1
  32. package/dist/chat-agents/jiwa.test.js +1 -1
  33. package/dist/cli/auth.d.ts.map +1 -1
  34. package/dist/cli/auth.js +33 -36
  35. package/dist/cli/auth.js.map +1 -1
  36. package/dist/cli/mcp.js +30 -10
  37. package/dist/cli/mcp.js.map +1 -1
  38. package/dist/cli.d.ts.map +1 -1
  39. package/dist/cli.js +18 -5
  40. package/dist/cli.js.map +1 -1
  41. package/dist/config.d.ts +28 -5
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +50 -76
  44. package/dist/config.js.map +1 -1
  45. package/dist/config.test.js +56 -13
  46. package/dist/config.test.js.map +1 -1
  47. package/dist/core/agent-session-checkpoint-flush.test.js +2 -2
  48. package/dist/core/agent-session-compaction.test.js +60 -0
  49. package/dist/core/agent-session-compaction.test.js.map +1 -1
  50. package/dist/core/agent-session-crash-durability.test.js +3 -3
  51. package/dist/core/agent-session-model-switch.test.js +1 -1
  52. package/dist/core/agent-session-process-gate.test.js +7 -1
  53. package/dist/core/agent-session-process-gate.test.js.map +1 -1
  54. package/dist/core/agent-session-review-coverage.test.js +52 -1
  55. package/dist/core/agent-session-review-coverage.test.js.map +1 -1
  56. package/dist/core/agent-session-tool-tiers.test.d.ts +2 -0
  57. package/dist/core/agent-session-tool-tiers.test.d.ts.map +1 -0
  58. package/dist/core/agent-session-tool-tiers.test.js +138 -0
  59. package/dist/core/agent-session-tool-tiers.test.js.map +1 -0
  60. package/dist/core/agent-session-verification-gate.test.d.ts +2 -0
  61. package/dist/core/agent-session-verification-gate.test.d.ts.map +1 -0
  62. package/dist/core/agent-session-verification-gate.test.js +135 -0
  63. package/dist/core/agent-session-verification-gate.test.js.map +1 -0
  64. package/dist/core/agent-session.d.ts +77 -3
  65. package/dist/core/agent-session.d.ts.map +1 -1
  66. package/dist/core/agent-session.js +268 -33
  67. package/dist/core/agent-session.js.map +1 -1
  68. package/dist/core/agents.d.ts +29 -1
  69. package/dist/core/agents.d.ts.map +1 -1
  70. package/dist/core/agents.js +47 -97
  71. package/dist/core/agents.js.map +1 -1
  72. package/dist/core/agents.test.js +87 -7
  73. package/dist/core/agents.test.js.map +1 -1
  74. package/dist/core/api-benchmark.d.ts +3 -3
  75. package/dist/core/api-benchmark.js +7 -7
  76. package/dist/core/auth-providers.d.ts +37 -1
  77. package/dist/core/auth-providers.d.ts.map +1 -1
  78. package/dist/core/auth-providers.js +84 -4
  79. package/dist/core/auth-providers.js.map +1 -1
  80. package/dist/core/auto-update.test.js +1 -1
  81. package/dist/core/autopilot-gate.d.ts +2 -2
  82. package/dist/core/autopilot-gate.js +1 -1
  83. package/dist/core/autopilot-gate.test.js +7 -4
  84. package/dist/core/autopilot-gate.test.js.map +1 -1
  85. package/dist/core/bash-spawn-benchmark.js +6 -6
  86. package/dist/core/bundled-agents.d.ts +3 -0
  87. package/dist/core/bundled-agents.d.ts.map +1 -0
  88. package/dist/core/bundled-agents.js +255 -0
  89. package/dist/core/bundled-agents.js.map +1 -0
  90. package/dist/core/cache-warm-benchmark.d.ts +2 -2
  91. package/dist/core/cache-warm-benchmark.js +5 -5
  92. package/dist/core/code-retrieval-chunkers.d.ts +27 -0
  93. package/dist/core/code-retrieval-chunkers.d.ts.map +1 -0
  94. package/dist/core/code-retrieval-chunkers.js +264 -0
  95. package/dist/core/code-retrieval-chunkers.js.map +1 -0
  96. package/dist/core/code-retrieval-chunkers.test.d.ts +2 -0
  97. package/dist/core/code-retrieval-chunkers.test.d.ts.map +1 -0
  98. package/dist/core/code-retrieval-chunkers.test.js +203 -0
  99. package/dist/core/code-retrieval-chunkers.test.js.map +1 -0
  100. package/dist/core/code-retrieval.d.ts +6 -0
  101. package/dist/core/code-retrieval.d.ts.map +1 -1
  102. package/dist/core/code-retrieval.js +38 -1
  103. package/dist/core/code-retrieval.js.map +1 -1
  104. package/dist/core/compaction/token-estimator.test.js +1 -1
  105. package/dist/core/encode-cwd.test.js +1 -1
  106. package/dist/core/event-bus.d.ts +11 -1
  107. package/dist/core/event-bus.d.ts.map +1 -1
  108. package/dist/core/event-bus.js +1 -0
  109. package/dist/core/event-bus.js.map +1 -1
  110. package/dist/core/event-bus.test.d.ts +2 -0
  111. package/dist/core/event-bus.test.d.ts.map +1 -0
  112. package/dist/core/event-bus.test.js +33 -0
  113. package/dist/core/event-bus.test.js.map +1 -0
  114. package/dist/core/extensions/plugin-bundles.js +2 -2
  115. package/dist/core/extensions/plugin-bundles.js.map +1 -1
  116. package/dist/core/extensions/plugin-bundles.test.js +2 -2
  117. package/dist/core/extensions/plugin-bundles.test.js.map +1 -1
  118. package/dist/core/fast-apply-benchmark.d.ts +2 -2
  119. package/dist/core/fast-apply-benchmark.js +6 -6
  120. package/dist/core/foreign-session-import.test.js +1 -1
  121. package/dist/core/goal-lifecycle-smoke.test.js +2 -2
  122. package/dist/core/goal-store.js +1 -1
  123. package/dist/core/goal-store.test.js +2 -2
  124. package/dist/core/goal-verifier.test.js +2 -2
  125. package/dist/core/goal-worker-dev-server-lifecycle.test.js +1 -1
  126. package/dist/core/goal-worker.test.js +2 -2
  127. package/dist/core/hashline-edit-benchmark.d.ts +2 -2
  128. package/dist/core/hashline-edit-benchmark.js +7 -7
  129. package/dist/core/local-endpoint-store.test.js +1 -1
  130. package/dist/core/logger.d.ts +1 -1
  131. package/dist/core/logger.js +2 -2
  132. package/dist/core/logger.js.map +1 -1
  133. package/dist/core/lsp/client-navigation.test.d.ts +2 -0
  134. package/dist/core/lsp/client-navigation.test.d.ts.map +1 -0
  135. package/dist/core/lsp/client-navigation.test.js +135 -0
  136. package/dist/core/lsp/client-navigation.test.js.map +1 -0
  137. package/dist/core/lsp/client.d.ts +69 -0
  138. package/dist/core/lsp/client.d.ts.map +1 -1
  139. package/dist/core/lsp/client.js +151 -2
  140. package/dist/core/lsp/client.js.map +1 -1
  141. package/dist/core/lsp/integration.test.js +2 -2
  142. package/dist/core/lsp/manager-navigation.test.d.ts +2 -0
  143. package/dist/core/lsp/manager-navigation.test.d.ts.map +1 -0
  144. package/dist/core/lsp/manager-navigation.test.js +121 -0
  145. package/dist/core/lsp/manager-navigation.test.js.map +1 -0
  146. package/dist/core/lsp/manager.d.ts +35 -1
  147. package/dist/core/lsp/manager.d.ts.map +1 -1
  148. package/dist/core/lsp/manager.js +92 -0
  149. package/dist/core/lsp/manager.js.map +1 -1
  150. package/dist/core/lsp/servers.test.js +1 -1
  151. package/dist/core/lsp/windows.test.js +4 -4
  152. package/dist/core/mcp/catalog-cache.test.js +1 -1
  153. package/dist/core/mcp/client-session-recovery.test.js +1 -1
  154. package/dist/core/mcp/client.d.ts.map +1 -1
  155. package/dist/core/mcp/client.js +2 -10
  156. package/dist/core/mcp/client.js.map +1 -1
  157. package/dist/core/mcp/content.d.ts +19 -0
  158. package/dist/core/mcp/content.d.ts.map +1 -0
  159. package/dist/core/mcp/content.js +172 -0
  160. package/dist/core/mcp/content.js.map +1 -0
  161. package/dist/core/mcp/content.test.d.ts +2 -0
  162. package/dist/core/mcp/content.test.d.ts.map +1 -0
  163. package/dist/core/mcp/content.test.js +135 -0
  164. package/dist/core/mcp/content.test.js.map +1 -0
  165. package/dist/core/mcp/defaults.d.ts +3 -1
  166. package/dist/core/mcp/defaults.d.ts.map +1 -1
  167. package/dist/core/mcp/defaults.js +9 -2
  168. package/dist/core/mcp/defaults.js.map +1 -1
  169. package/dist/core/mcp/defaults.test.d.ts +2 -0
  170. package/dist/core/mcp/defaults.test.d.ts.map +1 -0
  171. package/dist/core/mcp/defaults.test.js +60 -0
  172. package/dist/core/mcp/defaults.test.js.map +1 -0
  173. package/dist/core/mcp/deferred-catalog.d.ts +1 -1
  174. package/dist/core/mcp/deferred-catalog.js +1 -1
  175. package/dist/core/mcp/elicitation.test.js +1 -1
  176. package/dist/core/mcp/legacy-negotiation.test.js +2 -2
  177. package/dist/core/mcp/oauth-store.test.js +2 -2
  178. package/dist/core/mcp/resolve-stdio.test.js +5 -5
  179. package/dist/core/mcp/shared-pool.test.js +5 -5
  180. package/dist/core/mcp/store.test.js +5 -5
  181. package/dist/core/mcp/when-connected.test.js +2 -2
  182. package/dist/core/nolan-model.test.js +1 -1
  183. package/dist/core/nolan-prompt.js +5 -5
  184. package/dist/core/oauth/xai.d.ts +2 -0
  185. package/dist/core/oauth/xai.d.ts.map +1 -0
  186. package/dist/core/oauth/xai.js +4 -0
  187. package/dist/core/oauth/xai.js.map +1 -0
  188. package/dist/core/persistent-shell.js +1 -1
  189. package/dist/core/persistent-shell.test.js +6 -6
  190. package/dist/core/process-manager-dev-server-repro.test.js +51 -12
  191. package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
  192. package/dist/core/process-manager-notifications.test.js +24 -14
  193. package/dist/core/process-manager-notifications.test.js.map +1 -1
  194. package/dist/core/process-manager-wake.test.d.ts +2 -0
  195. package/dist/core/process-manager-wake.test.d.ts.map +1 -0
  196. package/dist/core/process-manager-wake.test.js +96 -0
  197. package/dist/core/process-manager-wake.test.js.map +1 -0
  198. package/dist/core/process-manager.d.ts +61 -1
  199. package/dist/core/process-manager.d.ts.map +1 -1
  200. package/dist/core/process-manager.js +204 -4
  201. package/dist/core/process-manager.js.map +1 -1
  202. package/dist/core/progress/git-xp.test.js +2 -2
  203. package/dist/core/progress/ranks.d.ts +14 -3
  204. package/dist/core/progress/ranks.d.ts.map +1 -1
  205. package/dist/core/progress/ranks.js +181 -12
  206. package/dist/core/progress/ranks.js.map +1 -1
  207. package/dist/core/progress/ranks.test.js +48 -7
  208. package/dist/core/progress/ranks.test.js.map +1 -1
  209. package/dist/core/progress/rebuild.test.js +1 -1
  210. package/dist/core/progress/store.test.js +1 -1
  211. package/dist/core/progress/types.d.ts +2 -1
  212. package/dist/core/progress/types.d.ts.map +1 -1
  213. package/dist/core/project-discovery.d.ts.map +1 -1
  214. package/dist/core/project-discovery.js +62 -48
  215. package/dist/core/project-discovery.js.map +1 -1
  216. package/dist/core/project-discovery.test.js +10 -10
  217. package/dist/core/prompt-commands.d.ts.map +1 -1
  218. package/dist/core/prompt-commands.js +3 -3
  219. package/dist/core/prompt-commands.js.map +1 -1
  220. package/dist/core/provider-cache.e2e.test.d.ts +2 -0
  221. package/dist/core/provider-cache.e2e.test.d.ts.map +1 -0
  222. package/dist/core/provider-cache.e2e.test.js +125 -0
  223. package/dist/core/provider-cache.e2e.test.js.map +1 -0
  224. package/dist/core/radio.d.ts +1 -1
  225. package/dist/core/radio.d.ts.map +1 -1
  226. package/dist/core/radio.js +90 -6
  227. package/dist/core/radio.js.map +1 -1
  228. package/dist/core/radio.test.js +15 -2
  229. package/dist/core/radio.test.js.map +1 -1
  230. package/dist/core/run-journal.test.js +2 -2
  231. package/dist/core/sandbox.test.js +6 -6
  232. package/dist/core/semantic-search-benchmark.d.ts +2 -2
  233. package/dist/core/semantic-search-benchmark.js +5 -5
  234. package/dist/core/session-compaction.test.js +1 -1
  235. package/dist/core/session-injections.test.d.ts +2 -0
  236. package/dist/core/session-injections.test.d.ts.map +1 -0
  237. package/dist/core/session-injections.test.js +117 -0
  238. package/dist/core/session-injections.test.js.map +1 -0
  239. package/dist/core/session-manager.d.ts.map +1 -1
  240. package/dist/core/session-manager.js +83 -67
  241. package/dist/core/session-manager.js.map +1 -1
  242. package/dist/core/session-manager.test.js +7 -7
  243. package/dist/core/session-reconstructability.test.d.ts +2 -0
  244. package/dist/core/session-reconstructability.test.d.ts.map +1 -0
  245. package/dist/core/session-reconstructability.test.js +105 -0
  246. package/dist/core/session-reconstructability.test.js.map +1 -0
  247. package/dist/core/session-storage-fd-leak.test.d.ts +2 -0
  248. package/dist/core/session-storage-fd-leak.test.d.ts.map +1 -0
  249. package/dist/core/session-storage-fd-leak.test.js +116 -0
  250. package/dist/core/session-storage-fd-leak.test.js.map +1 -0
  251. package/dist/core/session-storage.d.ts +22 -2
  252. package/dist/core/session-storage.d.ts.map +1 -1
  253. package/dist/core/session-storage.js +33 -6
  254. package/dist/core/session-storage.js.map +1 -1
  255. package/dist/core/session-storage.test.js +2 -2
  256. package/dist/core/settings-manager.d.ts +12 -0
  257. package/dist/core/settings-manager.d.ts.map +1 -1
  258. package/dist/core/settings-manager.js +46 -1
  259. package/dist/core/settings-manager.js.map +1 -1
  260. package/dist/core/shell-path.js +1 -1
  261. package/dist/core/shell.d.ts +1 -1
  262. package/dist/core/shell.js +2 -2
  263. package/dist/core/shell.test.js +2 -2
  264. package/dist/core/shutdown.d.ts +71 -0
  265. package/dist/core/shutdown.d.ts.map +1 -0
  266. package/dist/core/shutdown.js +144 -0
  267. package/dist/core/shutdown.js.map +1 -0
  268. package/dist/core/shutdown.test.d.ts +2 -0
  269. package/dist/core/shutdown.test.d.ts.map +1 -0
  270. package/dist/core/shutdown.test.js +160 -0
  271. package/dist/core/shutdown.test.js.map +1 -0
  272. package/dist/core/sidecar-error-reporter.js +1 -1
  273. package/dist/core/sidecar-error-reporter.test.js +2 -2
  274. package/dist/core/skills-routing.test.js +142 -0
  275. package/dist/core/skills-routing.test.js.map +1 -1
  276. package/dist/core/skills.d.ts.map +1 -1
  277. package/dist/core/skills.js +4 -1
  278. package/dist/core/skills.js.map +1 -1
  279. package/dist/core/subagent-manager.d.ts +9 -0
  280. package/dist/core/subagent-manager.d.ts.map +1 -1
  281. package/dist/core/subagent-manager.js +45 -12
  282. package/dist/core/subagent-manager.js.map +1 -1
  283. package/dist/core/subagent-manager.test.js +14 -8
  284. package/dist/core/subagent-manager.test.js.map +1 -1
  285. package/dist/core/subagent-store.test.js +3 -3
  286. package/dist/core/task-store.js +1 -1
  287. package/dist/core/verification-gate.d.ts +55 -0
  288. package/dist/core/verification-gate.d.ts.map +1 -0
  289. package/dist/core/verification-gate.js +188 -0
  290. package/dist/core/verification-gate.js.map +1 -0
  291. package/dist/core/verification-gate.test.d.ts +2 -0
  292. package/dist/core/verification-gate.test.d.ts.map +1 -0
  293. package/dist/core/verification-gate.test.js +102 -0
  294. package/dist/core/verification-gate.test.js.map +1 -0
  295. package/dist/index.js +1 -1
  296. package/dist/index.js.map +1 -1
  297. package/dist/modes/acp-mode.js +2 -2
  298. package/dist/modes/acp-mode.test.js +10 -8
  299. package/dist/modes/acp-mode.test.js.map +1 -1
  300. package/dist/modes/agent-home-mode.d.ts.map +1 -1
  301. package/dist/modes/agent-home-mode.js +15 -11
  302. package/dist/modes/agent-home-mode.js.map +1 -1
  303. package/dist/modes/json-mode.d.ts +9 -1
  304. package/dist/modes/json-mode.d.ts.map +1 -1
  305. package/dist/modes/json-mode.js +3 -1
  306. package/dist/modes/json-mode.js.map +1 -1
  307. package/dist/modes/serve-mode.d.ts +1 -1
  308. package/dist/modes/serve-mode.d.ts.map +1 -1
  309. package/dist/modes/serve-mode.js +14 -9
  310. package/dist/modes/serve-mode.js.map +1 -1
  311. package/dist/modes/subagent-worker-mode.d.ts +5 -0
  312. package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
  313. package/dist/modes/subagent-worker-mode.js.map +1 -1
  314. package/dist/session.test.js +1 -1
  315. package/dist/system-prompt.d.ts +37 -1
  316. package/dist/system-prompt.d.ts.map +1 -1
  317. package/dist/system-prompt.js +170 -12
  318. package/dist/system-prompt.js.map +1 -1
  319. package/dist/system-prompt.test.js +170 -11
  320. package/dist/system-prompt.test.js.map +1 -1
  321. package/dist/system-prompt.tiering.test.d.ts +2 -0
  322. package/dist/system-prompt.tiering.test.d.ts.map +1 -0
  323. package/dist/system-prompt.tiering.test.js +108 -0
  324. package/dist/system-prompt.tiering.test.js.map +1 -0
  325. package/dist/tools/bash.d.ts +4 -0
  326. package/dist/tools/bash.d.ts.map +1 -1
  327. package/dist/tools/bash.js +57 -3
  328. package/dist/tools/bash.js.map +1 -1
  329. package/dist/tools/bash.test.js +37 -0
  330. package/dist/tools/bash.test.js.map +1 -1
  331. package/dist/tools/code-nav.d.ts +30 -0
  332. package/dist/tools/code-nav.d.ts.map +1 -0
  333. package/dist/tools/code-nav.js +320 -0
  334. package/dist/tools/code-nav.js.map +1 -0
  335. package/dist/tools/code-nav.test.d.ts +2 -0
  336. package/dist/tools/code-nav.test.d.ts.map +1 -0
  337. package/dist/tools/code-nav.test.js +136 -0
  338. package/dist/tools/code-nav.test.js.map +1 -0
  339. package/dist/tools/edit.d.ts.map +1 -1
  340. package/dist/tools/edit.js +37 -4
  341. package/dist/tools/edit.js.map +1 -1
  342. package/dist/tools/edit.test.js +45 -1
  343. package/dist/tools/edit.test.js.map +1 -1
  344. package/dist/tools/generate-image.d.ts +1 -1
  345. package/dist/tools/goals.d.ts +2 -2
  346. package/dist/tools/goals.test.js +2 -2
  347. package/dist/tools/grep-parity.test.d.ts +2 -0
  348. package/dist/tools/grep-parity.test.d.ts.map +1 -0
  349. package/dist/tools/grep-parity.test.js +92 -0
  350. package/dist/tools/grep-parity.test.js.map +1 -0
  351. package/dist/tools/grep-recall.test.d.ts +2 -0
  352. package/dist/tools/grep-recall.test.d.ts.map +1 -0
  353. package/dist/tools/grep-recall.test.js +114 -0
  354. package/dist/tools/grep-recall.test.js.map +1 -0
  355. package/dist/tools/grep-spawn.test.d.ts +2 -0
  356. package/dist/tools/grep-spawn.test.d.ts.map +1 -0
  357. package/dist/tools/grep-spawn.test.js +66 -0
  358. package/dist/tools/grep-spawn.test.js.map +1 -0
  359. package/dist/tools/grep.d.ts +13 -3
  360. package/dist/tools/grep.d.ts.map +1 -1
  361. package/dist/tools/grep.js +340 -40
  362. package/dist/tools/grep.js.map +1 -1
  363. package/dist/tools/grep.test.js +17 -14
  364. package/dist/tools/grep.test.js.map +1 -1
  365. package/dist/tools/index.d.ts +7 -0
  366. package/dist/tools/index.d.ts.map +1 -1
  367. package/dist/tools/index.js +8 -1
  368. package/dist/tools/index.js.map +1 -1
  369. package/dist/tools/plan-mode.test.js +1 -1
  370. package/dist/tools/prompt-hints.d.ts +9 -0
  371. package/dist/tools/prompt-hints.d.ts.map +1 -1
  372. package/dist/tools/prompt-hints.js +62 -8
  373. package/dist/tools/prompt-hints.js.map +1 -1
  374. package/dist/tools/read-only-bash.d.ts +6 -0
  375. package/dist/tools/read-only-bash.d.ts.map +1 -1
  376. package/dist/tools/read-only-bash.js +168 -9
  377. package/dist/tools/read-only-bash.js.map +1 -1
  378. package/dist/tools/read-only-bash.test.js +49 -0
  379. package/dist/tools/read-only-bash.test.js.map +1 -1
  380. package/dist/tools/safe-env.js +1 -1
  381. package/dist/tools/search-code.d.ts.map +1 -1
  382. package/dist/tools/search-code.js +7 -6
  383. package/dist/tools/search-code.js.map +1 -1
  384. package/dist/tools/search-code.test.js +8 -2
  385. package/dist/tools/search-code.test.js.map +1 -1
  386. package/dist/tools/skill.js +3 -1
  387. package/dist/tools/skill.js.map +1 -1
  388. package/dist/tools/subagent-control.d.ts.map +1 -1
  389. package/dist/tools/subagent-control.js +20 -5
  390. package/dist/tools/subagent-control.js.map +1 -1
  391. package/dist/tools/subagent-control.test.js +34 -1
  392. package/dist/tools/subagent-control.test.js.map +1 -1
  393. package/dist/tools/subagent-shared.d.ts +20 -1
  394. package/dist/tools/subagent-shared.d.ts.map +1 -1
  395. package/dist/tools/subagent-shared.js +34 -7
  396. package/dist/tools/subagent-shared.js.map +1 -1
  397. package/dist/tools/subagent-shared.test.js +43 -9
  398. package/dist/tools/subagent-shared.test.js.map +1 -1
  399. package/dist/tools/subagent.d.ts.map +1 -1
  400. package/dist/tools/subagent.js +11 -8
  401. package/dist/tools/subagent.js.map +1 -1
  402. package/dist/tools/subagent.test.js +3 -0
  403. package/dist/tools/subagent.test.js.map +1 -1
  404. package/dist/tools/task-send.test.js +19 -10
  405. package/dist/tools/task-send.test.js.map +1 -1
  406. package/dist/tools/tool-catalog.test.d.ts +2 -0
  407. package/dist/tools/tool-catalog.test.d.ts.map +1 -0
  408. package/dist/tools/tool-catalog.test.js +124 -0
  409. package/dist/tools/tool-catalog.test.js.map +1 -0
  410. package/dist/tools/tool-search.d.ts.map +1 -1
  411. package/dist/tools/tool-search.js +2 -1
  412. package/dist/tools/tool-search.js.map +1 -1
  413. package/dist/tools/tool-tiers.d.ts +42 -0
  414. package/dist/tools/tool-tiers.d.ts.map +1 -0
  415. package/dist/tools/tool-tiers.js +78 -0
  416. package/dist/tools/tool-tiers.js.map +1 -0
  417. package/dist/tools/tool-tiers.test.d.ts +2 -0
  418. package/dist/tools/tool-tiers.test.d.ts.map +1 -0
  419. package/dist/tools/tool-tiers.test.js +48 -0
  420. package/dist/tools/tool-tiers.test.js.map +1 -0
  421. package/dist/tools/write.test.js +1 -1
  422. package/dist/ui/App.d.ts +3 -0
  423. package/dist/ui/App.d.ts.map +1 -1
  424. package/dist/ui/App.js +39 -3
  425. package/dist/ui/App.js.map +1 -1
  426. package/dist/ui/app-items.d.ts +2 -0
  427. package/dist/ui/app-items.d.ts.map +1 -1
  428. package/dist/ui/app-items.js +2 -0
  429. package/dist/ui/app-items.js.map +1 -1
  430. package/dist/ui/components/Banner.d.ts.map +1 -1
  431. package/dist/ui/components/Banner.js +24 -16
  432. package/dist/ui/components/Banner.js.map +1 -1
  433. package/dist/ui/components/ToolExecution.js +42 -34
  434. package/dist/ui/components/ToolExecution.js.map +1 -1
  435. package/dist/ui/components/force-color.d.ts +2 -0
  436. package/dist/ui/components/force-color.d.ts.map +1 -0
  437. package/dist/ui/components/force-color.js +11 -0
  438. package/dist/ui/components/force-color.js.map +1 -0
  439. package/dist/ui/components/themed-output.test.d.ts +2 -0
  440. package/dist/ui/components/themed-output.test.d.ts.map +1 -0
  441. package/dist/ui/components/themed-output.test.js +111 -0
  442. package/dist/ui/components/themed-output.test.js.map +1 -0
  443. package/dist/ui/error-item.d.ts +1 -1
  444. package/dist/ui/error-item.js +3 -3
  445. package/dist/ui/error-item.js.map +1 -1
  446. package/dist/ui/hooks/useAgentLoop.d.ts +4 -1
  447. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  448. package/dist/ui/hooks/useAgentLoop.js +27 -2
  449. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  450. package/dist/ui/login.js +2 -2
  451. package/dist/ui/login.js.map +1 -1
  452. package/dist/ui/render.d.ts +1 -1
  453. package/dist/ui/render.js +6 -6
  454. package/dist/ui/theme/dark-ansi.json +6 -0
  455. package/dist/ui/theme/dark-daltonized.json +6 -0
  456. package/dist/ui/theme/dark.json +6 -0
  457. package/dist/ui/theme/light-ansi.json +6 -0
  458. package/dist/ui/theme/light-daltonized.json +6 -0
  459. package/dist/ui/theme/light.json +6 -0
  460. package/dist/ui/theme/theme.d.ts +6 -0
  461. package/dist/ui/theme/theme.d.ts.map +1 -1
  462. package/dist/ui/tui-terminal-recorder.test.js +1 -1
  463. package/dist/utils/git.test.js +3 -3
  464. package/dist/utils/image.d.ts.map +1 -1
  465. package/dist/utils/image.js.map +1 -1
  466. package/package.json +7 -7
@@ -6,9 +6,10 @@ import { PROMPT_COMMANDS, getPromptCommand } from "./prompt-commands.js";
6
6
  import { loadCustomCommands } from "./custom-commands.js";
7
7
  import { SettingsManager } from "./settings-manager.js";
8
8
  import { AuthStorage } from "./auth-storage.js";
9
- import { MOONSHOT_OAUTH_KEY } from "@prestyj/core";
9
+ import { dualAuthProvider } from "@prestyj/core";
10
10
  import { getClaudeCliUserAgent } from "./claude-code-version.js";
11
11
  import { kimiCodingHeaders, isKimiCodingEndpoint } from "./oauth/kimi.js";
12
+ import { isGrokCliEndpoint } from "./oauth/xai.js";
12
13
  import { SessionManager, NOLAN_TURN_CUSTOM_KIND, AUTOPILOT_MARKER_CUSTOM_KIND, APP_MARKER_CUSTOM_KIND, } from "./session-manager.js";
13
14
  import { ExtensionLoader } from "./extensions/loader.js";
14
15
  import { shouldCompact, compact, } from "./compaction/compactor.js";
@@ -17,8 +18,9 @@ import { sourceFingerprint as computeSourceFingerprint } from "./session-compact
17
18
  import { getAuthStorageKeys, getContextWindow, getModel, getToolResultCharLimit, MODELS, } from "./model-registry.js";
18
19
  import { discoverSkills } from "./skills.js";
19
20
  import { ensureAppDirs } from "../config.js";
20
- import { buildSystemPrompt } from "../system-prompt.js";
21
+ import { buildSubAgentSystemPrompt, buildSystemPrompt, } from "../system-prompt.js";
21
22
  import { createTools, createWebSearchTool, } from "../tools/index.js";
23
+ import { partitionToolsByTier } from "../tools/tool-tiers.js";
22
24
  import { buildProcessCompletionFollowUp } from "./process-gate.js";
23
25
  import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
24
26
  import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
@@ -41,6 +43,7 @@ import { evaluateLoopBreak, buildLoopBreakMessage, CycleDetector, ToolCallProgre
41
43
  import { buildRegroundingMessage } from "./regrounding.js";
42
44
  import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
43
45
  import { AgentNotificationQueue } from "./agent-notifications.js";
46
+ import { VerificationGate, isCodeFilePath, isVerificationCommand } from "./verification-gate.js";
44
47
  import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
45
48
  import { normalizeMessageImages } from "./message-images.js";
46
49
  import crypto from "node:crypto";
@@ -138,6 +141,14 @@ export class AgentSession {
138
141
  idealReviewPhase = "idle";
139
142
  /** Runtime-only suppression while Nolan owns verification in autopilot mode. */
140
143
  idealReviewSuppressed = false;
144
+ /** Mirror of the last `hook_armed` value broadcast this run, so the event
145
+ * fires only on a real edge. */
146
+ idealReviewArmed = false;
147
+ /** Cached test-drift probe, keyed by the size of the edited-file set. Drift
148
+ * depends only on WHICH files were edited and that set only grows, so this
149
+ * keeps the arming check off the filesystem on most tool results — the probe
150
+ * is several sync existsSync calls per edited file. */
151
+ idealDriftProbe = null;
141
152
  reviewCoverage;
142
153
  /** Coverage follow-ups spent this run, capped by MAX_REVIEW_COVERAGE_INJECTIONS. */
143
154
  reviewCoverageInjected = 0;
@@ -148,6 +159,8 @@ export class AgentSession {
148
159
  runStartedAt = 0;
149
160
  /** Gate injections spent this run, capped by MAX_PROCESS_GATE_INJECTIONS. */
150
161
  processGateInjected = 0;
162
+ /** Verification gate: code edited this run, nothing proved it since. */
163
+ verificationGate = new VerificationGate();
151
164
  compactionOccurred = false;
152
165
  lastCompactionCompacted = false;
153
166
  compactionRetryAfter = 0;
@@ -180,8 +193,14 @@ export class AgentSession {
180
193
  void this.subAgentManager?.interruptAll();
181
194
  };
182
195
  mcpManager;
183
- /** Deferred MCP tools awaiting discovery via tool_search (bench A win). */
196
+ /** Deferred MCP tools awaiting discovery via tool_search. */
184
197
  mcpCatalog;
198
+ /**
199
+ * Built-in tools held in the catalog instead of the live toolset. Their names
200
+ * still render as one-line hints in the prompt's Tools section, so the model
201
+ * can discover and promote them; a promoted name drops out of this list.
202
+ */
203
+ deferredBuiltinToolNames = [];
185
204
  /** Live (connected) MCP tools by name — the reconcile target for cached stubs. */
186
205
  liveMcpTools = new Map();
187
206
  /** Server name for each cached-only tool, so a stub knows what to wait on. */
@@ -199,6 +218,8 @@ export class AgentSession {
199
218
  maxTokens;
200
219
  thinkingLevel;
201
220
  customSystemPrompt;
221
+ /** Sub-agent definition body composed into the standard prompt scaffolding. */
222
+ agentPrompt;
202
223
  /** Stable prompt prefix retained separately from the volatile uncached tail. */
203
224
  baseSystemPrompt = "";
204
225
  /** Shared with the tool layer so plan-mode restrictions read live state. */
@@ -258,6 +279,7 @@ export class AgentSession {
258
279
  this.maxTokens = this.resolveMaxTokens(options.model);
259
280
  this.thinkingLevel = options.thinkingLevel;
260
281
  this.customSystemPrompt = options.systemPrompt;
282
+ this.agentPrompt = options.agentPrompt;
261
283
  }
262
284
  /**
263
285
  * Derive the output-token cap for a model. Follows the active model's
@@ -338,6 +360,7 @@ export class AgentSession {
338
360
  additionalRoots: this.additionalRoots,
339
361
  allowOutsideWorkspaceWrites: this.settingsManager.get("allowOutsideWorkspaceWrites"),
340
362
  }),
363
+ getUseExternalGrep: () => this.settingsManager.get("grepUseRipgrep"),
341
364
  authStorage: this.authStorage,
342
365
  onFileRead: (filePath) => this.reviewCoverage.recordRead(filePath),
343
366
  onFileMutated: (filePath) => {
@@ -375,6 +398,23 @@ export class AgentSession {
375
398
  // a hallucinated call can't mutate the repo — and buildSystemPrompt below is
376
399
  // fed the same filtered names so the Tools section matches exactly.
377
400
  this.tools = this.opts.allowedTools ? tools.filter((t) => this.isToolAllowed(t.name)) : tools;
401
+ // Tier the built-ins: rarely reached schemas move into the tool_search
402
+ // catalog and cost one hint line each instead of a full parameter schema on
403
+ // every request. Allow-listed sessions keep the eager path — their fixed
404
+ // tool expectations predate the catalog, and tool_search isn't allow-listed.
405
+ if (!this.opts.allowedTools && this.settingsManager.get("deferredBuiltinTools")) {
406
+ const { core, deferred } = partitionToolsByTier(this.tools);
407
+ if (deferred.length > 0) {
408
+ // Append-only: `core` preserves the original relative order and
409
+ // tool_search is pushed after it, so the serialized tool block that
410
+ // sits inside the cached prefix stays byte-stable across turns.
411
+ this.tools = core;
412
+ this.deferredBuiltinToolNames = deferred.map((t) => t.name);
413
+ this.mcpCatalog ??= new DeferredToolCatalog();
414
+ this.mcpCatalog.add(deferred);
415
+ this.ensureToolSearchTool();
416
+ }
417
+ }
378
418
  this.rebuildReadTool = rebuildReadTool;
379
419
  this.processManager = processManager;
380
420
  this.lspManager = lspManager;
@@ -394,8 +434,7 @@ export class AgentSession {
394
434
  else {
395
435
  await this.connectMcpServers();
396
436
  }
397
- const basePrompt = this.customSystemPrompt ??
398
- (await buildSystemPrompt(this.cwd, this.skills, false, undefined, this.tools.map((tool) => tool.name), undefined, this.provider, this.promptEnvironment()));
437
+ const basePrompt = await this.buildBasePrompt(false, undefined);
399
438
  this.baseSystemPrompt = basePrompt;
400
439
  this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
401
440
  // Load or create session. Transient sessions (subagent spawns) never
@@ -542,7 +581,9 @@ export class AgentSession {
542
581
  // GLM not configured — skip Z.AI MCP servers
543
582
  }
544
583
  }
545
- let servers = await getAllMcpServers(this.provider, apiKey, this.cwd);
584
+ let servers = await getAllMcpServers(this.provider, apiKey, this.cwd, {
585
+ allowProjectScope: this.settingsManager.isProjectTrusted(this.cwd),
586
+ });
546
587
  // Whitelisted allow-listed session: connect ONLY the named servers, never
547
588
  // the user's full configured set (which could include mutating tools). The
548
589
  // whitelist only restricts in allow-list mode (the documented contract) so
@@ -573,15 +614,25 @@ export class AgentSession {
573
614
  if (this.opts.backgroundMcpConnect && mcpTools.length > 0) {
574
615
  await this.rebuildSystemPromptInPlace();
575
616
  }
617
+ // Detect project-scope servers excluded because the repo isn't trusted,
618
+ // so the host can offer a per-repo "Trust" button. Computed after the
619
+ // connect so the blocked list reflects the same connect attempt.
576
620
  }
577
621
  catch (err) {
578
622
  log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
579
623
  }
580
624
  }
625
+ /** Persist `cwd` as a trusted project for project-scope MCP. Called by the
626
+ * sidecar's `/mcp/add` handler when a user adds a project-scope server via
627
+ * the MCP modal — the explicit add is itself the trust signal. The next
628
+ * session load connects its `.ezcoder/mcp.json` servers. */
629
+ async trustProject(cwd) {
630
+ await this.settingsManager.trustProject(cwd);
631
+ }
581
632
  /**
582
633
  * Route freshly connected MCP tools: deferred into the tool_search catalog
583
- * (default — keeps ~8k tokens of schema out of every cache-miss turn, see
584
- * bench/RESULTS.md bench A) or pushed eagerly when the user opted out.
634
+ * (default — keeps ~8k tokens of schema out of every cache-miss turn) or
635
+ * pushed eagerly when the user opted out.
585
636
  * Allow-listed sessions (Nolan) always get the eager path — their fixed tool
586
637
  * expectations predate the catalog, and tool_search isn't allow-listed.
587
638
  * Promotion pushes onto the live `this.tools` array the running agent loop
@@ -611,10 +662,13 @@ export class AgentSession {
611
662
  * Register `tool_search` once. Promotion of a cached-only entry waits for its
612
663
  * server so the model is told immediately when that capability turns out to
613
664
  * be unreachable, instead of promoting a tool that fails on first call.
665
+ *
666
+ * The catalog is created on demand rather than required up front: deferred
667
+ * built-in tools populate it with zero MCP servers connected, so gating
668
+ * registration on an existing catalog would leave those tools unreachable.
614
669
  */
615
670
  ensureToolSearchTool() {
616
- if (!this.mcpCatalog)
617
- return;
671
+ this.mcpCatalog ??= new DeferredToolCatalog();
618
672
  if (this.tools.some((t) => t.name === "tool_search"))
619
673
  return;
620
674
  this.tools.push(createToolSearchTool(this.mcpCatalog, (promoted) => {
@@ -905,10 +959,14 @@ export class AgentSession {
905
959
  this.reviewCoverage.reset();
906
960
  this.reviewCoverageInjected = 0;
907
961
  this.idealReviewPhase = "idle";
962
+ // No event here: clients reset their own hold on run_start.
963
+ this.idealReviewArmed = false;
964
+ this.idealDriftProbe = null;
908
965
  this.loopBreakInjected = 0;
909
966
  this.regroundingInjected = false;
910
967
  this.runStartedAt = Date.now();
911
968
  this.processGateInjected = 0;
969
+ this.verificationGate.reset();
912
970
  this.compactionOccurred = false;
913
971
  this.originalRequest = originalRequest;
914
972
  }
@@ -947,10 +1005,40 @@ export class AgentSession {
947
1005
  const removed = (diff.match(/^-[^-]/gm) ?? []).length;
948
1006
  this.hookStats.changedLines += added + removed;
949
1007
  }
1008
+ // Verification-gate bookkeeping: successful code mutations and completed
1009
+ // foreground verification commands, in occurrence order.
1010
+ if (!event.isError && args) {
1011
+ if ((name === "edit" || name === "write") &&
1012
+ isCodeFilePath(String(args.file_path ?? ""))) {
1013
+ this.verificationGate.recordMutation(String(args.file_path));
1014
+ }
1015
+ if (name === "bash" &&
1016
+ !args.run_in_background &&
1017
+ isVerificationCommand(String(args.command ?? ""))) {
1018
+ this.verificationGate.recordVerification();
1019
+ }
1020
+ // Reading the final output of an EXITED background verification run
1021
+ // counts: the process gate forces this read anyway, so without it the
1022
+ // gate would demand a redundant foreground re-run of tests the agent
1023
+ // already watched finish.
1024
+ if (name === "task_output") {
1025
+ const proc = this.processManager
1026
+ ?.list()
1027
+ .find((p) => p.id === args.id);
1028
+ if (proc && proc.exitCode !== null && isVerificationCommand(proc.command)) {
1029
+ this.verificationGate.recordVerification();
1030
+ }
1031
+ }
1032
+ }
1033
+ // Tool results are what push the run over the review gate, and they all
1034
+ // land before the model writes its candidate final answer — so this is
1035
+ // the point where arming still beats the draft's first token.
1036
+ this.refreshIdealReviewArmed();
950
1037
  break;
951
1038
  }
952
1039
  case "turn_end":
953
1040
  this.hookStats.turns = event.turn;
1041
+ this.refreshIdealReviewArmed();
954
1042
  for (let index = this.messages.length - 1; index >= 0; index--) {
955
1043
  const anchor = this.messages[index];
956
1044
  if (anchor?.role === "assistant") {
@@ -1134,6 +1222,51 @@ export class AgentSession {
1134
1222
  });
1135
1223
  return granted;
1136
1224
  }
1225
+ /**
1226
+ * Would the stop AFTER the current turn inject the Ideal review? Same inputs
1227
+ * as the pre-stop gate below, evaluated early so clients know a candidate
1228
+ * final answer is a review draft BEFORE it streams.
1229
+ *
1230
+ * The turn count is looked ahead by one on purpose. `hookStats.turns` only
1231
+ * advances at `turn_end`, so while the model is writing the draft the counter
1232
+ * still reads the PREVIOUS turn; the real gate sees one more. Without the
1233
+ * lookahead a run sitting on score 3 crosses to 4 on the draft's own
1234
+ * `turn_end` — after the text already streamed — which is precisely the
1235
+ * appear-then-vanish flash. Over-arming by one turn point costs only live
1236
+ * token streaming on a final answer that then shows whole; under-arming costs
1237
+ * the flash, so this errs toward arming.
1238
+ */
1239
+ wouldInjectIdealReview() {
1240
+ if (this.opts.selfCorrectionHooks === false || this.idealReviewSuppressed)
1241
+ return false;
1242
+ if (this.idealReviewPhase !== "idle")
1243
+ return false;
1244
+ if (!this.settingsManager.get("idealReviewEnabled"))
1245
+ return false;
1246
+ if (evaluateIdealReview({ ...this.hookStats, turns: this.hookStats.turns + 1 }).shouldReview) {
1247
+ return true;
1248
+ }
1249
+ const files = this.hookFileEditCounts.size;
1250
+ if (files === 0)
1251
+ return false;
1252
+ if (this.idealDriftProbe?.files !== files) {
1253
+ this.idealDriftProbe = {
1254
+ files,
1255
+ drifted: detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).length > 0,
1256
+ };
1257
+ }
1258
+ return this.idealDriftProbe.drifted;
1259
+ }
1260
+ /** Broadcast Ideal-review arming on change. Both edges matter: armed=false
1261
+ * after the review fires is what lets a client stream the REVIEWED final
1262
+ * answer live again. */
1263
+ refreshIdealReviewArmed() {
1264
+ const armed = this.wouldInjectIdealReview();
1265
+ if (armed === this.idealReviewArmed)
1266
+ return;
1267
+ this.idealReviewArmed = armed;
1268
+ this.eventBus.emit("hook_armed", { kind: "ideal", armed });
1269
+ }
1137
1270
  /**
1138
1271
  * Pre-stop Ideal review phase machine. Once review starts, completion is
1139
1272
  * blocked until harness-owned post-injection reads cover every changed file.
@@ -1153,6 +1286,18 @@ export class AgentSession {
1153
1286
  });
1154
1287
  return processFollowUp;
1155
1288
  }
1289
+ // Verification gate: code was edited but nothing verified since the last
1290
+ // edit. Above the Ideal review so checks RUN before the read-based review
1291
+ // starts; off for allow-listed sessions that cannot run commands at all.
1292
+ if (this.opts.selfCorrectionHooks !== false &&
1293
+ this.settingsManager.get("verificationGateEnabled") &&
1294
+ (!this.opts.allowedTools || this.opts.allowedTools.includes("bash"))) {
1295
+ const verificationFollowUp = this.verificationGate.followUp();
1296
+ if (verificationFollowUp) {
1297
+ log("INFO", "verification-gate", "Injecting verification follow-up", {});
1298
+ return verificationFollowUp;
1299
+ }
1300
+ }
1156
1301
  if (this.opts.selfCorrectionHooks === false || this.idealReviewSuppressed)
1157
1302
  return null;
1158
1303
  if (this.idealReviewPhase === "reviewing") {
@@ -1202,6 +1347,11 @@ export class AgentSession {
1202
1347
  coverageExpected: coverage.expected,
1203
1348
  coverageMissing: coverage.missing,
1204
1349
  });
1350
+ // Disarm strictly AFTER the hook event. Clients release held text on
1351
+ // disarm, so the reverse order would paint the draft and then delete it —
1352
+ // the exact flash arming exists to prevent. Leaving `idle` is what lets the
1353
+ // reviewed final answer stream live instead of being held.
1354
+ this.refreshIdealReviewArmed();
1205
1355
  log("INFO", "ideal", "Injecting ideal review before final response", {
1206
1356
  coverageExpected: coverage.expected,
1207
1357
  coverageMissing: coverage.missing,
@@ -1271,6 +1421,10 @@ export class AgentSession {
1271
1421
  // Cache for sync callers (see field doc) — kept in step with `creds`
1272
1422
  // through the 401 force-refresh retry below.
1273
1423
  this.lastAccountId = creds.accountId;
1424
+ // The access token most recently handed to the provider. Tracked separately
1425
+ // from `creds` because the per-turn resolver can rotate it mid-run, and the
1426
+ // 401 handler must name the token that was actually rejected.
1427
+ let lastResolvedAccessToken = creds.accessToken;
1274
1428
  // Auto-compact if needed. This must happen after credential resolution so
1275
1429
  // OpenAI OAuth/Codex sessions use the Codex product context window instead
1276
1430
  // of the public API model window. Failed/no-op attempts cool down across
@@ -1332,6 +1486,7 @@ export class AgentSession {
1332
1486
  const userAgent = this.provider === "anthropic" ? await getClaudeCliUserAgent() : undefined;
1333
1487
  const loopMessages = await this.prepareDynamicContext();
1334
1488
  const runAgentLoop = async (apiKey, accountId, projectId) => {
1489
+ lastResolvedAccessToken = apiKey;
1335
1490
  const modelInfo = getModel(this.model);
1336
1491
  const effectiveBaseUrl = this.baseUrl ?? creds.baseUrl;
1337
1492
  const generator = agentLoop(loopMessages, {
@@ -1344,6 +1499,23 @@ export class AgentSession {
1344
1499
  maxTurnExtensions: this.opts.maxTurnExtensions,
1345
1500
  thinking: this.thinkingLevel,
1346
1501
  apiKey,
1502
+ // Per-turn credential resolution. A run can span many minutes; if any
1503
+ // process sharing auth.json refreshes this grant meanwhile, the token
1504
+ // captured above is invalidated server-side and every remaining turn
1505
+ // would fail with an authentication error until the user restarted.
1506
+ // Static API keys resolve to the same value, so this is a no-op for them.
1507
+ resolveCredentials: async () => {
1508
+ const live = await this.authStorage.resolveCredentials(this.provider, {
1509
+ storageKeys: this.currentAuthStorageKeys(),
1510
+ });
1511
+ this.lastAccountId = live.accountId;
1512
+ lastResolvedAccessToken = live.accessToken;
1513
+ return {
1514
+ apiKey: live.accessToken,
1515
+ ...(live.accountId !== undefined ? { accountId: live.accountId } : {}),
1516
+ ...(live.projectId !== undefined ? { projectId: live.projectId } : {}),
1517
+ };
1518
+ },
1347
1519
  baseUrl: effectiveBaseUrl,
1348
1520
  signal: this.opts.signal,
1349
1521
  accountId,
@@ -1507,29 +1679,45 @@ export class AgentSession {
1507
1679
  if (isAbortError(err) || this.opts.signal?.aborted) {
1508
1680
  return;
1509
1681
  }
1510
- // Kimi OAuth plan ran out of usage (hard usage-limit stop, or an HTTP 402
1511
- // billing stop). If the user ALSO configured a Moonshot API key, mark
1512
- // the OAuth credential usage-exhausted (honoring the provider-stated
1513
- // reset time when present) and retry this turn on the API key — OAuth
1514
- // stays the preferred credential and resumes automatically once the mark
1515
- // lapses. A generic 429 is deliberately excluded: it may be a transient
1516
- // rate limit and must not silently switch the user to a billed API key.
1517
- // Guarded on the Kimi managed endpoint actually being in use: if the API
1518
- // key was already active, the same error means BOTH are out and must surface.
1519
- if (this.provider === "moonshot" &&
1682
+ // A subscription OAuth plan ran out of usage (hard usage-limit stop, or an
1683
+ // HTTP 402 billing stop). If the user ALSO configured that provider's API
1684
+ // key, mark the OAuth credential usage-exhausted (honoring the
1685
+ // provider-stated reset time when present) and retry this turn on the API
1686
+ // key — OAuth stays the preferred credential and resumes automatically once
1687
+ // the mark lapses. A generic 429 is deliberately excluded: it may be a
1688
+ // transient rate limit and must not silently switch the user to a billed
1689
+ // API key.
1690
+ //
1691
+ // Grok adds one case Kimi doesn't have: xAI gates its CLI chat proxy by
1692
+ // subscription tier, so an entitled-looking account can be refused outright
1693
+ // with a 403. That means "OAuth cannot serve this", not a transient error,
1694
+ // so it counts as exhausted too — otherwise a dual-configured user would
1695
+ // hard-fail while holding a perfectly good API key.
1696
+ //
1697
+ // Guarded on the subscription endpoint actually being in use: if the API key
1698
+ // was already active, the same error means BOTH are out and must surface.
1699
+ const dualAuth = dualAuthProvider(this.provider);
1700
+ const onSubscriptionEndpoint = (this.provider === "moonshot" && isKimiCodingEndpoint(creds.baseUrl)) ||
1701
+ (this.provider === "xai" && isGrokCliEndpoint(creds.baseUrl));
1702
+ const tierRefusal = this.provider === "xai" && err instanceof ProviderError && err.statusCode === 403;
1703
+ const oauthIsOut = isUsageLimitError(err) ||
1704
+ (err instanceof ProviderError && err.statusCode === 402) ||
1705
+ tierRefusal;
1706
+ if (dualAuth &&
1520
1707
  !this.baseUrl &&
1521
- isKimiCodingEndpoint(creds.baseUrl) &&
1522
- (isUsageLimitError(err) || (err instanceof ProviderError && err.statusCode === 402)) &&
1523
- (await this.authStorage.hasCredentials("moonshot"))) {
1708
+ onSubscriptionEndpoint &&
1709
+ oauthIsOut &&
1710
+ (await this.authStorage.hasCredentials(dualAuth.provider))) {
1524
1711
  const resetsAt = err instanceof ProviderError ? err.resetsAt : undefined;
1525
- await this.authStorage.markUsageExhausted(MOONSHOT_OAUTH_KEY, resetsAt);
1526
- log("WARN", "auth", "Kimi OAuth usage limit reached — retrying this turn on the Moonshot API key", { resetsAt: resetsAt !== undefined ? String(resetsAt) : "unknown" });
1712
+ await this.authStorage.markUsageExhausted(dualAuth.oauthKey, resetsAt);
1713
+ log("WARN", "auth", `${dualAuth.oauthLabel} usage limit reached — retrying this turn on the ${dualAuth.apiKeyLabel}`, { resetsAt: resetsAt !== undefined ? String(resetsAt) : "unknown" });
1527
1714
  creds = await this.authStorage.resolveCredentials(this.provider, {
1528
1715
  storageKeys: this.currentAuthStorageKeys(),
1529
1716
  });
1530
1717
  this.lastAccountId = creds.accountId;
1531
1718
  // The runAgentLoop closure re-reads `creds`, so the retry picks up the
1532
- // API key's baseUrl (api.moonshot.ai) and drops the Kimi coding headers.
1719
+ // API key's public baseUrl (api.moonshot.ai / api.x.ai) and drops the
1720
+ // subscription endpoint's client-identity headers.
1533
1721
  try {
1534
1722
  await runAgentLoop(creds.accessToken, creds.accountId, creds.projectId);
1535
1723
  }
@@ -1544,15 +1732,21 @@ export class AgentSession {
1544
1732
  else if (err instanceof ProviderError && err.statusCode === 401) {
1545
1733
  // Static API-key providers (GLM, Moonshot API key, etc.) have no refresh
1546
1734
  // mechanism — retrying with the same key is pointless. Clear the
1547
- // credential and surface the error so the user re-logins. Kimi OAuth
1548
- // (active for `moonshot` when present) is refreshable, so it falls
1549
- // through to the force-refresh path below.
1735
+ // credential and surface the error so the user re-logins. Subscription
1736
+ // OAuth (active for `moonshot`/`xai` when present) is refreshable, so it
1737
+ // falls through to the force-refresh path below.
1550
1738
  if (await clearInvalidStaticApiKey(err))
1551
1739
  throw err;
1552
1740
  log("INFO", "auth", "Got 401, force-refreshing token and retrying");
1553
1741
  creds = await this.authStorage.resolveCredentials(this.provider, {
1554
1742
  forceRefresh: true,
1555
1743
  storageKeys: this.currentAuthStorageKeys(),
1744
+ // Name the token the provider actually rejected (which the per-turn
1745
+ // resolver may have rotated since the run started). If disk already
1746
+ // holds a newer one, adopt it instead of minting another — a fresh
1747
+ // refresh would invalidate the token every sibling process is using
1748
+ // and turn one 401 into a cascade of them.
1749
+ rejectedToken: lastResolvedAccessToken,
1556
1750
  });
1557
1751
  this.lastAccountId = creds.accountId;
1558
1752
  await runAgentLoop(creds.accessToken, creds.accountId, creds.projectId);
@@ -1666,7 +1860,9 @@ export class AgentSession {
1666
1860
  }
1667
1861
  }
1668
1862
  // Use getAllMcpServers so user-configured servers survive the reconnect.
1669
- const servers = await getAllMcpServers(this.provider, apiKey, this.cwd);
1863
+ const servers = await getAllMcpServers(this.provider, apiKey, this.cwd, {
1864
+ allowProjectScope: this.settingsManager.isProjectTrusted(this.cwd),
1865
+ });
1670
1866
  const mcpTools = await this.mcpManager.connectAll(servers);
1671
1867
  // Drop stale MCP tools from both the live set and deferred catalog before
1672
1868
  // re-adding. Some tools may already have been promoted out of the catalog.
@@ -1934,8 +2130,7 @@ export class AgentSession {
1934
2130
  this.autopilotMarkers = [];
1935
2131
  this.appMarkers = [];
1936
2132
  this.turnMetrics = [];
1937
- const basePrompt = this.customSystemPrompt ??
1938
- (await buildSystemPrompt(this.cwd, this.skills, false, undefined, this.tools.map((tool) => tool.name), undefined, this.provider, this.promptEnvironment()));
2133
+ const basePrompt = await this.buildBasePrompt(false, undefined);
1939
2134
  this.baseSystemPrompt = basePrompt;
1940
2135
  this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
1941
2136
  // Fresh conversation — new entries must not chain onto the old DAG's leaf.
@@ -2070,6 +2265,9 @@ export class AgentSession {
2070
2265
  this.idealReviewPhase = "idle";
2071
2266
  this.reviewCoverage.reset();
2072
2267
  }
2268
+ // Suppression flips mid-run (autopilot takes over verification), so a client
2269
+ // holding a draft under a stale arming must be released.
2270
+ this.refreshIdealReviewArmed();
2073
2271
  }
2074
2272
  /** Queue a user message (optionally with attachments) to be injected mid-run
2075
2273
  * as steering. Returns the new queue length. No-op semantics are the caller's
@@ -2217,6 +2415,17 @@ export class AgentSession {
2217
2415
  await this.rebuildSystemPromptInPlace();
2218
2416
  return { ok: true, root: resolved };
2219
2417
  }
2418
+ /**
2419
+ * Names to advertise as available-on-demand. A tool the model already
2420
+ * promoted lives in `this.tools` and carries its own schema, so it drops out
2421
+ * of the index rather than being listed twice.
2422
+ */
2423
+ deferredToolNamesForPrompt(liveNames) {
2424
+ if (this.deferredBuiltinToolNames.length === 0)
2425
+ return [];
2426
+ const live = new Set(liveNames);
2427
+ return this.deferredBuiltinToolNames.filter((name) => !live.has(name));
2428
+ }
2220
2429
  /** Environment facts that vary per session rather than per host. */
2221
2430
  promptEnvironment() {
2222
2431
  const networkAllow = this.settingsManager.get("networkMode") === "allowlist"
@@ -2224,11 +2433,37 @@ export class AgentSession {
2224
2433
  : [];
2225
2434
  return { additionalRoots: this.additionalRoots, networkAllow };
2226
2435
  }
2436
+ /**
2437
+ * Build the stable system-prompt prefix for the current tool set and state.
2438
+ *
2439
+ * Three modes, in precedence order: a full replacement (`systemPrompt`), a
2440
+ * composed sub-agent prompt (`agentPrompt` — agent body plus Tools, project
2441
+ * context, return contract and Environment), or the standard prompt.
2442
+ */
2443
+ async buildBasePrompt(planMode, approvedPlanPath) {
2444
+ if (this.customSystemPrompt)
2445
+ return this.customSystemPrompt;
2446
+ const toolNames = this.tools.map((tool) => tool.name);
2447
+ const deferredToolNames = this.deferredToolNamesForPrompt(toolNames);
2448
+ if (this.agentPrompt !== undefined) {
2449
+ return buildSubAgentSystemPrompt(this.agentPrompt, {
2450
+ cwd: this.cwd,
2451
+ toolNames,
2452
+ deferredToolNames,
2453
+ context: this.opts.agentContext,
2454
+ environment: this.promptEnvironment(),
2455
+ });
2456
+ }
2457
+ return buildSystemPrompt(this.cwd, this.skills, planMode, approvedPlanPath, toolNames, undefined, this.provider, this.promptEnvironment(), deferredToolNames);
2458
+ }
2227
2459
  /** Rebuild messages[0] from current plan-mode + approved-plan state. */
2228
2460
  async rebuildSystemPromptInPlace() {
2461
+ // A full replacement is verbatim by contract — there is nothing to rebuild.
2462
+ // A composed agent prompt still must be: its Tools section has to follow
2463
+ // late-arriving MCP tools, or a compacted child loses tools it can call.
2229
2464
  if (this.customSystemPrompt)
2230
2465
  return;
2231
- const rebuilt = await buildSystemPrompt(this.cwd, this.skills, this.planModeRef.current, this.approvedPlanPath, this.tools.map((tool) => tool.name), undefined, this.provider, this.promptEnvironment());
2466
+ const rebuilt = await this.buildBasePrompt(this.planModeRef.current, this.approvedPlanPath);
2232
2467
  this.baseSystemPrompt = rebuilt;
2233
2468
  const content = this.withSystemPromptTail(rebuilt);
2234
2469
  if (this.messages[0]?.role === "system") {