dsh-goal-ledger 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-goal-ledger contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,228 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # dsh-goal-ledger
2
+
3
+ A DeepSeek Harness plugin that audits your own sessions: **which goals drifted from what you asked**, and **where tool calls actually failed**. Everything renders in DSH's settings panel under 「目标台账」.
4
+
5
+ 只读分析本机会话日志,不修改任何会话,不上传除 embedding 文本外的任何数据。
6
+
7
+ ## 它解决什么问题
8
+
9
+ DSH 把每个会话完整记录在 `$DSH_HOME/sessions/` 下(目标、每轮输入输出、每次工具调用与报错)。这些数据一直在,但**没有入口让你看见模式**。本插件把它变成两张表:
10
+
11
+ 1. **失败聚类** —— 硬事实。工具报错按错误特征聚簇,告诉你「最常因为什么失败」。
12
+ 2. **目标漂移** —— 排序信号。用 embedding 算每轮输出离最初目标有多远,帮你挑出值得复查的会话。
13
+
14
+ ## 为什么失败聚类比距离更可靠
15
+
16
+ 在真实数据上跑出来的对比(27 个目标 / 203 轮 / 6105 次调用 / 81 次失败):
17
+
18
+ | 信号 | 可靠性 | 原因 |
19
+ |---|---|---|
20
+ | 工具失败聚类 | **高** | `data.error` 要么存在要么不存在,是客观事实 |
21
+ | 轮次与调用比 | 中 | 长轨迹+少产出通常意味着在工具调用里打转 |
22
+ | embedding 距离 | **低** | 见下方「已知局限」 |
23
+
24
+ 所以界面上把「失败聚类」放在第一个标签页,距离放在后面。
25
+
26
+ ## 已知局限(请务必读)
27
+
28
+ embedding 距离衡量的是「目标与输出的字面语义分离度」,有两种已实测的误判:
29
+
30
+ - **「词相近但答错了」** —— 自信但错误的输出,距离照样很低。
31
+ - **长技术任务天然漂移** —— 实现细节、文件路径、报错文本都会把输出推离最初的措辞,距离升高但没有任何问题。
32
+
33
+ 因此距离**只用于排序候选,不用于判定对错**。距离分档(0.35 / 0.50 / 0.65)是启发式初值,跑几批后应按你自己的分布调整(`lib/client.js` 的 `band()`)。
34
+
35
+ 纯工具调用轮次(无文字输出)已被标记 `scorable: false` 并排除在均值外 —— 它们的向量没有意义,算进去会显示为「最大漂移」。
36
+
37
+ ## 安装
38
+
39
+ ### 从插件市场装(推荐)
40
+
41
+ DSH 的插件市场读取你**自己配置的索引地址**,本仓库同时托管了一份索引(`market-index.json`)。在 设置 → 插件 → 发现(Discover)里添加这个地址:
42
+
43
+ ```
44
+ https://raw.githubusercontent.com/zhegemingzimeibanquan/dsh-goal-ledger/master/market-index.json
45
+ ```
46
+
47
+ 然后在市场里搜索「目标台账」或 `dsh-goal-ledger` 安装即可。
48
+
49
+ ### 命令行装
50
+
51
+ ```bash
52
+ # 从市场索引的同一份源码安装(固定 commit,无需 npm 账号)
53
+ dsh plugin --profile desktop add git+https://github.com/zhegemingzimeibanquan/dsh-goal-ledger.git#6afaf8038d0d4b84b1b92eb4f16a5cc103b55bfe
54
+
55
+ # 或者从本地目录装(开发用)
56
+ dsh plugin --profile desktop add file:C:/Project/dsh-goal-ledger
57
+ ```
58
+
59
+ 装完重启 DSH 桌面端,设置面板里会出现「目标台账」。
60
+
61
+ ### 系统要求
62
+
63
+ | 项 | 要求 |
64
+ |---|---|
65
+ | DSH | **>= 0.2.1-rc.3**(`engines.dsh`,安装前会校验,低于此版本会被拒装) |
66
+ | Node.js | >= 22 |
67
+ | 网络 | 可选。失败聚类完全离线;只有「向量模型」打分需要网络 |
68
+
69
+ 插件本身**零运行时依赖**,不引入任何第三方包。
70
+
71
+ ## 使用
72
+
73
+ 1. 打开 设置 → 目标台账
74
+ 2. 点「开始分析」(首次扫描约几十秒;嵌入打分需要 embedding 凭据)
75
+ 3. 切换六个标签页查看:
76
+ - **漂移场**:光点图,半径 = 离目标多远(见下)
77
+ - **失败聚类**:按次数排序,点行展开原始错误与出现位置
78
+ - **目标漂移**:按平均距离排序,标注判定档位
79
+ - **漂移轮次**:单轮最漂移的 10 条
80
+ - **向量模型**:改 embedding 端点 / 模型 / key 变量名,带连通性测试
81
+ - **漂移纠正**:让模型在持续偏离时被提醒拉回(默认关闭,见下)
82
+
83
+ 结果会缓存到 `$DSH_HOME/dsh-goal-ledger/snapshot.json`,下次打开立即显示上次结果。
84
+
85
+ ## 漂移纠正(opt-in)
86
+
87
+ 开启后,当某个目标**连续多轮**偏离时,插件在下一步的系统提示里插入一段提醒:逐字引用你最初的目标,指出当前偏离,并要求模型回到目标继续。
88
+
89
+ **它不做什么**(这些是刻意的设计边界):
90
+
91
+ | 不做 | 为什么 |
92
+ |---|---|
93
+ | **不修改你的目标** | 自动改写目标会让「判断漂移」的锚点跟着漂移一起动,最后你可能得到一个自己没要求过的目标 |
94
+ | **不打断正在跑的轮次** | 只在轮次之间生效,只影响后续步骤 |
95
+ | **不调用模型来判断** | 判断完全基于本地已有的距离数据,不额外增加延迟和 token |
96
+ | **默认不开启** | 关闭时插件不向提示里写任何东西,行为与没装插件逐字节相同 |
97
+
98
+ **防误报**:长技术任务天然会偏离最初措辞(上面「已知局限」里就写了这个)。所以只有**连续 N 轮**都超过阈值才提醒,提醒后还有**冷却轮数**静默期,避免模型在「回到目标」和「推进进度」之间来回抖动。
99
+
100
+ 注入的文本明确要求模型:先复述目标要求什么、已完成到哪一步;指出当前哪些工作不是目标要求的;**保留所有已完成的有效工作**,不要重开任务。最后还允许它判断「其实没偏」—— 否则降低距离最省事的做法就是推倒重来,那比漂移更糟。
101
+
102
+ | 参数 | 默认 | 含义 |
103
+ |---|---|---|
104
+ | 触发阈值 | 0.55 | 单轮距离 ≥ 此值算一次「偏离」 |
105
+ | 连续轮数 | 2 | 连续偏离多少轮才提醒 |
106
+ | 冷却轮数 | 3 | 提醒后静默几轮 |
107
+
108
+ 配置写在 `$DSH_HOME/dsh-goal-ledger/correction.json`。实现上它挂在 **agent 作用域**(`agent.ctx`)而不是全局:只影响该会话,agent 销毁时自动解绑,多会话互不干扰。
109
+
110
+ **实时打分**:正在跑的会话不在缓存快照里(快照只有已落盘的会话),所以纠正用的是 `LiveScorer` —— 它折叠 `session/event` 的实时事件流,事件形状和离线分析器读的是同一套(`turn/start` 开轮、`assistant/message` 累积、`turn/end` 收轮),所以「当场决定要不要提醒」和「事后报表里的距离」不会互相矛盾。锚点优先取 `create_goal` 的 objective,没有则取第一条真实用户消息(同样剥掉宿主注入的样板文本)。每个已打分轮次一次 embed 调用,objective 只 embed 一次。
111
+
112
+ ## 漂移场怎么看
113
+
114
+ 灰蓝底、金色光点,Canvas 2D 实时绘制(无图表库,无动画依赖):
115
+
116
+ - **中心亮点** = 「最初的目标」。它是标尺的原点,不是一个会话。
117
+ - **每个光点** = 一个目标。**半径就是距离** —— 越靠外,整段会话偏离最初措辞越远。
118
+ - **虚线圈** = 档位阈值 0.35 / 0.50 / 0.65,和表格里的标签是同一套数字,图和数不会互相矛盾。
119
+ - **颜色**:灰蓝 = 聚焦,浅金 = 有偏离,橙金 = 偏题,亮金 = 严重漂移。颜色只强化档位,不额外编码信息。
120
+ - **光点大小** = 该目标经历的轮次数(取对数,防止单个长会话压掉其他点)。
121
+ - **角度没有含义** —— 黄金角螺旋 + 按 goalId 哈希的抖动,只为了不让点叠在一起,并保证同一目标每次渲染位置一致。
122
+ - **悬停**看目标原文、轮次、调用数、失败数。
123
+
124
+ 纯装饰元素(光尘、核心光晕、轨道弧、光点周围的喷溅)都在 `drawField()` 里,和几何布局分开:改观感不用碰布局,改布局不用碰观感。
125
+
126
+ ## Embedding 配置
127
+
128
+ 默认复用你已有的火山 coding plan 凭据,无需额外配置;也可以在设置页的「向量模型」标签里改,或直接编辑配置文件:
129
+
130
+ | 项 | 默认值 |
131
+ |---|---|
132
+ | base URL | `https://ark.cn-beijing.volces.com/api/coding/v3` |
133
+ | model | `doubao-embedding-vision`(2048 维) |
134
+ | key 环境变量 | `VOLCENGINE_CODING_PLAN_API_KEY` |
135
+ | 批量大小 | 4(上限 10) |
136
+
137
+ 任何兼容 OpenAI `POST {baseUrl}/embeddings` 的端点都能用,内置火山方舟 / OpenAI / 硅基流动 / 阿里百炼四个预设,也可自定义。
138
+
139
+ **配置里只存 key 的变量名,不存 key 本身**。key 解析顺序:环境变量 → `$DSH_HOME/.credentials.yaml` 的 `refs:`。所以 `config.json` 可以放心备份或分享。
140
+
141
+ 配置文件:`$DSH_HOME/dsh-goal-ledger/config.json`(`PUT /config` 写入,非法值整体拒绝、不落盘)。
142
+
143
+ 没有 key 时插件仍可用:失败聚类不需要网络,界面会标注「嵌入:未配置」。
144
+
145
+ ### 供应商限制(已内置处理)
146
+
147
+ - 单次请求最多 **10** 条文本(超了返回 400)
148
+ - 大批量在本机偶发响应截断(`IncompleteRead` / aborted)→ 自动重试 3 次并逐次减半 batch
149
+
150
+ ## 架构
151
+
152
+ ```
153
+ lib/index.js 宿主半侧:HTTP 路由 + 缓存 + 配置读写 + agent 作用域的纠正注入
154
+ lib/client.js 客户端半侧:设置页 UI + 漂移场 Canvas 渲染
155
+ src/session-reader.js 会话日志解析(zstd 帧拆分、目标/轮次抽取)
156
+ src/failure-cluster.js 失败聚类(错误签名归一 + 分类)
157
+ src/embed-config.js embedding 配置校验 / 预设 / 持久化形状
158
+ src/distance.js embedding 打分(余弦距离)+ 连通性探测
159
+ src/correction.js 漂移纠正:滞后判定、冷却、实时打分(LiveScorer)、提示文本生成
160
+ test/smoke.mjs 冒烟测试:解析 + 聚类,跑真实会话,不需要 DSH 运行
161
+ test/client-render.mjs 渲染测试:在 jsdom + 真 React 里挂载设置页组件,断言每个标签页都画出内容
162
+ test/correction.mjs 纠正决策逻辑 + 动态配置路由,离线
163
+ test/correction-wiring.mjs 接线测试:用形状一致的假 ctx 驱动 apply(),验证纠正文本真的到达提示词
164
+ test/render-field.mjs 把漂移场渲染成 PNG,用真实 snapshot 数据
165
+ test/full.mjs 全链路:解析 → 聚类 → 真实 embedding 调用
166
+ ```
167
+
168
+ ### 五个必须知道的实现陷阱
169
+
170
+ 写这个插件时踩到、并已在代码里注释说明的:
171
+
172
+ 1. **会话日志是数千个独立 zstd 帧的串联**。单次 `zstdDecompress` 只返回第一帧(会话头),静默丢掉整个对话。必须按 magic bytes `28 B5 2F FD` 拆帧逐个解。实测 4MB 日志 → 2857 帧 / 13MB JSONL。
173
+
174
+ 2. **失败在 `data.error`,不在 `data.ok`**。`tool/result` 事件带 `data.message` 和一个可选的兄弟字段 `data.error`,没有布尔 `ok`。检查 `ok` 会让每个会话都报告 0 失败 —— 这个 bug 在本项目里真实发生过一次,修复后从 0 变成 81。
175
+
176
+ 3. **`h` 必须是 `React.createElement`,不能是 `react/jsx-runtime` 的 `jsx`**。`jsx(type, props, key)` 的第三个参数是 **key**,不是子节点;而本项目所有调用点都按 `createElement(type, props, ...children)` 变参传子节点。绑错了不会报错、不会告警、宿主日志干净,**设置页只会画出一个空壳** —— 目录里能看到「目标台账」,点进去一片空白。这个 bug 也真实发生过一次,`test/client-render.mjs` 就是为它加的回归测试。
177
+
178
+ 4. **`ctx.systemPrompt.section()` 的 `text()` 拿不到会话身份**。它是个无参 thunk,全局注册时无法知道这次组装是给哪个会话的 —— 只能靠「猜」哪个 pending 最可能,多会话并发时必然错。正确做法是把 section 注册到 **`agent.ctx`(agent 作用域)**,每个 agent 各注册一份,闭包里直接捕获 `sessionId`,销毁时自动解绑。这也正是 agent preset 用的同一个接缝。
179
+
180
+ 5. **DSH 的服务是属性访问,事件 payload 是对象**。写宿主接线时想当然地用了 `ctx.get('agents')` 和 `agents.on('agent/created', agent => …)` —— 两个都是错的:Cordis 里服务挂在 `ctx.agents` / `agent.ctx.systemPrompt` 上(`ctx.get` 在整个 DSH 代码库里不存在),而 `agent/created` 的 payload 是 `{ agent, source }`,`agent/disposed` 是 `{ agent }`。错的结果是**整条纠正链路静默不挂载**:不抛异常、不打日志,功能就是不生效。`test/correction-wiring.mjs` 用形状一致的假 ctx 驱动 `apply()`,把这两处各自注入回去验证过,测试都会红。
181
+
182
+ 另外客户端沙箱有两点约束:`fetch` 被替换成 trap(须用 `window.fetch`),且动态 client realm 没有全局 timer(需 `inject: ['timer']`)。动画这里用 `requestAnimationFrame`,页面自己提供,不受这条限制。
183
+
184
+ ## HTTP API
185
+
186
+ 宿主在 `/dsh-goal-ledger-7821` 前缀下暴露:
187
+
188
+ | 方法 | 路径 | 说明 |
189
+ |---|---|---|
190
+ | GET | `/status` | 缓存快照 + embedding 配置状态 |
191
+ | GET | `/config` | 解析后的配置 + 预设(**不含 key**,只有变量名) |
192
+ | PUT | `/config` | `{baseUrl?, model?, keyEnv?, batchSize?, enabled?}`,非法值整体拒绝 |
193
+ | POST | `/probe` | 用一条短文本探测端点,返回是否连通 + 维度 |
194
+ | POST | `/analyze` | `{cwdFilter?, limit?, embed?}` 运行分析 |
195
+ | GET | `/clusters` | 仅失败聚类(不调 embedding,快速免费) |
196
+ | GET | `/correction` | 漂移纠正配置 + 各会话的实时状态 |
197
+ | PUT | `/correction` | `{enabled?, threshold?, minStreak?, cooldownTurns?, windowSize?}` |
198
+
199
+ ## 开发
200
+
201
+ ```bash
202
+ npm install # 装 devDependencies(react / react-dom / jsdom)
203
+ npm run test:render # 渲染回归测试,离线,不需要 DSH 运行
204
+ npm run test:correction # 纠正逻辑 + 动态配置路由,离线
205
+ npm test # 冒烟测试,直接跑真实会话
206
+ npm run test:full # 全链路(会真实调用 embedding API)
207
+ npm run check # 全部文件语法检查
208
+ ```
209
+
210
+ 改了 `lib/client.js` 之后**务必跑 `npm run test:render`**:宿主路由 200、语法检查通过、界面依然可能是空白,只有真正挂载组件才能发现。这个测试同时验证 Canvas 真的画了东西(jsdom 没有 2D context,测试装了记录型 stub)、悬停能命中光点、向量模型表单的编辑→测试→保存链路,以及纠正面板的开关→保存链路。
211
+
212
+ 想单看漂移场效果,可以把它渲染成 PNG:
213
+
214
+ ```bash
215
+ npm install --no-save @napi-rs/canvas
216
+ npm run test:render-field # 读真实 snapshot.json,输出 drift-field.png
217
+ ```
218
+
219
+ ### 测试里两个 jsdom 坑(都真实踩过)
220
+
221
+ 1. **`react-dom` 必须在 jsdom 全局就位之后再 `require`**。它在模块初始化时就固化 `canUseDOM`;提前加载会让它判定「没有 DOM」,整套事件系统都不装,之后合成的 `input` 事件**永远到不了 `onChange`** —— 表现和「受控输入框坏了」一模一样,但插件代码其实是对的。
222
+ 2. **`act()` 要 await**。`act(() => {...})` 返回 thenable,不 await 会把 React 的 act 队列弄脏,后面的事件派发就静默失效。
223
+
224
+ 安装到 profile 后,改动同步到 `~/.dsh/profiles/desktop/node_modules/dsh-goal-ledger/`(`file:` 依赖装的是副本,不是软链),然后刷新页面;页面没换新代码就重启 DSH 桌面端。
225
+
226
+ ## License
227
+
228
+ MIT
@@ -0,0 +1,9 @@
1
+ # dsh bundle patch: inserts this plugin into a profile's layer stack.
2
+ #
3
+ # id must stay identical to the package name — the store contract matches
4
+ # entryIds against it, and a bare name like "goal-ledger" collides with other
5
+ # plugins' row ids. Keep the id line free of trailing comments: the store
6
+ # preflight regex reads the value up to end-of-line.
7
+ - insert:
8
+ - id: dsh-goal-ledger
9
+ name: 'dsh-goal-ledger'