mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-08-01 16:43:37 +08:00
Two bugs in the v0.5.7 "Continue from here" hand-off path landed in the wild and the user caught them while exercising 0.5.8 prep: 1. **Stray empty session.** When the user previously opened a "New session in X" tab from the sidebar, then closed the tab to declutter, the freshly-created empty session was already on disk and visible in the sidebar. Coming back to the welcome screen (activeTabId = null → EmptySession route) and clicking "Continue from here" called `createSession` unconditionally, minting yet another empty session next to the now-stale one. The user ended up with `Untitled Session 27 minutes ago` lingering in the sidebar while the hand-off ran in a completely separate fresh session. Extract the picker into `desktop/src/lib/sessionReuse.ts` — `pickReusableEmptySession(sessions, workDir, excludeSessionId?)` filters by exact-workDir match, `messageCount === 0`, excludes the previous session being handed off FROM, and sorts by `modifiedAt` desc. Caller in `EmptySession.onAutoHandoff` runs this before `createSession`; on hit, openTab on the existing sessionId and let ContentRouter switch to ActiveSession naturally; on miss, create fresh as before. 7 unit tests cover the boundaries: workDir mismatch, non-zero messageCount, excludeSessionId honored, null workDir treated as "no candidates" (so we never silently merge home-dir sessions into a project hand-off), empty workDir argument, empty session list, freshness sort order. 2. **Summary too abstract; AI doesn't know specific recent state.** The previous implementation only injected the LLM-summarized `main` + `recent` paragraphs into the next session's system prompt. That summarization tends to wash out exact wording, file paths, error messages, and the user's literal last question — the user reported "AI doesn't know what we just hit a wall on." Add `recentRaw?: string` to `SessionSummary`. New helper `buildRecentRawSlice` keeps the LAST ~12 turns (env-tunable via `CLAUDE_CODE_HANDOFF_RAW_TURNS`, range 0–50, default 12), each truncated to 400 chars preserving the `USER:` / `ASSISTANT:` role prefix, total capped at ~8000 chars (env-tunable via `CLAUDE_CODE_HANDOFF_RAW_CHARS`, range 500–30000, default 8000). Older turns drop first when the total cap is busted by dense recent turns. `formatHandoffSystemPrompt` renders the raw slice inside a fenced code block AFTER the abstracted recent summary, so the next session sees both the digest and the literal text. Section is omitted entirely when `recentRaw` is absent — v0.5.7 caches still load and produce the exact same prompt bytes (back-compat). 8 unit tests cover the formatter's back-compat path, the raw block placement, fence rendering, tail-N selection, all-fits case, per-turn truncation with role prefix preservation, total cap busting (older drops first), and the `RAW_TURNS=0` opt-out. Both fixes fold into the unreleased v0.5.8 — release-notes/v0.5.8.md gets two new fix bullets and the 范围 / 验证 sections call out the new files and 15 added tests. desktop/package.json bump to 0.5.8 stays as-is (preset earlier in the same worktree before this commit). Tested: - bun run lint (desktop) clean - bunx vitest run sessionReuse.test.ts → 7/7 pass - bun test sessionSummaryService.test.ts → 8/8 pass - bunx vitest run EmptySession.test.tsx → 22/22 pass (regression check after the helper extraction — the existing tests don't go through the hand-off branch but exercise the rest of the file) Confidence: high Scope-risk: narrow (no shape changes to any persistence — recentRaw is an additive field, old caches deserialize cleanly because `tryParseSummaryResponse` already only required main+recent) Co-authored-by: 你的姓名 <you@example.com>
112 lines
9.4 KiB
Markdown
112 lines
9.4 KiB
Markdown
# v0.5.8
|
||
|
||
## 新功能
|
||
|
||
- **逆向工程插件 v0.4.0**(`plugins/reverse-engineering`)。仓库自带的 marketplace 现在收录一个多平台
|
||
逆向工具箱,一键安装即可获得 7 个 MCP 服务器、1 个编排 agent、11 个 skill、2 个 slash 命令:
|
||
- **静态分析**:Ghidra(pyghidra-mcp)/ radare2 / JADX / apktool。覆盖 PE / ELF / Mach-O,以及
|
||
无 header 的固件 blob(路由器固件、Cortex-M flash dump、ECU dump 等)。多架构:x86/x64、ARM
|
||
(含 Cortex-M Thumb-only)、AArch64、MIPS、PowerPC(含 VLE)、Motorola 68k / ColdFire、SuperH、
|
||
RISC-V,以及 AVR / MSP430 / 6502 / Z80 等小架构
|
||
- **动态分析**:Frida(Java/ObjC hook + Stalker trace)/ GDB(单步、硬件断点、watchpoint、跨架构
|
||
via `gdb-multiarch + qemu`)/ LLDB(Apple 平台单步 + ObjC method hook)。`dynamic-debug-overview`
|
||
skill 让 agent 自动选车道
|
||
- **编排**:`reverse-engineer` agent 走 triage → static → 可选 dynamic → report 流程,产物落在
|
||
`${ARTIFACT_DIR}/<sample-id>/`(默认 `artifacts/re-runs`)
|
||
- **slash 命令**:`/reverse-engineering:triage <path>`、`/reverse-engineering:report <sample-id>`
|
||
- **配置**:`GHIDRA_INSTALL_DIR`(可选,Ghidra 安装目录)、`ARTIFACT_DIR`(产物输出位置)
|
||
- **规则**:对样本只读,不向外推任何样本(无 VT / no malware-bazaar),不破解商业授权,Frida 只跑
|
||
用户授权过的目标,纯静态结论可信度上限封在 medium
|
||
|
||
桌面端「设置 → 插件」添加本仓库 `plugins/` 目录作为 marketplace 即可启用。详见
|
||
`plugins/reverse-engineering/README.md`。
|
||
- **零成本会话接力**。新建会话欢迎页"最近活动"卡片新增"从这里继续"按钮。点击后服务端用 LLM
|
||
生成上一个会话的两层摘要(主线 + 最新进展)落盘缓存,通过 `--append-system-prompt` 注入到新
|
||
会话的 system prompt,再自动发一条短的 trigger message 让 AI 接续。前端展示生成进度阶段
|
||
("读缓存 → 生成摘要 → 启动会话"),完成后会话头部出现 `↗ 已接续 (N tokens)` 小芯片。摘要生成
|
||
失败时静默降级到原有的 zero-token textarea 预填路径。摘要 token 上限受 `CLAUDE_CODE_HANDOFF_MAX_TOKENS`
|
||
(默认 1500)和 `CLAUDE_CODE_HANDOFF_INPUT_CHARS`(默认 80000)约束,WS handler 缓存命中后不重生成。
|
||
- **欢迎任务卡片项目感知**。原本写死的 4 个起始 prompt 现在按项目状态动态填充:
|
||
- "PR 预合并审查"会替换成实际的 `git.defaultBranch`,develop 分支上不会再让 AI 对着 main 做审查
|
||
- "排查失败的测试"和"补单元测试"会从最新的 dirty files 列表里挑一个真实文件路径(测试文件正则 + 源码文件正则,跳过锁文件 / 构建产物 / 二进制),自动填进 prompt
|
||
- 每张卡片的 `title=` 属性都是完整解析后的 prompt,鼠标悬停就能预览要发送什么
|
||
- 第 4 张卡 `explainCode` 重命名为 `understandProject`,prompt 改成读 CLAUDE.md / AGENTS.md / README + 列技术栈和顶层目录的项目全景介绍
|
||
- 服务端 `recent-activity` 接口新增 `dirtyFiles[]` 字段(`git status --porcelain=v1 -z` 解析,按 mtime 倒序,最多 20 个),走原有 TTL 缓存
|
||
- **伪 tool_use 检测与 provider 兼容追踪**。某些第三方 Anthropic 兼容网关(mimo / lgfzer / 部分代理)
|
||
不转发原生 tool_use block,模型只能在 `content_delta` 里输出 XML 风格的 `<tool_use ...>{...}</tool_use>`
|
||
当文本流过来,渲染后变成 `<tool_useid="..."` 那样的乱码,而且模型自己以为调用成功还会写"Done."。
|
||
这次三层防御:
|
||
- 桌面端 `desktop/src/lib/fakeToolUseDetection.ts` 用正则提取这种伪块(闭合 / 流式半开 / 多块连续 /
|
||
属性顺序互换 / 引号变体 / 自闭合 / 围栏代码块内的合法示例不误伤),`AssistantMessage` 在交给
|
||
`MarkdownRenderer` 前清掉,`copyText` 也走净化版本
|
||
- 新组件 `FakeToolUseNotice` 在气泡上方渲染灰色提示卡:"模型尝试用 XML 文本块调用 `Bash`,但当前 provider
|
||
似乎不转发原生 tool_use,实际并未执行",让用户不会误信后续"Done."
|
||
- 新 store `providerCompatStore` 按 provider id 计数(localStorage 持久化),累计 3 次伪 tool_use 弹一次
|
||
warning toast 建议切换 provider;Settings → Provider 列表上对应 provider 显示"工具调用异常"红色徽标,
|
||
编辑该 provider 配置后会自动清零(给新配置一次机会),阈值之后只增不弹,不打扰
|
||
|
||
## 修复
|
||
|
||
- **会话接力不再留下空白会话**。当用户从 sidebar 顶部点过"新建会话"创建了一个空白 sessionA(然后
|
||
关掉 tab 留在 sidebar),回到欢迎屏点"从这里继续"时,之前的实现会再 createSession 一个新会话
|
||
sessionB,sessionA 变成孤儿空白会话。现在 EmptySession.onAutoHandoff 在 createSession 前先用
|
||
`pickReusableEmptySession` 找同 workDir 下消息数=0 且不是 previousSessionId 的最新空白会话,有
|
||
就 openTab 复用它,没有才创建新的。新增 `desktop/src/lib/sessionReuse.ts` + 7 个单测覆盖
|
||
workDir 过滤、消息数过滤、excludeSessionId、null workDir 防御等边界。
|
||
- **会话接力摘要现在带最后 12 轮原文(可调)**。原本的两层摘要 main + recent 是 LLM 抽象出来的,
|
||
容易丢失"用户上一句具体问什么"、"AI 上一次回复到哪了"的真实措辞。现在 `sessionSummaryService`
|
||
生成摘要时额外保留原会话最后 12 轮(默认)的 USER:/ASSISTANT: 原文,每条截到 400 字、整体 cap
|
||
8000 字,新字段 `recentRaw` 落进缓存,`formatHandoffSystemPrompt` 在 main+recent 之后追加这段
|
||
到接续 system prompt 里。可通过环境变量 `CLAUDE_CODE_HANDOFF_RAW_TURNS`(0–50,默认 12)和
|
||
`CLAUDE_CODE_HANDOFF_RAW_CHARS`(500–30000,默认 8000)调节。0.5.7 旧缓存没有 `recentRaw` 字段,
|
||
formatter 自动跳过原文段,完全向后兼容。
|
||
- **服务端模型上下文不再示范错误格式**。`specialistRouter.ts` 早就警告过 `key="value"` 属性式片段
|
||
会让网关模型把 tool_use 退化成 XML 文本,但还有 3 处反复给模型喂 `subagent_type="verification"`:
|
||
`verification_gate_reminder`(messages.ts)、TodoWriteTool 的 verification 提醒、TaskUpdateTool 的
|
||
verification 提醒。改成散文形式("set the subagent_type parameter to verification"),从源头降低
|
||
伪 tool_use 概率。新增 `noKeyValueNudges.test.ts` 锁住这 3 处,以后改回属性形式立即挂 CI。
|
||
|
||
## 范围
|
||
|
||
- 会话接力修复:`desktop/src/lib/sessionReuse.ts`(空白会话复用 picker)、
|
||
`desktop/src/lib/sessionReuse.test.ts`(7 单测)、`desktop/src/pages/EmptySession.tsx`
|
||
(onAutoHandoff 接入 picker)、`src/server/services/sessionSummaryService.ts`(`recentRaw`
|
||
字段 + buildRecentRawSlice + formatHandoffSystemPrompt 注入)、
|
||
`src/server/services/sessionSummaryService.test.ts`(8 单测)。
|
||
- 逆向插件:`plugins/.claude-plugin/marketplace.json`、
|
||
`plugins/reverse-engineering/`(plugin.json / mcp/servers.json /
|
||
agents/reverse-engineer.md / 11 个 SKILL.md / 2 个 slash 命令 / scripts/{dev-link,smoke,validate}.ts /
|
||
README.md)。
|
||
- 会话接力:`src/server/services/sessionSummaryService.ts`(LLM 双层摘要 + 磁盘缓存)、
|
||
`src/server/api/sessions.ts`、`src/server/ws/handler.ts`(`set_handoff_summary` 路径)、
|
||
`desktop/src/components/welcome/RecentActivityCard.tsx`、`desktop/src/api/projects.ts`、
|
||
`desktop/src/stores/sessionRuntimeStore.ts`(handoffInfo)。
|
||
- 欢迎卡片:`desktop/src/components/welcome/WelcomeTaskCards.tsx`(resolver 模式 + 内部 fetch)、
|
||
`desktop/src/api/projects.ts`、`desktop/src/pages/{EmptySession,ActiveSession}.tsx`、
|
||
`src/server/services/projectActivityService.ts`(dirtyFiles 派生)、5 个语言的 i18n 模板参数化。
|
||
- 伪 tool_use 检测:`desktop/src/lib/fakeToolUseDetection.ts`、
|
||
`desktop/src/stores/providerCompatStore.ts`、
|
||
`desktop/src/components/chat/{FakeToolUseNotice.tsx,AssistantMessage.tsx}`、
|
||
`desktop/src/pages/Settings.tsx`(badge + clear-on-edit)、
|
||
`src/utils/messages.ts`、`src/tools/{TodoWriteTool,TaskUpdateTool}/`、
|
||
`src/utils/noKeyValueNudges.test.ts`。
|
||
- i18n:5 个语言新增会话接力、欢迎卡片参数化 prompt、providerCompat 共 10+ 条 key。
|
||
|
||
## 验证
|
||
|
||
- 单元测试:`fakeToolUseDetection.test.ts` 14 / `providerCompatStore.test.ts` 8 /
|
||
`AssistantMessage.faketooluse.test.tsx` 6 / `EmptySession.test.tsx` 22 /
|
||
`sessionRuntimeStore.test.ts`(含 handoffInfo)/ `noKeyValueNudges.test.ts` 3 /
|
||
`sessionReuse.test.ts` 7 / `sessionSummaryService.test.ts` 8 全部通过 ✅
|
||
- `cd desktop && bun run lint`:tsc --noEmit 通过 ✅
|
||
- 服务端 `bun test src/utils/{verificationGate,noKeyValueNudges} src/tools/AgentTool/specialistRouter`:
|
||
31 个测试通过 ✅
|
||
- 浏览器 MCP 端到端:在 `feat/welcome-cards-polish` worktree 下,4 张卡片正确替换为
|
||
`feat/welcome-cards-polish` 分支名,脏文件 `desktop/src/pages/EmptySession.test.tsx` /
|
||
`desktop/src/pages/ActiveSession.tsx` 自动填进 testPath / filePath 槽位,点击后撰写框正确预填 ✅
|
||
- 会话接力 MCP 端到端:`feat/session-handoff-summary` 分支生成两层摘要,新会话头部出现
|
||
`↗ 已接续 (N tokens)` 芯片,AI 首条响应直接基于摘要继续,不再要求用户重新解释上下文 ✅
|
||
- 逆向插件:本次发布未运行端到端 smoke。校验入口在 `plugins/reverse-engineering/scripts/smoke.ts`
|
||
(注册 marketplace → enable → `/api/plugins/update` + `/reload` → 断言版本号、组件计数与磁盘源
|
||
一致),需服务端 + vite 起来后手动跑 ⚠️ pending
|