@furongjun1999/dsh-memory 0.5.0 → 0.5.1
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/README.md +552 -465
- package/docs/README.md +1 -0
- package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
- package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
- package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
- package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +19 -3
- package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
- package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
- package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
- package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
- package/lib/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/index.js +7 -1
- package/lib/lib/roleplay_web.js +530 -443
- package/lib/lib/token_store.d.ts +7 -1
- package/lib/lib/token_store.js +12 -3
- package/md_cg/audit.py +12 -1
- package/md_cg/backfill.py +16 -15
- package/md_cg/backfill_bucket_zh.py +35 -0
- package/md_cg/bench_e2e_judge.py +532 -0
- package/md_cg/bench_e2e_locomo_qa.py +368 -0
- package/md_cg/bench_e2e_qa.py +256 -0
- package/md_cg/branches.py +18 -2
- package/md_cg/ccgc.py +3 -2
- package/md_cg/chain.py +19 -4
- package/md_cg/consolidate.py +7 -6
- package/md_cg/crosscheck.py +4 -3
- package/md_cg/crypto.py +439 -437
- package/md_cg/datapath.py +395 -335
- package/md_cg/evidence.py +582 -580
- package/md_cg/export.py +3 -1
- package/md_cg/forgetting.py +2 -2
- package/md_cg/fsutil.py +48 -0
- package/md_cg/hotcache.py +255 -238
- package/md_cg/interop.py +161 -22
- package/md_cg/judgment_manifest.py +177 -0
- package/md_cg/links.py +655 -622
- package/md_cg/mcp_server.py +280 -29
- package/md_cg/mdcg.py +616 -125
- package/md_cg/mdcos.py +430 -66
- package/md_cg/mreview/govern.py +5 -4
- package/md_cg/postings.py +4 -2
- package/md_cg/readcache.py +76 -18
- package/md_cg/reconcile.py +228 -0
- package/md_cg/review_cli.py +170 -0
- package/md_cg/routing.py +28 -0
- package/md_cg/run_tests.py +211 -0
- package/md_cg/scrub.py +862 -852
- package/md_cg/security.py +385 -275
- package/md_cg/selfreport.py +3 -2
- package/md_cg/signer.py +565 -562
- package/md_cg/sources.py +3 -2
- package/md_cg/stg.py +6 -0
- package/md_cg/sustain.py +1168 -1138
- package/md_cg/test_access_hints.py +147 -0
- package/md_cg/test_branch_discard_tombstone.py +136 -0
- package/md_cg/test_branches.py +259 -249
- package/md_cg/test_chain_read_isolate.py +168 -0
- package/md_cg/test_datapath_device_name.py +203 -0
- package/md_cg/test_emit_negtail_cache.py +156 -0
- package/md_cg/test_en_pipeline.py +186 -166
- package/md_cg/test_govern_directread.py +421 -0
- package/md_cg/test_i32_hotcache_env_key.py +218 -0
- package/md_cg/test_identity_attribution.py +228 -147
- package/md_cg/test_index_durability.py +238 -224
- package/md_cg/test_interop.py +4 -2
- package/md_cg/test_interop_judgment.py +228 -0
- package/md_cg/test_issue39_utf8_stdio.py +273 -0
- package/md_cg/test_links_concurrent_write.py +188 -0
- package/md_cg/test_merge_upsert.py +168 -0
- package/md_cg/test_n123_derive_expiry_chain.py +205 -0
- package/md_cg/test_n130_verify_falsified_protect.py +185 -0
- package/md_cg/test_n131_merge_gate.py +205 -0
- package/md_cg/test_p1x_ref_root.py +160 -0
- package/md_cg/test_p27_docindex.py +774 -765
- package/md_cg/test_p2_mcp.py +3 -0
- package/md_cg/test_p32_backfill.py +304 -298
- package/md_cg/test_p39_verify_flow.py +90 -50
- package/md_cg/test_p47_session_view.py +60 -25
- package/md_cg/test_propose_tail_index.py +157 -0
- package/md_cg/test_read_scope_b27.py +277 -0
- package/md_cg/test_readcache_default_on.py +168 -0
- package/md_cg/test_readcache_precise_inval.py +270 -0
- package/md_cg/test_readcache_prodpath.py +55 -7
- package/md_cg/test_reconcile_v0.py +294 -0
- package/md_cg/test_retr_s1.py +6 -2
- package/md_cg/test_retr_s1b.py +67 -0
- package/md_cg/test_retr_s7.py +8 -0
- package/md_cg/test_retr_s9_entity_ctx.py +181 -175
- package/md_cg/test_retr_score_once.py +208 -0
- package/md_cg/test_review_onepass.py +170 -0
- package/md_cg/test_rrf_graph_seed_cache.py +154 -0
- package/md_cg/test_security_audit.py +155 -0
- package/md_cg/test_security_audit_b26.py +161 -0
- package/md_cg/test_security_audit_v21.py +250 -0
- package/md_cg/test_semantic_canonical.py +255 -241
- package/md_cg/test_session_isolation.py +168 -0
- package/md_cg/test_snapshot_autoclose.py +187 -0
- package/md_cg/test_tail_watermark_race.py +208 -0
- package/md_cg/test_tenant_env_override_warn.py +139 -0
- package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
- package/md_cg/test_v14_fixes.py +415 -397
- package/md_cg/test_verify_dirty_reconcile.py +157 -0
- package/md_cg/theory.py +276 -273
- package/md_cg/tokens.py +734 -677
- package/md_cg/units.py +3 -2
- package/md_cg/vision_evidence.py +4 -3
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
- package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
- package/md_cg/writepipe.py +554 -550
- package/package.json +6 -2
- package/src/bridge.ts +434 -401
- package/src/index.ts +526 -518
- package/src/lib/roleplay_web.ts +1019 -932
- package/src/lib/token_store.ts +202 -192
package/src/index.ts
CHANGED
|
@@ -1,518 +1,526 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @furongjun1999/dsh-memory —— 灵枢 DeepSeek Harness 插件
|
|
3
|
-
* (大脑唯一真源 = md_cg 认知图;aeis 能力库已下线)
|
|
4
|
-
*
|
|
5
|
-
* 把灵枢的时空记忆/知识飞轮/自我认知接入 DSH:
|
|
6
|
-
* - 记忆唯一真源:**md_cg 认知图(md 文档)**,插件侧唯一显式入口
|
|
7
|
-
* `src/lib/mdcg_client.ts`(每个方法 = 一条 MCP cg / stg 调用)
|
|
8
|
-
* - 工具桥接:Agent 可调用工具,经**唯一的大脑桥**(同一 md_cg 子进程)从
|
|
9
|
-
* **md_cg.mcp_server** 拉取(`MDCG_MCP_SURFACE=full`:`cg` / `stg` 基元 +
|
|
10
|
-
* `mdcg_*` 细粒度全家,再由 `config.tools` 选择表筛),注册为 `lingshu_<name>`
|
|
11
|
-
* - 自动记忆:DSH 对话经**认知图**自动沉淀(主动遗忘闸门去重 + 重要性 + 脱敏)
|
|
12
|
-
* - 使命分离:理论仓(CTP)只留方法论文档,工程代码全部在主仓 md_cg
|
|
13
|
-
*
|
|
14
|
-
* 用法(cordis.yml):
|
|
15
|
-
* ```yaml
|
|
16
|
-
* - id: lingshu-memory
|
|
17
|
-
* name: '@furongjun1999/dsh-memory'
|
|
18
|
-
* config:
|
|
19
|
-
* mdcg: # 记忆真源:认知图(md 文档)
|
|
20
|
-
* # root = 记忆写入路径(用户可改)。缺省 = 用户级状态根 data/mdcg
|
|
21
|
-
* # (~/.dsh/.dsh-memory/data/mdcg;**不在插件包内**——包内数据会被
|
|
22
|
-
* # pnpm 更新连目录一起删掉,见 src/lib/datapath.ts 头注)
|
|
23
|
-
* # 覆盖优先级:env MDCG_ROOT > <用户级状态根>/paths.json > 本项 > 默认
|
|
24
|
-
* # root: 'D:/somewhere/mdcg' # 例:把记忆库放到别处
|
|
25
|
-
* actor: 'dsh-memory'
|
|
26
|
-
* env: # 写入凭据:默认【关闭】,由你决定是否打开
|
|
27
|
-
* # 不配 → 只读 guest:读/召回/时间线可用,自动记忆/转录/落图不落盘(启动会告警)
|
|
28
|
-
* # 打开①推荐:先签发再引用(明文不进配置文件)
|
|
29
|
-
* # python -m md_cg.tokens issue --role designer --actor dsh-memory \
|
|
30
|
-
* # --clearance internal \
|
|
31
|
-
* # --ops-allow info,route,read,write,recent,goal,identity,whitebox,verify \
|
|
32
|
-
* # --layers-allow knowledge,contextual,structural,self,goals,unresolved,rejected
|
|
33
|
-
* # MDCG_TOKEN: !!js process.env.MDCG_TOKEN
|
|
34
|
-
* # 打开②最小权限:--role recorder(无 whitebox/identity/verify,不能写 self 层)
|
|
35
|
-
* # 打开③兼容旧部署(不推荐):MDCG_LEGACY_ENV_AUTH: '1'
|
|
36
|
-
* dbPath: '/path/to/legacy.db' # 遗留:仅角色数据目录推导用(不存记忆)
|
|
37
|
-
* capability: # 可选「身体」后端(默认关 → 单进程纯大脑)
|
|
38
|
-
* enabled: false
|
|
39
|
-
* args: ['-m', 'aeis.mcp.server']
|
|
40
|
-
* identity: '灵枢'
|
|
41
|
-
* memory:
|
|
42
|
-
* userMessage: true
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
47
|
-
import z from '@deepseek-ai/schemastery'
|
|
48
|
-
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
49
|
-
import { dirname, join } from 'node:path'
|
|
50
|
-
import { homedir } from 'node:os'
|
|
51
|
-
import { LingshuBridge, type McpCallResult } from './bridge.js'
|
|
52
|
-
import { registerLingshuTools, type ToolSelection } from './tools.js'
|
|
53
|
-
import { installMemoryHooks, type MemoryHooksOptions } from './hooks.js'
|
|
54
|
-
// LIB 本地库:角色扮演网页 / 互维维护 / 白箱 LLM 适配器统一收在 src/lib/。
|
|
55
|
-
import { installRoleplayWeb } from './lib/roleplay_web.js'
|
|
56
|
-
import { MdcgClient } from './lib/mdcg_client.js'
|
|
57
|
-
// 解释器解析(issue #19):默认值按平台取(Windows: python / 其它: python3),
|
|
58
|
-
// 可用 env MDCG_PYTHON 覆盖;此前写死 'python' 使 Linux/macOS 首装即挂。
|
|
59
|
-
import { defaultPython, selfCheckCommand } from './lib/python_path.js'
|
|
60
|
-
import { describeDataPaths, migrateLegacyData, mdcgRoot, repoRoot } from './lib/datapath.js'
|
|
61
|
-
// 写入凭据密钥环(首启引导):显式配置 → ~/.mdcg/token → 首启自动签发。
|
|
62
|
-
import { resolveToken, type TokenResolution } from './lib/token_store.js'
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 调试探针:记录 apply 失败到独立文件(绕过 DSH 日志系统)。
|
|
66
|
-
* 路径从用户家目录动态解析(issue #5,与 bridge.ts 同因同修)。
|
|
67
|
-
*/
|
|
68
|
-
const APPLY_ERROR_LOG = join(homedir(), '.dsh', 'logs', 'dsh-memory-apply-error.log')
|
|
69
|
-
let applyLogDirReady = false
|
|
70
|
-
function probeApplyError(err: unknown): void {
|
|
71
|
-
try {
|
|
72
|
-
if (!applyLogDirReady) {
|
|
73
|
-
mkdirSync(dirname(APPLY_ERROR_LOG), { recursive: true })
|
|
74
|
-
applyLogDirReady = true
|
|
75
|
-
}
|
|
76
|
-
appendFileSync(APPLY_ERROR_LOG, `[${new Date().toISOString()}] apply failed: ${String(err)}\n${(err as Error).stack ?? ''}\n`)
|
|
77
|
-
} catch { /* 探针失败忽略 */ }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const name = 'dsh-memory'
|
|
81
|
-
|
|
82
|
-
/** P1 修复(GPT 审查):MCP 调用的标准结果在 content[].text,
|
|
83
|
-
* 代码此前误读不存在的 .result 字段导致互维拿不到 judgment/best/复核文本 */
|
|
84
|
-
function mcpText(r: McpCallResult): string {
|
|
85
|
-
return (r.content ?? []).map((c) => c.text ?? '').join('\n')
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 本插件依赖的工具注册服务。
|
|
89
|
-
* P1 修复(GPT 审查):timer/webServer 是可选增强(互维/角色网页),此前强声明
|
|
90
|
-
* 导致最小 host(只有 tools)插件永远 PENDING 不激活。只强依赖 tools;
|
|
91
|
-
* timer/webServer 在 apply 内动态检测(存在则启用,缺失则告警跳过)。 */
|
|
92
|
-
export const inject = ['tools']
|
|
93
|
-
|
|
94
|
-
/** 插件配置。 */
|
|
95
|
-
export interface Config {
|
|
96
|
-
/** 工具命名空间前缀(默认 lingshu → lingshu_remember)。 */
|
|
97
|
-
serverName: string
|
|
98
|
-
/** Python 可执行文件(或 md_cg-mcp console script)。
|
|
99
|
-
* 缺省 = 平台感知(issue #19):Windows `python` / Linux·macOS `python3`,
|
|
100
|
-
* 可用 env `MDCG_PYTHON` 覆盖;显式配置本项优先级最高。 */
|
|
101
|
-
python: string
|
|
102
|
-
/** 传给 python 的参数(默认启动 md_cg MCP server)。 */
|
|
103
|
-
moduleArgs: string[]
|
|
104
|
-
/** ⚠️ 遗留项:SQLite 库文件路径(经 `AEIS_DB` 传给子进程),目录自动创建。
|
|
105
|
-
* 大脑真源已统一到 `mdcg`(认知图);此项仅供角色数据目录 roleDataDir
|
|
106
|
-
* (由它的父目录推导)使用,**不再存记忆**,md_cg server 也不读取它。 */
|
|
107
|
-
dbPath: string
|
|
108
|
-
/** 灵枢身份标识(写入记忆的自我模型)。 */
|
|
109
|
-
identity: string
|
|
110
|
-
/** 额外环境变量(BOCHA_API_KEY / AEIS_DESIGNER_KEY 等,可 !!js 注入)。 */
|
|
111
|
-
env: Record<string, string>
|
|
112
|
-
/** 暴露的工具集合:'core'(默认,仅 cg/stg 两基元)| 'brain'(完整认知面)
|
|
113
|
-
* | 'all' | 工具名数组。 */
|
|
114
|
-
tools: ToolSelection
|
|
115
|
-
/** 护栏宪章版本声明(接入即接受宪章约束,docs/guardrail-charter.md)。 */
|
|
116
|
-
charter: string
|
|
117
|
-
/** 自动记忆开关。 */
|
|
118
|
-
memory: MemoryHooksOptions
|
|
119
|
-
/** 单次工具调用超时(毫秒)。 */
|
|
120
|
-
toolCallTimeoutMs: number
|
|
121
|
-
/** 断线重连最大间隔(毫秒)。 */
|
|
122
|
-
maxRetryDelayMs: number
|
|
123
|
-
/** 启动失败是否让插件激活失败(否则告警后继续重试)。 */
|
|
124
|
-
failOnStartupError: boolean
|
|
125
|
-
/** 互维维护(Mutual Sustain Loop v1.1):心跳写戳 + 守护 A + 任务验证。 */
|
|
126
|
-
mutual: {
|
|
127
|
-
enabled: boolean
|
|
128
|
-
heartbeatMs: number
|
|
129
|
-
}
|
|
130
|
-
/** 认知图(md_cg)= 记忆唯一真源;AEIS 降为能力库。 */
|
|
131
|
-
mdcg: {
|
|
132
|
-
enabled: boolean
|
|
133
|
-
root: string
|
|
134
|
-
actor: string
|
|
135
|
-
tenant: string
|
|
136
|
-
clearance: string
|
|
137
|
-
}
|
|
138
|
-
/** 「身体」能力后端(可选):角色扮演生成等「身」的生成能力。
|
|
139
|
-
*
|
|
140
|
-
* 默认**不挂载** —— 主仓保持纯大脑单进程(只起 md_cg)。配置 enabled 后
|
|
141
|
-
* 额外起一个能力库子进程(如 AEIS),仅角色扮演生成使用;记忆真源仍是
|
|
142
|
-
* md_cg(大脑),本后端不存记忆。未挂载时角色生成接口 fail-closed。 */
|
|
143
|
-
capability: {
|
|
144
|
-
enabled: boolean
|
|
145
|
-
python: string
|
|
146
|
-
args: string[]
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export const Config: z<Config> = z.object({
|
|
151
|
-
serverName: z.string().default('lingshu'),
|
|
152
|
-
// issue #19:默认值按平台解析(Windows: python / 其它: python3;env MDCG_PYTHON 可覆盖)。
|
|
153
|
-
// 写死 'python' 时 Linux/macOS 首装即 spawn ENOENT(PEP 394 只有 python3),
|
|
154
|
-
// 且表现为「装好了但记忆永远为空」——最难定位的失效形态。
|
|
155
|
-
python: z.string().default(defaultPython()),
|
|
156
|
-
moduleArgs: z.array(String).default(['-m', 'md_cg.mcp_server']),
|
|
157
|
-
dbPath: z.string().default('data/lingshu.db'),
|
|
158
|
-
identity: z.string().default('灵枢'),
|
|
159
|
-
env: z.dict(String).default({}),
|
|
160
|
-
tools: z.union([z.const('core'), z.const('brain'), z.const('all'), z.array(String)]).default('core'),
|
|
161
|
-
/** 护栏宪章版本声明(接入即接受宪章约束,docs/guardrail-charter.md)。 */
|
|
162
|
-
charter: z.string().default('v2.0-published'),
|
|
163
|
-
memory: z
|
|
164
|
-
.object({
|
|
165
|
-
userMessage: z.boolean().default(true),
|
|
166
|
-
assistantMessage: z.boolean().default(false),
|
|
167
|
-
toolResult: z.boolean().default(false),
|
|
168
|
-
importance: z.number().default(0.6),
|
|
169
|
-
autoRecall: z.boolean().default(true),
|
|
170
|
-
autoRecallLimit: z.number().default(4),
|
|
171
|
-
desensitize: z.boolean().default(true),
|
|
172
|
-
})
|
|
173
|
-
.default({ userMessage: true, assistantMessage: false, toolResult: false, importance: 0.6, autoRecall: true, autoRecallLimit: 4, desensitize: true }),
|
|
174
|
-
toolCallTimeoutMs: z.number().default(60_000),
|
|
175
|
-
maxRetryDelayMs: z.number().default(30_000),
|
|
176
|
-
failOnStartupError: z.boolean().default(false),
|
|
177
|
-
/** 角色扮演入口按钮(issue #8):注入 dsh 首页右上角浮动入口;
|
|
178
|
-
* **默认关闭**:该功能尚未开放,右上角浮动按钮会遮挡 GUI 侧边栏;
|
|
179
|
-
* 显式设 true 才注入;/roleplay 页面仍可直接访问。按钮支持拖动,
|
|
180
|
-
* 位置记忆在浏览器 localStorage。 */
|
|
181
|
-
roleplayEntryButton: z.boolean().default(false),
|
|
182
|
-
/** 互维维护(v1.1):心跳 10min / 任务验证双通道。 */
|
|
183
|
-
mutual: z
|
|
184
|
-
.object({
|
|
185
|
-
enabled: z.boolean().default(false),
|
|
186
|
-
heartbeatMs: z.number().default(10 * 60 * 1000),
|
|
187
|
-
})
|
|
188
|
-
.default({ enabled: false, heartbeatMs: 10 * 60 * 1000 }),
|
|
189
|
-
/** 认知图(md_cg):记忆唯一真源。
|
|
190
|
-
* root 为**记忆写入路径**(用户可改)。缺省空串 = 未指定,按优先级解析:
|
|
191
|
-
* ① env MDCG_ROOT ② `<用户级状态根>/paths.json` 的 root
|
|
192
|
-
* (旧 `<插件仓>/data/paths.json` 兼容读)
|
|
193
|
-
* ③ 本项 ④ 默认 `<用户级状态根>/data/mdcg`
|
|
194
|
-
* 相对路径一律相对**插件仓根**解析——历史教训:相对 cwd 的相对路径随
|
|
195
|
-
* 宿主 cwd 漂移,cwd 落在别仓时记忆真源分裂成互不可见的两处。
|
|
196
|
-
* tenant/actor 决定私有内容加解密的身份:与 migrate_roleplay 的
|
|
197
|
-
* --tenant/--actor 必须一致,否则读不到已迁移节点。 */
|
|
198
|
-
mdcg: z
|
|
199
|
-
.object({
|
|
200
|
-
enabled: z.boolean().default(true),
|
|
201
|
-
root: z.string().default(''),
|
|
202
|
-
actor: z.string().default('dsh-memory'),
|
|
203
|
-
tenant: z.string().default('default'),
|
|
204
|
-
clearance: z.string().default('private'),
|
|
205
|
-
})
|
|
206
|
-
.default({ enabled: true, root: '', actor: 'dsh-memory', tenant: 'default', clearance: 'private' }),
|
|
207
|
-
/** 「身体」能力后端(可选):默认关闭 → 单进程纯大脑。
|
|
208
|
-
* 开启需同时给出 args(能力库启动参数),否则跳过并告警。 */
|
|
209
|
-
capability: z
|
|
210
|
-
.object({
|
|
211
|
-
enabled: z.boolean().default(false),
|
|
212
|
-
python: z.string().default(''),
|
|
213
|
-
args: z.array(String).default([]),
|
|
214
|
-
})
|
|
215
|
-
.default({ enabled: false, python: '', args: [] }),
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* 插件激活:启动灵枢子进程 → 注册工具 → 安装自动记忆钩子。
|
|
220
|
-
* 卸载时清理全部资源(effect 作用域内自动回收)。
|
|
221
|
-
*/
|
|
222
|
-
export async function apply(ctx: Context, config: Config): Promise<void> {
|
|
223
|
-
// 宪章宣告(接入即接受宪章约束——docs/guardrail-charter.md v2.0-published)
|
|
224
|
-
ctx.logger.info(
|
|
225
|
-
`dsh-memory: 灵枢插件激活(大脑模式)· 接受护栏宪章 ${config.charter} —— ` +
|
|
226
|
-
'接入即接受宪章约束(公开/可执行/可审计/设计者终裁)',
|
|
227
|
-
)
|
|
228
|
-
// ═══ 大脑进程:md_cg(主仓自带)——**唯一**子进程 ═══
|
|
229
|
-
// 工具面 / 记忆写入 / 互维核验 / 角色落图共用这一个桥。S4(2026-09-10)已删除
|
|
230
|
-
// 历史上并存的第二个 aeis 进程:双进程方案下两侧工具名互不交集(86 vs 33,
|
|
231
|
-
// 交集为 0),切换时会静默丢功能;收敛为单进程后由 MDCG_MCP_SURFACE=full
|
|
232
|
-
// 提供完整认知面。插件侧唯一显式入口:src/lib/mdcg_client.ts(一方法 = 一条 MCP 调用)。
|
|
233
|
-
let mdcg: MdcgClient | null = null
|
|
234
|
-
let brainReady = false
|
|
235
|
-
/** 写入凭据解析结果(首启引导;诊断与启动日志用)。 */
|
|
236
|
-
let cred: TokenResolution | null = null
|
|
237
|
-
/** 记忆真源解析结果(「新写入去哪」)——用户可改:env > paths.json > 配置 > 默认自身仓 data/。 */
|
|
238
|
-
const resolvedRoot = mdcgRoot(config.mdcg.root)
|
|
239
|
-
if (config.mdcg.enabled) {
|
|
240
|
-
// ── 写入凭据解析(首启引导):显式配置 → 密钥环 → 首启自动签发 ──
|
|
241
|
-
// 为什么需要:md_cg 无令牌即降级只读 guest,自动记忆 / 转录 / 角色落图
|
|
242
|
-
// **静默不落盘**——「装好了插件,但记忆永远是空的」是最难定位的失效形态。
|
|
243
|
-
// 解析顺序与「自动签发不降低实际安全强度」的论证见 lib/token_store.ts 文件头。
|
|
244
|
-
// legacy env 认证本就等价于「已显式授权」→ 不再签发,避免多签一枚无用令牌。
|
|
245
|
-
const legacyAuth = config.env.MDCG_LEGACY_ENV_AUTH ?? process.env.MDCG_LEGACY_ENV_AUTH
|
|
246
|
-
const resolution = resolveToken({
|
|
247
|
-
python: config.python,
|
|
248
|
-
configured: config.env.MDCG_TOKEN,
|
|
249
|
-
actor: config.mdcg.actor,
|
|
250
|
-
clearance: config.mdcg.clearance,
|
|
251
|
-
autoIssue: !legacyAuth,
|
|
252
|
-
})
|
|
253
|
-
cred = resolution
|
|
254
|
-
// 注入子进程 env:显式配置与密钥环都在此汇合成**唯一**的 MDCG_TOKEN。
|
|
255
|
-
const mdcgEnv: Record<string, string> = { ...config.env }
|
|
256
|
-
if (resolution.token) mdcgEnv.MDCG_TOKEN = resolution.token
|
|
257
|
-
mdcg = new MdcgClient({
|
|
258
|
-
python: config.python,
|
|
259
|
-
args: config.moduleArgs,
|
|
260
|
-
root: resolvedRoot,
|
|
261
|
-
actor: config.mdcg.actor,
|
|
262
|
-
tenant: config.mdcg.tenant,
|
|
263
|
-
clearance: config.mdcg.clearance,
|
|
264
|
-
identity: config.identity,
|
|
265
|
-
env: mdcgEnv,
|
|
266
|
-
timeoutMs: config.toolCallTimeoutMs,
|
|
267
|
-
maxRetryDelayMs: config.maxRetryDelayMs,
|
|
268
|
-
})
|
|
269
|
-
// 数据面迁出插件包(issue #18 相邻问题):旧版把运行时数据与路径配置写在包内
|
|
270
|
-
// `<pkg>/data`,pnpm 更新会连目录一起替换(实机实证:data/ 54 文件 → 0,
|
|
271
|
-
// 46 条记忆节点靠人工备份回填)。首启把「旧位置仍有货」的数据**复制**到用户级
|
|
272
|
-
// 数据根;必须在 mdcg.start() 之前——store 要在 Python 子进程接管前就位。
|
|
273
|
-
const migrated = migrateLegacyData()
|
|
274
|
-
if (migrated.ran) {
|
|
275
|
-
ctx.logger.info(
|
|
276
|
-
`dsh-memory: 数据面已迁出插件包(旧位置只复制未删除):`
|
|
277
|
-
+ `${migrated.from} → ${migrated.to}(${migrated.copied} 项`
|
|
278
|
-
+ `${migrated.failed.length ? `,失败 ${migrated.failed.join(',')}` : ''})`,
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
mdcg.start()
|
|
282
|
-
// 启动留痕:把「记忆真源在哪、由谁决定、路径是否存在」写进可审计日志,
|
|
283
|
-
// 避免再次出现「以为在记忆、其实写到了别仓」的静默分裂(历史事故)。
|
|
284
|
-
const dp = describeDataPaths(config.mdcg.root)
|
|
285
|
-
ctx.logger.info(
|
|
286
|
-
`dsh-memory: 记忆真源路径 = ${dp.mdcgRoot}(来源 ${dp.source},`
|
|
287
|
-
+ `状态根=${dp.stateRoot},dataRoot=${dp.dataRoot},`
|
|
288
|
-
+ `存在=${dp.mdcgRootExists ? '是' : '否(首次写入将创建)'},`
|
|
289
|
-
+ `用户可改:${dp.pathsFile})`,
|
|
290
|
-
)
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
ctx.logger.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
ctx.logger.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
+ '
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @furongjun1999/dsh-memory —— 灵枢 DeepSeek Harness 插件
|
|
3
|
+
* (大脑唯一真源 = md_cg 认知图;aeis 能力库已下线)
|
|
4
|
+
*
|
|
5
|
+
* 把灵枢的时空记忆/知识飞轮/自我认知接入 DSH:
|
|
6
|
+
* - 记忆唯一真源:**md_cg 认知图(md 文档)**,插件侧唯一显式入口
|
|
7
|
+
* `src/lib/mdcg_client.ts`(每个方法 = 一条 MCP cg / stg 调用)
|
|
8
|
+
* - 工具桥接:Agent 可调用工具,经**唯一的大脑桥**(同一 md_cg 子进程)从
|
|
9
|
+
* **md_cg.mcp_server** 拉取(`MDCG_MCP_SURFACE=full`:`cg` / `stg` 基元 +
|
|
10
|
+
* `mdcg_*` 细粒度全家,再由 `config.tools` 选择表筛),注册为 `lingshu_<name>`
|
|
11
|
+
* - 自动记忆:DSH 对话经**认知图**自动沉淀(主动遗忘闸门去重 + 重要性 + 脱敏)
|
|
12
|
+
* - 使命分离:理论仓(CTP)只留方法论文档,工程代码全部在主仓 md_cg
|
|
13
|
+
*
|
|
14
|
+
* 用法(cordis.yml):
|
|
15
|
+
* ```yaml
|
|
16
|
+
* - id: lingshu-memory
|
|
17
|
+
* name: '@furongjun1999/dsh-memory'
|
|
18
|
+
* config:
|
|
19
|
+
* mdcg: # 记忆真源:认知图(md 文档)
|
|
20
|
+
* # root = 记忆写入路径(用户可改)。缺省 = 用户级状态根 data/mdcg
|
|
21
|
+
* # (~/.dsh/.dsh-memory/data/mdcg;**不在插件包内**——包内数据会被
|
|
22
|
+
* # pnpm 更新连目录一起删掉,见 src/lib/datapath.ts 头注)
|
|
23
|
+
* # 覆盖优先级:env MDCG_ROOT > <用户级状态根>/paths.json > 本项 > 默认
|
|
24
|
+
* # root: 'D:/somewhere/mdcg' # 例:把记忆库放到别处
|
|
25
|
+
* actor: 'dsh-memory'
|
|
26
|
+
* env: # 写入凭据:默认【关闭】,由你决定是否打开
|
|
27
|
+
* # 不配 → 只读 guest:读/召回/时间线可用,自动记忆/转录/落图不落盘(启动会告警)
|
|
28
|
+
* # 打开①推荐:先签发再引用(明文不进配置文件)
|
|
29
|
+
* # python -m md_cg.tokens issue --role designer --actor dsh-memory \
|
|
30
|
+
* # --clearance internal \
|
|
31
|
+
* # --ops-allow info,route,read,write,recent,goal,identity,whitebox,verify \
|
|
32
|
+
* # --layers-allow knowledge,contextual,structural,self,goals,unresolved,rejected
|
|
33
|
+
* # MDCG_TOKEN: !!js process.env.MDCG_TOKEN
|
|
34
|
+
* # 打开②最小权限:--role recorder(无 whitebox/identity/verify,不能写 self 层)
|
|
35
|
+
* # 打开③兼容旧部署(不推荐):MDCG_LEGACY_ENV_AUTH: '1'
|
|
36
|
+
* dbPath: '/path/to/legacy.db' # 遗留:仅角色数据目录推导用(不存记忆)
|
|
37
|
+
* capability: # 可选「身体」后端(默认关 → 单进程纯大脑)
|
|
38
|
+
* enabled: false
|
|
39
|
+
* args: ['-m', 'aeis.mcp.server']
|
|
40
|
+
* identity: '灵枢'
|
|
41
|
+
* memory:
|
|
42
|
+
* userMessage: true
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
47
|
+
import z from '@deepseek-ai/schemastery'
|
|
48
|
+
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
49
|
+
import { dirname, join } from 'node:path'
|
|
50
|
+
import { homedir } from 'node:os'
|
|
51
|
+
import { LingshuBridge, type McpCallResult } from './bridge.js'
|
|
52
|
+
import { registerLingshuTools, type ToolSelection } from './tools.js'
|
|
53
|
+
import { installMemoryHooks, type MemoryHooksOptions } from './hooks.js'
|
|
54
|
+
// LIB 本地库:角色扮演网页 / 互维维护 / 白箱 LLM 适配器统一收在 src/lib/。
|
|
55
|
+
import { installRoleplayWeb } from './lib/roleplay_web.js'
|
|
56
|
+
import { MdcgClient } from './lib/mdcg_client.js'
|
|
57
|
+
// 解释器解析(issue #19):默认值按平台取(Windows: python / 其它: python3),
|
|
58
|
+
// 可用 env MDCG_PYTHON 覆盖;此前写死 'python' 使 Linux/macOS 首装即挂。
|
|
59
|
+
import { defaultPython, selfCheckCommand } from './lib/python_path.js'
|
|
60
|
+
import { describeDataPaths, migrateLegacyData, mdcgRoot, repoRoot } from './lib/datapath.js'
|
|
61
|
+
// 写入凭据密钥环(首启引导):显式配置 → ~/.mdcg/token → 首启自动签发。
|
|
62
|
+
import { auxRoot, defaultKeyringPath, resolveToken, type TokenResolution } from './lib/token_store.js'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 调试探针:记录 apply 失败到独立文件(绕过 DSH 日志系统)。
|
|
66
|
+
* 路径从用户家目录动态解析(issue #5,与 bridge.ts 同因同修)。
|
|
67
|
+
*/
|
|
68
|
+
const APPLY_ERROR_LOG = join(homedir(), '.dsh', 'logs', 'dsh-memory-apply-error.log')
|
|
69
|
+
let applyLogDirReady = false
|
|
70
|
+
function probeApplyError(err: unknown): void {
|
|
71
|
+
try {
|
|
72
|
+
if (!applyLogDirReady) {
|
|
73
|
+
mkdirSync(dirname(APPLY_ERROR_LOG), { recursive: true })
|
|
74
|
+
applyLogDirReady = true
|
|
75
|
+
}
|
|
76
|
+
appendFileSync(APPLY_ERROR_LOG, `[${new Date().toISOString()}] apply failed: ${String(err)}\n${(err as Error).stack ?? ''}\n`)
|
|
77
|
+
} catch { /* 探针失败忽略 */ }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const name = 'dsh-memory'
|
|
81
|
+
|
|
82
|
+
/** P1 修复(GPT 审查):MCP 调用的标准结果在 content[].text,
|
|
83
|
+
* 代码此前误读不存在的 .result 字段导致互维拿不到 judgment/best/复核文本 */
|
|
84
|
+
function mcpText(r: McpCallResult): string {
|
|
85
|
+
return (r.content ?? []).map((c) => c.text ?? '').join('\n')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 本插件依赖的工具注册服务。
|
|
89
|
+
* P1 修复(GPT 审查):timer/webServer 是可选增强(互维/角色网页),此前强声明
|
|
90
|
+
* 导致最小 host(只有 tools)插件永远 PENDING 不激活。只强依赖 tools;
|
|
91
|
+
* timer/webServer 在 apply 内动态检测(存在则启用,缺失则告警跳过)。 */
|
|
92
|
+
export const inject = ['tools']
|
|
93
|
+
|
|
94
|
+
/** 插件配置。 */
|
|
95
|
+
export interface Config {
|
|
96
|
+
/** 工具命名空间前缀(默认 lingshu → lingshu_remember)。 */
|
|
97
|
+
serverName: string
|
|
98
|
+
/** Python 可执行文件(或 md_cg-mcp console script)。
|
|
99
|
+
* 缺省 = 平台感知(issue #19):Windows `python` / Linux·macOS `python3`,
|
|
100
|
+
* 可用 env `MDCG_PYTHON` 覆盖;显式配置本项优先级最高。 */
|
|
101
|
+
python: string
|
|
102
|
+
/** 传给 python 的参数(默认启动 md_cg MCP server)。 */
|
|
103
|
+
moduleArgs: string[]
|
|
104
|
+
/** ⚠️ 遗留项:SQLite 库文件路径(经 `AEIS_DB` 传给子进程),目录自动创建。
|
|
105
|
+
* 大脑真源已统一到 `mdcg`(认知图);此项仅供角色数据目录 roleDataDir
|
|
106
|
+
* (由它的父目录推导)使用,**不再存记忆**,md_cg server 也不读取它。 */
|
|
107
|
+
dbPath: string
|
|
108
|
+
/** 灵枢身份标识(写入记忆的自我模型)。 */
|
|
109
|
+
identity: string
|
|
110
|
+
/** 额外环境变量(BOCHA_API_KEY / AEIS_DESIGNER_KEY 等,可 !!js 注入)。 */
|
|
111
|
+
env: Record<string, string>
|
|
112
|
+
/** 暴露的工具集合:'core'(默认,仅 cg/stg 两基元)| 'brain'(完整认知面)
|
|
113
|
+
* | 'all' | 工具名数组。 */
|
|
114
|
+
tools: ToolSelection
|
|
115
|
+
/** 护栏宪章版本声明(接入即接受宪章约束,docs/guardrail-charter.md)。 */
|
|
116
|
+
charter: string
|
|
117
|
+
/** 自动记忆开关。 */
|
|
118
|
+
memory: MemoryHooksOptions
|
|
119
|
+
/** 单次工具调用超时(毫秒)。 */
|
|
120
|
+
toolCallTimeoutMs: number
|
|
121
|
+
/** 断线重连最大间隔(毫秒)。 */
|
|
122
|
+
maxRetryDelayMs: number
|
|
123
|
+
/** 启动失败是否让插件激活失败(否则告警后继续重试)。 */
|
|
124
|
+
failOnStartupError: boolean
|
|
125
|
+
/** 互维维护(Mutual Sustain Loop v1.1):心跳写戳 + 守护 A + 任务验证。 */
|
|
126
|
+
mutual: {
|
|
127
|
+
enabled: boolean
|
|
128
|
+
heartbeatMs: number
|
|
129
|
+
}
|
|
130
|
+
/** 认知图(md_cg)= 记忆唯一真源;AEIS 降为能力库。 */
|
|
131
|
+
mdcg: {
|
|
132
|
+
enabled: boolean
|
|
133
|
+
root: string
|
|
134
|
+
actor: string
|
|
135
|
+
tenant: string
|
|
136
|
+
clearance: string
|
|
137
|
+
}
|
|
138
|
+
/** 「身体」能力后端(可选):角色扮演生成等「身」的生成能力。
|
|
139
|
+
*
|
|
140
|
+
* 默认**不挂载** —— 主仓保持纯大脑单进程(只起 md_cg)。配置 enabled 后
|
|
141
|
+
* 额外起一个能力库子进程(如 AEIS),仅角色扮演生成使用;记忆真源仍是
|
|
142
|
+
* md_cg(大脑),本后端不存记忆。未挂载时角色生成接口 fail-closed。 */
|
|
143
|
+
capability: {
|
|
144
|
+
enabled: boolean
|
|
145
|
+
python: string
|
|
146
|
+
args: string[]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const Config: z<Config> = z.object({
|
|
151
|
+
serverName: z.string().default('lingshu'),
|
|
152
|
+
// issue #19:默认值按平台解析(Windows: python / 其它: python3;env MDCG_PYTHON 可覆盖)。
|
|
153
|
+
// 写死 'python' 时 Linux/macOS 首装即 spawn ENOENT(PEP 394 只有 python3),
|
|
154
|
+
// 且表现为「装好了但记忆永远为空」——最难定位的失效形态。
|
|
155
|
+
python: z.string().default(defaultPython()),
|
|
156
|
+
moduleArgs: z.array(String).default(['-m', 'md_cg.mcp_server']),
|
|
157
|
+
dbPath: z.string().default('data/lingshu.db'),
|
|
158
|
+
identity: z.string().default('灵枢'),
|
|
159
|
+
env: z.dict(String).default({}),
|
|
160
|
+
tools: z.union([z.const('core'), z.const('brain'), z.const('all'), z.array(String)]).default('core'),
|
|
161
|
+
/** 护栏宪章版本声明(接入即接受宪章约束,docs/guardrail-charter.md)。 */
|
|
162
|
+
charter: z.string().default('v2.0-published'),
|
|
163
|
+
memory: z
|
|
164
|
+
.object({
|
|
165
|
+
userMessage: z.boolean().default(true),
|
|
166
|
+
assistantMessage: z.boolean().default(false),
|
|
167
|
+
toolResult: z.boolean().default(false),
|
|
168
|
+
importance: z.number().default(0.6),
|
|
169
|
+
autoRecall: z.boolean().default(true),
|
|
170
|
+
autoRecallLimit: z.number().default(4),
|
|
171
|
+
desensitize: z.boolean().default(true),
|
|
172
|
+
})
|
|
173
|
+
.default({ userMessage: true, assistantMessage: false, toolResult: false, importance: 0.6, autoRecall: true, autoRecallLimit: 4, desensitize: true }),
|
|
174
|
+
toolCallTimeoutMs: z.number().default(60_000),
|
|
175
|
+
maxRetryDelayMs: z.number().default(30_000),
|
|
176
|
+
failOnStartupError: z.boolean().default(false),
|
|
177
|
+
/** 角色扮演入口按钮(issue #8):注入 dsh 首页右上角浮动入口;
|
|
178
|
+
* **默认关闭**:该功能尚未开放,右上角浮动按钮会遮挡 GUI 侧边栏;
|
|
179
|
+
* 显式设 true 才注入;/roleplay 页面仍可直接访问。按钮支持拖动,
|
|
180
|
+
* 位置记忆在浏览器 localStorage。 */
|
|
181
|
+
roleplayEntryButton: z.boolean().default(false),
|
|
182
|
+
/** 互维维护(v1.1):心跳 10min / 任务验证双通道。 */
|
|
183
|
+
mutual: z
|
|
184
|
+
.object({
|
|
185
|
+
enabled: z.boolean().default(false),
|
|
186
|
+
heartbeatMs: z.number().default(10 * 60 * 1000),
|
|
187
|
+
})
|
|
188
|
+
.default({ enabled: false, heartbeatMs: 10 * 60 * 1000 }),
|
|
189
|
+
/** 认知图(md_cg):记忆唯一真源。
|
|
190
|
+
* root 为**记忆写入路径**(用户可改)。缺省空串 = 未指定,按优先级解析:
|
|
191
|
+
* ① env MDCG_ROOT ② `<用户级状态根>/paths.json` 的 root
|
|
192
|
+
* (旧 `<插件仓>/data/paths.json` 兼容读)
|
|
193
|
+
* ③ 本项 ④ 默认 `<用户级状态根>/data/mdcg`
|
|
194
|
+
* 相对路径一律相对**插件仓根**解析——历史教训:相对 cwd 的相对路径随
|
|
195
|
+
* 宿主 cwd 漂移,cwd 落在别仓时记忆真源分裂成互不可见的两处。
|
|
196
|
+
* tenant/actor 决定私有内容加解密的身份:与 migrate_roleplay 的
|
|
197
|
+
* --tenant/--actor 必须一致,否则读不到已迁移节点。 */
|
|
198
|
+
mdcg: z
|
|
199
|
+
.object({
|
|
200
|
+
enabled: z.boolean().default(true),
|
|
201
|
+
root: z.string().default(''),
|
|
202
|
+
actor: z.string().default('dsh-memory'),
|
|
203
|
+
tenant: z.string().default('default'),
|
|
204
|
+
clearance: z.string().default('private'),
|
|
205
|
+
})
|
|
206
|
+
.default({ enabled: true, root: '', actor: 'dsh-memory', tenant: 'default', clearance: 'private' }),
|
|
207
|
+
/** 「身体」能力后端(可选):默认关闭 → 单进程纯大脑。
|
|
208
|
+
* 开启需同时给出 args(能力库启动参数),否则跳过并告警。 */
|
|
209
|
+
capability: z
|
|
210
|
+
.object({
|
|
211
|
+
enabled: z.boolean().default(false),
|
|
212
|
+
python: z.string().default(''),
|
|
213
|
+
args: z.array(String).default([]),
|
|
214
|
+
})
|
|
215
|
+
.default({ enabled: false, python: '', args: [] }),
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 插件激活:启动灵枢子进程 → 注册工具 → 安装自动记忆钩子。
|
|
220
|
+
* 卸载时清理全部资源(effect 作用域内自动回收)。
|
|
221
|
+
*/
|
|
222
|
+
export async function apply(ctx: Context, config: Config): Promise<void> {
|
|
223
|
+
// 宪章宣告(接入即接受宪章约束——docs/guardrail-charter.md v2.0-published)
|
|
224
|
+
ctx.logger.info(
|
|
225
|
+
`dsh-memory: 灵枢插件激活(大脑模式)· 接受护栏宪章 ${config.charter} —— ` +
|
|
226
|
+
'接入即接受宪章约束(公开/可执行/可审计/设计者终裁)',
|
|
227
|
+
)
|
|
228
|
+
// ═══ 大脑进程:md_cg(主仓自带)——**唯一**子进程 ═══
|
|
229
|
+
// 工具面 / 记忆写入 / 互维核验 / 角色落图共用这一个桥。S4(2026-09-10)已删除
|
|
230
|
+
// 历史上并存的第二个 aeis 进程:双进程方案下两侧工具名互不交集(86 vs 33,
|
|
231
|
+
// 交集为 0),切换时会静默丢功能;收敛为单进程后由 MDCG_MCP_SURFACE=full
|
|
232
|
+
// 提供完整认知面。插件侧唯一显式入口:src/lib/mdcg_client.ts(一方法 = 一条 MCP 调用)。
|
|
233
|
+
let mdcg: MdcgClient | null = null
|
|
234
|
+
let brainReady = false
|
|
235
|
+
/** 写入凭据解析结果(首启引导;诊断与启动日志用)。 */
|
|
236
|
+
let cred: TokenResolution | null = null
|
|
237
|
+
/** 记忆真源解析结果(「新写入去哪」)——用户可改:env > paths.json > 配置 > 默认自身仓 data/。 */
|
|
238
|
+
const resolvedRoot = mdcgRoot(config.mdcg.root)
|
|
239
|
+
if (config.mdcg.enabled) {
|
|
240
|
+
// ── 写入凭据解析(首启引导):显式配置 → 密钥环 → 首启自动签发 ──
|
|
241
|
+
// 为什么需要:md_cg 无令牌即降级只读 guest,自动记忆 / 转录 / 角色落图
|
|
242
|
+
// **静默不落盘**——「装好了插件,但记忆永远是空的」是最难定位的失效形态。
|
|
243
|
+
// 解析顺序与「自动签发不降低实际安全强度」的论证见 lib/token_store.ts 文件头。
|
|
244
|
+
// legacy env 认证本就等价于「已显式授权」→ 不再签发,避免多签一枚无用令牌。
|
|
245
|
+
const legacyAuth = config.env.MDCG_LEGACY_ENV_AUTH ?? process.env.MDCG_LEGACY_ENV_AUTH
|
|
246
|
+
const resolution = resolveToken({
|
|
247
|
+
python: config.python,
|
|
248
|
+
configured: config.env.MDCG_TOKEN,
|
|
249
|
+
actor: config.mdcg.actor,
|
|
250
|
+
clearance: config.mdcg.clearance,
|
|
251
|
+
autoIssue: !legacyAuth,
|
|
252
|
+
})
|
|
253
|
+
cred = resolution
|
|
254
|
+
// 注入子进程 env:显式配置与密钥环都在此汇合成**唯一**的 MDCG_TOKEN。
|
|
255
|
+
const mdcgEnv: Record<string, string> = { ...config.env }
|
|
256
|
+
if (resolution.token) mdcgEnv.MDCG_TOKEN = resolution.token
|
|
257
|
+
mdcg = new MdcgClient({
|
|
258
|
+
python: config.python,
|
|
259
|
+
args: config.moduleArgs,
|
|
260
|
+
root: resolvedRoot,
|
|
261
|
+
actor: config.mdcg.actor,
|
|
262
|
+
tenant: config.mdcg.tenant,
|
|
263
|
+
clearance: config.mdcg.clearance,
|
|
264
|
+
identity: config.identity,
|
|
265
|
+
env: mdcgEnv,
|
|
266
|
+
timeoutMs: config.toolCallTimeoutMs,
|
|
267
|
+
maxRetryDelayMs: config.maxRetryDelayMs,
|
|
268
|
+
})
|
|
269
|
+
// 数据面迁出插件包(issue #18 相邻问题):旧版把运行时数据与路径配置写在包内
|
|
270
|
+
// `<pkg>/data`,pnpm 更新会连目录一起替换(实机实证:data/ 54 文件 → 0,
|
|
271
|
+
// 46 条记忆节点靠人工备份回填)。首启把「旧位置仍有货」的数据**复制**到用户级
|
|
272
|
+
// 数据根;必须在 mdcg.start() 之前——store 要在 Python 子进程接管前就位。
|
|
273
|
+
const migrated = migrateLegacyData()
|
|
274
|
+
if (migrated.ran) {
|
|
275
|
+
ctx.logger.info(
|
|
276
|
+
`dsh-memory: 数据面已迁出插件包(旧位置只复制未删除):`
|
|
277
|
+
+ `${migrated.from} → ${migrated.to}(${migrated.copied} 项`
|
|
278
|
+
+ `${migrated.failed.length ? `,失败 ${migrated.failed.join(',')}` : ''})`,
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
mdcg.start()
|
|
282
|
+
// 启动留痕:把「记忆真源在哪、由谁决定、路径是否存在」写进可审计日志,
|
|
283
|
+
// 避免再次出现「以为在记忆、其实写到了别仓」的静默分裂(历史事故)。
|
|
284
|
+
const dp = describeDataPaths(config.mdcg.root)
|
|
285
|
+
ctx.logger.info(
|
|
286
|
+
`dsh-memory: 记忆真源路径 = ${dp.mdcgRoot}(来源 ${dp.source},`
|
|
287
|
+
+ `状态根=${dp.stateRoot},dataRoot=${dp.dataRoot},`
|
|
288
|
+
+ `存在=${dp.mdcgRootExists ? '是' : '否(首次写入将创建)'},`
|
|
289
|
+
+ `用户可改:${dp.pathsFile})`,
|
|
290
|
+
)
|
|
291
|
+
// 辅助根(密钥/令牌/信任/心跳)与记忆真源**有意分离**:身份不随认知图迁移。
|
|
292
|
+
// 但两者分居两处是历史事故的温床(「以为在同一处」),故一并留痕;
|
|
293
|
+
// 想合并/搬走:设 MDCG_AUX_ROOT(md_cg.datapath.aux_root() 同口径)。
|
|
294
|
+
ctx.logger.info(
|
|
295
|
+
`dsh-memory: 身份/凭据根(aux)= ${auxRoot()}`
|
|
296
|
+
+ `(密钥环 ${cred?.keyringPath ?? defaultKeyringPath()};`
|
|
297
|
+
+ `与记忆真源分离,可用 MDCG_AUX_ROOT 覆盖)`,
|
|
298
|
+
)
|
|
299
|
+
// 包管理器装的插件 + 路径配置还在旧包内位置:pnpm 下次更新会把该文件连目录一起
|
|
300
|
+
// 删除,用户配置随之丢失(回落默认根 → 表现为「记忆不见了」)。只在真的会被删的
|
|
301
|
+
// 布局下提醒(开发用的 git clone 不含 node_modules 段 → 不打扰)。
|
|
302
|
+
const pnpmManaged = repoRoot().split(/[\\/]/).includes('node_modules')
|
|
303
|
+
if (pnpmManaged && String(dp.pathsFileSource) === 'legacy') {
|
|
304
|
+
ctx.logger.warn(
|
|
305
|
+
`dsh-memory: 路径配置仍在插件包内(${dp.pathsFile})——pnpm 更新该包会连目录`
|
|
306
|
+
+ `一起删除。请复制到 ${String(dp.stateRoot)}/paths.json 后重启`
|
|
307
|
+
+ `(数据面已自动复制到 ${String(dp.dataRoot)})。`,
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
brainReady = await mdcg.waitReady()
|
|
311
|
+
if (brainReady) {
|
|
312
|
+
ctx.logger.info(`dsh-memory: 认知图已就绪(MDCG_ROOT=${resolvedRoot})`)
|
|
313
|
+
} else {
|
|
314
|
+
ctx.logger.warn(
|
|
315
|
+
`dsh-memory: 认知图未就绪(MDCG_ROOT=${resolvedRoot}),`
|
|
316
|
+
+ '核验按白箱纪律 fail-closed;桥将后台重连并在就绪后补注册工具。',
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
// 写入凭据检查(fail-closed):凭据来源已由 token_store 解析(见上)。
|
|
320
|
+
// ① 显式配置 / 密钥环 / 首启自动签发 → 可写;② 都不行 → 只读 guest,必须告警。
|
|
321
|
+
// 告警仍是硬要求:guest 下读 / 召回 / 时间线照常,但自动记忆、转录、
|
|
322
|
+
// 角色落图**不落盘**——不吭声就是「看起来在记忆、其实没落盘」。
|
|
323
|
+
const authEnv: Record<string, string | undefined> = { ...process.env, ...config.env }
|
|
324
|
+
if (!cred.token && !authEnv.MDCG_LEGACY_ENV_AUTH) {
|
|
325
|
+
ctx.logger.warn(
|
|
326
|
+
'dsh-memory: 认知图写入凭据不可用,以只读 guest 运行——'
|
|
327
|
+
+ '读 / 召回 / 时间线可用,但自动记忆、转录、角色落图不会落盘。'
|
|
328
|
+
+ (cred.note ? `原因:${cred.note}。` : '')
|
|
329
|
+
+ '要打开:签发令牌 MDCG_TOKEN 经 config.env 注入(见 dsh/cordis.yml.example),'
|
|
330
|
+
+ '或删除密钥环后重启以重新自动签发。')
|
|
331
|
+
} else if (cred.source === 'issued') {
|
|
332
|
+
// 首启引导:自动签发是**改变用户可写权限**的动作,必须留痕(含关闭/吊销方式)。
|
|
333
|
+
ctx.logger.info(`dsh-memory: ${cred.note}`)
|
|
334
|
+
} else {
|
|
335
|
+
ctx.logger.info(
|
|
336
|
+
`dsh-memory: 认知图写入凭据来源=${cred.source}(密钥环 ${cred.keyringPath})`
|
|
337
|
+
+ '——自动记忆 / 转录 / 角色落图已开启')
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
ctx.logger.warn('dsh-memory: mdcg.enabled=false —— 大脑(认知图)未启动,工具与自动记忆不可用')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** 大脑桥:工具面 + 互维 + 角色落图共用的唯一 md_cg 通道(mdcg 关闭时为 null)。 */
|
|
344
|
+
const bridge = mdcg?.bridge ?? null
|
|
345
|
+
if (!brainReady) {
|
|
346
|
+
// issue #19:自检命令按当前解释器给出(Windows python / 其它 python3),
|
|
347
|
+
// 照抄报错里的命令在用户平台上必须真的可执行。
|
|
348
|
+
const message = `灵枢大脑(md_cg)未就绪(检查 ${config.python} 是否可用、md_cg 是否可导入:${selfCheckCommand(config.python)})`
|
|
349
|
+
if (config.failOnStartupError && config.mdcg.enabled) {
|
|
350
|
+
// P1 修复(GPT 审查):启动失败抛错前必须 dispose——此前 throw 在 try 之前,
|
|
351
|
+
// 桥接对象泄漏 + 后台重试计时器继续跑
|
|
352
|
+
mdcg?.dispose()
|
|
353
|
+
throw new Error(message)
|
|
354
|
+
}
|
|
355
|
+
ctx.logger.warn(`dsh-memory: ${message},继续后台重试`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ═══ 「身体」能力后端(可选)—— 角色扮演生成等「身」的生成能力 ═══
|
|
359
|
+
// 默认**不挂载**:主仓保持纯大脑单进程(只起 md_cg)。开启后额外起一个能力库
|
|
360
|
+
// 子进程(如 AEIS),**仅**角色扮演生成使用;记忆真源仍是 md_cg(大脑)。
|
|
361
|
+
// 未挂载时角色生成接口 fail-closed(返回明确原因,不编造回复)。
|
|
362
|
+
let capability: LingshuBridge | null = null
|
|
363
|
+
if (config.capability.enabled) {
|
|
364
|
+
if (!config.capability.args.length) {
|
|
365
|
+
ctx.logger.warn('dsh-memory: capability.enabled=true 但未配置 capability.args,跳过身体能力后端')
|
|
366
|
+
} else {
|
|
367
|
+
capability = new LingshuBridge({
|
|
368
|
+
python: config.capability.python || config.python,
|
|
369
|
+
args: config.capability.args,
|
|
370
|
+
env: {
|
|
371
|
+
// 能力库的旧版数据目录约定(AEIS_* 为遗留名,md_cg 不读)。
|
|
372
|
+
AEIS_DB: config.dbPath,
|
|
373
|
+
AEIS_IDENTITY: config.identity,
|
|
374
|
+
...config.env,
|
|
375
|
+
},
|
|
376
|
+
timeoutMs: config.toolCallTimeoutMs,
|
|
377
|
+
maxRetryDelayMs: config.maxRetryDelayMs,
|
|
378
|
+
})
|
|
379
|
+
capability.start()
|
|
380
|
+
if (!(await capability.waitReady())) {
|
|
381
|
+
ctx.logger.warn('dsh-memory: 身体能力后端未就绪,角色生成接口将 fail-closed(不编造回复)')
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const disposers: Array<() => void> = []
|
|
387
|
+
let toolsPoll: NodeJS.Timeout | null = null
|
|
388
|
+
try {
|
|
389
|
+
// 工具注册:初始就绪立即注册;若启动时未就绪(python 暂不可用等
|
|
390
|
+
// 竞态),桥重连成功后自动补注册——修复"工具永久缺失"问题。
|
|
391
|
+
let toolsRegistered = false
|
|
392
|
+
const tryRegister = async () => {
|
|
393
|
+
if (toolsRegistered || !bridge?.isReady()) return
|
|
394
|
+
try {
|
|
395
|
+
const dispose = await registerLingshuTools(ctx, bridge, {
|
|
396
|
+
selection: config.tools,
|
|
397
|
+
toolPrefix: `${config.serverName}_`,
|
|
398
|
+
})
|
|
399
|
+
disposers.push(dispose)
|
|
400
|
+
toolsRegistered = true
|
|
401
|
+
// P1 修复(GPT 审查):注册成功后清除轮询——此前 setInterval 永久保留
|
|
402
|
+
if (toolsPoll) {
|
|
403
|
+
clearInterval(toolsPoll)
|
|
404
|
+
toolsPoll = null
|
|
405
|
+
}
|
|
406
|
+
ctx.logger.info('dsh-memory: 灵枢工具已注册(就绪后补注册)')
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
ctx.logger.warn(`dsh-memory: 工具注册失败,稍后重试: ${String(err)}`)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (brainReady) await tryRegister()
|
|
413
|
+
if (!toolsRegistered) {
|
|
414
|
+
toolsPoll = setInterval(() => { void tryRegister() }, 2000)
|
|
415
|
+
disposers.push(() => { if (toolsPoll) clearInterval(toolsPoll) })
|
|
416
|
+
}
|
|
417
|
+
// 自动记忆:沉淀进 md_cg 认知图(记忆唯一真源)。mdcg=null 时整体停用。
|
|
418
|
+
installMemoryHooks(ctx, mdcg, config.memory)
|
|
419
|
+
// 角色扮演网页(同源挂载 /roleplay):生成能力走可选「身体」后端(capability),
|
|
420
|
+
// 转录/历史/翻译/落图走大脑(mdcg)。capability=null 时生成接口 fail-closed。
|
|
421
|
+
await installRoleplayWeb(ctx, capability, config, disposers, mdcg)
|
|
422
|
+
|
|
423
|
+
// 白箱 LLM provider 已下线(2026-09-10):功能尚不完善,不再注册
|
|
424
|
+
// 'lingshu-whitebox' provider。适配器保留为 LIB 本地库
|
|
425
|
+
// (src/lib/whitebox_llm.ts),白箱能力改由 md_cg 显式调用验证:
|
|
426
|
+
// MCP cg(op=whitebox, action=verify_encoding|verify_existing)
|
|
427
|
+
// 见 docs/功能调用映射表_v0.1.md。
|
|
428
|
+
|
|
429
|
+
// 互维维护(v1.1):心跳写戳 + 守护 A + 任务验证双通道
|
|
430
|
+
if (config.mutual.enabled) {
|
|
431
|
+
// P1 修复(GPT 审查):timer 是可选服务——缺失时告警跳过互维,
|
|
432
|
+
// 不让插件因互维而阻塞(inject 已不再强声明 timer)。
|
|
433
|
+
// 注意:不能直接读 ctx.timer——Cordis 未声明 inject 的属性访问会抛
|
|
434
|
+
// "cannot get property without inject"(getter 严格);ctx.get() 安全。
|
|
435
|
+
const hasTimer = ctx.get('timer') !== undefined
|
|
436
|
+
if (!hasTimer) {
|
|
437
|
+
ctx.logger.warn('dsh-memory: timer 服务不可用,跳过互维维护(mutual.enabled=true 但无 timer)')
|
|
438
|
+
} else {
|
|
439
|
+
const { installMutualMaintenance } = await import('./lib/mutual.js')
|
|
440
|
+
// 双通道 hooks:白箱 base_verify(mdcg.verifyClaim 走认知图)+ 宿主 LLM 复核
|
|
441
|
+
installMutualMaintenance(
|
|
442
|
+
ctx as never,
|
|
443
|
+
{ heartbeatMs: config.mutual.heartbeatMs },
|
|
444
|
+
{
|
|
445
|
+
// memory 通道 → 认知图(md_cg = 唯一真源)。
|
|
446
|
+
// 显式调用:MdcgClient.verifyClaim → cg(op=read) + 依据强度判定。
|
|
447
|
+
// 回退通道已下线(aeis 能力库随进程剥离;md_cg 是唯一大脑):
|
|
448
|
+
// 认知图不可用时按白箱纪律 fail-closed,不编造判定。
|
|
449
|
+
verify: async (claim: string) => {
|
|
450
|
+
if (mdcg?.isReady()) {
|
|
451
|
+
try {
|
|
452
|
+
return await mdcg.verifyClaim(claim)
|
|
453
|
+
} catch (err) {
|
|
454
|
+
ctx.logger.warn(`dsh-memory: 认知图核验失败,按 fail-closed 处理:${String(err)}`)
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
ctx.logger.warn('dsh-memory: 认知图未就绪,核验按 fail-closed 处理')
|
|
458
|
+
}
|
|
459
|
+
return { judgment: '无法核验', best: '', d_norm: -1, record_id: '' }
|
|
460
|
+
},
|
|
461
|
+
review: async (claim: string, w) => {
|
|
462
|
+
// 复核通道改走**宿主 LLM**(aeis 的 think 工具随进程下线)。
|
|
463
|
+
// 取 llm 服务:Cordis 未 inject 的属性访问会抛,ctx.get 安全返回 undefined。
|
|
464
|
+
const llm = ctx.get('llm') as
|
|
465
|
+
| {
|
|
466
|
+
stream?: (o: Record<string, unknown>) => AsyncIterable<{ type?: string; text?: string }>
|
|
467
|
+
listProviders?: () => Array<{ id?: string; model?: string }>
|
|
468
|
+
}
|
|
469
|
+
| undefined
|
|
470
|
+
const prov = llm?.listProviders?.()
|
|
471
|
+
?.find((p) => p?.id && p.id !== 'lingshu-whitebox')
|
|
472
|
+
if (!llm?.stream || !prov?.id) {
|
|
473
|
+
ctx.logger.warn('dsh-memory: 宿主 LLM 不可用,复核按 fail-closed 处理(不编造结论)')
|
|
474
|
+
return { conclusion: '不同意', reason: '复核通道不可用(宿主 LLM 未就绪)' }
|
|
475
|
+
}
|
|
476
|
+
let text = ''
|
|
477
|
+
try {
|
|
478
|
+
const stream = llm.stream({
|
|
479
|
+
provider: prov.id,
|
|
480
|
+
model: prov.model ?? prov.id,
|
|
481
|
+
messages: [{
|
|
482
|
+
role: 'user',
|
|
483
|
+
content: [{
|
|
484
|
+
type: 'text',
|
|
485
|
+
text: `复核以下主张(白箱判定已给出,请独立评估是否同意):${claim.slice(0, 200)}。白箱判定:${w.judgment},best=${w.best}。只输出 同意/质疑/不同意 + 一句话理由`,
|
|
486
|
+
}],
|
|
487
|
+
}],
|
|
488
|
+
maxTokens: 256,
|
|
489
|
+
})
|
|
490
|
+
for await (const chunk of stream) {
|
|
491
|
+
if (typeof chunk?.text === 'string' && chunk.type !== 'reasoning-delta') text += chunk.text
|
|
492
|
+
}
|
|
493
|
+
} catch (err) {
|
|
494
|
+
ctx.logger.warn(`dsh-memory: 复核 LLM 调用失败,按 fail-closed 处理:${String(err)}`)
|
|
495
|
+
return { conclusion: '不同意', reason: '复核调用失败(fail-closed)' }
|
|
496
|
+
}
|
|
497
|
+
// 结论解析:必须先查「不同意/不通过」再「质疑」再「同意」——「不同意」
|
|
498
|
+
// 含子串「同意」,颠倒顺序会把不同意误判为同意(P1 修复,勿回退)。
|
|
499
|
+
const conclusion = text.includes('不同意') || text.includes('不通过')
|
|
500
|
+
? '不同意'
|
|
501
|
+
: text.includes('质疑') ? '质疑'
|
|
502
|
+
: text.includes('同意') ? '同意' : '不同意'
|
|
503
|
+
return { conclusion, reason: text.slice(0, 120) }
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
)
|
|
507
|
+
ctx.logger.info('dsh-memory: 互维维护已启用(心跳 10min + 守护 A + 任务验证双通道)')
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} catch (err) {
|
|
511
|
+
// 探针:记录 apply 失败的具体错误(定位插件加载失败根因)
|
|
512
|
+
probeApplyError(err)
|
|
513
|
+
mdcg?.dispose()
|
|
514
|
+
capability?.dispose()
|
|
515
|
+
throw err
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
ctx.effect(() => {
|
|
519
|
+
return () => {
|
|
520
|
+
for (const dispose of disposers) dispose()
|
|
521
|
+
mdcg?.dispose()
|
|
522
|
+
capability?.dispose()
|
|
523
|
+
ctx.logger.info('dsh-memory: 已卸载(工具已注销,md_cg 大脑 / 可选身体后端进程已退出)')
|
|
524
|
+
}
|
|
525
|
+
}, 'dsh-memory')
|
|
526
|
+
}
|