@try-works/dsh-model-as-tool 0.1.3 → 0.3.6

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 (257) hide show
  1. package/bin/dsh-tool-mode-credentials.mjs +20 -0
  2. package/bin/dsh-tool-mode.mjs +202 -0
  3. package/catalog/desert-ant-models.json +693 -693
  4. package/client/client.model-tools.js +592 -0
  5. package/client/index.js +1679 -0
  6. package/lib/adapters/index.d.ts.map +1 -1
  7. package/lib/adapters/index.js +4 -4
  8. package/lib/bindings.d.ts.map +1 -1
  9. package/lib/bindings.js +26 -25
  10. package/lib/capability.d.ts.map +1 -1
  11. package/lib/capability.js +17 -0
  12. package/lib/capture-path.d.ts +97 -0
  13. package/lib/capture-path.d.ts.map +1 -0
  14. package/lib/capture-path.js +152 -0
  15. package/lib/diagnostics.d.ts +166 -0
  16. package/lib/diagnostics.d.ts.map +1 -0
  17. package/lib/diagnostics.js +135 -0
  18. package/lib/extension-dependencies.d.ts +197 -0
  19. package/lib/extension-dependencies.d.ts.map +1 -0
  20. package/lib/extension-dependencies.js +369 -0
  21. package/lib/extension-dispatch.d.ts +117 -0
  22. package/lib/extension-dispatch.d.ts.map +1 -0
  23. package/lib/extension-dispatch.js +124 -0
  24. package/lib/extension-registrar.d.ts +100 -0
  25. package/lib/extension-registrar.d.ts.map +1 -0
  26. package/lib/extension-registrar.js +134 -0
  27. package/lib/extensions-api.d.ts +217 -0
  28. package/lib/extensions-api.d.ts.map +1 -0
  29. package/lib/extensions-api.js +17 -0
  30. package/lib/extensions-remote.d.ts +54 -0
  31. package/lib/extensions-remote.d.ts.map +1 -0
  32. package/lib/extensions-remote.js +66 -0
  33. package/lib/extensions-service.d.ts +334 -0
  34. package/lib/extensions-service.d.ts.map +1 -0
  35. package/lib/extensions-service.js +1351 -0
  36. package/lib/harness-llm.d.ts +138 -0
  37. package/lib/harness-llm.d.ts.map +1 -0
  38. package/lib/harness-llm.js +218 -0
  39. package/lib/harness-serving.d.ts +95 -0
  40. package/lib/harness-serving.d.ts.map +1 -0
  41. package/lib/harness-serving.js +157 -0
  42. package/lib/headless.d.ts +97 -0
  43. package/lib/headless.d.ts.map +1 -0
  44. package/lib/headless.js +349 -0
  45. package/lib/http-acquisition.d.ts +27 -0
  46. package/lib/http-acquisition.d.ts.map +1 -0
  47. package/lib/http-acquisition.js +136 -0
  48. package/lib/index.d.ts +250 -7
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2625 -70
  51. package/lib/invocation-accounting.d.ts +169 -0
  52. package/lib/invocation-accounting.d.ts.map +1 -0
  53. package/lib/invocation-accounting.js +148 -0
  54. package/lib/legacy-migration.d.ts +95 -0
  55. package/lib/legacy-migration.d.ts.map +1 -0
  56. package/lib/legacy-migration.js +429 -0
  57. package/lib/managed-workers.d.ts +151 -0
  58. package/lib/managed-workers.d.ts.map +1 -0
  59. package/lib/managed-workers.js +249 -0
  60. package/lib/mcp/client-grant.d.ts +74 -0
  61. package/lib/mcp/client-grant.d.ts.map +1 -0
  62. package/lib/mcp/client-grant.js +60 -0
  63. package/lib/mcp/credential-cli.d.ts +38 -0
  64. package/lib/mcp/credential-cli.d.ts.map +1 -0
  65. package/lib/mcp/credential-cli.js +148 -0
  66. package/lib/mcp/credential-store.d.ts +143 -0
  67. package/lib/mcp/credential-store.d.ts.map +1 -0
  68. package/lib/mcp/credential-store.js +454 -0
  69. package/lib/mcp/host-binding.d.ts +25 -0
  70. package/lib/mcp/host-binding.d.ts.map +1 -1
  71. package/lib/mcp/host-binding.js +107 -14
  72. package/lib/mcp/http.d.ts +68 -18
  73. package/lib/mcp/http.d.ts.map +1 -1
  74. package/lib/mcp/http.js +390 -62
  75. package/lib/mcp/oauth.d.ts +149 -0
  76. package/lib/mcp/oauth.d.ts.map +1 -0
  77. package/lib/mcp/oauth.js +396 -0
  78. package/lib/mcp/protocol.d.ts +46 -7
  79. package/lib/mcp/protocol.d.ts.map +1 -1
  80. package/lib/mcp/protocol.js +63 -16
  81. package/lib/mcp/rate-limit.d.ts +68 -0
  82. package/lib/mcp/rate-limit.d.ts.map +1 -0
  83. package/lib/mcp/rate-limit.js +114 -0
  84. package/lib/mcp/sdk-v2.d.ts +17 -0
  85. package/lib/mcp/sdk-v2.d.ts.map +1 -0
  86. package/lib/mcp/sdk-v2.js +181 -0
  87. package/lib/mcp/server.d.ts +11 -1
  88. package/lib/mcp/server.d.ts.map +1 -1
  89. package/lib/mcp/server.js +80 -13
  90. package/lib/mcp/stdio.d.ts.map +1 -1
  91. package/lib/mcp/stdio.js +9 -1
  92. package/lib/mcp/subscriptions.d.ts +43 -1
  93. package/lib/mcp/subscriptions.d.ts.map +1 -1
  94. package/lib/mcp/subscriptions.js +90 -9
  95. package/lib/model-qualification.d.ts +192 -0
  96. package/lib/model-qualification.d.ts.map +1 -0
  97. package/lib/model-qualification.js +178 -0
  98. package/lib/planning.d.ts +246 -0
  99. package/lib/planning.d.ts.map +1 -0
  100. package/lib/planning.js +457 -0
  101. package/lib/prepared-assets.d.ts +178 -0
  102. package/lib/prepared-assets.d.ts.map +1 -0
  103. package/lib/prepared-assets.js +286 -0
  104. package/lib/provider-bridge.d.ts +37 -0
  105. package/lib/provider-bridge.d.ts.map +1 -1
  106. package/lib/provider-bridge.js +189 -12
  107. package/lib/ptc-authority.d.ts +140 -0
  108. package/lib/ptc-authority.d.ts.map +1 -0
  109. package/lib/ptc-authority.js +216 -0
  110. package/lib/ptc-runtime.d.ts +156 -0
  111. package/lib/ptc-runtime.d.ts.map +1 -0
  112. package/lib/ptc-runtime.js +191 -0
  113. package/lib/publication.d.ts +2 -1
  114. package/lib/publication.d.ts.map +1 -1
  115. package/lib/publication.js +13 -5
  116. package/lib/receipt.d.ts.map +1 -1
  117. package/lib/receipt.js +8 -0
  118. package/lib/title-proposal.d.ts +194 -0
  119. package/lib/title-proposal.d.ts.map +1 -0
  120. package/lib/title-proposal.js +244 -0
  121. package/lib/tool-mode-run.d.ts +235 -0
  122. package/lib/tool-mode-run.d.ts.map +1 -0
  123. package/lib/tool-mode-run.js +283 -0
  124. package/lib/types.d.ts +3 -1
  125. package/lib/types.d.ts.map +1 -1
  126. package/lib/update-surface.d.ts +79 -0
  127. package/lib/update-surface.d.ts.map +1 -0
  128. package/lib/update-surface.js +224 -0
  129. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
  130. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
  131. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
  132. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
  133. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
  134. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
  135. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
  136. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
  137. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
  138. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
  139. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
  140. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
  141. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
  142. package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
  143. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
  144. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
  145. package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
  146. package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
  147. package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
  148. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
  149. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
  150. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
  151. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
  152. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
  153. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
  154. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
  155. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
  156. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
  157. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
  158. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
  159. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
  160. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +31 -0
  161. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
  162. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +31 -0
  163. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
  164. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
  165. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +185 -0
  166. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
  167. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
  168. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
  169. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
  170. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
  171. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
  172. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
  173. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
  174. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
  175. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
  176. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
  177. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
  178. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
  179. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
  180. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
  181. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
  182. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
  183. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
  184. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
  185. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
  186. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
  187. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
  188. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
  189. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
  190. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
  191. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
  192. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
  193. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
  194. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
  195. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
  196. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
  197. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
  198. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
  199. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
  200. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
  201. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
  202. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
  203. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
  204. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
  205. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
  206. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
  207. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
  208. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
  209. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
  210. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
  211. package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
  212. package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
  213. package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
  214. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
  215. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
  216. package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
  217. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
  218. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
  219. package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
  220. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
  221. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
  222. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
  223. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
  224. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
  225. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
  226. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
  227. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
  228. package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
  229. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
  230. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
  231. package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
  232. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
  233. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
  234. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
  235. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
  236. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
  237. package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
  238. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
  239. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
  240. package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
  241. package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
  242. package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
  243. package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
  244. package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
  245. package/package.json +43 -15
  246. package/remote/spec.mjs +439 -0
  247. package/remote/typert.host.mjs +14 -0
  248. package/remote/typert.remote-client.mjs +16 -0
  249. package/lib/adapters/tongue.d.ts +0 -31
  250. package/lib/adapters/tongue.d.ts.map +0 -1
  251. package/lib/adapters/tongue.js +0 -93
  252. package/lib/adapters/voz.d.ts +0 -28
  253. package/lib/adapters/voz.d.ts.map +0 -1
  254. package/lib/adapters/voz.js +0 -102
  255. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
  256. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
  257. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
@@ -0,0 +1,670 @@
1
+ /**
2
+ * The managed extension worker (EXT-20, the SDK half).
3
+ *
4
+ * An extension's executable entry runs OUTSIDE the host, in a managed Node
5
+ * child process with a scrubbed startup environment and a bounded
6
+ * line-delimited JSON protocol. This module is the host-side of that bridge;
7
+ * {@link ./worker/worker-child.ts} is the child.
8
+ *
9
+ * What "managed" means here, concretely:
10
+ *
11
+ * - **Scrubbed environment.** The child env is built from an explicit
12
+ * ALLOWLIST (the platform basics a Node child needs: PATH, SystemRoot,
13
+ * TEMP/TMP, PATHEXT, COMSPEC, WINDIR) plus names the operator explicitly
14
+ * passed through — NEVER `process.env` wholesale, so a credential or a
15
+ * planted marker in the host environment does not leak into extension code.
16
+ * - **Bounded protocol.** One JSON message per line; lines over
17
+ * `maxLineBytes` and inputs over `maxInputBytes` are refused, and a child
18
+ * that violates the framing is killed and faulted rather than trusted.
19
+ * - **Timeouts on every phase.** Spawn must answer `ready` within
20
+ * `readyTimeoutMs`; a cancelled invoke must settle within `killTimeoutMs` or
21
+ * the child is killed; a dispose that outlives the grace is killed.
22
+ * - **One process per extension identity**, reused across invocations, with a
23
+ * restart-on-exit policy: a child that exits (crash or protocol violation)
24
+ * faults its in-flight calls and the NEXT invoke spawns a fresh one.
25
+ *
26
+ * **The boundary, documented:** a worker failure fails the CALL with the SDK's
27
+ * management-error vocabulary (EXTENSION_FAULT / INVALID_EXTENSION), never the
28
+ * host process — the extension runs in another process by design, and this
29
+ * module never lets a child's exit, hang or protocol violation propagate as an
30
+ * uncaught failure in the caller.
31
+ *
32
+ * Distinct process lifetime per extension generation is the caller's
33
+ * discipline (EXT-20): the host half disposes the old worker and creates a new
34
+ * one when the activation generation advances. `generation` is recorded here
35
+ * so diagnostics can name which generation a worker served.
36
+ *
37
+ * @module @try-works/tool-mode-extension-sdk/worker
38
+ */
39
+ import { spawn } from 'node:child_process';
40
+ import { fileURLToPath } from 'node:url';
41
+ import { ManagementError } from "./errors.js";
42
+ /** The environment names a Node child needs to start, and nothing else. */
43
+ const ENV_ALLOWLIST = ['PATH', 'SystemRoot', 'WINDIR', 'TEMP', 'TMP', 'PATHEXT', 'COMSPEC'];
44
+ /**
45
+ * Build the scrubbed child environment (EXT-20).
46
+ *
47
+ * The allowlist is the platform's Node bootstrap minimum; the operator's
48
+ * explicit `passthrough` names are the only additions. Everything else the
49
+ * parent holds — credentials, markers, DSH internals — is absent in the child.
50
+ * @param env - the parent environment to scrub.
51
+ * @param passthrough - explicit names to carry over.
52
+ * @returns the child environment.
53
+ */
54
+ export function scrubbedEnv(env = process.env, passthrough = []) {
55
+ const scrubbed = {};
56
+ for (const name of [...ENV_ALLOWLIST, ...passthrough]) {
57
+ const value = env[name];
58
+ if (typeof value === 'string')
59
+ scrubbed[name] = value;
60
+ }
61
+ // The marker a well-behaved child may branch on, and a probe for scrub tests.
62
+ scrubbed['TOOL_MODE_MANAGED_WORKER'] = '1';
63
+ return scrubbed;
64
+ }
65
+ /** The child module this package ships, resolved relative to this module. */
66
+ const CHILD_URL = new URL('./worker/worker-child.js', import.meta.url);
67
+ /** Reserved wire tag; user objects containing it are refused, never reinterpreted. */
68
+ const BINARY_TAG = '$toolModeBinaryV1';
69
+ /** A protocol object is never null, an array, or a scalar. */
70
+ export function isWorkerObject(value) {
71
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
72
+ }
73
+ /** Lossless bounded wire codec. Other non-JSON/typed values fail explicitly. */
74
+ export function workerWire(value, direction, maxBytes) {
75
+ let bytes = 0;
76
+ let nodes = 0;
77
+ const visit = (item, depth) => {
78
+ if (++nodes > 100_000 || depth > 64)
79
+ throw new Error('worker value exceeds structural limits');
80
+ if (item === null || typeof item === 'boolean' || typeof item === 'string')
81
+ return item;
82
+ if (typeof item === 'number' && Number.isFinite(item))
83
+ return item;
84
+ if (direction === 'encode' && (ArrayBuffer.isView(item) || item instanceof ArrayBuffer)) {
85
+ if (!(item instanceof Uint8Array) && !(item instanceof Float32Array)) {
86
+ throw new Error(`unsupported worker typed value: ${item.constructor.name}`);
87
+ }
88
+ bytes += item.byteLength;
89
+ if (bytes > maxBytes)
90
+ throw new Error('worker binary payload exceeds byte limit');
91
+ const type = item instanceof Float32Array ? 'Float32Array' : 'Uint8Array';
92
+ // Float32 values use a defined little-endian wire representation.
93
+ const data = Buffer.alloc(item.byteLength);
94
+ if (item instanceof Float32Array)
95
+ item.forEach((v, i) => data.writeFloatLE(v, i * 4));
96
+ else
97
+ data.set(item);
98
+ return { [BINARY_TAG]: type, data: data.toString('base64') };
99
+ }
100
+ if (Array.isArray(item))
101
+ return item.map(v => visit(v, depth + 1));
102
+ if (!isWorkerObject(item))
103
+ throw new Error(`unsupported worker value: ${typeof item}`);
104
+ if (Object.hasOwn(item, BINARY_TAG)) {
105
+ if (direction === 'encode')
106
+ throw new Error(`reserved worker wire key: ${BINARY_TAG}`);
107
+ const type = item[BINARY_TAG];
108
+ const data = item['data'];
109
+ if (Object.keys(item).length !== 2 || (type !== 'Uint8Array' && type !== 'Float32Array')
110
+ || typeof data !== 'string' || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data)) {
111
+ throw new Error('malformed worker binary value');
112
+ }
113
+ if (data.length > Math.ceil(maxBytes / 3) * 4)
114
+ throw new Error('worker binary payload exceeds byte limit');
115
+ const decoded = Buffer.from(data, 'base64');
116
+ bytes += decoded.length;
117
+ if (bytes > maxBytes || decoded.toString('base64') !== data)
118
+ throw new Error('worker binary payload exceeds limit or is noncanonical');
119
+ if (type === 'Uint8Array')
120
+ return new Uint8Array(decoded);
121
+ if (decoded.length % 4 !== 0)
122
+ throw new Error('malformed Float32Array worker value');
123
+ const result = new Float32Array(decoded.length / 4);
124
+ for (let i = 0; i < result.length; i++)
125
+ result[i] = decoded.readFloatLE(i * 4);
126
+ return result;
127
+ }
128
+ if (direction === 'encode' && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {
129
+ throw new Error(`unsupported worker value: ${item.constructor?.name ?? 'object'}`);
130
+ }
131
+ return Object.fromEntries(Object.entries(item).map(([key, v]) => [key, visit(v, depth + 1)]));
132
+ };
133
+ return visit(value, 0);
134
+ }
135
+ /** Shared envelope guard; every protocol frame is bounded before this runs. */
136
+ export function workerFrame(value, fields) {
137
+ if (!isWorkerObject(value) || typeof value['kind'] !== 'string'
138
+ || Object.keys(value).some(key => !fields.includes(key)))
139
+ throw new Error('malformed protocol frame');
140
+ }
141
+ export function workerId(value) {
142
+ return typeof value === 'string' && value.length > 0 && value.length <= 1024;
143
+ }
144
+ /**
145
+ * One managed extension worker process.
146
+ *
147
+ * Spawn is LAZY: the first invocation starts the child, and later invocations
148
+ * reuse it. A child that exits is replaced on the next invocation; the
149
+ * in-flight calls at exit time fault with EXTENSION_FAULT.
150
+ */
151
+ export class ManagedExtensionWorker {
152
+ extensionId;
153
+ generation;
154
+ #entryPath;
155
+ #nodePath;
156
+ #limits;
157
+ #diagnostics;
158
+ #scrubbed;
159
+ #child;
160
+ #ready = false;
161
+ #buffer = '';
162
+ #stderrTail = '';
163
+ #sequence = 0;
164
+ #pending = new Map();
165
+ #disposed = false;
166
+ #starting;
167
+ #disposing;
168
+ #diagnosticBytes = 0;
169
+ #brokerPending = new Set();
170
+ constructor(options) {
171
+ this.extensionId = options.extensionId;
172
+ this.generation = options.generation;
173
+ this.#entryPath = options.entryPath;
174
+ this.#nodePath = options.nodePath ?? process.execPath;
175
+ this.#diagnostics = options.diagnostics ?? (line => { process.stderr.write(`${line}\n`); });
176
+ this.#limits = {
177
+ maxLineBytes: options.limits?.maxLineBytes ?? 1024 * 1024,
178
+ maxInputBytes: options.limits?.maxInputBytes ?? 4 * 1024 * 1024,
179
+ readyTimeoutMs: options.limits?.readyTimeoutMs ?? 30_000,
180
+ killTimeoutMs: options.limits?.killTimeoutMs ?? 5_000,
181
+ resolvePathTimeoutMs: options.limits?.resolvePathTimeoutMs ?? 5_000,
182
+ };
183
+ for (const value of Object.values(this.#limits)) {
184
+ if (!Number.isSafeInteger(value) || value <= 0)
185
+ throw new Error('worker limits must be positive safe integers');
186
+ }
187
+ this.#scrubbed = scrubbedEnv(process.env, options.envPassthrough);
188
+ }
189
+ /** Build a worker; nothing is spawned until the first invocation. */
190
+ static create(options) {
191
+ return new ManagedExtensionWorker(options);
192
+ }
193
+ /** Whether the worker process is currently alive. */
194
+ get alive() {
195
+ return this.#child !== undefined && this.#child.exitCode === null && this.#child.signalCode === null;
196
+ }
197
+ /** The worker's pid, when it has one. */
198
+ get pid() {
199
+ return this.alive ? this.#child?.pid : undefined;
200
+ }
201
+ /** How many invocations are in flight. */
202
+ get inFlight() {
203
+ return this.#pending.size;
204
+ }
205
+ /**
206
+ * Run one invocation in the managed process.
207
+ *
208
+ * The caller's abort cancels the invocation BY MESSAGE; a child that does
209
+ * not settle within `killTimeoutMs` after cancellation is killed and the
210
+ * call fails with EXTENSION_FAULT — a hung extension can never hang the
211
+ * caller. There is no normal inference deadline here; a caller-owned request
212
+ * timeout can abort its signal. Killing this shared process also faults all
213
+ * noncancelled in-flight calls, without labelling them caller cancellations.
214
+ * Every failure here is a ManagementError with the SDK's own vocabulary;
215
+ * nothing propagates from the child as a raw throw.
216
+ * @param request - which deployment to call, with what input.
217
+ * @returns the extension's payload.
218
+ * @throws ManagementError on worker fault, timeout, protocol violation or a
219
+ * refused input.
220
+ */
221
+ async invoke(request) {
222
+ const result = await this.#request('invoke', {
223
+ implementationId: request.implementationId, input: request.input,
224
+ ...(request.configuration === undefined ? {} : { configuration: request.configuration }),
225
+ }, request);
226
+ return result['value'];
227
+ }
228
+ /** Inspect the exact manifest model, entirely in the managed process. */
229
+ async inspect(modelId, options = {}) {
230
+ const result = await this.#request('inspect', { modelId, ...(options.configuration === undefined ? {} : { configuration: options.configuration }) }, options);
231
+ return result['inspection'];
232
+ }
233
+ /** Load ONLY handles explicitly prepared and verified by the host. No auto-load. */
234
+ async load(modelId, options) {
235
+ const assets = options.assets.assetIds.map(id => {
236
+ const handle = options.assets.get(id);
237
+ if (!workerId(id) || handle === undefined || typeof handle.path !== 'string' || typeof handle.digest !== 'string')
238
+ throw new Error('invalid host-prepared asset handle');
239
+ return { id, path: handle.path, digest: handle.digest };
240
+ });
241
+ if (new Set(assets.map(asset => asset.id)).size !== assets.length)
242
+ throw new Error('duplicate host-prepared asset id');
243
+ await this.#request('load', { modelId, assets, ...(options.configuration === undefined ? {} : { configuration: options.configuration }) }, options);
244
+ }
245
+ /** Release a selected model provider's loaded resources without disposing it. */
246
+ async unload(modelId, options = {}) {
247
+ await this.#request('unload', { modelId, ...(options.configuration === undefined ? {} : { configuration: options.configuration }) }, options);
248
+ }
249
+ async #request(kind, payload, request) {
250
+ if (this.#disposed) {
251
+ throw new ManagementError('EXTENSION_FAULT', 'invoke', `the worker for ${this.extensionId} was disposed`, 'Activate the extension generation again; a disposed worker does not serve calls.');
252
+ }
253
+ const id = `${kind === 'invoke' ? 'inv' : kind}-${this.extensionId}-${++this.#sequence}`;
254
+ let input;
255
+ let serialized;
256
+ try {
257
+ if (kind === 'invoke' && !isWorkerObject(payload['input']))
258
+ throw new Error('worker input must be an object');
259
+ if (payload['configuration'] !== undefined && !isWorkerObject(payload['configuration']))
260
+ throw new Error('configuration must be an object');
261
+ if (kind === 'invoke' ? !workerId(payload['implementationId']) : !workerId(payload['modelId']))
262
+ throw new Error('invalid model or implementation id');
263
+ input = workerWire(payload, 'encode', this.#limits.maxInputBytes);
264
+ serialized = JSON.stringify(input);
265
+ }
266
+ catch (error) {
267
+ throw new ManagementError('EXTENSION_FAULT', 'invoke', `worker input serialization failed: ${String(error)}`, 'Use JSON values or supported bounded Uint8Array/Float32Array media values.');
268
+ }
269
+ if (Buffer.byteLength(serialized, 'utf8') > this.#limits.maxInputBytes) {
270
+ throw new ManagementError('EXTENSION_FAULT', 'invoke', `the input for ${String(payload['implementationId'] ?? payload['modelId'])} is ${Buffer.byteLength(serialized, 'utf8')} bytes, `
271
+ + `over the ${this.#limits.maxInputBytes} worker limit`, 'Reduce the input, or raise the worker input limit deliberately.');
272
+ }
273
+ await this.#ensureReady();
274
+ if (this.#disposed)
275
+ throw new ManagementError('EXTENSION_FAULT', 'invoke', 'the worker was disposed during startup', 'Create a new worker.');
276
+ return new Promise((resolve, reject) => {
277
+ // This is a cancellation-settle deadline, NOT an inference timeout.
278
+ // Even an unref'd timer fires while the child pipes keep the loop alive,
279
+ // so do not create it at all until the caller actually aborts.
280
+ let killTimer;
281
+ const cleanup = () => {
282
+ clearTimeout(killTimer);
283
+ request.signal?.removeEventListener('abort', abortListener);
284
+ this.#pending.delete(id);
285
+ };
286
+ const settled = (value) => {
287
+ cleanup();
288
+ resolve(value);
289
+ };
290
+ const failed = (error) => {
291
+ cleanup();
292
+ reject(error);
293
+ };
294
+ const abortListener = () => {
295
+ // Cancellation BY MESSAGE: only a process kill can bound a child that
296
+ // ignores abort (including a runaway synchronous handler).
297
+ if (killTimer !== undefined || !this.#pending.has(id))
298
+ return;
299
+ killTimer = setTimeout(() => {
300
+ failed(new ManagementError('EXTENSION_FAULT', 'invoke', `the worker for ${this.extensionId} did not settle the cancelled invocation within ${this.#limits.killTimeoutMs} ms; the process was killed`, 'The extension hung; it will be restarted on the next invocation.'));
301
+ this.#kill('a cancelled invocation did not settle');
302
+ // All calls share this process. Peers fail as worker faults, NOT as
303
+ // caller cancellations; clear their listeners/deadlines immediately.
304
+ this.#faultAll('the process was killed because another invocation did not settle after abort');
305
+ }, this.#limits.killTimeoutMs);
306
+ try {
307
+ this.#write({ kind: 'cancel', invokeId: id });
308
+ }
309
+ catch (error) {
310
+ failed(error);
311
+ }
312
+ };
313
+ this.#pending.set(id, { id, kind, resolve: settled, reject: failed, ...(request.resolvePath === undefined ? {} : { resolvePath: request.resolvePath }) });
314
+ try {
315
+ // An already-aborted signal must still send invoke BEFORE cancel, so
316
+ // the child has an invocation controller to receive the cancellation.
317
+ this.#write({ id, kind, ...input });
318
+ if (request.signal?.aborted === true)
319
+ abortListener();
320
+ else
321
+ request.signal?.addEventListener('abort', abortListener, { once: true });
322
+ }
323
+ catch (error) {
324
+ failed(error);
325
+ }
326
+ });
327
+ }
328
+ /** Spawn (or re-spawn) the child and wait for its `ready` line. */
329
+ async #ensureReady() {
330
+ if (this.#starting !== undefined)
331
+ return this.#starting;
332
+ this.#starting = this.#start();
333
+ try {
334
+ await this.#starting;
335
+ }
336
+ finally {
337
+ this.#starting = undefined;
338
+ }
339
+ }
340
+ async #start() {
341
+ if (this.alive && this.#ready)
342
+ return;
343
+ this.#kill('a fresh worker is taking over');
344
+ this.#ready = false;
345
+ this.#buffer = '';
346
+ const child = spawn(this.#nodePath, [
347
+ fileURLToPath(CHILD_URL),
348
+ '--entry', this.#entryPath,
349
+ '--extension-id', this.extensionId,
350
+ '--generation', String(this.generation),
351
+ '--max-line-bytes', String(this.#limits.maxLineBytes),
352
+ '--max-input-bytes', String(this.#limits.maxInputBytes),
353
+ '--resolve-path-timeout-ms', String(this.#limits.resolvePathTimeoutMs),
354
+ ], {
355
+ stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
356
+ env: this.#scrubbed,
357
+ windowsHide: true,
358
+ });
359
+ this.#child = child;
360
+ child.stdio[3]?.on('error', () => {
361
+ if (this.#child !== child)
362
+ return;
363
+ this.#kill('the worker path broker pipe failed');
364
+ this.#faultAll('the worker path broker pipe failed');
365
+ });
366
+ child.stdout?.setEncoding('utf8');
367
+ child.stdout?.on('data', (chunk) => { if (this.#child === child)
368
+ this.#ingest(chunk); });
369
+ child.stdin?.on('error', () => {
370
+ if (this.#child !== child)
371
+ return;
372
+ this.#kill('the worker input pipe failed');
373
+ this.#faultAll('the worker input pipe failed');
374
+ });
375
+ child.stderr?.setEncoding('utf8');
376
+ child.stderr?.on('data', (chunk) => {
377
+ for (const line of chunk.split(/\r?\n/)) {
378
+ if (line.length === 0)
379
+ continue;
380
+ this.#stderrTail = `${this.#stderrTail}${line}\n`.slice(-64 * 1024);
381
+ this.#diagnostics(`[worker ${this.extensionId}] ${line}`);
382
+ }
383
+ });
384
+ child.on('exit', (code, signal) => {
385
+ if (this.#child !== child)
386
+ return;
387
+ this.#ready = false;
388
+ this.#faultAll(`the worker process exited (code ${String(code)}, signal ${String(signal)})`);
389
+ });
390
+ child.on('error', error => {
391
+ if (this.#child !== child)
392
+ return;
393
+ this.#ready = false;
394
+ this.#faultAll(`the worker process could not be spawned: ${error.message}`);
395
+ });
396
+ await new Promise((resolve, reject) => {
397
+ const cleanup = () => {
398
+ clearTimeout(timer);
399
+ child.stdout?.removeListener('data', check);
400
+ child.removeListener('exit', exited);
401
+ child.removeListener('error', errored);
402
+ };
403
+ const timer = setTimeout(() => {
404
+ cleanup();
405
+ this.#kill('the worker did not answer ready');
406
+ reject(new ManagementError('EXTENSION_FAULT', 'invoke', `the worker for ${this.extensionId} did not report ready within ${this.#limits.readyTimeoutMs} ms`, 'Inspect the extension entry; a worker that cannot start is refused, not waited on.'));
407
+ }, this.#limits.readyTimeoutMs);
408
+ const check = () => {
409
+ if (this.#ready) {
410
+ cleanup();
411
+ resolve();
412
+ }
413
+ };
414
+ const exited = () => {
415
+ cleanup();
416
+ reject(new ManagementError('EXTENSION_FAULT', 'invoke', `the worker for ${this.extensionId} exited before reporting ready`, 'Inspect the extension entry and the worker diagnostics.'));
417
+ };
418
+ const errored = (error) => {
419
+ cleanup();
420
+ reject(new ManagementError('EXTENSION_FAULT', 'invoke', `the worker for ${this.extensionId} could not be spawned: ${error.message}`, 'Inspect the Node executable and the worker diagnostics.'));
421
+ };
422
+ child.stdout?.on('data', check);
423
+ child.once('exit', exited);
424
+ child.once('error', errored);
425
+ check();
426
+ });
427
+ }
428
+ /** Consume stdout bytes, honoring the line cap and dispatching messages. */
429
+ #ingest(chunk) {
430
+ this.#buffer += chunk;
431
+ let newline = this.#buffer.indexOf('\n');
432
+ while (newline !== -1) {
433
+ const line = this.#buffer.slice(0, newline);
434
+ this.#buffer = this.#buffer.slice(newline + 1);
435
+ this.#dispatch(line);
436
+ if (this.#child === undefined) {
437
+ this.#buffer = '';
438
+ return;
439
+ }
440
+ newline = this.#buffer.indexOf('\n');
441
+ }
442
+ // The un-terminated tail counts against the cap: a child streaming
443
+ // endless bytes without a newline is a framing violation.
444
+ if (Buffer.byteLength(this.#buffer, 'utf8') > this.#limits.maxLineBytes) {
445
+ this.#kill('a protocol line exceeded the length cap');
446
+ this.#faultAll(`a protocol line exceeded the ${this.#limits.maxLineBytes}-byte cap`);
447
+ }
448
+ }
449
+ /** Both per-line and per-worker lifetime diagnostic caps. */
450
+ #diagnose(message) {
451
+ const remaining = 64 * 1024 - this.#diagnosticBytes;
452
+ if (remaining <= 0)
453
+ return;
454
+ const line = Buffer.from(message).subarray(0, Math.min(4096, remaining)).toString('utf8');
455
+ this.#diagnosticBytes += Buffer.byteLength(line);
456
+ this.#stderrTail = `${this.#stderrTail}${line}\n`.slice(-64 * 1024);
457
+ try {
458
+ this.#diagnostics(`[worker ${this.extensionId}] ${line}`);
459
+ }
460
+ catch { /* diagnostics cannot fault the host */ }
461
+ }
462
+ /** Dedicated reply pipe preserves InvocationContextV1's synchronous API. */
463
+ #resolvePath(message) {
464
+ const child = this.#child;
465
+ const id = message['id'];
466
+ const invokeId = message['invokeId'];
467
+ const pending = this.#pending.get(invokeId);
468
+ if (this.#brokerPending.has(id) || this.#brokerPending.size >= 128 || pending?.kind !== 'invoke') {
469
+ this.#kill('invalid path broker request scope');
470
+ this.#faultAll('invalid path broker request scope');
471
+ return;
472
+ }
473
+ this.#brokerPending.add(id);
474
+ let finished = false;
475
+ const reply = (answer) => {
476
+ if (finished)
477
+ return;
478
+ finished = true;
479
+ clearTimeout(timer);
480
+ this.#brokerPending.delete(id);
481
+ if (child !== this.#child)
482
+ return;
483
+ const line = JSON.stringify({ kind: 'resolvePathResult', id, invokeId, ...answer });
484
+ if (Buffer.byteLength(line) > this.#limits.maxLineBytes) {
485
+ this.#kill('path broker result exceeded line cap');
486
+ this.#faultAll('path broker result exceeded line cap');
487
+ return;
488
+ }
489
+ ;
490
+ child?.stdio[3]?.write(`${line}\n`, error => {
491
+ if (error && child === this.#child) {
492
+ this.#kill('path broker pipe failed');
493
+ this.#faultAll('path broker pipe failed');
494
+ }
495
+ });
496
+ };
497
+ const timer = setTimeout(() => reply({ error: 'path access denied: host path broker deadline elapsed' }), this.#limits.resolvePathTimeoutMs);
498
+ void Promise.resolve().then(async () => {
499
+ if (pending.resolvePath === undefined)
500
+ throw new Error('path access denied: managed worker has no configured host path authority');
501
+ const path = await pending.resolvePath(message['candidate']);
502
+ if (typeof path !== 'string' || path.length === 0 || Buffer.byteLength(path) > 16 * 1024)
503
+ throw new Error('path access denied: invalid canonical host path');
504
+ if (!this.#pending.has(invokeId))
505
+ throw new Error('path access denied: request is no longer active');
506
+ reply({ path });
507
+ }).catch(error => reply({ error: String(error).slice(0, 4096) }));
508
+ }
509
+ /** Dispatch one child line. */
510
+ #dispatch(line) {
511
+ if (Buffer.byteLength(line, 'utf8') > this.#limits.maxLineBytes) {
512
+ this.#kill('a protocol line exceeded the length cap');
513
+ this.#faultAll(`a protocol line exceeded the ${this.#limits.maxLineBytes}-byte cap`);
514
+ return;
515
+ }
516
+ let message;
517
+ try {
518
+ const parsed = JSON.parse(line);
519
+ if (!isWorkerObject(parsed) || typeof parsed['kind'] !== 'string')
520
+ throw new Error('expected an object with a string kind');
521
+ message = parsed;
522
+ const kind = message['kind'];
523
+ const keys = kind === 'ready' ? ['kind'] : kind === 'result' ? ['kind', 'id', 'result']
524
+ : kind === 'error' ? ['kind', 'id', 'message'] : kind === 'fault' ? ['kind', 'message']
525
+ : kind === 'resolvePath' ? ['kind', 'id', 'invokeId', 'candidate'] : [];
526
+ if (keys.length === 0)
527
+ throw new Error('unknown protocol kind');
528
+ workerFrame(message, keys);
529
+ if ((kind === 'result' || kind === 'error' || kind === 'resolvePath') && !workerId(message['id']))
530
+ throw new Error('invalid response id');
531
+ if ((kind === 'error' || kind === 'fault') && typeof message['message'] !== 'string')
532
+ throw new Error('invalid error message');
533
+ if (kind === 'resolvePath' && (!workerId(message['invokeId']) || typeof message['candidate'] !== 'string'
534
+ || Buffer.byteLength(message['candidate']) > 16 * 1024))
535
+ throw new Error('malformed path broker request');
536
+ if (kind === 'result') {
537
+ const result = workerWire(message['result'], 'decode', this.#limits.maxLineBytes);
538
+ if (!isWorkerObject(result))
539
+ throw new Error('result must be a typed object');
540
+ const type = result['type'];
541
+ const pending = this.#pending.get(message['id']);
542
+ if (!['invoke', 'inspect', 'load', 'unload'].includes(type) || (pending !== undefined && type !== pending.kind))
543
+ throw new Error('mismatched result type');
544
+ if (Object.keys(result).some(key => !(type === 'invoke' ? ['type', 'value'] : type === 'inspect' ? ['type', 'inspection'] : ['type']).includes(key)))
545
+ throw new Error('unknown typed result fields');
546
+ if (type === 'invoke' && (!isWorkerObject(result['value']) || ArrayBuffer.isView(result['value'])))
547
+ throw new Error('invoke result must be an object');
548
+ if (type === 'inspect') {
549
+ const inspection = result['inspection'];
550
+ if (!isWorkerObject(inspection) || !['unavailable', 'needs-preparation', 'preparing', 'prepared-cold', 'loading', 'ready', 'busy', 'failed'].includes(inspection['readiness'])
551
+ || !Array.isArray(inspection['features']) || !inspection['features'].every(item => typeof item === 'string')
552
+ || !Array.isArray(inspection['missing']) || !inspection['missing'].every(item => isWorkerObject(item) && typeof item['name'] === 'string' && typeof item['reason'] === 'string')
553
+ || (inspection['reason'] !== undefined && typeof inspection['reason'] !== 'string')
554
+ || (inspection['runtimeVersion'] !== undefined && typeof inspection['runtimeVersion'] !== 'string'))
555
+ throw new Error('malformed inspection result');
556
+ }
557
+ message['result'] = result;
558
+ }
559
+ }
560
+ catch (error) {
561
+ this.#kill('the worker wrote a malformed protocol line');
562
+ this.#faultAll(`the worker wrote a malformed protocol line: ${String(error)}`);
563
+ return;
564
+ }
565
+ switch (message.kind) {
566
+ case 'ready': {
567
+ this.#ready = true;
568
+ break;
569
+ }
570
+ case 'resolvePath': {
571
+ this.#resolvePath(message);
572
+ break;
573
+ }
574
+ case 'result': {
575
+ const pending = this.#pending.get(message.id);
576
+ if (pending === undefined)
577
+ break;
578
+ pending.resolve(message['result']);
579
+ break;
580
+ }
581
+ case 'error': {
582
+ const pending = this.#pending.get(message.id);
583
+ if (pending === undefined)
584
+ break;
585
+ pending.reject(new ManagementError('EXTENSION_FAULT', 'invoke', `the extension worker reported a failure: ${message.message ?? 'no message'}`, 'The extension failed in its managed process; the host was not affected.'));
586
+ break;
587
+ }
588
+ case 'fault': {
589
+ this.#kill('the worker reported a fault');
590
+ this.#faultAll(message.message);
591
+ break;
592
+ }
593
+ default: {
594
+ this.#kill('the worker sent an unknown message kind');
595
+ this.#faultAll(`the worker sent an unknown message kind ${JSON.stringify(message.kind)}`);
596
+ }
597
+ }
598
+ }
599
+ /** Reject every in-flight call because the child can no longer serve them. */
600
+ #faultAll(reason) {
601
+ const fault = new ManagementError('EXTENSION_FAULT', 'invoke', `the managed worker for ${this.extensionId} failed: ${reason}`, 'The extension runs in a managed process; restart it by invoking again, or inspect the worker diagnostics.');
602
+ for (const pending of this.#pending.values())
603
+ pending.reject(fault);
604
+ this.#pending.clear();
605
+ }
606
+ /** Write one protocol line to the child. */
607
+ #write(message) {
608
+ const line = JSON.stringify(message);
609
+ if (Buffer.byteLength(line, 'utf8') > this.#limits.maxLineBytes) {
610
+ throw new ManagementError('EXTENSION_FAULT', 'invoke', 'the worker request exceeded the protocol line cap', 'Reduce the request, or raise the worker line limit deliberately.');
611
+ }
612
+ this.#child?.stdin?.write(`${line}\n`);
613
+ }
614
+ /** Terminate the child, hard. Idempotent. */
615
+ #kill(reason) {
616
+ const child = this.#child;
617
+ this.#child = undefined;
618
+ this.#ready = false;
619
+ if (child === undefined)
620
+ return;
621
+ this.#diagnose(`killing: ${reason}`);
622
+ try {
623
+ child.stdin?.end();
624
+ }
625
+ catch { /* the pipe may already be closed */ }
626
+ try {
627
+ if (child.exitCode === null && child.signalCode === null)
628
+ child.kill('SIGKILL');
629
+ }
630
+ catch { /* an already-dead child needs no kill */ }
631
+ }
632
+ /**
633
+ * Dispose the worker: ask the child to stop, then kill it after the grace.
634
+ */
635
+ async dispose() {
636
+ if (this.#disposing !== undefined)
637
+ return this.#disposing;
638
+ this.#disposed = true;
639
+ this.#faultAll('the worker was disposed');
640
+ this.#disposing = (async () => {
641
+ const child = this.#child;
642
+ if (child === undefined || !this.alive)
643
+ return;
644
+ await new Promise(resolve => {
645
+ const finish = () => {
646
+ clearTimeout(timer);
647
+ child.removeListener('exit', finish);
648
+ resolve();
649
+ };
650
+ const timer = setTimeout(() => {
651
+ this.#kill('dispose grace elapsed');
652
+ // Join the killed child too; SIGKILL completion is observed, not guessed.
653
+ if (child.exitCode !== null || child.signalCode !== null)
654
+ finish();
655
+ }, this.#limits.killTimeoutMs);
656
+ child.once('exit', finish);
657
+ try {
658
+ this.#write({ kind: 'dispose', reason: 'shutdown' });
659
+ }
660
+ catch {
661
+ this.#kill('dispose write failed');
662
+ }
663
+ if (child.exitCode !== null || child.signalCode !== null)
664
+ finish();
665
+ });
666
+ })();
667
+ return this.#disposing;
668
+ }
669
+ }
670
+ //# sourceMappingURL=worker.js.map