通用工具与支持库
通用工具与支持库 stage-22(横切基础设施)
这一阶段像整套系统的公共工具间,平时不站在主流程中央,却从启动、运行到排错都被反复用到。路径和文件工具负责把地址认准、把文件安全打开;文字工具负责截短、脱敏、排版;配置、认证和网络工具帮程序带对钥匙、走对门;命令、Git、插件工具负责把外部活儿跑稳。还有等待、取消、图片、构建脚本等小零件。核心 util、错误类型、模糊匹配和 hook 公用代码,则把常见规则统一起来,避免各处各写一套。
子阶段
本阶段涉及的状态6
reg-effective-config系统最终采用的那份配置,把文件、云端规则、项目设置、命令行参数和临时覆盖合在一起,后面各模块都按它办事。reg-installation-environment程序知道自己装在哪里、家目录在哪里、运行在哪台机器和什么终端里的那组环境信息。reg-secret-store本机保存的 API Key、ChatGPT 令牌、个人访问令牌、Bedrock Key 等秘密凭据。reg-sandbox-execution-policy命令和工具运行时要遵守的沙箱、读写、联网、身份和执行策略。reg-hook-lifecycle-state钩子在配置、回合、工具调用和审批过程里会触发什么、传什么上下文、返回什么结果的共享状态。reg-network-client-proxyHTTP 客户端、证书、Cookie、重试、流式连接和网络代理放行/拒绝规则的共享联网状态。
核心工具接口
这些文件定义主要共享工具入口点,以及在整个代码库中广泛使用的通用辅助工具。
core/src/util.rs源码 ↗
这个文件解决的是一些“看起来小,但到处都要统一”的问题。比如网络请求失败后不能马上疯狂重试,否则会把服务压垮,所以这里用 backoff 算出一次比一次更长、还带一点随机抖动的等待时间;比如用户给的路径可能是绝对路径,也可能是相对路径,resolve_path 会统一变成真正可用的路径;比如线程名两边有空格或干脆是空的,normalize_thread_name 会清理掉无效名字。它还提供了反馈标签机制,把认证失败恢复过程里的 request id、Cloudflare 标识等信息记下来,方便之后上传反馈时带上排查线索。另一个重要点是 error_or_panic:开发调试时直接崩溃,逼开发者立刻修;正式运行时只记录错误,避免用户程序随便退出。
Auth401FeedbackSnapshot::from_optional_fields44–56 ↗
fn from_optional_fields(
request_id: Option<&'a str>,
cf_ray: Option<&'a str>,
error: Option<&'a str>,
error_code: Option<&'a str>,
) -> Self
作用:把一次 401 认证失败相关的可选信息整理成一个固定形状的小快照。401 是 HTTP 状态码,通常表示“未授权”或“登录凭证有问题”。
数据流:进去的是 request_id、cf_ray、error、error_code 这些可能有、也可能没有的文本;它把没有的值统一换成空字符串;出来的是一个 Auth401FeedbackSnapshot,后面记录反馈标签时就不用再到处判断“有没有值”。
调用关系:它是 emit_feedback_auth_recovery_tags 的准备步骤。外层函数要发出反馈标签前,先请它把零散的认证失败字段收拾整齐,再统一写进日志标签。
调用图:被 1 处调用(emit_feedback_auth_recovery_tags)。
emit_feedback_auth_recovery_tags59–83 ↗
fn emit_feedback_auth_recovery_tags(
auth_recovery_mode: &str,
auth_recovery_phase: &str,
auth_recovery_outcome: &str,
auth_request_id: Option<&str>,
auth_cf_ray: Option<&str>,
作用:把认证恢复过程中的关键信息写成结构化反馈标签,方便之后用户提交反馈或排查登录问题时带上证据。它记录的不只是失败本身,还包括恢复模式、阶段和结果。
数据流:进去的是认证恢复的模式、阶段、结果,以及可选的 401 错误细节;它先调用 Auth401FeedbackSnapshot::from_optional_fields 把可选字段补齐成空字符串;然后通过 feedback_tags! 宏把这些字段作为 tracing 日志事件发出去。结果不是返回一个值,而是在系统的日志/反馈管道里留下可被收集的元数据。
调用关系:它会被 handle_unauthorized 在处理未授权响应时调用。也就是说,当系统发现登录或认证出了问题,并尝试恢复时,这个函数负责把当时的现场信息贴上标签,交给反馈收集层后续使用。
调用图:调用 1 个内部函数(from_optional_fields);被 1 处调用(handle_unauthorized);外部调用 1 个(feedback_tags!)。
backoff85–90 ↗
fn backoff(attempt: u64) -> Duration
作用:计算下一次重试前应该等多久。它避免失败后立刻反复请求,像排队等一会儿再试,既保护远端服务,也减少本程序白忙活。
数据流:进去的是第几次尝试 attempt;它用固定的初始等待时间 200 毫秒,再按 2 倍递增来算基础等待时间;然后加上 0.9 到 1.1 之间的随机抖动,避免很多任务同时在同一秒重试;出来的是一个 Duration,也就是“要睡多久”的时间长度。
调用关系:它被很多重试场景使用,比如重新连接、认证失败后的恢复、请求失败后的再试、压缩任务重试、guardian 重试、流式响应出错后的重试等。它本身只负责算时间,真正等待和重试由调用它的那些流程完成。
调用图:被 6 处调用(next_reconnect_delay, handle_unauthorized, retry_after_request_failure, run_compact_task_inner_impl, wait_before_guardian_retry, handle_retryable_response_stream_error);外部调用 2 个(from_millis, rng)。
error_or_panic92–98 ↗
fn error_or_panic(message: impl std::string::ToString)
作用:在发现不该发生的内部问题时,决定是直接让程序崩掉,还是只记一条错误日志。它让开发环境更严格,正式环境更稳妥。
数据流:进去的是一段错误说明;它检查当前是否是带调试断言的构建,也就是常说的开发/调试模式;如果是,就 panic,意思是立刻中止并暴露问题;如果不是,就用 error! 记录错误日志。它没有正常返回内容,但可能会让调试版程序停止。
调用关系:它被多个核心流程在发现异常状态时调用,比如检查工具调用输出、清空正在进行的任务、运行一轮对话、发送采样请求、从工具定义构造数据等。调用者把“这里不该出错”的信息交给它,由它按运行环境决定处理方式。
调用图:被 5 处调用(ensure_call_outputs_present, drain_in_flight, run_turn, try_run_sampling_request, from_tools);外部调用 3 个(cfg!, error!, panic!)。
resolve_path100–106 ↗
fn resolve_path(base: &Path, path: &PathBuf) -> PathBuf
作用:把用户或配置里给出的路径变成可直接使用的路径。它处理“已经是完整路径”和“只是相对某个目录的路径”这两种情况。
数据流:进去的是一个 base 基准目录和一个 path 路径;它先看 path 是不是绝对路径,也就是从根目录开始的完整地址;如果是,就原样复制返回;如果不是,就把它接到 base 后面。出来的是一个 PathBuf,表示最终解析好的路径。
调用关系:它是一个独立的小工具,供需要解释文件路径的地方使用。调用者不用自己重复判断绝对路径、相对路径,只要把基准目录和原路径交给它即可。
normalize_thread_name109–116 ↗
fn normalize_thread_name(name: &str) -> Option<String>
作用:清理线程名,把前后空白去掉,并拒绝空名字。线程名可以理解为给后台小工贴的标签,方便看日志和排查问题。
数据流:进去的是一段线程名文本;它先去掉开头和结尾的空格、换行等空白;如果清理后什么都不剩,就返回 None,表示没有有效名字;否则返回 Some(String),里面是清理后的名字。
调用关系:它会被 thread_set_name_response_inner 在设置线程名的响应流程中使用。那个流程负责接收外部给的名字,而这个函数负责把名字变干净、判断是否有效。
调用图:被 1 处调用(thread_set_name_response_inner)。
core/src/utils/mod.rs源码 ↗
这个文件很小,只做一件事:把 path_utils 这个模块公开挂到 utils 下面。可以把它想成工具箱外面的标签纸,上面写着“这里有路径相关工具”。真正的功能不在这里,而是在 path_utils 里;这里负责让项目其他地方能通过 utils::path_utils 这样的方式找到它。这样做的好处是结构清楚:以后 utils 下面如果再加别的通用工具,也可以继续在这个入口里登记。它没有函数、没有运行时逻辑,主要是在编译时帮助 Rust 组织代码。
utils/cli/src/lib.rs源码 ↗
可以把这个文件理解成一家商店的前台。真正的货物放在后面的不同货架上,比如审批模式参数、沙箱模式参数、配置覆盖、恢复命令、环境变量显示格式等;这个文件不亲自干活,而是告诉外部:“这些东西可以从我这里取。”这样做的好处是,其他地方不用知道每个功能具体藏在哪个文件里,只要引用这个库暴露出来的名字就行。它还把一些内部模块藏起来,只公开需要给外面用的类型和函数,避免外部代码直接依赖太多细节。这样以后内部文件怎么拆、怎么改,外部使用方式可以尽量保持稳定。
utils/plugins/src/lib.rs源码 ↗
在一个大项目里,很多地方都可能需要找插件、识别插件名字,或者处理用户文本里提到插件的写法。如果每个地方都自己拼路径、自己理解规则,就很容易不一致。这个文件就像工具箱的总入口:它不亲自做所有活,而是把几个子模块挂出来,让别人从同一个地方拿工具。这里公开了插件命名空间相关的函数,比如查找插件清单文件、从技能路径推断插件名字等。它还定义了 PluginSkillRoot,用来装三件关键信息:技能所在路径、插件编号、插件根目录。这样后面的代码拿到这个结构后,就不用再猜“这个技能属于谁、插件从哪里开始”。
共享错误词汇
这些文件建立可复用的错误类型,使支持性 crate 拥有一致的失败模型。
execpolicy-legacy/src/error.rs源码 ↗
这个文件像一本“错误字典”。程序检查外部命令能不能安全执行时,会遇到很多具体问题:比如某个程序没有规则说明、选项后面少了值、出现了不认识的参数、文件路径不在允许读写的文件夹里,或者路径根本无法确认。这里的 Error 枚举把这些情况逐一列出来,并且每种错误都带上必要信息,比如程序名、出错的选项、实际收到的参数、相关文件路径等。它还定义了 Result<T>,意思是“要么得到正常结果 T,要么得到这里定义的 Error”。这些错误支持序列化,也就是能被转换成结构化数据,方便返回给调用方、写日志或给上层系统判断。需要注意的是,这里本身不负责检查规则,也不负责修复问题;它只负责把“哪里错了、为什么错了”说清楚。
git-utils/src/errors.rs源码 ↗
这个文件像一本“故障对照表”。项目里会调用 git 命令、读取文件、遍历目录、检查路径是不是还在仓库里面,这些步骤都可能失败。这里用一个叫 GitToolingError 的枚举来把这些失败分门别类:比如 git 命令退出失败、git 输出不是合法文字编码、给的目录根本不是 git 仓库、路径不是相对路径、路径偷偷跑出了仓库范围、遍历目录失败、普通文件读写失败等。thiserror 是一个帮忙生成错误文字的工具库,它让每种错误都能自动变成给人看的说明。这样做的好处是,出错时不仅程序知道哪里坏了,用户或调用者也能看到具体原因,例如是哪条 git 命令失败、返回了什么状态、错误输出是什么。
事件与匹配辅助工具
这些文件提供可复用的匹配和标准化逻辑,从通用模糊过滤到 hook-event 专用约定。
utils/fuzzy-match/src/lib.rs源码 ↗
这个文件解决的是搜索框里常见的问题:用户不一定输入完整名字,只输入“hl”也希望能找到“hello”。核心函数 fuzzy_match 会做大小写不敏感的子序列匹配,也就是只要求输入的字符按顺序出现,中间可以隔着别的字符。它返回两样东西:原字符串里哪些字符被命中了,以及一个分数,分数越小表示越好。它特别注意 Unicode(世界各地文字的统一编码)里的大小写变化问题,比如某些字符转小写后会变成多个字符。为了高亮不乱,它一边用小写后的内容匹配,一边记住每个小写字符来自原字符串的哪个字符位置。最后它会偏爱连续匹配,也会明显奖励从开头就匹配上的结果。文件后半部分是一组测试,像安全检查清单,确保普通英文、大小写、空输入、Unicode 特例和排序分数都按预期工作。
fuzzy_match12–69 ↗
fn fuzzy_match(haystack: &str, needle: &str) -> Option<(Vec<usize>, i32)>
作用:这是这个文件的核心函数,用来判断一段文字里能不能按顺序找到用户输入的字符。能找到就返回匹配到的原字符位置和排序分数,方便调用方做高亮和结果排序;找不到就返回空结果。
数据流:输入是一段被搜索的文字 haystack 和用户输入的 needle。它先把 haystack 逐字符转成小写,同时记录“小写后的每个字符对应原文第几个字符”;再把 needle 也转小写;然后从前往后找 needle 的每个字符是否依次出现。匹配成功后,它把命中的原文位置整理、去重,并计算一个分数:匹配越连续分数越小,从字符串开头命中还会额外减分;如果 needle 为空,则直接返回空位置和最大分数;如果中途有字符找不到,则返回 None。
调用关系:这是所有测试围绕验证的对象。测试函数会用各种输入调用它,检查它给出的索引和分数是否可靠。函数内部只用到标准容器创建能力,比如 new 和 with_capacity,主要工作都在自己完成。
调用图:被 7 处调用(ascii_basic_indices, case_insensitive_matching_basic, empty_needle_matches_with_max_score_and_no_indices, indices_are_deduped_for_multichar_lowercase_expansion, prefer_contiguous_match_over_spread, start_of_string_bonus_applies, unicode_dotted_i_istanbul_highlighting);外部调用 2 个(new, with_capacity)。
tests::ascii_basic_indices76–84 ↗
fn ascii_basic_indices()
作用:这个测试确认最普通的英文匹配能正确返回字符位置和分数。它用“hello”匹配“hl”,检查命中的确是第 0 个和第 2 个字符。
数据流:它把固定输入“hello”和“hl”交给 fuzzy_match。拿到结果后,检查位置是不是 [0, 2],分数是不是因为从开头匹配而得到奖励后的 -99;如果没匹配上,就直接让测试失败。
调用关系:它是 fuzzy_match 的基础用例测试,像先检查机器最简单的按钮能不能按。它调用 fuzzy_match,然后用断言来判断结果是否符合预期。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 2 个(assert_eq!, panic!)。
tests::unicode_dotted_i_istanbul_highlighting87–95 ↗
fn unicode_dotted_i_istanbul_highlighting()
作用:这个测试确认带点的大写 İ 这类 Unicode 字符在转小写后,仍然能正确映射回原字符串位置。它主要保护“高亮位置不要错”这个行为。
数据流:它把“İstanbul”和“is”交给 fuzzy_match。函数内部会把 İ 小写成多个字符相关的形式,但测试期望最终返回的原文索引仍是 [0, 1],分数也符合从开头匹配的规则;如果匹配失败就让测试失败。
调用关系:它专门验证 fuzzy_match 里“小写字符到原文字符位置”的映射是否可靠。这个测试很重要,因为界面高亮通常用的是原字符串位置,而不是变形后的小写字符串位置。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 2 个(assert_eq!, panic!)。
tests::unicode_german_sharp_s_casefold98–100 ↗
fn unicode_german_sharp_s_casefold()
作用:这个测试说明当前匹配并不等同于完整的“大小写折叠”。比如德语 ß 和 ss 在某些语义里可视为相近,但这里不会把“straße”匹配成“strasse”。
数据流:它检查 fuzzy_match("straße", "strasse") 的结果应当是 None。也就是说,输入进去后,预期没有成功匹配出来;测试用 assert 确认这一点。
调用关系:它记录了 fuzzy_match 的边界:函数做的是 Rust 字符转小写后的逐字符匹配,不是所有语言规则都覆盖的高级文本等价匹配。这个测试防止以后有人误以为或无意改变这个行为。
调用图:外部调用 1 个(assert!)。
tests::prefer_contiguous_match_over_spread103–117 ↗
fn prefer_contiguous_match_over_spread()
作用:这个测试确认连续匹配会比隔得很开的匹配排名更靠前。比如“abc”匹配“abc”应当优于“a-b-c”匹配“abc”。
数据流:它分别把“abc”和“a-b-c”拿去匹配同一个 needle“abc”。然后比较两个分数:连续命中的分数应是 -100,隔开命中的分数应是 -98,并且前者要更小,表示排序更好。
调用关系:它验证 fuzzy_match 的打分规则是否能服务真实搜索体验:用户通常更想先看到紧凑、直接的结果。它调用 fuzzy_match 两次,再用断言比较两个结果。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 3 个(assert!, assert_eq!, panic!)。
tests::start_of_string_bonus_applies120–134 ↗
fn start_of_string_bonus_applies()
作用:这个测试确认“从开头就匹配上”的结果会被明显优先展示。比如搜索 file 时,“file_name”应该比“my_file_name”更好。
数据流:它分别用“file_name”和“my_file_name”匹配“file”。两个都是连续匹配,但第一个从开头开始,所以分数应是 -100;第二个不是开头,所以分数是 0。测试还检查第一个分数确实更小。
调用关系:它验证 fuzzy_match 的前缀奖励规则。这个规则让搜索结果更符合直觉:名字一开始就符合输入的项目,通常应该排在更前面。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 3 个(assert!, assert_eq!, panic!)。
tests::empty_needle_matches_with_max_score_and_no_indices137–144 ↗
fn empty_needle_matches_with_max_score_and_no_indices()
作用:这个测试确认用户什么都没输入时,函数会认为“可以匹配”,但不会给出任何高亮位置,并给一个很差的排序分数。
数据流:它把“anything”和空字符串交给 fuzzy_match。预期结果是索引列表为空,分数是 i32::MAX,也就是 32 位整数能表示的最大值;如果函数返回 None,测试就失败。
调用关系:它验证 fuzzy_match 对空输入的特殊处理。这个行为让调用方不用把空搜索当成错误处理,同时又避免空输入在排序里被当成最优匹配。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 3 个(assert!, assert_eq!, panic!)。
tests::case_insensitive_matching_basic147–155 ↗
fn case_insensitive_matching_basic()
作用:这个测试确认匹配不区分大小写。用户输入“foO”时,应该能匹配到“FooBar”的开头。
数据流:它把“FooBar”和“foO”传给 fuzzy_match。预期命中的原文位置是 [0, 1, 2],因为前三个字符就是 Foo;分数应是 -100,表示连续且从开头匹配。
调用关系:它覆盖 fuzzy_match 的大小写不敏感能力。这个测试保证搜索框不会因为用户大小写输入不同而漏掉结果。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 2 个(assert_eq!, panic!)。
tests::indices_are_deduped_for_multichar_lowercase_expansion158–167 ↗
fn indices_are_deduped_for_multichar_lowercase_expansion()
作用:这个测试确认当一个原字符转小写后变成多个字符时,返回的高亮位置不会重复。比如大写 İ 变形后可对应多个小写字符,但界面只应该高亮原来的一个字符。
数据流:它把原文“İ”和由 i 加组合点组成的 needle 传给 fuzzy_match。函数可能在小写后的内部表示里匹配到两个字符,但最终返回的原文索引应该去重为 [0],分数是 -100。
调用关系:它直接验证 fuzzy_match 最容易出错的 Unicode 映射细节。这个测试保护最终使用者看到的高亮效果:同一个原字符不会因为内部变成多个字符而被重复标记。
调用图:调用 1 个内部函数(fuzzy_match);外部调用 2 个(assert_eq!, panic!)。
hooks/src/events/common.rs源码 ↗
这个文件像 hook 系统里的“工具箱”。不同事件都会遇到一些重复小事:把多段文字拼起来、去掉空白、把额外上下文同时放进展示结果和模型可读列表里、在序列化输入失败时伪造一条失败完成事件。它还负责判断某个 hook 的匹配器该不该生效,以及匹配器到底是“全部都匹配”、“精确名字匹配”,还是正则表达式(用一套规则描述文本长什么样)。一个重要细节是:有些 hook 是针对某次工具调用的,所以这里会把工具调用 ID 拼进运行 ID,避免多次运行看起来像同一个记录。底部的测试专门确认这些匹配规则不会误伤,比如 Bash 不会误匹配 BashOutput。
join_text_chunks18–24 ↗
fn join_text_chunks(chunks: Vec<String>) -> Option<String>
作用:把多段文字合成一段,方便后面统一显示或交给模型看。如果根本没有文字,它会明确返回“没有内容”,而不是空字符串。
数据流:进去的是一个字符串列表 → 如果列表为空就返回 None;否则用两个换行把每段隔开并拼成一段 → 出来的是可选的合并后文本,原列表被消费掉。
调用关系:它被 run 和 aggregate_results 使用,通常出现在 hook 跑完之后,把多个来源的说明或上下文整理成一段更好读的文字。
调用图:被 2 处调用(run, aggregate_results)。
trimmed_non_empty26–33 ↗
fn trimmed_non_empty(text: &str) -> Option<String>
作用:清理一段文字两头的空白,并判断它是不是真的有内容。这样可以避免把只有空格或换行的“假内容”当成有效输出。
数据流:进去的是一段文本引用 → 先去掉前后空白,再检查是否为空 → 有内容就返回清理后的新字符串,没有内容就返回 None。
调用关系:它被多个 parse_completed 和 parse_pre_completed 调用,用在解析 hook 输出时,先把用户脚本吐出的文字筛一遍,后面才决定是否记录。
调用图:被 7 处调用(parse_completed, parse_pre_completed, parse_completed, parse_completed, parse_completed, parse_completed, parse_completed)。
append_additional_context35–45 ↗
fn append_additional_context(
entries: &mut Vec<HookOutputEntry>,
additional_contexts_for_model: &mut Vec<String>,
additional_context: String,
)
作用:把一段额外上下文同时放到两个地方:一个给事件结果展示,一个给模型继续使用。这样系统和用户看到的是同一份补充信息。
数据流:进去的是输出条目列表、模型上下文列表,以及一段新上下文 → 它新增一个类型为 Context 的输出条目,并把同样文字追加到模型上下文列表 → 两个列表都被改动,没有单独返回值。
调用关系:它被多个 parse_completed 调用,属于解析 hook 返回值时的收纳步骤:发现脚本提供了额外上下文,就统一登记。
调用图:被 4 处调用(parse_completed, parse_completed, parse_completed, parse_completed)。
flatten_additional_contexts47–54 ↗
fn flatten_additional_contexts(
additional_contexts: impl IntoIterator<Item = &'a [String]>,
) -> Vec<String>
作用:把多组额外上下文摊平成一个列表。可以理解成把好几叠便签合并成一叠,方便后面一次性交给模型。
数据流:进去的是若干个字符串切片组成的集合 → 它逐组遍历,把里面的字符串复制出来 → 出来的是一个新的扁平字符串列表,原数据不被修改。
调用关系:它被多个 run 调用,通常在多个 hook 或多个阶段都产出上下文之后,把这些上下文汇总成模型可读的一份总清单。
serialization_failure_hook_events56–78 ↗
fn serialization_failure_hook_events(
handlers: Vec<ConfiguredHandler>,
turn_id: Option<String>,
error_message: String,
) -> Vec<HookCompletedEvent>
作用:当系统没法把 hook 需要的输入序列化出来时,给每个原本要运行的处理器生成一条“失败了”的完成事件。这样前端或日志不会以为 hook 消失了。
数据流:进去的是处理器列表、可选的 turn_id,以及错误消息 → 对每个处理器先生成一份“正在运行”的摘要,再立刻改成失败、结束时间等于开始时间、耗时为 0,并加入错误输出 → 出来是一组 HookCompletedEvent。
调用关系:它被 run_pre、run_post 和多个 run 调用,也被 serialization_failure_hook_events_for_tool_use 包装使用;它是 hook 输入准备失败时的统一兜底出口。
调用图:被 6 处调用(serialization_failure_hook_events_for_tool_use, run_post, run_pre, run, run, run)。
serialization_failure_hook_events_for_tool_use80–90 ↗
fn serialization_failure_hook_events_for_tool_use(
handlers: Vec<ConfiguredHandler>,
turn_id: Option<String>,
error_message: String,
tool_use_id: &str,
) -> Vec<HookCompletedEvent>
作用:这是工具调用场景下的序列化失败兜底。除了生成失败事件,还会把具体工具调用 ID 写进运行 ID,方便区分是哪一次工具出了问题。
数据流:进去的是处理器列表、turn_id、错误消息和 tool_use_id → 先调用 serialization_failure_hook_events 生成普通失败事件 → 再逐个改造成带工具调用 ID 的事件 → 出来的是带工具调用标记的失败事件列表。
调用关系:它被多个 run 调用,也被相关测试验证;它把通用失败生成工作交给 serialization_failure_hook_events,再把工具调用身份补上。
调用图:调用 1 个内部函数(serialization_failure_hook_events);被 5 处调用(run, run, serialization_failure_run_ids_include_tool_use_id, run, serialization_failure_run_ids_include_tool_use_id)。
hook_completed_for_tool_use92–98 ↗
fn hook_completed_for_tool_use(
mut event: HookCompletedEvent,
tool_use_id: &str,
) -> HookCompletedEvent
作用:把一条已经完成的 hook 事件标记成“属于某次工具调用”。这样同一个 hook 在不同工具调用里运行时,记录不会混在一起。
数据流:进去的是一条 HookCompletedEvent 和 tool_use_id → 它取出事件里的 run 摘要,交给 hook_run_for_tool_use 修改 ID → 出来的是更新后的完成事件。
调用关系:它被测试 preview_and_completed_run_ids_include_tool_use_id 使用;内部把具体改 ID 的工作交给 hook_run_for_tool_use,自己负责更新整个事件。
调用图:调用 1 个内部函数(hook_run_for_tool_use);被 2 处调用(preview_and_completed_run_ids_include_tool_use_id, preview_and_completed_run_ids_include_tool_use_id)。
hook_run_for_tool_use100–103 ↗
fn hook_run_for_tool_use(mut run: HookRunSummary, tool_use_id: &str) -> HookRunSummary
作用:给一次 hook 运行的 ID 后面追加工具调用 ID。它的作用是让“同一个 hook 名字”在不同工具调用中也能有不同身份。
数据流:进去的是 HookRunSummary 和 tool_use_id → 它把原来的 run.id 改成“原 ID:工具调用 ID” → 出来的是更新后的 HookRunSummary。
调用关系:它只被 hook_completed_for_tool_use 调用,是一个很小的改名工具,专门保证工具调用相关事件的运行 ID 唯一。
调用图:被 1 处调用(hook_completed_for_tool_use);外部调用 1 个(format!)。
matcher_pattern_for_event105–120 ↗
fn matcher_pattern_for_event(
event_name: HookEventName,
matcher: Option<&str>,
) -> Option<&str>
作用:判断某类 hook 事件是否支持 matcher。matcher 是“匹配规则”,用来决定某个 hook 是否应该对某个工具或事件生效。
数据流:进去的是事件名和可选 matcher 字符串 → 如果这个事件类型支持匹配,就原样返回 matcher;如果不支持,比如用户提交提示或停止事件,就返回 None → 出来的是实际应采用的匹配规则。
调用关系:它被 append_matcher_groups 调用,用在加载或整理 hook 配置时,防止用户给不支持匹配的事件写了规则却被误用。
调用图:被 1 处调用(append_matcher_groups)。
validate_matcher_pattern122–127 ↗
fn validate_matcher_pattern(matcher: &str) -> Result<(), regex::Error>
作用:检查 matcher 写得是否合法。它允许“全部匹配”和简单精确匹配;如果看起来像正则表达式,就尝试编译,编不过就报错。
数据流:进去的是 matcher 文本 → 先用 is_match_all_matcher 和 is_exact_matcher 判断是否属于简单合法情况;否则交给 regex::Regex::new 检查正则语法 → 出来是成功或正则错误。
调用关系:它被 append_matcher_groups 调用,通常发生在配置整理阶段;它依赖两个内部小判断函数,必要时再交给正则库做严格检查。
调用图:调用 2 个内部函数(is_exact_matcher, is_match_all_matcher);被 1 处调用(append_matcher_groups);外部调用 1 个(new)。
matches_matcher129–144 ↗
fn matches_matcher(matcher: Option<&str>, input: Option<&str>) -> bool
作用:真正判断一个输入名字是否命中 matcher。比如 hook 写的是 Bash,它就只匹配 Bash;写的是 mcp__memory__.*,就按正则匹配一类名字。
数据流:进去的是可选 matcher 和可选输入文本 → 没有 matcher、空字符串或星号就视为全部匹配;简单 matcher 就按竖线分隔做精确比对;复杂 matcher 就当正则表达式尝试匹配 → 出来是 true 或 false。
调用关系:这个函数是运行时筛选 hook 的核心判断之一。它调用 is_match_all_matcher 和 is_exact_matcher 来决定走哪条判断路线;测试模块大量覆盖它的边界行为。
调用图:调用 2 个内部函数(is_exact_matcher, is_match_all_matcher)。
matcher_inputs146–155 ↗
fn matcher_inputs(
tool_name: &'a str,
matcher_aliases: &'a [String],
) -> Vec<&'a str>
作用:准备一组可用于匹配的名字:先放工具的正式名字,再放它的别名。这样 hook 可以用正式名或别名命中同一个工具。
数据流:进去的是工具正式名和别名列表 → 它创建一个新列表,第一项永远是正式名,后面依次接上别名 → 出来的是字符串引用列表,不复制原字符串内容。
调用关系:它被 preview 和 run 调用。预览和真正执行都会用同样顺序,保证用户看到的匹配效果和实际运行时一致。
调用图:被 6 处调用(preview, run, preview, run, preview, run);外部调用 1 个(once)。
is_match_all_matcher157–159 ↗
fn is_match_all_matcher(matcher: &str) -> bool
作用:判断 matcher 是不是“匹配所有”。这里空字符串和星号都代表不过滤,什么输入都可以通过。
数据流:进去的是 matcher 文本 → 检查它是否为空或等于“*” → 出来是一个布尔值,表示是否全匹配。
调用关系:它被 matches_matcher 和 validate_matcher_pattern 调用,是匹配判断和配置校验里的第一道捷径。
调用图:被 2 处调用(matches_matcher, validate_matcher_pattern)。
is_exact_matcher161–165 ↗
fn is_exact_matcher(matcher: &str) -> bool
作用:判断 matcher 能不能按简单名字精确匹配,而不需要当成正则表达式。只包含字母、数字、下划线和竖线时,就属于这种简单情况。
数据流:进去的是 matcher 文本 → 逐个字符检查是否都在允许范围内 → 出来是布尔值,表示能否走精确匹配路线。
调用关系:它被 matches_matcher 和 validate_matcher_pattern 调用。这样普通工具名不会被误当成正则,也能避免不必要的正则解析。
调用图:被 2 处调用(matches_matcher, validate_matcher_pattern)。
tests::matcher_omitted_matches_all_occurrences177–180 ↗
fn matcher_omitted_matches_all_occurrences()
作用:测试没有写 matcher 时是否默认匹配所有输入。它确保“不写规则”不会意外变成“什么都不匹配”。
数据流:进去的是测试里构造的 None matcher 和几个工具名 → 调用 matches_matcher 做判断 → 如果结果不是 true,测试就失败;不会改动运行数据。
调用关系:这是 matches_matcher 的基础行为测试,说明运行时筛选 hook 时,缺省 matcher 应该放行所有情况。
调用图:外部调用 1 个(assert!)。
tests::matcher_star_matches_all_occurrences183–187 ↗
fn matcher_star_matches_all_occurrences()
作用:测试星号 matcher 是否表示全部匹配,并且是否能通过合法性检查。
数据流:进去的是 matcher “*” 和几个输入名 → 分别调用 matches_matcher 与 validate_matcher_pattern → 期望都成功,结果不符就让测试失败。
调用关系:它覆盖 is_match_all_matcher 相关行为,保护配置里常见的通配写法不会被改坏。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::matcher_empty_string_matches_all_occurrences190–194 ↗
fn matcher_empty_string_matches_all_occurrences()
作用:测试空字符串 matcher 是否也表示全部匹配。这样配置里写空值时,行为和星号一致。
数据流:进去的是空字符串 matcher 和若干输入 → 调用 matches_matcher 和 validate_matcher_pattern → 期望匹配成功且校验通过。
调用关系:它验证 matches_matcher 与 validate_matcher_pattern 对空 matcher 的共同约定,避免一个函数放行、另一个函数拒绝。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::exact_matcher_supports_pipe_alternatives197–202 ↗
fn exact_matcher_supports_pipe_alternatives()
作用:测试简单 matcher 里用竖线表示多个候选名是否有效。比如 Edit|Write 应该匹配 Edit 或 Write,但不匹配 Bash。
数据流:进去的是 “Edit|Write” 和三个输入名 → 调用 matches_matcher 检查命中与不命中,再调用 validate_matcher_pattern 检查合法性 → 预期不满足就失败。
调用关系:它保护 is_exact_matcher 和 matches_matcher 的精确匹配分支,确保竖线在这里是“多个名字任选一个”的意思。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::literal_matcher_uses_exact_matching205–217 ↗
fn literal_matcher_uses_exact_matching()
作用:测试普通名字必须精确匹配,不能只因为前缀相同就算命中。比如 Bash 不应该匹配 BashOutput。
数据流:进去的是若干普通工具名和输入名 → 调用 matches_matcher 验证相等才通过、不相等就失败,并检查 validate_matcher_pattern 接受普通名字 → 测试只产生断言结果。
调用关系:它专门防止普通工具名被当成正则或模糊匹配,保证 hook 不会误跑到名字相似的工具上。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::matcher_uses_regex_when_it_contains_regex_characters220–223 ↗
fn matcher_uses_regex_when_it_contains_regex_characters()
作用:测试 matcher 含有正则特殊字符时会按正则表达式处理。比如 ^Bash 表示以 Bash 开头。
数据流:进去的是 “^Bash” 和输入 “BashOutput” → 调用 matches_matcher 判断应命中,再调用 validate_matcher_pattern 判断正则合法 → 断言这些结果符合预期。
调用关系:它覆盖 matches_matcher 的正则分支,说明复杂匹配需求可以通过正则实现。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::mcp_matchers_support_regex_wildcards226–240 ↗
fn mcp_matchers_support_regex_wildcards()
作用:测试 MCP 工具名这类较长名字能用正则通配。MCP 可以理解成外部工具服务的命名体系,名字里常带多段下划线。
数据流:进去的是带 .* 的 matcher 和几个 MCP 风格工具名 → 调用 matches_matcher 看该匹配的是否匹配、不该匹配的是否被拒绝,再校验正则合法 → 结果不符就失败。
调用关系:它保护正则 matcher 对外部工具命名的支持,避免用户无法用一条规则覆盖一组相关工具。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::matcher_supports_anchored_regexes243–247 ↗
fn matcher_supports_anchored_regexes()
作用:测试带开头和结尾锚点的正则是否正常工作。锚点就是要求整段文本从哪里开始、到哪里结束。
数据流:进去的是 “^Bash$” 和两个输入 → 调用 matches_matcher 确认只匹配完整的 Bash,不匹配 BashOutput,再调用 validate_matcher_pattern → 用断言检查结果。
调用关系:它进一步保护正则分支,确保用户可以写严格匹配规则,而不是只能写宽松规则。
调用图:外部调用 2 个(assert!, assert_eq!)。
tests::invalid_regex_is_rejected250–253 ↗
fn invalid_regex_is_rejected()
作用:测试错误的正则表达式会被拒绝,并且运行时不会误匹配。比如单独一个左中括号不是合法正则。
数据流:进去的是非法 matcher “[” 和输入 “Bash” → 调用 validate_matcher_pattern 期望返回错误,再调用 matches_matcher 期望返回 false → 断言这些安全结果。
调用关系:它保护 validate_matcher_pattern 和 matches_matcher 的错误处理路径,避免坏配置导致误运行或崩溃。
调用图:外部调用 1 个(assert!)。
tests::unsupported_events_ignore_matchers256–265 ↗
fn unsupported_events_ignore_matchers()
作用:测试不支持 matcher 的事件会忽略 matcher。比如用户提交提示和停止事件,即使配置里写了规则,也不应该拿来筛选。
数据流:进去的是 UserPromptSubmit、Stop 这类事件和 matcher 文本 → 调用 matcher_pattern_for_event → 期望返回 None。
调用关系:它保护 matcher_pattern_for_event 的事件白名单逻辑,防止不该支持匹配的事件偷偷开始使用 matcher。
调用图:外部调用 1 个(assert_eq!)。
tests::supported_events_keep_matchers268–289 ↗
fn supported_events_keep_matchers()
作用:测试支持 matcher 的事件会保留用户写的 matcher。比如工具使用前后、会话开始、压缩前后这些事件都应该能继续用匹配规则。
数据流:进去的是多种支持 matcher 的事件和对应 matcher 文本 → 调用 matcher_pattern_for_event → 期望原样返回这些 matcher。
调用关系:它和 unsupported_events_ignore_matchers 配成一组,明确哪些事件能用 matcher,哪些不能,防止以后改代码时破坏配置语义。
调用图:外部调用 1 个(assert_eq!)。