十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qwen Code Session Recap 设计解析:AI 编程助手的“离开后回来“会话摘要机制

Qwen Code Session Recap 设计解析:AI 编程助手的“离开后回来“会话摘要机制 Qwen Code Session Recap 设计解析AI 编程助手的离开后回来会话摘要机制【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-codeSession Recap会话回顾是 Qwen Code 中一项最佳努力best-effort的辅助能力当用户离开终端一段时间后回来或在任何时刻通过/recap命令主动请求时CLI 会基于当前会话历史生成一句 1–2 句话的上次进行到哪了摘要高层面任务 → 下一步动作并以视觉上区别于真实助手回复的样式插入会话流。本文以 docs/design/session-recap/session-recap-design.md 设计文档为骨架结合 packages/core/src/services/sessionRecap.ts、useAwaySummary.ts、recapCommand.ts 等源码实现完整讲解该功能的三种触发路径、提示词与结构化输出设计、历史过滤策略、并发边界、配置项与可观测性并给出可直接落地的配置与调用方法。读完本文你将掌握如何开启并调优自动 recap、何时使用/recap手动触发、远程客户端如何通过 Daemon HTTP 接口获取会话 recap以及这套机制在绝不打断主流程、绝不向用户抛错约束下是如何在源码层面被保证的。设计动机与核心原则用户几天后通过/resume恢复一个旧会话时仅靠重新加载历史消息并不能解决一个真实的 UX 痛点用户需要翻过多页历史才能想起来我当时在做什么、接下来该做什么。Session Recap 的目标是在用户回到会话时主动呈现一句简短的摘要其设计遵循三条原则内容结构固定高层任务正在做什么→ 下一步接下来做什么视觉可区分摘要必须与真实助手回复在视觉上明显不同避免被误认为新的模型输出最佳努力任何失败都必须静默处理绝不能破坏主流程。从实现上看这一绝不打扰用户的承诺贯穿始终核心服务函数在try/catch中兜底返回null见 sessionRecap.tsUI 层则保证失败时不渲染任何内容。三种触发路径与统一入口Session Recap 有三条触发路径全部汇入同一个底层函数generateSessionRecap()位于 packages/core/src/services/sessionRecap.ts从而保证行为完全一致触发方式条件实现手动命令用户运行/recaprecapCommand.ts 调用同一底层服务自动触发终端失焦DECSET 1004 focus 协议≥ 5 分钟 焦点回归 流状态为IdleuseAwaySummary.ts5 分钟失焦计时器 useFocus事件监听Daemon HTTP远程客户端调用POST /session/:id/recapserver.ts路由 →bridge.generateSessionRecapext-method 往返→ acpAgent.ts 调用generateSessionRecap(session.getConfig(), signal)自动触发受general.showSessionRecap设置约束默认关闭——显式选择加入避免在用户账单上静默增加环境 LLM 调用而手动命令与 Daemon HTTP 路由忽略该设置因为调用者是显式发起请求。Daemon 访问路径Daemon 路由采用非严格门控non-strict-gated与/session/:id/prompt的姿态一致——recap 消耗 token 但不改变任何状态。能力标签session_recap会在/capabilities.features上通告该路由见 capabilities.ts。SDK 侧提供了两个辅助方法DaemonClient.recapSession(sessionId, opts)见 DaemonClient.ts直接POST /session/:id/recapDaemonSessionClient.recap(opts)见 DaemonSessionClient.ts。完整的线上协议与错误封装见 docs/developers/qwen-serve-protocol.md 中POST /session/:id/recap一节。需要特别注意的是 SDK 文档中明确记录的契约细节DaemonClient.ts非严格变更门控姿势与/session/:id/prompt相同花费 token 但不改动状态绕过默认 30s 超时recapSession直接调用_fetch不套用每调用fetchTimeoutMs包装器因为底层 side-query 在慢模型下可能超过默认 30 秒向前兼容旧版本 daemon不支持 recap会返回 404——调用前应先预检caps.features.session_recaprecap可能为null历史过短或模型瞬时失败时返回 200 但recap: null见 types.ts 中DaemonSessionRecapResult的契约。v1 的取消语义v1 中不提供取消cancellation absent路由不监听 HTTP 客户端断开没有把AbortSignal穿入bridge.generateSessionRecapACP 子进程处理器向核心助手传入的是一个永不被中止的new AbortController().signal见 acpAgent.ts目前尚无跨进程中止管线。唯一的天花板是 bridge 侧 60 秒的SESSION_RECAP_TIMEOUT_MS兜底超时以及针对 ACP 通道死亡的传输关闭竞态。仅单独接线 HTTP 侧的 AbortController 只是表面功夫——子进程侧的 LLM 调用仍会跑完因此在缺少跨进程中止组件的情况下无法实现端到端取消。这对 v1 是可接受的recap 是短查询单次尝试 side-querymaxOutputTokens: 300典型耗时约 1–5 秒。未来可以引入基于 request-id 的取消 ext-method在带宽成本合理时打通完整的端到端取消。架构与文件职责设计文档给出了完整的调用链架构图核心流程为AppContainer.tsx isFocused useFocus() isIdle streamingState Idle ├─→ useAwaySummary({enabled, config, isFocused, isIdle, addItem}) │ └─→ 5 min blur timer idle/dedupe gates │ ↓ └─→ recapCommand (slash) ─→ generateSessionRecap(config, signal) ↓ packages/core/services/sessionRecap.ts ↓ GeminiClient.generateContent (fastModel tools:[]) addItem({type: away_recap, text}) ─→ HistoryItemDisplay └─ AwayRecapMessage rendered inline like any other history item (※ bold recap: italic content, all dim); scrolls naturally with the conversation涉及的文件与职责对应关系如下文件职责packages/core/src/services/sessionRecap.ts一次性 LLM 调用 历史过滤 标签提取packages/cli/src/ui/hooks/useAwaySummary.ts自动触发 React hookpackages/cli/src/ui/commands/recapCommand.ts/recap手动入口packages/cli/src/ui/components/messages/StatusMessages.tsxAwayRecapMessage渲染器※ 加粗recap: 斜体内容全部弱化色packages/cli/src/ui/types.tsHistoryItemAwayRecap类型packages/cli/src/ui/components/HistoryItemDisplay.tsx将away_recap历史项分派给渲染器packages/cli/src/config/settingsSchema.tsgeneral.showSessionRecapgeneral.sessionRecapAwayThresholdMinutes设置值得注意的 UI 细节源码注释明确说明AwayRecapMessage复刻了 Claude Code 的 away-summary 渲染方式——固定 2 列宽的※前缀加粗的recap:标签 斜体内容全部使用次要色dim。它作为普通历史项渲染随对话自然滚动而不是固定在输入框上方见 StatusMessages.tsx。HistoryItemAwayRecap的注释同样强调作为常规历史项内联渲染与 Claude Code 的away_summary消息一致随会话滚动、不粘顶types.ts。Prompt 设计让模型只当一个 recap 生成器System PromptgenerationConfig.systemInstruction会替换主 Agent 的系统提示词使模型在这一次调用中只充当 recap 生成器而不是编码助手。需要注意GeminiClient.generateContent()内部会经过getCustomSystemPrompt()将用户的记忆QWEN.md / 托管 auto-memory作为后缀追加。因此最终 system prompt 是recap prompt 用户记忆——这对 recap 是有用的项目上下文而非泄漏。源码中RECAP_SYSTEM_PROMPTsessionRecap.ts与设计文档的要点一一对应40 词以内、1–2 句平实句子无 markdown / 列表 / 标题中文场景约按 80 字符预算第一句讲高层任务然后给出具体的下一步动作明确禁止罗列已完成事项、复述工具调用、状态汇报匹配对话的主导语言英文或中文输出包裹在recap.../recap标签内标签外不得有任何内容。源码中的示例recapDebugging the auth retry race condition. Next: add deterministic timing to the integration test./recap用户侧 prompt 同样强制约束Generate the recap now. Wrap it in recap.../recap. Nothing outside the tags.sessionRecap.ts。结构化输出与提取模型被要求把答案包裹在recap.../recap中。原因部分模型GLM 家族、推理模型会在最终答案前写一段思考文本直接返回原始文本会把那段推理泄漏到 UI 中。extractRecap()提供三级回退sessionRecap.ts双标签齐全取recap.../recap之间的内容首选路径正则/recap([\s\S]*?)\/recap/i仅有开标签例如maxOutputTokens截断了闭标签取开标签之后的所有内容完全没有标签返回空字符串 → 服务返回null→ UI 不渲染任何内容。第三级是跳过而非展示错误内容策略——展示模型的推理前导文本比不展示 recap 更糟糕。调用参数参数值原因modelgetFastModel() ?? getModel()recap 不需要前沿模型tools[]一次性查询不涉及工具调用maxOutputTokens300为 1–2 句短句 标签留足余量temperature0.3基本确定性输出保留少量自然变化systemInstruction上述仅 recap 的提示词替换主 Agent 的角色定义从源码看这些参数经由runSideQuery生效sessionRecap.ts其中purpose: session-recap、maxAttempts: 1——注释明确说明recap 是best-effort cosmetic尽力而为的外观功能不值得消耗默认的 7 次重试。历史过滤只把对话喂给模型geminiClient.getChat().getHistory()返回的Content[]包含多种内容user/model文本消息model的functionCall部分user的functionResponse部分可能包含完整文件内容model的 thought 部分part.thought/part.thoughtSignature即模型的隐藏推理filterToDialog()sessionRecap.ts只保留有非空文本且不是 thought的user/model部分原因有二工具调用 / 响应单个functionResponse可达 10K token30 条这样的消息会让 recap LLM 淹没在无关细节中——既浪费 token又会让 recap 偏向调用了 X 工具去读 Y 文件这类实现噪音thought 部分承载模型的内部推理。纳入其中可能把隐藏的思维链当作对话并在 recap 文本中将其表面化。此外该函数还调用了getStartupContextLength(history)切掉启动上下文并对每条文本执行stripSystemReminderBlocks()剥除系统提醒块如STARTUP_SKILL_LIST、ADDED_MCP_TOOLS、PLAN_MODE_REMINDER、IDE_CONTEXT等。这一点有单元测试直接验证sessionRecap.test.ts 断言最终的序列化输入中不包含任何系统提醒块内容同时保留真实用户消息。在丢弃空消息之后takeRecentDialog()sessionRecap.ts切片到最近 30 条消息RECENT_MESSAGE_WINDOW 30并且拒绝让切片起始于悬空的 model/tool 响应——即向后推进到第一条user角色消息为止保证回合结构完整。并发与边界情况自动触发 hook 状态机useAwaySummary维护三个 refuseAwaySummary.tsRef含义blurredAtRef失焦开始时间在焦点回归前不清除recapPendingRef是否有一次 LLM 调用在途inFlightRef当前在途的AbortControlleruseEffect依赖数组为[enabled, config, isFocused, isIdle, addItem, thresholdMs]各事件的处理逻辑事件动作!enabled \|\| !config中止在途调用 清空inFlightRef 清空blurredAtRef!isFocused且blurredAtRef null设置blurredAtRef Date.now()isFocused且blurredAtRef null直接返回无失焦周期可处理——首次渲染或短暂失焦重置之后isFocused且失焦时长 5 分钟清空blurredAtRef等待下一个失焦周期isFocused且失焦 ≥ 5 分钟且recapPendingRef返回去重isFocused且失焦 ≥ 5 分钟且!isIdle保留blurredAtRef等待本轮结束isIdle在依赖中流式完成后 effect 会重新触发isFocused且失焦 ≥ 5 分钟且shouldFireRecap返回 false清空blurredAtRef并返回——会话自上次 recap 以来没有足够的新进展要求 ≥ 2 个用户回合复刻 Claude CodeisFocused且所有条件满足清空blurredAtRef、置recapPendingRef true、创建AbortController、发起 LLM 请求.then回调会重新检查isIdleRef.current如果用户在 LLM 运行期间已经开始新一轮对话迟到的 recap 会被丢弃避免在回合中途插入。.finally清空recapPendingRef并且仅当inFlightRef.current controller时才清空inFlightRef避免覆盖更新的 controller。另有一个独立的useEffect在卸载时中止在途 controller。值得展开的细节history通过historyRef在触发时读取而不是加入 effect 依赖——这样历史变化不会导致每次消息都重新评估useAwaySummary.ts。去重门控Dedup gatesshouldFireRecap()useAwaySummary.ts实现与 Claude CodeSc1/Rc1/Ic1对齐的门控MIN_USER_MESSAGES_TO_FIRE 3至少要有 3 条用户回合总数sentToModel ! false才算steer 消息不计入MIN_USER_MESSAGES_SINCE_LAST_RECAP 2若历史中已有 recap则自上次 recap 之后至少需要 2 条新用户回合才能再次触发。后者防止用户两次短暂 alt-tab 且中间没有做任何新工作时产生背靠背的重复 recap。这两个门控都有测试覆盖useAwaySummary.test.ts例如 2 条真实用户消息 1 条 steer 消息时总计 3 条但只有 2 条真实回合不会触发调用历史中已有 recap 且其后只有 1 条新用户回合时同样不会触发。自动 recap 的持久化一个容易被忽略但很关键的行为自动触发的 recap 会通过config.getChatRecordingService()?.recordSlashCommand({ phase: result, rawCommand: /recap, outputHistoryItems: [...] })记录useAwaySummary.ts镜像手动/recap由斜杠命令处理器执行的记录方式从而保证自动 recap 在/resume后也能存活。只记录result阶段——若记录invocation阶段会在恢复时重放一行伪造的 /recap用户行。该行为同样有测试覆盖useAwaySummary.test.ts。/recap门控CommandContext.ui.isIdleRef暴露当前流状态镜像已有的btwAbortControllerRef模式。在交互模式下recapCommand在!isIdleRef.current或pendingItem ! null时拒绝执行recapCommand.ts返回错误消息另一个操作正在进行中。仅判断pendingItem是不够的因为正常的模型回复运行在streamingState Responding且pendingItem null的状态下。其他行为细节/recap在无配置时返回错误消息非交互模式下recap 结果作为messageType: info的消息返回而不是插入历史项若 recap 为null交互模式返回信息提示对话上下文还不足以生成 recaprecapCommand.ts。配置与模型选择用户可配置项设置默认值说明general.showSessionRecapfalse仅影响自动触发。手动/recap忽略此设置。general.sessionRecapAwayThresholdMinutes5失焦多少分钟后在焦点回归时触发自动 recap。与 Claude Code 默认值一致。fastModel未设置推荐设置如qwen3-coder-flash以获得又快又便宜的 recap。这些设置在 settingsSchema.ts 中注册为showInDialog: true的常规项requiresRestart: false可在设置对话框直接修改。sessionRecapAwayThresholdMinutes还声明了minimum: 1而useAwaySummary侧对非正值会回退到 5 分钟默认值DEFAULT_AWAY_THRESHOLD_MINUTES 5见 useAwaySummary.ts。showSessionRecap默认关闭的理由在源码注释中写得很清楚settingsSchema.ts环境性的后台 LLM 调用不应在用户不知情的情况下被默默开启尤其当fastModel未设置时调用会落在主编码模型上。手动/recap不受此限制。模型回退config.getFastModel() ?? config.getModel()用户设置了fastModel且对当前认证类型有效 → 使用fastModel否则 → 回退到主会话模型可用只是更贵更慢。可观测性createDebugLogger(SESSION_RECAP)输出调试日志sessionRecap.ts包括recap 路径捕获的异常debugLogger.warn如Recap generation failed: ...各类跳过原因的debugLogger.debug信息无 LLM 客户端、历史过短少于 2 条消息、过滤后无对话消息、模型返回空文本、标签提取失败、被信号中止等。所有失败对用户完全透明——recap 是辅助功能绝不向 UI 抛错。开发者可在调试日志文件中按[SESSION_RECAP]标签 grep日志默认写入~/.qwen/debug/sessionId.txtlatest.txt符号链接指向当前会话通过QWEN_DEBUG_LOG_FILE0禁用。明确排除的范围Out of Scope项原因/recap的进度 UIspinner / pendingItem3–5 秒等待可以接受增加复杂度不值得。自动化测试服务较小约 150 行先手动端到端验证单元测试可单独 PR 落地注仓库中已存在 sessionRecap.test.ts 与 useAwaySummary.test.ts覆盖系统提醒剥离与去重门控等关键路径。本地化提示词system prompt 是给模型的英文是最可靠的基底。输出语言由模型根据对话自行选择。QWEN_CODE_ENABLE_AWAY_SUMMARY环境变量Claude Code 用它来在遥测关闭时保持功能开启Qwen Code 当前的遥测模型不需要这个开关。/resume完成后的自动 recap是自然的后续演进但需要在useResumeCommand中寻找挂载点超出本 PR 范围。实战速查在交互终端中# 手动生成一次 recap不受 showSessionRecap 限制 /recap # 开启自动 recap写入 settings.json 或通过设置对话框 # general.showSessionRecap: true # general.sessionRecapAwayThresholdMinutes: 5 # 可调如 10通过 Daemon HTTP / TypeScript SDKimport { DaemonClient } from qwen-code-sdk; const client new DaemonClient({ baseUrl: http://127.0.0.1:PORT }); // 预检能力 const caps await client.capabilities(); if (caps.features.session_recap) { const { recap } await client.recapSession(sessionId); // recap 可能是 null历史过短或模型瞬时失败属正常契约 }排查问题# 在调试日志中检索 recap 相关记录 grep SESSION_RECAP ~/.qwen/debug/latest.txt小结Session Recap 是 Qwen Code 中一个小而完整的功能切片三条触发路径收敛到单一核心服务函数提示词与标签提取保证了输出的形状可控历史过滤在 token 成本与信息相关性之间取得平衡hook 状态机与去重门控覆盖了失焦、跨回合、重复触发等全部边界而最佳努力原则通过贯穿 UI 层、服务层与 daemon 层的空值返回与静默失败得到严格贯彻。理解这套设计不仅有助于熟练使用/recap与自动摘要能力也能为在 Qwen Code 中接入其他旁路查询型 AI 能力side-query提供一份可参考的实现范式。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表