Skip to content

Commit 9ecbb66

Browse files
committed
release: v0.43.1 — repack PR #180 (interactive narration + auto-resume + prefix-cache tuning)
1 parent 3c39676 commit 9ecbb66

2 files changed

Lines changed: 198 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "deep-copilot",
33
"displayName": "Deep Copilot",
44
"description": "Deep Copilot — AI coding agent embedded in VS Code. Powered by DeepSeek V4, with file editing, terminal, search, and plan/todos. Standalone, no backend required.",
5-
"version": "0.43.0",
5+
"version": "0.43.1",
66
"publisher": "ZhouChaunge",
77
"icon": "imgs/logo.png",
88
"repository": {

release/notes_v0431.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Deep Copilot v0.43.1
2+
3+
> 中文版在下方 / English notes below.
4+
5+
## 🇨🇳 中文
6+
7+
主题:**工具调用前穿插逐步文字解说 + 后台唤醒调度 + DeepSeek 前缀缓存调优**
8+
9+
> 本版本主要打包 PR #180(v0.43.0)的全部能力,并以 0.43.1 重新构建 VSIX。
10+
11+
### ✨ 新特性 1:每个工具调用前都有「人话解说」
12+
13+
此前多步工具回合常常表现为「一堆工具先跑,最后才蹦出文字」。日志里每个 `ITER_END``assistant_chars:0` 说明模型把「我现在要做 X」的解说全塞进了 DeepSeek 的隐藏 `reasoning_content` 通道,可见正文 `content` 全程为空。
14+
15+
**修复(`src/prompts/system.js`**
16+
17+
- **Tone 段**:把原来禁止前导解说的 `No preamble, no "I'll now…"` 改成「禁空话(Great question/Sure),但要求动作解说」——每次工具调用前用一句可见正文说明「要做什么 + 为什么」。
18+
- **System 段**:新增强约束,要求把面向用户的解说写进 `content` 而非 reasoning/thinking 通道;多步工具回合里每个工具调用前先写一句,而不是沉默到最后。
19+
- 流式管道(`src/api/openai-client.js`)本身已支持 `content` 先于 `tool_calls` 穿插渲染,无需改动。
20+
21+
体验上更接近 GitHub Copilot「先说一句话 → 再调工具」的节奏,用户能跟着模型一步一步看。
22+
23+
### ✨ 新特性 2:`watch` + `yield_turn` 后台唤醒调度
24+
25+
长跑任务(模型训练、大型构建、长下载、dev server)的痛点是:要么阻塞 turn 等到超时,要么手动轮询。
26+
27+
**新增工具**
28+
29+
- `watch({ condition, description? })`:注册一个声明式触发器,支持 `time_elapsed` / `job_end` / `output_match` / `output_silent` / `progress_at`,可用 `first_of` 组合(成功 OR 失败 OR 卡死 OR 兜底超时)。
30+
- `yield_turn({ reason? })`:干净地结束当前 turn,会话进入挂起状态。
31+
- 任一 watcher 触发 → 由 `wake-scheduler` 构建结构化 digest(异常 / 进度 / 输出尾巴)→ 通过 `Provider.autoResume` 自动恢复对话。
32+
33+
**安全约束**
34+
35+
- `watch` 的条件**必须**包含 `time_elapsed` 兜底(直接或在 `first_of` 中),否则被工具拒绝——避免会话永久挂起。
36+
- `yield_turn` 必须在 `watch` 之后调用;空 watcher 列表会被拒绝。
37+
- 每会话最多 8 个 active watcher,每小时最多 12 次 auto-resume(可通过 `deepseekAgent.autoResumeMaxPerHour` 配置)。
38+
- 挂起前必须有一句用户可见的总结(「我做了 X / 现在在等 Y」),否则系统会主动 nudge 一次。
39+
40+
**新增模块**
41+
42+
- `src/chat/wake-scheduler.js`(407 行):watcher 生命周期、轮询、限流、路由
43+
- `src/chat/digest.js`(144 行):本地、确定性、无网络的输出 digest 构造器,自动抽取 anomaly / progress
44+
- `src/tools/sleep.js`(139 行):`watch` / `yield_turn` 的模型侧入口
45+
46+
### ⚡ 优化:DeepSeek 前缀缓存命中率
47+
48+
DeepSeek 服务端 KV 前缀缓存按字节比对。任何在历史中段重写消息的操作都会让从该字节起的整条 prefix cache miss,明显推高 token 成本。本版本系统性地审计并修复了若干「无意中破坏前缀」的代码路径:
49+
50+
- **系统提示重排**`src/prompts/system.js`):按 stability 由低到高排序(env → paradigm → skills → memory → workspace),让最长可能的前缀在跨 turn 时保持字节一致;移除原本的 `__DYNAMIC_BOUNDARY__` 噪音 marker。
51+
- **持久化时不再 eager 压缩**`src/chat/session-store.js`):之前每次 turn save 都强制压缩到 40% context window,反复改写历史头部 → 每次「重开同一会话」都吃满 prefix cache miss。改为完全 lazy,只在 agent loop 里真正撑爆时压缩。
52+
- **去掉定期重压缩**`src/chat/agent-loop.js`):原本每 12 iter 把 budget 收紧到 80% 触发预防性压缩——节奏性地清空 prefix cache。改为只在真正超出(更宽松的 70%)才触发。
53+
- **dedup 阈值提到 95%**`src/chat/compact.js`):重复文件读取去重会改写中段消息,cost cache。改成「快要溢出才做」。
54+
- **附件排序稳定化**`src/chat/agent-loop.js`):同一组文件按不同顺序点选 → byte-identical 的 user content,命中前缀缓存。
55+
- **截断结果冻结**`src/chat/compact.js`):已被截断的工具结果加 `_cacheFrozen` 标记,避免反复重算导致对象身份变化。
56+
- **可观测性**:每轮 USAGE 事件新增 `cache_hit_rate` 字段;日志同步打出 `CACHE_HIT_RATE` 行。
57+
58+
### 🐞 修复 1:空响应自愈
59+
60+
模型偶发输出 0 token + 0 工具调用(mimic reasoning placeholder 后停止),旧代码当作「正常结束」直接退出 turn,表现为「鬼魂中断」。新增最多 2 次的 `EMPTY_REPLY_RETRY`:注入 forward nudge 让模型继续,超过上限才放弃,避免无限循环。
61+
62+
### 🐞 修复 2:reasoning_content 占位符不再诱导模型「跟着说停」
63+
64+
DeepSeek thinking 模式要求一旦出现过 `reasoning_content`,后续每条 assistant 消息都必须带非空 reasoning,否则 HTTP 400。我们之前用的占位符 `"(no thoughts surfaced for this step)"` 重复出现后变成强 in-context 模板,模型开始照着发然后停下——又一种「鬼魂中断」。
65+
66+
通过 `scripts/probe-reasoning-placeholder.js` 实测验证:thinking 模式只要求**非空**字符串。改成 forward-nudging 的 `"Continue with the next concrete step."`——即便被模型 mimic,也是「继续干活」而不是「停下来」。
67+
68+
### 🐞 修复 3:Windows cmd.exe 多行 inline 脚本陷阱提示
69+
70+
`python -c "..."` / `node -e "..."` 这类多行内联脚本在 Windows 上会被 cmd.exe 按换行切碎,常表现为「exit 0 + 无输出」。`src/tools/shell.js` 现在会检测这种 pattern,在结果里追加 actionable hint:建议写到临时文件再执行,或改用单行命令。
71+
72+
### 🐞 修复 4:Provider sanitize 防止 `_` 前缀字段泄露到 HTTP
73+
74+
`_cacheFrozen` 这类 compactor / agent-loop 用的内部标志一旦混进 OpenAI 兼容 API 的 payload,会被 HTTP 400 拒绝。`src/providers/index.js``sanitizeMessages` 现在统一剥离所有 `_` 前缀字段,并用 spread(非 `Object.assign`)规避 `__proto__` 原型污染风险。
75+
76+
### 🐞 修复 5:terminal capture 饱和不再被误判为「沉默」
77+
78+
`run_shell_bg` 的输出捕获有 64KB×20 上限。原来当 capture 触顶停止增长时,`output_silent` watcher 会误以为任务沉默而提前触发。现在 `terminal-monitor.js` 在 record 上设置 `truncated` 标记,watcher 看到饱和后保持「仍活跃」语义。
79+
80+
### 🐞 修复 6:rate limit 不再永久销毁 watcher
81+
82+
`_fire` 命中限流时旧代码会先 `_cleanupWatcher` 再检查 rate——直接把 watcher 拆了,挂起的会话再也唤不醒。改为先检查、限流命中时保留 watcher 并 arm 单个 retry timer 到限流窗口释放为止。
83+
84+
### 🧹 其他
85+
86+
- 侧边栏抽屉打开时主区域 push(不再 cover),右侧空出 280px,体验更接近原生侧栏(`media/chat.css` + `media/chat.js`)。
87+
- 新增配置项 `deepseekAgent.autoResumeMaxPerHour`(默认 12,范围 1–120)。
88+
- 默认 `compactBudgetTokens` 从 50% context window 提到 70%,配合前缀缓存调优。
89+
90+
### 🔒 安全 / 兼容性
91+
92+
- Webview CSP 不变。
93+
- 不引入新运行时依赖。
94+
- 旧会话兼容,无需迁移。
95+
- 新工具 `watch` / `yield_turn` 不暴露 destructive 能力,被 schema 验证 + 安全兜底约束。
96+
97+
### 关联
98+
99+
- PR #180(本版本合并 PR)
100+
101+
---
102+
103+
## 🇺🇸 English
104+
105+
Theme: **Per-step narration before each tool call · auto-resume scheduler · DeepSeek prefix-cache tuning**
106+
107+
> This release packages everything from PR #180 (v0.43.0) and rebuilds the VSIX as 0.43.1.
108+
109+
### ✨ Feature 1 — Plain-text narration before every tool call
110+
111+
Previously, multi-step tool turns showed up as "a bunch of tools fire silently, then text appears at the very end". Logs consistently reported `ITER_END` with `assistant_chars:0` — the model was stuffing its "I'm about to do X because Y" narration into DeepSeek's hidden `reasoning_content` channel, leaving the visible `content` channel empty.
112+
113+
**Fix (`src/prompts/system.js`)**:
114+
115+
- **Tone section**: replaced the old anti-preamble line (`No preamble, no "I'll now…"`) with "no empty filler (Great question/Sure), but DO narrate actions" — one short visible sentence before each tool call saying what and why.
116+
- **System section**: hard rule that user-facing narration must land in `content`, not in `reasoning_content`/`thinking`; in multi-step turns, narrate before every tool call rather than at the end.
117+
- The streaming pipeline (`src/api/openai-client.js`) already supports interleaving `content` before `tool_calls`, so no plumbing change was needed.
118+
119+
The result reads like GitHub Copilot's "say one sentence → call tool" cadence; users can follow the agent step by step.
120+
121+
### ✨ Feature 2 — `watch` + `yield_turn` auto-resume scheduler
122+
123+
Long-running shell work (training, large builds, downloads, dev servers) used to either block the turn until timeout or require manual polling. This release introduces declarative auto-resume:
124+
125+
**New tools**:
126+
127+
- `watch({ condition, description? })` — register a declarative trigger. Supported kinds: `time_elapsed` / `job_end` / `output_match` / `output_silent` / `progress_at`. Compose with `first_of` (success OR failure OR hang OR fallback timeout).
128+
- `yield_turn({ reason? })` — cleanly end the current turn; the session enters a suspended state.
129+
- When any watcher fires, `wake-scheduler` builds a structured digest (anomalies / progress snapshot / output tail) and `Provider.autoResume` re-enters the agent loop with it injected as a `<system-reminder channel="auto-wake">`.
130+
131+
**Safety constraints**:
132+
133+
- `watch` conditions **must** include a `time_elapsed` safety bound (directly or inside `first_of`) — otherwise the tool rejects the call. A session can never be suspended forever.
134+
- `yield_turn` requires at least one armed watcher, otherwise it is rejected.
135+
- Per-session caps: 8 active watchers; 12 auto-resumes per hour (configurable via `deepseekAgent.autoResumeMaxPerHour`).
136+
- The model is required to write a brief user-facing summary ("did X / now waiting for Y") before suspending — a nudge is injected once if it tries to suspend silently.
137+
138+
**New modules**:
139+
140+
- `src/chat/wake-scheduler.js` (407 lines) — watcher lifecycle, polling, rate-limiting, routing.
141+
- `src/chat/digest.js` (144 lines) — local, deterministic, network-free digest builder; extracts anomaly lines and progress snapshots.
142+
- `src/tools/sleep.js` (139 lines) — `watch` / `yield_turn` entry points exposed to the model.
143+
144+
### ⚡ Optimisation — DeepSeek prefix-cache hit-rate
145+
146+
DeepSeek's server-side KV prefix cache hashes the request byte-by-byte. Any rewrite of mid-history bytes invalidates the cache from that point onward — silently driving up token cost. This release systematically audits and fixes paths that were unintentionally breaking the prefix:
147+
148+
- **System prompt re-ordering** (`src/prompts/system.js`): sections re-ordered most-stable-first (env → paradigm → skills → memory → workspace). The literal `__DYNAMIC_BOUNDARY__` marker is removed (cache-irrelevant noise).
149+
- **No more eager compaction on persist** (`src/chat/session-store.js`): the old "compact to 40% of context window on every save" pass rewrote the head of history on every turn-save and every reload, so "reopen the same session" always paid a full prefix-cache miss. Compaction is now fully lazy.
150+
- **No more periodic re-compaction** (`src/chat/agent-loop.js`): the old "every 12 iterations, tighten budget to 80%" trigger was a periodic cache-buster. Compaction now fires only when the real token estimate actually exceeds the (already-generous) 70% budget.
151+
- **Dedup raised to 95%** (`src/chat/compact.js`): deduplicating repeated file reads rewrites mid-history messages and costs the cache. Defer to a near-overflow trigger.
152+
- **Stable attachment ordering** (`src/chat/agent-loop.js`): sorting text attachments by (path, line-range) means the same files added in different click orders yield byte-identical user content — and therefore the same prefix-cache hit.
153+
- **Frozen truncated tool results** (`src/chat/compact.js`): once a tool result has been truncated, it's marked `_cacheFrozen` so the truncator never runs over it again (guaranteed object identity → byte identity).
154+
- **Observability**: every USAGE event now carries a `cache_hit_rate` field; `CACHE_HIT_RATE` log lines surface the hit/miss split per turn.
155+
156+
### 🐞 Fix 1 — Empty-response self-heal
157+
158+
The model occasionally emits a degenerate turn: no text AND no tool call (it just mimics the reasoning placeholder and stops). The old code treated that as a normal end-of-turn and silently broke out — a "ghost interruption". A capped `EMPTY_REPLY_RETRY` (max 2) now injects a forward-nudge and continues; only after that gives up.
159+
160+
### 🐞 Fix 2 — `reasoning_content` placeholder no longer trains the model to stop
161+
162+
DeepSeek thinking-mode enforces that once any assistant message carries a non-empty `reasoning_content`, every subsequent assistant message must too (else HTTP 400). The previous placeholder `"(no thoughts surfaced for this step)"` repeated across many turns became a strong in-context template — the model started mimicking it and then stopping (another flavour of ghost interruption).
163+
164+
A live probe (`scripts/probe-reasoning-placeholder.js`) verified that the 400 gate only requires *any* non-empty string. The placeholder is now `"Continue with the next concrete step."` — when mimicked, the in-context pattern says "keep going" instead of "I'm done".
165+
166+
### 🐞 Fix 3 — Windows cmd.exe multi-line inline-script hint
167+
168+
On Windows, `run_shell` spawns via `shell: true` → cmd.exe, which treats a literal newline as a command separator. A multi-line `python -c "..."` / `node -e "..."` gets split apart mid-quote and typically runs as a no-op (exit 0, empty stdout) — which then confuses the model ("did my script run?"). `src/tools/shell.js` now detects this exact pattern and appends an actionable hint: write the code to a temp file first, or use a single-line command.
169+
170+
### 🐞 Fix 4 — Provider sanitize strips `_`-prefixed fields
171+
172+
Internal flags like `_cacheFrozen` used by the compactor / agent-loop must never reach the OpenAI-compatible API payload (it rejects unknown fields with HTTP 400). `sanitizeMessages` in `src/providers/index.js` now strips every `_`-prefixed field, and uses object spread (not `Object.assign`) to avoid `__proto__` prototype-pollution footguns.
173+
174+
### 🐞 Fix 5 — Saturated terminal capture is no longer mistaken for "silence"
175+
176+
`run_shell_bg`'s output capture is capped at 64KB × 20. When the cap is hit, the captured length stops growing even though the job is still chatty. The `output_silent` watcher would then fire spuriously. `terminal-monitor.js` now sets a `truncated` flag on the record; the watcher treats saturated captures as "still active" instead of silent.
177+
178+
### 🐞 Fix 6 — Rate-limited watchers no longer get permanently destroyed
179+
180+
When `_fire` hit the rate limit, the old code tore down the watcher *before* checking the limit — leaving the suspended session with no way to ever resume. The watcher is now kept armed; a single retry timer is scheduled for when a slot frees up.
181+
182+
### 🧹 Misc
183+
184+
- The session drawer now uses push-layout (main shrinks by 280px) instead of overlay/cover — feels more like a native sidebar (`media/chat.css` + `media/chat.js`).
185+
- New config `deepseekAgent.autoResumeMaxPerHour` (default 12, range 1–120).
186+
- Default `compactBudgetTokens` raised from 50% → 70% of context window, complementing the prefix-cache tuning.
187+
188+
### 🔒 Security / Compatibility
189+
190+
- Webview CSP unchanged.
191+
- No new runtime dependencies.
192+
- Existing sessions remain compatible; no migration required.
193+
- New `watch` / `yield_turn` tools expose no destructive capability and are constrained by schema validation + safety bounds.
194+
195+
### Related
196+
197+
- PR #180 (the merge PR for this release)

0 commit comments

Comments
 (0)