@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,95 @@
1
+ /**
2
+ * Local Preview 的 worldline 命令边界。
3
+ *
4
+ * 这个 facade 故意不读取 local-preview-session JSONL,也不保有 catalog、op
5
+ * 或 blob 的副本。它只把 SDK 的三个 FAB 意图送到拥有 capture、SAV 和
6
+ * activation.sqlite 的 Runtime primitive。该 primitive 必须在同一 SQLite CAS
7
+ * 事务中复用正式 SameSaveWorldlineService 的 catalog/DAG/blob 语义,并在
8
+ * activate 时完成所有 state section 的 checkout 后才返回成功。
9
+ */
10
+ export interface LocalPreviewWorldlineScope {
11
+ readonly authority: "worldengine.local-preview-worldlines/v1";
12
+ readonly authorNamespace: string;
13
+ readonly projectId: string;
14
+ readonly scenarioId: string;
15
+ readonly saveId: string;
16
+ readonly captureSha256: string;
17
+ }
18
+ export interface LocalPreviewWorldlineSnapshot {
19
+ readonly scope: LocalPreviewWorldlineScope;
20
+ readonly activeWorldlineId: string;
21
+ readonly rootWorldlineId: string;
22
+ readonly catalogRevision: number;
23
+ }
24
+ export interface LocalPreviewWorldlineSummary {
25
+ readonly worldlineId: string;
26
+ readonly title: string;
27
+ readonly parentWorldlineId: string | null;
28
+ readonly forkAnchorOpId: string | null;
29
+ readonly headOpId: string | null;
30
+ readonly durableStatus: "durable";
31
+ readonly createdAtMs: number;
32
+ readonly updatedAtMs: number;
33
+ readonly revision: number;
34
+ }
35
+ export interface LocalPreviewWorldlineListResult {
36
+ readonly activeWorldlineId: string;
37
+ readonly rootWorldlineId: string;
38
+ readonly worldlineCount: number;
39
+ readonly worldlines: readonly LocalPreviewWorldlineSummary[];
40
+ }
41
+ export interface LocalPreviewWorldlineForkResult {
42
+ readonly worldlineId: string;
43
+ readonly activeWorldlineId: string;
44
+ readonly anchorMessageId: string;
45
+ readonly worldlineCount: number;
46
+ readonly removedMessageIds: readonly string[];
47
+ readonly deletedEntities: number;
48
+ readonly deletedRelations: number;
49
+ }
50
+ export interface LocalPreviewWorldlineActivateResult {
51
+ readonly worldlineId: string;
52
+ readonly activeWorldlineId: string;
53
+ readonly worldlineCount: number;
54
+ readonly messageCount: number;
55
+ }
56
+ /** A trusted GUI/server request identity; it never comes from View params. */
57
+ export interface LocalPreviewWorldlineRequestContext {
58
+ readonly requestId: string;
59
+ readonly createdAtMs: number;
60
+ }
61
+ /**
62
+ * Required LocalPreviewRuntime seam. The Runtime owner implements this against
63
+ * its capture-bound SAV + activation.sqlite authority; a session message log is
64
+ * not an acceptable implementation.
65
+ */
66
+ export interface LocalPreviewWorldlineRuntime {
67
+ snapshotWorldlines(): Promise<LocalPreviewWorldlineSnapshot | null>;
68
+ listWorldlines(scope: LocalPreviewWorldlineScope): Promise<LocalPreviewWorldlineListResult>;
69
+ forkWorldlineAtMessage(scope: LocalPreviewWorldlineScope, input: Readonly<{
70
+ messageId: string;
71
+ requestedTitle: string | null;
72
+ expectedCatalogRevision: number;
73
+ expectedActiveWorldlineId: string;
74
+ idempotencyKey: string;
75
+ createdAtMs: number;
76
+ }>): Promise<LocalPreviewWorldlineForkResult>;
77
+ activateWorldline(scope: LocalPreviewWorldlineScope, input: Readonly<{
78
+ worldlineId: string;
79
+ expectedCatalogRevision: number;
80
+ expectedActiveWorldlineId: string;
81
+ idempotencyKey: string;
82
+ createdAtMs: number;
83
+ }>): Promise<LocalPreviewWorldlineActivateResult>;
84
+ }
85
+ /**
86
+ * Host-side implementation target for worldline.list / fork / activate.
87
+ * It derives the only permissible save scope from the Runtime snapshot and
88
+ * rejects both missing authority and any response that claims an impossible
89
+ * success. `trim` deliberately is not in this Local Preview FAB surface.
90
+ */
91
+ export declare class LocalPreviewWorldlineFacade {
92
+ #private;
93
+ constructor(runtime: LocalPreviewWorldlineRuntime);
94
+ handle(method: "worldline.list" | "worldline.fork" | "worldline.activate", params: unknown, context: LocalPreviewWorldlineRequestContext): Promise<LocalPreviewWorldlineListResult | LocalPreviewWorldlineForkResult | LocalPreviewWorldlineActivateResult>;
95
+ }
@@ -0,0 +1,136 @@
1
+ import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
2
+ const SHA256 = /^[a-f0-9]{64}$/u;
3
+ function fail(code, message) {
4
+ throw new AuthoringBridgeError(code, message);
5
+ }
6
+ function record(value) {
7
+ if (typeof value !== "object" || value === null || Array.isArray(value))
8
+ fail("EINVAL", "worldline params 无效");
9
+ return value;
10
+ }
11
+ function assertOnlyKeys(value, allowed) {
12
+ const permitted = new Set(allowed);
13
+ if (Object.keys(value).some((key) => !permitted.has(key)))
14
+ fail("EPERM", "worldline scope 或参数未授权");
15
+ }
16
+ function identifier(value, label) {
17
+ if (!isIdentifier(value))
18
+ fail("EINVAL", `${label} 无效`);
19
+ return value;
20
+ }
21
+ function hasControl(value) {
22
+ for (let index = 0; index < value.length; index += 1) {
23
+ const code = value.charCodeAt(index);
24
+ if (code <= 0x1f || code === 0x7f)
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ function isIdentifier(value) {
30
+ return typeof value === "string" && value.length > 0 && value.length <= 256 && !/\s/u.test(value) && !hasControl(value);
31
+ }
32
+ function isTitle(value) {
33
+ return typeof value === "string" && value.trim() === value && value.length > 0 && value.length <= 160 && !hasControl(value);
34
+ }
35
+ function title(value) {
36
+ if (value === undefined)
37
+ return null;
38
+ if (!isTitle(value))
39
+ fail("EINVAL", "worldline title 无效");
40
+ return value;
41
+ }
42
+ function requestContext(value) {
43
+ const requestId = identifier(value.requestId, "worldline requestId");
44
+ if (!Number.isSafeInteger(value.createdAtMs) || value.createdAtMs < 0)
45
+ fail("EINVAL", "worldline createdAtMs 无效");
46
+ return Object.freeze({ requestId, createdAtMs: value.createdAtMs });
47
+ }
48
+ function idempotencyKey(kind, context, target) {
49
+ return `local-preview-worldline:${kind}:${context.requestId}:${target}`;
50
+ }
51
+ function assertScope(value) {
52
+ if (value.authority !== "worldengine.local-preview-worldlines/v1"
53
+ || !isIdentifier(value.authorNamespace) || !isIdentifier(value.projectId) || !isIdentifier(value.scenarioId)
54
+ || !isIdentifier(value.saveId) || !SHA256.test(value.captureSha256)) {
55
+ fail("E_OPERATION_UNCERTAIN", "local Preview worldline authority snapshot 无效");
56
+ }
57
+ }
58
+ function assertList(snapshot, value) {
59
+ if (!Array.isArray(value.worldlines) || value.worldlineCount !== value.worldlines.length || value.worldlineCount < 1
60
+ || value.activeWorldlineId !== snapshot.activeWorldlineId || value.rootWorldlineId !== snapshot.rootWorldlineId) {
61
+ fail("E_OPERATION_UNCERTAIN", "local Preview worldline list 与 authority snapshot 不一致");
62
+ }
63
+ const ids = new Set();
64
+ for (const line of value.worldlines) {
65
+ if (!isIdentifier(line.worldlineId) || ids.has(line.worldlineId) || !isTitle(line.title)
66
+ || (line.parentWorldlineId !== null && !isIdentifier(line.parentWorldlineId))
67
+ || (line.forkAnchorOpId !== null && !SHA256.test(line.forkAnchorOpId))
68
+ || (line.headOpId !== null && !SHA256.test(line.headOpId))
69
+ || line.durableStatus !== "durable" || !Number.isSafeInteger(line.createdAtMs)
70
+ || !Number.isSafeInteger(line.updatedAtMs) || line.updatedAtMs < line.createdAtMs
71
+ || !Number.isSafeInteger(line.revision) || line.revision < 1) {
72
+ fail("E_OPERATION_UNCERTAIN", "local Preview worldline descriptor 无效");
73
+ }
74
+ ids.add(line.worldlineId);
75
+ }
76
+ if (!ids.has(value.activeWorldlineId) || !ids.has(value.rootWorldlineId))
77
+ fail("E_OPERATION_UNCERTAIN", "local Preview worldline catalog 缺少 active/root");
78
+ return Object.freeze({ ...value, worldlines: Object.freeze([...value.worldlines]) });
79
+ }
80
+ /**
81
+ * Host-side implementation target for worldline.list / fork / activate.
82
+ * It derives the only permissible save scope from the Runtime snapshot and
83
+ * rejects both missing authority and any response that claims an impossible
84
+ * success. `trim` deliberately is not in this Local Preview FAB surface.
85
+ */
86
+ export class LocalPreviewWorldlineFacade {
87
+ #runtime;
88
+ constructor(runtime) { this.#runtime = runtime; }
89
+ async handle(method, params, context) {
90
+ const trustedContext = requestContext(context);
91
+ const snapshot = await this.#runtime.snapshotWorldlines();
92
+ if (snapshot === null)
93
+ fail("E_CAPABILITY_UNAVAILABLE", "本地 Preview Runtime 尚未提供 capture 绑定的 worldline snapshot");
94
+ assertScope(snapshot.scope);
95
+ identifier(snapshot.activeWorldlineId, "active worldline id");
96
+ identifier(snapshot.rootWorldlineId, "root worldline id");
97
+ if (!Number.isSafeInteger(snapshot.catalogRevision) || snapshot.catalogRevision < 1)
98
+ fail("E_OPERATION_UNCERTAIN", "local Preview worldline catalog revision 无效");
99
+ const p = record(params);
100
+ if (method === "worldline.list") {
101
+ assertOnlyKeys(p, []);
102
+ return assertList(snapshot, await this.#runtime.listWorldlines(snapshot.scope));
103
+ }
104
+ if (method === "worldline.fork") {
105
+ assertOnlyKeys(p, ["messageId", "title"]);
106
+ const messageId = identifier(p.messageId, "fork message id");
107
+ const result = await this.#runtime.forkWorldlineAtMessage(snapshot.scope, Object.freeze({
108
+ messageId, requestedTitle: title(p.title), expectedCatalogRevision: snapshot.catalogRevision,
109
+ expectedActiveWorldlineId: snapshot.activeWorldlineId,
110
+ idempotencyKey: idempotencyKey("fork", trustedContext, messageId), createdAtMs: trustedContext.createdAtMs,
111
+ }));
112
+ if (result.anchorMessageId !== messageId || result.worldlineId !== result.activeWorldlineId
113
+ || !isIdentifier(result.worldlineId) || !Number.isSafeInteger(result.worldlineCount)
114
+ || result.worldlineCount < 2 || !Array.isArray(result.removedMessageIds)
115
+ || !result.removedMessageIds.every((id) => isIdentifier(id))
116
+ || !Number.isSafeInteger(result.deletedEntities) || result.deletedEntities < 0
117
+ || !Number.isSafeInteger(result.deletedRelations) || result.deletedRelations < 0) {
118
+ fail("E_OPERATION_UNCERTAIN", "local Preview fork 未提交同一 authority 的有效结果");
119
+ }
120
+ return Object.freeze({ ...result, removedMessageIds: Object.freeze([...result.removedMessageIds]) });
121
+ }
122
+ assertOnlyKeys(p, ["worldlineId"]);
123
+ const worldlineId = identifier(p.worldlineId, "worldline id");
124
+ const result = await this.#runtime.activateWorldline(snapshot.scope, Object.freeze({
125
+ worldlineId, expectedCatalogRevision: snapshot.catalogRevision,
126
+ expectedActiveWorldlineId: snapshot.activeWorldlineId,
127
+ idempotencyKey: idempotencyKey("activate", trustedContext, worldlineId), createdAtMs: trustedContext.createdAtMs,
128
+ }));
129
+ if (result.worldlineId !== worldlineId || result.activeWorldlineId !== worldlineId
130
+ || !Number.isSafeInteger(result.worldlineCount) || result.worldlineCount < 1
131
+ || !Number.isSafeInteger(result.messageCount) || result.messageCount < 0) {
132
+ fail("E_OPERATION_UNCERTAIN", "local Preview activate 未完成完整 state checkout");
133
+ }
134
+ return Object.freeze({ ...result });
135
+ }
136
+ }
@@ -0,0 +1,3 @@
1
+ import { LocalProjectHost, type LocalProjectHostOptions } from "./project-host.js";
2
+ /** 内部异步写入 dispatcher;不经 package exports 暴露。 */
3
+ export declare function runAsyncProjectHostMcp(cwd: string, lines: AsyncIterable<string>, write: (line: string) => Promise<void>, writeError: (line: string) => void, onReady: (() => void) | undefined, handle: (host: LocalProjectHost, request: unknown) => Promise<unknown | undefined>, hostOptions?: LocalProjectHostOptions): Promise<number>;
@@ -0,0 +1,129 @@
1
+ import { LocalProjectHost } from "./project-host.js";
2
+ import { toPublicError } from "./public-error.js";
3
+ const MCP_MAX_CONCURRENT_REQUESTS = 8;
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function requestId(value) {
8
+ if (!isRecord(value))
9
+ return undefined;
10
+ const id = value.id;
11
+ return id === null || typeof id === "string" || (typeof id === "number" && Number.isFinite(id))
12
+ ? id
13
+ : undefined;
14
+ }
15
+ function cancellationTarget(value) {
16
+ if (!isRecord(value) || value.jsonrpc !== "2.0" || value.id !== undefined || value.method !== "notifications/cancelled")
17
+ return undefined;
18
+ if (!isRecord(value.params))
19
+ return undefined;
20
+ const id = value.params.requestId;
21
+ return id === null || typeof id === "string" || (typeof id === "number" && Number.isFinite(id))
22
+ ? id
23
+ : undefined;
24
+ }
25
+ function requestKey(id) {
26
+ return `${typeof id}:${String(id)}`;
27
+ }
28
+ function cancelledResponse(id) {
29
+ return { jsonrpc: "2.0", id, error: { code: -32800, message: "Request cancelled" } };
30
+ }
31
+ /** 内部异步写入 dispatcher;不经 package exports 暴露。 */
32
+ export async function runAsyncProjectHostMcp(cwd, lines, write, writeError, onReady, handle, hostOptions = {}) {
33
+ let host;
34
+ try {
35
+ host = await LocalProjectHost.open(cwd, hostOptions);
36
+ }
37
+ catch (error) {
38
+ const publicError = toPublicError(error, "E_PROJECT_NOT_OPEN");
39
+ writeError(`${publicError.code}: 请求失败\n`);
40
+ return 1;
41
+ }
42
+ try {
43
+ onReady?.();
44
+ const pending = [];
45
+ const requestsById = new Map();
46
+ const active = new Set();
47
+ let outputTail = Promise.resolve();
48
+ let outputPending = 0;
49
+ let firstFailure;
50
+ const writeResponse = (response) => {
51
+ outputPending += 1;
52
+ const previous = outputTail;
53
+ const current = previous.then(() => write(`${JSON.stringify(response)}\n`));
54
+ // 后一个 response 仍需等待前一个 write,但不能让已观察的 rejection
55
+ // 变成 outputTail 的永久 rejected 链。
56
+ outputTail = current.catch(() => undefined).finally(() => { outputPending -= 1; });
57
+ return current;
58
+ };
59
+ const launch = () => {
60
+ while (active.size < MCP_MAX_CONCURRENT_REQUESTS) {
61
+ const item = pending.shift();
62
+ if (item === undefined)
63
+ return;
64
+ item.started = true;
65
+ const task = (async () => {
66
+ if (item.cancelled && item.requestId !== undefined) {
67
+ await writeResponse(cancelledResponse(item.requestId));
68
+ return;
69
+ }
70
+ const response = await handle(host, item.request);
71
+ if (response !== undefined)
72
+ await writeResponse(response);
73
+ })().catch((error) => {
74
+ firstFailure ??= error;
75
+ }).finally(() => {
76
+ if (item.requestId !== undefined)
77
+ requestsById.delete(requestKey(item.requestId));
78
+ active.delete(task);
79
+ launch();
80
+ });
81
+ active.add(task);
82
+ }
83
+ };
84
+ for await (const line of lines) {
85
+ if (line.trim() === "")
86
+ continue;
87
+ let request;
88
+ try {
89
+ request = JSON.parse(line);
90
+ }
91
+ catch {
92
+ await writeResponse({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
93
+ continue;
94
+ }
95
+ const cancelId = cancellationTarget(request);
96
+ if (cancelId !== undefined) {
97
+ const target = requestsById.get(requestKey(cancelId));
98
+ // 已开始的 effect 必须完成并返回 authoritative receipt;只有尚未
99
+ // dispatch 的请求能安全取消,避免“客户端看到 cancelled、磁盘已写入”。
100
+ if (target !== undefined && !target.started)
101
+ target.cancelled = true;
102
+ continue;
103
+ }
104
+ const id = requestId(request);
105
+ const item = { request, requestId: id, cancelled: false, started: false };
106
+ if (id !== undefined)
107
+ requestsById.set(requestKey(id), item);
108
+ pending.push(item);
109
+ launch();
110
+ // 快速请求已经进入 response backpressure 时,先停止拉取更多输入;
111
+ // 慢请求尚未产生输出时仍可继续填充最多八个并发槽。
112
+ await Promise.resolve();
113
+ if (outputPending > 0)
114
+ await outputTail;
115
+ }
116
+ while (active.size > 0 || pending.length > 0) {
117
+ launch();
118
+ if (active.size > 0)
119
+ await Promise.race(active);
120
+ }
121
+ await outputTail;
122
+ if (firstFailure !== undefined)
123
+ throw firstFailure;
124
+ return 0;
125
+ }
126
+ finally {
127
+ await host.close();
128
+ }
129
+ }
@@ -0,0 +1,14 @@
1
+ import type { Readable } from "node:stream";
2
+ export declare const MCP_MAX_LINE_BYTES: number;
3
+ /**
4
+ * MCP 原始字节分帧与有界队列。partial line 在追加前受 12MiB 上限约束,
5
+ * 因此没有换行的输入不能绕过 readline 的 line 级限制并持续累积内存。
6
+ * 此模块不经 package exports 暴露;仅 MCP 入口和同包测试使用。
7
+ */
8
+ export declare function createBoundedMcpLineQueue(input: Readable): {
9
+ readonly lines: AsyncIterable<string>;
10
+ readonly close: () => void;
11
+ readonly pause: () => void;
12
+ readonly resume: () => void;
13
+ readonly bufferedBytes: number;
14
+ };
@@ -0,0 +1,184 @@
1
+ import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
2
+ export const MCP_MAX_LINE_BYTES = 12 * 1024 * 1024;
3
+ const MAX_QUEUED_BYTES = 16 * 1024 * 1024;
4
+ const MAX_QUEUED_LINES = 128;
5
+ const PAUSE_QUEUED_BYTES = 12 * 1024 * 1024;
6
+ const PAUSE_QUEUED_LINES = 96;
7
+ const RESUME_QUEUED_BYTES = 8 * 1024 * 1024;
8
+ const RESUME_QUEUED_LINES = 64;
9
+ /**
10
+ * MCP 原始字节分帧与有界队列。partial line 在追加前受 12MiB 上限约束,
11
+ * 因此没有换行的输入不能绕过 readline 的 line 级限制并持续累积内存。
12
+ * 此模块不经 package exports 暴露;仅 MCP 入口和同包测试使用。
13
+ */
14
+ export function createBoundedMcpLineQueue(input) {
15
+ const buffered = [];
16
+ const partial = [];
17
+ let head = 0;
18
+ let queuedBytes = 0;
19
+ let partialBytes = 0;
20
+ let previousWasCarriageReturn = false;
21
+ let inputPaused = false;
22
+ let closed = false;
23
+ let failure;
24
+ let wake;
25
+ const bufferedBytes = () => queuedBytes + partialBytes;
26
+ const notify = () => {
27
+ const current = wake;
28
+ wake = undefined;
29
+ current?.();
30
+ };
31
+ const pauseInput = () => {
32
+ if (inputPaused)
33
+ return;
34
+ input.pause();
35
+ inputPaused = true;
36
+ };
37
+ const resumeInput = () => {
38
+ if (!inputPaused || closed)
39
+ return;
40
+ input.resume();
41
+ inputPaused = false;
42
+ };
43
+ const close = () => {
44
+ if (closed)
45
+ return;
46
+ closed = true;
47
+ pauseInput();
48
+ notify();
49
+ };
50
+ const fail = (error) => {
51
+ if (closed || failure !== undefined)
52
+ return;
53
+ failure = error;
54
+ partial.length = 0;
55
+ partialBytes = 0;
56
+ close();
57
+ input.destroy();
58
+ };
59
+ const enqueue = (rawLine) => {
60
+ let line;
61
+ try {
62
+ line = new TextDecoder("utf-8", { fatal: true }).decode(rawLine);
63
+ }
64
+ catch {
65
+ fail(new AuthoringBridgeError("E_SCHEMA_INVALID", "MCP 行不是有效 UTF-8"));
66
+ return;
67
+ }
68
+ const bytes = rawLine.length;
69
+ const queuedLines = buffered.length - head;
70
+ if (queuedLines >= MAX_QUEUED_LINES || queuedBytes + bytes > MAX_QUEUED_BYTES) {
71
+ fail(new AuthoringBridgeError("E_SCHEMA_INVALID", "MCP 请求队列超过上限"));
72
+ return;
73
+ }
74
+ buffered.push({ line, bytes });
75
+ queuedBytes += bytes;
76
+ if (queuedLines + 1 >= PAUSE_QUEUED_LINES || queuedBytes >= PAUSE_QUEUED_BYTES)
77
+ pauseInput();
78
+ notify();
79
+ };
80
+ const completeLine = () => {
81
+ const onlyPartial = partial[0];
82
+ const line = partial.length === 0
83
+ ? Buffer.alloc(0)
84
+ : partial.length === 1 && onlyPartial !== undefined
85
+ ? onlyPartial
86
+ : Buffer.concat(partial, partialBytes);
87
+ partial.length = 0;
88
+ partialBytes = 0;
89
+ enqueue(line);
90
+ };
91
+ const appendPartial = (segment) => {
92
+ if (partialBytes + segment.length > MCP_MAX_LINE_BYTES) {
93
+ fail(new AuthoringBridgeError("E_SCHEMA_INVALID", "MCP 单行超过上限"));
94
+ return;
95
+ }
96
+ if (queuedBytes + partialBytes + segment.length > MAX_QUEUED_BYTES) {
97
+ fail(new AuthoringBridgeError("E_SCHEMA_INVALID", "MCP 缓存超过上限"));
98
+ return;
99
+ }
100
+ if (segment.length === 0)
101
+ return;
102
+ partial.push(segment);
103
+ partialBytes += segment.length;
104
+ };
105
+ const onData = (chunk) => {
106
+ if (closed)
107
+ return;
108
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
109
+ let start = 0;
110
+ if (previousWasCarriageReturn && bytes.length > 0) {
111
+ if (bytes[0] === 0x0a)
112
+ start = 1;
113
+ previousWasCarriageReturn = false;
114
+ }
115
+ while (start < bytes.length && !closed) {
116
+ let delimiter = start;
117
+ while (delimiter < bytes.length && bytes[delimiter] !== 0x0a && bytes[delimiter] !== 0x0d)
118
+ delimiter += 1;
119
+ appendPartial(bytes.subarray(start, delimiter));
120
+ if (closed)
121
+ return;
122
+ if (delimiter === bytes.length)
123
+ return;
124
+ if (bytes[delimiter] === 0x0a) {
125
+ completeLine();
126
+ start = delimiter + 1;
127
+ continue;
128
+ }
129
+ if (delimiter + 1 === bytes.length) {
130
+ completeLine();
131
+ previousWasCarriageReturn = true;
132
+ return;
133
+ }
134
+ completeLine();
135
+ previousWasCarriageReturn = true;
136
+ start = delimiter + 1;
137
+ if (bytes[start] === 0x0a) {
138
+ previousWasCarriageReturn = false;
139
+ start += 1;
140
+ }
141
+ else {
142
+ previousWasCarriageReturn = false;
143
+ }
144
+ }
145
+ };
146
+ const onEnd = () => {
147
+ if (closed)
148
+ return;
149
+ if (partialBytes > 0)
150
+ completeLine();
151
+ if (closed)
152
+ return;
153
+ closed = true;
154
+ notify();
155
+ };
156
+ const onError = () => fail(new AuthoringBridgeError("E_SCHEMA_INVALID", "MCP input 失败"));
157
+ input.on("data", onData);
158
+ input.once("end", onEnd);
159
+ input.once("close", onEnd);
160
+ input.on("error", onError);
161
+ const lines = (async function* () {
162
+ while (true) {
163
+ if (failure !== undefined)
164
+ throw failure;
165
+ const entry = buffered[head];
166
+ if (entry !== undefined) {
167
+ head += 1;
168
+ queuedBytes -= entry.bytes;
169
+ if (head === buffered.length) {
170
+ buffered.length = 0;
171
+ head = 0;
172
+ }
173
+ if (inputPaused && queuedBytes <= RESUME_QUEUED_BYTES && buffered.length - head <= RESUME_QUEUED_LINES)
174
+ resumeInput();
175
+ yield entry.line;
176
+ continue;
177
+ }
178
+ if (closed)
179
+ return;
180
+ await new Promise((resolvePromise) => { wake = resolvePromise; });
181
+ }
182
+ })();
183
+ return { lines, close, pause: pauseInput, resume: resumeInput, get bufferedBytes() { return bufferedBytes(); } };
184
+ }
@@ -0,0 +1,6 @@
1
+ import { type Server, type Socket } from "node:net";
2
+ export interface ProjectHostMcpPipeRuntime {
3
+ readonly createServer?: (connectionListener: (socket: Socket) => void) => Server;
4
+ }
5
+ /** T027 的 inherited-capability 内部入口;不由公开 package export 或 CLI 暴露。 */
6
+ export declare function runProjectHostMcpPipe(cwd: string, pipe: string, challenge: string, runtime?: ProjectHostMcpPipeRuntime): Promise<number>;