@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,12 @@
1
+ import { type LocalPreviewFrozenAttributeResourceRef } from "./local-preview-player-read-models.js";
2
+ export interface LocalPreviewBoundResourceProjection {
3
+ /** 全部引用都必须由当前 committed SAV 的 stable ID 再做可见性过滤。 */
4
+ readonly frozen_attribute_resource_refs: readonly LocalPreviewFrozenAttributeResourceRef[];
5
+ /** 只允许在没有恢复资源 checkpoint 的新存档初始化时消费。 */
6
+ readonly initial_unlocked_resource_ids: readonly string[];
7
+ }
8
+ /**
9
+ * 从 immutable logical_graph capture 投影 Player 资源读模型。
10
+ * 本函数只解析正式 `_bound_attr_media.value`;不会读取或推断 Gallery 路径。
11
+ */
12
+ export declare function localPreviewBoundResourcesFromLogicalGraph(logicalGraph: unknown): LocalPreviewBoundResourceProjection;
@@ -0,0 +1,80 @@
1
+ import { localPreviewOwnedResourceStableId, } from "./local-preview-player-read-models.js";
2
+ const BOUND_ATTR_MEDIA_PROP_KEY = "_bound_attr_media";
3
+ function record(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value)
5
+ ? value
6
+ : null;
7
+ }
8
+ /**
9
+ * 与 wasm-core `parse_bound_attr_media` 对齐:包装或数组任一字段不合法时,
10
+ * 整个实体的绑定资源都视为空;serde 未声明的字段会被忽略。
11
+ */
12
+ function parseBoundAttrMedia(props) {
13
+ const propsRecord = record(props);
14
+ const wrapper = propsRecord === null ? null : record(propsRecord[BOUND_ATTR_MEDIA_PROP_KEY]);
15
+ const serialized = wrapper?.value;
16
+ if (typeof serialized !== "string")
17
+ return Object.freeze([]);
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(serialized);
21
+ }
22
+ catch {
23
+ return Object.freeze([]);
24
+ }
25
+ if (!Array.isArray(parsed))
26
+ return Object.freeze([]);
27
+ const entries = [];
28
+ for (const candidate of parsed) {
29
+ const entry = record(candidate);
30
+ if (entry === null
31
+ || typeof entry.attr_key !== "string"
32
+ || typeof entry.r2_uri !== "string"
33
+ || typeof entry.kind !== "string"
34
+ || (entry.media_name !== undefined && typeof entry.media_name !== "string")
35
+ || (entry.trigger_desc !== undefined && typeof entry.trigger_desc !== "string")
36
+ || (entry.default_unlocked !== undefined && typeof entry.default_unlocked !== "boolean")) {
37
+ return Object.freeze([]);
38
+ }
39
+ entries.push(Object.freeze({
40
+ attr_key: entry.attr_key,
41
+ r2_uri: entry.r2_uri,
42
+ kind: entry.kind,
43
+ default_unlocked: entry.default_unlocked === true,
44
+ }));
45
+ }
46
+ return Object.freeze(entries);
47
+ }
48
+ /**
49
+ * 从 immutable logical_graph capture 投影 Player 资源读模型。
50
+ * 本函数只解析正式 `_bound_attr_media.value`;不会读取或推断 Gallery 路径。
51
+ */
52
+ export function localPreviewBoundResourcesFromLogicalGraph(logicalGraph) {
53
+ const graph = record(logicalGraph);
54
+ const entities = graph === null || !Array.isArray(graph.entities) ? [] : graph.entities;
55
+ const refs = [];
56
+ const initialUnlocked = new Set();
57
+ for (const candidate of entities) {
58
+ const entity = record(candidate);
59
+ if (entity === null)
60
+ continue;
61
+ const nodeId = entity.entity_id ?? entity.id;
62
+ if (typeof nodeId !== "string")
63
+ continue;
64
+ for (const entry of parseBoundAttrMedia(entity.props)) {
65
+ refs.push(Object.freeze({
66
+ node_id: nodeId,
67
+ attr_key: entry.attr_key,
68
+ type: entry.kind,
69
+ uri: entry.r2_uri,
70
+ }));
71
+ if (entry.default_unlocked) {
72
+ initialUnlocked.add(localPreviewOwnedResourceStableId(nodeId, entry.attr_key));
73
+ }
74
+ }
75
+ }
76
+ return Object.freeze({
77
+ frozen_attribute_resource_refs: Object.freeze(refs),
78
+ initial_unlocked_resource_ids: Object.freeze([...initialUnlocked]),
79
+ });
80
+ }
@@ -0,0 +1,63 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { LocalPreviewWorldlineScope } from "./local-preview-worldlines.js";
3
+ export interface LocalPreviewMessageScope {
4
+ readonly captureId: string;
5
+ readonly saveId: string;
6
+ /** Full scope is required to derive the same DB-owned worldline authority key. */
7
+ readonly worldlineScope: LocalPreviewWorldlineScope;
8
+ }
9
+ export interface LocalPreviewMessageHead {
10
+ readonly revisionId: string;
11
+ readonly revisionSequence: number;
12
+ readonly ledgerHead: string | null;
13
+ }
14
+ export interface LocalPreviewCommittedMessage {
15
+ readonly messageId: string;
16
+ readonly operationId: string;
17
+ readonly turnId: string;
18
+ readonly worldlineId: string;
19
+ readonly role: "user" | "assistant";
20
+ readonly text: string;
21
+ }
22
+ export declare class LocalPreviewMessageStore {
23
+ #private;
24
+ constructor(input: Readonly<{
25
+ database: DatabaseSync;
26
+ scope: LocalPreviewMessageScope;
27
+ }>);
28
+ /**
29
+ * Records the accepted Player message. It does not create a worldline
30
+ * checkpoint or pretend that an assistant turn succeeded; Runtime commits
31
+ * the later assistant message together with the actual SAV activation.
32
+ */
33
+ appendAcceptedUserMessage(input: Readonly<{
34
+ operationId: string;
35
+ turnId: string;
36
+ messageId: string;
37
+ text: string;
38
+ activeWorldlineId: string;
39
+ expectedRuntimeHead: LocalPreviewMessageHead;
40
+ }>): Promise<Readonly<{
41
+ message: LocalPreviewCommittedMessage;
42
+ replayed: boolean;
43
+ }>>;
44
+ /**
45
+ * The Runtime's successful activation transaction is the only caller. A
46
+ * caller cannot synthesize an assistant answer: this method refuses to open
47
+ * its own transaction, validates every prior ordered ID, and rolls back with
48
+ * the enclosing SAV activation if anything fails.
49
+ */
50
+ recordCommittedAssistantInTransaction(input: Readonly<{
51
+ operationId: string;
52
+ turnId: string;
53
+ messageId: string;
54
+ text: string;
55
+ activeWorldlineId: string;
56
+ orderedMessageIds: readonly string[];
57
+ }>): Readonly<{
58
+ message: LocalPreviewCommittedMessage;
59
+ replayed: boolean;
60
+ }>;
61
+ /** Reads the active line's DB-owned checkpoint plus its durable pending-user tail. */
62
+ listCurrentWorldline(): readonly LocalPreviewCommittedMessage[];
63
+ }
@@ -0,0 +1,214 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Capture-bound, SQLite-backed message bodies for the local Player Preview.
4
+ *
5
+ * This is deliberately not a session JSONL replacement and it does not decide
6
+ * a worldline's message order. `LocalPreviewWorldlineStore` remains the
7
+ * authority for the active line and checkpointed ordered message IDs. This
8
+ * store merely keeps the bodies for those IDs in the Runtime's existing
9
+ * activation.sqlite connection.
10
+ */
11
+ const ID = /^[A-Za-z0-9._:-]{1,128}$/u;
12
+ const MAX_TEXT_BYTES = 64 * 1024;
13
+ export class LocalPreviewMessageStore {
14
+ #db;
15
+ #scope;
16
+ #worldlineScopeKey;
17
+ constructor(input) {
18
+ assertScope(input.scope);
19
+ this.#db = input.database;
20
+ this.#scope = Object.freeze({ ...input.scope });
21
+ this.#worldlineScopeKey = worldlineScopeKey(input.scope.worldlineScope);
22
+ this.#migrateLegacyAssistantRows();
23
+ }
24
+ /**
25
+ * Records the accepted Player message. It does not create a worldline
26
+ * checkpoint or pretend that an assistant turn succeeded; Runtime commits
27
+ * the later assistant message together with the actual SAV activation.
28
+ */
29
+ async appendAcceptedUserMessage(input) {
30
+ assertMessageInput(input, "user");
31
+ const requestDigest = digest({ scope: this.#scope, role: "user", ...input });
32
+ return this.#transaction(() => {
33
+ const existing = this.#byOperation(input.operationId);
34
+ if (existing !== null) {
35
+ if (existing.request_digest !== requestDigest)
36
+ fail("operation_id_conflict");
37
+ return Object.freeze({ message: toMessage(existing), replayed: true });
38
+ }
39
+ const sameId = this.#byMessage(input.messageId);
40
+ if (sameId !== null)
41
+ fail("message_id_conflict");
42
+ if (!sameHead(this.#readHead(), input.expectedRuntimeHead))
43
+ fail("cas_conflict");
44
+ const current = this.#currentLine();
45
+ if (current.activeWorldlineId !== input.activeWorldlineId)
46
+ fail("active_worldline_mismatch");
47
+ const row = makeRow(input, "user", requestDigest, input.expectedRuntimeHead, this.#nextSequence());
48
+ this.#insert(row);
49
+ return Object.freeze({ message: toMessage(row), replayed: false });
50
+ });
51
+ }
52
+ /**
53
+ * The Runtime's successful activation transaction is the only caller. A
54
+ * caller cannot synthesize an assistant answer: this method refuses to open
55
+ * its own transaction, validates every prior ordered ID, and rolls back with
56
+ * the enclosing SAV activation if anything fails.
57
+ */
58
+ recordCommittedAssistantInTransaction(input) {
59
+ if (!inTransaction(this.#db))
60
+ fail("assistant_requires_activation_transaction");
61
+ assertMessageInput(input, "assistant");
62
+ assertOrderedIds(input.orderedMessageIds);
63
+ if (input.orderedMessageIds.at(-1) !== input.messageId)
64
+ fail("assistant_not_order_tail");
65
+ const head = this.#readHead();
66
+ const requestDigest = digest({ scope: this.#scope, role: "assistant", ...input, committedRuntimeHead: head });
67
+ const existing = this.#byOperation(input.operationId);
68
+ if (existing !== null) {
69
+ if (existing.request_digest !== requestDigest)
70
+ fail("operation_id_conflict");
71
+ return Object.freeze({ message: toMessage(existing), replayed: true });
72
+ }
73
+ if (this.#byMessage(input.messageId) !== null)
74
+ fail("message_id_conflict");
75
+ const current = this.#currentLine();
76
+ if (current.activeWorldlineId !== input.activeWorldlineId)
77
+ fail("active_worldline_mismatch");
78
+ const expectedOrdered = Object.freeze([...current.checkpointIds, ...this.#pendingUserIds(current.activeWorldlineId, current.checkpointIds), input.messageId]);
79
+ if (!sameIds(input.orderedMessageIds, expectedOrdered))
80
+ fail("assistant_order_not_current_worldline");
81
+ this.#assertExistingMessages(expectedOrdered.slice(0, -1));
82
+ const row = makeRow(input, "assistant", requestDigest, head, this.#nextSequence());
83
+ this.#insert(row);
84
+ return Object.freeze({ message: toMessage(row), replayed: false });
85
+ }
86
+ /** Reads the active line's DB-owned checkpoint plus its durable pending-user tail. */
87
+ listCurrentWorldline() {
88
+ const current = this.#currentLine();
89
+ const ids = Object.freeze([...current.checkpointIds, ...this.#pendingUserIds(current.activeWorldlineId, current.checkpointIds)]);
90
+ return Object.freeze(ids.map((messageId) => {
91
+ const row = this.#byMessage(messageId);
92
+ if (row === null)
93
+ fail("ordered_message_missing");
94
+ return toMessage(row);
95
+ }));
96
+ }
97
+ #migrateLegacyAssistantRows() {
98
+ this.#db.exec("CREATE TABLE IF NOT EXISTS local_preview_message_v2 (message_id TEXT PRIMARY KEY, operation_id TEXT UNIQUE NOT NULL, turn_id TEXT NOT NULL, worldline_id TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN ('user','assistant')), text TEXT NOT NULL, request_digest TEXT NOT NULL, head_json TEXT NOT NULL, sequence INTEGER NOT NULL UNIQUE);");
99
+ const old = this.#db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='local_preview_message'").get();
100
+ if (old === undefined)
101
+ return;
102
+ const rows = this.#db.prepare("SELECT message_id,operation_id,role,text FROM local_preview_message").all();
103
+ if (rows.length === 0)
104
+ return;
105
+ this.#transaction(() => {
106
+ for (const oldRow of rows) {
107
+ if (typeof oldRow.message_id !== "string" || typeof oldRow.operation_id !== "string" || oldRow.role !== "assistant" || typeof oldRow.text !== "string")
108
+ fail("legacy_message_invalid");
109
+ const existing = this.#byMessage(oldRow.message_id);
110
+ if (existing !== null)
111
+ continue;
112
+ const row = Object.freeze({ message_id: oldRow.message_id, operation_id: oldRow.operation_id, turn_id: "legacy", worldline_id: "legacy", role: "assistant", text: oldRow.text, request_digest: digest({ legacy: true, operationId: oldRow.operation_id, messageId: oldRow.message_id, text: oldRow.text }), head_json: JSON.stringify({ revisionId: "legacy", revisionSequence: 0, ledgerHead: null }), sequence: this.#nextSequence() });
113
+ this.#insert(row);
114
+ }
115
+ });
116
+ }
117
+ #readHead() {
118
+ const row = this.#db.prepare("SELECT value FROM local_preview_state WHERE key='head'").get();
119
+ if (row === undefined)
120
+ fail("runtime_head_missing");
121
+ let value;
122
+ try {
123
+ value = JSON.parse(row.value);
124
+ }
125
+ catch {
126
+ fail("runtime_head_invalid");
127
+ }
128
+ assertHead(value);
129
+ return Object.freeze({ ...value });
130
+ }
131
+ #byOperation(operationId) { const row = this.#db.prepare("SELECT message_id,operation_id,turn_id,worldline_id,role,text,request_digest,head_json,sequence FROM local_preview_message_v2 WHERE operation_id=?").get(operationId); return row === undefined ? null : validateRow(row); }
132
+ #byMessage(messageId) { const row = this.#db.prepare("SELECT message_id,operation_id,turn_id,worldline_id,role,text,request_digest,head_json,sequence FROM local_preview_message_v2 WHERE message_id=?").get(messageId); return row === undefined ? null : validateRow(row); }
133
+ #insert(row) { this.#db.prepare("INSERT INTO local_preview_message_v2(message_id,operation_id,turn_id,worldline_id,role,text,request_digest,head_json,sequence) VALUES (?,?,?,?,?,?,?,?,?)").run(row.message_id, row.operation_id, row.turn_id, row.worldline_id, row.role, row.text, row.request_digest, row.head_json, row.sequence); }
134
+ #nextSequence() { return (this.#db.prepare("SELECT COALESCE(MAX(sequence),0) AS n FROM local_preview_message_v2").get().n) + 1; }
135
+ #currentLine() {
136
+ const catalog = this.#db.prepare("SELECT active_worldline_id FROM local_preview_worldline_catalog_v2 WHERE scope_key=?").get(this.#worldlineScopeKey);
137
+ if (catalog === undefined)
138
+ fail("worldline_catalog_missing");
139
+ assertId(catalog.active_worldline_id, "active_worldline_id");
140
+ const line = this.#db.prepare("SELECT message_ids_json FROM local_preview_worldline_v2 WHERE scope_key=? AND worldline_id=?").get(this.#worldlineScopeKey, catalog.active_worldline_id);
141
+ if (line === undefined)
142
+ fail("active_worldline_missing");
143
+ return Object.freeze({ activeWorldlineId: catalog.active_worldline_id, checkpointIds: parseIds(line.message_ids_json) });
144
+ }
145
+ #pendingUserIds(activeWorldlineId, checkpointIds) {
146
+ const rows = this.#db.prepare("SELECT message_id FROM local_preview_message_v2 WHERE role='user' AND worldline_id=? ORDER BY sequence").all(activeWorldlineId);
147
+ const checkpoint = new Set(checkpointIds);
148
+ const pending = [];
149
+ for (const row of rows) {
150
+ assertId(row.message_id, "pending_message_id");
151
+ if (!checkpoint.has(row.message_id))
152
+ pending.push(row.message_id);
153
+ }
154
+ return Object.freeze(pending);
155
+ }
156
+ #assertExistingMessages(ids) { for (const id of ids)
157
+ if (this.#byMessage(id) === null)
158
+ fail("ordered_message_missing"); }
159
+ #transaction(work) { if (inTransaction(this.#db))
160
+ return work(); this.#db.exec("BEGIN IMMEDIATE"); try {
161
+ const result = work();
162
+ this.#db.exec("COMMIT");
163
+ return result;
164
+ }
165
+ catch (error) {
166
+ try {
167
+ this.#db.exec("ROLLBACK");
168
+ }
169
+ catch { }
170
+ throw error;
171
+ } }
172
+ }
173
+ function makeRow(input, role, requestDigest, head, sequence) { return Object.freeze({ message_id: input.messageId, operation_id: input.operationId, turn_id: input.turnId, worldline_id: input.activeWorldlineId, role, text: input.text, request_digest: requestDigest, head_json: JSON.stringify(head), sequence }); }
174
+ function toMessage(row) { return Object.freeze({ messageId: row.message_id, operationId: row.operation_id, turnId: row.turn_id, worldlineId: row.worldline_id, role: row.role, text: row.text }); }
175
+ function validateRow(row) { assertId(row.message_id, "message_id"); assertId(row.operation_id, "operation_id"); assertId(row.turn_id, "turn_id"); assertId(row.worldline_id, "worldline_id"); if (row.role !== "user" && row.role !== "assistant")
176
+ fail("stored_role_invalid"); assertText(row.text); if (!/^[a-f0-9]{64}$/u.test(row.request_digest))
177
+ fail("stored_digest_invalid"); if (!Number.isSafeInteger(row.sequence) || row.sequence < 1)
178
+ fail("stored_sequence_invalid"); let head; try {
179
+ head = JSON.parse(row.head_json);
180
+ }
181
+ catch {
182
+ fail("stored_head_invalid");
183
+ } assertHead(head); return Object.freeze({ ...row }); }
184
+ function assertMessageInput(input, _role) { assertId(input.operationId, "operation_id"); assertId(input.turnId, "turn_id"); assertId(input.messageId, "message_id"); assertId(input.activeWorldlineId, "active_worldline_id"); assertText(input.text); }
185
+ function assertScope(scope) {
186
+ if (!isRecord(scope) || !ID.test(scope.captureId) || !ID.test(scope.saveId) || !isRecord(scope.worldlineScope))
187
+ fail("invalid_scope");
188
+ const line = scope.worldlineScope;
189
+ if (line.authority !== "worldengine.local-preview-worldlines/v1" || !ID.test(line.authorNamespace) || !ID.test(line.projectId) || !ID.test(line.scenarioId) || !ID.test(line.saveId) || !/^[a-f0-9]{64}$/u.test(line.captureSha256) || line.captureSha256 !== scope.captureId || line.saveId !== scope.saveId)
190
+ fail("invalid_worldline_scope");
191
+ }
192
+ function assertId(value, label) { if (typeof value !== "string" || !ID.test(value))
193
+ fail(`invalid_${label}`); }
194
+ function assertText(value) { if (typeof value !== "string" || value.length === 0 || new TextEncoder().encode(value).byteLength > MAX_TEXT_BYTES)
195
+ fail("invalid_text"); }
196
+ function assertHead(value) { if (!isRecord(value) || typeof value.revisionId !== "string" || !ID.test(value.revisionId) || typeof value.revisionSequence !== "number" || !Number.isSafeInteger(value.revisionSequence) || value.revisionSequence < 0 || (value.ledgerHead !== null && (typeof value.ledgerHead !== "string" || !ID.test(value.ledgerHead))))
197
+ fail("invalid_runtime_head"); }
198
+ function assertOrderedIds(ids) { if (!Array.isArray(ids) || new Set(ids).size !== ids.length)
199
+ fail("invalid_ordered_message_ids"); for (const id of ids)
200
+ assertId(id, "ordered_message_id"); }
201
+ function sameHead(left, right) { return left.revisionId === right.revisionId && left.revisionSequence === right.revisionSequence && left.ledgerHead === right.ledgerHead; }
202
+ function sameIds(left, right) { return left.length === right.length && left.every((id, index) => id === right[index]); }
203
+ function parseIds(value) { let parsed; try {
204
+ parsed = JSON.parse(value);
205
+ }
206
+ catch {
207
+ fail("checkpoint_ids_invalid");
208
+ } if (!Array.isArray(parsed))
209
+ fail("checkpoint_ids_invalid"); assertOrderedIds(parsed); return Object.freeze([...parsed]); }
210
+ function worldlineScopeKey(scope) { return digest([scope.authority, scope.authorNamespace, scope.projectId, scope.scenarioId, scope.saveId, scope.captureSha256]); }
211
+ function digest(value) { return createHash("sha256").update(JSON.stringify(value)).digest("hex"); }
212
+ function inTransaction(db) { return db.isTransaction === true; }
213
+ function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype; }
214
+ function fail(code) { throw new Error(`local_preview_message_store:${code}`); }
@@ -0,0 +1,47 @@
1
+ export interface LocalPreviewScenarioRuntimeCheckpoint {
2
+ readonly schema_version: 1;
3
+ /** 与同一 activation head 一起提交的正式 ScenarioRuntimeState.state_revision。 */
4
+ readonly state_revision: string;
5
+ readonly progress: number;
6
+ readonly unlocked_resource_ids: readonly string[];
7
+ }
8
+ /**
9
+ * Runtime 在 immutable source capture 解析出的属性资源引用。
10
+ * stable_id 不由调用者声明:始终从 node_id + attr_key 重新推导,避免跨节点借用 unlock。
11
+ */
12
+ export interface LocalPreviewFrozenAttributeResourceRef {
13
+ readonly node_id: string;
14
+ readonly attr_key: string;
15
+ readonly type: string;
16
+ readonly uri: string;
17
+ }
18
+ export interface LocalPreviewPlayerReadModelSnapshot {
19
+ /** Runtime 的同 head SAV 投影,不接受 View/bridge 写入。 */
20
+ readonly scenario_runtime_state: LocalPreviewScenarioRuntimeCheckpoint;
21
+ /** 同一个 immutable source capture 的 logical_graph 属性资源引用。 */
22
+ readonly frozen_attribute_resource_refs: readonly LocalPreviewFrozenAttributeResourceRef[];
23
+ }
24
+ /** Runtime owner 以 activation.sqlite 的 current head 构造这一窄快照。 */
25
+ export interface LocalPreviewPlayerReadModelPort {
26
+ readCommittedPlayerReadModel(): Promise<LocalPreviewPlayerReadModelSnapshot>;
27
+ }
28
+ export interface LocalPreviewOwnedResourceGroup {
29
+ readonly node_id: string;
30
+ readonly attr_key: string;
31
+ readonly resources: readonly Readonly<{
32
+ type: string;
33
+ uri: string;
34
+ }>[];
35
+ }
36
+ /** `world-engines-resource:v1` 的唯一 stable ID 公式,供 runtime 写 checkpoint 与测试共享。 */
37
+ export declare function localPreviewOwnedResourceStableId(nodeId: string, attrKey: string): string;
38
+ /**
39
+ * 给 LocalPreviewReadOnlyProjection 注入的真实 producer。
40
+ * 每次读取均从 Runtime 的 committed head 取 checkpoint,因此缺 checkpoint、过期/错 shape 都 fail closed。
41
+ */
42
+ export declare class LocalPreviewPlayerReadModels {
43
+ #private;
44
+ constructor(port: LocalPreviewPlayerReadModelPort);
45
+ progress(): Promise<number>;
46
+ ownedResources(): Promise<readonly LocalPreviewOwnedResourceGroup[]>;
47
+ }
@@ -0,0 +1,90 @@
1
+ import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
2
+ const STATE_REVISION = /^(?:0|[1-9][0-9]*)$/u;
3
+ function fail(message) {
4
+ throw new AuthoringBridgeError("E_OPERATION_UNCERTAIN", message);
5
+ }
6
+ function anchor(value, field) {
7
+ if (typeof value !== "string" || value.length === 0 || value.length > 512 || value.includes("\u0000"))
8
+ fail(`local Preview ${field} 无效`);
9
+ return value;
10
+ }
11
+ function stableId(nodeId, attrKey) {
12
+ return `world-engines-resource:v1:${encodeURIComponent(nodeId)}:${encodeURIComponent(attrKey)}`;
13
+ }
14
+ function validateCheckpoint(value) {
15
+ if (value === null || typeof value !== "object" || Array.isArray(value))
16
+ fail("local Preview ScenarioRuntimeState checkpoint 无效");
17
+ const checkpoint = value;
18
+ if (checkpoint.schema_version !== 1 || !STATE_REVISION.test(checkpoint.state_revision)
19
+ || !Number.isFinite(checkpoint.progress) || checkpoint.progress < 0 || checkpoint.progress > 1
20
+ || !Array.isArray(checkpoint.unlocked_resource_ids)) {
21
+ fail("local Preview ScenarioRuntimeState checkpoint 无效");
22
+ }
23
+ const unlocked = checkpoint.unlocked_resource_ids.map((resourceId) => {
24
+ if (typeof resourceId !== "string" || resourceId.length === 0 || resourceId.length > 512)
25
+ fail("local Preview unlocked_resource_ids 无效");
26
+ return resourceId;
27
+ });
28
+ if (new Set(unlocked).size !== unlocked.length)
29
+ fail("local Preview unlocked_resource_ids 重复");
30
+ return Object.freeze({ schema_version: 1, state_revision: checkpoint.state_revision, progress: checkpoint.progress, unlocked_resource_ids: Object.freeze(unlocked) });
31
+ }
32
+ function validateResource(value) {
33
+ if (value === null || typeof value !== "object" || Array.isArray(value))
34
+ fail("local Preview frozen attribute resource ref 无效");
35
+ const ref = value;
36
+ const nodeId = anchor(ref.node_id, "resource node_id");
37
+ const attrKey = anchor(ref.attr_key, "resource attr_key");
38
+ if (typeof ref.type !== "string" || ref.type.length === 0 || ref.type.length > 128
39
+ || typeof ref.uri !== "string" || ref.uri.length === 0 || ref.uri.length > 4096) {
40
+ fail("local Preview frozen attribute resource ref 无效");
41
+ }
42
+ return Object.freeze({ node_id: nodeId, attr_key: attrKey, type: ref.type, uri: ref.uri });
43
+ }
44
+ /** `world-engines-resource:v1` 的唯一 stable ID 公式,供 runtime 写 checkpoint 与测试共享。 */
45
+ export function localPreviewOwnedResourceStableId(nodeId, attrKey) {
46
+ return stableId(anchor(nodeId, "resource node_id"), anchor(attrKey, "resource attr_key"));
47
+ }
48
+ /**
49
+ * 给 LocalPreviewReadOnlyProjection 注入的真实 producer。
50
+ * 每次读取均从 Runtime 的 committed head 取 checkpoint,因此缺 checkpoint、过期/错 shape 都 fail closed。
51
+ */
52
+ export class LocalPreviewPlayerReadModels {
53
+ #port;
54
+ constructor(port) {
55
+ this.#port = port;
56
+ }
57
+ async progress() {
58
+ return (await this.#snapshot()).scenario_runtime_state.progress;
59
+ }
60
+ async ownedResources() {
61
+ const snapshot = await this.#snapshot();
62
+ const unlocked = new Set(snapshot.scenario_runtime_state.unlocked_resource_ids);
63
+ const groups = new Map();
64
+ const seen = new Set();
65
+ for (const ref of snapshot.frozen_attribute_resource_refs) {
66
+ const resource = validateResource(ref);
67
+ const resourceStableId = stableId(resource.node_id, resource.attr_key);
68
+ if (!unlocked.has(resourceStableId))
69
+ continue;
70
+ const duplicate = `${resourceStableId}\u0000${resource.type}\u0000${resource.uri}`;
71
+ if (seen.has(duplicate))
72
+ fail("local Preview frozen attribute resource 重复");
73
+ seen.add(duplicate);
74
+ const group = groups.get(resourceStableId) ?? { node_id: resource.node_id, attr_key: resource.attr_key, resources: [] };
75
+ group.resources.push(Object.freeze({ type: resource.type, uri: resource.uri }));
76
+ groups.set(resourceStableId, group);
77
+ }
78
+ return Object.freeze([...groups.values()]
79
+ .sort((left, right) => left.node_id.localeCompare(right.node_id) || left.attr_key.localeCompare(right.attr_key))
80
+ .map((group) => Object.freeze({ node_id: group.node_id, attr_key: group.attr_key, resources: Object.freeze([...group.resources].sort((left, right) => left.type.localeCompare(right.type) || left.uri.localeCompare(right.uri))) })));
81
+ }
82
+ async #snapshot() {
83
+ const snapshot = await this.#port.readCommittedPlayerReadModel();
84
+ if (snapshot === null || typeof snapshot !== "object" || !Array.isArray(snapshot.frozen_attribute_resource_refs)) {
85
+ fail("local Preview committed player read model 缺失");
86
+ }
87
+ const checkpoint = validateCheckpoint(snapshot.scenario_runtime_state);
88
+ return Object.freeze({ scenario_runtime_state: checkpoint, frozen_attribute_resource_refs: Object.freeze(snapshot.frozen_attribute_resource_refs.map((ref) => validateResource(ref))) });
89
+ }
90
+ }
@@ -0,0 +1,61 @@
1
+ export interface LocalPreviewCustomStateEntry {
2
+ readonly key: string;
3
+ readonly value: unknown;
4
+ readonly scope: "public" | "protected";
5
+ readonly authorizedUserIds: readonly string[];
6
+ readonly revision: number;
7
+ }
8
+ export interface LocalPreviewAsset {
9
+ readonly id: string;
10
+ readonly filename: string;
11
+ readonly mime: string;
12
+ readonly size: number; /** 同一 immutable capture 的 manifest snapshot 时间。 */
13
+ readonly createdTs: number;
14
+ readonly source: "capture"; /** 仅在 host→top-window 传递 hash fence;bridge 不会转发给 iframe。 */
15
+ readonly sha256: string;
16
+ }
17
+ export interface LocalPreviewOwnedResourceGroup {
18
+ readonly node_id: string;
19
+ readonly attr_key: string;
20
+ readonly resources: readonly Readonly<{
21
+ type: string;
22
+ uri: string;
23
+ }>[];
24
+ }
25
+ export interface LocalPreviewCoreSnapshot {
26
+ readonly scriptId: string;
27
+ readonly scenarioId: string; /** 合法新剧本可尚未命名,SDK 返回空字符串而非伪造 scriptId。 */
28
+ readonly title: string;
29
+ readonly saveVariables: readonly Readonly<{
30
+ name: string;
31
+ scope: "save";
32
+ valueType: string;
33
+ value: unknown;
34
+ }>[];
35
+ readonly unlocks: Readonly<Record<string, boolean>>;
36
+ }
37
+ export interface LocalPreviewReadOnlyReaders {
38
+ readonly progress?: () => Promise<number>;
39
+ /** Host 已按当前 principal 过滤的 View custom state;不能传入 iframe 声明的身份。 */
40
+ readonly customState?: () => Promise<readonly LocalPreviewCustomStateEntry[]>;
41
+ readonly ownedResources?: () => Promise<readonly LocalPreviewOwnedResourceGroup[]>;
42
+ readonly assets?: Readonly<{
43
+ list: () => Promise<readonly LocalPreviewAsset[]>;
44
+ getUrl: (asset: LocalPreviewAsset) => Promise<string>;
45
+ }>;
46
+ }
47
+ /** 从 LocalPreviewRuntime.readProjectionSnapshot 的窄 Host snapshot 读取已有真值。 */
48
+ export declare function localPreviewCoreFromRuntimeSnapshot(value: unknown): LocalPreviewCoreSnapshot;
49
+ /** SDK 只读 dispatcher。扩展读取面各自接入,缺真值不会阻断已可用的 SAV 读取。 */
50
+ export declare class LocalPreviewReadOnlyProjection {
51
+ #private;
52
+ constructor(core: LocalPreviewCoreSnapshot, readers?: LocalPreviewReadOnlyReaders);
53
+ capabilities(): Readonly<{
54
+ customStateMaxBytes: number;
55
+ assetMaxBytes: 0;
56
+ supportsAssets: boolean;
57
+ supportsEdit: false;
58
+ supportsDelete: false;
59
+ }>;
60
+ handle(method: string, params: unknown): Promise<unknown>;
61
+ }