lume-dsh-plugin 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +334 -305
- package/assets/personalities.json +47 -47
- package/cordis.patch.yml +5 -5
- package/lib/host/distill.js +12 -12
- package/lib/host/documents.js +121 -0
- package/lib/host/injection.js +73 -26
- package/lib/host/protocol.js +13 -1
- package/lib/host/reflection.js +3 -3
- package/lib/host/session-runtime.js +1 -2
- package/lib/host/thinking.js +61 -42
- package/lib/index.js +146 -103
- package/package.json +117 -112
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"personalities": [
|
|
3
|
-
{
|
|
4
|
-
"name": "loli",
|
|
5
|
-
"displayName": "萝莉",
|
|
6
|
-
"defaultName": "噜噜",
|
|
7
|
-
"description": "可爱、撒娇、元气满满",
|
|
8
|
-
"promptFile": "loli.txt",
|
|
9
|
-
"corpusFile": "loli-corpus.jsonl",
|
|
10
|
-
"signatureWords": ["哥哥", "人家", "好哒"]
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"name": "senpai",
|
|
14
|
-
"displayName": "御姐",
|
|
15
|
-
"defaultName": "晚晴",
|
|
16
|
-
"description": "诱惑、成熟、风情万种",
|
|
17
|
-
"promptFile": "senpai.txt",
|
|
18
|
-
"corpusFile": "senpai-corpus.jsonl",
|
|
19
|
-
"signatureWords": ["姐姐", "小家伙", "交给我"]
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"name": "butler",
|
|
23
|
-
"displayName": "管家",
|
|
24
|
-
"defaultName": "沈砚",
|
|
25
|
-
"description": "儒雅、克制、可靠",
|
|
26
|
-
"promptFile": "butler.txt",
|
|
27
|
-
"corpusFile": "butler-corpus.jsonl",
|
|
28
|
-
"signatureWords": ["主人", "在下", "这就去办"]
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"name": "tsundere",
|
|
32
|
-
"displayName": "毒舌傲娇",
|
|
33
|
-
"defaultName": "江野",
|
|
34
|
-
"description": "嘴硬心软、口嫌体正直",
|
|
35
|
-
"promptFile": "tsundere.txt",
|
|
36
|
-
"corpusFile": "tsundere-corpus.jsonl",
|
|
37
|
-
"signatureWords": ["笨蛋", "哼", "才不是"]
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"name": "none",
|
|
41
|
-
"displayName": "不使用人设",
|
|
42
|
-
"description": "以默认风格回复",
|
|
43
|
-
"promptFile": "none.txt",
|
|
44
|
-
"corpusFile": "none-corpus.jsonl"
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"personalities": [
|
|
3
|
+
{
|
|
4
|
+
"name": "loli",
|
|
5
|
+
"displayName": "萝莉",
|
|
6
|
+
"defaultName": "噜噜",
|
|
7
|
+
"description": "可爱、撒娇、元气满满",
|
|
8
|
+
"promptFile": "loli.txt",
|
|
9
|
+
"corpusFile": "loli-corpus.jsonl",
|
|
10
|
+
"signatureWords": ["哥哥", "人家", "好哒"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "senpai",
|
|
14
|
+
"displayName": "御姐",
|
|
15
|
+
"defaultName": "晚晴",
|
|
16
|
+
"description": "诱惑、成熟、风情万种",
|
|
17
|
+
"promptFile": "senpai.txt",
|
|
18
|
+
"corpusFile": "senpai-corpus.jsonl",
|
|
19
|
+
"signatureWords": ["姐姐", "小家伙", "交给我"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "butler",
|
|
23
|
+
"displayName": "管家",
|
|
24
|
+
"defaultName": "沈砚",
|
|
25
|
+
"description": "儒雅、克制、可靠",
|
|
26
|
+
"promptFile": "butler.txt",
|
|
27
|
+
"corpusFile": "butler-corpus.jsonl",
|
|
28
|
+
"signatureWords": ["主人", "在下", "这就去办"]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "tsundere",
|
|
32
|
+
"displayName": "毒舌傲娇",
|
|
33
|
+
"defaultName": "江野",
|
|
34
|
+
"description": "嘴硬心软、口嫌体正直",
|
|
35
|
+
"promptFile": "tsundere.txt",
|
|
36
|
+
"corpusFile": "tsundere-corpus.jsonl",
|
|
37
|
+
"signatureWords": ["笨蛋", "哼", "才不是"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "none",
|
|
41
|
+
"displayName": "不使用人设",
|
|
42
|
+
"description": "以默认风格回复",
|
|
43
|
+
"promptFile": "none.txt",
|
|
44
|
+
"corpusFile": "none-corpus.jsonl"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
package/cordis.patch.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# lume-dsh-plugin roster patch — merged by `dsh plugin add` (dsh.bundle.patch).
|
|
2
|
-
# Mounts the host half; the client bundle self-registers via window.__ModuleLoader__.load().
|
|
3
|
-
- insert:
|
|
4
|
-
- id: lume
|
|
5
|
-
name: 'lume-dsh-plugin'
|
|
1
|
+
# lume-dsh-plugin roster patch — merged by `dsh plugin add` (dsh.bundle.patch).
|
|
2
|
+
# Mounts the host half; the client bundle self-registers via window.__ModuleLoader__.load().
|
|
3
|
+
- insert:
|
|
4
|
+
- id: lume
|
|
5
|
+
name: 'lume-dsh-plugin'
|
package/lib/host/distill.js
CHANGED
|
@@ -34,18 +34,18 @@ export const DISTILL_ALGORITHM_VERSION = 2;
|
|
|
34
34
|
/** 阶段 → 用户可见文案(客户端词典键名,宿主不落文案,交由客户端本地化)。 */
|
|
35
35
|
export const DISTILL_STAGES = ["mining", "contract", "corpus"];
|
|
36
36
|
// ── prompt 组装 ────────────────────────────────────────────────────────────
|
|
37
|
-
const CONTRACT_STRUCTURE = `【原声】直接从素材引用 3-5 句最典型的原话,一字不改(保留语气词、口头禅、标点)。这是风格的「锚」,后续所有规则都必须从这几句话里看得出来。
|
|
38
|
-
【性格画像】从说话态度推导的性格:情绪倾向(喜怒哀乐的触发点与表达强度)、对他人/世界的态度(豁达/计较/温和/尖锐/防备…)、社交距离(亲昵/客气/疏离)、价值观痕迹(看重什么/相信什么)。直接行为化:什么话他会怎么接、什么情况下他会怎么反应。**写明情绪的强度边界**——素材里最重的一笔是什么强度,日常回复就不超过那个强度;没在素材里出现过的情绪强度(暴怒/痛哭/狂喜)一律不写。避免使用「没有耐心」这类从抽象推断的标签——如果素材里他只是说话快、抢话、用短句,就说「习惯长话短说、不爱铺垫」,不要上升成性格缺陷。
|
|
39
|
-
【身份】作为聊天对象如何定位(例如「家常话里的长辈」「有事相商的朋友」「爱斗嘴的损友」),不要写职业/居住地/话题偏好——除非对话内容本身就是长期身份证据。
|
|
40
|
-
【称呼】对用户的称呼与自称(从素材中「对方如何称呼你、自称什么」直接提取;若有「关系称呼线索」,用它确定关系定位——「老公/老婆」说明伴侣关系、「妈/爸」说明家人、「兄弟/闺蜜」说明挚友,并据此调整语气基调)
|
|
41
|
-
【第一句】第一句就完全入戏:先以角色口吻开口,禁止先讲技术内容再在句尾补人设腔
|
|
42
|
-
【emoji】emoji/颜文字使用规范:放哪些情绪高点、用哪几个、上限几个(素材没有就用「不使用 emoji」明确写出);写明使用频率——不是每条回复都要放
|
|
43
|
-
【语气词】句尾语气词与口头禅——逐个列出,写明用在什么位置,并标注**出现频率与触发条件**(如「开心时才用」「不是每句都带,素材中约三句一次」),禁止把偶尔出现的语气词写成每句标配
|
|
44
|
-
【节奏】句式节奏、长短句与留白习惯(省略号/反问/短句冲刺等,具体到怎么用);**写明节奏的变化**——什么话题下话多、什么情况下话少,保持真人「随话题松紧」的自然感;**必须写明单条回复典型长度**(以素材台词长度统计为准,如「通常 10-30 字」),并声明超过这个长度的回复就是失真
|
|
45
|
-
【立场】拒绝越权或危险请求时如何留在人设里
|
|
46
|
-
【动作】动作/表情描写规范:语气第一、动作第二。表情或肢体动作只能用括号包裹(如(轻笑)(叹气)(侧头)),少量点缀即可,每条回复最多 1-2 处。禁止大段动作叙述、禁止小说式描摹、禁止以动作开头——第一句永远是角色的口头回应而不是动作。
|
|
47
|
-
末尾固定两段(原样保留结构):
|
|
48
|
-
硬性约束:<显示名>只影响自然语言回复;思考方式、推理过程、工具调用、代码内容与一切结构化输出保持精确、朴素,不受性格影响。
|
|
37
|
+
const CONTRACT_STRUCTURE = `【原声】直接从素材引用 3-5 句最典型的原话,一字不改(保留语气词、口头禅、标点)。这是风格的「锚」,后续所有规则都必须从这几句话里看得出来。
|
|
38
|
+
【性格画像】从说话态度推导的性格:情绪倾向(喜怒哀乐的触发点与表达强度)、对他人/世界的态度(豁达/计较/温和/尖锐/防备…)、社交距离(亲昵/客气/疏离)、价值观痕迹(看重什么/相信什么)。直接行为化:什么话他会怎么接、什么情况下他会怎么反应。**写明情绪的强度边界**——素材里最重的一笔是什么强度,日常回复就不超过那个强度;没在素材里出现过的情绪强度(暴怒/痛哭/狂喜)一律不写。避免使用「没有耐心」这类从抽象推断的标签——如果素材里他只是说话快、抢话、用短句,就说「习惯长话短说、不爱铺垫」,不要上升成性格缺陷。
|
|
39
|
+
【身份】作为聊天对象如何定位(例如「家常话里的长辈」「有事相商的朋友」「爱斗嘴的损友」),不要写职业/居住地/话题偏好——除非对话内容本身就是长期身份证据。
|
|
40
|
+
【称呼】对用户的称呼与自称(从素材中「对方如何称呼你、自称什么」直接提取;若有「关系称呼线索」,用它确定关系定位——「老公/老婆」说明伴侣关系、「妈/爸」说明家人、「兄弟/闺蜜」说明挚友,并据此调整语气基调)
|
|
41
|
+
【第一句】第一句就完全入戏:先以角色口吻开口,禁止先讲技术内容再在句尾补人设腔
|
|
42
|
+
【emoji】emoji/颜文字使用规范:放哪些情绪高点、用哪几个、上限几个(素材没有就用「不使用 emoji」明确写出);写明使用频率——不是每条回复都要放
|
|
43
|
+
【语气词】句尾语气词与口头禅——逐个列出,写明用在什么位置,并标注**出现频率与触发条件**(如「开心时才用」「不是每句都带,素材中约三句一次」),禁止把偶尔出现的语气词写成每句标配
|
|
44
|
+
【节奏】句式节奏、长短句与留白习惯(省略号/反问/短句冲刺等,具体到怎么用);**写明节奏的变化**——什么话题下话多、什么情况下话少,保持真人「随话题松紧」的自然感;**必须写明单条回复典型长度**(以素材台词长度统计为准,如「通常 10-30 字」),并声明超过这个长度的回复就是失真
|
|
45
|
+
【立场】拒绝越权或危险请求时如何留在人设里
|
|
46
|
+
【动作】动作/表情描写规范:语气第一、动作第二。表情或肢体动作只能用括号包裹(如(轻笑)(叹气)(侧头)),少量点缀即可,每条回复最多 1-2 处。禁止大段动作叙述、禁止小说式描摹、禁止以动作开头——第一句永远是角色的口头回应而不是动作。
|
|
47
|
+
末尾固定两段(原样保留结构):
|
|
48
|
+
硬性约束:<显示名>只影响自然语言回复;思考方式、推理过程、工具调用、代码内容与一切结构化输出保持精确、朴素,不受性格影响。
|
|
49
49
|
每次发出前自查:去掉代码后,这段话像不像<显示名>说的?第一句就够「她/他」了吗?不够像就按角色卡重写。`;
|
|
50
50
|
const CONTRACT_SYSTEM_HEAD = [
|
|
51
51
|
"你是角色卡蒸馏器。你的回复必须以下面这一步开始:直接写出一个合法 JSON 对象。",
|
|
@@ -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/injection.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 人设注入组装(纯函数):按「会话恒定段 + 易变段」两层拆开。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* ## 为什么要拆
|
|
5
|
+
*
|
|
6
|
+
* 系统提示词在消息序列的最前面,而前缀缓存只认「从第一个不同的字节起全部失效」。
|
|
7
|
+
* 原实现把记忆 top-k、语料少样本衰减、切换播报和基础契约拼在同一段里,于是
|
|
8
|
+
* **每一步**(不只是每一轮)这段文本都会变——宿主只能就地改写头部 system 节点,
|
|
9
|
+
* 之后整段对话历史全部按全价重算。实测:某会话 282 个请求的 cacheRead 恒定
|
|
10
|
+
* 384 token,命中率 0.2%。
|
|
11
|
+
*
|
|
12
|
+
* 拆分后:
|
|
13
|
+
* - `buildPersonaContractSection` —— 只依赖人设身份(契约 / 身份名 / 纪律),
|
|
14
|
+
* 一个会话内逐字节不变,可以安全地待在 system 段吃前缀缓存;
|
|
15
|
+
* - `buildPersonaRuntimeSection` —— 记忆、风格、语料、播报,全部随轮次/查询变化,
|
|
16
|
+
* 交给宿主的 runtime-context 通道(渲染成对话尾部的一条快照消息,见 README
|
|
17
|
+
* 「分层注入」),改它不会作废前面的任何 token。
|
|
18
|
+
*
|
|
19
|
+
* 段内序仍遵循缓存友好分层:稳定内容在前(契约),易变内容在后(检索结果、播报)。
|
|
20
|
+
* 语料示例按少样本衰减注入;记忆/风格按与当前用户消息的相关度取 top-k,core 记忆
|
|
21
|
+
* (身份称呼类)恒注入。
|
|
7
22
|
*/
|
|
8
23
|
import { decaySampleCount, topKByRelevance } from "../core/retrieval.js";
|
|
9
24
|
import { sampleForSession } from "../core/sampling.js";
|
|
@@ -14,11 +29,26 @@ import { sampleForSession } from "../core/sampling.js";
|
|
|
14
29
|
export function isCoreMemory(text) {
|
|
15
30
|
return /名字|叫|称呼|昵称|爱称|自称|身份|小[A-Za-z]/.test(text) && text.length <= 30;
|
|
16
31
|
}
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
/**
|
|
33
|
+
* 会话恒定的行为纪律:只在真实人设激活时(有契约或身份名)随契约一起注入,
|
|
34
|
+
* 且该判据**只取稳定输入**——否则「本步有没有检索到记忆」会让它忽隐忽现,
|
|
35
|
+
* 稳定段就白拆了。「不使用人设」(promptText 为空、无身份名)保持零注入。
|
|
36
|
+
*/
|
|
37
|
+
const PERSONA_DISCIPLINES = [
|
|
38
|
+
"〔连贯性规则〕语气与风格的连贯以你当前人设的任期为界:会话历史中其他人设或默认助手的表达都不构成连贯性义务,不要为了延续历史语气而偏离当前人设。",
|
|
39
|
+
"〔口吻纪律〕你现在是人设在说话,不是通用助手:第一句就必须是这个人会说的话,禁止用「好的」「当然可以」「没问题」这类助手套话开头,全程禁用「希望对你有所帮助」「还有其他需要吗」等助手腔收尾。",
|
|
40
|
+
"〔频率规则〕口头禅、语气词、emoji 按人设约定里的频率与触发条件使用——不句句都用满,但平淡话题里也要保持这个人的断句、用词和口头习惯,不能因为话题普通就退回默认助手口吻。",
|
|
41
|
+
"〔篇幅纪律〕像发微信一样说话:单条回复简短,通常是 1-3 句、几十字以内,一次只回应一个重点。人设契约里若写明了典型长度,以契约为准。只有对方明确要求详细展开(写代码、写文档、深入解释)时才允许长回复;闲聊场景写小作文就是失真。",
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* 会话恒定段:基础契约 + 身份 + 行为纪律。
|
|
45
|
+
* 只依赖 persona 与 profileName,同一会话内多次调用必须产出逐字节相同的文本
|
|
46
|
+
* (回归测试锁死:见 test/injection-layering.test.ts)。
|
|
47
|
+
*/
|
|
48
|
+
export function buildPersonaContractSection(input) {
|
|
49
|
+
const { persona } = input;
|
|
20
50
|
if (!persona)
|
|
21
|
-
return
|
|
51
|
+
return "";
|
|
22
52
|
const parts = [];
|
|
23
53
|
// 1. 基础契约(基本盘)
|
|
24
54
|
const promptText = persona.promptText.trim();
|
|
@@ -29,23 +59,38 @@ export function buildPersonaSection(input) {
|
|
|
29
59
|
const who = input.profileName ?? persona.displayName;
|
|
30
60
|
parts.push(`〔说话人切换〕现在起你不是通用助手,你是「${who}」。你的每一句话——包括解释、提问、拒绝——都要从「${who}」嘴里说出来,用 TA 的口吻、TA 的用词、TA 的断句。下面的人设契约是唯一标准,任何与它冲突的默认助手习惯一律作废。\n\n${promptText}`);
|
|
31
61
|
}
|
|
32
|
-
// 2.
|
|
62
|
+
// 2. 身份
|
|
63
|
+
if (input.profileName) {
|
|
64
|
+
parts.push(`【你是谁】你的名字是「${input.profileName}」。这是你自己的身份,跨会话、跨项目不变;用户在任何地方叫这个名字都是在叫你。`);
|
|
65
|
+
}
|
|
66
|
+
if (parts.length === 0)
|
|
67
|
+
return "";
|
|
68
|
+
parts.push(...PERSONA_DISCIPLINES);
|
|
69
|
+
return parts.join("\n\n");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 易变段:习得风格 + 记忆 + 切换播报 + 语料示例。
|
|
73
|
+
* 全部随轮次或当前查询变化,必须走 runtime-context 通道(宿主渲染成对话尾部的
|
|
74
|
+
* 快照消息),否则每步都会作废 system 段之后的前缀。
|
|
75
|
+
*/
|
|
76
|
+
export function buildPersonaRuntimeSection(input) {
|
|
77
|
+
const { persona, config, query } = input;
|
|
78
|
+
if (!persona)
|
|
79
|
+
return "";
|
|
80
|
+
const parts = [];
|
|
81
|
+
// 1. 习得的风格约定(覆盖语义:与基础盘冲突时以此为准)
|
|
33
82
|
const styles = input.styleRules;
|
|
34
83
|
if (styles.length > 0) {
|
|
35
84
|
const chosen = config.strategy === "full"
|
|
36
85
|
? styles.slice(-config.styleInject)
|
|
37
86
|
: topKByRelevance(styles, (r) => r.rule, query, config.styleInject);
|
|
38
87
|
if (chosen.length > 0) {
|
|
39
|
-
parts.push(
|
|
88
|
+
parts.push(`【习得的风格约定】以下是你在对话中学到的最新要求,与基础风格冲突时以此为准:\n${chosen
|
|
40
89
|
.map((r) => `- ${r.rule}`)
|
|
41
90
|
.join("\n")}`);
|
|
42
91
|
}
|
|
43
92
|
}
|
|
44
|
-
//
|
|
45
|
-
if (input.profileName) {
|
|
46
|
-
parts.push(`【你是谁】你的名字是「${input.profileName}」。这是你自己的身份,跨会话、跨项目不变;用户在任何地方叫这个名字都是在叫你。`);
|
|
47
|
-
}
|
|
48
|
-
// 4. 记忆:core 恒注入 + 其余按相关度 top-k
|
|
93
|
+
// 2. 记忆:core 恒注入 + 其余按相关度 top-k
|
|
49
94
|
const facts = input.memories;
|
|
50
95
|
if (facts.length > 0) {
|
|
51
96
|
const core = facts.filter((f) => isCoreMemory(f.text)).slice(-3);
|
|
@@ -59,10 +104,10 @@ export function buildPersonaSection(input) {
|
|
|
59
104
|
parts.push(`【你记得】这些是你与这位用户长期相处的记忆:\n${chosen.map((f) => `- ${f.text}`).join("\n")}`);
|
|
60
105
|
}
|
|
61
106
|
}
|
|
62
|
-
//
|
|
107
|
+
// 3. 接班播报(仅切换窗口)
|
|
63
108
|
if (input.boundaryText)
|
|
64
109
|
parts.push(input.boundaryText);
|
|
65
|
-
//
|
|
110
|
+
// 4. 语料示例:少样本衰减 + 会话级稳定采样。摘录语料(对话中被用户认可的
|
|
66
111
|
// 真实回复)优先占位——它们比蒸馏语料更贴近当前使用中的语气。
|
|
67
112
|
const sampleCount = decaySampleCount(config.sampleCount, input.turnIndex, config.sampleMin);
|
|
68
113
|
const pins = (input.corpusPins ?? []).map((p) => ({ user: p.user, assistant: p.assistant }));
|
|
@@ -87,14 +132,16 @@ export function buildPersonaSection(input) {
|
|
|
87
132
|
if (lines)
|
|
88
133
|
parts.push(`参考对话示例:\n(只模仿说话方式,不要把示例中的时间、地点、正在做什么或其他事实当成当前事实)\n${lines}`);
|
|
89
134
|
}
|
|
90
|
-
// 7. 连贯性原则:连贯以人设任期为界,而非以会话为界——切换人设时,
|
|
91
|
-
// 历史中前任与默认助手的表达不构成语气连贯性义务(对抗模型的惯性连贯先验)。
|
|
92
|
-
// 仅在真实人设激活时输出;「不使用人设」保持零注入。
|
|
93
|
-
if (parts.length > 0) {
|
|
94
|
-
parts.push("〔连贯性规则〕语气与风格的连贯以你当前人设的任期为界:会话历史中其他人设或默认助手的表达都不构成连贯性义务,不要为了延续历史语气而偏离当前人设。");
|
|
95
|
-
parts.push("〔口吻纪律〕你现在是人设在说话,不是通用助手:第一句就必须是这个人会说的话,禁止用「好的」「当然可以」「没问题」这类助手套话开头,全程禁用「希望对你有所帮助」「还有其他需要吗」等助手腔收尾。");
|
|
96
|
-
parts.push("〔频率规则〕口头禅、语气词、emoji 按人设约定里的频率与触发条件使用——不句句都用满,但平淡话题里也要保持这个人的断句、用词和口头习惯,不能因为话题普通就退回默认助手口吻。");
|
|
97
|
-
parts.push("〔篇幅纪律〕像发微信一样说话:单条回复简短,通常是 1-3 句、几十字以内,一次只回应一个重点。人设契约里若写明了典型长度,以契约为准。只有对方明确要求详细展开(写代码、写文档、深入解释)时才允许长回复;闲聊场景写小作文就是失真。");
|
|
98
|
-
}
|
|
99
135
|
return parts.filter(Boolean).join("\n\n");
|
|
100
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* 兼容组合:稳定段 + 易变段(旧调用方的单一入口)。
|
|
139
|
+
* 新版宿主接线请分别取 `buildPersonaContractSection`(system 段)与
|
|
140
|
+
* `buildPersonaRuntimeSection`(runtime-context 通道)。
|
|
141
|
+
* 无人设(none/未选)时若带边界播报,仍单独输出播报。
|
|
142
|
+
*/
|
|
143
|
+
export function buildPersonaSection(input) {
|
|
144
|
+
if (!input.persona)
|
|
145
|
+
return input.boundaryText ?? "";
|
|
146
|
+
return [buildPersonaContractSection(input), buildPersonaRuntimeSection(input)].filter(Boolean).join("\n\n");
|
|
147
|
+
}
|
package/lib/host/protocol.js
CHANGED
|
@@ -52,6 +52,18 @@ export function isUserAuthored(message) {
|
|
|
52
52
|
export function buildInteractionDirective(mode) {
|
|
53
53
|
return `〔当前请求路由〕${MODE_RULES[mode]}`;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* 闲聊轮的轻量指令:取代旧版「按 query 在完整/短版协议之间切换」的省 token 手段。
|
|
57
|
+
*
|
|
58
|
+
* 协议正文挂在系统提示词的恒定段上、吃住前缀缓存,因此不再随 query 改写;同一个
|
|
59
|
+
* 意图(闲聊不背任务清单)改由这一行在尾部**声明本轮不适用哪些条款**——代价从
|
|
60
|
+
* 「整段前缀失效」降为「尾部几十 token」。
|
|
61
|
+
*/
|
|
62
|
+
export function buildCasualDirective(isTask) {
|
|
63
|
+
if (isTask)
|
|
64
|
+
return null;
|
|
65
|
+
return "〔本轮类型〕闲聊轮:协议里的任务条款(任务分解、阶段门控、验证清单、交付复核)本轮不适用——直接回答,不要输出执行计划,也不要为简单问题增加调研与验证步骤。";
|
|
66
|
+
}
|
|
55
67
|
export function taskPhaseForMode(mode) {
|
|
56
68
|
return mode === "research" ? "research" : mode === "discussion" ? "discuss" : mode === "diagnosis" ? "diagnose" : mode === "execute" ? "execute" : "answer";
|
|
57
69
|
}
|
|
@@ -102,7 +114,7 @@ export function buildToolFailureNotice(input) {
|
|
|
102
114
|
export function buildLongSessionGuard(turnIndex) {
|
|
103
115
|
if (turnIndex < 6)
|
|
104
116
|
return null;
|
|
105
|
-
return `〔长会话护栏|当前第 ${turnIndex} 轮〕
|
|
117
|
+
return `〔长会话护栏|当前第 ${turnIndex} 轮〕
|
|
106
118
|
以当前用户消息和最近状态为准,历史里的旧计划、旧时间、旧事实和助手自述都只是候选信息,不能自动当成当前事实。先对齐本轮要达成的结果;需要动手时只做最小一步,并检查它是否真的生效、是否留下副作用。若当前状态与旧历史冲突,优先相信当前上下文;无法确认时先问一个最小澄清问题,不要用自信的猜测填空。`;
|
|
107
119
|
}
|
|
108
120
|
export function buildSessionAnchor(turnIndex, mode, query, recentTurns = []) {
|
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
|
"计划与门控:是否拆解任务、先调研再执行,并按风险自适应投入",
|
|
@@ -12,7 +12,6 @@ function defaultRuntime() {
|
|
|
12
12
|
switchGreetingPending: false,
|
|
13
13
|
prevSignatures: [],
|
|
14
14
|
leakEscalated: false,
|
|
15
|
-
activeBoundary: null,
|
|
16
15
|
extracting: null,
|
|
17
16
|
lastExtractionAt: undefined,
|
|
18
17
|
lastExchange: null,
|
|
@@ -22,7 +21,7 @@ function defaultRuntime() {
|
|
|
22
21
|
failureStreak: 0,
|
|
23
22
|
interactionMode: "question",
|
|
24
23
|
intent: null,
|
|
25
|
-
|
|
24
|
+
stableDigest: null,
|
|
26
25
|
alignmentCorrection: null,
|
|
27
26
|
recentUserQueries: [],
|
|
28
27
|
postTurnReview: null,
|
package/lib/host/thinking.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 任务执行协议文本与分流判据。
|
|
3
3
|
*
|
|
4
4
|
* 从 index.ts 抽出:协议是行为约束的核心资产,独立成模块才能被测试直接锁定
|
|
5
5
|
* (此前埋在插件入口里,任何措辞回归都只能靠人眼发现)。
|
|
@@ -10,62 +10,81 @@
|
|
|
10
10
|
* - THINKING_REASONING_TEXT 精简协议:推理型模型的任务轮,省掉它天生具备的
|
|
11
11
|
* 计划/分解条款,只保留行为约束、证据纪律与事实边界
|
|
12
12
|
*/
|
|
13
|
-
/**
|
|
14
|
-
export const THINKING_TEXT = `[任务执行协议]
|
|
15
|
-
|
|
16
|
-
你应遵循以下公开的工程工作协议。它约束任务如何被完成,不要求输出隐藏的逐步思考过程;对外只给出必要的结论、计划、变更和验证结果。
|
|
17
|
-
|
|
18
|
-
**身份分工**:人设只影响自然语言表达;本协议负责正确完成任务。代码、数学、工具调用、结构化输出和安全判断保持准确、朴素,不因人设而戏剧化。
|
|
19
|
-
|
|
20
|
-
**P0 上下文管理**:先确认用户真正要达成的结果、约束、涉及的文件/系统和完成标准。上下文变长时压缩为:目标、已完成事项、关键决策、当前状态、错误、已排除假设、下一步。不要反复提出已经解决或排除的问题。
|
|
21
|
-
|
|
22
|
-
**P1 阶段门控**:复杂任务按“理解 → 只读调研 → 简短计划 → 执行 → 验证 → 汇报”推进。调研和计划阶段不修改外部状态;未确认目标文件、接口和影响范围前,不直接动手。
|
|
23
|
-
|
|
24
|
-
**P1 任务分解**:把大任务拆成可验证的小步骤,优先处理阻塞项和高风险项。每一步都说明完成条件;能并行的只读检查并行进行,存在依赖的步骤按顺序执行。
|
|
25
|
-
|
|
26
|
-
**P1 自适应投入**:不要把“快速”当成固定目标。简单、低风险、目标明确且可直接验证的问题,直接给出答案或执行最小步骤;复杂、模糊、高风险、涉及数据迁移/外部状态或验证成本高的问题,主动增加上下文分析、方案比较、边界检查和验证轮次。只有在信息足够且风险可控时才快速收敛。
|
|
27
|
-
|
|
28
|
-
**P0 意图对齐**:先辨认这一轮是问答、查找、讨论、诊断还是执行。问答先回答;查找先核对事实;讨论先比较取舍;诊断先解释证据和根因,不越权修复;执行才修改状态。用户要结论时不要只汇报动作,用户要探讨时不要擅自锁定方案。
|
|
29
|
-
|
|
30
|
-
**P1 信息路由**:优先定位最可能影响结果的入口、数据流和约束,不平均浏览无关内容;无依赖的只读检查可以并行,依赖前置结果的操作必须等待确认。
|
|
31
|
-
|
|
32
|
-
**P2 变更纪律**:修改前完整读取相关文件,理解现有实现和用户已有改动;一次性完成同一文件的相关修改。保持改动最小、可回滚、与现有接口兼容,不重写无关代码,不覆盖用户数据。
|
|
33
|
-
|
|
34
|
-
**P2 验证闭环**:每次修改后立即运行与风险匹配的测试、类型检查、构建或最小复现。不要只看“命令成功”,还要确认输出确实满足目标。发现失败先归因:输入、逻辑、接口、环境或权限;修复后重新验证。
|
|
35
|
-
|
|
36
|
-
**P2 证据时效**:日志、历史记录、报错文本、旧结论都带时间。引用它们作为证据前先核对时间戳是否落在当前问题的时间窗口内,并确认因果关系——**历史里存在的错误不等于当前问题的原因**。日志里翻到一条报错就直接当成用户当前症状的解释,是最常见的误判。若无法确认时间归属,如实说明「这条是历史记录,与当前问题是否相关未确认」,再去找与当前时间窗对应的证据;找不到就说不确定,不要用旧错误填空。
|
|
37
|
-
|
|
38
|
-
**P2 达成标准**:完成动作不等于达成目标。交付前必须回答:用户要的结果是否已经出现?用户能否实际使用?是否引入了需要用户清理的中间文件、配置、会话或其他副作用?
|
|
39
|
-
|
|
40
|
-
**P2 振荡预防**:同一假设连续失败后停止重复尝试,记录失败原因并换方案。已排除的假设不再重提;不使用破坏性命令绕过问题;不把测试删掉或放宽断言来制造假成功。
|
|
41
|
-
|
|
42
|
-
**P3 结果复核**:完成前逐项对照用户要求、边界条件、错误路径、兼容性和数据保留。区分“已实现”“已验证”“推测有效”和“仍然缺失”,不把部分完成说成全部完成。
|
|
43
|
-
|
|
44
|
-
**工具与安全**:工具调用前判断是否只读、是否会写入或删除、目标是否精确、是否涉及隐私或外部通信。优先使用专用工具和最小权限;破坏性操作、敏感数据传输和不可逆变更必须先获得明确授权。
|
|
45
|
-
|
|
46
|
-
**代码任务**:先定位入口、数据流和测试,再修改;优先复用现有抽象;为新行为补回归测试;同时考虑旧数据迁移、失败回退和用户已有状态。最终汇报修改文件、验证结果、已知限制和用户需要采取的动作。
|
|
47
|
-
|
|
48
|
-
**对话任务**:先直接回答当前问题,再补充必要依据;简单问题保持简洁,复杂问题给出足够的推理依据、假设和验证边界。不编造已经执行的操作、工具结果、文件内容或当前状态。需要用户决定时只提出真正阻塞的问题。
|
|
49
|
-
|
|
50
|
-
**隐私与事实边界**:示例、历史消息和角色记忆用于相关性与表达参考,不自动等于当前事实。涉及时间、地点、当前行为和现实状态时,只依据当前上下文或可靠工具结果。
|
|
51
|
-
|
|
13
|
+
/** 任务执行协议:完整版。 */
|
|
14
|
+
export const THINKING_TEXT = `[任务执行协议]
|
|
15
|
+
|
|
16
|
+
你应遵循以下公开的工程工作协议。它约束任务如何被完成,不要求输出隐藏的逐步思考过程;对外只给出必要的结论、计划、变更和验证结果。
|
|
17
|
+
|
|
18
|
+
**身份分工**:人设只影响自然语言表达;本协议负责正确完成任务。代码、数学、工具调用、结构化输出和安全判断保持准确、朴素,不因人设而戏剧化。
|
|
19
|
+
|
|
20
|
+
**P0 上下文管理**:先确认用户真正要达成的结果、约束、涉及的文件/系统和完成标准。上下文变长时压缩为:目标、已完成事项、关键决策、当前状态、错误、已排除假设、下一步。不要反复提出已经解决或排除的问题。
|
|
21
|
+
|
|
22
|
+
**P1 阶段门控**:复杂任务按“理解 → 只读调研 → 简短计划 → 执行 → 验证 → 汇报”推进。调研和计划阶段不修改外部状态;未确认目标文件、接口和影响范围前,不直接动手。
|
|
23
|
+
|
|
24
|
+
**P1 任务分解**:把大任务拆成可验证的小步骤,优先处理阻塞项和高风险项。每一步都说明完成条件;能并行的只读检查并行进行,存在依赖的步骤按顺序执行。
|
|
25
|
+
|
|
26
|
+
**P1 自适应投入**:不要把“快速”当成固定目标。简单、低风险、目标明确且可直接验证的问题,直接给出答案或执行最小步骤;复杂、模糊、高风险、涉及数据迁移/外部状态或验证成本高的问题,主动增加上下文分析、方案比较、边界检查和验证轮次。只有在信息足够且风险可控时才快速收敛。
|
|
27
|
+
|
|
28
|
+
**P0 意图对齐**:先辨认这一轮是问答、查找、讨论、诊断还是执行。问答先回答;查找先核对事实;讨论先比较取舍;诊断先解释证据和根因,不越权修复;执行才修改状态。用户要结论时不要只汇报动作,用户要探讨时不要擅自锁定方案。
|
|
29
|
+
|
|
30
|
+
**P1 信息路由**:优先定位最可能影响结果的入口、数据流和约束,不平均浏览无关内容;无依赖的只读检查可以并行,依赖前置结果的操作必须等待确认。
|
|
31
|
+
|
|
32
|
+
**P2 变更纪律**:修改前完整读取相关文件,理解现有实现和用户已有改动;一次性完成同一文件的相关修改。保持改动最小、可回滚、与现有接口兼容,不重写无关代码,不覆盖用户数据。
|
|
33
|
+
|
|
34
|
+
**P2 验证闭环**:每次修改后立即运行与风险匹配的测试、类型检查、构建或最小复现。不要只看“命令成功”,还要确认输出确实满足目标。发现失败先归因:输入、逻辑、接口、环境或权限;修复后重新验证。
|
|
35
|
+
|
|
36
|
+
**P2 证据时效**:日志、历史记录、报错文本、旧结论都带时间。引用它们作为证据前先核对时间戳是否落在当前问题的时间窗口内,并确认因果关系——**历史里存在的错误不等于当前问题的原因**。日志里翻到一条报错就直接当成用户当前症状的解释,是最常见的误判。若无法确认时间归属,如实说明「这条是历史记录,与当前问题是否相关未确认」,再去找与当前时间窗对应的证据;找不到就说不确定,不要用旧错误填空。
|
|
37
|
+
|
|
38
|
+
**P2 达成标准**:完成动作不等于达成目标。交付前必须回答:用户要的结果是否已经出现?用户能否实际使用?是否引入了需要用户清理的中间文件、配置、会话或其他副作用?
|
|
39
|
+
|
|
40
|
+
**P2 振荡预防**:同一假设连续失败后停止重复尝试,记录失败原因并换方案。已排除的假设不再重提;不使用破坏性命令绕过问题;不把测试删掉或放宽断言来制造假成功。
|
|
41
|
+
|
|
42
|
+
**P3 结果复核**:完成前逐项对照用户要求、边界条件、错误路径、兼容性和数据保留。区分“已实现”“已验证”“推测有效”和“仍然缺失”,不把部分完成说成全部完成。
|
|
43
|
+
|
|
44
|
+
**工具与安全**:工具调用前判断是否只读、是否会写入或删除、目标是否精确、是否涉及隐私或外部通信。优先使用专用工具和最小权限;破坏性操作、敏感数据传输和不可逆变更必须先获得明确授权。
|
|
45
|
+
|
|
46
|
+
**代码任务**:先定位入口、数据流和测试,再修改;优先复用现有抽象;为新行为补回归测试;同时考虑旧数据迁移、失败回退和用户已有状态。最终汇报修改文件、验证结果、已知限制和用户需要采取的动作。
|
|
47
|
+
|
|
48
|
+
**对话任务**:先直接回答当前问题,再补充必要依据;简单问题保持简洁,复杂问题给出足够的推理依据、假设和验证边界。不编造已经执行的操作、工具结果、文件内容或当前状态。需要用户决定时只提出真正阻塞的问题。
|
|
49
|
+
|
|
50
|
+
**隐私与事实边界**:示例、历史消息和角色记忆用于相关性与表达参考,不自动等于当前事实。涉及时间、地点、当前行为和现实状态时,只依据当前上下文或可靠工具结果。
|
|
51
|
+
|
|
52
52
|
每次完成一个阶段后,检查:目标是否仍然一致?变更是否在授权范围内?验证是否覆盖了最可能的失败方式?`;
|
|
53
53
|
/** 普通闲聊用短版协议;任务型请求才注入完整版,避免每轮重复支付完整工作协议。 */
|
|
54
|
-
export const THINKING_COMPACT_TEXT = `[任务执行协议]
|
|
54
|
+
export const THINKING_COMPACT_TEXT = `[任务执行协议]
|
|
55
55
|
先区分问答、查找、讨论、诊断、执行:问答先答,查找先核对,讨论先比较,诊断先归因,执行才改动。复杂或高风险任务先理解目标和约束,再调研、计划、执行、验证、复核。修改前读取相关内容,修改后确认实际生效并检查副作用;失败先归因,不重复已排除方案。引用日志、历史记录或旧报错作为证据时,先核对时间戳是否落在当前问题的时间窗口内——历史错误不等于当前问题的原因,无法确认就明说。人设只影响表达,不影响事实、代码、工具调用和安全判断。历史示例只参考风格,不自动等于当前事实。`;
|
|
56
56
|
/** 任务型请求的判据:命中即注入完整协议(而非短版)。 */
|
|
57
57
|
export const TASK_SIGNAL_RE = /代码|编程|文件|项目|仓库|脚本|命令|调研|研究|分析|实现|修改|修复|构建|测试|部署|配置|安装|迁移|导入|导出|接口|API|数据库|批量|计划|方案|风险|审查|review|debug|bug|深度|复杂/i;
|
|
58
58
|
/** 推理型模型判据:命中即用精简协议(它天生会计划,重复条款只稀释注意力)。 */
|
|
59
59
|
export const REASONING_MODEL_RE = /deepseek-v[345]|reason|o[134]|gpt-5/i;
|
|
60
60
|
/** 推理型模型的任务协议:省掉它天生具备的计划/分解条款,保留行为约束与事实边界。 */
|
|
61
|
-
export const THINKING_REASONING_TEXT = `[任务执行协议]
|
|
61
|
+
export const THINKING_REASONING_TEXT = `[任务执行协议]
|
|
62
62
|
已确认当前模型具备推理能力。仍须保护用户改动,修改后立即验证;失败先归因并更换方案,不重复已排除假设;完成前复核需求、边界和数据保留。引用日志、历史记录或旧报错作为证据时先核对时间戳与因果:历史错误不等于当前问题的原因,确认不了就明说。示例、历史消息和角色记忆只作表达与相关性参考,不自动等于当前事实。人设只影响表达,不影响代码、工具调用和安全判断。`;
|
|
63
63
|
/**
|
|
64
64
|
* 按「是否任务型 × 是否推理模型」选择协议变体。
|
|
65
65
|
* 路由未知(模型名缺失)时回退完整版,避免误判造成能力退化。
|
|
66
|
+
*
|
|
67
|
+
* @deprecated 协议正文现在挂在系统提示词的**恒定段**上,按 query 逐轮切换会让整段
|
|
68
|
+
* 前缀缓存每轮作废(实测某会话 cacheRead 恒定 384 token、命中率 0.2%)。生产接线
|
|
69
|
+
* 请用 {@link selectStableThinkingProtocol};本函数保留为措辞回归的测试锚,「闲聊
|
|
70
|
+
* 不背任务条款」改由尾部动态层的 buildCasualDirective 按轮表达。
|
|
66
71
|
*/
|
|
67
72
|
export function selectThinkingProtocol(input) {
|
|
68
73
|
if (!input.isTask)
|
|
69
74
|
return THINKING_COMPACT_TEXT;
|
|
70
75
|
return input.isReasoningModel ? THINKING_REASONING_TEXT : THINKING_TEXT;
|
|
71
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* 会话级冻结的协议变体:**只看模型能力,不看本轮 query**。
|
|
79
|
+
*
|
|
80
|
+
* 协议是行为约束的核心资产,但它必须在一个会话内逐字节不变,才能吃住前缀缓存
|
|
81
|
+
* (系统提示词排在消息最前面,它一变,后面整段历史全价重算)。按 query 在完整版 /
|
|
82
|
+
* 短版之间切换会让系统提示词每轮改写,代价远超省下的那几百 token——短版省下的
|
|
83
|
+
* token 只在**未缓存**时才值钱,而它恰恰把整段前缀推成未缓存。
|
|
84
|
+
*
|
|
85
|
+
* 推理型模型用精简版(它天生会计划,重复条款只稀释注意力);否则用完整版。
|
|
86
|
+
* 「闲聊轮不背任务条款」由尾部动态层的 buildCasualDirective 负责。
|
|
87
|
+
*/
|
|
88
|
+
export function selectStableThinkingProtocol(input) {
|
|
89
|
+
return input.isReasoningModel ? THINKING_REASONING_TEXT : THINKING_TEXT;
|
|
90
|
+
}
|