@world-engines/project-host 0.1.0-alpha.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 (218) hide show
  1. package/LICENSE +46 -0
  2. package/assets/gui/WELogo-title.svg +11 -0
  3. package/assets/gui/app.js +1326 -0
  4. package/assets/gui/index.html +118 -0
  5. package/assets/gui/local-preview-asset-urls.js +61 -0
  6. package/assets/gui/local-preview-bridge.js +393 -0
  7. package/assets/gui/spatial-template-panel.js +233 -0
  8. package/assets/gui/styles.css +115 -0
  9. package/assets/gui/terrain-render.js +120 -0
  10. package/assets/gui/workspace-status.css +17 -0
  11. package/assets/gui/workspace-status.js +255 -0
  12. package/assets/gui/world-map-globe.js +187 -0
  13. package/assets/gui/world-map-worker.js +62 -0
  14. package/assets/gui/world-map.css +30 -0
  15. package/assets/gui/world-map.js +2466 -0
  16. package/dist/account-model-proxy.d.ts +21 -0
  17. package/dist/account-model-proxy.js +470 -0
  18. package/dist/authoring-browser-runtime.d.ts +7 -0
  19. package/dist/authoring-browser-runtime.js +110 -0
  20. package/dist/authoring-ingestion.d.ts +38 -0
  21. package/dist/authoring-ingestion.js +410 -0
  22. package/dist/authoring-model-connection.d.ts +166 -0
  23. package/dist/authoring-model-connection.js +757 -0
  24. package/dist/chinese-lexical.d.ts +152 -0
  25. package/dist/chinese-lexical.js +555 -0
  26. package/dist/chromium-preview-browser-driver.d.ts +43 -0
  27. package/dist/chromium-preview-browser-driver.js +575 -0
  28. package/dist/cli/download.d.ts +12 -0
  29. package/dist/cli/download.js +50 -0
  30. package/dist/cli/dump.d.ts +32 -0
  31. package/dist/cli/dump.js +91 -0
  32. package/dist/cli/ingest.d.ts +49 -0
  33. package/dist/cli/ingest.js +225 -0
  34. package/dist/cli/review.d.ts +10 -0
  35. package/dist/cli/review.js +44 -0
  36. package/dist/cli/upload.d.ts +57 -0
  37. package/dist/cli/upload.js +112 -0
  38. package/dist/cli-command.d.ts +60 -0
  39. package/dist/cli-command.js +613 -0
  40. package/dist/cli.d.ts +2 -0
  41. package/dist/cli.js +182 -0
  42. package/dist/desktop-authoring-runtime.d.ts +88 -0
  43. package/dist/desktop-authoring-runtime.js +1602 -0
  44. package/dist/desktop-sidecar.d.ts +11 -0
  45. package/dist/desktop-sidecar.js +143 -0
  46. package/dist/document-dump.d.ts +113 -0
  47. package/dist/document-dump.js +431 -0
  48. package/dist/document-graph-extractor.d.ts +125 -0
  49. package/dist/document-graph-extractor.js +588 -0
  50. package/dist/document-ingestion-journal.d.ts +68 -0
  51. package/dist/document-ingestion-journal.js +471 -0
  52. package/dist/document-ingestion.d.ts +183 -0
  53. package/dist/document-ingestion.js +976 -0
  54. package/dist/document-query-path-proof.d.ts +30 -0
  55. package/dist/document-query-path-proof.js +34 -0
  56. package/dist/document-query-path-renderer.d.ts +50 -0
  57. package/dist/document-query-path-renderer.js +75 -0
  58. package/dist/document-query-proof-paths.d.ts +22 -0
  59. package/dist/document-query-proof-paths.js +71 -0
  60. package/dist/document-query.d.ts +97 -0
  61. package/dist/document-query.js +1106 -0
  62. package/dist/document-relation-role.d.ts +14 -0
  63. package/dist/document-relation-role.js +86 -0
  64. package/dist/document-source.d.ts +107 -0
  65. package/dist/document-source.js +255 -0
  66. package/dist/embedding-cache.d.ts +35 -0
  67. package/dist/embedding-cache.js +317 -0
  68. package/dist/frozen-review-local-pipeline.d.ts +88 -0
  69. package/dist/frozen-review-local-pipeline.js +609 -0
  70. package/dist/frozen-review-source-capture.d.ts +15 -0
  71. package/dist/frozen-review-source-capture.js +551 -0
  72. package/dist/frozen-review-wasm-encryptor.d.ts +16 -0
  73. package/dist/frozen-review-wasm-encryptor.js +72 -0
  74. package/dist/frozen-review.d.ts +131 -0
  75. package/dist/frozen-review.js +802 -0
  76. package/dist/graph-traversal.d.ts +23 -0
  77. package/dist/graph-traversal.js +100 -0
  78. package/dist/gui/graph-layout-worker.js +1 -0
  79. package/dist/gui/graph-layout.wasm +0 -0
  80. package/dist/gui/graph.css +1 -0
  81. package/dist/gui/graph.js +76 -0
  82. package/dist/gui/plane-generation.js +112 -0
  83. package/dist/gui/plane-generator.js +310 -0
  84. package/dist/gui/plane-generator_bg.wasm +0 -0
  85. package/dist/gui/procedural-city.js +595 -0
  86. package/dist/gui/road-generation.js +129 -0
  87. package/dist/gui/road-parcels.js +1584 -0
  88. package/dist/gui/score.css +1 -0
  89. package/dist/gui/score.js +54 -0
  90. package/dist/gui/spatial-runtime.js +1231 -0
  91. package/dist/gui/spatial-templates.js +200 -0
  92. package/dist/gui/trigger.css +1 -0
  93. package/dist/gui/trigger.js +142 -0
  94. package/dist/harness-callback-transport.d.ts +17 -0
  95. package/dist/harness-callback-transport.js +128 -0
  96. package/dist/harness-document-ingestion.d.ts +77 -0
  97. package/dist/harness-document-ingestion.js +343 -0
  98. package/dist/index.d.ts +43 -0
  99. package/dist/index.js +43 -0
  100. package/dist/ingestion-source-reconciliation.d.ts +88 -0
  101. package/dist/ingestion-source-reconciliation.js +253 -0
  102. package/dist/ladybug-runtime.d.ts +117 -0
  103. package/dist/ladybug-runtime.js +886 -0
  104. package/dist/local-account-embedding.d.ts +34 -0
  105. package/dist/local-account-embedding.js +294 -0
  106. package/dist/local-gallery.d.ts +35 -0
  107. package/dist/local-gallery.js +227 -0
  108. package/dist/local-gui-auth.d.ts +96 -0
  109. package/dist/local-gui-auth.js +703 -0
  110. package/dist/local-gui-contract.d.ts +53 -0
  111. package/dist/local-gui-contract.js +5 -0
  112. package/dist/local-gui-image-assets.d.ts +17 -0
  113. package/dist/local-gui-image-assets.js +96 -0
  114. package/dist/local-gui-preview-client.d.ts +3 -0
  115. package/dist/local-gui-preview-client.js +111 -0
  116. package/dist/local-gui-score.d.ts +72 -0
  117. package/dist/local-gui-score.js +184 -0
  118. package/dist/local-gui-server.d.ts +10 -0
  119. package/dist/local-gui-server.js +987 -0
  120. package/dist/local-gui-version.d.ts +17 -0
  121. package/dist/local-gui-version.js +147 -0
  122. package/dist/local-gui-workspace.d.ts +14 -0
  123. package/dist/local-gui-workspace.js +171 -0
  124. package/dist/local-model-settings.d.ts +28 -0
  125. package/dist/local-model-settings.js +124 -0
  126. package/dist/local-preview-assets.d.ts +28 -0
  127. package/dist/local-preview-assets.js +124 -0
  128. package/dist/local-preview-bound-resources.d.ts +12 -0
  129. package/dist/local-preview-bound-resources.js +80 -0
  130. package/dist/local-preview-message-store.d.ts +63 -0
  131. package/dist/local-preview-message-store.js +214 -0
  132. package/dist/local-preview-player-read-models.d.ts +47 -0
  133. package/dist/local-preview-player-read-models.js +90 -0
  134. package/dist/local-preview-projections.d.ts +61 -0
  135. package/dist/local-preview-projections.js +117 -0
  136. package/dist/local-preview-runtime.d.ts +105 -0
  137. package/dist/local-preview-runtime.js +553 -0
  138. package/dist/local-preview-session.d.ts +73 -0
  139. package/dist/local-preview-session.js +395 -0
  140. package/dist/local-preview-source.d.ts +40 -0
  141. package/dist/local-preview-source.js +192 -0
  142. package/dist/local-preview-view-events.d.ts +101 -0
  143. package/dist/local-preview-view-events.js +179 -0
  144. package/dist/local-preview-view-state-sqlite.d.ts +24 -0
  145. package/dist/local-preview-view-state-sqlite.js +263 -0
  146. package/dist/local-preview-view-state.d.ts +93 -0
  147. package/dist/local-preview-view-state.js +108 -0
  148. package/dist/local-preview-vite-config.d.ts +6 -0
  149. package/dist/local-preview-vite-config.js +122 -0
  150. package/dist/local-preview-world-effects.d.ts +9 -0
  151. package/dist/local-preview-world-effects.js +55 -0
  152. package/dist/local-preview-worldline-store.d.ts +47 -0
  153. package/dist/local-preview-worldline-store.js +282 -0
  154. package/dist/local-preview-worldlines.d.ts +95 -0
  155. package/dist/local-preview-worldlines.js +136 -0
  156. package/dist/mcp-async-dispatcher.d.ts +3 -0
  157. package/dist/mcp-async-dispatcher.js +129 -0
  158. package/dist/mcp-line-queue.d.ts +14 -0
  159. package/dist/mcp-line-queue.js +184 -0
  160. package/dist/mcp-pipe-internal.d.ts +6 -0
  161. package/dist/mcp-pipe-internal.js +151 -0
  162. package/dist/mcp-pipe-response.d.ts +14 -0
  163. package/dist/mcp-pipe-response.js +50 -0
  164. package/dist/mcp-stdio-response.d.ts +12 -0
  165. package/dist/mcp-stdio-response.js +27 -0
  166. package/dist/mcp.d.ts +27 -0
  167. package/dist/mcp.js +194 -0
  168. package/dist/prepared-embedding-cache.d.ts +38 -0
  169. package/dist/prepared-embedding-cache.js +238 -0
  170. package/dist/preview-browser-adapter.d.ts +53 -0
  171. package/dist/preview-browser-adapter.js +171 -0
  172. package/dist/preview-runtime.d.ts +67 -0
  173. package/dist/preview-runtime.js +460 -0
  174. package/dist/project-auth-transport.d.ts +27 -0
  175. package/dist/project-auth-transport.js +121 -0
  176. package/dist/project-auth.d.ts +51 -0
  177. package/dist/project-auth.js +108 -0
  178. package/dist/project-git-capture-recovery.d.ts +16 -0
  179. package/dist/project-git-capture-recovery.js +347 -0
  180. package/dist/project-git-cli.d.ts +5 -0
  181. package/dist/project-git-cli.js +40 -0
  182. package/dist/project-git-scenario-source.d.ts +10 -0
  183. package/dist/project-git-scenario-source.js +240 -0
  184. package/dist/project-git-view-source.d.ts +13 -0
  185. package/dist/project-git-view-source.js +134 -0
  186. package/dist/project-git.d.ts +97 -0
  187. package/dist/project-git.js +656 -0
  188. package/dist/project-host.d.ts +59 -0
  189. package/dist/project-host.js +471 -0
  190. package/dist/project-writer-lease.d.ts +18 -0
  191. package/dist/project-writer-lease.js +430 -0
  192. package/dist/public-error.d.ts +7 -0
  193. package/dist/public-error.js +61 -0
  194. package/dist/retrieval-benchmark.d.ts +46 -0
  195. package/dist/retrieval-benchmark.js +58 -0
  196. package/dist/safe-project-path.d.ts +8 -0
  197. package/dist/safe-project-path.js +93 -0
  198. package/dist/scenario-writer.d.ts +32 -0
  199. package/dist/scenario-writer.js +191 -0
  200. package/dist/transfer/download.d.ts +159 -0
  201. package/dist/transfer/download.js +598 -0
  202. package/dist/transfer/transport.d.ts +26 -0
  203. package/dist/transfer/transport.js +1 -0
  204. package/dist/transfer/upload.d.ts +162 -0
  205. package/dist/transfer/upload.js +354 -0
  206. package/dist/transfer-types.d.ts +95 -0
  207. package/dist/transfer-types.js +1 -0
  208. package/dist/transfer.d.ts +18 -0
  209. package/dist/transfer.js +273 -0
  210. package/dist/trigger-runtime.d.ts +121 -0
  211. package/dist/trigger-runtime.js +153 -0
  212. package/dist/vendor/wasm-core/asset-hashes.json +5 -0
  213. package/dist/vendor/wasm-core/wasm_core.d.ts +490 -0
  214. package/dist/vendor/wasm-core/wasm_core.js +1 -0
  215. package/dist/vendor/wasm-core/wasm_core_bg.wasm +0 -0
  216. package/dist/view-watcher.d.ts +9 -0
  217. package/dist/view-watcher.js +34 -0
  218. package/package.json +110 -0
@@ -0,0 +1,317 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { open, readFile, rename, rm } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { EMBEDDING_BATCH_SIZE } from "./authoring-model-connection.js";
5
+ import { assertSafeExistingProjectPath, assertSafeProjectParent, ensureSafeProjectDirectory, safeProjectPath, } from "./safe-project-path.js";
6
+ export class EmbeddingCacheError extends Error {
7
+ code;
8
+ name = "EmbeddingCacheError";
9
+ constructor(code) {
10
+ super(`embedding_cache:${code}`);
11
+ this.code = code;
12
+ }
13
+ }
14
+ const inFlight = new Map();
15
+ const directoryFlights = new Map();
16
+ const CACHE_IO_CONCURRENCY = 16;
17
+ const EMBEDDING_BATCH_CONCURRENCY = 4;
18
+ // 发生错误后停止领取新任务,但已发出的请求仍须完成校验和持久化。
19
+ async function runBounded(count, concurrency, task) {
20
+ let next = 0;
21
+ let failed = false;
22
+ let failure;
23
+ await Promise.all(Array.from({ length: Math.min(count, concurrency) }, async () => {
24
+ while (!failed && next < count) {
25
+ const index = next++;
26
+ try {
27
+ await task(index);
28
+ }
29
+ catch (error) {
30
+ if (!failed) {
31
+ failed = true;
32
+ failure = error;
33
+ }
34
+ }
35
+ }
36
+ }));
37
+ if (failed)
38
+ throw failure;
39
+ }
40
+ function assertNotAborted(signal) {
41
+ if (signal?.aborted)
42
+ fail("aborted");
43
+ }
44
+ function fail(code) {
45
+ throw new EmbeddingCacheError(code);
46
+ }
47
+ function sha256(value) {
48
+ return createHash("sha256").update(value, "utf8").digest("hex");
49
+ }
50
+ function canonicalOrigin(value) {
51
+ if (typeof value !== "string")
52
+ fail("invalid_identity");
53
+ try {
54
+ const url = new URL(value);
55
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== ""
56
+ || (url.pathname !== "" && url.pathname !== "/") || url.origin === "null")
57
+ fail("invalid_identity");
58
+ return url.origin;
59
+ }
60
+ catch (error) {
61
+ if (error instanceof EmbeddingCacheError)
62
+ throw error;
63
+ fail("invalid_identity");
64
+ }
65
+ }
66
+ function identity(options) {
67
+ const embeddedOrigin = options.embedding.origin;
68
+ if (options.origin !== undefined && embeddedOrigin !== undefined
69
+ && canonicalOrigin(options.origin) !== canonicalOrigin(embeddedOrigin))
70
+ fail("invalid_identity");
71
+ const origin = canonicalOrigin(options.origin ?? embeddedOrigin);
72
+ if (typeof options.embedding.providerId !== "string" || options.embedding.providerId.length === 0
73
+ || typeof options.embedding.modelId !== "string" || options.embedding.modelId.length === 0
74
+ || !Number.isSafeInteger(options.embedding.dimensions) || options.embedding.dimensions <= 0
75
+ || (options.embedding.descriptorSha256 !== undefined && !/^[a-f0-9]{64}$/u.test(options.embedding.descriptorSha256))
76
+ || (options.embedding.descriptorSha256ByInputKind !== undefined
77
+ && (!/^[a-f0-9]{64}$/u.test(options.embedding.descriptorSha256ByInputKind.document)
78
+ || !/^[a-f0-9]{64}$/u.test(options.embedding.descriptorSha256ByInputKind.query))))
79
+ fail("invalid_identity");
80
+ const documentDescriptor = options.embedding.descriptorSha256ByInputKind?.document ?? options.embedding.descriptorSha256 ?? null;
81
+ const queryDescriptor = options.embedding.descriptorSha256ByInputKind?.query ?? options.embedding.descriptorSha256 ?? null;
82
+ return Object.freeze({
83
+ origin,
84
+ providerId: options.embedding.providerId,
85
+ modelId: options.embedding.modelId,
86
+ dimensions: options.embedding.dimensions,
87
+ descriptorSha256ByInputKind: Object.freeze({ document: documentDescriptor, query: queryDescriptor }),
88
+ });
89
+ }
90
+ function inputIdentity(base, inputKind) {
91
+ return Object.freeze({
92
+ origin: base.origin, providerId: base.providerId, modelId: base.modelId, dimensions: base.dimensions,
93
+ descriptorSha256: base.descriptorSha256ByInputKind[inputKind], inputKind,
94
+ });
95
+ }
96
+ function cacheLocation(cacheIdentity) {
97
+ const identityJson = JSON.stringify({ schema_version: 2, ...cacheIdentity });
98
+ const identitySha256 = sha256(identityJson);
99
+ return { identitySha256, relativeRoot: `.worldengine/cache/embeddings/v1/${identitySha256}` };
100
+ }
101
+ function validVector(value, dimensions) {
102
+ return Array.isArray(value) && value.length === dimensions
103
+ && Array.from(value).every((entry) => typeof entry === "number" && Number.isFinite(entry));
104
+ }
105
+ /** 只读查询现有 corpus cache;任一 miss 返回 null,绝不调用 provider 或创建目录。 */
106
+ export async function readCachedEmbeddingVectors(options, textSha256s) {
107
+ const cacheIdentity = inputIdentity(identity(options), options.inputKind ?? "document");
108
+ const { identitySha256, relativeRoot } = cacheLocation(cacheIdentity);
109
+ const vectors = [];
110
+ for (const textSha256 of textSha256s) {
111
+ if (!/^[a-f0-9]{64}$/u.test(textSha256))
112
+ fail("invalid_input");
113
+ const path = await safeProjectPath(options.projectRoot, `${relativeRoot}/${textSha256.slice(0, 2)}/${textSha256}.json`);
114
+ try {
115
+ await assertSafeExistingProjectPath(options.projectRoot, path);
116
+ }
117
+ catch (error) {
118
+ if (error.code === "ENOENT")
119
+ return null;
120
+ throw error;
121
+ }
122
+ let entry;
123
+ try {
124
+ entry = JSON.parse(await readFile(path, "utf8"));
125
+ }
126
+ catch {
127
+ fail("corrupt");
128
+ }
129
+ if (entry.schema_version !== 1 || entry.identity_sha256 !== identitySha256 || entry.text_sha256 !== textSha256
130
+ || entry.dimensions !== cacheIdentity.dimensions || !validVector(entry.vector, cacheIdentity.dimensions))
131
+ fail("corrupt");
132
+ vectors.push(Object.freeze([...entry.vector]));
133
+ }
134
+ return Object.freeze(vectors);
135
+ }
136
+ function deferred() {
137
+ let resolve;
138
+ let reject;
139
+ const promise = new Promise((resolvePromise, rejectPromise) => {
140
+ resolve = resolvePromise;
141
+ reject = rejectPromise;
142
+ });
143
+ // 所有在途批次落盘后才汇总结果;提前观察拒绝,避免共享 waiter 出现未处理拒绝。
144
+ void promise.catch(() => { });
145
+ return { promise, resolve, reject };
146
+ }
147
+ async function ensureDirectory(root, relative) {
148
+ const key = `${root}\0${relative}`;
149
+ const pending = directoryFlights.get(key);
150
+ if (pending !== undefined)
151
+ return pending;
152
+ const created = (async () => {
153
+ try {
154
+ return await ensureSafeProjectDirectory(root, relative);
155
+ }
156
+ catch (error) {
157
+ if (error.code !== "EEXIST")
158
+ throw error;
159
+ return await assertSafeExistingProjectPath(root, join(root, ...relative.split("/")));
160
+ }
161
+ })();
162
+ directoryFlights.set(key, created);
163
+ try {
164
+ return await created;
165
+ }
166
+ finally {
167
+ if (directoryFlights.get(key) === created)
168
+ directoryFlights.delete(key);
169
+ }
170
+ }
171
+ async function atomicWrite(root, path, bytes) {
172
+ await assertSafeProjectParent(root, path);
173
+ const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
174
+ const handle = await open(temporary, "wx");
175
+ try {
176
+ await handle.writeFile(bytes, "utf8");
177
+ await handle.sync();
178
+ }
179
+ catch (error) {
180
+ await handle.close();
181
+ await rm(temporary, { force: true });
182
+ throw error;
183
+ }
184
+ await handle.close();
185
+ try {
186
+ await assertSafeProjectParent(root, path);
187
+ await rename(temporary, path);
188
+ }
189
+ catch (error) {
190
+ await rm(temporary, { force: true });
191
+ throw error;
192
+ }
193
+ }
194
+ export async function createCachedEmbeddingPort(options) {
195
+ const baseIdentity = identity(options);
196
+ const counts = { hits: 0, misses: 0, remoteTexts: 0, writes: 0 };
197
+ const readEntry = async (path, textSha256, cacheIdentity, identitySha256) => {
198
+ try {
199
+ await assertSafeExistingProjectPath(options.projectRoot, path);
200
+ }
201
+ catch (error) {
202
+ if (error.code === "ENOENT")
203
+ return null;
204
+ throw error;
205
+ }
206
+ let entry;
207
+ try {
208
+ entry = JSON.parse(await readFile(path, "utf8"));
209
+ }
210
+ catch {
211
+ fail("corrupt");
212
+ }
213
+ if (entry.schema_version !== 1 || entry.identity_sha256 !== identitySha256 || entry.text_sha256 !== textSha256
214
+ || entry.dimensions !== cacheIdentity.dimensions || !validVector(entry.vector, cacheIdentity.dimensions))
215
+ fail("corrupt");
216
+ return Object.freeze([...entry.vector]);
217
+ };
218
+ const writeEntry = async (path, textSha256, vector, cacheIdentity, identitySha256) => {
219
+ await atomicWrite(options.projectRoot, path, `${JSON.stringify({
220
+ schema_version: 1,
221
+ identity: cacheIdentity,
222
+ identity_sha256: identitySha256,
223
+ text_sha256: textSha256,
224
+ dimensions: cacheIdentity.dimensions,
225
+ vector,
226
+ })}\n`);
227
+ counts.writes += 1;
228
+ };
229
+ return Object.freeze({
230
+ origin: baseIdentity.origin,
231
+ providerId: baseIdentity.providerId,
232
+ modelId: baseIdentity.modelId,
233
+ dimensions: baseIdentity.dimensions,
234
+ ...(baseIdentity.descriptorSha256ByInputKind.document === null ? {} : { descriptorSha256: baseIdentity.descriptorSha256ByInputKind.document }),
235
+ ...(baseIdentity.descriptorSha256ByInputKind.document === null || baseIdentity.descriptorSha256ByInputKind.query === null ? {} : {
236
+ descriptorSha256ByInputKind: Object.freeze({ document: baseIdentity.descriptorSha256ByInputKind.document, query: baseIdentity.descriptorSha256ByInputKind.query }),
237
+ }),
238
+ async embed(texts, embedOptions) {
239
+ if (!Array.isArray(texts) || texts.length === 0 || texts.some((value) => typeof value !== "string" || value.length === 0))
240
+ fail("invalid_input");
241
+ assertNotAborted(embedOptions?.signal);
242
+ const inputKind = embedOptions?.inputKind ?? "document";
243
+ const cacheIdentity = inputIdentity(baseIdentity, inputKind);
244
+ const { identitySha256, relativeRoot } = cacheLocation(cacheIdentity);
245
+ await ensureDirectory(options.projectRoot, relativeRoot);
246
+ const entryPath = async (textSha256) => {
247
+ const relativeDirectory = `${relativeRoot}/${textSha256.slice(0, 2)}`;
248
+ await ensureDirectory(options.projectRoot, relativeDirectory);
249
+ return safeProjectPath(options.projectRoot, `${relativeDirectory}/${textSha256}.json`);
250
+ };
251
+ const unique = new Map();
252
+ for (const value of texts)
253
+ unique.set(sha256(value), value);
254
+ const resolved = new Map();
255
+ const waits = new Map();
256
+ const owned = [];
257
+ try {
258
+ const entries = [...unique];
259
+ await runBounded(entries.length, CACHE_IO_CONCURRENCY, async (index) => {
260
+ assertNotAborted(embedOptions?.signal);
261
+ const [textSha256, value] = entries[index];
262
+ const path = await entryPath(textSha256);
263
+ const cached = await readEntry(path, textSha256, cacheIdentity, identitySha256);
264
+ if (cached !== null) {
265
+ counts.hits += 1;
266
+ resolved.set(textSha256, cached);
267
+ return;
268
+ }
269
+ counts.misses += 1;
270
+ const key = `${path}\0${textSha256}`;
271
+ const current = inFlight.get(key);
272
+ if (current !== undefined) {
273
+ waits.set(textSha256, current);
274
+ return;
275
+ }
276
+ const claim = deferred();
277
+ inFlight.set(key, claim.promise);
278
+ waits.set(textSha256, claim.promise);
279
+ owned.push({ index, textSha256, text: value, path, deferred: claim });
280
+ });
281
+ owned.sort((left, right) => left.index - right.index);
282
+ await runBounded(Math.ceil(owned.length / EMBEDDING_BATCH_SIZE), EMBEDDING_BATCH_CONCURRENCY, async (index) => {
283
+ assertNotAborted(embedOptions?.signal);
284
+ const batch = owned.slice(index * EMBEDDING_BATCH_SIZE, (index + 1) * EMBEDDING_BATCH_SIZE);
285
+ counts.remoteTexts += batch.length;
286
+ const vectors = await options.embedding.embed(batch.map((entry) => entry.text), embedOptions);
287
+ if (!Array.isArray(vectors) || vectors.length !== batch.length
288
+ || Array.from(vectors).some((vector) => !validVector(vector, cacheIdentity.dimensions)))
289
+ fail("provider_response_invalid");
290
+ const snapshots = vectors.map((vector) => Object.freeze([...vector]));
291
+ // 收到成功响应后即使 signal 已取消也提交本批;每个 worker 最多一个 fsync。
292
+ for (const [vectorIndex, entry] of batch.entries()) {
293
+ const vector = snapshots[vectorIndex];
294
+ await writeEntry(entry.path, entry.textSha256, vector, cacheIdentity, identitySha256);
295
+ entry.deferred.resolve(vector);
296
+ inFlight.delete(`${entry.path}\0${entry.textSha256}`);
297
+ }
298
+ });
299
+ }
300
+ catch (error) {
301
+ for (const entry of owned)
302
+ entry.deferred.reject(error);
303
+ throw error;
304
+ }
305
+ finally {
306
+ for (const entry of owned) {
307
+ const key = `${entry.path}\0${entry.textSha256}`;
308
+ if (inFlight.get(key) === entry.deferred.promise)
309
+ inFlight.delete(key);
310
+ }
311
+ }
312
+ await Promise.all([...waits].map(async ([textSha256, promise]) => { resolved.set(textSha256, await promise); }));
313
+ return Object.freeze(texts.map((value) => resolved.get(sha256(value))));
314
+ },
315
+ getCounts() { return Object.freeze({ ...counts }); },
316
+ });
317
+ }
@@ -0,0 +1,88 @@
1
+ import { type NeutralSourceCompileInput } from "@world-engines/scenario-author-source";
2
+ import type { FrozenArtifactEmbeddingBatch, FrozenPublishInput, NeutralArtifactGraphProjection, PreparedNeutralArtifactEmbeddingSnapshot } from "@world-engines/scenario-review-snapshot";
3
+ import { type CompileNeutralScenarioSourceOutcome, type CompileTargetProfile } from "@world-engines/tmw";
4
+ import type { FrozenReviewCapture, FrozenReviewEmbedding, FrozenReviewLocalPipeline, FrozenReviewRecoveryPort } from "./frozen-review.js";
5
+ import { type FrozenReviewWasmEncryptor } from "./frozen-review-wasm-encryptor.js";
6
+ import type { ProjectTransferTransport } from "./transfer/transport.js";
7
+ import type { AuthoringEmbeddingPort } from "./authoring-model-connection.js";
8
+ export interface FrozenReviewTargetBinding {
9
+ readonly schema_version: 1;
10
+ readonly project_id: string;
11
+ readonly scenario_id: string;
12
+ readonly source_digest: string;
13
+ readonly source_head_digest: string;
14
+ }
15
+ export interface FrozenReviewLocalAssemblerInput {
16
+ readonly capture: FrozenReviewCapture;
17
+ readonly source: NeutralSourceCompileInput;
18
+ readonly profile: CompileTargetProfile;
19
+ readonly compilation: Extract<CompileNeutralScenarioSourceOutcome, {
20
+ readonly ok: true;
21
+ }>;
22
+ readonly embedding: FrozenReviewEmbedding;
23
+ readonly vectors: ReadonlyMap<string, readonly number[]>;
24
+ readonly embedding_snapshot: PreparedNeutralArtifactEmbeddingSnapshot;
25
+ readonly signal?: AbortSignal;
26
+ }
27
+ export interface CreateFrozenReviewLocalArtifactAssemblerOptions {
28
+ readonly project_root: string;
29
+ readonly project_id: string;
30
+ readonly transport: ProjectTransferTransport;
31
+ readonly crypto?: Pick<FrozenReviewWasmEncryptor, "encrypt"> & Partial<Pick<FrozenReviewWasmEncryptor, "pack">>;
32
+ }
33
+ export declare function createFrozenReviewEmbeddingBatch(embedding: AuthoringEmbeddingPort): FrozenArtifactEmbeddingBatch;
34
+ export interface CreateFrozenReviewRecoveryPortOptions {
35
+ readonly project_root: string;
36
+ readonly project_id: string;
37
+ readonly transport: ProjectTransferTransport;
38
+ readonly crypto?: Pick<FrozenReviewWasmEncryptor, "encrypt" | "decrypt">;
39
+ }
40
+ /**
41
+ * Browser 与 Node 必须共用的最后一段 authority。它负责把已编译模型、向量、View build
42
+ * 和 WASM scnb-v2 delivery 组装为 FrozenPublishInput;Node pipeline 不复制这些规则。
43
+ */
44
+ export interface FrozenReviewLocalArtifactAssembler {
45
+ embeddingGraph(input: Readonly<{
46
+ capture: FrozenReviewCapture;
47
+ source: NeutralSourceCompileInput;
48
+ profile: CompileTargetProfile;
49
+ compilation: Extract<CompileNeutralScenarioSourceOutcome, {
50
+ readonly ok: true;
51
+ }>;
52
+ }>): Promise<NeutralArtifactGraphProjection>;
53
+ assemble(input: FrozenReviewLocalAssemblerInput): Promise<FrozenPublishInput>;
54
+ }
55
+ export interface CreateFrozenReviewLocalPipelineOptions {
56
+ readonly project_root: string;
57
+ readonly project_id: string;
58
+ readonly run_embedding_batch: FrozenArtifactEmbeddingBatch;
59
+ /** Host query-only ready adapter 可注入已校验 formal snapshot;返回 null 时才允许本次显式提审调用 provider。 */
60
+ readonly read_prepared_embedding_snapshot?: (capture: FrozenReviewCapture) => Promise<PreparedNeutralArtifactEmbeddingSnapshot | null>;
61
+ readonly assembler: FrozenReviewLocalArtifactAssembler;
62
+ /** WESP owner 提供的正式远端 materialization readback;revision_bytes 是 canonical Neutral protobuf。 */
63
+ readonly read_source_ready: () => Promise<Readonly<{
64
+ target: FrozenReviewTargetBinding;
65
+ content_epoch: number;
66
+ head: NeutralSourceCompileInput["witness"];
67
+ revision_bytes: Uint8Array;
68
+ }>>;
69
+ readonly admit_profile?: (profile: CompileTargetProfile) => Promise<void>;
70
+ }
71
+ export interface FrozenReviewLocalPipelineWithPreparedEmbedding extends FrozenReviewLocalPipeline {
72
+ /** 只读;仅返回 embed 步骤已经由 shared module 校验的 formal batch/index。 */
73
+ getPreparedEmbeddingSnapshot(capture: Pick<FrozenReviewCapture, "scenario_id" | "source_revision" | "revision_digest">): PreparedNeutralArtifactEmbeddingSnapshot | null;
74
+ }
75
+ /** 与 Creator 共读 Edge CompilerReleaseAuthority;transport 持有认证,CLI 不接触 cookie/token。 */
76
+ export declare function createCompilerReleaseProfileAdmission(transport: ProjectTransferTransport, projectId: string): (profile: CompileTargetProfile) => Promise<void>;
77
+ export declare function readFrozenReviewTargetBinding(root: string, expectedProjectId: string): Promise<FrozenReviewTargetBinding>;
78
+ /** Coordinator 的随机密文恢复 port;每次仅短暂消费同 scenario database key。 */
79
+ export declare function createFrozenReviewRecoveryPort(options: CreateFrozenReviewRecoveryPortOptions): FrozenReviewRecoveryPort;
80
+ /**
81
+ * Standalone Node 的真实发布装配器:remote capture 的同一 compiler outcome 在共享模块物化,
82
+ * 随后构建双端 View、用 broker key + Rust/WASM 生成 SCNB,再返回 FrozenPublishInput。
83
+ * prepare 不写 mutable module;唯一远端写由 Coordinator 的 attempt POST 执行。
84
+ */
85
+ export declare function createFrozenReviewLocalArtifactAssembler(options: CreateFrozenReviewLocalArtifactAssemblerOptions): FrozenReviewLocalArtifactAssembler;
86
+ /** neutral-transfer 在远端 readback 成功后调用;只持久化公开 identity/digest。 */
87
+ export declare function writeFrozenReviewTargetBinding(root: string, binding: FrozenReviewTargetBinding): Promise<FrozenReviewTargetBinding>;
88
+ export declare function createFrozenReviewLocalPipeline(options: CreateFrozenReviewLocalPipelineOptions): FrozenReviewLocalPipelineWithPreparedEmbedding;