@leonhardthust/dsh-decision-engine 0.0.0-stage → 1.9.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -2
  3. package/README.zh.md +57 -0
  4. package/config.example.yml +436 -0
  5. package/cordis.patch.yml +14 -0
  6. package/docs/assets/config-page.png +0 -0
  7. package/docs/assets/decisions-tab.png +0 -0
  8. package/docs/assets/hero.jpeg +0 -0
  9. package/docs/assets/stats-charts-preview.png +0 -0
  10. package/docs/assets/stats-donut-toggle.png +0 -0
  11. package/docs/assets/stats-interactive-charts.png +0 -0
  12. package/docs/assets/stats-point-donut.png +0 -0
  13. package/docs/assets/stats-ranking-bars.png +0 -0
  14. package/docs/lessons/cloudflare-builds-root-directory.md +51 -0
  15. package/docs/lessons/cold-start-timeout.md +134 -0
  16. package/docs/lessons/endpoint-path-vs-model.md +26 -0
  17. package/docs/lessons/model-field-required-by-provider.md +31 -0
  18. package/docs/local-dev-link.md +54 -0
  19. package/docs/releasing.md +67 -0
  20. package/docs/security-revision.md +130 -0
  21. package/icon.svg +6 -0
  22. package/lib/adapters/agent.js +314 -0
  23. package/lib/adapters/route.js +144 -0
  24. package/lib/adapters/tools.js +186 -0
  25. package/lib/adapters/translate.js +143 -0
  26. package/lib/approval-detail.js +325 -0
  27. package/lib/client.charts.js +36 -0
  28. package/lib/client.js +8589 -0
  29. package/lib/config-file.js +136 -0
  30. package/lib/credentials.js +187 -0
  31. package/lib/decision-cache.js +188 -0
  32. package/lib/decision-points.js +205 -0
  33. package/lib/decision-store.js +774 -0
  34. package/lib/endpoint-fallback.js +143 -0
  35. package/lib/engine.js +312 -0
  36. package/lib/guard/advisory.js +100 -0
  37. package/lib/guard/audit.js +79 -0
  38. package/lib/guard/context.js +128 -0
  39. package/lib/guard/effects.js +96 -0
  40. package/lib/guard/fallback.js +139 -0
  41. package/lib/guard/listener.js +191 -0
  42. package/lib/guard/policy.js +28 -0
  43. package/lib/home-paths.js +86 -0
  44. package/lib/index.js +3823 -0
  45. package/lib/outcomes.js +169 -0
  46. package/lib/points/spec.js +645 -0
  47. package/lib/providers.js +958 -0
  48. package/lib/resilience.js +308 -0
  49. package/lib/scope.js +68 -0
  50. package/lib/server-config.js +351 -0
  51. package/lib/session-events.js +75 -0
  52. package/lib/stats-store.js +916 -0
  53. package/lib/stats.js +1293 -0
  54. package/lib/telemetry.js +465 -0
  55. package/lib/tokenizer.js +201 -0
  56. package/lib/tolerance.js +498 -0
  57. package/lib/vendor/microcharts-charts.css +47 -0
  58. package/lib/vendor-src/entry.js +13 -0
  59. package/lib/vendor-src/jsx-shim.js +34 -0
  60. package/locale/en.json +6 -0
  61. package/locale/zh.json +6 -0
  62. package/package.json +101 -3
  63. package/scripts/migrate-profile.mjs +102 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * 裁决(verdict)的三分类:**allow / ask / reject**。
3
+ *
4
+ * ## 为什么需要这一层映射
5
+ *
6
+ * 九个决策点各有自己的裁决词:
7
+ *
8
+ * authorize delegate / ask / deny
9
+ * route keep / switch
10
+ * delegate inline / advise
11
+ * compact summarize / prune
12
+ * turnStop continue / stop
13
+ * retrieval allow / skip
14
+ * skillPick allow / redirect
15
+ * cotTranslate translate / skip
16
+ *
17
+ * 但「这次裁决到底让事情过去了,还是被拦下了」这个问题,在九个点上
18
+ * 问的是同一件事。没有统一映射时,只能逐点硬编码一份清单——而那份清单
19
+ * 会随新增决策点而腐烂:新增一个 kind 忘了加,统计就少一类,而且
20
+ * **少得悄无声息**(数字看着仍然合理,只是偏小)。
21
+ *
22
+ * 因此映射集中在这里一处,`lib/stats.js` 的聚合与 `lib/index.js` 的
23
+ * 统计接口都从这里取,不各自抄一份。
24
+ *
25
+ * ## 映射口径
26
+ *
27
+ * 一句话:**这次裁决对待办的用户动作做了什么**。
28
+ *
29
+ * allow 动作照常进行——不打断用户,也不拦下任何东西。
30
+ * ask 动作被升级到人工确认(用户会被打断)。
31
+ * reject 动作被拦下,不执行。
32
+ *
33
+ * 这个口径与实际生效行为一一对应:适配器里真正返回
34
+ * `{ kind: "ask" }` 的只有工具授权的 ask,真正返回 `{ kind: "deny" }`
35
+ * (即拦截)的只有授权的 deny、检索的 skip、技能选择的 redirect。
36
+ * 三张表对得上,是因为它们本来就描述同一件事。
37
+ *
38
+ * ## 关键细节:同一个 kind 在不同点上含义不同
39
+ *
40
+ * 所以映射按 **(point, kind)** 而不是只按 kind:
41
+ *
42
+ * retrieval.skip → reject(这次 web_search 被拦下,不执行)
43
+ * cotTranslate.skip → allow (消息照常送达,只是没翻译)
44
+ *
45
+ * 只按 kind 映射会把「不翻译一句话」和「拦下一次危险检索」算进同一格,
46
+ * 而这两件事的严重程度天差地别——混在一起的「拦截次数」没有意义。
47
+ *
48
+ * @module dsh-decision-engine/outcomes
49
+ */
50
+
51
+ /** 三种裁决结果的固定顺序(界面与接口都按这个顺序渲染)。 */
52
+ export const OUTCOMES = ["allow", "ask", "reject"];
53
+
54
+ /**
55
+ * 裁决结果的显示名。
56
+ *
57
+ * `none` 表示「没有裁决」——降级(模型没给出可用的答案)不是第四种裁决,
58
+ * 它是「这次根本没有裁决」,不能混进三个计数里。
59
+ */
60
+ export const OUTCOME_LABELS = {
61
+ allow: "放行",
62
+ ask: "询问",
63
+ reject: "拦截",
64
+ none: "未裁决",
65
+ };
66
+
67
+ /**
68
+ * (决策点, 裁决词)→ 裁决结果。
69
+ *
70
+ * 覆盖九个决策点的全部裁决词。**新增决策点或新增裁决词时必须在这里
71
+ * 登记**:漏登记不会报错,只会让那个裁决悄悄不计入任何一格。
72
+ * `lib/stats.js` 的聚合会把「有裁决但没登记」的情形单独计入
73
+ * `otherVerdicts`,界面上会显式提示,不会被静默吞掉。
74
+ */
75
+ export const OUTCOME_MATRIX = {
76
+ // 工具授权:唯一真正会打断用户或拦下工具调用的点。
77
+ // defer 是「目标在工作区写根之外,判定权不归本插件」(DEC-045):放行,
78
+ // 不询问也不拦下——所以它归到 allow 这一格(动作照常进行)。
79
+ authorize: {
80
+ delegate: "allow",
81
+ ask: "ask",
82
+ deny: "reject",
83
+ defer: "allow",
84
+ },
85
+ // 模型路由:两种处置都照常发请求,只是目标模型不同。
86
+ route: {
87
+ keep: "allow",
88
+ switch: "allow",
89
+ },
90
+ // 子任务分派:只注入建议,从不替模型开子代理。
91
+ delegate: {
92
+ inline: "allow",
93
+ advise: "allow",
94
+ },
95
+ // 上下文压缩:摘要是省钱的做法,裁剪更省钱,两种都不拦下什么。
96
+ compact: {
97
+ summarize: "allow",
98
+ prune: "allow",
99
+ },
100
+ // 轮次延续:收束轮次是正常结束,不是拦下用户的请求。
101
+ // defer 是「这一轮在等用户,插件主动让位」(DEC-045)——与 stop 一样不续轮,
102
+ // 但它说明的是**插件为什么没插手**,因此单列一格而不并进 stop。
103
+ turnStop: {
104
+ continue: "allow",
105
+ stop: "allow",
106
+ defer: "allow",
107
+ },
108
+ // 检索必要性:skip 会真的拦下这次检索工具调用。
109
+ retrieval: {
110
+ allow: "allow",
111
+ skip: "reject",
112
+ },
113
+ // 技能选择:redirect 会真的拦下这次 skill 调用并指路。
114
+ skillPick: {
115
+ allow: "allow",
116
+ redirect: "reject",
117
+ },
118
+ // 思维链翻译门槛:skip 只是不翻译,消息照常送达。
119
+ cotTranslate: {
120
+ translate: "allow",
121
+ skip: "allow",
122
+ },
123
+ };
124
+
125
+ /**
126
+ * 老遥测回填记录的裁决分类**兜底**。
127
+ *
128
+ * 老遥测里有一批记录**没有 `point` 字段**——那批来自工具授权守卫,
129
+ * `tag` 是 `guard`,但 `point` 字段是后来才加的。回填后它们的 point 变成
130
+ * `"unknown"`,走不了(point, kind)矩阵。
131
+ *
132
+ * 这批记录只可能来自授权守卫,因此只有授权的三个裁决词会出现。其它裁决词
133
+ * 在 unknown point 下一律**不猜**:同一个词在不同点上含义可能相反——
134
+ * `retrieval.skip` 拦下了一次检索,`cotTranslate.skip` 只是没翻译一句话。
135
+ * 把它们混在一起算「拦截次数」,得到的数字没有意义。
136
+ */
137
+ export const LEGACY_UNKNOWN_POINT_OUTCOMES = {
138
+ delegate: "allow",
139
+ ask: "ask",
140
+ deny: "reject",
141
+ };
142
+
143
+ /**
144
+ * 查一次裁决的分类结果。
145
+ *
146
+ * 查不到的(没登记的 point 或 kind)返回 `"none"`——**不猜**。
147
+ * 猜一个默认分类会让一个未知裁决看起来像「放行」,而放行是三格里
148
+ * 看起来最无害、实则最能掩盖问题的一格。
149
+ *
150
+ * @param point - 决策点 id。
151
+ * @param kind - 裁决词(`spec.decide()` 返回的 `action.kind`)。
152
+ * @returns `"allow"` / `"ask"` / `"reject"` / `"none"`。
153
+ */
154
+ export function outcomeOf(point, kind) {
155
+ if (typeof point !== "string" || typeof kind !== "string") return "none";
156
+ if (typeof kind.length !== "number" || kind.length === 0) return "none";
157
+ const bucket = OUTCOME_MATRIX[point];
158
+ if (bucket !== undefined) {
159
+ const outcome = bucket[kind];
160
+ if (outcome === "allow" || outcome === "ask" || outcome === "reject") return outcome;
161
+ return "none";
162
+ }
163
+ // point 为 unknown 的老记录:只认授权那三个裁决词。
164
+ if (point === "unknown") {
165
+ const fallback = LEGACY_UNKNOWN_POINT_OUTCOMES[kind];
166
+ if (fallback === "allow" || fallback === "ask" || fallback === "reject") return fallback;
167
+ }
168
+ return "none";
169
+ }