lume-dsh-plugin 0.5.0 → 0.6.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 +23 -11
- package/lib/host/documents.js +121 -0
- package/lib/host/protocol.js +51 -8
- package/lib/host/reflection.js +3 -3
- package/lib/host/session-runtime.js +2 -0
- package/lib/host/thinking.js +2 -2
- package/lib/index.js +141 -30
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
- **真实工具结果验证**——监听工具成败与结果未知;失败或未知不允许报完成,交付时区分「已验证 / 未验证 / 推测有效」
|
|
15
15
|
- **会话内自愈与复盘回环**——相同请求连续失败自动注入归因纠偏;跨会话聚合反思评分,对持续低分的维度定向提醒
|
|
16
16
|
- **上下文压缩感知**——压缩发生后重锚状态,提醒模型摘要不是完整历史,细节依赖先确认
|
|
17
|
+
- **文档能力感知**——按当前环境探测文档工具:有就要求先读后写、交付前回读验证;没有就如实说明边界,不用文本读取或脚本硬解二进制办公文件
|
|
17
18
|
|
|
18
19
|
### 能力二:人设系统(人设即人)
|
|
19
20
|
|
|
20
21
|
塑造「以何种风格表达」:具名角色、长期记忆、随对话演进:
|
|
21
22
|
|
|
22
|
-
-
|
|
23
|
+
- **素材蒸馏**——从聊天记录、小说、剧本、人物设定文档蒸馏角色卡,语气、口头禅与回复篇幅锚定真实素材统计
|
|
23
24
|
- **长期记忆与生命周期**——事件记忆 + 故事记忆;相对时间的临时记忆自动过期,长期事实不受影响
|
|
24
25
|
- **双向反馈闭环**——负面反馈自动转成风格约定,被认可的回复摘录为语料,语气随使用收敛
|
|
25
26
|
- **记忆星图与角色卡导入导出**——可视化记忆、行内编辑,卡片可分享迁移
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
人设只影响自然语言表达,不介入任务执行,也不影响代码、命令与工具调用的结果。
|
|
28
29
|
|
|
29
30
|
[](https://github.com/cayan0x/Lume/actions/workflows/ci.yml)
|
|
30
|
-
[](./CHANGELOG.md)
|
|
31
32
|
[](./LICENSE)
|
|
32
33
|
|
|
33
34
|
*人设系统:内置角色卡、蒸馏与管理入口,以及记忆星图*
|
|
@@ -77,6 +78,14 @@
|
|
|
77
78
|
|
|
78
79
|
这三层共同形成“常规协议 → 检测问题 → 临时强化 → 成功解除 → 跨会话复盘”的闭环,在需要深度处理时增加约束,在简单问题上控制 Token 消耗。
|
|
79
80
|
|
|
81
|
+
### 文档能力感知(与文档工具插件协作)
|
|
82
|
+
|
|
83
|
+
DSH 本身不带 Office / PDF 读写能力:附件只接受图片,工具名册里没有任何文档工具。模型面对 `.docx`、`.xlsx` 这类二进制容器时,只能在“当文本读”“现场解压 zip”“手写解析脚本”之间瞎试——慢,而且几乎必然出错。
|
|
84
|
+
|
|
85
|
+
微光不重复造这套工具,而是**做能力探测与约束**:注入前查询宿主的工具注册表,按工具名的能力族前缀(`word_*` / `excel_*` / `ppt_*` / `pdf_*` 等)判断当前环境具备哪些文档能力,再据此分叉——**有工具**时要求走工具而不是自己解析、先读后写、交付前回读验证;**没有工具**时要求第一轮就如实说明边界、不要静默硬解二进制,并给出替代交付方式。用户明确说「就用脚本自己试」时仍然照做,但要先说明代价。
|
|
86
|
+
|
|
87
|
+
两条都只在**文档任务轮注入**:闲聊、代码、排查类请求零成本,只有请求里出现文件后缀、Word / Excel / PPT / PDF 等格式名,或“写一份报告”这类产物请求时才生效;中文里“查一下官方文档”这种泛称不会被误判成文档任务。判据取自冻结的意图文本,因此在一轮之内稳定,不会像工具结果那样在轮中作废前缀缓存。探测按能力族前缀而非插件名,因此不绑定任何第三方实现——`dsh-office-tools`、`dsh-excel-chat`、`dsh-ppt` 装了哪个都能识别,一个都不装就退化为边界声明模式。
|
|
88
|
+
|
|
80
89
|
### 优化清单
|
|
81
90
|
|
|
82
91
|
- [x] 问答、查找、讨论、诊断、执行五类请求路由
|
|
@@ -92,6 +101,8 @@
|
|
|
92
101
|
- [x] 记忆生命周期:相对时间记忆标记为临时记忆,30 天后自动失效;旧记忆无感兼容
|
|
93
102
|
- [x] 反思日志跨会话反馈、旧字段迁移和模型感知协议
|
|
94
103
|
- [x] 上下文压缩感知:识别宿主压缩检查点,压缩后重锚状态并提醒“摘要不是完整历史”
|
|
104
|
+
- [x] 文档能力感知:探测文档工具并按需注入——有工具要求先读后写与回读验证,没工具要求如实说明边界、不硬解二进制
|
|
105
|
+
- [x] 系统提示词轮内稳定:意图冻结(只认真实用户消息)+ 会变的内容走 runtime-context 通道,一轮只产生一份系统提示词
|
|
95
106
|
- [x] 角色卡算法自动升级且保留记忆、风格和认可语料
|
|
96
107
|
|
|
97
108
|
### 为什么不接管宿主的历史压缩
|
|
@@ -178,7 +189,7 @@ Lume 因此选择「观察 + 重锚」:压缩发生时记录规模,在随后
|
|
|
178
189
|
|
|
179
190
|
## 六、反思日志
|
|
180
191
|
|
|
181
|
-
|
|
192
|
+
会话结束时,插件在空闲时间跑一次小模型调用,对整段对话的任务执行协议执行情况进行复盘:上下文管理、计划与门控、验证与失败处理、结果复核,各打 0-2 分并附一句中文备注,写入 `lume_reflection` 域。
|
|
182
193
|
|
|
183
194
|
升级到 0.4.0 时,旧版反思日志会在域打开后自动从旧字段迁移到新字段;迁移幂等,不影响角色卡、记忆或会话。
|
|
184
195
|
|
|
@@ -198,13 +209,14 @@ Lume 因此选择「观察 + 重锚」:压缩发生时记录规模,在随后
|
|
|
198
209
|
|
|
199
210
|
| 注入段 | 无优化 | 优化后 | 使用的算法 |
|
|
200
211
|
|---|---|---|---|
|
|
201
|
-
|
|
|
212
|
+
| 任务执行协议 | ~500 | 闲聊约 100;任务约 500 | 普通闲聊短版注入;代码/复杂任务自动切换完整版 |
|
|
202
213
|
| 人设契约 | ~350 | ~250 | 契约精简 |
|
|
203
214
|
| 语料示例 | 6 条 ~600 | 稳态 2 条 ~200 | 少样本衰减 `max(2, 6−轮数)` |
|
|
204
215
|
| 工具定义 ×3 | ~600 | ~450 | description 精简 |
|
|
205
216
|
| 记忆 | 15 条 ~350 | core + top5 ~120 | 相关性检索(本地分词 + mini-IDF,零成本) |
|
|
206
217
|
| 风格层 | 10 条 ~250 | top5 ~120 | 同上 |
|
|
207
218
|
| 身份 | ~80 | ~80 | 恒注入 |
|
|
219
|
+
| 文档能力指引 | 常驻 ~120 | 文档任务轮 ~120,其余 0 | 工具能力探测 + 按轮触发 |
|
|
208
220
|
|
|
209
221
|
- **成熟态稳态约 1,570 tok/请求,较无优化降低 39%**;缓存友好分层(静态内容前置于易变内容)叠加前缀缓存后,有效成本可再降约一个数量级
|
|
210
222
|
- 相比 v0.2.0(约 1,400 tok),v0.3.0 全部新功能的稳态净增仅约 **170 tok/请求**
|
|
@@ -222,7 +234,7 @@ Lume 因此选择「观察 + 重锚」:压缩发生时记录规模,在随后
|
|
|
222
234
|
| `extractionCooldownMs` | 600000 | 被动提取冷却(毫秒) |
|
|
223
235
|
| `extractionProvider` / `extractionModel` | 回落主对话 | 提取专用模型档(可仅配置其一) |
|
|
224
236
|
| `distillProvider` / `distillModel` | 回落主对话 | 蒸馏专用模型档(可仅配置其一) |
|
|
225
|
-
| `reflectionEnabled` | `true` |
|
|
237
|
+
| `reflectionEnabled` | `true` | 会话结束时运行任务执行协议反思评估,写入 `lume_reflection` 域 |
|
|
226
238
|
|
|
227
239
|
## 存储
|
|
228
240
|
|
|
@@ -249,7 +261,7 @@ dsh plugin add lume-dsh-plugin
|
|
|
249
261
|
若网络无法访问 npm,也可直接从仓库安装:
|
|
250
262
|
|
|
251
263
|
```bash
|
|
252
|
-
dsh plugin add github:cayan0x/Lume#v0.
|
|
264
|
+
dsh plugin add github:cayan0x/Lume#v0.6.1
|
|
253
265
|
```
|
|
254
266
|
|
|
255
267
|
安装后需**完全重启 DSH(包含托盘进程)**方可加载;启动日志中出现 `lume: 已加载(builtins=loli,senpai,butler,tsundere,none)` 即表示加载成功。构建产物随仓库发布,两种路径都不需要本地构建。
|
|
@@ -261,7 +273,7 @@ dsh plugin add github:cayan0x/Lume#v0.4.4
|
|
|
261
273
|
```bash
|
|
262
274
|
dsh plugin add lume-dsh-plugin # npm(推荐)
|
|
263
275
|
# 或
|
|
264
|
-
dsh plugin add github:cayan0x/Lume#v0.
|
|
276
|
+
dsh plugin add github:cayan0x/Lume#v0.6.1 # GitHub(备选)
|
|
265
277
|
```
|
|
266
278
|
|
|
267
279
|
人设选择、记忆与风格数据存放在 `storages/` 目录,升级不会丢失。
|
|
@@ -271,13 +283,13 @@ dsh plugin add github:cayan0x/Lume#v0.4.4 # GitHub(备选)
|
|
|
271
283
|
### 指定其他版本
|
|
272
284
|
|
|
273
285
|
```bash
|
|
274
|
-
dsh plugin add lume-dsh-plugin@0.
|
|
286
|
+
dsh plugin add lume-dsh-plugin@0.6.1 # npm 指定版本
|
|
275
287
|
dsh plugin add lume-dsh-plugin@latest # npm 最新
|
|
276
288
|
dsh plugin add github:cayan0x/Lume # GitHub 最新 main
|
|
277
|
-
dsh plugin add github:cayan0x/Lume#v0.
|
|
289
|
+
dsh plugin add github:cayan0x/Lume#v0.6.0 # GitHub 任意历史标签
|
|
278
290
|
```
|
|
279
291
|
|
|
280
|
-
标签与版本的对应关系见 [CHANGELOG](./CHANGELOG.md)
|
|
292
|
+
标签与版本的对应关系见 [CHANGELOG](./CHANGELOG.md),建议始终使用最新标签。
|
|
281
293
|
|
|
282
294
|
## 开发
|
|
283
295
|
|
|
@@ -293,7 +305,7 @@ npm run watch # 客户端 bundle 增量构建
|
|
|
293
305
|
```
|
|
294
306
|
src/index.ts 宿主入口:注入 + RPC + 工具 + 事件接线
|
|
295
307
|
src/core/ 纯逻辑:种子采样、检索打分、衰减、对话挖掘、manifest 解析、文本组装
|
|
296
|
-
src/host/
|
|
308
|
+
src/host/ 存储(选择/身份)、蒸馏管线、提取器、工具、协议与文档能力、RPC、注册表
|
|
297
309
|
src/client/ 前端:人设菜单、蒸馏弹窗、管理弹窗(插槽 conversation.input.left)
|
|
298
310
|
lib/ 构建产物(随仓库提交,GitHub 安装路径依赖它)
|
|
299
311
|
test/ vitest 单元测试 + storage 栈集成测试(含带数据重开域回归)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文档能力感知:把「这一轮是不是在做办公文档」与「当前环境有没有文档工具」
|
|
3
|
+
* 合成一条按需注入的指引。
|
|
4
|
+
*
|
|
5
|
+
* 为什么需要它:DSH 本身不带文档读写能力——附件只接受光栅图片,工具名册里
|
|
6
|
+
* 没有任何 Office / PDF 工具。模型面对 .docx / .xlsx 这类二进制容器时,只能
|
|
7
|
+
* 在「当文本读」、「现场解压 zip」、「手写解析脚本」之间瞎试,慢且几乎必然出错;
|
|
8
|
+
* 而第三方文档工具插件(dsh-office-tools、dsh-excel-chat、dsh-ppt 等)装与
|
|
9
|
+
* 不装由用户自由决定,插件不能假定它存在。所以指引按探测结果分叉:
|
|
10
|
+
*
|
|
11
|
+
* - 有工具:约束「走工具、先读后写、交付前回读验证」;
|
|
12
|
+
* - 没工具:约束「如实说明能力边界,不要静默硬解二进制」,并给出替代交付方式。
|
|
13
|
+
*
|
|
14
|
+
* 两条都在「这一轮看起来是文档任务」时才注入,闲聊与非文档轮次零成本——与长
|
|
15
|
+
* 会话护栏、压缩提示同属按需注入族。
|
|
16
|
+
*
|
|
17
|
+
* 探测按工具名的能力族前缀匹配,不按插件名,因此不绑定任何第三方实现:
|
|
18
|
+
* 用户装哪一个文档插件都能被识别;一个都不装就退化为边界声明。
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* 能力族 → 工具名前缀。用前缀而非白名单,是为了让新出现的文档插件无需改动
|
|
22
|
+
* 这里就能被识别(例如 word_* / excel_* / ppt_* / pptd_* / slides_* / pdf_*)。
|
|
23
|
+
*/
|
|
24
|
+
const FAMILY_PREFIXES = [
|
|
25
|
+
{ family: "word", re: /^(?:word|docx?|doc)_/i },
|
|
26
|
+
{ family: "excel", re: /^(?:excel|xlsx?|sheet|spreadsheet)_/i },
|
|
27
|
+
{ family: "slides", re: /^(?:pptx?|pptd?|slide|slides|deck|presentation)_/i },
|
|
28
|
+
{ family: "pdf", re: /^pdf_/i },
|
|
29
|
+
];
|
|
30
|
+
const FAMILY_LABEL = {
|
|
31
|
+
word: "Word",
|
|
32
|
+
excel: "Excel",
|
|
33
|
+
slides: "PPT",
|
|
34
|
+
pdf: "PDF",
|
|
35
|
+
};
|
|
36
|
+
/** 明确的办公文件后缀:出现即视为文档任务,误判率最低。 */
|
|
37
|
+
const DOC_FILE_RE = /\.(?:docx?|xlsx?|pptx?|pdf)\b/i;
|
|
38
|
+
/** 办公套件 / 格式名:用户点名了产出格式(含不带点的 docx / xlsx / pptx)。 */
|
|
39
|
+
const DOC_SUITE_RE = /\bword\b|\bexcel\b|\bppt\b|\bpptx\b|\bpdf\b|\bdocx\b|\bxlsx\b|电子表格|工作簿|幻灯片|演示文稿|spreadsheet|presentation/i;
|
|
40
|
+
/** 产生或改动文件的动作。 */
|
|
41
|
+
const DOC_ACTION_RE = /写|撰写|生成|制作|做一?[份个张]|创建|新建|导出|输出|保存为|另存为|整理成|汇总成|排版|转换|转成|转格式|填表|填写/;
|
|
42
|
+
/** 以文件形态交付的产物名。 */
|
|
43
|
+
const DOC_ARTIFACT_RE = /文档|文件|报告|汇报|合同|简历|论文|纪要|表格|表单|报表|标书|方案书|提案|周报|月报|季报/;
|
|
44
|
+
export function detectDocumentCapabilities(toolNames) {
|
|
45
|
+
const caps = { word: false, excel: false, slides: false, pdf: false };
|
|
46
|
+
for (const raw of toolNames) {
|
|
47
|
+
const name = String(raw ?? "").trim();
|
|
48
|
+
if (!name)
|
|
49
|
+
continue;
|
|
50
|
+
for (const { family, re } of FAMILY_PREFIXES) {
|
|
51
|
+
if (re.test(name))
|
|
52
|
+
caps[family] = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return caps;
|
|
56
|
+
}
|
|
57
|
+
export function hasDocumentCapability(caps) {
|
|
58
|
+
return caps.word || caps.excel || caps.slides || caps.pdf;
|
|
59
|
+
}
|
|
60
|
+
function probeNames(tools, scope) {
|
|
61
|
+
try {
|
|
62
|
+
const schemas = tools?.schemas?.(scope);
|
|
63
|
+
if (!Array.isArray(schemas))
|
|
64
|
+
return detectDocumentCapabilities([]);
|
|
65
|
+
return detectDocumentCapabilities(schemas.map((schema) => String(schema?.name ?? "")));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return detectDocumentCapabilities([]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 探测当前环境可见的文档工具。
|
|
73
|
+
*
|
|
74
|
+
* 先按调用方作用域探测(尊重单个 agent 的工具限制),为空时退回全局视图:作用域
|
|
75
|
+
* 参数在宿主版本漂移时可能被忽略,退回可以避免「明明有工具却报告没有」这种最坏的
|
|
76
|
+
* 误判——那等于给模型一条与事实相反的边界声明。任何异常均按「没有文档工具」处理:
|
|
77
|
+
* 宁可保守地如实说明边界,也不凭空承诺一项不存在的能力。
|
|
78
|
+
*/
|
|
79
|
+
export function probeDocumentCapabilities(tools, scope) {
|
|
80
|
+
const scoped = probeNames(tools, scope);
|
|
81
|
+
if (hasDocumentCapability(scoped) || scope === undefined)
|
|
82
|
+
return scoped;
|
|
83
|
+
return probeNames(tools, undefined);
|
|
84
|
+
}
|
|
85
|
+
function familyList(caps) {
|
|
86
|
+
return Object.keys(FAMILY_LABEL)
|
|
87
|
+
.filter((key) => caps[key])
|
|
88
|
+
.map((key) => FAMILY_LABEL[key])
|
|
89
|
+
.join(" / ");
|
|
90
|
+
}
|
|
91
|
+
function buildRouting(caps) {
|
|
92
|
+
return `〔文档任务〕本轮要产出或改动办公文档(本会话可用:${familyList(caps)})。这类文件是二进制容器,不要用文本读取或 shell 命令去解析内容,也不要现场解压 zip、手写解析脚本——直接调用已经存在的文档工具。
|
|
93
|
+
顺序是先读后写:先读出目标文件的现有内容和结构,再最小范围地创建或更新;没有读过目标文件就不要整份覆盖。
|
|
94
|
+
交付前必须回读一次生成结果,确认关键内容确实写进去了。没有回读证据不要说“已生成 / 已完成”,也不要声称做过未执行的检查(例如用 Office 逐页打开核对)。`;
|
|
95
|
+
}
|
|
96
|
+
function buildBoundary() {
|
|
97
|
+
return `〔文档任务·能力边界〕本轮涉及办公文档(Word / Excel / PPT / PDF),但当前环境没有任何文档读写工具:DSH 的附件只接受图片,工具名册里也没有 Office / PDF 工具。这些格式是二进制容器,用文本读取、解压 zip 或手写解析脚本去取内容既慢又几乎必然出错——不要静默尝试。
|
|
98
|
+
正确做法是第一轮就说明这个边界,并给出可行选项:改用 Markdown / CSV / HTML 等纯文本格式交付(用户可自行另存为所需格式),或由用户安装文档工具插件后重开会话再直接产出文件。
|
|
99
|
+
如果用户明确要求「就用命令行或脚本自己试」,可以照做,但先说明代价与不确定性,再动手,并如实报告结果。`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 注入判据刻意保守,两个信号分叉:
|
|
103
|
+
*
|
|
104
|
+
* - **点名了格式或文件**(`.docx`、Word、Excel、PPT、PDF…):无论有没有工具都值得
|
|
105
|
+
* 说话——有工具就走工具,没工具就如实说明边界。
|
|
106
|
+
* - **只是模糊的产物请求**(「写一份季度报告」):只有存在工具时才提示走工具;
|
|
107
|
+
* 没有工具时不注入,因为退回纯文本交付本来就是正确结果,多说一句反而误导。
|
|
108
|
+
*
|
|
109
|
+
* 反过来,「查一下官方文档」这类常见的「文档」泛称不会被误判——中文里的「文档」
|
|
110
|
+
* 绝大多数指技术文档,误注入会让每一轮问答都平白多背一段无关约束。
|
|
111
|
+
*/
|
|
112
|
+
export function buildDocumentDirective(input) {
|
|
113
|
+
const text = String(input.query ?? "").trim();
|
|
114
|
+
if (!text)
|
|
115
|
+
return null;
|
|
116
|
+
const officeIntent = DOC_FILE_RE.test(text) || DOC_SUITE_RE.test(text);
|
|
117
|
+
if (!hasDocumentCapability(input.capabilities))
|
|
118
|
+
return officeIntent ? buildBoundary() : null;
|
|
119
|
+
const artifactIntent = officeIntent || (DOC_ACTION_RE.test(text) && DOC_ARTIFACT_RE.test(text));
|
|
120
|
+
return artifactIntent ? buildRouting(input.capabilities) : null;
|
|
121
|
+
}
|
package/lib/host/protocol.js
CHANGED
|
@@ -5,8 +5,15 @@ const MODE_RULES = {
|
|
|
5
5
|
diagnosis: "当前模式:诊断。先说明现象、证据、可能根因和验证办法;除非用户明确要求修复,不越权修复,不要越过诊断边界动手。",
|
|
6
6
|
execute: "当前模式:执行。先确认目标和完成标准,再做最小变更;交付时明确列出“已完成、已验证、未验证、残留副作用”,不要用动作完成冒充目标达成。",
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// 显式请求标记后必须紧跟一个动作动词,且限制在同一小句内(旧版用 `.*` 贪婪跨越
|
|
9
|
+
// 整句,导致「是什么驱动你去这么做的」也被判成执行)。句首祈使不再放行「做」:
|
|
10
|
+
// 「做一件事…」这类名词化表述是讨论而非执行。
|
|
11
|
+
const EXECUTE_RE = new RegExp([
|
|
12
|
+
"(?:请|帮我|帮忙|直接|把|给我|替我|麻烦|需要你)\\s*[^,。!?;\\n]{0,24}?(?:做|改|修|写|加|删|建|跑|执行|完成|实现|优化|更新|部署|安装|迁移|提交|发布|检查|核对|补|替换|重命名|合并|回滚|加上)",
|
|
13
|
+
"^(?:改|修|写|加|删|建|跑|执行|完成|实现|优化|更新|部署|安装|迁移|提交|发布|检查|补|替换|重命名|合并|回滚)",
|
|
14
|
+
"\\b(?:add|commit|push|pull|merge|rebase|fix|build|rebuild|install|uninstall|deploy|migrate|refactor|rename|update|upgrade|write|create|delete|remove|revert|rollback)\\b",
|
|
15
|
+
].join("|"), "i");
|
|
16
|
+
const DIAGNOSIS_RE = /为什么|为啥|原因|问题在哪|哪里不对|诊断|排查|分析一下|评估一下|是不是.*问题|能不能解释|怎么会|是什么驱动/i;
|
|
10
17
|
const DISCUSSION_RE = /讨论|聊聊|怎么看|你觉得|比较一下|方案|取舍|利弊|可能性|有没有更好|先别做|探讨/i;
|
|
11
18
|
const RESEARCH_RE = /查一下|查找|搜索|检索|资料|文档|来源|证据|最新|核对|确认事实|看一下.*是否/i;
|
|
12
19
|
/**
|
|
@@ -26,12 +33,42 @@ export function classifyInteraction(text) {
|
|
|
26
33
|
return "research";
|
|
27
34
|
return "question";
|
|
28
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 判定一条消息是否出自真实用户。
|
|
38
|
+
*
|
|
39
|
+
* 宿主的 `user/message` 通道混着大量非用户消息:运行时快照
|
|
40
|
+
* (`plugin:@deepseek-ai/dsh-system-prompt`)、工作区指令(`agent-instructions`)、
|
|
41
|
+
* 技能目录(`skill-catalog`)。它们都带 `role: "user"`,只靠角色无法区分——实测
|
|
42
|
+
* 曾被当成“用户当前说的话”,覆盖真实请求并清零工具计数。
|
|
43
|
+
* `source.kind` 缺失时放行,避免在不上报来源的宿主版本上把意图彻底丢掉。
|
|
44
|
+
*/
|
|
45
|
+
export function isUserAuthored(message) {
|
|
46
|
+
const m = message;
|
|
47
|
+
if (m?.role !== "user")
|
|
48
|
+
return false;
|
|
49
|
+
const kind = m.source?.kind;
|
|
50
|
+
return kind === "user" || kind === undefined;
|
|
51
|
+
}
|
|
29
52
|
export function buildInteractionDirective(mode) {
|
|
30
53
|
return `〔当前请求路由〕${MODE_RULES[mode]}`;
|
|
31
54
|
}
|
|
32
55
|
export function taskPhaseForMode(mode) {
|
|
33
56
|
return mode === "research" ? "research" : mode === "discussion" ? "discuss" : mode === "diagnosis" ? "diagnose" : mode === "execute" ? "execute" : "answer";
|
|
34
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* 阶段只前进,不回退到初始的「回答」。
|
|
60
|
+
*
|
|
61
|
+
* 一轮内阶段若被重置回 answer,系统提示词会在轮内变化——宿主的 `request/header`
|
|
62
|
+
* 因内容变化而重新记录,聊天界面每次渲染一行「系统提示词」,前缀缓存也随之作废。
|
|
63
|
+
* 失败后回到 diagnose 是合法回退(不属于「重置为初始态」),因此只拦截 answer。
|
|
64
|
+
*/
|
|
65
|
+
export function advancePhase(current, next) {
|
|
66
|
+
if (current === "answer")
|
|
67
|
+
return next;
|
|
68
|
+
if (next === "answer")
|
|
69
|
+
return current;
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
35
72
|
export function buildTaskPhaseDirective(phase) {
|
|
36
73
|
const rules = {
|
|
37
74
|
answer: "当前阶段:回答。直接处理当前问题,不把普通问答扩张成任务执行。",
|
|
@@ -44,13 +81,19 @@ export function buildTaskPhaseDirective(phase) {
|
|
|
44
81
|
};
|
|
45
82
|
return `〔任务阶段〕${rules[phase]}`;
|
|
46
83
|
}
|
|
47
|
-
|
|
48
|
-
|
|
84
|
+
/**
|
|
85
|
+
* 工具证据提示:只在出现失败或结果未知时给出,且不带计数。
|
|
86
|
+
*
|
|
87
|
+
* 旧版把「本轮已调用 N 次工具」写进系统提示词段落,N 每步递增——于是每一轮对话
|
|
88
|
+
* 里系统提示词被改写数十次(实测一轮 37 次工具调用产生 28 份不同的系统提示词),
|
|
89
|
+
* 前缀缓存几乎每步作废。计数对模型没有增量信息(工具结果本身就在上下文里),
|
|
90
|
+
* 真正需要提醒的只有「失败/未知不等于完成」。判定改为常量文本后,一轮内至多变
|
|
91
|
+
* 一次,且注册在 runtime-context 通道(不进 system 串、不作废前缀)。
|
|
92
|
+
*/
|
|
93
|
+
export function buildToolFailureNotice(input) {
|
|
94
|
+
if (input.failures === 0 && input.unknown === 0)
|
|
49
95
|
return null;
|
|
50
|
-
|
|
51
|
-
return `〔工具证据〕本轮已调用 ${input.calls} 次工具,其中成功 ${input.successes} 次、失败 ${input.failures} 次、结果未知 ${input.unknown} 次。失败或未知结果不能当成完成;先归因或检查实际状态,再决定是否重试。`;
|
|
52
|
-
}
|
|
53
|
-
return `〔工具证据〕本轮已调用 ${input.calls} 次工具且均返回成功,但工具成功只证明动作执行成功,不等于用户目标已经达成;仍需检查实际结果、兼容性和副作用。`;
|
|
96
|
+
return "〔工具证据〕本轮有工具调用失败或结果未知。失败或未知结果不能当成完成:先归因或检查实际状态,再决定是否重试。";
|
|
54
97
|
}
|
|
55
98
|
/**
|
|
56
99
|
* 长会话不重述整段历史,只提醒模型以最新状态为准。
|
package/lib/host/reflection.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 反思日志:会话结束时评估对话是否遵守任务执行协议,写入本地存储。
|
|
3
3
|
*
|
|
4
4
|
* 零用户感知 token:会话结束后(session/disposed)在空闲时间跑一次小模型调用,
|
|
5
5
|
* 读完对话片段后给四条规则各打 0-2 分并附一句备注,写到 `lume_reflection` 域。
|
|
@@ -33,7 +33,7 @@ export class ReflectionStore {
|
|
|
33
33
|
await this.#table.put(sessionId, entry);
|
|
34
34
|
this.#feedbackCache = null; // 新日志可能改变反馈结论,缓存失效
|
|
35
35
|
}
|
|
36
|
-
/** 把旧版 p0~p3
|
|
36
|
+
/** 把旧版 p0~p3 日志迁移为公开的协议字段;幂等且只处理旧记录。 */
|
|
37
37
|
async migrateLegacy() {
|
|
38
38
|
let migrated = 0;
|
|
39
39
|
for (const key of this.#table.keys()) {
|
|
@@ -83,7 +83,7 @@ export class ReflectionStore {
|
|
|
83
83
|
}
|
|
84
84
|
export const REFLECTION_SYSTEM = [
|
|
85
85
|
"你是一个冷静的复盘评估器。下面会给你一段与用户对话的片段。",
|
|
86
|
-
"
|
|
86
|
+
"请评估其中的助手是否遵守了任务执行协议,每项打 0/1/2 分(0=明显违反,1=一般,2=良好):",
|
|
87
87
|
"",
|
|
88
88
|
"上下文管理:是否理解并保留目标、约束、状态、关键决策和已排除假设",
|
|
89
89
|
"计划与门控:是否拆解任务、先调研再执行,并按风险自适应投入",
|
package/lib/host/thinking.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 任务执行协议文本与分流判据。
|
|
3
3
|
*
|
|
4
4
|
* 从 index.ts 抽出:协议是行为约束的核心资产,独立成模块才能被测试直接锁定
|
|
5
5
|
* (此前埋在插件入口里,任何措辞回归都只能靠人眼发现)。
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - THINKING_REASONING_TEXT 精简协议:推理型模型的任务轮,省掉它天生具备的
|
|
11
11
|
* 计划/分解条款,只保留行为约束、证据纪律与事实边界
|
|
12
12
|
*/
|
|
13
|
-
/**
|
|
13
|
+
/** 任务执行协议:完整版。 */
|
|
14
14
|
export const THINKING_TEXT = `[任务执行协议]
|
|
15
15
|
|
|
16
16
|
你应遵循以下公开的工程工作协议。它约束任务如何被完成,不要求输出隐藏的逐步思考过程;对外只给出必要的结论、计划、变更和验证结果。
|
package/lib/index.js
CHANGED
|
@@ -34,7 +34,8 @@ import { SessionRuntimeStore } from "./host/session-runtime.js";
|
|
|
34
34
|
import { isCompactionCheckpoint } from "./host/compaction.js";
|
|
35
35
|
import { LUME_REFLECTION_SPEC, ReflectionStore, buildReflectionPrompt, parseReflectionScore } from "./host/reflection.js";
|
|
36
36
|
import { appendLumeLog } from "./host/diag.js";
|
|
37
|
-
import { buildAlignmentCorrection, buildCompactionNotice, buildInteractionDirective, buildLongSessionGuard, buildSessionAnchor, buildTaskPhaseDirective,
|
|
37
|
+
import { advancePhase, buildAlignmentCorrection, buildCompactionNotice, buildInteractionDirective, buildLongSessionGuard, buildSessionAnchor, buildTaskPhaseDirective, buildToolFailureNotice, classifyInteraction, isUserAuthored, taskPhaseForMode } from "./host/protocol.js";
|
|
38
|
+
import { buildDocumentDirective, probeDocumentCapabilities } from "./host/documents.js";
|
|
38
39
|
import { REASONING_MODEL_RE, TASK_SIGNAL_RE, selectThinkingProtocol } from "./host/thinking.js";
|
|
39
40
|
/** schemastery → domainTable 形参的桥接(与 identity.ts 同款)。 */
|
|
40
41
|
const recordSchema = zodLike;
|
|
@@ -63,6 +64,10 @@ const LUME_THINKING_ORDER = 1;
|
|
|
63
64
|
const LUME_PERSONA_ORDER = 10000;
|
|
64
65
|
const LUME_BOUNDARY_SECTION = "lume:boundary";
|
|
65
66
|
const LUME_BOUNDARY_ORDER = 10100;
|
|
67
|
+
/** 工具失败提示:走 runtime-context 通道(渲染成对话尾部的一条消息),
|
|
68
|
+
* 不进 system 串——避免会变的内容每步作废前缀缓存。 */
|
|
69
|
+
const LUME_TOOL_NOTICE_CONTEXT = "lume:tool-notice";
|
|
70
|
+
const LUME_TOOL_NOTICE_ORDER = 10150;
|
|
66
71
|
const MAX_SESSIONS = 200;
|
|
67
72
|
const SWITCH_BOUNDARY_TURNS = 2;
|
|
68
73
|
/** Cordis 插件名 */
|
|
@@ -434,19 +439,17 @@ export function apply(ctx, config = {}) {
|
|
|
434
439
|
appendLumeLog(`[${sid}] 检测到上下文压缩检查点(第 ${st.turnIndex} 轮)`);
|
|
435
440
|
break;
|
|
436
441
|
}
|
|
442
|
+
// 只有真实用户消息能定义本轮意图。宿主快照(@deepseek-ai/dsh-system-prompt)、
|
|
443
|
+
// 工作区指令(agent-instructions)、技能目录(skill-catalog)都经这条通道投递,
|
|
444
|
+
// 曾被当成用户发言:覆盖真实请求,并把模式从「执行」冲成「问答」。
|
|
445
|
+
if (!isUserAuthored(event.data))
|
|
446
|
+
break;
|
|
437
447
|
const text = messageText(event.data);
|
|
438
448
|
if (text) {
|
|
439
449
|
const normalized = text.trim().replace(/\s+/g, " ").slice(0, 240);
|
|
440
450
|
const explicitCorrection = /不是这个意思|不是我说的|你理解错|答非所问|听不懂|我说的是|我指的是|不对|错了|别这样|重新来/i.test(text);
|
|
441
451
|
const repeatedRequest = normalized.length >= 5 && st.recentUserQueries.includes(normalized);
|
|
442
452
|
st.userText = text;
|
|
443
|
-
st.lastQuery = text;
|
|
444
|
-
st.interactionMode = classifyInteraction(text);
|
|
445
|
-
st.taskPhase = taskPhaseForMode(st.interactionMode);
|
|
446
|
-
st.toolCalls = 0;
|
|
447
|
-
st.toolSuccesses = 0;
|
|
448
|
-
st.toolFailures = 0;
|
|
449
|
-
st.toolUnknown = 0;
|
|
450
453
|
st.alignmentCorrection = explicitCorrection
|
|
451
454
|
? buildAlignmentCorrection("user-correction")
|
|
452
455
|
: repeatedRequest
|
|
@@ -458,6 +461,8 @@ export function apply(ctx, config = {}) {
|
|
|
458
461
|
st.recentTurns.push(`用户: ${text.slice(0, 300)}`);
|
|
459
462
|
if (st.recentTurns.length > 12)
|
|
460
463
|
st.recentTurns.shift();
|
|
464
|
+
// 模式/阶段的冻结统一由 resolveIntent(组装时读会话权威历史)负责,
|
|
465
|
+
// 这里只记账:两处都写会让「同一条消息」被判定为不同轮而反复重算。
|
|
461
466
|
}
|
|
462
467
|
break;
|
|
463
468
|
}
|
|
@@ -474,7 +479,7 @@ export function apply(ctx, config = {}) {
|
|
|
474
479
|
case "tool/call": {
|
|
475
480
|
st.toolCalls++;
|
|
476
481
|
if (st.interactionMode === "execute")
|
|
477
|
-
st.taskPhase = "execute";
|
|
482
|
+
st.taskPhase = advancePhase(st.taskPhase, "execute");
|
|
478
483
|
break;
|
|
479
484
|
}
|
|
480
485
|
case "tool/result": {
|
|
@@ -489,7 +494,7 @@ export function apply(ctx, config = {}) {
|
|
|
489
494
|
else
|
|
490
495
|
st.toolSuccesses++;
|
|
491
496
|
if (st.interactionMode === "execute")
|
|
492
|
-
st.taskPhase = unknownResult || explicitError ? "diagnose" : "verify";
|
|
497
|
+
st.taskPhase = advancePhase(st.taskPhase, unknownResult || explicitError ? "diagnose" : "verify");
|
|
493
498
|
break;
|
|
494
499
|
}
|
|
495
500
|
case "compaction/summary": {
|
|
@@ -525,7 +530,7 @@ export function apply(ctx, config = {}) {
|
|
|
525
530
|
? "〔上轮交付复核〕上一轮执行回复没有给出可见的验证证据。本轮若继续处理同一任务,先确认上轮变更是否真实生效,再继续扩大范围。"
|
|
526
531
|
: null;
|
|
527
532
|
if (st.interactionMode === "execute")
|
|
528
|
-
st.taskPhase = st.toolFailures > 0 || st.toolUnknown > 0 ? "diagnose" : claimsVerification ? "deliver" : "verify";
|
|
533
|
+
st.taskPhase = advancePhase(st.taskPhase, st.toolFailures > 0 || st.toolUnknown > 0 ? "diagnose" : claimsVerification ? "deliver" : "verify");
|
|
529
534
|
// 即时对齐只影响当前轮;下一轮重新根据用户消息判断,避免纠偏条款滞留。
|
|
530
535
|
st.alignmentCorrection = null;
|
|
531
536
|
// 风格泄漏检测挂在 turn/end(该事件已被窗口机制验证可靠;assistant/message
|
|
@@ -654,7 +659,50 @@ export function apply(ctx, config = {}) {
|
|
|
654
659
|
}));
|
|
655
660
|
}, "lume: persona tools");
|
|
656
661
|
// ── 人设五段式注入 + 切换播报 ──
|
|
657
|
-
|
|
662
|
+
/**
|
|
663
|
+
* 权威意图解析:与宿主会话历史对账,而不是只信事件缓存。
|
|
664
|
+
*
|
|
665
|
+
* 事件投递与首次提示词组装存在时序差——实测同一轮的 step 1 仍带上一轮的模式,
|
|
666
|
+
* step 2 才切到本轮分类,系统提示词因此在轮内变化、前缀缓存每步作废。这里直接
|
|
667
|
+
* 读会话投影出的消息(`deriveMessages`,宿主侧带增量缓存),取最后一条真实用户
|
|
668
|
+
* 消息作为本轮意图;messageId 未变时零成本短路,保证一轮内只冻结一次。
|
|
669
|
+
*/
|
|
670
|
+
function resolveIntent(context, st) {
|
|
671
|
+
const session = context?.agent?.session;
|
|
672
|
+
const messages = typeof session?.deriveMessages === "function" ? session.deriveMessages() : [];
|
|
673
|
+
let text = null;
|
|
674
|
+
let messageId = "";
|
|
675
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
676
|
+
const msg = messages[i];
|
|
677
|
+
if (!isUserAuthored(msg))
|
|
678
|
+
continue;
|
|
679
|
+
const candidate = messageText(msg);
|
|
680
|
+
if (!candidate)
|
|
681
|
+
continue;
|
|
682
|
+
text = candidate;
|
|
683
|
+
messageId = String(msg.id ?? "");
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
// 权威会话历史不可用(旧宿主、非标准 session 对象)时退回事件记录的用户文本:
|
|
687
|
+
// 否则意图会永远停在默认的「问答」,比改动前更差。
|
|
688
|
+
if (text === null && st.userText) {
|
|
689
|
+
text = st.userText;
|
|
690
|
+
messageId = `text:${st.userText.slice(0, 120)}`;
|
|
691
|
+
}
|
|
692
|
+
if (text !== null && (st.intent === null || st.intent.messageId !== messageId)) {
|
|
693
|
+
const mode = classifyInteraction(text);
|
|
694
|
+
st.intent = { turnIndex: st.turnIndex, messageId, text };
|
|
695
|
+
st.lastQuery = text;
|
|
696
|
+
st.interactionMode = mode;
|
|
697
|
+
st.taskPhase = taskPhaseForMode(mode);
|
|
698
|
+
st.toolCalls = 0;
|
|
699
|
+
st.toolSuccesses = 0;
|
|
700
|
+
st.toolFailures = 0;
|
|
701
|
+
st.toolUnknown = 0;
|
|
702
|
+
}
|
|
703
|
+
return { text: st.intent?.text ?? st.lastQuery ?? "", mode: st.interactionMode };
|
|
704
|
+
}
|
|
705
|
+
function buildSessionText(sid, context) {
|
|
658
706
|
if (!currentStore)
|
|
659
707
|
return "";
|
|
660
708
|
const st = runtime.get(sid);
|
|
@@ -680,18 +728,49 @@ export function apply(ctx, config = {}) {
|
|
|
680
728
|
: null;
|
|
681
729
|
// 播报改由独立的尾部 section 渲染(LUME_BOUNDARY_SECTION),人设段不再内联
|
|
682
730
|
st.activeBoundary = boundaryText;
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
731
|
+
const memories = personaName ? identity?.getMemory(personaName) ?? [] : [];
|
|
732
|
+
const styleRules = personaName ? identity?.getStyleRules(personaName) ?? [] : [];
|
|
733
|
+
const corpusPins = personaName ? identity?.getCorpusPins(personaName) ?? [] : [];
|
|
734
|
+
const { text: query } = resolveIntent(context, st);
|
|
735
|
+
// 一轮一算:注入段按查询做 top-k 检索,查询或记忆每步抖动会让整段改写,
|
|
736
|
+
// 系统提示词随之在轮内变化(实测一轮产生 28 份),前缀缓存每步作废。
|
|
737
|
+
// 指纹覆盖 persona、查询、记忆/风格/语料与注入配置;人设切换、边界窗口、
|
|
738
|
+
// 记忆写入都会改变指纹而自动失效。
|
|
739
|
+
const last = (list, field) => list.length > 0 ? list[list.length - 1]?.[field] : "";
|
|
740
|
+
const key = [
|
|
741
|
+
String(personaName),
|
|
742
|
+
String(persona?.name ?? ""),
|
|
743
|
+
query,
|
|
744
|
+
String(st.turnIndex),
|
|
745
|
+
String(memories.length),
|
|
746
|
+
String(last(memories, "text")),
|
|
747
|
+
String(styleRules.length),
|
|
748
|
+
String(last(styleRules, "rule")),
|
|
749
|
+
String(corpusPins.length),
|
|
750
|
+
String(last(corpusPins, "assistant")),
|
|
751
|
+
String(boundaryText ?? ""),
|
|
752
|
+
String(sampleCount),
|
|
753
|
+
String(sampleMin),
|
|
754
|
+
String(memoryInject),
|
|
755
|
+
String(styleInject),
|
|
756
|
+
strategy,
|
|
757
|
+
].join("\u0001");
|
|
758
|
+
let text = st.personaCache !== null && st.personaCache.key === key ? st.personaCache.text : null;
|
|
759
|
+
if (text === null) {
|
|
760
|
+
text = buildPersonaSection({
|
|
761
|
+
persona,
|
|
762
|
+
profileName: personaName ? registry.profileNameOf(personaName) : null,
|
|
763
|
+
memories,
|
|
764
|
+
styleRules,
|
|
765
|
+
corpusPins,
|
|
766
|
+
query,
|
|
767
|
+
turnIndex: st.turnIndex,
|
|
768
|
+
sessionKey: sid,
|
|
769
|
+
boundaryText: null,
|
|
770
|
+
config: { sampleCount, sampleMin, memoryInject, styleInject, strategy },
|
|
771
|
+
});
|
|
772
|
+
st.personaCache = { turnIndex: st.turnIndex, key, text };
|
|
773
|
+
}
|
|
695
774
|
st.lastInjected = personaName;
|
|
696
775
|
if (greeting)
|
|
697
776
|
st.switchGreetingPending = false;
|
|
@@ -732,7 +811,7 @@ export function apply(ctx, config = {}) {
|
|
|
732
811
|
order: personaOrder,
|
|
733
812
|
text: (context) => {
|
|
734
813
|
const sid = context.agent?.session?.id ?? context.agent?.id;
|
|
735
|
-
return sid ? buildSessionText(String(sid)) : "";
|
|
814
|
+
return sid ? buildSessionText(String(sid), context) : "";
|
|
736
815
|
},
|
|
737
816
|
}), "lume.persona-section()");
|
|
738
817
|
ctx.effect(() => ctx.systemPrompt.section({
|
|
@@ -751,7 +830,10 @@ export function apply(ctx, config = {}) {
|
|
|
751
830
|
text: (context) => {
|
|
752
831
|
const sid = context.agent?.session?.id ?? context.agent?.id;
|
|
753
832
|
const st = sid ? runtime.get(String(sid)) : null;
|
|
754
|
-
|
|
833
|
+
// 意图取自会话权威历史(resolveIntent),不再读事件缓存里的最新文本:
|
|
834
|
+
// 后者会被注入消息覆盖,且首步可能还带着上一轮的值,导致模式在轮内漂移。
|
|
835
|
+
const intent = st !== null && sid ? resolveIntent(context, st) : null;
|
|
836
|
+
const query = intent?.text ?? "";
|
|
755
837
|
// 已知推理型模型具备计划能力,任务轮只保留变更、验证、归因和复核约束;
|
|
756
838
|
// 路由未知时使用完整版,避免误判造成能力退化。
|
|
757
839
|
const base = selectThinkingProtocol({
|
|
@@ -762,17 +844,46 @@ export function apply(ctx, config = {}) {
|
|
|
762
844
|
// 才能在「不使用人设」的纯任务会话里也生效。
|
|
763
845
|
const correction = st?.protocolCorrection;
|
|
764
846
|
const reflectionHint = reflectionStore?.getFeedback() ?? null;
|
|
765
|
-
const
|
|
847
|
+
const mode = intent?.mode ?? "question";
|
|
848
|
+
const route = buildInteractionDirective(mode);
|
|
766
849
|
const longSession = buildLongSessionGuard(st?.turnIndex ?? 0);
|
|
767
|
-
const anchor = buildSessionAnchor(st?.turnIndex ?? 0,
|
|
850
|
+
const anchor = buildSessionAnchor(st?.turnIndex ?? 0, mode, query, st?.recentTurns ?? []);
|
|
768
851
|
const alignment = st?.alignmentCorrection;
|
|
769
852
|
const postTurnReview = st?.postTurnReview;
|
|
770
853
|
const phase = buildTaskPhaseDirective(st?.taskPhase ?? "answer");
|
|
771
|
-
|
|
854
|
+
// 文档能力感知:DSH 本身不带 Office/PDF 读写能力,第三方文档工具插件
|
|
855
|
+
// 装与不装由用户决定。按探测结果分叉——有工具就约束走工具与回读验证,
|
|
856
|
+
// 没工具就约束「如实说明边界、不要硬解二进制」。只在文档任务轮注入,
|
|
857
|
+
// 且判据取自冻结的意图文本,因此轮内稳定,不会在轮中作废前缀缓存。
|
|
858
|
+
const document = buildDocumentDirective({ query, capabilities: probeDocumentCapabilities(ctx.tools, context.agent) });
|
|
859
|
+
// 工具失败提示已移到 runtime-context 通道(见 lume.tool-notice-context):
|
|
860
|
+
// 它一出现就固定不变,但会变的内容不该待在 system 串里——那会作废前缀缓存。
|
|
772
861
|
// 压缩重锚:宿主的 preset 隔离域负责压缩,Lume 只能观察事件;
|
|
773
862
|
// 在压缩后一轮提醒「摘要不是完整历史」。
|
|
774
863
|
const compactionNotice = st?.compaction ? buildCompactionNotice(st.compaction, st.turnIndex) : null;
|
|
775
|
-
return [base, route, phase, longSession, anchor, alignment, postTurnReview,
|
|
864
|
+
return [base, route, phase, document, longSession, anchor, alignment, postTurnReview, compactionNotice, correction, reflectionHint].filter(Boolean).join("\n\n");
|
|
776
865
|
},
|
|
777
866
|
}), "lume.thinking-section()");
|
|
867
|
+
// 工具失败提示走 runtime-context 通道:宿主把它渲染成对话尾部的一条消息,
|
|
868
|
+
// 而不是拼进 system 串。system 串只要变化就会写一条新的 request/header,
|
|
869
|
+
// 既在界面上多出一行「系统提示词」,也让前缀缓存从系统提示词处整段失效。
|
|
870
|
+
// `systemPrompt.context` 是宿主较新版本才有的 API,缺失时静默跳过(旧宿主下
|
|
871
|
+
// 只是失去这条提示,不影响其余功能)。
|
|
872
|
+
ctx.effect(() => {
|
|
873
|
+
if (typeof ctx.systemPrompt?.context !== "function") {
|
|
874
|
+
ctx.logger?.warn?.("lume: 当前宿主不支持 systemPrompt.context,工具失败提示已跳过(不影响其余功能)");
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
return ctx.systemPrompt.context({
|
|
878
|
+
name: LUME_TOOL_NOTICE_CONTEXT,
|
|
879
|
+
order: LUME_TOOL_NOTICE_ORDER,
|
|
880
|
+
text: (context) => {
|
|
881
|
+
const sid = context.agent?.session?.id ?? context.agent?.id;
|
|
882
|
+
const st = sid ? runtime.get(String(sid)) : null;
|
|
883
|
+
if (!st)
|
|
884
|
+
return "";
|
|
885
|
+
return buildToolFailureNotice({ failures: st.toolFailures, unknown: st.toolUnknown }) ?? "";
|
|
886
|
+
},
|
|
887
|
+
});
|
|
888
|
+
}, "lume.tool-notice-context()");
|
|
778
889
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lume-dsh-plugin",
|
|
3
|
-
"description": "微光 (Lume) — DSH Desktop
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "微光 (Lume) — DSH Desktop 增强插件:给会话装上工程纪律与真实关系。纪律层约束「如何正确完成任务」——意图路由、阶段门控、真实工具证据、交付前复核、文档能力感知;人设层塑造「以何种风格表达」——从聊天记录、小说、剧本、设定文档蒸馏具名角色,长期记忆与风格随对话演进。约束按需注入,闲聊不额外付 token。",
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -44,7 +44,12 @@
|
|
|
44
44
|
"persona",
|
|
45
45
|
"personality",
|
|
46
46
|
"identity",
|
|
47
|
-
"memory"
|
|
47
|
+
"memory",
|
|
48
|
+
"protocol",
|
|
49
|
+
"task-execution",
|
|
50
|
+
"distillation",
|
|
51
|
+
"人设",
|
|
52
|
+
"记忆"
|
|
48
53
|
],
|
|
49
54
|
"engines": {
|
|
50
55
|
"node": ">=20"
|