@leonhardthust/dsh-decision-engine 1.9.2 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/providers.js CHANGED
@@ -93,9 +93,9 @@ const ALTERNATE_STYLE = { decisions: "systemone", systemone: "decisions" };
93
93
  /**
94
94
  * 合法的思考模式(与 startlux 服务的 `think_mode` 取值一致)。
95
95
  *
96
- * never 不思考——最省延迟(实测约 1.9s vs 7.2s),默认值
97
- * auto 仅在模型自己判断需要时思考
98
- * always 总是思考——判断更充分,但热路径上每次多等约 5s
96
+ * never 不思考——最省延迟(bamboonode 实测;默认值)
97
+ * auto 由服务端决定——实测比 never 慢(约 7.2s vs 1.9s)
98
+ * always 总是思考——每次多等约 5s;质量收益尚未实测
99
99
  *
100
100
  * 单独导出是为了让校验、前端下拉、文档三处只有一份取值来源。
101
101
  */
@@ -131,7 +131,7 @@ export function normalizeThinkMode(raw) {
131
131
  * 默认模型与三个配置字段名。用户选了预设就用这里的值,不需要在设置界面
132
132
  * 重复填写。
133
133
  *
134
- * 两个可选标记(都是「按服务商实测」得出的,不是风格偏好):
134
+ * 三个可选标记(都是「按服务商实测」得出的,不是风格偏好):
135
135
  *
136
136
  * - `requiresModel: true`:该端点**不接受**省略 model 的请求(实测
137
137
  * SiliconFlow 的 /v1/systemone 省略或空串都回 400
@@ -144,6 +144,14 @@ export function normalizeThinkMode(raw) {
144
144
  * `supported_endpoint_types`(实测 SiliconFlow 返回 98 个,其中 chat
145
145
  * 模型打到 /v1/systemone 是 400 `Model does not exist.`)。
146
146
  * 列了这份清单就按它筛,不让下拉里出现「选了就失败」的选项。
147
+ * - `thinkModeSupport`:`think_mode` 这个字段该端点认不认。`supported` =
148
+ * 实测认(bamboonode 的决策端点就是靠它区分 never / auto / always);
149
+ * `unknown` = 未实测(TypeSafe、两个 SiliconFlow 站点与 custom 网关都会
150
+ * 收下请求,但「思考」是否真被兑现没有验证过——SiliconFlow 的实测只覆盖
151
+ * 了路径与 model 必填,没覆盖 think_mode);`unsupported` = 公开 API 是
152
+ * chat 补全形状、没有 think_mode 这个字段(Perplexity / OpenRouter)。
153
+ * 界面据此决定要不要提示「切 always 会更快/更慢」,而不是对每家的行为
154
+ * 都编一套说法。
147
155
  */
148
156
  export const PROVIDER_PRESETS = {
149
157
  systemone: {
@@ -160,6 +168,9 @@ export const PROVIDER_PRESETS = {
160
168
  defaultModel: "jev-latest",
161
169
  modelField: "systemoneModel",
162
170
  keyEnvField: "systemoneApiKeyEnv",
171
+ // 请求体形状与 bamboonode 相同(见模块头的线协议约定),但该端点对
172
+ // think_mode 是否真被兑现未实测,因此给 unknown 而不是 supported。
173
+ thinkModeSupport: "unknown",
163
174
  hint: "TypeSafe / Jev 的 System One 协议(POST /v1/systemone),"
164
175
  + "也是 llama.cpp、vLLM 等自建决策服务通用的线协议",
165
176
  hintEn: "TypeSafe / Jev System One protocol (POST /v1/systemone); "
@@ -181,6 +192,9 @@ export const PROVIDER_PRESETS = {
181
192
  defaultModel: "startlux-decision-v1.1-27b-autothink",
182
193
  modelField: "model",
183
194
  keyEnvField: "bamboonodeApiKeyEnv",
195
+ // 这是 think_mode 的原始出处:never / auto / always 三档由该端点兑现
196
+ //(实测 never ≈1.9s、auto ≈7.2s;always 的额外耗时未单独实测)。
197
+ thinkModeSupport: "supported",
184
198
  hint: "StartLux 官方端点(POST /v1/decisions),本插件的原有默认值",
185
199
  hintEn: "StartLux official endpoint (POST /v1/decisions); the original default",
186
200
  },
@@ -208,6 +222,9 @@ export const PROVIDER_PRESETS = {
208
222
  // 不带 supported_endpoint_types(实测把 chat 模型打过来是 400
209
223
  // `Model does not exist.`),所以按这份清单筛,不让下拉里出现选了就坏的项。
210
224
  knownModels: ["Kev-4B", "SemIf"],
225
+ // think_mode 在 SiliconFlow 上没有实测过(实测只覆盖了路径与 model 必填),
226
+ // 因此给 unknown:界面不该声称「切 always 会更准」或「会更快」。
227
+ thinkModeSupport: "unknown",
211
228
  modelField: "siliconflowModel",
212
229
  keyEnvField: "siliconflowApiKeyEnv",
213
230
  hint: "硅基流动(国内站)的 System One 端点(POST /v1/systemone);可用模型 Kev-4B / SemIf,默认 Kev-4B,只填一个 API key 即可使用",
@@ -228,6 +245,8 @@ export const PROVIDER_PRESETS = {
228
245
  requiresModel: true,
229
246
  defaultModel: "Kev-4B",
230
247
  knownModels: ["Kev-4B", "SemIf"],
248
+ // 与国内站同源,同样没有覆盖过 think_mode 的实测。
249
+ thinkModeSupport: "unknown",
231
250
  modelField: "siliconflowIntlModel",
232
251
  keyEnvField: "siliconflowIntlApiKeyEnv",
233
252
  hint: "Silicon Flow(国际站)的 System One 端点(POST /v1/systemone);与国内站账号不通用,可用模型 Kev-4B / SemIf,默认 Kev-4B,只填一个 API key 即可使用",
@@ -251,6 +270,9 @@ export const PROVIDER_PRESETS = {
251
270
  // 占位值,未实测。
252
271
  requiresModel: true,
253
272
  defaultModel: "sonar",
273
+ // Perplexity 的公开 API 是 chat 补全形状,没有 think_mode 这个字段:
274
+ // 带上它只会被忽略或 400,因此明确标 unsupported。
275
+ thinkModeSupport: "unsupported",
254
276
  modelField: "perplexityModel",
255
277
  keyEnvField: "perplexityApiKeyEnv",
256
278
  hint: "Perplexity Decider 端点(api.perplexity.ai);地址与默认模型待实测,接通后建议把端点风格钉死",
@@ -270,6 +292,8 @@ export const PROVIDER_PRESETS = {
270
292
  // requiresModel;默认 slug 是占位值,未实测,换模型直接改配置即可。
271
293
  requiresModel: true,
272
294
  defaultModel: "openai/gpt-4o-mini",
295
+ // 同 Perplexity:OpenRouter 的 /chat/completions 形状里没有 think_mode。
296
+ thinkModeSupport: "unsupported",
273
297
  modelField: "openrouterModel",
274
298
  keyEnvField: "openrouterApiKeyEnv",
275
299
  hint: "OpenRouter 端点(openrouter.ai/api/v1);模型填 OpenRouter 的 slug,地址与默认模型待实测",
@@ -287,6 +311,9 @@ export const PROVIDER_PRESETS = {
287
311
  legacyApiKeyEnvs: [],
288
312
  keyField: "customApiKey",
289
313
  defaultModel: "",
314
+ // 用户自己的服务:认不认 think_mode 只有他知道,因此 unknown 而不是
315
+ // supported——能力位是「已知事实」,不是「希望如此」。
316
+ thinkModeSupport: "unknown",
290
317
  modelField: "customModel",
291
318
  keyEnvField: "customApiKeyEnv",
292
319
  hint: "任意提供决策端点的服务:自建 llama.cpp / vLLM、企业网关、第三方中转",
@@ -624,15 +651,23 @@ export function parseCustomHeaders(raw) {
624
651
  *
625
652
  * @param cfg - 已合并的配置对象(可能缺字段)。
626
653
  * @param options.server - 服务端下发的覆盖值(`{ baseUrl, model, endpointStyle }`)。
627
- * 存在时整段走覆盖分支,本地配置一律不读:服务端下发代表**当前权威配置**,
628
- * 若还让本地字段参与覆盖,用户机器上残留的旧地址就会盖掉管理员的改动。
654
+ * 下发载荷里**没有** thinkMode 这一项(见 `server-config.js` 的 `parseServerConfig`,
655
+ * 它只返回 baseUrl / model / endpointStyle / key / ttlSeconds)。存在时整段走
656
+ * 覆盖分支,本地配置只保留 `thinkMode` 一项:它是**本机行为偏好**(省延迟还是
657
+ * 判得更充分),与「往哪发、用谁的 key」无关,跟着服务端下发一起丢掉会让用户
658
+ * 配的 `always` 静默退回 `never`。其余本地字段一律不读——服务端下发代表
659
+ * **当前权威配置**,若还让本地字段参与覆盖,用户机器上残留的旧地址就会盖掉
660
+ * 管理员的改动。
629
661
  * @returns 生效配置;`valid=false` 时 `error` 是可展示的中文原因。
630
662
  */
631
663
  export function resolveProvider(cfg = {}, options = {}) {
632
664
  // 只有拿到对象才算「服务端下发了配置」:null / 字符串等一律视为没有,
633
665
  // 完全走既有逻辑。既有调用方只传 cfg,因此升级后行为逐字不变。
634
666
  const server = options?.server;
635
- if (server !== null && typeof server === "object") return buildServerProvider(server);
667
+ if (server !== null && typeof server === "object") {
668
+ // 可选链:这条早退分支过去完全不读 cfg,别为了一项本机偏好引入新的抛错点。
669
+ return buildServerProvider(server, cfg?.thinkMode);
670
+ }
636
671
 
637
672
  const requested = typeof cfg.provider === "string" ? cfg.provider.trim() : "";
638
673
  const id = PROVIDER_ID_SET.has(requested) ? requested : DEFAULT_PROVIDER;
@@ -746,14 +781,21 @@ export function resolveProvider(cfg = {}, options = {}) {
746
781
  * 因此这里 `explicitKey` 为空、`apiKeyEnv` 也为空。刻意不留本地变量名:
747
782
  * 留了就会在下发失效时退回用户自己的 key 打向服务端地址,那是拿错了
748
783
  * 账号的 key,而守卫是 fail-open 的,表现为静默失效。宁可「没有 key」。
784
+ * 3. `thinkMode` 仍是**本机**设定(由 `resolveProvider` 从 cfg 传进来):
785
+ * 它是省延迟还是判得更充分的偏好,不属于「往哪发、用谁的 key」这套下发
786
+ * 契约。此前这里整个字段缺失,`buildRequestBody` 的
787
+ * `normalizeThinkMode(payload.thinkMode ?? provider.thinkMode)` 于是收到
788
+ * undefined → 恒为 never,用户配的 `always` 在服务端下发期间静默失效。
749
789
  *
750
790
  * base URL 非法时不抛错,只把 `valid` 置 false 并给出中文原因:解析点遍布
751
791
  * 请求路径,抛错会把一次「配置暂时不可用」放大成决策失败。
752
792
  *
753
793
  * @param server - 服务端下发的覆盖值,字段可能缺失或类型不对。
794
+ * @param configuredThinkMode - 本机配置里的 `thinkMode`(未归一,交给
795
+ * `normalizeThinkMode` 兜底)。
754
796
  * @returns 与预设分支同形状的 provider 对象。
755
797
  */
756
- function buildServerProvider(server) {
798
+ function buildServerProvider(server, configuredThinkMode) {
757
799
  const checked = validateBaseUrl(server.baseUrl);
758
800
  // 风格必须是三者之一;缺失或非法回落 auto,避免拼出 `/undefined` 这类路径。
759
801
  const endpointStyle = ENDPOINT_STYLES.includes(server.endpointStyle) ? server.endpointStyle : "auto";
@@ -772,6 +814,8 @@ function buildServerProvider(server) {
772
814
  candidates: [...plan.candidates],
773
815
  endpointStyle,
774
816
  model,
817
+ // 与两个本地分支逐字相同:非法/缺失一律归一为 never。
818
+ thinkMode: normalizeThinkMode(configuredThinkMode),
775
819
  headers: {},
776
820
  apiKeyEnv: "",
777
821
  legacyApiKeyEnvs: [],
@@ -918,6 +962,11 @@ export function providerCatalog() {
918
962
  // 都由预设表下发,前端不必知道任何一家服务商的脾气。
919
963
  requiresModel: preset.requiresModel === true,
920
964
  knownModels: [...(preset.knownModels ?? [])],
965
+ // think_mode 能力位:`supported` / `unknown` / `unsupported`。
966
+ // 界面据此决定「思考模式」那一格要不要提示代价、要不要警告可能无效,
967
+ // 而不是对每家服务商各编一套说法。缺省按 unknown 处理——未知不等于
968
+ // 可用,也不等于不可用。
969
+ thinkModeSupport: preset.thinkModeSupport ?? "unknown",
921
970
  hint: preset.hint,
922
971
  hintEn: preset.hintEn,
923
972
  customizable: id === "custom",
package/lib/tolerance.js CHANGED
@@ -27,21 +27,30 @@
27
27
  *
28
28
  * ## 四档语义
29
29
  *
30
- * 自治 autonomous 消除软询问;mandatory deny 与必须人工批准的操作不受影响。
31
- * 必须审批但审批不可用时直接拒绝,不能用模型替代许可。
32
- * 轻松 relaxed 只有严重度排在最前的 1% 才询问用户。
33
- * 默认 default 按决策模型自己的判断提问(**当前的默认行为**,逐字不变)。
34
- * 谨慎 cautious 在默认判断之外,再把严重度靠前的 25% 也拿来询问。
35
- *
36
- * 三档之间是**单调**的(校准完成后):1% ≤ 默认 ≤ 默认 ∪ 25%。自治是例外——
37
- * 它是「不打扰」,不是「按分位数」。
30
+ * 不问 autonomous 软性询问一律不弹窗、不按比例;mandatory deny 照旧,
31
+ * 必须人工批准的操作**仍然询问**(审批不可用时直接拒绝,
32
+ * 不能用模型替代许可)。
33
+ * 少问 relaxed 只按严重度尾部:校准后约最前的 1%,校准前固定分 0.60。
34
+ * 它**替换**默认判断而不是叠加,因此同一次调用既可能
35
+ * 从问变成不问,也可能从放行变成问。
36
+ * 默认 default 沿用决策模型自己的判断提问(**当前的默认行为**,逐字不变)。
37
+ * 多问 cautious 在默认判断之上**再叠加**严重度靠前的一批:校准后约 25%,
38
+ * 校准前固定分 0.30。取并集,所以逐调用一定 ⊇ 默认。
39
+ *
40
+ * 四档之间只有**汇总询问率**单调(少问 ≤ 默认 ≤ 多问;具体数值取决于决策
41
+ * 模型与阈值,DEC-035 那张 22.5% / 41% 的实测表来自已经退役的旧判定路径,
42
+ * 不能当成当前默认档的询问率)。
43
+ * 逐调用并不单调:少问档是替换语义,同一次调用与默认判断可以**双向**不同;
44
+ * 不问档只是不弹软性询问,强制审批那条路径与其它档完全一样。
38
45
  *
39
46
  * ## 校准不足时怎么办
40
47
  *
41
48
  * 窗口里还没攒够样本时**算不出可靠的分位数**。此时不猜分位数,改用
42
- * `fallbackSeverity`——这几个绝对值是**在真实数据上量出来的典型分位数**
43
- * (见 DEC-035 的实测表),因此冷启动的行为与校准后的行为量级一致,不会
44
- * 出现「一开始问很多、攒够数据后突然变安静」这种跳变。
49
+ * `fallbackSeverity`——这几个绝对值是**在真实数据上量出来的典型分界线**
50
+ * (见 DEC-035 的实测表)。代价要说实话:冷启动比校准后**更安静**——
51
+ * 少问档冷启动阈值覆盖 0.47%(校准后的 P99 ≈0.50 覆盖 1.1%),多问档
52
+ * 冷启动覆盖 19.3%(校准后的 P75 ≈0.28 覆盖 25.1%)。因此攒够 50 个样本的
53
+ * 那一刻是**变吵**一点,而不是变安静。
45
54
  *
46
55
  * 校准状态会显示在界面上(校准中 n/50),用户不会以为设置没生效。
47
56
  *
@@ -58,7 +67,7 @@ export const DEFAULT_TOLERANCE = "default";
58
67
  * 滚动窗口容量(每个 provider × model 一份)。
59
68
  *
60
69
  * 取 200 的理由:分位数要稳,至少要几十个样本;200 个样本能让 P99 落在
61
- * 「最���的那一两个」之外(插值后是一个连续阈值),同时窗口不必大到让
70
+ * 「最前的那一两个」之外(插值后是一个连续阈值),同时窗口不必大到让
62
71
  * 「最近」失去意义——用户改用模型后,几十次调用之内就该反映新分布。
63
72
  */
64
73
  export const TOLERANCE_WINDOW_SIZE = 200;
@@ -117,44 +126,45 @@ export const HIGH_RISK_FLOOR_SCORE = 0.7;
117
126
  export const TOLERANCE_PRESETS = {
118
127
  autonomous: {
119
128
  id: "autonomous",
120
- label: "自治",
121
- labelEn: "Autonomous",
122
- summary: "免除软询问;保留强制审批与拒绝,无人审批时阻止未授权操作",
123
- summaryEn: "Skips soft questions; preserves mandatory approval and denial, blocking unapproved actions when approval is unavailable",
129
+ label: "不问",
130
+ labelEn: "Don't ask",
131
+ summary: "软性审查不弹窗、不按比例;权限强制的人工批准与拒绝照旧",
132
+ summaryEn: "Soft checks never prompt and no percentile band applies; permission-mandated approval and denial are unchanged",
124
133
  percentile: null,
125
- // 自治档不走分位数路径,这里的值只是「让通用分支也不会触发」的占位。
134
+ // 不问档不走分位数路径,这里的值只是「让通用分支也不会触发」的占位。
126
135
  fallbackSeverity: Number.POSITIVE_INFINITY,
127
136
  },
128
137
  relaxed: {
129
138
  id: "relaxed",
130
- label: "轻松",
131
- labelEn: "Relaxed",
132
- summary: "只有严重度排在最前的 1% 才询问用户",
133
- summaryEn: "Only the top 1% by severity asks the user",
139
+ label: "少问",
140
+ labelEn: "Ask less",
141
+ summary: "只按严重度尾部:校准后约最前 1%,校准前固定分 0.60;替换默认判断,因此可能多问也可能少问",
142
+ summaryEn: "Severity tail only: about the top 1% once calibrated, a fixed score 0.60 before that; replaces the default judgement, so it can ask more or less often",
134
143
  percentile: 0.99,
135
144
  // 实测(1421 条真实授权决策,见 DEC-035):severity ≥ 0.60 覆盖 0.47%
136
- // 的调用,与 P99 分位(≈0.50,覆盖 1.1%)在同一量级。冷启动时用它,
137
- // 行为与校准后一致,不会出现「一开始问一堆、攒够数据后突然变安静」。
145
+ // 的调用,而校准后的 P99(≈0.50)覆盖 1.1%。冷启动时用它,所以冷启动
146
+ // 比校准后**更安静**;攒够样本的那一刻是稍微变吵,不是变安静。
138
147
  fallbackSeverity: 0.6,
139
148
  },
140
149
  default: {
141
150
  id: "default",
142
151
  label: "默认",
143
152
  labelEn: "Default",
144
- summary: "按决策模型自己的判断提问(当前的默认行为)",
145
- summaryEn: "Ask according to the decision model's own judgement (current behaviour)",
153
+ summary: "沿用决策模型判断:中/高风险、疑似注入、不可逆且与任务无关会问",
154
+ summaryEn: "Keeps the decision model's own judgement: asks on medium/high risk, suspected injection, or irreversible and off-task calls",
146
155
  percentile: null,
147
156
  fallbackSeverity: null,
148
157
  },
149
158
  cautious: {
150
159
  id: "cautious",
151
- label: "谨慎",
152
- labelEn: "Cautious",
153
- summary: "在默认判断之外,再把严重度靠前的 25% 也拿来询问",
154
- summaryEn: "Beyond the default judgement, also ask for the top 25% by severity",
160
+ label: "多问",
161
+ labelEn: "Ask more",
162
+ summary: "默认判断之上再加严重度靠前一批:校准后约 25%,校准前固定分 0.30",
163
+ summaryEn: "Adds the top severity band on top of the default judgement: about 25% once calibrated, a fixed score 0.30 before that",
155
164
  percentile: 0.75,
156
- // 实测:severity ≥ 0.30 覆盖 19.3% 的调用;P75 分位(≈0.28)覆盖 25.1%。
157
- // 兜底值取实测的中位口径,量级一致。
165
+ // 实测:severity ≥ 0.30 覆盖 19.3% 的调用;校准后的 P75 分位(≈0.28)
166
+ // 覆盖 25.1%。两个比例都只描述「叠加的那一批」;并入默认判断之后的
167
+ // 总询问率取决于默认判断本身,不是这两个数之和。
158
168
  fallbackSeverity: 0.3,
159
169
  },
160
170
  };
@@ -389,9 +399,9 @@ export function resolveTolerance(options = {}) {
389
399
 
390
400
  // 拦截(deny)不是「打扰用户」,四档一律保留。
391
401
  //
392
- // 这条边界必须守住:自治档的语义是「不问我」,不是「什么都放行」。
402
+ // 这条边界必须守住:不问档的语义是「软性询问不弹窗」,不是「什么都放行」。
393
403
  // deny 不会打断用户(它直接告诉模型「这次调用被拒了」),而把它一并
394
- // 取消等于让自治档变成「关掉安全检查」——那与用户说的「0% 询问」是两件事。
404
+ // 取消等于让不问档变成「关掉安全检查」——那与用户说的「0% 询问」是两件事。
395
405
  if (legacyKind === "deny") {
396
406
  return { kind: "deny", changed: false, severity, cutoff: null, note: "" };
397
407
  }
@@ -413,7 +423,7 @@ export function resolveTolerance(options = {}) {
413
423
  changed: true,
414
424
  severity,
415
425
  cutoff: null,
416
- note: `tolerance=autonomous(自治:不询问用户,severity=${severity.toFixed(3)})`,
426
+ note: `tolerance=autonomous(不问:软性询问不弹窗,severity=${severity.toFixed(3)})`,
417
427
  }
418
428
  : { kind: "delegate", changed: false, severity, cutoff: null, note: "" };
419
429
  }
@@ -423,13 +433,13 @@ export function resolveTolerance(options = {}) {
423
433
  return { kind: legacyKind, changed: false, severity, cutoff: null, note: "" };
424
434
  }
425
435
 
426
- // 轻松 / 谨慎:按分位数带。
436
+ // 少问 / 多问:按分位数带。
427
437
  const cutoff = Number.isFinite(options.cutoff)
428
438
  ? clamp01(options.cutoff)
429
439
  : preset.fallbackSeverity;
430
440
 
431
441
  if (preset.id === "relaxed") {
432
- // 轻松档**替换**默认判断,而不是叠加在它上面——否则「top 1%」会变成
442
+ // 少问档**替换**默认判断,而不是叠加在它上面——否则「top 1%」会变成
433
443
  // 「top 1% ∪ 默认判断」,实测那是 23% 的打扰率,与档位语义不符。
434
444
  if (severity >= cutoff) {
435
445
  return {
@@ -437,7 +447,7 @@ export function resolveTolerance(options = {}) {
437
447
  changed: legacyKind !== "ask",
438
448
  severity,
439
449
  cutoff,
440
- note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)},属最前的 1%)`,
450
+ note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)},落在严重度尾部)`,
441
451
  };
442
452
  }
443
453
  return legacyKind === "ask"
@@ -446,12 +456,12 @@ export function resolveTolerance(options = {}) {
446
456
  changed: true,
447
457
  severity,
448
458
  cutoff,
449
- note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} < ${cutoff.toFixed(3)},不在最前的 1%)`,
459
+ note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} < ${cutoff.toFixed(3)},不在严重度尾部)`,
450
460
  }
451
461
  : { kind: "delegate", changed: false, severity, cutoff, note: "" };
452
462
  }
453
463
 
454
- // 谨慎档:默认判断 ∪ 严重度靠前的 25%(**叠加**,因此一定 ≥ 默认)。
464
+ // 多问档:默认判断 ∪ 严重度靠前的 25%(**叠加**,因此逐调用一定 ⊇ 默认)。
455
465
  if (legacyKind === "ask" || severity >= cutoff) {
456
466
  return {
457
467
  kind: "ask",
@@ -461,7 +471,7 @@ export function resolveTolerance(options = {}) {
461
471
  note:
462
472
  legacyKind === "ask"
463
473
  ? `tolerance=cautious(默认判断已升级询问)`
464
- : `tolerance=cautious(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)},属最前的 25%)`,
474
+ : `tolerance=cautious(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)},落在默认判断之外的严重度带)`,
465
475
  };
466
476
  }
467
477
  return { kind: "delegate", changed: false, severity, cutoff, note: "" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leonhardthust/dsh-decision-engine",
3
- "version": "1.9.2",
3
+ "version": "1.11.0",
4
4
  "description": "dsh-decision-engine: a cheap judgement layer for DeepSeek Harness — a small decision model answers structured questions before the main model acts, so calls can be allowed, escalated, rerouted to a cheaper model, or skipped. All eight decision points (tool authorization, model routing, delegation, compaction, turn continuation, retrieval, skill pick, COT translation) are implemented and individually switchable. Works against any service exposing a decision endpoint: built-in presets (system one, bamboonode, SiliconFlow, Silicon Flow) or a custom base URL.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",