@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,555 @@
1
+ import { createHash } from "node:crypto";
2
+ import OpenCC from "opencc-js/t2cn";
3
+ export const LEGACY_CHINESE_LEXICAL_PROFILE_VERSION = "chinese-lexical/v1";
4
+ export const LEGACY_CHINESE_LEXICAL_PROFILE_V2_VERSION = "chinese-lexical/v2";
5
+ export const LEGACY_CHINESE_LEXICAL_PROFILE_V3_VERSION = "chinese-lexical/v3";
6
+ export const CHINESE_LEXICAL_PROFILE_VERSION = "chinese-lexical/v4";
7
+ const LEGACY_CHINESE_LEXICAL_STRATEGY_V2_VERSION = "icu-word+han-bigram+entity-overlay/v1";
8
+ const LEGACY_CHINESE_LEXICAL_STRATEGY_V3_VERSION = "icu-word+in-word-han-bigram+entity-overlay/v2";
9
+ const CHINESE_LEXICAL_STRATEGY_VERSION = "icu-word+in-word-han-bigram+entity-overlay-bigram/v3";
10
+ const OPENCC_VERSION = "1.4.2";
11
+ const OPENCC_DICTIONARY_IDENTITY = "opencc-js/t2cn@1.4.2:t-to-cn";
12
+ const MIN_CORPUS_TERM_FREQUENCY = 2;
13
+ const MIN_HAN_TERM_CODE_POINTS = 2;
14
+ const MAX_HAN_TERM_CODE_POINTS = 8;
15
+ const MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS = 64;
16
+ const MAX_PROFILE_DICTIONARY_TERMS = 100_000;
17
+ const MAX_SOURCE_TEXTS = 100_000;
18
+ const MAX_SOURCE_CODE_UNITS = 10_000_000;
19
+ const MAX_ENTITY_LEXICON_ENTRIES = 100_000;
20
+ const MAX_ENTITY_LEXICON_ENTRY_CODE_UNITS = 512;
21
+ const MAX_LEXICALIZE_INPUT_CODE_UNITS = 1_000_000;
22
+ const MAX_LEXICALIZE_OUTPUT_TERMS = 1_000_000;
23
+ const CHINESE_QUERY_STOP_TERMS = new Set(["的", "了", "在", "是", "吗", "呢", "与", "和", "跟", "向", "从", "由", "把", "被", "为", "到",
24
+ "什么", "哪里", "哪儿", "哪个", "哪些", "谁", "是否", "的吗", "怎么", "如何", "为何", "为什么"]);
25
+ export class ChineseLexicalError extends Error {
26
+ code;
27
+ name = "ChineseLexicalError";
28
+ constructor(code) {
29
+ super(`chinese_lexical:${code}`);
30
+ this.code = code;
31
+ }
32
+ }
33
+ const legacyTries = new WeakMap();
34
+ const overlayTries = new WeakMap();
35
+ const hanCharacter = /^\p{Script=Han}$/u;
36
+ const latinOrNumberCharacter = /^(?:\p{Script=Latin}|\p{Number})$/u;
37
+ const sha256Pattern = /^[a-f\d]{64}$/u;
38
+ const normalizeToSimplified = OpenCC.Converter({ from: "t", to: "cn" });
39
+ function fail(code) {
40
+ throw new ChineseLexicalError(code);
41
+ }
42
+ function sha256(parts) {
43
+ const hash = createHash("sha256");
44
+ for (const part of parts) {
45
+ hash.update(String(Buffer.byteLength(part, "utf8")), "utf8");
46
+ hash.update(":", "utf8");
47
+ hash.update(part, "utf8");
48
+ hash.update(";", "utf8");
49
+ }
50
+ return hash.digest("hex");
51
+ }
52
+ function multisetHash(kind, values) {
53
+ const entries = values.map((value) => sha256([kind, value])).sort(compareText);
54
+ return sha256([kind, ...entries]);
55
+ }
56
+ function compareText(left, right) {
57
+ return left < right ? -1 : left > right ? 1 : 0;
58
+ }
59
+ function isHanTerm(value) {
60
+ const characters = [...value];
61
+ return characters.length >= MIN_HAN_TERM_CODE_POINTS
62
+ && characters.length <= MAX_HAN_TERM_CODE_POINTS
63
+ && characters.every((character) => hanCharacter.test(character));
64
+ }
65
+ function validateStringArray(value, maximumEntries, maximumCodeUnits) {
66
+ if (!Array.isArray(value) || value.length > maximumEntries
67
+ || value.some((entry) => typeof entry !== "string" || entry.length > maximumCodeUnits))
68
+ fail("invalid_input");
69
+ return value;
70
+ }
71
+ function legacyRuntimeIdentity(segmenter) {
72
+ return Object.freeze({
73
+ engine: "Intl.Segmenter",
74
+ locale: segmenter.resolvedOptions().locale,
75
+ granularity: "word",
76
+ node: process.versions.node,
77
+ icu: process.versions.icu ?? "unknown",
78
+ unicode: process.versions.unicode ?? "unknown",
79
+ cldr: process.versions.cldr ?? "unknown",
80
+ });
81
+ }
82
+ function normalizedRuntimeIdentity(segmenter, strategy) {
83
+ return Object.freeze({
84
+ ...legacyRuntimeIdentity(segmenter),
85
+ strategy,
86
+ normalization: Object.freeze({
87
+ engine: "opencc-js",
88
+ version: OPENCC_VERSION,
89
+ mode: "t-to-cn",
90
+ dictionaryIdentity: OPENCC_DICTIONARY_IDENTITY,
91
+ }),
92
+ });
93
+ }
94
+ function identityString(identity) {
95
+ return JSON.stringify(identity);
96
+ }
97
+ function addEntityLexiconTerms(segmenter, value, destination) {
98
+ const trimmed = value.trim();
99
+ if (isHanTerm(trimmed))
100
+ destination.add(trimmed);
101
+ for (const item of segmenter.segment(trimmed)) {
102
+ if (item.isWordLike && isHanTerm(item.segment))
103
+ destination.add(item.segment);
104
+ }
105
+ }
106
+ /**
107
+ * 按 v1 规则从已校验的 source 重建 checkpoint 派生 profile。
108
+ *
109
+ * 保留这个具名入口,迁移代码不得把 checkpoint 中未经重算的 dictionary/profileHash
110
+ * 直接当成可信派生数据。
111
+ */
112
+ export function buildLegacyChineseLexicalIndexProfileV1(texts, options = {}) {
113
+ const sources = validateStringArray(texts, MAX_SOURCE_TEXTS, MAX_SOURCE_CODE_UNITS);
114
+ if (sources.length === 0)
115
+ fail("invalid_input");
116
+ const sourceCodeUnits = sources.reduce((sum, value) => sum + value.length, 0);
117
+ if (!Number.isSafeInteger(sourceCodeUnits) || sourceCodeUnits > MAX_SOURCE_CODE_UNITS)
118
+ fail("resource_limit");
119
+ const entityLabels = validateStringArray(options.entityLabels ?? [], MAX_ENTITY_LEXICON_ENTRIES, MAX_ENTITY_LEXICON_ENTRY_CODE_UNITS);
120
+ const aliases = validateStringArray(options.aliases ?? [], MAX_ENTITY_LEXICON_ENTRIES, MAX_ENTITY_LEXICON_ENTRY_CODE_UNITS);
121
+ if (entityLabels.length + aliases.length > MAX_ENTITY_LEXICON_ENTRIES)
122
+ fail("resource_limit");
123
+ const segmenter = new Intl.Segmenter("zh", { granularity: "word" });
124
+ const counts = new Map();
125
+ for (const text of sources) {
126
+ for (const item of segmenter.segment(text)) {
127
+ if (item.isWordLike && isHanTerm(item.segment))
128
+ counts.set(item.segment, (counts.get(item.segment) ?? 0) + 1);
129
+ }
130
+ }
131
+ const corpusTerms = new Set([...counts].filter(([, count]) => count >= MIN_CORPUS_TERM_FREQUENCY).map(([term]) => term));
132
+ const entityTerms = new Set();
133
+ for (const value of [...entityLabels, ...aliases])
134
+ addEntityLexiconTerms(segmenter, value, entityTerms);
135
+ const dictionary = [...new Set([...corpusTerms, ...entityTerms])].sort(compareText);
136
+ if (dictionary.length > MAX_PROFILE_DICTIONARY_TERMS)
137
+ fail("resource_limit");
138
+ const identity = legacyRuntimeIdentity(segmenter);
139
+ const corpusHash = multisetHash("source-text", sources);
140
+ const entityLexiconHash = multisetHash("entity-lexicon", [...entityLabels, ...aliases]);
141
+ const profileHash = sha256([
142
+ LEGACY_CHINESE_LEXICAL_PROFILE_VERSION,
143
+ identityString(identity),
144
+ corpusHash,
145
+ entityLexiconHash,
146
+ ...dictionary,
147
+ ]);
148
+ return Object.freeze({
149
+ schemaVersion: 1,
150
+ version: LEGACY_CHINESE_LEXICAL_PROFILE_VERSION,
151
+ runtimeIdentity: identity,
152
+ corpusHash,
153
+ entityLexiconHash,
154
+ profileHash,
155
+ sourceCodeUnits,
156
+ dictionary: Object.freeze(dictionary),
157
+ dictionaryStats: Object.freeze({
158
+ corpusTerms: corpusTerms.size,
159
+ entityLexiconTerms: entityTerms.size,
160
+ totalTerms: dictionary.length,
161
+ }),
162
+ limits: Object.freeze({
163
+ minCorpusTermFrequency: MIN_CORPUS_TERM_FREQUENCY,
164
+ minHanTermCodePoints: MIN_HAN_TERM_CODE_POINTS,
165
+ maxHanTermCodePoints: MAX_HAN_TERM_CODE_POINTS,
166
+ maxLatinNumericTokenCodePoints: MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS,
167
+ maxDictionaryTerms: MAX_PROFILE_DICTIONARY_TERMS,
168
+ maxLexicalizeInputCodeUnits: MAX_LEXICALIZE_INPUT_CODE_UNITS,
169
+ maxLexicalizeOutputTerms: MAX_LEXICALIZE_OUTPUT_TERMS,
170
+ }),
171
+ simplifiedTraditionalConversion: "none",
172
+ });
173
+ }
174
+ function buildNormalizedChineseLexicalIndexProfile(texts, options, schemaVersion) {
175
+ const sources = validateStringArray(texts, MAX_SOURCE_TEXTS, MAX_SOURCE_CODE_UNITS);
176
+ if (sources.length === 0)
177
+ fail("invalid_input");
178
+ const sourceCodeUnits = sources.reduce((sum, value) => sum + value.length, 0);
179
+ if (!Number.isSafeInteger(sourceCodeUnits) || sourceCodeUnits > MAX_SOURCE_CODE_UNITS)
180
+ fail("resource_limit");
181
+ const entityLabels = validateStringArray(options.entityLabels ?? [], MAX_ENTITY_LEXICON_ENTRIES, MAX_ENTITY_LEXICON_ENTRY_CODE_UNITS);
182
+ const aliases = validateStringArray(options.aliases ?? [], MAX_ENTITY_LEXICON_ENTRIES, MAX_ENTITY_LEXICON_ENTRY_CODE_UNITS);
183
+ if (entityLabels.length + aliases.length > MAX_ENTITY_LEXICON_ENTRIES)
184
+ fail("resource_limit");
185
+ const normalizedSources = sources.map((source) => normalizeToSimplified(source));
186
+ const rawEntityLexicon = [...entityLabels, ...aliases];
187
+ const normalizedEntityLexicon = rawEntityLexicon.map((entry) => normalizeToSimplified(entry));
188
+ const dictionary = [...new Set(normalizedEntityLexicon.map((entry) => entry.trim()).filter(isHanTerm))].sort(compareText);
189
+ if (dictionary.length > MAX_PROFILE_DICTIONARY_TERMS)
190
+ fail("resource_limit");
191
+ const version = schemaVersion === 2
192
+ ? LEGACY_CHINESE_LEXICAL_PROFILE_V2_VERSION
193
+ : schemaVersion === 3 ? LEGACY_CHINESE_LEXICAL_PROFILE_V3_VERSION : CHINESE_LEXICAL_PROFILE_VERSION;
194
+ const strategy = schemaVersion === 2
195
+ ? LEGACY_CHINESE_LEXICAL_STRATEGY_V2_VERSION
196
+ : schemaVersion === 3 ? LEGACY_CHINESE_LEXICAL_STRATEGY_V3_VERSION : CHINESE_LEXICAL_STRATEGY_VERSION;
197
+ const identity = normalizedRuntimeIdentity(new Intl.Segmenter("zh", { granularity: "word" }), strategy);
198
+ const rawCorpusHash = multisetHash("raw-source-text", sources);
199
+ const corpusHash = multisetHash("normalized-source-text", normalizedSources);
200
+ const rawEntityLexiconHash = multisetHash("raw-entity-lexicon", rawEntityLexicon);
201
+ const entityLexiconHash = multisetHash("normalized-entity-lexicon", normalizedEntityLexicon);
202
+ const dictionaryHash = sha256(["entity-overlay", ...dictionary]);
203
+ const profileHash = sha256([
204
+ version,
205
+ identityString(identity),
206
+ rawCorpusHash,
207
+ corpusHash,
208
+ rawEntityLexiconHash,
209
+ entityLexiconHash,
210
+ dictionaryHash,
211
+ ]);
212
+ return Object.freeze({
213
+ schemaVersion,
214
+ version,
215
+ runtimeIdentity: identity,
216
+ rawCorpusHash,
217
+ corpusHash,
218
+ rawEntityLexiconHash,
219
+ entityLexiconHash,
220
+ dictionaryHash,
221
+ profileHash,
222
+ sourceCodeUnits,
223
+ dictionary: Object.freeze(dictionary),
224
+ dictionaryStats: Object.freeze({ entityLexiconTerms: dictionary.length, totalTerms: dictionary.length }),
225
+ limits: Object.freeze({
226
+ minHanTermCodePoints: MIN_HAN_TERM_CODE_POINTS,
227
+ maxHanTermCodePoints: MAX_HAN_TERM_CODE_POINTS,
228
+ maxLatinNumericTokenCodePoints: MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS,
229
+ maxDictionaryTerms: MAX_PROFILE_DICTIONARY_TERMS,
230
+ maxLexicalizeInputCodeUnits: MAX_LEXICALIZE_INPUT_CODE_UNITS,
231
+ maxLexicalizeOutputTerms: MAX_LEXICALIZE_OUTPUT_TERMS,
232
+ }),
233
+ simplifiedTraditionalConversion: "opencc-t-to-cn",
234
+ });
235
+ }
236
+ /** 按旧 v2 hash/strategy 原样重建,供已校验 checkpoint 的显式迁移使用。 */
237
+ export function buildLegacyChineseLexicalIndexProfileV2(texts, options = {}) {
238
+ return buildNormalizedChineseLexicalIndexProfile(texts, options, 2);
239
+ }
240
+ /** 按旧 v3 hash/strategy 原样重建,供已校验 checkpoint 的显式迁移使用。 */
241
+ export function buildLegacyChineseLexicalIndexProfileV3(texts, options = {}) {
242
+ return buildNormalizedChineseLexicalIndexProfile(texts, options, 3);
243
+ }
244
+ export function buildChineseLexicalIndexProfile(texts, options = {}) {
245
+ return buildNormalizedChineseLexicalIndexProfile(texts, options, 4);
246
+ }
247
+ function validateLegacyProfile(profile) {
248
+ const runtime = profile?.runtimeIdentity;
249
+ if (profile?.schemaVersion !== 1 || profile.version !== LEGACY_CHINESE_LEXICAL_PROFILE_VERSION
250
+ || profile.simplifiedTraditionalConversion !== "none"
251
+ || !sha256Pattern.test(profile.corpusHash) || !sha256Pattern.test(profile.entityLexiconHash)
252
+ || !sha256Pattern.test(profile.profileHash)
253
+ || runtime?.engine !== "Intl.Segmenter" || runtime.granularity !== "word"
254
+ || typeof runtime.locale !== "string" || runtime.locale.length === 0
255
+ || !Array.isArray(profile.dictionary) || profile.dictionary.length > MAX_PROFILE_DICTIONARY_TERMS
256
+ || profile.dictionary.some((term, index) => !isHanTerm(term)
257
+ || (index > 0 && compareText(profile.dictionary[index - 1], term) >= 0)))
258
+ fail("profile_invalid");
259
+ const expectedProfileHash = sha256([
260
+ LEGACY_CHINESE_LEXICAL_PROFILE_VERSION,
261
+ identityString(runtime),
262
+ profile.corpusHash,
263
+ profile.entityLexiconHash,
264
+ ...profile.dictionary,
265
+ ]);
266
+ if (profile.profileHash !== expectedProfileHash)
267
+ fail("profile_invalid");
268
+ }
269
+ function buildTrie(dictionary) {
270
+ const root = { terminal: false, children: new Map() };
271
+ for (const term of dictionary) {
272
+ let node = root;
273
+ for (const character of term) {
274
+ let child = node.children.get(character);
275
+ if (child === undefined) {
276
+ child = { terminal: false, children: new Map() };
277
+ node.children.set(character, child);
278
+ }
279
+ node = child;
280
+ }
281
+ node.terminal = true;
282
+ }
283
+ return root;
284
+ }
285
+ function legacyTrie(profile) {
286
+ const cached = legacyTries.get(profile);
287
+ if (cached !== undefined)
288
+ return cached;
289
+ validateLegacyProfile(profile);
290
+ const root = buildTrie(profile.dictionary);
291
+ legacyTries.set(profile, root);
292
+ return root;
293
+ }
294
+ function validateNormalizedProfile(profile) {
295
+ const runtime = profile?.runtimeIdentity;
296
+ const normalization = runtime?.normalization;
297
+ const expectedVersion = profile?.schemaVersion === 2
298
+ ? LEGACY_CHINESE_LEXICAL_PROFILE_V2_VERSION
299
+ : profile?.schemaVersion === 3 ? LEGACY_CHINESE_LEXICAL_PROFILE_V3_VERSION : CHINESE_LEXICAL_PROFILE_VERSION;
300
+ const expectedStrategy = profile?.schemaVersion === 2
301
+ ? LEGACY_CHINESE_LEXICAL_STRATEGY_V2_VERSION
302
+ : profile?.schemaVersion === 3 ? LEGACY_CHINESE_LEXICAL_STRATEGY_V3_VERSION : CHINESE_LEXICAL_STRATEGY_VERSION;
303
+ if ((profile?.schemaVersion !== 2 && profile?.schemaVersion !== 3 && profile?.schemaVersion !== 4)
304
+ || profile.version !== expectedVersion
305
+ || profile.simplifiedTraditionalConversion !== "opencc-t-to-cn"
306
+ || !sha256Pattern.test(profile.rawCorpusHash) || !sha256Pattern.test(profile.corpusHash)
307
+ || !sha256Pattern.test(profile.rawEntityLexiconHash) || !sha256Pattern.test(profile.entityLexiconHash)
308
+ || !sha256Pattern.test(profile.dictionaryHash) || !sha256Pattern.test(profile.profileHash)
309
+ || runtime?.engine !== "Intl.Segmenter" || runtime.granularity !== "word"
310
+ || runtime.strategy !== expectedStrategy
311
+ || typeof runtime.locale !== "string" || runtime.locale.length === 0
312
+ || normalization?.engine !== "opencc-js" || normalization.version !== OPENCC_VERSION
313
+ || normalization.mode !== "t-to-cn" || normalization.dictionaryIdentity !== OPENCC_DICTIONARY_IDENTITY
314
+ || !Array.isArray(profile.dictionary) || profile.dictionary.length > MAX_PROFILE_DICTIONARY_TERMS
315
+ || profile.dictionary.some((term, index) => !isHanTerm(term)
316
+ || term !== normalizeToSimplified(term)
317
+ || (index > 0 && compareText(profile.dictionary[index - 1], term) >= 0)))
318
+ fail("profile_invalid");
319
+ const currentRuntime = normalizedRuntimeIdentity(new Intl.Segmenter("zh", { granularity: "word" }), expectedStrategy);
320
+ if (identityString(runtime) !== identityString(currentRuntime))
321
+ fail("profile_invalid");
322
+ const expectedDictionaryHash = sha256(["entity-overlay", ...profile.dictionary]);
323
+ const expectedProfileHash = sha256([
324
+ expectedVersion,
325
+ identityString(runtime),
326
+ profile.rawCorpusHash,
327
+ profile.corpusHash,
328
+ profile.rawEntityLexiconHash,
329
+ profile.entityLexiconHash,
330
+ expectedDictionaryHash,
331
+ ]);
332
+ if (profile.dictionaryHash !== expectedDictionaryHash || profile.profileHash !== expectedProfileHash)
333
+ fail("profile_invalid");
334
+ }
335
+ function overlayTrie(profile) {
336
+ const cached = overlayTries.get(profile);
337
+ if (cached !== undefined)
338
+ return cached;
339
+ validateNormalizedProfile(profile);
340
+ const root = buildTrie(profile.dictionary);
341
+ overlayTries.set(profile, root);
342
+ return root;
343
+ }
344
+ function emitBounded(tokens, token) {
345
+ tokens.push(token);
346
+ if (tokens.length > MAX_LEXICALIZE_OUTPUT_TERMS)
347
+ fail("resource_limit");
348
+ }
349
+ /**
350
+ * 生成供 FTS 派生字段与 query planner 共用的确定性 terms。
351
+ * 不修改原文、不做简繁转换;profile 首次使用时构建一次 trie,后续按对象 identity 复用。
352
+ */
353
+ export function legacyChineseLexicalTermsV1(text, profile) {
354
+ if (typeof text !== "string")
355
+ fail("invalid_input");
356
+ if (text.length > MAX_LEXICALIZE_INPUT_CODE_UNITS)
357
+ fail("resource_limit");
358
+ const root = legacyTrie(profile);
359
+ const characters = [...text];
360
+ const tokens = [];
361
+ let cursor = 0;
362
+ while (cursor < characters.length) {
363
+ const current = characters[cursor];
364
+ if (hanCharacter.test(current)) {
365
+ let node = root;
366
+ let bestEnd = -1;
367
+ let scan = cursor;
368
+ while (scan < characters.length && hanCharacter.test(characters[scan])) {
369
+ node = node.children.get(characters[scan]);
370
+ if (node === undefined)
371
+ break;
372
+ scan += 1;
373
+ if (node.terminal)
374
+ bestEnd = scan;
375
+ }
376
+ if (bestEnd > cursor) {
377
+ emitBounded(tokens, characters.slice(cursor, bestEnd).join(""));
378
+ cursor = bestEnd;
379
+ }
380
+ else {
381
+ emitBounded(tokens, current);
382
+ cursor += 1;
383
+ }
384
+ continue;
385
+ }
386
+ if (latinOrNumberCharacter.test(current)) {
387
+ const start = cursor;
388
+ while (cursor < characters.length && latinOrNumberCharacter.test(characters[cursor]))
389
+ cursor += 1;
390
+ for (let offset = start; offset < cursor; offset += MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS) {
391
+ emitBounded(tokens, characters.slice(offset, Math.min(cursor, offset + MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS)).join(""));
392
+ }
393
+ continue;
394
+ }
395
+ cursor += 1;
396
+ }
397
+ return Object.freeze(tokens);
398
+ }
399
+ function emitHanBigrams(characters, tokens) {
400
+ let cursor = 0;
401
+ while (cursor < characters.length) {
402
+ if (!hanCharacter.test(characters[cursor])) {
403
+ cursor += 1;
404
+ continue;
405
+ }
406
+ const start = cursor;
407
+ while (cursor < characters.length && hanCharacter.test(characters[cursor]))
408
+ cursor += 1;
409
+ if (cursor - start === 1) {
410
+ emitBounded(tokens, characters[start]);
411
+ continue;
412
+ }
413
+ for (let offset = start; offset + 1 < cursor; offset += 1) {
414
+ emitBounded(tokens, characters.slice(offset, offset + 2).join(""));
415
+ }
416
+ }
417
+ }
418
+ function emitInWordHanBigrams(characters, tokens) {
419
+ if (characters.length <= 2)
420
+ return;
421
+ for (let offset = 0; offset + 1 < characters.length; offset += 1) {
422
+ emitBounded(tokens, characters.slice(offset, offset + 2).join(""));
423
+ }
424
+ }
425
+ function emitLatinNumericRuns(characters, tokens) {
426
+ let cursor = 0;
427
+ while (cursor < characters.length) {
428
+ if (!latinOrNumberCharacter.test(characters[cursor])) {
429
+ cursor += 1;
430
+ continue;
431
+ }
432
+ const start = cursor;
433
+ while (cursor < characters.length && latinOrNumberCharacter.test(characters[cursor]))
434
+ cursor += 1;
435
+ for (let offset = start; offset < cursor; offset += MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS) {
436
+ emitBounded(tokens, characters.slice(offset, Math.min(cursor, offset + MAX_LATIN_NUMERIC_TOKEN_CODE_POINTS)).join(""));
437
+ }
438
+ }
439
+ }
440
+ function emitEntityOverlay(characters, root, tokens, includeInternalBigrams) {
441
+ const alreadyEmitted = new Set(tokens);
442
+ const overlayEmitted = new Set();
443
+ for (let cursor = 0; cursor < characters.length; cursor += 1) {
444
+ if (!hanCharacter.test(characters[cursor]))
445
+ continue;
446
+ let node = root;
447
+ const maximumEnd = Math.min(characters.length, cursor + MAX_HAN_TERM_CODE_POINTS);
448
+ for (let scan = cursor; scan < maximumEnd && hanCharacter.test(characters[scan]); scan += 1) {
449
+ node = node.children.get(characters[scan]);
450
+ if (node === undefined)
451
+ break;
452
+ if (!node.terminal)
453
+ continue;
454
+ const term = characters.slice(cursor, scan + 1).join("");
455
+ if (!alreadyEmitted.has(term) && !overlayEmitted.has(term)) {
456
+ emitBounded(tokens, term);
457
+ overlayEmitted.add(term);
458
+ }
459
+ if (includeInternalBigrams && scan - cursor + 1 > 2) {
460
+ for (let offset = cursor; offset < scan; offset += 1) {
461
+ const bigram = characters.slice(offset, offset + 2).join("");
462
+ if (!alreadyEmitted.has(bigram) && !overlayEmitted.has(bigram)) {
463
+ emitBounded(tokens, bigram);
464
+ overlayEmitted.add(bigram);
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+ }
471
+ function prepareNormalizedTerms(text, profile) {
472
+ if (typeof text !== "string")
473
+ fail("invalid_input");
474
+ if (text.length > MAX_LEXICALIZE_INPUT_CODE_UNITS)
475
+ fail("resource_limit");
476
+ const root = overlayTrie(profile);
477
+ const normalizedText = normalizeToSimplified(text);
478
+ const characters = [...normalizedText];
479
+ return { root, normalizedText, characters };
480
+ }
481
+ /** 按旧 v2 full-run bigram 规则生成 terms,供 checkpoint 验证和显式迁移使用。 */
482
+ export function legacyChineseLexicalTermsV2(text, profile) {
483
+ const { root, normalizedText, characters } = prepareNormalizedTerms(text, profile);
484
+ const tokens = [];
485
+ const segmenter = new Intl.Segmenter(profile.runtimeIdentity.locale, { granularity: "word" });
486
+ for (const item of segmenter.segment(normalizedText)) {
487
+ if (!item.isWordLike)
488
+ continue;
489
+ const wordCharacters = [...item.segment];
490
+ if (wordCharacters.length > 0 && wordCharacters.every((character) => hanCharacter.test(character))) {
491
+ emitBounded(tokens, item.segment);
492
+ }
493
+ }
494
+ emitHanBigrams(characters, tokens);
495
+ emitLatinNumericRuns(characters, tokens);
496
+ emitEntityOverlay(characters, root, tokens, false);
497
+ return Object.freeze(tokens);
498
+ }
499
+ /** 按旧 v3 词内 bigram 规则生成 terms,供 checkpoint 验证和显式迁移使用。 */
500
+ export function legacyChineseLexicalTermsV3(text, profile) {
501
+ const { root, normalizedText, characters } = prepareNormalizedTerms(text, profile);
502
+ const tokens = [];
503
+ const segmenter = new Intl.Segmenter(profile.runtimeIdentity.locale, { granularity: "word" });
504
+ for (const item of segmenter.segment(normalizedText)) {
505
+ if (!item.isWordLike)
506
+ continue;
507
+ const wordCharacters = [...item.segment];
508
+ if (wordCharacters.length > 0 && wordCharacters.every((character) => hanCharacter.test(character))) {
509
+ emitBounded(tokens, item.segment);
510
+ emitInWordHanBigrams(wordCharacters, tokens);
511
+ }
512
+ }
513
+ emitLatinNumericRuns(characters, tokens);
514
+ emitEntityOverlay(characters, root, tokens, false);
515
+ return Object.freeze(tokens);
516
+ }
517
+ /**
518
+ * 只生成 derived lexical terms。OpenCC 转换不回写原文,caller 持有的 text/provenance/offset 保持不变。
519
+ */
520
+ export function chineseLexicalTerms(text, profile) {
521
+ if (profile?.schemaVersion === 1)
522
+ return legacyChineseLexicalTermsV1(text, profile);
523
+ if (profile?.schemaVersion === 2)
524
+ return legacyChineseLexicalTermsV2(text, profile);
525
+ if (profile?.schemaVersion === 3)
526
+ return legacyChineseLexicalTermsV3(text, profile);
527
+ const { root, normalizedText, characters } = prepareNormalizedTerms(text, profile);
528
+ const tokens = [];
529
+ const segmenter = new Intl.Segmenter(profile.runtimeIdentity.locale, { granularity: "word" });
530
+ for (const item of segmenter.segment(normalizedText)) {
531
+ if (!item.isWordLike)
532
+ continue;
533
+ const wordCharacters = [...item.segment];
534
+ if (wordCharacters.length > 0 && wordCharacters.every((character) => hanCharacter.test(character))) {
535
+ emitBounded(tokens, item.segment);
536
+ emitInWordHanBigrams(wordCharacters, tokens);
537
+ }
538
+ }
539
+ emitLatinNumericRuns(characters, tokens);
540
+ emitEntityOverlay(characters, root, tokens, true);
541
+ return Object.freeze(tokens);
542
+ }
543
+ /** 仅用于 FTS derived field;调用方必须另外保存原始 text/provenance/offset。 */
544
+ export function lexicalize(text, profile) {
545
+ return chineseLexicalTerms(text, profile).join(" ");
546
+ }
547
+ /**
548
+ * FTS query 专用投影:只移除封闭集合中的中文疑问功能词,保留实体、否定、时间和谓词。
549
+ * corpus 仍使用完整 lexicalize,不能借 stop-term 规则改写原文或推断语义。
550
+ */
551
+ export function lexicalizeQuery(text, profile) {
552
+ const terms = chineseLexicalTerms(text, profile);
553
+ const filtered = terms.filter((term) => !CHINESE_QUERY_STOP_TERMS.has(term));
554
+ return (filtered.length === 0 ? terms : filtered).join(" ");
555
+ }
@@ -0,0 +1,43 @@
1
+ import type { PreviewBrowserDriver, PreviewBrowserInspectCapture } from "./preview-browser-adapter.js";
2
+ import type { DesktopAuthoringCapabilities } from "./desktop-authoring-runtime.js";
3
+ export interface ChromiumPreviewBrowserDriverOptions {
4
+ readonly executablePath?: string;
5
+ readonly tempRoot?: string;
6
+ readonly headless?: boolean;
7
+ /** 可选真实 Host 页面;提供时 DOM/交互只在其 sandbox Preview frame context 中执行。 */
8
+ readonly hostPageUrl?: string;
9
+ readonly previewFrameSelector?: string;
10
+ readonly activatePreviewSelector?: string;
11
+ readonly expectedProjectId?: string;
12
+ /** 首次 inspect 截图前等待 ChatPlay ready 与可绘制 View 的最长时限。 */
13
+ readonly renderReadyTimeoutMs?: number;
14
+ }
15
+ export interface ChromiumPreviewBrowserHarness {
16
+ readonly driver: ChromiumPreviewBrowserDriver;
17
+ readonly desktopAuthoringCapabilities: Pick<DesktopAuthoringCapabilities, "preview_inspect" | "preview_interact">;
18
+ close(): Promise<void>;
19
+ }
20
+ export interface PreviewBrowserHarnessRuntime {
21
+ inspect(): import("./preview-runtime.js").PreviewSession | null;
22
+ }
23
+ export interface CreateChromiumPreviewBrowserHarnessOptions extends ChromiumPreviewBrowserDriverOptions {
24
+ readonly startTimeoutMs?: number;
25
+ }
26
+ /** 系统 Chrome/Edge 的真实 CDP driver;默认 headless,不弹窗,也不注入 cookie 或作者能力。 */
27
+ export declare class ChromiumPreviewBrowserDriver implements PreviewBrowserDriver {
28
+ #private;
29
+ constructor(options?: ChromiumPreviewBrowserDriverOptions);
30
+ inspect(request: Parameters<PreviewBrowserDriver["inspect"]>[0]): Promise<PreviewBrowserInspectCapture>;
31
+ interact(request: Parameters<PreviewBrowserDriver["interact"]>[0]): Promise<Readonly<{
32
+ url: string;
33
+ }>>;
34
+ /** 仅供开发 harness 验证正式高层 SDK;不暴露任意 RPC/eval。 */
35
+ sendChatPlayUserMessage(sessionId: string, previewUrl: string, text: string): Promise<Readonly<{
36
+ messageId?: string;
37
+ turnId?: string;
38
+ }>>;
39
+ listChatPlayMessages(sessionId: string, previewUrl: string): Promise<readonly Readonly<Record<string, unknown>>[]>;
40
+ close(): Promise<void>;
41
+ }
42
+ /** ProjectHost/CLI 的窄接线入口:CLI 只需把返回 capability 传入 LocalProjectHostOptions。 */
43
+ export declare function createChromiumPreviewBrowserHarness(runtime: PreviewBrowserHarnessRuntime, options?: CreateChromiumPreviewBrowserHarnessOptions): Promise<ChromiumPreviewBrowserHarness>;