ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

qwen-code 实现 @session: 会话引用:确定性瘦身注入与 Tab 式补全 UI 的完整设计

2026/9/14 3:07:16 拓冰建站 浏览量
qwen-code 实现 @session: 会话引用:确定性瘦身注入与 Tab 式补全 UI 的完整设计 qwen-code 实现 session: 会话引用确定性瘦身注入与 Tab 式补全 UI 的完整设计【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本文讲解 qwen-code 中session:会话引用特性的完整实现方案用户如何在提示词中通过引用本项目中任一历史会话将一份确定性瘦身不经过 LLM的历史副本作为只读上下文注入当前对话同时把补全下拉框重构为带分类标签页Files / Sessions / MCP / Extensions的 Tab 切换布局。文章以官方实现计划 2026-07-17-at-session-reference.md 为骨架结合仓库中已落地的源码逐项展开读者可掌握从 ref 解析、瘦身服务、注入路由到 TUI 标签页交互的完整调用链与关键工程取舍。目标与总体架构该特性由两个目标组成会话引用session:允许用户引用一个先前的 chat session把其历史的瘦身副本作为只读上下文注入当前会话。瘦身是确定性的——保留 user assistant 的可见文本工具调用折叠为单行摘要[tool: name — status]绝不包含工具结果正文并按固定 token 预算做尾部保留裁剪。Tab 式补全 UI给Suggestion增加category字段在useAtCompletion中新增会话建议生产者在SuggestionsDisplay中渲染标签页栏并由←/→键绑定驱动切换。整体架构上后端是一个纯函数、可单元测试的核心服务SessionReferenceService位于packages/core加一个 ref 解析器session-mention-ref位于packages/cli的 UI hooks 层它通过现有SessionService加载会话瘦身记录后按预算截断。前端在atCommandProcessor中新增session:路由分支把瘦身后文本块作为一个 scoped-mention part 注入。从源码结构看实际落地与计划高度一致且实现比计划稿更精细例如 session-reference-service.ts 将预算裁剪从逐行重估的 O(N²) 改成了单趟累积的 O(N) 算法下文详解。全局约束Global Constraints计划文档锁定了以下硬性约束这也是评审实现时最重要的验收清单约束说明作用域仅限当前项目依赖SessionService.loadSession/listSessions二者已强制sessionBelongsToCurrentProject绝不扫描其他项目的 chats 目录瘦身确定性、无模型调用禁止 importrunSideQuery或ChatCompressionService瘦身是纯机械转换瘦身内容边界只保留 user assistant 可见文本和每个工具调用的单行摘要[tool: name — status]永不包含工具结果正文注入体积上限固定 token 预算 尾部保留tail-retention——先丢弃最旧回合前缀[earlier turns omitted]并置truncated: true失败降级未解析 / 未找到 / 跨项目引用一律回退为字面文本并给出显式提示绝不静默丢弃也绝不抛异常中断回合遵循既有模式ref 解析器与生产者镜像 extension-mention-ref.ts标签页 UI 镜像StatsDialog.tsx的 tab 三件套提交风格Conventional Commitsfeat:、test:、refactor:Task 1session-mention-ref— 解析/构造/校验session:引用文件创建 session-mention-ref.ts测试 session-mention-ref.test.ts。这是一个纯字符串模块消费方为补全生产者与atCommandProcessor产出四个成员const SESSION_MENTION_PREFIX session:interface SessionRef { id?: string; title?: string }parseSessionRef(pathName: string): SessionRef | null—— 不以session:开头返回null余量为合法 UUID v4 形状时返回{ id }否则返回{ title }余量为空裸前缀返回nullbuildSessionRef(idOrTitle: string): string—— 返回session:idOrTitleisSessionId(value: string): boolean—— UUID 形状校验一个容易踩坑的细节buildSessionRef不带前导因为插入补全缓冲区时已经存在于缓冲中与extension-mention-ref.ts中buildExtensionRef的约定一致即buildSessionRef(abc) → session:abc。仓库中的实际实现与计划一致并且多处理了一个跨平台细节在 Windows 上unescapePath会保留反斜杠路径分隔符所以会话引用在win32平台改用共享的 shell 特殊字符反转义器处理余量避免标题中的\序列污染匹配const rawRemainder pathName.slice(SESSION_MENTION_PREFIX.length).trim(); const remainder process.platform win32 ? unescapeShellSpecials(rawRemainder) : rawRemainder; if (remainder.length 0) return null; return isSessionId(remainder) ? { id: remainder } : { title: remainder };该模块的 6 个单测非 session token 返回 null、UUID 解析为 id、非 UUID 解析为 title、空余量返回 null、构造不带前导、UUID 识别由npx vitest run packages/cli/src/ui/hooks/session-mention-ref.test.ts验证。Task 2SessionReferenceService— 加载 瘦身 预算裁剪文件创建 session-reference-service.ts、session-reference-service.test.ts并在 packages/core/src/index.ts 的 barrel 中追加export * from ./services/session-reference-service.js;。核心 APIexport const SESSION_REF_TOKEN_BUDGET 8000; export interface SlimmedSessionReference { text: string; meta: { sessionId: string; title: string; messageCount: number; approxTokens: number; }; truncated: boolean; } class SessionReferenceService { constructor(cwd: string); resolve(sessionId: string, opts?: { budgetTokens?: number; title?: string }): PromiseSlimmedSessionReference | { notFound: true }; }瘦身规则与实现要点计划文档给实现者定了几条关键设计注记仓库中的落地实现逐一印证不复用filterToDialog它位于sessionTitle.ts且为 private还会丢弃工具调用——而本特性需要把工具调用总结成单行。所以直接遍历messagesChatRecord[]。记录到行的映射recordsToLinestype user→ 收集文本并以User:前缀输出type assistant→ 收集文本并以Assistant:前缀输出跳过thought部件工具调用记录 → 输出单行[tool: name — status]忽略system记录。这里有一个计划稿中的实现注记非常关键ToolCallResponseInfo在生产代码中没有displayName字段工具名要从functionResponse部件中派生。只从响应侧functionResponse生成工具摘要这是计划稿Implementation note明确指出的调用侧assistant 记录上的functionCall在结果尚未到达时会产生重复的、恒为ok的摘要行因此落地实现 recordsToLines 只遍历functionResponse部件并做了更细的状态归一toolCallResult.error存在 →errorstatus success→ok其余原样透传。assistant 回合同时携带文本与工具调用时不能短路这是同一记录上既有可见文本又有functionCall部件的情况若因检测到工具部件就跳过会静默丢掉该回合的 assistant 推理。落地实现把文本输出与工具摘要拆成两条独立路径注释中专门说明了这一动机。用户消息文本还多了一层投影visibleUserText优先使用 projectUserTranscriptForDisplay 得到的展示文本使瘦身副本与 TUI 中用户看到的转录一致。预算裁剪从 O(N²) 到单趟累积计划稿给出的朴素算法是先拼成字符串超预算就shift()最旧一行并整体重估对长会话是 O(N²)。落地实现改为单趟尾部保留resolve 方法// 单趟尾部保留每行只估一次然后从最新一行向旧方向累加直到达到预算。 const perLine lines.map((l) this.estimate([l])); let total headerCost; let start lines.length; while (start 0 total perLine[start - 1] budget) { total perLine[start - 1]; start--; } // 始终至少保留最新一行超预算的最终回合也能产出内容 if (start lines.length lines.length 0) { start lines.length - 1; }token 估算复用estimateContentTokens来自 tokenEstimation.ts把行数组拼成单条 user content 估算。裁剪发生时在正文前加[earlier turns omitted]并置truncated: truemeta.approxTokens还额外计入了 header 与截断标记的开销。标题推导计划稿中 title 是个 TODO直接用 sessionId。落地实现补上了deriveTitlesession-reference-service.ts#L185-L210优先取custom_title系统记录last-write-wins即重命名后的会话显示所选名称否则取第一条 user 消息的首行超过 80 字符截断并追加...都没有则回落到 sessionId。最终文本以--- Referenced session title (slimmed, read-only) ---开头——这个 header 就是 Task 7 手工冒烟时要确认的标记。对应单测覆盖四类行为session 缺失返回notFound保留 user/assistant 文本并丢弃 thought工具调用折叠为单行摘要且不含结果正文expect(res.text).not.toContain(BODY)尾部裁剪到 200 token 预算时truncated: true、包含[earlier turns omitted]、保留最新turn 49而丢弃最旧turn 0。Task 3atCommandProcessor中的session:路由与注入文件修改 atCommandProcessor.ts新增 atCommandProcessor.session.test.ts。计划稿建议在 MCP-server 分支之后约 L281、文件系统 containment 检查约 L320之前加一个路由分支。落地实现做了一个更符合该处理器整体结构的调整循环内只收集循环外再解析——在循环中parseSessionRef(pathName)命中时把{ originalAtPath, ref }压入sessionMentions按 id/title 去重并通过atPathToResolvedSpecMap把原始 token 保留为字面文本。代码注释说明了两点原因token 中的:不应被误认为路径或撞上一个恰名叫 session 的 MCP server且加载 瘦身放在循环之后统一进行。循环结束后的Resolve session references阶段atCommandProcessor.ts#L683-L800 区间逐个解析{ id }直接使用{ title }走SessionService.findSessionsByTitle——0 匹配 → not-found1 → ambiguous恰好 1 个才取其sessionId调new SessionReferenceService(config.getWorkingDir()).resolve(sessionId)任何失败not-found、歧义、异常如损坏的 session 文件或 I/O 错误→ 推送MessageType.INFO提示并把session:…token 保留为字面文本不抛异常、不中止回合成功 → 把{ text: ref.text }推入scopedMentionEntries并挂一张显示卡片。从源码看显示卡片实际走的是Referenced Session工具调用展示name: Referenced Sessiondescription: Referenced session title(truncated)失败路径用ToolCallStatus.ErrorresultDisplay原因比计划稿中镜像Activate Extension卡片的描述更具体成功与失败都有卡片形态失败时原因直接可见与绝不静默丢弃的全局约束对齐。测试侧atCommandProcessor.session.test.ts用vi.mock替换qwen-code/qwen-code-core中的SessionReferenceService保留其余真实导出断言processedQuery中同时出现Referenced session与瘦身后文本User: hi。Task 4category字段与会话建议生产者文件修改SuggestionsDisplay.tsx的Suggestion类型计划稿要求在此文件内新增SuggestionCategory创建 session-completion.ts修改 useAtCompletion.ts测试 session-completion.test.ts。从源码结构看落地实现把Suggestion与SuggestionCategory抽到了 packages/cli/src/ui/utils/suggestions.tsSuggestionsDisplay.tsx仅 re-export避免 UI 组件直接持有类型定义。分类枚举为type SuggestionCategory file | session | mcp | extension;Suggestion增加category?: SuggestionCategory缺省按file处理保证旧的 file 建议无需逐一改标签。getSessionSuggestions会话 → 建议项生产者签名async function getSessionSuggestions(cwd, pattern, nowMs?): PromiseSuggestion[]。行为要点调new SessionService(cwd).listSessions({ size: MAX_SESSION_SUGGESTIONS })MAX_SESSION_SUGGESTIONS 20作用域由SessionService保证仅限当前项目每个会话映射为label customTitle?.trim() || prompt || sessionId无自定义标题时回落到首条 promptvalue buildSessionRef(sessionId)即session:uuiddescription在有自定义标题时显示原 promptsourceBadge为 i18n 化的Sessioncategory: sessionpattern 过滤先剥掉session:前缀裸session无冒号视为空过滤词让用户看到全部会话而不是按字面 session 过滤再对label description做小写包含匹配。落地实现比计划稿多了一个重要的性能层listSessionsCached。会话列表要走 chats 目录同步 readdir/stat 每个文件有界尾部读取每次击键都重新列举会引入可感知的输入延迟因此按 cwd 缓存未过滤的列表TTL 为SESSION_LIST_CACHE_TTL_MS 3000——新创建/重命名的会话几秒内仍会浮现而连续击键复用同一份列表。列举失败时不写缓存下次击键重试并返回空列表保证 file/MCP/extension 补全永不被阻塞。测试通过注入的nowMs时钟对 TTL 做确定性验证并提供__resetSessionSuggestionCacheForTest清理模块级缓存。接入useAtCompletion按计划文件建议打上category: fileMCP 建议category: mcp扩展建议category: extension在extension-mention-ref.ts的getExtensionSuggestions内打标会话建议在空 pattern 时也要出现与 extensions 一致合并顺序为 extensions、sessions、mcp、files并置于同一条可中断abortable的异步路径中使新击键能取消本次会话列举。Task 5SuggestionsDisplay的标签页栏与分类过滤文件修改 SuggestionsDisplay.tsx新增 SuggestionsDisplay.test.tsx若存在则以实际文件为准计划稿以此为准。新增两个 propsactiveCategory?: SuggestionCategory | all; availableCategories?: ArraySuggestionCategory | all;过滤逻辑在切片/渲染前执行缺省 category 按file兜底const filteredSuggestions activeCategory all ? suggestions : suggestions.filter((s) (s.category ?? file) activeCategory);标签页栏仅在availableCategories.length 2时渲染即多于all 单一分类纯文件补全场景不出现 tab 栏无回归。每个分类有本地化标签All / Files / Sessions / MCP / Extensions落地实现通过categoryLabel i18nt()提供。落地实现还计划稿之外的增强onSelectCategory回调 CompletionCategoryMouseController允许鼠标点击标签页直接切换mouseEnabled时启用。计划稿附带的实现注记指出类别过滤最终整合进useCompletion把rawSuggestions过滤成对外暴露的suggestions记忆值这样activeSuggestionIndex永远指向同一份可见列表——如果在组件与 hook 两处各自过滤高亮索引会错位。仓库中 useCompletion.ts 正是如此组织suggestions记忆值按activeCategory从rawSuggestions过滤组件内再保留一道防御性过滤。对应单测断言三件事多分类时帧内同时出现Files与Sessions标签activeCategorysession时仅渲染会话行not.toContain(a.ts)单分类仅 file时不渲染Sessions标签。Task 6activeCategory状态与←/→标签页切换文件修改 useCompletion.ts、keybindings 命令定义、InputPrompt.tsx扩展 useCompletion.test.ts。useCompletion新增的对外契约activeCategory: SuggestionCategory | allavailableCategories: ArraySuggestionCategory | allswitchCategory(direction: 1 | -1): void行为规格计划文档定义落地实现一致availableCategories [all, ...suggestions 中实际出现的 distinct 分类按固定顺序 file/session/mcp/extension 排列]只有一个分类时退化为[all]switchCategory在availableCategories内按方向环形切换取模回绕并重置activeSuggestionIndex 0、visibleStartIndex 0当建议变化后当前activeCategory不再存在于availableCategories时回落为all。落地代码中switchCategory还带了一条防御计划稿的实现注记idx -1React 状态批处理期间出现陈旧分类时直接返回all再做取模运算。useCompletion.ts中可见CATEGORY_ORDER常量、availableCategories记忆值、activeCategorystate 与 stale 回落 effect返回值里带出activeCategory / availableCategories / switchCategory三个成员。按键绑定新增COMPLETION_TAB_LEFT←/COMPLETION_TAB_RIGHT→。在InputPrompt.tsx的showCompletionSuggestions块中、ACCEPT_SUGGESTION处理之前拦截if (keyMatchersCommand.COMPLETION_TAB_RIGHT) { completion.switchCategory(1); return true; } if (keyMatchersCommand.COMPLETION_TAB_LEFT) { completion.switchCategory(-1); return true; }并把这些值传入suggestionDisplayPropsactiveCategory: completion.activeCategory, availableCategories: completion.availableCategories。关键守卫仅在availableCategories.length 2时消费←/→普通文件补全期间的左右光标移动不受影响。测试断言给定 file session 两条建议时availableCategories恰为[all,file,session]activeCategory初始为allswitchCategory(1)后变为file且activeSuggestionIndex归零。Task 7端到端验证与全量门禁文件无纯验证任务。全量测试npm run build npx vitest run packages/core packages/cli要求包括新套件在内全部通过Lint 类型检查对齐 CInpm run lint npm run typecheck期望 clean手工 TUI 冒烟在拥有 ≥2 个历史会话的项目中输入→ 确认出现All / Files / Sessions标签页栏按→到Sessions↑/↓选择一项Enter/Tab接受 → 缓冲区显示session:id提交 → 确认Referenced session卡片渲染且瘦方块--- Referenced session … ---不含工具结果正文到达模型输入session:garbage-uuid提交 → 确认 not found 信息行与字面文本回退不崩溃。更新设计稿状态把 2026-07-17-at-session-reference-design.md 中的Status: Proposed改为Status: Implemented并以docs: mark session reference design as implemented提交。计划自审覆盖矩阵与已知执行期适配计划文档末尾的 Self-Review 给出了规格覆盖映射值得作为理解整套实现的索引Goal-1引用会话→ Task 1–4Goal-2Tab UI→ Task 4–6每条锁定决策都有归属无 LLM 瘦身 → Task 2全局约束禁止runSideQuery工具单行摘要 → Task 2recordsToLines当前项目作用域 → Task 2/4 经由SessionService裸显示会话 → Task 4 合并逻辑固定预算尾部保留 → Task 2 预算循环错误处理表 → Task 3not-found/歧义/空 Task 4listSessions 失败 Task 5单分类隐藏 tab。类型一致性链条也值得记住SessionRef {id?,title?}Task 1被 Task 3 消费SlimmedSessionReference/resolve(sessionId, opts)Task 2被 Task 3 消费SuggestionCategory/Suggestion.categoryTask 4被 Task 5/6 消费switchCategory(1|-1)、activeCategory、availableCategoriesTask 6被InputPromptSuggestionsDisplaypropsTask 5消费。计划同时如实标注了执行期适配而非占位符atCommandProcessor.ts中scopedMentionEntries元素/卡片的确切形状、useCompletion的 setter API、keybindings 命令文件的实际位置都以以执行时读到的本地命名为准的方式内联标注在所属 Task 中——这正是仓库中落地代码收集-解析两阶段、Referenced Session工具卡片形态、suggestions.ts类型抽出、3 秒列举缓存、鼠标点选标签页与计划稿存在差异的原因差异全部发生在被标注的适配点上核心契约类型名、函数签名、预算值、降级策略保持一致。关键文件索引关注点文件实现计划本文骨架docs/superpowers/plans/2026-07-17-at-session-reference.md设计规格docs/superpowers/specs/2026-07-17-at-session-reference-design.mdref 解析/构造packages/cli/src/ui/hooks/session-mention-ref.ts瘦身服务packages/core/src/services/session-reference-service.ts会话建议生产者packages/cli/src/ui/hooks/session-completion.ts处理与注入packages/cli/src/ui/hooks/atCommandProcessor.ts补全数据源packages/cli/src/ui/hooks/useAtCompletion.ts标签页状态机packages/cli/src/ui/hooks/useCompletion.ts下拉渲染packages/cli/src/ui/components/SuggestionsDisplay.tsx【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考