@leonhardthust/dsh-decision-engine 0.0.0-stage → 1.9.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.
Files changed (65) 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/auto-review-hotfix.md +33 -0
  15. package/docs/lessons/cloudflare-builds-root-directory.md +51 -0
  16. package/docs/lessons/cold-start-timeout.md +134 -0
  17. package/docs/lessons/endpoint-path-vs-model.md +26 -0
  18. package/docs/lessons/model-field-required-by-provider.md +31 -0
  19. package/docs/local-dev-link.md +54 -0
  20. package/docs/releasing.md +69 -0
  21. package/docs/security-revision.md +130 -0
  22. package/icon.svg +6 -0
  23. package/lib/adapters/agent.js +314 -0
  24. package/lib/adapters/route.js +144 -0
  25. package/lib/adapters/tools.js +186 -0
  26. package/lib/adapters/translate.js +143 -0
  27. package/lib/approval-detail.js +325 -0
  28. package/lib/client.charts.js +36 -0
  29. package/lib/client.js +8715 -0
  30. package/lib/config-file.js +136 -0
  31. package/lib/credentials.js +187 -0
  32. package/lib/decision-cache.js +188 -0
  33. package/lib/decision-points.js +205 -0
  34. package/lib/decision-store.js +774 -0
  35. package/lib/endpoint-fallback.js +143 -0
  36. package/lib/engine.js +312 -0
  37. package/lib/guard/advisory.js +100 -0
  38. package/lib/guard/audit.js +81 -0
  39. package/lib/guard/context.js +128 -0
  40. package/lib/guard/effects.js +104 -0
  41. package/lib/guard/fallback.js +139 -0
  42. package/lib/guard/listener.js +214 -0
  43. package/lib/guard/messages.js +31 -0
  44. package/lib/guard/policy.js +28 -0
  45. package/lib/home-paths.js +86 -0
  46. package/lib/index.js +3823 -0
  47. package/lib/outcomes.js +169 -0
  48. package/lib/points/spec.js +645 -0
  49. package/lib/providers.js +958 -0
  50. package/lib/resilience.js +308 -0
  51. package/lib/scope.js +67 -0
  52. package/lib/server-config.js +351 -0
  53. package/lib/session-events.js +75 -0
  54. package/lib/stats-store.js +916 -0
  55. package/lib/stats.js +1293 -0
  56. package/lib/telemetry.js +465 -0
  57. package/lib/tokenizer.js +201 -0
  58. package/lib/tolerance.js +498 -0
  59. package/lib/vendor/microcharts-charts.css +47 -0
  60. package/lib/vendor-src/entry.js +13 -0
  61. package/lib/vendor-src/jsx-shim.js +34 -0
  62. package/locale/en.json +6 -0
  63. package/locale/zh.json +6 -0
  64. package/package.json +101 -3
  65. package/scripts/migrate-profile.mjs +102 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * 决策点注册表。
3
+ *
4
+ * 一个「决策点」= 一个已经设计好问题与输出映射的适配器,而不是一个裸 hook。
5
+ * 裸 hook 名对用户没有意义:勾上 `agent/request` 之后要回答「换成哪个模型」,
6
+ * 勾上 `agent/pre-step` 要回答「注入什么消息」——这些答案必须由适配器给出。
7
+ *
8
+ * 因此配置页列的是这里定义的决策点,hook 只是它的实现细节。
9
+ *
10
+ * 字段说明:
11
+ * id 稳定标识,写进配置(pointAuthorize)与遥测(point 字段)
12
+ * hook 底层挂载的 DSH 事件名(或它替代的宿主服务)
13
+ * hotPath true = 每步/每次模型调用都触发,延迟敏感,需要预取或触发条件
14
+ * available false = 尚未实装,配置页显示为「即将支持」;本版本全部为 true
15
+ * defaultMode 三态默认值
16
+ * gate 触发闸说明:说明为什么不是「每次都问」,是冷却/采样/条件判断哪一种
17
+ */
18
+
19
+ /** 三态:off 不参与;observe 只记录不生效;enforce 真正生效。 */
20
+ export const POINT_MODES = ["off", "observe", "enforce"];
21
+
22
+ /** @type {ReadonlyArray<object>} */
23
+ export const DECISION_POINTS = [
24
+ {
25
+ id: "authorize",
26
+ key: "pointAuthorize",
27
+ hook: "tools/pre-execute",
28
+ hotPath: false,
29
+ available: true,
30
+ defaultMode: "observe",
31
+ trigger: "每次工具调用",
32
+ triggerEn: "every tool call",
33
+ label: "工具授权",
34
+ labelEn: "Tool authorization",
35
+ summary:
36
+ "判断这次工具调用能否放行。可替代 auto-review——后者把完整会话历史发给主模型,每次工具调用都跑一遍。",
37
+ summaryEn:
38
+ "Decide whether this tool call may proceed. Can replace auto-review, which sends the full session history to the main model on every call.",
39
+ saving: "替代 auto-review:每次约省 60k token",
40
+ savingEn: "Replaces auto-review: saves ~60k tokens per call",
41
+ },
42
+ {
43
+ id: "route",
44
+ key: "pointRoute",
45
+ hook: "agent/request",
46
+ hotPath: true,
47
+ available: true,
48
+ defaultMode: "off",
49
+ gate: "每轮最多判断前 N 步;同一 provider/model 连续多步只问一次",
50
+ trigger: "每次模型请求",
51
+ triggerEn: "every model request",
52
+ label: "模型路由",
53
+ labelEn: "Model routing",
54
+ summary:
55
+ "逐步判断这一步值不值得用强模型:机械步骤(读结果、改格式、重试)下沉到便宜模型。",
56
+ summaryEn:
57
+ "Decide per step whether the strong model is warranted; route mechanical steps to a cheaper one.",
58
+ saving: "主通道约省 20–40% token",
59
+ savingEn: "~20–40% of main-channel tokens",
60
+ },
61
+ {
62
+ id: "delegate",
63
+ key: "pointDelegate",
64
+ hook: "agent/pre-step",
65
+ hotPath: true,
66
+ available: true,
67
+ defaultMode: "off",
68
+ gate: "仅每轮第一步且带新用户输入时判断(分派决策只在这一刻可能改变)",
69
+ trigger: "每步",
70
+ triggerEn: "every step",
71
+ label: "子任务分派",
72
+ labelEn: "Subtask delegation",
73
+ summary:
74
+ "判断该自己接着做、开 subagent、还是组 agent team。每开一个子代理都等于新开一整个上下文。",
75
+ summaryEn:
76
+ "Decide whether to continue inline, spawn a subagent, or form an agent team. Each spawn costs a whole fresh context.",
77
+ saving: "少开一次 = 省一整个上下文",
78
+ savingEn: "One avoided spawn = one whole context saved",
79
+ },
80
+ {
81
+ id: "compact",
82
+ key: "pointCompact",
83
+ hook: "agent/pre-step → ctx.compaction",
84
+ hotPath: false,
85
+ available: true,
86
+ defaultMode: "off",
87
+ gate: "仅当实测 token 已达阈值触发线的 70% 才问(compaction-basic 的 0.8 阈值)",
88
+ trigger: "接近上下文阈值",
89
+ triggerEn: "approaching the context limit",
90
+ label: "上下文压缩",
91
+ labelEn: "Context compaction",
92
+ summary:
93
+ "判断现在该摘要压缩,还是只丢掉几条已经没用的工具结果——后者不花摘要的钱。",
94
+ summaryEn:
95
+ "Decide whether to summarize now, or simply drop a few stale tool results without paying for a summary.",
96
+ saving: "省掉一次摘要调用",
97
+ savingEn: "Skips one summarisation call",
98
+ },
99
+ {
100
+ id: "turnStop",
101
+ key: "pointTurnStop",
102
+ hook: "agent/turn-stopping",
103
+ hotPath: false,
104
+ available: true,
105
+ defaultMode: "off",
106
+ gate: "同一轮最多续一次(steer 次数上限),避免无限自续",
107
+ trigger: "每轮结束",
108
+ triggerEn: "every turn end",
109
+ label: "轮次延续",
110
+ labelEn: "Turn continuation",
111
+ summary: "判断再来一轮还有没有增量价值,避免空转一整轮。",
112
+ summaryEn: "Decide whether another round adds value, avoiding a wasted full turn.",
113
+ saving: "省一整个 turn 的上下文",
114
+ savingEn: "Saves one full turn of context",
115
+ },
116
+ {
117
+ id: "retrieval",
118
+ key: "pointRetrieval",
119
+ hook: "tools/pre-execute",
120
+ hotPath: false,
121
+ available: true,
122
+ defaultMode: "off",
123
+ gate: "仅检索类工具(web_search/web_fetch/research 等)才问;其余工具零开销",
124
+ trigger: "每次搜索/抓取",
125
+ triggerEn: "every search or fetch",
126
+ label: "检索必要性",
127
+ labelEn: "Retrieval necessity",
128
+ summary: "判断这条信息是否已经知道、或能否直接推断,避免不必要的搜索与抓取。",
129
+ summaryEn: "Decide whether the information is already known or inferable, avoiding needless fetches.",
130
+ saving: "省搜索与结果 token",
131
+ savingEn: "Saves search and result tokens",
132
+ },
133
+ {
134
+ id: "skillPick",
135
+ key: "pointSkillPick",
136
+ hook: "tools/pre-execute",
137
+ hotPath: false,
138
+ available: true,
139
+ defaultMode: "off",
140
+ gate: "仅 `skill` 工具加载时问,且候选清单非空才问",
141
+ trigger: "每次加载技能",
142
+ triggerEn: "every skill load",
143
+ label: "技能选择",
144
+ labelEn: "Skill selection",
145
+ summary: "判断该装哪个技能。装错再换等于付两遍技能正文的 token。",
146
+ summaryEn: "Decide which skill to load; a wrong pick pays the skill body twice.",
147
+ saving: "省 2–5k / 次误加载",
148
+ savingEn: "Saves 2–5k per wrong load",
149
+ },
150
+ {
151
+ id: "cotTranslate",
152
+ key: "pointCotTranslate",
153
+ hook: "cotTranslation.translate()",
154
+ hotPath: true,
155
+ available: true,
156
+ defaultMode: "off",
157
+ gate: "超长文本采样头部判断;同一文本摘要命中缓存即复用",
158
+ trigger: "每条消息",
159
+ triggerEn: "every message",
160
+ label: "思维链翻译门槛",
161
+ labelEn: "COT translation gate",
162
+ summary: "判断这条内容是否真的需要翻译,避免逐条调用翻译模型。",
163
+ summaryEn: "Decide whether content truly needs translation, avoiding a model call per message.",
164
+ saving: "按条省翻译调用",
165
+ savingEn: "Saves per-message translation calls",
166
+ },
167
+ ];
168
+
169
+ /** 按 id 取决策点。 */
170
+ export function findPoint(id) {
171
+ return DECISION_POINTS.find((point) => point.id === id);
172
+ }
173
+
174
+ /**
175
+ * 把配置里的 `pointXxx` 字段读成一张 id → 模式 的表。
176
+ *
177
+ * 未知值一律退回 off:宁可少判断一次,也不要因为配置写错而在热路径上
178
+ * 意外开始发请求。
179
+ *
180
+ * @param cfg - 已合并的配置。
181
+ * @returns 记录每个决策点当前模式的普通对象。
182
+ */
183
+ export function readPointModes(cfg) {
184
+ /** @type {Record<string, string>} */
185
+ const modes = {};
186
+ for (const point of DECISION_POINTS) {
187
+ const raw = cfg?.[point.key];
188
+ modes[point.id] = POINT_MODES.includes(raw) ? raw : "off";
189
+ }
190
+ return modes;
191
+ }
192
+
193
+ /**
194
+ * 某个决策点此刻是否应当真的调用决策模型。
195
+ * observe 也调用(否则无从记录「本该怎样」),只是不改变行为。
196
+ */
197
+ export function shouldInvoke(cfg, id) {
198
+ const mode = readPointModes(cfg)[id];
199
+ return mode === "observe" || mode === "enforce";
200
+ }
201
+
202
+ /** 某个决策点此刻是否允许改变行为。 */
203
+ export function shouldEnforce(cfg, id) {
204
+ return readPointModes(cfg)[id] === "enforce";
205
+ }