@jayyuen66/dsh-quality-gate 0.1.2 → 0.1.3
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/CHANGELOG.md +79 -0
- package/LICENSE +1 -1
- package/README.md +178 -93
- package/README.zh-CN.md +180 -91
- package/client.js +45 -26
- package/cordis.patch.yml +3 -1
- package/host.js +176 -60
- package/package.json +41 -33
package/README.zh-CN.md
CHANGED
|
@@ -1,102 +1,191 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://cdn.jsdelivr.net/gh/JayYuen666/dsh-quality-gate@main/icon.svg" width="96" height="96" alt="dsh-quality-gate logo" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">dsh-quality-gate</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><em>回合收口时自动跑项目自己声明的检查;没跑成的检查,绝不会被记成通过。</em></p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/v/@jayyuen66/dsh-quality-gate?style=flat-square&logo=npm&logoColor=white" alt="npm version" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/dm/@jayyuen66/dsh-quality-gate?style=flat-square" alt="npm downloads" /></a>
|
|
12
|
+
<a href="https://github.com/JayYuen666/dsh-quality-gate/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-quality-gate/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
|
|
14
|
+
<img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
|
|
15
|
+
<img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.3-658EFF?style=flat-square" alt="dsh" />
|
|
16
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://skillicons.dev/icons?i=typescript,nodejs,react,vitest,githubactions" alt="tech stack" />
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<a href="https://github.com/JayYuen666"><img src="https://img.shields.io/badge/GitHub-JayYuen666-181717?style=flat-square&logo=github&logoColor=white" alt="GitHub" /></a>
|
|
25
|
+
<a href="mailto:cqwkkj@gmail.com"><img src="https://img.shields.io/badge/Email-cqwkkj@gmail.com-EA4335?style=flat-square&logo=gmail&logoColor=white" alt="Email" /></a>
|
|
26
|
+
<a href="https://twitter.com/JayYuen666"><img src="https://img.shields.io/badge/Twitter-X%2FJayYuen666-000000?style=flat-square&logo=x&logoColor=white" alt="Twitter/X" /></a>
|
|
27
|
+
<a href="./README.md"><img src="https://img.shields.io/badge/lang-English-658EFF?style=flat-square" alt="English" /></a>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
**一个 [dsh](https://github.com/deepseek-ai/deepseek-harness) 插件:回合收口质量门禁——本轮改过代码/配置文件,就在回合收口前跑该项目自己声明的检查;未通过经 `agent.followup` 注入修复指令、开新回合接着修,配额与总预算封顶。**
|
|
31
|
+
|
|
32
|
+
## <img src="https://api.iconify.design/lucide/file-text.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 目录
|
|
33
|
+
|
|
34
|
+
- [它做什么](#它做什么)
|
|
35
|
+
- [安装](#安装)
|
|
36
|
+
- [确认它真的在工作](#确认它真的在工作)
|
|
37
|
+
- [它不提供什么](#它不提供什么)
|
|
38
|
+
- [在 dsh 里启用](#在-dsh-里启用)
|
|
39
|
+
- [跑哪四条门禁](#跑哪四条门禁)
|
|
40
|
+
- [设置项](#设置项)
|
|
41
|
+
- [对外接口](#对外接口)
|
|
42
|
+
- [数据与隐私](#数据与隐私)
|
|
43
|
+
- [已知边界](#已知边界)
|
|
44
|
+
- [常见问题](#常见问题)
|
|
45
|
+
- [开发](#开发)
|
|
46
|
+
- [许可](#许可)
|
|
47
|
+
|
|
48
|
+
## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 它做什么
|
|
49
|
+
|
|
50
|
+
- **回合收口,不拦过程。** 判定挂在宿主事件 `agent/turn-stopping`:读本会话**成功的**编辑 → 每个文件向上找最近清单根 → 探测门禁 → 执行。失败经 `agent.followup` 注入修复指令;宿主在收口之后仍会检查收件箱,于是开新回合接着修。
|
|
51
|
+
- **一次检查只有三种结论**:`pass`(真的跑完且 exit 0)、`code-failure`(跑了且报代码错)、`not-run`(没跑成)。一条没真正跑完的检查绝不会被记成通过;「没跑成」与「代码错」也绝不混同——前者注入的是「请手动确认」,不写记忆、不以 `gate-failure` 进教训总线。
|
|
52
|
+
- **只跑项目自己声明的命令。** 四条白名单命令按根探测择一,拿不到声明就宁可不给门禁,也不替项目发明一条稳定假失败的命令(判据见「跑哪四条门禁」)。
|
|
53
|
+
- **防跑飞。** 修复注入按会话跨回合计配额,耗尽即注入一次用户可见的放行说明并停止自动修复;门禁总预算封顶,超时按「未执行」注入、不静默放行。
|
|
54
|
+
- **它不改代码、不拦截危险命令(那是 danger-guard 的职责)、不写任何文件。**
|
|
55
|
+
|
|
56
|
+
## <img src="https://api.iconify.design/lucide/package.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 安装
|
|
2
57
|
|
|
3
|
-
|
|
58
|
+
```sh
|
|
59
|
+
dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
卸载:`dsh plugin --profile web remove @jayyuen66/dsh-quality-gate`。包在公共 npm 上,安装不需要凭据,也不需要配作用域源。
|
|
63
|
+
|
|
64
|
+
- **要求 Node.js `>= 22.12`。** 产物用到 `AbortSignal.any` 这类较新的运行面。
|
|
65
|
+
- **要求宿主 `dsh` 在 `0.2.1-rc.3` 这条线上。** 宿主读取的版本要求是 `engines.dsh`(`^0.2.1-rc.3`),它**单独决定**兼容性;预发布版本参与范围比较,`0.3.0` 及以后要走显式担责的授权 `dsh plugin --profile <p> allow-version <pkg>@<ver> --dsh-version <exact> --accept-risk`。
|
|
66
|
+
- **运行时依赖 `@jayyuen66/dsh-plugin-shared` 必须已在同一 registry 上**,否则整组 404。
|
|
67
|
+
- **`typescript` 是真实的 `dependencies`**:`tsc` 门禁的兜底入口——工程自己装的那份优先,其次随包安装的这份,运行期无网络动作,从不走 `npx` 的自动下载。
|
|
68
|
+
- **`@deepseek-ai/schemastery` 由宿主解析、不由本包安装**:只有宿主 fork 的那一枚能把 `.volatile()` 字段解析成活引用,换成公共 schemastery 就变成改设置要重启才生效。`@deepseek-ai/dsh-credentials` 的两枚校验函数是值导入,因此落在 `peerDependencies`——`dependencies` 里不放任何 `@deepseek-ai/*` 是发布契约(`test/publish-manifest.test.ts` 钉着:写在 `dependencies` 里的宿主包会在消费方 profile 里物化出第二份,与运行中的宿主并存)。
|
|
69
|
+
- 本地目录形态安装需 pnpm ≥ 12.3.0(12.0.0 的本地目录 `add` 有已知缺陷)。
|
|
70
|
+
|
|
71
|
+
### <img src="https://api.iconify.design/lucide/arrow-down-to-line.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> shared 的版本下限
|
|
72
|
+
|
|
73
|
+
如果你的 profile 用 pnpm `overrides` 把 `@jayyuen66/dsh-plugin-shared` 钉在某个版本,那个钉住的值必须不低于本包声明的那一版。钉得更低会装到一份缺少运行期所导入名字的产物,宿主起来时报 `N entries did not activate` 与 `does not provide an export named ...`——包装得上、永远不生效,而各仓单仓 CI 看不见这一类。
|
|
74
|
+
|
|
75
|
+
## <img src="https://api.iconify.design/lucide/circle-check.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 确认它真的在工作
|
|
76
|
+
|
|
77
|
+
1. **层已经进栈。** `dsh --profile web --dump-config` 末尾出现 `# == @jayyuen66/dsh-quality-gate` 与 `- id: quality-gate`。
|
|
78
|
+
- 那条命令是唯一被接受的「进了层栈」信号,而且它**不是只读命令**——它会重写该 profile 的根装配文件。层行要从 stdout 读,**并且**单独检查 stderr:没匹配上的 patch 目标只出现在 stderr,只看退出码证明不了任何事。两种形态都要认:`# == <层>` 与 `# == <层>, patched by <路径>`。
|
|
79
|
+
2. **卡片渲染出来。** 设置页的插件列表出现「quality-gate 回合收口门禁」;改一个值,「保存」由 disabled 转 enabled,点保存后写入生效。
|
|
80
|
+
3. **门禁真的会跑。** 在它认得的工程里(例如根有 `package.json` + `tsconfig.json` 的 TS 项目)让 agent 改一个代码文件再结束回合:门禁命令在该根执行;命令失败时模型收到 `[quality-gate]` 前缀的修复指令,回合继续而非直接结束。宿主日志里同一前缀的行带着命令与根。若毫无变化,读法是**层没装**,不是规则太松。
|
|
81
|
+
4. **「未能执行」的读法。** 环境类失败(沙箱拒绝、工具装不上、预算不足)注入的是「请手动确认」说明而不是修复指令——这不是代码错误的证据,别照着它改代码。
|
|
82
|
+
|
|
83
|
+
## <img src="https://api.iconify.design/lucide/circle-slash.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 它不提供什么
|
|
84
|
+
|
|
85
|
+
找之前先知道这几件事:
|
|
86
|
+
|
|
87
|
+
- **它不注册任何模型工具。** 整个面就是 `agent/turn-stopping` 事件监听;没有 `gate_status` 这类可调工具。
|
|
88
|
+
- **它没有 HTTP 路由,也没有 Remote 命名空间。** 设置卡直接消费官方 `configForms` 表单、经宿主通道写设置——与自带数据面的兄弟插件不同,本包的数据面就只有设置这一块。
|
|
89
|
+
- **它不加 CLI 子命令。**
|
|
90
|
+
- **它不写文件、不遥测**;唯一的出站请求是设置里显式开启的记忆网关(见「数据与隐私」)。
|
|
91
|
+
- 它**可选**消费的东西:`lessonLoop` 总线(教训上报)、`credentials`(密钥解析)、`sandboxPolicy`(按会话解析策略)、`sessionQuery`(事件读面)、`shell`(执行面)。都是可选 `ctx.get` 读——缺哪个只让对应能力消失,插件照常装载。
|
|
92
|
+
|
|
93
|
+
## <img src="https://api.iconify.design/lucide/plug.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 在 dsh 里启用
|
|
94
|
+
|
|
95
|
+
- 组合包形态:`package.json` 的 `dsh.bundle.patch` 指向 `cordis.patch.yml`,唯一的注册行是 `- id: quality-gate`、`name` 为裸包名(宿主只对裸包名条目扫 `dsh.client`);`dsh plugin --profile web add/remove` 维护该行。
|
|
96
|
+
- Host 半硬依赖只有 `settings`(`default.inject = ["settings"]`);优先级是 设置卡运行时值 > 行 `config:` > schema 内置默认。
|
|
97
|
+
- Client 半(`dsh.client.platform = web`、`immediately: true`)往插件管理页的 `plugins.bundle.config` 槽注入一张卡。两枚标识刻意不同源:槽位 key 是 **bundle 包名** `@jayyuen66/dsh-quality-gate`,设置命名空间与 `configForms.get()` 用的是**裸条目 id** `quality-gate`——写混了卡片哪都不渲染。
|
|
98
|
+
- 卡的保存是显式两段式:改动先留在本地暂存(状态条提示),点「保存」才逐键**串行**写入并消费 `Promise<boolean>` 受理位——首个 `false` 即停手、显示 `saveRejected` 并保留未落盘的改动;写入 reject 显示 `saveFailed`。保存期间输入框仍可编辑,期间产生的新改动保留在待保存面里,不会被这次保存顺带抹掉;「撤销」丢弃暂存。清空一个输入框 = unset,恢复该字段的 schema 默认。
|
|
99
|
+
- 两枚部署值没有卡上的行,只能写在注册行的 `config:` 上、改值随重启生效:`maxRootDepth`(向上找根的层数)与 `gateStdoutMaxBytes`(证据捕获上限),默认值见设置表。
|
|
100
|
+
|
|
101
|
+
## <img src="https://api.iconify.design/lucide/terminal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 跑哪四条门禁
|
|
4
102
|
|
|
5
|
-
|
|
103
|
+
白名单在类型层锁死(`lib/gate-detect.ts` 的 `GateCommand` 联合),只有这四条能进执行面;每个根至多一条,优先级 workspace > Rust > Python > 裸 TS。
|
|
6
104
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
105
|
+
| 命令 | 什么时候发 |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `pnpm check` | 根同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check`。没声明就退回下一候选 |
|
|
108
|
+
| `cargo check` | 根有 `Cargo.toml` |
|
|
109
|
+
| `ruff check .` | 根有 `ruff.toml`,或 `pyproject.toml` 里有 `[tool.ruff]` 一族段。只有 `pyproject.toml` 不算——那是 Python 元数据文件,不是「用 ruff」的声明 |
|
|
110
|
+
| `tsc --noEmit` | 根同时有 `package.json` 与 `tsconfig.json`;缺 `tsconfig.json` 不算 TS 项目 |
|
|
10
111
|
|
|
11
|
-
|
|
112
|
+
- `tsc` 在命令里只是逻辑名:入口在运行期解析——工程自己装的 `typescript` 优先,其次随包那份,最后 PATH 上的 `tsc`(由「工具不可用」出口报成未执行);绝不走 `npx` 的自动下载。
|
|
113
|
+
- 根探测:从编辑文件向上最多 `maxRootDepth`(默认 10)层找最近清单根;monorepo 子包升到 `pnpm-workspace.yaml` 所在的工作区根——但只在该根**产得出门禁**时升级,否则保留子包自己的根(外层没有 `check` 脚本的 workspace 不得吃掉子包的 `tsc` / `ruff` 门禁)。
|
|
114
|
+
- 预算与并发:总预算 `gateBudgetMs`;每根取 `max(10s, 预算/根数)` 与实际剩余的较小值;剩余不足 5s 的尾段不再起跑,按「未执行」注入清单。门禁顺序执行,一次只注入一个失败。
|
|
115
|
+
- 证据上限:每条流内存留存 64 KiB(截断保尾部,首条错误才是根因),被截断时从落盘文件有界读回 4 KiB 头部;单会话编辑记账 500 个文件,超出按插入序淘汰并 warn。
|
|
12
116
|
|
|
13
|
-
|
|
14
|
-
- 编辑口径:`edit` / `write` / `str_replace_editor` 的写入;`view` 只读不算。
|
|
15
|
-
- 清单根:最多向上找 10 层;monorepo 子包升到 `pnpm-workspace.yaml` 所在的工作区根——但只在该根**产得出门禁**时升级,否则保留子包自己的根(外层没有 `check` 脚本的 workspace 不得吃掉子包的 `tsc` / `ruff` 门禁)。
|
|
16
|
-
- 不触发:
|
|
17
|
-
- `enabled: false`;本回合没有可检编辑(只改 md/txt/图片/`.sh`,或被拒、执行失败的编辑)。
|
|
18
|
-
- 子代理会话(`origin === "subagent"` 或 `delegationDepth > 0`);inbox 已有待处理消息(不抢话)。
|
|
19
|
-
- 探测不出门禁的根;回合 signal 已取消或插件已卸载。
|
|
20
|
-
- 宿主没有 shell 服务时 warn 一次并放行;plan mode 不豁免——规划回合改了代码同样收口。
|
|
117
|
+
## <img src="https://api.iconify.design/lucide/sliders-horizontal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 设置项
|
|
21
118
|
|
|
22
|
-
|
|
119
|
+
命名空间 `quality-gate` 即设置条目 id;投影进设置表单的字段就是那几个 `.volatile()` 的。
|
|
120
|
+
|
|
121
|
+
| 字段 | 类型 | 默认值 | 作用 |
|
|
122
|
+
| --- | --- | --- | --- |
|
|
123
|
+
| `enabled` | boolean | `true` | 总开关;关掉即收口不再核验 |
|
|
124
|
+
| `maxInjectsPerTurn` | natural,1–5 | `2` | 修复指令注入配额,按会话跨回合累计 |
|
|
125
|
+
| `gateBudgetMs` | natural,10000–600000 | `300000` | 门禁总预算;超时按失败注入,不静默放行 |
|
|
126
|
+
| `memoryFeedback` | boolean | `false` | 是否把代码型失败额外沉淀到记忆网关 |
|
|
127
|
+
| `memoryGatewayUrl` | string | `""`(未配置) | 网关基址;设置值优先,留空回落 env `TDAI_GATEWAY_URL` |
|
|
128
|
+
| `memoryGatewayKeyRef` | string(凭据引用名) | `TDAI_GATEWAY_KEY` | 网关密钥的引用名;值走宿主凭据服务,不落设置 |
|
|
129
|
+
| `maxRootDepth` | natural | `10` | 向上找项目根的最大层数(部署值,卡上无行) |
|
|
130
|
+
| `gateStdoutMaxBytes` | natural | `65536` | 每条流的证据捕获上限(部署值,卡上无行) |
|
|
131
|
+
|
|
132
|
+
设置持久化在 dsh 数据目录的宿主设置文件里;卡的保存语义见「在 dsh 里启用」。
|
|
133
|
+
|
|
134
|
+
## <img src="https://api.iconify.design/lucide/network.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 对外接口
|
|
135
|
+
|
|
136
|
+
- **无 HTTP 面**:本包不注册任何路由或端点,也没有供外部调用的接口。
|
|
137
|
+
- **对宿主**:settings 命名空间 `quality-gate` 是隐式注册(命名空间 = 条目 id,可编辑字段由 `Config` 上标 `.volatile()` 的项决定,不再有 `settings.register`);schema 与注册行 `config` 共用同一份 `Config`。页面策略声明为 `auto: false`——本包自带卡片,别让宿主再生成一份自动表单。
|
|
138
|
+
- **注入的 followup**:`role: "user"`、`source: { kind: "plugin:quality-gate" }`、id 为 `quality-gate-` 前缀加一个 UUID。
|
|
139
|
+
- **注入给模型的文案只有四类**,统一带 `[quality-gate]` 前缀与命令/根上下文:修复指令;门禁「未能执行」说明(内嵌预算不足的未执行清单);配额耗尽后的放行说明。
|
|
140
|
+
- **日志**走宿主具名 logger(`info` / `warn` / `error` 面),该服务缺席才回退 `console`。
|
|
141
|
+
- **语言**:host 侧文案随官方 settings 的 `locale` 偏好,未注册即中文,改语言下一回合生效;卡片文案走官方 client locale,切语言由宿主重渲染、不必重载页面。
|
|
142
|
+
- **对总线**(装了 lesson-loop 时):`report({ source: "quality-gate", category: "gate-failure" | "gate-not-run", cwd, sessionId, turn, signature: 命令串, detail: 全文证据, evidence: { root, command, kind } })`。总线缺失时报告直接丢弃,门禁照常运行;`memoryFeedback` 开关不影响总线上报。
|
|
143
|
+
- **可选外发**:向 `memoryGatewayUrl` 发 `POST /v2/conversation/add`,5s 超时封顶。请求头带 Bearer 令牌与 `x-tdai-service-id`(env `TDAI_SERVICE_ID` 可覆盖,默认 `default`);body 为 `session_id: "dsh-agent"` 加一条 `assistant` 消息,`agent_id` 取会话 cwd 的尾目录名加 8 位 sha256 前缀(与 lesson-loop 的项目分桶同源)。
|
|
144
|
+
|
|
145
|
+
## <img src="https://api.iconify.design/lucide/lock.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 数据与隐私
|
|
146
|
+
|
|
147
|
+
| 口径 | 内容 |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| 默认零外发 | `memoryFeedback` 关 + 地址空是双保险,任一为空都不推送、也不去查凭据 |
|
|
150
|
+
| 出域内容 | 开启后只把命令、项目根与全文报错(单行化、不截断)发往**你自己配的**网关 |
|
|
151
|
+
| 地址与凭据安全 | 凭据只发 `https` 或回环地址(`localhost`、`127.0.0.0/8`、IPv6 回环);明文 `http` 非回环按未配置拒绝并说明理由。地址解析不出合法 `http(s)` URL 同样按未配置 |
|
|
152
|
+
| 密钥 | 值由宿主凭据服务持有(`$DSH_HOME` 下的凭据文件),设置里只存引用名;引用名不合 POSIX 变量名语法即视为未配置 |
|
|
153
|
+
| 失败处理 | 推送失败只 warn 且每次装载只说一次,绝不阻断收口 |
|
|
154
|
+
| 读盘 | 清单存在性、根 `package.json` 的 scripts、`pyproject.toml` 的段头、截断落盘文件的头部;不写任何文件、不遥测 |
|
|
155
|
+
| 内存 | 会话级账本(编辑记账/注入配额/事件游标)都在进程内存,插件卸载即清 |
|
|
156
|
+
|
|
157
|
+
## <img src="https://api.iconify.design/lucide/triangle-alert.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 已知边界
|
|
158
|
+
|
|
159
|
+
- **白名单只有四条命令,不能经设置扩展。** 用别的检查器(`go test`、`mypy`、`eslint`…)的根得不到门禁。
|
|
160
|
+
- **只记成功编辑**,且只认 `edit` / `write` / `str_replace_editor` 的写入(`view` 只读不算)。扩展名白名单之外的文件(md/txt/图片/`.sh`)不计——没有一条门禁命令能校验它们,`.sh` 只会把根解析到别处跑出假失败。
|
|
161
|
+
- **out-of-process 子代理(ACP / claude-code / codex)不发 dsh 事件**,天然不在门禁面内;in-process 子代理跳过,其编辑由父会话下一回合统一核验。
|
|
162
|
+
- **不抢话**:inbox 已有待处理消息、回合信号已取消或插件已卸载时不注入。plan mode 不豁免——规划回合改了代码同样收口。
|
|
163
|
+
- **证据被截断且没有落盘文件时,首个错误可能已随截断丢弃**;文案如实说明证据不完整,不假装完整。
|
|
164
|
+
- **沙箱策略解析失败时一门都不执行**(按未执行注入),绝不回落部署级策略——那会让门禁写在沙箱外被拒、产出假的失败指令。
|
|
165
|
+
|
|
166
|
+
## <img src="https://api.iconify.design/lucide/circle-help.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 常见问题
|
|
167
|
+
|
|
168
|
+
| 问题 | 回答 |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| pnpm 工程为什么没跑 `pnpm check`? | 根 `package.json` 没声明非空 `check` 脚本。刻意取舍:假失败比漏检查更贵 |
|
|
171
|
+
| 门禁说「未能执行」,是代码错了吗? | 不是。沙箱策略拒绝、runner 起不来、超时、取消、exit 126/127、预算不足都属此类。注入文案明确要求**不要为此修改代码**,并以 `gate-not-run` 单独上报总线 |
|
|
172
|
+
| 会不会无限修下去? | 配额耗尽后注入一次用户可见的放行说明并停止自动修复,直到某次全部干净通过才重新计数 |
|
|
173
|
+
| 临时停掉? | 设置卡关 `enabled`,或 `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate` |
|
|
174
|
+
| `tsc` 用哪个版本? | 工程自己装的那份优先,其次随包安装的 `typescript`,都不在才回落 PATH——从不经 `npx` 下载 |
|
|
175
|
+
| 子代理改的代码呢? | in-process 子代理由父会话下一回合统一核验;out-of-process 子代理不发 dsh 事件,天然不在门禁面内 |
|
|
176
|
+
|
|
177
|
+
## <img src="https://api.iconify.design/lucide/wrench.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 开发
|
|
23
178
|
|
|
24
179
|
```sh
|
|
25
|
-
|
|
180
|
+
pnpm install
|
|
181
|
+
pnpm run check # typecheck(三个 tsconfig)→ lint → client 构建 → host 构建 → 测试(含覆盖率)→ tarball 体检 → 格式
|
|
26
182
|
```
|
|
27
183
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
184
|
+
- 覆盖率只统计 `host.ts` 与 `lib/**`,四项阈值 100%。`src/` 不是"没有用例执行":`test/client-card.test.ts` 无头跑过卡片接线与两语字典(`ui-messages.ts` 被完整执行),但 `client-entry.ts` 的大头是渲染面,要 DOM 基座才能测,本包刻意不引 `react-dom` 与 `jsdom`——渲染测试是另一套基座。把 `src/` 整体计进分母会让阈值变成常红,所以阈值钉在被完整验证的那一面。
|
|
185
|
+
- `test/publish-manifest.test.ts` 钉发布形态:入口可解析、`@deepseek-ai/*` 不进 `dependencies`、产物内容无日期与本机路径,注释与文档面同受其扫描。`test/dist-size.test.ts` 守两个产物的体积预算;`check:pack` 对 `pnpm pack` 的真 tarball 做体检。
|
|
186
|
+
|
|
187
|
+
## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 许可
|
|
188
|
+
|
|
189
|
+
MIT。源码在 [github.com/JayYuen666/dsh-quality-gate](https://github.com/JayYuen666/dsh-quality-gate)。
|
|
33
190
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- 装载由包内 `cordis.patch.yml` 负责(`package.json` 的 `dsh.bundle.patch` 指向它),插一行 `- id: quality-gate`、`name` 为本包名;`dsh plugin --profile web add/remove` 维护该行。
|
|
37
|
-
- Host 半只硬依赖宿主的 `settings`(`default.inject = ["settings"]`);`shell` / `sandboxPolicy` / `lessonLoop` / `credentials` / `sessionQuery` 一律 `ctx.get` 可选读,缺失不会让插件加载失败。
|
|
38
|
-
- 优先级:设置卡的运行时值 > 该行 `config:` > 包内置默认。client 半(`dsh.client.platform = web`、`immediately: true`)往插件管理页的 `plugins.bundle.config` 槽注入一张卡(key = 本包名;`configForms.get()` 与 settings 命名空间用的是裸条目 id `quality-gate`)。
|
|
39
|
-
- 卡的保存是显式的:改动先留在本地暂存,点「保存」才逐键写 settings(状态条提示「有未保存的修改,点「保存」生效」)。
|
|
40
|
-
- 写入通道把 `Promise<boolean>` 受理位原样交给卡片——首个 `false` 即停手、显示 `saveRejected` 那行并保留未落盘的改动(吞掉受理位会让宿主拒写时卡仍显示已保存);写入 reject 显示 `saveFailed`,「撤销」丢弃暂存。
|
|
41
|
-
|
|
42
|
-
### 设置的检查命令
|
|
43
|
-
|
|
44
|
-
四条白名单命令在类型层锁死(只有这四条能进执行面),每根择一,优先级 workspace > Rust > Python > 裸 TS:
|
|
45
|
-
|
|
46
|
-
- `pnpm check`:根同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check`;没声明就退回下一候选——宁可不给门禁,也不替项目发明一条稳定假失败的命令。
|
|
47
|
-
- `cargo check`(`Cargo.toml`)、`ruff check .`(`pyproject.toml` 或 `ruff.toml`)、`npx tsc --noEmit`(`package.json` + `tsconfig.json`,缺 `tsconfig.json` 不算 TS 项目)。
|
|
48
|
-
- 预算与并发:总预算 `gateBudgetMs` 封顶,每根取 `max(10s, 预算/根数)` 与实际剩余的小值。
|
|
49
|
-
- 剩余不足 5s 的尾段不再起跑,按「未执行」注入清单。
|
|
50
|
-
- 门禁顺序执行,一次只注入一个失败。
|
|
51
|
-
- 证据上限:每条流内存留存 64 KiB,被截断时从落盘文件有界读回 4 KiB 头部(首条错误才是根因);单会话编辑记账 500 个文件,超出按插入序淘汰并 warn。
|
|
52
|
-
|
|
53
|
-
### 设置项
|
|
54
|
-
|
|
55
|
-
- `enabled`:boolean,默认 `true`。关掉即收口不再核验。
|
|
56
|
-
- `maxInjectsPerTurn`:1–5,默认 `2`。修复指令注入配额,按会话跨回合累计。
|
|
57
|
-
- `gateBudgetMs`:10000–600000,默认 `300000`。超时按失败注入,不静默放行。
|
|
58
|
-
- `memoryFeedback`:boolean,默认 `false`。是否把代码型失败额外沉淀到记忆网关。
|
|
59
|
-
- `memoryGatewayUrl`:string,默认空 = 不启用;设置值优先,留空回落 env `TDAI_GATEWAY_URL`。
|
|
60
|
-
- `memoryGatewayKeyRef`:string(凭据引用名),默认 `TDAI_GATEWAY_KEY`;设置里只存引用名。
|
|
61
|
-
- 另有两枚非 volatile 的部署值:设置卡上没有它们的行,只在注册行的 `config:` 上给(cordis 交进 apply 的是值而非引用,改值随重启生效)。
|
|
62
|
-
- `maxRootDepth` 默认 `10`——「最多向上找 10 层」说的就是它;`gateStdoutMaxBytes` 默认 `65536`——「每条流内存留存 64 KiB」说的就是它。
|
|
63
|
-
|
|
64
|
-
### 对外接口
|
|
65
|
-
|
|
66
|
-
- 无 HTTP 面:本包不注册任何路由或端点,也没有供外部调用的接口。
|
|
67
|
-
- 对宿主:settings 命名空间 `quality-gate` 是 0.1.7 的隐式注册(= 条目 id,可编辑字段由条目 `Config` 上标了 `.volatile()` 的项决定,本包不再调 `settings.register`),schema 与行 `config` 共用同一份 `Config`。
|
|
68
|
-
- 只剩页面策略要声明:`svc.inject(["settings"], …)` 的子上下文里 `settings.configure({ auto: false }, svc.fiber)`——本包自带卡片,别让宿主再生成一份自动表单。
|
|
69
|
-
- 注入的 followup 为 `role: "user"`、`source: { kind: "plugin:quality-gate" }`、id 为 `quality-gate-` 前缀加一个 UUID。
|
|
70
|
-
- 日志走宿主具名 logger(`ctx.logger("quality-gate")`,本包用的方法面是 `info` / `warn` / `error`),该服务缺席才回退 `console`。
|
|
71
|
-
- 注入给模型的文案只有四类,消息统一带 `[quality-gate]` 前缀与命令/根上下文:
|
|
72
|
-
- 修复指令。
|
|
73
|
-
- 门禁「未能执行」说明。
|
|
74
|
-
- 配额耗尽后的放行说明。
|
|
75
|
-
- 嵌在「未能执行」里的预算不足清单。
|
|
76
|
-
- 两语文案随官方 settings 命名空间 `locale` 的偏好,未注册即中文,改语言下一回合生效。
|
|
77
|
-
- 卡片文案另走官方 client locale:`ctx.locale.register(NS, UI_MESSAGES)` 交字典、`ctx.locale.bind(NS)` 出取文案函数,切语言由宿主重渲染 slot、不必重载页面。
|
|
78
|
-
- 对总线:`ctx.get("lessonLoop")?.report({ source: "quality-gate", category: "gate-failure" | "gate-not-run", cwd, sessionId, turn, signature: 命令串, detail: 全文证据, evidence: { root, command, kind } })`。
|
|
79
|
-
- 可选外发:向 `memoryGatewayUrl` 配的地址发 `POST /v2/conversation/add`,5s 超时封顶。
|
|
80
|
-
- 请求头带 Bearer 令牌与 `x-tdai-service-id`(env 可覆盖,默认 `default`)。
|
|
81
|
-
- body 为 `session_id: "dsh-agent"` + 一条 `assistant` 消息,`agent_id` 取会话 cwd 的尾目录名加 8 位 sha256 前缀(与 lesson-loop 项目分桶同源)。
|
|
82
|
-
|
|
83
|
-
### 数据与隐私
|
|
84
|
-
|
|
85
|
-
- 默认零外发:`memoryFeedback` 关 + 地址空是双保险,任一为空都不推送、也不去查凭据。
|
|
86
|
-
- 开启后送出的只有命令、项目根与全文报错(不截断),落到你自配的网关。
|
|
87
|
-
- 失败只 warn 且每次装载只说一次,绝不阻断收口。
|
|
88
|
-
- 密钥值由宿主凭据服务持有(`$DSH_HOME` 下的凭据文件),引用名不合 POSIX 环境变量语法即视为未配置。
|
|
89
|
-
- 与 lesson-loop 的关系是**可选**的:总线缺失时报告直接丢弃,门禁照常运行;装了才多一份教训沉淀,`memoryFeedback` 开关不影响总线上报。
|
|
90
|
-
- 本包只读盘(清单存在性、根 `package.json` 的 scripts、截断落盘文件的头部),不写文件、不遥测。
|
|
91
|
-
|
|
92
|
-
### 常见问题
|
|
93
|
-
|
|
94
|
-
- pnpm 工程为什么没跑 `pnpm check`?根 `package.json` 没声明非空 `check` 脚本,这是刻意取舍:假失败比漏检查更贵。
|
|
95
|
-
- 门禁说「未能执行」是代码错了吗?不是。
|
|
96
|
-
- 沙箱策略拒绝、runner 起不来、超时、取消、exit 126/127、`npx` 抓到 stub 包、预算不足都属此类。
|
|
97
|
-
- 注入文案明确要求「不要为此修改代码」,不写记忆,并以 `gate-not-run` 单独上报总线。
|
|
98
|
-
- 会不会无限修?配额耗尽后注入一次用户可见的「放行说明」并停止自动修复,直到某次全部干净通过才重新计数。
|
|
99
|
-
- 临时停掉:设置卡关 `enabled`,或 `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate`。
|
|
100
|
-
- 子代理改的代码呢?in-process 子代理跳过,由父会话下一回合统一核验。
|
|
101
|
-
- out-of-process 子代理(ACP/claude-code/codex)不发 dsh 事件,天然不在门禁面面内。
|
|
102
|
-
- 许可 MIT,仓库见 `package.json` 的 `repository.url`。
|
|
191
|
+
机制层面移植自 ECC(MIT,© Affaan Mustafa)——回合收口跑项目自己的检查、编辑记账两处机制。[`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) 记录上游来源、本包在其之上做了什么,以及运行期依赖的边界。
|
package/client.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
// GENERATED by buildClient() in build-client.mjs — edit src/client-entry.ts, then run `node build-client.mjs`.
|
|
2
2
|
window.__ModuleLoader__.load({
|
|
3
|
-
id:
|
|
3
|
+
id: "@jayyuen66/dsh-quality-gate",
|
|
4
4
|
factory: (require) => {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
'use strict';
|
|
6
|
+
const module = { exports: {} };
|
|
7
|
+
let exports = module.exports;
|
|
7
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
9
|
let react = require("react");
|
|
9
|
-
|
|
10
|
-
function e(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
10
|
+
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
|
|
11
|
+
function e(t, n, r) {
|
|
12
|
+
if (t == null || n == null) return (t ?? null) === (n ?? null);
|
|
13
|
+
if (Object.is(t, n)) return !0;
|
|
14
|
+
if (typeof t != `object` || typeof n != `object`) return !1;
|
|
15
|
+
if (r.get(t) === n) return !0;
|
|
16
|
+
if (r.set(t, n), Array.isArray(t) || Array.isArray(n)) return !Array.isArray(t) || !Array.isArray(n) || t.length !== n.length ? !1 : t.every((t, i) => e(t, n[i], r));
|
|
17
|
+
let i = Object.keys(t), a = Object.keys(n);
|
|
18
|
+
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
function t(t, n) {
|
|
21
|
+
let r = [];
|
|
22
|
+
for (let i of Object.keys(t)) e(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
23
|
+
return r;
|
|
24
|
+
}
|
|
25
|
+
// src/ui-messages.ts
|
|
17
26
|
const UI_MESSAGES = {
|
|
18
27
|
zh: {
|
|
19
28
|
cardTitle: "quality-gate 回合收口质量门禁",
|
|
@@ -34,11 +43,11 @@ const UI_MESSAGES = {
|
|
|
34
43
|
budgetLabel: "门禁预算(毫秒)",
|
|
35
44
|
budgetHint: "单次门禁最大耗时(10s-600s,默认 300s;超时按失败注入不静默放行)",
|
|
36
45
|
memoryLabel: "门禁失败沉淀记忆",
|
|
37
|
-
memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0
|
|
46
|
+
memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop 的同步上报不受此开关影响。关闭后只注入不沉淀。真正决定能否投递的是网关地址能否解析出值(见下面两项)",
|
|
38
47
|
gatewayUrlLabel: "记忆网关地址",
|
|
39
|
-
gatewayUrlHint: "
|
|
48
|
+
gatewayUrlHint: "插件不带任何默认地址:本机用户请填自己的网关地址(例如 http://127.0.0.1:8420),没有网关就让它空着。此项留空时回落到环境变量 TDAI_GATEWAY_URL,两处都空才算未配置、不启用记忆反馈",
|
|
40
49
|
keyRefLabel: "网关密钥引用",
|
|
41
|
-
keyRefHint: "网关鉴权令牌的凭据引用名(环境变量名形态,默认 TDAI_GATEWAY_KEY)。这里存的只是引用名,密钥值由 dsh 凭据服务管理(Web 凭据页或 ~/.dsh/.credentials.yaml 的 refs),不会写进 settings.yaml
|
|
50
|
+
keyRefHint: "网关鉴权令牌的凭据引用名(环境变量名形态,默认 TDAI_GATEWAY_KEY)。这里存的只是引用名,密钥值由 dsh 凭据服务管理(Web 凭据页或 ~/.dsh/.credentials.yaml 的 refs),不会写进 settings.yaml;清空即恢复默认引用名,而不是停用记忆反馈"
|
|
42
51
|
},
|
|
43
52
|
en: {
|
|
44
53
|
cardTitle: "quality-gate turn-end quality gate",
|
|
@@ -59,15 +68,14 @@ const UI_MESSAGES = {
|
|
|
59
68
|
budgetLabel: "Gate budget (milliseconds)",
|
|
60
69
|
budgetHint: "Max duration of one gate run (10s-600s, default 300s; a timeout injects a failure instead of passing silently)",
|
|
61
70
|
memoryLabel: "Persist gate failures to memory",
|
|
62
|
-
memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it
|
|
71
|
+
memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it; synchronous reporting to the lesson-loop bus is unaffected by this switch. When off, repairs are injected but nothing is persisted. Whether it can actually deliver is decided by the gateway URL resolving to a value (see the two fields below)",
|
|
63
72
|
gatewayUrlLabel: "Memory gateway URL",
|
|
64
|
-
gatewayUrlHint: "
|
|
73
|
+
gatewayUrlHint: "The plugin ships no default address: fill in your own gateway URL (e.g. http://127.0.0.1:8420), or leave it empty if you have no gateway. When this is empty the TDAI_GATEWAY_URL environment variable is used instead; only when both are empty is memory feedback unconfigured and disabled",
|
|
65
74
|
keyRefLabel: "Gateway credential ref",
|
|
66
|
-
keyRefHint: "Name of the credential holding the gateway token (POSIX env-var form, default TDAI_GATEWAY_KEY). Only the ref name is stored here — the secret itself is managed by the dsh credentials service (the web credentials page or the refs in ~/.dsh/.credentials.yaml) and never lands in settings.yaml; clearing it restores the default ref name"
|
|
75
|
+
keyRefHint: "Name of the credential holding the gateway token (POSIX env-var form, default TDAI_GATEWAY_KEY). Only the ref name is stored here — the secret itself is managed by the dsh credentials service (the web credentials page or the refs in ~/.dsh/.credentials.yaml) and never lands in settings.yaml; clearing it restores the default ref name rather than disabling memory feedback"
|
|
67
76
|
}
|
|
68
77
|
};
|
|
69
|
-
|
|
70
|
-
//#region src/client-entry.ts
|
|
78
|
+
// src/client-entry.ts
|
|
71
79
|
const NS = "quality-gate";
|
|
72
80
|
const BUNDLE_PKG = "@jayyuen66/dsh-quality-gate";
|
|
73
81
|
const CARD_CSS = [
|
|
@@ -198,11 +206,19 @@ function SaveBar(props) {
|
|
|
198
206
|
onClick: props.onDiscard
|
|
199
207
|
}, t("revert")), props.error === null ? (0, react.createElement)("span", { className: "qgc-dirty" }, dirtyText) : (0, react.createElement)("span", { className: "qgc-saveerr" }, props.error));
|
|
200
208
|
}
|
|
209
|
+
/** 值语义相等(与 shared 的 diffTouched 同口径):表单值是标量与数组,引用相等会把等值数组判成改动。 */
|
|
210
|
+
function sameValue(left, right) {
|
|
211
|
+
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
|
212
|
+
}
|
|
201
213
|
/**
|
|
202
214
|
* 逐键 await 写入并**消费受理位**(0.1.7 的 set/unset 回 Promise<boolean>:
|
|
203
215
|
* true=宿主受理;false=拒绝或写入被跳过(官方语义:"refuses stale revisions";
|
|
204
216
|
* 传输失败才 reject)。false 或 rejection 都**不清 touched**——否则宿主拒写时
|
|
205
217
|
* 卡片仍显示"已保存",用户修改被静默丢弃。
|
|
218
|
+
*
|
|
219
|
+
* 成功后也只清**本次真正写下的那些键**:保存期间只有保存按钮被禁用,输入框仍可编辑,
|
|
220
|
+
* 期间产生的新改动若一并清掉就是静默丢失。这里的判据是「当前草稿值仍等于本次快照值」——
|
|
221
|
+
* 相等说明这条已落到宿主、可以从草稿层移除;不等说明用户又改过,保留下来重新变脏。
|
|
206
222
|
*/
|
|
207
223
|
async function writeTouched(channels) {
|
|
208
224
|
const { t, set, unset, keys, touched, setBusy, setSaveError, setTouched } = channels;
|
|
@@ -216,7 +232,11 @@ async function writeTouched(channels) {
|
|
|
216
232
|
}
|
|
217
233
|
}
|
|
218
234
|
setBusy(false);
|
|
219
|
-
setTouched({
|
|
235
|
+
setTouched((prev) => {
|
|
236
|
+
const next = {};
|
|
237
|
+
for (const [key, now] of Object.entries(prev)) if (!(keys.includes(key) && sameValue(now, touched[key]))) next[key] = now;
|
|
238
|
+
return next;
|
|
239
|
+
});
|
|
220
240
|
} catch (error) {
|
|
221
241
|
console.error("[quality-gate] save write failed:", error);
|
|
222
242
|
setBusy(false);
|
|
@@ -349,7 +369,7 @@ function buildCardBody(props) {
|
|
|
349
369
|
return (0, react.createElement)("ul", { className: "qgc-body" }, rowEnabled, rowInjects, rowBudget, rowMemory, rowGatewayUrl, rowGatewayKeyRef, rowSave);
|
|
350
370
|
}
|
|
351
371
|
function DgCard(props) {
|
|
352
|
-
const { t } = props;
|
|
372
|
+
const { t: t$1 } = props;
|
|
353
373
|
const [open, setOpen] = (0, react.useState)(false);
|
|
354
374
|
const snap = props.useCard((cardSnap) => cardSnap);
|
|
355
375
|
const { value } = snap;
|
|
@@ -359,7 +379,7 @@ function DgCard(props) {
|
|
|
359
379
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
360
380
|
/** 渲染取值:touched 优先,快照兜底(未触碰字段实时跟随外部快照)。 */
|
|
361
381
|
const eff = (field) => field in touched ? touched[field] : value[field];
|
|
362
|
-
const dirty =
|
|
382
|
+
const dirty = t(touched, value).length > 0;
|
|
363
383
|
const setField = (field, val) => {
|
|
364
384
|
setTouched((prev) => ({
|
|
365
385
|
...prev,
|
|
@@ -373,12 +393,12 @@ function DgCard(props) {
|
|
|
373
393
|
}));
|
|
374
394
|
};
|
|
375
395
|
const save = () => {
|
|
376
|
-
const keys =
|
|
396
|
+
const keys = t(touched, value);
|
|
377
397
|
if (keys.length === 0) return;
|
|
378
398
|
setBusy(true);
|
|
379
399
|
setSaveError(null);
|
|
380
400
|
writeTouched({
|
|
381
|
-
t,
|
|
401
|
+
t: t$1,
|
|
382
402
|
set: props.set,
|
|
383
403
|
unset: props.unset,
|
|
384
404
|
keys,
|
|
@@ -393,14 +413,14 @@ function DgCard(props) {
|
|
|
393
413
|
setSaveError(null);
|
|
394
414
|
};
|
|
395
415
|
const header = buildCardHeader({
|
|
396
|
-
t,
|
|
416
|
+
t: t$1,
|
|
397
417
|
open,
|
|
398
418
|
onToggle: () => {
|
|
399
419
|
setOpen(!open);
|
|
400
420
|
}
|
|
401
421
|
});
|
|
402
422
|
const body = open ? buildCardBody({
|
|
403
|
-
t,
|
|
423
|
+
t: t$1,
|
|
404
424
|
writable,
|
|
405
425
|
eff,
|
|
406
426
|
setField,
|
|
@@ -471,7 +491,6 @@ function apply(ctx) {
|
|
|
471
491
|
}, DgCard);
|
|
472
492
|
});
|
|
473
493
|
}
|
|
474
|
-
//#endregion
|
|
475
494
|
exports.apply = apply;
|
|
476
495
|
exports.inject = inject;
|
|
477
496
|
return module.exports;
|
package/cordis.patch.yml
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
# - agent/turn-stopping 批处理:从编辑路径向上探测最近清单根
|
|
5
5
|
# (pnpm-workspace 且根 package.json 声明了 check→pnpm check /
|
|
6
6
|
# Cargo.toml→cargo check / pyproject→ruff / package.json+tsconfig→tsc),
|
|
7
|
-
# 门禁失败经 followup 注入修复指令 →
|
|
7
|
+
# 门禁失败经 followup 注入修复指令 → 回合物理不结束(宿主 agent 循环在 turn-stopping
|
|
8
|
+
# 之后二次检查 inbox:有排队项就再开一轮。此处刻意不写行号——那是另一个包的源码位置,
|
|
9
|
+
# 随宿主升级漂移,而它恰恰是本插件唯一的机制前提);
|
|
8
10
|
# 探测只提项目自己声明过的命令,拿不到脚本清单时宁可不给门禁(防假失败注入);
|
|
9
11
|
# - 防跑飞:每回合每根至多 2 次注入、总预算封顶、超时按「未执行」注入不静默通过;
|
|
10
12
|
# inbox 有待处理时不抢话。
|