@earendil-works/pi-coding-agent 0.85.0 → 0.85.1

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 (271) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +0 -2
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.d.ts.map +1 -1
  5. package/dist/bun/cli.js +3 -12
  6. package/dist/bun/cli.js.map +1 -1
  7. package/dist/bun/sandbox-env-setup.d.ts +2 -0
  8. package/dist/bun/sandbox-env-setup.d.ts.map +1 -0
  9. package/dist/bun/sandbox-env-setup.js +4 -0
  10. package/dist/bun/sandbox-env-setup.js.map +1 -0
  11. package/dist/bundle/chunks/{anthropic-messages-QAQLEQIY.js → anthropic-messages-VWZZOSJQ.js} +1 -1
  12. package/dist/bundle/chunks/{azure-openai-responses-Q7MJBRE6.js → azure-openai-responses-BJLAM6CD.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-R7UUJ4C2.js → chunk-CLNPYIDP.js} +1 -1
  14. package/dist/bundle/chunks/chunk-JVUZSMYM.js +1547 -0
  15. package/dist/bundle/chunks/{chunk-2KVJKXS2.js → chunk-U6ZMQKGD.js} +1 -1
  16. package/dist/bundle/chunks/github-copilot.js +1 -1
  17. package/dist/bundle/chunks/{google-generative-ai-BSMTYDAM.js → google-generative-ai-Q2K7FBDI.js} +1 -1
  18. package/dist/bundle/chunks/{google-vertex-5C3HASOJ.js → google-vertex-7UJBF4S7.js} +1 -1
  19. package/dist/bundle/chunks/{node-EM5FIVUN.js → node-KG362ECQ.js} +1 -1
  20. package/dist/bundle/chunks/{openai-codex-responses-YJOO52RW.js → openai-codex-responses-R6VYZTVY.js} +1 -1
  21. package/dist/bundle/chunks/{openai-completions-QOT3QX7D.js → openai-completions-EKZT2IH2.js} +1 -1
  22. package/dist/bundle/chunks/openai-responses-TFDINO6W.js +2 -0
  23. package/dist/bundle/chunks/{pi-messages-EGZIFQGG.js → pi-messages-PML4SPVJ.js} +1 -1
  24. package/dist/bundle/cli.js +1 -1
  25. package/dist/bundle/index.js +1 -1
  26. package/dist/bundle/rpc-entry.js +1 -1
  27. package/dist/cli/args.d.ts.map +1 -1
  28. package/dist/cli/args.js +0 -2
  29. package/dist/cli/args.js.map +1 -1
  30. package/dist/cli/setup.d.ts +2 -0
  31. package/dist/cli/setup.d.ts.map +1 -0
  32. package/dist/cli/setup.js +12 -0
  33. package/dist/cli/setup.js.map +1 -0
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/cli.js +3 -25
  36. package/dist/cli.js.map +1 -1
  37. package/dist/core/keybindings.d.ts +5 -0
  38. package/dist/core/keybindings.d.ts.map +1 -1
  39. package/dist/core/keybindings.js +4 -0
  40. package/dist/core/keybindings.js.map +1 -1
  41. package/dist/main.d.ts.map +1 -1
  42. package/dist/main.js +0 -122
  43. package/dist/main.js.map +1 -1
  44. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  45. package/dist/modes/interactive/components/model-selector.js +5 -5
  46. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  47. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  48. package/dist/modes/interactive/components/scoped-models-selector.js +8 -8
  49. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  50. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  51. package/dist/modes/interactive/components/thinking-selector.js +4 -4
  52. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  53. package/docs/containerization.md +45 -0
  54. package/docs/development.md +19 -0
  55. package/docs/environment-variables.md +2 -2
  56. package/docs/keybindings.md +2 -1
  57. package/docs/models.md +1 -1
  58. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  59. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  60. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  61. package/examples/extensions/gondolin/package-lock.json +2 -2
  62. package/examples/extensions/gondolin/package.json +1 -1
  63. package/examples/extensions/sandbox/package-lock.json +2 -2
  64. package/examples/extensions/sandbox/package.json +1 -1
  65. package/examples/extensions/with-deps/package-lock.json +2 -2
  66. package/examples/extensions/with-deps/package.json +1 -1
  67. package/npm-shrinkwrap.json +20 -46
  68. package/package.json +13 -12
  69. package/dist/bundle/chunks/chunk-EQ6DR6XK.js +0 -5
  70. package/dist/bundle/chunks/chunk-LZKJTKCT.js +0 -2
  71. package/dist/bundle/chunks/chunk-WBE4J4AU.js +0 -9
  72. package/dist/bundle/chunks/chunk-WZB2R5YO.js +0 -1558
  73. package/dist/bundle/chunks/openai-responses-U7LNPQN4.js +0 -2
  74. package/dist/bundle/client.js +0 -2
  75. package/dist/bundle/coordinator.js +0 -3
  76. package/dist/cli/experimental/cli.d.ts +0 -10
  77. package/dist/cli/experimental/cli.d.ts.map +0 -1
  78. package/dist/cli/experimental/cli.js +0 -9
  79. package/dist/cli/experimental/cli.js.map +0 -1
  80. package/dist/cli/experimental/command-options.d.ts +0 -28
  81. package/dist/cli/experimental/command-options.d.ts.map +0 -1
  82. package/dist/cli/experimental/command-options.js +0 -84
  83. package/dist/cli/experimental/command-options.js.map +0 -1
  84. package/dist/cli/experimental/command.d.ts +0 -70
  85. package/dist/cli/experimental/command.d.ts.map +0 -1
  86. package/dist/cli/experimental/command.js +0 -144
  87. package/dist/cli/experimental/command.js.map +0 -1
  88. package/dist/cli/experimental/commands/client.d.ts +0 -19
  89. package/dist/cli/experimental/commands/client.d.ts.map +0 -1
  90. package/dist/cli/experimental/commands/client.js +0 -63
  91. package/dist/cli/experimental/commands/client.js.map +0 -1
  92. package/dist/cli/experimental/commands/server.d.ts +0 -17
  93. package/dist/cli/experimental/commands/server.d.ts.map +0 -1
  94. package/dist/cli/experimental/commands/server.js +0 -44
  95. package/dist/cli/experimental/commands/server.js.map +0 -1
  96. package/dist/client/index.d.ts +0 -2
  97. package/dist/client/index.d.ts.map +0 -1
  98. package/dist/client/index.js +0 -2
  99. package/dist/client/index.js.map +0 -1
  100. package/dist/experimental/client-runtime.d.ts +0 -43
  101. package/dist/experimental/client-runtime.d.ts.map +0 -1
  102. package/dist/experimental/client-runtime.js +0 -183
  103. package/dist/experimental/client-runtime.js.map +0 -1
  104. package/dist/experimental/client-tui-chat.d.ts +0 -14
  105. package/dist/experimental/client-tui-chat.d.ts.map +0 -1
  106. package/dist/experimental/client-tui-chat.js +0 -171
  107. package/dist/experimental/client-tui-chat.js.map +0 -1
  108. package/dist/experimental/client-tui.d.ts +0 -37
  109. package/dist/experimental/client-tui.d.ts.map +0 -1
  110. package/dist/experimental/client-tui.js +0 -702
  111. package/dist/experimental/client-tui.js.map +0 -1
  112. package/dist/experimental/client.d.ts +0 -25
  113. package/dist/experimental/client.d.ts.map +0 -1
  114. package/dist/experimental/client.js +0 -99
  115. package/dist/experimental/client.js.map +0 -1
  116. package/dist/experimental/coordinator-entry.d.ts +0 -3
  117. package/dist/experimental/coordinator-entry.d.ts.map +0 -1
  118. package/dist/experimental/coordinator-entry.js +0 -11
  119. package/dist/experimental/coordinator-entry.js.map +0 -1
  120. package/dist/experimental/coordinator.d.ts +0 -38
  121. package/dist/experimental/coordinator.d.ts.map +0 -1
  122. package/dist/experimental/coordinator.js +0 -570
  123. package/dist/experimental/coordinator.js.map +0 -1
  124. package/dist/experimental/mini/main.d.ts +0 -7
  125. package/dist/experimental/mini/main.d.ts.map +0 -1
  126. package/dist/experimental/mini/main.js +0 -21
  127. package/dist/experimental/mini/main.js.map +0 -1
  128. package/dist/experimental/mini/server/entry.d.ts +0 -3
  129. package/dist/experimental/mini/server/entry.d.ts.map +0 -1
  130. package/dist/experimental/mini/server/entry.js +0 -11
  131. package/dist/experimental/mini/server/entry.js.map +0 -1
  132. package/dist/experimental/mini/server/run.d.ts +0 -13
  133. package/dist/experimental/mini/server/run.d.ts.map +0 -1
  134. package/dist/experimental/mini/server/run.js +0 -150
  135. package/dist/experimental/mini/server/run.js.map +0 -1
  136. package/dist/experimental/mini/shared/protocol.d.ts +0 -127
  137. package/dist/experimental/mini/shared/protocol.d.ts.map +0 -1
  138. package/dist/experimental/mini/shared/protocol.js +0 -13
  139. package/dist/experimental/mini/shared/protocol.js.map +0 -1
  140. package/dist/experimental/mini/shared/rpc.d.ts +0 -49
  141. package/dist/experimental/mini/shared/rpc.d.ts.map +0 -1
  142. package/dist/experimental/mini/shared/rpc.js +0 -171
  143. package/dist/experimental/mini/shared/rpc.js.map +0 -1
  144. package/dist/experimental/mini/shared/transport.d.ts +0 -35
  145. package/dist/experimental/mini/shared/transport.d.ts.map +0 -1
  146. package/dist/experimental/mini/shared/transport.js +0 -94
  147. package/dist/experimental/mini/shared/transport.js.map +0 -1
  148. package/dist/experimental/mini/tui/run.d.ts +0 -9
  149. package/dist/experimental/mini/tui/run.d.ts.map +0 -1
  150. package/dist/experimental/mini/tui/run.js +0 -62
  151. package/dist/experimental/mini/tui/run.js.map +0 -1
  152. package/dist/experimental/mini/tui/session.d.ts +0 -26
  153. package/dist/experimental/mini/tui/session.d.ts.map +0 -1
  154. package/dist/experimental/mini/tui/session.js +0 -94
  155. package/dist/experimental/mini/tui/session.js.map +0 -1
  156. package/dist/experimental/mini/tui/view.d.ts +0 -10
  157. package/dist/experimental/mini/tui/view.d.ts.map +0 -1
  158. package/dist/experimental/mini/tui/view.js +0 -459
  159. package/dist/experimental/mini/tui/view.js.map +0 -1
  160. package/dist/experimental/mini/worker/entry.d.ts +0 -3
  161. package/dist/experimental/mini/worker/entry.d.ts.map +0 -1
  162. package/dist/experimental/mini/worker/entry.js +0 -10
  163. package/dist/experimental/mini/worker/entry.js.map +0 -1
  164. package/dist/experimental/mini/worker/lane-service.d.ts +0 -44
  165. package/dist/experimental/mini/worker/lane-service.d.ts.map +0 -1
  166. package/dist/experimental/mini/worker/lane-service.js +0 -97
  167. package/dist/experimental/mini/worker/lane-service.js.map +0 -1
  168. package/dist/experimental/mini/worker/models-service.d.ts +0 -19
  169. package/dist/experimental/mini/worker/models-service.d.ts.map +0 -1
  170. package/dist/experimental/mini/worker/models-service.js +0 -136
  171. package/dist/experimental/mini/worker/models-service.js.map +0 -1
  172. package/dist/experimental/mini/worker/run.d.ts +0 -14
  173. package/dist/experimental/mini/worker/run.d.ts.map +0 -1
  174. package/dist/experimental/mini/worker/run.js +0 -88
  175. package/dist/experimental/mini/worker/run.js.map +0 -1
  176. package/dist/experimental/plugin.d.ts +0 -4
  177. package/dist/experimental/plugin.d.ts.map +0 -1
  178. package/dist/experimental/plugin.js +0 -4
  179. package/dist/experimental/plugin.js.map +0 -1
  180. package/dist/experimental/plugins/bundled.d.ts +0 -7
  181. package/dist/experimental/plugins/bundled.d.ts.map +0 -1
  182. package/dist/experimental/plugins/bundled.js +0 -48
  183. package/dist/experimental/plugins/bundled.js.map +0 -1
  184. package/dist/experimental/plugins/package.d.ts +0 -18
  185. package/dist/experimental/plugins/package.d.ts.map +0 -1
  186. package/dist/experimental/plugins/package.js +0 -113
  187. package/dist/experimental/plugins/package.js.map +0 -1
  188. package/dist/experimental/process.d.ts +0 -20
  189. package/dist/experimental/process.d.ts.map +0 -1
  190. package/dist/experimental/process.js +0 -85
  191. package/dist/experimental/process.js.map +0 -1
  192. package/dist/experimental/radius-auth.d.ts +0 -17
  193. package/dist/experimental/radius-auth.d.ts.map +0 -1
  194. package/dist/experimental/radius-auth.js +0 -52
  195. package/dist/experimental/radius-auth.js.map +0 -1
  196. package/dist/experimental/radius-relay.d.ts +0 -90
  197. package/dist/experimental/radius-relay.d.ts.map +0 -1
  198. package/dist/experimental/radius-relay.js +0 -582
  199. package/dist/experimental/radius-relay.js.map +0 -1
  200. package/dist/experimental/server.d.ts +0 -75
  201. package/dist/experimental/server.d.ts.map +0 -1
  202. package/dist/experimental/server.js +0 -682
  203. package/dist/experimental/server.js.map +0 -1
  204. package/dist/experimental/services/agent-controller-provider.d.ts +0 -4
  205. package/dist/experimental/services/agent-controller-provider.d.ts.map +0 -1
  206. package/dist/experimental/services/agent-controller-provider.js +0 -84
  207. package/dist/experimental/services/agent-controller-provider.js.map +0 -1
  208. package/dist/experimental/services/agent-controller.d.ts +0 -57
  209. package/dist/experimental/services/agent-controller.d.ts.map +0 -1
  210. package/dist/experimental/services/agent-controller.js +0 -3
  211. package/dist/experimental/services/agent-controller.js.map +0 -1
  212. package/dist/experimental/services/connection.d.ts +0 -40
  213. package/dist/experimental/services/connection.d.ts.map +0 -1
  214. package/dist/experimental/services/connection.js +0 -300
  215. package/dist/experimental/services/connection.js.map +0 -1
  216. package/dist/experimental/services/models-provider.d.ts +0 -16
  217. package/dist/experimental/services/models-provider.d.ts.map +0 -1
  218. package/dist/experimental/services/models-provider.js +0 -116
  219. package/dist/experimental/services/models-provider.js.map +0 -1
  220. package/dist/experimental/services/models.d.ts +0 -36
  221. package/dist/experimental/services/models.d.ts.map +0 -1
  222. package/dist/experimental/services/models.js +0 -3
  223. package/dist/experimental/services/models.js.map +0 -1
  224. package/dist/experimental/services/plugins.d.ts +0 -16
  225. package/dist/experimental/services/plugins.d.ts.map +0 -1
  226. package/dist/experimental/services/plugins.js +0 -4
  227. package/dist/experimental/services/plugins.js.map +0 -1
  228. package/dist/experimental/services/presentation-ui.d.ts +0 -13
  229. package/dist/experimental/services/presentation-ui.d.ts.map +0 -1
  230. package/dist/experimental/services/presentation-ui.js +0 -3
  231. package/dist/experimental/services/presentation-ui.js.map +0 -1
  232. package/dist/experimental/services/server.d.ts +0 -19
  233. package/dist/experimental/services/server.d.ts.map +0 -1
  234. package/dist/experimental/services/server.js +0 -104
  235. package/dist/experimental/services/server.js.map +0 -1
  236. package/dist/experimental/services/sessions.d.ts +0 -28
  237. package/dist/experimental/services/sessions.d.ts.map +0 -1
  238. package/dist/experimental/services/sessions.js +0 -4
  239. package/dist/experimental/services/sessions.js.map +0 -1
  240. package/dist/experimental/services/slash-commands-provider.d.ts +0 -14
  241. package/dist/experimental/services/slash-commands-provider.d.ts.map +0 -1
  242. package/dist/experimental/services/slash-commands-provider.js +0 -200
  243. package/dist/experimental/services/slash-commands-provider.js.map +0 -1
  244. package/dist/experimental/services/slash-commands.d.ts +0 -24
  245. package/dist/experimental/services/slash-commands.d.ts.map +0 -1
  246. package/dist/experimental/services/slash-commands.js +0 -3
  247. package/dist/experimental/services/slash-commands.js.map +0 -1
  248. package/dist/experimental/services/transcript-provider.d.ts +0 -12
  249. package/dist/experimental/services/transcript-provider.d.ts.map +0 -1
  250. package/dist/experimental/services/transcript-provider.js +0 -109
  251. package/dist/experimental/services/transcript-provider.js.map +0 -1
  252. package/dist/experimental/services/transcript.d.ts +0 -13
  253. package/dist/experimental/services/transcript.d.ts.map +0 -1
  254. package/dist/experimental/services/transcript.js +0 -3
  255. package/dist/experimental/services/transcript.js.map +0 -1
  256. package/dist/experimental/services/worker.d.ts +0 -28
  257. package/dist/experimental/services/worker.d.ts.map +0 -1
  258. package/dist/experimental/services/worker.js +0 -107
  259. package/dist/experimental/services/worker.js.map +0 -1
  260. package/dist/experimental/session-worker-manager.d.ts +0 -24
  261. package/dist/experimental/session-worker-manager.d.ts.map +0 -1
  262. package/dist/experimental/session-worker-manager.js +0 -691
  263. package/dist/experimental/session-worker-manager.js.map +0 -1
  264. package/dist/experimental/session-worker.d.ts +0 -188
  265. package/dist/experimental/session-worker.d.ts.map +0 -1
  266. package/dist/experimental/session-worker.js +0 -785
  267. package/dist/experimental/session-worker.js.map +0 -1
  268. package/dist/experimental/source-resolver.d.ts +0 -2
  269. package/dist/experimental/source-resolver.d.ts.map +0 -1
  270. package/dist/experimental/source-resolver.js +0 -71
  271. package/dist/experimental/source-resolver.js.map +0 -1
@@ -1,691 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { isAbsolute } from "node:path";
3
- import { createServiceUnsubscribeCall, decodeServiceControlCall, parseServiceProviderUpdate, } from "@earendil-works/chord";
4
- import { BACKGROUND_CONTEXT, TODO_CONTEXT, } from "@earendil-works/pi-agent-core";
5
- import { ServerError } from "@earendil-works/pi-server";
6
- import { Check } from "typebox/value";
7
- import { spawnInternalProcess } from "./process.js";
8
- import { SESSION_WORKER_CONTROL_ADDRESS_ENV, SESSION_WORKER_CONTROL_TOKEN_ENV, SESSION_WORKER_PEER_ID_ENV, SESSION_WORKER_SESSION_KEY_ENV, SessionWorkerEventSchema, } from "./session-worker.js";
9
- const WORKER_STARTUP_TIMEOUT_MS = 15_000;
10
- const WORKER_SHUTDOWN_TIMEOUT_MS = 10_000;
11
- const WORKER_DISCOVERY_TIMEOUT_MS = 5_000;
12
- const WORKER_DEMAND_TIMEOUT_MS = 5_000;
13
- export class SessionPluginSelectionConflictError extends Error {
14
- constructor(message) {
15
- super(message);
16
- this.name = "SessionPluginSelectionConflictError";
17
- }
18
- }
19
- /** Session and process bookkeeping owned by one replaceable server process. */
20
- export class SessionWorkerManager {
21
- workerPids = new Map();
22
- #coordinator;
23
- #sessionDir;
24
- #model;
25
- #workersBySession = new Map();
26
- #workersByPeer = new Map();
27
- #pending = new Map();
28
- #pendingDemand = new Map();
29
- #pendingOperations = new Map();
30
- #serviceSubscriptions = new Map();
31
- #removeListener;
32
- #onWorkerCountChanged;
33
- #discoveryPeers;
34
- #resolveDiscovery;
35
- #detached = false;
36
- #shuttingDown = false;
37
- constructor(coordinator, sessionDir, model, onWorkerCountChanged) {
38
- this.#coordinator = coordinator;
39
- this.#sessionDir = sessionDir;
40
- this.#model = model;
41
- this.#onWorkerCountChanged = onWorkerCountChanged;
42
- this.#removeListener = coordinator.onEvent((event) => this.#handleCoordinatorEvent(event));
43
- }
44
- get trackedSessions() {
45
- return [...this.#workersBySession.values()].map((worker) => worker.metadata);
46
- }
47
- assertSessionPluginManifestPaths(metadata, manifestPaths) {
48
- const existing = this.#workersBySession.get(metadata.path);
49
- if (existing !== undefined && !sameStrings(existing.pluginManifestPaths, manifestPaths)) {
50
- throw new SessionPluginSelectionConflictError(`Session ${metadata.id} is active with a different plugin selection`);
51
- }
52
- const pending = this.#pending.get(metadata.path);
53
- if (pending !== undefined && !sameStrings(pending.pluginManifestPaths, manifestPaths)) {
54
- throw new SessionPluginSelectionConflictError(`Session ${metadata.id} is starting with a different plugin selection`);
55
- }
56
- }
57
- async discover(peerIds) {
58
- if (this.#detached)
59
- return;
60
- const undiscovered = new Set([...peerIds].filter((peerId) => !this.#workersByPeer.has(peerId) && !this.#pendingPeer(peerId)));
61
- if (undiscovered.size === 0)
62
- return;
63
- this.#discoveryPeers = undiscovered;
64
- const discovered = new Promise((resolve) => {
65
- this.#resolveDiscovery = resolve;
66
- });
67
- await this.#coordinator.broadcast({ type: "discover_workers" });
68
- let timer;
69
- await Promise.race([
70
- discovered,
71
- new Promise((resolve) => {
72
- timer = setTimeout(resolve, WORKER_DISCOVERY_TIMEOUT_MS);
73
- timer.unref();
74
- }),
75
- ]);
76
- if (timer)
77
- clearTimeout(timer);
78
- this.#discoveryPeers = undefined;
79
- this.#resolveDiscovery = undefined;
80
- }
81
- async openSession(metadata, context, pluginManifestPaths) {
82
- if (this.#detached || this.#shuttingDown)
83
- throw new Error("Experimental server is shutting down");
84
- this.assertSessionPluginManifestPaths(metadata, pluginManifestPaths);
85
- const existing = this.#workersBySession.get(metadata.path);
86
- if (existing)
87
- return this.#routedHandle(existing);
88
- const pending = this.#pending.get(metadata.path);
89
- if (pending)
90
- return this.#routedHandle(await pending.promise);
91
- return this.#routedHandle(await this.#launch(metadata, context, pluginManifestPaths));
92
- }
93
- async closeSession(metadata, context) {
94
- const worker = this.#workersBySession.get(metadata.path) ?? (await this.#pending.get(metadata.path)?.promise);
95
- if (worker !== undefined)
96
- await this.#stopWorker(worker, context);
97
- }
98
- #routedHandle(worker) {
99
- return {
100
- terminated: worker.terminated,
101
- attachClient: (context) => this.#attachClient(worker, context),
102
- close: (context) => this.#stopWorker(worker, context),
103
- };
104
- }
105
- async #attachClient(worker, context) {
106
- if (this.#detached || this.#shuttingDown || worker.stopping) {
107
- throw new Error("Experimental Session worker is stopping");
108
- }
109
- if (this.#workersByPeer.get(worker.peerId) !== worker) {
110
- throw new Error("Experimental Session worker is no longer available");
111
- }
112
- const attachmentId = randomUUID();
113
- worker.attachmentIds.add(attachmentId);
114
- try {
115
- await this.#applyDemand(worker, attachmentId, true, true, context);
116
- }
117
- catch (error) {
118
- worker.attachmentIds.delete(attachmentId);
119
- throw error;
120
- }
121
- const scope = this.#operationScope(worker, attachmentId);
122
- let released = false;
123
- return {
124
- invokeService: (call, publish, serviceContext) => this.#invokeService(worker, scope, call, publish, serviceContext),
125
- release: async (releaseContext) => {
126
- if (released)
127
- return;
128
- released = true;
129
- if (this.#detached || !worker.attachmentIds.has(attachmentId))
130
- return;
131
- try {
132
- await this.#applyDemand(worker, attachmentId, false, true, releaseContext);
133
- }
134
- catch (error) {
135
- if (!this.#detached && !this.#coordinator.wasReplaced)
136
- throw error;
137
- }
138
- finally {
139
- worker.attachmentIds.delete(attachmentId);
140
- this.#removeServiceSubscriptions((entry) => entry.worker === worker && sameScope(entry.scope, scope));
141
- }
142
- },
143
- };
144
- }
145
- async #invokeService(worker, scope, call, publish, context) {
146
- const control = decodeServiceControlCall(call);
147
- let addedSubscriptionKey;
148
- if (control?.type === "subscribe") {
149
- const key = scopedServiceSubscriptionKey(scope, control.subscriptionId);
150
- if (this.#serviceSubscriptions.has(key))
151
- throw new Error("Service subscription ID is already active");
152
- this.#serviceSubscriptions.set(key, {
153
- worker,
154
- scope,
155
- listener: (update, updateContext) => publish(control.subscriptionId, update, updateContext),
156
- deliveryTail: Promise.resolve(),
157
- });
158
- addedSubscriptionKey = key;
159
- }
160
- let response;
161
- try {
162
- response = await this.#invoke(worker, scope, call, context);
163
- }
164
- catch (error) {
165
- if (addedSubscriptionKey !== undefined && control?.type === "subscribe") {
166
- this.#serviceSubscriptions.delete(addedSubscriptionKey);
167
- void this.#invoke(worker, scope, createServiceUnsubscribeCall(control.subscriptionId), BACKGROUND_CONTEXT).catch(() => { });
168
- }
169
- throw error;
170
- }
171
- if (control?.type === "unsubscribe") {
172
- this.#serviceSubscriptions.delete(scopedServiceSubscriptionKey(scope, control.subscriptionId));
173
- }
174
- return response;
175
- }
176
- async #applyDemand(worker, attachmentId, attached, compensateOnTimeout, _context) {
177
- if (worker.stopping || this.#workersByPeer.get(worker.peerId) !== worker) {
178
- throw new Error("Experimental Session worker is stopping");
179
- }
180
- const requestId = randomUUID();
181
- let resolve;
182
- let reject;
183
- const applied = new Promise((resolvePromise, rejectPromise) => {
184
- resolve = resolvePromise;
185
- reject = rejectPromise;
186
- });
187
- const timer = setTimeout(() => {
188
- if (compensateOnTimeout)
189
- void this.#reconcileDemandTimeout(requestId);
190
- else
191
- this.#rejectDemand(requestId, new Error("Session worker demand update timed out"));
192
- }, WORKER_DEMAND_TIMEOUT_MS);
193
- timer.unref();
194
- const pending = { attachmentId, attached, requestId, timer, worker, resolve, reject };
195
- this.#pendingDemand.set(requestId, pending);
196
- try {
197
- await this.#coordinator.send(worker.peerId, {
198
- type: "session_demand",
199
- serverConnectionId: this.#coordinator.serverConnectionId,
200
- requestId,
201
- attachmentId,
202
- attached,
203
- });
204
- }
205
- catch (error) {
206
- this.#rejectDemand(requestId, error instanceof Error ? error : new Error(String(error)));
207
- }
208
- return applied;
209
- }
210
- /** Worker operations have no wall-clock timeout: completion, disconnect, replacement, or shutdown settles them. */
211
- #invoke(worker, scope, call, context) {
212
- try {
213
- this.#operationScope(worker, scope.attachmentId);
214
- }
215
- catch (error) {
216
- return Promise.reject(error);
217
- }
218
- if (context.abortSignal?.aborted)
219
- return Promise.reject(abortError(context.abortSignal));
220
- const requestId = randomUUID();
221
- let resolve;
222
- let reject;
223
- const result = new Promise((resolvePromise, rejectPromise) => {
224
- resolve = resolvePromise;
225
- reject = rejectPromise;
226
- });
227
- const signal = context.abortSignal;
228
- const onAbort = () => {
229
- if (signal === undefined || !this.#pendingOperations.has(requestId))
230
- return;
231
- void this.#coordinator.send(worker.peerId, { type: "operation_cancel", requestId, scope }).catch(() => { });
232
- this.#rejectOperation(requestId, abortError(signal));
233
- };
234
- if (signal !== undefined)
235
- signal.addEventListener("abort", onAbort, { once: true });
236
- this.#pendingOperations.set(requestId, {
237
- worker,
238
- scope,
239
- cleanup: () => signal?.removeEventListener("abort", onAbort),
240
- resolve,
241
- reject,
242
- });
243
- void this.#coordinator
244
- .send(worker.peerId, { type: "operation", requestId, scope, call })
245
- .catch((error) => this.#rejectOperation(requestId, error instanceof Error ? error : new Error(String(error))));
246
- return result;
247
- }
248
- #operationScope(worker, attachmentId) {
249
- if (this.#detached || this.#shuttingDown || worker.stopping) {
250
- throw new Error("Experimental Session worker is stopping");
251
- }
252
- if (this.#workersByPeer.get(worker.peerId) !== worker || !worker.attachmentIds.has(attachmentId)) {
253
- throw new Error("Experimental Session worker has no active attachment");
254
- }
255
- return {
256
- serverConnectionId: this.#coordinator.serverConnectionId,
257
- attachmentId,
258
- };
259
- }
260
- #stopWorker(worker, _context) {
261
- if (this.#detached || this.#workersByPeer.get(worker.peerId) !== worker)
262
- return Promise.resolve();
263
- worker.stopPromise ??= this.#stopWorkerInternal(worker);
264
- return worker.stopPromise;
265
- }
266
- async #stopWorkerInternal(worker) {
267
- this.#rejectWorkerOperations(worker, new Error("Session worker is stopping"));
268
- worker.stopping = true;
269
- worker.expectedStop = true;
270
- void this.#coordinator.send(worker.peerId, { type: "shutdown" }).catch(() => { });
271
- let timer;
272
- const timedOut = new Promise((resolve) => {
273
- timer = setTimeout(() => resolve(true), WORKER_SHUTDOWN_TIMEOUT_MS);
274
- timer.unref();
275
- });
276
- try {
277
- if ((await Promise.race([worker.terminated.then(() => false), timedOut])) === true) {
278
- try {
279
- process.kill(worker.pid, "SIGKILL");
280
- }
281
- catch (error) {
282
- if (!(error instanceof Error && "code" in error && error.code === "ESRCH"))
283
- throw error;
284
- }
285
- this.#removeWorker(worker, undefined);
286
- await worker.terminated;
287
- }
288
- }
289
- finally {
290
- if (timer)
291
- clearTimeout(timer);
292
- }
293
- }
294
- async shutdown() {
295
- if (this.#detached || this.#shuttingDown)
296
- return;
297
- this.#shuttingDown = true;
298
- const pendingWorkers = [...this.#pending.values()];
299
- for (const pending of pendingWorkers) {
300
- void this.#coordinator.send(pending.peerId, { type: "shutdown" }).catch(() => { });
301
- }
302
- const pendingFinished = Promise.all(pendingWorkers.map((pending) => {
303
- if (pending.child.exitCode !== null || pending.child.signalCode !== null)
304
- return Promise.resolve();
305
- return new Promise((resolve) => pending.child.once("exit", () => resolve()));
306
- })).then(() => undefined);
307
- let timer;
308
- const pendingTimedOut = new Promise((resolve) => {
309
- timer = setTimeout(() => resolve(true), WORKER_SHUTDOWN_TIMEOUT_MS);
310
- timer.unref();
311
- });
312
- const stopPending = (async () => {
313
- if ((await Promise.race([pendingFinished.then(() => false), pendingTimedOut])) === true) {
314
- for (const pending of pendingWorkers)
315
- pending.child.kill("SIGKILL");
316
- await pendingFinished;
317
- }
318
- if (timer)
319
- clearTimeout(timer);
320
- })();
321
- await Promise.all([
322
- stopPending,
323
- ...[...this.#workersBySession.values()].map((worker) => this.#stopWorker(worker, BACKGROUND_CONTEXT)),
324
- ]);
325
- this.#detachState();
326
- }
327
- /** Forget workers without stopping them when this server is replaced. */
328
- detach() {
329
- if (this.#detached)
330
- return;
331
- this.#detached = true;
332
- for (const pending of this.#pending.values()) {
333
- clearTimeout(pending.timer);
334
- pending.reject(new Error("Experimental server was replaced"));
335
- }
336
- for (const requestId of [...this.#pendingOperations.keys()]) {
337
- this.#rejectOperation(requestId, new Error("Experimental server was replaced during a worker operation"));
338
- }
339
- this.#detachState();
340
- }
341
- #launch(metadata, _context, pluginManifestPaths) {
342
- const sessionKey = metadata.path;
343
- const peerId = `worker-${randomUUID()}`;
344
- const token = randomUUID();
345
- let child;
346
- try {
347
- const options = {
348
- sessionDir: this.#sessionDir,
349
- metadata: {
350
- id: metadata.id,
351
- createdAt: metadata.createdAt,
352
- storageVersion: metadata.storageVersion,
353
- cwd: metadata.cwd,
354
- path: metadata.path,
355
- modifiedAt: metadata.modifiedAt,
356
- ...(metadata.parentSessionId === undefined ? {} : { parentSessionId: metadata.parentSessionId }),
357
- },
358
- pluginManifestPaths: [...pluginManifestPaths],
359
- ...(this.#model ?? {}),
360
- };
361
- child = spawnInternalProcess("session-worker", [JSON.stringify(options)], {
362
- env: {
363
- [SESSION_WORKER_CONTROL_ADDRESS_ENV]: this.#coordinator.controlPath,
364
- [SESSION_WORKER_CONTROL_TOKEN_ENV]: token,
365
- [SESSION_WORKER_SESSION_KEY_ENV]: Buffer.from(sessionKey).toString("base64url"),
366
- [SESSION_WORKER_PEER_ID_ENV]: peerId,
367
- },
368
- });
369
- }
370
- catch (error) {
371
- return Promise.reject(error);
372
- }
373
- let resolve;
374
- let reject;
375
- const promise = new Promise((resolvePromise, rejectPromise) => {
376
- resolve = resolvePromise;
377
- reject = rejectPromise;
378
- });
379
- const timer = setTimeout(() => this.#failPending(sessionKey, new Error("Session worker startup timed out")), WORKER_STARTUP_TIMEOUT_MS);
380
- timer.unref();
381
- const pending = {
382
- sessionKey,
383
- peerId,
384
- token,
385
- pluginManifestPaths: Object.freeze([...pluginManifestPaths]),
386
- child,
387
- timer,
388
- promise,
389
- resolve,
390
- reject,
391
- };
392
- this.#pending.set(sessionKey, pending);
393
- this.#notifyWorkerCountChanged();
394
- child.once("error", (error) => this.#failPending(sessionKey, error));
395
- child.once("exit", (code, signal) => this.#childExited(pending, code, signal));
396
- return promise;
397
- }
398
- #handleCoordinatorEvent(event) {
399
- if (this.#detached)
400
- return;
401
- if (event.type === "peer_disconnected") {
402
- this.#markDiscovered(event.peerId);
403
- const worker = this.#workersByPeer.get(event.peerId);
404
- if (worker) {
405
- this.#removeWorker(worker, worker.expectedStop
406
- ? undefined
407
- : new Error(`Session worker ${worker.metadata.id} disconnected unexpectedly`));
408
- }
409
- const pending = this.#pendingPeer(event.peerId);
410
- if (pending)
411
- this.#failPending(pending.sessionKey, new Error("Session worker disconnected during startup"));
412
- return;
413
- }
414
- if (event.type !== "message")
415
- return;
416
- if (!Check(SessionWorkerEventSchema, event.payload)) {
417
- if (typeof event.payload === "object" &&
418
- event.payload !== null &&
419
- "type" in event.payload &&
420
- event.payload.type === "operation_response") {
421
- const worker = this.#workersByPeer.get(event.from);
422
- if (worker)
423
- this.#rejectWorkerOperations(worker, new Error("Session worker returned an invalid operation response"));
424
- }
425
- return;
426
- }
427
- const message = event.payload;
428
- if (message.type === "worker_failed") {
429
- const pending = this.#pending.get(message.sessionKey);
430
- if (pending?.peerId === event.from && pending.token === message.token) {
431
- this.#failPending(message.sessionKey, new Error(`Session worker failed: ${message.message}`));
432
- }
433
- return;
434
- }
435
- if (message.type === "demand_applied" || message.type === "demand_rejected") {
436
- const pending = this.#pendingDemand.get(message.requestId);
437
- if (!pending ||
438
- pending.worker.peerId !== event.from ||
439
- pending.worker.token !== message.token ||
440
- pending.worker.metadata.path !== message.sessionKey ||
441
- (message.type === "demand_applied" &&
442
- (pending.attachmentId !== message.attachmentId || pending.attached !== message.attached))) {
443
- return;
444
- }
445
- this.#pendingDemand.delete(message.requestId);
446
- clearTimeout(pending.timer);
447
- if (message.type === "demand_applied")
448
- pending.resolve();
449
- else
450
- pending.reject(new Error(`Session worker rejected demand: ${message.message}`));
451
- return;
452
- }
453
- if (message.type === "operation_response") {
454
- this.#handleOperationResponse(event.from, message.token, message.sessionKey, message.response);
455
- return;
456
- }
457
- if (message.type === "service_update") {
458
- this.#handleServiceEvent(event.from, message.token, message.sessionKey, message.scope, message.subscriptionId, message.update);
459
- return;
460
- }
461
- this.#recordReadyWorker(event.from, message);
462
- }
463
- #handleOperationResponse(peerId, token, sessionKey, response) {
464
- const pending = this.#pendingOperations.get(response.requestId);
465
- if (!pending)
466
- return;
467
- if (pending.worker.peerId !== peerId ||
468
- pending.worker.token !== token ||
469
- pending.worker.metadata.path !== sessionKey ||
470
- response.scope.serverConnectionId !== pending.scope.serverConnectionId ||
471
- response.scope.attachmentId !== pending.scope.attachmentId) {
472
- this.#rejectOperation(response.requestId, new Error("Session worker returned a mismatched operation response"));
473
- return;
474
- }
475
- this.#pendingOperations.delete(response.requestId);
476
- pending.cleanup();
477
- if (response.type === "operation_error") {
478
- pending.reject(response.code === undefined
479
- ? new Error(`Session worker operation failed: ${response.message}`)
480
- : new ServerError(response.code, response.message));
481
- }
482
- else {
483
- pending.resolve(response.result);
484
- }
485
- }
486
- #handleServiceEvent(peerId, token, sessionKey, scope, subscriptionId, update) {
487
- const entry = this.#serviceSubscriptions.get(scopedServiceSubscriptionKey(scope, subscriptionId));
488
- if (entry === undefined ||
489
- entry.worker.peerId !== peerId ||
490
- entry.worker.token !== token ||
491
- entry.worker.metadata.path !== sessionKey ||
492
- !sameScope(entry.scope, scope)) {
493
- return;
494
- }
495
- let parsed;
496
- try {
497
- parsed = parseServiceProviderUpdate(update);
498
- }
499
- catch {
500
- return;
501
- }
502
- entry.deliveryTail = entry.deliveryTail.then(() => entry.listener(parsed, TODO_CONTEXT)).catch(() => { });
503
- }
504
- #recordReadyWorker(peerId, message) {
505
- if (message.sessionKey !== message.metadata.path ||
506
- message.sessionId !== message.metadata.id ||
507
- !isAbsolute(message.metadata.cwd) ||
508
- !isAbsolute(message.metadata.path)) {
509
- return;
510
- }
511
- this.#markDiscovered(peerId);
512
- const pending = this.#pending.get(message.sessionKey);
513
- if (pending?.peerId === peerId && !sameStrings(message.pluginManifestPaths, pending.pluginManifestPaths)) {
514
- this.#failPending(message.sessionKey, new Error("Session worker started with stale plugin packages"));
515
- return;
516
- }
517
- const existing = this.#workersBySession.get(message.sessionKey);
518
- if (existing && existing.peerId !== peerId) {
519
- void this.#coordinator.send(peerId, { type: "shutdown" }).catch(() => { });
520
- return;
521
- }
522
- if (pending &&
523
- (pending.peerId !== peerId || pending.token !== message.token || pending.child.pid !== message.pid)) {
524
- return;
525
- }
526
- if (existing)
527
- return;
528
- let resolveTerminated;
529
- const terminated = new Promise((resolve) => {
530
- resolveTerminated = resolve;
531
- });
532
- const worker = {
533
- peerId,
534
- metadata: message.metadata,
535
- pid: message.pid,
536
- token: message.token,
537
- pluginManifestPaths: Object.freeze([...message.pluginManifestPaths]),
538
- terminated,
539
- resolveTerminated,
540
- attachmentIds: new Set(),
541
- expectedStop: false,
542
- stopping: false,
543
- };
544
- this.#workersBySession.set(message.sessionKey, worker);
545
- this.#workersByPeer.set(peerId, worker);
546
- this.workerPids.set(message.sessionId, message.pid);
547
- if (pending) {
548
- this.#pending.delete(message.sessionKey);
549
- clearTimeout(pending.timer);
550
- pending.resolve(worker);
551
- }
552
- this.#notifyWorkerCountChanged();
553
- }
554
- #childExited(pending, code, signal) {
555
- if (this.#pending.get(pending.sessionKey) === pending) {
556
- this.#failPending(pending.sessionKey, new Error(`Session worker exited before readiness (${signal ?? code ?? "unknown"})`));
557
- return;
558
- }
559
- const worker = this.#workersByPeer.get(pending.peerId);
560
- if (worker) {
561
- this.#removeWorker(worker, worker.expectedStop
562
- ? undefined
563
- : new Error(`Session worker ${worker.metadata.id} exited unexpectedly (${signal ?? code ?? "unknown"})`));
564
- }
565
- }
566
- #failPending(sessionKey, error) {
567
- const pending = this.#pending.get(sessionKey);
568
- if (!pending)
569
- return;
570
- this.#pending.delete(sessionKey);
571
- clearTimeout(pending.timer);
572
- this.#notifyWorkerCountChanged();
573
- if (pending.child.exitCode === null && pending.child.signalCode === null)
574
- pending.child.kill("SIGKILL");
575
- pending.reject(error);
576
- }
577
- #rejectDemand(requestId, error) {
578
- const pending = this.#pendingDemand.get(requestId);
579
- if (!pending)
580
- return;
581
- this.#pendingDemand.delete(requestId);
582
- clearTimeout(pending.timer);
583
- pending.reject(error);
584
- }
585
- #rejectOperation(requestId, error) {
586
- const pending = this.#pendingOperations.get(requestId);
587
- if (!pending)
588
- return;
589
- this.#pendingOperations.delete(requestId);
590
- pending.cleanup();
591
- pending.reject(error);
592
- }
593
- #rejectWorkerOperations(worker, error) {
594
- for (const [requestId, pending] of this.#pendingOperations) {
595
- if (pending.worker === worker)
596
- this.#rejectOperation(requestId, error);
597
- }
598
- }
599
- async #reconcileDemandTimeout(requestId) {
600
- const pending = this.#pendingDemand.get(requestId);
601
- if (!pending)
602
- return;
603
- this.#pendingDemand.delete(requestId);
604
- clearTimeout(pending.timer);
605
- const timeoutError = new Error("Session worker demand update timed out");
606
- try {
607
- await this.#applyDemand(pending.worker, pending.attachmentId, false, false, BACKGROUND_CONTEXT);
608
- if (pending.attached)
609
- pending.reject(timeoutError);
610
- else
611
- pending.resolve();
612
- }
613
- catch (cleanupError) {
614
- try {
615
- await this.#stopWorker(pending.worker, BACKGROUND_CONTEXT);
616
- }
617
- catch (stopError) {
618
- pending.reject(new AggregateError([timeoutError, cleanupError, stopError], "Session worker demand reconciliation and termination failed"));
619
- return;
620
- }
621
- pending.reject(new AggregateError([timeoutError, cleanupError], "Session worker demand reconciliation failed; worker was terminated"));
622
- }
623
- }
624
- #removeWorker(worker, error) {
625
- if (this.#workersByPeer.get(worker.peerId) !== worker)
626
- return;
627
- this.#rejectWorkerOperations(worker, new Error("Session worker disconnected during an operation"));
628
- this.#removeServiceSubscriptions((entry) => entry.worker === worker);
629
- for (const pending of [...this.#pendingDemand.values()]) {
630
- if (pending.worker === worker) {
631
- this.#rejectDemand(pending.requestId, new Error("Session worker disconnected during demand update"));
632
- }
633
- }
634
- this.#workersByPeer.delete(worker.peerId);
635
- this.#workersBySession.delete(worker.metadata.path);
636
- if (this.workerPids.get(worker.metadata.id) === worker.pid)
637
- this.workerPids.delete(worker.metadata.id);
638
- worker.resolveTerminated(error);
639
- this.#notifyWorkerCountChanged();
640
- }
641
- #removeServiceSubscriptions(matches) {
642
- for (const [key, entry] of this.#serviceSubscriptions) {
643
- if (matches(entry))
644
- this.#serviceSubscriptions.delete(key);
645
- }
646
- }
647
- #notifyWorkerCountChanged() {
648
- this.#onWorkerCountChanged?.(this.#workersBySession.size + this.#pending.size);
649
- }
650
- #pendingPeer(peerId) {
651
- return [...this.#pending.values()].find((pending) => pending.peerId === peerId);
652
- }
653
- #markDiscovered(peerId) {
654
- if (!this.#discoveryPeers?.delete(peerId) || this.#discoveryPeers.size !== 0)
655
- return;
656
- this.#resolveDiscovery?.();
657
- }
658
- #detachState() {
659
- this.#removeListener();
660
- for (const requestId of [...this.#pendingOperations.keys()]) {
661
- this.#rejectOperation(requestId, new Error("Experimental server detached during a worker operation"));
662
- }
663
- for (const pending of [...this.#pendingDemand.values()]) {
664
- this.#pendingDemand.delete(pending.requestId);
665
- clearTimeout(pending.timer);
666
- pending.resolve();
667
- }
668
- this.#pending.clear();
669
- this.#serviceSubscriptions.clear();
670
- this.#workersByPeer.clear();
671
- this.#workersBySession.clear();
672
- this.workerPids.clear();
673
- this.#discoveryPeers = undefined;
674
- this.#resolveDiscovery?.();
675
- this.#resolveDiscovery = undefined;
676
- }
677
- }
678
- function sameStrings(left, right) {
679
- return left.length === right.length && left.every((value, index) => value === right[index]);
680
- }
681
- function sameScope(left, right) {
682
- return left.serverConnectionId === right.serverConnectionId && left.attachmentId === right.attachmentId;
683
- }
684
- function scopedServiceSubscriptionKey(scope, subscriptionId) {
685
- return `${scope.serverConnectionId}\0${scope.attachmentId}\0${subscriptionId}`;
686
- }
687
- function abortError(signal) {
688
- const reason = signal.reason;
689
- return reason instanceof Error ? reason : new DOMException("The operation was aborted", "AbortError");
690
- }
691
- //# sourceMappingURL=session-worker-manager.js.map