@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,273 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, open, readFile, readdir, rename } from "node:fs/promises";
3
+ import { join, relative } from "node:path";
4
+ import { AuthoringBridgeError, validateAuthoringEffectIdentity } from "@world-engines/authoring-bridge";
5
+ import { validateLocalAuthorProjectManifest } from "@world-engines/project-format";
6
+ import { assertSafeExistingProjectPath, assertSafeProjectParent, ensureSafeProjectDirectory, safeProjectPath } from "./safe-project-path.js";
7
+ function sha256Hex(bytes) {
8
+ return createHash("sha256").update(bytes).digest("hex");
9
+ }
10
+ function jsonDigest(value) {
11
+ return sha256Hex(new TextEncoder().encode(JSON.stringify(value)));
12
+ }
13
+ const VIEW_TOOLCHAIN_DIRECTORY_NAMES = new Set(["node_modules", "dist", ".vite"]);
14
+ function comparePath(left, right) {
15
+ return left < right ? -1 : left > right ? 1 : 0;
16
+ }
17
+ function isNotFound(error) {
18
+ return error.code === "ENOENT";
19
+ }
20
+ async function atomicWriteJson(root, path, stagingRoot, value) {
21
+ await assertSafeExistingProjectPath(root, stagingRoot);
22
+ await assertSafeProjectParent(root, path);
23
+ const bytes = new TextEncoder().encode(`${JSON.stringify(value, null, 2)}\n`);
24
+ const temporaryPath = join(stagingRoot, `transfer-${sha256Hex(bytes)}.tmp`);
25
+ await assertSafeProjectParent(root, temporaryPath);
26
+ const handle = await open(temporaryPath, "wx");
27
+ try {
28
+ await handle.writeFile(bytes);
29
+ await handle.sync();
30
+ }
31
+ finally {
32
+ await handle.close();
33
+ }
34
+ await assertSafeProjectParent(root, path);
35
+ await rename(temporaryPath, path);
36
+ await assertSafeExistingProjectPath(root, path);
37
+ }
38
+ async function readManifest(root) {
39
+ try {
40
+ const path = await safeProjectPath(root, "worldengine.project.json");
41
+ return validateLocalAuthorProjectManifest(JSON.parse(await readFile(await assertSafeExistingProjectPath(root, path), "utf8")));
42
+ }
43
+ catch (error) {
44
+ if (error instanceof AuthoringBridgeError)
45
+ throw error;
46
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "worldengine.project.json 无效", { cause: error instanceof Error ? error.message : String(error) });
47
+ }
48
+ }
49
+ async function collectAuthorityFiles(root, directory, entries, excludedDirectoryNames) {
50
+ const canonicalDirectory = await assertSafeExistingProjectPath(root, directory);
51
+ const children = await readdir(canonicalDirectory, { withFileTypes: true });
52
+ children.sort((left, right) => comparePath(left.name, right.name));
53
+ for (const child of children) {
54
+ if (excludedDirectoryNames?.has(child.name))
55
+ continue;
56
+ const path = join(canonicalDirectory, child.name);
57
+ const info = await lstat(path);
58
+ if (info.isSymbolicLink()) {
59
+ throw new AuthoringBridgeError("E_PATH_OUTSIDE_PROJECT", `拒绝符号链接、junction 或 reparse point:${path}`);
60
+ }
61
+ if (info.isDirectory()) {
62
+ await collectAuthorityFiles(root, path, entries, excludedDirectoryNames);
63
+ continue;
64
+ }
65
+ if (!info.isFile())
66
+ continue;
67
+ const canonical = await assertSafeExistingProjectPath(root, path);
68
+ const bytes = new Uint8Array(await readFile(canonical));
69
+ entries.push({
70
+ project_relative_path: relative(root, canonical).replaceAll("\\", "/"),
71
+ sha256: sha256Hex(bytes),
72
+ bytes: bytes.byteLength,
73
+ });
74
+ }
75
+ }
76
+ /**
77
+ * 只枚举 manifest、Scenario Part 与 View Part;toolchain、认证态、缓存、日志、staging 和 operation ledger 不属于可传输 authority。
78
+ */
79
+ export async function buildLocalProjectTransferInventory(root, expectedProjectRevision) {
80
+ const canonicalRoot = await assertSafeExistingProjectPath(root, root);
81
+ const manifest = await readManifest(canonicalRoot);
82
+ if (expectedProjectRevision !== undefined && expectedProjectRevision !== manifest.project_revision) {
83
+ throw new AuthoringBridgeError("E_STALE_PROJECT_REVISION", `期望 revision ${expectedProjectRevision},当前 ${manifest.project_revision}`);
84
+ }
85
+ const manifestPath = await safeProjectPath(canonicalRoot, "worldengine.project.json");
86
+ const canonicalManifestPath = await assertSafeExistingProjectPath(canonicalRoot, manifestPath);
87
+ const manifestBytes = new Uint8Array(await readFile(canonicalManifestPath));
88
+ const entries = [{
89
+ project_relative_path: "worldengine.project.json",
90
+ sha256: sha256Hex(manifestBytes),
91
+ bytes: manifestBytes.byteLength,
92
+ }];
93
+ // Scenario 的所有普通文件都是 source authority;只有 View 会承载本地 toolchain 目录。
94
+ await collectAuthorityFiles(canonicalRoot, join(canonicalRoot, "scenario"), entries);
95
+ await collectAuthorityFiles(canonicalRoot, join(canonicalRoot, "view"), entries, VIEW_TOOLCHAIN_DIRECTORY_NAMES);
96
+ entries.sort((left, right) => comparePath(left.project_relative_path, right.project_relative_path));
97
+ return {
98
+ schema_version: 1,
99
+ project_id: manifest.project_id,
100
+ project_revision: manifest.project_revision,
101
+ entries,
102
+ };
103
+ }
104
+ async function canonicalInventory(root, manifest, request) {
105
+ if (request.inventory.schema_version !== 1 || request.inventory.project_id !== manifest.project_id) {
106
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "transfer inventory project_id 必须匹配本地 manifest");
107
+ }
108
+ if (request.expected_project_revision !== manifest.project_revision) {
109
+ throw new AuthoringBridgeError("E_STALE_PROJECT_REVISION", `期望 revision ${request.expected_project_revision},当前 ${manifest.project_revision}`);
110
+ }
111
+ if (!Array.isArray(request.inventory.entries)) {
112
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "transfer inventory entries 必须为数组");
113
+ }
114
+ // 请求中的 inventory 不是 authority,但仍拒绝其携带的越界路径,避免将未受信任输入悄悄接受为有效 transfer 请求。
115
+ await Promise.all(request.inventory.entries.map((entry) => safeProjectPath(root, entry.project_relative_path)));
116
+ const local = await buildLocalProjectTransferInventory(root, manifest.project_revision);
117
+ return { schema_version: 1, project_id: local.project_id, entries: local.entries };
118
+ }
119
+ function assertVerifiedReceipt(receipt, operationId) {
120
+ if (receipt.operation_id !== operationId || !receipt.remote_transfer_id || !receipt.readback_verified) {
121
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "remote transfer 未返回权威读回回执");
122
+ }
123
+ }
124
+ function statusFailure(error) {
125
+ return new AuthoringBridgeError(error instanceof AuthoringBridgeError ? error.code : "E_VALIDATION_FAILED", "本地同步状态不可用");
126
+ }
127
+ /** 本地 Project 与真实远端 adapter 之间的 fail-closed 边界;不包含 MCP 或网络 fallback。 */
128
+ export class ProjectTransferCoordinator {
129
+ root;
130
+ #remoteAdapter;
131
+ constructor(root, options) {
132
+ this.root = root;
133
+ this.#remoteAdapter = options.remote_adapter;
134
+ }
135
+ static async open(root, options = {}) {
136
+ return new ProjectTransferCoordinator(await assertSafeExistingProjectPath(root, root), options);
137
+ }
138
+ async availability() {
139
+ if (this.#remoteAdapter === undefined)
140
+ return { state: "unavailable", detail: "未配置真实 remote transfer adapter" };
141
+ if ((await this.#remoteAdapter.authState()) === "signed_out")
142
+ return { state: "signed_out", detail: "当前 identity 未登录" };
143
+ return { state: "ready", detail: "已认证的 remote transfer adapter 可用" };
144
+ }
145
+ async inventory() {
146
+ return buildLocalProjectTransferInventory(this.root);
147
+ }
148
+ async status() {
149
+ try {
150
+ const inventory = await this.inventory();
151
+ const remoteAdapter = await this.availability();
152
+ const operationsPath = await safeProjectPath(this.root, ".worldengine/operations");
153
+ let pendingDurableLedgerCount = 0;
154
+ try {
155
+ const canonicalOperationsPath = await assertSafeExistingProjectPath(this.root, operationsPath);
156
+ const operations = await readdir(canonicalOperationsPath, { withFileTypes: true });
157
+ for (const operation of operations) {
158
+ if (!operation.name.startsWith("transfer-") || !operation.name.endsWith(".json"))
159
+ continue;
160
+ const path = join(canonicalOperationsPath, operation.name);
161
+ const info = await lstat(path);
162
+ if (info.isSymbolicLink()) {
163
+ throw new AuthoringBridgeError("E_PATH_OUTSIDE_PROJECT", "transfer operation ledger 路径无效");
164
+ }
165
+ if (!info.isFile()) {
166
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "transfer operation ledger 不是普通文件");
167
+ }
168
+ const ledger = JSON.parse(await readFile(await assertSafeExistingProjectPath(this.root, path), "utf8"));
169
+ if (ledger.execution_state === "prepared" || ledger.execution_state === "in_flight")
170
+ pendingDurableLedgerCount += 1;
171
+ else if (ledger.execution_state !== "completed") {
172
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "transfer operation ledger 状态无效");
173
+ }
174
+ }
175
+ }
176
+ catch (error) {
177
+ if (!isNotFound(error))
178
+ throw error;
179
+ }
180
+ return {
181
+ schema_version: 1,
182
+ project_id: inventory.project_id,
183
+ project_revision: inventory.project_revision,
184
+ inventory_digest: jsonDigest({ schema_version: inventory.schema_version, project_id: inventory.project_id, entries: inventory.entries }),
185
+ inventory_entry_count: inventory.entries.length,
186
+ pending_durable_ledger_count: pendingDurableLedgerCount,
187
+ auth_state: remoteAdapter.state === "ready" ? "authenticated" : remoteAdapter.state,
188
+ remote_adapter: remoteAdapter,
189
+ };
190
+ }
191
+ catch (error) {
192
+ throw statusFailure(error);
193
+ }
194
+ }
195
+ async prepare(request) {
196
+ validateAuthoringEffectIdentity(request);
197
+ const requestDigest = jsonDigest(request);
198
+ const operationKey = sha256Hex(new TextEncoder().encode(request.operation_id));
199
+ await ensureSafeProjectDirectory(this.root, ".worldengine/operations");
200
+ await ensureSafeProjectDirectory(this.root, ".worldengine/staging");
201
+ await ensureSafeProjectDirectory(this.root, ".worldengine/preimages");
202
+ const ledgerPath = await safeProjectPath(this.root, `.worldengine/operations/transfer-${operationKey}.json`);
203
+ try {
204
+ const ledger = JSON.parse(await readFile(await assertSafeExistingProjectPath(this.root, ledgerPath), "utf8"));
205
+ if (ledger.request_digest !== requestDigest)
206
+ throw new AuthoringBridgeError("E_OPERATION_ID_CONFLICT", "同一 transfer operation_id 使用了不同请求");
207
+ return ledger.plan;
208
+ }
209
+ catch (error) {
210
+ if (error.code !== "ENOENT")
211
+ throw error;
212
+ }
213
+ const manifest = await readManifest(this.root);
214
+ const inventory = await canonicalInventory(this.root, manifest, request);
215
+ const stagingPath = await safeProjectPath(this.root, `.worldengine/staging/transfer-${operationKey}`);
216
+ const preimagePath = await safeProjectPath(this.root, `.worldengine/preimages/transfer-${operationKey}.json`);
217
+ const plan = {
218
+ schema_version: 1,
219
+ operation_id: request.operation_id,
220
+ request_digest: requestDigest,
221
+ plan_digest: jsonDigest({ request_digest: requestDigest, manifest_project_revision: manifest.project_revision, inventory }),
222
+ inventory,
223
+ staging_path: stagingPath,
224
+ preimage_path: preimagePath,
225
+ };
226
+ await ensureSafeProjectDirectory(this.root, `.worldengine/staging/transfer-${operationKey}`);
227
+ await atomicWriteJson(this.root, preimagePath, await safeProjectPath(this.root, ".worldengine/staging"), plan);
228
+ const ledger = { schema_version: 1, request_digest: requestDigest, plan, execution_state: "prepared" };
229
+ await atomicWriteJson(this.root, ledgerPath, await safeProjectPath(this.root, ".worldengine/staging"), ledger);
230
+ return plan;
231
+ }
232
+ async execute(plan, confirmation) {
233
+ if (confirmation?.operation_id !== plan.operation_id || confirmation.plan_digest !== plan.plan_digest) {
234
+ throw new AuthoringBridgeError("E_CONFIRMATION_REQUIRED", "transfer 需要匹配当前计划的明确确认");
235
+ }
236
+ const availability = await this.availability();
237
+ if (availability.state !== "ready")
238
+ throw new AuthoringBridgeError("E_AUTH_REQUIRED", availability.detail, { state: availability.state });
239
+ const operationKey = sha256Hex(new TextEncoder().encode(plan.operation_id));
240
+ const ledgerPath = await safeProjectPath(this.root, `.worldengine/operations/transfer-${operationKey}.json`);
241
+ const ledger = JSON.parse(await readFile(await assertSafeExistingProjectPath(this.root, ledgerPath), "utf8"));
242
+ if (ledger.plan.plan_digest !== plan.plan_digest || ledger.plan.request_digest !== plan.request_digest) {
243
+ throw new AuthoringBridgeError("E_OPERATION_ID_CONFLICT", "transfer 计划与 operation ledger 不一致");
244
+ }
245
+ if (ledger.receipt !== undefined)
246
+ return ledger.receipt;
247
+ const stagingRoot = await safeProjectPath(this.root, ".worldengine/staging");
248
+ const inFlight = { ...ledger, execution_state: "in_flight" };
249
+ await atomicWriteJson(this.root, ledgerPath, stagingRoot, inFlight);
250
+ const reconciled = await this.#remoteAdapter.reconcile(plan.operation_id);
251
+ if (reconciled.state === "completed") {
252
+ assertVerifiedReceipt(reconciled.receipt, plan.operation_id);
253
+ await atomicWriteJson(this.root, ledgerPath, stagingRoot, { ...inFlight, execution_state: "completed", receipt: reconciled.receipt });
254
+ return reconciled.receipt;
255
+ }
256
+ const receipt = await this.#remoteAdapter.execute(inFlight.plan);
257
+ assertVerifiedReceipt(receipt, plan.operation_id);
258
+ await atomicWriteJson(this.root, ledgerPath, stagingRoot, { ...inFlight, execution_state: "completed", receipt });
259
+ return receipt;
260
+ }
261
+ async reconcile(operationId) {
262
+ if (typeof operationId !== "string" || operationId.trim() === "")
263
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "operation_id 不能为空");
264
+ const availability = await this.availability();
265
+ if (availability.state !== "ready")
266
+ return availability;
267
+ const result = await this.#remoteAdapter.reconcile(operationId);
268
+ if (result.state === "not_found")
269
+ return result;
270
+ assertVerifiedReceipt(result.receipt, operationId);
271
+ return result;
272
+ }
273
+ }
@@ -0,0 +1,121 @@
1
+ import { type ExecuteTmw3AuthoringOutcome } from "@world-engines/tmw/authoring-runtime";
2
+ import { type TmwJsonValue } from "@world-engines/tmw/runtime-trace";
3
+ import { type TriggerDocumentV3 } from "@world-engines/tmw";
4
+ /** ProjectHost 的受控试玩 state executor;不暴露 Ladybug Connection 或任意 statement。 */
5
+ export declare class ProjectTriggerRuntime {
6
+ #private;
7
+ authoringCapabilities(): {
8
+ capability_registry: import("@world-engines/tmw").CanonicalTmwCapabilityRegistryV3;
9
+ local_effect_capabilities: ({
10
+ capability_id: string;
11
+ argument_schema: {
12
+ type: string;
13
+ additionalProperties: boolean;
14
+ required: string[];
15
+ properties: Record<string, unknown>;
16
+ } | {
17
+ type: string;
18
+ additionalProperties: boolean;
19
+ required: string[];
20
+ properties: Record<string, unknown>;
21
+ } | {
22
+ type: string;
23
+ additionalProperties: boolean;
24
+ required: string[];
25
+ properties: Record<string, unknown>;
26
+ } | {
27
+ type: string;
28
+ additionalProperties: boolean;
29
+ required: string[];
30
+ properties: Record<string, unknown>;
31
+ } | {
32
+ type: string;
33
+ additionalProperties: boolean;
34
+ required: string[];
35
+ properties: Record<string, unknown>;
36
+ } | {
37
+ type: string;
38
+ additionalProperties: boolean;
39
+ required: string[];
40
+ properties: Record<string, unknown>;
41
+ } | {
42
+ type: string;
43
+ additionalProperties: boolean;
44
+ required: string[];
45
+ properties: Record<string, unknown>;
46
+ } | {
47
+ type: string;
48
+ additionalProperties: boolean;
49
+ required: string[];
50
+ properties: Record<string, unknown>;
51
+ } | {
52
+ type: string;
53
+ additionalProperties: boolean;
54
+ required: string[];
55
+ properties: Record<string, unknown>;
56
+ } | {
57
+ type: string;
58
+ additionalProperties: boolean;
59
+ required: string[];
60
+ properties: Record<string, unknown>;
61
+ } | {
62
+ type: string;
63
+ additionalProperties: boolean;
64
+ required: string[];
65
+ properties: Record<string, unknown>;
66
+ };
67
+ } | {
68
+ capability_id: string;
69
+ argument_schema: Readonly<{
70
+ type: "object";
71
+ additionalProperties: false;
72
+ properties: Record<string, unknown>;
73
+ required: string[];
74
+ }> | Readonly<{
75
+ type: "object";
76
+ additionalProperties: false;
77
+ properties: Record<string, unknown>;
78
+ required: string[];
79
+ }> | Readonly<{
80
+ type: "object";
81
+ additionalProperties: false;
82
+ properties: Record<string, unknown>;
83
+ required: string[];
84
+ }> | Readonly<{
85
+ type: "object";
86
+ additionalProperties: false;
87
+ properties: Record<string, unknown>;
88
+ required: string[];
89
+ }> | Readonly<{
90
+ type: "object";
91
+ additionalProperties: false;
92
+ properties: Record<string, unknown>;
93
+ required: string[];
94
+ }>;
95
+ } | {
96
+ capability_id: string;
97
+ argument_schema: Readonly<Record<string, unknown>>;
98
+ } | {
99
+ capability_id: string;
100
+ argument_schema: import("@world-engines/tmw").TmwJsonValue;
101
+ })[];
102
+ };
103
+ /** GUI/Agent 新建 trigger 时的合法空白候选;只返回值,不修改 source。 */
104
+ emptyDocument(): Promise<TriggerDocumentV3>;
105
+ validate(document: unknown): Promise<Readonly<{
106
+ ok: boolean;
107
+ issues: readonly string[];
108
+ document_sha256?: string;
109
+ plan_sha256?: string;
110
+ }>>;
111
+ execute(input: Readonly<{
112
+ document: unknown;
113
+ event: Readonly<{
114
+ type: string;
115
+ payload?: Readonly<Record<string, TmwJsonValue>>;
116
+ id?: string;
117
+ }>;
118
+ state?: TmwJsonValue;
119
+ semantic_predicates?: Readonly<Record<string, "true" | "false" | "unknown">>;
120
+ }>): Promise<ExecuteTmw3AuthoringOutcome>;
121
+ }
@@ -0,0 +1,153 @@
1
+ import { executeTmw3Authoring, validateTmw3Authoring, } from "@world-engines/tmw/authoring-runtime";
2
+ import { canonicalTmwJson, canonicalTmwJsonValue, tmwSha256Hex, } from "@world-engines/tmw/runtime-trace";
3
+ import { getCanonicalTmwCapabilityRegistryV3, runtimeContractHashV3, TMW_EVENT_CAPABILITY_ARGUMENT_SCHEMAS, } from "@world-engines/tmw";
4
+ import { beginTmwBuilder } from "@world-engines/tmw/builder";
5
+ import { canonicalTmwSceneStateV1, reduceTmwSceneV1, tmwSceneOperationFromEffect } from "@world-engines/tmw/scene-state";
6
+ import { validateTmwSceneSourceReferencesV1 } from "@world-engines/tmw/scene-author-source";
7
+ import { initialTmwGameplayStateFromWorldV1, persistentTmwGameplayStateV1, reduceTmwGameplayEffectV1, prepareTmwGameplayEffectsV1, TMW_GAMEPLAY_CAPABILITY_ARGUMENT_SCHEMAS } from "@world-engines/tmw/gameplay-state";
8
+ function object(value, label) {
9
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10
+ throw new Error(`tmw_authoring_${label}_object_required`);
11
+ }
12
+ return structuredClone(value);
13
+ }
14
+ function nonEmptyStringArray(value, label) {
15
+ if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) {
16
+ throw new Error(`tmw_authoring_${label}_string_array_required`);
17
+ }
18
+ return [...value];
19
+ }
20
+ function entityId(effect) {
21
+ if (effect.selector?.op !== "entity_id")
22
+ throw new Error("tmw_authoring_entity_id_selector_required");
23
+ return effect.selector.entity_id;
24
+ }
25
+ function writePath(target, path, value) {
26
+ let cursor = target;
27
+ for (const segment of path.slice(0, -1)) {
28
+ const current = cursor[segment];
29
+ const next = current === undefined ? {} : object(current, "attribute_path");
30
+ cursor[segment] = next;
31
+ cursor = next;
32
+ }
33
+ cursor[path[path.length - 1]] = canonicalTmwJsonValue(value, "authoring_attribute_value");
34
+ }
35
+ /** 既有 world effect 的本地适配器;Scene 注册与语义 hash authority 来自 TMW 核心。 */
36
+ const AUTHORING_EFFECT_HANDLERS = Object.freeze({
37
+ "world.set_attribute": {
38
+ argument_schema: { type: "object", required: ["path"], properties: { path: { type: "array", minItems: 1, items: { type: "string", minLength: 1 } }, value: {} } },
39
+ apply: (entities, _emittedEvents, effect) => {
40
+ const id = entityId(effect);
41
+ const argumentsObject = object(effect.input, "set_attribute_arguments");
42
+ const path = nonEmptyStringArray(argumentsObject.path, "set_attribute_path");
43
+ const entity = object(entities[id] ?? {}, "entity");
44
+ writePath(entity, path, argumentsObject.value ?? null);
45
+ entities[id] = entity;
46
+ },
47
+ },
48
+ "world.emit_event": {
49
+ argument_schema: { type: "object" },
50
+ apply: (_entities, emittedEvents, effect) => { emittedEvents.push(canonicalTmwJsonValue(effect.input, "emitted_event")); },
51
+ },
52
+ });
53
+ /** ProjectHost 的受控试玩 state executor;不暴露 Ladybug Connection 或任意 statement。 */
54
+ export class ProjectTriggerRuntime {
55
+ authoringCapabilities() {
56
+ return {
57
+ capability_registry: getCanonicalTmwCapabilityRegistryV3(),
58
+ local_effect_capabilities: [...Object.entries(TMW_GAMEPLAY_CAPABILITY_ARGUMENT_SCHEMAS).map(([capability_id, argument_schema]) => ({ capability_id, argument_schema })), ...Object.entries(TMW_EVENT_CAPABILITY_ARGUMENT_SCHEMAS).map(([capability_id, argument_schema]) => ({ capability_id, argument_schema })), ...Object.entries(AUTHORING_EFFECT_HANDLERS).map(([capability_id, handler]) => ({
59
+ capability_id,
60
+ argument_schema: structuredClone(handler.argument_schema),
61
+ })), ...getCanonicalTmwCapabilityRegistryV3().capabilities.filter((item) => item.capability_id.startsWith("scene.")).map((item) => ({ capability_id: item.capability_id, argument_schema: structuredClone(item.argument_schema) }))],
62
+ };
63
+ }
64
+ #effectPort = {
65
+ stage: async (input) => {
66
+ const state = object(input.state, "state");
67
+ const entities = object(state.entities ?? {}, "state_entities");
68
+ const emittedEvents = Array.isArray(state.emitted_events)
69
+ ? [...state.emitted_events]
70
+ : [];
71
+ state.entities = entities;
72
+ state.emitted_events = emittedEvents;
73
+ let gameplay = initialTmwGameplayStateFromWorldV1(state);
74
+ let gameplayTouched = state.gameplayState !== undefined;
75
+ for (const plan of input.plans) {
76
+ if (plan.status !== "ready")
77
+ continue;
78
+ for (const effect of plan.effects) {
79
+ const guarded = prepareTmwGameplayEffectsV1(gameplay, [effect]);
80
+ if (guarded.effects.length === 0)
81
+ continue;
82
+ const gameplayNext = reduceTmwGameplayEffectV1(gameplay, effect.capabilityId, effect.input);
83
+ if (gameplayNext !== null) {
84
+ gameplay = gameplayNext;
85
+ gameplayTouched = true;
86
+ continue;
87
+ }
88
+ if (guarded.state !== gameplay) {
89
+ gameplay = guarded.state;
90
+ gameplayTouched = true;
91
+ }
92
+ const operation = tmwSceneOperationFromEffect(effect.capabilityId, effect.input);
93
+ if (operation === null)
94
+ this.#applyEffect(entities, emittedEvents, effect);
95
+ else {
96
+ const current = state.scene === null || state.scene === undefined ? null : canonicalTmwSceneStateV1(state.scene);
97
+ const next = reduceTmwSceneV1(current, operation, `${input.operation_id}:${effect.effectId}`);
98
+ const references = object(state.scene_source_references ?? null, "scene_source_references");
99
+ validateTmwSceneSourceReferencesV1(next, references);
100
+ state.scene = next;
101
+ }
102
+ }
103
+ }
104
+ if (gameplayTouched)
105
+ state.gameplayState = persistentTmwGameplayStateV1(gameplay);
106
+ const canonicalState = canonicalTmwJsonValue(state, "authoring_state_readback");
107
+ return Object.freeze({
108
+ state: canonicalState,
109
+ receipt_digest: await tmwSha256Hex(canonicalTmwJson({
110
+ operationId: input.operation_id,
111
+ eventId: input.event.eventId,
112
+ state: canonicalState,
113
+ })),
114
+ });
115
+ },
116
+ };
117
+ /** GUI/Agent 新建 trigger 时的合法空白候选;只返回值,不修改 source。 */
118
+ async emptyDocument() {
119
+ const registry = getCanonicalTmwCapabilityRegistryV3();
120
+ const built = await beginTmwBuilder({
121
+ runtime_contract_hash: await runtimeContractHashV3(),
122
+ registry,
123
+ }).build();
124
+ if (!built.ok)
125
+ throw new Error(`tmw_authoring_empty_document_invalid:${built.diagnostics.map((item) => item.code).join(",")}`);
126
+ return built.value;
127
+ }
128
+ async validate(document) {
129
+ const result = await validateTmw3Authoring(document);
130
+ if (!result.ok)
131
+ return Object.freeze({ ok: false, issues: result.diagnostics.map((item) => `${item.code}:${item.path}`) });
132
+ return Object.freeze({ ok: true, issues: Object.freeze([]), document_sha256: result.document_sha256, plan_sha256: result.plan_sha256 });
133
+ }
134
+ execute(input) {
135
+ return executeTmw3Authoring({
136
+ document: input.document,
137
+ event: {
138
+ event_type: input.event.type,
139
+ payload: input.event.payload ?? {},
140
+ ...(input.event.id === undefined ? {} : { event_id: input.event.id }),
141
+ },
142
+ initial_state: input.state ?? { entities: {}, emitted_events: [] },
143
+ ...(input.semantic_predicates === undefined ? {} : { semantic_predicates: input.semantic_predicates }),
144
+ effect_port: this.#effectPort,
145
+ });
146
+ }
147
+ #applyEffect(entities, emittedEvents, effect) {
148
+ const handler = Object.hasOwn(AUTHORING_EFFECT_HANDLERS, effect.capabilityId) ? AUTHORING_EFFECT_HANDLERS[effect.capabilityId] : undefined;
149
+ if (handler === undefined)
150
+ throw new Error(`tmw_authoring_effect_capability_forbidden:${effect.capabilityId}`);
151
+ handler.apply(entities, emittedEvents, effect);
152
+ }
153
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "wasm_core.js": "7ee6ad38b7a265bf1684a83f791e1e3a183f5e5010e2e1592dddfdf99ba72ff4",
3
+ "wasm_core_bg.wasm": "4ffe27b5576d3f10a9e2b37a1fec7f741655e43fb9cfc9329005ec4201d083d1",
4
+ "wasm_core.d.ts": "5f8dc1320a5c7267aacafe65ff6c6b88e908124d8609f7ec0163211733e85486"
5
+ }