@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,553 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { link, readFile, unlink, writeFile } from "node:fs/promises";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
5
+ import { CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS, InMemoryCompilerStageCache, buildCurrentTmw3NeutralCompilerStageRegistry, canonicalTmwBytes, canonicalTmwJsonValue, decodeRuntimeSavSectionSelfBound, compileNeutralScenarioSource, createTmwRuntimeHost, emptyTmwGameplayStateV1, getCanonicalTmwCapabilityRegistryV3, initialTmwSceneFromMetadata, readTmwUnlockV1, readTmwVariableV1, tmwSha256Hex, tmwActivationCandidateDigest, } from "@world-engines/tmw";
6
+ import { captureLocalPreviewSource } from "./local-preview-source.js";
7
+ import { ensureSafeProjectDirectory, safeProjectPath } from "./safe-project-path.js";
8
+ import { createLocalPreviewWorldProjection, reduceLocalPreviewWorldEffects } from "./local-preview-world-effects.js";
9
+ import { LocalPreviewWorldlineStore } from "./local-preview-worldline-store.js";
10
+ import { LocalPreviewViewStateSqlitePort } from "./local-preview-view-state-sqlite.js";
11
+ import { LocalPreviewMessageStore } from "./local-preview-message-store.js";
12
+ import { LocalPreviewPlayerReadModels } from "./local-preview-player-read-models.js";
13
+ import { localPreviewBoundResourcesFromLogicalGraph } from "./local-preview-bound-resources.js";
14
+ const ZERO_SHA256 = "0".repeat(64);
15
+ const SAVE_SCHEMA_VERSION = 1;
16
+ /**
17
+ * 本地开发预览的单实例 Runtime seam。
18
+ * 它只消费 immutable capture,SAV 也只写入 capture-hash 隔离目录;不是正式 Player 保存、head、lease 或发布 authority。
19
+ */
20
+ export class LocalPreviewRuntime {
21
+ #capture;
22
+ #compiledPlan;
23
+ #runtime;
24
+ #savePath;
25
+ #receiptSha256;
26
+ #store;
27
+ #writerPreparations = new Map();
28
+ constructor(input) {
29
+ this.#capture = input.capture;
30
+ this.#compiledPlan = input.compiledPlan;
31
+ this.#runtime = input.runtime;
32
+ this.#savePath = input.savePath;
33
+ this.#receiptSha256 = input.receiptSha256;
34
+ this.#store = input.store;
35
+ }
36
+ static async open(input) {
37
+ const capturedNow = await captureLocalPreviewSource({ project_root: input.project_root });
38
+ if (input.capture !== undefined && input.capture.identity.capture_sha256 !== capturedNow.identity.capture_sha256) {
39
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "外部 local Preview capture 与当前 Project immutable capture 不一致");
40
+ }
41
+ const capture = capturedNow;
42
+ const profile = Object.freeze({
43
+ schema_version: 1,
44
+ compiler_release: "tmw.compiler.semantic-v2",
45
+ runtime_semantics: "tmw3.semantic-v2",
46
+ sdk_schema: "chatplay.v3",
47
+ artifact_format: "artifact.v5",
48
+ stage_versions: CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS,
49
+ });
50
+ const compilation = await compileNeutralScenarioSource({
51
+ witness: { scenario_id: capture.identity.local_scenario_id, manifest_digest: capture.identity.neutral_manifest_sha256 },
52
+ manifest: capture.manifest,
53
+ sections: capture.sections,
54
+ }, profile, buildCurrentTmw3NeutralCompilerStageRegistry(profile), new InMemoryCompilerStageCache());
55
+ if (!compilation.ok) {
56
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", `本地 Preview 编译失败:${compilation.diagnostics.map((item) => item.code).join(",")}`);
57
+ }
58
+ const trigger = compilation.derived_objects.find((object) => object.stage_id === "tmw.trigger-semantics");
59
+ if (trigger === undefined)
60
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview 缺少 TMW compiled plan");
61
+ let compiledPlan;
62
+ try {
63
+ compiledPlan = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(trigger.bytes));
64
+ }
65
+ catch {
66
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview TMW compiled plan 无效");
67
+ }
68
+ if (compiledPlan.plan_version !== 3 || await tmwSha256Hex(trigger.bytes) !== trigger.sha256) {
69
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview compiled plan 未绑定 stage 输出");
70
+ }
71
+ const directory = await ensureSafeProjectDirectory(input.project_root, `.worldengine/local-player-preview/${capture.identity.capture_sha256}`);
72
+ const savePath = await safeProjectPath(input.project_root, `.worldengine/local-player-preview/${capture.identity.capture_sha256}/runtime-sav.v1.json`);
73
+ const initial = await createInitialSav(capture, compiledPlan);
74
+ const binding = planBinding(capture, compiledPlan);
75
+ let stored = await readSavedEnvelope(savePath, capture.identity.capture_sha256, binding);
76
+ if (stored === null) {
77
+ const logicalGraph = previewSectionJson(capture, "logical_graph");
78
+ const bound = localPreviewBoundResourcesFromLogicalGraph(logicalGraph);
79
+ const worldProjection = createLocalPreviewWorldProjection(logicalGraph);
80
+ await writeSavedEnvelope(input.project_root, directory, savePath, capture.identity.capture_sha256, { sav: initial, projection: Object.freeze({ ...worldProjection, frozen_attribute_resource_refs: Object.freeze(bound.frozen_attribute_resource_refs.map((ref) => Object.freeze({ ...ref, visibility: "unlocked" }))), scenario_runtime_state: Object.freeze({ ...worldProjection.scenario_runtime_state, unlocked_resource_ids: bound.initial_unlocked_resource_ids }) }), receipts: Object.freeze({}) });
81
+ stored = await readSavedEnvelope(savePath, capture.identity.capture_sha256, binding);
82
+ if (stored === null)
83
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview SAV 创建后丢失");
84
+ }
85
+ const store = LocalPreviewActivationStore.open(directory, stored, capture.identity.capture_sha256);
86
+ const sav = store.currentSav();
87
+ const runtime = createTmwRuntimeHost({
88
+ binding: {
89
+ epoch: sav.epoch,
90
+ runtimeGeneration: 3,
91
+ runtimeContractHash: sav.runtimeContractHash,
92
+ artifactDigest: sav.artifactDigest,
93
+ sourceRevision: sav.sourceRevision,
94
+ timelineRevision: sav.activeTimelineRevision,
95
+ leaseId: `local.preview.${capture.identity.capture_sha256.slice(0, 24)}`,
96
+ },
97
+ compiledPlan,
98
+ capabilityRegistry: getCanonicalTmwCapabilityRegistryV3(),
99
+ });
100
+ runtime.restore(sav);
101
+ store.configureWorldlines(Object.freeze({ authority: "worldengine.local-preview-worldlines/v1", authorNamespace: "local-preview", projectId: capture.identity.project_id, scenarioId: capture.identity.local_scenario_id, saveId: `local.preview.${capture.identity.capture_sha256.slice(0, 24)}`, captureSha256: capture.identity.capture_sha256 }));
102
+ store.configureMessages(Object.freeze({ captureId: capture.identity.capture_sha256, saveId: `local.preview.${capture.identity.capture_sha256.slice(0, 24)}`, worldlineScope: Object.freeze({ authority: "worldengine.local-preview-worldlines/v1", authorNamespace: "local-preview", projectId: capture.identity.project_id, scenarioId: capture.identity.local_scenario_id, saveId: `local.preview.${capture.identity.capture_sha256.slice(0, 24)}`, captureSha256: capture.identity.capture_sha256 }) }));
103
+ return new LocalPreviewRuntime({ capture, compiledPlan, runtime, savePath, receiptSha256: compilation.receipt.receipt_sha256, store });
104
+ }
105
+ inspect() {
106
+ return Object.freeze({ development_preview: true, capture_sha256: this.#capture.identity.capture_sha256, project_id: this.#capture.identity.project_id, scenario_id: this.#capture.identity.local_scenario_id, compiler_receipt_sha256: this.#receiptSha256, compiled_plan_sha256: this.#compiledPlan.plan_sha256, save_path: this.#savePath, event_execution: "local_deterministic" });
107
+ }
108
+ readScene() { return this.#sav().scene; }
109
+ readVariable(name, scope) { return readTmwVariableV1(this.#sav().gameplayState ?? emptyTmwGameplayStateV1(), name, scope); }
110
+ readUnlock(kind, id) { return readTmwUnlockV1(this.#sav().gameplayState ?? emptyTmwGameplayStateV1(), kind, id); }
111
+ readViewInteractionBinding() {
112
+ const sav = this.#sav();
113
+ return structuredClone(Object.freeze({ source: previewSectionJson(this.#capture, "layout"), epoch: sav.epoch, artifact_digest: sav.artifactDigest, runtime_contract_hash: sav.runtimeContractHash }));
114
+ }
115
+ async openViewState(viewId) {
116
+ const port = this.#store.openViewState(Object.freeze({ captureId: this.#capture.identity.capture_sha256, namespace: "chatplay.custom-state.v1", saveId: `local.preview.${this.#capture.identity.capture_sha256.slice(0, 24)}`, viewId: requireId(viewId, "view_id") }));
117
+ await port.initialize(Object.freeze({ captureId: this.#capture.identity.capture_sha256, namespace: "chatplay.custom-state.v1", saveId: `local.preview.${this.#capture.identity.capture_sha256.slice(0, 24)}`, viewId }));
118
+ return port;
119
+ }
120
+ async appendAcceptedUserMessage(input) {
121
+ const sav = this.#sav();
122
+ return await this.#store.messages().appendAcceptedUserMessage({ operationId: requireId(input.operation_id, "user operation_id"), turnId: requireId(input.turn_id, "user turn_id"), messageId: requireId(input.message_id, "user message_id"), text: input.text, activeWorldlineId: sav.activeTimeline.worldlineId, expectedRuntimeHead: { revisionId: sav.activeTimeline.revisionId, revisionSequence: sav.activeTimeline.revisionSequence, ledgerHead: sav.ledgerHead } });
123
+ }
124
+ async readCommittedMessages() {
125
+ return this.#store.messages().listCurrentWorldline();
126
+ }
127
+ /** 仅恢复指定 operation 仍在当前 SAV 中、且可由 source ledger 精确验证的 View 事件;不扫描历史。 */
128
+ readAuthorViewEventsByOperation(operationIdInput) {
129
+ const operationId = requireId(operationIdInput, "author view operation_id");
130
+ const sav = this.#sav();
131
+ const eventId = operationId.startsWith("tmw3:") || operationId.startsWith("local.preview.event.") ? operationId : `local.preview.event.${operationId}`;
132
+ const recovered = this.#store.readAuthorViewEvents(`tmw3:event:${eventId}`, sav);
133
+ if (recovered === null)
134
+ throw new AuthoringBridgeError("E_REFERENCE_MISSING", "指定 operation 没有已提交的 author View delivery");
135
+ return recovered;
136
+ }
137
+ playerReadModels() { return new LocalPreviewPlayerReadModels({ readCommittedPlayerReadModel: async () => { const projection = this.#store.currentProjection(); if (!Object.hasOwn(projection, "frozen_attribute_resource_refs"))
138
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "local Preview resource refs 尚未由 source capture 初始化"); return Object.freeze({ scenario_runtime_state: projection.scenario_runtime_state, frozen_attribute_resource_refs: projection.frozen_attribute_resource_refs }); } }); }
139
+ /** Host-only facade; it reuses activation.sqlite and never exposes DatabaseSync to iframe code. */
140
+ worldlineRuntime() {
141
+ const store = this.#store.openWorldlines(this.#worldlineScope());
142
+ return Object.freeze({
143
+ snapshotWorldlines: () => store.snapshotWorldlines(), listWorldlines: (scope) => store.listWorldlines(scope),
144
+ forkWorldlineAtMessage: async (scope, input) => { const result = await store.forkWorldlineAtMessage(scope, input); await this.#restoreCheckedOutWorldline(); return result; },
145
+ activateWorldline: async (scope, input) => { const result = await store.activateWorldline(scope, input); await this.#restoreCheckedOutWorldline(); return result; },
146
+ });
147
+ }
148
+ async prepareWriterTurn(input) {
149
+ const operationId = requireId(input.operation_id, "writer operation_id");
150
+ const turnId = requireId(input.turn_id, "writer turn_id");
151
+ const sav = this.#sav();
152
+ const core = { operation_id: operationId, turn_id: turnId, capture_sha256: this.#capture.identity.capture_sha256, timeline_revision: sav.activeTimelineRevision, ledger_head: sav.ledgerHead, worldline_id: sav.activeTimeline.worldlineId, world_time: sav.scene?.worldTime ?? null };
153
+ const prepared = Object.freeze({ ...core, identity: await tmwSha256Hex(canonicalTmwBytes(core)), scene: structuredClone(sav.scene) });
154
+ this.#writerPreparations.set(prepared.identity, prepared);
155
+ return prepared;
156
+ }
157
+ async commitSuccessfulWriterResult(input) {
158
+ const prepared = this.#writerPreparations.get(input.prepare_identity);
159
+ if (prepared === undefined)
160
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "writer preparation 不存在或已失效");
161
+ if (typeof input.text !== "string" || input.text.length === 0 || input.text.length > 64 * 1024)
162
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "writer text 无效");
163
+ const messageId = requireId(input.message_id, "writer message_id");
164
+ if (!Array.isArray(input.ordered_message_ids) || input.ordered_message_ids.at(-1) !== messageId)
165
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "writer message 顺序无效");
166
+ const requestDigest = await tmwSha256Hex(canonicalTmwBytes({ prepared: input.prepare_identity, text: input.text, messageId, ordered: input.ordered_message_ids, writerReceiptDigest: input.writer_receipt_digest }));
167
+ const existing = await this.#store.prepareRequest(prepared.operation_id, requestDigest);
168
+ if (existing !== null)
169
+ return Object.freeze({ status: "already_committed", message_id: messageId });
170
+ const sav = this.#sav();
171
+ if (sav.activeTimelineRevision !== prepared.timeline_revision || sav.ledgerHead !== prepared.ledger_head)
172
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "writer preparation head 已改变");
173
+ const noteConsumption = sav.scene === null ? null : Object.freeze({ sceneRevision: sav.scene.revision, noteIds: sav.scene.manuscriptNotes.filter((note) => note.lifetime === "once").map((note) => note.noteId) });
174
+ this.#store.stageProjection(prepared.operation_id, this.#store.currentProjection());
175
+ this.#store.stageWriterCommit(prepared.operation_id, { messageId, turnId: prepared.turn_id, text: input.text, orderedMessageIds: input.ordered_message_ids });
176
+ try {
177
+ const result = await this.#runtime.commitSuccessfulWriterTurn({ operationId: prepared.operation_id, turnId: prepared.turn_id, worldlineId: prepared.worldline_id, logicalSeq: sav.activeTimeline.revisionSequence + 1, worldTime: prepared.world_time, writerReceiptDigest: input.writer_receipt_digest, noteConsumption, pendingClockCrossings: input.pending_clock_crossings ?? Object.freeze([]) }, this.#store);
178
+ return Object.freeze({ status: result.status, message_id: messageId });
179
+ }
180
+ catch (error) {
181
+ this.#store.cancelPreparedRequest(prepared.operation_id);
182
+ this.#store.rollbackProjection(prepared.operation_id);
183
+ if (error instanceof AuthoringBridgeError)
184
+ throw error;
185
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", error instanceof Error ? error.message : "writer commit 失败");
186
+ }
187
+ }
188
+ readCommittedWriterMessage(messageId) { return this.#store.readMessage(requireId(messageId, "writer message_id")); }
189
+ /** Host-only typed input for a separate allowlisted View projector; never expose this object directly through iframe RPC. */
190
+ readProjectionSnapshot() {
191
+ const sav = this.#sav();
192
+ const gameplay = sav.gameplayState ?? emptyTmwGameplayStateV1();
193
+ return structuredClone(Object.freeze({
194
+ kind: "worldengine.local-preview-projection/v1", development_preview: true,
195
+ capture_identity: this.#capture.identity, scene: sav.scene,
196
+ gameplay: Object.freeze({
197
+ save_variables: gameplay.variables.filter((item) => item.scope === "save"),
198
+ unlocks: Object.freeze(Object.fromEntries(Object.keys(gameplay.entity_state_stacks).map((key) => [key,
199
+ readTmwUnlockV1(gameplay, key.startsWith("edge:") ? "edge" : "node", key.slice(key.indexOf(":") + 1)),
200
+ ]))),
201
+ object_states: gameplay.entity_state_stacks,
202
+ }),
203
+ world_projection: this.#store.currentProjection(),
204
+ source_projection: Object.freeze({ metadata: previewSectionJson(this.#capture, "metadata"), logical_graph: previewSectionJson(this.#capture, "logical_graph"), resource_refs: previewSectionJson(this.#capture, "resource_refs") }),
205
+ runtime: Object.freeze({ timeline_revision: sav.activeTimelineRevision, ledger_head: sav.ledgerHead }),
206
+ }));
207
+ }
208
+ dispose() { this.#runtime.dispose(); this.#store.dispose(); }
209
+ /** 本地开发 Runtime:无远端 lease/head;同一 operation_id 读取已持久 receipt,不重复执行。 */
210
+ async executeEvent(input) {
211
+ try {
212
+ return await this.#executeEvent(input);
213
+ }
214
+ catch (error) {
215
+ if (error instanceof AuthoringBridgeError)
216
+ throw error;
217
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", error instanceof Error ? error.message : "local Preview event 执行失败");
218
+ }
219
+ }
220
+ /** Host-only:仅消费当前 SAV 已到期的 pending crossing;不创建或推进新的时钟事件。 */
221
+ async drainPendingSceneCrossings(input) {
222
+ const turnId = requireId(input.turn_id, "drain turn_id");
223
+ const maximum = input.max_events ?? 16;
224
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 64)
225
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "drain max_events 无效");
226
+ const receipts = [];
227
+ for (let count = 0; count < maximum; count += 1) {
228
+ const sav = this.#sav();
229
+ const pending = sav.pendingSceneClockCrossings[0];
230
+ if (pending === undefined)
231
+ break;
232
+ const eventId = requireId(pending.eventId, "pending event_id");
233
+ const eventType = requireId(pending.eventType, "pending event_type");
234
+ const payload = canonicalTmwJsonValue(pending.payload, "pending payload");
235
+ const runtimeOperationId = `tmw3:event:${eventId}`;
236
+ const requestDigest = await tmwSha256Hex(canonicalTmwBytes({ eventId, eventType, payload, payloadHash: pending.payloadHash, commitReceiptDigest: pending.commitReceiptDigest, logicalSeq: pending.logicalSeq, worldTime: pending.worldTime, crossingDirection: pending.crossingDirection, turnId }));
237
+ const existing = await this.#store.prepareRequest(runtimeOperationId, requestDigest);
238
+ if (existing !== null) {
239
+ receipts.push(Object.freeze({ status: "already_committed", operation_id: eventId, author_view_events: Object.freeze([]) }));
240
+ break;
241
+ }
242
+ const context = this.#runtime.viewEventCommitContext(eventType, { coordinates: Object.freeze({ logicalSeq: pending.logicalSeq, worldTime: pending.worldTime, presentation: null, realTime: null }), crossingDirection: pending.crossingDirection });
243
+ const record = payload;
244
+ const event = Object.freeze({ eventId, eventType, hardFact: context.hardFact, epoch: sav.epoch, runtimeGeneration: 3, runtimeContractHash: sav.runtimeContractHash, artifactDigest: sav.artifactDigest, sourceRevision: sav.sourceRevision, timelineRevision: sav.activeTimelineRevision, scope: Object.freeze({ scenarioId: this.#capture.identity.local_scenario_id, saveId: `local.preview.${this.#capture.identity.capture_sha256.slice(0, 24)}`, worldlineId: sav.activeTimeline.worldlineId }), coordinates: context.coordinates, effectiveTime: context.effectiveTime, crossingDirection: context.crossingDirection, correlationId: typeof record.correlationId === "string" ? record.correlationId : turnId, causationId: typeof record.causationId === "string" ? record.causationId : null, replacementLineage: Object.freeze([]), payload, payloadHash: pending.payloadHash, commitReceiptDigest: pending.commitReceiptDigest });
245
+ try {
246
+ const receipt = await this.#runtime.executeCommittedEvent(event, this.#ports());
247
+ if (receipt.activation.status === "conflict")
248
+ throw new Error("local_preview_activation_conflict");
249
+ receipts.push(Object.freeze({ status: receipt.activation.status === "already_committed" ? "already_committed" : "committed", operation_id: eventId, author_view_events: receipt.authorViewEvents }));
250
+ }
251
+ catch (error) {
252
+ this.#store.cancelPreparedRequest(runtimeOperationId);
253
+ throw error;
254
+ }
255
+ }
256
+ return Object.freeze(receipts);
257
+ }
258
+ async #executeEvent(input) {
259
+ const eventType = requireId(input.event_type, "event_type");
260
+ const operationId = requireId(input.operation_id, "operation_id");
261
+ const turnId = requireId(input.turn_id, "turn_id");
262
+ const payload = canonicalTmwJsonValue(input.payload, "local_preview_event_payload");
263
+ const sav = this.#sav();
264
+ const worldTime = input.world_time === undefined ? sav.scene?.worldTime ?? null : input.world_time;
265
+ if (worldTime !== null && !Number.isFinite(worldTime))
266
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "local Preview world_time 无效");
267
+ const requestDigest = await tmwSha256Hex(canonicalTmwBytes({ eventType, payload, operationId, turnId, worldTime }));
268
+ const eventId = `local.preview.event.${operationId}`;
269
+ const runtimeOperationId = `tmw3:event:${eventId}`;
270
+ const existing = await this.#store.prepareRequest(runtimeOperationId, requestDigest);
271
+ if (existing !== null)
272
+ return Object.freeze({ status: "already_committed", operation_id: operationId, author_view_events: Object.freeze([]) });
273
+ const context = this.#runtime.viewEventCommitContext(eventType, { coordinates: Object.freeze({ logicalSeq: sav.activeTimeline.revisionSequence, worldTime, presentation: null, realTime: null }), crossingDirection: "forward" });
274
+ const payloadHash = await tmwSha256Hex(canonicalTmwBytes(payload));
275
+ const event = Object.freeze({ eventId, eventType, hardFact: context.hardFact, epoch: sav.epoch, runtimeGeneration: 3, runtimeContractHash: sav.runtimeContractHash, artifactDigest: sav.artifactDigest, sourceRevision: sav.sourceRevision, timelineRevision: sav.activeTimelineRevision, scope: Object.freeze({ scenarioId: this.#capture.identity.local_scenario_id, saveId: `local.preview.${this.#capture.identity.capture_sha256.slice(0, 24)}`, worldlineId: sav.activeTimeline.worldlineId }), coordinates: context.coordinates, effectiveTime: context.effectiveTime, crossingDirection: context.crossingDirection, correlationId: turnId, causationId: null, replacementLineage: Object.freeze([]), payloadHash, payload, commitReceiptDigest: await tmwSha256Hex(canonicalTmwBytes({ operationId, payloadHash })) });
276
+ try {
277
+ const receipt = await this.#runtime.executeCommittedEvent(event, this.#ports());
278
+ if (receipt.activation.status === "conflict")
279
+ throw new Error("local_preview_activation_conflict");
280
+ return Object.freeze({ status: receipt.activation.status === "already_committed" ? "already_committed" : "committed", operation_id: operationId, author_view_events: receipt.authorViewEvents });
281
+ }
282
+ catch (error) {
283
+ this.#store.cancelPreparedRequest(runtimeOperationId);
284
+ throw error;
285
+ }
286
+ }
287
+ #sav() { const sav = this.#runtime.restoredSav(); if (sav === null)
288
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview Runtime 缺少 SAV"); return sav; }
289
+ #worldlineScope() { return Object.freeze({ authority: "worldengine.local-preview-worldlines/v1", authorNamespace: "local-preview", projectId: this.#capture.identity.project_id, scenarioId: this.#capture.identity.local_scenario_id, saveId: `local.preview.${this.#capture.identity.capture_sha256.slice(0, 24)}`, captureSha256: this.#capture.identity.capture_sha256 }); }
290
+ async #restoreCheckedOutWorldline() {
291
+ const sav = this.#store.currentSav();
292
+ const binding = Object.freeze({ epoch: sav.epoch, runtimeGeneration: 3, runtimeContractHash: sav.runtimeContractHash, artifactDigest: sav.artifactDigest, sourceRevision: sav.sourceRevision, timelineRevision: sav.activeTimelineRevision, leaseId: `local.preview.${this.#capture.identity.capture_sha256.slice(0, 24)}` });
293
+ this.#runtime.dispose();
294
+ try {
295
+ const next = createTmwRuntimeHost({ binding, compiledPlan: this.#compiledPlan, capabilityRegistry: getCanonicalTmwCapabilityRegistryV3() });
296
+ next.restore(sav);
297
+ this.#runtime = next;
298
+ this.#writerPreparations.clear();
299
+ }
300
+ catch (error) {
301
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", error instanceof Error ? `worldline checkout restore 失败:${error.message}` : "worldline checkout restore 失败");
302
+ }
303
+ }
304
+ #ports() {
305
+ return Object.freeze({
306
+ semanticJudge: { async judge() { throw new Error("local_preview_semantic_judge_unavailable"); } },
307
+ effectCommit: {
308
+ prepareWorldMutation: async (value) => Object.freeze({ bindingId: `local.preview.effect.${value.operationId}`, operationId: value.operationId, turnId: value.turnId }),
309
+ stage: async (value) => {
310
+ this.#store.stageProjection(value.operationId, reduceLocalPreviewWorldEffects(this.#store.currentProjection(), value.plans));
311
+ return Object.freeze({ preActivationTerminalDigest: await tmwSha256Hex(canonicalTmwBytes({ operationId: value.operationId, eventId: value.event.eventId, effects: value.plans.flatMap((plan) => plan.effects.map((effect) => effect.effectId)) })), worldMutationRecoveryOperation: null });
312
+ }, finalizeWorldMutation: async (value) => { this.#store.finalizeProjection(value.operationId); }, rollbackWorldMutation: async (value) => { this.#store.rollbackProjection(value.operationId); },
313
+ }, activation: this.#store,
314
+ verifiedAuthorViewOutbox: {
315
+ stage: async (value) => { this.#store.stageVerifiedAuthorViewEvents({ operationId: value.operationId, ...value.binding, events: value.events }); },
316
+ rollback: async (value) => { this.#store.rollbackVerifiedAuthorViewEvents(value.operationId); },
317
+ },
318
+ });
319
+ }
320
+ }
321
+ function metadataScene(capture) {
322
+ const metadata = capture.sections.find((section) => section.section_key === "metadata");
323
+ if (metadata === undefined || metadata.payload.byteLength === 0)
324
+ return null;
325
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(metadata.payload));
326
+ const record = value.metadata !== undefined && value.metadata !== null ? value.metadata : value;
327
+ if (typeof record !== "object" || record === null || Array.isArray(record))
328
+ throw new Error("local_preview_metadata_invalid");
329
+ return initialTmwSceneFromMetadata(record);
330
+ }
331
+ function previewSectionJson(capture, sectionKey) {
332
+ const section = capture.sections.find((item) => item.section_key === sectionKey);
333
+ if (section === undefined || section.payload.byteLength === 0)
334
+ return null;
335
+ try {
336
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(section.payload));
337
+ }
338
+ catch {
339
+ return null;
340
+ }
341
+ }
342
+ async function createInitialSav(capture, plan) {
343
+ const [narrativeDigest, emptyDigest] = await Promise.all([tmwSha256Hex(canonicalTmwBytes({})), tmwSha256Hex(canonicalTmwBytes([]))]);
344
+ const sourceRevision = `local.preview.${capture.identity.capture_sha256.slice(0, 24)}`;
345
+ const timelineRevision = `${sourceRevision}.timeline.0`;
346
+ const scene = metadataScene(capture);
347
+ return Object.freeze({
348
+ schemaVersion: 3, epoch: 1, runtimeGeneration: 3, runtimeContractHash: plan.runtime_contract_hash,
349
+ artifactDigest: plan.plan_sha256, sourceRevision, activeTimelineRevision: timelineRevision,
350
+ turnId: "local.preview.fresh", preActivationTerminalDigest: ZERO_SHA256,
351
+ activeTimeline: Object.freeze({ revisionId: timelineRevision, parentRevisionId: null, revisionSequence: 0, epoch: 1, runtimeGeneration: 3, runtimeContractHash: plan.runtime_contract_hash, artifactDigest: plan.plan_sha256, sourceRevision, worldlineId: `${sourceRevision}.worldline`, operationId: "local.preview.fresh", ledgerHead: "0".repeat(64), narrativeDigest, savSectionDigest: ZERO_SHA256, intentSetDigest: emptyDigest, presentationIntentSetDigest: emptyDigest, createdFrom: "fresh", active: true }),
352
+ ledgerHead: "0".repeat(64), eventLedger: Object.freeze({ events: Object.freeze([]) }), narrative: Object.freeze({}), scene,
353
+ gameplayState: emptyTmwGameplayStateV1(), pendingSceneClockCrossings: Object.freeze([]), scheduler: Object.freeze({ cursor: scene === null ? null : Object.freeze({ logicalSeq: 0, worldTime: scene.worldTime, presentation: null, realTime: Object.freeze({}) }), suspensions: Object.freeze([]) }), conditionState: Object.freeze({}), durableIntents: Object.freeze([]), presentationIntents: Object.freeze([]), semanticVerdictRefs: Object.freeze([]), candidateSetRevisions: Object.freeze([]), operationReceiptDigests: Object.freeze([]), worldMutationRecoveryOperations: Object.freeze([]),
354
+ });
355
+ }
356
+ function planBinding(capture, plan) {
357
+ const sourceRevision = `local.preview.${capture.identity.capture_sha256.slice(0, 24)}`;
358
+ return Object.freeze({ runtimeContractHash: plan.runtime_contract_hash, artifactDigest: plan.plan_sha256, sourceRevision });
359
+ }
360
+ function requireId(value, field) {
361
+ if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/u.test(value))
362
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", `local Preview ${field} 无效`);
363
+ return value;
364
+ }
365
+ class LocalPreviewActivationStore {
366
+ #db;
367
+ #captureSha256;
368
+ #pendingRequestDigests = new Map();
369
+ #pendingProjections = new Map();
370
+ #pendingWriters = new Map();
371
+ #pendingAuthorViewEvents = new Map();
372
+ #worldlines = null;
373
+ #messages = null;
374
+ constructor(directory, initial, captureSha256) {
375
+ this.#captureSha256 = captureSha256;
376
+ this.#db = new DatabaseSync(`${directory}/activation.sqlite`);
377
+ this.#db.exec("CREATE TABLE IF NOT EXISTS local_preview_state (key TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS local_preview_receipt (operation_id TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS local_preview_request (operation_id TEXT PRIMARY KEY, digest TEXT NOT NULL); CREATE TABLE IF NOT EXISTS local_preview_message (message_id TEXT PRIMARY KEY, operation_id TEXT UNIQUE NOT NULL, role TEXT NOT NULL CHECK(role='assistant'), text TEXT NOT NULL); CREATE TABLE IF NOT EXISTS local_preview_author_view_outbox (operation_id TEXT PRIMARY KEY, capture_sha256 TEXT NOT NULL, epoch INTEGER NOT NULL, artifact_digest TEXT NOT NULL, source_revision TEXT NOT NULL, timeline_revision TEXT NOT NULL, save_id TEXT NOT NULL, worldline_id TEXT NOT NULL, events_json TEXT NOT NULL);");
378
+ const count = this.#db.prepare("SELECT COUNT(*) AS n FROM local_preview_state").get();
379
+ if (count.n === 0) {
380
+ this.#db.exec("BEGIN IMMEDIATE");
381
+ try {
382
+ for (const [key, value] of Object.entries(stateValues(initial.sav, initial.projection)))
383
+ this.#db.prepare("INSERT INTO local_preview_state(key,value) VALUES (?,?)").run(key, JSON.stringify(value));
384
+ for (const [operationId, receipt] of Object.entries(initial.receipts))
385
+ this.#db.prepare("INSERT INTO local_preview_receipt(operation_id,value) VALUES (?,?)").run(operationId, JSON.stringify(receipt));
386
+ this.#db.exec("COMMIT");
387
+ }
388
+ catch (error) {
389
+ this.#db.exec("ROLLBACK");
390
+ throw error;
391
+ }
392
+ }
393
+ }
394
+ static open(directory, initial, captureSha256) { return new LocalPreviewActivationStore(directory, initial, captureSha256); }
395
+ dispose() { this.#db.close(); }
396
+ currentSav() {
397
+ const row = this.#db.prepare("SELECT value FROM local_preview_state WHERE key='sav'").get();
398
+ if (row === undefined)
399
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview activation store 缺少 SAV");
400
+ return JSON.parse(row.value);
401
+ }
402
+ currentProjection() {
403
+ const row = this.#db.prepare("SELECT value FROM local_preview_state WHERE key='projection'").get();
404
+ if (row === undefined)
405
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview activation store 缺少 world projection");
406
+ return JSON.parse(row.value);
407
+ }
408
+ stageProjection(operationId, projection) { this.#pendingProjections.set(operationId, projection); }
409
+ finalizeProjection(operationId) { this.#pendingProjections.delete(operationId); }
410
+ rollbackProjection(operationId) { this.#pendingProjections.delete(operationId); }
411
+ configureWorldlines(scope) { this.#worldlines = new LocalPreviewWorldlineStore({ database: this.#db, scope }); }
412
+ openWorldlines(scope) { this.#worldlines ??= new LocalPreviewWorldlineStore({ database: this.#db, scope }); return this.#worldlines; }
413
+ configureMessages(scope) { this.#messages = new LocalPreviewMessageStore({ database: this.#db, scope }); }
414
+ messages() { if (this.#messages === null)
415
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "local Preview message store 未配置"); return this.#messages; }
416
+ openViewState(scope) { return new LocalPreviewViewStateSqlitePort({ database: this.#db, scope }); }
417
+ stageWriterCommit(operationId, value) { this.#pendingWriters.set(operationId, value); }
418
+ readMessage(messageId) { const row = this.#db.prepare("SELECT message_id,text FROM local_preview_message WHERE message_id=?").get(messageId); return row === undefined ? null : Object.freeze(row); }
419
+ stageVerifiedAuthorViewEvents(input) { this.#pendingAuthorViewEvents.set(input.operationId, Object.freeze({ ...input, events: Object.freeze(input.events.map((event) => Object.freeze({ ...event, viewVisiblePayload: Object.freeze({ ...event.viewVisiblePayload }) }))) })); }
420
+ rollbackVerifiedAuthorViewEvents(operationId) { this.#pendingAuthorViewEvents.delete(operationId); }
421
+ readAuthorViewEvents(operationId, sav) { const row = this.#db.prepare("SELECT capture_sha256,epoch,artifact_digest,source_revision,timeline_revision,save_id,worldline_id,events_json FROM local_preview_author_view_outbox WHERE operation_id=?").get(operationId); if (row === undefined)
422
+ return null; if (row.capture_sha256 !== this.#captureSha256 || row.epoch !== sav.epoch || row.artifact_digest !== sav.artifactDigest || row.source_revision !== sav.sourceRevision || row.worldline_id !== sav.activeTimeline.worldlineId)
423
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "author View outbox binding 不匹配"); const events = JSON.parse(row.events_json); if (!Array.isArray(events))
424
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "author View outbox 损坏"); return Object.freeze(events); }
425
+ async prepareRequest(operationId, digest) {
426
+ const receipt = await this.readByOperation(operationId);
427
+ if (receipt === null) {
428
+ this.#pendingRequestDigests.set(operationId, digest);
429
+ return null;
430
+ }
431
+ const row = this.#db.prepare("SELECT digest FROM local_preview_request WHERE operation_id=?").get(operationId);
432
+ if (row === undefined || row.digest !== digest)
433
+ throw new AuthoringBridgeError("E_OPERATION_ID_CONFLICT", "local Preview operation_id 已绑定不同请求");
434
+ return receipt;
435
+ }
436
+ cancelPreparedRequest(operationId) { this.#pendingRequestDigests.delete(operationId); }
437
+ async readByOperation(operationId) {
438
+ const row = this.#db.prepare("SELECT value FROM local_preview_receipt WHERE operation_id=?").get(operationId);
439
+ return row === undefined ? null : JSON.parse(row.value);
440
+ }
441
+ async commitAtomic(candidate) {
442
+ const requestDigest = this.#pendingRequestDigests.get(candidate.operationId);
443
+ if (requestDigest === undefined)
444
+ throw new Error("local_preview_operation_not_prepared");
445
+ const decoded = await decodeRuntimeSavSectionSelfBound(candidate.savSection);
446
+ const candidateDigest = await tmwActivationCandidateDigest(candidate);
447
+ this.#db.exec("BEGIN IMMEDIATE");
448
+ try {
449
+ const existing = this.#db.prepare("SELECT value FROM local_preview_receipt WHERE operation_id=?").get(candidate.operationId);
450
+ if (existing !== undefined) {
451
+ this.#db.exec("COMMIT");
452
+ return JSON.parse(existing.value);
453
+ }
454
+ const head = JSON.parse(this.#db.prepare("SELECT value FROM local_preview_state WHERE key='head'").get().value);
455
+ const expected = candidate.expectedHead;
456
+ if (expected === null || expected.revisionId !== head.revisionId || expected.revisionSequence !== head.revisionSequence || expected.ledgerHead !== head.ledgerHead) {
457
+ const receipt = await localReceipt(candidate.operationId, "conflict", expected, head, 0, candidateDigest);
458
+ this.#db.exec("COMMIT");
459
+ return receipt;
460
+ }
461
+ const projection = this.#pendingProjections.get(candidate.operationId);
462
+ if (projection === undefined)
463
+ throw new Error("local_preview_projection_not_staged");
464
+ const progressed = withScenarioRuntimeRevision(projection, candidate.timeline.revisionSequence);
465
+ for (const [key, value] of Object.entries(stateValues(decoded, progressed)))
466
+ this.#db.prepare("INSERT OR REPLACE INTO local_preview_state(key,value) VALUES (?,?)").run(key, JSON.stringify(value));
467
+ const nextHead = Object.freeze({ revisionId: candidate.timeline.revisionId, revisionSequence: candidate.timeline.revisionSequence, ledgerHead: candidate.ledgerHead });
468
+ const receipt = await localReceipt(candidate.operationId, "committed", head, nextHead, 5, candidateDigest);
469
+ this.#db.prepare("INSERT INTO local_preview_receipt(operation_id,value) VALUES (?,?)").run(candidate.operationId, JSON.stringify(receipt));
470
+ this.#db.prepare("INSERT INTO local_preview_request(operation_id,digest) VALUES (?,?)").run(candidate.operationId, requestDigest);
471
+ const writer = this.#pendingWriters.get(candidate.operationId);
472
+ if (writer !== undefined) {
473
+ this.#db.prepare("INSERT INTO local_preview_message(message_id,operation_id,role,text) VALUES (?,?,?,?)").run(writer.messageId, candidate.operationId, "assistant", writer.text);
474
+ this.messages().recordCommittedAssistantInTransaction({ operationId: candidate.operationId, turnId: writer.turnId, messageId: writer.messageId, text: writer.text, activeWorldlineId: decoded.activeTimeline.worldlineId, orderedMessageIds: writer.orderedMessageIds });
475
+ if (this.#worldlines === null)
476
+ throw new Error("local_preview_worldline_store_missing");
477
+ this.#worldlines.recordCommittedMessageCheckpointInTransaction({ messageId: writer.messageId, orderedMessageIds: writer.orderedMessageIds, sav: decoded, createdAtMs: Date.now() });
478
+ }
479
+ const authorView = this.#pendingAuthorViewEvents.get(candidate.operationId);
480
+ if (authorView !== undefined)
481
+ this.#db.prepare("INSERT INTO local_preview_author_view_outbox(operation_id,capture_sha256,epoch,artifact_digest,source_revision,timeline_revision,save_id,worldline_id,events_json) VALUES (?,?,?,?,?,?,?,?,?)").run(candidate.operationId, this.#captureSha256, authorView.epoch, authorView.artifactDigest, authorView.sourceRevision, candidate.timeline.revisionId, authorView.saveId, authorView.worldlineId, JSON.stringify(authorView.events));
482
+ this.#db.exec("COMMIT");
483
+ return receipt;
484
+ }
485
+ catch (error) {
486
+ try {
487
+ this.#db.exec("ROLLBACK");
488
+ }
489
+ catch { }
490
+ throw error;
491
+ }
492
+ finally {
493
+ this.#pendingRequestDigests.delete(candidate.operationId);
494
+ this.#pendingProjections.delete(candidate.operationId);
495
+ this.#pendingWriters.delete(candidate.operationId);
496
+ this.#pendingAuthorViewEvents.delete(candidate.operationId);
497
+ }
498
+ }
499
+ }
500
+ function stateValues(sav, projection) {
501
+ return Object.freeze({ head: Object.freeze({ revisionId: sav.activeTimeline.revisionId, revisionSequence: sav.activeTimeline.revisionSequence, ledgerHead: sav.ledgerHead }), sav, ledger: sav.eventLedger, narrative: sav.narrative, intents: sav.durableIntents, presentations: sav.presentationIntents, projection });
502
+ }
503
+ function withScenarioRuntimeRevision(projection, revision) {
504
+ const state = projection.scenario_runtime_state;
505
+ if (typeof state !== "object" || state === null || Array.isArray(state))
506
+ throw new Error("local_preview_scenario_runtime_state_missing");
507
+ return Object.freeze({ ...projection, scenario_runtime_state: Object.freeze({ ...state, schema_version: 1, state_revision: String(revision) }) });
508
+ }
509
+ async function localReceipt(operationId, status, previousHead, activeHead, atomicWriteCount, candidateDigest) {
510
+ const core = { operationId, status, previousHead, activeHead, atomicWriteCount, candidateDigest };
511
+ return Object.freeze({ ...core, receiptDigest: await tmwSha256Hex(canonicalTmwBytes(core)) });
512
+ }
513
+ async function readSavedEnvelope(path, captureSha256, binding) {
514
+ let bytes;
515
+ try {
516
+ bytes = new Uint8Array(await readFile(path));
517
+ }
518
+ catch (error) {
519
+ if (error.code === "ENOENT")
520
+ return null;
521
+ throw error;
522
+ }
523
+ let envelope;
524
+ try {
525
+ envelope = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
526
+ }
527
+ catch {
528
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview SAV 损坏");
529
+ }
530
+ if (envelope.schema_version !== SAVE_SCHEMA_VERSION || envelope.capture_sha256 !== captureSha256 || !envelope.sav || typeof envelope.sav !== "object")
531
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview SAV 与 capture 不匹配");
532
+ const sav = envelope.sav;
533
+ if (sav.epoch !== 1 || sav.runtimeGeneration !== 3 || sav.runtimeContractHash !== binding.runtimeContractHash
534
+ || sav.artifactDigest !== binding.artifactDigest || sav.sourceRevision !== binding.sourceRevision) {
535
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", "本地 Preview SAV runtime binding 不匹配");
536
+ }
537
+ return Object.freeze({ sav, projection: envelope.projection && typeof envelope.projection === "object" && !Array.isArray(envelope.projection) ? envelope.projection : createLocalPreviewWorldProjection({}), receipts: envelope.receipts && typeof envelope.receipts === "object" && !Array.isArray(envelope.receipts) ? envelope.receipts : Object.freeze({}) });
538
+ }
539
+ async function writeSavedEnvelope(root, directory, path, captureSha256, value) {
540
+ const tmp = await safeProjectPath(root, `.worldengine/local-player-preview/${captureSha256}/runtime-sav.${randomUUID()}.tmp`);
541
+ await writeFile(tmp, `${JSON.stringify({ schema_version: SAVE_SCHEMA_VERSION, capture_sha256: captureSha256, ...value })}\n`, { encoding: "utf8", flag: "wx" });
542
+ try {
543
+ await link(tmp, path);
544
+ }
545
+ catch (error) {
546
+ if (error.code !== "EEXIST")
547
+ throw error;
548
+ }
549
+ finally {
550
+ await unlink(tmp).catch(() => undefined);
551
+ }
552
+ void directory;
553
+ }
@@ -0,0 +1,73 @@
1
+ import { LocalPreviewReadOnlyProjection } from "./local-preview-projections.js";
2
+ import { LocalPreviewRuntime } from "./local-preview-runtime.js";
3
+ import { type LocalPreviewWorldlineRequestContext } from "./local-preview-worldlines.js";
4
+ export type LocalPreviewSessionNotification = Readonly<{
5
+ topic: "message.added" | "turn.changed";
6
+ data: Readonly<Record<string, unknown>>;
7
+ }>;
8
+ export type LocalPreviewModelClient = (request: Readonly<{
9
+ projectId: string;
10
+ rootIdentity: string;
11
+ accountIdentity: string | null;
12
+ bindAccount: (accountIdentity: string) => Promise<void>;
13
+ model: string;
14
+ idempotencyKey: string;
15
+ messages: readonly Readonly<{
16
+ role: "system" | "user" | "assistant";
17
+ content: string;
18
+ }>[];
19
+ }>) => Promise<Readonly<{
20
+ content: string;
21
+ }>>;
22
+ export type LocalPreviewReadOnlyProjectionReader = () => Promise<LocalPreviewReadOnlyProjection>;
23
+ export type LocalPreviewRuntimeReader = () => Promise<LocalPreviewRuntime>;
24
+ export type LocalPreviewHostNotificationSnapshotReader = () => Promise<Readonly<{
25
+ state: Readonly<{
26
+ generation: number;
27
+ entries: readonly Readonly<{
28
+ key: string;
29
+ revision: number;
30
+ }>[];
31
+ }>;
32
+ owned_resources: readonly Readonly<{
33
+ node_id: string;
34
+ attr_key: string;
35
+ resources: readonly Readonly<{
36
+ type: string;
37
+ uri: string;
38
+ }>[];
39
+ }>[];
40
+ }>>;
41
+ export type LocalPreviewWriterCommittedListener = (input: Readonly<{
42
+ turn_id: string;
43
+ }>) => Promise<void>;
44
+ /** 项目内、host-owned 的开发 Preview 消息会话;它不是正式 Player save authority。 */
45
+ export declare class LocalPreviewSession {
46
+ #private;
47
+ private constructor();
48
+ static open(input: Readonly<{
49
+ root: string;
50
+ projectId: string;
51
+ modelClient?: LocalPreviewModelClient;
52
+ now?: () => number;
53
+ readOnlyProjection?: LocalPreviewReadOnlyProjection | LocalPreviewReadOnlyProjectionReader;
54
+ runtime?: LocalPreviewRuntimeReader;
55
+ hostNotificationSnapshot?: LocalPreviewHostNotificationSnapshotReader;
56
+ writerCommitted?: LocalPreviewWriterCommittedListener;
57
+ }>): Promise<LocalPreviewSession>;
58
+ onNotification(listener: (notification: LocalPreviewSessionNotification) => void): () => void;
59
+ inspect(): Readonly<Record<string, unknown>>;
60
+ inspectForHost(): Promise<Readonly<Record<string, unknown>>>;
61
+ waitForRevision(after: number, timeoutMs: number): Promise<Readonly<Record<string, unknown>>>;
62
+ get saveId(): string;
63
+ pendingViewDeliveryFailures(): readonly Readonly<{
64
+ operation_id: string;
65
+ capture_sha256: string;
66
+ }>[];
67
+ recordViewDeliveryFailure(input: Readonly<{
68
+ operation_id: string;
69
+ capture_sha256: string;
70
+ }>): Promise<void>;
71
+ resolveViewDeliveryFailure(operationId: string): Promise<void>;
72
+ handle(method: string, params: unknown, worldlineContext?: LocalPreviewWorldlineRequestContext): Promise<unknown>;
73
+ }