@earendil-works/pi-coding-agent 0.84.4 → 0.85.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 (486) hide show
  1. package/CHANGELOG.md +57 -5
  2. package/README.md +3 -1
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +10 -7
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/runtime-setup.d.ts +2 -0
  7. package/dist/bun/runtime-setup.d.ts.map +1 -0
  8. package/dist/bun/runtime-setup.js +9 -0
  9. package/dist/bun/runtime-setup.js.map +1 -0
  10. package/dist/bundle/chunks/anthropic-messages-QAQLEQIY.js +6 -0
  11. package/dist/bundle/chunks/{azure-openai-responses-5ENVCQ7U.js → azure-openai-responses-Q7MJBRE6.js} +1 -1
  12. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  13. package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
  14. package/dist/bundle/chunks/chunk-EQ6DR6XK.js +5 -0
  15. package/dist/bundle/chunks/{chunk-MNAIPA3J.js → chunk-IDDQWTHI.js} +1 -1
  16. package/dist/bundle/chunks/chunk-LZKJTKCT.js +2 -0
  17. package/dist/bundle/chunks/{chunk-NBBFIJUL.js → chunk-OUXLLA64.js} +2 -2
  18. package/dist/bundle/chunks/chunk-R7UUJ4C2.js +42 -0
  19. package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
  20. package/dist/bundle/chunks/chunk-WBE4J4AU.js +9 -0
  21. package/dist/bundle/chunks/chunk-WZB2R5YO.js +1558 -0
  22. package/dist/bundle/chunks/github-copilot.js +1 -1
  23. package/dist/bundle/chunks/{google-generative-ai-YMUPJBKR.js → google-generative-ai-BSMTYDAM.js} +1 -1
  24. package/dist/bundle/chunks/{google-vertex-MPWMV4OF.js → google-vertex-5C3HASOJ.js} +1 -1
  25. package/dist/bundle/chunks/image-resize-worker.js +1 -1
  26. package/dist/bundle/chunks/{mistral-conversations-Q3AWZJAZ.js → mistral-conversations-4M6BZBX6.js} +1 -1
  27. package/dist/bundle/chunks/node-EM5FIVUN.js +16 -0
  28. package/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
  29. package/dist/bundle/chunks/openai-codex-responses-YJOO52RW.js +10 -0
  30. package/dist/bundle/chunks/{openai-completions-ERMU2SS7.js → openai-completions-QOT3QX7D.js} +3 -3
  31. package/dist/bundle/chunks/openai-responses-U7LNPQN4.js +2 -0
  32. package/dist/bundle/chunks/{pi-messages-TPFH44NP.js → pi-messages-EGZIFQGG.js} +1 -1
  33. package/dist/bundle/cli.js +1 -1
  34. package/dist/bundle/client.js +1 -1
  35. package/dist/bundle/coordinator.js +3 -0
  36. package/dist/bundle/index.js +1 -1
  37. package/dist/bundle/rpc-entry.js +1 -1
  38. package/dist/cli/args.d.ts.map +1 -1
  39. package/dist/cli/args.js +2 -0
  40. package/dist/cli/args.js.map +1 -1
  41. package/dist/cli/config-selector.d.ts.map +1 -1
  42. package/dist/cli/config-selector.js +2 -1
  43. package/dist/cli/config-selector.js.map +1 -1
  44. package/dist/cli/experimental/cli.d.ts +7 -3
  45. package/dist/cli/experimental/cli.d.ts.map +1 -1
  46. package/dist/cli/experimental/cli.js +6 -2
  47. package/dist/cli/experimental/cli.js.map +1 -1
  48. package/dist/cli/experimental/command-options.d.ts +23 -12
  49. package/dist/cli/experimental/command-options.d.ts.map +1 -1
  50. package/dist/cli/experimental/command-options.js +69 -20
  51. package/dist/cli/experimental/command-options.js.map +1 -1
  52. package/dist/cli/experimental/command.d.ts +10 -3
  53. package/dist/cli/experimental/command.d.ts.map +1 -1
  54. package/dist/cli/experimental/command.js +28 -14
  55. package/dist/cli/experimental/command.js.map +1 -1
  56. package/dist/cli/experimental/commands/client.d.ts +8 -2
  57. package/dist/cli/experimental/commands/client.d.ts.map +1 -1
  58. package/dist/cli/experimental/commands/client.js +43 -5
  59. package/dist/cli/experimental/commands/client.js.map +1 -1
  60. package/dist/cli/experimental/commands/server.d.ts +7 -3
  61. package/dist/cli/experimental/commands/server.d.ts.map +1 -1
  62. package/dist/cli/experimental/commands/server.js +27 -8
  63. package/dist/cli/experimental/commands/server.js.map +1 -1
  64. package/dist/cli.d.ts.map +1 -1
  65. package/dist/cli.js +23 -14
  66. package/dist/cli.js.map +1 -1
  67. package/dist/client/index.d.ts +1 -2
  68. package/dist/client/index.d.ts.map +1 -1
  69. package/dist/client/index.js +1 -2
  70. package/dist/client/index.js.map +1 -1
  71. package/dist/config.d.ts +1 -0
  72. package/dist/config.d.ts.map +1 -1
  73. package/dist/config.js +1 -0
  74. package/dist/config.js.map +1 -1
  75. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  76. package/dist/core/agent-session-runtime.js +15 -7
  77. package/dist/core/agent-session-runtime.js.map +1 -1
  78. package/dist/core/agent-session.d.ts +2 -6
  79. package/dist/core/agent-session.d.ts.map +1 -1
  80. package/dist/core/agent-session.js +14 -6
  81. package/dist/core/agent-session.js.map +1 -1
  82. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  83. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  84. package/dist/core/compaction/branch-summarization.js +2 -1
  85. package/dist/core/compaction/branch-summarization.js.map +1 -1
  86. package/dist/core/extensions/loader.d.ts.map +1 -1
  87. package/dist/core/extensions/loader.js +1 -2
  88. package/dist/core/extensions/loader.js.map +1 -1
  89. package/dist/core/extensions/types.d.ts +8 -2
  90. package/dist/core/extensions/types.d.ts.map +1 -1
  91. package/dist/core/extensions/types.js.map +1 -1
  92. package/dist/core/http-dispatcher.d.ts.map +1 -1
  93. package/dist/core/http-dispatcher.js +2 -0
  94. package/dist/core/http-dispatcher.js.map +1 -1
  95. package/dist/core/messages.d.ts +1 -1
  96. package/dist/core/messages.d.ts.map +1 -1
  97. package/dist/core/messages.js.map +1 -1
  98. package/dist/core/model-config.d.ts +15 -0
  99. package/dist/core/model-config.d.ts.map +1 -1
  100. package/dist/core/model-config.js +3 -0
  101. package/dist/core/model-config.js.map +1 -1
  102. package/dist/core/model-runtime.d.ts +1 -0
  103. package/dist/core/model-runtime.d.ts.map +1 -1
  104. package/dist/core/model-runtime.js +5 -2
  105. package/dist/core/model-runtime.js.map +1 -1
  106. package/dist/core/provider-composer.d.ts.map +1 -1
  107. package/dist/core/provider-composer.js +8 -2
  108. package/dist/core/provider-composer.js.map +1 -1
  109. package/dist/core/session-manager.d.ts +3 -2
  110. package/dist/core/session-manager.d.ts.map +1 -1
  111. package/dist/core/session-manager.js +40 -13
  112. package/dist/core/session-manager.js.map +1 -1
  113. package/dist/core/settings-manager.d.ts.map +1 -1
  114. package/dist/core/settings-manager.js.map +1 -1
  115. package/dist/core/skills.d.ts +1 -1
  116. package/dist/core/skills.d.ts.map +1 -1
  117. package/dist/core/skills.js +4 -2
  118. package/dist/core/skills.js.map +1 -1
  119. package/dist/core/system-prompt.d.ts.map +1 -1
  120. package/dist/core/system-prompt.js +8 -9
  121. package/dist/core/system-prompt.js.map +1 -1
  122. package/dist/core/tools/bash.d.ts.map +1 -1
  123. package/dist/core/tools/bash.js +3 -117
  124. package/dist/core/tools/bash.js.map +1 -1
  125. package/dist/core/tools/edit.d.ts +1 -12
  126. package/dist/core/tools/edit.d.ts.map +1 -1
  127. package/dist/core/tools/edit.js +5 -163
  128. package/dist/core/tools/edit.js.map +1 -1
  129. package/dist/core/tools/find.d.ts.map +1 -1
  130. package/dist/core/tools/find.js +4 -55
  131. package/dist/core/tools/find.js.map +1 -1
  132. package/dist/core/tools/grep.d.ts.map +1 -1
  133. package/dist/core/tools/grep.js +4 -60
  134. package/dist/core/tools/grep.js.map +1 -1
  135. package/dist/core/tools/ls.d.ts.map +1 -1
  136. package/dist/core/tools/ls.js +4 -49
  137. package/dist/core/tools/ls.js.map +1 -1
  138. package/dist/core/tools/read.d.ts.map +1 -1
  139. package/dist/core/tools/read.js +4 -120
  140. package/dist/core/tools/read.js.map +1 -1
  141. package/dist/core/tools/renderers/bash.d.ts +12 -0
  142. package/dist/core/tools/renderers/bash.d.ts.map +1 -0
  143. package/dist/core/tools/renderers/bash.js +130 -0
  144. package/dist/core/tools/renderers/bash.js.map +1 -0
  145. package/dist/core/tools/renderers/edit.d.ts +23 -0
  146. package/dist/core/tools/renderers/edit.d.ts.map +1 -0
  147. package/dist/core/tools/renderers/edit.js +167 -0
  148. package/dist/core/tools/renderers/edit.js.map +1 -0
  149. package/dist/core/tools/renderers/find.d.ts +10 -0
  150. package/dist/core/tools/renderers/find.d.ts.map +1 -0
  151. package/dist/core/tools/renderers/find.js +64 -0
  152. package/dist/core/tools/renderers/find.js.map +1 -0
  153. package/dist/core/tools/renderers/grep.d.ts +10 -0
  154. package/dist/core/tools/renderers/grep.d.ts.map +1 -0
  155. package/dist/core/tools/renderers/grep.js +69 -0
  156. package/dist/core/tools/renderers/grep.js.map +1 -0
  157. package/dist/core/tools/renderers/index.d.ts +28 -0
  158. package/dist/core/tools/renderers/index.d.ts.map +1 -0
  159. package/dist/core/tools/renderers/index.js +47 -0
  160. package/dist/core/tools/renderers/index.js.map +1 -0
  161. package/dist/core/tools/renderers/ls.d.ts +10 -0
  162. package/dist/core/tools/renderers/ls.d.ts.map +1 -0
  163. package/dist/core/tools/renderers/ls.js +58 -0
  164. package/dist/core/tools/renderers/ls.js.map +1 -0
  165. package/dist/core/tools/renderers/read.d.ts +11 -0
  166. package/dist/core/tools/renderers/read.d.ts.map +1 -0
  167. package/dist/core/tools/renderers/read.js +129 -0
  168. package/dist/core/tools/renderers/read.js.map +1 -0
  169. package/dist/core/tools/renderers/write.d.ts +10 -0
  170. package/dist/core/tools/renderers/write.d.ts.map +1 -0
  171. package/dist/core/tools/renderers/write.js +153 -0
  172. package/dist/core/tools/renderers/write.js.map +1 -0
  173. package/dist/core/tools/write.d.ts.map +1 -1
  174. package/dist/core/tools/write.js +5 -146
  175. package/dist/core/tools/write.js.map +1 -1
  176. package/dist/experimental/client-runtime.d.ts +43 -0
  177. package/dist/experimental/client-runtime.d.ts.map +1 -0
  178. package/dist/experimental/client-runtime.js +183 -0
  179. package/dist/experimental/client-runtime.js.map +1 -0
  180. package/dist/experimental/client-tui-chat.d.ts +14 -0
  181. package/dist/experimental/client-tui-chat.d.ts.map +1 -0
  182. package/dist/experimental/client-tui-chat.js +171 -0
  183. package/dist/experimental/client-tui-chat.js.map +1 -0
  184. package/dist/experimental/client-tui.d.ts +37 -0
  185. package/dist/experimental/client-tui.d.ts.map +1 -0
  186. package/dist/experimental/client-tui.js +702 -0
  187. package/dist/experimental/client-tui.js.map +1 -0
  188. package/dist/experimental/client.d.ts +25 -0
  189. package/dist/experimental/client.d.ts.map +1 -0
  190. package/dist/experimental/client.js +99 -0
  191. package/dist/experimental/client.js.map +1 -0
  192. package/dist/experimental/coordinator-entry.d.ts +3 -0
  193. package/dist/experimental/coordinator-entry.d.ts.map +1 -0
  194. package/dist/experimental/coordinator-entry.js +11 -0
  195. package/dist/experimental/coordinator-entry.js.map +1 -0
  196. package/dist/experimental/coordinator.d.ts +38 -0
  197. package/dist/experimental/coordinator.d.ts.map +1 -0
  198. package/dist/experimental/coordinator.js +570 -0
  199. package/dist/experimental/coordinator.js.map +1 -0
  200. package/dist/experimental/mini/main.d.ts +7 -0
  201. package/dist/experimental/mini/main.d.ts.map +1 -0
  202. package/dist/experimental/mini/main.js +21 -0
  203. package/dist/experimental/mini/main.js.map +1 -0
  204. package/dist/experimental/mini/server/entry.d.ts +3 -0
  205. package/dist/experimental/mini/server/entry.d.ts.map +1 -0
  206. package/dist/experimental/mini/server/entry.js +11 -0
  207. package/dist/experimental/mini/server/entry.js.map +1 -0
  208. package/dist/experimental/mini/server/run.d.ts +13 -0
  209. package/dist/experimental/mini/server/run.d.ts.map +1 -0
  210. package/dist/experimental/mini/server/run.js +150 -0
  211. package/dist/experimental/mini/server/run.js.map +1 -0
  212. package/dist/experimental/mini/shared/protocol.d.ts +127 -0
  213. package/dist/experimental/mini/shared/protocol.d.ts.map +1 -0
  214. package/dist/experimental/mini/shared/protocol.js +13 -0
  215. package/dist/experimental/mini/shared/protocol.js.map +1 -0
  216. package/dist/experimental/mini/shared/rpc.d.ts +49 -0
  217. package/dist/experimental/mini/shared/rpc.d.ts.map +1 -0
  218. package/dist/experimental/mini/shared/rpc.js +171 -0
  219. package/dist/experimental/mini/shared/rpc.js.map +1 -0
  220. package/dist/experimental/mini/shared/transport.d.ts +35 -0
  221. package/dist/experimental/mini/shared/transport.d.ts.map +1 -0
  222. package/dist/experimental/mini/shared/transport.js +94 -0
  223. package/dist/experimental/mini/shared/transport.js.map +1 -0
  224. package/dist/experimental/mini/tui/run.d.ts +9 -0
  225. package/dist/experimental/mini/tui/run.d.ts.map +1 -0
  226. package/dist/experimental/mini/tui/run.js +62 -0
  227. package/dist/experimental/mini/tui/run.js.map +1 -0
  228. package/dist/experimental/mini/tui/session.d.ts +26 -0
  229. package/dist/experimental/mini/tui/session.d.ts.map +1 -0
  230. package/dist/experimental/mini/tui/session.js +94 -0
  231. package/dist/experimental/mini/tui/session.js.map +1 -0
  232. package/dist/experimental/mini/tui/view.d.ts +10 -0
  233. package/dist/experimental/mini/tui/view.d.ts.map +1 -0
  234. package/dist/experimental/mini/tui/view.js +459 -0
  235. package/dist/experimental/mini/tui/view.js.map +1 -0
  236. package/dist/experimental/mini/worker/entry.d.ts +3 -0
  237. package/dist/experimental/mini/worker/entry.d.ts.map +1 -0
  238. package/dist/experimental/mini/worker/entry.js +10 -0
  239. package/dist/experimental/mini/worker/entry.js.map +1 -0
  240. package/dist/experimental/mini/worker/lane-service.d.ts +44 -0
  241. package/dist/experimental/mini/worker/lane-service.d.ts.map +1 -0
  242. package/dist/experimental/mini/worker/lane-service.js +97 -0
  243. package/dist/experimental/mini/worker/lane-service.js.map +1 -0
  244. package/dist/experimental/mini/worker/models-service.d.ts +19 -0
  245. package/dist/experimental/mini/worker/models-service.d.ts.map +1 -0
  246. package/dist/experimental/mini/worker/models-service.js +136 -0
  247. package/dist/experimental/mini/worker/models-service.js.map +1 -0
  248. package/dist/experimental/mini/worker/run.d.ts +14 -0
  249. package/dist/experimental/mini/worker/run.d.ts.map +1 -0
  250. package/dist/experimental/mini/worker/run.js +88 -0
  251. package/dist/experimental/mini/worker/run.js.map +1 -0
  252. package/dist/experimental/plugin.d.ts +4 -0
  253. package/dist/experimental/plugin.d.ts.map +1 -0
  254. package/dist/experimental/plugin.js +4 -0
  255. package/dist/experimental/plugin.js.map +1 -0
  256. package/dist/experimental/plugins/bundled.d.ts +7 -0
  257. package/dist/experimental/plugins/bundled.d.ts.map +1 -0
  258. package/dist/experimental/plugins/bundled.js +48 -0
  259. package/dist/experimental/plugins/bundled.js.map +1 -0
  260. package/dist/experimental/plugins/package.d.ts +18 -0
  261. package/dist/experimental/plugins/package.d.ts.map +1 -0
  262. package/dist/experimental/plugins/package.js +113 -0
  263. package/dist/experimental/plugins/package.js.map +1 -0
  264. package/dist/experimental/process.d.ts +20 -0
  265. package/dist/experimental/process.d.ts.map +1 -0
  266. package/dist/experimental/process.js +85 -0
  267. package/dist/experimental/process.js.map +1 -0
  268. package/dist/experimental/radius-auth.d.ts +17 -0
  269. package/dist/experimental/radius-auth.d.ts.map +1 -0
  270. package/dist/experimental/radius-auth.js +52 -0
  271. package/dist/experimental/radius-auth.js.map +1 -0
  272. package/dist/experimental/radius-relay.d.ts +90 -0
  273. package/dist/experimental/radius-relay.d.ts.map +1 -0
  274. package/dist/experimental/radius-relay.js +582 -0
  275. package/dist/experimental/radius-relay.js.map +1 -0
  276. package/dist/experimental/server.d.ts +75 -0
  277. package/dist/experimental/server.d.ts.map +1 -0
  278. package/dist/experimental/server.js +682 -0
  279. package/dist/experimental/server.js.map +1 -0
  280. package/dist/experimental/services/agent-controller-provider.d.ts +4 -0
  281. package/dist/experimental/services/agent-controller-provider.d.ts.map +1 -0
  282. package/dist/experimental/services/agent-controller-provider.js +84 -0
  283. package/dist/experimental/services/agent-controller-provider.js.map +1 -0
  284. package/dist/experimental/services/agent-controller.d.ts +57 -0
  285. package/dist/experimental/services/agent-controller.d.ts.map +1 -0
  286. package/dist/experimental/services/agent-controller.js +3 -0
  287. package/dist/experimental/services/agent-controller.js.map +1 -0
  288. package/dist/experimental/services/connection.d.ts +40 -0
  289. package/dist/experimental/services/connection.d.ts.map +1 -0
  290. package/dist/experimental/services/connection.js +300 -0
  291. package/dist/experimental/services/connection.js.map +1 -0
  292. package/dist/experimental/services/models-provider.d.ts +16 -0
  293. package/dist/experimental/services/models-provider.d.ts.map +1 -0
  294. package/dist/experimental/services/models-provider.js +116 -0
  295. package/dist/experimental/services/models-provider.js.map +1 -0
  296. package/dist/experimental/services/models.d.ts +36 -0
  297. package/dist/experimental/services/models.d.ts.map +1 -0
  298. package/dist/experimental/services/models.js +3 -0
  299. package/dist/experimental/services/models.js.map +1 -0
  300. package/dist/experimental/services/plugins.d.ts +16 -0
  301. package/dist/experimental/services/plugins.d.ts.map +1 -0
  302. package/dist/experimental/services/plugins.js +4 -0
  303. package/dist/experimental/services/plugins.js.map +1 -0
  304. package/dist/experimental/services/presentation-ui.d.ts +13 -0
  305. package/dist/experimental/services/presentation-ui.d.ts.map +1 -0
  306. package/dist/experimental/services/presentation-ui.js +3 -0
  307. package/dist/experimental/services/presentation-ui.js.map +1 -0
  308. package/dist/experimental/services/server.d.ts +19 -0
  309. package/dist/experimental/services/server.d.ts.map +1 -0
  310. package/dist/experimental/services/server.js +104 -0
  311. package/dist/experimental/services/server.js.map +1 -0
  312. package/dist/experimental/services/sessions.d.ts +28 -0
  313. package/dist/experimental/services/sessions.d.ts.map +1 -0
  314. package/dist/experimental/services/sessions.js +4 -0
  315. package/dist/experimental/services/sessions.js.map +1 -0
  316. package/dist/experimental/services/slash-commands-provider.d.ts +14 -0
  317. package/dist/experimental/services/slash-commands-provider.d.ts.map +1 -0
  318. package/dist/experimental/services/slash-commands-provider.js +200 -0
  319. package/dist/experimental/services/slash-commands-provider.js.map +1 -0
  320. package/dist/experimental/services/slash-commands.d.ts +24 -0
  321. package/dist/experimental/services/slash-commands.d.ts.map +1 -0
  322. package/dist/experimental/services/slash-commands.js +3 -0
  323. package/dist/experimental/services/slash-commands.js.map +1 -0
  324. package/dist/experimental/services/transcript-provider.d.ts +12 -0
  325. package/dist/experimental/services/transcript-provider.d.ts.map +1 -0
  326. package/dist/experimental/services/transcript-provider.js +109 -0
  327. package/dist/experimental/services/transcript-provider.js.map +1 -0
  328. package/dist/experimental/services/transcript.d.ts +13 -0
  329. package/dist/experimental/services/transcript.d.ts.map +1 -0
  330. package/dist/experimental/services/transcript.js +3 -0
  331. package/dist/experimental/services/transcript.js.map +1 -0
  332. package/dist/experimental/services/worker.d.ts +28 -0
  333. package/dist/experimental/services/worker.d.ts.map +1 -0
  334. package/dist/experimental/services/worker.js +107 -0
  335. package/dist/experimental/services/worker.js.map +1 -0
  336. package/dist/experimental/session-worker-manager.d.ts +24 -0
  337. package/dist/experimental/session-worker-manager.d.ts.map +1 -0
  338. package/dist/experimental/session-worker-manager.js +691 -0
  339. package/dist/experimental/session-worker-manager.js.map +1 -0
  340. package/dist/experimental/session-worker.d.ts +188 -0
  341. package/dist/experimental/session-worker.d.ts.map +1 -0
  342. package/dist/experimental/session-worker.js +785 -0
  343. package/dist/experimental/session-worker.js.map +1 -0
  344. package/dist/experimental/source-resolver.d.ts +2 -0
  345. package/dist/experimental/source-resolver.d.ts.map +1 -0
  346. package/dist/experimental/source-resolver.js +71 -0
  347. package/dist/experimental/source-resolver.js.map +1 -0
  348. package/dist/index.d.ts +1 -1
  349. package/dist/index.d.ts.map +1 -1
  350. package/dist/index.js.map +1 -1
  351. package/dist/main.d.ts.map +1 -1
  352. package/dist/main.js +126 -1
  353. package/dist/main.js.map +1 -1
  354. package/dist/modes/interactive/chat-viewport.d.ts +20 -0
  355. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
  356. package/dist/modes/interactive/chat-viewport.js +28 -0
  357. package/dist/modes/interactive/chat-viewport.js.map +1 -0
  358. package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  359. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  360. package/dist/modes/interactive/components/assistant-message.js +18 -10
  361. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  362. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  363. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  364. package/dist/modes/interactive/components/custom-editor.js +39 -1
  365. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  366. package/dist/modes/interactive/components/index.d.ts +1 -1
  367. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  368. package/dist/modes/interactive/components/index.js.map +1 -1
  369. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  370. package/dist/modes/interactive/components/model-selector.js +5 -14
  371. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  372. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  373. package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
  374. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  375. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  376. package/dist/modes/interactive/components/settings-selector.js +14 -10
  377. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  378. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  379. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  380. package/dist/modes/interactive/components/status-indicator.js +10 -3
  381. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  382. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  383. package/dist/modes/interactive/components/thinking-selector.js +2 -2
  384. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  385. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  386. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  387. package/dist/modes/interactive/components/tool-execution.js +35 -33
  388. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  389. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
  390. package/dist/modes/interactive/components/trust-selector.js +2 -2
  391. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  392. package/dist/modes/interactive/interactive-mode.d.ts +8 -15
  393. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  394. package/dist/modes/interactive/interactive-mode.js +95 -80
  395. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  396. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  397. package/dist/modes/interactive/session-share.js +8 -12
  398. package/dist/modes/interactive/session-share.js.map +1 -1
  399. package/dist/modes/interactive/theme/dark.json +2 -1
  400. package/dist/modes/interactive/theme/light.json +2 -1
  401. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  402. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  403. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  404. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  405. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  406. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  407. package/dist/modes/interactive/theme/theme-json.js +130 -0
  408. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  409. package/dist/modes/interactive/theme/theme-schema.json +6 -2
  410. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  411. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  412. package/dist/modes/interactive/theme/theme.js +18 -119
  413. package/dist/modes/interactive/theme/theme.js.map +1 -1
  414. package/dist/modes/interactive/tui-renderer.d.ts +21 -0
  415. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
  416. package/dist/modes/interactive/tui-renderer.js +66 -0
  417. package/dist/modes/interactive/tui-renderer.js.map +1 -0
  418. package/dist/utils/exif-orientation.d.ts.map +1 -1
  419. package/dist/utils/exif-orientation.js +2 -3
  420. package/dist/utils/exif-orientation.js.map +1 -1
  421. package/dist/utils/tools-manager.d.ts +1 -0
  422. package/dist/utils/tools-manager.d.ts.map +1 -1
  423. package/dist/utils/tools-manager.js +41 -19
  424. package/dist/utils/tools-manager.js.map +1 -1
  425. package/docs/environment-variables.md +2 -0
  426. package/docs/extensions.md +5 -2
  427. package/docs/keybindings.md +3 -1
  428. package/docs/models.md +3 -0
  429. package/docs/rpc.md +1 -1
  430. package/docs/sdk.md +5 -0
  431. package/docs/settings.md +5 -5
  432. package/docs/skills.md +1 -1
  433. package/docs/terminal-setup.md +15 -0
  434. package/docs/themes.md +8 -6
  435. package/docs/tui.md +20 -1
  436. package/examples/README.md +3 -0
  437. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  438. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  439. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  440. package/examples/extensions/dynamic-resources/dynamic.json +2 -0
  441. package/examples/extensions/gondolin/package-lock.json +2 -2
  442. package/examples/extensions/gondolin/package.json +1 -1
  443. package/examples/extensions/sandbox/package-lock.json +2 -2
  444. package/examples/extensions/sandbox/package.json +1 -1
  445. package/examples/extensions/with-deps/package-lock.json +2 -2
  446. package/examples/extensions/with-deps/package.json +1 -1
  447. package/examples/plugins/pi-example-plugin/README.md +38 -0
  448. package/examples/plugins/pi-example-plugin/package.json +10 -0
  449. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  450. package/examples/plugins/pi-example-plugin/src/session.ts +23 -0
  451. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  452. package/npm-shrinkwrap.json +523 -28
  453. package/package.json +12 -6
  454. package/dist/bun/register-bedrock.d.ts +0 -2
  455. package/dist/bun/register-bedrock.d.ts.map +0 -1
  456. package/dist/bun/register-bedrock.js +0 -4
  457. package/dist/bun/register-bedrock.js.map +0 -1
  458. package/dist/bundle/chunks/anthropic-messages-JWX2WP65.js +0 -45
  459. package/dist/bundle/chunks/chunk-JUUC5AZY.js +0 -2
  460. package/dist/bundle/chunks/chunk-OMWWHBTG.js +0 -1548
  461. package/dist/bundle/chunks/openai-codex-responses-GJVBJXLB.js +0 -8
  462. package/dist/bundle/chunks/openai-responses-BSOJMSEV.js +0 -2
  463. package/dist/cli/experimental/auth.d.ts +0 -16
  464. package/dist/cli/experimental/auth.d.ts.map +0 -1
  465. package/dist/cli/experimental/auth.js +0 -13
  466. package/dist/cli/experimental/auth.js.map +0 -1
  467. package/dist/cli/experimental/commands/pi.d.ts +0 -15
  468. package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
  469. package/dist/cli/experimental/commands/pi.js +0 -28
  470. package/dist/cli/experimental/commands/pi.js.map +0 -1
  471. package/dist/cli/experimental/transport-address.d.ts +0 -10
  472. package/dist/cli/experimental/transport-address.d.ts.map +0 -1
  473. package/dist/cli/experimental/transport-address.js +0 -38
  474. package/dist/cli/experimental/transport-address.js.map +0 -1
  475. package/dist/client/remote-session.d.ts +0 -53
  476. package/dist/client/remote-session.d.ts.map +0 -1
  477. package/dist/client/remote-session.js +0 -340
  478. package/dist/client/remote-session.js.map +0 -1
  479. package/dist/client/transcript.d.ts +0 -12
  480. package/dist/client/transcript.d.ts.map +0 -1
  481. package/dist/client/transcript.js +0 -98
  482. package/dist/client/transcript.js.map +0 -1
  483. package/dist/server/create-harness.d.ts +0 -26
  484. package/dist/server/create-harness.d.ts.map +0 -1
  485. package/dist/server/create-harness.js +0 -106
  486. package/dist/server/create-harness.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The `mini` command. Runs the presentation host.
3
+ *
4
+ * Run: node packages/coding-agent/src/experimental/mini/main.ts [--continue]
5
+ */
6
+ import { runTui } from "./tui/run.js";
7
+ function parseArgs(argv) {
8
+ const options = {};
9
+ for (const arg of argv) {
10
+ if (arg === "--continue" || arg === "-c")
11
+ options.continueSession = true;
12
+ else
13
+ throw new Error(`Unknown argument: ${arg}`);
14
+ }
15
+ return options;
16
+ }
17
+ runTui(parseArgs(process.argv.slice(2))).then(() => process.exit(0), (error) => {
18
+ console.error(error);
19
+ process.exit(1);
20
+ });
21
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../../src/experimental/mini/main.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAmB,MAAM,cAAc,CAAC;AAEvD,SAAS,SAAS,CAAC,IAAuB,EAAc;IACvD,MAAM,OAAO,GAAe,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;;YACpE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,OAAO,CAAC;AAAA,CACf;AAED,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC5C,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EACrB,CAAC,KAAc,EAAE,EAAE,CAAC;IACnB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,CAChB,CACD,CAAC","sourcesContent":["/**\n * The `mini` command. Runs the presentation host.\n *\n * Run: node packages/coding-agent/src/experimental/mini/main.ts [--continue]\n */\n\nimport { runTui, type TuiOptions } from \"./tui/run.ts\";\n\nfunction parseArgs(argv: readonly string[]): TuiOptions {\n\tconst options: TuiOptions = {};\n\tfor (const arg of argv) {\n\t\tif (arg === \"--continue\" || arg === \"-c\") options.continueSession = true;\n\t\telse throw new Error(`Unknown argument: ${arg}`);\n\t}\n\treturn options;\n}\n\nrunTui(parseArgs(process.argv.slice(2))).then(\n\t() => process.exit(0),\n\t(error: unknown) => {\n\t\tconsole.error(error);\n\t\tprocess.exit(1);\n\t},\n);\n"]}
@@ -0,0 +1,3 @@
1
+ /** Server process entry: `node server/entry.ts <socketPath> <sessionsRoot>`. Spawned detached by the CLI. */
2
+ export {};
3
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/server/entry.ts"],"names":[],"mappings":"AAAA,6GAA6G","sourcesContent":["/** Server process entry: `node server/entry.ts <socketPath> <sessionsRoot>`. Spawned detached by the CLI. */\n\nimport { socketTransport } from \"../shared/transport.ts\";\nimport { runServer } from \"./run.ts\";\n\nconst [socketPath, sessionsRoot] = process.argv.slice(2);\nif (!socketPath || !sessionsRoot) throw new Error(\"Server requires <socketPath> <sessionsRoot>\");\n\nvoid runServer({ transport: socketTransport(socketPath), sessionsRoot }).catch((error: unknown) => {\n\tconsole.error(error);\n\tprocess.exit(1);\n});\n"]}
@@ -0,0 +1,11 @@
1
+ /** Server process entry: `node server/entry.ts <socketPath> <sessionsRoot>`. Spawned detached by the CLI. */
2
+ import { socketTransport } from "../shared/transport.js";
3
+ import { runServer } from "./run.js";
4
+ const [socketPath, sessionsRoot] = process.argv.slice(2);
5
+ if (!socketPath || !sessionsRoot)
6
+ throw new Error("Server requires <socketPath> <sessionsRoot>");
7
+ void runServer({ transport: socketTransport(socketPath), sessionsRoot }).catch((error) => {
8
+ console.error(error);
9
+ process.exit(1);
10
+ });
11
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../src/experimental/mini/server/entry.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAE7G,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzD,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY;IAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;AAEjG,KAAK,SAAS,CAAC,EAAE,SAAS,EAAE,eAAe,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC;IAClG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,CAChB,CAAC,CAAC","sourcesContent":["/** Server process entry: `node server/entry.ts <socketPath> <sessionsRoot>`. Spawned detached by the CLI. */\n\nimport { socketTransport } from \"../shared/transport.ts\";\nimport { runServer } from \"./run.ts\";\n\nconst [socketPath, sessionsRoot] = process.argv.slice(2);\nif (!socketPath || !sessionsRoot) throw new Error(\"Server requires <socketPath> <sessionsRoot>\");\n\nvoid runServer({ transport: socketTransport(socketPath), sessionsRoot }).catch((error: unknown) => {\n\tconsole.error(error);\n\tprocess.exit(1);\n});\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Session server: accepts client connections, spawns one worker process per session, routes.
3
+ *
4
+ * It provides `Sessions` and holds no agent state. Any other service name is forwarded to the worker
5
+ * the calling client is attached to, and every worker event is pushed back to that worker's clients.
6
+ * Workers reach `Sessions` over the same peer, because the routing rule is symmetric.
7
+ */
8
+ import { type Transport } from "../shared/transport.ts";
9
+ export declare function runServer(options: {
10
+ transport: Transport;
11
+ sessionsRoot: string;
12
+ }): Promise<void>;
13
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/server/run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAkCzE,wBAAsB,SAAS,CAAC,OAAO,EAAE;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4GtG","sourcesContent":["/**\n * Session server: accepts client connections, spawns one worker process per session, routes.\n *\n * It provides `Sessions` and holds no agent state. Any other service name is forwarded to the worker\n * the calling client is attached to, and every worker event is pushed back to that worker's clients.\n * Workers reach `Sessions` over the same peer, because the routing rule is symmetric.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { extname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n// Narrow entries: the server routes and lists sessions, it never runs an agent.\nimport { BACKGROUND_CONTEXT } from \"@earendil-works/pi-agent-core/harness/context\";\nimport { NodeExecutionEnv } from \"@earendil-works/pi-agent-core/harness/env/nodejs\";\nimport { JsonlSessionRepo } from \"@earendil-works/pi-agent-core/harness/session\";\nimport { type SessionSummary, Sessions, type SessionsServiceApi, Worker } from \"../shared/protocol.ts\";\nimport { createPeer, type RpcPeer } from \"../shared/rpc.ts\";\nimport { childConnection, type Transport } from \"../shared/transport.ts\";\n\nconst SELF_EXTENSION = extname(fileURLToPath(import.meta.url));\nconst WORKER_ENTRY = fileURLToPath(new URL(`../worker/entry${SELF_EXTENSION}`, import.meta.url));\nconst WORKER_START_TIMEOUT_MS = 30_000;\n/** Grace period before an idle server retires, so a reconnecting presentation does not race it. */\nconst IDLE_SHUTDOWN_MS = 10_000;\n\n/** The server's routing entry for one session worker process. The worker knows none of this. */\ninterface Route {\n\tsessionId: string;\n\t/** Peer to the worker process. */\n\tworker: RpcPeer;\n\t/** Attached presentations by id, so an addressed event goes to exactly one of them. */\n\tsubscribers: Map<string, RpcPeer>;\n\tstop(): void;\n}\n\nasync function listSessions(sessionsRoot: string): Promise<SessionSummary[]> {\n\tconst executionEnv = new NodeExecutionEnv({ cwd: process.cwd() });\n\tconst repo = new JsonlSessionRepo({ fileSystem: executionEnv, sessionsRoot });\n\ttry {\n\t\treturn (await repo.list(undefined, BACKGROUND_CONTEXT)).map((metadata) => ({\n\t\t\tid: metadata.id,\n\t\t\tpath: metadata.path,\n\t\t\tcwd: metadata.cwd,\n\t\t\tcreatedAt: metadata.createdAt,\n\t\t}));\n\t} finally {\n\t\tawait repo.close(BACKGROUND_CONTEXT);\n\t\tawait executionEnv.cleanup(BACKGROUND_CONTEXT);\n\t}\n}\n\nexport async function runServer(options: { transport: Transport; sessionsRoot: string }): Promise<void> {\n\tconst routes = new Map<string, Route>();\n\tlet presentations = 0;\n\tlet retire = (): void => {};\n\tconst retired = new Promise<void>((resolve) => {\n\t\tretire = resolve;\n\t});\n\tlet idleTimer: NodeJS.Timeout | undefined;\n\t/** Nothing to serve and nothing running: leave, so the next start always gets current code. */\n\tconst considerRetiring = (): void => {\n\t\tif (idleTimer) clearTimeout(idleTimer);\n\t\tif (presentations > 0 || routes.size > 0) return;\n\t\tidleTimer = setTimeout(() => {\n\t\t\tif (presentations === 0 && routes.size === 0) retire();\n\t\t}, IDLE_SHUTDOWN_MS);\n\t\tidleTimer.unref();\n\t};\n\t/** Concurrent attaches to one session must share a worker: two writers would corrupt the session. */\n\tconst spawning = new Map<string, Promise<Route>>();\n\n\tconst spawnWorker = async (sessionId: string | undefined, cwd: string): Promise<Route> => {\n\t\tconst args = [...process.execArgv, WORKER_ENTRY, options.sessionsRoot, cwd, ...(sessionId ? [sessionId] : [])];\n\t\tconst child = spawn(process.execPath, args, { stdio: [\"pipe\", \"pipe\", \"inherit\"] });\n\t\tconst connection = childConnection(child);\n\t\tconst peer = createPeer(connection);\n\t\t// Workers consume `Sessions` through this same peer.\n\t\tpeer.provide(Sessions, { list, attach: attachUnsupported });\n\t\t// A worker that never answers must not wedge the attach that spawned it.\n\t\tconst described = await peer.use(Worker, { timeoutMs: WORKER_START_TIMEOUT_MS }).describe();\n\t\tconst route: Route = {\n\t\t\tsessionId: described.sessionId,\n\t\t\tworker: peer,\n\t\t\tsubscribers: new Map(),\n\t\t\tstop: () => child.kill(),\n\t\t};\n\t\t// Routing is the server's job. An addressed event reaches one presentation; the rest are shared.\n\t\tpeer.onEvent((service, payload, to) => {\n\t\t\tif (to !== undefined) {\n\t\t\t\troute.subscribers.get(to)?.emitRaw(service, payload);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const subscriber of route.subscribers.values()) subscriber.emitRaw(service, payload);\n\t\t});\n\t\tpeer.onClose(() => {\n\t\t\troutes.delete(route.sessionId);\n\t\t\tconsiderRetiring();\n\t\t});\n\t\troutes.set(route.sessionId, route);\n\t\treturn route;\n\t};\n\n\tconst ensureRoute = (sessionId: string | null, cwd: string): Promise<Route> => {\n\t\tif (sessionId === null) return spawnWorker(undefined, cwd);\n\t\tconst existing = routes.get(sessionId);\n\t\tif (existing) return Promise.resolve(existing);\n\t\tconst pending = spawning.get(sessionId);\n\t\tif (pending) return pending;\n\t\tconst started = spawnWorker(sessionId, cwd).finally(() => spawning.delete(sessionId));\n\t\tspawning.set(sessionId, started);\n\t\treturn started;\n\t};\n\n\tconst list = (): Promise<SessionSummary[]> => listSessions(options.sessionsRoot);\n\tconst attachUnsupported = async (): Promise<string> => {\n\t\tthrow new Error(\"Only presentations attach to sessions\");\n\t};\n\n\tconst listener = await options.transport.listen((connection) => {\n\t\tpresentations += 1;\n\t\tlet route: Route | undefined;\n\t\tlet attachedAs: string | undefined;\n\t\tconst sessions: SessionsServiceApi = {\n\t\t\tlist,\n\t\t\tattach: async (sessionId, cwd, presentationId) => {\n\t\t\t\troute?.subscribers.delete(attachedAs ?? \"\");\n\t\t\t\tattachedAs = presentationId;\n\t\t\t\troute = await ensureRoute(sessionId, cwd);\n\t\t\t\troute.subscribers.set(presentationId, presentation);\n\t\t\t\treturn route.sessionId;\n\t\t\t},\n\t\t};\n\t\tconst presentation: RpcPeer = createPeer(connection, {\n\t\t\tforward: (method, args) => {\n\t\t\t\tif (!route) throw new Error(\"Not attached to a session\");\n\t\t\t\tconst service = method.slice(0, method.indexOf(\".\"));\n\t\t\t\tif (!route.worker.announced.has(service)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No host provides ${service}: server has [${[...presentation.provided]}], worker has [${[...route.worker.announced]}]`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn route.worker.call(method, ...args);\n\t\t\t},\n\t\t});\n\t\tpresentation.provide(Sessions, sessions);\n\t\tconnection.onClose(() => {\n\t\t\tpresentations -= 1;\n\t\t\tif (route && attachedAs !== undefined) {\n\t\t\t\troute.subscribers.delete(attachedAs);\n\t\t\t\t// One worker per session, kept alive only while someone is looking at it.\n\t\t\t\tif (route.subscribers.size === 0) route.stop();\n\t\t\t}\n\t\t\tconsiderRetiring();\n\t\t});\n\t});\n\n\tconsiderRetiring();\n\tawait retired;\n\tawait listener.close();\n}\n"]}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Session server: accepts client connections, spawns one worker process per session, routes.
3
+ *
4
+ * It provides `Sessions` and holds no agent state. Any other service name is forwarded to the worker
5
+ * the calling client is attached to, and every worker event is pushed back to that worker's clients.
6
+ * Workers reach `Sessions` over the same peer, because the routing rule is symmetric.
7
+ */
8
+ import { spawn } from "node:child_process";
9
+ import { extname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ // Narrow entries: the server routes and lists sessions, it never runs an agent.
12
+ import { BACKGROUND_CONTEXT } from "@earendil-works/pi-agent-core/harness/context";
13
+ import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/harness/env/nodejs";
14
+ import { JsonlSessionRepo } from "@earendil-works/pi-agent-core/harness/session";
15
+ import { Sessions, Worker } from "../shared/protocol.js";
16
+ import { createPeer } from "../shared/rpc.js";
17
+ import { childConnection } from "../shared/transport.js";
18
+ const SELF_EXTENSION = extname(fileURLToPath(import.meta.url));
19
+ const WORKER_ENTRY = fileURLToPath(new URL(`../worker/entry${SELF_EXTENSION}`, import.meta.url));
20
+ const WORKER_START_TIMEOUT_MS = 30_000;
21
+ /** Grace period before an idle server retires, so a reconnecting presentation does not race it. */
22
+ const IDLE_SHUTDOWN_MS = 10_000;
23
+ async function listSessions(sessionsRoot) {
24
+ const executionEnv = new NodeExecutionEnv({ cwd: process.cwd() });
25
+ const repo = new JsonlSessionRepo({ fileSystem: executionEnv, sessionsRoot });
26
+ try {
27
+ return (await repo.list(undefined, BACKGROUND_CONTEXT)).map((metadata) => ({
28
+ id: metadata.id,
29
+ path: metadata.path,
30
+ cwd: metadata.cwd,
31
+ createdAt: metadata.createdAt,
32
+ }));
33
+ }
34
+ finally {
35
+ await repo.close(BACKGROUND_CONTEXT);
36
+ await executionEnv.cleanup(BACKGROUND_CONTEXT);
37
+ }
38
+ }
39
+ export async function runServer(options) {
40
+ const routes = new Map();
41
+ let presentations = 0;
42
+ let retire = () => { };
43
+ const retired = new Promise((resolve) => {
44
+ retire = resolve;
45
+ });
46
+ let idleTimer;
47
+ /** Nothing to serve and nothing running: leave, so the next start always gets current code. */
48
+ const considerRetiring = () => {
49
+ if (idleTimer)
50
+ clearTimeout(idleTimer);
51
+ if (presentations > 0 || routes.size > 0)
52
+ return;
53
+ idleTimer = setTimeout(() => {
54
+ if (presentations === 0 && routes.size === 0)
55
+ retire();
56
+ }, IDLE_SHUTDOWN_MS);
57
+ idleTimer.unref();
58
+ };
59
+ /** Concurrent attaches to one session must share a worker: two writers would corrupt the session. */
60
+ const spawning = new Map();
61
+ const spawnWorker = async (sessionId, cwd) => {
62
+ const args = [...process.execArgv, WORKER_ENTRY, options.sessionsRoot, cwd, ...(sessionId ? [sessionId] : [])];
63
+ const child = spawn(process.execPath, args, { stdio: ["pipe", "pipe", "inherit"] });
64
+ const connection = childConnection(child);
65
+ const peer = createPeer(connection);
66
+ // Workers consume `Sessions` through this same peer.
67
+ peer.provide(Sessions, { list, attach: attachUnsupported });
68
+ // A worker that never answers must not wedge the attach that spawned it.
69
+ const described = await peer.use(Worker, { timeoutMs: WORKER_START_TIMEOUT_MS }).describe();
70
+ const route = {
71
+ sessionId: described.sessionId,
72
+ worker: peer,
73
+ subscribers: new Map(),
74
+ stop: () => child.kill(),
75
+ };
76
+ // Routing is the server's job. An addressed event reaches one presentation; the rest are shared.
77
+ peer.onEvent((service, payload, to) => {
78
+ if (to !== undefined) {
79
+ route.subscribers.get(to)?.emitRaw(service, payload);
80
+ return;
81
+ }
82
+ for (const subscriber of route.subscribers.values())
83
+ subscriber.emitRaw(service, payload);
84
+ });
85
+ peer.onClose(() => {
86
+ routes.delete(route.sessionId);
87
+ considerRetiring();
88
+ });
89
+ routes.set(route.sessionId, route);
90
+ return route;
91
+ };
92
+ const ensureRoute = (sessionId, cwd) => {
93
+ if (sessionId === null)
94
+ return spawnWorker(undefined, cwd);
95
+ const existing = routes.get(sessionId);
96
+ if (existing)
97
+ return Promise.resolve(existing);
98
+ const pending = spawning.get(sessionId);
99
+ if (pending)
100
+ return pending;
101
+ const started = spawnWorker(sessionId, cwd).finally(() => spawning.delete(sessionId));
102
+ spawning.set(sessionId, started);
103
+ return started;
104
+ };
105
+ const list = () => listSessions(options.sessionsRoot);
106
+ const attachUnsupported = async () => {
107
+ throw new Error("Only presentations attach to sessions");
108
+ };
109
+ const listener = await options.transport.listen((connection) => {
110
+ presentations += 1;
111
+ let route;
112
+ let attachedAs;
113
+ const sessions = {
114
+ list,
115
+ attach: async (sessionId, cwd, presentationId) => {
116
+ route?.subscribers.delete(attachedAs ?? "");
117
+ attachedAs = presentationId;
118
+ route = await ensureRoute(sessionId, cwd);
119
+ route.subscribers.set(presentationId, presentation);
120
+ return route.sessionId;
121
+ },
122
+ };
123
+ const presentation = createPeer(connection, {
124
+ forward: (method, args) => {
125
+ if (!route)
126
+ throw new Error("Not attached to a session");
127
+ const service = method.slice(0, method.indexOf("."));
128
+ if (!route.worker.announced.has(service)) {
129
+ throw new Error(`No host provides ${service}: server has [${[...presentation.provided]}], worker has [${[...route.worker.announced]}]`);
130
+ }
131
+ return route.worker.call(method, ...args);
132
+ },
133
+ });
134
+ presentation.provide(Sessions, sessions);
135
+ connection.onClose(() => {
136
+ presentations -= 1;
137
+ if (route && attachedAs !== undefined) {
138
+ route.subscribers.delete(attachedAs);
139
+ // One worker per session, kept alive only while someone is looking at it.
140
+ if (route.subscribers.size === 0)
141
+ route.stop();
142
+ }
143
+ considerRetiring();
144
+ });
145
+ });
146
+ considerRetiring();
147
+ await retired;
148
+ await listener.close();
149
+ }
150
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../../src/experimental/mini/server/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session server: accepts client connections, spawns one worker process per session, routes.\n *\n * It provides `Sessions` and holds no agent state. Any other service name is forwarded to the worker\n * the calling client is attached to, and every worker event is pushed back to that worker's clients.\n * Workers reach `Sessions` over the same peer, because the routing rule is symmetric.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { extname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n// Narrow entries: the server routes and lists sessions, it never runs an agent.\nimport { BACKGROUND_CONTEXT } from \"@earendil-works/pi-agent-core/harness/context\";\nimport { NodeExecutionEnv } from \"@earendil-works/pi-agent-core/harness/env/nodejs\";\nimport { JsonlSessionRepo } from \"@earendil-works/pi-agent-core/harness/session\";\nimport { type SessionSummary, Sessions, type SessionsServiceApi, Worker } from \"../shared/protocol.ts\";\nimport { createPeer, type RpcPeer } from \"../shared/rpc.ts\";\nimport { childConnection, type Transport } from \"../shared/transport.ts\";\n\nconst SELF_EXTENSION = extname(fileURLToPath(import.meta.url));\nconst WORKER_ENTRY = fileURLToPath(new URL(`../worker/entry${SELF_EXTENSION}`, import.meta.url));\nconst WORKER_START_TIMEOUT_MS = 30_000;\n/** Grace period before an idle server retires, so a reconnecting presentation does not race it. */\nconst IDLE_SHUTDOWN_MS = 10_000;\n\n/** The server's routing entry for one session worker process. The worker knows none of this. */\ninterface Route {\n\tsessionId: string;\n\t/** Peer to the worker process. */\n\tworker: RpcPeer;\n\t/** Attached presentations by id, so an addressed event goes to exactly one of them. */\n\tsubscribers: Map<string, RpcPeer>;\n\tstop(): void;\n}\n\nasync function listSessions(sessionsRoot: string): Promise<SessionSummary[]> {\n\tconst executionEnv = new NodeExecutionEnv({ cwd: process.cwd() });\n\tconst repo = new JsonlSessionRepo({ fileSystem: executionEnv, sessionsRoot });\n\ttry {\n\t\treturn (await repo.list(undefined, BACKGROUND_CONTEXT)).map((metadata) => ({\n\t\t\tid: metadata.id,\n\t\t\tpath: metadata.path,\n\t\t\tcwd: metadata.cwd,\n\t\t\tcreatedAt: metadata.createdAt,\n\t\t}));\n\t} finally {\n\t\tawait repo.close(BACKGROUND_CONTEXT);\n\t\tawait executionEnv.cleanup(BACKGROUND_CONTEXT);\n\t}\n}\n\nexport async function runServer(options: { transport: Transport; sessionsRoot: string }): Promise<void> {\n\tconst routes = new Map<string, Route>();\n\tlet presentations = 0;\n\tlet retire = (): void => {};\n\tconst retired = new Promise<void>((resolve) => {\n\t\tretire = resolve;\n\t});\n\tlet idleTimer: NodeJS.Timeout | undefined;\n\t/** Nothing to serve and nothing running: leave, so the next start always gets current code. */\n\tconst considerRetiring = (): void => {\n\t\tif (idleTimer) clearTimeout(idleTimer);\n\t\tif (presentations > 0 || routes.size > 0) return;\n\t\tidleTimer = setTimeout(() => {\n\t\t\tif (presentations === 0 && routes.size === 0) retire();\n\t\t}, IDLE_SHUTDOWN_MS);\n\t\tidleTimer.unref();\n\t};\n\t/** Concurrent attaches to one session must share a worker: two writers would corrupt the session. */\n\tconst spawning = new Map<string, Promise<Route>>();\n\n\tconst spawnWorker = async (sessionId: string | undefined, cwd: string): Promise<Route> => {\n\t\tconst args = [...process.execArgv, WORKER_ENTRY, options.sessionsRoot, cwd, ...(sessionId ? [sessionId] : [])];\n\t\tconst child = spawn(process.execPath, args, { stdio: [\"pipe\", \"pipe\", \"inherit\"] });\n\t\tconst connection = childConnection(child);\n\t\tconst peer = createPeer(connection);\n\t\t// Workers consume `Sessions` through this same peer.\n\t\tpeer.provide(Sessions, { list, attach: attachUnsupported });\n\t\t// A worker that never answers must not wedge the attach that spawned it.\n\t\tconst described = await peer.use(Worker, { timeoutMs: WORKER_START_TIMEOUT_MS }).describe();\n\t\tconst route: Route = {\n\t\t\tsessionId: described.sessionId,\n\t\t\tworker: peer,\n\t\t\tsubscribers: new Map(),\n\t\t\tstop: () => child.kill(),\n\t\t};\n\t\t// Routing is the server's job. An addressed event reaches one presentation; the rest are shared.\n\t\tpeer.onEvent((service, payload, to) => {\n\t\t\tif (to !== undefined) {\n\t\t\t\troute.subscribers.get(to)?.emitRaw(service, payload);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const subscriber of route.subscribers.values()) subscriber.emitRaw(service, payload);\n\t\t});\n\t\tpeer.onClose(() => {\n\t\t\troutes.delete(route.sessionId);\n\t\t\tconsiderRetiring();\n\t\t});\n\t\troutes.set(route.sessionId, route);\n\t\treturn route;\n\t};\n\n\tconst ensureRoute = (sessionId: string | null, cwd: string): Promise<Route> => {\n\t\tif (sessionId === null) return spawnWorker(undefined, cwd);\n\t\tconst existing = routes.get(sessionId);\n\t\tif (existing) return Promise.resolve(existing);\n\t\tconst pending = spawning.get(sessionId);\n\t\tif (pending) return pending;\n\t\tconst started = spawnWorker(sessionId, cwd).finally(() => spawning.delete(sessionId));\n\t\tspawning.set(sessionId, started);\n\t\treturn started;\n\t};\n\n\tconst list = (): Promise<SessionSummary[]> => listSessions(options.sessionsRoot);\n\tconst attachUnsupported = async (): Promise<string> => {\n\t\tthrow new Error(\"Only presentations attach to sessions\");\n\t};\n\n\tconst listener = await options.transport.listen((connection) => {\n\t\tpresentations += 1;\n\t\tlet route: Route | undefined;\n\t\tlet attachedAs: string | undefined;\n\t\tconst sessions: SessionsServiceApi = {\n\t\t\tlist,\n\t\t\tattach: async (sessionId, cwd, presentationId) => {\n\t\t\t\troute?.subscribers.delete(attachedAs ?? \"\");\n\t\t\t\tattachedAs = presentationId;\n\t\t\t\troute = await ensureRoute(sessionId, cwd);\n\t\t\t\troute.subscribers.set(presentationId, presentation);\n\t\t\t\treturn route.sessionId;\n\t\t\t},\n\t\t};\n\t\tconst presentation: RpcPeer = createPeer(connection, {\n\t\t\tforward: (method, args) => {\n\t\t\t\tif (!route) throw new Error(\"Not attached to a session\");\n\t\t\t\tconst service = method.slice(0, method.indexOf(\".\"));\n\t\t\t\tif (!route.worker.announced.has(service)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No host provides ${service}: server has [${[...presentation.provided]}], worker has [${[...route.worker.announced]}]`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn route.worker.call(method, ...args);\n\t\t\t},\n\t\t});\n\t\tpresentation.provide(Sessions, sessions);\n\t\tconnection.onClose(() => {\n\t\t\tpresentations -= 1;\n\t\t\tif (route && attachedAs !== undefined) {\n\t\t\t\troute.subscribers.delete(attachedAs);\n\t\t\t\t// One worker per session, kept alive only while someone is looking at it.\n\t\t\t\tif (route.subscribers.size === 0) route.stop();\n\t\t\t}\n\t\t\tconsiderRetiring();\n\t\t});\n\t});\n\n\tconsiderRetiring();\n\tawait retired;\n\tawait listener.close();\n}\n"]}
@@ -0,0 +1,127 @@
1
+ /** Service contracts and everything that crosses the wire. */
2
+ import type { HarnessEvent, LaneSnapshot } from "@earendil-works/pi-agent-core";
3
+ import type { AuthEvent, AuthPrompt } from "@earendil-works/pi-ai";
4
+ /** Commands answer with data, never exceptions, exactly like a remote call would. */
5
+ export type CommandResult = {
6
+ ok: true;
7
+ } | {
8
+ ok: false;
9
+ error: string;
10
+ };
11
+ /** Durable model identity. Nothing outside a worker holds a `Model` object. */
12
+ export interface ModelRef {
13
+ provider: string;
14
+ modelId: string;
15
+ }
16
+ export interface ModelSummary extends ModelRef {
17
+ name: string;
18
+ }
19
+ export interface ProviderAccount {
20
+ id: string;
21
+ name: string;
22
+ authType: "oauth" | "api_key";
23
+ configured: boolean;
24
+ /** Where the credential came from, for display: "stored", "environment", an env var name. */
25
+ source?: string;
26
+ /** False for ambient credentials pi cannot collect itself, such as AWS profiles or env vars. */
27
+ interactive: boolean;
28
+ methodName?: string;
29
+ }
30
+ export interface ModelsState {
31
+ readonly models: readonly ModelSummary[];
32
+ readonly accounts: readonly ProviderAccount[];
33
+ readonly refreshing: boolean;
34
+ }
35
+ /** An auth prompt without its `AbortSignal`: the part that can cross a transport. */
36
+ export type AuthPromptRequest = AuthPrompt extends infer Prompt ? Prompt extends unknown ? Omit<Prompt, "signal"> : never : never;
37
+ /** A service seen from the other side of a connection: every method returns a promise. */
38
+ export type Remote<T> = {
39
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : never;
40
+ };
41
+ /** Names one service and carries its call and event types. Names are globally unique. */
42
+ export interface ServiceToken<TApi extends object, TEvent = never> {
43
+ readonly name: string;
44
+ /** Phantom, never read: keeps the types attached to the token. */
45
+ readonly types?: (api: TApi, event: TEvent) => void;
46
+ }
47
+ export declare function defineService<TApi extends object, TEvent = never>(name: string): ServiceToken<TApi, TEvent>;
48
+ export interface SessionSummary {
49
+ id: string;
50
+ path: string;
51
+ cwd: string;
52
+ createdAt: number;
53
+ }
54
+ export interface SessionSnapshot {
55
+ sessionId: string;
56
+ cwd: string;
57
+ sessionPath: string;
58
+ /** Carries the lane configuration, queues, and stats: no side-channel replication. */
59
+ lane: LaneSnapshot;
60
+ models: ModelsState;
61
+ }
62
+ /** Everything the `Models` service publishes. */
63
+ export type ModelsEvent = {
64
+ type: "state";
65
+ state: ModelsState;
66
+ } | {
67
+ type: "prompt";
68
+ requestId: string;
69
+ request: AuthPromptRequest;
70
+ } | {
71
+ type: "notice";
72
+ notice: AuthEvent;
73
+ };
74
+ /** The login half, for whatever drives the dialog. */
75
+ export type AuthEventPayload = Exclude<ModelsEvent, {
76
+ type: "state";
77
+ }>;
78
+ /** One presentation's subscription: a `lane.watch()` in the worker, named so its events can be filtered. */
79
+ export interface LaneSubscription {
80
+ subscriptionId: string;
81
+ snapshot: SessionSnapshot;
82
+ }
83
+ /** Lane events are addressed to the subscription whose watch produced them. */
84
+ export interface LaneEvent {
85
+ subscriptionId: string;
86
+ event: HarnessEvent;
87
+ }
88
+ export interface LaneServiceApi {
89
+ /**
90
+ * Capture a snapshot and open a subscription for one presentation. Its events are addressed to
91
+ * `presentationId`, so the server routes them instead of broadcasting. Buffered until `start`.
92
+ */
93
+ watch(presentationId: string): Promise<LaneSubscription>;
94
+ /** Begin delivery, draining everything buffered since the snapshot. */
95
+ start(subscriptionId: string): Promise<void>;
96
+ unwatch(subscriptionId: string): Promise<void>;
97
+ prompt(text: string): Promise<CommandResult>;
98
+ steer(text: string): Promise<CommandResult>;
99
+ followUp(text: string): Promise<CommandResult>;
100
+ compact(): Promise<CommandResult>;
101
+ abort(): Promise<CommandResult>;
102
+ setModel(ref: ModelRef): Promise<CommandResult>;
103
+ }
104
+ export interface ModelsServiceApi {
105
+ refresh(): Promise<CommandResult>;
106
+ login(providerId: string, authType: "oauth" | "api_key"): Promise<CommandResult>;
107
+ authReply(requestId: string, answer: string | null): Promise<void>;
108
+ }
109
+ /** Provided by a worker so the server can identify the session it opened, without naming lane methods. */
110
+ export interface WorkerServiceApi {
111
+ describe(): Promise<{
112
+ sessionId: string;
113
+ }>;
114
+ }
115
+ export interface SessionsServiceApi {
116
+ list(): Promise<SessionSummary[]>;
117
+ attach(sessionId: string | null, cwd: string, presentationId: string): Promise<string>;
118
+ }
119
+ /** Provided by the worker. One subscription per presentation; `watch` again to rebase. */
120
+ export declare const Lane: ServiceToken<LaneServiceApi, LaneEvent>;
121
+ /** Provided by the worker. Small enough to publish whole. */
122
+ export declare const Models: ServiceToken<ModelsServiceApi, ModelsEvent>;
123
+ /** Provided by the worker, consumed only by the server. */
124
+ export declare const Worker: ServiceToken<WorkerServiceApi, never>;
125
+ /** Provided by the server. */
126
+ export declare const Sessions: ServiceToken<SessionsServiceApi, never>;
127
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/shared/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/** Service contracts and everything that crosses the wire. */\n\nimport type { HarnessEvent, LaneSnapshot } from \"@earendil-works/pi-agent-core\";\nimport type { AuthEvent, AuthPrompt } from \"@earendil-works/pi-ai\";\n\n/** Commands answer with data, never exceptions, exactly like a remote call would. */\nexport type CommandResult = { ok: true } | { ok: false; error: string };\n\n/** Durable model identity. Nothing outside a worker holds a `Model` object. */\nexport interface ModelRef {\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport interface ModelSummary extends ModelRef {\n\tname: string;\n}\n\nexport interface ProviderAccount {\n\tid: string;\n\tname: string;\n\tauthType: \"oauth\" | \"api_key\";\n\tconfigured: boolean;\n\t/** Where the credential came from, for display: \"stored\", \"environment\", an env var name. */\n\tsource?: string;\n\t/** False for ambient credentials pi cannot collect itself, such as AWS profiles or env vars. */\n\tinteractive: boolean;\n\tmethodName?: string;\n}\n\nexport interface ModelsState {\n\treadonly models: readonly ModelSummary[];\n\treadonly accounts: readonly ProviderAccount[];\n\treadonly refreshing: boolean;\n}\n\n/** An auth prompt without its `AbortSignal`: the part that can cross a transport. */\nexport type AuthPromptRequest = AuthPrompt extends infer Prompt\n\t? Prompt extends unknown\n\t\t? Omit<Prompt, \"signal\">\n\t\t: never\n\t: never;\n\n/** A service seen from the other side of a connection: every method returns a promise. */\nexport type Remote<T> = {\n\t[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : never;\n};\n\n/** Names one service and carries its call and event types. Names are globally unique. */\nexport interface ServiceToken<TApi extends object, TEvent = never> {\n\treadonly name: string;\n\t/** Phantom, never read: keeps the types attached to the token. */\n\treadonly types?: (api: TApi, event: TEvent) => void;\n}\n\nexport function defineService<TApi extends object, TEvent = never>(name: string): ServiceToken<TApi, TEvent> {\n\treturn { name };\n}\n\nexport interface SessionSummary {\n\tid: string;\n\tpath: string;\n\tcwd: string;\n\tcreatedAt: number;\n}\n\nexport interface SessionSnapshot {\n\tsessionId: string;\n\tcwd: string;\n\tsessionPath: string;\n\t/** Carries the lane configuration, queues, and stats: no side-channel replication. */\n\tlane: LaneSnapshot;\n\tmodels: ModelsState;\n}\n\n/** Everything the `Models` service publishes. */\nexport type ModelsEvent =\n\t| { type: \"state\"; state: ModelsState }\n\t// Login runs the wrong way round: the request is an event, the answer is an ordinary call.\n\t| { type: \"prompt\"; requestId: string; request: AuthPromptRequest }\n\t| { type: \"notice\"; notice: AuthEvent };\n\n/** The login half, for whatever drives the dialog. */\nexport type AuthEventPayload = Exclude<ModelsEvent, { type: \"state\" }>;\n\n/** One presentation's subscription: a `lane.watch()` in the worker, named so its events can be filtered. */\nexport interface LaneSubscription {\n\tsubscriptionId: string;\n\tsnapshot: SessionSnapshot;\n}\n\n/** Lane events are addressed to the subscription whose watch produced them. */\nexport interface LaneEvent {\n\tsubscriptionId: string;\n\tevent: HarnessEvent;\n}\n\nexport interface LaneServiceApi {\n\t/**\n\t * Capture a snapshot and open a subscription for one presentation. Its events are addressed to\n\t * `presentationId`, so the server routes them instead of broadcasting. Buffered until `start`.\n\t */\n\twatch(presentationId: string): Promise<LaneSubscription>;\n\t/** Begin delivery, draining everything buffered since the snapshot. */\n\tstart(subscriptionId: string): Promise<void>;\n\tunwatch(subscriptionId: string): Promise<void>;\n\tprompt(text: string): Promise<CommandResult>;\n\tsteer(text: string): Promise<CommandResult>;\n\tfollowUp(text: string): Promise<CommandResult>;\n\tcompact(): Promise<CommandResult>;\n\tabort(): Promise<CommandResult>;\n\tsetModel(ref: ModelRef): Promise<CommandResult>;\n}\n\nexport interface ModelsServiceApi {\n\trefresh(): Promise<CommandResult>;\n\tlogin(providerId: string, authType: \"oauth\" | \"api_key\"): Promise<CommandResult>;\n\tauthReply(requestId: string, answer: string | null): Promise<void>;\n}\n\n/** Provided by a worker so the server can identify the session it opened, without naming lane methods. */\nexport interface WorkerServiceApi {\n\tdescribe(): Promise<{ sessionId: string }>;\n}\n\nexport interface SessionsServiceApi {\n\tlist(): Promise<SessionSummary[]>;\n\tattach(sessionId: string | null, cwd: string, presentationId: string): Promise<string>;\n}\n\n/** Provided by the worker. One subscription per presentation; `watch` again to rebase. */\nexport const Lane = defineService<LaneServiceApi, LaneEvent>(\"lane\");\n/** Provided by the worker. Small enough to publish whole. */\nexport const Models = defineService<ModelsServiceApi, ModelsEvent>(\"models\");\n/** Provided by the worker, consumed only by the server. */\nexport const Worker = defineService<WorkerServiceApi>(\"worker\");\n/** Provided by the server. */\nexport const Sessions = defineService<SessionsServiceApi>(\"sessions\");\n"]}
@@ -0,0 +1,13 @@
1
+ /** Service contracts and everything that crosses the wire. */
2
+ export function defineService(name) {
3
+ return { name };
4
+ }
5
+ /** Provided by the worker. One subscription per presentation; `watch` again to rebase. */
6
+ export const Lane = defineService("lane");
7
+ /** Provided by the worker. Small enough to publish whole. */
8
+ export const Models = defineService("models");
9
+ /** Provided by the worker, consumed only by the server. */
10
+ export const Worker = defineService("worker");
11
+ /** Provided by the server. */
12
+ export const Sessions = defineService("sessions");
13
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../../src/experimental/mini/shared/protocol.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAuD9D,MAAM,UAAU,aAAa,CAAsC,IAAY,EAA8B;IAC5G,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA,CAChB;AAyED,0FAA0F;AAC1F,MAAM,CAAC,MAAM,IAAI,GAAG,aAAa,CAA4B,MAAM,CAAC,CAAC;AACrE,6DAA6D;AAC7D,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAgC,QAAQ,CAAC,CAAC;AAC7E,2DAA2D;AAC3D,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAmB,QAAQ,CAAC,CAAC;AAChE,8BAA8B;AAC9B,MAAM,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAqB,UAAU,CAAC,CAAC","sourcesContent":["/** Service contracts and everything that crosses the wire. */\n\nimport type { HarnessEvent, LaneSnapshot } from \"@earendil-works/pi-agent-core\";\nimport type { AuthEvent, AuthPrompt } from \"@earendil-works/pi-ai\";\n\n/** Commands answer with data, never exceptions, exactly like a remote call would. */\nexport type CommandResult = { ok: true } | { ok: false; error: string };\n\n/** Durable model identity. Nothing outside a worker holds a `Model` object. */\nexport interface ModelRef {\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport interface ModelSummary extends ModelRef {\n\tname: string;\n}\n\nexport interface ProviderAccount {\n\tid: string;\n\tname: string;\n\tauthType: \"oauth\" | \"api_key\";\n\tconfigured: boolean;\n\t/** Where the credential came from, for display: \"stored\", \"environment\", an env var name. */\n\tsource?: string;\n\t/** False for ambient credentials pi cannot collect itself, such as AWS profiles or env vars. */\n\tinteractive: boolean;\n\tmethodName?: string;\n}\n\nexport interface ModelsState {\n\treadonly models: readonly ModelSummary[];\n\treadonly accounts: readonly ProviderAccount[];\n\treadonly refreshing: boolean;\n}\n\n/** An auth prompt without its `AbortSignal`: the part that can cross a transport. */\nexport type AuthPromptRequest = AuthPrompt extends infer Prompt\n\t? Prompt extends unknown\n\t\t? Omit<Prompt, \"signal\">\n\t\t: never\n\t: never;\n\n/** A service seen from the other side of a connection: every method returns a promise. */\nexport type Remote<T> = {\n\t[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : never;\n};\n\n/** Names one service and carries its call and event types. Names are globally unique. */\nexport interface ServiceToken<TApi extends object, TEvent = never> {\n\treadonly name: string;\n\t/** Phantom, never read: keeps the types attached to the token. */\n\treadonly types?: (api: TApi, event: TEvent) => void;\n}\n\nexport function defineService<TApi extends object, TEvent = never>(name: string): ServiceToken<TApi, TEvent> {\n\treturn { name };\n}\n\nexport interface SessionSummary {\n\tid: string;\n\tpath: string;\n\tcwd: string;\n\tcreatedAt: number;\n}\n\nexport interface SessionSnapshot {\n\tsessionId: string;\n\tcwd: string;\n\tsessionPath: string;\n\t/** Carries the lane configuration, queues, and stats: no side-channel replication. */\n\tlane: LaneSnapshot;\n\tmodels: ModelsState;\n}\n\n/** Everything the `Models` service publishes. */\nexport type ModelsEvent =\n\t| { type: \"state\"; state: ModelsState }\n\t// Login runs the wrong way round: the request is an event, the answer is an ordinary call.\n\t| { type: \"prompt\"; requestId: string; request: AuthPromptRequest }\n\t| { type: \"notice\"; notice: AuthEvent };\n\n/** The login half, for whatever drives the dialog. */\nexport type AuthEventPayload = Exclude<ModelsEvent, { type: \"state\" }>;\n\n/** One presentation's subscription: a `lane.watch()` in the worker, named so its events can be filtered. */\nexport interface LaneSubscription {\n\tsubscriptionId: string;\n\tsnapshot: SessionSnapshot;\n}\n\n/** Lane events are addressed to the subscription whose watch produced them. */\nexport interface LaneEvent {\n\tsubscriptionId: string;\n\tevent: HarnessEvent;\n}\n\nexport interface LaneServiceApi {\n\t/**\n\t * Capture a snapshot and open a subscription for one presentation. Its events are addressed to\n\t * `presentationId`, so the server routes them instead of broadcasting. Buffered until `start`.\n\t */\n\twatch(presentationId: string): Promise<LaneSubscription>;\n\t/** Begin delivery, draining everything buffered since the snapshot. */\n\tstart(subscriptionId: string): Promise<void>;\n\tunwatch(subscriptionId: string): Promise<void>;\n\tprompt(text: string): Promise<CommandResult>;\n\tsteer(text: string): Promise<CommandResult>;\n\tfollowUp(text: string): Promise<CommandResult>;\n\tcompact(): Promise<CommandResult>;\n\tabort(): Promise<CommandResult>;\n\tsetModel(ref: ModelRef): Promise<CommandResult>;\n}\n\nexport interface ModelsServiceApi {\n\trefresh(): Promise<CommandResult>;\n\tlogin(providerId: string, authType: \"oauth\" | \"api_key\"): Promise<CommandResult>;\n\tauthReply(requestId: string, answer: string | null): Promise<void>;\n}\n\n/** Provided by a worker so the server can identify the session it opened, without naming lane methods. */\nexport interface WorkerServiceApi {\n\tdescribe(): Promise<{ sessionId: string }>;\n}\n\nexport interface SessionsServiceApi {\n\tlist(): Promise<SessionSummary[]>;\n\tattach(sessionId: string | null, cwd: string, presentationId: string): Promise<string>;\n}\n\n/** Provided by the worker. One subscription per presentation; `watch` again to rebase. */\nexport const Lane = defineService<LaneServiceApi, LaneEvent>(\"lane\");\n/** Provided by the worker. Small enough to publish whole. */\nexport const Models = defineService<ModelsServiceApi, ModelsEvent>(\"models\");\n/** Provided by the worker, consumed only by the server. */\nexport const Worker = defineService<WorkerServiceApi>(\"worker\");\n/** Provided by the server. */\nexport const Sessions = defineService<SessionsServiceApi>(\"sessions\");\n"]}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The whole protocol: call, result, error, cancel, event, ping. Plus named services and one routing
3
+ * rule.
4
+ *
5
+ * A peer provides any number of services and uses the other side's. A call for a service this peer
6
+ * does not provide goes to `forward`, which is what makes the server transparent: a TUI uses
7
+ * `lane.prompt`, the server does not provide `lane`, so it hands the call to the attached worker.
8
+ * The same rule lets a worker use `sessions.list` back through the server.
9
+ */
10
+ import type { Remote, ServiceToken } from "./protocol.ts";
11
+ import type { Connection } from "./transport.ts";
12
+ export type Forward = (method: string, args: unknown[]) => Promise<unknown>;
13
+ export interface CallOptions {
14
+ /** Abandon the call and tell the peer to stop. */
15
+ signal?: AbortSignal;
16
+ /** Reject if the peer has not answered in time. Omit for calls with no bounded duration. */
17
+ timeoutMs?: number;
18
+ }
19
+ export interface PeerOptions {
20
+ /** Handles calls for services this peer does not provide. */
21
+ forward?: Forward;
22
+ /** Silence tolerated before the peer is declared gone. Default 15s; 0 disables liveness. */
23
+ deadMs?: number;
24
+ }
25
+ export interface RpcPeer {
26
+ /** Register an implementation and announce the name to the other side. */
27
+ provide<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, implementation: TApi): void;
28
+ /** Services this peer provides. */
29
+ readonly provided: ReadonlySet<string>;
30
+ /** Services the other side announced. */
31
+ readonly announced: ReadonlySet<string>;
32
+ /** Use a service, wherever it is provided: this peer's other side, or its next hop. */
33
+ use<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, options?: CallOptions): Remote<TApi>;
34
+ /** Publish to everyone listening on the other side. */
35
+ emit<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, event: TEvent): void;
36
+ /** Publish for one destination. A router delivers it there instead of broadcasting. */
37
+ emitTo<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, event: TEvent, to: string): void;
38
+ on<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, handler: (event: TEvent) => void): void;
39
+ /** Router half of the event channel: observe and republish without knowing the service. */
40
+ onEvent(handler: (service: string, payload: unknown, to: string | undefined) => void): void;
41
+ emitRaw(service: string, payload: unknown, to?: string): void;
42
+ call(method: string, ...args: unknown[]): Promise<unknown>;
43
+ callWith(options: CallOptions, method: string, ...args: unknown[]): Promise<unknown>;
44
+ onClose(handler: () => void): void;
45
+ close(): void;
46
+ }
47
+ /** A bidirectional peer on one connection. */
48
+ export declare function createPeer(connection: Connection, options?: PeerOptions): RpcPeer;
49
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/shared/rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAWjD,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE5E,MAAM,WAAW,WAAW;IAC3B,kDAAkD;IAClD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,MAAM,WAAW,OAAO;IACvB,0EAA0E;IAC1E,OAAO,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IACpG,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,uFAAuF;IACvF,GAAG,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACzG,uDAAuD;IACvD,IAAI,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1F,uFAAuF;IACvF,MAAM,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACxG,EAAE,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3G,2FAA2F;IAC3F,OAAO,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5F,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrF,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACd;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAoKrF","sourcesContent":["/**\n * The whole protocol: call, result, error, cancel, event, ping. Plus named services and one routing\n * rule.\n *\n * A peer provides any number of services and uses the other side's. A call for a service this peer\n * does not provide goes to `forward`, which is what makes the server transparent: a TUI uses\n * `lane.prompt`, the server does not provide `lane`, so it hands the call to the attached worker.\n * The same rule lets a worker use `sessions.list` back through the server.\n */\n\nimport type { Remote, ServiceToken } from \"./protocol.ts\";\nimport type { Connection } from \"./transport.ts\";\n\ntype Frame =\n\t| { kind: \"call\"; id: number; method: string; args: unknown[] }\n\t| { kind: \"result\"; id: number; result: unknown }\n\t| { kind: \"error\"; id: number; error: string }\n\t| { kind: \"cancel\"; id: number }\n\t| { kind: \"event\"; service: string; payload: unknown; to?: string }\n\t| { kind: \"announce\"; services: string[] }\n\t| { kind: \"ping\" };\n\nexport type Forward = (method: string, args: unknown[]) => Promise<unknown>;\n\nexport interface CallOptions {\n\t/** Abandon the call and tell the peer to stop. */\n\tsignal?: AbortSignal;\n\t/** Reject if the peer has not answered in time. Omit for calls with no bounded duration. */\n\ttimeoutMs?: number;\n}\n\nexport interface PeerOptions {\n\t/** Handles calls for services this peer does not provide. */\n\tforward?: Forward;\n\t/** Silence tolerated before the peer is declared gone. Default 15s; 0 disables liveness. */\n\tdeadMs?: number;\n}\n\nconst DEFAULT_DEAD_MS = 15_000;\n\nexport interface RpcPeer {\n\t/** Register an implementation and announce the name to the other side. */\n\tprovide<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, implementation: TApi): void;\n\t/** Services this peer provides. */\n\treadonly provided: ReadonlySet<string>;\n\t/** Services the other side announced. */\n\treadonly announced: ReadonlySet<string>;\n\t/** Use a service, wherever it is provided: this peer's other side, or its next hop. */\n\tuse<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, options?: CallOptions): Remote<TApi>;\n\t/** Publish to everyone listening on the other side. */\n\temit<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, event: TEvent): void;\n\t/** Publish for one destination. A router delivers it there instead of broadcasting. */\n\temitTo<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, event: TEvent, to: string): void;\n\ton<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, handler: (event: TEvent) => void): void;\n\t/** Router half of the event channel: observe and republish without knowing the service. */\n\tonEvent(handler: (service: string, payload: unknown, to: string | undefined) => void): void;\n\temitRaw(service: string, payload: unknown, to?: string): void;\n\tcall(method: string, ...args: unknown[]): Promise<unknown>;\n\tcallWith(options: CallOptions, method: string, ...args: unknown[]): Promise<unknown>;\n\tonClose(handler: () => void): void;\n\tclose(): void;\n}\n\n/** A bidirectional peer on one connection. */\nexport function createPeer(connection: Connection, options: PeerOptions = {}): RpcPeer {\n\tconst services = new Map<string, object>();\n\tconst provided = new Set<string>();\n\t/** What the other side told us it provides, so routing is a lookup rather than a guess. */\n\tconst announced = new Set<string>();\n\tconst pending = new Map<number, { resolve: (value: unknown) => void; reject: (error: Error) => void }>();\n\t/** Controllers for calls this peer is currently answering, so a `cancel` frame can stop them. */\n\tconst inflight = new Map<number, AbortController>();\n\tconst eventHandlers: ((service: string, payload: unknown, to: string | undefined) => void)[] = [];\n\tlet nextId = 1;\n\tlet lastFrameAt = Date.now();\n\n\t// The signal is appended to every handler call: services that care declare a trailing\n\t// `AbortSignal` parameter, the rest ignore an extra argument.\n\tconst dispatch = async (method: string, args: unknown[], signal: AbortSignal): Promise<unknown> => {\n\t\tconst dot = method.indexOf(\".\");\n\t\tconst local = dot === -1 ? undefined : services.get(method.slice(0, dot));\n\t\tif (!local) {\n\t\t\tif (!options.forward) throw new Error(`No service provides ${method}`);\n\t\t\treturn options.forward(method, args);\n\t\t}\n\t\tconst handler = (local as Record<string, unknown>)[method.slice(dot + 1)];\n\t\tif (typeof handler !== \"function\") throw new Error(`Unknown method: ${method}`);\n\t\treturn (handler as (...args: unknown[]) => unknown).apply(local, [...args, signal]);\n\t};\n\n\tconnection.onMessage((frameValue) => {\n\t\tconst frame = frameValue as Frame;\n\t\tlastFrameAt = Date.now();\n\t\tswitch (frame.kind) {\n\t\t\tcase \"event\": {\n\t\t\t\tfor (const handler of eventHandlers) handler(frame.service, frame.payload, frame.to);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"call\": {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tinflight.set(frame.id, controller);\n\t\t\t\tvoid dispatch(frame.method, frame.args, controller.signal)\n\t\t\t\t\t.then(\n\t\t\t\t\t\t// `undefined` vanishes through JSON, so an absent result is sent as null.\n\t\t\t\t\t\t(result) => connection.send({ kind: \"result\", id: frame.id, result: result ?? null }),\n\t\t\t\t\t\t(error: unknown) => connection.send({ kind: \"error\", id: frame.id, error: message(error) }),\n\t\t\t\t\t)\n\t\t\t\t\t.finally(() => inflight.delete(frame.id));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"cancel\": {\n\t\t\t\tinflight.get(frame.id)?.abort(new Error(\"Cancelled by caller\"));\n\t\t\t\tinflight.delete(frame.id);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"result\":\n\t\t\tcase \"error\": {\n\t\t\t\tconst waiter = pending.get(frame.id);\n\t\t\t\tpending.delete(frame.id);\n\t\t\t\tif (frame.kind === \"error\") waiter?.reject(new Error(frame.error));\n\t\t\t\telse waiter?.resolve(frame.result);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"announce\": {\n\t\t\t\tannounced.clear();\n\t\t\t\tfor (const service of frame.services) announced.add(service);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"ping\":\n\t\t\t\treturn;\n\t\t\tdefault: {\n\t\t\t\tconst unknownFrame: never = frame;\n\t\t\t\tthrow new Error(`Unknown frame: ${JSON.stringify(unknownFrame)}`);\n\t\t\t}\n\t\t}\n\t});\n\n\tconnection.onClose(() => {\n\t\tif (liveness) clearInterval(liveness);\n\t\tfor (const waiter of pending.values()) waiter.reject(new Error(\"Connection closed\"));\n\t\tpending.clear();\n\t\tfor (const controller of inflight.values()) controller.abort(new Error(\"Connection closed\"));\n\t\tinflight.clear();\n\t});\n\n\t/**\n\t * A peer can vanish without closing: a killed machine, a wedged event loop. Any frame counts as\n\t * proof of life, and pings keep an idle connection proving it.\n\t */\n\tconst deadMs = options.deadMs ?? DEFAULT_DEAD_MS;\n\tconst liveness =\n\t\tdeadMs > 0\n\t\t\t? setInterval(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (Date.now() - lastFrameAt > deadMs) connection.close();\n\t\t\t\t\t\telse connection.send({ kind: \"ping\" });\n\t\t\t\t\t},\n\t\t\t\t\tMath.floor(deadMs / 3),\n\t\t\t\t)\n\t\t\t: undefined;\n\tliveness?.unref();\n\n\tconst callWith = (callOptions: CallOptions, method: string, ...args: unknown[]): Promise<unknown> =>\n\t\tnew Promise((resolve, reject) => {\n\t\t\tconst id = nextId++;\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tconst abandon = (error: Error): void => {\n\t\t\t\tif (!pending.delete(id)) return;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tcallOptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t// Tell the peer to stop; it may already be gone, in which case this is a no-op.\n\t\t\t\tconnection.send({ kind: \"cancel\", id });\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst onAbort = (): void => abandon(new Error(\"Call cancelled\"));\n\t\t\tconst settle =\n\t\t\t\t<T>(handler: (value: T) => void) =>\n\t\t\t\t(value: T) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tcallOptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\thandler(value);\n\t\t\t\t};\n\t\t\tif (callOptions.signal?.aborted) {\n\t\t\t\treject(new Error(\"Call cancelled\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpending.set(id, { resolve: settle(resolve), reject: settle(reject) });\n\t\t\tcallOptions.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tif (callOptions.timeoutMs !== undefined) {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => abandon(new Error(`${method} timed out after ${callOptions.timeoutMs}ms`)),\n\t\t\t\t\tcallOptions.timeoutMs,\n\t\t\t\t);\n\t\t\t\ttimer.unref();\n\t\t\t}\n\t\t\tconnection.send({ kind: \"call\", id, method, args });\n\t\t});\n\n\tconst peer: RpcPeer = {\n\t\tprovide: (token, implementation) => {\n\t\t\tservices.set(token.name, implementation);\n\t\t\tprovided.add(token.name);\n\t\t\tconnection.send({ kind: \"announce\", services: [...provided] });\n\t\t},\n\t\tprovided,\n\t\tannounced,\n\t\tuse: (token, callOptions = {}) =>\n\t\t\tnew Proxy({} as Remote<typeof token extends ServiceToken<infer TApi, never> ? TApi : never>, {\n\t\t\t\tget:\n\t\t\t\t\t(_target, method) =>\n\t\t\t\t\t(...args: unknown[]) =>\n\t\t\t\t\t\tcallWith(callOptions, `${token.name}.${String(method)}`, ...args),\n\t\t\t}) as never,\n\t\temit: (token, event) => connection.send({ kind: \"event\", service: token.name, payload: event }),\n\t\temitTo: (token, event, to) => connection.send({ kind: \"event\", service: token.name, payload: event, to }),\n\t\temitRaw: (service, payload, to) =>\n\t\t\tconnection.send({ kind: \"event\", service, payload, ...(to === undefined ? {} : { to }) }),\n\t\ton: (token, handler) =>\n\t\t\teventHandlers.push((name, payload) => {\n\t\t\t\tif (name === token.name) handler(payload as never);\n\t\t\t}),\n\t\tonEvent: (handler) => eventHandlers.push(handler),\n\t\tcall: (method, ...args) => callWith({}, method, ...args),\n\t\tcallWith,\n\t\tonClose: (handler) => connection.onClose(handler),\n\t\tclose: () => connection.close(),\n\t};\n\treturn peer;\n}\n\nfunction message(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n"]}