dsh-rule-engine 0.6.4 → 0.6.5
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 +461 -444
- package/lib/core/authorization.js +1 -1
- package/lib/core/intent.js +1 -1
- package/lib/core/llm-intent.js +13 -5
- package/lib/core/patterns.js +14 -4
- package/lib/core/state.js +4 -1
- package/lib/core/text-detect.js +20 -2
- package/lib/core/tool-catalog.js +5 -0
- package/lib/index.js +113 -14
- package/lib/messages.js +5 -0
- package/lib/service.js +43 -0
- package/package.json +4 -10
- package/scripts/audit-mount-consistency.mjs +0 -198
- package/scripts/build.sh +0 -13
- package/scripts/check-real.mjs +0 -24
- package/scripts/check-tool-coverage.mjs +0 -65
- package/scripts/dualtrack-check.mjs +0 -481
- package/scripts/dualtrack-whitelist.json +0 -7
- package/scripts/health-audit.mjs +0 -66
- package/scripts/lib/pnpm-exempt.mjs +0 -56
- package/scripts/local-residue-scan.mjs +0 -46
- package/scripts/plugins.json +0 -10
- package/scripts/probe-home.mjs +0 -3
- package/scripts/probe-intent.mjs +0 -14
- package/scripts/publish-aptitude-check.mjs +0 -105
- package/scripts/readme-version-check.mjs +0 -41
- package/scripts/release-plugin.mjs +0 -432
- package/scripts/rules-health.mjs +0 -55
- package/scripts/verify-all.mjs +0 -219
- package/scripts/verify-guard-live.mjs +0 -30
- package/scripts/verify-v474.mjs +0 -14
- package/upgrade-impact.json +0 -55
|
@@ -374,7 +374,7 @@ function pathUnderAuth(p, authPath) {
|
|
|
374
374
|
export function authMatches(auth, op) {
|
|
375
375
|
if (!auth || !op) return false;
|
|
376
376
|
if (auth.type !== "any" && op.type !== "any" && auth.type !== op.type) return false;
|
|
377
|
-
// A2(2026-09-10):授权侧支持多路径——ask 多路径问题文本经 index.js
|
|
377
|
+
// A2(2026-09-10):授权侧支持多路径——ask 多路径问题文本经 index.js 的 ask 授权登记写入 pathPrefixes。
|
|
378
378
|
// 语义:任一授权路径覆盖任一操作候选路径即匹配(此前只认单一 auth.pathPrefix,ask 侧只记一条 → 其余路径的授权形同虚设)。
|
|
379
379
|
const authPaths = Array.isArray(auth.pathPrefixes) && auth.pathPrefixes.length > 0
|
|
380
380
|
? auth.pathPrefixes.map(normalizePath).filter(Boolean)
|
package/lib/core/intent.js
CHANGED
|
@@ -87,7 +87,7 @@ export function isEngineInjectedMessage(text) {
|
|
|
87
87
|
// source.kind 主判之外,对"标记为 user 但实为注入"的消息做模板兜底——
|
|
88
88
|
// 命中即整体跳过:不覆盖回合状态、不产生 scopes/授权。
|
|
89
89
|
// 本机实证来源:runtime context 快照(无 system-reminder 标签)、子代理完成通知(B3)、
|
|
90
|
-
// vision-router
|
|
90
|
+
// vision-router 自动挂载提醒(该包 client 侧注入)、引擎自身注入([规则引擎])。
|
|
91
91
|
// example-injector 的近距离引导模板待实测后收窄补充。
|
|
92
92
|
const SYSTEM_INJECTION_PREFIXES = [
|
|
93
93
|
/^Current runtime context\./,
|
package/lib/core/llm-intent.js
CHANGED
|
@@ -50,7 +50,9 @@ export function verdictForDeny(turn, cfg = {}, lexiconDenied = true) {
|
|
|
50
50
|
if (conf >= high) return { mutationDenied: false, source: "llm-high", reason: `LLM 高置信(${conf})` };
|
|
51
51
|
if (conf >= low) return { mutationDenied: false, source: "llm-low", reason: `LLM 低置信(${conf})` };
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
// 2026-09-13:把「兜底失败原因」透出——此前失败降级与「词表缺词」在返回值上完全同形,无法区分。
|
|
54
|
+
const llmError = (llm && llm.raw === turn?.userText && llm.error) ? String(llm.error) : "";
|
|
55
|
+
return { mutationDenied: true, source: "lexicon", reason: "", llmError };
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -120,18 +122,24 @@ export async function enrichIntentWithLlm(ctx, state, sid, text, cfg) {
|
|
|
120
122
|
state.llmIntentCache.delete(firstKey);
|
|
121
123
|
}
|
|
122
124
|
auditIntent(state, sid, text, verdict, ms, false);
|
|
123
|
-
} catch {
|
|
125
|
+
} catch (e) {
|
|
124
126
|
session.turn.intentState = "lexicon";
|
|
125
|
-
|
|
127
|
+
// 2026-09-13:失败也要留痕——此前 catch 路径不写 turn.llmIntent,
|
|
128
|
+
// 导致 96.7% 的失败率长期不可归因(审计只有「失败降级」,无原因)。
|
|
129
|
+
const errMsg = String((e && e.message) || e);
|
|
130
|
+
session.turn.llmIntent = { raw: text, verdict: null, error: errMsg, at: Date.now() };
|
|
131
|
+
auditIntent(state, sid, text, null, Date.now() - started, false, errMsg);
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
function auditIntent(state, sid, text, verdict, ms, limitHit) {
|
|
135
|
+
function auditIntent(state, sid, text, verdict, ms, limitHit, llmError) {
|
|
130
136
|
try {
|
|
131
137
|
state.llmIntentLast = {
|
|
132
138
|
text: String(text).slice(0, 60),
|
|
133
139
|
verdict: verdict ? { ...verdict, confidence: Number(verdict.confidence) } : null,
|
|
134
140
|
source: verdict ? "llm" : "fail",
|
|
141
|
+
// 2026-09-13:失败原因(供 /guard status 与排障读取;此前只记「fail」)
|
|
142
|
+
error: llmError || null,
|
|
135
143
|
at: Date.now()
|
|
136
144
|
};
|
|
137
145
|
audit({
|
|
@@ -141,7 +149,7 @@ function auditIntent(state, sid, text, verdict, ms, limitHit) {
|
|
|
141
149
|
event: "user/message",
|
|
142
150
|
reason: verdict
|
|
143
151
|
? `词表低置信 → LLM:${JSON.stringify(verdict)}(延迟 ${ms}ms${limitHit ? " 限额" : ""})`
|
|
144
|
-
: `词表低置信 → LLM 失败降级(延迟 ${ms}ms)`,
|
|
152
|
+
: `词表低置信 → LLM 失败降级(延迟 ${ms}ms${llmError ? `:${llmError}` : "(原因未捕获)"})`,
|
|
145
153
|
session: sid,
|
|
146
154
|
verdictSource: verdict ? "llm-intent" : "lexicon",
|
|
147
155
|
text: String(text).slice(0, 120)
|
package/lib/core/patterns.js
CHANGED
|
@@ -328,7 +328,7 @@ export function isMutationCommand(command) {
|
|
|
328
328
|
/** 只读命令链按分隔符折段(机制 B,2026-08-24):`; 换行 && || |`(管道前后是命令链;`2>&1`/`2>$null` 的 stderr 重定向不拆分)
|
|
329
329
|
* F5(2026-09-10 修复,第三方实测):**引号感知**——此前裸拆 `|` 会把**引号内的 `|`** 当分隔符
|
|
330
330
|
* (正则 alternation `-Pattern 'a|b'`、格式串 `"{0} | x"`、`-notmatch 'x|y'`)→ 切出无法识别的残段
|
|
331
|
-
* →
|
|
331
|
+
* → 保守判非只读(本文件的引号感知分支)→ 整条只读命令被规则 22 按 write 拦(ERR-4WOAKG / 9T886X / Z8M7YC / IKXLU8)。
|
|
332
332
|
* 现:`'…'` / `"…"` / `` `…` `` 内的分隔符不拆。转义语义按 PS:`'` 内不转义(`''` 表一个单引号);
|
|
333
333
|
* `"` 与 `` ` `` 内 `\` / `` ` `` 转义下一字符(兼容跨语言写法)。 */
|
|
334
334
|
function splitCommandSegments(command) {
|
|
@@ -469,9 +469,11 @@ export function isAnalysisOp(toolName, args) {
|
|
|
469
469
|
// ═══════════ 0.5.10 已知环境坑特征表(建议5——错误码自动召回,K-01/K-05 依据)═══════════
|
|
470
470
|
// 工具/命令错误文本命中特征 → 审计 error-hint + 注入指向知识库的提示(低频:仅命中才提示;语义=指路不打扰)。
|
|
471
471
|
// hint 文案经 messages 层取(第 1 批 1b:去本机标识——原 hint 引用本机手册踩坑编号/代理端口/发布变量)
|
|
472
|
+
// 2026-09-11 去本机残留(分层 P4):第二条原写死本机代理端口号(key 亦含该端口号)——
|
|
473
|
+
// 通用层不应含任何本机标识;判据放宽为「连接被拒」本身(提示仅是"先查代理/服务是否启动",泛指无害)。
|
|
472
474
|
const KNOWN_PITFALLS = [
|
|
473
475
|
{ key: "SEC_E_NO_CREDENTIALS", re: /SEC_E_NO_CREDENTIALS/i, hintKey: "pitfall.tls" },
|
|
474
|
-
{ key: "
|
|
476
|
+
{ key: "conn-refused", re: /ECONNREFUSED/i, hintKey: "pitfall.proxy" },
|
|
475
477
|
{ key: "sandbox-pipe-EPERM", re: /EPERM/i, hintKey: "pitfall.sandbox-pipe" },
|
|
476
478
|
{ key: "ERR_MODULE_NOT_FOUND", re: /ERR_MODULE_NOT_FOUND/i, hintKey: "pitfall.module-missing" }
|
|
477
479
|
];
|
|
@@ -650,12 +652,15 @@ export function isAuditCommand(command) {
|
|
|
650
652
|
* 2026-08-31(验证通道放行面,用户定稿):清单扩展 + 由 isAnalysisOp 独立调用——
|
|
651
653
|
* ① 单测通配(任意 *.test.mjs)、② 验证/门禁脚本(check-tool-coverage / publish-aptitude-check)、
|
|
652
654
|
* ③ --dry-run(发布脚本 dry-run=只读推导,发布链无 dry-run 不匹配)、④ gh api 只读 GET、npm whoami。
|
|
655
|
+
* 2026-09-11(手册条款 16「重复劳动升级律」落地):新增 ⑤ 收尾脚本 session-wrapup.mjs——清理 .staging-* 入回收站
|
|
656
|
+
* + 列待提交清单 + 沉淀自检。放行依据=规则 13A 豁免原文「移动文件到 .backups/ 或 .backups/trash-<时间戳>/
|
|
657
|
+
* 的保护性备份操作,免询问」;**不含** commit / publish / push 等执行类动作(仍走严格授权,脚本只"列出"待提交文件)。
|
|
653
658
|
* 安全边界:执行类脚本(release-plugin 无 --dry-run / inject / uninject / install / publish / commit /
|
|
654
659
|
* push / Remove-Item 等)一律不匹配——执行类仍走授权语义严格版(0.5.11 防借道回归不放松)。
|
|
655
660
|
*/
|
|
656
661
|
export function isVerificationCommand(command) {
|
|
657
662
|
if (typeof command !== "string") return false;
|
|
658
|
-
if (/\b(?:node|npm|npx)\s+[^\n]*(?:run-all\.mjs|audit-mount-consistency\.mjs|loader-smoke\.e2e\.mjs|verify-all\.mjs|health-audit\.mjs|check-tool-coverage\.mjs|publish-aptitude-check\.mjs|\.test\.mjs\b|--check|--test|--dry-run)\b/i.test(command)) return true;
|
|
663
|
+
if (/\b(?:node|npm|npx)\s+[^\n]*(?:run-all\.mjs|audit-mount-consistency\.mjs|loader-smoke\.e2e\.mjs|verify-all\.mjs|health-audit\.mjs|check-tool-coverage\.mjs|publish-aptitude-check\.mjs|session-wrapup\.mjs|\.test\.mjs\b|--check|--test|--dry-run)\b/i.test(command)) return true;
|
|
659
664
|
if (/\b(?:npm|pnpm)\s+(?:test|run\s+test)\b/i.test(command)) return true;
|
|
660
665
|
if (/\bgh\s+api\b/i.test(command)) {
|
|
661
666
|
// 只读 GET 形态;写形态(--method/-X 显式方法、-F/--field 表单)不豁免(gg api 写=执行类)
|
|
@@ -886,7 +891,12 @@ export const PATTERN_MAP_KEYS = {
|
|
|
886
891
|
|
|
887
892
|
/** 数值型配置键(键 → 内置默认;与正则键共用同一配置层与校验/回退语义) */
|
|
888
893
|
export const PATTERN_NUM_KEYS = {
|
|
889
|
-
criticism_caps_ratio: 0.6 // 英文全大写比率阈值(≥ 该值且字母数 ≥6 视为"喊叫"强形态)
|
|
894
|
+
criticism_caps_ratio: 0.6, // 英文全大写比率阈值(≥ 该值且字母数 ≥6 视为"喊叫"强形态)
|
|
895
|
+
// 2026-09-15(踩坑 144 修法 (b) 收紧档落地):纯比率版把中文消息里的**编号标签**(A1→A3→B5→D)
|
|
896
|
+
// 与**缩写**(ESR/DSH)判成"喊叫"→ A″ 误冻结写类工具(本会话连续两回合实测)。
|
|
897
|
+
// 现要求"词形"证据:≥ min_tokens 个「连续字母 ≥ min_word_len」的 token,再看这些 token 的大写比率。
|
|
898
|
+
criticism_caps_min_tokens: 2,
|
|
899
|
+
criticism_caps_min_word_len: 4
|
|
890
900
|
};
|
|
891
901
|
|
|
892
902
|
// 内置默认:语言无关最小集(无中文)
|
package/lib/core/state.js
CHANGED
|
@@ -275,7 +275,10 @@ export function freshTurn() {
|
|
|
275
275
|
scopes: [],
|
|
276
276
|
// M8 双通道机制(2026-08-24):统一入口落盘后同轮 engram_store 校验(entryMarker 经配置)
|
|
277
277
|
manualWriteSeen: false,
|
|
278
|
-
engramStoreSeen: false
|
|
278
|
+
engramStoreSeen: false,
|
|
279
|
+
// D(2026-09-13):本回合是否已在「落盘那一刻」提示过——防 turn/end 兜底路径重复投递
|
|
280
|
+
// (maybeInject 的闸是同规则最多 3 次,非一次即止,故需回合级标记)
|
|
281
|
+
m8Hinted: false
|
|
279
282
|
};
|
|
280
283
|
}
|
|
281
284
|
|
package/lib/core/text-detect.js
CHANGED
|
@@ -44,6 +44,8 @@ import { detectOverengineeringText } from "./overengineering.js";
|
|
|
44
44
|
// 用户批评形态(规则 22②;2026-09-02 建、2026-09-08 第二批 §1.3 重构为 A″):
|
|
45
45
|
// A″ = 语义主判、无通用词表、宽松触发、行为闸:
|
|
46
46
|
// - 通用层只留**语言无关形态**:连续问号/叹号(CRITICISM_SHAPE_RE)+ 英文全大写比率(hasHighCapsRatio);
|
|
47
|
+
// 2026-09-15(踩坑 144 修法 (b)):大写比率一支改由 **hasShoutingWordShape** 把关——只认"词形" token
|
|
48
|
+
// (连续字母 ≥4、≥2 个),编号标签(A1/B5)与缩写(ESR/DSH)不再计为"喊叫";纯比率函数保留导出供配置层复用。
|
|
47
49
|
// - 辱骂词枚举**迁出**到本机 rule-engine.json 的 criticismPersonal(发布面不含个人话术),经 setCriticismPersonal 注入;
|
|
48
50
|
// - 机器层只产"疑似"信号(不再直接产 correct),裁决归 LLM 层(judge/deliverSuspects 兜底);
|
|
49
51
|
// - 行为闸在 index.js:命中疑似 → 冻结本回合写类工具 + 注入四段模板(停止/归因四问/三件套/等指令)。
|
|
@@ -66,7 +68,9 @@ function personalCriticismRe() {
|
|
|
66
68
|
return new RegExp(`(?:${alts.join("|")})`);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
/** 情绪强度信号:英文全大写比率 ≥ 阈值(默认 0.6,经 patterns.criticism_caps_ratio 可配置)且字母数 ≥6
|
|
71
|
+
/** 情绪强度信号:英文全大写比率 ≥ 阈值(默认 0.6,经 patterns.criticism_caps_ratio 可配置)且字母数 ≥6
|
|
72
|
+
*(语言无关的"喊叫"形态)。**注意**:本函数只看比率,不区分"词"与"标识符"——判"喊叫"请用
|
|
73
|
+
* hasShoutingWordShape(2026-09-15 起 criticismSignals 用的是后者)。 */
|
|
70
74
|
export function hasHighCapsRatio(text) {
|
|
71
75
|
const t = String(text || "");
|
|
72
76
|
const letters = t.replace(/[^A-Za-z]/g, "");
|
|
@@ -75,10 +79,24 @@ export function hasHighCapsRatio(text) {
|
|
|
75
79
|
return caps.length / letters.length >= patNum("criticism_caps_ratio");
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
/** 词形大写"喊叫"形态(2026-09-15,踩坑 144 修法 (b) 收紧档):**只认词形 token**——
|
|
83
|
+
* 取「连续字母 ≥ criticism_caps_min_word_len」的 token(`A1`/`B5`/`12B` 这类编号因连续字母不足被排除、
|
|
84
|
+
* 3 字母缩写 `ESR` 亦不足默认 4),需此类 token ≥ criticism_caps_min_tokens 个,
|
|
85
|
+
* 再对这些 token 的整体大写比率套 hasHighCapsRatio。
|
|
86
|
+
* 由来:纯比率版在中文/技术语境必然误报(几个大写标记即 100%),A″ 因此误冻结写类工具。 */
|
|
87
|
+
export function hasShoutingWordShape(text) {
|
|
88
|
+
const t = String(text || "");
|
|
89
|
+
const minLen = Math.max(2, patNum("criticism_caps_min_word_len"));
|
|
90
|
+
const minToks = Math.max(1, patNum("criticism_caps_min_tokens"));
|
|
91
|
+
const toks = t.match(new RegExp(`[A-Za-z]{${minLen},}`, "g")) || [];
|
|
92
|
+
if (toks.length < minToks) return false;
|
|
93
|
+
return hasHighCapsRatio(toks.join(" "));
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
/** 批评疑似检测(A″):{shape, personal, weak, suspect}——机器只产疑似,判定权归模型 */
|
|
79
97
|
export function criticismSignals(text) {
|
|
80
98
|
const t = String(text || "");
|
|
81
|
-
const shape = criticismShapeRe().test(t) ||
|
|
99
|
+
const shape = criticismShapeRe().test(t) || hasShoutingWordShape(t);
|
|
82
100
|
const personal = (personalCriticismRe() || /$^/).test(t);
|
|
83
101
|
const weak = criticismWeakRe().test(t);
|
|
84
102
|
return { shape, personal, weak, suspect: shape || personal ? "strong" : weak ? "weak" : null };
|
package/lib/core/tool-catalog.js
CHANGED
|
@@ -26,6 +26,11 @@ const ANALYSIS_TOOLS = new Set([
|
|
|
26
26
|
// 子代理交付工具(官方 dsh-tool-subagent-report,2026-08-24 实测发现):仅子代理环境注册,
|
|
27
27
|
// 参数仅 output 文本 → 直接交付父代理,无文件副作用 → analysis(避免子代理交付被 unknown deny 阻断)
|
|
28
28
|
"report",
|
|
29
|
+
// 官方交付物声明工具(2026-09-14 补录:verify-all ⑤「工具箱覆盖」实测 COVERAGE-FAIL —— 官方
|
|
30
|
+
// tool-catalog 全集含 present 而本表未收录,会被 unknownToolDecision 当未知工具首调处置):
|
|
31
|
+
// present 只把**已存在**的文件登记为交付物(纯会话态;不复制内容、不写盘、不改文件)→ analysis。
|
|
32
|
+
// 与相邻两类工具的分界:report/visualize 会写 artifact(visualize 落一份卡片副本),present 不写。
|
|
33
|
+
"present",
|
|
29
34
|
// 官方子代理模型发现(@deepseek-ai/dsh-tool-subagent,2026-09-06 工具目录漂移补录):只读枚举
|
|
30
35
|
// 可用子代理模型(模型发现/所选路由校验),无持久副作用 → analysis
|
|
31
36
|
"list_subagent_models",
|
package/lib/index.js
CHANGED
|
@@ -64,7 +64,10 @@ import { toolClass } from "./core/tool-catalog.js";
|
|
|
64
64
|
import { parseWhitelist, mergeWhitelist, serializeWhitelist } from "./core/whitelist.js";
|
|
65
65
|
import { state } from "./core/runtime.js";
|
|
66
66
|
import { APPROVE_TYPES, normalizePath, setTypeHints } from "./core/authorization.js";
|
|
67
|
-
import { setLexicons } from "./core/lexicon.js";
|
|
67
|
+
import { setLexicons, approvalExecRe } from "./core/lexicon.js";
|
|
68
|
+
// C4(批 3,2026-09-15):质量账本查询入口(/guard quality)。机制自 0.6.3 已在 core/quality-ledger.js,
|
|
69
|
+
// 但 lib/ 内此前零引用(grep "quality" = 0 命中)——机制写了却没人能用,本批补入口。
|
|
70
|
+
import { loadLedger, qualityTrend } from "./core/quality-ledger.js";
|
|
68
71
|
import { getMessage } from "./messages.js";
|
|
69
72
|
import { getBlindSpots, getFreedomSurface, setGuardFreedom } from "./core/guard-core.js";
|
|
70
73
|
import { detectLang, getLang, getLangSource, pickLang } from "./core/lang.js";
|
|
@@ -109,11 +112,30 @@ export const inject = ["tools", "commands", "agents", "workspaceRegistry", "skil
|
|
|
109
112
|
|
|
110
113
|
const pluginConfig = loadPluginConfig();
|
|
111
114
|
// §1.3 行为闸判定(纯函数,2026-09-08 第二批 A″):批评疑似回合冻结写类工具——返回拒绝原因或 null
|
|
112
|
-
|
|
115
|
+
// C2-C 档(批 3,2026-09-15):新增 opts.bypassActive —— `/guard bypass` 的语义是「全部守卫暂停」
|
|
116
|
+
//(guard status 文案原文),而本闸此前只查 turn.criticismFrozen、不检查 state.bypassUntil →
|
|
117
|
+
// 用户显式放行后仍被 A″ 冻结,语义不一致。bypassActive 由调用方从 state.bypassUntil 计算后传入,
|
|
118
|
+
// 本函数保持**纯函数**(可单测);窗口内的留痕由 guard 路径既有的 bypass-action 审计统一负责(M2)。
|
|
119
|
+
export function criticismFreezeDecision(session, toolName, args, opts = {}) {
|
|
120
|
+
if (opts.bypassActive) return null;
|
|
113
121
|
if (!session?.turn?.criticismFrozen) return null;
|
|
114
122
|
if (toolClass(toolName, args || {}) !== "mutating") return null;
|
|
115
123
|
return "本回合命中批评/指错疑似信号——写类工具已冻结(A″ 行为闸)。请按四段模板回应:① 停止;② 归因四问;③ 三件套(事实/原因/方案);④ 等指令。只读操作不受限。";
|
|
116
124
|
}
|
|
125
|
+
|
|
126
|
+
/** C3(批 3,2026-09-15,用户裁定方案 A):**是否冻结**本回合写类工具(纯函数,可单测)。
|
|
127
|
+
* 由来:置位逻辑原对强弱信号**不对称**——弱信号已做反向检查(hasExplicitExecWord,只认动作词),
|
|
128
|
+
* 强信号却无条件冻结 → 「批评 + 明确指令」的混合消息会把用户同时下达的指令一起冻掉,
|
|
129
|
+
* 而那条指令本身是**完整授权**(许可词 + 执行语,规则 22 的授权定义)。
|
|
130
|
+
* 判据:强信号下只有 `approvalExecRe()`(许可词 + [0,16] 字符 + 执行语,缺一不可)才豁免——
|
|
131
|
+
* 比弱信号的反向检查更严,避免「批评里提个动作词就算授权」的过宽。
|
|
132
|
+
* 与 criticismFreezeDecision 分工:本函数管「要不要冻」,那个管「冻了之后拦不拦」。
|
|
133
|
+
* @returns {boolean} true = 冻结本回合写类工具
|
|
134
|
+
*/
|
|
135
|
+
export function criticismFreezeBySignal(suspect, userText) {
|
|
136
|
+
if (suspect !== "strong") return false;
|
|
137
|
+
return !approvalExecRe().test(String(userText || ""));
|
|
138
|
+
}
|
|
117
139
|
// C4:启动时合并配置层 typeHints(本机扩展授权类型;默认空=仅内置 8+archive 类)
|
|
118
140
|
setTypeHints(pluginConfig.typeHints);
|
|
119
141
|
// P8 小批 A(2026-09-08):行为词表配置层注入(rule-engine.json 的 lexicons 键)——
|
|
@@ -537,10 +559,14 @@ export function handleSessionEvent(ctx, session, event) {
|
|
|
537
559
|
reason: getMessage("memory.sediment-missing"),
|
|
538
560
|
session: sid
|
|
539
561
|
});
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
562
|
+
// D(2026-09-13):事中已提示过(落盘那一刻)→ 此处只留审计,不重复投递;
|
|
563
|
+
// 未开启 hintOnWrite 或事中未提示时,此处仍是兜底投递(默认关时行为与改动前一致)。
|
|
564
|
+
if (!s.turn.m8Hinted) {
|
|
565
|
+
maybeInject(ctx, sid, {
|
|
566
|
+
ruleId: "__engram-gap",
|
|
567
|
+
reason: getMessage("memory.sediment-missing")
|
|
568
|
+
});
|
|
569
|
+
}
|
|
544
570
|
}
|
|
545
571
|
// F1(2026-08-28 阶段三):规则 2 时序竞态修复——assistant/message 检测到时间词违规时
|
|
546
572
|
// 不立即投递(Get-Date 工具常在本回合后续步骤才执行,事故实弹:correct 早于 Get-Date 放行),
|
|
@@ -871,8 +897,34 @@ export function handleSessionEvent(ctx, session, event) {
|
|
|
871
897
|
const cmd = pendingCall.args?.command || pendingCall.args?.code || "";
|
|
872
898
|
const m8Cfg2 = state.localIntegrations?.m8 || {};
|
|
873
899
|
const marker = m8Cfg2.entryMarker;
|
|
874
|
-
|
|
900
|
+
// C1(批 3,2026-09-15):判据由「命令文本含 entryMarker」收紧为
|
|
901
|
+
// 「命令含 entryMarker **且 结果文本含落盘成功标志**」。
|
|
902
|
+
// 原判据不区分「跑了」与「跑成」:--dry-run(不写入)、写隔离夹具目录、脚本中途失败
|
|
903
|
+
// (工具层不标 isError)都会置位 manualWriteSeen → turn/end 误判 __engram-gap 并要求
|
|
904
|
+
// 同回合 engram_store(2026-09-15 会话实证:dry-run 亦被提示)。
|
|
905
|
+
// 成功标志走配置 successMarker(与 entryMarker 同风格;未配置时用统一入口的既有标志)。
|
|
906
|
+
const successMarker = m8Cfg2.successMarker || "MANUAL_WRITE_OK";
|
|
907
|
+
const m8ResultText = String(
|
|
908
|
+
resultBlock?.content?.map?.((c) => c?.text || "").join(" ") ||
|
|
909
|
+
d?.message?.content?.map?.((c) => c?.text || "").join(" ") ||
|
|
910
|
+
""
|
|
911
|
+
);
|
|
912
|
+
if (m8Cfg2.enabled === true && marker && (pendingCall.name === "pwsh" || pendingCall.name === "bash")
|
|
913
|
+
&& cmd.includes(marker) && m8ResultText.includes(successMarker)) {
|
|
875
914
|
s.turn.manualWriteSeen = true;
|
|
915
|
+
// D(2026-09-13):提示前移——落盘被识别的这一刻就提示(陈述式),不再等到 turn/end
|
|
916
|
+
//(turn/end 之后模型已停止行动,提示只能影响下一回合=事后才说,正是 M8 反复断链的结构原因)。
|
|
917
|
+
// 分工:此处=事中提示(预防);turn/end 的 engram-gap 审计保留,兜底投递仅在此处未提示时进行。
|
|
918
|
+
// 防重复:maybeInject 的闸是「同规则最多 3 次」(phase1f-inject.test.mjs 用例 5 为证),
|
|
919
|
+
// 并非一次即止 —— 故必须由回合标记 m8Hinted 保证「每个回合最多提示一次」。
|
|
920
|
+
// 默认关(hintOnWrite),与 M8 系列一贯的「显式启用」风格一致。
|
|
921
|
+
if (m8Cfg2.hintOnWrite === true && !s.turn.engramStoreSeen && !s.turn.m8Hinted) {
|
|
922
|
+
s.turn.m8Hinted = true;
|
|
923
|
+
maybeInject(ctx, sid, {
|
|
924
|
+
ruleId: "__engram-gap",
|
|
925
|
+
reason: getMessage("memory.sediment-hint")
|
|
926
|
+
});
|
|
927
|
+
}
|
|
876
928
|
}
|
|
877
929
|
if (pendingCall.name === "engram_store") {
|
|
878
930
|
s.turn.engramStoreSeen = true;
|
|
@@ -1190,9 +1242,12 @@ export function handleSessionEvent(ctx, session, event) {
|
|
|
1190
1242
|
const critHit = violations.find((v) => v.mode === "criticism" && v.suspect);
|
|
1191
1243
|
if (critHit) {
|
|
1192
1244
|
s.turn.criticismSuspect = critHit.suspect;
|
|
1193
|
-
|
|
1245
|
+
// C3(批 3,2026-09-15,用户裁定方案 A):冻结判定抽为纯函数——强信号下只有**完整执行许可**
|
|
1246
|
+
// (许可词 + 执行语)才豁免,避免把用户同一条消息里同时下达的指令一起冻掉。
|
|
1247
|
+
const freezeCrit = criticismFreezeBySignal(critHit.suspect, s.turn.userText || "");
|
|
1248
|
+
if (freezeCrit) {
|
|
1194
1249
|
s.turn.criticismFrozen = true;
|
|
1195
|
-
} else {
|
|
1250
|
+
} else if (critHit.suspect === "weak") {
|
|
1196
1251
|
// 弱信号(2026-09-09 第三批后果分级):不冻结;含执行指令词则仅留痕不提示
|
|
1197
1252
|
const execDirective = hasExplicitExecWord(s.turn.userText || "");
|
|
1198
1253
|
if (!execDirective) {
|
|
@@ -1201,13 +1256,18 @@ export function handleSessionEvent(ctx, session, event) {
|
|
|
1201
1256
|
reason: "用户消息疑似批评/质问形态(规则 22②,待确认):若确属批评——批评不构成任何授权,请先按归因四问产出三件套(事实/原因/方案)再等指令;若只是普通提问可忽略本条"
|
|
1202
1257
|
});
|
|
1203
1258
|
}
|
|
1259
|
+
} else {
|
|
1260
|
+
// C3:强信号 + 完整执行许可 → 不冻结,但**不静默放行**(留痕 + 陈述式提示;用户裁定「给一句提示」)
|
|
1261
|
+
maybeInject(ctx, sid, { ruleId: "22", reason: getMessage("criticism.strong-permitted") });
|
|
1204
1262
|
}
|
|
1205
1263
|
audit({
|
|
1206
1264
|
kind: "criticism-suspect",
|
|
1207
1265
|
rule: "22",
|
|
1208
1266
|
name: "批评疑似信号(A″)",
|
|
1209
1267
|
event: "assistant/message",
|
|
1210
|
-
reason: critHit.suspect === "strong"
|
|
1268
|
+
reason: critHit.suspect === "strong"
|
|
1269
|
+
? (freezeCrit ? "信号=strong——本回合写类工具冻结(A″ 强信号闸)" : "信号=strong + 检出完整执行许可——不冻结(C3 方案 A 豁免,已提示)")
|
|
1270
|
+
: "信号=weak——不冻结(后果分级:留痕+提示)",
|
|
1211
1271
|
session: sid
|
|
1212
1272
|
});
|
|
1213
1273
|
}
|
|
@@ -1261,7 +1321,7 @@ export function handleSessionEvent(ctx, session, event) {
|
|
|
1261
1321
|
|
|
1262
1322
|
// ── /guard 子命令单一真源(0.5.13:hint 手写漏 tools 修复)──
|
|
1263
1323
|
// hint(输入框提示)与 USAGE(完整帮助)均由此派生:未来新增子命令只改这一个数组。
|
|
1264
|
-
const COMMAND_SPECS = [
|
|
1324
|
+
export const COMMAND_SPECS = [
|
|
1265
1325
|
{ name: "status", args: "", desc: "引擎状态" },
|
|
1266
1326
|
{ name: "rules", args: "", desc: "规则清单 + 理解产物" },
|
|
1267
1327
|
{ name: "active", args: "", desc: "最近激活的规则" },
|
|
@@ -1278,6 +1338,8 @@ const COMMAND_SPECS = [
|
|
|
1278
1338
|
{ name: "contract", args: "", desc: "查看当前任务契约" },
|
|
1279
1339
|
{ name: "contract categories", args: "...", desc: "设定契约类别白名单(0.5.12)" },
|
|
1280
1340
|
{ name: "label", args: "<id> <label>", desc: "给审计记录打标(correct/incorrect/inconclusive)" },
|
|
1341
|
+
// C4(批 3,2026-09-15):质量账本查询——机制在 core/quality-ledger.js,本项补的是**缺失的入口**
|
|
1342
|
+
{ name: "quality", args: "", desc: "质量账本趋势(同类任务签名 × 返工/介入/摩擦/token)" },
|
|
1281
1343
|
{ name: "approve", args: "<type> <路径> [min]", desc: "物理确认:授予指定类型+路径的临时授权(仅用户输入;默认 10 分钟)" },
|
|
1282
1344
|
{ name: "freedom", args: "", desc: "查看守卫自由面(不拦什么)与盲区(看不见/判不准什么)(第三批 P1-2)" }
|
|
1283
1345
|
];
|
|
@@ -1296,7 +1358,7 @@ const USAGE = [
|
|
|
1296
1358
|
// eslint-disable-next-line no-unused-vars
|
|
1297
1359
|
const GUARD_HINT = "[" + [...new Set(COMMAND_SPECS.map((s) => s.name.split(" ")[0]))].join("|") + "]";
|
|
1298
1360
|
|
|
1299
|
-
function parseCommand(rawInput) {
|
|
1361
|
+
export function parseCommand(rawInput) {
|
|
1300
1362
|
const text = (rawInput || "").trim();
|
|
1301
1363
|
if (!text || /^(status|state)$/i.test(text)) return { kind: "status" };
|
|
1302
1364
|
if (/^(rules|list|ls)$/i.test(text)) return { kind: "rules" };
|
|
@@ -1325,6 +1387,8 @@ function parseCommand(rawInput) {
|
|
|
1325
1387
|
if (parsed) return { kind: "budget", patch: parsed };
|
|
1326
1388
|
}
|
|
1327
1389
|
if (/^contract$/i.test(text)) return { kind: "contract" };
|
|
1390
|
+
// C4(批 3,2026-09-15):质量账本查询(白名单登记——未登记即落 invalid,见本函数上方注释)
|
|
1391
|
+
if (/^quality$/i.test(text)) return { kind: "quality" };
|
|
1328
1392
|
// 0.5.12(F5 批次 3):/guard contract categories <build,test,install,...> 设定类别白名单
|
|
1329
1393
|
m = text.match(/^contract\s+categories\s+(.+)$/i);
|
|
1330
1394
|
if (m) {
|
|
@@ -1434,6 +1498,26 @@ async function executeGuard(ctx, invocation) {
|
|
|
1434
1498
|
text: `已临时放行全部守卫 ${minutes} 分钟。到期自动恢复,也可 /guard reload 后立即恢复。`
|
|
1435
1499
|
};
|
|
1436
1500
|
}
|
|
1501
|
+
case "quality": {
|
|
1502
|
+
// C4(批 3,2026-09-15):质量账本查询入口。
|
|
1503
|
+
// 语义:账本默认关(rule-engine.json 的 qualityLedger.enabled=false)→ 无数据时**如实说明**、不伪造趋势;
|
|
1504
|
+
// 有数据时按签名分组各出趋势摘要。纯旁路统计:不拦截、不评分、不上传(数据留本机 ~/.dsh/quality-ledger.jsonl)。
|
|
1505
|
+
const qCfg = loadPluginConfig().qualityLedger || {};
|
|
1506
|
+
const qRows = loadLedger({ config: qCfg });
|
|
1507
|
+
if (qRows.length === 0) {
|
|
1508
|
+
return {
|
|
1509
|
+
kind: "success",
|
|
1510
|
+
text: qCfg.enabled
|
|
1511
|
+
? "质量账本已开启,但暂无记录(账本只在任务收尾时记录;纯旁路统计,不拦截)"
|
|
1512
|
+
: "质量账本未开启(rule-engine.json 的 qualityLedger.enabled=false)——开启后按同类任务签名统计返工/介入/摩擦/token 趋势"
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
const bySig = new Map();
|
|
1516
|
+
for (const r of qRows) bySig.set(r.sig, (bySig.get(r.sig) || 0) + 1);
|
|
1517
|
+
const top = [...bySig.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
|
|
1518
|
+
const body = top.map(([sig, n]) => ` ${sig} ${n} 单 ${qualityTrend(sig, { config: qCfg }).summary}`).join("\n");
|
|
1519
|
+
return { kind: "success", text: `质量账本:${qRows.length} 单 / ${bySig.size} 类签名\n${body}` };
|
|
1520
|
+
}
|
|
1437
1521
|
case "approve": {
|
|
1438
1522
|
// C4(2026-09-03)物理确认:用户亲手输入=词表无法误读;最小范围(类型+路径+时长);无全局通配
|
|
1439
1523
|
if (!loadPluginConfig().approveEnabled) {
|
|
@@ -1899,7 +1983,7 @@ export function apply(ctx) {
|
|
|
1899
1983
|
|
|
1900
1984
|
// 1.1 任务契约裁决钩子:deny 物理拒绝 + ask 通道 + 委托预算启动即计。
|
|
1901
1985
|
// 2026-08-25 修复(预算语义偏差,实弹:第 2 个子代理被误拒——允许 N 实际 N-1):
|
|
1902
|
-
// 根因 = 扣减(本钩子)先于 guard 层的 decideContractAction
|
|
1986
|
+
// 根因 = 扣减(本钩子)先于 guard 层的 decideContractAction(该处用已扣 contract 重判 → 2+1>2 → 误拒)。
|
|
1903
1987
|
// 修复 = 本钩子成为契约唯一裁决点(deny 在扣减**前**判定并物理拒绝),guard 层跳过预算类 deny(防双判)。
|
|
1904
1988
|
ctx.on("tools/pre-execute", async (exec, next) => {
|
|
1905
1989
|
try {
|
|
@@ -1983,7 +2067,9 @@ export function apply(ctx) {
|
|
|
1983
2067
|
const sid = sessionIdOfExec(exec);
|
|
1984
2068
|
const s = getSessionState(state, sid);
|
|
1985
2069
|
if (!s?.turn?.criticismFrozen) return next();
|
|
1986
|
-
|
|
2070
|
+
// C2-C 档(批 3,2026-09-15):bypass 窗口内让步(语义=全部守卫暂停,与 guard status 文案一致);
|
|
2071
|
+
// 窗口内的变更类调用由 guard 路径既有的 bypass-action 审计统一留痕(M2),此处不重复记。
|
|
2072
|
+
const freeze = criticismFreezeDecision(s, exec?.name, exec?.arguments, { bypassActive: state.bypassUntil > Date.now() });
|
|
1987
2073
|
if (!freeze) return next();
|
|
1988
2074
|
audit({
|
|
1989
2075
|
kind: "criticism-freeze",
|
|
@@ -1995,6 +2081,19 @@ export function apply(ctx) {
|
|
|
1995
2081
|
reason: `疑似信号=${s.turn.criticismSuspect}(A″ 行为闸:批评不构成授权)`,
|
|
1996
2082
|
session: sid
|
|
1997
2083
|
});
|
|
2084
|
+
// 卡片数据源补齐(2026-09-11 P7):guard 路径(本文件的 guard 命中分支)在命中时写 cardHits,
|
|
2085
|
+
// 本闸此前只 audit → 卡片被记成「本回合无裁决事件」(verdict=clear),用户看不到这次拦截
|
|
2086
|
+
// —— 与 turn-card.js 注释声明的「全量记,含 __ 内部拦截」不符,属遗漏。
|
|
2087
|
+
try {
|
|
2088
|
+
(s.turn.cardHits ||= []).push({
|
|
2089
|
+
tool: exec?.name || "",
|
|
2090
|
+
args: summarizeArgs(exec?.arguments || {}),
|
|
2091
|
+
ruleId: "22",
|
|
2092
|
+
title: "批评回合冻结写类工具",
|
|
2093
|
+
reason: `疑似信号=${s.turn.criticismSuspect}(A″ 行为闸:批评不构成授权)`,
|
|
2094
|
+
errId: ""
|
|
2095
|
+
});
|
|
2096
|
+
} catch { /* 卡片是展示层增强,失败不影响拦截本身 */ }
|
|
1998
2097
|
return {
|
|
1999
2098
|
kind: "deny",
|
|
2000
2099
|
reason: "[guardian:rule22] 【硬拦截】本回合命中批评/指错疑似信号——写类工具已冻结(A″ 行为闸)。请按四段模板回应:① 停止;② 归因四问;③ 三件套(事实/原因/方案);④ 等指令。只读操作不受限。"
|
package/lib/messages.js
CHANGED
|
@@ -42,8 +42,13 @@ export const DEFAULT_MESSAGES = {
|
|
|
42
42
|
// ── 批评检测引用(原 text-detect 引用本机手册章节号,1b 去本机化)──
|
|
43
43
|
"criticism.strong": "User message matched a strong criticism/rebuke signal (rule 22②, pending adjudication): criticism is NOT authorization — write-class tools are frozen this turn. Respond with the four-part template: 1) stop; 2) attribution questions; 3) facts/cause/plan; 4) wait for instructions.",
|
|
44
44
|
"criticism.weak": "User message looks like a criticism/challenge (rule 22②, pending adjudication): if it really is criticism — it is not authorization. Produce facts/cause/plan via the attribution questions, then wait. Ignore this note if it was just an ordinary question.",
|
|
45
|
+
// C3(批 3,2026-09-15,用户裁定方案 A):强信号 + 完整执行许可 → 不冻结,但**不静默放行**(留痕 + 本条提示)。
|
|
46
|
+
// 措辞须为陈述式:注入受 INJECT_COMMAND_RE 静态闸约束(不得含「请直接执行/请立即/立刻执行」等命令词)。
|
|
47
|
+
"criticism.strong-permitted": "User message matched a strong criticism signal BUT also carries a complete execution permission (approval word + action speech) — write-class tools are NOT frozen this turn and that permission stands. The criticism itself still grants nothing extra.",
|
|
45
48
|
// ── M8 记忆沉淀提示(原 index.js 引用本机规则号,1b 去本机化)──
|
|
46
49
|
"memory.sediment-missing": "This turn persisted manual/AGENTS changes through the configured entry script but did not call engram_store in the same turn; the memory-sediment chain should complete within one turn.",
|
|
50
|
+
// ── D(2026-09-13):提示前移到落盘那一刻(陈述式,不含命令词;区别于上面的 turn/end 违规判定文案)──
|
|
51
|
+
"memory.sediment-hint": "This turn persisted manual/AGENTS changes through the configured entry script; the memory-sediment chain expects an engram_store call in this same turn.",
|
|
47
52
|
// ── 规则放行提示(原 guard-core RULE_HINTS,1c 迁出)──
|
|
48
53
|
"rule-hint.1": "Analyze the root cause first, then continue once the problem is confirmed",
|
|
49
54
|
"rule-hint.9": "Use a script file, or an explicit UTF-8 (no BOM) flow",
|
package/lib/service.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
// service.js —— 规则引擎 host 端 Remote 服务
|
|
2
2
|
// 供设置面板(dsh-rules-manager-client 的“规则引擎”页签)通过 ctx.remote.ruleEngine.* 调用。
|
|
3
3
|
// 与 rules-manager/service.js 同构:TypertRemoteService + 手动 @Remote 标记。
|
|
4
|
+
// 2026-09-14 修(B0-2 实测踩坑):readFile 必须来自 node:fs/promises——
|
|
5
|
+
// 从 node:fs 引的是回调版(第三参须为函数),按 Promise 写法 await readFile(p, "utf8")
|
|
6
|
+
// 会抛 `The "cb" argument must be of type function. Received type string ('utf8')`;
|
|
7
|
+
// 该异常被本文件 whitelistStatus 的 try/catch 吞进 debug.readError,外层仍回 ok:true
|
|
8
|
+
// → 面板把"读取失败"显示成"白名单为空"(静默弱化)。readFileSync 仍走 node:fs。
|
|
4
9
|
import { readFileSync } from "node:fs";
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
5
12
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
13
|
import { state } from "./core/runtime.js";
|
|
7
14
|
import { readAuditLog, audit } from "./core/audit.js";
|
|
@@ -15,6 +22,7 @@ const REMOTE_METHODS = [
|
|
|
15
22
|
"getVersion",
|
|
16
23
|
"checkUpdate",
|
|
17
24
|
"getAuditLog",
|
|
25
|
+
"whitelistStatus",
|
|
18
26
|
"getUnderstanding",
|
|
19
27
|
"getTaskContractConfig",
|
|
20
28
|
"setTaskContractConfig",
|
|
@@ -178,6 +186,41 @@ class RuleEngineService extends TypertRemoteService {
|
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
188
|
|
|
189
|
+
/** 工具放行白名单状态(B0-2,2026-09-14 自 dsh-rules-manager 移植):
|
|
190
|
+
* 永久清单(`rule-engine-tools.json`)+ 近 24h 会话放行记录(从 `rule-engine.log.jsonl` 派生);
|
|
191
|
+
* `debug` 字段供面板白名单区异常时一眼定位。移植理由=引擎自带设置页(B0)不该依赖规则管理包。 */
|
|
192
|
+
async whitelistStatus() {
|
|
193
|
+
const home = dshHome();
|
|
194
|
+
const debug = { home };
|
|
195
|
+
const permanent = [];
|
|
196
|
+
try {
|
|
197
|
+
const raw = await readFile(join(home, "rule-engine-tools.json"), "utf8");
|
|
198
|
+
debug.fileLen = raw.length;
|
|
199
|
+
const arr = JSON.parse(raw);
|
|
200
|
+
if (Array.isArray(arr)) {
|
|
201
|
+
for (const it of arr) {
|
|
202
|
+
if (typeof it === "string") permanent.push({ name: it, time: null, session: null });
|
|
203
|
+
else if (it && typeof it.name === "string") permanent.push({ name: it.name, time: it.time ?? null, session: it.session ?? null });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (e) { debug.readError = String((e && e.message) || e); }
|
|
207
|
+
const sessionAdded = [];
|
|
208
|
+
try {
|
|
209
|
+
const log = await readFile(join(home, "rule-engine.log.jsonl"), "utf8");
|
|
210
|
+
const cutoff = Date.now() - 24 * 3600 * 1000;
|
|
211
|
+
for (const line of log.split("\n")) {
|
|
212
|
+
if (!line.includes("unknown-tool-whitelist")) continue;
|
|
213
|
+
const name = line.match(/"reason":"[^"]*允许使用 ([A-Za-z_][A-Za-z0-9_:.-]*)/);
|
|
214
|
+
const ts = line.match(/"ts":"([^"]+)"/);
|
|
215
|
+
const sid = line.match(/"session":"([^"]+)"/);
|
|
216
|
+
if (name && sid && ts && new Date(ts[1]).getTime() >= cutoff) {
|
|
217
|
+
sessionAdded.push({ name: name[1], session: sid[1], time: ts[1] });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch { /* 无日志 = 无派生记录 */ }
|
|
221
|
+
return { ok: true, debug, permanent, sessionAdded: sessionAdded.slice(-50).reverse() };
|
|
222
|
+
}
|
|
223
|
+
|
|
181
224
|
/** 规则理解摘要(ruleId/title/level/handler/confidence/actions) */
|
|
182
225
|
async getUnderstanding() {
|
|
183
226
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rule-engine",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "DSH 规则执行引擎 v3:容器解析 AGENTS.md + 理解器 + 匹配机 + 执行框架",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"lib",
|
|
14
|
-
"scripts",
|
|
15
|
-
"!scripts/local-residue-markers.txt",
|
|
16
|
-
"!scripts/dualtrack-baseline.json",
|
|
17
|
-
"upgrade-impact.json",
|
|
18
14
|
"cordis.patch.yml",
|
|
19
15
|
"README.md",
|
|
20
16
|
"LICENSE"
|
|
@@ -44,14 +40,12 @@
|
|
|
44
40
|
},
|
|
45
41
|
"scripts": {
|
|
46
42
|
"test": "node test/run-all.mjs",
|
|
47
|
-
"check": "node --check lib/index.js"
|
|
48
|
-
"verify": "node scripts/verify-all.mjs",
|
|
49
|
-
"audit:mount": "node scripts/audit-mount-consistency.mjs --profile web",
|
|
50
|
-
"check:meta": "node scripts/readme-version-check.mjs && node scripts/local-residue-scan.mjs && node scripts/dualtrack-check.mjs"
|
|
43
|
+
"check": "node --check lib/index.js"
|
|
51
44
|
},
|
|
52
45
|
"peerDependencies": {
|
|
53
46
|
"@deepseek-ai/dsh-home-paths": ">=0.1.0-rc.3 <0.2.0 || >=0.1.1-rc.0 <0.2.0 || >=0.1.2-rc.0 <0.2.0",
|
|
54
|
-
"@deepseek-ai/dsh-typert-protocol": ">=0.1.0-rc.3 <0.2.0 || >=0.1.1-rc.0 <0.2.0 || >=0.1.2-rc.0 <0.2.0"
|
|
47
|
+
"@deepseek-ai/dsh-typert-protocol": ">=0.1.0-rc.3 <0.2.0 || >=0.1.1-rc.0 <0.2.0 || >=0.1.2-rc.0 <0.2.0",
|
|
48
|
+
"dsh-rule-engine-client": ">=0.1.0 <0.2.0"
|
|
55
49
|
},
|
|
56
50
|
"dsh": {
|
|
57
51
|
"bundle": {
|