@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,21 @@
1
+ import type { LocalGuiAuth } from "./local-gui-auth.js";
2
+ export declare const LOCAL_ACCOUNT_MODEL_PROXY_HOST = "127.0.0.1";
3
+ export declare const LOCAL_ACCOUNT_MODEL_PROXY_PORT = 11450;
4
+ export declare const LOCAL_ACCOUNT_MODEL_PROXY_ORIGIN = "http://127.0.0.1:11450";
5
+ export interface LocalAccountModelProxy {
6
+ readonly origin: typeof LOCAL_ACCOUNT_MODEL_PROXY_ORIGIN | string;
7
+ close(): Promise<void>;
8
+ }
9
+ export interface StartLocalAccountModelProxyOptions {
10
+ readonly projectRoot: string;
11
+ readonly projectId: string;
12
+ readonly auth: LocalGuiAuth;
13
+ /** 仅供 HTTP contract 测试;生产必须独占固定 11450。 */
14
+ readonly testOnlyPort?: 0;
15
+ }
16
+ export type LocalAccountProjectBinding = Readonly<{
17
+ canonicalRoot: string;
18
+ rootIdentity: string;
19
+ }>;
20
+ export declare function resolveLocalAccountProjectBinding(projectRoot: string, projectId: string): Promise<LocalAccountProjectBinding>;
21
+ export declare function startLocalAccountModelProxy(options: StartLocalAccountModelProxyOptions): Promise<LocalAccountModelProxy>;
@@ -0,0 +1,470 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, realpath } from "node:fs/promises";
3
+ import { createServer } from "node:http";
4
+ import { join, resolve } from "node:path";
5
+ import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
6
+ import { canonicalAccountProjectRootIdentityBytes } from "@world-engines/project-format";
7
+ import { assertSafeExistingProjectPath } from "./safe-project-path.js";
8
+ export const LOCAL_ACCOUNT_MODEL_PROXY_HOST = "127.0.0.1";
9
+ export const LOCAL_ACCOUNT_MODEL_PROXY_PORT = 11450;
10
+ export const LOCAL_ACCOUNT_MODEL_PROXY_ORIGIN = `http://${LOCAL_ACCOUNT_MODEL_PROXY_HOST}:${LOCAL_ACCOUNT_MODEL_PROXY_PORT}`;
11
+ const MAX_REQUEST_BYTES = 8 * 1024 * 1024;
12
+ const PROJECT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
13
+ const IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/u;
14
+ const OPERATION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,256}$/u;
15
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
16
+ const TERMINAL_STATUS = new Set([
17
+ "authorized_no_response", "settled_no_response", "failed_no_response", "reconcile_required",
18
+ ]);
19
+ const LEDGER_STATUS = new Set([
20
+ "authorized", "dispatched", "provider_succeeded", "settled", "failed", "ambiguous", "reconcile_required",
21
+ ]);
22
+ class ModelProxyHttpError extends Error {
23
+ status;
24
+ code;
25
+ constructor(status, code, message) {
26
+ super(message);
27
+ this.status = status;
28
+ this.code = code;
29
+ }
30
+ }
31
+ function record(value) {
32
+ return typeof value === "object" && value !== null && !Array.isArray(value)
33
+ ? value
34
+ : undefined;
35
+ }
36
+ function fail(status, code, message) {
37
+ throw new ModelProxyHttpError(status, code, message);
38
+ }
39
+ function accountIdentity(projectId, principalId) {
40
+ return createHash("sha256").update("worldengine-account-model-v1\0").update(projectId).update("\0").update(principalId).digest("hex");
41
+ }
42
+ function sessionIdentity(response, expectedProjectId) {
43
+ if (response.status === 401)
44
+ fail(401, "E_AUTH_REQUIRED", "账户模型代理需要登录");
45
+ if (response.status !== 200)
46
+ fail(response.status, "E_OPERATION_UNCERTAIN", "账户身份读取失败");
47
+ const principalId = response.body.principal_id;
48
+ const projectId = response.body.project_id;
49
+ if (typeof principalId !== "string" || principalId.length === 0 || principalId.length > 256
50
+ || typeof projectId !== "string" || projectId !== expectedProjectId || response.session === undefined) {
51
+ fail(502, "E_VALIDATION_FAILED", "账户身份响应无效");
52
+ }
53
+ return Object.freeze({ principalId, projectId, accountIdentity: accountIdentity(projectId, principalId), lease: response.session });
54
+ }
55
+ export async function resolveLocalAccountProjectBinding(projectRoot, projectId) {
56
+ if (!PROJECT_ID_PATTERN.test(projectId))
57
+ fail(400, "E_SCHEMA_INVALID", "project_id 无效");
58
+ const canonicalRoot = await realpath(resolve(projectRoot));
59
+ const manifestPath = await assertSafeExistingProjectPath(canonicalRoot, join(canonicalRoot, "worldengine.project.json"));
60
+ let manifest;
61
+ try {
62
+ manifest = JSON.parse(await readFile(manifestPath, "utf8"));
63
+ }
64
+ catch {
65
+ fail(400, "E_SCHEMA_INVALID", "项目 manifest 无效");
66
+ }
67
+ if (record(manifest)?.project_id !== projectId)
68
+ fail(409, "E_AUTH_REQUIRED", "项目 identity 不匹配");
69
+ const rootIdentity = createHash("sha256")
70
+ .update(canonicalAccountProjectRootIdentityBytes(canonicalRoot, process.platform === "win32" ? "win32" : "posix"))
71
+ .digest("hex");
72
+ return Object.freeze({ canonicalRoot, rootIdentity });
73
+ }
74
+ function assertNodeRequest(request, host, projectId, rootIdentity) {
75
+ if (request.headers.host !== host)
76
+ fail(403, "E_PROXY_HOST_FORBIDDEN", "Host 无效");
77
+ if (request.headers.origin !== undefined || request.headers.referer !== undefined
78
+ || request.headers["sec-fetch-site"] !== undefined || request.headers["sec-fetch-dest"] !== undefined) {
79
+ fail(403, "E_PROXY_BROWSER_FORBIDDEN", "浏览器请求不允许访问账户模型代理");
80
+ }
81
+ if (request.headers["x-worldengine-project-id"] !== projectId) {
82
+ fail(403, "E_AUTH_REQUIRED", "请求未绑定当前项目");
83
+ }
84
+ if (request.headers["x-worldengine-project-root-sha256"] !== rootIdentity) {
85
+ fail(403, "E_AUTH_REQUIRED", "请求未绑定当前项目根");
86
+ }
87
+ }
88
+ function assertExpectedAccount(request, current) {
89
+ if (request.headers["x-worldengine-account-identity"] !== current.accountIdentity) {
90
+ fail(409, "E_ACCOUNT_CHANGED", "请求账户 identity 已失效");
91
+ }
92
+ }
93
+ async function readJsonBody(request) {
94
+ const declared = Number(request.headers["content-length"]);
95
+ if (Number.isFinite(declared) && declared > MAX_REQUEST_BYTES)
96
+ fail(413, "E_SCHEMA_INVALID", "模型请求过大");
97
+ const chunks = [];
98
+ let total = 0;
99
+ for await (const chunk of request) {
100
+ const bytes = Buffer.from(chunk);
101
+ total += bytes.byteLength;
102
+ if (total > MAX_REQUEST_BYTES)
103
+ fail(413, "E_SCHEMA_INVALID", "模型请求过大");
104
+ chunks.push(bytes);
105
+ }
106
+ try {
107
+ const parsed = record(JSON.parse(Buffer.concat(chunks).toString("utf8")));
108
+ if (parsed !== undefined)
109
+ return parsed;
110
+ }
111
+ catch { /* 统一返回公开 schema 错误。 */ }
112
+ fail(400, "E_SCHEMA_INVALID", "模型请求不是有效 JSON object");
113
+ }
114
+ function sendJson(response, status, body) {
115
+ const encoded = JSON.stringify(body);
116
+ response.statusCode = status;
117
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
118
+ response.setHeader("Cache-Control", "no-store");
119
+ response.setHeader("X-Content-Type-Options", "nosniff");
120
+ response.setHeader("Content-Length", Buffer.byteLength(encoded));
121
+ response.end(encoded);
122
+ }
123
+ function publicError(error) {
124
+ if (error instanceof ModelProxyHttpError)
125
+ return error;
126
+ if (error instanceof AuthoringBridgeError) {
127
+ if (error.code === "E_AUTH_REQUIRED")
128
+ return { status: 401, code: error.code, message: "账户模型代理需要登录" };
129
+ if (error.code === "E_REMOTE_FORBIDDEN")
130
+ return { status: 403, code: error.code, message: "账户模型请求被拒绝" };
131
+ }
132
+ return { status: 503, code: "E_OPERATION_UNCERTAIN", message: "账户模型代理暂时不可用" };
133
+ }
134
+ const CHAT_REQUEST_FIELDS = new Set([
135
+ "model", "messages", "max_tokens", "max_completion_tokens", "temperature", "top_p", "stop",
136
+ "frequency_penalty", "presence_penalty", "seed", "response_format", "tools", "tool_choice",
137
+ "parallel_tool_calls", "stream",
138
+ ]);
139
+ function assertChatRequest(body) {
140
+ if (Object.keys(body).some((key) => !CHAT_REQUEST_FIELDS.has(key))
141
+ || typeof body.model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u.test(body.model)
142
+ || !Array.isArray(body.messages) || body.messages.length === 0 || body.messages.length > 256
143
+ || (body.stream !== undefined && body.stream !== false)) {
144
+ fail(400, "E_SCHEMA_INVALID", "chat completions 请求字段无效");
145
+ }
146
+ for (const messageValue of body.messages) {
147
+ const message = record(messageValue);
148
+ if (message === undefined || typeof message.role !== "string"
149
+ || !["system", "user", "assistant", "tool"].includes(message.role)
150
+ || (typeof message.content !== "string" && !Array.isArray(message.content))) {
151
+ fail(400, "E_SCHEMA_INVALID", "chat completions message 无效");
152
+ }
153
+ }
154
+ }
155
+ function publicChatResponse(body) {
156
+ if (typeof body.model !== "string" || !Array.isArray(body.choices) || record(body.usage) === undefined
157
+ || typeof body.operation_id !== "string" || !/^[A-Za-z0-9._:-]{1,256}$/u.test(body.operation_id)) {
158
+ fail(502, "E_VALIDATION_FAILED", "chat completions 响应无效");
159
+ }
160
+ const allowed = ["id", "object", "created", "model", "provider", "choices", "usage", "operation_id", "cost_status"];
161
+ return Object.freeze(Object.fromEntries(allowed.filter((key) => body[key] !== undefined).map((key) => [key, body[key]])));
162
+ }
163
+ /** chat 和 embedding 的已持久化计费终态共用同一公开、脱敏的幂等恢复契约。 */
164
+ function publicTerminalOperationReceipt(body) {
165
+ const error = record(body.error);
166
+ const operationId = body.operation_id;
167
+ const status = body.status;
168
+ const ledgerStatus = body.ledger_status;
169
+ const resultSha256 = body.provider_result_sha256;
170
+ if (error?.code !== "E_OPERATION_UNCERTAIN"
171
+ || typeof operationId !== "string" || !OPERATION_ID_PATTERN.test(operationId)
172
+ || typeof status !== "string" || !TERMINAL_STATUS.has(status)
173
+ || typeof ledgerStatus !== "string" || !LEDGER_STATUS.has(ledgerStatus)
174
+ || (resultSha256 !== undefined && resultSha256 !== null && (typeof resultSha256 !== "string" || !SHA256_PATTERN.test(resultSha256)))
175
+ || body.retryable !== false || body.retry_policy !== "do_not_change_idempotency_key") {
176
+ return undefined;
177
+ }
178
+ return Object.freeze({
179
+ error: Object.freeze({
180
+ code: "E_OPERATION_UNCERTAIN",
181
+ message: "模型操作已有持久终态;保留并使用同一 Idempotency-Key,不得换 key 自动重试",
182
+ }),
183
+ operation_id: operationId,
184
+ status,
185
+ ledger_status: ledgerStatus,
186
+ ...(resultSha256 === undefined ? {} : { provider_result_sha256: resultSha256 }),
187
+ retryable: false,
188
+ retry_policy: "do_not_change_idempotency_key",
189
+ });
190
+ }
191
+ function executionErrorCode(status) {
192
+ return status === 401 ? "E_AUTH_REQUIRED"
193
+ : status === 402 ? "E_BALANCE_REQUIRED"
194
+ : status === 429 ? "E_RATE_LIMITED"
195
+ : "E_OPERATION_UNCERTAIN";
196
+ }
197
+ function embeddingDescriptor(body) {
198
+ const inputKinds = record(body.input_kinds);
199
+ const documentKind = record(inputKinds?.document);
200
+ const queryKind = record(inputKinds?.query);
201
+ if (typeof body.provider !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(body.provider)
202
+ || typeof body.model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u.test(body.model)
203
+ || !Number.isSafeInteger(body.dimensions) || body.dimensions < 1 || body.dimensions > 65_536
204
+ || !Number.isSafeInteger(body.max_inputs) || body.max_inputs < 1 || body.max_inputs > 128
205
+ || !Number.isSafeInteger(body.max_input_chars) || body.max_input_chars < 1 || body.max_input_chars > 1_000_000
206
+ || typeof body.descriptor_sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(body.descriptor_sha256)
207
+ || documentKind?.descriptor_sha256 !== body.descriptor_sha256
208
+ || typeof queryKind?.descriptor_sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(queryKind.descriptor_sha256)) {
209
+ fail(502, "E_VALIDATION_FAILED", "embedding descriptor 无效");
210
+ }
211
+ return Object.freeze({
212
+ provider: body.provider,
213
+ model: body.model,
214
+ dimensions: body.dimensions,
215
+ maxInputs: body.max_inputs,
216
+ maxInputChars: body.max_input_chars,
217
+ descriptorSha256: body.descriptor_sha256,
218
+ descriptorSha256ByInputKind: Object.freeze({ document: body.descriptor_sha256, query: queryKind.descriptor_sha256 }),
219
+ });
220
+ }
221
+ function chatCatalog(body) {
222
+ if (typeof body.catalog_digest !== "string" || !SHA256_PATTERN.test(body.catalog_digest)
223
+ || !Array.isArray(body.models) || body.models.length === 0 || body.models.length > 512) {
224
+ fail(502, "E_VALIDATION_FAILED", "chat catalog 无效");
225
+ }
226
+ const seen = new Set();
227
+ const models = body.models.map((value) => {
228
+ const item = record(value);
229
+ if (item === undefined || typeof item.model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u.test(item.model)
230
+ || !Number.isSafeInteger(item.max_output_tokens) || item.max_output_tokens < 1) {
231
+ fail(502, "E_VALIDATION_FAILED", "chat catalog 无效");
232
+ }
233
+ if (seen.has(item.model))
234
+ fail(502, "E_VALIDATION_FAILED", "chat catalog 无效");
235
+ seen.add(item.model);
236
+ return Object.freeze({ model: item.model, maxOutputTokens: item.max_output_tokens });
237
+ });
238
+ return Object.freeze({ catalogDigest: body.catalog_digest, models: Object.freeze(models) });
239
+ }
240
+ function assertEmbeddingRequest(body, descriptor) {
241
+ if (Object.keys(body).sort().join("\0") !== "dimensions\0encoding_format\0input\0input_kind\0model"
242
+ || body.model !== descriptor.model || body.dimensions !== descriptor.dimensions || body.encoding_format !== "float"
243
+ || (body.input_kind !== "document" && body.input_kind !== "query")
244
+ || !Array.isArray(body.input) || body.input.length === 0 || body.input.length > descriptor.maxInputs
245
+ || body.input.some((text) => typeof text !== "string" || text.length === 0 || [...text].length > descriptor.maxInputChars)) {
246
+ fail(400, "E_SCHEMA_INVALID", "embedding 请求不符合当前 descriptor");
247
+ }
248
+ }
249
+ /**
250
+ * 本地 loopback 是账户凭据边界,不能把 provider 成功响应的任意嵌套字段转给桌面。
251
+ * 只重建 embedding 客户端实际消费的稳定结构;任何漂移一律作为上游校验失败。
252
+ */
253
+ function publicEmbeddingResponse(body, descriptor) {
254
+ const usage = record(body.usage);
255
+ if ((body.object !== undefined && body.object !== "list")
256
+ || body.model !== descriptor.model || body.provider !== descriptor.provider
257
+ || body.dimensions !== descriptor.dimensions
258
+ || (body.input_kind !== "document" && body.input_kind !== "query")
259
+ || typeof body.operation_id !== "string" || !OPERATION_ID_PATTERN.test(body.operation_id)
260
+ || !Number.isSafeInteger(usage?.prompt_tokens) || usage?.prompt_tokens < 0
261
+ || !Number.isSafeInteger(usage?.total_tokens) || usage?.total_tokens < usage?.prompt_tokens
262
+ || !Array.isArray(body.data)) {
263
+ fail(502, "E_VALIDATION_FAILED", "embedding 响应无效");
264
+ }
265
+ const data = new Array(body.data.length);
266
+ for (const value of body.data) {
267
+ const item = record(value);
268
+ if (item === undefined || (item.object !== undefined && item.object !== "embedding"))
269
+ fail(502, "E_VALIDATION_FAILED", "embedding 响应无效");
270
+ if (!Number.isSafeInteger(item.index) || item.index < 0 || item.index >= data.length
271
+ || data[item.index] !== undefined || !Array.isArray(item.embedding)
272
+ || item.embedding.length !== descriptor.dimensions
273
+ || item.embedding.some((entry) => typeof entry !== "number" || !Number.isFinite(entry))) {
274
+ fail(502, "E_VALIDATION_FAILED", "embedding 响应无效");
275
+ }
276
+ data[item.index] = Object.freeze({ object: "embedding", index: item.index, embedding: Object.freeze([...item.embedding]) });
277
+ }
278
+ if (data.some((item) => item === undefined))
279
+ fail(502, "E_VALIDATION_FAILED", "embedding 响应无效");
280
+ return Object.freeze({
281
+ object: "list",
282
+ data: Object.freeze(data),
283
+ model: descriptor.model,
284
+ provider: descriptor.provider,
285
+ dimensions: descriptor.dimensions,
286
+ input_kind: body.input_kind,
287
+ usage: Object.freeze({ prompt_tokens: usage.prompt_tokens, total_tokens: usage.total_tokens }),
288
+ operation_id: body.operation_id,
289
+ });
290
+ }
291
+ function publicDescriptorResponse(descriptor, projectId, identity) {
292
+ return Object.freeze({
293
+ provider: descriptor.provider,
294
+ model: descriptor.model,
295
+ dimensions: descriptor.dimensions,
296
+ max_inputs: descriptor.maxInputs,
297
+ max_input_chars: descriptor.maxInputChars,
298
+ descriptor_sha256: descriptor.descriptorSha256,
299
+ input_kinds: Object.freeze({
300
+ document: Object.freeze({ descriptor_sha256: descriptor.descriptorSha256ByInputKind.document }),
301
+ query: Object.freeze({ descriptor_sha256: descriptor.descriptorSha256ByInputKind.query }),
302
+ }),
303
+ project_id: projectId,
304
+ account_identity: identity,
305
+ });
306
+ }
307
+ function publicChatCatalogResponse(catalog, principalId, projectId, identity) {
308
+ return Object.freeze({
309
+ principal_id: principalId,
310
+ catalog_digest: catalog.catalogDigest,
311
+ models: catalog.models.map(({ model, maxOutputTokens }) => Object.freeze({ model, max_output_tokens: maxOutputTokens })),
312
+ project_id: projectId,
313
+ account_identity: identity,
314
+ });
315
+ }
316
+ export async function startLocalAccountModelProxy(options) {
317
+ const projectBinding = await resolveLocalAccountProjectBinding(options.projectRoot, options.projectId);
318
+ let hostHeader = "";
319
+ const server = createServer(async (request, response) => {
320
+ try {
321
+ assertNodeRequest(request, hostHeader, options.projectId, projectBinding.rootIdentity);
322
+ const url = new URL(request.url ?? "/", `http://${hostHeader}`);
323
+ if (url.search !== "")
324
+ fail(400, "E_SCHEMA_INVALID", "账户模型代理不接受查询参数");
325
+ if (options.auth.status().state !== "signed_in")
326
+ fail(401, "E_AUTH_REQUIRED", "账户模型代理需要登录");
327
+ const current = sessionIdentity(await options.auth.requestModel({ kind: "session" }), options.projectId);
328
+ if (request.method === "GET" && url.pathname === "/v1/session") {
329
+ sendJson(response, 200, { project_id: options.projectId, account_identity: current.accountIdentity });
330
+ return;
331
+ }
332
+ if (request.method === "GET" && url.pathname === "/v1/chat/catalog") {
333
+ assertExpectedAccount(request, current);
334
+ const remote = await options.auth.requestModel({ kind: "chat_catalog", session: current.lease });
335
+ if (remote.status !== 200) {
336
+ sendJson(response, remote.status, { error: { code: remote.status === 401 ? "E_AUTH_REQUIRED" : "E_OPERATION_UNCERTAIN" } });
337
+ return;
338
+ }
339
+ if ((remote.body.project_id !== undefined && remote.body.project_id !== options.projectId)
340
+ || (remote.body.principal_id !== undefined && remote.body.principal_id !== current.principalId)) {
341
+ fail(409, "E_ACCOUNT_CHANGED", "chat catalog 账户 identity 不匹配");
342
+ }
343
+ sendJson(response, 200, publicChatCatalogResponse(chatCatalog(remote.body), current.principalId, options.projectId, current.accountIdentity));
344
+ return;
345
+ }
346
+ if (request.method === "GET" && url.pathname === "/v1/embedding/descriptor") {
347
+ assertExpectedAccount(request, current);
348
+ const remote = await options.auth.requestModel({ kind: "embedding_descriptor", session: current.lease });
349
+ if (remote.status !== 200) {
350
+ sendJson(response, remote.status, { error: { code: remote.status === 401 ? "E_AUTH_REQUIRED" : "E_OPERATION_UNCERTAIN" } });
351
+ return;
352
+ }
353
+ const remoteProject = remote.body.project_id;
354
+ const remotePrincipal = remote.body.principal_id;
355
+ if ((remoteProject !== undefined && remoteProject !== options.projectId)
356
+ || (remotePrincipal !== undefined && remotePrincipal !== current.principalId)) {
357
+ fail(409, "E_ACCOUNT_CHANGED", "descriptor 账户 identity 不匹配");
358
+ }
359
+ sendJson(response, 200, publicDescriptorResponse(embeddingDescriptor(remote.body), options.projectId, current.accountIdentity));
360
+ return;
361
+ }
362
+ if (request.method === "POST" && url.pathname === "/v1/embeddings") {
363
+ assertExpectedAccount(request, current);
364
+ if (request.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
365
+ fail(415, "E_SCHEMA_INVALID", "模型请求 content-type 无效");
366
+ }
367
+ const idempotencyKey = request.headers["idempotency-key"];
368
+ if (typeof idempotencyKey !== "string" || !IDEMPOTENCY_KEY_PATTERN.test(idempotencyKey)) {
369
+ fail(400, "E_SCHEMA_INVALID", "idempotency-key 无效");
370
+ }
371
+ const body = await readJsonBody(request);
372
+ const descriptorResponse = await options.auth.requestModel({ kind: "embedding_descriptor", session: current.lease });
373
+ if (descriptorResponse.status !== 200) {
374
+ sendJson(response, descriptorResponse.status, { error: { code: descriptorResponse.status === 401 ? "E_AUTH_REQUIRED" : "E_OPERATION_UNCERTAIN" } });
375
+ return;
376
+ }
377
+ if ((descriptorResponse.body.project_id !== undefined && descriptorResponse.body.project_id !== options.projectId)
378
+ || (descriptorResponse.body.principal_id !== undefined && descriptorResponse.body.principal_id !== current.principalId)) {
379
+ fail(409, "E_ACCOUNT_CHANGED", "descriptor 账户 identity 不匹配");
380
+ }
381
+ const descriptor = embeddingDescriptor(descriptorResponse.body);
382
+ assertEmbeddingRequest(body, descriptor);
383
+ const remote = await options.auth.requestModel({ kind: "embedding", session: current.lease, idempotency_key: idempotencyKey, body });
384
+ if (remote.status < 200 || remote.status >= 300) {
385
+ const terminal = publicTerminalOperationReceipt(remote.body);
386
+ if (remote.status === 409 && terminal !== undefined) {
387
+ sendJson(response, 409, terminal);
388
+ return;
389
+ }
390
+ sendJson(response, remote.status, { error: { code: executionErrorCode(remote.status) } });
391
+ return;
392
+ }
393
+ if ((remote.body.project_id !== undefined && remote.body.project_id !== options.projectId)
394
+ || (remote.body.principal_id !== undefined && remote.body.principal_id !== current.principalId)) {
395
+ fail(409, "E_ACCOUNT_CHANGED", "模型响应账户 identity 不匹配");
396
+ }
397
+ sendJson(response, remote.status, publicEmbeddingResponse(remote.body, descriptor));
398
+ return;
399
+ }
400
+ if (request.method === "POST" && url.pathname === "/v1/chat/completions") {
401
+ assertExpectedAccount(request, current);
402
+ if (request.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
403
+ fail(415, "E_SCHEMA_INVALID", "模型请求 content-type 无效");
404
+ }
405
+ const idempotencyKey = request.headers["idempotency-key"];
406
+ if (typeof idempotencyKey !== "string" || !IDEMPOTENCY_KEY_PATTERN.test(idempotencyKey)) {
407
+ fail(400, "E_SCHEMA_INVALID", "idempotency-key 无效");
408
+ }
409
+ const body = await readJsonBody(request);
410
+ assertChatRequest(body);
411
+ const remote = await options.auth.requestModel({ kind: "chat_completions", session: current.lease, idempotency_key: idempotencyKey, body });
412
+ if (remote.status < 200 || remote.status >= 300) {
413
+ const terminal = publicTerminalOperationReceipt(remote.body);
414
+ if (remote.status === 409 && terminal !== undefined) {
415
+ sendJson(response, 409, terminal);
416
+ return;
417
+ }
418
+ sendJson(response, remote.status, { error: { code: executionErrorCode(remote.status) } });
419
+ return;
420
+ }
421
+ if ((remote.body.project_id !== undefined && remote.body.project_id !== options.projectId)
422
+ || (remote.body.principal_id !== undefined && remote.body.principal_id !== current.principalId)) {
423
+ fail(409, "E_ACCOUNT_CHANGED", "模型响应账户 identity 不匹配");
424
+ }
425
+ sendJson(response, remote.status, publicChatResponse(remote.body));
426
+ return;
427
+ }
428
+ fail(404, "E_NOT_FOUND", "账户模型端点不存在");
429
+ }
430
+ catch (error) {
431
+ if (response.headersSent) {
432
+ response.destroy();
433
+ return;
434
+ }
435
+ const rendered = publicError(error);
436
+ sendJson(response, rendered.status, { error: { code: rendered.code, message: rendered.message } });
437
+ }
438
+ });
439
+ const port = options.testOnlyPort ?? LOCAL_ACCOUNT_MODEL_PROXY_PORT;
440
+ try {
441
+ await new Promise((resolvePromise, rejectPromise) => {
442
+ const onError = (error) => { server.off("listening", onListening); rejectPromise(error); };
443
+ const onListening = () => { server.off("error", onError); resolvePromise(); };
444
+ server.once("error", onError);
445
+ server.once("listening", onListening);
446
+ server.listen({ host: LOCAL_ACCOUNT_MODEL_PROXY_HOST, port });
447
+ });
448
+ }
449
+ catch (error) {
450
+ const code = error.code;
451
+ if (code === "EADDRINUSE")
452
+ fail(409, "E_ACCOUNT_MODEL_PROXY_PORT_IN_USE", "127.0.0.1:11450 已被其他服务占用");
453
+ fail(503, "E_ACCOUNT_MODEL_PROXY_UNAVAILABLE", "账户模型代理启动失败");
454
+ }
455
+ const address = server.address();
456
+ if (address === null || typeof address === "string") {
457
+ await new Promise((resolvePromise) => server.close(() => resolvePromise()));
458
+ fail(503, "E_ACCOUNT_MODEL_PROXY_UNAVAILABLE", "账户模型代理地址无效");
459
+ }
460
+ hostHeader = `${LOCAL_ACCOUNT_MODEL_PROXY_HOST}:${address.port}`;
461
+ const origin = `http://${hostHeader}`;
462
+ let closed;
463
+ return Object.freeze({
464
+ origin,
465
+ close() {
466
+ closed ??= new Promise((resolvePromise, rejectPromise) => server.close((error) => error === undefined ? resolvePromise() : rejectPromise(error)));
467
+ return closed;
468
+ },
469
+ });
470
+ }
@@ -0,0 +1,7 @@
1
+ import type { DesktopAuthoringCapabilities } from "./desktop-authoring-runtime.js";
2
+ import { LocalPreviewRuntime } from "./preview-runtime.js";
3
+ /** 浏览器只在外部开发 harness 实际调用 Preview 时启动;不让 iframe 接触作者工具。 */
4
+ export declare function createAuthoringBrowserRuntime(projectRoot: string, preview: LocalPreviewRuntime): {
5
+ capabilities: Pick<DesktopAuthoringCapabilities, "preview_inspect" | "preview_interact">;
6
+ close(): Promise<void>;
7
+ };
@@ -0,0 +1,110 @@
1
+ import { lstat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
4
+ import { createChromiumPreviewBrowserHarness } from "./chromium-preview-browser-driver.js";
5
+ import { startLocalGuiServer } from "./local-gui-server.js";
6
+ import { LOCAL_GUI_ORIGIN } from "./local-gui-contract.js";
7
+ import { LocalProjectHost } from "./project-host.js";
8
+ import { attachLocalGuiPreview } from "./local-gui-preview-client.js";
9
+ import { LocalPreviewRuntime, readLivePreviewSessionOwner } from "./preview-runtime.js";
10
+ /** 浏览器只在外部开发 harness 实际调用 Preview 时启动;不让 iframe 接触作者工具。 */
11
+ export function createAuthoringBrowserRuntime(projectRoot, preview) {
12
+ let opening;
13
+ let gui;
14
+ let attachedPreview = null;
15
+ let ownedPreview = false;
16
+ let closed = false;
17
+ const ensure = () => {
18
+ if (closed)
19
+ return Promise.reject(new AuthoringBridgeError("E_PREVIEW_NOT_RUNNING", "开发浏览器已关闭"));
20
+ opening ??= (async () => {
21
+ const identityHost = await LocalProjectHost.open(projectRoot);
22
+ let projectId;
23
+ try {
24
+ projectId = (await identityHost.inspect()).project_id;
25
+ }
26
+ finally {
27
+ await identityHost.close();
28
+ }
29
+ attachedPreview = await attachLocalGuiPreview(projectRoot, projectId);
30
+ if (attachedPreview === null && preview.inspect() === null) {
31
+ // 其他 GUI/MCP 的预览不能被本进程自动接管或重启。
32
+ const owner = await readLivePreviewSessionOwner(projectRoot);
33
+ if (owner !== null) {
34
+ attachedPreview = Object.freeze({
35
+ session_id: owner.session_id, project_root: owner.project_root, cwd: owner.cwd,
36
+ url: owner.url, port: owner.port, state: "running", pid: owner.pid,
37
+ started_at_ms: 0, stopped_at_ms: null, exit_code: null,
38
+ stdout: "", stderr: "", stdout_truncated: false, stderr_truncated: false,
39
+ });
40
+ }
41
+ else {
42
+ try {
43
+ await lstat(join(projectRoot, ".worldengine", "preview-session.json"));
44
+ throw new AuthoringBridgeError("E_PROJECT_BUSY", "Preview ledger 存在但无法安全附着;拒绝接管或重启");
45
+ }
46
+ catch (error) {
47
+ if (error.code !== "ENOENT")
48
+ throw error;
49
+ }
50
+ await preview.start(projectRoot);
51
+ ownedPreview = true;
52
+ }
53
+ }
54
+ const deadline = Date.now() + 20_000;
55
+ while (preview.inspect()?.state === "starting" && Date.now() < deadline) {
56
+ await new Promise((resolve) => setTimeout(resolve, 50));
57
+ }
58
+ const sessionPort = { inspect: () => preview.inspect() ?? attachedPreview };
59
+ if (sessionPort.inspect()?.state !== "running")
60
+ throw new AuthoringBridgeError("E_PREVIEW_NOT_RUNNING", "Preview 未在时限内就绪");
61
+ if (attachedPreview === null)
62
+ gui = await startLocalGuiServer(projectRoot, { previewRuntime: preview });
63
+ return createChromiumPreviewBrowserHarness(sessionPort, {
64
+ hostPageUrl: gui?.url ?? LOCAL_GUI_ORIGIN,
65
+ previewFrameSelector: "#preview-frame",
66
+ activatePreviewSelector: '[data-view="view"]',
67
+ expectedProjectId: projectId,
68
+ });
69
+ })().catch(async (error) => {
70
+ opening = undefined;
71
+ await gui?.close();
72
+ gui = undefined;
73
+ attachedPreview = null;
74
+ if (ownedPreview) {
75
+ ownedPreview = false;
76
+ await preview.stop();
77
+ }
78
+ throw error;
79
+ });
80
+ return opening;
81
+ };
82
+ const capabilities = {
83
+ async preview_inspect(input) {
84
+ return (await ensure()).desktopAuthoringCapabilities.preview_inspect(input);
85
+ },
86
+ async preview_interact(input) {
87
+ return (await ensure()).desktopAuthoringCapabilities.preview_interact(input);
88
+ },
89
+ };
90
+ return {
91
+ capabilities,
92
+ async close() {
93
+ if (closed)
94
+ return;
95
+ closed = true;
96
+ try {
97
+ await (await opening)?.close();
98
+ }
99
+ finally {
100
+ await gui?.close();
101
+ gui = undefined;
102
+ attachedPreview = null;
103
+ if (ownedPreview) {
104
+ ownedPreview = false;
105
+ await preview.stop();
106
+ }
107
+ }
108
+ },
109
+ };
110
+ }
@@ -0,0 +1,38 @@
1
+ import type { AuthoringToolCall } from "@world-engines/authoring-bridge";
2
+ import { type NaturalLanguageChunkingOptions, type NaturalLanguageSource, type WorldFactCommitReceipt } from "@world-engines/tmw/natural-language-ingestion";
3
+ import type { AuthoringModelConnection } from "./authoring-model-connection.js";
4
+ export interface ProjectDocumentIngestionHost {
5
+ inspect(): Promise<Readonly<{
6
+ project_revision: number;
7
+ }>>;
8
+ dispatchTool(call: AuthoringToolCall): Promise<unknown>;
9
+ }
10
+ /** secret 由调用方在 host process 内构造 model connection;本 capability 不序列化认证材料。 */
11
+ export interface ProjectDocumentIngestionCapability {
12
+ readonly modelConnection: AuthoringModelConnection;
13
+ readonly maximumConcurrentExtractions?: number;
14
+ readonly chunking?: NaturalLanguageChunkingOptions;
15
+ readonly clock?: () => number;
16
+ }
17
+ export type ProjectDocumentIngestionWarning = Readonly<{
18
+ code: "world_facts_rejected" | "evidence_offsets_corrected" | "unverified_chunks";
19
+ count: number;
20
+ }>;
21
+ export interface ProjectDocumentIngestionReceipt extends WorldFactCommitReceipt {
22
+ readonly completeness: "complete" | "partial";
23
+ readonly rejectedFactCount: number;
24
+ readonly correctedEvidenceCount: number;
25
+ readonly warnings: readonly ProjectDocumentIngestionWarning[];
26
+ readonly committedFacts: readonly Readonly<{
27
+ scopeId: string;
28
+ factId: string;
29
+ }>[];
30
+ readonly rejectionReasons: Readonly<Record<string, number>>;
31
+ readonly unverifiedChunkCount: number;
32
+ readonly unverifiedChunkIndexes: readonly number[];
33
+ }
34
+ /**
35
+ * 把全文抽取、真实 embedding 与 Host authority commit 串成一个作者调用。
36
+ * 唯一持久化入口是 host.dispatchTool(world_apply),不得直接写 Ladybug checkpoint。
37
+ */
38
+ export declare function ingestProjectDocument(host: ProjectDocumentIngestionHost, source: NaturalLanguageSource, capability: ProjectDocumentIngestionCapability): Promise<ProjectDocumentIngestionReceipt>;