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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) 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 +614 -0
  5. package/client/index.js +1717 -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 +325 -0
  19. package/lib/extension-dependencies.d.ts.map +1 -0
  20. package/lib/extension-dependencies.js +858 -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 +221 -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 +367 -0
  34. package/lib/extensions-service.d.ts.map +1 -0
  35. package/lib/extensions-service.js +1527 -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 +271 -7
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2644 -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 +32 -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 +32 -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 +191 -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/tar-gzip-source.d.ts +44 -0
  188. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts.map +1 -0
  189. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.js +243 -0
  190. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
  191. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
  192. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
  193. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
  194. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
  195. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
  196. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
  197. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
  198. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
  199. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
  200. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
  201. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
  202. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
  203. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
  204. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
  205. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
  206. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
  207. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
  208. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
  209. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
  210. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
  211. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
  212. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
  213. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
  214. package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
  215. package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
  216. package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
  217. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
  218. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
  219. package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
  220. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
  221. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
  222. package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
  223. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
  224. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
  225. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
  226. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
  227. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
  228. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
  229. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
  230. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
  231. package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
  232. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
  233. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
  234. package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
  235. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
  236. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
  237. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
  238. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
  239. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
  240. package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
  241. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
  242. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
  243. package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
  244. package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
  245. package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
  246. package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
  247. package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
  248. package/package.json +43 -15
  249. package/remote/spec.mjs +462 -0
  250. package/remote/typert.host.mjs +14 -0
  251. package/remote/typert.remote-client.mjs +16 -0
  252. package/lib/adapters/tongue.d.ts +0 -31
  253. package/lib/adapters/tongue.d.ts.map +0 -1
  254. package/lib/adapters/tongue.js +0 -93
  255. package/lib/adapters/voz.d.ts +0 -28
  256. package/lib/adapters/voz.d.ts.map +0 -1
  257. package/lib/adapters/voz.js +0 -102
  258. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
  259. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
  260. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * The host's prepared-assets path (RES-02/03/05, the last in-repo slice):
3
+ * lock reading → preparation plan → approval-gated, digest-verified acquisition
4
+ * into an isolated content-addressed cache → the prepared-cold readiness flip →
5
+ * the PreparedAssetMapV1 the provider routes consume.
6
+ *
7
+ * The verified components this module finally gives callers to:
8
+ *
9
+ * - `planPreparation`/`DeploymentIdentity` (deployment-coordinator.ts) — the
10
+ * pre-approval disclosure;
11
+ * - `acquireAsset` (asset-acquisition.ts) — digest+size verified, atomic,
12
+ * resumable acquisition with tamper refusal;
13
+ * - `createHttpSource` (http-acquisition.ts) — the default ranged source.
14
+ *
15
+ * Honesty rules carried through:
16
+ *
17
+ * - a lock with no digests REFUSES (no guessed values);
18
+ * - an executable asset WITHOUT the explicit executable-preparation approval
19
+ * refuses (RES-02);
20
+ * - a tampered final path is refused and never flips readiness;
21
+ * - the cache lives under the host's isolated state dir, never the repo or
22
+ * HOME; a REAL network run is opt-in (the default source), and the suite
23
+ * injects a local file source so it performs no network.
24
+ *
25
+ * @module @try-works/dsh-model-as-tool/prepared-assets
26
+ */
27
+ import { createHash } from 'node:crypto';
28
+ import { existsSync, linkSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
29
+ import { join } from 'node:path';
30
+ import { acquireAsset } from "./asset-acquisition.js";
31
+ /**
32
+ * Parse a models.lock.json document.
33
+ *
34
+ * A lock with no digest on any asset REFUSES: a preparation built on guessed
35
+ * bytes would verify nothing (RES-03).
36
+ * @param bytes - the lock document.
37
+ * @returns the parsed lock.
38
+ */
39
+ export function parseAssetLock(bytes) {
40
+ let document;
41
+ try {
42
+ document = JSON.parse(bytes);
43
+ }
44
+ catch (error) {
45
+ throw new Error(`the asset lock is unreadable: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ if (typeof document !== 'object' || document === null)
48
+ throw new Error('the asset lock is not a document');
49
+ const root = document;
50
+ const assetSets = root['assetSets'];
51
+ if (typeof assetSets !== 'object' || assetSets === null)
52
+ throw new Error('the asset lock declares no assetSets');
53
+ const sets = {};
54
+ for (const [setId, rawSet] of Object.entries(assetSets)) {
55
+ const set = rawSet;
56
+ const revision = typeof set['pinning'] === 'object' && set['pinning'] !== null
57
+ ? set['pinning']['revision']
58
+ : undefined;
59
+ const license = typeof set['license'] === 'object' && set['license'] !== null
60
+ ? set['license']
61
+ : {};
62
+ const rawGroups = set['groups'];
63
+ if (!Array.isArray(rawGroups))
64
+ throw new Error(`asset set ${setId} declares no groups`);
65
+ const groups = rawGroups.map((rawGroup, groupIndex) => {
66
+ const group = rawGroup;
67
+ const rawAssets = group['assets'];
68
+ if (!Array.isArray(rawAssets))
69
+ throw new Error(`asset set ${setId} group ${groupIndex} declares no assets`);
70
+ const assets = rawAssets.map((rawAsset, assetIndex) => {
71
+ const asset = rawAsset;
72
+ const digest = typeof asset['digest'] === 'string' ? asset['digest'] : '';
73
+ const bytes = typeof asset['bytes'] === 'number' ? asset['bytes'] : NaN;
74
+ const id = typeof asset['id'] === 'string' ? asset['id'] : '';
75
+ const file = typeof asset['file'] === 'string' ? asset['file'] : '';
76
+ const source = typeof asset['source'] === 'string' ? asset['source'] : '';
77
+ if (digest.length === 0 || !Number.isFinite(bytes) || id.length === 0 || file.length === 0) {
78
+ throw new Error(`asset set ${setId} group ${groupIndex} asset ${assetIndex} is incomplete; a lock without pinned digests and sizes refuses (RES-03)`);
79
+ }
80
+ return { id, file, bytes, source, digest, executable: asset['executable'] === true };
81
+ });
82
+ return { id: typeof group['id'] === 'string' ? group['id'] : `group-${groupIndex}`, assets };
83
+ });
84
+ sets[setId] = {
85
+ groups,
86
+ revision: typeof revision === 'string' ? revision : '',
87
+ license: {
88
+ name: typeof license['name'] === 'string' ? license['name'] : 'undeclared',
89
+ url: typeof license['url'] === 'string' ? license['url'] : '',
90
+ },
91
+ };
92
+ }
93
+ const lockRevision = Object.values(sets)[0]?.revision ?? '';
94
+ if (lockRevision.length === 0)
95
+ throw new Error('the asset lock pins no model revision; a preparation cannot be planned');
96
+ return { revision: lockRevision, license: Object.values(sets)[0]?.license ?? { name: 'undeclared', url: '' }, sets };
97
+ }
98
+ /** The identity the lock implies for one model deployment. */
99
+ export function identityFor(extensionId, modelId, lock) {
100
+ return {
101
+ extensionId,
102
+ modelId,
103
+ modelRevision: lock.revision,
104
+ engineVersion: 'onnxruntime-node',
105
+ quantization: 'unquantized-fp32',
106
+ tokenizer: 'gemma',
107
+ preprocessingRecipe: 'pinned',
108
+ };
109
+ }
110
+ /** The flat PlannedAsset list for one asset set. */
111
+ export function plannedAssets(lock, assetSetId) {
112
+ const set = lock.sets[assetSetId];
113
+ if (set === undefined)
114
+ throw new Error(`the lock declares no asset set "${assetSetId}"`);
115
+ return set.groups.flatMap(group => group.assets.map(asset => ({
116
+ assetId: `${group.id}/${asset.id}`,
117
+ bytes: asset.bytes,
118
+ source: asset.source,
119
+ digest: asset.digest,
120
+ executable: asset.executable,
121
+ })));
122
+ }
123
+ /** The group map for one asset set, keyed by group id. */
124
+ export function lockGroups(lock, assetSetId) {
125
+ const set = lock.sets[assetSetId];
126
+ if (set === undefined)
127
+ throw new Error(`the lock declares no asset set "${assetSetId}"`);
128
+ return set.groups;
129
+ }
130
+ /** The filesystem store acquisition needs, over node:fs. */
131
+ function fsStore() {
132
+ return {
133
+ write(path, bytes) {
134
+ mkdirSync(join(path, '..'), { recursive: true });
135
+ writeFileSync(path, bytes);
136
+ },
137
+ read(path) {
138
+ if (!existsSync(path))
139
+ return undefined;
140
+ return new Uint8Array(readFileSync(path));
141
+ },
142
+ sizeOf(path) {
143
+ if (!existsSync(path))
144
+ return 0;
145
+ return statSyncSize(path);
146
+ },
147
+ rename(from, to) {
148
+ renameSync(from, to);
149
+ },
150
+ remove(path) {
151
+ rmSync(path, { force: true });
152
+ },
153
+ exists(path) {
154
+ return existsSync(path);
155
+ },
156
+ };
157
+ }
158
+ /** Synchronous size (the store interface is sync). */
159
+ function statSyncSize(path) {
160
+ try {
161
+ return statSync(path).size;
162
+ }
163
+ catch {
164
+ return 0;
165
+ }
166
+ }
167
+ /**
168
+ * Create the prepared-assets registry.
169
+ * @param options - the cache root and the default source.
170
+ * @returns the registry.
171
+ */
172
+ export function createPreparedAssetRegistry(options) {
173
+ const cacheRoot = options.cacheRoot;
174
+ const held = new Map();
175
+ // The content-addressed blob pool, shared across extensions (RES-03: two
176
+ // owners share one set of bytes).
177
+ return {
178
+ plans() {
179
+ return new Map([...held.entries()].map(([extensionId, preparation]) => [extensionId, preparation.plan]));
180
+ },
181
+ assetMapFor(extensionId) {
182
+ const preparation = held.get(extensionId);
183
+ if (preparation === undefined)
184
+ return undefined;
185
+ /**
186
+ * TWO projections over one verified preparation, both truthful:
187
+ *
188
+ * - the GROUP id resolves to the group directory (embeddinggemma's selective
189
+ * loading asks for `text-backbone` and reads `onnx/model.onnx` under it);
190
+ * - each ASSET id resolves to the individual materialized file (needle asks
191
+ * for `needle-engine-win-x64` and needs the executable's path).
192
+ *
193
+ * The SDK documents the map as asset ids to local handles, so the per-asset
194
+ * entries are the canonical projection and the group entry is the directory
195
+ * convenience the first-party group consumers already rely on. An exact
196
+ * asset-id match wins over a group-id match; the ids are namespaced by the
197
+ * lock and a collision is not silently served as the other kind.
198
+ */
199
+ const groupHandles = new Map();
200
+ const assetHandles = new Map();
201
+ for (const group of preparation.groups) {
202
+ groupHandles.set(group.groupId, { path: group.path, digest: group.digests.join(',') });
203
+ for (const file of group.files) {
204
+ assetHandles.set(file.assetId, { path: file.path, digest: file.digest });
205
+ }
206
+ }
207
+ return {
208
+ assetIds: [...groupHandles.keys(), ...assetHandles.keys()],
209
+ get(assetId) {
210
+ return assetHandles.get(assetId) ?? groupHandles.get(assetId);
211
+ },
212
+ };
213
+ },
214
+ groupsFor(extensionId) {
215
+ return held.get(extensionId)?.groups ?? [];
216
+ },
217
+ async prepare(extensionId, plan, groups, options) {
218
+ if (!options.allowExecutable && plan.requiresExecutableApproval) {
219
+ return {
220
+ ok: false,
221
+ reason: 'the plan includes an EXECUTABLE asset, which requires the separate executable-preparation approval (RES-02); this call did not grant it',
222
+ };
223
+ }
224
+ const store = fsStore();
225
+ mkdirSync(cacheRoot, { recursive: true });
226
+ const prepared = [];
227
+ for (const group of groups) {
228
+ const groupDir = join(cacheRoot, 'groups', plan.identity.extensionId.replaceAll('/', '__'), plan.identity.modelId, group.id);
229
+ const digests = [];
230
+ const files = [];
231
+ for (const asset of group.assets) {
232
+ const planned = {
233
+ assetId: asset.id,
234
+ bytes: asset.bytes,
235
+ source: asset.source,
236
+ digest: asset.digest,
237
+ executable: asset.executable,
238
+ };
239
+ // NO in-memory blob shortcut: acquireAsset re-verifies the on-disk
240
+ // bytes on every call (a cache HIT transfers zero bytes but still
241
+ // checks the digest), so a blob tampered on disk is re-acquired
242
+ // rather than trusted from a stale memory record — and two owners
243
+ // still share the verified bytes.
244
+ const result = await acquireAsset(planned, {
245
+ store,
246
+ source: options.source,
247
+ root: cacheRoot,
248
+ signal: options.signal,
249
+ });
250
+ if (!result.acquired) {
251
+ return { ok: false, reason: `${group.id}/${asset.id}: ${result.reason}` };
252
+ }
253
+ const blob = { digest: result.digest, path: result.path };
254
+ // Materialize the lock's per-asset FILE layout inside the group dir via
255
+ // a hard link: the provider resolves `${groupDir}/onnx/model.onnx` etc.
256
+ // The link shares the verified blob's bytes (same volume); the digest
257
+ // was verified at acquisition and the blob path never changes.
258
+ mkdirSync(join(groupDir, asset.file, '..'), { recursive: true });
259
+ const linkTarget = join(groupDir, asset.file);
260
+ if (!existsSync(linkTarget)) {
261
+ try {
262
+ linkSync(blob.path, linkTarget);
263
+ }
264
+ catch {
265
+ // Hard links can fail across volumes; a copy is the fallback and
266
+ // is equally verified because the blob's digest was checked.
267
+ writeFileSync(linkTarget, readFileSync(blob.path));
268
+ }
269
+ }
270
+ else if (createHash('sha256').update(readFileSync(linkTarget)).digest('hex') !== asset.digest) {
271
+ // A tampered FINAL path never poisons readiness: it is removed and
272
+ // the verified blob re-linked.
273
+ rmSync(linkTarget, { force: true });
274
+ linkSync(blob.path, linkTarget);
275
+ }
276
+ digests.push(asset.digest);
277
+ files.push({ assetId: asset.id, file: asset.file, path: linkTarget, digest: asset.digest });
278
+ }
279
+ prepared.push({ groupId: group.id, path: groupDir, digests, files });
280
+ }
281
+ held.set(extensionId, { plan, groups: prepared });
282
+ return { ok: true, groups: prepared };
283
+ },
284
+ };
285
+ }
286
+ //# sourceMappingURL=prepared-assets.js.map
@@ -14,7 +14,9 @@
14
14
  *
15
15
  * @module @try-works/dsh-model-as-tool/provider-bridge
16
16
  */
17
+ import { type ProviderInspectionV1 } from '@try-works/tool-mode-extension-sdk';
17
18
  import { type ToolGrant } from './authority.ts';
19
+ import type { ManagedWorkerRegistry } from './managed-workers.ts';
18
20
  import type { ModelCatalogEntry } from './types.ts';
19
21
  import type { ModelAdapter } from './adapters/types.ts';
20
22
  /** How a model's provider is obtained. */
@@ -43,10 +45,45 @@ export declare class ProviderBridge {
43
45
  constructor(options: {
44
46
  readonly grant: ToolGrant;
45
47
  readonly activationGeneration: number;
48
+ readonly managed?: ManagedWorkerRegistry;
46
49
  readonly log?: (message: string, fields?: Record<string, unknown>) => void;
47
50
  });
48
51
  /** The extensions currently activated, for diagnostics. */
49
52
  get activatedExtensions(): readonly string[];
53
+ /**
54
+ * Inspect one extension-backed catalog entry through its provider.
55
+ *
56
+ * Inspection is the extension's read-only readiness path (EXT-18). It may import
57
+ * and activate the extension, but it does not load model assets or invoke the
58
+ * model; the provider itself must report only approved local state. This is used
59
+ * by publication and discovery so the host does not equate "the package
60
+ * resolves" with "the selected deployment is prepared".
61
+ * @param entry - the catalog entry to inspect.
62
+ * @param signal - cancellation for the read-only inspection.
63
+ * @returns the provider's inspection, or undefined for entries with no provider binding.
64
+ */
65
+ inspect(entry: ModelCatalogEntry, signal: AbortSignal): Promise<ProviderInspectionV1 | undefined>;
66
+ /**
67
+ * Inspect one extension INSTALLED IN THE STORE, keyed by its own manifest model id.
68
+ *
69
+ * The catalog-driven {@link inspect} cannot reach an extension that has no catalog
70
+ * row (EmbeddingGemma and Needle ship declared tools but are not catalogued), which
71
+ * left their deployments permanently `needs-preparation`. This route activates the
72
+ * extension through its public entry and inspects the provider the MANIFEST declares,
73
+ * so "prepared" can become a durable fact for store-installed packages too.
74
+ *
75
+ * Inspection remains read-only (EXT-18): no model assets are loaded.
76
+ * @param specifier - the installed extension package name or ABSOLUTE install path.
77
+ * An absolute path runs the exact store copy the caller verified, rather than
78
+ * whatever the package name resolves to in the host's own dependency graph.
79
+ * @param modelId - the model id the extension's manifest declares.
80
+ * @param signal - cancellation for the read-only inspection.
81
+ * @returns the provider's inspection, or undefined when the model is not declared.
82
+ * @throws ProviderBridgeError when the package cannot be resolved or the provider
83
+ * refuses creation for this model id (the model's declaredness is the provider's
84
+ * to assert, so an undeclared id surfaces as a throw rather than undefined).
85
+ */
86
+ inspectExtension(specifier: string, modelId: string, signal: AbortSignal): Promise<ProviderInspectionV1 | undefined>;
50
87
  /**
51
88
  * Build an adapter for one catalog entry.
52
89
  *
@@ -1 +1 @@
1
- {"version":3,"file":"provider-bridge.d.ts","sourceRoot":"","sources":["../src/provider-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAaH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EAAgC,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAErF,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAeD,2EAA2E;AAC3E,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAkB,IAAI,yBAAwB;CAC/C;AAED;;;;;GAKG;AACH,qBAAa,cAAc;;gBAMb,OAAO,EAAE;QACnB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;QACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;QACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KAC3E;IAMD,2DAA2D;IAC3D,IAAI,mBAAmB,IAAI,SAAS,MAAM,EAAE,CAE3C;IAED;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,GAAG,SAAS;IAyB9D,yCAAyC;IACnC,OAAO,CAAC,MAAM,GAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,OAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CAoHjH"}
1
+ {"version":3,"file":"provider-bridge.d.ts","sourceRoot":"","sources":["../src/provider-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EASL,KAAK,oBAAoB,EAC1B,MAAM,oCAAoC,CAAA;AAE3C,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EAAgC,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAErF,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAeD,2EAA2E;AAC3E,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAkB,IAAI,yBAAwB;CAC/C;AAED;;;;;GAKG;AACH,qBAAa,cAAc;;gBAcb,OAAO,EAAE;QACnB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;QACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;QACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAA;QACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KAC3E;IAOD,2DAA2D;IAC3D,IAAI,mBAAmB,IAAI,SAAS,MAAM,EAAE,CAE3C;IAED;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAoBvG;;;;;;;;;;;;;;;;;;;OAmBG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAa1H;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,GAAG,SAAS;IAkF9D,yCAAyC;IACnC,OAAO,CAAC,MAAM,GAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,OAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CA0HjH"}
@@ -16,6 +16,8 @@
16
16
  */
17
17
  import { activateExtension, assertNoReservedMeta, EXTENSION_ENTRY, loadExtensionModule, } from '@try-works/tool-mode-extension-sdk';
18
18
  import { createInvocationAuthority } from "./authority.js";
19
+ import { isAbsolute, join } from 'node:path';
20
+ import { pathToFileURL } from 'node:url';
19
21
  /** Raised when a model cannot be served through its declared extension. */
20
22
  export class ProviderBridgeError extends Error {
21
23
  name = 'ProviderBridgeError';
@@ -31,15 +33,88 @@ export class ProviderBridge {
31
33
  #grant;
32
34
  #generation;
33
35
  #log;
36
+ /**
37
+ * The managed-worker registry (EXT-20). Present ONLY when the operator opted in
38
+ * (`managedWorkers: true`): every executable route — catalog invocation and
39
+ * inspection — then goes through the worker, and the in-process import/activate
40
+ * path below is never taken for it. There is NO silent in-process fallback: a
41
+ * phase the protocol cannot serve is REFUSED with its reason.
42
+ */
43
+ #managed;
34
44
  constructor(options) {
35
45
  this.#grant = options.grant;
36
46
  this.#generation = options.activationGeneration;
47
+ this.#managed = options.managed;
37
48
  this.#log = options.log ?? (() => { });
38
49
  }
39
50
  /** The extensions currently activated, for diagnostics. */
40
51
  get activatedExtensions() {
41
52
  return [...this.#activated.keys()];
42
53
  }
54
+ /**
55
+ * Inspect one extension-backed catalog entry through its provider.
56
+ *
57
+ * Inspection is the extension's read-only readiness path (EXT-18). It may import
58
+ * and activate the extension, but it does not load model assets or invoke the
59
+ * model; the provider itself must report only approved local state. This is used
60
+ * by publication and discovery so the host does not equate "the package
61
+ * resolves" with "the selected deployment is prepared".
62
+ * @param entry - the catalog entry to inspect.
63
+ * @param signal - cancellation for the read-only inspection.
64
+ * @returns the provider's inspection, or undefined for entries with no provider binding.
65
+ */
66
+ async inspect(entry, signal) {
67
+ const binding = entry.provider;
68
+ if (binding === undefined)
69
+ return undefined;
70
+ if (this.#managed !== undefined) {
71
+ // EXT-20, managed mode: inspection runs ENTIRELY in the managed process
72
+ // through the verified SDK inspect API — the entry is never imported in-host.
73
+ const resolved = await resolveExtensionPackage(binding.extension, entry);
74
+ const worker = await this.#managed.adapterFor({
75
+ extensionId: resolved.manifestId,
76
+ entryPath: resolved.entryPath,
77
+ manifestPath: resolved.manifestPath,
78
+ generation: this.#generation,
79
+ });
80
+ return await worker.inspect(binding.modelId, { signal });
81
+ }
82
+ const activated = await this.#activate(binding.extension, entry);
83
+ const provider = await this.#providerFor(activated, binding);
84
+ return provider.inspect(signal);
85
+ }
86
+ /**
87
+ * Inspect one extension INSTALLED IN THE STORE, keyed by its own manifest model id.
88
+ *
89
+ * The catalog-driven {@link inspect} cannot reach an extension that has no catalog
90
+ * row (EmbeddingGemma and Needle ship declared tools but are not catalogued), which
91
+ * left their deployments permanently `needs-preparation`. This route activates the
92
+ * extension through its public entry and inspects the provider the MANIFEST declares,
93
+ * so "prepared" can become a durable fact for store-installed packages too.
94
+ *
95
+ * Inspection remains read-only (EXT-18): no model assets are loaded.
96
+ * @param specifier - the installed extension package name or ABSOLUTE install path.
97
+ * An absolute path runs the exact store copy the caller verified, rather than
98
+ * whatever the package name resolves to in the host's own dependency graph.
99
+ * @param modelId - the model id the extension's manifest declares.
100
+ * @param signal - cancellation for the read-only inspection.
101
+ * @returns the provider's inspection, or undefined when the model is not declared.
102
+ * @throws ProviderBridgeError when the package cannot be resolved or the provider
103
+ * refuses creation for this model id (the model's declaredness is the provider's
104
+ * to assert, so an undeclared id surfaces as a throw rather than undefined).
105
+ */
106
+ async inspectExtension(specifier, modelId, signal) {
107
+ if (this.#managed !== undefined) {
108
+ // Managed mode: the SAME worker-backed inspection as the catalog path —
109
+ // the entry is imported and created only in the child process.
110
+ const entry = { id: modelId, provider: { extension: specifier, modelId } };
111
+ return await this.inspect(entry, signal);
112
+ }
113
+ const entry = { id: modelId, provider: { extension: specifier, modelId } };
114
+ const activated = await this.#activate(specifier, entry);
115
+ const provider = await this.#providerFor(activated, { extension: specifier, modelId });
116
+ return provider.inspect(signal);
117
+ }
43
118
  /**
44
119
  * Build an adapter for one catalog entry.
45
120
  *
@@ -59,6 +134,62 @@ export class ProviderBridge {
59
134
  const binding = entry.provider;
60
135
  if (binding === undefined)
61
136
  return undefined;
137
+ if (this.#managed !== undefined) {
138
+ /**
139
+ * EXT-20, MANAGED MODE: the catalog invocation runs OUT OF PROCESS. The
140
+ * bridge resolves the entry path by DISCOVERY ONLY (reading the manifest is
141
+ * not importing), binds the worker to the activation generation, and sends
142
+ * the pre-authorized input over the wire — the grant, the caller identity
143
+ * and the host environment never cross, and the HOST resolves the path
144
+ * authority before the wire because the child's resolver DENIES by design.
145
+ */
146
+ const registry = this.#managed;
147
+ return {
148
+ modelId: entry.id,
149
+ invoke: async (invocation) => {
150
+ const resolved = await resolveExtensionPackage(binding.extension, entry);
151
+ const authority = invocation.authority ?? createInvocationAuthority(this.#grant, 'interactive');
152
+ const input = {};
153
+ for (const [key, value] of Object.entries(invocation.args)) {
154
+ input[key] = value;
155
+ }
156
+ // SEC-06: the HOST's authority is the child's path broker — the verified
157
+ // SDK protocol asks the host for EVERY path the provider touches, and an
158
+ // unauthorized candidate is refused by the authority inside this callback
159
+ // BEFORE the child ever opens the file. The broker is request-scoped and
160
+ // carries no grant or identity beyond the resolution function.
161
+ const resolvePath = (candidate) => authority.resolvePath(candidate);
162
+ // EXT-20: the worker is keyed by the TRUE MANIFEST ID, never by the path
163
+ // or the package specifier — and the manifest path rides along so the
164
+ // registry can stage an owning-package layout for manifest-less store copies.
165
+ const worker = await registry.adapterFor({
166
+ extensionId: resolved.manifestId,
167
+ entryPath: resolved.entryPath,
168
+ manifestPath: resolved.manifestPath,
169
+ generation: this.#generation,
170
+ });
171
+ // The CHILD's strict protocol maps IMPLEMENTATION ids to model ids through
172
+ // the manifest, so the wire carries the manifest's implementation id for
173
+ // this model — never the model id itself (a strict provider refuses a
174
+ // model id passed as an implementation id).
175
+ const implementationId = resolved.implementations
176
+ .find(implementation => implementation.modelId === binding.modelId)?.id
177
+ ?? binding.modelId;
178
+ const payload = await worker.invoke({
179
+ implementationId,
180
+ input,
181
+ signal: invocation.signal,
182
+ resolvePath,
183
+ });
184
+ // The same reserved-root assertion as the in-process path (EXT-17).
185
+ assertNoReservedMeta(payload, binding.modelId);
186
+ return payload;
187
+ },
188
+ dispose: async () => {
189
+ await registry.dispose(binding.extension);
190
+ },
191
+ };
192
+ }
62
193
  return {
63
194
  modelId: entry.id,
64
195
  invoke: async (invocation) => {
@@ -111,6 +242,10 @@ export class ProviderBridge {
111
242
  return { instance, providers: new Map() };
112
243
  })();
113
244
  this.#activated.set(specifier, pending);
245
+ pending.catch(() => {
246
+ if (this.#activated.get(specifier) === pending)
247
+ this.#activated.delete(specifier);
248
+ });
114
249
  return pending;
115
250
  }
116
251
  /** Create or reuse the provider for one model. */
@@ -123,6 +258,10 @@ export class ProviderBridge {
123
258
  configuration: {},
124
259
  });
125
260
  activated.providers.set(binding.modelId, pending);
261
+ pending.catch(() => {
262
+ if (activated.providers.get(binding.modelId) === pending)
263
+ activated.providers.delete(binding.modelId);
264
+ });
126
265
  return pending;
127
266
  }
128
267
  /** Run one invocation through the provider. */
@@ -187,32 +326,70 @@ export class ProviderBridge {
187
326
  }
188
327
  }
189
328
  /**
190
- * Import an extension package's public entry.
329
+ * Resolve an extension package's entry path, manifest identity and manifest path BY
330
+ * DISCOVERY ONLY (EXT-20).
191
331
  *
192
- * The specifier is resolved by Node like any other dependency, then the package's
193
- * own discovery runs so the entry comes from its real `exports` map rather than
194
- * from a host-side convention.
195
- * @param specifier - the package name or path.
332
+ * This reads the package's metadata — the package.json and the manifest — and
333
+ * returns the absolute `./tool-mode` entry path, the manifest's TRUE id, and the
334
+ * manifest path WITHOUT importing the entry. Managed mode needs exactly this: the
335
+ * worker child imports the entry in its own process and derives its model mapping
336
+ * from the manifest, so the host must never load the module — and must key the
337
+ * worker by the manifest id, never by a path.
338
+ * @param specifier - the package name or absolute install path.
196
339
  * @param entry - the catalog entry, for diagnostics.
197
- * @returns the validated extension module.
340
+ * @returns the entry path, the manifest id and the manifest path.
198
341
  * @throws ProviderBridgeError when the package cannot be resolved or has no entry.
199
342
  */
200
- async function loadExtensionPackage(specifier, entry) {
343
+ async function resolveExtensionPackage(specifier, entry) {
201
344
  let packageJsonPath;
202
345
  try {
203
346
  // `import.meta.resolve` is the resolver the runtime itself uses, so the
204
- // extension is found exactly as Node would find it at call time.
205
- packageJsonPath = import.meta.resolve(`${specifier}/package.json`);
347
+ // extension is found exactly as Node would find it at call time. An ABSOLUTE
348
+ // path resolves to the exact installed copy.
349
+ if (isAbsolute(specifier)) {
350
+ packageJsonPath = pathToFileURL(join(specifier, 'package.json')).href;
351
+ }
352
+ else {
353
+ packageJsonPath = import.meta.resolve(`${specifier}/package.json`);
354
+ }
206
355
  }
207
356
  catch (error) {
208
- throw new ProviderBridgeError(`model "${entry.id}" declares the extension "${specifier}", which could not be resolved: `
209
- + `${error instanceof Error ? error.message : String(error)}. Install it, or correct the catalog binding.`);
357
+ const origin = isAbsolute(specifier)
358
+ ? 'the installed package'
359
+ : `the dependency "${specifier}"`;
360
+ throw new ProviderBridgeError(`model "${entry.id}" cannot reach ${origin}, which could not be resolved: `
361
+ + `${error instanceof Error ? error.message : String(error)}. `
362
+ + (isAbsolute(specifier)
363
+ ? 'The store install path is unreadable; reinstall the package and retry.'
364
+ : 'Install it, or correct the catalog binding.'));
210
365
  }
211
366
  const { discoverExtension } = await import('@try-works/tool-mode-extension-sdk');
212
367
  const discovery = discoverExtension(new URL('.', packageJsonPath).pathname.replace(/^\/([A-Za-z]:)/, '$1'));
213
368
  if (discovery.entryPath === undefined) {
214
369
  throw new ProviderBridgeError(`extension "${specifier}" declares no ${EXTENSION_ENTRY} entry, so it cannot provide model "${entry.id}"`);
215
370
  }
216
- return loadExtensionModule(discovery.entryPath);
371
+ return {
372
+ entryPath: discovery.entryPath,
373
+ manifestId: discovery.manifest.id,
374
+ manifestPath: discovery.manifestPath,
375
+ implementations: discovery.manifest.implementations
376
+ .filter((implementation) => typeof implementation.modelId === 'string')
377
+ .map(implementation => ({ id: implementation.id, modelId: implementation.modelId })),
378
+ };
379
+ }
380
+ /**
381
+ * Import an extension package's public entry.
382
+ *
383
+ * The specifier is resolved by Node like any other dependency, then the package's
384
+ * own discovery runs so the entry comes from its real `exports` map rather than
385
+ * from a host-side convention.
386
+ * @param specifier - the package name or path.
387
+ * @param entry - the catalog entry, for diagnostics.
388
+ * @returns the validated extension module.
389
+ * @throws ProviderBridgeError when the package cannot be resolved or has no entry.
390
+ */
391
+ async function loadExtensionPackage(specifier, entry) {
392
+ const { entryPath } = await resolveExtensionPackage(specifier, entry);
393
+ return loadExtensionModule(entryPath);
217
394
  }
218
395
  //# sourceMappingURL=provider-bridge.js.map