@prestyj/cli 5.10.0 → 5.12.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 (703) hide show
  1. package/README.md +3 -4
  2. package/assets/skills/compliance-guard/SKILL.md +251 -0
  3. package/assets/skills/compliance-guard/references/artifacts.md +186 -0
  4. package/assets/skills/compliance-guard/references/eu-uk.md +151 -0
  5. package/assets/skills/compliance-guard/references/exposure-triage.md +161 -0
  6. package/assets/skills/compliance-guard/references/lawsuit-vectors.md +141 -0
  7. package/assets/skills/compliance-guard/references/provenance.md +42 -0
  8. package/assets/skills/compliance-guard/references/sector-gates.md +173 -0
  9. package/assets/skills/compliance-guard/references/security-baseline.md +130 -0
  10. package/assets/skills/compliance-guard/references/trigger-map.md +163 -0
  11. package/assets/skills/compliance-guard/references/us.md +164 -0
  12. package/dist/app-sidecar-auth-broadcast.test.d.ts +2 -0
  13. package/dist/app-sidecar-auth-broadcast.test.d.ts.map +1 -0
  14. package/dist/app-sidecar-auth-broadcast.test.js +348 -0
  15. package/dist/app-sidecar-auth-broadcast.test.js.map +1 -0
  16. package/dist/app-sidecar-sessions.d.ts +5 -2
  17. package/dist/app-sidecar-sessions.d.ts.map +1 -1
  18. package/dist/app-sidecar-sessions.js +34 -8
  19. package/dist/app-sidecar-sessions.js.map +1 -1
  20. package/dist/app-sidecar-sessions.test.js +96 -1
  21. package/dist/app-sidecar-sessions.test.js.map +1 -1
  22. package/dist/app-sidecar.js +616 -173
  23. package/dist/app-sidecar.js.map +1 -1
  24. package/dist/chat-agents/jiwa.test.js +1 -1
  25. package/dist/cli/auth.d.ts.map +1 -1
  26. package/dist/cli/auth.js +33 -36
  27. package/dist/cli/auth.js.map +1 -1
  28. package/dist/cli/command-routing.d.ts +1 -1
  29. package/dist/cli/command-routing.d.ts.map +1 -1
  30. package/dist/cli/command-routing.js +1 -0
  31. package/dist/cli/command-routing.js.map +1 -1
  32. package/dist/cli/command-routing.test.js +1 -0
  33. package/dist/cli/command-routing.test.js.map +1 -1
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/cli.js +179 -48
  36. package/dist/cli.js.map +1 -1
  37. package/dist/config.d.ts +18 -5
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +45 -76
  40. package/dist/config.js.map +1 -1
  41. package/dist/config.test.js +30 -13
  42. package/dist/config.test.js.map +1 -1
  43. package/dist/core/agent-notifications.d.ts +62 -0
  44. package/dist/core/agent-notifications.d.ts.map +1 -0
  45. package/dist/core/agent-notifications.js +92 -0
  46. package/dist/core/agent-notifications.js.map +1 -0
  47. package/dist/core/agent-notifications.test.d.ts +2 -0
  48. package/dist/core/agent-notifications.test.d.ts.map +1 -0
  49. package/dist/core/agent-notifications.test.js +97 -0
  50. package/dist/core/agent-notifications.test.js.map +1 -0
  51. package/dist/core/agent-session-checkpoint-flush.test.d.ts +2 -0
  52. package/dist/core/agent-session-checkpoint-flush.test.d.ts.map +1 -0
  53. package/dist/core/agent-session-checkpoint-flush.test.js +201 -0
  54. package/dist/core/agent-session-checkpoint-flush.test.js.map +1 -0
  55. package/dist/core/agent-session-compaction.test.js +323 -6
  56. package/dist/core/agent-session-compaction.test.js.map +1 -1
  57. package/dist/core/agent-session-crash-durability.test.d.ts +2 -0
  58. package/dist/core/agent-session-crash-durability.test.d.ts.map +1 -0
  59. package/dist/core/agent-session-crash-durability.test.js +102 -0
  60. package/dist/core/agent-session-crash-durability.test.js.map +1 -0
  61. package/dist/core/agent-session-marker-anchors.test.js +181 -2
  62. package/dist/core/agent-session-marker-anchors.test.js.map +1 -1
  63. package/dist/core/agent-session-model-switch.test.d.ts +2 -0
  64. package/dist/core/agent-session-model-switch.test.d.ts.map +1 -0
  65. package/dist/core/agent-session-model-switch.test.js +161 -0
  66. package/dist/core/agent-session-model-switch.test.js.map +1 -0
  67. package/dist/core/agent-session-notifications.test.d.ts +2 -0
  68. package/dist/core/agent-session-notifications.test.d.ts.map +1 -0
  69. package/dist/core/agent-session-notifications.test.js +114 -0
  70. package/dist/core/agent-session-notifications.test.js.map +1 -0
  71. package/dist/core/agent-session-process-gate.test.d.ts +2 -0
  72. package/dist/core/agent-session-process-gate.test.d.ts.map +1 -0
  73. package/dist/core/agent-session-process-gate.test.js +98 -0
  74. package/dist/core/agent-session-process-gate.test.js.map +1 -0
  75. package/dist/core/agent-session-queue.test.js +65 -0
  76. package/dist/core/agent-session-queue.test.js.map +1 -1
  77. package/dist/core/agent-session-review-coverage.test.js +80 -23
  78. package/dist/core/agent-session-review-coverage.test.js.map +1 -1
  79. package/dist/core/agent-session-slash-restore.test.d.ts +2 -0
  80. package/dist/core/agent-session-slash-restore.test.d.ts.map +1 -0
  81. package/dist/core/agent-session-slash-restore.test.js +121 -0
  82. package/dist/core/agent-session-slash-restore.test.js.map +1 -0
  83. package/dist/core/agent-session-tool-tiers.test.d.ts +2 -0
  84. package/dist/core/agent-session-tool-tiers.test.d.ts.map +1 -0
  85. package/dist/core/agent-session-tool-tiers.test.js +138 -0
  86. package/dist/core/agent-session-tool-tiers.test.js.map +1 -0
  87. package/dist/core/agent-session.d.ts +257 -13
  88. package/dist/core/agent-session.d.ts.map +1 -1
  89. package/dist/core/agent-session.js +1118 -241
  90. package/dist/core/agent-session.js.map +1 -1
  91. package/dist/core/agents.d.ts +29 -1
  92. package/dist/core/agents.d.ts.map +1 -1
  93. package/dist/core/agents.js +47 -97
  94. package/dist/core/agents.js.map +1 -1
  95. package/dist/core/agents.test.js +87 -7
  96. package/dist/core/agents.test.js.map +1 -1
  97. package/dist/core/api-benchmark.d.ts +3 -3
  98. package/dist/core/api-benchmark.js +7 -7
  99. package/dist/core/auth-providers.d.ts +37 -1
  100. package/dist/core/auth-providers.d.ts.map +1 -1
  101. package/dist/core/auth-providers.js +83 -3
  102. package/dist/core/auth-providers.js.map +1 -1
  103. package/dist/core/auto-update.test.js +1 -1
  104. package/dist/core/autopilot-gate.d.ts +2 -2
  105. package/dist/core/autopilot-gate.js +1 -1
  106. package/dist/core/bash-spawn-benchmark.js +6 -6
  107. package/dist/core/bundled-agents.d.ts +3 -0
  108. package/dist/core/bundled-agents.d.ts.map +1 -0
  109. package/dist/core/bundled-agents.js +252 -0
  110. package/dist/core/bundled-agents.js.map +1 -0
  111. package/dist/core/cache-warm-benchmark.d.ts +2 -2
  112. package/dist/core/cache-warm-benchmark.js +5 -5
  113. package/dist/core/code-retrieval-chunkers.d.ts +27 -0
  114. package/dist/core/code-retrieval-chunkers.d.ts.map +1 -0
  115. package/dist/core/code-retrieval-chunkers.js +264 -0
  116. package/dist/core/code-retrieval-chunkers.js.map +1 -0
  117. package/dist/core/code-retrieval-chunkers.test.d.ts +2 -0
  118. package/dist/core/code-retrieval-chunkers.test.d.ts.map +1 -0
  119. package/dist/core/code-retrieval-chunkers.test.js +203 -0
  120. package/dist/core/code-retrieval-chunkers.test.js.map +1 -0
  121. package/dist/core/code-retrieval.d.ts +6 -0
  122. package/dist/core/code-retrieval.d.ts.map +1 -1
  123. package/dist/core/code-retrieval.js +38 -1
  124. package/dist/core/code-retrieval.js.map +1 -1
  125. package/dist/core/compaction/active-context.test.js +21 -0
  126. package/dist/core/compaction/active-context.test.js.map +1 -1
  127. package/dist/core/compaction/compactor.d.ts +108 -8
  128. package/dist/core/compaction/compactor.d.ts.map +1 -1
  129. package/dist/core/compaction/compactor.js +423 -119
  130. package/dist/core/compaction/compactor.js.map +1 -1
  131. package/dist/core/compaction/compactor.test.js +360 -8
  132. package/dist/core/compaction/compactor.test.js.map +1 -1
  133. package/dist/core/compaction/policy.d.ts +17 -0
  134. package/dist/core/compaction/policy.d.ts.map +1 -0
  135. package/dist/core/compaction/policy.js +22 -0
  136. package/dist/core/compaction/policy.js.map +1 -0
  137. package/dist/core/compaction/policy.test.d.ts +2 -0
  138. package/dist/core/compaction/policy.test.d.ts.map +1 -0
  139. package/dist/core/compaction/policy.test.js +35 -0
  140. package/dist/core/compaction/policy.test.js.map +1 -0
  141. package/dist/core/compaction/query-aware-selector.d.ts +33 -0
  142. package/dist/core/compaction/query-aware-selector.d.ts.map +1 -0
  143. package/dist/core/compaction/query-aware-selector.js +202 -0
  144. package/dist/core/compaction/query-aware-selector.js.map +1 -0
  145. package/dist/core/compaction/query-aware-selector.test.d.ts +2 -0
  146. package/dist/core/compaction/query-aware-selector.test.d.ts.map +1 -0
  147. package/dist/core/compaction/query-aware-selector.test.js +96 -0
  148. package/dist/core/compaction/query-aware-selector.test.js.map +1 -0
  149. package/dist/core/compaction/tool-result-pruner.d.ts +24 -11
  150. package/dist/core/compaction/tool-result-pruner.d.ts.map +1 -1
  151. package/dist/core/compaction/tool-result-pruner.js +98 -26
  152. package/dist/core/compaction/tool-result-pruner.js.map +1 -1
  153. package/dist/core/compaction/tool-result-pruner.test.js +178 -6
  154. package/dist/core/compaction/tool-result-pruner.test.js.map +1 -1
  155. package/dist/core/encode-cwd.test.js +1 -1
  156. package/dist/core/event-bus.d.ts +23 -0
  157. package/dist/core/event-bus.d.ts.map +1 -1
  158. package/dist/core/event-bus.js +10 -0
  159. package/dist/core/event-bus.js.map +1 -1
  160. package/dist/core/extensions/loader.d.ts.map +1 -1
  161. package/dist/core/extensions/loader.js +28 -8
  162. package/dist/core/extensions/loader.js.map +1 -1
  163. package/dist/core/extensions/plugin-bundles.d.ts +23 -0
  164. package/dist/core/extensions/plugin-bundles.d.ts.map +1 -0
  165. package/dist/core/extensions/plugin-bundles.js +228 -0
  166. package/dist/core/extensions/plugin-bundles.js.map +1 -0
  167. package/dist/core/extensions/plugin-bundles.test.d.ts +2 -0
  168. package/dist/core/extensions/plugin-bundles.test.d.ts.map +1 -0
  169. package/dist/core/extensions/plugin-bundles.test.js +110 -0
  170. package/dist/core/extensions/plugin-bundles.test.js.map +1 -0
  171. package/dist/core/fast-apply-benchmark.d.ts +2 -2
  172. package/dist/core/fast-apply-benchmark.js +6 -6
  173. package/dist/core/foreign-session-import.d.ts +120 -0
  174. package/dist/core/foreign-session-import.d.ts.map +1 -0
  175. package/dist/core/foreign-session-import.js +598 -0
  176. package/dist/core/foreign-session-import.js.map +1 -0
  177. package/dist/core/foreign-session-import.test.d.ts +2 -0
  178. package/dist/core/foreign-session-import.test.d.ts.map +1 -0
  179. package/dist/core/foreign-session-import.test.js +220 -0
  180. package/dist/core/foreign-session-import.test.js.map +1 -0
  181. package/dist/core/foreign-transcript-blocks.d.ts +51 -0
  182. package/dist/core/foreign-transcript-blocks.d.ts.map +1 -0
  183. package/dist/core/foreign-transcript-blocks.js +151 -0
  184. package/dist/core/foreign-transcript-blocks.js.map +1 -0
  185. package/dist/core/goal-lifecycle-smoke.test.js +2 -2
  186. package/dist/core/goal-store.js +1 -1
  187. package/dist/core/goal-store.test.js +2 -2
  188. package/dist/core/goal-verifier.test.js +2 -2
  189. package/dist/core/goal-worker-dev-server-lifecycle.test.js +1 -1
  190. package/dist/core/goal-worker.test.js +2 -2
  191. package/dist/core/hashline-edit-benchmark.d.ts +2 -2
  192. package/dist/core/hashline-edit-benchmark.js +7 -7
  193. package/dist/core/ideal-review.d.ts +20 -1
  194. package/dist/core/ideal-review.d.ts.map +1 -1
  195. package/dist/core/ideal-review.js +37 -2
  196. package/dist/core/ideal-review.js.map +1 -1
  197. package/dist/core/ideal-review.test.js +99 -3
  198. package/dist/core/ideal-review.test.js.map +1 -1
  199. package/dist/core/local-endpoint-store.test.js +1 -1
  200. package/dist/core/logger.d.ts +1 -1
  201. package/dist/core/logger.js +2 -2
  202. package/dist/core/logger.js.map +1 -1
  203. package/dist/core/loop-breaker.d.ts.map +1 -1
  204. package/dist/core/loop-breaker.js +1 -0
  205. package/dist/core/loop-breaker.js.map +1 -1
  206. package/dist/core/lsp/client-navigation.test.d.ts +2 -0
  207. package/dist/core/lsp/client-navigation.test.d.ts.map +1 -0
  208. package/dist/core/lsp/client-navigation.test.js +135 -0
  209. package/dist/core/lsp/client-navigation.test.js.map +1 -0
  210. package/dist/core/lsp/client.d.ts +84 -0
  211. package/dist/core/lsp/client.d.ts.map +1 -1
  212. package/dist/core/lsp/client.js +190 -3
  213. package/dist/core/lsp/client.js.map +1 -1
  214. package/dist/core/lsp/integration.test.js +2 -2
  215. package/dist/core/lsp/manager-navigation.test.d.ts +2 -0
  216. package/dist/core/lsp/manager-navigation.test.d.ts.map +1 -0
  217. package/dist/core/lsp/manager-navigation.test.js +121 -0
  218. package/dist/core/lsp/manager-navigation.test.js.map +1 -0
  219. package/dist/core/lsp/manager.d.ts +81 -9
  220. package/dist/core/lsp/manager.d.ts.map +1 -1
  221. package/dist/core/lsp/manager.js +183 -64
  222. package/dist/core/lsp/manager.js.map +1 -1
  223. package/dist/core/lsp/manager.test.js +18 -0
  224. package/dist/core/lsp/manager.test.js.map +1 -1
  225. package/dist/core/lsp/pool.d.ts +113 -0
  226. package/dist/core/lsp/pool.d.ts.map +1 -0
  227. package/dist/core/lsp/pool.js +255 -0
  228. package/dist/core/lsp/pool.js.map +1 -0
  229. package/dist/core/lsp/pool.test.d.ts +2 -0
  230. package/dist/core/lsp/pool.test.d.ts.map +1 -0
  231. package/dist/core/lsp/pool.test.js +286 -0
  232. package/dist/core/lsp/pool.test.js.map +1 -0
  233. package/dist/core/lsp/servers.d.ts.map +1 -1
  234. package/dist/core/lsp/servers.js +43 -3
  235. package/dist/core/lsp/servers.js.map +1 -1
  236. package/dist/core/lsp/servers.test.d.ts +2 -0
  237. package/dist/core/lsp/servers.test.d.ts.map +1 -0
  238. package/dist/core/lsp/servers.test.js +67 -0
  239. package/dist/core/lsp/servers.test.js.map +1 -0
  240. package/dist/core/lsp/windows.test.js +6 -18
  241. package/dist/core/lsp/windows.test.js.map +1 -1
  242. package/dist/core/mcp/catalog-cache.d.ts +63 -0
  243. package/dist/core/mcp/catalog-cache.d.ts.map +1 -0
  244. package/dist/core/mcp/catalog-cache.js +163 -0
  245. package/dist/core/mcp/catalog-cache.js.map +1 -0
  246. package/dist/core/mcp/catalog-cache.test.d.ts +2 -0
  247. package/dist/core/mcp/catalog-cache.test.d.ts.map +1 -0
  248. package/dist/core/mcp/catalog-cache.test.js +99 -0
  249. package/dist/core/mcp/catalog-cache.test.js.map +1 -0
  250. package/dist/core/mcp/client-session-recovery.test.d.ts +2 -0
  251. package/dist/core/mcp/client-session-recovery.test.d.ts.map +1 -0
  252. package/dist/core/mcp/client-session-recovery.test.js +231 -0
  253. package/dist/core/mcp/client-session-recovery.test.js.map +1 -0
  254. package/dist/core/mcp/client.d.ts +158 -0
  255. package/dist/core/mcp/client.d.ts.map +1 -1
  256. package/dist/core/mcp/client.js +478 -45
  257. package/dist/core/mcp/client.js.map +1 -1
  258. package/dist/core/mcp/content.d.ts +19 -0
  259. package/dist/core/mcp/content.d.ts.map +1 -0
  260. package/dist/core/mcp/content.js +172 -0
  261. package/dist/core/mcp/content.js.map +1 -0
  262. package/dist/core/mcp/content.test.d.ts +2 -0
  263. package/dist/core/mcp/content.test.d.ts.map +1 -0
  264. package/dist/core/mcp/content.test.js +135 -0
  265. package/dist/core/mcp/content.test.js.map +1 -0
  266. package/dist/core/mcp/deferred-catalog.d.ts +1 -1
  267. package/dist/core/mcp/deferred-catalog.js +1 -1
  268. package/dist/core/mcp/elicitation-bridge.d.ts +41 -0
  269. package/dist/core/mcp/elicitation-bridge.d.ts.map +1 -0
  270. package/dist/core/mcp/elicitation-bridge.js +52 -0
  271. package/dist/core/mcp/elicitation-bridge.js.map +1 -0
  272. package/dist/core/mcp/elicitation.test.d.ts +2 -0
  273. package/dist/core/mcp/elicitation.test.d.ts.map +1 -0
  274. package/dist/core/mcp/elicitation.test.js +158 -0
  275. package/dist/core/mcp/elicitation.test.js.map +1 -0
  276. package/dist/core/mcp/index.d.ts +5 -1
  277. package/dist/core/mcp/index.d.ts.map +1 -1
  278. package/dist/core/mcp/index.js +2 -0
  279. package/dist/core/mcp/index.js.map +1 -1
  280. package/dist/core/mcp/legacy-negotiation.test.d.ts +2 -0
  281. package/dist/core/mcp/legacy-negotiation.test.d.ts.map +1 -0
  282. package/dist/core/mcp/legacy-negotiation.test.js +80 -0
  283. package/dist/core/mcp/legacy-negotiation.test.js.map +1 -0
  284. package/dist/core/mcp/oauth-provider.d.ts +1 -2
  285. package/dist/core/mcp/oauth-provider.d.ts.map +1 -1
  286. package/dist/core/mcp/oauth-provider.js +6 -2
  287. package/dist/core/mcp/oauth-provider.js.map +1 -1
  288. package/dist/core/mcp/oauth-store.d.ts +1 -1
  289. package/dist/core/mcp/oauth-store.d.ts.map +1 -1
  290. package/dist/core/mcp/oauth-store.js.map +1 -1
  291. package/dist/core/mcp/oauth-store.test.js +2 -2
  292. package/dist/core/mcp/resolve-stdio.test.js +5 -5
  293. package/dist/core/mcp/session-elicitation-wiring.test.d.ts +2 -0
  294. package/dist/core/mcp/session-elicitation-wiring.test.d.ts.map +1 -0
  295. package/dist/core/mcp/session-elicitation-wiring.test.js +138 -0
  296. package/dist/core/mcp/session-elicitation-wiring.test.js.map +1 -0
  297. package/dist/core/mcp/shared-pool.d.ts +158 -0
  298. package/dist/core/mcp/shared-pool.d.ts.map +1 -0
  299. package/dist/core/mcp/shared-pool.js +235 -0
  300. package/dist/core/mcp/shared-pool.js.map +1 -0
  301. package/dist/core/mcp/shared-pool.test.d.ts +2 -0
  302. package/dist/core/mcp/shared-pool.test.d.ts.map +1 -0
  303. package/dist/core/mcp/shared-pool.test.js +425 -0
  304. package/dist/core/mcp/shared-pool.test.js.map +1 -0
  305. package/dist/core/mcp/store.test.js +5 -5
  306. package/dist/core/mcp/types.d.ts +12 -0
  307. package/dist/core/mcp/types.d.ts.map +1 -1
  308. package/dist/core/mcp/when-connected.test.d.ts +2 -0
  309. package/dist/core/mcp/when-connected.test.d.ts.map +1 -0
  310. package/dist/core/mcp/when-connected.test.js +46 -0
  311. package/dist/core/mcp/when-connected.test.js.map +1 -0
  312. package/dist/core/nolan-context.d.ts.map +1 -1
  313. package/dist/core/nolan-context.js +8 -0
  314. package/dist/core/nolan-context.js.map +1 -1
  315. package/dist/core/nolan-context.test.js +40 -0
  316. package/dist/core/nolan-context.test.js.map +1 -1
  317. package/dist/core/nolan-model.test.js +1 -1
  318. package/dist/core/nolan-prompt.js +8 -5
  319. package/dist/core/nolan-prompt.js.map +1 -1
  320. package/dist/core/nolan-prompt.test.js +5 -0
  321. package/dist/core/nolan-prompt.test.js.map +1 -1
  322. package/dist/core/oauth/xai.d.ts +2 -0
  323. package/dist/core/oauth/xai.d.ts.map +1 -0
  324. package/dist/core/oauth/xai.js +4 -0
  325. package/dist/core/oauth/xai.js.map +1 -0
  326. package/dist/core/persistent-shell.d.ts +3 -2
  327. package/dist/core/persistent-shell.d.ts.map +1 -1
  328. package/dist/core/persistent-shell.js +11 -4
  329. package/dist/core/persistent-shell.js.map +1 -1
  330. package/dist/core/persistent-shell.test.js +6 -6
  331. package/dist/core/process-gate.d.ts +45 -0
  332. package/dist/core/process-gate.d.ts.map +1 -0
  333. package/dist/core/process-gate.js +79 -0
  334. package/dist/core/process-gate.js.map +1 -0
  335. package/dist/core/process-gate.test.d.ts +2 -0
  336. package/dist/core/process-gate.test.d.ts.map +1 -0
  337. package/dist/core/process-gate.test.js +87 -0
  338. package/dist/core/process-gate.test.js.map +1 -0
  339. package/dist/core/process-manager-dev-server-repro.test.js +51 -12
  340. package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
  341. package/dist/core/process-manager-notifications.test.d.ts +2 -0
  342. package/dist/core/process-manager-notifications.test.d.ts.map +1 -0
  343. package/dist/core/process-manager-notifications.test.js +227 -0
  344. package/dist/core/process-manager-notifications.test.js.map +1 -0
  345. package/dist/core/process-manager.d.ts +72 -1
  346. package/dist/core/process-manager.d.ts.map +1 -1
  347. package/dist/core/process-manager.js +247 -4
  348. package/dist/core/process-manager.js.map +1 -1
  349. package/dist/core/progress/git-xp.test.js +2 -2
  350. package/dist/core/progress/ranks.d.ts +14 -3
  351. package/dist/core/progress/ranks.d.ts.map +1 -1
  352. package/dist/core/progress/ranks.js +181 -12
  353. package/dist/core/progress/ranks.js.map +1 -1
  354. package/dist/core/progress/ranks.test.js +48 -7
  355. package/dist/core/progress/ranks.test.js.map +1 -1
  356. package/dist/core/progress/rebuild.test.js +1 -1
  357. package/dist/core/progress/store.test.js +1 -1
  358. package/dist/core/progress/types.d.ts +2 -1
  359. package/dist/core/progress/types.d.ts.map +1 -1
  360. package/dist/core/project-discovery.d.ts +31 -6
  361. package/dist/core/project-discovery.d.ts.map +1 -1
  362. package/dist/core/project-discovery.js +349 -79
  363. package/dist/core/project-discovery.js.map +1 -1
  364. package/dist/core/project-discovery.test.js +128 -4
  365. package/dist/core/project-discovery.test.js.map +1 -1
  366. package/dist/core/prompt-commands.js +3 -3
  367. package/dist/core/radio.d.ts +1 -1
  368. package/dist/core/radio.d.ts.map +1 -1
  369. package/dist/core/radio.js +90 -6
  370. package/dist/core/radio.js.map +1 -1
  371. package/dist/core/radio.test.js +15 -2
  372. package/dist/core/radio.test.js.map +1 -1
  373. package/dist/core/regrounding.d.ts.map +1 -1
  374. package/dist/core/regrounding.js +1 -0
  375. package/dist/core/regrounding.js.map +1 -1
  376. package/dist/core/run-claim.d.ts +37 -0
  377. package/dist/core/run-claim.d.ts.map +1 -0
  378. package/dist/core/run-claim.js +46 -0
  379. package/dist/core/run-claim.js.map +1 -0
  380. package/dist/core/run-claim.test.d.ts +2 -0
  381. package/dist/core/run-claim.test.d.ts.map +1 -0
  382. package/dist/core/run-claim.test.js +107 -0
  383. package/dist/core/run-claim.test.js.map +1 -0
  384. package/dist/core/run-journal.test.d.ts +2 -0
  385. package/dist/core/run-journal.test.d.ts.map +1 -0
  386. package/dist/core/run-journal.test.js +195 -0
  387. package/dist/core/run-journal.test.js.map +1 -0
  388. package/dist/core/run-lifecycle.d.ts +22 -3
  389. package/dist/core/run-lifecycle.d.ts.map +1 -1
  390. package/dist/core/run-lifecycle.js +12 -3
  391. package/dist/core/run-lifecycle.js.map +1 -1
  392. package/dist/core/sandbox-domains.d.ts +14 -0
  393. package/dist/core/sandbox-domains.d.ts.map +1 -0
  394. package/dist/core/sandbox-domains.js +76 -0
  395. package/dist/core/sandbox-domains.js.map +1 -0
  396. package/dist/core/sandbox-feedback.d.ts +22 -0
  397. package/dist/core/sandbox-feedback.d.ts.map +1 -0
  398. package/dist/core/sandbox-feedback.js +69 -0
  399. package/dist/core/sandbox-feedback.js.map +1 -0
  400. package/dist/core/sandbox-feedback.test.d.ts +2 -0
  401. package/dist/core/sandbox-feedback.test.d.ts.map +1 -0
  402. package/dist/core/sandbox-feedback.test.js +32 -0
  403. package/dist/core/sandbox-feedback.test.js.map +1 -0
  404. package/dist/core/sandbox.d.ts +91 -0
  405. package/dist/core/sandbox.d.ts.map +1 -0
  406. package/dist/core/sandbox.js +254 -0
  407. package/dist/core/sandbox.js.map +1 -0
  408. package/dist/core/sandbox.test.d.ts +2 -0
  409. package/dist/core/sandbox.test.d.ts.map +1 -0
  410. package/dist/core/sandbox.test.js +157 -0
  411. package/dist/core/sandbox.test.js.map +1 -0
  412. package/dist/core/semantic-search-benchmark.d.ts +2 -2
  413. package/dist/core/semantic-search-benchmark.js +5 -5
  414. package/dist/core/session-compaction.d.ts +6 -0
  415. package/dist/core/session-compaction.d.ts.map +1 -1
  416. package/dist/core/session-compaction.js +11 -1
  417. package/dist/core/session-compaction.js.map +1 -1
  418. package/dist/core/session-compaction.test.js +19 -2
  419. package/dist/core/session-compaction.test.js.map +1 -1
  420. package/dist/core/session-export.d.ts.map +1 -1
  421. package/dist/core/session-export.js +12 -4
  422. package/dist/core/session-export.js.map +1 -1
  423. package/dist/core/session-history.d.ts +95 -6
  424. package/dist/core/session-history.d.ts.map +1 -1
  425. package/dist/core/session-history.js +261 -17
  426. package/dist/core/session-history.js.map +1 -1
  427. package/dist/core/session-history.test.js +173 -2
  428. package/dist/core/session-history.test.js.map +1 -1
  429. package/dist/core/session-manager.d.ts +223 -6
  430. package/dist/core/session-manager.d.ts.map +1 -1
  431. package/dist/core/session-manager.js +579 -74
  432. package/dist/core/session-manager.js.map +1 -1
  433. package/dist/core/session-manager.test.js +154 -10
  434. package/dist/core/session-manager.test.js.map +1 -1
  435. package/dist/core/session-notification-visibility.test.d.ts +2 -0
  436. package/dist/core/session-notification-visibility.test.d.ts.map +1 -0
  437. package/dist/core/session-notification-visibility.test.js +113 -0
  438. package/dist/core/session-notification-visibility.test.js.map +1 -0
  439. package/dist/core/session-preview.d.ts +5 -4
  440. package/dist/core/session-preview.d.ts.map +1 -1
  441. package/dist/core/session-preview.js +12 -5
  442. package/dist/core/session-preview.js.map +1 -1
  443. package/dist/core/session-restore-display.test.js +25 -0
  444. package/dist/core/session-restore-display.test.js.map +1 -1
  445. package/dist/core/session-storage-fd-leak.test.d.ts +2 -0
  446. package/dist/core/session-storage-fd-leak.test.d.ts.map +1 -0
  447. package/dist/core/session-storage-fd-leak.test.js +116 -0
  448. package/dist/core/session-storage-fd-leak.test.js.map +1 -0
  449. package/dist/core/session-storage.d.ts +22 -2
  450. package/dist/core/session-storage.d.ts.map +1 -1
  451. package/dist/core/session-storage.js +72 -9
  452. package/dist/core/session-storage.js.map +1 -1
  453. package/dist/core/session-storage.test.js +8 -3
  454. package/dist/core/session-storage.test.js.map +1 -1
  455. package/dist/core/settings-manager.d.ts +10 -2
  456. package/dist/core/settings-manager.d.ts.map +1 -1
  457. package/dist/core/settings-manager.js +36 -1
  458. package/dist/core/settings-manager.js.map +1 -1
  459. package/dist/core/shell-path.js +1 -1
  460. package/dist/core/shell.d.ts +1 -1
  461. package/dist/core/shell.js +2 -2
  462. package/dist/core/shell.test.js +2 -2
  463. package/dist/core/shutdown.d.ts +71 -0
  464. package/dist/core/shutdown.d.ts.map +1 -0
  465. package/dist/core/shutdown.js +144 -0
  466. package/dist/core/shutdown.js.map +1 -0
  467. package/dist/core/shutdown.test.d.ts +2 -0
  468. package/dist/core/shutdown.test.d.ts.map +1 -0
  469. package/dist/core/shutdown.test.js +160 -0
  470. package/dist/core/shutdown.test.js.map +1 -0
  471. package/dist/core/sidecar-error-reporter.js +1 -1
  472. package/dist/core/sidecar-error-reporter.test.js +2 -2
  473. package/dist/core/skills-routing.test.js +96 -0
  474. package/dist/core/skills-routing.test.js.map +1 -1
  475. package/dist/core/skills.d.ts.map +1 -1
  476. package/dist/core/skills.js +3 -0
  477. package/dist/core/skills.js.map +1 -1
  478. package/dist/core/steering.d.ts +12 -0
  479. package/dist/core/steering.d.ts.map +1 -1
  480. package/dist/core/steering.js +16 -0
  481. package/dist/core/steering.js.map +1 -1
  482. package/dist/core/subagent-manager.d.ts +22 -0
  483. package/dist/core/subagent-manager.d.ts.map +1 -1
  484. package/dist/core/subagent-manager.js +84 -13
  485. package/dist/core/subagent-manager.js.map +1 -1
  486. package/dist/core/subagent-manager.test.js +57 -15
  487. package/dist/core/subagent-manager.test.js.map +1 -1
  488. package/dist/core/subagent-store.test.js +3 -3
  489. package/dist/core/task-store.js +1 -1
  490. package/dist/core/verification-evidence.d.ts +17 -0
  491. package/dist/core/verification-evidence.d.ts.map +1 -0
  492. package/dist/core/verification-evidence.js +283 -0
  493. package/dist/core/verification-evidence.js.map +1 -0
  494. package/dist/core/verification-evidence.test.d.ts +2 -0
  495. package/dist/core/verification-evidence.test.d.ts.map +1 -0
  496. package/dist/core/verification-evidence.test.js +103 -0
  497. package/dist/core/verification-evidence.test.js.map +1 -0
  498. package/dist/index.js +1 -1
  499. package/dist/index.js.map +1 -1
  500. package/dist/interactive.d.ts.map +1 -1
  501. package/dist/interactive.js +17 -3
  502. package/dist/interactive.js.map +1 -1
  503. package/dist/modes/acp-mode.d.ts +122 -0
  504. package/dist/modes/acp-mode.d.ts.map +1 -0
  505. package/dist/modes/acp-mode.js +1366 -0
  506. package/dist/modes/acp-mode.js.map +1 -0
  507. package/dist/modes/acp-mode.test.d.ts +2 -0
  508. package/dist/modes/acp-mode.test.d.ts.map +1 -0
  509. package/dist/modes/acp-mode.test.js +1065 -0
  510. package/dist/modes/acp-mode.test.js.map +1 -0
  511. package/dist/modes/agent-home-mode.d.ts.map +1 -1
  512. package/dist/modes/agent-home-mode.js +15 -11
  513. package/dist/modes/agent-home-mode.js.map +1 -1
  514. package/dist/modes/index.d.ts +1 -0
  515. package/dist/modes/index.d.ts.map +1 -1
  516. package/dist/modes/index.js +1 -0
  517. package/dist/modes/index.js.map +1 -1
  518. package/dist/modes/json-mode.d.ts +36 -0
  519. package/dist/modes/json-mode.d.ts.map +1 -1
  520. package/dist/modes/json-mode.js +39 -0
  521. package/dist/modes/json-mode.js.map +1 -1
  522. package/dist/modes/json-mode.test.d.ts +2 -0
  523. package/dist/modes/json-mode.test.d.ts.map +1 -0
  524. package/dist/modes/json-mode.test.js +84 -0
  525. package/dist/modes/json-mode.test.js.map +1 -0
  526. package/dist/modes/serve-mode.d.ts +1 -1
  527. package/dist/modes/serve-mode.d.ts.map +1 -1
  528. package/dist/modes/serve-mode.js +14 -9
  529. package/dist/modes/serve-mode.js.map +1 -1
  530. package/dist/modes/subagent-worker-mode.d.ts +8 -0
  531. package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
  532. package/dist/modes/subagent-worker-mode.js +91 -11
  533. package/dist/modes/subagent-worker-mode.js.map +1 -1
  534. package/dist/modes/subagent-worker-mode.test.d.ts +2 -0
  535. package/dist/modes/subagent-worker-mode.test.d.ts.map +1 -0
  536. package/dist/modes/subagent-worker-mode.test.js +52 -0
  537. package/dist/modes/subagent-worker-mode.test.js.map +1 -0
  538. package/dist/session.d.ts +29 -0
  539. package/dist/session.d.ts.map +1 -1
  540. package/dist/session.js +41 -0
  541. package/dist/session.js.map +1 -1
  542. package/dist/session.test.js +1 -1
  543. package/dist/system-prompt.d.ts +37 -1
  544. package/dist/system-prompt.d.ts.map +1 -1
  545. package/dist/system-prompt.js +123 -11
  546. package/dist/system-prompt.js.map +1 -1
  547. package/dist/system-prompt.test.js +90 -11
  548. package/dist/system-prompt.test.js.map +1 -1
  549. package/dist/system-prompt.tiering.test.d.ts +2 -0
  550. package/dist/system-prompt.tiering.test.d.ts.map +1 -0
  551. package/dist/system-prompt.tiering.test.js +105 -0
  552. package/dist/system-prompt.tiering.test.js.map +1 -0
  553. package/dist/tools/bash.d.ts +2 -1
  554. package/dist/tools/bash.d.ts.map +1 -1
  555. package/dist/tools/bash.js +55 -16
  556. package/dist/tools/bash.js.map +1 -1
  557. package/dist/tools/code-nav.d.ts +30 -0
  558. package/dist/tools/code-nav.d.ts.map +1 -0
  559. package/dist/tools/code-nav.js +320 -0
  560. package/dist/tools/code-nav.js.map +1 -0
  561. package/dist/tools/code-nav.test.d.ts +2 -0
  562. package/dist/tools/code-nav.test.d.ts.map +1 -0
  563. package/dist/tools/code-nav.test.js +136 -0
  564. package/dist/tools/code-nav.test.js.map +1 -0
  565. package/dist/tools/edit.test.js +1 -1
  566. package/dist/tools/generate-image.d.ts +1 -1
  567. package/dist/tools/goals.d.ts +4 -4
  568. package/dist/tools/goals.test.js +2 -2
  569. package/dist/tools/grep-parity.test.d.ts +2 -0
  570. package/dist/tools/grep-parity.test.d.ts.map +1 -0
  571. package/dist/tools/grep-parity.test.js +92 -0
  572. package/dist/tools/grep-parity.test.js.map +1 -0
  573. package/dist/tools/grep-recall.test.d.ts +2 -0
  574. package/dist/tools/grep-recall.test.d.ts.map +1 -0
  575. package/dist/tools/grep-recall.test.js +114 -0
  576. package/dist/tools/grep-recall.test.js.map +1 -0
  577. package/dist/tools/grep-spawn.test.d.ts +2 -0
  578. package/dist/tools/grep-spawn.test.d.ts.map +1 -0
  579. package/dist/tools/grep-spawn.test.js +66 -0
  580. package/dist/tools/grep-spawn.test.js.map +1 -0
  581. package/dist/tools/grep.d.ts +13 -3
  582. package/dist/tools/grep.d.ts.map +1 -1
  583. package/dist/tools/grep.js +342 -41
  584. package/dist/tools/grep.js.map +1 -1
  585. package/dist/tools/grep.test.js +17 -14
  586. package/dist/tools/grep.test.js.map +1 -1
  587. package/dist/tools/index.d.ts +20 -2
  588. package/dist/tools/index.d.ts.map +1 -1
  589. package/dist/tools/index.js +28 -18
  590. package/dist/tools/index.js.map +1 -1
  591. package/dist/tools/index.test.d.ts +2 -0
  592. package/dist/tools/index.test.d.ts.map +1 -0
  593. package/dist/tools/index.test.js +55 -0
  594. package/dist/tools/index.test.js.map +1 -0
  595. package/dist/tools/plan-mode.test.js +1 -1
  596. package/dist/tools/prompt-hints.d.ts +9 -0
  597. package/dist/tools/prompt-hints.d.ts.map +1 -1
  598. package/dist/tools/prompt-hints.js +61 -8
  599. package/dist/tools/prompt-hints.js.map +1 -1
  600. package/dist/tools/read-only-bash.d.ts +3 -0
  601. package/dist/tools/read-only-bash.d.ts.map +1 -1
  602. package/dist/tools/read-only-bash.js +7 -9
  603. package/dist/tools/read-only-bash.js.map +1 -1
  604. package/dist/tools/safe-env.js +1 -1
  605. package/dist/tools/search-code.d.ts.map +1 -1
  606. package/dist/tools/search-code.js +7 -6
  607. package/dist/tools/search-code.js.map +1 -1
  608. package/dist/tools/search-code.test.js +8 -2
  609. package/dist/tools/search-code.test.js.map +1 -1
  610. package/dist/tools/skill.js +2 -1
  611. package/dist/tools/skill.js.map +1 -1
  612. package/dist/tools/subagent-control.d.ts.map +1 -1
  613. package/dist/tools/subagent-control.js +25 -6
  614. package/dist/tools/subagent-control.js.map +1 -1
  615. package/dist/tools/subagent-control.test.js +34 -1
  616. package/dist/tools/subagent-control.test.js.map +1 -1
  617. package/dist/tools/subagent-shared.d.ts +26 -1
  618. package/dist/tools/subagent-shared.d.ts.map +1 -1
  619. package/dist/tools/subagent-shared.js +40 -7
  620. package/dist/tools/subagent-shared.js.map +1 -1
  621. package/dist/tools/subagent-shared.test.js +43 -9
  622. package/dist/tools/subagent-shared.test.js.map +1 -1
  623. package/dist/tools/subagent.d.ts.map +1 -1
  624. package/dist/tools/subagent.js +54 -16
  625. package/dist/tools/subagent.js.map +1 -1
  626. package/dist/tools/subagent.test.js +68 -3
  627. package/dist/tools/subagent.test.js.map +1 -1
  628. package/dist/tools/task-output.d.ts.map +1 -1
  629. package/dist/tools/task-output.js +3 -1
  630. package/dist/tools/task-output.js.map +1 -1
  631. package/dist/tools/task-send.test.js +19 -10
  632. package/dist/tools/task-send.test.js.map +1 -1
  633. package/dist/tools/tool-search.d.ts +12 -1
  634. package/dist/tools/tool-search.d.ts.map +1 -1
  635. package/dist/tools/tool-search.js +24 -4
  636. package/dist/tools/tool-search.js.map +1 -1
  637. package/dist/tools/tool-search.test.d.ts +2 -0
  638. package/dist/tools/tool-search.test.d.ts.map +1 -0
  639. package/dist/tools/tool-search.test.js +69 -0
  640. package/dist/tools/tool-search.test.js.map +1 -0
  641. package/dist/tools/tool-tiers.d.ts +42 -0
  642. package/dist/tools/tool-tiers.d.ts.map +1 -0
  643. package/dist/tools/tool-tiers.js +78 -0
  644. package/dist/tools/tool-tiers.js.map +1 -0
  645. package/dist/tools/tool-tiers.test.d.ts +2 -0
  646. package/dist/tools/tool-tiers.test.d.ts.map +1 -0
  647. package/dist/tools/tool-tiers.test.js +48 -0
  648. package/dist/tools/tool-tiers.test.js.map +1 -0
  649. package/dist/tools/web-fetch.d.ts +1 -1
  650. package/dist/tools/web-fetch.d.ts.map +1 -1
  651. package/dist/tools/web-fetch.js +5 -2
  652. package/dist/tools/web-fetch.js.map +1 -1
  653. package/dist/tools/write.test.js +1 -1
  654. package/dist/types.d.ts +7 -0
  655. package/dist/types.d.ts.map +1 -1
  656. package/dist/ui/App.d.ts.map +1 -1
  657. package/dist/ui/App.js +27 -1
  658. package/dist/ui/App.js.map +1 -1
  659. package/dist/ui/components/Banner.d.ts.map +1 -1
  660. package/dist/ui/components/Banner.js +24 -16
  661. package/dist/ui/components/Banner.js.map +1 -1
  662. package/dist/ui/components/ToolExecution.js +42 -34
  663. package/dist/ui/components/ToolExecution.js.map +1 -1
  664. package/dist/ui/components/force-color.d.ts +2 -0
  665. package/dist/ui/components/force-color.d.ts.map +1 -0
  666. package/dist/ui/components/force-color.js +11 -0
  667. package/dist/ui/components/force-color.js.map +1 -0
  668. package/dist/ui/components/themed-output.test.d.ts +2 -0
  669. package/dist/ui/components/themed-output.test.d.ts.map +1 -0
  670. package/dist/ui/components/themed-output.test.js +111 -0
  671. package/dist/ui/components/themed-output.test.js.map +1 -0
  672. package/dist/ui/error-item.d.ts +1 -1
  673. package/dist/ui/error-item.js +3 -3
  674. package/dist/ui/error-item.js.map +1 -1
  675. package/dist/ui/hooks/useAgentLoop.d.ts +3 -0
  676. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  677. package/dist/ui/hooks/useAgentLoop.js +45 -6
  678. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  679. package/dist/ui/login.js +1 -1
  680. package/dist/ui/login.js.map +1 -1
  681. package/dist/ui/render.d.ts +1 -1
  682. package/dist/ui/render.js +6 -6
  683. package/dist/ui/theme/dark-ansi.json +6 -0
  684. package/dist/ui/theme/dark-daltonized.json +6 -0
  685. package/dist/ui/theme/dark.json +6 -0
  686. package/dist/ui/theme/light-ansi.json +6 -0
  687. package/dist/ui/theme/light-daltonized.json +6 -0
  688. package/dist/ui/theme/light.json +6 -0
  689. package/dist/ui/theme/theme.d.ts +6 -0
  690. package/dist/ui/theme/theme.d.ts.map +1 -1
  691. package/dist/ui/tui-terminal-recorder.test.js +1 -1
  692. package/dist/utils/git.test.js +3 -3
  693. package/dist/utils/github.test.js +3 -3
  694. package/dist/utils/github.test.js.map +1 -1
  695. package/dist/utils/image.d.ts +26 -3
  696. package/dist/utils/image.d.ts.map +1 -1
  697. package/dist/utils/image.js +83 -14
  698. package/dist/utils/image.js.map +1 -1
  699. package/dist/utils/image.test.d.ts +2 -0
  700. package/dist/utils/image.test.d.ts.map +1 -0
  701. package/dist/utils/image.test.js +139 -0
  702. package/dist/utils/image.test.js.map +1 -0
  703. package/package.json +7 -6
@@ -0,0 +1,1366 @@
1
+ /**
2
+ * ACP mode: expose EZ Coder as an Agent Client Protocol agent over stdio.
3
+ *
4
+ * This is the integration surface for ACP clients (Zed, pew2, any editor that
5
+ * speaks the protocol). It is deliberately a sibling of `rpc-mode.ts` — same
6
+ * shape, same `AgentSession`, same NDJSON-on-stdio transport — but the frames
7
+ * are the standard protocol instead of ezcoder's bespoke one, so a client
8
+ * written against the spec works with no ezcoder-specific code.
9
+ *
10
+ * Spec: https://agentclientprotocol.com/protocol/overview
11
+ *
12
+ * Scope: `initialize`, `session/new`, `session/prompt`, `session/cancel`,
13
+ * `session/list`, `session/load`, `session/resume`, `session/close`,
14
+ * `session/delete`, `session/set_mode` and `session/set_config_option`.
15
+ * Everything advertised in `agentCapabilities` is implemented, because a client
16
+ * must be able to trust that list — advertising a method that then errors is
17
+ * worse than advertising nothing.
18
+ *
19
+ * stdout carries protocol frames ONLY. Anything diagnostic goes to stderr or
20
+ * the log file; a stray `console.log` anywhere in the process corrupts the
21
+ * stream and the client disconnects.
22
+ */
23
+ import readline from "node:readline";
24
+ import path from "node:path";
25
+ import { readFileSync, statSync } from "node:fs";
26
+ import { rm } from "node:fs/promises";
27
+ import { isAbortError } from "@prestyj/agent";
28
+ import { getAllModels, getMaxThinkingLevel, getModel } from "@prestyj/core";
29
+ import { AgentSession } from "../core/agent-session.js";
30
+ import { PROMPT_COMMANDS } from "../core/prompt-commands.js";
31
+ import { loadCustomCommands } from "../core/custom-commands.js";
32
+ import { findSessionById, listAllSessions, listSessionSummaries, loadSessionCheckpointChain, } from "../session.js";
33
+ import { getHistoryMessageVisibility, reconstructCheckpointHistory, restoreUserRow, } from "../core/session-history.js";
34
+ import { findUserSessionPrompt } from "../core/session-preview.js";
35
+ import { sessionGroupPaths } from "../core/session-storage.js";
36
+ import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, rebasePlanSteps, } from "../utils/plan-steps.js";
37
+ import { formatUserError } from "../utils/error-handler.js";
38
+ import { closeLogger } from "../core/logger.js";
39
+ /** The ACP major version this mode implements. Bumped only for breaking changes. */
40
+ export const ACP_PROTOCOL_VERSION = 1;
41
+ /** JSON-RPC 2.0 reserved codes. ACP adds no codes of its own. */
42
+ const RPC_PARSE_ERROR = -32700;
43
+ const RPC_INVALID_REQUEST = -32600;
44
+ const RPC_METHOD_NOT_FOUND = -32601;
45
+ const RPC_INVALID_PARAMS = -32602;
46
+ const RPC_INTERNAL_ERROR = -32603;
47
+ /**
48
+ * The client asked for something impossible, as opposed to the agent failing.
49
+ *
50
+ * Typed rather than sniffed from the message text, so that a client can tell a
51
+ * bug in its own sequencing from an agent that fell over — and so that a later
52
+ * reworded message cannot silently change the code on the wire.
53
+ */
54
+ class InvalidParams extends Error {
55
+ }
56
+ // ── Tool mapping ───────────────────────────────────────────
57
+ /**
58
+ * ezcoder tool name → ACP `ToolKind`.
59
+ *
60
+ * Purely cosmetic on the client (icon + progress treatment), so an unmapped
61
+ * tool falls back to `other` rather than failing. Keep in step with
62
+ * `src/tools/index.ts` as tools are added.
63
+ */
64
+ const TOOL_KINDS = {
65
+ read: "read",
66
+ ls: "read",
67
+ find: "read",
68
+ grep: "search",
69
+ code_search: "search",
70
+ web_search: "search",
71
+ edit: "edit",
72
+ write: "edit",
73
+ bash: "execute",
74
+ screenshot: "execute",
75
+ web_fetch: "fetch",
76
+ source_path: "fetch",
77
+ subagent: "think",
78
+ spawn_agent: "think",
79
+ enter_plan: "switch_mode",
80
+ exit_plan: "switch_mode",
81
+ };
82
+ function toolKind(name) {
83
+ return TOOL_KINDS[name] ?? "other";
84
+ }
85
+ /**
86
+ * A one-line tool title for the client's activity list.
87
+ *
88
+ * The first string-ish argument is nearly always the interesting one (a path, a
89
+ * command, a query), and it is truncated because this renders on a phone.
90
+ */
91
+ function toolTitle(name, args) {
92
+ for (const value of Object.values(args)) {
93
+ if (typeof value !== "string" || value.length === 0)
94
+ continue;
95
+ const flat = value.replace(/\s+/g, " ").trim();
96
+ const clipped = flat.length > 80 ? `${flat.slice(0, 79)}…` : flat;
97
+ return `${name}(${clipped})`;
98
+ }
99
+ return name;
100
+ }
101
+ // ── Tool locations ─────────────────────────────────────────
102
+ /**
103
+ * Argument names that hold the path a tool works on, most specific first.
104
+ *
105
+ * Deliberately a small allowlist rather than "any string that looks like a
106
+ * path": `bash`'s command and `web_fetch`'s url would both pass a heuristic and
107
+ * both would send the client's editor somewhere that does not exist.
108
+ */
109
+ const PATH_ARG_KEYS = ["file_path", "path", "out_path"];
110
+ /**
111
+ * The file a tool call touches, which is what drives "follow the agent" in a
112
+ * client: the editor jumps to whatever EZ is reading or editing right now.
113
+ *
114
+ * Paths are resolved to absolute against the session cwd, because the client
115
+ * runs somewhere else entirely and cannot know what a relative path was
116
+ * relative to. A wrong location is worse than none, so anything unrecognised
117
+ * reports nothing.
118
+ */
119
+ function toolLocations(args, cwd) {
120
+ for (const key of PATH_ARG_KEYS) {
121
+ const value = args[key];
122
+ if (typeof value !== "string" || !value)
123
+ continue;
124
+ const absolute = path.isAbsolute(value) ? value : path.resolve(cwd, value);
125
+ // `read`'s offset is a 1-based line, which is exactly what ACP wants for
126
+ // scrolling the client to the region being looked at.
127
+ const offset = args.offset;
128
+ return typeof offset === "number" && Number.isInteger(offset) && offset > 0
129
+ ? [{ path: absolute, line: offset }]
130
+ : [{ path: absolute }];
131
+ }
132
+ return [];
133
+ }
134
+ // ── File diffs ─────────────────────────────────────────────
135
+ /** Tools whose whole purpose is changing a file's contents. */
136
+ const DIFF_TOOLS = new Set(["edit", "write"]);
137
+ /**
138
+ * Largest file we will snapshot to build a diff.
139
+ *
140
+ * The contents cross the wire twice (old and new) and are read on the event
141
+ * loop, so a generated bundle or lockfile would stall the turn and flood the
142
+ * client with a diff no human is going to read.
143
+ */
144
+ const MAX_DIFF_BYTES = 256 * 1024;
145
+ /**
146
+ * Read a file for diffing, SYNCHRONOUSLY and on purpose.
147
+ *
148
+ * The "before" snapshot is taken inside the `tool_call_start` handler, and the
149
+ * tool it belongs to begins writing in the same tick. An async read would race
150
+ * that write and could capture the file as it is AFTER the edit, which renders
151
+ * in the client as a real change with an empty diff.
152
+ */
153
+ function snapshotForDiff(filePath) {
154
+ let size;
155
+ try {
156
+ size = statSync(filePath).size;
157
+ }
158
+ catch {
159
+ // Missing: `write` creating a new file. ACP represents that as a null
160
+ // `oldText`, which clients render as an all-additions diff.
161
+ return { text: null };
162
+ }
163
+ if (size > MAX_DIFF_BYTES)
164
+ return undefined;
165
+ try {
166
+ return { text: readFileSync(filePath, "utf8") };
167
+ }
168
+ catch {
169
+ return undefined;
170
+ }
171
+ }
172
+ // ── Plans ──────────────────────────────────────────────────
173
+ /**
174
+ * GG's plan steps as ACP plan entries.
175
+ *
176
+ * ACP requires a priority per entry and GG's plans have no such concept, so
177
+ * every entry reports `medium` rather than inventing a ranking the user never
178
+ * expressed. The first unfinished step is reported `in_progress`: entries are
179
+ * worked in order, so this is what the agent is actually doing now, and it
180
+ * gives the client a live marker instead of a list that only ever flips from
181
+ * pending to completed.
182
+ */
183
+ function planEntries(steps) {
184
+ let activeMarked = false;
185
+ return steps.map((step) => {
186
+ let status;
187
+ if (step.completed) {
188
+ status = "completed";
189
+ }
190
+ else if (activeMarked) {
191
+ status = "pending";
192
+ }
193
+ else {
194
+ activeMarked = true;
195
+ status = "in_progress";
196
+ }
197
+ return { content: step.text, priority: "medium", status };
198
+ });
199
+ }
200
+ /** Read a plan markdown file, or empty string when it has gone missing. */
201
+ function readPlanFile(planPath) {
202
+ try {
203
+ return readFileSync(planPath, "utf8");
204
+ }
205
+ catch {
206
+ return "";
207
+ }
208
+ }
209
+ /**
210
+ * Why the current prompt turn ended.
211
+ *
212
+ * ACP has a closed set; ezcoder's `turn_end` carries whatever the provider
213
+ * said. Only the reasons that map cleanly are translated — everything else is
214
+ * a normal completion, which is what the client renders anyway.
215
+ */
216
+ function stopReasonFor(truncation, hitMaxTurns) {
217
+ if (hitMaxTurns)
218
+ return "max_turn_requests";
219
+ if (truncation === "max_tokens")
220
+ return "max_tokens";
221
+ if (truncation === "refusal")
222
+ return "refusal";
223
+ return "end_turn";
224
+ }
225
+ // ── Config options ─────────────────────────────────────
226
+ /** The selectors this agent exposes. Ids are part of the wire contract. */
227
+ export const MODEL_CONFIG_ID = "model";
228
+ export const THINKING_CONFIG_ID = "thinking";
229
+ export const MODE_CONFIG_ID = "mode";
230
+ /** ACP session modes, mapped onto GG's plan mode. */
231
+ const MODE_DEFAULT = "default";
232
+ const MODE_PLAN = "plan";
233
+ /** Thinking off is a real choice, so it needs a value id of its own. */
234
+ const THINKING_OFF = "off";
235
+ const THINKING_LADDER = [
236
+ "low",
237
+ "medium",
238
+ "high",
239
+ "xhigh",
240
+ "max",
241
+ "ultra",
242
+ ];
243
+ /**
244
+ * Thinking levels this model actually honours, hardest last.
245
+ *
246
+ * Offering levels above the model's ceiling would let a phone pick a setting
247
+ * that silently degrades to something else — a control that lies.
248
+ */
249
+ function thinkingOptionsFor(modelId) {
250
+ const ceiling = getMaxThinkingLevel(modelId);
251
+ const top = THINKING_LADDER.indexOf(ceiling);
252
+ const levels = THINKING_LADDER.slice(0, top < 0 ? THINKING_LADDER.length : top + 1);
253
+ return [
254
+ { value: THINKING_OFF, name: "Off", description: "No extended reasoning" },
255
+ ...levels.map((level) => ({ value: level, name: level })),
256
+ ];
257
+ }
258
+ /**
259
+ * The selectors a client renders for a session, with their live values.
260
+ *
261
+ * Model and thinking come from ezcoder's own registry rather than anything
262
+ * pew2- or Zed-specific, which is what lets a phone show EZ Coder's real model
263
+ * list without either side hard-coding the other's.
264
+ */
265
+ function configOptionsFor(session) {
266
+ const { model } = session.getState();
267
+ const thinking = session.getThinkingLevel();
268
+ return [
269
+ {
270
+ id: MODEL_CONFIG_ID,
271
+ name: "Model",
272
+ category: "model",
273
+ type: "select",
274
+ currentValue: model,
275
+ options: getAllModels().map((entry) => ({
276
+ value: entry.id,
277
+ name: entry.name,
278
+ description: entry.provider,
279
+ })),
280
+ },
281
+ {
282
+ id: THINKING_CONFIG_ID,
283
+ name: "Thinking",
284
+ description: "Extended reasoning effort",
285
+ category: "thought_level",
286
+ type: "select",
287
+ // A model that cannot think reports `off`, which is the truth rather than
288
+ // a level it would ignore.
289
+ currentValue: getModel(model)?.supportsThinking ? (thinking ?? THINKING_OFF) : THINKING_OFF,
290
+ options: thinkingOptionsFor(model),
291
+ },
292
+ {
293
+ id: MODE_CONFIG_ID,
294
+ name: "Mode",
295
+ description: "Plan mode is read-only research until a plan is approved",
296
+ category: "mode",
297
+ type: "select",
298
+ currentValue: session.getPlanMode() ? MODE_PLAN : MODE_DEFAULT,
299
+ options: [
300
+ { value: MODE_DEFAULT, name: "Default", description: "Full tool access" },
301
+ { value: MODE_PLAN, name: "Plan", description: "Read-only until exit_plan" },
302
+ ],
303
+ },
304
+ ];
305
+ }
306
+ /** What a prompt-template command does with whatever follows the name. */
307
+ const TEMPLATE_ARG_HINT = "extra instructions (optional)";
308
+ /**
309
+ * The argument hint for a registry command, taken from its own usage string.
310
+ *
311
+ * `/add-dir [path] — no path lists the current roots` becomes `[path]`: the
312
+ * client renders a hint, not a man page, and the prose after the dash is the
313
+ * description's job.
314
+ */
315
+ function hintFromUsage(name, usage) {
316
+ const withoutName = usage.replace(new RegExp(`^\\s*/${name}\\b`), "").trim();
317
+ const hint = withoutName.split("—")[0].split(" - ")[0].trim();
318
+ return hint ? { hint } : undefined;
319
+ }
320
+ /**
321
+ * Every slash command this session would actually honour, in the precedence
322
+ * `AgentSession.resolveSlashInput` uses: built-in prompt templates, then
323
+ * `.ezcoder/commands/*.md` from the project, then registry commands.
324
+ *
325
+ * That order is not cosmetic. `resolveSlashInput` looks for a template body
326
+ * FIRST and only falls through to the registry when there is none, so a project
327
+ * file named `new.md` really does shadow the registry's `/new`. Listing the
328
+ * registry entry for that name would point a client's user at a description of
329
+ * something the agent will not run.
330
+ *
331
+ * Names already claimed — including a built-in's ALIASES, which
332
+ * `getPromptCommand` matches — are dropped rather than listed twice.
333
+ */
334
+ async function availableCommands(session, cwd) {
335
+ const commands = [];
336
+ const taken = new Set();
337
+ const add = (command) => {
338
+ if (taken.has(command.name))
339
+ return;
340
+ taken.add(command.name);
341
+ commands.push(command);
342
+ };
343
+ for (const command of PROMPT_COMMANDS) {
344
+ for (const alias of command.aliases)
345
+ taken.add(alias);
346
+ add({
347
+ name: command.name,
348
+ description: command.description,
349
+ input: { hint: TEMPLATE_ARG_HINT },
350
+ });
351
+ }
352
+ // Project commands are files on disk, so a client that never rescans still
353
+ // gets whatever existed when the session opened.
354
+ for (const command of await loadCustomCommands(cwd)) {
355
+ add({
356
+ name: command.name,
357
+ description: command.description,
358
+ input: { hint: TEMPLATE_ARG_HINT },
359
+ });
360
+ }
361
+ // Last: anything above with the same NAME beats a registry command. An alias
362
+ // collision does not, so aliases are deliberately not claimed here — a
363
+ // project `q.md` shadows `/q` without hiding `/quit` itself.
364
+ for (const command of session.slashCommands?.getAll() ?? []) {
365
+ add({
366
+ name: command.name,
367
+ description: command.description,
368
+ input: hintFromUsage(command.name, command.usage),
369
+ });
370
+ }
371
+ return commands;
372
+ }
373
+ /** The `modes` block ACP clients like Zed read from session/new and session/load. */
374
+ function sessionModes(session) {
375
+ return {
376
+ currentModeId: session.getPlanMode() ? MODE_PLAN : MODE_DEFAULT,
377
+ availableModes: [
378
+ { id: MODE_DEFAULT, name: "Default", description: "Full tool access" },
379
+ { id: MODE_PLAN, name: "Plan", description: "Read-only research until a plan is approved" },
380
+ ],
381
+ };
382
+ }
383
+ // ── History replay ───────────────────────────────────
384
+ /** Flatten a persisted message's content to the text a client would show. */
385
+ function messageText(content) {
386
+ if (typeof content === "string")
387
+ return content;
388
+ if (!Array.isArray(content))
389
+ return "";
390
+ return content
391
+ .filter((block) => block?.type === "text")
392
+ .map((block) => block.text ?? "")
393
+ .join("");
394
+ }
395
+ /**
396
+ * Turn a restored conversation into the `session/update` stream a client needs
397
+ * to draw it.
398
+ *
399
+ * ACP has no "here is the transcript" response: a loaded session is replayed as
400
+ * the same notifications a live turn produces, so the client needs no second
401
+ * rendering path. Thinking is deliberately NOT replayed — it is transient by
402
+ * design, and a wall of stale reasoning above a resumed conversation buries the
403
+ * thing the user came back for.
404
+ *
405
+ * Each replayed chunk carries a `messageId` so a client can group chunks into
406
+ * the messages they came from; ids are per-replay and positional, which is all
407
+ * the protocol needs of them.
408
+ */
409
+ export function historyUpdates(messages) {
410
+ const updates = [];
411
+ let replayed = 0;
412
+ const nextMessageId = () => `hist-${++replayed}`;
413
+ for (const message of messages) {
414
+ if (getHistoryMessageVisibility(message) === "hidden")
415
+ continue;
416
+ if (message.role === "user") {
417
+ const restored = restoreUserRow(message.content, message.provenance);
418
+ if (restored.autopilotInjected || restored.notification)
419
+ continue;
420
+ if (restored.text) {
421
+ updates.push({
422
+ sessionUpdate: "user_message_chunk",
423
+ messageId: nextMessageId(),
424
+ content: { type: "text", text: restored.text },
425
+ });
426
+ }
427
+ continue;
428
+ }
429
+ if (message.role === "assistant") {
430
+ const text = messageText(message.content);
431
+ if (text) {
432
+ updates.push({
433
+ sessionUpdate: "agent_message_chunk",
434
+ messageId: nextMessageId(),
435
+ content: { type: "text", text },
436
+ });
437
+ }
438
+ if (Array.isArray(message.content)) {
439
+ for (const block of message.content) {
440
+ if (block.type !== "tool_call")
441
+ continue;
442
+ const call = block;
443
+ const args = call.args ?? {};
444
+ updates.push({
445
+ sessionUpdate: "tool_call",
446
+ toolCallId: call.id,
447
+ title: toolTitle(call.name, args),
448
+ name: call.name,
449
+ kind: toolKind(call.name),
450
+ // History is settled: a replayed call that still said `in_progress`
451
+ // would leave a spinner running forever on a resumed session.
452
+ status: "completed",
453
+ rawInput: args,
454
+ });
455
+ }
456
+ }
457
+ continue;
458
+ }
459
+ for (const result of message.content) {
460
+ const entry = result;
461
+ updates.push({
462
+ sessionUpdate: "tool_call_update",
463
+ toolCallId: entry.toolCallId,
464
+ status: entry.isError ? "failed" : "completed",
465
+ content: [
466
+ {
467
+ type: "content",
468
+ content: { type: "text", text: messageText(entry.content) },
469
+ },
470
+ ],
471
+ });
472
+ }
473
+ }
474
+ return updates;
475
+ }
476
+ // ── Mode ───────────────────────────────────────────────────
477
+ /**
478
+ * Serve ACP on stdio until the input stream ends.
479
+ *
480
+ * Resolves when the client disconnects; the caller owns process exit.
481
+ */
482
+ export async function runAcpMode(options) {
483
+ const input = options.input ?? process.stdin;
484
+ const output = options.output ?? process.stdout;
485
+ function write(frame) {
486
+ output.write(`${JSON.stringify(frame)}\n`);
487
+ }
488
+ function respond(id, result) {
489
+ write({ jsonrpc: "2.0", id, result });
490
+ }
491
+ function fail(id, code, message) {
492
+ write({ jsonrpc: "2.0", id, error: { code, message } });
493
+ }
494
+ /**
495
+ * The single session this process serves.
496
+ *
497
+ * ACP allows many per connection, but an ACP client spawns one agent process
498
+ * per session anyway, and `AgentSession` owns process-wide state (cwd, tool
499
+ * registry, session file). One-per-process is the honest model; a second
500
+ * `session/new` replaces the first rather than pretending to multiplex.
501
+ */
502
+ let session = null;
503
+ let sessionId = "";
504
+ let abort = new AbortController();
505
+ /** Set while a `session/prompt` is in flight, so a second one is rejected. */
506
+ let running = false;
507
+ let cancelled = false;
508
+ let truncation;
509
+ let hitMaxTurns = false;
510
+ /** Detaches every event listener when the session is replaced or disposed. */
511
+ let unwire = [];
512
+ /**
513
+ * Chunk grouping. A message runs until something interrupts it — a tool call
514
+ * or the end of the turn — so the id is minted lazily on the first chunk and
515
+ * dropped at those boundaries, which is exactly where the client should start
516
+ * a new bubble.
517
+ */
518
+ let messageSeq = 0;
519
+ let currentMessageId = null;
520
+ /** Before-snapshots for in-flight `edit`/`write` calls, keyed by tool call. */
521
+ const diffSnapshots = new Map();
522
+ /** The approved plan being implemented, and how far through it the agent is. */
523
+ let planPath;
524
+ let planSteps = [];
525
+ const completedSteps = new Set();
526
+ /** This turn's assistant text, scanned for `[DONE:n]` markers. */
527
+ let turnText = "";
528
+ /** Whether this session has already announced a title to the client. */
529
+ let titleAnnounced = false;
530
+ function notifyUpdate(update) {
531
+ write({
532
+ jsonrpc: "2.0",
533
+ method: "session/update",
534
+ params: { sessionId, update },
535
+ });
536
+ }
537
+ /**
538
+ * Report context-window usage to the client.
539
+ *
540
+ * Sent whenever token accounting moves — after each model response and after
541
+ * a compaction — because a client's context meter is otherwise frozen at
542
+ * whatever it last inferred. Compaction is only visible to a client as a drop
543
+ * in `used` at unchanged `size`, so the post-compaction emit is what makes
544
+ * that detectable at all.
545
+ *
546
+ * `used`/`size` are required by the schema; a session that cannot count
547
+ * tokens sends nothing rather than a zero, which would render as an empty
548
+ * context the user does not have.
549
+ */
550
+ function notifyUsage(target = session) {
551
+ if (!target || target !== session || !sessionId)
552
+ return;
553
+ const usage = target.getContextUsage?.();
554
+ if (!usage || !Number.isFinite(usage.used) || !Number.isFinite(usage.size))
555
+ return;
556
+ notifyUpdate({
557
+ sessionUpdate: "usage_update",
558
+ used: usage.used,
559
+ size: usage.size,
560
+ ...(usage.costUsd === undefined ? {} : { cost: { amount: usage.costUsd, currency: "USD" } }),
561
+ });
562
+ }
563
+ /**
564
+ * State for a session that was just created or restored, sent after the
565
+ * response that told the client the session exists.
566
+ *
567
+ * Same deferral (and same staleness guard) as {@link notifyAvailableCommands}:
568
+ * a notification addressed to a sessionId the client has not seen yet has
569
+ * nowhere to land. Without this a resumed conversation shows no usage and no
570
+ * title until its first reply, which is exactly when they matter least.
571
+ */
572
+ function announceSessionSoon(target) {
573
+ const forSession = sessionId;
574
+ setTimeout(() => {
575
+ if (session !== target || sessionId !== forSession)
576
+ return;
577
+ notifyUsage(target);
578
+ notifySessionInfo(target);
579
+ }, 0);
580
+ }
581
+ /**
582
+ * The id chunks of the current agent message share, minted on demand.
583
+ */
584
+ function messageId() {
585
+ currentMessageId ??= `msg-${++messageSeq}`;
586
+ return currentMessageId;
587
+ }
588
+ /** Start a new message at the next chunk (tool call, or end of turn). */
589
+ function endMessage() {
590
+ currentMessageId = null;
591
+ }
592
+ /**
593
+ * Send the whole plan, which is what ACP requires: the client REPLACES its
594
+ * copy on every update rather than patching it, so a partial list would
595
+ * silently delete steps.
596
+ */
597
+ function notifyPlan() {
598
+ if (planSteps.length === 0)
599
+ return;
600
+ notifyUpdate({ sessionUpdate: "plan", entries: planEntries(planSteps) });
601
+ }
602
+ /**
603
+ * Adopt a freshly approved plan and show it to the client as a to-do list.
604
+ *
605
+ * Progress resets with the plan: `[DONE:n]` markers are relative to the plan
606
+ * that was approved, so carrying completions across a new one would mark
607
+ * steps of the new plan done that nobody has started.
608
+ */
609
+ function adoptPlan(approvedPath) {
610
+ planPath = approvedPath;
611
+ planSteps = extractPlanSteps(readPlanFile(approvedPath));
612
+ completedSteps.clear();
613
+ notifyPlan();
614
+ }
615
+ /**
616
+ * Advance the plan from `[DONE:n]` markers in the agent's own text.
617
+ *
618
+ * The plan is re-read rather than trusted from approval time because the
619
+ * agent is allowed to rewrite it while implementing (a 2-step plan becoming
620
+ * 12 is normal), and a frozen snapshot would report the wrong total and drop
621
+ * markers for steps it has never heard of.
622
+ */
623
+ function refreshPlanProgress() {
624
+ if (planSteps.length === 0)
625
+ return;
626
+ let advanced = false;
627
+ for (const step of findCompletedMarkers(turnText)) {
628
+ if (completedSteps.has(step))
629
+ continue;
630
+ completedSteps.add(step);
631
+ advanced = true;
632
+ }
633
+ if (!advanced)
634
+ return;
635
+ const fresh = planPath ? extractPlanSteps(readPlanFile(planPath)) : [];
636
+ planSteps = markStepsCompleted(rebasePlanSteps(planSteps, fresh), completedSteps);
637
+ notifyPlan();
638
+ }
639
+ /**
640
+ * Give the session a human-readable title, once.
641
+ *
642
+ * ACP expects this "after the first meaningful exchange", and EZ already
643
+ * derives the same first-prompt title for its own session list — reusing it
644
+ * means a session is named identically on a phone, in the picker, and on
645
+ * disk instead of three near-misses.
646
+ */
647
+ function notifySessionInfo(target) {
648
+ if (titleAnnounced || target !== session || !sessionId)
649
+ return;
650
+ const prompt = findUserSessionPrompt(target.getMessages()).replace(/\s+/g, " ").trim();
651
+ if (!prompt)
652
+ return;
653
+ titleAnnounced = true;
654
+ notifyUpdate({
655
+ sessionUpdate: "session_info_update",
656
+ title: prompt.length > 80 ? `${prompt.slice(0, 79)}…` : prompt,
657
+ updatedAt: new Date().toISOString(),
658
+ });
659
+ }
660
+ /**
661
+ * Tell the client the session mode changed outside a request it made — the
662
+ * model itself can enter/exit plan mode mid-run via the enter_plan/exit_plan
663
+ * tools, and a picker that only tracks its own changes would lie.
664
+ */
665
+ function notifyModeChange(modeId) {
666
+ if (!session)
667
+ return;
668
+ notifyUpdate({ sessionUpdate: "current_mode_update", currentModeId: modeId });
669
+ notifyUpdate({
670
+ sessionUpdate: "config_option_update",
671
+ configOptions: configOptionsFor(session),
672
+ });
673
+ }
674
+ /**
675
+ * Tell the client which slash commands exist, once the session is up.
676
+ *
677
+ * Sent as a notification AFTER the response that created the session (a
678
+ * timer, not a microtask, so it cannot overtake it): the payload is addressed
679
+ * by sessionId, and a client that has not yet seen its own session/new result
680
+ * has nowhere to put it. The agent is the only party that can know its
681
+ * built-ins, so a client scanning `.ezcoder/commands` on its own would miss them.
682
+ */
683
+ function notifyAvailableCommands(target) {
684
+ const forSession = sessionId;
685
+ setTimeout(() => {
686
+ // A second session/new (or a dispose) beat us here; that session will
687
+ // announce its own commands.
688
+ if (session !== target || sessionId !== forSession)
689
+ return;
690
+ void availableCommands(target, options.cwd)
691
+ .then((commands) => {
692
+ if (session !== target || sessionId !== forSession)
693
+ return;
694
+ notifyUpdate({
695
+ sessionUpdate: "available_commands_update",
696
+ availableCommands: commands,
697
+ });
698
+ })
699
+ // Command discovery reads the disk. A failure there must not take down
700
+ // a session that is otherwise fine.
701
+ .catch(() => { });
702
+ }, 0);
703
+ }
704
+ /**
705
+ * The finished tool call's result as an ACP file diff, or undefined when we
706
+ * cannot honestly produce one.
707
+ *
708
+ * A real diff is what lets a client render a reviewable side-by-side edit
709
+ * instead of a wall of text. It REPLACES the tool's text result rather than
710
+ * accompanying it: `edit` already returns a unified diff as prose, and
711
+ * showing both means the same change twice in two formats.
712
+ *
713
+ * A failed call is left as text on purpose — the error message is the useful
714
+ * output, and the file on disk did not change.
715
+ */
716
+ function diffContent(toolCallId, isError) {
717
+ const snapshot = diffSnapshots.get(toolCallId);
718
+ if (!snapshot)
719
+ return undefined;
720
+ diffSnapshots.delete(toolCallId);
721
+ if (isError || !snapshot.before)
722
+ return undefined;
723
+ const after = snapshotForDiff(snapshot.path);
724
+ // `newText` is required by the schema, so a file that vanished or grew past
725
+ // the diff budget mid-call falls back to the tool's own text output.
726
+ if (!after || after.text === null)
727
+ return undefined;
728
+ return [
729
+ {
730
+ type: "diff",
731
+ path: snapshot.path,
732
+ oldText: snapshot.before.text,
733
+ newText: after.text,
734
+ },
735
+ ];
736
+ }
737
+ /**
738
+ * Bridge ezcoder's event bus onto `session/update` notifications.
739
+ *
740
+ * Every handler is synchronous and writes immediately, which is what keeps
741
+ * updates ordered ahead of the `session/prompt` response — a client that sees
742
+ * the result before the chunks renders an empty turn.
743
+ */
744
+ function wire(target) {
745
+ const bus = target.eventBus;
746
+ unwire = [
747
+ bus.on("text_delta", ({ text }) => {
748
+ notifyUpdate({
749
+ sessionUpdate: "agent_message_chunk",
750
+ messageId: messageId(),
751
+ content: { type: "text", text },
752
+ });
753
+ // Plan markers arrive inside this text and can straddle two deltas, so
754
+ // the scan runs over the turn's accumulated text rather than the chunk.
755
+ // Only a delta that closes a bracket can complete a marker, which keeps
756
+ // this from re-scanning the whole turn on every token.
757
+ turnText += text;
758
+ if (text.includes("]"))
759
+ refreshPlanProgress();
760
+ }),
761
+ bus.on("thinking_delta", ({ text }) => {
762
+ notifyUpdate({
763
+ sessionUpdate: "agent_thought_chunk",
764
+ content: { type: "text", text },
765
+ });
766
+ }),
767
+ bus.on("tool_call_start", ({ toolCallId, name, args }) => {
768
+ // A tool call ends the message it interrupted; whatever the agent says
769
+ // afterwards is a new one.
770
+ endMessage();
771
+ const locations = toolLocations(args, options.cwd);
772
+ // Snapshot BEFORE the tool runs. This handler is synchronous and the
773
+ // tool starts writing immediately after it, which is the only window
774
+ // where the file still holds its pre-edit contents.
775
+ if (DIFF_TOOLS.has(name) && locations[0]) {
776
+ diffSnapshots.set(toolCallId, {
777
+ path: locations[0].path,
778
+ before: snapshotForDiff(locations[0].path),
779
+ });
780
+ }
781
+ notifyUpdate({
782
+ sessionUpdate: "tool_call",
783
+ toolCallId,
784
+ title: toolTitle(name, args),
785
+ name,
786
+ kind: toolKind(name),
787
+ status: "in_progress",
788
+ rawInput: args,
789
+ ...(locations.length > 0 ? { locations } : {}),
790
+ });
791
+ }),
792
+ // Mid-flight tool progress. The payload is tool-defined, so it rides in
793
+ // `rawOutput` rather than being invented into content the client would
794
+ // then render as if it were final.
795
+ bus.on("tool_call_update", ({ toolCallId, update }) => {
796
+ notifyUpdate({
797
+ sessionUpdate: "tool_call_update",
798
+ toolCallId,
799
+ status: "in_progress",
800
+ rawOutput: update,
801
+ });
802
+ }),
803
+ bus.on("tool_call_end", ({ toolCallId, result, isError }) => {
804
+ notifyUpdate({
805
+ sessionUpdate: "tool_call_update",
806
+ toolCallId,
807
+ status: isError ? "failed" : "completed",
808
+ content: diffContent(toolCallId, isError) ?? [
809
+ { type: "content", content: { type: "text", text: result } },
810
+ ],
811
+ });
812
+ }),
813
+ // Turn-level outcomes are remembered rather than sent: ACP reports them
814
+ // once, as the `stopReason` of the prompt response.
815
+ bus.on("truncated", ({ reason }) => {
816
+ truncation = reason;
817
+ }),
818
+ bus.on("max_turns", () => {
819
+ hitMaxTurns = true;
820
+ }),
821
+ // Token accounting changes: after every model response, and after a
822
+ // compaction rebuilds the context. `compaction_end` fires once the
823
+ // compacted messages are installed, so the emit carries the POST-
824
+ // compaction count — the drop the client watches for.
825
+ bus.on("turn_end", () => {
826
+ notifyUsage(target);
827
+ notifySessionInfo(target);
828
+ // The turn is over: the next chunk starts a new message, and the next
829
+ // turn's markers are scanned against its own text.
830
+ endMessage();
831
+ turnText = "";
832
+ }),
833
+ bus.on("compaction_end", () => {
834
+ notifyUsage(target);
835
+ }),
836
+ ];
837
+ }
838
+ function unwireAll() {
839
+ for (const off of unwire)
840
+ off();
841
+ unwire = [];
842
+ }
843
+ /**
844
+ * Drop everything scoped to one session's lifetime. A new session inherits
845
+ * none of it: another session's plan progress, half-finished diffs or message
846
+ * numbering would all be reported as if they were its own.
847
+ */
848
+ function resetSessionState() {
849
+ diffSnapshots.clear();
850
+ planPath = undefined;
851
+ planSteps = [];
852
+ completedSteps.clear();
853
+ turnText = "";
854
+ titleAnnounced = false;
855
+ currentMessageId = null;
856
+ messageSeq = 0;
857
+ }
858
+ async function disposeSession() {
859
+ if (!session)
860
+ return;
861
+ unwireAll();
862
+ resetSessionState();
863
+ const previous = session;
864
+ session = null;
865
+ sessionId = "";
866
+ await previous.dispose();
867
+ }
868
+ /**
869
+ * Plan mode. Supplying these callbacks is what registers the
870
+ * enter_plan/exit_plan tools at all — without them the mode exists but the
871
+ * model cannot move between states. EZ Coder runs without approvals, so a
872
+ * submitted plan is auto-approved, the [DONE:n] contract is baked in so
873
+ * progress markers work as on the desktop, and the client is told about every
874
+ * mode change.
875
+ *
876
+ * They act on the CURRENT session rather than closing over one: a tool can
877
+ * only run inside a prompt, which is long after `startSession` published it.
878
+ */
879
+ const planHooks = {
880
+ onEnterPlan: async () => {
881
+ await session?.setPlanMode(true);
882
+ notifyModeChange(MODE_PLAN);
883
+ },
884
+ onExitPlan: async (approvedPath) => {
885
+ await session?.setPlanMode(false);
886
+ await session?.setApprovedPlan(approvedPath);
887
+ notifyModeChange(MODE_DEFAULT);
888
+ // The approved plan becomes the client's to-do list, which then advances
889
+ // from the [DONE:n] markers the returned instruction asks for.
890
+ adoptPlan(approvedPath);
891
+ return "Plan approved. Proceed with implementation, marking each completed step with [DONE:n].";
892
+ },
893
+ };
894
+ const createSession = options.createSession ??
895
+ ((signal, hooks) => new AgentSession({
896
+ provider: options.provider,
897
+ model: options.model,
898
+ cwd: options.cwd,
899
+ baseUrl: options.baseUrl,
900
+ systemPrompt: options.systemPrompt,
901
+ thinkingLevel: options.thinkingLevel,
902
+ // MCP connect (spawning stdio servers, HTTP handshakes) takes seconds
903
+ // and would otherwise sit on the critical path of session/new and
904
+ // session/load. The desktop sidecar already ships this path: the tool
905
+ // catalog is seeded from the disk cache so tools are visible
906
+ // immediately, and live connections promote in the background. A phone
907
+ // client gets its session in milliseconds and the same tools a moment
908
+ // later.
909
+ backgroundMcpConnect: true,
910
+ onEnterPlan: hooks.onEnterPlan,
911
+ onExitPlan: hooks.onExitPlan,
912
+ signal,
913
+ }));
914
+ // ── Method handlers ──────────────────────────────────────
915
+ function handleInitialize() {
916
+ return {
917
+ protocolVersion: ACP_PROTOCOL_VERSION,
918
+ agentCapabilities: {
919
+ loadSession: true,
920
+ promptCapabilities: { image: false, audio: false, embeddedContext: false },
921
+ mcpCapabilities: { http: false, sse: false, acp: false },
922
+ // `{}` is how ACP says "supported" for a capability with no options of
923
+ // its own. Omitting the key means unsupported, so this is not cosmetic.
924
+ sessionCapabilities: { list: {}, resume: {}, close: {}, delete: {} },
925
+ },
926
+ authMethods: [],
927
+ agentInfo: { name: "ezcoder", title: "EZ Coder", version: options.version },
928
+ };
929
+ }
930
+ /**
931
+ * Start a session, optionally replaying a stored one into it.
932
+ *
933
+ * `session/new` and `session/load` differ only in whether history is
934
+ * restored first, so they share this path rather than drifting apart.
935
+ */
936
+ async function startSession(restorePath) {
937
+ // Replacing an in-flight session would strand the prompt that is running
938
+ // on it, so the old one is stopped first, deliberately and visibly.
939
+ await disposeSession();
940
+ abort = new AbortController();
941
+ const created = createSession(abort.signal, planHooks);
942
+ await created.initialize();
943
+ if (restorePath)
944
+ await created.loadSession(restorePath);
945
+ session = created;
946
+ sessionId = created.getState().sessionId;
947
+ wire(created);
948
+ return created;
949
+ }
950
+ async function handleNewSession() {
951
+ const created = await startSession();
952
+ notifyAvailableCommands(created);
953
+ announceSessionSoon(created);
954
+ return { sessionId, configOptions: configOptionsFor(created), modes: sessionModes(created) };
955
+ }
956
+ /** The directory a request is about, defaulting to the one we were started in. */
957
+ function requestCwd(params) {
958
+ const cwd = params?.cwd;
959
+ return typeof cwd === "string" && cwd ? cwd : options.cwd;
960
+ }
961
+ /**
962
+ * Stored sessions, newest first.
963
+ *
964
+ * This is the answer to "see everything that was on EZ Coder": the phone asks
965
+ * the agent, and the agent reads the same `~/.ezcoder/sessions` files the desktop
966
+ * browses — no separate index to fall out of step.
967
+ *
968
+ * ACP makes `cwd` nullable here on purpose. Omitted means EVERY project, not
969
+ * the agent's own directory: a remote client is asking what you have been
970
+ * working on, and that spans checkouts. Each entry carries its own `cwd` so
971
+ * the client can group by project.
972
+ */
973
+ async function handleListSessions(params) {
974
+ const scope = params?.cwd;
975
+ const stored = typeof scope === "string" && scope
976
+ ? await listSessionSummaries(scope)
977
+ : await listAllSessions();
978
+ return {
979
+ sessions: stored
980
+ // An empty session has nothing to resume and nothing to title; listing
981
+ // it would fill the phone with identical blank rows.
982
+ .filter((entry) => entry.hasMessages)
983
+ .map((entry) => ({
984
+ sessionId: entry.id,
985
+ cwd: entry.cwd,
986
+ // Captured during the listing pass, so titling the whole machine's
987
+ // history costs no extra reads.
988
+ title: entry.preview ?? null,
989
+ updatedAt: entry.lastActivity,
990
+ })),
991
+ };
992
+ }
993
+ /**
994
+ * Resume a stored session and replay it to the client.
995
+ *
996
+ * The transcript is streamed as `session/update` notifications BEFORE this
997
+ * resolves, which is what the protocol requires and what lets a client draw a
998
+ * resumed conversation with its normal live-turn rendering.
999
+ */
1000
+ async function handleLoadSession(params) {
1001
+ const requested = params?.sessionId;
1002
+ if (typeof requested !== "string" || !requested) {
1003
+ throw new InvalidParams("session/load requires a sessionId.");
1004
+ }
1005
+ // Searched by id across every project directory, not just `cwd`: a client
1006
+ // lists sessions from wherever it probed and reopens them against the
1007
+ // directory the session belongs to, so those two rarely match.
1008
+ const sessionPath = await findSessionById(requested, requestCwd(params));
1009
+ if (!sessionPath)
1010
+ throw new InvalidParams(`Unknown session '${requested}'.`);
1011
+ const restored = await startSession(sessionPath);
1012
+ // Display history is reconstructed separately. The live AgentSession above
1013
+ // deliberately keeps only the canonical newest checkpoint as model context.
1014
+ const checkpoints = await loadSessionCheckpointChain(sessionPath);
1015
+ const displayMessages = reconstructCheckpointHistory(checkpoints);
1016
+ // The id the client asked for is the id it keeps using; `loadSession` may
1017
+ // adopt a different internal one, and answering with that would leave the
1018
+ // client addressing a session it never heard of.
1019
+ sessionId = requested;
1020
+ for (const update of historyUpdates(displayMessages))
1021
+ notifyUpdate(update);
1022
+ notifyAvailableCommands(restored);
1023
+ announceSessionSoon(restored);
1024
+ return { configOptions: configOptionsFor(restored), modes: sessionModes(restored) };
1025
+ }
1026
+ /**
1027
+ * Abort the running turn and WAIT for it to unwind.
1028
+ *
1029
+ * `handleCancel` only signals: `session.prompt()` keeps unwinding after it
1030
+ * returns, and its last act is persisting the turn. Disposing before that
1031
+ * finishes clears the session path out from under the write, so the final
1032
+ * exchange is silently dropped and the session is missing its tail when the
1033
+ * user comes back to it. The read loop's own teardown already waits like
1034
+ * this; a lifecycle request that tears a session down mid-turn must too.
1035
+ */
1036
+ async function cancelAndSettle() {
1037
+ handleCancel();
1038
+ await Promise.allSettled([...inFlight]);
1039
+ }
1040
+ /** The sessionId a lifecycle request names, validated. */
1041
+ function requestedSessionId(params, method) {
1042
+ const requested = params?.sessionId;
1043
+ if (typeof requested !== "string" || !requested) {
1044
+ throw new InvalidParams(`${method} requires a sessionId.`);
1045
+ }
1046
+ return requested;
1047
+ }
1048
+ /**
1049
+ * Reconnect to a stored session WITHOUT replaying it.
1050
+ *
1051
+ * The difference from `session/load` is the whole point: a client that still
1052
+ * holds the transcript (it was showing this session a moment ago) wants the
1053
+ * agent-side context back, not a second copy of every message pushed at it.
1054
+ */
1055
+ async function handleResumeSession(params) {
1056
+ const requested = requestedSessionId(params, "session/resume");
1057
+ const sessionPath = await findSessionById(requested, requestCwd(params));
1058
+ if (!sessionPath)
1059
+ throw new InvalidParams(`Unknown session '${requested}'.`);
1060
+ const restored = await startSession(sessionPath);
1061
+ // As in session/load: the client keeps addressing the id it asked for.
1062
+ sessionId = requested;
1063
+ notifyAvailableCommands(restored);
1064
+ announceSessionSoon(restored);
1065
+ return { configOptions: configOptionsFor(restored), modes: sessionModes(restored) };
1066
+ }
1067
+ /**
1068
+ * Close the active session, cancelling whatever it is doing.
1069
+ *
1070
+ * The spec requires the in-flight turn to be cancelled exactly as
1071
+ * `session/cancel` would, so this reuses that path rather than tearing the
1072
+ * session down underneath a running agent loop.
1073
+ */
1074
+ async function handleCloseSession(params) {
1075
+ const requested = requestedSessionId(params, "session/close");
1076
+ if (!session || requested !== sessionId) {
1077
+ throw new InvalidParams(`Session '${requested}' is not active.`);
1078
+ }
1079
+ await cancelAndSettle();
1080
+ await disposeSession();
1081
+ return {};
1082
+ }
1083
+ /**
1084
+ * Delete a stored session from disk.
1085
+ *
1086
+ * Hard delete, including the archive and asset siblings, because a session
1087
+ * left half-present would come back as a broken row in the next
1088
+ * `session/list`. Deleting something that is not there succeeds silently:
1089
+ * the spec asks for idempotence, and the user's intent is already satisfied.
1090
+ */
1091
+ async function handleDeleteSession(params) {
1092
+ const requested = requestedSessionId(params, "session/delete");
1093
+ const sessionPath = await findSessionById(requested, requestCwd(params));
1094
+ if (!sessionPath)
1095
+ return {};
1096
+ // Deleting the session we are serving would leave a live AgentSession
1097
+ // appending to a file that no longer exists, quietly recreating it.
1098
+ if (session && requested === sessionId) {
1099
+ await cancelAndSettle();
1100
+ await disposeSession();
1101
+ }
1102
+ const group = sessionGroupPaths(sessionPath);
1103
+ for (const target of [group.plainPath, group.archivePath, group.assetsPath]) {
1104
+ await rm(target, { recursive: true, force: true });
1105
+ }
1106
+ return {};
1107
+ }
1108
+ /**
1109
+ * Switch session mode (ACP `session/set_mode`; Zed's mode picker uses this,
1110
+ * pew2 routes it through session/set_config_option with configId "mode").
1111
+ */
1112
+ async function handleSetMode(params) {
1113
+ if (!session)
1114
+ throw new InvalidParams("No session. Call session/new first.");
1115
+ const modeId = params?.modeId;
1116
+ if (modeId !== MODE_DEFAULT && modeId !== MODE_PLAN) {
1117
+ throw new InvalidParams(`Unknown mode '${String(modeId)}'.`);
1118
+ }
1119
+ const plan = modeId === MODE_PLAN;
1120
+ if (session.getPlanMode() !== plan) {
1121
+ await session.setPlanMode(plan);
1122
+ notifyModeChange(modeId);
1123
+ }
1124
+ return {};
1125
+ }
1126
+ /**
1127
+ * Apply a selector change from the client.
1128
+ *
1129
+ * Replies with the COMPLETE option set rather than the one that changed:
1130
+ * switching model can move the thinking ceiling, and a client that only
1131
+ * patched the field it sent would keep offering levels the new model ignores.
1132
+ */
1133
+ async function handleSetConfigOption(params) {
1134
+ if (!session)
1135
+ throw new InvalidParams("No session. Call session/new first.");
1136
+ const { configId, value } = (params ?? {});
1137
+ if (typeof configId !== "string" || typeof value !== "string") {
1138
+ throw new InvalidParams("session/set_config_option requires a configId and a string value.");
1139
+ }
1140
+ if (configId === MODEL_CONFIG_ID) {
1141
+ const target = getModel(value);
1142
+ if (!target)
1143
+ throw new InvalidParams(`Unknown model '${value}'.`);
1144
+ await session.switchModel(target.provider, target.id);
1145
+ // Thinking levels are per-model, so a level the previous model allowed can
1146
+ // be above the new one's ceiling. Clamping here keeps the session's actual
1147
+ // effort equal to what the client is about to be told it is.
1148
+ const current = session.getThinkingLevel();
1149
+ if (current) {
1150
+ const allowed = thinkingOptionsFor(target.id).map((option) => option.value);
1151
+ if (!allowed.includes(current))
1152
+ session.setThinkingLevel(getMaxThinkingLevel(target.id));
1153
+ }
1154
+ }
1155
+ else if (configId === MODE_CONFIG_ID) {
1156
+ // Share the dedicated handler so both entry points behave identically.
1157
+ await handleSetMode({
1158
+ sessionId: params?.sessionId,
1159
+ modeId: value,
1160
+ });
1161
+ }
1162
+ else if (configId === THINKING_CONFIG_ID) {
1163
+ if (value === THINKING_OFF) {
1164
+ session.setThinkingLevel(undefined);
1165
+ }
1166
+ else {
1167
+ const { model } = session.getState();
1168
+ const allowed = thinkingOptionsFor(model).map((option) => option.value);
1169
+ if (!allowed.includes(value)) {
1170
+ throw new InvalidParams(`Model '${model}' does not support thinking level '${value}'.`);
1171
+ }
1172
+ session.setThinkingLevel(value);
1173
+ }
1174
+ }
1175
+ else {
1176
+ throw new InvalidParams(`Unknown config option '${configId}'.`);
1177
+ }
1178
+ return { configOptions: configOptionsFor(session) };
1179
+ }
1180
+ /** Flatten ACP prompt blocks to the plain text `AgentSession` accepts. */
1181
+ function promptText(params) {
1182
+ const blocks = params?.prompt;
1183
+ if (!Array.isArray(blocks))
1184
+ return "";
1185
+ return blocks
1186
+ .map((block) => {
1187
+ const entry = block;
1188
+ return entry?.type === "text" && typeof entry.text === "string" ? entry.text : "";
1189
+ })
1190
+ .filter(Boolean)
1191
+ .join("\n")
1192
+ .trim();
1193
+ }
1194
+ async function handlePrompt(params) {
1195
+ if (!session)
1196
+ throw new InvalidParams("No session. Call session/new first.");
1197
+ if (running)
1198
+ throw new InvalidParams("A prompt is already running in this session.");
1199
+ const text = promptText(params);
1200
+ if (!text)
1201
+ throw new InvalidParams("prompt must contain at least one non-empty text block.");
1202
+ running = true;
1203
+ cancelled = false;
1204
+ truncation = undefined;
1205
+ hitMaxTurns = false;
1206
+ try {
1207
+ await session.prompt(text);
1208
+ }
1209
+ catch (err) {
1210
+ // A cancel aborts the loop, which surfaces here. The spec requires the
1211
+ // turn to still resolve with `cancelled` rather than reject — the client
1212
+ // is confirming its own cancel, not being told something went wrong.
1213
+ if (cancelled || isAbortError(err))
1214
+ return { stopReason: "cancelled" };
1215
+ throw err;
1216
+ }
1217
+ finally {
1218
+ running = false;
1219
+ // Drop any before-snapshot whose tool never reported an end. A cancelled
1220
+ // turn stops emitting tool events, so `diffContent` — the only other
1221
+ // place these are removed — never runs for the call that was in flight,
1222
+ // and its file contents would stay pinned for the rest of the session.
1223
+ // Cleared here rather than on `turn_end`, which fires before that turn's
1224
+ // tools execute and would discard snapshots still in use.
1225
+ diffSnapshots.clear();
1226
+ }
1227
+ return { stopReason: cancelled ? "cancelled" : stopReasonFor(truncation, hitMaxTurns) };
1228
+ }
1229
+ function handleCancel() {
1230
+ if (!running || !session)
1231
+ return;
1232
+ cancelled = true;
1233
+ abort.abort();
1234
+ // `AgentSession` keeps the construction signal for future turns. Re-arm it
1235
+ // immediately after aborting this turn, or every later prompt sees an
1236
+ // already-aborted signal and silently completes with no output.
1237
+ abort = new AbortController();
1238
+ session.setSignal(abort.signal);
1239
+ }
1240
+ async function dispatch(method, params) {
1241
+ switch (method) {
1242
+ case "initialize":
1243
+ return handleInitialize();
1244
+ case "session/new":
1245
+ return handleNewSession();
1246
+ case "session/prompt":
1247
+ return handlePrompt(params);
1248
+ case "session/list":
1249
+ return handleListSessions(params);
1250
+ case "session/load":
1251
+ return handleLoadSession(params);
1252
+ case "session/resume":
1253
+ return handleResumeSession(params);
1254
+ case "session/close":
1255
+ return handleCloseSession(params);
1256
+ case "session/delete":
1257
+ return handleDeleteSession(params);
1258
+ case "session/set_config_option":
1259
+ return handleSetConfigOption(params);
1260
+ case "session/set_mode":
1261
+ return handleSetMode(params);
1262
+ default:
1263
+ return undefined;
1264
+ }
1265
+ }
1266
+ // ── Read loop ────────────────────────────────────────────
1267
+ const rl = readline.createInterface({ input, terminal: false });
1268
+ /**
1269
+ * Requests still being served.
1270
+ *
1271
+ * Requests are dispatched WITHOUT blocking the read loop, because
1272
+ * `session/prompt` runs for as long as the agent does — awaiting it here
1273
+ * would mean `session/cancel` is not read until the turn it cancels has
1274
+ * already finished. That is a deadlock, not a slow path.
1275
+ */
1276
+ const inFlight = new Set();
1277
+ function serve(id, method, params) {
1278
+ const task = (async () => {
1279
+ try {
1280
+ const result = await dispatch(method, params);
1281
+ if (result === undefined) {
1282
+ fail(id, RPC_METHOD_NOT_FOUND, `Unsupported method: ${method}`);
1283
+ return;
1284
+ }
1285
+ respond(id, result);
1286
+ }
1287
+ catch (err) {
1288
+ const message = err instanceof Error ? err.message : String(err);
1289
+ const code = err instanceof InvalidParams ? RPC_INVALID_PARAMS : RPC_INTERNAL_ERROR;
1290
+ fail(id, code, message);
1291
+ }
1292
+ })();
1293
+ inFlight.add(task);
1294
+ void task.finally(() => inFlight.delete(task));
1295
+ }
1296
+ try {
1297
+ for await (const line of rl) {
1298
+ if (!line.trim())
1299
+ continue;
1300
+ let frame;
1301
+ try {
1302
+ frame = JSON.parse(line);
1303
+ }
1304
+ catch {
1305
+ // No id to answer against, so this is the one case that uses a null id,
1306
+ // exactly as JSON-RPC 2.0 prescribes for unparseable input.
1307
+ write({
1308
+ jsonrpc: "2.0",
1309
+ id: null,
1310
+ error: { code: RPC_PARSE_ERROR, message: "Invalid JSON" },
1311
+ });
1312
+ continue;
1313
+ }
1314
+ const { id, method } = frame;
1315
+ if (typeof method !== "string") {
1316
+ if (id !== undefined)
1317
+ fail(id, RPC_INVALID_REQUEST, "Missing 'method'");
1318
+ continue;
1319
+ }
1320
+ // Notifications: no id, no response, ever — answering one is a protocol
1321
+ // violation that some clients treat as a fatal desync.
1322
+ if (id === undefined) {
1323
+ if (method === "session/cancel")
1324
+ handleCancel();
1325
+ continue;
1326
+ }
1327
+ serve(id, method, frame.params);
1328
+ }
1329
+ }
1330
+ finally {
1331
+ rl.close();
1332
+ // The client is gone, so a turn still running has nowhere to report to.
1333
+ // Cancelling first is what stops the wait below from being unbounded.
1334
+ handleCancel();
1335
+ // A disconnect mid-turn must still not tear the session out from under the
1336
+ // run that is writing to it.
1337
+ await Promise.allSettled([...inFlight]);
1338
+ await disposeSession().catch(() => { });
1339
+ }
1340
+ }
1341
+ /**
1342
+ * CLI entry point: serve ACP on the real stdio, then exit.
1343
+ *
1344
+ * Split from {@link runAcpMode} so the protocol can be tested without the
1345
+ * process-level side effects (signal handlers, logger teardown, exit codes).
1346
+ */
1347
+ export async function runAcpModeCli(options) {
1348
+ const onSigint = () => {
1349
+ // An ACP agent is driven entirely by its client; Ctrl-C is the user asking
1350
+ // the process to end, not the current turn to stop.
1351
+ process.exit(130);
1352
+ };
1353
+ process.on("SIGINT", onSigint);
1354
+ try {
1355
+ await runAcpMode(options);
1356
+ }
1357
+ catch (err) {
1358
+ process.stderr.write(`${formatUserError(err)}\n`);
1359
+ process.exitCode = 1;
1360
+ }
1361
+ finally {
1362
+ process.removeListener("SIGINT", onSigint);
1363
+ closeLogger();
1364
+ }
1365
+ }
1366
+ //# sourceMappingURL=acp-mode.js.map