内置智能体工具
这些是内置智能体运行时直接注册的绑定服务端资源的工具——语言服务器、结构化搜索、调试器、内核、提问与工作进程控制——它们既不同于它桥接进来的 MCP 工具,也不同于运行器运行时提供的通用文件、搜索和 shell 工具。它们不在 MCP 注册表里:外部 MCP 客户端看不到它们,因为每一个都绑定着一项服务器端的实时资源——一个语言服务器进程、一个调试适配器、一个解释器——而这项资源只属于一次运行。
这里的每一个工具,只有在它的支撑资源为本次运行接入之后才会注册——语言服务器或调试监督进程、一个 tree-sitter 解析器、一个内核工厂、一个提问端口、一份 vibe 名册。对这次检出的语言服务器或调试适配器的检测,是另一项更靠后的检查(status / adapters):这些工具会一直留在工具面上,好让智能体能问清楚为什么什么都不可用,而不是靠猜。
审批层级
标题为“审批层级”的部分每个工具都会声明一个层级,而层级决定的是哪些调用方能看到这个工具,而不只是它是否会弹出提示。计划模式和只读子智能体都被限制在 read,因此 write 或 exec 层级的工具根本不会出现在它们的 schema 中。
| 层级 | 含义 | 这里的工具 |
|---|---|---|
read |
无副作用 | lsp、ast_grep、ask_user,以及五个 vibe_* |
write |
会改动文件 | lsp_rename、ast_edit、resolve |
exec |
启动或控制一个进程 | debug、eval |
lsp — 语言服务器查询
标题为“lsp — 语言服务器查询”的部分当一次运行拥有语言服务器监督进程和工作目录时才会注册。服务器能否真正作答是在调用时按交集来检测的:根标记必须存在,并且服务器二进制文件必须能被解析到。只有其中一个成立都不会启动服务器——status 会如实说明——而不是悄悄靠猜。
定位由 line 加一个 symbol 子串指定,而绝不用列号。被要求给出列号的模型只能靠猜,而猜错会解析成同一行上的另一个符号——一个看起来正确、说的却是别的东西的答案。在导航类操作中省略 symbol 是一种错误,而不是默认猜第一列。
| 操作 | 参数 | 返回 |
|---|---|---|
diagnostics |
file(或 "*") |
错误和警告;"*" 改为运行项目检查器 |
definition |
file, line, symbol |
符号声明的位置 |
type_definition |
file, line, symbol |
它的类型声明的位置 |
implementation |
file, line, symbol |
具体的实现 |
references |
file, line, symbol |
每一处使用,上限 50 处 |
hover |
file, line, symbol |
签名和文档注释 |
symbols |
query 或 file |
用 query 做工作区搜索(上限 100);用 file 取文档符号(上限 200) |
code_actions |
file, line, symbol |
服务器提供的快速修复 |
status |
— | 哪些服务器正在运行,以及各自对应哪些根 |
reload |
— | 重启这个根的服务器 |
写入时的诊断
标题为“写入时的诊断”的部分真正的收益并不在工具本身。write、edit 和 apply_patch 都被包了一层:写入之后,文件会经过语言服务器同步并保存,只有新引入的诊断会被并入工具结果。智能体在弄坏构建的那一刻就知道自己弄坏了,而不必记得去问。
已经报告过的诊断会由一份按运行维护的台账抑制,该台账以严重级别、代码和消息为键——刻意排除了位置,因此仅仅向下移动了四行的诊断不会被当作新增重新报告。
lsp_rename — 全项目重命名
标题为“lsp_rename — 全项目重命名”的部分它是一个独立的 write 层级工具,理由只有一个:层级决定可见性。把重命名并进 lsp,要么是把改写仓库的能力交给一个只读的探索者,要么是让每一次 hover 都先过一道审批提示。
| 参数 | 含义 |
|---|---|
file |
符号所在的位置 |
line |
从 1 开始计数的行号 |
symbol |
在该行上标识它的子串 |
new_name |
改成什么名字 |
ast_grep — 结构化搜索
标题为“ast_grep — 结构化搜索”的部分它按形状而不是按文本查找代码。$NAME 匹配任意节点,$$$NAME 匹配一串同级节点,$_ 是通配符,不绑定任何东西。
让它成为一个匹配器、而不只是一个多绕几步的正则的特性在于:同一个模式里重复出现的元变量必须捕获相同的文本。
ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")它会匹配那种“销毁自己刚刚检查过的对象”的守卫,而不会匹配销毁别的东西的守卫。
| 参数 | 含义 |
|---|---|
pattern |
一段带元变量的代码片段 |
path |
要搜索的文件或目录;默认为整个工作区 |
language |
随附的语法 id(dart、python、rust、c 等)。当 path 指向单个文件时会被推断出来 |
max_results |
默认 50 |
ast_edit — 结构化重写,先暂存
标题为“ast_edit — 结构化重写,先暂存”的部分匹配方式相同,另外还接受一个 rewrite 模板,把元变量代回其中。不会写入任何内容。 改动会被暂存起来,结果中会给出一个 edit_id。
ast_edit(pattern: "dispose($X)", rewrite: "$X.dispose()", language: "dart")→ (proposed) 12 replacements in 4 files … call resolve with edit_id "edit_1"| 参数 | 含义 |
|---|---|
pattern |
要匹配什么 |
rewrite |
换成什么,其中 $NAME 会被代回 |
path |
作用范围;默认为整个工作区 |
language |
语言 id |
resolve — 提交或丢弃一次暂存的改动
标题为“resolve — 提交或丢弃一次暂存的改动”的部分| 参数 | 含义 |
|---|---|
edit_id |
来自 (proposed) 结果 |
action |
accept 或 discard |
每个文件都会与暂存时捕获的内容比对,任何不一致都会拒绝整个改动。只应用了一半的结构化重写,会留下一棵在旧形状和新形状下都编译不过的树。
debug — 驱动调试器
标题为“debug — 驱动调试器”的部分当一次运行拥有调试会话监督进程、工作目录和对话 id 时才会注册。适配器能否真正启动是在调用时按与语言服务器相同的交集规则检测的(根标记并且二进制文件)。属于 exec 层级;非并行安全(会话是跨调用累积的状态,因此 continue 抢在 stack 前面会读到一个已经移动过的栈帧)。
每个对话只允许一个会话,并带一个硬性 TTL(30 分钟)。第二次 launch 会被拒绝,而不是悄悄替换掉第一个。
| 操作 | 参数 | 说明 |
|---|---|---|
adapters |
— | 这里哪些适配器可用,以及为什么不可用 |
status |
— | 是否有会话正在运行,以及它停在了哪里 |
breakpoints |
file, lines[] |
lines 为空会清除该文件的断点。可以在启动之前设置 |
launch |
program, args[] |
启动程序,并停在你的断点处 |
attach |
port |
连接到一个已在运行的被调试进程 |
continue |
thread_id? |
运行到下一个停止点 |
pause |
thread_id? |
让它就地停止 |
step_over / step_in / step_out |
thread_id? |
单步移动一步 |
threads |
— | 线程 id 和名称 |
stack |
thread_id? |
栈帧,上限 30 个 |
scopes |
frame_id |
某个栈帧上的作用域,每个都带一个 reference |
variables |
reference |
子项,上限 50 个;值上限 400 个字符 |
evaluate |
expression, frame_id? |
在该栈帧自身作用域中求值 |
output |
— | 被调试进程的 stdout/stderr,有上限 |
terminate |
— | 结束会话并杀掉适配器 |
适配器
标题为“适配器”的部分| Id | 命令 | 根标记 |
|---|---|---|
dart |
dart debug_adapter |
pubspec.yaml |
debugpy |
python3 -m debugpy.adapter |
pyproject.toml, setup.py, requirements.txt |
lldb |
lldb-dap |
Cargo.toml, CMakeLists.txt |
delve |
dlv dap |
go.mod |
debugpy 会优先选择项目本地的 .venv/bin 或 venv/bin,而不是 $PATH:$PATH 里的 python 是另一个环境,从错误的环境附加时,失败的样子读起来像代码坏了。
eval — 持久解释器
标题为“eval — 持久解释器”的部分一个 Python 或 JavaScript 单元格,它的变量、导入和已加载的数据在多次调用之间都会保留。属于 exec 层级;非并行安全。
| 参数 | 含义 |
|---|---|
code |
单元格的代码 |
description |
必填。5–10 个词,用作转录标签 |
language |
python(默认)或 javascript |
reset |
先丢弃解释器,从干净状态重新开始 |
- 最后一个表达式会像 notebook 那样被回显。
display()可用,pandas 数据框会以表格形式渲染,matplotlib 图形会作为转录中会显示出来的图像返回。- 人们下意识会敲的那些 IPython 惯用写法——
%pip、%cd、!cmd、%%bash、%%timeit——都会被改写成普通代码。%pip还会清空sys.modules,让下一次import能看到刚刚安装的东西。
在单元格内调用你自己的工具
标题为“在单元格内调用你自己的工具”的部分contents = tool("read", {"path": "lib/auth.dart"})这座桥会重新进入运行器的工具注册表,因此护栏、审批以及每一层包装都以与模型发起的调用完全相同的方式生效。它走的是内核自己的管道,而不是回环套接字:没有端口、没有密钥、没有出网规则。
单元格的超时是一份无活动时间预算(默认 5 分钟),并且在一次桥接调用尚未返回期间会被暂停,因此一个扇出到子智能体的单元格不会在扇出途中被杀掉。
ask_user — 向人提问
标题为“ask_user — 向人提问”的部分属于 read 层级。这是一次运行向操作者提问的唯一方式。它会在对话中渲染一张编号卡片,并一直阻塞,直到有人回答、跳过,或者提问超时。绝不要把问题写成一条聊天消息——那会结束当前回合。
| 参数 | 含义 |
|---|---|
question |
要问什么 |
context |
可选:你为什么问,以及答案会决定什么 |
options[] |
可选项,每一项为 {label, description?} |
allow_free_text |
是否接受手动输入的答案 |
multi_select |
是否可以选择多项 |
跳过即成功:基于一个明确说明的假设继续推进,并且不再询问。超时则是一种错误,附带同样的指示。权限提示(“我可以这样做吗?”)是另一道闸门,在对话中渲染为同样的卡片。
只有当提问端口和目标空间都存在时才会注册。在没有人工接入的情况下,这次调用会失败,而不是永远阻塞下去——这与护栏 prompt 在没有连接任何审批人时所采取的失败关闭姿态相同。
vibe_* — 指挥后台工作进程
标题为“vibe_* — 指挥后台工作进程”的部分只有在 vibe 模式 打开时才会注册。这五个工具都属于 read 层级:闸门应当设在工作进程的那些工具上,因为编辑真正发生的地方在那里。
| 工具 | 参数 | 说明 |
|---|---|---|
vibe_spawn |
brief, label, tier? |
立即返回;工作进程继续运行 |
vibe_send |
worker_id, message |
追加到简报中并重新运行 |
vibe_wait |
worker_id, timeout_seconds? |
阻塞,直到它结束 |
vibe_kill |
worker_id |
停止它 |
vibe_list |
— | 层级、状态、已用时间和最近一次结果 |
tier 可以是 fast(规格明确的机械性工作)或 good(设计、判断,或审查一个 fast 工作进程的产出)。