dsh-continual-evolve 0.10.4 → 0.10.6
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/README.md +14 -12
- package/README.zh.md +14 -12
- package/lib/approval.d.ts +17 -5
- package/lib/approval.js +73 -12
- package/lib/auto.js +1 -0
- package/lib/command.d.ts +3 -0
- package/lib/command.js +20 -148
- package/lib/index.d.ts +36 -0
- package/lib/index.js +48 -2
- package/lib/inject.d.ts +14 -0
- package/lib/inject.js +21 -6
- package/lib/memory-agent.d.ts +3 -1
- package/lib/memory-agent.js +1 -1
- package/lib/memory-guide.d.ts +19 -0
- package/lib/memory-guide.js +73 -0
- package/lib/memory-index.d.ts +114 -0
- package/lib/memory-index.js +197 -0
- package/lib/tool.d.ts +7 -0
- package/lib/tool.js +30 -30
- package/lib/wrapup-command.d.ts +2 -1
- package/lib/wrapup-command.js +7 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[]()
|
|
13
13
|
[]()
|
|
@@ -34,7 +34,7 @@ Agents accumulate reusable experience (repeated failures, durable facts, reusabl
|
|
|
34
34
|
2. **Capability-aware auxiliary calls** — the memory loop, review, planner, wrapup, and fate resolve exact provider/model metadata through [`src/llm-text.ts`](src/llm-text.ts), use the lowest advertised enabled reasoning effort (falling back to a closing effort only when no enabled level exists), and forward the host session id for provider routing; models without reasoning metadata use their provider default.
|
|
35
35
|
3. **Guard** — code-enforced validation: edit schema, blast-radius/scope coherence, and the promotion policy (project-scoped markers, thin content, near-duplicate detection, credential screening keep the global store clean — secrets are rejected at every write sink, including mount materialization). Global creates that near-duplicate an existing entry are rejected at write time (≥0.8 similarity); moderate overlaps carry a `conflictHint` for later consolidation.
|
|
36
36
|
4. **Approve** — global and project writes require explicit human approval; the dialog shows the bounded structured edit diff and conflict warnings, while malformed/lost responses remain retryable rather than counting as rejection.
|
|
37
|
-
5. **Apply & inject** — memory batches preflight every persistent approval, recheck abort before writes, and compensate earlier scope writes if a later batch fails; every successful scope still passes through snapshot + audit. Prompt notes and delegation specs inject into the system prompt (capped, relevance-ranked, contradicted entries demoted, zero tokens when empty);
|
|
37
|
+
5. **Apply & inject** — memory batches preflight every persistent approval, recheck abort before writes, and compensate earlier scope writes if a later batch fails; every successful scope still passes through snapshot + audit. Prompt notes and delegation specs inject into the system prompt (capped, relevance-ranked, contradicted entries demoted, zero tokens when empty); memory CONTENT injects at session start under a hard character budget, frozen per session, with the when_to_save guide riding along; skills still appear as a relevance-ordered capped directory index.
|
|
38
38
|
6. **Validate & roll back** — benchmarks score candidates against frozen cases; rejected candidates roll back deterministically and are captured as draft regression cases (`auto_regression` benchmark).
|
|
39
39
|
|
|
40
40
|
## Install
|
|
@@ -55,14 +55,11 @@ Commands (in-session):
|
|
|
55
55
|
|
|
56
56
|
| Command | Effect |
|
|
57
57
|
|---|---|
|
|
58
|
-
| `/evolve` | help + current local store |
|
|
58
|
+
| `/evolve` | short help + current local store (`/evolve help all` lists every subcommand) |
|
|
59
59
|
| `/evolve list · history · rollback <id>` | inspect and revert (add `project` for this project's store, `global` for the cross-project store) |
|
|
60
60
|
| `/evolve plan [msg]` | run the LLM planner against the store |
|
|
61
61
|
| `/evolve wrapup` | assess this session's local entries: promote / archive / keep |
|
|
62
|
-
| `/evolve archive · unarchive
|
|
63
|
-
| `/evolve recall [scope] <query…>` | targeted memory recall: full content with version, source, and staleness |
|
|
64
|
-
| `/evolve remember <type> [scope] <text…>` | immediately persist one typed memory (`user|feedback|project|reference`) |
|
|
65
|
-
| `/evolve forget [scope] <query…>` | locate one memory and archive it (restorable); ambiguous queries only list |
|
|
62
|
+
| `/evolve archive · unarchive <id>` | hide from injection (data kept, restorable) |
|
|
66
63
|
| `/evolve consolidate [apply] [merge]` | report (or apply) one batch archive of conflict-hinted + stale zero-use global entries; `merge` folds near-duplicate content into the survivors |
|
|
67
64
|
| `/evolve failures` | aggregated failure classes (gate + benchmark) |
|
|
68
65
|
| `/evolve log [tail N] [session <id>]` | plugin log |
|
|
@@ -73,13 +70,13 @@ Commands (in-session):
|
|
|
73
70
|
| `/evolve pause · resume · status` | pause/resume the auto-review gate (manual tools keep working), gate state |
|
|
74
71
|
| `/evolve usage` | per-entry injection counts + exact provider-reported tokens for direct memory/review/planner/wrapup/fate calls (benchmark host subagents excluded) |
|
|
75
72
|
|
|
76
|
-
Model tools: `evolve_list / add / update / delete / rollback / recall` (`evolve_delete` takes `id` or a batch `ids` array — one refinement, one approval; `evolve_recall` filters by query, kinds, scopes, memory types, and limit, and returns full content with version, source, and staleness).
|
|
73
|
+
Model tools: `evolve_list / add / update / delete / rollback / recall` (`evolve_delete` takes `id` or a batch `ids` array — one refinement, one approval; `evolve_recall` filters by query, kinds, scopes, memory types, and limit, and returns full content with version, source, and staleness). Memory reads and writes live in the conversation — the model carries the injected memory index plus `evolve_recall`/`evolve_add` — so there are no `/evolve remember · forget · recall` counterparts: ask in chat.
|
|
77
74
|
|
|
78
75
|
For third-party consumers: every applied evolution (gate or manual) appends a structured `evolve_complete` event to `reviews.jsonl` (`src/evolve-event.ts` defines the shape) alongside the human-readable audit records.
|
|
79
76
|
|
|
80
77
|
`/evolve usage` also reads `evolve/token-usage.jsonl`: exact provider-reported input/cache/output/total tokens for the plugin's direct memory-agent, review, planner, manual-wrapup, and automatic-fate calls. The report covers a retained tail rather than lifetime usage, distinguishes missing provider samples, and explicitly excludes host benchmark subagents, their agent-loop calls, and per-entry injection attribution.
|
|
81
78
|
|
|
82
|
-
Injection shape: prompt notes and delegation specs inject with content (≤6/kind × 180 chars, relevance-ranked). Memories
|
|
79
|
+
Injection shape: prompt notes and delegation specs inject with content (≤6/kind × 180 chars, relevance-ranked). Memories inject as CONTENT at session start: the `evolve:memory-index` section (order 400) carries the when_to_save guide plus every memory body the budget allows, ranked `project > feedback > user > reference`, with overflow degraded to `[memory:type:id] title` hooks and the remainder counted (hard cap `memoryIndex.maxChars`, default 6000 chars; `evolve_recall` reads anything not shown). The section is computed once per session and reused byte-for-byte, so the system prompt stays stable and prompt-cache reads keep hitting — a memory written mid-session appears in the next session. Skills still appear as a relevance-ordered directory index (capped at 15 lines with a fold counter); memories no longer do. Every memory apply also refreshes a readable `MEMORY.md` index plus one fact file per entry in the store directory. With `memoryIndex.guide` off and an empty store, injection is zero tokens.
|
|
83
80
|
|
|
84
81
|
## Configuration
|
|
85
82
|
|
|
@@ -94,13 +91,18 @@ Injection shape: prompt notes and delegation specs inject with content (≤6/kin
|
|
|
94
91
|
| `reviewBudgetTokens` | `4096` | output budget for the gate call |
|
|
95
92
|
| `notifyOnAutoReview` | `true` | visible follow-up notice after an applied gate run |
|
|
96
93
|
| `requireGlobalApproval` | `true` | global and project edits ask for explicit approval |
|
|
94
|
+
| `recordLanguage` | `auto` | authoring **and dialog** language: `auto` follows the DSH client preference, then the session's own user text, then `en`; `zh`/`en` pin it |
|
|
97
95
|
| `localFate` | `false` | optional local-entry promote/archive fate assessment; unreachable while the listener runs memory-only, so it only affects direct/full callers |
|
|
98
96
|
| `fateIntervalTurns` | follows `reviewIntervalTurns` | minimum turns between fate assessments |
|
|
99
97
|
| `goalBlockedWrapupTurns` | `3` | consecutive blocked-goal gate runs trigger one fate assessment (`0` disables) |
|
|
100
98
|
| `promotionBlockPatterns` | POSIX paths, session ids, `~/.dsh` | content matching these is project-scoped and never promoted to global |
|
|
101
99
|
| `promotionMinChars` | `100` | whole promotions below this length stay local |
|
|
102
100
|
| `injectionDirectoryLines` | `15` | entry-directory lines per build before folding into a counter |
|
|
103
|
-
| `sectionOrder` | `118` | system-prompt section order |
|
|
101
|
+
| `sectionOrder` | `118` | system-prompt section order for the guidance + entries sections |
|
|
102
|
+
| `memoryIndex.enabled` | `true` | register the `evolve:memory-index` content section |
|
|
103
|
+
| `memoryIndex.guide` | `true` | include the when_to_save guide (with the store empty and this off, the section renders nothing) |
|
|
104
|
+
| `memoryIndex.order` | `400` | section order (upstream named slots start at `PLAN_POLICY=500`; the plugin's other sections sit at 118/119) |
|
|
105
|
+
| `memoryIndex.maxChars` | `6000` | hard character budget for the injected memories block |
|
|
104
106
|
| `skillsDir` | `<dshHome>/skills` | where skill entries materialize as SKILL.md bundles |
|
|
105
107
|
| `rubricKey` | auto-generated key file | AES-256-GCM passphrase for benchmark rubrics (`DSH_EVOLVE_RUBRIC_KEY` overrides) |
|
|
106
108
|
| `logToFile` / `logLevel` / `logMaxBytes` | `true` / `1` / 5 MiB | plugin-owned JSONL file log with rotation |
|
|
@@ -142,7 +144,7 @@ before aborting.
|
|
|
142
144
|
|
|
143
145
|
```bash
|
|
144
146
|
pnpm install && pnpm build # deps + tsc -> lib/
|
|
145
|
-
pnpm test # vitest (
|
|
147
|
+
pnpm test # vitest (1097 tests)
|
|
146
148
|
pnpm test:coverage # v8 coverage, thresholds enforced in CI
|
|
147
149
|
pnpm coverage:gaps # locate uncovered lines per file (read-only)
|
|
148
150
|
pnpm lint # oxlint src test
|
|
@@ -152,7 +154,7 @@ Project layout:
|
|
|
152
154
|
|
|
153
155
|
```
|
|
154
156
|
├── src/ # engine, tools, commands, memory agent, recall, projection, gate, fate, benchmark, injection + token usage…
|
|
155
|
-
├── test/ # vitest suites (
|
|
157
|
+
├── test/ # vitest suites (59 files)
|
|
156
158
|
├── lib/ # build output (tsc)
|
|
157
159
|
├── docs/
|
|
158
160
|
│ ├── design.md # full design doc (hardening matrix)
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[]()
|
|
13
13
|
[]()
|
|
@@ -34,7 +34,7 @@ Agent 在每个会话里积累可复用经验(重复失败、持久事实、
|
|
|
34
34
|
2. **能力感知的辅助调用**——memory loop、review、planner、wrapup、fate 通过 [`src/llm-text.ts`](src/llm-text.ts) 解析精确 provider/model 能力并使用模型公布的最低开启 reasoning effort,同时转发 host session id 供 provider 路由;只有没有开启档时才回退关闭档,没有 reasoning 元数据时使用 provider 默认行为。
|
|
35
35
|
3. **守卫**——代码强制校验:编辑 schema、blast-radius 与作用域一致性、晋升政策(项目专属标记 / 过薄内容 / 近似重复检测 / 凭据筛查保持全局库干净——密钥类内容在所有写入出口被拒,含 mount 物化)。全局 create 与既有条目高度相似(≥0.8)时写入即拒;中等重叠带 `conflictHint` 供后续合并。
|
|
36
36
|
4. **审批**——全局与项目写入需明确人工批准;弹窗展示有界结构化编辑 diff 与冲突提示,弹窗丢失/响应畸形会重试,不会被误记为拒绝。
|
|
37
|
-
5. **应用与注入**——memory 批次先完成所有持久化审批,写前重查 abort;后续 scope 失败时补偿回滚先前写入,成功 scope 仍保留快照与审计。prompt 补充与委派规格注入系统提示词(封顶、按相关性排序、被证伪条目降权、空 store 零 token
|
|
37
|
+
5. **应用与注入**——memory 批次先完成所有持久化审批,写前重查 abort;后续 scope 失败时补偿回滚先前写入,成功 scope 仍保留快照与审计。prompt 补充与委派规格注入系统提示词(封顶、按相关性排序、被证伪条目降权、空 store 零 token);**memory 正文**在会话开场按硬预算注入(会话内冻结,when_to_save 指南随行);skill 仍以按相关性排序的目录索引出现。
|
|
38
38
|
6. **验证与回滚**——benchmark 用冻结用例为候选打分;被拒候选确定性回滚,并自动沉淀为 draft 回归用例(`auto_regression` 基准)。
|
|
39
39
|
|
|
40
40
|
## 安装
|
|
@@ -55,14 +55,11 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
55
55
|
|
|
56
56
|
| 命令 | 效果 |
|
|
57
57
|
|---|---|
|
|
58
|
-
| `/evolve` |
|
|
58
|
+
| `/evolve` | 简短帮助 + 当前 local store(`/evolve help all` 列全部子命令) |
|
|
59
59
|
| `/evolve list · history · rollback <id>` | 查看与回滚(加 `project` 操作本项目库,`global` 操作跨项目库) |
|
|
60
60
|
| `/evolve plan [msg]` | 对 store 运行 LLM 规划器 |
|
|
61
61
|
| `/evolve wrapup` | 收尾本会话 local 条目:晋升 / 归档 / 保留 |
|
|
62
|
-
| `/evolve archive · unarchive
|
|
63
|
-
| `/evolve recall [scope] <query…>` | 定向记忆召回:全文 + 版本 + 来源 + 过时信号 |
|
|
64
|
-
| `/evolve remember <type> [scope] <text…>` | 立即持久化一条类型化记忆(`user|feedback|project|reference`) |
|
|
65
|
-
| `/evolve forget [scope] <query…>` | 按 query 定位一条记忆并归档(可恢复);多义只列候选不写 |
|
|
62
|
+
| `/evolve archive · unarchive <id>` | 从注入中隐藏(数据保留可恢复) |
|
|
66
63
|
| `/evolve consolidate [apply] [merge]` | 报告(或应用)冲突提示 + 零使用陈旧全局条目的批量归档;`merge` 将近重复内容并入幸存原条目 |
|
|
67
64
|
| `/evolve failures` | 失败类聚合(门禁 + benchmark) |
|
|
68
65
|
| `/evolve log [tail N] [session <id>]` | 插件日志 |
|
|
@@ -73,13 +70,13 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
73
70
|
| `/evolve pause · resume · status` | 暂停/恢复自动门禁(手动工具不受影响)、门禁状态 |
|
|
74
71
|
| `/evolve usage` | 每条目注入次数 + memory/review/planner/wrapup/fate 直属调用的 provider 精确 token(不含 benchmark 宿主子代理) |
|
|
75
72
|
|
|
76
|
-
模型工具:`evolve_list / add / update / delete / rollback / recall`(`evolve_delete` 支持 `id` 或批量 `ids` 数组——一次 refinement、一次审批;`evolve_recall` 按 query/kind/scope/memoryType/limit 过滤,返回全文 + 版本 + 来源 +
|
|
73
|
+
模型工具:`evolve_list / add / update / delete / rollback / recall`(`evolve_delete` 支持 `id` 或批量 `ids` 数组——一次 refinement、一次审批;`evolve_recall` 按 query/kind/scope/memoryType/limit 过滤,返回全文 + 版本 + 来源 + 过时信号)。记忆的读写都在对话里完成——模型手里有注入的记忆索引 + `evolve_recall`/`evolve_add`——因此不再有 `/evolve remember · forget · recall` 对应命令:直接在对话里说。
|
|
77
74
|
|
|
78
75
|
第三方消费:每次进化落地(门禁或手动)都会向 `reviews.jsonl` 追加结构化 `evolve_complete` 事件(shape 见 `src/evolve-event.ts`),与人类可读的审计记录并存。
|
|
79
76
|
|
|
80
77
|
`/evolve usage` 还会读取 `evolve/token-usage.jsonl`:插件直属 memory Agent、review、planner、手动 wrapup 与自动 fate 调用的 provider 精确 input/cache/output/total token。报告只覆盖保留尾部而非终身累计,单独显示 provider 未返回 usage 的调用,并明确排除宿主 benchmark 子代理、其 agent-loop 调用与逐条 memory 注入归因。
|
|
81
78
|
|
|
82
|
-
注入形态:prompt 补充与委派规格带内容注入(每 kind ≤6 条 × 180 字符,按相关性排序)。memory
|
|
79
|
+
注入形态:prompt 补充与委派规格带内容注入(每 kind ≤6 条 × 180 字符,按相关性排序)。memory 以**正文**在会话开场注入:`evolve:memory-index` section(order 400)承载 when_to_save 指南 + 预算内的全部记忆正文,排序 `project > feedback > user > reference`,放不下的降级为 `[memory:type:id] 标题`钩子、其余折叠计数(硬上限 `memoryIndex.maxChars`,默认 6000 字符;未展示的用 `evolve_recall` 读)。该 section 每会话只算一次并逐字节复用,system prompt 因此稳定、prompt cache 持续命中——会话中途写入的记忆下个会话生效。skill 仍以按相关性排序的目录索引出现(15 行封顶 + 折叠计数行),memory 不再列入。每次 memory 落盘同时刷新 store 目录下的可读 `MEMORY.md` 索引 + 单事实文件。`memoryIndex.guide` 关闭且 store 为空时 = 零注入 token。
|
|
83
80
|
|
|
84
81
|
## 配置
|
|
85
82
|
|
|
@@ -94,13 +91,18 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
94
91
|
| `reviewBudgetTokens` | `4096` | 门禁调用输出预算 |
|
|
95
92
|
| `notifyOnAutoReview` | `true` | 门禁应用后发可见跟进通知 |
|
|
96
93
|
| `requireGlobalApproval` | `true` | 全局与项目写入需明确批准 |
|
|
94
|
+
| `recordLanguage` | `auto` | 记录**与弹窗**语言:`auto` 依次跟随 DSH 客户端偏好 → 会话自身用户文本 → `en`;`zh`/`en` 固定 |
|
|
97
95
|
| `localFate` | `false` | 可选的本地条目晋升/归档 fate 评估;listener 以 memory-only 运行时不可达,只对直接/完整调用者生效 |
|
|
98
96
|
| `fateIntervalTurns` | 跟随 `reviewIntervalTurns` | 归宿评估的最小回合间隔 |
|
|
99
97
|
| `goalBlockedWrapupTurns` | `3` | 连续阻塞目标的门禁轮数触发一次归宿评估(`0` 关闭) |
|
|
100
98
|
| `promotionBlockPatterns` | POSIX 路径、session id、`~/.dsh` | 内容命中即判定项目专属,永不晋升全局 |
|
|
101
99
|
| `promotionMinChars` | `100` | 低于此长度的整体晋升留在本地 |
|
|
102
100
|
| `injectionDirectoryLines` | `15` | 每次构建的目录行数上限,超出折叠为计数行 |
|
|
103
|
-
| `sectionOrder` | `118` |
|
|
101
|
+
| `sectionOrder` | `118` | 指南与条目两个 section 的系统提示词顺序 |
|
|
102
|
+
| `memoryIndex.enabled` | `true` | 是否注册 `evolve:memory-index` 内容 section |
|
|
103
|
+
| `memoryIndex.guide` | `true` | 是否注入 when_to_save 指南(关闭且 store 为空时该 section 不渲染) |
|
|
104
|
+
| `memoryIndex.order` | `400` | section 顺序(上游命名槽位自 `PLAN_POLICY=500` 起;本插件另两段在 118/119) |
|
|
105
|
+
| `memoryIndex.maxChars` | `6000` | 注入记忆块的硬字符预算 |
|
|
104
106
|
| `skillsDir` | `<dshHome>/skills` | 技能条目物化为 SKILL.md 的根目录 |
|
|
105
107
|
| `rubricKey` | 自动生成本地密钥文件 | benchmark rubric 的 AES-256-GCM 口令(`DSH_EVOLVE_RUBRIC_KEY` 可覆盖) |
|
|
106
108
|
| `logToFile` / `logLevel` / `logMaxBytes` | `true` / `1` / 5 MiB | 插件自带 JSONL 文件日志带轮转 |
|
|
@@ -126,7 +128,7 @@ profile patch 示例:
|
|
|
126
128
|
|
|
127
129
|
```bash
|
|
128
130
|
pnpm install && pnpm build # 依赖 + tsc -> lib/
|
|
129
|
-
pnpm test # vitest(
|
|
131
|
+
pnpm test # vitest(1097 例)
|
|
130
132
|
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
131
133
|
pnpm coverage:gaps # 定位各文件未覆盖行(只读)
|
|
132
134
|
pnpm lint # oxlint src test
|
|
@@ -136,7 +138,7 @@ pnpm lint # oxlint src test
|
|
|
136
138
|
|
|
137
139
|
```
|
|
138
140
|
├── src/ # 引擎、工具、命令、memory Agent、召回、投影、门禁、fate、benchmark、注入 + token 用量…
|
|
139
|
-
├── test/ # vitest 测试套件(
|
|
141
|
+
├── test/ # vitest 测试套件(59 个文件)
|
|
140
142
|
├── lib/ # 构建产物(tsc)
|
|
141
143
|
├── docs/
|
|
142
144
|
│ ├── design.md # 完整设计文档(硬化矩阵)
|
package/lib/approval.d.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Context } from "@deepseek-ai/cordis";
|
|
8
8
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
9
|
-
import type
|
|
9
|
+
import { type RecordLanguagePreference } from "./record-language.js";
|
|
10
10
|
export type ScopeApprovalDecision = "approved" | "declined";
|
|
11
|
+
/** Labels the approval dialog offers for each decision (parser contract). */
|
|
12
|
+
export declare const APPROVE_LABELS: readonly ["批准", "Approve"];
|
|
13
|
+
export declare const DECLINE_LABELS: readonly ["拒绝", "Decline"];
|
|
11
14
|
export interface QuestionService {
|
|
12
15
|
ask(request: {
|
|
13
16
|
questions: {
|
|
@@ -24,6 +27,7 @@ export interface QuestionService {
|
|
|
24
27
|
answers?: {
|
|
25
28
|
id: string;
|
|
26
29
|
selected?: string[];
|
|
30
|
+
custom?: string;
|
|
27
31
|
}[];
|
|
28
32
|
}>;
|
|
29
33
|
}
|
|
@@ -41,20 +45,28 @@ export declare function questionServiceOf(ctx: Context): QuestionService | undef
|
|
|
41
45
|
* Copy follows the headline → details → impact shape in the resolved dialog
|
|
42
46
|
* language; labels stay literal per language for parser compatibility.
|
|
43
47
|
*
|
|
48
|
+
* Language resolution runs the full record-language chain — explicit config,
|
|
49
|
+
* then the durable client preference, then the session's own recent user text
|
|
50
|
+
* — because an approval dialog raised from a tool call DOES have a session
|
|
51
|
+
* behind it. Stopping at the durable tier gave a Chinese-speaking user English
|
|
52
|
+
* dialogs whenever the stored preference was unset (2026-10-01).
|
|
53
|
+
*
|
|
44
54
|
* @param ctx Cordis context carrying the userQuestions service.
|
|
45
55
|
* @param agent Agent prompting the question, if any.
|
|
46
56
|
* @param signal Abort signal for the question, if any.
|
|
47
57
|
* @param scope Target persistent scope.
|
|
48
58
|
* @param what Edit summary; truncated to 300 chars with "…" when longer.
|
|
49
|
-
* @param
|
|
50
|
-
*
|
|
59
|
+
* @param configured Plugin `recordLanguage` preference; `auto`/absent defers
|
|
60
|
+
* to the durable preference and the session's user text.
|
|
51
61
|
* @returns "approved" when the user picks the approval label, otherwise "declined".
|
|
52
62
|
* @throws When the service is missing or the answer is not unique/explicit.
|
|
53
63
|
*/
|
|
54
|
-
export declare function requestScopeApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, scope: "project" | "global", what: string,
|
|
64
|
+
export declare function requestScopeApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, scope: "project" | "global", what: string, configured?: RecordLanguagePreference): Promise<ScopeApprovalDecision>;
|
|
55
65
|
/**
|
|
56
66
|
* Ask the user to approve a global edit. Throws when the service is missing,
|
|
57
67
|
* the user declines, or the question cannot be answered.
|
|
68
|
+
*
|
|
69
|
+
* @param configured Plugin `recordLanguage` preference (see {@link requestScopeApproval}).
|
|
58
70
|
*/
|
|
59
|
-
export declare function requireGlobalApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, what: string): Promise<void>;
|
|
71
|
+
export declare function requireGlobalApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, what: string, configured?: RecordLanguagePreference): Promise<void>;
|
|
60
72
|
//# sourceMappingURL=approval.d.ts.map
|
package/lib/approval.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
|
-
import { approvalCopy
|
|
1
|
+
import { approvalCopy } from "./copy.js";
|
|
2
|
+
import { normalizeRecordLanguage, resolveRecordLanguage } from "./record-language.js";
|
|
3
|
+
import { recentUserText } from "./inject.js";
|
|
4
|
+
/** Labels the approval dialog offers for each decision (parser contract). */
|
|
5
|
+
export const APPROVE_LABELS = ["批准", "Approve"];
|
|
6
|
+
export const DECLINE_LABELS = ["拒绝", "Decline"];
|
|
7
|
+
/**
|
|
8
|
+
* Typed ("Other") answers accepted as an explicit decision once normalized.
|
|
9
|
+
* Deliberately exact: a substring rule would read "不批准" as approval, and a
|
|
10
|
+
* misread decision is far worse than asking the human to click the button.
|
|
11
|
+
*/
|
|
12
|
+
const TYPED_APPROVALS = new Set(["批准", "approve"]);
|
|
13
|
+
const TYPED_DECLINES = new Set(["拒绝", "decline"]);
|
|
14
|
+
/** Trim case/quotes/trailing punctuation and a trailing 吧/了 from a typed answer. */
|
|
15
|
+
function normalizeTypedAnswer(text) {
|
|
16
|
+
return text
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/^[\s"'“”「」]+/, "")
|
|
20
|
+
.replace(/[\s"'“”「」。!!,,、]+$/, "")
|
|
21
|
+
.replace(/[吧了]$/, "");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read a free-text answer as a decision, or `undefined` when it matches no
|
|
25
|
+
* label exactly. Never guesses.
|
|
26
|
+
*/
|
|
27
|
+
function typedDecision(custom) {
|
|
28
|
+
if (typeof custom !== "string")
|
|
29
|
+
return undefined;
|
|
30
|
+
const normalized = normalizeTypedAnswer(custom);
|
|
31
|
+
if (TYPED_APPROVALS.has(normalized))
|
|
32
|
+
return "approved";
|
|
33
|
+
if (TYPED_DECLINES.has(normalized))
|
|
34
|
+
return "declined";
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
2
37
|
/**
|
|
3
38
|
* Lazily resolve the userQuestions service from the context.
|
|
4
39
|
* Returns undefined when the service is not loaded — callers decide
|
|
@@ -15,22 +50,30 @@ export function questionServiceOf(ctx) {
|
|
|
15
50
|
* Copy follows the headline → details → impact shape in the resolved dialog
|
|
16
51
|
* language; labels stay literal per language for parser compatibility.
|
|
17
52
|
*
|
|
53
|
+
* Language resolution runs the full record-language chain — explicit config,
|
|
54
|
+
* then the durable client preference, then the session's own recent user text
|
|
55
|
+
* — because an approval dialog raised from a tool call DOES have a session
|
|
56
|
+
* behind it. Stopping at the durable tier gave a Chinese-speaking user English
|
|
57
|
+
* dialogs whenever the stored preference was unset (2026-10-01).
|
|
58
|
+
*
|
|
18
59
|
* @param ctx Cordis context carrying the userQuestions service.
|
|
19
60
|
* @param agent Agent prompting the question, if any.
|
|
20
61
|
* @param signal Abort signal for the question, if any.
|
|
21
62
|
* @param scope Target persistent scope.
|
|
22
63
|
* @param what Edit summary; truncated to 300 chars with "…" when longer.
|
|
23
|
-
* @param
|
|
24
|
-
*
|
|
64
|
+
* @param configured Plugin `recordLanguage` preference; `auto`/absent defers
|
|
65
|
+
* to the durable preference and the session's user text.
|
|
25
66
|
* @returns "approved" when the user picks the approval label, otherwise "declined".
|
|
26
67
|
* @throws When the service is missing or the answer is not unique/explicit.
|
|
27
68
|
*/
|
|
28
|
-
export async function requestScopeApproval(ctx, agent, signal, scope, what,
|
|
69
|
+
export async function requestScopeApproval(ctx, agent, signal, scope, what, configured) {
|
|
29
70
|
const userQuestions = questionServiceOf(ctx);
|
|
30
71
|
if (!userQuestions) {
|
|
31
72
|
throw new Error("global evolution edits require the userQuestions service (load @deepseek-ai/dsh-user-questions)");
|
|
32
73
|
}
|
|
33
|
-
const
|
|
74
|
+
const lang = normalizeRecordLanguage(configured) ??
|
|
75
|
+
resolveRecordLanguage({ ctx, trajectoryText: recentUserText(agent) });
|
|
76
|
+
const copy = approvalCopy(scope, what, lang);
|
|
34
77
|
const answer = await userQuestions.ask({
|
|
35
78
|
questions: [
|
|
36
79
|
{
|
|
@@ -46,20 +89,38 @@ export async function requestScopeApproval(ctx, agent, signal, scope, what, lang
|
|
|
46
89
|
if (matching.length !== 1 || !Array.isArray(matching[0]?.selected)) {
|
|
47
90
|
throw new Error("evolution approval returned no unique explicit decision");
|
|
48
91
|
}
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
92
|
+
const entry = matching[0];
|
|
93
|
+
const selected = [...new Set(entry.selected)];
|
|
94
|
+
if (selected.length === 0) {
|
|
95
|
+
// A typed "Other" answer counts only when it matches a dialog label
|
|
96
|
+
// exactly; anything fuzzier stays an error, so a lost or ambiguous
|
|
97
|
+
// reply is never recorded as a durable rejection.
|
|
98
|
+
const typed = typedDecision(entry.custom);
|
|
99
|
+
if (typed !== undefined) {
|
|
100
|
+
return typed;
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`evolution approval needs one clicked option (${APPROVE_LABELS.join("/")} or ${DECLINE_LABELS.join("/")}) — a typed reply cannot serve as a decision`);
|
|
103
|
+
}
|
|
104
|
+
if (selected.length !== 1) {
|
|
53
105
|
throw new Error(`evolution approval returned an invalid decision: ${JSON.stringify(selected)}`);
|
|
54
106
|
}
|
|
55
|
-
|
|
107
|
+
const label = selected[0];
|
|
108
|
+
if (APPROVE_LABELS.includes(label)) {
|
|
109
|
+
return "approved";
|
|
110
|
+
}
|
|
111
|
+
if (DECLINE_LABELS.includes(label)) {
|
|
112
|
+
return "declined";
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`evolution approval returned an unknown decision label: ${JSON.stringify(label)}`);
|
|
56
115
|
}
|
|
57
116
|
/**
|
|
58
117
|
* Ask the user to approve a global edit. Throws when the service is missing,
|
|
59
118
|
* the user declines, or the question cannot be answered.
|
|
119
|
+
*
|
|
120
|
+
* @param configured Plugin `recordLanguage` preference (see {@link requestScopeApproval}).
|
|
60
121
|
*/
|
|
61
|
-
export async function requireGlobalApproval(ctx, agent, signal, what) {
|
|
62
|
-
if ((await requestScopeApproval(ctx, agent, signal, "global", what)) !== "approved") {
|
|
122
|
+
export async function requireGlobalApproval(ctx, agent, signal, what, configured) {
|
|
123
|
+
if ((await requestScopeApproval(ctx, agent, signal, "global", what, configured)) !== "approved") {
|
|
63
124
|
throw new Error("global evolution edit rejected by the user");
|
|
64
125
|
}
|
|
65
126
|
}
|
package/lib/auto.js
CHANGED
|
@@ -504,6 +504,7 @@ export async function runMemoryExtractionPhase(ctx, engine, agent, config, state
|
|
|
504
504
|
decisionCursor: memorySnapshot.cursor,
|
|
505
505
|
scopeDecisions: state.memoryDecisions,
|
|
506
506
|
onScopeDecision: (key, decision) => rememberMemoryDecision(state, key, decision),
|
|
507
|
+
...(config.recordLanguage !== undefined ? { recordLanguage: config.recordLanguage } : {}),
|
|
507
508
|
...(signal ? { signal } : {}),
|
|
508
509
|
});
|
|
509
510
|
for (const result of application.results) {
|
package/lib/command.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Context } from "@deepseek-ai/cordis";
|
|
|
6
6
|
import type { HarnessEntry, HarnessState, RefinementKind } from "./types.js";
|
|
7
7
|
import type { EvolutionEngine } from "./service.js";
|
|
8
8
|
import type { PromotionPolicy } from "./promotion.js";
|
|
9
|
+
import type { RecordLanguagePreference } from "./record-language.js";
|
|
9
10
|
export interface CommandGateOptions {
|
|
10
11
|
requireGlobalApproval: boolean;
|
|
11
12
|
}
|
|
@@ -22,6 +23,8 @@ export interface CommandRuntimeOptions {
|
|
|
22
23
|
* registered; this is not a registration gate.
|
|
23
24
|
*/
|
|
24
25
|
autoReview?: boolean;
|
|
26
|
+
/** Plugin `recordLanguage` preference for dialog copy (see `approval.ts`). */
|
|
27
|
+
recordLanguage?: RecordLanguagePreference;
|
|
25
28
|
}
|
|
26
29
|
export declare function registerEvolveCommand(ctx: Context, engine: EvolutionEngine, opts: CommandGateOptions, runtime: CommandRuntimeOptions): void;
|
|
27
30
|
/**
|