dsh-prime-memory 0.26.5-beta.0 → 0.26.6
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.
- package/CHANGELOG.md +39 -0
- package/README.en.md +62 -1
- package/README.md +52 -1
- package/dist/client.js +36785 -134
- package/dist/config.d.ts +9 -0
- package/dist/config.js +8 -0
- package/dist/contract.d.ts +76 -0
- package/dist/embedding-child.cjs +192 -0
- package/dist/embedding-worker.cjs +2 -0
- package/dist/graph/apply.d.ts +9 -0
- package/dist/graph/apply.js +12 -2
- package/dist/graph/constraints.d.ts +3 -1
- package/dist/graph/constraints.js +4 -2
- package/dist/graph/preview.d.ts +92 -0
- package/dist/graph/preview.js +186 -0
- package/dist/graph/search.d.ts +32 -6
- package/dist/graph/search.js +102 -12
- package/dist/graph/types.d.ts +26 -1
- package/dist/graph/types.js +26 -1
- package/dist/hooks/recall.js +36 -2
- package/dist/index.js +23 -3
- package/dist/llm.d.ts +7 -2
- package/dist/llm.js +14 -22
- package/dist/pipeline/graph.d.ts +26 -1
- package/dist/pipeline/graph.js +74 -4
- package/dist/pipeline/l1.js +87 -26
- package/dist/pipeline/md-sync.js +4 -2
- package/dist/pipeline/runner.d.ts +26 -0
- package/dist/pipeline/runner.js +74 -7
- package/dist/pipeline/slot-migrate.d.ts +3 -2
- package/dist/pipeline/slot-migrate.js +3 -2
- package/dist/projection/recall.d.ts +10 -3
- package/dist/projection/recall.js +14 -0
- package/dist/settings.js +3 -0
- package/dist/stats.d.ts +1 -1
- package/dist/stats.js +47 -12
- package/dist/store/embedding-source.d.ts +4 -2
- package/dist/store/embedding-source.js +5 -2
- package/dist/store/graph-store.d.ts +111 -4
- package/dist/store/graph-store.js +324 -15
- package/dist/store/l1.d.ts +17 -9
- package/dist/store/l1.js +55 -18
- package/dist/store/local-embedding.d.ts +9 -5
- package/dist/store/local-embedding.js +137 -17
- package/dist/store/memory-md-disable.d.ts +3 -2
- package/dist/store/memory-md.d.ts +3 -3
- package/dist/store/memory-md.js +6 -4
- package/dist/store/runtime-installer.d.ts +13 -1
- package/dist/store/runtime-installer.js +40 -2
- package/dist/store/session-recalled.d.ts +9 -3
- package/dist/store/session-recalled.js +24 -5
- package/dist/store/sqlite.d.ts +24 -12
- package/dist/store/sqlite.js +97 -31
- package/dist/store/supersede.d.ts +10 -2
- package/dist/store/supersede.js +10 -2
- package/dist/store/validity.d.ts +150 -0
- package/dist/store/validity.js +209 -0
- package/dist/tools/index.d.ts +15 -1
- package/dist/tools/index.js +393 -11
- package/dist/types.d.ts +9 -0
- package/dist/util/json-repair.d.ts +48 -0
- package/dist/util/json-repair.js +285 -0
- package/dist/validity-backfill.d.ts +78 -0
- package/dist/validity-backfill.js +160 -0
- package/dsh.plugin.json +2 -2
- package/package.json +5 -4
package/dist/config.d.ts
CHANGED
|
@@ -81,6 +81,15 @@ export interface MemoryConfig {
|
|
|
81
81
|
* 同时为真才执行;图谱是 L1 的可重建投影,关闭不影响记忆主链路。 */
|
|
82
82
|
enabled: boolean;
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* 时效分级(expiry-tiering 门2):`tieringEnabled` 灰度闸,**默认关**。
|
|
86
|
+
* 关时被动注入与改前逐字一致(零漂移);开时注入候选按 classifyValidity 现算态
|
|
87
|
+
* 剔除 `expired`(仍在索引可检索,只是不自动浮现),`expired_suspect` 保留并带标注。
|
|
88
|
+
* 与部署 cfg.validity.tieringEnabled 取或的还有面板运行时开关(live.validityTiering)。
|
|
89
|
+
* 门1 解耦本身不受本闸控(它是 bugfix:误遮蔽历史应尽快恢复)。 */
|
|
90
|
+
validity: {
|
|
91
|
+
tieringEnabled: boolean;
|
|
92
|
+
};
|
|
84
93
|
/** §C 矛盾冻结:去重判定"两边都像是对的、机器判不了"时不自动裁决,
|
|
85
94
|
* 把冲突对停放到待人工裁决区(conflict_pending)。**默认关**——
|
|
86
95
|
* 冻结消耗人的注意力,不可默认全开。 */
|
package/dist/config.js
CHANGED
|
@@ -87,6 +87,9 @@ export function liveSettingsSchema() {
|
|
|
87
87
|
redistillEnabled: Schema.boolean().default(false),
|
|
88
88
|
mdSyncEnabled: Schema.boolean().default(false),
|
|
89
89
|
memportEnabled: Schema.boolean().default(false),
|
|
90
|
+
/** 时效分级门2 面板开关:与部署 cfg.validity.tieringEnabled 取或(任一为真即启用
|
|
91
|
+
* 注入侧 expired 过滤)。默认关;先面板验证再翻部署默认(照 keywordEnabled 先例)。 */
|
|
92
|
+
validityTiering: Schema.boolean().default(false),
|
|
90
93
|
/**
|
|
91
94
|
* 存量回填(keyword / redistill)LLM 批调用的**运行时并行度**覆盖:
|
|
92
95
|
* `0` = 跟随部署 `cfg.backfill.concurrency`;1–8 = 直接覆盖。
|
|
@@ -156,6 +159,11 @@ export const memorySchema = Schema.object({
|
|
|
156
159
|
graph: Schema.object({
|
|
157
160
|
enabled: Schema.boolean().default(false),
|
|
158
161
|
}),
|
|
162
|
+
// 时效分级门2 灰度闸:默认关(关=注入行为与改前逐字一致)。验后翻默认为 true 的
|
|
163
|
+
// 发版决策记在计划 findings(task_8.6)。
|
|
164
|
+
validity: Schema.object({
|
|
165
|
+
tieringEnabled: Schema.boolean().default(false),
|
|
166
|
+
}),
|
|
159
167
|
// §C 矛盾冻结:默认关。开启后去重决策词表多出 conflict 动作,冲突对停放待人工裁决。
|
|
160
168
|
conflictFreeze: Schema.object({
|
|
161
169
|
enabled: Schema.boolean().default(false),
|
package/dist/contract.d.ts
CHANGED
|
@@ -116,6 +116,8 @@ export interface MemoryLiveSettings {
|
|
|
116
116
|
mdSyncEnabled: boolean;
|
|
117
117
|
/** 外部记忆来源 Connect 启用开关(概览区可拨):true = 放行 memport-* 端点。与部署 cfg.memport.enabled 取或。 */
|
|
118
118
|
memportEnabled: boolean;
|
|
119
|
+
/** 时效分级门2 启用开关(概览区可拨):true = 被动注入剔除 expired。与部署 cfg.validity.tieringEnabled 取或;关=注入与改前逐字一致。 */
|
|
120
|
+
validityTiering: boolean;
|
|
119
121
|
/** md 投射/后缀收敛的单次动作护栏运行时覆盖:0 = 跟随部署 `cfg.memExport.maxRecords`(默认 2000)。
|
|
120
122
|
* 存在的理由:一次点完多少条属于“回填历史时才改一下”的旋钮,不该要求改 YAML + 重启。 */
|
|
121
123
|
mdExportMaxRecords: number;
|
|
@@ -927,6 +929,13 @@ export interface UiRecord {
|
|
|
927
929
|
* 只在 `dsh-memory/session-recalled` 这条路径上有意义;缺省 = 无此概念(旧服务端)。
|
|
928
930
|
*/
|
|
929
931
|
superseded?: boolean;
|
|
932
|
+
/**
|
|
933
|
+
* 路归因:hybrid 融合时这条命中来自哪几路(`'fts'|'vec'|'graph'|'recency'`)。
|
|
934
|
+
* 只在 `dsh-memory/session-recalled` 路径上有意义;缺省 = 无从归因
|
|
935
|
+
* (单路检索策略/旧文件条目/旧服务端),展示层不渲染徽标。纯投影字段,
|
|
936
|
+
* 存储侧落 `session-recalled.json` 的 lanes 键(读时拆管道串)。
|
|
937
|
+
*/
|
|
938
|
+
lanes?: string[];
|
|
930
939
|
/**
|
|
931
940
|
* 成熟度(G3 读时派生):priority + 被用次数 + 老化权重合成。
|
|
932
941
|
* 可选字段——旧 client 忽略=向后兼容;不参与存储/快照(纯投影)。
|
|
@@ -1228,6 +1237,69 @@ export interface GraphNodeGetResponse {
|
|
|
1228
1237
|
/** 与该节点相连的 active 边。 */
|
|
1229
1238
|
edges: GraphEdge[];
|
|
1230
1239
|
}
|
|
1240
|
+
/** dsh-memory/graph-preview(面板图谱预览:缺省返回推荐种子榜,seedId 给定展开该节点邻域)。
|
|
1241
|
+
* 候选只含 active|disputed 节点×active 边(与检索同判据);空图/未启用返全零结构不抛。 */
|
|
1242
|
+
export interface GraphPreviewRequest {
|
|
1243
|
+
/** 给定即以该节点为唯一中心(悬挂 id 返空 seeds,不解析不抛——graph-node-get 同款宽容)。 */
|
|
1244
|
+
seedId?: string;
|
|
1245
|
+
/** 'full' = 响应附全图轻量块(mode:'full' 与大图星图共用同端点,一次 loadGraph 全供)。
|
|
1246
|
+
* 非 'full' 一律按缺省——无 mode 响应形状与旧版逐字一致(可选键缺席=零漂移)。 */
|
|
1247
|
+
mode?: 'full';
|
|
1248
|
+
}
|
|
1249
|
+
export interface GraphPreviewResponse {
|
|
1250
|
+
seeds: Array<{
|
|
1251
|
+
node: {
|
|
1252
|
+
id: string;
|
|
1253
|
+
name: string;
|
|
1254
|
+
type: string;
|
|
1255
|
+
degree: number;
|
|
1256
|
+
causalDegree: number;
|
|
1257
|
+
};
|
|
1258
|
+
/** 因果边优先→邻居度数降序→updatedAt 新先;长度 ≤ GRAPH_PREVIEW_NEIGHBORS。 */
|
|
1259
|
+
neighbors: Array<{
|
|
1260
|
+
id: string;
|
|
1261
|
+
name: string;
|
|
1262
|
+
type: string;
|
|
1263
|
+
relation: string;
|
|
1264
|
+
causal: boolean;
|
|
1265
|
+
}>;
|
|
1266
|
+
truncated: boolean;
|
|
1267
|
+
}>;
|
|
1268
|
+
stats: {
|
|
1269
|
+
nodes: number;
|
|
1270
|
+
edges: number;
|
|
1271
|
+
causalEdges: number;
|
|
1272
|
+
/** 未定投影 job 数(pending/running/failed 退避中),>0 → UI 显"构建中 x/y"(x=jobsTotal-pendingJobs)。 */
|
|
1273
|
+
pendingJobs: number;
|
|
1274
|
+
/** 全量 job 数(含各状态),"构建中 x/y"的分母——名字即分母,不拿 done 冒充分子。 */
|
|
1275
|
+
jobsTotal: number;
|
|
1276
|
+
/** 永久失败作业数(dead),可选(零漂移先例);>0 → UI 拆成"完成/失败/排队"三档显示。 */
|
|
1277
|
+
deadJobs?: number;
|
|
1278
|
+
};
|
|
1279
|
+
/** mode==='full' 才出现;全图星图轻量块(可见节点×active 边,截断标记 truncated)。
|
|
1280
|
+
* 不带此键时响应与旧形状逐字节一致——消费方按需读 `res.full?`。 */
|
|
1281
|
+
full?: {
|
|
1282
|
+
nodes: Array<{
|
|
1283
|
+
id: string;
|
|
1284
|
+
name: string;
|
|
1285
|
+
type: string;
|
|
1286
|
+
}>;
|
|
1287
|
+
edges: Array<{
|
|
1288
|
+
from: string;
|
|
1289
|
+
to: string;
|
|
1290
|
+
relation: string;
|
|
1291
|
+
causal: boolean;
|
|
1292
|
+
}>;
|
|
1293
|
+
truncated: boolean;
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
/** dsh-memory/graph-cleanup-dead(清理历史失败作业:删除 status='dead' 的行,不影响已重入队的记录)。 */
|
|
1297
|
+
export interface GraphCleanupDeadRequest {
|
|
1298
|
+
olderThanMs?: number;
|
|
1299
|
+
}
|
|
1300
|
+
export interface GraphCleanupDeadResponse {
|
|
1301
|
+
removed: number;
|
|
1302
|
+
}
|
|
1231
1303
|
/** dsh-memory/scenes(两族拼接的混合视图)。 */
|
|
1232
1304
|
export interface ScenesResponse {
|
|
1233
1305
|
items: Array<{
|
|
@@ -1778,6 +1850,8 @@ export interface DshMemoryRequestMap {
|
|
|
1778
1850
|
'dsh-memory/conflicts-rejected': ConflictRejectedRequest;
|
|
1779
1851
|
'dsh-memory/graph-search': GraphSearchRequest;
|
|
1780
1852
|
'dsh-memory/graph-node-get': GraphNodeGetRequest;
|
|
1853
|
+
'dsh-memory/graph-preview': GraphPreviewRequest;
|
|
1854
|
+
'dsh-memory/graph-cleanup-dead': GraphCleanupDeadRequest;
|
|
1781
1855
|
'dsh-memory/scenes': Record<string, never>;
|
|
1782
1856
|
'dsh-memory/persona': Record<string, never>;
|
|
1783
1857
|
'dsh-memory/log-tail': LogTailRequest;
|
|
@@ -1843,6 +1917,8 @@ export interface DshMemoryResponseMap {
|
|
|
1843
1917
|
'dsh-memory/conflicts-rejected': ConflictRejectedResponse;
|
|
1844
1918
|
'dsh-memory/graph-search': GraphSearchResponse;
|
|
1845
1919
|
'dsh-memory/graph-node-get': GraphNodeGetResponse;
|
|
1920
|
+
'dsh-memory/graph-preview': GraphPreviewResponse;
|
|
1921
|
+
'dsh-memory/graph-cleanup-dead': GraphCleanupDeadResponse;
|
|
1846
1922
|
'dsh-memory/scenes': ScenesResponse;
|
|
1847
1923
|
'dsh-memory/persona': PersonaResponse;
|
|
1848
1924
|
'dsh-memory/log-tail': LogTailResponse;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地嵌入子进程(fix #11):transformers.js 的模型加载与推理在 fork 出的独立
|
|
3
|
+
* 子进程执行,主线程只投递文本、收回向量。取代 worker_threads 的原因:
|
|
4
|
+
* 插件 runtime 的 sharp/libvips 与宿主 Electron 的 sharp/libvips 在同一进程内
|
|
5
|
+
* 加载会撞 libvips-42.dll 名字(OS LoadLibrary 层冲突,同名不同路径行为未定义)
|
|
6
|
+
* ——worker_thread 与主线程同进程,撞名前提仍在;子进程自带独立 DLL 表,物理隔离。
|
|
7
|
+
*
|
|
8
|
+
* 推理队列语义与 embedding-worker.cjs 完全一致(单线程串行、priority 插队、
|
|
9
|
+
* 条间 setImmediate 让路),只有 IPC 传输层不同:
|
|
10
|
+
*
|
|
11
|
+
* 协议(与 src/store/local-embedding.ts 的 RealChildChannel 配对;自增 id):
|
|
12
|
+
* - 启动配置:env DSH_EMBED_CFG(JSON 字符串:{runtimeDir,modelDir,pooling,dtype,
|
|
13
|
+
* maxInputChars}——fork 的 env 只收标量,嵌套对象经序列化传递);
|
|
14
|
+
* - 入向:{id,type:'ping'} | {id,type:'warmup'} | {id,type:'embed',texts,priority}
|
|
15
|
+
* - 出向:{id,ok:true,type:'pong'|'ready'|'embedded',vectors?}(vectors 为
|
|
16
|
+
* number[][],fork IPC 默认 JSON 序列化不支持 TypedArray,主线程侧重建
|
|
17
|
+
* Float32Array;512d×32 条 ≈64KB,序列化 <5ms,瓶颈在推理不在传输)
|
|
18
|
+
* | {id,ok:false,stage:'load'|'infer',error} | {type:'fatal',error}
|
|
19
|
+
*
|
|
20
|
+
* 信任边界(沿 AGENTS.md 审查记录):runtime/ 目录在数据目录信任边界内,
|
|
21
|
+
* 本进程按其 package.json createRequire 加载 transformers——对数据目录有写
|
|
22
|
+
* 权限的进程理论上可植入恶意模块;模型文件逐文件 sha256 锁定,运行时代码
|
|
23
|
+
* 无同级防护,完整修复需安装指纹设计,暂以文档化假设为准。
|
|
24
|
+
*/
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
const { createRequire } = require('node:module');
|
|
29
|
+
|
|
30
|
+
// 必须由 fork 启动(需要 IPC channel);裸 node 执行时静默退出,不报 SyntaxError、不永挂。
|
|
31
|
+
if (typeof process.send !== 'function') {
|
|
32
|
+
console.error('[embedding-child] 未经 fork 启动,无 IPC 通道,退出');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 错误消息归一化(Error 取 message,其余 String 化)。 */
|
|
37
|
+
function errMsg(err) {
|
|
38
|
+
return err && err.message ? err.message : String(err);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const cfg = JSON.parse(process.env.DSH_EMBED_CFG || '{}');
|
|
42
|
+
const RUNTIME_DIR = typeof cfg.runtimeDir === 'string' ? cfg.runtimeDir : '';
|
|
43
|
+
const MODEL_DIR = typeof cfg.modelDir === 'string' ? cfg.modelDir : '';
|
|
44
|
+
const POOLING = cfg.pooling === 'cls' ? 'cls' : 'mean';
|
|
45
|
+
const DTYPE = typeof cfg.dtype === 'string' ? cfg.dtype : 'q8';
|
|
46
|
+
const MAX_INPUT_CHARS = cfg.maxInputChars > 0 ? cfg.maxInputChars : 5000;
|
|
47
|
+
|
|
48
|
+
let extractor = null; // pipeline('feature-extraction', …)
|
|
49
|
+
let loadState = 'idle'; // idle | loading | ready | failed
|
|
50
|
+
let loadPromise = null;
|
|
51
|
+
let loadError = null;
|
|
52
|
+
|
|
53
|
+
// ── 模型懒加载(首请求触发;失败后经 warmup 可重试,embed 请求失败快返回) ──
|
|
54
|
+
function ensureLoaded() {
|
|
55
|
+
if (loadState === 'ready') return Promise.resolve();
|
|
56
|
+
if (loadState === 'loading' && loadPromise) return loadPromise;
|
|
57
|
+
loadState = 'loading';
|
|
58
|
+
loadError = null;
|
|
59
|
+
loadPromise = (async () => {
|
|
60
|
+
try {
|
|
61
|
+
if (!RUNTIME_DIR || !MODEL_DIR) throw new Error('DSH_EMBED_CFG 缺少 runtimeDir/modelDir');
|
|
62
|
+
const req = createRequire(path.join(RUNTIME_DIR, 'package.json'));
|
|
63
|
+
const mod = req('@huggingface/transformers');
|
|
64
|
+
if (mod.env) {
|
|
65
|
+
mod.env.allowRemoteModels = false;
|
|
66
|
+
if (mod.env.allowLocalModels !== undefined) mod.env.allowLocalModels = true;
|
|
67
|
+
}
|
|
68
|
+
extractor = await mod.pipeline('feature-extraction', MODEL_DIR, { dtype: DTYPE });
|
|
69
|
+
loadState = 'ready';
|
|
70
|
+
} catch (err) {
|
|
71
|
+
loadState = 'failed';
|
|
72
|
+
loadError = errMsg(err);
|
|
73
|
+
loadPromise = null; // 失败后下一次 warmup 重新走加载
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
return loadPromise;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── 推理队列(逐条 job;priority 插队;条间让路) ──
|
|
81
|
+
const queue = []; // [{ request, index, text }]
|
|
82
|
+
let pumping = false;
|
|
83
|
+
|
|
84
|
+
function postReply(msg) {
|
|
85
|
+
// 通道已断(父进程退出/正在终止)时 send 抛错——静默即可,父进程侧有 exit 兜底
|
|
86
|
+
try {
|
|
87
|
+
process.send(msg);
|
|
88
|
+
} catch {
|
|
89
|
+
/* IPC 已关闭,回复无处可去 */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 请求全部条目完成 → 回执(Float32Array → number[] 序列化,IPC JSON 通道不支持 TypedArray)。 */
|
|
94
|
+
function settle(request) {
|
|
95
|
+
if (request.failed || request.remaining !== 0) return;
|
|
96
|
+
postReply({
|
|
97
|
+
id: request.id,
|
|
98
|
+
ok: true,
|
|
99
|
+
type: 'embedded',
|
|
100
|
+
vectors: request.vectors.map((v) => Array.from(v)),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 首个失败即回执错误,并移除该请求尚未开跑的条目(正在跑的至多 1 条,算完即弃)。 */
|
|
105
|
+
function failRequest(request, error, stage) {
|
|
106
|
+
if (request.failed) return;
|
|
107
|
+
request.failed = true;
|
|
108
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
109
|
+
if (queue[i].request === request) queue.splice(i, 1);
|
|
110
|
+
}
|
|
111
|
+
postReply({ id: request.id, ok: false, stage: stage, error: errMsg(error) });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function pump() {
|
|
115
|
+
if (pumping) return;
|
|
116
|
+
pumping = true;
|
|
117
|
+
try {
|
|
118
|
+
while (queue.length > 0) {
|
|
119
|
+
const job = queue.shift();
|
|
120
|
+
if (job.request.failed) continue;
|
|
121
|
+
try {
|
|
122
|
+
const result = await extractor([job.text.slice(0, MAX_INPUT_CHARS)], { pooling: POOLING, normalize: true });
|
|
123
|
+
const data = result[0] && result[0].data;
|
|
124
|
+
job.request.vectors[job.index] = data instanceof Float32Array ? data : new Float32Array(data);
|
|
125
|
+
job.request.remaining -= 1;
|
|
126
|
+
settle(job.request);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
failRequest(job.request, err, 'infer');
|
|
129
|
+
}
|
|
130
|
+
// 条间让路:交还本进程事件循环,新消息(含 priority 插队)可在批间被处理
|
|
131
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
pumping = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleEmbed(msg) {
|
|
139
|
+
const texts = Array.isArray(msg.texts) ? msg.texts : [];
|
|
140
|
+
if (texts.length === 0) {
|
|
141
|
+
postReply({ id: msg.id, ok: true, type: 'embedded', vectors: [] });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const request = { id: msg.id, vectors: new Array(texts.length), remaining: texts.length, failed: false };
|
|
145
|
+
// 单条请求(召回 query)插队到队首;批次追加到队尾(批内条目间仍会被后来的插队)
|
|
146
|
+
const priority = msg.priority === true && texts.length === 1;
|
|
147
|
+
for (let i = 0; i < texts.length; i++) {
|
|
148
|
+
const job = { request: request, index: i, text: String(texts[i] == null ? '' : texts[i]) };
|
|
149
|
+
if (priority) queue.unshift(job);
|
|
150
|
+
else queue.push(job);
|
|
151
|
+
}
|
|
152
|
+
void pump();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
process.on('message', (msg) => {
|
|
156
|
+
if (!msg || typeof msg.id !== 'number' || typeof msg.type !== 'string') return;
|
|
157
|
+
if (msg.type === 'ping') {
|
|
158
|
+
postReply({ id: msg.id, ok: true, type: 'pong' });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (msg.type === 'warmup') {
|
|
162
|
+
ensureLoaded().then(
|
|
163
|
+
() => postReply({ id: msg.id, ok: true, type: 'ready' }),
|
|
164
|
+
() => postReply({ id: msg.id, ok: false, stage: 'load', error: loadError ?? '模型加载失败' }),
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (msg.type === 'embed') {
|
|
169
|
+
void (async () => {
|
|
170
|
+
try {
|
|
171
|
+
await ensureLoaded();
|
|
172
|
+
} catch {
|
|
173
|
+
postReply({ id: msg.id, ok: false, stage: 'load', error: loadError ?? '模型加载失败' });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
handleEmbed(msg);
|
|
177
|
+
})();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// 父进程disconnect(通道被主动关闭,如宿主优雅退出/RealChildChannel 前置断开):
|
|
183
|
+
// 模型与 onnxruntime 都只服务于这条通道,留着就是孤儿常驻——直接退出。
|
|
184
|
+
// taskkill /T /F 兜底覆盖"父进程被硬杀、无 disconnect"的路径。
|
|
185
|
+
process.on('disconnect', () => process.exit(0));
|
|
186
|
+
|
|
187
|
+
// 未捕获异常:尽力通知父进程后强制退出(父进程的 exit 处理会拒绝全部 pending
|
|
188
|
+
// 并转入 failed 态)。不保持半死状态——半死的子进程只会让调用方挂到超时。
|
|
189
|
+
process.on('uncaughtException', (err) => {
|
|
190
|
+
postReply({ type: 'fatal', error: errMsg(err) });
|
|
191
|
+
setTimeout(() => process.exit(1), 50);
|
|
192
|
+
});
|
package/dist/graph/apply.d.ts
CHANGED
|
@@ -31,7 +31,16 @@ export declare function normalizeEntityName(name: string): string;
|
|
|
31
31
|
* validTo → validFrom(时间增强列,新写入路径)→ metadata.activity_end_time →
|
|
32
32
|
* metadata.activity_start_time(列迁移前的存量数据)→ timestamps 最新 → createdAt。
|
|
33
33
|
* 全部无证据才用 fallback(now),绝不猜测。
|
|
34
|
+
*
|
|
35
|
+
* task_2.6(expiry-tiering):兜底 now 分支回传 `'fallback_now'` 证据等级——供图谱侧
|
|
36
|
+
* 同口径读时派生使用(落到 now 的 validFrom **不是**时间证据,与 activity_* 链的
|
|
37
|
+
* 'evidence' 必须可区分;否则假过期与真过期同形)。
|
|
34
38
|
*/
|
|
39
|
+
export declare function anchorTimeFromRecordsDetailed(records: readonly MemoryRecord[], fallbackIso: string): {
|
|
40
|
+
iso: string;
|
|
41
|
+
source: 'evidence' | 'fallback_now';
|
|
42
|
+
};
|
|
43
|
+
/** {@link anchorTimeFromRecordsDetailed} 的旧签名封装(调用方只要时刻时的零改动通道)。 */
|
|
35
44
|
export declare function anchorTimeFromRecords(records: readonly MemoryRecord[], fallbackIso: string): string;
|
|
36
45
|
/** 应用一次投影提案(硬校验 + 消歧 + supersede + 状态重建)。 */
|
|
37
46
|
export declare function applyGraphProjection(options: ApplyGraphProjectionOptions): ApplyGraphProjectionOutcome;
|
package/dist/graph/apply.js
CHANGED
|
@@ -66,8 +66,12 @@ function recordFamily(r) {
|
|
|
66
66
|
* validTo → validFrom(时间增强列,新写入路径)→ metadata.activity_end_time →
|
|
67
67
|
* metadata.activity_start_time(列迁移前的存量数据)→ timestamps 最新 → createdAt。
|
|
68
68
|
* 全部无证据才用 fallback(now),绝不猜测。
|
|
69
|
+
*
|
|
70
|
+
* task_2.6(expiry-tiering):兜底 now 分支回传 `'fallback_now'` 证据等级——供图谱侧
|
|
71
|
+
* 同口径读时派生使用(落到 now 的 validFrom **不是**时间证据,与 activity_* 链的
|
|
72
|
+
* 'evidence' 必须可区分;否则假过期与真过期同形)。
|
|
69
73
|
*/
|
|
70
|
-
export function
|
|
74
|
+
export function anchorTimeFromRecordsDetailed(records, fallbackIso) {
|
|
71
75
|
let latest = Number.NaN;
|
|
72
76
|
const consider = (raw) => {
|
|
73
77
|
const t = typeof raw === 'number' ? raw : typeof raw === 'string' ? Date.parse(raw) : Number.NaN;
|
|
@@ -85,7 +89,13 @@ export function anchorTimeFromRecords(records, fallbackIso) {
|
|
|
85
89
|
consider(t);
|
|
86
90
|
consider(r.createdAt);
|
|
87
91
|
}
|
|
88
|
-
return Number.isNaN(latest)
|
|
92
|
+
return Number.isNaN(latest)
|
|
93
|
+
? { iso: fallbackIso, source: 'fallback_now' }
|
|
94
|
+
: { iso: new Date(latest).toISOString(), source: 'evidence' };
|
|
95
|
+
}
|
|
96
|
+
/** {@link anchorTimeFromRecordsDetailed} 的旧签名封装(调用方只要时刻时的零改动通道)。 */
|
|
97
|
+
export function anchorTimeFromRecords(records, fallbackIso) {
|
|
98
|
+
return anchorTimeFromRecordsDetailed(records, fallbackIso).iso;
|
|
89
99
|
}
|
|
90
100
|
/** 来源集并集封顶(保最新:新引用追加在尾部,溢出丢头部最老引用)。 */
|
|
91
101
|
function unionSourcesCapped(old, add) {
|
|
@@ -13,8 +13,10 @@ export declare const GRAPH_NODE_TYPE_SET: ReadonlySet<string>;
|
|
|
13
13
|
/** 记录生命周期四态(节点/fact/边通用)。 */
|
|
14
14
|
export declare const GRAPH_STATUSES: readonly GraphRecordStatus[];
|
|
15
15
|
export declare const GRAPH_STATUS_SET: ReadonlySet<string>;
|
|
16
|
+
/** 因果边四词单源(投影提示词枚举、校验与面板预览加权共用——防两处词表漂移)。 */
|
|
17
|
+
export declare const GRAPH_CAUSAL_RELATIONS: readonly ["导致", "因为", "修复了", "取代"];
|
|
16
18
|
/** 规范中文关系词(提示词给枚举;校验不做白名单——关系是开放词表,只做长度钳制)。
|
|
17
|
-
*
|
|
19
|
+
* 尾部即因果集 GRAPH_CAUSAL_RELATIONS——让"前因后果"跨记录成链;仅当 graph.enabled 时
|
|
18
20
|
* 才参与投影(整块图谱默认关,不碰非图谱用户的蒸馏产物)。 */
|
|
19
21
|
export declare const GRAPH_RELATION_WORDS: readonly ["使用", "属于", "创建", "参与", "贡献", "依赖", "位于", "相关", "导致", "因为", "修复了", "取代"];
|
|
20
22
|
/** 提案字段上限(校验侧截断/丢弃与提示词内插共用同一组数字)。 */
|
|
@@ -4,10 +4,12 @@ export const GRAPH_NODE_TYPE_SET = new Set(GRAPH_NODE_TYPES);
|
|
|
4
4
|
/** 记录生命周期四态(节点/fact/边通用)。 */
|
|
5
5
|
export const GRAPH_STATUSES = ['active', 'superseded', 'disputed', 'archived'];
|
|
6
6
|
export const GRAPH_STATUS_SET = new Set(GRAPH_STATUSES);
|
|
7
|
+
/** 因果边四词单源(投影提示词枚举、校验与面板预览加权共用——防两处词表漂移)。 */
|
|
8
|
+
export const GRAPH_CAUSAL_RELATIONS = ['导致', '因为', '修复了', '取代'];
|
|
7
9
|
/** 规范中文关系词(提示词给枚举;校验不做白名单——关系是开放词表,只做长度钳制)。
|
|
8
|
-
*
|
|
10
|
+
* 尾部即因果集 GRAPH_CAUSAL_RELATIONS——让"前因后果"跨记录成链;仅当 graph.enabled 时
|
|
9
11
|
* 才参与投影(整块图谱默认关,不碰非图谱用户的蒸馏产物)。 */
|
|
10
|
-
export const GRAPH_RELATION_WORDS = ['使用', '属于', '创建', '参与', '贡献', '依赖', '位于', '相关',
|
|
12
|
+
export const GRAPH_RELATION_WORDS = ['使用', '属于', '创建', '参与', '贡献', '依赖', '位于', '相关', ...GRAPH_CAUSAL_RELATIONS];
|
|
11
13
|
/** 提案字段上限(校验侧截断/丢弃与提示词内插共用同一组数字)。 */
|
|
12
14
|
export const GRAPH_CAP = {
|
|
13
15
|
/** 提案内临时引用(ref)长度。 */
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { GraphEdge, GraphNode, GraphNodeType } from './types.js';
|
|
2
|
+
export interface GraphSeedNode {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: GraphNodeType;
|
|
6
|
+
/** active 边双向计数(两端都可见才计入,与 degreeMaps 同口径)。 */
|
|
7
|
+
degree: number;
|
|
8
|
+
/** 其中因果边(constraints.GRAPH_CAUSAL_RELATIONS)计数。 */
|
|
9
|
+
causalDegree: number;
|
|
10
|
+
}
|
|
11
|
+
export interface GraphSeedNeighbor {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: GraphNodeType;
|
|
15
|
+
/** 该条边的关系词(平行多边各占一条,标注各画各的)。 */
|
|
16
|
+
relation: string;
|
|
17
|
+
causal: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface GraphSeedView {
|
|
20
|
+
node: GraphSeedNode;
|
|
21
|
+
/** 因果边优先→邻居度数降序→updatedAt 新先→id 升序;长度 ≤ neighborLimit。 */
|
|
22
|
+
neighbors: GraphSeedNeighbor[];
|
|
23
|
+
/** 邻居超上限被截断的标记(UI 画 "+n" 角标)。 */
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface GraphPreviewStats {
|
|
27
|
+
nodes: number;
|
|
28
|
+
edges: number;
|
|
29
|
+
causalEdges: number;
|
|
30
|
+
/** 未定投影 job 数(pending/running/failed),>0 → UI "构建中 x/y"。 */
|
|
31
|
+
pendingJobs: number;
|
|
32
|
+
/** 全量 job 数(分母)。 */
|
|
33
|
+
jobsTotal: number;
|
|
34
|
+
/** 永久失败(dead)作业数,可选(零漂移先例);>0 → UI 拆三档。 */
|
|
35
|
+
deadJobs?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface GraphPreviewResult {
|
|
38
|
+
seeds: GraphSeedView[];
|
|
39
|
+
stats: GraphPreviewStats;
|
|
40
|
+
}
|
|
41
|
+
export interface PreviewOpts {
|
|
42
|
+
/** 给定即只展开该节点为唯一中心(悬挂/不可见 → 空 seeds,不抛)。 */
|
|
43
|
+
seedId?: string;
|
|
44
|
+
seedLimit?: number;
|
|
45
|
+
neighborLimit?: number;
|
|
46
|
+
pendingJobs?: number;
|
|
47
|
+
jobsTotal?: number;
|
|
48
|
+
deadJobs?: number;
|
|
49
|
+
}
|
|
50
|
+
/** S-2:邻接统计——active 边且两端都可见才计度(双向)。 */
|
|
51
|
+
export interface DegreeMaps {
|
|
52
|
+
degree: Map<string, number>;
|
|
53
|
+
causal: Map<string, number>;
|
|
54
|
+
}
|
|
55
|
+
export declare function degreeMaps(nodes: readonly GraphNode[], edges: readonly GraphEdge[]): DegreeMaps;
|
|
56
|
+
/**
|
|
57
|
+
* S-3/S-4:预览主函数。
|
|
58
|
+
* - seedId 视图:直接展开该节点(跳过挑选);不可见/悬挂 → 空 seeds。
|
|
59
|
+
* - 榜单视图:score = degree + causal×WEIGHT;同分链 causal→sources→updatedAt→id↑;
|
|
60
|
+
* 候选落入已选种子的"自身∪邻居"覆盖集则跳过(相邻合并,画布片不重复);
|
|
61
|
+
* 孤立节点(度 0)不入榜——画布只有中心一个点没有信息量。
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildGraphPreview(nodes: readonly GraphNode[], edges: readonly GraphEdge[], opts?: PreviewOpts): GraphPreviewResult;
|
|
64
|
+
/** 全图节点轻量项(星图渲染只需这三字段,facts/sources 不下发——带宽与隐私面同步收紧)。 */
|
|
65
|
+
export interface FullViewNode {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
type: GraphNodeType;
|
|
69
|
+
}
|
|
70
|
+
/** 全图边轻量项(causal 由单源词表判出,渲染侧不再抄词表)。 */
|
|
71
|
+
export interface FullViewEdge {
|
|
72
|
+
from: string;
|
|
73
|
+
to: string;
|
|
74
|
+
relation: string;
|
|
75
|
+
causal: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface GraphFullView {
|
|
78
|
+
nodes: FullViewNode[];
|
|
79
|
+
edges: FullViewEdge[];
|
|
80
|
+
/** 任一维触顶即 true(截断标记而非硬拒;渲染侧可据此提示"仅画部分")。 */
|
|
81
|
+
truncated: boolean;
|
|
82
|
+
}
|
|
83
|
+
export interface FullViewCaps {
|
|
84
|
+
nodeCap?: number;
|
|
85
|
+
edgeCap?: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 全图星图数据块(纯函数,不查库):可见节点轻量投影 × active 边,
|
|
89
|
+
* 双上限截断。**顺序纪律**:节点按入参序截断(调用方 loadGraph 的行序),
|
|
90
|
+
* 悬挂边(端点之一被截掉/不可见)在截断前剔除——星图上不画悬空线。
|
|
91
|
+
*/
|
|
92
|
+
export declare function buildFullView(nodes: readonly GraphNode[], edges: readonly GraphEdge[], caps?: FullViewCaps): GraphFullView;
|