@earendil-works/pi-coding-agent 0.84.3 → 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 (533) hide show
  1. package/CHANGELOG.md +98 -2
  2. package/README.md +8 -5
  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-GPPBJGBU.js → chunk-2KVJKXS2.js} +1 -1
  14. package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
  15. package/dist/bundle/chunks/chunk-EQ6DR6XK.js +5 -0
  16. package/dist/bundle/chunks/{chunk-MNAIPA3J.js → chunk-IDDQWTHI.js} +1 -1
  17. package/dist/bundle/chunks/chunk-LZKJTKCT.js +2 -0
  18. package/dist/bundle/chunks/{chunk-NBBFIJUL.js → chunk-OUXLLA64.js} +2 -2
  19. package/dist/bundle/chunks/chunk-R7UUJ4C2.js +42 -0
  20. package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
  21. package/dist/bundle/chunks/chunk-WBE4J4AU.js +9 -0
  22. package/dist/bundle/chunks/chunk-WZB2R5YO.js +1558 -0
  23. package/dist/bundle/chunks/github-copilot.js +1 -1
  24. package/dist/bundle/chunks/{google-generative-ai-XDKMGBCJ.js → google-generative-ai-BSMTYDAM.js} +1 -1
  25. package/dist/bundle/chunks/{google-vertex-D5FGEO3Y.js → google-vertex-5C3HASOJ.js} +1 -1
  26. package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +13 -0
  27. package/dist/bundle/chunks/image-resize-worker.js +1 -1
  28. package/dist/bundle/chunks/{mistral-conversations-YK73UAOZ.js → mistral-conversations-4M6BZBX6.js} +2 -2
  29. package/dist/bundle/chunks/node-EM5FIVUN.js +16 -0
  30. package/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
  31. package/dist/bundle/chunks/openai-codex-responses-YJOO52RW.js +10 -0
  32. package/dist/bundle/chunks/openai-completions-QOT3QX7D.js +7 -0
  33. package/dist/bundle/chunks/openai-responses-U7LNPQN4.js +2 -0
  34. package/dist/bundle/chunks/{pi-messages-TPFH44NP.js → pi-messages-EGZIFQGG.js} +1 -1
  35. package/dist/bundle/cli.js +1 -1
  36. package/dist/bundle/client.js +1 -1
  37. package/dist/bundle/coordinator.js +3 -0
  38. package/dist/bundle/index.js +1 -1
  39. package/dist/bundle/rpc-entry.js +1 -1
  40. package/dist/cli/args.d.ts.map +1 -1
  41. package/dist/cli/args.js +2 -0
  42. package/dist/cli/args.js.map +1 -1
  43. package/dist/cli/config-selector.d.ts.map +1 -1
  44. package/dist/cli/config-selector.js +2 -1
  45. package/dist/cli/config-selector.js.map +1 -1
  46. package/dist/cli/experimental/cli.d.ts +7 -3
  47. package/dist/cli/experimental/cli.d.ts.map +1 -1
  48. package/dist/cli/experimental/cli.js +6 -2
  49. package/dist/cli/experimental/cli.js.map +1 -1
  50. package/dist/cli/experimental/command-options.d.ts +23 -12
  51. package/dist/cli/experimental/command-options.d.ts.map +1 -1
  52. package/dist/cli/experimental/command-options.js +69 -20
  53. package/dist/cli/experimental/command-options.js.map +1 -1
  54. package/dist/cli/experimental/command.d.ts +10 -3
  55. package/dist/cli/experimental/command.d.ts.map +1 -1
  56. package/dist/cli/experimental/command.js +28 -14
  57. package/dist/cli/experimental/command.js.map +1 -1
  58. package/dist/cli/experimental/commands/client.d.ts +8 -2
  59. package/dist/cli/experimental/commands/client.d.ts.map +1 -1
  60. package/dist/cli/experimental/commands/client.js +43 -5
  61. package/dist/cli/experimental/commands/client.js.map +1 -1
  62. package/dist/cli/experimental/commands/server.d.ts +7 -3
  63. package/dist/cli/experimental/commands/server.d.ts.map +1 -1
  64. package/dist/cli/experimental/commands/server.js +27 -8
  65. package/dist/cli/experimental/commands/server.js.map +1 -1
  66. package/dist/cli/startup-ui.d.ts.map +1 -1
  67. package/dist/cli/startup-ui.js +2 -1
  68. package/dist/cli/startup-ui.js.map +1 -1
  69. package/dist/cli.d.ts.map +1 -1
  70. package/dist/cli.js +23 -14
  71. package/dist/cli.js.map +1 -1
  72. package/dist/client/index.d.ts +1 -2
  73. package/dist/client/index.d.ts.map +1 -1
  74. package/dist/client/index.js +1 -2
  75. package/dist/client/index.js.map +1 -1
  76. package/dist/config.d.ts +1 -0
  77. package/dist/config.d.ts.map +1 -1
  78. package/dist/config.js +1 -0
  79. package/dist/config.js.map +1 -1
  80. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  81. package/dist/core/agent-session-runtime.js +15 -7
  82. package/dist/core/agent-session-runtime.js.map +1 -1
  83. package/dist/core/agent-session.d.ts +14 -7
  84. package/dist/core/agent-session.d.ts.map +1 -1
  85. package/dist/core/agent-session.js +91 -15
  86. package/dist/core/agent-session.js.map +1 -1
  87. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  88. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  89. package/dist/core/compaction/branch-summarization.js +6 -4
  90. package/dist/core/compaction/branch-summarization.js.map +1 -1
  91. package/dist/core/compaction/compaction.d.ts +5 -0
  92. package/dist/core/compaction/compaction.d.ts.map +1 -1
  93. package/dist/core/compaction/compaction.js +19 -5
  94. package/dist/core/compaction/compaction.js.map +1 -1
  95. package/dist/core/extensions/index.d.ts +1 -1
  96. package/dist/core/extensions/index.d.ts.map +1 -1
  97. package/dist/core/extensions/index.js.map +1 -1
  98. package/dist/core/extensions/loader.d.ts.map +1 -1
  99. package/dist/core/extensions/loader.js +1 -2
  100. package/dist/core/extensions/loader.js.map +1 -1
  101. package/dist/core/extensions/runner.d.ts +5 -0
  102. package/dist/core/extensions/runner.d.ts.map +1 -1
  103. package/dist/core/extensions/runner.js +45 -1
  104. package/dist/core/extensions/runner.js.map +1 -1
  105. package/dist/core/extensions/types.d.ts +26 -3
  106. package/dist/core/extensions/types.d.ts.map +1 -1
  107. package/dist/core/extensions/types.js.map +1 -1
  108. package/dist/core/http-dispatcher.d.ts.map +1 -1
  109. package/dist/core/http-dispatcher.js +2 -0
  110. package/dist/core/http-dispatcher.js.map +1 -1
  111. package/dist/core/messages.d.ts +1 -1
  112. package/dist/core/messages.d.ts.map +1 -1
  113. package/dist/core/messages.js.map +1 -1
  114. package/dist/core/model-config.d.ts +15 -0
  115. package/dist/core/model-config.d.ts.map +1 -1
  116. package/dist/core/model-config.js +3 -0
  117. package/dist/core/model-config.js.map +1 -1
  118. package/dist/core/model-runtime.d.ts +1 -0
  119. package/dist/core/model-runtime.d.ts.map +1 -1
  120. package/dist/core/model-runtime.js +5 -2
  121. package/dist/core/model-runtime.js.map +1 -1
  122. package/dist/core/provider-composer.d.ts.map +1 -1
  123. package/dist/core/provider-composer.js +8 -2
  124. package/dist/core/provider-composer.js.map +1 -1
  125. package/dist/core/session-manager.d.ts +3 -2
  126. package/dist/core/session-manager.d.ts.map +1 -1
  127. package/dist/core/session-manager.js +44 -15
  128. package/dist/core/session-manager.js.map +1 -1
  129. package/dist/core/settings-manager.d.ts +8 -1
  130. package/dist/core/settings-manager.d.ts.map +1 -1
  131. package/dist/core/settings-manager.js +17 -0
  132. package/dist/core/settings-manager.js.map +1 -1
  133. package/dist/core/skills.d.ts +1 -1
  134. package/dist/core/skills.d.ts.map +1 -1
  135. package/dist/core/skills.js +4 -2
  136. package/dist/core/skills.js.map +1 -1
  137. package/dist/core/system-prompt.d.ts.map +1 -1
  138. package/dist/core/system-prompt.js +8 -9
  139. package/dist/core/system-prompt.js.map +1 -1
  140. package/dist/core/tools/bash.d.ts.map +1 -1
  141. package/dist/core/tools/bash.js +3 -117
  142. package/dist/core/tools/bash.js.map +1 -1
  143. package/dist/core/tools/edit.d.ts +1 -12
  144. package/dist/core/tools/edit.d.ts.map +1 -1
  145. package/dist/core/tools/edit.js +5 -163
  146. package/dist/core/tools/edit.js.map +1 -1
  147. package/dist/core/tools/find.d.ts.map +1 -1
  148. package/dist/core/tools/find.js +4 -55
  149. package/dist/core/tools/find.js.map +1 -1
  150. package/dist/core/tools/grep.d.ts.map +1 -1
  151. package/dist/core/tools/grep.js +4 -60
  152. package/dist/core/tools/grep.js.map +1 -1
  153. package/dist/core/tools/ls.d.ts.map +1 -1
  154. package/dist/core/tools/ls.js +4 -49
  155. package/dist/core/tools/ls.js.map +1 -1
  156. package/dist/core/tools/read.d.ts.map +1 -1
  157. package/dist/core/tools/read.js +4 -120
  158. package/dist/core/tools/read.js.map +1 -1
  159. package/dist/core/tools/renderers/bash.d.ts +12 -0
  160. package/dist/core/tools/renderers/bash.d.ts.map +1 -0
  161. package/dist/core/tools/renderers/bash.js +130 -0
  162. package/dist/core/tools/renderers/bash.js.map +1 -0
  163. package/dist/core/tools/renderers/edit.d.ts +23 -0
  164. package/dist/core/tools/renderers/edit.d.ts.map +1 -0
  165. package/dist/core/tools/renderers/edit.js +167 -0
  166. package/dist/core/tools/renderers/edit.js.map +1 -0
  167. package/dist/core/tools/renderers/find.d.ts +10 -0
  168. package/dist/core/tools/renderers/find.d.ts.map +1 -0
  169. package/dist/core/tools/renderers/find.js +64 -0
  170. package/dist/core/tools/renderers/find.js.map +1 -0
  171. package/dist/core/tools/renderers/grep.d.ts +10 -0
  172. package/dist/core/tools/renderers/grep.d.ts.map +1 -0
  173. package/dist/core/tools/renderers/grep.js +69 -0
  174. package/dist/core/tools/renderers/grep.js.map +1 -0
  175. package/dist/core/tools/renderers/index.d.ts +28 -0
  176. package/dist/core/tools/renderers/index.d.ts.map +1 -0
  177. package/dist/core/tools/renderers/index.js +47 -0
  178. package/dist/core/tools/renderers/index.js.map +1 -0
  179. package/dist/core/tools/renderers/ls.d.ts +10 -0
  180. package/dist/core/tools/renderers/ls.d.ts.map +1 -0
  181. package/dist/core/tools/renderers/ls.js +58 -0
  182. package/dist/core/tools/renderers/ls.js.map +1 -0
  183. package/dist/core/tools/renderers/read.d.ts +11 -0
  184. package/dist/core/tools/renderers/read.d.ts.map +1 -0
  185. package/dist/core/tools/renderers/read.js +129 -0
  186. package/dist/core/tools/renderers/read.js.map +1 -0
  187. package/dist/core/tools/renderers/write.d.ts +10 -0
  188. package/dist/core/tools/renderers/write.d.ts.map +1 -0
  189. package/dist/core/tools/renderers/write.js +153 -0
  190. package/dist/core/tools/renderers/write.js.map +1 -0
  191. package/dist/core/tools/write.d.ts.map +1 -1
  192. package/dist/core/tools/write.js +5 -146
  193. package/dist/core/tools/write.js.map +1 -1
  194. package/dist/experimental/client-runtime.d.ts +43 -0
  195. package/dist/experimental/client-runtime.d.ts.map +1 -0
  196. package/dist/experimental/client-runtime.js +183 -0
  197. package/dist/experimental/client-runtime.js.map +1 -0
  198. package/dist/experimental/client-tui-chat.d.ts +14 -0
  199. package/dist/experimental/client-tui-chat.d.ts.map +1 -0
  200. package/dist/experimental/client-tui-chat.js +171 -0
  201. package/dist/experimental/client-tui-chat.js.map +1 -0
  202. package/dist/experimental/client-tui.d.ts +37 -0
  203. package/dist/experimental/client-tui.d.ts.map +1 -0
  204. package/dist/experimental/client-tui.js +702 -0
  205. package/dist/experimental/client-tui.js.map +1 -0
  206. package/dist/experimental/client.d.ts +25 -0
  207. package/dist/experimental/client.d.ts.map +1 -0
  208. package/dist/experimental/client.js +99 -0
  209. package/dist/experimental/client.js.map +1 -0
  210. package/dist/experimental/coordinator-entry.d.ts +3 -0
  211. package/dist/experimental/coordinator-entry.d.ts.map +1 -0
  212. package/dist/experimental/coordinator-entry.js +11 -0
  213. package/dist/experimental/coordinator-entry.js.map +1 -0
  214. package/dist/experimental/coordinator.d.ts +38 -0
  215. package/dist/experimental/coordinator.d.ts.map +1 -0
  216. package/dist/experimental/coordinator.js +570 -0
  217. package/dist/experimental/coordinator.js.map +1 -0
  218. package/dist/experimental/mini/main.d.ts +7 -0
  219. package/dist/experimental/mini/main.d.ts.map +1 -0
  220. package/dist/experimental/mini/main.js +21 -0
  221. package/dist/experimental/mini/main.js.map +1 -0
  222. package/dist/experimental/mini/server/entry.d.ts +3 -0
  223. package/dist/experimental/mini/server/entry.d.ts.map +1 -0
  224. package/dist/experimental/mini/server/entry.js +11 -0
  225. package/dist/experimental/mini/server/entry.js.map +1 -0
  226. package/dist/experimental/mini/server/run.d.ts +13 -0
  227. package/dist/experimental/mini/server/run.d.ts.map +1 -0
  228. package/dist/experimental/mini/server/run.js +150 -0
  229. package/dist/experimental/mini/server/run.js.map +1 -0
  230. package/dist/experimental/mini/shared/protocol.d.ts +127 -0
  231. package/dist/experimental/mini/shared/protocol.d.ts.map +1 -0
  232. package/dist/experimental/mini/shared/protocol.js +13 -0
  233. package/dist/experimental/mini/shared/protocol.js.map +1 -0
  234. package/dist/experimental/mini/shared/rpc.d.ts +49 -0
  235. package/dist/experimental/mini/shared/rpc.d.ts.map +1 -0
  236. package/dist/experimental/mini/shared/rpc.js +171 -0
  237. package/dist/experimental/mini/shared/rpc.js.map +1 -0
  238. package/dist/experimental/mini/shared/transport.d.ts +35 -0
  239. package/dist/experimental/mini/shared/transport.d.ts.map +1 -0
  240. package/dist/experimental/mini/shared/transport.js +94 -0
  241. package/dist/experimental/mini/shared/transport.js.map +1 -0
  242. package/dist/experimental/mini/tui/run.d.ts +9 -0
  243. package/dist/experimental/mini/tui/run.d.ts.map +1 -0
  244. package/dist/experimental/mini/tui/run.js +62 -0
  245. package/dist/experimental/mini/tui/run.js.map +1 -0
  246. package/dist/experimental/mini/tui/session.d.ts +26 -0
  247. package/dist/experimental/mini/tui/session.d.ts.map +1 -0
  248. package/dist/experimental/mini/tui/session.js +94 -0
  249. package/dist/experimental/mini/tui/session.js.map +1 -0
  250. package/dist/experimental/mini/tui/view.d.ts +10 -0
  251. package/dist/experimental/mini/tui/view.d.ts.map +1 -0
  252. package/dist/experimental/mini/tui/view.js +459 -0
  253. package/dist/experimental/mini/tui/view.js.map +1 -0
  254. package/dist/experimental/mini/worker/entry.d.ts +3 -0
  255. package/dist/experimental/mini/worker/entry.d.ts.map +1 -0
  256. package/dist/experimental/mini/worker/entry.js +10 -0
  257. package/dist/experimental/mini/worker/entry.js.map +1 -0
  258. package/dist/experimental/mini/worker/lane-service.d.ts +44 -0
  259. package/dist/experimental/mini/worker/lane-service.d.ts.map +1 -0
  260. package/dist/experimental/mini/worker/lane-service.js +97 -0
  261. package/dist/experimental/mini/worker/lane-service.js.map +1 -0
  262. package/dist/experimental/mini/worker/models-service.d.ts +19 -0
  263. package/dist/experimental/mini/worker/models-service.d.ts.map +1 -0
  264. package/dist/experimental/mini/worker/models-service.js +136 -0
  265. package/dist/experimental/mini/worker/models-service.js.map +1 -0
  266. package/dist/experimental/mini/worker/run.d.ts +14 -0
  267. package/dist/experimental/mini/worker/run.d.ts.map +1 -0
  268. package/dist/experimental/mini/worker/run.js +88 -0
  269. package/dist/experimental/mini/worker/run.js.map +1 -0
  270. package/dist/experimental/plugin.d.ts +4 -0
  271. package/dist/experimental/plugin.d.ts.map +1 -0
  272. package/dist/experimental/plugin.js +4 -0
  273. package/dist/experimental/plugin.js.map +1 -0
  274. package/dist/experimental/plugins/bundled.d.ts +7 -0
  275. package/dist/experimental/plugins/bundled.d.ts.map +1 -0
  276. package/dist/experimental/plugins/bundled.js +48 -0
  277. package/dist/experimental/plugins/bundled.js.map +1 -0
  278. package/dist/experimental/plugins/package.d.ts +18 -0
  279. package/dist/experimental/plugins/package.d.ts.map +1 -0
  280. package/dist/experimental/plugins/package.js +113 -0
  281. package/dist/experimental/plugins/package.js.map +1 -0
  282. package/dist/experimental/process.d.ts +20 -0
  283. package/dist/experimental/process.d.ts.map +1 -0
  284. package/dist/experimental/process.js +85 -0
  285. package/dist/experimental/process.js.map +1 -0
  286. package/dist/experimental/radius-auth.d.ts +17 -0
  287. package/dist/experimental/radius-auth.d.ts.map +1 -0
  288. package/dist/experimental/radius-auth.js +52 -0
  289. package/dist/experimental/radius-auth.js.map +1 -0
  290. package/dist/experimental/radius-relay.d.ts +90 -0
  291. package/dist/experimental/radius-relay.d.ts.map +1 -0
  292. package/dist/experimental/radius-relay.js +582 -0
  293. package/dist/experimental/radius-relay.js.map +1 -0
  294. package/dist/experimental/server.d.ts +75 -0
  295. package/dist/experimental/server.d.ts.map +1 -0
  296. package/dist/experimental/server.js +682 -0
  297. package/dist/experimental/server.js.map +1 -0
  298. package/dist/experimental/services/agent-controller-provider.d.ts +4 -0
  299. package/dist/experimental/services/agent-controller-provider.d.ts.map +1 -0
  300. package/dist/experimental/services/agent-controller-provider.js +84 -0
  301. package/dist/experimental/services/agent-controller-provider.js.map +1 -0
  302. package/dist/experimental/services/agent-controller.d.ts +57 -0
  303. package/dist/experimental/services/agent-controller.d.ts.map +1 -0
  304. package/dist/experimental/services/agent-controller.js +3 -0
  305. package/dist/experimental/services/agent-controller.js.map +1 -0
  306. package/dist/experimental/services/connection.d.ts +40 -0
  307. package/dist/experimental/services/connection.d.ts.map +1 -0
  308. package/dist/experimental/services/connection.js +300 -0
  309. package/dist/experimental/services/connection.js.map +1 -0
  310. package/dist/experimental/services/models-provider.d.ts +16 -0
  311. package/dist/experimental/services/models-provider.d.ts.map +1 -0
  312. package/dist/experimental/services/models-provider.js +116 -0
  313. package/dist/experimental/services/models-provider.js.map +1 -0
  314. package/dist/experimental/services/models.d.ts +36 -0
  315. package/dist/experimental/services/models.d.ts.map +1 -0
  316. package/dist/experimental/services/models.js +3 -0
  317. package/dist/experimental/services/models.js.map +1 -0
  318. package/dist/experimental/services/plugins.d.ts +16 -0
  319. package/dist/experimental/services/plugins.d.ts.map +1 -0
  320. package/dist/experimental/services/plugins.js +4 -0
  321. package/dist/experimental/services/plugins.js.map +1 -0
  322. package/dist/experimental/services/presentation-ui.d.ts +13 -0
  323. package/dist/experimental/services/presentation-ui.d.ts.map +1 -0
  324. package/dist/experimental/services/presentation-ui.js +3 -0
  325. package/dist/experimental/services/presentation-ui.js.map +1 -0
  326. package/dist/experimental/services/server.d.ts +19 -0
  327. package/dist/experimental/services/server.d.ts.map +1 -0
  328. package/dist/experimental/services/server.js +104 -0
  329. package/dist/experimental/services/server.js.map +1 -0
  330. package/dist/experimental/services/sessions.d.ts +28 -0
  331. package/dist/experimental/services/sessions.d.ts.map +1 -0
  332. package/dist/experimental/services/sessions.js +4 -0
  333. package/dist/experimental/services/sessions.js.map +1 -0
  334. package/dist/experimental/services/slash-commands-provider.d.ts +14 -0
  335. package/dist/experimental/services/slash-commands-provider.d.ts.map +1 -0
  336. package/dist/experimental/services/slash-commands-provider.js +200 -0
  337. package/dist/experimental/services/slash-commands-provider.js.map +1 -0
  338. package/dist/experimental/services/slash-commands.d.ts +24 -0
  339. package/dist/experimental/services/slash-commands.d.ts.map +1 -0
  340. package/dist/experimental/services/slash-commands.js +3 -0
  341. package/dist/experimental/services/slash-commands.js.map +1 -0
  342. package/dist/experimental/services/transcript-provider.d.ts +12 -0
  343. package/dist/experimental/services/transcript-provider.d.ts.map +1 -0
  344. package/dist/experimental/services/transcript-provider.js +109 -0
  345. package/dist/experimental/services/transcript-provider.js.map +1 -0
  346. package/dist/experimental/services/transcript.d.ts +13 -0
  347. package/dist/experimental/services/transcript.d.ts.map +1 -0
  348. package/dist/experimental/services/transcript.js +3 -0
  349. package/dist/experimental/services/transcript.js.map +1 -0
  350. package/dist/experimental/services/worker.d.ts +28 -0
  351. package/dist/experimental/services/worker.d.ts.map +1 -0
  352. package/dist/experimental/services/worker.js +107 -0
  353. package/dist/experimental/services/worker.js.map +1 -0
  354. package/dist/experimental/session-worker-manager.d.ts +24 -0
  355. package/dist/experimental/session-worker-manager.d.ts.map +1 -0
  356. package/dist/experimental/session-worker-manager.js +691 -0
  357. package/dist/experimental/session-worker-manager.js.map +1 -0
  358. package/dist/experimental/session-worker.d.ts +188 -0
  359. package/dist/experimental/session-worker.d.ts.map +1 -0
  360. package/dist/experimental/session-worker.js +785 -0
  361. package/dist/experimental/session-worker.js.map +1 -0
  362. package/dist/experimental/source-resolver.d.ts +2 -0
  363. package/dist/experimental/source-resolver.d.ts.map +1 -0
  364. package/dist/experimental/source-resolver.js +71 -0
  365. package/dist/experimental/source-resolver.js.map +1 -0
  366. package/dist/extensions/llama/client.d.ts +6 -0
  367. package/dist/extensions/llama/client.d.ts.map +1 -1
  368. package/dist/extensions/llama/client.js +7 -0
  369. package/dist/extensions/llama/client.js.map +1 -1
  370. package/dist/extensions/llama/provider.d.ts +3 -1
  371. package/dist/extensions/llama/provider.d.ts.map +1 -1
  372. package/dist/extensions/llama/provider.js +27 -6
  373. package/dist/extensions/llama/provider.js.map +1 -1
  374. package/dist/index.d.ts +3 -2
  375. package/dist/index.d.ts.map +1 -1
  376. package/dist/index.js +1 -0
  377. package/dist/index.js.map +1 -1
  378. package/dist/main.d.ts.map +1 -1
  379. package/dist/main.js +128 -1
  380. package/dist/main.js.map +1 -1
  381. package/dist/modes/interactive/chat-viewport.d.ts +20 -0
  382. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
  383. package/dist/modes/interactive/chat-viewport.js +28 -0
  384. package/dist/modes/interactive/chat-viewport.js.map +1 -0
  385. package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  386. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  387. package/dist/modes/interactive/components/assistant-message.js +18 -10
  388. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  389. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  390. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  391. package/dist/modes/interactive/components/custom-editor.js +39 -1
  392. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  393. package/dist/modes/interactive/components/index.d.ts +1 -1
  394. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  395. package/dist/modes/interactive/components/index.js.map +1 -1
  396. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  397. package/dist/modes/interactive/components/model-selector.js +5 -14
  398. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  399. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  400. package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
  401. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  402. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  403. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  404. package/dist/modes/interactive/components/settings-selector.js +24 -10
  405. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  406. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  407. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  408. package/dist/modes/interactive/components/status-indicator.js +10 -3
  409. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  410. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  411. package/dist/modes/interactive/components/thinking-selector.js +2 -2
  412. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  413. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  414. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  415. package/dist/modes/interactive/components/tool-execution.js +35 -33
  416. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  417. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
  418. package/dist/modes/interactive/components/trust-selector.js +2 -2
  419. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  420. package/dist/modes/interactive/interactive-mode.d.ts +11 -14
  421. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  422. package/dist/modes/interactive/interactive-mode.js +140 -101
  423. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  424. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  425. package/dist/modes/interactive/session-share.js +8 -12
  426. package/dist/modes/interactive/session-share.js.map +1 -1
  427. package/dist/modes/interactive/theme/dark.json +2 -1
  428. package/dist/modes/interactive/theme/light.json +2 -1
  429. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  430. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  431. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  432. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  433. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  434. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  435. package/dist/modes/interactive/theme/theme-json.js +130 -0
  436. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +6 -2
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  440. package/dist/modes/interactive/theme/theme.js +18 -119
  441. package/dist/modes/interactive/theme/theme.js.map +1 -1
  442. package/dist/modes/interactive/tui-renderer.d.ts +21 -0
  443. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
  444. package/dist/modes/interactive/tui-renderer.js +66 -0
  445. package/dist/modes/interactive/tui-renderer.js.map +1 -0
  446. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  447. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  448. package/dist/modes/rpc/rpc-client.js +7 -0
  449. package/dist/modes/rpc/rpc-client.js.map +1 -1
  450. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  451. package/dist/modes/rpc/rpc-mode.js +3 -0
  452. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  453. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  454. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  455. package/dist/modes/rpc/rpc-types.js.map +1 -1
  456. package/dist/utils/exif-orientation.d.ts.map +1 -1
  457. package/dist/utils/exif-orientation.js +2 -3
  458. package/dist/utils/exif-orientation.js.map +1 -1
  459. package/dist/utils/shell.d.ts.map +1 -1
  460. package/dist/utils/shell.js +6 -4
  461. package/dist/utils/shell.js.map +1 -1
  462. package/dist/utils/tools-manager.d.ts +1 -0
  463. package/dist/utils/tools-manager.d.ts.map +1 -1
  464. package/dist/utils/tools-manager.js +41 -19
  465. package/dist/utils/tools-manager.js.map +1 -1
  466. package/docs/compaction.md +2 -0
  467. package/docs/custom-provider.md +7 -7
  468. package/docs/environment-variables.md +5 -0
  469. package/docs/extensions.md +23 -2
  470. package/docs/keybindings.md +4 -2
  471. package/docs/models.md +3 -0
  472. package/docs/quickstart.md +1 -1
  473. package/docs/rpc.md +24 -1
  474. package/docs/sdk.md +5 -0
  475. package/docs/settings.md +17 -9
  476. package/docs/skills.md +1 -1
  477. package/docs/terminal-setup.md +27 -0
  478. package/docs/themes.md +8 -6
  479. package/docs/tui.md +20 -1
  480. package/docs/usage.md +4 -3
  481. package/examples/README.md +3 -0
  482. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  483. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  484. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  485. package/examples/extensions/dynamic-resources/dynamic.json +2 -0
  486. package/examples/extensions/gondolin/package-lock.json +2 -2
  487. package/examples/extensions/gondolin/package.json +1 -1
  488. package/examples/extensions/sandbox/package-lock.json +2 -2
  489. package/examples/extensions/sandbox/package.json +1 -1
  490. package/examples/extensions/with-deps/package-lock.json +2 -2
  491. package/examples/extensions/with-deps/package.json +1 -1
  492. package/examples/plugins/pi-example-plugin/README.md +38 -0
  493. package/examples/plugins/pi-example-plugin/package.json +10 -0
  494. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  495. package/examples/plugins/pi-example-plugin/src/session.ts +23 -0
  496. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  497. package/npm-shrinkwrap.json +523 -28
  498. package/package.json +12 -6
  499. package/dist/bun/register-bedrock.d.ts +0 -2
  500. package/dist/bun/register-bedrock.d.ts.map +0 -1
  501. package/dist/bun/register-bedrock.js +0 -4
  502. package/dist/bun/register-bedrock.js.map +0 -1
  503. package/dist/bundle/chunks/anthropic-messages-JWX2WP65.js +0 -45
  504. package/dist/bundle/chunks/chunk-E5KXRMZK.js +0 -1547
  505. package/dist/bundle/chunks/chunk-JUUC5AZY.js +0 -2
  506. package/dist/bundle/chunks/dist-RDWOYWHR.js +0 -13
  507. package/dist/bundle/chunks/openai-codex-responses-GJVBJXLB.js +0 -8
  508. package/dist/bundle/chunks/openai-completions-JD4WAC3R.js +0 -7
  509. package/dist/bundle/chunks/openai-responses-BSOJMSEV.js +0 -2
  510. package/dist/cli/experimental/auth.d.ts +0 -16
  511. package/dist/cli/experimental/auth.d.ts.map +0 -1
  512. package/dist/cli/experimental/auth.js +0 -13
  513. package/dist/cli/experimental/auth.js.map +0 -1
  514. package/dist/cli/experimental/commands/pi.d.ts +0 -15
  515. package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
  516. package/dist/cli/experimental/commands/pi.js +0 -28
  517. package/dist/cli/experimental/commands/pi.js.map +0 -1
  518. package/dist/cli/experimental/transport-address.d.ts +0 -10
  519. package/dist/cli/experimental/transport-address.d.ts.map +0 -1
  520. package/dist/cli/experimental/transport-address.js +0 -38
  521. package/dist/cli/experimental/transport-address.js.map +0 -1
  522. package/dist/client/remote-session.d.ts +0 -53
  523. package/dist/client/remote-session.d.ts.map +0 -1
  524. package/dist/client/remote-session.js +0 -340
  525. package/dist/client/remote-session.js.map +0 -1
  526. package/dist/client/transcript.d.ts +0 -12
  527. package/dist/client/transcript.d.ts.map +0 -1
  528. package/dist/client/transcript.js +0 -98
  529. package/dist/client/transcript.js.map +0 -1
  530. package/dist/server/create-harness.d.ts +0 -26
  531. package/dist/server/create-harness.d.ts.map +0 -1
  532. package/dist/server/create-harness.js +0 -106
  533. package/dist/server/create-harness.js.map +0 -1
@@ -0,0 +1,109 @@
1
+ import { defineFacet } from "@earendil-works/chord";
2
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
+ import { reduceLaneSnapshot, } from "@earendil-works/pi-agent-core";
4
+ import { Transcript } from "./transcript.js";
5
+ export function createTranscriptService(lane, createState) {
6
+ const state = createState({ snapshot: null, event: null });
7
+ let watch;
8
+ let rebase;
9
+ let rebaseError;
10
+ const publishSnapshot = (next, event, context) => {
11
+ state.state.snapshot = next;
12
+ state.state.event = event;
13
+ state.publish(context);
14
+ };
15
+ const scheduleRebase = (context) => {
16
+ if (rebase !== undefined)
17
+ return;
18
+ const activeWatch = watch;
19
+ if (activeWatch === undefined)
20
+ return;
21
+ const pending = (async () => {
22
+ const refreshed = await activeWatch.resnapshot(context);
23
+ publishSnapshot(refreshed, null, context);
24
+ })();
25
+ rebase = pending;
26
+ void pending.then(() => {
27
+ if (rebase === pending)
28
+ rebase = undefined;
29
+ }, (error) => {
30
+ rebaseError = error instanceof Error ? error : new Error(String(error));
31
+ if (rebase === pending)
32
+ rebase = undefined;
33
+ });
34
+ };
35
+ const onEvent = (event, context) => {
36
+ if (rebaseError !== undefined)
37
+ throw rebaseError;
38
+ const forwarded = toLaneWatchEvent(event);
39
+ if (forwarded === undefined)
40
+ return;
41
+ const snapshot = state.state.snapshot;
42
+ if (snapshot === null)
43
+ throw new Error("Transcript service is not active");
44
+ if (reduceLaneSnapshot(snapshot, event) === "rebase")
45
+ scheduleRebase(context);
46
+ state.state.event = forwarded;
47
+ state.publish(context);
48
+ };
49
+ return {
50
+ service: { state },
51
+ async activate() {
52
+ if (watch !== undefined)
53
+ throw new Error("Transcript service is already active");
54
+ const opened = await lane.watch(BACKGROUND_CONTEXT);
55
+ watch = opened;
56
+ publishSnapshot(opened.snapshot, null, BACKGROUND_CONTEXT);
57
+ opened.start(onEvent);
58
+ },
59
+ async dispose() {
60
+ let failure;
61
+ try {
62
+ await rebase;
63
+ }
64
+ catch (error) {
65
+ failure = error;
66
+ }
67
+ watch?.unsubscribe();
68
+ watch = undefined;
69
+ if (failure !== undefined)
70
+ throw failure;
71
+ },
72
+ };
73
+ }
74
+ export function createTranscriptServiceFacet(lane) {
75
+ return defineFacet({
76
+ id: "@pi/transcript",
77
+ setup(env) {
78
+ const runtime = createTranscriptService(lane, env.replicatedState);
79
+ env.provide(Transcript, runtime.service);
80
+ env.onActivate(() => runtime.activate());
81
+ env.own(() => runtime.dispose());
82
+ },
83
+ });
84
+ }
85
+ function toLaneWatchEvent(event) {
86
+ switch (event.type) {
87
+ case "handler_error":
88
+ case "turn_start":
89
+ case "turn_end":
90
+ case "value_update":
91
+ case "lane_created":
92
+ return undefined;
93
+ case "config_update":
94
+ if (event.property !== "model" && event.property !== "thinkingLevel" && event.property !== "activeTools") {
95
+ return undefined;
96
+ }
97
+ return event;
98
+ case "message_update": {
99
+ if (event.message.role !== "assistant") {
100
+ throw new TypeError("Harness message_update did not carry an assistant message");
101
+ }
102
+ const { event: _providerEvent, ...update } = event;
103
+ return update;
104
+ }
105
+ default:
106
+ return event;
107
+ }
108
+ }
109
+ //# sourceMappingURL=transcript-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-provider.js","sourceRoot":"","sources":["../../../src/experimental/services/transcript-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineFacet, type Facet, type MutableReplicatedState } from \"@earendil-works/chord\";\nimport { BACKGROUND_CONTEXT } from \"@earendil-works/chord/context\";\nimport {\n\ttype AgentLane,\n\ttype HarnessEvent,\n\ttype LaneSnapshot,\n\ttype LaneTranscriptSnapshot,\n\ttype LaneWatchEvent,\n\treduceLaneSnapshot,\n\ttype WatchHandle,\n} from \"@earendil-works/pi-agent-core\";\nimport { Transcript, type Transcript as TranscriptService, type TranscriptState } from \"./transcript.ts\";\n\ninterface TranscriptRuntime {\n\treadonly service: TranscriptService;\n\tactivate(): Promise<void>;\n\tdispose(): Promise<void>;\n}\n\nexport function createTranscriptService(\n\tlane: AgentLane,\n\tcreateState: (initial: TranscriptState) => MutableReplicatedState<TranscriptState>,\n): TranscriptRuntime {\n\tconst state = createState({ snapshot: null, event: null });\n\tlet watch: WatchHandle<LaneSnapshot> | undefined;\n\tlet rebase: Promise<void> | undefined;\n\tlet rebaseError: Error | undefined;\n\n\tconst publishSnapshot = (\n\t\tnext: LaneSnapshot,\n\t\tevent: LaneWatchEvent | null,\n\t\tcontext: Parameters<typeof state.publish>[0],\n\t): void => {\n\t\tstate.state.snapshot = next as LaneTranscriptSnapshot;\n\t\tstate.state.event = event;\n\t\tstate.publish(context);\n\t};\n\n\tconst scheduleRebase = (context: Parameters<typeof state.publish>[0]): void => {\n\t\tif (rebase !== undefined) return;\n\t\tconst activeWatch = watch;\n\t\tif (activeWatch === undefined) return;\n\t\tconst pending = (async () => {\n\t\t\tconst refreshed = await activeWatch.resnapshot(context);\n\t\t\tpublishSnapshot(refreshed, null, context);\n\t\t})();\n\t\trebase = pending;\n\t\tvoid pending.then(\n\t\t\t() => {\n\t\t\t\tif (rebase === pending) rebase = undefined;\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\trebaseError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\tif (rebase === pending) rebase = undefined;\n\t\t\t},\n\t\t);\n\t};\n\n\tconst onEvent = (event: HarnessEvent, context: Parameters<typeof state.publish>[0]): void => {\n\t\tif (rebaseError !== undefined) throw rebaseError;\n\t\tconst forwarded = toLaneWatchEvent(event);\n\t\tif (forwarded === undefined) return;\n\t\tconst snapshot = state.state.snapshot;\n\t\tif (snapshot === null) throw new Error(\"Transcript service is not active\");\n\t\tif (reduceLaneSnapshot(snapshot, event) === \"rebase\") scheduleRebase(context);\n\t\tstate.state.event = forwarded;\n\t\tstate.publish(context);\n\t};\n\n\treturn {\n\t\tservice: { state },\n\t\tasync activate() {\n\t\t\tif (watch !== undefined) throw new Error(\"Transcript service is already active\");\n\t\t\tconst opened = await lane.watch(BACKGROUND_CONTEXT);\n\t\t\twatch = opened;\n\t\t\tpublishSnapshot(opened.snapshot, null, BACKGROUND_CONTEXT);\n\t\t\topened.start(onEvent);\n\t\t},\n\t\tasync dispose() {\n\t\t\tlet failure: unknown;\n\t\t\ttry {\n\t\t\t\tawait rebase;\n\t\t\t} catch (error) {\n\t\t\t\tfailure = error;\n\t\t\t}\n\t\t\twatch?.unsubscribe();\n\t\t\twatch = undefined;\n\t\t\tif (failure !== undefined) throw failure;\n\t\t},\n\t};\n}\n\nexport function createTranscriptServiceFacet(lane: AgentLane): Facet {\n\treturn defineFacet({\n\t\tid: \"@pi/transcript\",\n\t\tsetup(env) {\n\t\t\tconst runtime = createTranscriptService(lane, env.replicatedState);\n\t\t\tenv.provide(Transcript, runtime.service);\n\t\t\tenv.onActivate(() => runtime.activate());\n\t\t\tenv.own(() => runtime.dispose());\n\t\t},\n\t});\n}\n\nfunction toLaneWatchEvent(event: HarnessEvent): LaneWatchEvent | undefined {\n\tswitch (event.type) {\n\t\tcase \"handler_error\":\n\t\tcase \"turn_start\":\n\t\tcase \"turn_end\":\n\t\tcase \"value_update\":\n\t\tcase \"lane_created\":\n\t\t\treturn undefined;\n\t\tcase \"config_update\":\n\t\t\tif (event.property !== \"model\" && event.property !== \"thinkingLevel\" && event.property !== \"activeTools\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn event as LaneWatchEvent;\n\t\tcase \"message_update\": {\n\t\t\tif (event.message.role !== \"assistant\") {\n\t\t\t\tthrow new TypeError(\"Harness message_update did not carry an assistant message\");\n\t\t\t}\n\t\t\tconst { event: _providerEvent, ...update } = event;\n\t\t\treturn update as LaneWatchEvent;\n\t\t}\n\t\tdefault:\n\t\t\treturn event as LaneWatchEvent;\n\t}\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { type ReplicatedState } from "@earendil-works/chord";
2
+ import type { LaneTranscriptSnapshot, LaneWatchEvent } from "@earendil-works/pi-agent-core";
3
+ export interface TranscriptState {
4
+ snapshot: LaneTranscriptSnapshot | null;
5
+ /** The source event is retained for presentation side effects; hydration does not replay it. */
6
+ event: LaneWatchEvent | null;
7
+ }
8
+ /** Coherent main-lane state replicated through Chord's operation stream. */
9
+ export interface Transcript {
10
+ readonly state: ReplicatedState<TranscriptState>;
11
+ }
12
+ export declare const Transcript: import("@earendil-works/chord").Service<Transcript>;
13
+ //# sourceMappingURL=transcript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../src/experimental/services/transcript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE5F,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACxC,gGAAgG;IAChG,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CAC7B;AAED,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;CACjD;AAED,eAAO,MAAM,UAAU,qDAA6C,CAAC","sourcesContent":["import { defineService, type ReplicatedState } from \"@earendil-works/chord\";\nimport type { LaneTranscriptSnapshot, LaneWatchEvent } from \"@earendil-works/pi-agent-core\";\n\nexport interface TranscriptState {\n\tsnapshot: LaneTranscriptSnapshot | null;\n\t/** The source event is retained for presentation side effects; hydration does not replay it. */\n\tevent: LaneWatchEvent | null;\n}\n\n/** Coherent main-lane state replicated through Chord's operation stream. */\nexport interface Transcript {\n\treadonly state: ReplicatedState<TranscriptState>;\n}\n\nexport const Transcript = defineService<Transcript>(\"pi.transcript\");\n"]}
@@ -0,0 +1,3 @@
1
+ import { defineService } from "@earendil-works/chord";
2
+ export const Transcript = defineService("pi.transcript");
3
+ //# sourceMappingURL=transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../../src/experimental/services/transcript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAwB,MAAM,uBAAuB,CAAC;AAc5E,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAa,eAAe,CAAC,CAAC","sourcesContent":["import { defineService, type ReplicatedState } from \"@earendil-works/chord\";\nimport type { LaneTranscriptSnapshot, LaneWatchEvent } from \"@earendil-works/pi-agent-core\";\n\nexport interface TranscriptState {\n\tsnapshot: LaneTranscriptSnapshot | null;\n\t/** The source event is retained for presentation side effects; hydration does not replay it. */\n\tevent: LaneWatchEvent | null;\n}\n\n/** Coherent main-lane state replicated through Chord's operation stream. */\nexport interface Transcript {\n\treadonly state: ReplicatedState<TranscriptState>;\n}\n\nexport const Transcript = defineService<Transcript>(\"pi.transcript\");\n"]}
@@ -0,0 +1,28 @@
1
+ import { type Context, type FacetLoader, type JsonValue, type ServiceCall, type ServiceProviderUpdate } from "@earendil-works/chord";
2
+ import type { AgentHarness, AgentLane } from "@earendil-works/pi-agent-core";
3
+ import type { ModelRuntime } from "../../core/model-runtime.ts";
4
+ import type { SettingsManager } from "../../core/settings-manager.ts";
5
+ export interface SessionWorkerRuntime {
6
+ readonly harness: AgentHarness;
7
+ readonly lane?: AgentLane;
8
+ readonly modelRuntime?: ModelRuntime;
9
+ readonly settingsManager?: SettingsManager;
10
+ readonly facetLoader?: FacetLoader;
11
+ }
12
+ export interface WorkerServiceScope {
13
+ readonly serverConnectionId: string;
14
+ readonly attachmentId: string;
15
+ }
16
+ export interface SessionWorkerServices {
17
+ invoke(call: ServiceCall, scope: WorkerServiceScope, context: Context): Promise<JsonValue | undefined>;
18
+ removeSubscriptions(matches: (scope: WorkerServiceScope) => boolean): void;
19
+ dispose(): Promise<void>;
20
+ }
21
+ export declare function createSessionWorkerServices(options: {
22
+ readonly lane: AgentLane;
23
+ readonly modelRuntime: ModelRuntime | undefined;
24
+ readonly settingsManager?: SettingsManager;
25
+ readonly facetLoader?: FacetLoader;
26
+ publish(scope: WorkerServiceScope, subscriptionId: string, update: ServiceProviderUpdate): Promise<void>;
27
+ }): Promise<SessionWorkerServices>;
28
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/experimental/services/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,OAAO,EAMZ,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAOtE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACnC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAOD,MAAM,WAAW,qBAAqB;IACrC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IACvG,mBAAmB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,GAAG,IAAI,CAAC;IAC3E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IAC1D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,OAAO,CAAC,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzG,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+FjC","sourcesContent":["import {\n\ttype Context,\n\tcreateFacetHost,\n\tcreateRemoteServiceEndpoint,\n\tcreateStaticFacetLoader,\n\tdefineFacet,\n\ttype FacetHost,\n\ttype FacetLoader,\n\ttype JsonValue,\n\ttype RemoteServiceEndpoint,\n\ttype ServiceCall,\n\ttype ServiceProviderUpdate,\n} from \"@earendil-works/chord\";\nimport type { AgentHarness, AgentLane } from \"@earendil-works/pi-agent-core\";\nimport type { ModelRuntime } from \"../../core/model-runtime.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { AgentController } from \"./agent-controller.ts\";\nimport { createAgentController } from \"./agent-controller-provider.ts\";\nimport { createModelsServiceFacet } from \"./models-provider.ts\";\nimport { SessionPlugins } from \"./plugins.ts\";\nimport { createTranscriptServiceFacet } from \"./transcript-provider.ts\";\n\nexport interface SessionWorkerRuntime {\n\treadonly harness: AgentHarness;\n\treadonly lane?: AgentLane;\n\treadonly modelRuntime?: ModelRuntime;\n\treadonly settingsManager?: SettingsManager;\n\treadonly facetLoader?: FacetLoader;\n}\n\nexport interface WorkerServiceScope {\n\treadonly serverConnectionId: string;\n\treadonly attachmentId: string;\n}\n\ninterface ScopedServiceEndpoint {\n\treadonly scope: WorkerServiceScope;\n\treadonly endpoint: RemoteServiceEndpoint;\n}\n\nexport interface SessionWorkerServices {\n\tinvoke(call: ServiceCall, scope: WorkerServiceScope, context: Context): Promise<JsonValue | undefined>;\n\tremoveSubscriptions(matches: (scope: WorkerServiceScope) => boolean): void;\n\tdispose(): Promise<void>;\n}\n\nexport async function createSessionWorkerServices(options: {\n\treadonly lane: AgentLane;\n\treadonly modelRuntime: ModelRuntime | undefined;\n\treadonly settingsManager?: SettingsManager;\n\treadonly facetLoader?: FacetLoader;\n\tpublish(scope: WorkerServiceScope, subscriptionId: string, update: ServiceProviderUpdate): Promise<void>;\n}): Promise<SessionWorkerServices> {\n\tconst agentControllerRuntimeFacet = defineFacet({\n\t\tid: \"@pi/agent-controller-runtime\",\n\t\tsetup(env) {\n\t\t\tenv.provide(AgentController, createAgentController(options.lane));\n\t\t},\n\t});\n\tlet reloadPlugins = (): Promise<void> => Promise.reject(new Error(\"Session plugins are not ready\"));\n\tconst pluginRuntimeFacet = defineFacet({\n\t\tid: \"@pi/session-plugins-runtime\",\n\t\tsetup(env) {\n\t\t\tenv.provide(SessionPlugins, { reload: () => reloadPlugins() });\n\t\t},\n\t});\n\tconst builtins = await createStaticFacetLoader([\n\t\tagentControllerRuntimeFacet,\n\t\tpluginRuntimeFacet,\n\t\tcreateModelsServiceFacet(options),\n\t\tcreateTranscriptServiceFacet(options.lane),\n\t]).load();\n\tconst pluginLoader = options.facetLoader ?? createStaticFacetLoader([]);\n\tlet loadedPlugins = await pluginLoader.load();\n\tlet facetHost: FacetHost;\n\ttry {\n\t\tfacetHost = await createFacetHost({ facets: [...builtins.facets, ...loadedPlugins.facets] });\n\t} catch (error) {\n\t\tconst cleanup = await Promise.allSettled([loadedPlugins.dispose(), builtins.dispose()]);\n\t\tconst cleanupErrors = cleanup.flatMap((result) => (result.status === \"rejected\" ? [result.reason] : []));\n\t\tif (cleanupErrors.length > 0) {\n\t\t\tthrow new AggregateError([error, ...cleanupErrors], \"Session facets failed to start and clean up\");\n\t\t}\n\t\tthrow error;\n\t}\n\tlet reloadTail = Promise.resolve();\n\treloadPlugins = () => {\n\t\tconst operation = reloadTail.then(async () => {\n\t\t\tconst candidate = await pluginLoader.load();\n\t\t\ttry {\n\t\t\t\tawait facetHost.reload(candidate.facets);\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tawait candidate.dispose();\n\t\t\t\t} catch (cleanupError) {\n\t\t\t\t\tthrow new AggregateError([error, cleanupError], \"Session plugin reload and cleanup failed\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst retired = loadedPlugins;\n\t\t\tloadedPlugins = candidate;\n\t\t\tawait retired.dispose();\n\t\t});\n\t\treloadTail = operation.catch(() => {});\n\t\treturn operation;\n\t};\n\tconst provider = facetHost.services;\n\n\tconst endpoints = new Map<string, ScopedServiceEndpoint>();\n\tconst removeSubscriptions = (matches: (scope: WorkerServiceScope) => boolean): void => {\n\t\tfor (const [key, entry] of endpoints) {\n\t\t\tif (!matches(entry.scope)) continue;\n\t\t\tentry.endpoint.dispose();\n\t\t\tendpoints.delete(key);\n\t\t}\n\t};\n\n\treturn {\n\t\tinvoke(call, scope, context) {\n\t\t\tconst key = serviceScopeKey(scope);\n\t\t\tlet entry = endpoints.get(key);\n\t\t\tif (entry === undefined) {\n\t\t\t\tentry = { scope, endpoint: createRemoteServiceEndpoint(provider) };\n\t\t\t\tendpoints.set(key, entry);\n\t\t\t}\n\t\t\treturn entry.endpoint.invoke(\n\t\t\t\tcall,\n\t\t\t\t(subscriptionId, update) => options.publish(scope, subscriptionId, update),\n\t\t\t\tcontext,\n\t\t\t);\n\t\t},\n\t\tremoveSubscriptions,\n\t\tasync dispose() {\n\t\t\tremoveSubscriptions(() => true);\n\t\t\tawait reloadTail;\n\t\t\tconst errors: unknown[] = [];\n\t\t\ttry {\n\t\t\t\tawait facetHost.dispose();\n\t\t\t} catch (error) {\n\t\t\t\terrors.push(error);\n\t\t\t}\n\t\t\tconst results = await Promise.allSettled([loadedPlugins.dispose(), builtins.dispose()]);\n\t\t\terrors.push(...results.flatMap((result) => (result.status === \"rejected\" ? [result.reason] : [])));\n\t\t\tif (errors.length === 1) throw errors[0];\n\t\t\tif (errors.length > 1) throw new AggregateError(errors, \"Failed to dispose Session facets\");\n\t\t},\n\t};\n}\n\nfunction serviceScopeKey(scope: WorkerServiceScope): string {\n\treturn `${scope.serverConnectionId}\\0${scope.attachmentId}`;\n}\n"]}
@@ -0,0 +1,107 @@
1
+ import { createFacetHost, createRemoteServiceEndpoint, createStaticFacetLoader, defineFacet, } from "@earendil-works/chord";
2
+ import { AgentController } from "./agent-controller.js";
3
+ import { createAgentController } from "./agent-controller-provider.js";
4
+ import { createModelsServiceFacet } from "./models-provider.js";
5
+ import { SessionPlugins } from "./plugins.js";
6
+ import { createTranscriptServiceFacet } from "./transcript-provider.js";
7
+ export async function createSessionWorkerServices(options) {
8
+ const agentControllerRuntimeFacet = defineFacet({
9
+ id: "@pi/agent-controller-runtime",
10
+ setup(env) {
11
+ env.provide(AgentController, createAgentController(options.lane));
12
+ },
13
+ });
14
+ let reloadPlugins = () => Promise.reject(new Error("Session plugins are not ready"));
15
+ const pluginRuntimeFacet = defineFacet({
16
+ id: "@pi/session-plugins-runtime",
17
+ setup(env) {
18
+ env.provide(SessionPlugins, { reload: () => reloadPlugins() });
19
+ },
20
+ });
21
+ const builtins = await createStaticFacetLoader([
22
+ agentControllerRuntimeFacet,
23
+ pluginRuntimeFacet,
24
+ createModelsServiceFacet(options),
25
+ createTranscriptServiceFacet(options.lane),
26
+ ]).load();
27
+ const pluginLoader = options.facetLoader ?? createStaticFacetLoader([]);
28
+ let loadedPlugins = await pluginLoader.load();
29
+ let facetHost;
30
+ try {
31
+ facetHost = await createFacetHost({ facets: [...builtins.facets, ...loadedPlugins.facets] });
32
+ }
33
+ catch (error) {
34
+ const cleanup = await Promise.allSettled([loadedPlugins.dispose(), builtins.dispose()]);
35
+ const cleanupErrors = cleanup.flatMap((result) => (result.status === "rejected" ? [result.reason] : []));
36
+ if (cleanupErrors.length > 0) {
37
+ throw new AggregateError([error, ...cleanupErrors], "Session facets failed to start and clean up");
38
+ }
39
+ throw error;
40
+ }
41
+ let reloadTail = Promise.resolve();
42
+ reloadPlugins = () => {
43
+ const operation = reloadTail.then(async () => {
44
+ const candidate = await pluginLoader.load();
45
+ try {
46
+ await facetHost.reload(candidate.facets);
47
+ }
48
+ catch (error) {
49
+ try {
50
+ await candidate.dispose();
51
+ }
52
+ catch (cleanupError) {
53
+ throw new AggregateError([error, cleanupError], "Session plugin reload and cleanup failed");
54
+ }
55
+ throw error;
56
+ }
57
+ const retired = loadedPlugins;
58
+ loadedPlugins = candidate;
59
+ await retired.dispose();
60
+ });
61
+ reloadTail = operation.catch(() => { });
62
+ return operation;
63
+ };
64
+ const provider = facetHost.services;
65
+ const endpoints = new Map();
66
+ const removeSubscriptions = (matches) => {
67
+ for (const [key, entry] of endpoints) {
68
+ if (!matches(entry.scope))
69
+ continue;
70
+ entry.endpoint.dispose();
71
+ endpoints.delete(key);
72
+ }
73
+ };
74
+ return {
75
+ invoke(call, scope, context) {
76
+ const key = serviceScopeKey(scope);
77
+ let entry = endpoints.get(key);
78
+ if (entry === undefined) {
79
+ entry = { scope, endpoint: createRemoteServiceEndpoint(provider) };
80
+ endpoints.set(key, entry);
81
+ }
82
+ return entry.endpoint.invoke(call, (subscriptionId, update) => options.publish(scope, subscriptionId, update), context);
83
+ },
84
+ removeSubscriptions,
85
+ async dispose() {
86
+ removeSubscriptions(() => true);
87
+ await reloadTail;
88
+ const errors = [];
89
+ try {
90
+ await facetHost.dispose();
91
+ }
92
+ catch (error) {
93
+ errors.push(error);
94
+ }
95
+ const results = await Promise.allSettled([loadedPlugins.dispose(), builtins.dispose()]);
96
+ errors.push(...results.flatMap((result) => (result.status === "rejected" ? [result.reason] : [])));
97
+ if (errors.length === 1)
98
+ throw errors[0];
99
+ if (errors.length > 1)
100
+ throw new AggregateError(errors, "Failed to dispose Session facets");
101
+ },
102
+ };
103
+ }
104
+ function serviceScopeKey(scope) {
105
+ return `${scope.serverConnectionId}\0${scope.attachmentId}`;
106
+ }
107
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/experimental/services/worker.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Context,\n\tcreateFacetHost,\n\tcreateRemoteServiceEndpoint,\n\tcreateStaticFacetLoader,\n\tdefineFacet,\n\ttype FacetHost,\n\ttype FacetLoader,\n\ttype JsonValue,\n\ttype RemoteServiceEndpoint,\n\ttype ServiceCall,\n\ttype ServiceProviderUpdate,\n} from \"@earendil-works/chord\";\nimport type { AgentHarness, AgentLane } from \"@earendil-works/pi-agent-core\";\nimport type { ModelRuntime } from \"../../core/model-runtime.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { AgentController } from \"./agent-controller.ts\";\nimport { createAgentController } from \"./agent-controller-provider.ts\";\nimport { createModelsServiceFacet } from \"./models-provider.ts\";\nimport { SessionPlugins } from \"./plugins.ts\";\nimport { createTranscriptServiceFacet } from \"./transcript-provider.ts\";\n\nexport interface SessionWorkerRuntime {\n\treadonly harness: AgentHarness;\n\treadonly lane?: AgentLane;\n\treadonly modelRuntime?: ModelRuntime;\n\treadonly settingsManager?: SettingsManager;\n\treadonly facetLoader?: FacetLoader;\n}\n\nexport interface WorkerServiceScope {\n\treadonly serverConnectionId: string;\n\treadonly attachmentId: string;\n}\n\ninterface ScopedServiceEndpoint {\n\treadonly scope: WorkerServiceScope;\n\treadonly endpoint: RemoteServiceEndpoint;\n}\n\nexport interface SessionWorkerServices {\n\tinvoke(call: ServiceCall, scope: WorkerServiceScope, context: Context): Promise<JsonValue | undefined>;\n\tremoveSubscriptions(matches: (scope: WorkerServiceScope) => boolean): void;\n\tdispose(): Promise<void>;\n}\n\nexport async function createSessionWorkerServices(options: {\n\treadonly lane: AgentLane;\n\treadonly modelRuntime: ModelRuntime | undefined;\n\treadonly settingsManager?: SettingsManager;\n\treadonly facetLoader?: FacetLoader;\n\tpublish(scope: WorkerServiceScope, subscriptionId: string, update: ServiceProviderUpdate): Promise<void>;\n}): Promise<SessionWorkerServices> {\n\tconst agentControllerRuntimeFacet = defineFacet({\n\t\tid: \"@pi/agent-controller-runtime\",\n\t\tsetup(env) {\n\t\t\tenv.provide(AgentController, createAgentController(options.lane));\n\t\t},\n\t});\n\tlet reloadPlugins = (): Promise<void> => Promise.reject(new Error(\"Session plugins are not ready\"));\n\tconst pluginRuntimeFacet = defineFacet({\n\t\tid: \"@pi/session-plugins-runtime\",\n\t\tsetup(env) {\n\t\t\tenv.provide(SessionPlugins, { reload: () => reloadPlugins() });\n\t\t},\n\t});\n\tconst builtins = await createStaticFacetLoader([\n\t\tagentControllerRuntimeFacet,\n\t\tpluginRuntimeFacet,\n\t\tcreateModelsServiceFacet(options),\n\t\tcreateTranscriptServiceFacet(options.lane),\n\t]).load();\n\tconst pluginLoader = options.facetLoader ?? createStaticFacetLoader([]);\n\tlet loadedPlugins = await pluginLoader.load();\n\tlet facetHost: FacetHost;\n\ttry {\n\t\tfacetHost = await createFacetHost({ facets: [...builtins.facets, ...loadedPlugins.facets] });\n\t} catch (error) {\n\t\tconst cleanup = await Promise.allSettled([loadedPlugins.dispose(), builtins.dispose()]);\n\t\tconst cleanupErrors = cleanup.flatMap((result) => (result.status === \"rejected\" ? [result.reason] : []));\n\t\tif (cleanupErrors.length > 0) {\n\t\t\tthrow new AggregateError([error, ...cleanupErrors], \"Session facets failed to start and clean up\");\n\t\t}\n\t\tthrow error;\n\t}\n\tlet reloadTail = Promise.resolve();\n\treloadPlugins = () => {\n\t\tconst operation = reloadTail.then(async () => {\n\t\t\tconst candidate = await pluginLoader.load();\n\t\t\ttry {\n\t\t\t\tawait facetHost.reload(candidate.facets);\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tawait candidate.dispose();\n\t\t\t\t} catch (cleanupError) {\n\t\t\t\t\tthrow new AggregateError([error, cleanupError], \"Session plugin reload and cleanup failed\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst retired = loadedPlugins;\n\t\t\tloadedPlugins = candidate;\n\t\t\tawait retired.dispose();\n\t\t});\n\t\treloadTail = operation.catch(() => {});\n\t\treturn operation;\n\t};\n\tconst provider = facetHost.services;\n\n\tconst endpoints = new Map<string, ScopedServiceEndpoint>();\n\tconst removeSubscriptions = (matches: (scope: WorkerServiceScope) => boolean): void => {\n\t\tfor (const [key, entry] of endpoints) {\n\t\t\tif (!matches(entry.scope)) continue;\n\t\t\tentry.endpoint.dispose();\n\t\t\tendpoints.delete(key);\n\t\t}\n\t};\n\n\treturn {\n\t\tinvoke(call, scope, context) {\n\t\t\tconst key = serviceScopeKey(scope);\n\t\t\tlet entry = endpoints.get(key);\n\t\t\tif (entry === undefined) {\n\t\t\t\tentry = { scope, endpoint: createRemoteServiceEndpoint(provider) };\n\t\t\t\tendpoints.set(key, entry);\n\t\t\t}\n\t\t\treturn entry.endpoint.invoke(\n\t\t\t\tcall,\n\t\t\t\t(subscriptionId, update) => options.publish(scope, subscriptionId, update),\n\t\t\t\tcontext,\n\t\t\t);\n\t\t},\n\t\tremoveSubscriptions,\n\t\tasync dispose() {\n\t\t\tremoveSubscriptions(() => true);\n\t\t\tawait reloadTail;\n\t\t\tconst errors: unknown[] = [];\n\t\t\ttry {\n\t\t\t\tawait facetHost.dispose();\n\t\t\t} catch (error) {\n\t\t\t\terrors.push(error);\n\t\t\t}\n\t\t\tconst results = await Promise.allSettled([loadedPlugins.dispose(), builtins.dispose()]);\n\t\t\terrors.push(...results.flatMap((result) => (result.status === \"rejected\" ? [result.reason] : [])));\n\t\t\tif (errors.length === 1) throw errors[0];\n\t\t\tif (errors.length > 1) throw new AggregateError(errors, \"Failed to dispose Session facets\");\n\t\t},\n\t};\n}\n\nfunction serviceScopeKey(scope: WorkerServiceScope): string {\n\treturn `${scope.serverConnectionId}\\0${scope.attachmentId}`;\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { type Context, type JsonlSessionMetadata } from "@earendil-works/pi-agent-core";
2
+ import { type RoutedSessionHandle } from "@earendil-works/pi-server";
3
+ import type { CoordinatorConnection } from "./coordinator.ts";
4
+ export declare class SessionPluginSelectionConflictError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ /** Session and process bookkeeping owned by one replaceable server process. */
8
+ export declare class SessionWorkerManager {
9
+ #private;
10
+ readonly workerPids: Map<string, number>;
11
+ constructor(coordinator: Pick<CoordinatorConnection, "controlPath" | "serverConnectionId" | "wasReplaced" | "onEvent" | "send" | "broadcast">, sessionDir: string, model?: {
12
+ readonly provider?: string;
13
+ readonly model: string;
14
+ }, onWorkerCountChanged?: (count: number) => void);
15
+ get trackedSessions(): readonly JsonlSessionMetadata[];
16
+ assertSessionPluginManifestPaths(metadata: JsonlSessionMetadata, manifestPaths: readonly string[]): void;
17
+ discover(peerIds: ReadonlySet<string>): Promise<void>;
18
+ openSession(metadata: JsonlSessionMetadata, context: Context, pluginManifestPaths: readonly string[]): Promise<RoutedSessionHandle>;
19
+ closeSession(metadata: JsonlSessionMetadata, context: Context): Promise<void>;
20
+ shutdown(): Promise<void>;
21
+ /** Forget workers without stopping them when this server is replaced. */
22
+ detach(): void;
23
+ }
24
+ //# sourceMappingURL=session-worker-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-worker-manager.d.ts","sourceRoot":"","sources":["../../src/experimental/session-worker-manager.ts"],"names":[],"mappings":"AAWA,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,oBAAoB,EAEzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAgC,KAAK,mBAAmB,EAAe,MAAM,2BAA2B,CAAC;AAEhH,OAAO,KAAK,EAAE,qBAAqB,EAA8B,MAAM,kBAAkB,CAAC;AAmB1F,qBAAa,mCAAoC,SAAQ,KAAK;IAC7D,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAqDD,+EAA+E;AAC/E,qBAAa,oBAAoB;;IAChC,QAAQ,CAAC,UAAU,sBAA6B;IAoBhD,YACC,WAAW,EAAE,IAAI,CAChB,qBAAqB,EACrB,aAAa,GAAG,oBAAoB,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CACvF,EACD,UAAU,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAC9D,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAO9C;IAED,IAAI,eAAe,IAAI,SAAS,oBAAoB,EAAE,CAErD;IAED,gCAAgC,CAAC,QAAQ,EAAE,oBAAoB,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAavG;IAEK,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB1D;IAEK,WAAW,CAChB,QAAQ,EAAE,oBAAoB,EAC9B,OAAO,EAAE,OAAO,EAChB,mBAAmB,EAAE,SAAS,MAAM,EAAE,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAQ9B;IAEK,YAAY,CAAC,QAAQ,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;IAmNK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA8B9B;IAED,yEAAyE;IACzE,MAAM,IAAI,IAAI,CAWb;CAwYD","sourcesContent":["import type { ChildProcess } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { isAbsolute } from \"node:path\";\nimport {\n\tcreateServiceUnsubscribeCall,\n\tdecodeServiceControlCall,\n\ttype JsonValue,\n\tparseServiceProviderUpdate,\n\ttype ServiceCall,\n\ttype ServiceProviderUpdate,\n} from \"@earendil-works/chord\";\nimport {\n\tBACKGROUND_CONTEXT,\n\ttype Context,\n\ttype JsonlSessionMetadata,\n\tTODO_CONTEXT,\n} from \"@earendil-works/pi-agent-core\";\nimport { type RoutedSessionAttachment, type RoutedSessionHandle, ServerError } from \"@earendil-works/pi-server\";\nimport { Check } from \"typebox/value\";\nimport type { CoordinatorConnection, CoordinatorConnectionEvent } from \"./coordinator.ts\";\nimport { spawnInternalProcess } from \"./process.ts\";\nimport {\n\tSESSION_WORKER_CONTROL_ADDRESS_ENV,\n\tSESSION_WORKER_CONTROL_TOKEN_ENV,\n\tSESSION_WORKER_PEER_ID_ENV,\n\tSESSION_WORKER_SESSION_KEY_ENV,\n\ttype SessionWorkerEvent,\n\tSessionWorkerEventSchema,\n\ttype SessionWorkerOptions,\n\ttype WorkerOperationResponse,\n\ttype WorkerOperationScope,\n} from \"./session-worker.ts\";\n\nconst WORKER_STARTUP_TIMEOUT_MS = 15_000;\nconst WORKER_SHUTDOWN_TIMEOUT_MS = 10_000;\nconst WORKER_DISCOVERY_TIMEOUT_MS = 5_000;\nconst WORKER_DEMAND_TIMEOUT_MS = 5_000;\n\nexport class SessionPluginSelectionConflictError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"SessionPluginSelectionConflictError\";\n\t}\n}\n\ninterface WorkerRecord {\n\treadonly peerId: string;\n\treadonly metadata: JsonlSessionMetadata;\n\treadonly pid: number;\n\treadonly token: string;\n\treadonly pluginManifestPaths: readonly string[];\n\treadonly terminated: Promise<Error | undefined>;\n\tresolveTerminated(error: Error | undefined): void;\n\treadonly attachmentIds: Set<string>;\n\texpectedStop: boolean;\n\tstopPromise?: Promise<void>;\n\tstopping: boolean;\n}\n\ninterface PendingDemand {\n\treadonly attachmentId: string;\n\treadonly attached: boolean;\n\treadonly requestId: string;\n\treadonly timer: NodeJS.Timeout;\n\treadonly worker: WorkerRecord;\n\tresolve(): void;\n\treject(error: Error): void;\n}\n\ninterface PendingWorkerOperation {\n\treadonly worker: WorkerRecord;\n\treadonly scope: WorkerOperationScope;\n\tcleanup(): void;\n\tresolve(result: JsonValue | undefined): void;\n\treject(error: Error): void;\n}\n\ninterface WorkerServiceSubscription {\n\treadonly worker: WorkerRecord;\n\treadonly scope: WorkerOperationScope;\n\treadonly listener: (update: ServiceProviderUpdate, context: Context) => void | Promise<void>;\n\tdeliveryTail: Promise<void>;\n}\n\ninterface PendingLaunch {\n\treadonly sessionKey: string;\n\treadonly peerId: string;\n\treadonly token: string;\n\treadonly pluginManifestPaths: readonly string[];\n\treadonly child: ChildProcess;\n\treadonly timer: NodeJS.Timeout;\n\treadonly promise: Promise<WorkerRecord>;\n\tresolve(worker: WorkerRecord): void;\n\treject(error: Error): void;\n}\n\n/** Session and process bookkeeping owned by one replaceable server process. */\nexport class SessionWorkerManager {\n\treadonly workerPids = new Map<string, number>();\n\treadonly #coordinator: Pick<\n\t\tCoordinatorConnection,\n\t\t\"controlPath\" | \"serverConnectionId\" | \"wasReplaced\" | \"onEvent\" | \"send\" | \"broadcast\"\n\t>;\n\treadonly #sessionDir: string;\n\treadonly #model: { readonly provider?: string; readonly model: string } | undefined;\n\treadonly #workersBySession = new Map<string, WorkerRecord>();\n\treadonly #workersByPeer = new Map<string, WorkerRecord>();\n\treadonly #pending = new Map<string, PendingLaunch>();\n\treadonly #pendingDemand = new Map<string, PendingDemand>();\n\treadonly #pendingOperations = new Map<string, PendingWorkerOperation>();\n\treadonly #serviceSubscriptions = new Map<string, WorkerServiceSubscription>();\n\treadonly #removeListener: () => void;\n\treadonly #onWorkerCountChanged: ((count: number) => void) | undefined;\n\t#discoveryPeers?: Set<string>;\n\t#resolveDiscovery?: () => void;\n\t#detached = false;\n\t#shuttingDown = false;\n\n\tconstructor(\n\t\tcoordinator: Pick<\n\t\t\tCoordinatorConnection,\n\t\t\t\"controlPath\" | \"serverConnectionId\" | \"wasReplaced\" | \"onEvent\" | \"send\" | \"broadcast\"\n\t\t>,\n\t\tsessionDir: string,\n\t\tmodel?: { readonly provider?: string; readonly model: string },\n\t\tonWorkerCountChanged?: (count: number) => void,\n\t) {\n\t\tthis.#coordinator = coordinator;\n\t\tthis.#sessionDir = sessionDir;\n\t\tthis.#model = model;\n\t\tthis.#onWorkerCountChanged = onWorkerCountChanged;\n\t\tthis.#removeListener = coordinator.onEvent((event) => this.#handleCoordinatorEvent(event));\n\t}\n\n\tget trackedSessions(): readonly JsonlSessionMetadata[] {\n\t\treturn [...this.#workersBySession.values()].map((worker) => worker.metadata);\n\t}\n\n\tassertSessionPluginManifestPaths(metadata: JsonlSessionMetadata, manifestPaths: readonly string[]): void {\n\t\tconst existing = this.#workersBySession.get(metadata.path);\n\t\tif (existing !== undefined && !sameStrings(existing.pluginManifestPaths, manifestPaths)) {\n\t\t\tthrow new SessionPluginSelectionConflictError(\n\t\t\t\t`Session ${metadata.id} is active with a different plugin selection`,\n\t\t\t);\n\t\t}\n\t\tconst pending = this.#pending.get(metadata.path);\n\t\tif (pending !== undefined && !sameStrings(pending.pluginManifestPaths, manifestPaths)) {\n\t\t\tthrow new SessionPluginSelectionConflictError(\n\t\t\t\t`Session ${metadata.id} is starting with a different plugin selection`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync discover(peerIds: ReadonlySet<string>): Promise<void> {\n\t\tif (this.#detached) return;\n\t\tconst undiscovered = new Set(\n\t\t\t[...peerIds].filter((peerId) => !this.#workersByPeer.has(peerId) && !this.#pendingPeer(peerId)),\n\t\t);\n\t\tif (undiscovered.size === 0) return;\n\t\tthis.#discoveryPeers = undiscovered;\n\t\tconst discovered = new Promise<void>((resolve) => {\n\t\t\tthis.#resolveDiscovery = resolve;\n\t\t});\n\t\tawait this.#coordinator.broadcast({ type: \"discover_workers\" });\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tawait Promise.race([\n\t\t\tdiscovered,\n\t\t\tnew Promise<void>((resolve) => {\n\t\t\t\ttimer = setTimeout(resolve, WORKER_DISCOVERY_TIMEOUT_MS);\n\t\t\t\ttimer.unref();\n\t\t\t}),\n\t\t]);\n\t\tif (timer) clearTimeout(timer);\n\t\tthis.#discoveryPeers = undefined;\n\t\tthis.#resolveDiscovery = undefined;\n\t}\n\n\tasync openSession(\n\t\tmetadata: JsonlSessionMetadata,\n\t\tcontext: Context,\n\t\tpluginManifestPaths: readonly string[],\n\t): Promise<RoutedSessionHandle> {\n\t\tif (this.#detached || this.#shuttingDown) throw new Error(\"Experimental server is shutting down\");\n\t\tthis.assertSessionPluginManifestPaths(metadata, pluginManifestPaths);\n\t\tconst existing = this.#workersBySession.get(metadata.path);\n\t\tif (existing) return this.#routedHandle(existing);\n\t\tconst pending = this.#pending.get(metadata.path);\n\t\tif (pending) return this.#routedHandle(await pending.promise);\n\t\treturn this.#routedHandle(await this.#launch(metadata, context, pluginManifestPaths));\n\t}\n\n\tasync closeSession(metadata: JsonlSessionMetadata, context: Context): Promise<void> {\n\t\tconst worker = this.#workersBySession.get(metadata.path) ?? (await this.#pending.get(metadata.path)?.promise);\n\t\tif (worker !== undefined) await this.#stopWorker(worker, context);\n\t}\n\n\t#routedHandle(worker: WorkerRecord): RoutedSessionHandle {\n\t\treturn {\n\t\t\tterminated: worker.terminated,\n\t\t\tattachClient: (context) => this.#attachClient(worker, context),\n\t\t\tclose: (context) => this.#stopWorker(worker, context),\n\t\t};\n\t}\n\n\tasync #attachClient(worker: WorkerRecord, context: Context): Promise<RoutedSessionAttachment> {\n\t\tif (this.#detached || this.#shuttingDown || worker.stopping) {\n\t\t\tthrow new Error(\"Experimental Session worker is stopping\");\n\t\t}\n\t\tif (this.#workersByPeer.get(worker.peerId) !== worker) {\n\t\t\tthrow new Error(\"Experimental Session worker is no longer available\");\n\t\t}\n\t\tconst attachmentId = randomUUID();\n\t\tworker.attachmentIds.add(attachmentId);\n\t\ttry {\n\t\t\tawait this.#applyDemand(worker, attachmentId, true, true, context);\n\t\t} catch (error) {\n\t\t\tworker.attachmentIds.delete(attachmentId);\n\t\t\tthrow error;\n\t\t}\n\t\tconst scope = this.#operationScope(worker, attachmentId);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tinvokeService: (call, publish, serviceContext) =>\n\t\t\t\tthis.#invokeService(worker, scope, call, publish, serviceContext),\n\t\t\trelease: async (releaseContext) => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tif (this.#detached || !worker.attachmentIds.has(attachmentId)) return;\n\t\t\t\ttry {\n\t\t\t\t\tawait this.#applyDemand(worker, attachmentId, false, true, releaseContext);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!this.#detached && !this.#coordinator.wasReplaced) throw error;\n\t\t\t\t} finally {\n\t\t\t\t\tworker.attachmentIds.delete(attachmentId);\n\t\t\t\t\tthis.#removeServiceSubscriptions((entry) => entry.worker === worker && sameScope(entry.scope, scope));\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\tasync #invokeService(\n\t\tworker: WorkerRecord,\n\t\tscope: WorkerOperationScope,\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => void | Promise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined> {\n\t\tconst control = decodeServiceControlCall(call);\n\t\tlet addedSubscriptionKey: string | undefined;\n\t\tif (control?.type === \"subscribe\") {\n\t\t\tconst key = scopedServiceSubscriptionKey(scope, control.subscriptionId);\n\t\t\tif (this.#serviceSubscriptions.has(key)) throw new Error(\"Service subscription ID is already active\");\n\t\t\tthis.#serviceSubscriptions.set(key, {\n\t\t\t\tworker,\n\t\t\t\tscope,\n\t\t\t\tlistener: (update, updateContext) => publish(control.subscriptionId, update, updateContext),\n\t\t\t\tdeliveryTail: Promise.resolve(),\n\t\t\t});\n\t\t\taddedSubscriptionKey = key;\n\t\t}\n\t\tlet response: JsonValue | undefined;\n\t\ttry {\n\t\t\tresponse = await this.#invoke(worker, scope, call, context);\n\t\t} catch (error) {\n\t\t\tif (addedSubscriptionKey !== undefined && control?.type === \"subscribe\") {\n\t\t\t\tthis.#serviceSubscriptions.delete(addedSubscriptionKey);\n\t\t\t\tvoid this.#invoke(\n\t\t\t\t\tworker,\n\t\t\t\t\tscope,\n\t\t\t\t\tcreateServiceUnsubscribeCall(control.subscriptionId),\n\t\t\t\t\tBACKGROUND_CONTEXT,\n\t\t\t\t).catch(() => {});\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tif (control?.type === \"unsubscribe\") {\n\t\t\tthis.#serviceSubscriptions.delete(scopedServiceSubscriptionKey(scope, control.subscriptionId));\n\t\t}\n\t\treturn response;\n\t}\n\n\tasync #applyDemand(\n\t\tworker: WorkerRecord,\n\t\tattachmentId: string,\n\t\tattached: boolean,\n\t\tcompensateOnTimeout: boolean,\n\t\t_context: Context,\n\t): Promise<void> {\n\t\tif (worker.stopping || this.#workersByPeer.get(worker.peerId) !== worker) {\n\t\t\tthrow new Error(\"Experimental Session worker is stopping\");\n\t\t}\n\t\tconst requestId = randomUUID();\n\t\tlet resolve!: () => void;\n\t\tlet reject!: (error: Error) => void;\n\t\tconst applied = new Promise<void>((resolvePromise, rejectPromise) => {\n\t\t\tresolve = resolvePromise;\n\t\t\treject = rejectPromise;\n\t\t});\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (compensateOnTimeout) void this.#reconcileDemandTimeout(requestId);\n\t\t\telse this.#rejectDemand(requestId, new Error(\"Session worker demand update timed out\"));\n\t\t}, WORKER_DEMAND_TIMEOUT_MS);\n\t\ttimer.unref();\n\t\tconst pending = { attachmentId, attached, requestId, timer, worker, resolve, reject };\n\t\tthis.#pendingDemand.set(requestId, pending);\n\t\ttry {\n\t\t\tawait this.#coordinator.send(worker.peerId, {\n\t\t\t\ttype: \"session_demand\",\n\t\t\t\tserverConnectionId: this.#coordinator.serverConnectionId,\n\t\t\t\trequestId,\n\t\t\t\tattachmentId,\n\t\t\t\tattached,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.#rejectDemand(requestId, error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t\treturn applied;\n\t}\n\n\t/** Worker operations have no wall-clock timeout: completion, disconnect, replacement, or shutdown settles them. */\n\t#invoke(\n\t\tworker: WorkerRecord,\n\t\tscope: WorkerOperationScope,\n\t\tcall: ServiceCall,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined> {\n\t\ttry {\n\t\t\tthis.#operationScope(worker, scope.attachmentId);\n\t\t} catch (error) {\n\t\t\treturn Promise.reject(error);\n\t\t}\n\t\tif (context.abortSignal?.aborted) return Promise.reject(abortError(context.abortSignal));\n\t\tconst requestId = randomUUID();\n\t\tlet resolve!: (result: JsonValue | undefined) => void;\n\t\tlet reject!: (error: Error) => void;\n\t\tconst result = new Promise<JsonValue | undefined>((resolvePromise, rejectPromise) => {\n\t\t\tresolve = resolvePromise;\n\t\t\treject = rejectPromise;\n\t\t});\n\t\tconst signal = context.abortSignal;\n\t\tconst onAbort = (): void => {\n\t\t\tif (signal === undefined || !this.#pendingOperations.has(requestId)) return;\n\t\t\tvoid this.#coordinator.send(worker.peerId, { type: \"operation_cancel\", requestId, scope }).catch(() => {});\n\t\t\tthis.#rejectOperation(requestId, abortError(signal));\n\t\t};\n\t\tif (signal !== undefined) signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tthis.#pendingOperations.set(requestId, {\n\t\t\tworker,\n\t\t\tscope,\n\t\t\tcleanup: () => signal?.removeEventListener(\"abort\", onAbort),\n\t\t\tresolve,\n\t\t\treject,\n\t\t});\n\t\tvoid this.#coordinator\n\t\t\t.send(worker.peerId, { type: \"operation\", requestId, scope, call })\n\t\t\t.catch((error: unknown) =>\n\t\t\t\tthis.#rejectOperation(requestId, error instanceof Error ? error : new Error(String(error))),\n\t\t\t);\n\t\treturn result;\n\t}\n\n\t#operationScope(worker: WorkerRecord, attachmentId: string): WorkerOperationScope {\n\t\tif (this.#detached || this.#shuttingDown || worker.stopping) {\n\t\t\tthrow new Error(\"Experimental Session worker is stopping\");\n\t\t}\n\t\tif (this.#workersByPeer.get(worker.peerId) !== worker || !worker.attachmentIds.has(attachmentId)) {\n\t\t\tthrow new Error(\"Experimental Session worker has no active attachment\");\n\t\t}\n\t\treturn {\n\t\t\tserverConnectionId: this.#coordinator.serverConnectionId,\n\t\t\tattachmentId,\n\t\t};\n\t}\n\n\t#stopWorker(worker: WorkerRecord, _context: Context): Promise<void> {\n\t\tif (this.#detached || this.#workersByPeer.get(worker.peerId) !== worker) return Promise.resolve();\n\t\tworker.stopPromise ??= this.#stopWorkerInternal(worker);\n\t\treturn worker.stopPromise;\n\t}\n\n\tasync #stopWorkerInternal(worker: WorkerRecord): Promise<void> {\n\t\tthis.#rejectWorkerOperations(worker, new Error(\"Session worker is stopping\"));\n\t\tworker.stopping = true;\n\t\tworker.expectedStop = true;\n\t\tvoid this.#coordinator.send(worker.peerId, { type: \"shutdown\" }).catch(() => {});\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst timedOut = new Promise<boolean>((resolve) => {\n\t\t\ttimer = setTimeout(() => resolve(true), WORKER_SHUTDOWN_TIMEOUT_MS);\n\t\t\ttimer.unref();\n\t\t});\n\t\ttry {\n\t\t\tif ((await Promise.race([worker.terminated.then(() => false), timedOut])) === true) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(worker.pid, \"SIGKILL\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!(error instanceof Error && \"code\" in error && error.code === \"ESRCH\")) throw error;\n\t\t\t\t}\n\t\t\t\tthis.#removeWorker(worker, undefined);\n\t\t\t\tawait worker.terminated;\n\t\t\t}\n\t\t} finally {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t}\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tif (this.#detached || this.#shuttingDown) return;\n\t\tthis.#shuttingDown = true;\n\t\tconst pendingWorkers = [...this.#pending.values()];\n\t\tfor (const pending of pendingWorkers) {\n\t\t\tvoid this.#coordinator.send(pending.peerId, { type: \"shutdown\" }).catch(() => {});\n\t\t}\n\t\tconst pendingFinished = Promise.all(\n\t\t\tpendingWorkers.map((pending) => {\n\t\t\t\tif (pending.child.exitCode !== null || pending.child.signalCode !== null) return Promise.resolve();\n\t\t\t\treturn new Promise<void>((resolve) => pending.child.once(\"exit\", () => resolve()));\n\t\t\t}),\n\t\t).then(() => undefined);\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst pendingTimedOut = new Promise<boolean>((resolve) => {\n\t\t\ttimer = setTimeout(() => resolve(true), WORKER_SHUTDOWN_TIMEOUT_MS);\n\t\t\ttimer.unref();\n\t\t});\n\t\tconst stopPending = (async () => {\n\t\t\tif ((await Promise.race([pendingFinished.then(() => false), pendingTimedOut])) === true) {\n\t\t\t\tfor (const pending of pendingWorkers) pending.child.kill(\"SIGKILL\");\n\t\t\t\tawait pendingFinished;\n\t\t\t}\n\t\t\tif (timer) clearTimeout(timer);\n\t\t})();\n\t\tawait Promise.all([\n\t\t\tstopPending,\n\t\t\t...[...this.#workersBySession.values()].map((worker) => this.#stopWorker(worker, BACKGROUND_CONTEXT)),\n\t\t]);\n\t\tthis.#detachState();\n\t}\n\n\t/** Forget workers without stopping them when this server is replaced. */\n\tdetach(): void {\n\t\tif (this.#detached) return;\n\t\tthis.#detached = true;\n\t\tfor (const pending of this.#pending.values()) {\n\t\t\tclearTimeout(pending.timer);\n\t\t\tpending.reject(new Error(\"Experimental server was replaced\"));\n\t\t}\n\t\tfor (const requestId of [...this.#pendingOperations.keys()]) {\n\t\t\tthis.#rejectOperation(requestId, new Error(\"Experimental server was replaced during a worker operation\"));\n\t\t}\n\t\tthis.#detachState();\n\t}\n\n\t#launch(\n\t\tmetadata: JsonlSessionMetadata,\n\t\t_context: Context,\n\t\tpluginManifestPaths: readonly string[],\n\t): Promise<WorkerRecord> {\n\t\tconst sessionKey = metadata.path;\n\t\tconst peerId = `worker-${randomUUID()}`;\n\t\tconst token = randomUUID();\n\t\tlet child: ChildProcess;\n\t\ttry {\n\t\t\tconst options: SessionWorkerOptions = {\n\t\t\t\tsessionDir: this.#sessionDir,\n\t\t\t\tmetadata: {\n\t\t\t\t\tid: metadata.id,\n\t\t\t\t\tcreatedAt: metadata.createdAt,\n\t\t\t\t\tstorageVersion: metadata.storageVersion,\n\t\t\t\t\tcwd: metadata.cwd,\n\t\t\t\t\tpath: metadata.path,\n\t\t\t\t\tmodifiedAt: metadata.modifiedAt,\n\t\t\t\t\t...(metadata.parentSessionId === undefined ? {} : { parentSessionId: metadata.parentSessionId }),\n\t\t\t\t},\n\t\t\t\tpluginManifestPaths: [...pluginManifestPaths],\n\t\t\t\t...(this.#model ?? {}),\n\t\t\t};\n\t\t\tchild = spawnInternalProcess(\"session-worker\", [JSON.stringify(options)], {\n\t\t\t\tenv: {\n\t\t\t\t\t[SESSION_WORKER_CONTROL_ADDRESS_ENV]: this.#coordinator.controlPath,\n\t\t\t\t\t[SESSION_WORKER_CONTROL_TOKEN_ENV]: token,\n\t\t\t\t\t[SESSION_WORKER_SESSION_KEY_ENV]: Buffer.from(sessionKey).toString(\"base64url\"),\n\t\t\t\t\t[SESSION_WORKER_PEER_ID_ENV]: peerId,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn Promise.reject(error);\n\t\t}\n\t\tlet resolve!: (worker: WorkerRecord) => void;\n\t\tlet reject!: (error: Error) => void;\n\t\tconst promise = new Promise<WorkerRecord>((resolvePromise, rejectPromise) => {\n\t\t\tresolve = resolvePromise;\n\t\t\treject = rejectPromise;\n\t\t});\n\t\tconst timer = setTimeout(\n\t\t\t() => this.#failPending(sessionKey, new Error(\"Session worker startup timed out\")),\n\t\t\tWORKER_STARTUP_TIMEOUT_MS,\n\t\t);\n\t\ttimer.unref();\n\t\tconst pending = {\n\t\t\tsessionKey,\n\t\t\tpeerId,\n\t\t\ttoken,\n\t\t\tpluginManifestPaths: Object.freeze([...pluginManifestPaths]),\n\t\t\tchild,\n\t\t\ttimer,\n\t\t\tpromise,\n\t\t\tresolve,\n\t\t\treject,\n\t\t};\n\t\tthis.#pending.set(sessionKey, pending);\n\t\tthis.#notifyWorkerCountChanged();\n\t\tchild.once(\"error\", (error) => this.#failPending(sessionKey, error));\n\t\tchild.once(\"exit\", (code, signal) => this.#childExited(pending, code, signal));\n\t\treturn promise;\n\t}\n\n\t#handleCoordinatorEvent(event: CoordinatorConnectionEvent): void {\n\t\tif (this.#detached) return;\n\t\tif (event.type === \"peer_disconnected\") {\n\t\t\tthis.#markDiscovered(event.peerId);\n\t\t\tconst worker = this.#workersByPeer.get(event.peerId);\n\t\t\tif (worker) {\n\t\t\t\tthis.#removeWorker(\n\t\t\t\t\tworker,\n\t\t\t\t\tworker.expectedStop\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: new Error(`Session worker ${worker.metadata.id} disconnected unexpectedly`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst pending = this.#pendingPeer(event.peerId);\n\t\t\tif (pending) this.#failPending(pending.sessionKey, new Error(\"Session worker disconnected during startup\"));\n\t\t\treturn;\n\t\t}\n\t\tif (event.type !== \"message\") return;\n\t\tif (!Check(SessionWorkerEventSchema, event.payload)) {\n\t\t\tif (\n\t\t\t\ttypeof event.payload === \"object\" &&\n\t\t\t\tevent.payload !== null &&\n\t\t\t\t\"type\" in event.payload &&\n\t\t\t\tevent.payload.type === \"operation_response\"\n\t\t\t) {\n\t\t\t\tconst worker = this.#workersByPeer.get(event.from);\n\t\t\t\tif (worker)\n\t\t\t\t\tthis.#rejectWorkerOperations(worker, new Error(\"Session worker returned an invalid operation response\"));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst message: SessionWorkerEvent = event.payload;\n\t\tif (message.type === \"worker_failed\") {\n\t\t\tconst pending = this.#pending.get(message.sessionKey);\n\t\t\tif (pending?.peerId === event.from && pending.token === message.token) {\n\t\t\t\tthis.#failPending(message.sessionKey, new Error(`Session worker failed: ${message.message}`));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (message.type === \"demand_applied\" || message.type === \"demand_rejected\") {\n\t\t\tconst pending = this.#pendingDemand.get(message.requestId);\n\t\t\tif (\n\t\t\t\t!pending ||\n\t\t\t\tpending.worker.peerId !== event.from ||\n\t\t\t\tpending.worker.token !== message.token ||\n\t\t\t\tpending.worker.metadata.path !== message.sessionKey ||\n\t\t\t\t(message.type === \"demand_applied\" &&\n\t\t\t\t\t(pending.attachmentId !== message.attachmentId || pending.attached !== message.attached))\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.#pendingDemand.delete(message.requestId);\n\t\t\tclearTimeout(pending.timer);\n\t\t\tif (message.type === \"demand_applied\") pending.resolve();\n\t\t\telse pending.reject(new Error(`Session worker rejected demand: ${message.message}`));\n\t\t\treturn;\n\t\t}\n\t\tif (message.type === \"operation_response\") {\n\t\t\tthis.#handleOperationResponse(event.from, message.token, message.sessionKey, message.response);\n\t\t\treturn;\n\t\t}\n\t\tif (message.type === \"service_update\") {\n\t\t\tthis.#handleServiceEvent(\n\t\t\t\tevent.from,\n\t\t\t\tmessage.token,\n\t\t\t\tmessage.sessionKey,\n\t\t\t\tmessage.scope,\n\t\t\t\tmessage.subscriptionId,\n\t\t\t\tmessage.update,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis.#recordReadyWorker(event.from, message);\n\t}\n\n\t#handleOperationResponse(\n\t\tpeerId: string,\n\t\ttoken: string,\n\t\tsessionKey: string,\n\t\tresponse: WorkerOperationResponse,\n\t): void {\n\t\tconst pending = this.#pendingOperations.get(response.requestId);\n\t\tif (!pending) return;\n\t\tif (\n\t\t\tpending.worker.peerId !== peerId ||\n\t\t\tpending.worker.token !== token ||\n\t\t\tpending.worker.metadata.path !== sessionKey ||\n\t\t\tresponse.scope.serverConnectionId !== pending.scope.serverConnectionId ||\n\t\t\tresponse.scope.attachmentId !== pending.scope.attachmentId\n\t\t) {\n\t\t\tthis.#rejectOperation(\n\t\t\t\tresponse.requestId,\n\t\t\t\tnew Error(\"Session worker returned a mismatched operation response\"),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis.#pendingOperations.delete(response.requestId);\n\t\tpending.cleanup();\n\t\tif (response.type === \"operation_error\") {\n\t\t\tpending.reject(\n\t\t\t\tresponse.code === undefined\n\t\t\t\t\t? new Error(`Session worker operation failed: ${response.message}`)\n\t\t\t\t\t: new ServerError(response.code, response.message),\n\t\t\t);\n\t\t} else {\n\t\t\tpending.resolve(response.result);\n\t\t}\n\t}\n\n\t#handleServiceEvent(\n\t\tpeerId: string,\n\t\ttoken: string,\n\t\tsessionKey: string,\n\t\tscope: WorkerOperationScope,\n\t\tsubscriptionId: string,\n\t\tupdate: unknown,\n\t): void {\n\t\tconst entry = this.#serviceSubscriptions.get(scopedServiceSubscriptionKey(scope, subscriptionId));\n\t\tif (\n\t\t\tentry === undefined ||\n\t\t\tentry.worker.peerId !== peerId ||\n\t\t\tentry.worker.token !== token ||\n\t\t\tentry.worker.metadata.path !== sessionKey ||\n\t\t\t!sameScope(entry.scope, scope)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tlet parsed: ServiceProviderUpdate;\n\t\ttry {\n\t\t\tparsed = parseServiceProviderUpdate(update);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tentry.deliveryTail = entry.deliveryTail.then(() => entry.listener(parsed, TODO_CONTEXT)).catch(() => {});\n\t}\n\n\t#recordReadyWorker(peerId: string, message: Extract<SessionWorkerEvent, { type: \"worker_ready\" }>): void {\n\t\tif (\n\t\t\tmessage.sessionKey !== message.metadata.path ||\n\t\t\tmessage.sessionId !== message.metadata.id ||\n\t\t\t!isAbsolute(message.metadata.cwd) ||\n\t\t\t!isAbsolute(message.metadata.path)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#markDiscovered(peerId);\n\t\tconst pending = this.#pending.get(message.sessionKey);\n\t\tif (pending?.peerId === peerId && !sameStrings(message.pluginManifestPaths, pending.pluginManifestPaths)) {\n\t\t\tthis.#failPending(message.sessionKey, new Error(\"Session worker started with stale plugin packages\"));\n\t\t\treturn;\n\t\t}\n\t\tconst existing = this.#workersBySession.get(message.sessionKey);\n\t\tif (existing && existing.peerId !== peerId) {\n\t\t\tvoid this.#coordinator.send(peerId, { type: \"shutdown\" }).catch(() => {});\n\t\t\treturn;\n\t\t}\n\t\tif (\n\t\t\tpending &&\n\t\t\t(pending.peerId !== peerId || pending.token !== message.token || pending.child.pid !== message.pid)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tif (existing) return;\n\t\tlet resolveTerminated!: (error: Error | undefined) => void;\n\t\tconst terminated = new Promise<Error | undefined>((resolve) => {\n\t\t\tresolveTerminated = resolve;\n\t\t});\n\t\tconst worker: WorkerRecord = {\n\t\t\tpeerId,\n\t\t\tmetadata: message.metadata,\n\t\t\tpid: message.pid,\n\t\t\ttoken: message.token,\n\t\t\tpluginManifestPaths: Object.freeze([...message.pluginManifestPaths]),\n\t\t\tterminated,\n\t\t\tresolveTerminated,\n\t\t\tattachmentIds: new Set(),\n\t\t\texpectedStop: false,\n\t\t\tstopping: false,\n\t\t};\n\t\tthis.#workersBySession.set(message.sessionKey, worker);\n\t\tthis.#workersByPeer.set(peerId, worker);\n\t\tthis.workerPids.set(message.sessionId, message.pid);\n\t\tif (pending) {\n\t\t\tthis.#pending.delete(message.sessionKey);\n\t\t\tclearTimeout(pending.timer);\n\t\t\tpending.resolve(worker);\n\t\t}\n\t\tthis.#notifyWorkerCountChanged();\n\t}\n\n\t#childExited(pending: PendingLaunch, code: number | null, signal: NodeJS.Signals | null): void {\n\t\tif (this.#pending.get(pending.sessionKey) === pending) {\n\t\t\tthis.#failPending(\n\t\t\t\tpending.sessionKey,\n\t\t\t\tnew Error(`Session worker exited before readiness (${signal ?? code ?? \"unknown\"})`),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst worker = this.#workersByPeer.get(pending.peerId);\n\t\tif (worker) {\n\t\t\tthis.#removeWorker(\n\t\t\t\tworker,\n\t\t\t\tworker.expectedStop\n\t\t\t\t\t? undefined\n\t\t\t\t\t: new Error(`Session worker ${worker.metadata.id} exited unexpectedly (${signal ?? code ?? \"unknown\"})`),\n\t\t\t);\n\t\t}\n\t}\n\n\t#failPending(sessionKey: string, error: Error): void {\n\t\tconst pending = this.#pending.get(sessionKey);\n\t\tif (!pending) return;\n\t\tthis.#pending.delete(sessionKey);\n\t\tclearTimeout(pending.timer);\n\t\tthis.#notifyWorkerCountChanged();\n\t\tif (pending.child.exitCode === null && pending.child.signalCode === null) pending.child.kill(\"SIGKILL\");\n\t\tpending.reject(error);\n\t}\n\n\t#rejectDemand(requestId: string, error: Error): void {\n\t\tconst pending = this.#pendingDemand.get(requestId);\n\t\tif (!pending) return;\n\t\tthis.#pendingDemand.delete(requestId);\n\t\tclearTimeout(pending.timer);\n\t\tpending.reject(error);\n\t}\n\n\t#rejectOperation(requestId: string, error: Error): void {\n\t\tconst pending = this.#pendingOperations.get(requestId);\n\t\tif (!pending) return;\n\t\tthis.#pendingOperations.delete(requestId);\n\t\tpending.cleanup();\n\t\tpending.reject(error);\n\t}\n\n\t#rejectWorkerOperations(worker: WorkerRecord, error: Error): void {\n\t\tfor (const [requestId, pending] of this.#pendingOperations) {\n\t\t\tif (pending.worker === worker) this.#rejectOperation(requestId, error);\n\t\t}\n\t}\n\n\tasync #reconcileDemandTimeout(requestId: string): Promise<void> {\n\t\tconst pending = this.#pendingDemand.get(requestId);\n\t\tif (!pending) return;\n\t\tthis.#pendingDemand.delete(requestId);\n\t\tclearTimeout(pending.timer);\n\t\tconst timeoutError = new Error(\"Session worker demand update timed out\");\n\t\ttry {\n\t\t\tawait this.#applyDemand(pending.worker, pending.attachmentId, false, false, BACKGROUND_CONTEXT);\n\t\t\tif (pending.attached) pending.reject(timeoutError);\n\t\t\telse pending.resolve();\n\t\t} catch (cleanupError) {\n\t\t\ttry {\n\t\t\t\tawait this.#stopWorker(pending.worker, BACKGROUND_CONTEXT);\n\t\t\t} catch (stopError) {\n\t\t\t\tpending.reject(\n\t\t\t\t\tnew AggregateError(\n\t\t\t\t\t\t[timeoutError, cleanupError, stopError],\n\t\t\t\t\t\t\"Session worker demand reconciliation and termination failed\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpending.reject(\n\t\t\t\tnew AggregateError(\n\t\t\t\t\t[timeoutError, cleanupError],\n\t\t\t\t\t\"Session worker demand reconciliation failed; worker was terminated\",\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\t#removeWorker(worker: WorkerRecord, error: Error | undefined): void {\n\t\tif (this.#workersByPeer.get(worker.peerId) !== worker) return;\n\t\tthis.#rejectWorkerOperations(worker, new Error(\"Session worker disconnected during an operation\"));\n\t\tthis.#removeServiceSubscriptions((entry) => entry.worker === worker);\n\t\tfor (const pending of [...this.#pendingDemand.values()]) {\n\t\t\tif (pending.worker === worker) {\n\t\t\t\tthis.#rejectDemand(pending.requestId, new Error(\"Session worker disconnected during demand update\"));\n\t\t\t}\n\t\t}\n\t\tthis.#workersByPeer.delete(worker.peerId);\n\t\tthis.#workersBySession.delete(worker.metadata.path);\n\t\tif (this.workerPids.get(worker.metadata.id) === worker.pid) this.workerPids.delete(worker.metadata.id);\n\t\tworker.resolveTerminated(error);\n\t\tthis.#notifyWorkerCountChanged();\n\t}\n\n\t#removeServiceSubscriptions(matches: (entry: WorkerServiceSubscription) => boolean): void {\n\t\tfor (const [key, entry] of this.#serviceSubscriptions) {\n\t\t\tif (matches(entry)) this.#serviceSubscriptions.delete(key);\n\t\t}\n\t}\n\n\t#notifyWorkerCountChanged(): void {\n\t\tthis.#onWorkerCountChanged?.(this.#workersBySession.size + this.#pending.size);\n\t}\n\n\t#pendingPeer(peerId: string): PendingLaunch | undefined {\n\t\treturn [...this.#pending.values()].find((pending) => pending.peerId === peerId);\n\t}\n\n\t#markDiscovered(peerId: string): void {\n\t\tif (!this.#discoveryPeers?.delete(peerId) || this.#discoveryPeers.size !== 0) return;\n\t\tthis.#resolveDiscovery?.();\n\t}\n\n\t#detachState(): void {\n\t\tthis.#removeListener();\n\t\tfor (const requestId of [...this.#pendingOperations.keys()]) {\n\t\t\tthis.#rejectOperation(requestId, new Error(\"Experimental server detached during a worker operation\"));\n\t\t}\n\t\tfor (const pending of [...this.#pendingDemand.values()]) {\n\t\t\tthis.#pendingDemand.delete(pending.requestId);\n\t\t\tclearTimeout(pending.timer);\n\t\t\tpending.resolve();\n\t\t}\n\t\tthis.#pending.clear();\n\t\tthis.#serviceSubscriptions.clear();\n\t\tthis.#workersByPeer.clear();\n\t\tthis.#workersBySession.clear();\n\t\tthis.workerPids.clear();\n\t\tthis.#discoveryPeers = undefined;\n\t\tthis.#resolveDiscovery?.();\n\t\tthis.#resolveDiscovery = undefined;\n\t}\n}\n\nfunction sameStrings(left: readonly string[], right: readonly string[]): boolean {\n\treturn left.length === right.length && left.every((value, index) => value === right[index]);\n}\n\nfunction sameScope(left: WorkerOperationScope, right: WorkerOperationScope): boolean {\n\treturn left.serverConnectionId === right.serverConnectionId && left.attachmentId === right.attachmentId;\n}\n\nfunction scopedServiceSubscriptionKey(scope: WorkerOperationScope, subscriptionId: string): string {\n\treturn `${scope.serverConnectionId}\\0${scope.attachmentId}\\0${subscriptionId}`;\n}\n\nfunction abortError(signal: AbortSignal): Error {\n\tconst reason: unknown = signal.reason;\n\treturn reason instanceof Error ? reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n"]}