深入理解 VS Code Copilot 的 /chronicle search:会话历史搜索提示词、SQL 工具与 SQLite FTS5 索引实现
【免费下载链接】vscodeVisual Studio Code项目地址: https://gitcode.com/GitHub_Trending/vscode6/vscode
本文以 VS Code 仓库中 Copilot 扩展的chronicle-search.prompt.md提示词文件为核心,完整讲解/chronicle search命令的工作机制:如何用关键词、文件路径或 PR/Issue 引用检索本地与云端会话历史、底层copilot_sessionStoreSql工具的参数契约与安全边界,以及本地 SQLite + FTS5 会话库的表结构与索引细节。读完之后,你可以准确理解这条提示词背后的检索策略、写出合规的搜索 SQL,并掌握云端 DuckDB 后端下的性能约束。
一、文档本体:/chronicle search斜杠提示词
extensions/copilot/assets/prompts/chronicle-search.prompt.md是 Copilot 扩展内建的一组 Chronicle 提示词之一(同目录还有chronicle-standup.prompt.md、chronicle-tips.prompt.md、chronicle-cost-tips.prompt.md、chronicle-improve.prompt.md、chronicle-reindex.prompt.md)。它的 YAML frontmatter 定义了命令名与用途:
--- name: chronicle:search description: Search recent chat sessions by keyword, file path, or PR/issue ref ---正文要求模型完成一件事:按用户提供的查询(关键词、文件路径,或 PR/Issue/Commit 引用)搜索 Copilot 会话历史,并列出匹配的会话。文档同时给出三条关键的实现约束:
- 必须使用 chronicle 技能(即
extensions/copilot/assets/prompts/skills/chronicle/SKILL.md)。该技能是copilot_sessionStoreSql工具、会话库 Schema 与搜索工作流的唯一权威说明; - Schema 要点:
sessions表的主键是id(不是session_id);对话内容存放在turns表中,而不在sessions上;本地 SQLite 后端应使用 FTS5 的search_index表,并且直接 SELECTsession_id列——绝不能把search_index.rowid与turns.rowid做 JOIN(两者彼此独立,JOIN 会拉入无关会话); - 云端性能规则:用
WITH hits ... JOIN sessions的“聚合一次”模式,且对turns表使用默认 90 天窗口(技能文档中进一步细化为 7 天起步、逐步放宽,见下文)。
文档最后一行是一条强制调用约定:
每次调用
copilot_sessionStoreSql时,都必须设置subcommand: "search"。
这条约定在工具源码中有明确落点——subcommand参数被定义为'standup' | 'tips' | 'cost-tips' | 'search' | 'improve' | 'reindex'的枚举,注释写明其用途是“仅用于遥测归因”(telemetry attribution only),见 sessionStoreSqlTool.ts。也就是说,设置subcommand: "search"不会改变查询行为,而是让chronicle.sqlQuery遥测事件能够区分这次调用来自哪条/chronicle命令,与模型自行发起的临时查询(unknown)区分开。
二、chronicle 技能:搜索工作流的权威定义
/chronicle search <query>被触发后,模型依据 chronicle/SKILL.md 执行以下流程。
1. 搜索策略
技能的 Search 工作流要求覆盖三类匹配目标,因为用户的查询可能命中的是主题、文件路径或 PR/Issue 编号中的任意一种:
- 跨会话摘要(
sessions.summary)、对话轮次(turns中的用户消息和助手回复)以及其他索引内容(checkpoints、文件路径、refs 如 PR/issue/commit)搜索; - 为每个匹配会话收集足够的元数据用于标注:
s.id、s.repository、s.branch、s.summary、s.updated_at,外加一段能说明“为什么匹配”的短片段(例如云端用substr(user_message, 1, 160),或命中的file_path/ref_value); - 结果按仓库分组,按最近更新时间排序输出。
2. 编写查询:Schema 硬约束
调用copilot_sessionStoreSql时使用action: "query",并附带description: "Search sessions for <query>"。技能中列出的 Schema 要点值得逐条掌握:
sessions表主键是id,不是session_id;其余所有表都以session_id作为指向sessions.id的外键。查询必须始终投影s.id;- 不要臆造列名:没有
started_at(用created_at/updated_at)、没有workspace(本地用cwd,云端没有该列)、没有title(用summary)、没有content/messages(用turns.user_message/turns.assistant_response,或云端events.user_content/events.assistant_content); - 本地 SQLite:正文检索优先使用 FTS5 的
search_index表——WHERE search_index MATCH '<query>'。search_index自带session_id列,直接 SELECT 即可;文件路径和 refs不在FTS 索引里,需要与session_files.file_path、session_refs.ref_value上的LIKE条件组合使用; - 云端 DuckDB:没有 FTS5,对
sessions、turns、checkpoints、session_files、session_refs的文本列使用ILIKE '%<query>%'。
转义规则:用户查询中的单引号要翻倍转义(it's→it''s);多词 FTS5 查询要整体加引号当作短语:MATCH '"apply patch"'。
3. 云端性能规则:避免超时
云端(DuckDB)上ILIKE '%X%'作用于turns是全文扫描,运行时间过长会返回context deadline exceeded。技能给出的对策:
- 两步法(推荐):先用 CTE 从
turns中按窄时间窗口收集匹配的session_id(GROUP BY session_id聚合),再从sessions用WHERE id IN (...)补全元数据——避免对大规模扫描结果做昂贵 JOIN; - 每会话的匹配信息用
GROUP BY session_id配合any_value()/MIN()/array_agg()聚合,而不是标量子查询或相关子查询; - 云端对重量表默认使用7 天窗口(
turns上WHERE timestamp >= now() - INTERVAL '7 days',checkpoints上同理用created_at);无结果时逐步放宽 7 天 → 30 天 → 90 天,并在摘要行中注明窗口范围; - 最终 SELECT 保持
LIMIT 50; - 查询超时时应缩小窗口(而不是扩大)或去掉最重的表(通常是
turns),并告知用户裁剪了什么;绝不能用相同窗口重试。
这与提示词原文“default 90-day window onturns”的表述相互印证:90 天是窗口放宽的上限,日常执行从更窄的窗口起步。
4. 输出格式与无结果处理
每个会话渲染为一行标签:优先用summary,否则用返回的片段(截断至约 80 字符),禁止输出(no summary)、(no metadata)或裸会话 ID 列表。技能规定的结果模板:
**Search results for "<query>"** (<n> sessions, <scope: e.g. "last 7 days" / "all time">) _owner/repo_ - `session-id` — **<summary 或 snippet>** `branch` · updated <相对时间> · matched in <match_kind>配套规则:每个仓库可见结果上限约 10 条,超出时追加…and N more (refine your query);尽量带上· matched in <match_kind>(turn / file / ref / checkpoint / meta),帮助用户理解每个会话为什么命中;repository为 NULL 的会话归入Other分组。
若无结果,技能要求给出四条建议:换更宽泛的关键词(单词或子串代替短语)、扩大时间窗口(“search all time”)、若尚未建索引则运行/chronicle reindex、或运行/chronicle standup查看近期活动。
三、源码佐证:copilot_sessionStoreSql工具的运行机制
提示词与技能描述的每一个约束,都能在 sessionStoreSqlTool.ts 中找到对应实现。
1. 参数契约与 action 路由
工具入参为action(query/reindex,缺省为query)、query、force(仅 reindex 用)、description(必填)与subcommand。invoke方法按action分派:reindex走_invokeReindex,其余一律走_invokeQuery(见 invoke 入口)。
2. 查询安全:黑名单 + 白名单 + 单语句
_invokeQuery对模型提交的 SQL 做四层校验:
- 去除首尾空白与尾随分号(模型常自作主张追加),空查询直接报错;
- 黑名单正则(
BLOCKED_PATTERNS)拦截INSERT/UPDATE/DELETE/DROP/CREATE/ALTER/TRUNCATE/REPLACE、ATTACH/DETACH、PRAGMA(保留data_version例外,供 FTS5 内部使用)、VACUUM、REINDEX、ANALYZE、LOAD_EXTENSION、事务控制词(BEGIN/COMMIT/ROLLBACK/SAVEPOINT/RELEASE); - 白名单:先剥掉前导行注释与块注释(防止
/* 注释 */ VACUUM这类“注释夹带”),再要求语句必须以SELECT或WITH开头; - 语句中出现任何分号即拒绝(每次调用只允许一条 SQL)。
这些行为有专门的测试固化:sessionStoreSqlTool.spec.ts 验证了DROP TABLE、VACUUM INTO、SELECT load_extension(...)、PRAGMA data_version、注释前缀夹带、多语句、空查询等全部被Blocked SQL拦截,而WITH x AS (SELECT 1 AS n) SELECT * FROM x的 CTE 查询可以正常放行,尾随分号会被剥离后再执行。
3. 本地/云端路由与方言切换
路由由SessionIndexingPreference.hasCloudConsent()决定:开启云同步时经CloudSessionStoreClient查询云端 DuckDB(包含跨设备、跨 Agent 的全部会话),鉴权或网络失败时自动回退本地(结果标记source: local_fallback);未开启则直接查本地 SQLite。方言差异不是靠模型自觉,而是由alternativeDefinition在运行时替换工具定义实现的——云同步开启时,工具描述被换成CLOUD_MODEL_DESCRIPTION(DuckDB 语法、now() - INTERVAL日期运算、ILIKE文本检索),query参数的 schema 描述同步改写(见 alternativeDefinition)。本地版描述则声明在 package.json 的languageModelTools中:SQLite 语法、datetime('now', '-1 day')日期运算、FTS5MATCH,并指回chronicle技能查列级细节。
值得注意的契约设计:测试文件 中有一段注释明确“列级 Schema 的唯一事实来源在 chronicle/SKILL.md”,工具描述只承载低漂移信号(方言、只读约束、表名、技能指引),并有回归测试钉住这些锚点字符串。搜索提示词文档与技能文档的分工正是这一设计的体现。
4. 结果格式与上下文预算
结果以 Markdown 表格返回,两个硬预算防止撑爆上下文窗口:行数上限MAX_ROWS = 100(对应技能中“Always use LIMIT (max 100)”的要求),总输出字符预算TOTAL_FORMAT_BUDGET = 30_000——超出时单元格先被均匀地自适应截断,整体再兜底截断,并附加[TRUNCATED]/ “Add a LIMIT clause or narrow your query” 提示(见 formatSqlResult)。
四、本地会话库:SQLite + FTS5 的表结构与索引
会话库由 sessionStore.ts 中的SessionStore类实现(node:sqlite+ FTS5,Schema 版本 3)。ensureSchema创建的表结构与技能文档一致,且注释说明该 Schema 与 copilot-agent-runtime(CLI 侧)的 SessionStore 相同,以便查询在两个表面间可移植:
| 表 | 关键列 | 说明 |
|---|---|---|
sessions | id(主键)、cwd、repository、branch、host_type、summary、agent_name、agent_description、created_at、updated_at | 会话元数据;云端中cwd恒为 NULL |
turns | session_id、turn_index、user_message、assistant_response、timestamp | 对话内容所在表,UNIQUE(session_id, turn_index);assistant_response仅存开头约 1000 字符 |
checkpoints | session_id、checkpoint_number、title、overview、history、work_done、technical_details、important_files、next_steps、created_at | 压缩(compaction)检查点 |
session_files | session_id、file_path、tool_name、turn_index、first_seen_at | 会话触碰过的文件 |
session_refs | session_id、ref_type(commit/pr/issue)、ref_value、turn_index、created_at | PR/Issue/Commit 引用 |
search_index | FTS5 虚拟表:content、session_id UNINDEXED、source_type UNINDEXED、source_id UNINDEXED | 本地全文索引 |
与搜索直接相关的三个实现细节:
- FTS 条目的写入路径:
insertTurn把user_message与assistant_response合并为一条source_type = 'turn'的索引记录,source_id为{session_id}:turn:{turn_index};insertCheckpoint则把 overview/history/work_done/technical_details/important_files/next_steps 六个非空段落分别建成独立条目(见 insertTurn)。这就解释了技能文档的告诫:search_index的rowid是自增序号,与turns.id(AUTOINCREMENT 主键)没有任何对应关系,JOIN 它们只会匹配到无关行——正确做法是直接投影search_index.session_id。 - BM25 排序的内置检索:
SessionStore.search()使用bm25(search_index) AS rank ... ORDER BY rank做全文检索(见 search 方法);技能同时给出取片段的两种方式:snippet(search_index, 0, '[', ']', '…', 12)或substr(content, 1, 160)。 - 引擎级只读强制:
executeReadOnly在 Node.js 24.2+(提供setAuthorizer)时安装一个动作码白名单——只放行SQLITE_READ、SQLITE_SELECT、SQLITE_FUNCTION(且load_extension被显式拒绝)、SQLITE_RECURSIVE,并为 FTS5 的内部探测放行PRAGMA data_version(见 executeReadOnly)。这是工具层正则校验之外的第二道防线,也是PRAGMA data_version能出现在例外注释里的原因。
此外,Schema 建立了idx_sessions_repo、idx_sessions_cwd、idx_session_files_path、idx_session_refs_type_value、idx_turns_session、idx_checkpoints_session等索引(见 ensureSchema),搜索中按仓库、文件路径、引用值过滤时可以走索引。远端工作区(网络文件系统)下存储会切换为PRAGMA journal_mode = DELETE+ 更长busy_timeout,并带有一次性的损坏库重建逻辑,属于运维细节,与查询语义无关。
五、实战查询形态
结合提示词、技能与上述实现,三类搜索目标对应的查询形态如下。
1. 关键词搜索(本地 SQLite,FTS5)
SELECT session_id, snippet(search_index, 0, '[', ']', '…', 12) AS snippet FROM search_index WHERE search_index MATCH 'apply patch' LIMIT 50多词短语查询用MATCH '"apply patch"',单引号翻倍转义。拿到session_id集合后再按技能要求从sessions补元数据。文件路径与 refs 不在 FTS 索引中,需要并行查询:
-- 文件路径命中 SELECT s.id, s.repository, s.branch, s.summary, s.updated_at, f.file_path FROM session_files f JOIN sessions s ON s.id = f.session_id WHERE f.file_path LIKE '%copilot%'; -- PR/Issue/Commit 引用命中 SELECT s.id, s.repository, s.branch, s.summary, s.updated_at, r.ref_type, r.ref_value FROM session_refs r JOIN sessions s ON s.id = r.session_id WHERE r.ref_value LIKE '%1234%';2. 云端 DuckDB 两步法(聚合一次,避免超时)
技能给出的“WITH hits ... JOIN sessions”模式落地为:
WITH hits AS ( SELECT session_id, MIN(timestamp) AS first_hit, array_agg(user_message) AS samples FROM turns WHERE timestamp >= now() - INTERVAL '7 days' AND (user_message ILIKE '%query%' OR assistant_response ILIKE '%query%') GROUP BY session_id ) SELECT s.id, s.repository, s.branch, s.summary, s.updated_at FROM sessions s JOIN hits h ON h.session_id = s.id ORDER BY s.updated_at DESC LIMIT 50;时间窗口从 7 天起步,无结果时放宽到 30/90 天,并在结果头部注明窗口;超时时缩小窗口或去掉turns条件,而不是原样重试。
3. 通用查询守则(Query Guidelines)
技能“Query Guidelines”一节对搜索同样适用:每次调用只发一条查询(不要分号拼接);仅允许SELECT/WITH,DESCRIBE/SHOW/PRAGMA一律被拦——不要试图自省库结构,直接读技能中的 Schema;始终带LIMIT(工具上限 100 行)、优先COUNT/GROUP BY聚合而非裸行转储;时间范围过滤一律用updated_at而非created_at;分析对话内容时 JOINsessions与turns,不要只依赖sessions.summary。
六、前置条件、配套命令与边界
- 前置设置:技能声明 Chronicle 要求
github.copilot.chat.localIndex.enabled为true,若copilot_sessionStoreSql工具不可用,应提示用户在 VS Code Settings 中开启;而工具声明本身的可见性条件写在 package.json 的when: "github.copilot.sessionSearch.enabled"中。两处分别控制“索引/工具能力开启”与“工具声明注册”,排查时都应检查。 - 云同步:
chat.sessionSync.enabled决定查询路由到云端 DuckDB(全设备、全 Agent 数据)还是本地 SQLite(仅本设备会话)。这也决定了文本检索手段——FTS5MATCH仅本地可用,云端用ILIKE。 - 重建索引:搜索无结果且怀疑未建索引时,运行
/chronicle reindex,即action: "reindex"(可选force: true重处理已索引会话);工具会从调试日志重建本地库并在开启云同步时上传新会话,返回前后统计对照表(见 _invokeReindex)。 - 删除不在工具能力内:
copilot_sessionStoreSql有意不支持删除语句(黑名单 + 白名单双重拦截,且引擎层 authorizer 只放行读操作)。删除会话数据须走命令面板的Delete Session Sync Data(github.copilot.sessionSync.deleteSessions)命令,从本地与云端选择删除。
七、小结
chronicle-search.prompt.md虽然只有一页篇幅,但它把一个完整的检索系统设计压缩成了三条硬约束加一条调用约定:用 chronicle 技能、守住 Schema 要点(主键id、内容在turns、FTS5 直查session_id)、遵循云端聚合一次的性能规则,并始终以subcommand: "search"调用工具。从源码看,这些约束背后分别对应SessionStoreSqlTool的白名单/黑名单校验与方言切换、SessionStore的 FTS5 写入与 BM25 检索、以及“列级 Schema 唯一事实来源在 SKILL.md”的契约化测试——提示词文档、技能文档与工具实现三者互为印证,构成了 VS Code Copilot 会话历史搜索这一特性的完整证据链。
【免费下载链接】vscodeVisual Studio Code项目地址: https://gitcode.com/GitHub_Trending/vscode6/vscode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考