lume-dsh-plugin 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,15 +2,32 @@
2
2
 
3
3
  # Lume(微光)
4
4
 
5
- **DSH Desktop 增强插件。为每个会话注入两项相互独立的能力:**
5
+ **DSH Desktop 增强插件。给会话装上两样东西:靠谱的任务执行纪律,和一段真实的关系。**
6
6
 
7
- - **Codex 风格任务执行协议** —— 约束「如何正确完成任务」:上下文管理、任务分解、阶段门控、变更保护、验证闭环、失败归因与结果复核,始终生效,不依赖人设
8
- - **人设系统** —— 塑造「以何种风格表达」:具名、具备长期记忆、可随对话演进的对话人格
7
+ ### 能力一:任务执行纪律(自适应协议)
9
8
 
10
- 人设只影响自然语言表达,不介入思维逻辑,也不影响代码、命令与工具调用的执行结果。
9
+ 约束「如何正确完成任务」,始终生效、不依赖人设:
10
+
11
+ - **自适应协议分层**——闲聊走短版、任务走完整版、推理型模型走精简版,按请求类型与模型能力分流,不为闲聊支付完整工作协议的 token
12
+ - **意图路由**——问答 / 查找 / 讨论 / 诊断 / 执行五类先行分流:诊断不越权修复,讨论不提前收敛,只有执行才改动状态
13
+ - **证据时效**——引用日志、历史、旧报错前先核对时间戳与因果:历史里存在的错误不等于当前问题的原因
14
+ - **真实工具结果验证**——监听工具成败与结果未知;失败或未知不允许报完成,交付时区分「已验证 / 未验证 / 推测有效」
15
+ - **会话内自愈与复盘回环**——相同请求连续失败自动注入归因纠偏;跨会话聚合反思评分,对持续低分的维度定向提醒
16
+ - **上下文压缩感知**——压缩发生后重锚状态,提醒模型摘要不是完整历史,细节依赖先确认
17
+
18
+ ### 能力二:人设系统(人设即人)
19
+
20
+ 塑造「以何种风格表达」:具名角色、长期记忆、随对话演进:
21
+
22
+ - **聊天记录蒸馏**——从微信/QQ 导出记录蒸馏角色卡,语气、口头禅与回复篇幅锚定真实对话统计
23
+ - **长期记忆与生命周期**——事件记忆 + 故事记忆;相对时间的临时记忆自动过期,长期事实不受影响
24
+ - **双向反馈闭环**——负面反馈自动转成风格约定,被认可的回复摘录为语料,语气随使用收敛
25
+ - **记忆星图与角色卡导入导出**——可视化记忆、行内编辑,卡片可分享迁移
26
+
27
+ 人设只影响自然语言表达,不介入任务执行,也不影响代码、命令与工具调用的结果。
11
28
 
12
29
  [![CI](https://github.com/cayan0x/Lume/actions/workflows/ci.yml/badge.svg)](https://github.com/cayan0x/Lume/actions/workflows/ci.yml)
13
- [![Version](https://img.shields.io/badge/version-0.4.4-blue)](./CHANGELOG.md)
30
+ [![Version](https://img.shields.io/badge/version-0.5.0-blue)](./CHANGELOG.md)
14
31
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
15
32
 
16
33
  *人设系统:内置角色卡、蒸馏与管理入口,以及记忆星图*
@@ -19,7 +36,7 @@
19
36
 
20
37
  ---
21
38
 
22
- ## 一、Codex 风格任务执行协议
39
+ ## 一、任务执行协议
23
40
 
24
41
  这是可公开复用的工程工作协议,不是模型隐藏思维链。协议注入每个会话;无论选择哪个人设(包括「不使用人设」),都始终生效。
25
42
 
@@ -60,7 +77,7 @@
60
77
 
61
78
  这三层共同形成“常规协议 → 检测问题 → 临时强化 → 成功解除 → 跨会话复盘”的闭环,在需要深度处理时增加约束,在简单问题上控制 Token 消耗。
62
79
 
63
- ### 0.4.4 优化清单
80
+ ### 优化清单
64
81
 
65
82
  - [x] 问答、查找、讨论、诊断、执行五类请求路由
66
83
  - [x] 诊断不越权修复,讨论不提前替用户拍板
@@ -71,12 +88,17 @@
71
88
  - [x] 上一轮执行回复缺少验证证据时的后续复核提醒
72
89
  - [x] 任务阶段状态机:回答 / 查找 / 讨论 / 归因 / 执行 / 验证 / 交付
73
90
  - [x] 真实工具证据:区分工具成功、失败和结果未知,不把工具调用本身当作目标完成
91
+ - [x] 证据时效:引用日志/历史/旧报错前核对时间戳与因果,不用旧错误填空
74
92
  - [x] 记忆生命周期:相对时间记忆标记为临时记忆,30 天后自动失效;旧记忆无感兼容
75
93
  - [x] 反思日志跨会话反馈、旧字段迁移和模型感知协议
94
+ - [x] 上下文压缩感知:识别宿主压缩检查点,压缩后重锚状态并提醒“摘要不是完整历史”
76
95
  - [x] 角色卡算法自动升级且保留记忆、风格和认可语料
77
- - [ ] DSH 宿主级历史压缩:当前由 DSH 的 `dsh-compact` 宿主能力负责,插件依赖未开放直接调用接口,不能由 Lume 安全替代
78
96
 
79
- 最后一项不是遗漏:Lume 已经在插件权限范围内做了状态锚定和历史降噪,但不会私自重写宿主的持久会话历史,避免破坏回放、分支和用户已有记录。
97
+ ### 为什么不接管宿主的历史压缩
98
+
99
+ 压缩服务由 DSH 的 agent preset 在自己的隔离域里挂载(`isolate: { compaction: true }`),profile 层的插件注册的同名服务不会被 `/compact` 或自动压缩使用——**第三方插件在标准 preset 下无法替换压缩后端**,这属于宿主的架构边界,不是接口开放与否的问题。
100
+
101
+ Lume 因此选择「观察 + 重锚」:压缩发生时记录规模,在随后一轮注入提示,提醒模型摘要只保留要点、依赖早期细节时先确认;压缩产生的摘要消息带固定来源标记,Lume 用它把摘要与真实用户消息区分开,避免摘要污染当前目标与协议路由。人设契约、长期记忆与协议本身注入在 system prompt 段,不参与对话历史压缩,因此不受影响。
80
102
 
81
103
  ## 二、人设系统:「人设即人」
82
104
 
@@ -201,7 +223,6 @@
201
223
  | `extractionProvider` / `extractionModel` | 回落主对话 | 提取专用模型档(可仅配置其一) |
202
224
  | `distillProvider` / `distillModel` | 回落主对话 | 蒸馏专用模型档(可仅配置其一) |
203
225
  | `reflectionEnabled` | `true` | 会话结束时运行 Codex 工作协议反思评估,写入 `lume_reflection` 域 |
204
- | `compactionTakeover` | `false` | 接管会话压缩(实验):需同时在 profile 的 `cordis.patch.yml` 里禁用 `compaction-basic`,否则同名服务已被占用、接管不会生效 |
205
226
 
206
227
  ## 存储
207
228
 
@@ -1,219 +1,41 @@
1
1
  /**
2
- * Lume 压缩后端:把较早对话压缩成结构化检查点。
2
+ * 压缩感知:识别宿主的上下文压缩,并把它变成一次状态重锚。
3
3
  *
4
- * 复用默认后端(dsh-compaction-basic)的触发策略、保留策略与重放安全逻辑,
5
- * 只替换「摘要生成」这一个钩子。默认模板面向 coding 场景(Files and Code、
6
- * Key Technical Concepts),对闲聊、人设对话和长任务状态不适用;Lume 模板
7
- * 保留:当前目标、已完成、未完成、已确认事实、已排除方案、当前错误、下一步,
8
- * 以及与人设相关的关系语气线索。
4
+ * ## 为什么不做「接管压缩后端」
9
5
  *
10
- * 接管方式:cordis 的同名服务不可由两个 fiber 重复注册(provide 会抛
11
- * `service "compaction" has been registered at <...>`)。因此用户需在 profile
12
- * 的 cordis.patch.yml 里按 id 禁用 `compaction-basic` 行,Lume 引擎才能注册
13
- * ctx.compaction;未禁用时注册被拒绝,这里捕获并提示,不影响 Lume 其他功能。
6
+ * 一度尝试继承 `dsh-compaction-basic` 用 Lume 的摘要模板替换宿主的 coding 模板,
7
+ * 结论是**在标准 preset 下做不到**:宿主的 standard/ptc preset 把压缩服务放在
8
+ * 自己的隔离域里——
14
9
  *
15
- * 宿主不提供压缩包时(import 失败)静默跳过——压缩是增强能力,不是依赖。
16
- */
17
- import { appendFileSync } from "node:fs";
18
- import { join } from "node:path";
19
- /** 摘要指令:七项状态结构 + 保留边界。空小节写「(无)」,禁止编造;强制短于原文。 */
20
- export const LUME_COMPACTION_INSTRUCTION = [
21
- "请把上面的对话压缩成一份结构化检查点,让另一个模型在不读原始历史的情况下无缝接续。",
22
- "",
23
- "严格按下面的 Markdown 结构输出,保留每个小节与顺序;用小短句、每条一行:",
24
- "",
25
- "## 当前目标",
26
- "- [用户当前真正想达成的结果;原话重要时逐字引用]",
27
- "",
28
- "## 已完成",
29
- "- [已确认完成的事项;只写有结果证据的,不写「尝试过」]",
30
- "",
31
- "## 未完成",
32
- "- [明确要求但尚未完成的工作]",
33
- "",
34
- "## 已确认事实",
35
- "- [对话中确认的稳定信息:决定、偏好、约定、结论;区分用户陈述与模型推断]",
36
- "",
37
- "## 已排除方案",
38
- "- [讨论过或尝试过但被否决、失败或放弃的方案,以及原因——避免重复走弯路]",
39
- "",
40
- "## 当前错误与阻塞",
41
- "- [正在遇到的错误、失败或卡点;没有就写(无)]",
42
- "",
43
- "## 下一步",
44
- "- [最直接的下一步动作;没有明确下一步就写(无)]",
45
- "",
46
- "## 关系与语气线索",
47
- "- [仅在与人设/关系相关时保留:双方称呼、用户明确提出过的语气或风格要求、关系定位;没有就写(无)]",
48
- "",
49
- "硬性要求:",
50
- "- 整份摘要必须明显短于被压缩的对话——它替代的是历史,不是复述历史;",
51
- "- 每个小节最多 3 条,每条不超过 40 字;没有内容的小节只写「(无)」,不要展开;",
52
- "- 不要逐轮复述,不要保留寒暄、重复确认和已被取代的中间过程;",
53
- "- 只压缩对话里真实出现过的信息,不要评价、不要补充对话中没有的事实。",
54
- ].join("\n");
55
- /**
56
- * 解析摘要调用的模型路由:优先会话最近一次真实请求的路由(与默认后端一致,
57
- * 保证摘要模型与对话模型同源),回退 agent 选项;都拿不到返回 null
58
- * (调用方回退默认实现,让默认后端的错误路径处理)。
59
- */
60
- export function resolveSummarizationTarget(agent) {
61
- const a = agent;
62
- const latest = a?.session?.requestHeader?.()?.config;
63
- if (typeof latest?.provider === "string" && latest.provider && typeof latest?.model === "string" && latest.model) {
64
- return { provider: latest.provider, model: latest.model };
65
- }
66
- const options = a?.options;
67
- if (typeof options?.provider === "string" && options.provider && typeof options?.model === "string" && options.model) {
68
- return { provider: options.provider, model: options.model };
69
- }
70
- return null;
71
- }
72
- /** 默认加载器:宿主包(dsh-compaction-basic / dsh-llm)由 DSH 模块图提供。
73
- * 模块名走变量,避免本地静态解析——这些包只在 DSH 运行时存在,且 npm 上的
74
- * 同名包与宿主内置版本的内容可能不同(例如 dsh-llm 的导出面差异)。 */
75
- async function loadHostCompactionBackend() {
76
- const compactionId = "@deepseek-ai/dsh-compaction-basic";
77
- const llmId = "@deepseek-ai/dsh-llm";
78
- const [compactionMod, llmModule] = await Promise.all([import(compactionId), import(llmId)]);
79
- return {
80
- Base: compactionMod?.BasicCompactionEngine,
81
- BlockAssembler: llmModule?.BlockAssembler,
82
- createUserMessage: llmModule?.createUserMessage,
83
- };
84
- }
85
- /**
86
- * 压缩诊断通道:宿主 stderr 会被桌面外壳缓冲,排查「接管了但没生效」这类问题时
87
- * 不可靠。压缩是静默功能(自动触发时用户无感),因此保留一个自己的追加日志:
88
- * 每次接管、摘要调用、失败各一行,路径 `$DSH_HOME/lume-compaction.log`。
89
- * 写失败一律忽略——诊断不能反过来影响功能。
90
- */
91
- function appendCompactionLog(message) {
92
- try {
93
- const home = process.env.DSH_HOME;
94
- if (!home)
95
- return;
96
- appendFileSync(join(home, "lume-compaction.log"), `${new Date().toISOString()} ${message}\n`, "utf8");
97
- }
98
- catch {
99
- /* 诊断失败不阻断 */
100
- }
101
- }
102
- /**
103
- * 注册 Lume 压缩引擎。异步动态加载宿主包:宿主不提供压缩子系统时静默返回,
104
- * 已由 compaction-basic 占用服务时记录一次可操作的提示。
10
+ * ```yaml
11
+ * - id: compaction
12
+ * name: cordis:group
13
+ * isolate:
14
+ * compaction: true
15
+ * config:
16
+ * - id: compaction-basic
17
+ * ```
18
+ *
19
+ * profile 层注册的同名服务在另一个隔离域,`/compact` 与自动压缩都只会用它自己
20
+ * 域内的后端。第三方插件无法把自己的实现塞进 preset 的隔离域,也不该改写宿主
21
+ * 的 preset 资产。因此这里放弃接管,改为「观察 + 重锚」。
105
22
  *
106
- * 宿主包只在 DSH 运行时由模块图解析(与 npm 上同名包内容可能不同,例如
107
- * dsh-llm 的导出面差异),因此加载失败一律按「宿主不支持」处理而不是抛错。
23
+ * ## 观察什么
108
24
  *
109
- * @param deps.resolveRoute - Lume 自己缓存的对话模型路由(request/context 事件)。
110
- * agent 上取不到路由时用它兜底,避免摘要静默回退到宿主默认模板。
111
- * @param deps.report - 诊断输出;默认同时写 stderr 与追加日志文件。
25
+ * 压缩把较早对话替换成一条摘要 user/message,该消息带 backend-independent 标记
26
+ * `{kind:"plugin", plugin:"compact"}`。用标记而不是文案匹配,宿主改模板也不会失效。
27
+ * 同时必须把它与真实用户消息区分——否则摘要会被当成「用户当前说的话」,污染
28
+ * 协议路由所依赖的 lastQuery 与对话缓冲。
29
+ *
30
+ * 规模信息(替换了多少项、多少 tokens)来自 `compaction/summary` 会话事件;
31
+ * 事件缺失时退化为不带规模的提醒,功能不依赖它。
32
+ */
33
+ /**
34
+ * 压缩检查点判定:宿主替换被压缩历史的摘要消息带
35
+ * `source = {kind:"plugin", plugin:"compact", compactionId, sourceCommandId?}`。
36
+ * 见 `@deepseek-ai/dsh-compaction` 的 `compactCheckpointSource`。
112
37
  */
113
- export async function registerLumeCompaction(ctx, logger, deps) {
114
- const report = deps?.report ??
115
- ((message) => {
116
- appendCompactionLog(message);
117
- process.stderr.write(`${message}\n`);
118
- });
119
- const load = deps?.load ?? loadHostCompactionBackend;
120
- let backend;
121
- try {
122
- backend = await load();
123
- }
124
- catch {
125
- return; // 宿主没有压缩子系统:跳过接管,Lume 其余功能不受影响
126
- }
127
- const Base = backend?.Base;
128
- const BlockAssembler = backend?.BlockAssembler;
129
- const createUserMessage = backend?.createUserMessage;
130
- if (typeof Base !== "function" || typeof BlockAssembler !== "function" || typeof createUserMessage !== "function")
131
- return;
132
- const fallbackRoute = deps?.resolveRoute;
133
- class LumeCompactionEngine extends Base {
134
- /** 手动 `/compact` 的入口追踪:确认命令是否到达 Lume 实例(而非宿主默认实现)。 */
135
- async compactNow(agent, signal, sourceCommandId) {
136
- report("[lume] compaction: compactNow 到达 Lume 实例(手动压缩)");
137
- return super.compactNow(agent, signal, sourceCommandId);
138
- }
139
- /** 只替换摘要生成;触发、保留、重放与计量策略全部继承默认后端。 */
140
- async summarize(input, agent, signal) {
141
- const target = resolveSummarizationTarget(agent) ?? fallbackRoute?.() ?? null;
142
- if (target === null) {
143
- report("[lume] compaction: 摘要回退宿主默认实现(无法解析模型路由)");
144
- return super.summarize(input, agent, signal);
145
- }
146
- const sessionId = agent?.session?.id;
147
- const maxTokens = this.config?.maxTokens;
148
- const messages = [
149
- ...(input?.messages ?? []),
150
- createUserMessage({
151
- content: [{ type: "text", text: LUME_COMPACTION_INSTRUCTION }],
152
- source: { kind: "plugin", plugin: "lume-dsh-plugin" },
153
- }),
154
- ];
155
- const assembler = new BlockAssembler();
156
- for await (const chunk of this.ctx.llm.stream({
157
- provider: target.provider,
158
- model: target.model,
159
- messages,
160
- ...(input?.system === undefined ? {} : { system: input.system }),
161
- ...(input?.tools === undefined ? {} : { tools: [...input.tools] }),
162
- ...(typeof maxTokens === "number" ? { maxTokens } : {}),
163
- ...(typeof sessionId === "string" ? { sessionId } : {}),
164
- purpose: "compaction",
165
- ...(signal === undefined ? {} : { signal }),
166
- })) {
167
- assembler.push(chunk);
168
- }
169
- const finish = assembler.finish;
170
- if (finish?.kind === "error") {
171
- throw new Error(String(finish.failure?.message ?? "lume compaction: summarization stream failed"));
172
- }
173
- const blocks = assembler.blocks();
174
- const summary = blocks.filter((block) => block?.type === "text" && typeof block.text === "string" && block.text.trim().length > 0);
175
- if (summary.length === 0)
176
- throw new Error("lume compaction: summarization produced no text summary content");
177
- const usage = assembler.usage;
178
- return {
179
- summary,
180
- rawOutput: blocks,
181
- llmStreamCall: true,
182
- provider: target.provider,
183
- model: target.model,
184
- ...(typeof maxTokens === "number" ? { maxTokens } : {}),
185
- ...(usage === undefined ? {} : { usage }),
186
- };
187
- }
188
- }
189
- try {
190
- // 注意:必须注册到 Lume 自己的 ctx——`ctx.inject` 回调收到的 scope 是
191
- // 隔离子上下文,服务注册在那里对命令与其他插件不可见(表现为引擎构造
192
- // 成功但 /compact 仍走默认实现)。inject 只用于等待依赖就绪。
193
- ctx.inject(["llm", "tokenMeter", "sessions"], () => {
194
- try {
195
- const existing = (() => {
196
- try {
197
- return ctx.get("compaction", false) ?? null;
198
- }
199
- catch {
200
- return null;
201
- }
202
- })();
203
- report(`[lume] compaction: 注册前 compaction 服务${existing === null ? "不存在" : "已存在(将被接管尝试拒绝)"}`);
204
- new LumeCompactionEngine(ctx, {});
205
- logger?.info?.("lume: 已接管会话压缩(结构化检查点摘要)");
206
- report("[lume] compaction: 已接管 ctx.compaction(结构化检查点摘要)");
207
- }
208
- catch (error) {
209
- const message = "lume: 压缩后端未接管——compaction 服务已被占用;在 profile 的 cordis.patch.yml 里禁用 compaction-basic 行后重启即可启用 Lume 摘要";
210
- logger?.warn?.(message, error);
211
- report(`[lume] compaction: 接管失败,服务已被占用(${String(error?.message ?? error)})——请在 profile 的 cordis.patch.yml 里禁用 compaction-basic 后重启`);
212
- }
213
- });
214
- }
215
- catch (error) {
216
- logger?.warn?.("lume: 压缩后端注册失败(不影响其他功能)", error);
217
- report(`[lume] compaction: 注册失败(${String(error?.message ?? error)})`);
218
- }
38
+ export function isCompactionCheckpoint(data) {
39
+ const source = data?.source;
40
+ return source?.kind === "plugin" && source?.plugin === "compact";
219
41
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Lume 诊断日志:写 `$DSH_HOME/lume-compaction.log`。
3
+ *
4
+ * 存在的理由:`ctx.logger` 的输出不落在 DSH Desktop 的 harness.log 里(实测为 0 行),
5
+ * 宿主 stderr 又会被桌面外壳缓冲——排查「静默功能」时两者都不可靠。压缩这类
6
+ * 自动触发、用户无感的行为需要一个稳定可见的通道:每次接管/摘要/压缩事件一行。
7
+ *
8
+ * 宿主未提供 DSH_HOME 时静默跳过;任何写失败都不影响功能。
9
+ */
10
+ import { appendFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ /** 诊断文件名(位于 DSH_HOME 下)。 */
13
+ export const LUME_LOG_FILE = "lume-compaction.log";
14
+ /** 追加一行诊断;失败静默。 */
15
+ export function appendLumeLog(message) {
16
+ try {
17
+ const home = process.env.DSH_HOME;
18
+ if (!home)
19
+ return;
20
+ appendFileSync(join(home, LUME_LOG_FILE), `${new Date().toISOString()} ${message}\n`, "utf8");
21
+ }
22
+ catch {
23
+ /* 诊断失败不阻断功能 */
24
+ }
25
+ }
@@ -79,3 +79,18 @@ export function buildAlignmentCorrection(kind) {
79
79
  ? "〔即时对齐纠偏〕用户正在纠正上一轮理解。先用一句话复述你现在理解的目标和边界,若仍有歧义只问一个关键问题;不要沿用上一轮假设,也不要直接继续执行。"
80
80
  : "〔即时对齐纠偏〕用户重复提出相近请求,说明上一轮可能没有解决真正目标。先检查上一轮回答是否答非所问或没有产生结果,再给出针对当前目标的回应;不要原样重复上一轮。";
81
81
  }
82
+ /**
83
+ * 压缩后的状态重锚:宿主的 preset 在自己的隔离域里执行压缩,Lume 无法接管该
84
+ * 服务,但能观察到压缩事件。压缩把较早对话替换成一条摘要——摘要必然丢细节,
85
+ * 而模型很容易把摘要当成完整历史。这里提醒它在依赖旧细节时先确认。
86
+ *
87
+ * 只在压缩后一轮内注入:更久之后摘要已成为正常上下文的一部分。
88
+ */
89
+ export function buildCompactionNotice(info, currentTurn) {
90
+ if (currentTurn - info.turnIndex > 1)
91
+ return null;
92
+ const scale = info.shadowedItems > 0
93
+ ? `约 ${info.shadowedItems} 项历史${info.tokens > 0 ? `(~${info.tokens} tokens)` : ""}已被摘要替换`
94
+ : "较早的历史已被摘要替换";
95
+ return `〔上下文压缩提示〕上一轮发生的上下文压缩已生效:${scale}。摘要只保留要点,早期对话的具体细节(文件路径、数字、原始报错、当时确认过的结论)可能已经不在上下文里。如果当前任务或用户的话依赖这些细节中的任何一项,先回看或直接问,不要假设摘要包含全部信息,也不要凭印象补全。`;
96
+ }
@@ -29,6 +29,7 @@ function defaultRuntime() {
29
29
  toolSuccesses: 0,
30
30
  toolFailures: 0,
31
31
  toolUnknown: 0,
32
+ compaction: null,
32
33
  };
33
34
  }
34
35
  export class SessionRuntimeStore {
package/lib/index.js CHANGED
@@ -31,9 +31,10 @@ import { detectLeak } from "./core/leak-detector.js";
31
31
  import { messageText } from "./core/text.js";
32
32
  import { composeBoundary } from "./host/boundary.js";
33
33
  import { SessionRuntimeStore } from "./host/session-runtime.js";
34
- import { registerLumeCompaction } from "./host/compaction.js";
34
+ import { isCompactionCheckpoint } from "./host/compaction.js";
35
35
  import { LUME_REFLECTION_SPEC, ReflectionStore, buildReflectionPrompt, parseReflectionScore } from "./host/reflection.js";
36
- import { buildAlignmentCorrection, buildInteractionDirective, buildLongSessionGuard, buildSessionAnchor, buildTaskPhaseDirective, buildToolEvidenceDirective, classifyInteraction, taskPhaseForMode } from "./host/protocol.js";
36
+ import { appendLumeLog } from "./host/diag.js";
37
+ import { buildAlignmentCorrection, buildCompactionNotice, buildInteractionDirective, buildLongSessionGuard, buildSessionAnchor, buildTaskPhaseDirective, buildToolEvidenceDirective, classifyInteraction, taskPhaseForMode } from "./host/protocol.js";
37
38
  import { REASONING_MODEL_RE, TASK_SIGNAL_RE, selectThinkingProtocol } from "./host/thinking.js";
38
39
  /** schemastery → domainTable 形参的桥接(与 identity.ts 同款)。 */
39
40
  const recordSchema = zodLike;
@@ -83,7 +84,6 @@ export function apply(ctx, config = {}) {
83
84
  const extractionRouteOverride = { provider: config.extractionProvider, model: config.extractionModel };
84
85
  const distillRouteOverride = { provider: config.distillProvider, model: config.distillModel };
85
86
  const reflectionEnabled = config.reflectionEnabled ?? true;
86
- const compactionTakeover = config.compactionTakeover ?? false;
87
87
  const boundaryTurns = config.switchBoundaryTurns ?? SWITCH_BOUNDARY_TURNS;
88
88
  const defaultName = builtins[NONE_PERSONA] ? NONE_PERSONA : null;
89
89
  const legacyStatePath = join(assetsDir, "persona-state.json");
@@ -423,6 +423,17 @@ export function apply(ctx, config = {}) {
423
423
  break;
424
424
  }
425
425
  case "user/message": {
426
+ // 压缩检查点:宿主把被压缩的历史替换成一条摘要消息,必须与真实
427
+ // 用户消息区分——否则摘要会被当成「用户当前说的话」,污染协议
428
+ // 路由所依赖的 lastQuery 与对话缓冲。这是兜底识别:同一轮里
429
+ // compaction/summary 通常先到且带规模,不要把那条覆盖成无规模的。
430
+ if (isCompactionCheckpoint(event.data)) {
431
+ if (!st.compaction || st.compaction.turnIndex !== st.turnIndex) {
432
+ st.compaction = { turnIndex: st.turnIndex, shadowedItems: 0, tokens: 0 };
433
+ }
434
+ appendLumeLog(`[${sid}] 检测到上下文压缩检查点(第 ${st.turnIndex} 轮)`);
435
+ break;
436
+ }
426
437
  const text = messageText(event.data);
427
438
  if (text) {
428
439
  const normalized = text.trim().replace(/\s+/g, " ").slice(0, 240);
@@ -481,6 +492,16 @@ export function apply(ctx, config = {}) {
481
492
  st.taskPhase = unknownResult || explicitError ? "diagnose" : "verify";
482
493
  break;
483
494
  }
495
+ case "compaction/summary": {
496
+ // 压缩由宿主 preset 在隔离域执行(Lume 无法接管该服务),但事件在
497
+ // 会话总线上可见。记录规模,供下一轮注入「摘要不是完整历史」的重锚。
498
+ const data = event.data;
499
+ const shadowedItems = Array.isArray(data?.shadowedSeqs) ? data.shadowedSeqs.length : 0;
500
+ const tokens = typeof data?.shadowedTokenCount === "number" ? data.shadowedTokenCount : 0;
501
+ st.compaction = { turnIndex: st.turnIndex, shadowedItems, tokens };
502
+ appendLumeLog(`[${sid}] 压缩完成:替换 ${shadowedItems} 项历史(~${tokens} tokens),下一轮注入状态重锚`);
503
+ break;
504
+ }
484
505
  case "turn/end": {
485
506
  st.turnIndex++;
486
507
  // 低成本会话内纠偏:只处理明确的错误/失败信号,且要求连续轮次用户请求相同。
@@ -526,6 +547,11 @@ export function apply(ctx, config = {}) {
526
547
  break;
527
548
  }
528
549
  default:
550
+ // 诊断:压缩事件是否经由 session/event 总线投递(宿主按 session
551
+ // 所属上下文收集监听者,隔离域里发出的日志事件可能不经过这里)。
552
+ if (typeof event.type === "string" && /compact/i.test(event.type)) {
553
+ appendLumeLog(`[${sid}] 收到未处理的压缩事件类型 ${event.type}`);
554
+ }
529
555
  break;
530
556
  }
531
557
  }), "lume: session events");
@@ -743,16 +769,10 @@ export function apply(ctx, config = {}) {
743
769
  const postTurnReview = st?.postTurnReview;
744
770
  const phase = buildTaskPhaseDirective(st?.taskPhase ?? "answer");
745
771
  const toolEvidence = st ? buildToolEvidenceDirective({ calls: st.toolCalls, successes: st.toolSuccesses, failures: st.toolFailures, unknown: st.toolUnknown }) : null;
746
- return [base, route, phase, longSession, anchor, alignment, postTurnReview, toolEvidence, correction, reflectionHint].filter(Boolean).join("\n\n");
772
+ // 压缩重锚:宿主的 preset 隔离域负责压缩,Lume 只能观察事件;
773
+ // 在压缩后一轮提醒「摘要不是完整历史」。
774
+ const compactionNotice = st?.compaction ? buildCompactionNotice(st.compaction, st.turnIndex) : null;
775
+ return [base, route, phase, longSession, anchor, alignment, postTurnReview, toolEvidence, compactionNotice, correction, reflectionHint].filter(Boolean).join("\n\n");
747
776
  },
748
777
  }), "lume.thinking-section()");
749
- // ── 会话压缩接管(实验特性,默认关闭)──
750
- // 开启后:宿主提供 ctx.compaction 时用 Lume 的结构化检查点摘要替换 coding 模板;
751
- // 需配合 profile 的 cordis.patch.yml 禁用 compaction-basic(同名服务不可重复注册)。
752
- // 默认关闭时完全不注册,避免在未配置的机器上产生无意义的失败日志。
753
- if (compactionTakeover) {
754
- // resolveRoute:agent 上取不到路由时用 Lume 自己缓存的主对话路由兜底,
755
- // 避免摘要静默回退到宿主的 coding 模板。
756
- void registerLumeCompaction(ctx, ctx.logger, { resolveRoute: () => llmRoute });
757
- }
758
778
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lume-dsh-plugin",
3
3
  "description": "微光 (Lume) — DSH Desktop 增强插件:Codex 风格自适应任务执行协议 + 人设系统(聊天记录蒸馏具名角色、长期记忆、风格纠偏自动捕获)",
4
- "version": "0.4.5",
4
+ "version": "0.5.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -55,8 +55,6 @@
55
55
  "@deepseek-ai/dsh-llm": "*",
56
56
  "@deepseek-ai/dsh-storage-domain": "*",
57
57
  "@deepseek-ai/dsh-tools": "*",
58
- "@deepseek-ai/dsh-compaction": "*",
59
- "@deepseek-ai/dsh-compaction-basic": "*",
60
58
  "@deepseek-ai/schemastery": "^3.18.1"
61
59
  },
62
60
  "scripts": {
@@ -107,12 +105,6 @@
107
105
  "@deepseek-ai/dsh-tools": {
108
106
  "optional": true
109
107
  },
110
- "@deepseek-ai/dsh-compaction": {
111
- "optional": true
112
- },
113
- "@deepseek-ai/dsh-compaction-basic": {
114
- "optional": true
115
- },
116
108
  "@deepseek-ai/schemastery": {
117
109
  "optional": true
118
110
  }