@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,613 @@
1
+ import { lstat, open, readFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { sha256Hex } from "@world-engines/project-format";
5
+ import { AuthoringBridgeError, authoringToolDescriptor } from "@world-engines/authoring-bridge";
6
+ import { LocalProjectHost } from "./project-host.js";
7
+ import { runProjectHostMcpStdio } from "./mcp.js";
8
+ import { LocalPreviewRuntime, PreviewRuntimeError } from "./preview-runtime.js";
9
+ import { ProjectTransferCoordinator } from "./transfer.js";
10
+ import { RemoteDownloadCoordinator } from "./transfer/download.js";
11
+ import { parseDownloadInvocation, toDownloadRemoteRequest } from "./cli/download.js";
12
+ import { parseUploadInvocation } from "./cli/upload.js";
13
+ import { RemoteUploadCoordinator } from "./transfer/upload.js";
14
+ import { toPublicError } from "./public-error.js";
15
+ import { ingestProjectDocument } from "./authoring-ingestion.js";
16
+ import { loadProjectDocumentSource, parseProjectDocumentIngestInvocation, parseProjectDocumentOperationInvocation, resolvePathOnlyProjectDocumentIngest, stageProjectDocumentBytes } from "./cli/ingest.js";
17
+ import { parseDocumentDumpInvocation, writeDocumentPlainTextDump } from "./cli/dump.js";
18
+ import { assertSafeExistingProjectPath } from "./safe-project-path.js";
19
+ import { answerDocumentQuestion, retrieveDocumentEvidenceBundle } from "./document-query.js";
20
+ import { ingestLosslessDocument, readLosslessDocumentStatus, resumeLosslessDocument } from "./document-ingestion.js";
21
+ import { prepareHarnessDocumentIngestion, nextHarnessDocumentIngestion, acceptHarnessDocumentIngestion, commitHarnessDocumentIngestion, } from "./harness-document-ingestion.js";
22
+ import { FrozenReviewCoordinator } from "./frozen-review.js";
23
+ import { parseReviewInvocation } from "./cli/review.js";
24
+ import { startLocalGuiServer } from "./local-gui-server.js";
25
+ import { prepareLocalRecallSnapshot } from "./prepared-embedding-cache.js";
26
+ export async function verifyLocalGuiAssets(overrides = {}) {
27
+ const roots = {
28
+ source: overrides.source ?? fileURLToPath(new URL("../assets/gui/", import.meta.url)),
29
+ generated: overrides.generated ?? fileURLToPath(new URL("./gui/", import.meta.url)),
30
+ };
31
+ for (const [area, path] of [
32
+ ["source", "index.html"],
33
+ ["source", "app.js"],
34
+ ["source", "local-preview-bridge.js"],
35
+ ["source", "local-preview-asset-urls.js"],
36
+ ["source", "styles.css"],
37
+ ["source", "WELogo-title.svg"],
38
+ ["generated", "graph.js"],
39
+ ["generated", "graph.css"],
40
+ ["generated", "score.js"],
41
+ ["generated", "score.css"],
42
+ ["generated", "trigger.js"],
43
+ ["generated", "trigger.css"],
44
+ ["generated", "graph-layout-worker.js"],
45
+ ["generated", "graph-layout.wasm"],
46
+ ]) {
47
+ try {
48
+ const info = await lstat(join(roots[area], path));
49
+ if (!info.isFile() || info.isSymbolicLink())
50
+ throw new Error("not a regular file");
51
+ }
52
+ catch {
53
+ const error = new Error(`GUI asset 缺失:${path}`);
54
+ error.code = "E_GUI_UNAVAILABLE";
55
+ throw error;
56
+ }
57
+ }
58
+ }
59
+ function authoringModelConnection(runtime) {
60
+ return runtime.modelConnection ?? runtime.losslessIngestion?.modelConnection ?? runtime.ingestion?.modelConnection;
61
+ }
62
+ /** 已持久化的恢复 identity 立即写入 stderr JSONL;stdout 仍只有最终 receipt。 */
63
+ function cliLosslessCapability(capability, io) {
64
+ return {
65
+ ...capability,
66
+ async onProgress(progress) {
67
+ io.stderr(`${JSON.stringify(progress)}\n`);
68
+ await capability.onProgress?.(progress);
69
+ },
70
+ };
71
+ }
72
+ function authoringHostOptions(runtime) {
73
+ const embedding = runtime.embedding ?? authoringModelConnection(runtime)?.embedding;
74
+ const capabilities = runtime.desktopAuthoringCapabilities;
75
+ return embedding === undefined && runtime.resolveEmbedding === undefined && capabilities === undefined ? {} : {
76
+ desktop_authoring_capabilities: {
77
+ ...capabilities,
78
+ ...(runtime.resolveEmbedding === undefined ? {} : { resolve_ladybug_embedding_port: runtime.resolveEmbedding }),
79
+ ...(embedding === undefined ? {} : {
80
+ ladybug_embedding_port: embedding,
81
+ }),
82
+ },
83
+ };
84
+ }
85
+ function writeJson(io, value) {
86
+ io.stdout(`${JSON.stringify(value, null, 2)}\n`);
87
+ }
88
+ function requiredCliValue(value, message) {
89
+ if (value === undefined)
90
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", message);
91
+ return value;
92
+ }
93
+ async function readSnapshotOperation(root, operationId) {
94
+ const ledgerKey = await sha256Hex(new TextEncoder().encode(operationId));
95
+ const path = join(root, ".worldengine", "operations", `snapshot-${ledgerKey}.json`);
96
+ return JSON.parse(await readFile(path, "utf8"));
97
+ }
98
+ /**
99
+ * 根脚本 `worldengine snapshot` 没有可由调用方持久化的 effect identity。
100
+ * 因此把项目 authority 中已经 readback 的 identity 和本命令固定请求一起哈希;
101
+ * 同一 project/revision 的崩溃重试会回到同一 durable ledger,而不会盲目生成新 ID。
102
+ */
103
+ async function deriveImplicitSnapshotOperation(projectId, expectedProjectRevision) {
104
+ const canonicalRequest = JSON.stringify({
105
+ command: "worldengine.snapshot.v1",
106
+ project_id: projectId,
107
+ expected_project_revision: expectedProjectRevision,
108
+ });
109
+ const digest = await sha256Hex(new TextEncoder().encode(canonicalRequest));
110
+ return `snapshot_${digest}`;
111
+ }
112
+ function parseToolCall(name, input, identityArgs) {
113
+ if (name === undefined) {
114
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool 需要 name 与 input JSON");
115
+ }
116
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
117
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool input 必须是 JSON object");
118
+ }
119
+ const descriptor = authoringToolDescriptor(name);
120
+ if (!descriptor.project_effect) {
121
+ if (identityArgs.length !== 0)
122
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "只读 tool 不接受 effect identity");
123
+ return { name: name, input: input };
124
+ }
125
+ const [operationId, expectedProjectRevision] = identityArgs;
126
+ if (operationId === undefined || expectedProjectRevision === undefined || identityArgs.length !== 2 || !/^\d+$/.test(expectedProjectRevision)) {
127
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "effect tool 需要稳定 operation_id 与 expected_project_revision");
128
+ }
129
+ return {
130
+ name: name,
131
+ input: input,
132
+ effect_identity: { operation_id: operationId, expected_project_revision: Number(expectedProjectRevision) },
133
+ };
134
+ }
135
+ async function readProjectToolInputJson(projectRoot, inputPath) {
136
+ const path = await assertSafeExistingProjectPath(projectRoot, resolve(projectRoot, inputPath));
137
+ const info = await lstat(path);
138
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 8 * 1024 * 1024) {
139
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool input JSON 文件无效或过大");
140
+ }
141
+ const handle = await open(path, "r");
142
+ try {
143
+ const before = await handle.stat();
144
+ if (!before.isFile() || before.size > 8 * 1024 * 1024 || before.dev !== info.dev || before.ino !== info.ino) {
145
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool input JSON 文件无效或过大");
146
+ }
147
+ const bytes = await handle.readFile();
148
+ const after = await handle.stat();
149
+ if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs
150
+ || bytes.byteLength !== after.size)
151
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool input JSON 文件读取期间发生变化");
152
+ try {
153
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
154
+ }
155
+ catch {
156
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool input JSON 文件必须是 JSON");
157
+ }
158
+ }
159
+ finally {
160
+ await handle.close();
161
+ }
162
+ }
163
+ async function parseProjectToolCall(projectRoot, args) {
164
+ const [name, input, ...rest] = args;
165
+ if (input === "--input-json-file") {
166
+ const [inputPath, ...identityArgs] = rest;
167
+ if (inputPath === undefined)
168
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool --input-json-file 需要项目相对 JSON 文件");
169
+ return parseToolCall(name, await readProjectToolInputJson(projectRoot, inputPath), identityArgs);
170
+ }
171
+ if (input === undefined)
172
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool 需要 name 与 input JSON");
173
+ let parsed;
174
+ try {
175
+ parsed = JSON.parse(input);
176
+ }
177
+ catch {
178
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "tool input 必须是 JSON object");
179
+ }
180
+ return parseToolCall(name, parsed, rest);
181
+ }
182
+ export async function runWorldengineCli(args, cwd, io, previewRuntime = new LocalPreviewRuntime(), downloadRuntime = {}, uploadRuntime = {}, authoringRuntime = {}, reviewRuntime = {}, guiRuntime = {}) {
183
+ try {
184
+ const [command, subcommand, operand] = args;
185
+ if (command === undefined || command === "help" || command === "--help") {
186
+ io.stdout("worldengine doctor | snapshot [operation_id expected_project_revision] | tool <registered-tool-name> <input-json>|--input-json-file <project-relative-json> [operation_id expected_project_revision] | ingest <path> <scope-id> <source-id> <source-revision> | ingest status|next|resume|commit <operation-id> | ingest accept <operation-id> <request-id> <candidate-json-file> | status|resume <operation-id> | ask <scope-id> <query> | dump <document-id> <output-relative-dir> | recall prepare [scope-id] | recall <query> [limit] | read <entity-id> | review prepare|submit|status | sync status | operation <id> | upload plan|confirm|operation|reconcile|retry|undo|bind-review-target | download plan|confirm|operation|reconcile|restore | preview | gui | mcp\n");
187
+ return 0;
188
+ }
189
+ if (command === "mcp") {
190
+ if (args.length === 1)
191
+ return await runProjectHostMcpStdio(cwd, undefined, authoringHostOptions(authoringRuntime));
192
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "mcp 参数无效");
193
+ }
194
+ const host = await LocalProjectHost.open(cwd, authoringHostOptions(authoringRuntime));
195
+ try {
196
+ if (command === "doctor") {
197
+ const report = await host.doctor();
198
+ writeJson(io, report);
199
+ return report.ok ? 0 : 1;
200
+ }
201
+ if (command === "snapshot") {
202
+ let operationId;
203
+ let expectedProjectRevision;
204
+ if (args.length === 1) {
205
+ const status = await host.inspect();
206
+ expectedProjectRevision = status.project_revision;
207
+ operationId = await deriveImplicitSnapshotOperation(status.project_id, expectedProjectRevision);
208
+ }
209
+ else if (subcommand !== undefined && operand !== undefined && args.length === 3 && /^\d+$/.test(operand)) {
210
+ operationId = subcommand;
211
+ expectedProjectRevision = Number(operand);
212
+ }
213
+ else {
214
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "snapshot 需要稳定 operation_id 与 expected_project_revision");
215
+ }
216
+ writeJson(io, await host.snapshot({
217
+ operation_id: operationId,
218
+ expected_project_revision: expectedProjectRevision,
219
+ }));
220
+ return 0;
221
+ }
222
+ if (command === "sync" && subcommand === "status") {
223
+ try {
224
+ const status = await host.inspect();
225
+ const transfer = await ProjectTransferCoordinator.open(host.root);
226
+ writeJson(io, {
227
+ ...(await transfer.status()),
228
+ snapshot_id: status.snapshot_id,
229
+ snapshot_digest: status.snapshot_digest,
230
+ });
231
+ return 0;
232
+ }
233
+ catch (error) {
234
+ io.stderr(`${toPublicError(error, "E_VALIDATION_FAILED").code}: 请求失败\n`);
235
+ return 1;
236
+ }
237
+ }
238
+ if (command === "operation" && subcommand !== undefined) {
239
+ writeJson(io, await readSnapshotOperation(host.root, subcommand));
240
+ return 0;
241
+ }
242
+ if (command === "download") {
243
+ const invocation = parseDownloadInvocation(args.slice(1));
244
+ const status = await host.inspect();
245
+ const coordinator = await RemoteDownloadCoordinator.open(host.root, status.project_id, downloadRuntime.transport, downloadRuntime.dependencies);
246
+ if (invocation.kind === "plan") {
247
+ writeJson(io, await coordinator.plan(toDownloadRemoteRequest(status.project_id, invocation)));
248
+ return 0;
249
+ }
250
+ if (invocation.kind === "confirm") {
251
+ const plan = requiredCliValue(invocation.plan, "download confirm 缺少 plan");
252
+ const planDigest = requiredCliValue(invocation.plan_digest, "download confirm 缺少 plan digest");
253
+ const operation = await coordinator.confirm(plan, planDigest);
254
+ writeJson(io, { operation, receipt: await coordinator.apply(plan, operation) });
255
+ return 0;
256
+ }
257
+ if (invocation.kind === "restore") {
258
+ const operationId = requiredCliValue(invocation.operation_id, "download restore 缺少 operation_id");
259
+ await coordinator.restore(operationId);
260
+ writeJson(io, { operation_id: invocation.operation_id, restored: true });
261
+ return 0;
262
+ }
263
+ writeJson(io, await coordinator.operation(requiredCliValue(invocation.operation_id, "download operation 缺少 operation_id"), invocation.kind === "reconcile"));
264
+ return 0;
265
+ }
266
+ if (command === "upload") {
267
+ const invocation = parseUploadInvocation(args.slice(1));
268
+ const status = await host.inspect();
269
+ const coordinator = await RemoteUploadCoordinator.open(host.root, status.project_id, uploadRuntime.transport ?? downloadRuntime.transport);
270
+ if (invocation.kind === "plan") {
271
+ // upload 只能消费已经由同一稳定 operation identity 冻结的 Project Snapshot。
272
+ const operationId = requiredCliValue(invocation.operation_id, "upload plan 缺少 operation_id");
273
+ const expectedProjectRevision = requiredCliValue(invocation.expected_project_revision, "upload plan 缺少 expected_project_revision");
274
+ const expiresAtMs = requiredCliValue(invocation.expires_at_ms, "upload plan 缺少 expires_at_ms");
275
+ const targets = requiredCliValue(invocation.targets, "upload plan 缺少 targets");
276
+ await host.snapshot({ operation_id: operationId, expected_project_revision: expectedProjectRevision });
277
+ writeJson(io, await coordinator.plan({ operation_id: operationId, expires_at_ms: expiresAtMs, targets }));
278
+ return 0;
279
+ }
280
+ if (invocation.kind === "confirm") {
281
+ const plan = invocation.plan;
282
+ if (invocation.plan_digest === undefined) {
283
+ if (io.confirmUploadTarget === undefined)
284
+ throw new AuthoringBridgeError("E_CONFIRMATION_REQUIRED", "非交互 upload 必须提供 --confirm-plan <digest>");
285
+ for (const target of plan.targets) {
286
+ if (!(await io.confirmUploadTarget(target)))
287
+ throw new AuthoringBridgeError("E_CONFIRMATION_REQUIRED", "upload 未获得逐目标明确确认");
288
+ }
289
+ }
290
+ writeJson(io, await coordinator.confirm(plan, invocation.plan_digest ?? plan.plan_digest));
291
+ return 0;
292
+ }
293
+ if (invocation.kind === "retry") {
294
+ writeJson(io, await coordinator.retry(requiredCliValue(invocation.operation_id, "upload retry 缺少 operation_id")));
295
+ return 0;
296
+ }
297
+ if (invocation.kind === "undo") {
298
+ writeJson(io, await coordinator.undo(requiredCliValue(invocation.operation_id, "upload undo 缺少 operation_id")));
299
+ return 0;
300
+ }
301
+ if (invocation.kind === "bind-review-target") {
302
+ writeJson(io, await coordinator.bindFrozenReviewTarget(requiredCliValue(invocation.operation_id, "upload bind-review-target 缺少 operation_id"), invocation.client_target_key === undefined ? {} : { client_target_key: invocation.client_target_key }));
303
+ return 0;
304
+ }
305
+ writeJson(io, await coordinator.operation(requiredCliValue(invocation.operation_id, "upload operation 缺少 operation_id"), invocation.kind === "reconcile"));
306
+ return 0;
307
+ }
308
+ if (command === "review") {
309
+ const invocation = parseReviewInvocation(args.slice(1));
310
+ const status = await host.inspect();
311
+ const coordinator = await FrozenReviewCoordinator.open(host.root, status.project_id, {
312
+ ...(reviewRuntime.pipeline === undefined ? {} : { pipeline: reviewRuntime.pipeline }),
313
+ ...(reviewRuntime.transport ?? uploadRuntime.transport ?? downloadRuntime.transport) === undefined
314
+ ? {}
315
+ : { transport: reviewRuntime.transport ?? uploadRuntime.transport ?? downloadRuntime.transport },
316
+ ...(reviewRuntime.recovery === undefined ? {} : { recovery: reviewRuntime.recovery }),
317
+ });
318
+ if (invocation.kind === "status") {
319
+ writeJson(io, await coordinator.status(invocation.operation_key));
320
+ }
321
+ else if (invocation.kind === "prepare") {
322
+ writeJson(io, await coordinator.prepare(invocation));
323
+ }
324
+ else {
325
+ writeJson(io, await coordinator.submit(invocation));
326
+ }
327
+ return 0;
328
+ }
329
+ if (command === "tool") {
330
+ writeJson(io, await host.dispatchTool(await parseProjectToolCall(host.root, args.slice(1))));
331
+ return 0;
332
+ }
333
+ if (command === "dump") {
334
+ writeJson(io, await writeDocumentPlainTextDump(host, parseDocumentDumpInvocation(args.slice(1))));
335
+ return 0;
336
+ }
337
+ if (command === "ingest" && ["next", "accept", "commit"].includes(subcommand ?? "")) {
338
+ if (operand === undefined)
339
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "ingest 需要 operation-id");
340
+ if (subcommand === "accept") {
341
+ const requestId = args[3];
342
+ const candidatePath = args[4];
343
+ if (args.length !== 5 || requestId === undefined || candidatePath === undefined) {
344
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "ingest accept 需要 operation-id/request-id/candidate-json-file");
345
+ }
346
+ const safePath = await assertSafeExistingProjectPath(host.root, resolve(host.root, candidatePath));
347
+ const info = await lstat(safePath);
348
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 8 * 1024 * 1024)
349
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "抽取结果文件无效或过大");
350
+ let candidate;
351
+ const candidateHandle = await open(safePath, "r");
352
+ try {
353
+ const before = await candidateHandle.stat();
354
+ if (!before.isFile() || before.size > 8 * 1024 * 1024 || before.dev !== info.dev || before.ino !== info.ino) {
355
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "抽取结果文件无效或过大");
356
+ }
357
+ const bytes = await candidateHandle.readFile();
358
+ const after = await candidateHandle.stat();
359
+ if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs
360
+ || bytes.byteLength !== after.size)
361
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "抽取结果文件读取期间发生变化");
362
+ candidate = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
363
+ }
364
+ catch {
365
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "抽取结果必须是 JSON");
366
+ }
367
+ finally {
368
+ await candidateHandle.close();
369
+ }
370
+ writeJson(io, await acceptHarnessDocumentIngestion(host.root, operand, requestId, candidate));
371
+ }
372
+ else {
373
+ if (args.length !== 3)
374
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "ingest next/commit 参数无效");
375
+ writeJson(io, subcommand === "next"
376
+ ? await nextHarnessDocumentIngestion(host.root, operand)
377
+ : await commitHarnessDocumentIngestion(host, host.root, operand));
378
+ }
379
+ return 0;
380
+ }
381
+ if (command === "status" || command === "resume" || (command === "ingest" && (subcommand === "status" || subcommand === "resume"))) {
382
+ let invocation;
383
+ try {
384
+ invocation = parseProjectDocumentOperationInvocation(command === "ingest" ? args.slice(1) : args);
385
+ }
386
+ catch {
387
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "status/resume 需要稳定 operation-id");
388
+ }
389
+ const status = await readLosslessDocumentStatus(host.root, invocation.operationId);
390
+ if (invocation.kind === "status") {
391
+ writeJson(io, status);
392
+ }
393
+ else if (status.method?.version === "harness-v1") {
394
+ const next = await nextHarnessDocumentIngestion(host.root, invocation.operationId);
395
+ writeJson(io, next.state === "ready_to_commit" || next.state === "committed"
396
+ ? await commitHarnessDocumentIngestion(host, host.root, invocation.operationId)
397
+ : next);
398
+ }
399
+ else {
400
+ if (authoringRuntime.losslessIngestion === undefined)
401
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "旧 operation 需要原执行适配器,不能静默改成 harness 方法", { failure: "E_CAPABILITY_UNAVAILABLE" });
402
+ writeJson(io, await resumeLosslessDocument(host, invocation.operationId, cliLosslessCapability(authoringRuntime.losslessIngestion, io)));
403
+ }
404
+ return 0;
405
+ }
406
+ if (command === "ingest") {
407
+ if (args.length === 2) {
408
+ let resolved;
409
+ try {
410
+ resolved = await resolvePathOnlyProjectDocumentIngest(host.root, requiredCliValue(args[1], "ingest 需要原文路径"));
411
+ }
412
+ catch {
413
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "ingest 单路径参数或项目原文无效");
414
+ }
415
+ // 单路径模式只准备 harness 请求。只有用户的 harness 可在稍后经 ingest accept 提交候选。
416
+ const prepared = await prepareHarnessDocumentIngestion(host, {
417
+ bytes: resolved.source.bytes,
418
+ scopeId: resolved.source.scopeId,
419
+ sourceId: resolved.source.sourceId,
420
+ sourceRevision: resolved.source.sourceRevision,
421
+ sourcePath: resolved.source.sourcePath,
422
+ ...(resolved.source.sourceStoragePath === undefined ? {} : { sourceStoragePath: resolved.source.sourceStoragePath }),
423
+ }, { projectRoot: host.root });
424
+ writeJson(io, {
425
+ mode: "path-only-harness-prepare-v1",
426
+ resolved_source: {
427
+ scope_id: resolved.invocation.scopeId,
428
+ source_id: resolved.invocation.sourceId,
429
+ source_revision: resolved.invocation.sourceRevision,
430
+ source_path: resolved.source.sourcePath,
431
+ ...(resolved.source.sourceStoragePath === undefined ? {} : { source_storage_path: resolved.source.sourceStoragePath }),
432
+ },
433
+ preparation: prepared,
434
+ });
435
+ return 0;
436
+ }
437
+ let invocation;
438
+ try {
439
+ invocation = parseProjectDocumentIngestInvocation(args.slice(1));
440
+ }
441
+ catch {
442
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "ingest 参数无效");
443
+ }
444
+ if (authoringRuntime.losslessIngestion === undefined && authoringRuntime.ingestion === undefined) {
445
+ const source = await stageProjectDocumentBytes(host.root, invocation);
446
+ writeJson(io, await prepareHarnessDocumentIngestion(host, {
447
+ bytes: source.bytes, scopeId: source.scopeId, sourceId: source.sourceId,
448
+ sourceRevision: source.sourceRevision, sourcePath: source.sourcePath,
449
+ ...(source.sourceStoragePath === undefined ? {} : { sourceStoragePath: source.sourceStoragePath }),
450
+ }, { projectRoot: host.root }));
451
+ }
452
+ else if (authoringRuntime.losslessIngestion !== undefined) {
453
+ const source = await stageProjectDocumentBytes(host.root, invocation);
454
+ writeJson(io, await ingestLosslessDocument(host, {
455
+ bytes: source.bytes,
456
+ scopeId: source.scopeId,
457
+ sourceId: source.sourceId,
458
+ sourceRevision: source.sourceRevision,
459
+ sourcePath: source.sourcePath,
460
+ ...(source.sourceStoragePath === undefined ? {} : { sourceStoragePath: source.sourceStoragePath }),
461
+ }, cliLosslessCapability(authoringRuntime.losslessIngestion, io)));
462
+ }
463
+ else {
464
+ writeJson(io, await ingestProjectDocument(host, await loadProjectDocumentSource(host.root, invocation), authoringRuntime.ingestion));
465
+ }
466
+ return 0;
467
+ }
468
+ if (command === "recall" && subcommand === "prepare") {
469
+ if (args.length > 3)
470
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "recall prepare 仅接受可选 scope-id");
471
+ const embedding = await authoringRuntime.resolveEmbedding?.() ?? authoringRuntime.embedding ?? authoringModelConnection(authoringRuntime)?.embedding;
472
+ if (embedding?.origin === undefined)
473
+ throw new AuthoringBridgeError("E_AUTH_REQUIRED", "请先在本地 GUI 登录并启动账户模型代理");
474
+ const prepared = await prepareLocalRecallSnapshot(host, { ...embedding, origin: embedding.origin }, operand === undefined ? {} : { scopeId: operand });
475
+ writeJson(io, {
476
+ status: "prepared", project_id: prepared.project_id, project_revision: prepared.project_revision,
477
+ source_sha256: prepared.source_sha256, scope_id: prepared.scope_id,
478
+ entity_count: prepared.snapshot.batch.entries.length,
479
+ descriptor: prepared.snapshot.batch.descriptor,
480
+ readback_verified: true,
481
+ });
482
+ return 0;
483
+ }
484
+ if (command === "recall") {
485
+ if (subcommand === undefined || args.length > 3 || (operand !== undefined && !/^\d+$/.test(operand))) {
486
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "recall 需要 query 与可选 limit");
487
+ }
488
+ writeJson(io, await host.dispatchTool({
489
+ name: "world_search",
490
+ input: { query: subcommand, mode: "semantic", ...(operand === undefined ? {} : { limit: Number(operand) }) },
491
+ }));
492
+ return 0;
493
+ }
494
+ if (command === "ask") {
495
+ const modelConnection = authoringModelConnection(authoringRuntime);
496
+ if (subcommand === undefined || operand === undefined || args.length !== 3) {
497
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "ask 需要 scope-id 与 query");
498
+ }
499
+ // npm 作者路径只返回可验证证据,由当前 harness 推理;保留显式内部 adapter 的历史验收。
500
+ writeJson(io, modelConnection === undefined
501
+ ? await retrieveDocumentEvidenceBundle(host, { scopeId: subcommand, query: operand })
502
+ : await answerDocumentQuestion(host, { scopeId: subcommand, query: operand }, { modelConnection }));
503
+ return 0;
504
+ }
505
+ if (command === "read") {
506
+ if (subcommand === undefined || args.length !== 2) {
507
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "read 需要 entity-id");
508
+ }
509
+ writeJson(io, await host.dispatchTool({ name: "world_get", input: { ids: [subcommand], include_relations: true } }));
510
+ return 0;
511
+ }
512
+ if (command === "preview") {
513
+ try {
514
+ writeJson(io, await previewRuntime.start(host.root));
515
+ return 0;
516
+ }
517
+ catch (error) {
518
+ if (error instanceof PreviewRuntimeError) {
519
+ io.stderr(`${toPublicError(error).code}: 请求失败\n`);
520
+ return 1;
521
+ }
522
+ throw error;
523
+ }
524
+ }
525
+ if (command === "gui") {
526
+ const guiArgs = args.slice(1);
527
+ if (guiArgs.length > 1 || (guiArgs[0] !== undefined && !["--no-open", "--check"].includes(guiArgs[0]))) {
528
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "gui 只接受 --no-open 或 --check");
529
+ }
530
+ if (guiArgs[0] === "--check") {
531
+ await verifyLocalGuiAssets();
532
+ return 0;
533
+ }
534
+ const commandOutputLimit = 1024 * 1024;
535
+ const runCommand = async (commandArgs, context) => {
536
+ let stdout = "";
537
+ let stderr = "";
538
+ const append = (current, text) => {
539
+ if (Buffer.byteLength(current, "utf8") + Buffer.byteLength(text, "utf8") > commandOutputLimit) {
540
+ throw new AuthoringBridgeError("E_VALIDATION_FAILED", "GUI command 输出超过上限");
541
+ }
542
+ return current + text;
543
+ };
544
+ const commandRuntimes = context.auth === undefined || guiRuntime.runtimesForTransport === undefined
545
+ ? { download: downloadRuntime, upload: uploadRuntime, review: reviewRuntime }
546
+ : guiRuntime.runtimesForTransport(context.auth.transport);
547
+ const exitCode = await runWorldengineCli(commandArgs, host.root, {
548
+ stdout: (text) => { stdout = append(stdout, text); },
549
+ stderr: (text) => { stderr = append(stderr, text); },
550
+ }, previewRuntime, commandRuntimes.download, commandRuntimes.upload, authoringRuntime, commandRuntimes.review, guiRuntime);
551
+ if (exitCode !== 0) {
552
+ const reportedCode = /^(E_[A-Z0-9_]+): 请求失败$/m.exec(stderr)?.[1];
553
+ const candidate = Object.assign(new Error("GUI command failed"), { code: reportedCode ?? "E_VALIDATION_FAILED" });
554
+ const error = Object.assign(new Error(`GUI command 退出码 ${exitCode}`), { code: toPublicError(candidate, "E_VALIDATION_FAILED").code });
555
+ throw error;
556
+ }
557
+ return { exitCode, stdout, stderr };
558
+ };
559
+ const options = {
560
+ hostOptions: authoringHostOptions(authoringRuntime),
561
+ runCommand,
562
+ previewRuntime,
563
+ authOptions: {},
564
+ };
565
+ const server = await (guiRuntime.startServer ?? startLocalGuiServer)(host.root, options);
566
+ let modelProxy;
567
+ let closed = false;
568
+ const managedServer = {
569
+ ...server,
570
+ async close() {
571
+ if (closed)
572
+ return;
573
+ closed = true;
574
+ try {
575
+ await modelProxy?.close();
576
+ }
577
+ finally {
578
+ await server.close();
579
+ }
580
+ },
581
+ };
582
+ try {
583
+ modelProxy = await guiRuntime.startModelProxy?.(server, {
584
+ root: host.root,
585
+ project_id: (await host.inspect()).project_id,
586
+ });
587
+ guiRuntime.onServerStarted?.(managedServer);
588
+ if (guiArgs[0] !== "--no-open")
589
+ await guiRuntime.openBrowser?.(server.url);
590
+ writeJson(io, { status: "listening", url: server.url });
591
+ }
592
+ catch (error) {
593
+ await managedServer.close();
594
+ throw error;
595
+ }
596
+ return 0;
597
+ }
598
+ if (command === "operation" && operand !== undefined) {
599
+ writeJson(io, await readSnapshotOperation(host.root, operand));
600
+ return 0;
601
+ }
602
+ throw new AuthoringBridgeError("E_SCHEMA_INVALID", "未知命令");
603
+ }
604
+ finally {
605
+ await host.close();
606
+ }
607
+ }
608
+ catch (error) {
609
+ const publicError = toPublicError(error);
610
+ io.stderr(`${publicError.code}: 请求失败\n`);
611
+ return publicError.code === "E_SCHEMA_INVALID" ? 2 : 1;
612
+ }
613
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};