@yugoo-design/dsh-design-knowledge-ladybug 0.0.0-stage → 0.1.0-beta.2

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 (44) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +24 -0
  3. package/README.md +33 -2
  4. package/THIRD_PARTY_NOTICES.md +35 -0
  5. package/lib/dsh-plugin-BPwDtoBY.d.ts +50 -0
  6. package/lib/dsh-plugin-C0TG9tJI.js +1681 -0
  7. package/lib/dsh-plugin.d.ts +2 -0
  8. package/lib/dsh-plugin.js +3 -0
  9. package/lib/embedding-worker.js +80 -0
  10. package/lib/index.d.ts +63 -0
  11. package/lib/index.js +3 -0
  12. package/lib/semantic-index.d.ts +63 -0
  13. package/lib/semantic-index.js +367 -0
  14. package/lib/third-party/ladybug-wasm-core-0.21.1/BUILD-SOURCE.json +112 -0
  15. package/lib/third-party/ladybug-wasm-core-0.21.1/LICENSE +22 -0
  16. package/lib/third-party/ladybug-wasm-core-0.21.1/README.md +43 -0
  17. package/lib/third-party/ladybug-wasm-core-0.21.1/SOURCE.json +37 -0
  18. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/INDEX.json +115 -0
  19. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/fulltext-supplement/CRoaring-4.5.1.LICENSE +236 -0
  20. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/fulltext-supplement/SOURCES.json +70 -0
  21. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/fulltext-supplement/cpp-httplib-0.14.2.LICENSE +22 -0
  22. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/INDEX.json +117 -0
  23. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/antlr4_cypher-lexer.GENERATED.txt +4 -0
  24. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/antlr4_cypher-parser.GENERATED.txt +5 -0
  25. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/fast_float.NOTICE.txt +37 -0
  26. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/httpfs-extension.LICENSE +22 -0
  27. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/httpfs-httplib.NOTICE.txt +7 -0
  28. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/header-notices/roaring_bitmap.NOTICE.txt +56 -0
  29. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/antlr4_runtime/LICENSE +84 -0
  30. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/brotli/LICENSE +19 -0
  31. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/fastpfor/LICENSE +191 -0
  32. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/lz4/LICENSE +24 -0
  33. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/mbedtls/LICENSE +202 -0
  34. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/miniz/LICENSE +22 -0
  35. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/parquet/LICENSE +217 -0
  36. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/re2/LICENSE +27 -0
  37. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/simsimd/LICENSE +201 -0
  38. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/snappy/LICENSE +54 -0
  39. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/thrift/LICENSE +239 -0
  40. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/utf8proc/LICENSE.md +93 -0
  41. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/yyjson/LICENSE +21 -0
  42. package/lib/third-party/ladybug-wasm-core-0.21.1/source-materials/third_party/zstd/LICENSE +30 -0
  43. package/licenses/dsh-distribution.json +36 -0
  44. package/package.json +44 -4
@@ -0,0 +1,2 @@
1
+ import { a as name, i as inject, n as LADYBUG_WASM_PROVIDER_ID, r as apply, t as Config } from "./dsh-plugin-BPwDtoBY.js";
2
+ export { Config, LADYBUG_WASM_PROVIDER_ID, apply, inject, name };
@@ -0,0 +1,3 @@
1
+ import { i as name, n as apply, r as inject, t as LADYBUG_WASM_PROVIDER_ID } from "./dsh-plugin-C0TG9tJI.js";
2
+ import "./semantic-index.js";
3
+ export { LADYBUG_WASM_PROVIDER_ID, apply, inject, name };
@@ -0,0 +1,80 @@
1
+ //#region src/embedding-text.mjs
2
+ /** Exact tokenizer windows. Preserve every character; never silently truncate evidence. */
3
+ function embeddingWindows(text, prefix, tokenizer, limit = 512, maxParts = 32) {
4
+ const pending = [text], result = [];
5
+ while (pending.length) {
6
+ const content = pending.pop(), input = prefix + content, tokens = tokenizer.encode(input).length;
7
+ if (tokens <= limit) {
8
+ result.push({
9
+ input,
10
+ content,
11
+ tokens
12
+ });
13
+ continue;
14
+ }
15
+ const chars = Array.from(content);
16
+ if (chars.length < 2 || result.length + pending.length + 2 > maxParts) throw new Error("Embedding text exceeds bounded tokenizer windows");
17
+ const middle = Math.floor(chars.length / 2);
18
+ pending.push(chars.slice(middle).join(""), chars.slice(0, middle).join(""));
19
+ }
20
+ return result;
21
+ }
22
+ function combineEmbeddingWindows(vectors, windows) {
23
+ if (!vectors.length || vectors.length !== windows.length) throw new Error("Embedding window count mismatch");
24
+ const sum = Array(vectors[0].length).fill(0);
25
+ for (let i = 0; i < vectors.length; i++) for (let j = 0; j < sum.length; j++) sum[j] += vectors[i][j] * windows[i].tokens;
26
+ const norm = Math.hypot(...sum);
27
+ if (!Number.isFinite(norm) || norm < 1e-12) throw new Error("Invalid combined embedding");
28
+ return sum.map((x) => x / norm);
29
+ }
30
+ //#endregion
31
+ //#region src/embedding-worker.mjs
32
+ let extractor;
33
+ process.on("disconnect", () => process.exit(0));
34
+ process.on("message", async ({ texts, config }) => {
35
+ let phase = "runtime";
36
+ try {
37
+ const { pipeline, env } = await import("@huggingface/transformers");
38
+ if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.https_proxy || process.env.http_proxy) {
39
+ const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
40
+ setGlobalDispatcher(new EnvHttpProxyAgent());
41
+ }
42
+ env.backends.onnx.wasm.numThreads = 1;
43
+ phase = "model";
44
+ env.cacheDir = config.cacheDir;
45
+ env.allowRemoteModels = !config.localOnly;
46
+ extractor ??= await pipeline("feature-extraction", config.model, {
47
+ revision: config.revision,
48
+ dtype: "q8",
49
+ device: "cpu",
50
+ session_options: {
51
+ intraOpNumThreads: 2,
52
+ interOpNumThreads: 1
53
+ },
54
+ progress_callback: (info) => {
55
+ if (info.status === "download") process.send?.({ progress: config.localOnly ? "正在加载本地模型文件;离线模式,设计文本不上传" : "正在准备模型文件(首次可能下载);设计文本不上传" });
56
+ }
57
+ });
58
+ phase = "inference";
59
+ const groups = texts.map((text) => embeddingWindows(text, config.prefix, extractor.tokenizer));
60
+ const windows = groups.flat(), vectors = [];
61
+ for (let start = 0; start < windows.length; start += 4) {
62
+ const result = await extractor(windows.slice(start, start + 4).map((w) => w.input), {
63
+ pooling: "mean",
64
+ normalize: true,
65
+ truncation: false
66
+ });
67
+ vectors.push(...result.tolist());
68
+ }
69
+ let offset = 0;
70
+ process.send?.({ vectors: groups.map((group) => {
71
+ const result = combineEmbeddingWindows(vectors.slice(offset, offset + group.length), group);
72
+ offset += group.length;
73
+ return result;
74
+ }) });
75
+ } catch {
76
+ process.send?.({ error: phase });
77
+ }
78
+ });
79
+ //#endregion
80
+ export {};
package/lib/index.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { a as name, c as resolveLadybugKnowledgeConfig, i as inject, n as LADYBUG_WASM_PROVIDER_ID, o as LadybugKnowledgeConfig, r as apply, s as LadybugKnowledgeConfigInput, t as Config } from "./dsh-plugin-BPwDtoBY.js";
2
+ import { DesignSemanticIndex } from "./semantic-index.js";
3
+ import { DesignKnowledgeArchive, DesignKnowledgeDecision, DesignKnowledgeDelta, DesignKnowledgeImpactRequest, DesignKnowledgeImportReport, DesignKnowledgeMutationResult, DesignKnowledgeProposal, DesignKnowledgeProposalBatch, DesignKnowledgeProposalBatchResult, DesignKnowledgeQuery, DesignKnowledgeQueryResult, DesignKnowledgeRevision, DesignKnowledgeService, DesignKnowledgeTraceRequest, DesignKnowledgeTraceResult, DesignProjectId } from "@yugoo-design/dsh-design-core";
4
+ //#region src/provider.d.ts
5
+ /** Current on-disk schema accepted by the provider. */
6
+ declare const LADYBUG_KNOWLEDGE_SCHEMA_VERSION = 2;
7
+ /** The per-project cross-process owner could not be acquired. */
8
+ declare class LadybugProjectLockError extends Error {
9
+ readonly databasePath: string;
10
+ constructor(databasePath: string, options?: ErrorOptions);
11
+ }
12
+ /** The bounded serialized-writer acquisition budget was exhausted. */
13
+ declare class LadybugWriterBusyError extends Error {
14
+ readonly attempts: number;
15
+ constructor(attempts: number);
16
+ }
17
+ /** Existing durable bytes do not identify the requested project and schema. */
18
+ declare class LadybugSchemaMismatchError extends Error {
19
+ readonly databasePath: string;
20
+ constructor(databasePath: string, detail: string, options?: ErrorOptions);
21
+ }
22
+ interface LadybugRecoveryState {
23
+ state: 'rebuilding' | 'ready' | 'error';
24
+ message: string;
25
+ generation?: string;
26
+ }
27
+ interface LadybugRecoveryOptions {
28
+ retryFailed?: boolean;
29
+ /** Rebuild only from current Design Files. Return a last-moment source fence. */
30
+ rebuild(provider: LadybugDesignKnowledgeProvider): Promise<() => Promise<void>>;
31
+ onState?(state: LadybugRecoveryState): void;
32
+ }
33
+ /** Only known durable-format failures permit discarding a derived index. */
34
+ declare function isRecoverableLadybugError(error: unknown): boolean;
35
+ /** Implemented subset in the first production slice. */
36
+ type LadybugDesignKnowledgeService = DesignKnowledgeService;
37
+ /** Stop the package-global Ladybug worker after every project provider closes. */
38
+ declare function shutdownLadybugKnowledgeRuntime(): Promise<void>;
39
+ /** Private embedded provider for revisioned Design Knowledge projection. */
40
+ declare class LadybugDesignKnowledgeProvider implements LadybugDesignKnowledgeService {
41
+ #private;
42
+ recovery?: LadybugRecoveryState;
43
+ private constructor();
44
+ /** Open one exclusively owned project database and initialize its strict schema. */
45
+ static open(projectId: DesignProjectId, input: LadybugKnowledgeConfigInput, recovery?: LadybugRecoveryOptions): Promise<LadybugDesignKnowledgeProvider>;
46
+ /** Close the database, release project ownership, and stop the shared worker when unused. */
47
+ close(): Promise<void>;
48
+ revision(projectId: Parameters<DesignKnowledgeService['revision']>[0]): Promise<DesignKnowledgeRevision>;
49
+ applyDelta(delta: DesignKnowledgeDelta): Promise<DesignKnowledgeMutationResult>;
50
+ /** Disposable vector cache in the same project DB, outside fact revisions/journal. */
51
+ readSemanticCache(key: string): Promise<string | undefined>;
52
+ writeSemanticCache(key: string, value: string): Promise<void>;
53
+ query(request: DesignKnowledgeQuery): Promise<DesignKnowledgeQueryResult>;
54
+ trace(request: DesignKnowledgeTraceRequest): Promise<DesignKnowledgeTraceResult>;
55
+ impact(request: DesignKnowledgeImpactRequest): Promise<DesignKnowledgeQueryResult>;
56
+ propose(proposal: DesignKnowledgeProposal, pin: Parameters<DesignKnowledgeService['propose']>[1]): Promise<DesignKnowledgeMutationResult>;
57
+ proposeBatch(batch: DesignKnowledgeProposalBatch): Promise<DesignKnowledgeProposalBatchResult>;
58
+ applyDecision(decision: DesignKnowledgeDecision, pin: Parameters<DesignKnowledgeService['applyDecision']>[1]): Promise<DesignKnowledgeMutationResult>;
59
+ exportProject(projectId: DesignProjectId, requested?: DesignKnowledgeRevision): Promise<DesignKnowledgeArchive>;
60
+ importProject(archive: DesignKnowledgeArchive): Promise<DesignKnowledgeImportReport>;
61
+ }
62
+ //#endregion
63
+ export { DesignSemanticIndex, type Config as DshLadybugKnowledgeConfig, LADYBUG_KNOWLEDGE_SCHEMA_VERSION, LADYBUG_WASM_PROVIDER_ID, LadybugDesignKnowledgeProvider, type LadybugDesignKnowledgeService, type LadybugKnowledgeConfig, type LadybugKnowledgeConfigInput, LadybugProjectLockError, type LadybugRecoveryOptions, type LadybugRecoveryState, LadybugSchemaMismatchError, LadybugWriterBusyError, apply as applyDshLadybugKnowledge, inject as dshLadybugKnowledgeInject, name as dshLadybugKnowledgeName, isRecoverableLadybugError, resolveLadybugKnowledgeConfig, shutdownLadybugKnowledgeRuntime };
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { a as LADYBUG_KNOWLEDGE_SCHEMA_VERSION, c as LadybugSchemaMismatchError, d as shutdownLadybugKnowledgeRuntime, f as resolveLadybugKnowledgeConfig, i as name, l as LadybugWriterBusyError, n as apply, o as LadybugDesignKnowledgeProvider, r as inject, s as LadybugProjectLockError, t as LADYBUG_WASM_PROVIDER_ID, u as isRecoverableLadybugError } from "./dsh-plugin-C0TG9tJI.js";
2
+ import { DesignSemanticIndex } from "./semantic-index.js";
3
+ export { DesignSemanticIndex, LADYBUG_KNOWLEDGE_SCHEMA_VERSION, LADYBUG_WASM_PROVIDER_ID, LadybugDesignKnowledgeProvider, LadybugProjectLockError, LadybugSchemaMismatchError, LadybugWriterBusyError, apply as applyDshLadybugKnowledge, inject as dshLadybugKnowledgeInject, name as dshLadybugKnowledgeName, isRecoverableLadybugError, resolveLadybugKnowledgeConfig, shutdownLadybugKnowledgeRuntime };
@@ -0,0 +1,63 @@
1
+ import { DesignFieldGraphEdge, DesignGraph, SemanticStatus } from "@yugoo-design/dsh-design-core";
2
+ import { ChildProcess } from "node:child_process";
3
+ //#region src/semantic-index.d.ts
4
+ interface EmbeddingConfig {
5
+ provider?: 'local' | 'remote' | 'disabled';
6
+ model?: string;
7
+ revision?: string;
8
+ cacheDir?: string;
9
+ localOnly?: boolean;
10
+ endpoint?: string;
11
+ apiKey?: string;
12
+ prefix?: string;
13
+ threshold?: number;
14
+ }
15
+ interface SemanticCache {
16
+ readSemanticCache(key: string): Promise<string | undefined>;
17
+ writeSemanticCache(key: string, value: string): Promise<void>;
18
+ }
19
+ /** Disposable, project-local cache. Never stores Knowledge/GRA facts. */
20
+ declare class MemorySemanticCache implements SemanticCache {
21
+ private readonly limit;
22
+ private readonly byteLimit;
23
+ private values;
24
+ private bytes;
25
+ constructor(limit?: number, byteLimit?: number);
26
+ readSemanticCache(key: string): Promise<string | undefined>;
27
+ writeSemanticCache(key: string, value: string): Promise<void>;
28
+ }
29
+ declare function stopEmbeddingChild(worker: ChildProcess): Promise<void>;
30
+ declare class DesignSemanticIndex {
31
+ private readonly idleMs;
32
+ readonly config: Required<Omit<EmbeddingConfig, 'endpoint' | 'apiKey'>> & Pick<EmbeddingConfig, 'endpoint' | 'apiKey'>;
33
+ readonly identity: string;
34
+ get publicationIdentity(): string;
35
+ private jobs;
36
+ private tail;
37
+ private closed;
38
+ constructor(config?: EmbeddingConfig, idleMs?: number);
39
+ read(project: string, graph: DesignGraph, cache?: SemanticCache): {
40
+ edges: DesignFieldGraphEdge[];
41
+ status: SemanticStatus;
42
+ };
43
+ /** Await the existing inference job for background persistence; no polling and
44
+ * no project write lock held during model execution. Partial edges never escape. */
45
+ compute(project: string, graph: DesignGraph, cache?: SemanticCache, signal?: AbortSignal): Promise<{
46
+ edges: DesignFieldGraphEdge[];
47
+ status: {
48
+ state: "indexing" | "ready" | "error" | "disabled";
49
+ completed: number;
50
+ total: number;
51
+ model: string;
52
+ message?: string;
53
+ cache?: "persistent" | "memory";
54
+ };
55
+ }>;
56
+ cancelProject(project: string): void;
57
+ close(): Promise<void>;
58
+ private build;
59
+ private localBatch;
60
+ private remoteBatch;
61
+ }
62
+ //#endregion
63
+ export { DesignSemanticIndex, EmbeddingConfig, MemorySemanticCache, SemanticCache, stopEmbeddingChild };
@@ -0,0 +1,367 @@
1
+ import { existsSync } from "node:fs";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ import { SEMANTIC_TEXT_VERSION, normalizeVector, semanticDigest, semanticEdges } from "@yugoo-design/dsh-design-core";
4
+ import { fork } from "node:child_process";
5
+ import { homedir } from "node:os";
6
+ //#region src/semantic-index.ts
7
+ const localEmbeddingFailures = {
8
+ runtime: "本地 embedding 运行依赖不可用;请按当前操作系统安装依赖(包括原生模块),不要复用其他平台的 node_modules。",
9
+ model: "本地 embedding 模型加载失败;请检查固定版本模型缓存,离线模式需要先准备完整模型文件。",
10
+ inference: "本地 embedding 推理失败;请检查输入限制和本地内存、CPU 运行资源。"
11
+ };
12
+ /** Disposable, project-local cache. Never stores Knowledge/GRA facts. */
13
+ var MemorySemanticCache = class {
14
+ limit;
15
+ byteLimit;
16
+ values = /* @__PURE__ */ new Map();
17
+ bytes = 0;
18
+ constructor(limit = 300, byteLimit = 4e6) {
19
+ this.limit = limit;
20
+ this.byteLimit = byteLimit;
21
+ if (!Number.isSafeInteger(limit) || limit < 1 || !Number.isSafeInteger(byteLimit) || byteLimit < 1) throw new Error("Invalid volatile cache bounds");
22
+ }
23
+ async readSemanticCache(key) {
24
+ return this.values.get(key);
25
+ }
26
+ async writeSemanticCache(key, value) {
27
+ const size = Buffer.byteLength(key) + Buffer.byteLength(value);
28
+ if (size > this.byteLimit) return;
29
+ const old = this.values.get(key);
30
+ if (old !== void 0) {
31
+ this.bytes -= Buffer.byteLength(key) + Buffer.byteLength(old);
32
+ this.values.delete(key);
33
+ }
34
+ while (this.values.size && (this.values.size >= this.limit || this.bytes + size > this.byteLimit)) {
35
+ const [id, text] = this.values.entries().next().value;
36
+ this.values.delete(id);
37
+ this.bytes -= Buffer.byteLength(id) + Buffer.byteLength(text);
38
+ }
39
+ this.values.set(key, value);
40
+ this.bytes += size;
41
+ }
42
+ };
43
+ async function stopEmbeddingChild(worker) {
44
+ if (worker.exitCode !== null || worker.signalCode !== null) return;
45
+ await new Promise((resolveExit) => {
46
+ const timer = setTimeout(() => worker.kill("SIGKILL"), 2e3);
47
+ worker.once("exit", () => {
48
+ clearTimeout(timer);
49
+ resolveExit();
50
+ });
51
+ worker.kill("SIGTERM");
52
+ });
53
+ }
54
+ var DesignSemanticIndex = class {
55
+ idleMs;
56
+ config;
57
+ identity;
58
+ get publicationIdentity() {
59
+ return semanticDigest(this.identity + ":similarity-v1:" + this.config.threshold);
60
+ }
61
+ jobs = /* @__PURE__ */ new Map();
62
+ tail = Promise.resolve();
63
+ closed = false;
64
+ constructor(config = {}, idleMs = 3e5) {
65
+ this.idleMs = idleMs;
66
+ if (!Number.isSafeInteger(idleMs) || idleMs < 1) throw new Error("Invalid semantic idle timeout");
67
+ this.config = {
68
+ provider: "local",
69
+ model: "Xenova/multilingual-e5-small",
70
+ revision: "761b726dd34fb83930e26aab4e9ac3899aa1fa78",
71
+ cacheDir: resolve(homedir(), ".cache/design-system/embeddings"),
72
+ localOnly: false,
73
+ prefix: "query: ",
74
+ threshold: .8,
75
+ ...config
76
+ };
77
+ if (![
78
+ "local",
79
+ "remote",
80
+ "disabled"
81
+ ].includes(this.config.provider)) throw new Error("Unknown embedding provider");
82
+ if (!this.config.model || !this.config.revision || !isAbsolute(this.config.cacheDir)) throw new Error("Invalid embedding configuration");
83
+ if (!(this.config.threshold >= 0 && this.config.threshold <= 1)) throw new Error("Invalid semantic threshold");
84
+ if (this.config.provider === "remote") {
85
+ if (!this.config.endpoint || !config.model || !config.revision) throw new Error("Remote embedding requires explicit endpoint, model and revision");
86
+ const url = new URL(this.config.endpoint);
87
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password) throw new Error("Invalid embedding endpoint");
88
+ if (url.protocol === "http:" && ![
89
+ "localhost",
90
+ "127.0.0.1",
91
+ "[::1]"
92
+ ].includes(url.hostname)) throw new Error("Remote embedding requires HTTPS except loopback");
93
+ }
94
+ this.identity = semanticDigest(JSON.stringify({
95
+ provider: this.config.provider,
96
+ model: this.config.model,
97
+ revision: this.config.revision,
98
+ endpoint: this.config.endpoint,
99
+ prefix: this.config.prefix,
100
+ text: SEMANTIC_TEXT_VERSION,
101
+ pooling: "mean-token-windows-v1",
102
+ dtype: "q8",
103
+ engine: "transformers-3.8.1"
104
+ }));
105
+ }
106
+ read(project, graph, cache) {
107
+ const units = (graph.semanticUnits ?? []).map((unit) => ({
108
+ ...unit,
109
+ fieldPointers: unit.fieldPointers?.slice()
110
+ }));
111
+ const snapshot = {
112
+ checkpoint: graph.checkpoint,
113
+ semanticUnits: units
114
+ };
115
+ const key = semanticDigest(graph.checkpoint + (graph.generation ?? "") + this.publicationIdentity + (cache ? "persistent" : "memory") + JSON.stringify(units.map((u) => [
116
+ u.id,
117
+ u.nodeId,
118
+ u.file,
119
+ u.pointer,
120
+ u.fieldPointers,
121
+ u.digest
122
+ ])));
123
+ if (this.closed || this.config.provider === "disabled") return {
124
+ edges: [],
125
+ status: {
126
+ state: "disabled",
127
+ completed: 0,
128
+ total: units.length,
129
+ model: this.config.model
130
+ }
131
+ };
132
+ let job = this.jobs.get(project);
133
+ if (job?.key !== key) {
134
+ if (this.jobs.size >= 8 && !job) return {
135
+ edges: [],
136
+ status: {
137
+ state: "error",
138
+ completed: 0,
139
+ total: units.length,
140
+ model: this.config.model,
141
+ message: "语义索引同时最多处理8个项目。"
142
+ }
143
+ };
144
+ job?.abort.abort();
145
+ clearTimeout(job?.idle);
146
+ job = {
147
+ key,
148
+ status: {
149
+ state: "indexing",
150
+ completed: 0,
151
+ total: units.length,
152
+ model: this.config.model,
153
+ cache: cache ? "persistent" : "memory",
154
+ message: this.config.provider === "local" ? "本地索引准备中;首次可能下载模型,文本不上传" : "使用已配置远程 embedding 服务处理设计文本"
155
+ },
156
+ edges: [],
157
+ abort: new AbortController(),
158
+ memory: job?.memory ?? new MemorySemanticCache()
159
+ };
160
+ this.jobs.set(project, job);
161
+ const current = job;
162
+ this.tail = this.tail.catch(() => {}).then(async () => {
163
+ if (current.abort.signal.aborted) return;
164
+ try {
165
+ await this.build(snapshot, cache, current);
166
+ } catch (error) {
167
+ const phase = error?.embeddingFailure;
168
+ const detail = phase && Object.hasOwn(localEmbeddingFailures, phase) ? localEmbeddingFailures[phase] : "语义索引失败;请检查本地运行资源或已配置的 embedding 服务。";
169
+ if (!current.abort.signal.aborted) current.status = {
170
+ ...current.status,
171
+ state: "error",
172
+ message: detail + " 保留结构图,不自动上传或重试。"
173
+ };
174
+ }
175
+ });
176
+ current.settled = this.tail;
177
+ }
178
+ clearTimeout(job.idle);
179
+ if (!job.waiters) {
180
+ job.idle = setTimeout(() => this.cancelProject(project), this.idleMs);
181
+ job.idle.unref();
182
+ }
183
+ return {
184
+ edges: job.edges,
185
+ status: { ...job.status }
186
+ };
187
+ }
188
+ /** Await the existing inference job for background persistence; no polling and
189
+ * no project write lock held during model execution. Partial edges never escape. */
190
+ async compute(project, graph, cache, signal) {
191
+ signal?.throwIfAborted();
192
+ const status = this.read(project, graph, cache);
193
+ const job = this.jobs.get(project);
194
+ if (!job || status.status.state === "disabled" || status.status.state === "error") throw new Error(status.status.message ?? "Semantic indexing unavailable");
195
+ clearTimeout(job.idle);
196
+ job.waiters = (job.waiters ?? 0) + 1;
197
+ let rejectAbort;
198
+ const cancelled = new Promise((_resolve, reject) => {
199
+ rejectAbort = reject;
200
+ });
201
+ const abort = () => rejectAbort(signal?.reason ?? /* @__PURE__ */ new Error("Semantic consumer cancelled"));
202
+ signal?.addEventListener("abort", abort, { once: true });
203
+ try {
204
+ signal?.throwIfAborted();
205
+ await Promise.race([job.settled, cancelled]);
206
+ signal?.throwIfAborted();
207
+ job.abort.signal.throwIfAborted();
208
+ if (this.jobs.get(project) !== job || job.status.state !== "ready") throw new Error(job.status.message ?? "Semantic indexing did not complete");
209
+ return {
210
+ edges: structuredClone(job.edges),
211
+ status: { ...job.status }
212
+ };
213
+ } finally {
214
+ signal?.removeEventListener("abort", abort);
215
+ job.waiters--;
216
+ if (!job.waiters && signal?.aborted && job.status.state === "indexing") job.abort.abort(signal.reason);
217
+ if (this.jobs.get(project) === job && !job.waiters) {
218
+ job.idle = setTimeout(() => this.cancelProject(project), this.idleMs);
219
+ job.idle.unref();
220
+ }
221
+ }
222
+ }
223
+ cancelProject(project) {
224
+ const job = this.jobs.get(project);
225
+ clearTimeout(job?.idle);
226
+ job?.abort.abort();
227
+ this.jobs.delete(project);
228
+ }
229
+ async close() {
230
+ this.closed = true;
231
+ for (const project of this.jobs.keys()) this.cancelProject(project);
232
+ await this.tail;
233
+ }
234
+ async build(graph, cache, job) {
235
+ const units = graph.semanticUnits ?? [], vectors = [], missing = [];
236
+ const signal = job.abort.signal;
237
+ for (let i = 0; i < units.length; i++) {
238
+ signal.throwIfAborted();
239
+ const key = semanticDigest(this.identity + units[i].digest);
240
+ let value;
241
+ if (cache && job.status.cache === "persistent") try {
242
+ value = await cache.readSemanticCache(key);
243
+ } catch {
244
+ job.status.cache = "memory";
245
+ }
246
+ value ??= await job.memory.readSemanticCache(key);
247
+ if (value) try {
248
+ vectors[i] = normalizeVector(JSON.parse(value));
249
+ job.status.completed++;
250
+ continue;
251
+ } catch {}
252
+ missing.push(i);
253
+ }
254
+ let worker;
255
+ const kill = () => {
256
+ worker?.kill("SIGTERM");
257
+ };
258
+ signal.addEventListener("abort", kill, { once: true });
259
+ try {
260
+ if (missing.length && this.config.provider === "local") {
261
+ const built = new URL("./embedding-worker.js", import.meta.url);
262
+ worker = fork(existsSync(built) ? built : new URL("./embedding-worker.mjs", import.meta.url), [], {
263
+ windowsHide: true,
264
+ execArgv: ["--max-old-space-size=512"],
265
+ stdio: [
266
+ "ignore",
267
+ "ignore",
268
+ "ignore",
269
+ "ipc"
270
+ ]
271
+ });
272
+ }
273
+ for (let start = 0; start < missing.length; start += 4) {
274
+ signal.throwIfAborted();
275
+ const indices = missing.slice(start, start + 4), texts = indices.map((i) => units[i].text);
276
+ const values = worker ? await this.localBatch(worker, texts, job) : await this.remoteBatch(texts, signal);
277
+ if (!Array.isArray(values) || values.length !== texts.length) throw new Error("Embedding batch count mismatch");
278
+ for (let offset = 0; offset < indices.length; offset++) {
279
+ signal.throwIfAborted();
280
+ const i = indices[offset], vector = normalizeVector(values[offset]);
281
+ vectors[i] = vector;
282
+ const key = semanticDigest(this.identity + units[i].digest), value = JSON.stringify(vector);
283
+ await job.memory.writeSemanticCache(key, value);
284
+ if (cache && job.status.cache === "persistent") try {
285
+ await cache.writeSemanticCache(key, value);
286
+ } catch {
287
+ job.status.cache = "memory";
288
+ }
289
+ job.status.completed++;
290
+ }
291
+ }
292
+ signal.throwIfAborted();
293
+ job.edges = semanticEdges(units, vectors, `${this.config.model}@${this.config.revision}/${this.identity.slice(0, 12)}`, graph.checkpoint, this.config.threshold);
294
+ job.status = {
295
+ ...job.status,
296
+ state: "ready",
297
+ message: `${job.edges.length} 条跨文件语义候选;相似度不等于确认关系`
298
+ };
299
+ } finally {
300
+ signal.removeEventListener("abort", kill);
301
+ if (worker) await stopEmbeddingChild(worker);
302
+ }
303
+ }
304
+ localBatch(worker, texts, job) {
305
+ return new Promise((resolveBatch, reject) => {
306
+ const cleanup = () => {
307
+ clearTimeout(timer);
308
+ worker.off("message", message);
309
+ worker.off("error", failure);
310
+ worker.off("exit", failure);
311
+ job.abort.signal.removeEventListener("abort", failure);
312
+ };
313
+ const failure = () => {
314
+ cleanup();
315
+ reject(/* @__PURE__ */ new Error("Embedding worker stopped"));
316
+ };
317
+ const message = (data) => {
318
+ if (data.progress) {
319
+ job.status.message = data.progress;
320
+ return;
321
+ }
322
+ cleanup();
323
+ if (data.error || !data.vectors) reject(Object.assign(/* @__PURE__ */ new Error("Embedding worker failed"), { embeddingFailure: data.error }));
324
+ else resolveBatch(data.vectors);
325
+ };
326
+ const timer = setTimeout(() => {
327
+ failure();
328
+ worker.kill("SIGTERM");
329
+ }, 18e4);
330
+ worker.on("message", message);
331
+ worker.once("error", failure);
332
+ worker.once("exit", failure);
333
+ job.abort.signal.addEventListener("abort", failure, { once: true });
334
+ if (job.abort.signal.aborted) {
335
+ failure();
336
+ return;
337
+ }
338
+ worker.send({
339
+ texts,
340
+ config: this.config
341
+ }, (error) => {
342
+ if (error) failure();
343
+ });
344
+ });
345
+ }
346
+ async remoteBatch(texts, signal) {
347
+ const response = await fetch(this.config.endpoint, {
348
+ method: "POST",
349
+ redirect: "error",
350
+ headers: {
351
+ "Content-Type": "application/json",
352
+ ...this.config.apiKey ? { Authorization: `Bearer ${this.config.apiKey}` } : {}
353
+ },
354
+ body: JSON.stringify({
355
+ model: this.config.model,
356
+ input: texts.map((t) => this.config.prefix + t)
357
+ }),
358
+ signal: AbortSignal.any([signal, AbortSignal.timeout(6e4)])
359
+ });
360
+ if (!response.ok) throw new Error("Remote embedding request failed");
361
+ const data = await response.json();
362
+ if (!Array.isArray(data.data) || data.data.length !== texts.length || new Set(data.data.map((d) => d.index)).size !== texts.length || data.data.some((d) => !Number.isInteger(d.index) || d.index < 0 || d.index >= texts.length)) throw new Error("Invalid remote embedding response");
363
+ return data.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
364
+ }
365
+ };
366
+ //#endregion
367
+ export { DesignSemanticIndex, MemorySemanticCache, stopEmbeddingChild };