通过 ACP 在 Zed 中使用 Zuno
Zuno 通过标准输入与标准输出暴露一个原生的 Agent Client Protocol(ACP)server。Zed 可以把该 server 作为自定义外部 Agent 启动。上游 Zed 的配置契约记录在 External agents;Zuno 已实现的协议边界与已固定的上游证据记录在 Zed ACP integration。
1. 验证已安装的 Zuno 二进制
定位 Zed 应当启动的那个二进制:
# Linux and macOS
command -v zuno
zuno acp --check2
3
# Windows PowerShell
(Get-Command zuno).Source
zuno acp --check2
3
这项检查必须在不启动会话的情况下完成,并打印:
ACP stdio adapter ready (protocol v1; schema v1.21.0)如果终端能找到 zuno 而 Zed 找不到,请使用 command -v zuno 或 Get-Command zuno 报告的绝对路径。桌面应用拿到的 PATH 往往与交互式 shell 不同。
2. 把 Zuno 添加为自定义 Zed Agent
打开 Zed 的 Agent Panel,打开 Agent Settings,选择 Add Agent,然后选择 Add Custom Agent。等价的 Zed 设置条目是:
{
"agent_servers": {
"Zuno": {
"type": "custom",
"command": "/absolute/path/to/zuno",
"args": ["acp"],
"env": {}
}
}
}2
3
4
5
6
7
8
9
10
使用可执行文件的绝对路径最可靠。示例:
Linux
{
"agent_servers": {
"Zuno": {
"type": "custom",
"command": "/home/you/.local/bin/zuno",
"args": ["acp"],
"env": {}
}
}
}2
3
4
5
6
7
8
9
10
macOS
{
"agent_servers": {
"Zuno": {
"type": "custom",
"command": "/Users/you/.local/bin/zuno",
"args": ["acp"],
"env": {}
}
}
}2
3
4
5
6
7
8
9
10
Windows
JSON 字符串中的反斜杠需要转义:
{
"agent_servers": {
"Zuno": {
"type": "custom",
"command": "C:\\Users\\you\\.local\\bin\\zuno.exe",
"args": ["acp"],
"env": {}
}
}
}2
3
4
5
6
7
8
9
10
不要把这条命令包在一个会向 stdout 写横幅或状态文本的 shell 脚本里。ACP 的 stdout 只包含以换行分隔的 JSON-RPC 帧。
3. 选择 Zed 所使用的 Zuno 配置
Zed 把所选项目作为绝对工作目录发送过来。Zuno 解析与它在 TUI 中相同的全局与项目 zuno.json/zuno.jsonc 链:
- 平台配置根之下的全局配置;
- 来自 worktree 与
.zuno/层的项目配置; - 已配置的 Agent 定义、Skill、扩展、MCP server、权限、沙箱策略、provider 和模型。
Provider 登录与凭据仍归 Zuno 拥有。请在启动 Zed Agent 之前完成配置与验证:
zuno debug config
zuno auth list
zuno models2
3
不要为了让 ACP 能启动就把 provider 密钥复制进 Zed 设置。请使用 Zuno 的凭据存储,或者 Provider 与凭据中描述的 provider 环境变量。
要为这个 Zed Agent 选择一个已有的可切换配置覆盖层,请在自定义 Agent 的环境中设置 ZUNO_CONFIG_DIR:
{
"agent_servers": {
"Zuno (Kiro profile)": {
"type": "custom",
"command": "/absolute/path/to/zuno",
"args": ["acp"],
"env": {
"ZUNO_CONFIG_DIR": "/home/you/.config/zuno/profiles/kiro"
}
}
}
}2
3
4
5
6
7
8
9
10
11
12
在 Windows 上请使用转义后的绝对路径。多个 Zed 条目可以用不同的 ZUNO_CONFIG_DIR 覆盖层启动同一个 Zuno 二进制。
4. 进程丢失、重连与后台工作
ACP stdio 归启动 zuno acp 的编辑器进程所有。该进程退出时,JSON-RPC transport 与实时 投影订阅会消失,但持久 session 不会:message、inbox、Goal、Plan、Todo、Job、usage、 prompt receipt、待处理人工请求和重试截止时间仍在 SQLite。
重连后:
- 启动新的
zuno acp; - 用客户端保存的 session id 调用
session/load; - 只有协商出的能力确实暴露该操作时才使用
session/resume; - 让 Zuno 重放持久状态并恢复符合条件的 continuation。
稳定 ACP v1 的基线重连契约是 load。将来或协商出的 resume 可以改善客户端体验,但不会替代 持久 load。TUI 文档中的 background supervisor 只保留 PTY,刻意不包装 ACP stdio;需要常驻 ACP 的部署应使用编辑器拥有的远程进程、service manager,或真正保留 ACP 进程的 transport supervisor。
peer-session message 同样持久。根 Agent 可以用 session_message 向同项目另一个 root 发送。 本 ACP session 在线时,它作为带来源的纯文本 peer context 被驱动;离线时保持 queued,并在 load 后处理。child Agent 永远拿不到发送工具。
5. 选择 deep 或其他会话 Agent
一个新的 ACP 会话会解析 Zuno 常规的默认 Agent 与模型。随后 Zuno 向 Zed 发布这些会话控制项:
- Mode:Build 或 Plan;
- Agent:可用的实现类 Agent;
- Model:来自解析出的 Zuno provider 目录的模型;
- Reasoning:
Configured default,加上所选模型支持的规范级别,例如 Low、High、Extra High 或 Maximum。
要使用可直接选择的 deep Agent:
- 创建一个 Zuno 外部 Agent 线程;
- 保持 Mode 为 Build;
- 打开 Agent 配置选择器并选择
deep; - 如果当前 Zuno profile 暴露了多个模型,选择想要的那个;
- 当所选模型公布了推理能力时,选择一个推理级别。
Plan 模式为下一次活跃回合选择只读的 plan Agent;冷会话只切换持久模式,不会因此启动宿主、MCP 或插件进程。回到 Build 模式会恢复所选的实现类 Agent并执行 Start Work 授权。Agent 与模型的更改是会话本地的,并且在会话有工作在飞时会被拒绝:一个提示词请求、一个活跃回合,或由 active Goal 恢复的后台续跑。
zuno acp 不接受 --agent 启动参数。Agent 选择是一个 ACP 会话配置操作,不是第二个进程级配置面。
6. 斜杠命令与 Skill
在会话创建、加载、恢复或一次成功的重新配置之后,Zuno 会发布原生会话控制项、来自其常规命令目录的可执行命令,以及可用斜杠调用的无歧义 Skill。随后 Zed 会在 / 补全中暴露它们。
来源与其他 Zuno 界面相同:
- 带真实运行时处理器的原生会话控制项:
/compact、/goal、/plan、/start-plan和/start-work; - Zuno 配置目录下的全局
command/*.md或commands/*.md; - 项目的
.zuno/command/*.md或.zuno/commands/*.md; - 拥有真实处理器的内置命令;
- 名称不与命令冲突的已发现 Skill。
/compact 不接受参数。它调用与 TUI 相同的持久压缩路径,只在该命令达到终态生命周期事件之后才返回,并且不会把字面的斜杠命令发送给模型。原生控制项优先,因此一个用户定义的名为 compact 的命令或 Skill 不会作为第二个有歧义的条目被发布。
/goal 暴露与 TUI 相同的持久 goal 处理器。不带参数时显示当前 Goal;/goal <目标> 会在尚无 Goal 或上一条 Goal 已完成、已取消时创建新 Goal,否则更新当前目标并保留生命周期状态、预算和累计用量。show、history、create <目标>、edit <目标>、budget <正整数 token|none>、pause、resume、block <原因>、complete 和 cancel 等显式 action 仍然可用,且首词匹配时优先。
目标变化还会同步活跃 Plan:多阶段工作会归档此前可见 Plan,并安装一个绑定当前 goal_id 的新根 Plan;若新目标是原子任务,已终态的历史 Plan 不会改绑;属于上一个 Goal 的终态 Plan 会归档为已完成的历史,面板随之清空。
该命令的输出投影为一条普通的 Agent 消息,而不是推理内容;显式 action 的非法参数会返回 JSON-RPC invalid params,而不是内部会话错误。创建或编辑成功后会立即推进 active Goal;新会话会先把目标持久化为首个 user turn anchor,字面的斜杠命令不会进入 provider 输入。
/plan 与 /start-plan 都会幂等进入只读 Plan,Agent selector 只更新未来 Work Agent, 不会退出 Plan。/start-work 是斜杠命令中唯一的 Build 交接:当前精确 Plan revision 必须 handoff-ready;绑定 Draft review 时默认拒绝,除非用户提供 --accept-draft-risk <原因>。成功后恢复保存的 Agent/provider/model/reasoning,并发出 current_mode_update 与 config_option_update。控制本身以 UserControl turn 启动, 不会伪造 user message。
执行 /name arguments 使用 Zuno 已有的命令模板或 Skill driver,包括常规的权限与持久会话行为。ACP 不会创建产品特定的 /dual-review、/auto-release 或其他工作流;用户可以在自己的命令或 Skill 目录中定义它们。
7. 图像、选区、分支 diff 与附件
Zuno 公布对 ACP image 与 embeddedContext 的支持。在 Zed 中这会启用图像附件以及通用的嵌入上下文,例如当前选区、诊断信息、抓取的上下文和分支 diff。
- 内联与嵌入图像支持 PNG、JPEG、GIF 和 WebP,base64 载荷需有效且不超过 5 MiB。
- 嵌入的文本资源会在持久提示词信封中保留它们的 URI、MIME 类型和文本,每个限制为 50 KiB 与 2,000 行。
- 除图像之外的二进制嵌入资源会被拒绝。
- 普通文件引用可能以
resource_link形式到达;Zuno 会让这些字段在持久存储与加载重放中保持类型化。 - 音频仍不受支持,也不会被公布。
所选的 provider/model 也必须公布图像输入能力。ACP 的能力协商无法让一个纯文本模型接受图像。
8. 权限、工具、diff 与生命周期
Zed 呈现权限与征询请求,但策略拥有者仍然是 Zuno:
- 由 Zuno 的权限规则决定某个工具是运行、被拒绝还是询问;
- 可复用的 ACP 询问提供
Allow once、Allow for session和Reject;会话级授予对权限与资源模式是确切匹配的,能在 Agent/模型/推理重挂载后存活,并由session/close清除; - strict 或 Shell 风险类的仅人工询问只提供
Allow once与Reject;生效的allow_all,包括danger-full-access,完全不会发出权限请求; - Zuno 的 Shell 沙箱控制文件系统与网络权限;
- 原生文件工具为 Zed 发出类型化的创建与编辑 diff,并在标准
title中展示Editing <filename>,在locations中提供绝对路径;完整参数到达前仍显示Editing files。多文件标题最多三个文件名、160 个 Unicode 字符,其余用(+N more)表示。相对 patch 路径先显示名称,不借用进程 cwd 猜测位置; 完成时优先使用实际修改路径,权限请求及历史回放同样保留文件名; - 当所选 Agent profile 允许时,Zuno 配置的 MCP server 仍然可用;
- ACP 客户端提供的 stdio 与 Streamable HTTP MCP server 以 session 为作用域,只有完整集合全部连接并发现成功后才发布工具;
- 取消、会话加载、恢复、关闭、plan 状态、用量和工具历史使用与 TUI 相同的持久运行时。
结构化的 question 调用使用 ACP 表单控件,而不是通用提示:
- 单选项从
oneOf渲染; - 多选项渲染为数组选择;
- 当该问题允许自定义答案时,选项仍可点击,并额外显示一个可选的
Other字段; - 提交
Other优先于已选中的选项,这与 Zuno TUI 一致,而一个空的可选表单会被报告为未回答。
用户主动关闭的表单、已过期的表单,以及 Zuno 无法解析其回复的表单,都属于已决结果,而不是丢失的工作:question 工具会把该撤回结果报告给模型,会话的 Goal 继续运行。持久记录把两件事分开表达——请求已结算,且用户没有作答——写法是一个空的答案列表,外加取值为 cancelled、expired 或 failed 的 outcome。权限对话框遵循同一规则:被关闭的回复,或指向本次对话从未提供过的选项的回复,都记为 reject,并同时记下导致它的 outcome,因此客户端的缺陷日后不会被当成用户有意做出的拒绝来回放。
当 Zuno 重新呈现由早前进程留下的待处理问题或权限请求,而客户端根本无法送达时,不会写入任何内容。该请求保持 pending,仍可从 TUI 或 HTTP API 作答;本次 session/prompt 以 stopReason: end_turn 结束,再发送一次 prompt 会重新呈现它。
在完成之后以及历史重放时,该问题仍是一张静态工具卡片,显示它的提示、选项、状态,以及在持久答案元数据可用时显示被选中的值。rawInput 与 rawOutput 在工具详情中仍然可用;加载历史绝不会重新打开一次征询请求。
只有 provider 的推理增量会被投影到 Zed 的 Thinking 界面,唯一带标记的例外是 Zuno 自身发出的通知:无法抓取的远程规则文件、因装不进 prompt 预算而整份跳过的完整本地规则 文件、被额度停下的回合,以及预算或上下文策略要求的压缩,会以带 _meta.zuno.notice 的 agent_thought_chunk 发送,其中 severity 取 info、warning 或 error。长工具回合主动达到上下文阈值时使用稳定 code context.compact;instruction 与 budget 通知仍分别使用 instruction.* 与 budget.* 族。客户端靠这个标记把通知与模型输出区分开。生成的标题使用 ACP 的 session_info_update,其他运维状态或 provider 失败文本由生命周期/错误报告处理,而不是 被渲染成模型的思考内容。
压缩成功后,编辑器会收到完全相同的持久摘要:它以 agent_message_chunk 投影,并带 _meta.zuno.kind: "compaction_summary"。回合中的自动压缩会在同一次 host drive 内、 重试之前发送摘要;load/resume replay 的也是同一份文本和标记。因此成功的内部压缩不会 先表现为终端 turn failure,也不需要用户再发一次 prompt。
ACP 从原生 ContextUsageSnapshot 投影绝对值 usage_update,完整快照位于 _meta.zuno.contextUsage。窗口占用为最近供应商确认基线,加尚未计入内容的估算, 不是会话累计账单。分批 usage 按快照合并,缓存与推理子项不重复相加;较小的请求粗估值 不能覆盖确认基线。压缩推进 epoch,load/resume 读取同一持久快照,未知值保持未知。
历史重放会为将来的 provider 请求保持 provider 推理胶囊持久,但当同一条消息已经包含它可见的推理摘要时,不会再渲染一份完全相同的胶囊副本。仅存在于 provider 侧的推理仍然可见,因此重放去重不会隐藏唯一可用的思考内容。
Shell 工具调用的标题是提交时的确切命令,而不是加了解释器前缀的伪命令。例如,Zed 收到的可复制标题是 git diff --check,而解析出的 zsh 身份单独通过 _meta.zuno.interpreter 提供。完成与历史重放保留同样的形态。
同一会话中的并发提示词
一个会话同一时刻只跑一个回合,但在回合运行期间发出的提示词绝不会被丢掉。Zuno 先把它提交到该会话的持久输入 inbox,然后才决定由谁来跑:
- 正常情况下它会被转向进正在运行的那个回合。模型在该回合的下一个安全点收到它 —— provider 请求之间,或在进行中的工具调用之后 —— 并在同一个回合里继续工作;
- 如果正在运行的回合在转向落地之前就结束了,这条提示词会持久地留在队列中,由该会话的下一个回合按接纳顺序提升。
标准 session/prompt 等待已接收输入的关联处理结果,不再用 busy 错误表示接收成功, 也不会在尚未处理时编造 stopReason。执行由原生会话持有,RPC 观察持久回执。 完成后返回合法 stopReason 与 _meta.zuno.receipt;接收、写入历史、进入模型、 执行完成是不同状态。要支持断线重试,可提交会话级客户端消息 ID:
{
"jsonrpc": "2.0",
"id": 4,
"method": "session/prompt",
"params": {
"sessionId": "ses_x",
"prompt": [{ "type": "text", "text": "Adjust the active work." }],
"_meta": { "zuno": { "messageId": "client-message-42" } }
}
}2
3
4
5
6
7
8
9
10
客户端 ID 为 1–256 字节。同一会话中,相同 ID 和内容只观察原输入,内容冲突则拒绝; 相同文本使用不同 ID 仍是独立输入。接收后的执行失败在错误数据中保留 receipt, 与真正拒绝区分。断线不会撤回已接收输入。需要立即接收回执的客户端使用带 expectedTurnId 的 session/steer。
斜杠命令不同。它要对宿主命令目录解析,并作为自己的回合运行,因此无法被转向进已经在飞的工作里。Zuno 用同一个错误码拒绝它,admission 为 "rejected",reason 为 "commandRequiresIdleSession",并且不写入任何持久内容;等会话空闲后重新发送即可。只有真正指名了某个命令、某个无歧义 Skill 或某个原生会话控制项的提示词才算命令调用。仅仅以 / 开头的提示词 —— 一个 POSIX 绝对路径、一个正则表达式 —— 是普通内容,因此会像其他提示词一样被持久接纳并转向,而不是被当成无法解析的命令拒绝。
取消按 RPC 请求身份键控,不按相同文本键控。$/cancel_request 只撤回该请求贡献且 尚未处理的输入,或在原子核对后取消它拥有且正在执行的原生输入。-32800 表示请求撤回, 不表示工具副作用已回滚;执行结果以持久 receipt 为准。它不能抹掉已进入模型的内容, 使用同一 messageId 的幂等观察者也不能撤回原贡献者的输入。响应后复用 JSON-RPC wire ID 会获得新的内部请求身份,旧取消状态不能继承到新请求,也不能取消无关的 Agent 到客户端 RPC。 观察者断开、显式撤回与 Zuno 进程退出是不同生命周期事件。
精确取消扩展
初始化响应通过 _meta.zuno.cancellation 宣告能力:
{
"version": 1,
"method": "session/cancel",
"expectedTurnIdPath": "_meta.zuno.expectedTurnId",
"turnIdSource": "session/update._meta.zuno.turnId",
"legacySessionIdOnly": "currentTargetAtDispatch",
"armsNextTurn": false
}2
3
4
5
6
7
8
客户端把实时 session/update 中的 params.update._meta.zuno.turnId 放到取消通知:
{
"jsonrpc": "2.0",
"method": "session/cancel",
"params": {
"sessionId": "ses_example",
"_meta": { "zuno": { "expectedTurnId": "turn_example" } }
}
}2
3
4
5
6
7
8
expectedTurnId 必须是非空字符串,最多 256 字节。原生注册表在同一把锁内核对身份并 发出取消。迟到的 T1 取消不会中断 T2;目标已结束、ID 不匹配或 exact metadata 非法时, 不会降级为取消当前回合。notification 没有响应 ID,拒绝信息写到 stderr;原 prompt 的 持久回执和更新报告真实结果。取消也不能证明远端副作用已撤销。
旧 sessionId-only 通知只在处理时绑定一次当前目标,空闲时没有效果,不会给下一回合 预置取消。它无法识别网络迟到意图:想取消 T1 的通知若在 T2 运行时到达,可能取消 T2。 只有携带精确身份的客户端才有更强的目标保证。后续输入仍走 prompt 准入或 session/steer, 不能通过取消后重发来模拟 steer。
被委派的子会话
普通的 task 工具始终是兼容界面。它的卡片显示 Agent、目标、状态,并在已知时显示子会话/job/模型/强度身份,同时保留原始工具详情。
Zuno 还支持经过评审的官方 codex-acp 适配器所使用的草案版原生子 Agent 投影。它只有在 ACP 客户端发送以下直接 initialize 能力时才启用:
"clientCapabilities": {
"subagents": {}
}2
3
协商成功后,前台委派会被按会话树路由:
- 父级收到
subagent_spawned; - 子会话获得自己的重放、提示词、消息、推理、工具、plan 和用量;
- 在子级输出排空之后,直接父级恰好收到一次终态的
subagent_state_update。
嵌套的前台子级使用它们直接的持久父级。历史子级树会在 session/load 时恢复,但它们的状态显示为 disconnected,因为一个重启后的进程无法证明旧工作仍然存活。针对子级的取消/关闭尚未公布。
后台委派刻意留在稳定的 task/job 生命周期上,即使已经协商了原生子 Agent 也是如此。关闭一个根会话只会取消并合入该根自己的后台 job,然后才释放它的运行时资源。
由子级发起的权限与提问只在原生模式下使用子会话 id。对于不支持原生子 Agent 的客户端,Zuno 会在已知的根会话上发送该请求,并在 _meta.zuno.childSessionId 中包含持久的子级 id;这既避免客户端收到一个未知的会话 id,又保留了归属信息。
ACP 提供的 MCP 公布 stdio 与 Streamable HTTP,SSE 仍不支持。每次 new/load/resume 都必须提供完整列表。名称会被校验或稳定 slug 化;stdio command 必须是绝对路径并使用会话目录作为 cwd;HTTP header 会被严格校验。load/resume 只校验并冻结该列表,不启动 transport。真正激活时,ACP 提供的 server 全部属于 required/eager 集合,必须连接并完成 discovery 才会原子发布;部分启动按逆序清理。宿主配置的 optional MCP 若命中完整连接身份一致的工具目录缓存,可以等到第一次真实工具调用再 singleflight 连接。command、environment 值与 header 永不写入 SQLite 或日志,也不会跨 session 共享 stdio 进程。客户端文件系统 RPC 和终端 RPC 仍不公布;Zuno 通过自己的工具、权限策略和沙箱处理文件与 Shell 工作。
恢复线程时,session/load 与 session/resume 默认执行冷恢复:恢复持久身份、配置、Plan 和客户端投影,但不启动 TurnHost、MCP、插件 host 或 watcher。同一 session id 的并发 load/resume 共享一个稳定 registry entry 与 activation gate。第一次 prompt、/start-work、session/set_mode(build),或 active 根 Goal 恢复才会精确激活一次;active Goal 不需要额外提示词,也不会制造假的 user message。加载重放受这些上限约束:最新 512 条保留消息、16 MiB 的已存储 part 与总投影预算,以及每次更新 8 MiB 的帧上限。当历史超出这些边界时,Zuno 会发出一条省略通知。已存储的 part blob 会先在 SQLite 中测量大小,再做 JSON 水合,因此一个过大的工具输出不会先被加载进进程内存然后丢弃。
历史文件引用不会仅因为它们是持久的就被信任。只有那些确实存在、且规范化后位于项目 worktree 内的普通文件,才仍然可作为 diff 路径、位置或本地资源链接使用。缺失的、外部的或通过符号链接逃逸的本地资源会显示为不可操作的说明文本。默认一个 ACP stdio 连接最多保留 32 个打开的会话,其中最多 8 个同时持有有资源的 runtime。满足条件的 runtime 空闲 15 分钟后会释放 TurnHost、MCP、插件进程与 watcher,但保留持久会话;active turn/Goal、queued/running/uncertain Job、未消费的 report/input、human request 与后台命令都会阻止休眠。容量不足时先休眠最久未使用的 eligible runtime,否则最多等待 30 秒并返回 typed retryable capacity error。session/close 会释放 open slot,并关停任何仍然激活的资源。这些默认值可通过 acp.runtime 调整。
9. 排障
Agent 启动失败
在终端里运行那条确切配置的命令:
/absolute/path/to/zuno acp --check检查该二进制是否可执行、它的配置/数据目录是否可写,以及它配置的 provider 能否被解析。使用绝对命令路径可以避免大多数 GUI 的 PATH 差异。
provider 或模型缺失
运行:
zuno debug config
zuno auth list
zuno models2
3
如果 Zed 条目使用了 ZUNO_CONFIG_DIR,运行这些命令时请使用同样的环境。项目特定的配置取决于在 Zed 中打开的文件夹。
协议或工具流格式错误
在 Zed 中运行:
dev: open acp logs若需临时的 Zuno 诊断,把参数改为:
"args": ["acp", "--print-logs", "--log-level", "DEBUG"]--print-logs 把诊断写到 stderr。它不会把日志放到 ACP 的 stdout 上。诊断结束后请移除详细日志。
反复打开工作区会恢复一个旧线程或占用 CPU
关闭或隐藏 Zed 的 Agent 面板并不一定会发送 session/close。Zed 可能在后台保持它的外部 Agent 进程与工作区线程选择存活。
当前的 Zuno 版本会为每个打开的 session 保留一个稳定进程内 entry;重复 load/resume 共享它,并且除非存在 active Goal,否则保持冷态。加载重放有界且显式,过期的可操作文件路径会被过滤;默认一个连接可打开 32 个 session,但只激活 8 套有资源的 runtime。恢复后立刻出现 provider 或工具活动表示 active Goal 在续跑,而不是面板渲染启动了宿主。
如果问题仍然存在:
- 运行
dev: open acp logs,确认 Zed 是否在反复发出session/load或重新打开同一个会话; - 关闭那个外部 Agent 线程,而不只是关闭面板;或者停止并重启已配置的 Agent server,让它的 stdio 进程达到 EOF;
- 如果 Zed 在重启后立刻又选中同一个已知有问题的线程,请在备份 Zed 状态之后,按所安装 Zed 版本的维护流程清除该工作区最后一个活跃的 Agent 线程关联;
- 单独检查 Zed 日志中反复出现的 worktree、watcher 或
OpenBufferByPath活动。Zuno 不拥有也不移除由 Zed 创建的 worktree 与文件系统 watcher。
满足休眠条件的空闲 runtime 会自动休眠,但持久会话仍保持打开。若内存或子进程占用没有下降,请检查 active Goal、Job、pending inbox/human request 与后台命令;这些状态会有意阻止休眠。
Agent 或模型选择器不见了
先确认 Zed 已成功连接,然后创建一个新的外部 Agent 线程。运行 zuno acp --check 验证生产适配器,并检查 ACP 日志中的初始化或会话创建错误。
一个 Kiro 提示词以 unsupported_content_block_projection 失败
2026-08-28 的 kiro-provider 构建接受连续的全文本块,并且仅在 Kiro 最终的标量文本边界处逐字节拼接、不插入任何分隔符。请使用:
"retry": {
"max_attempts": 3,
"recovery_window_ms": 660000
},
"options": {
"baseURL": "http://127.0.0.1:8787/v1",
"timeout": false,
"headerTimeout": 330000,
"chunkTimeout": 330000,
"reasoningReplay": "encrypted",
"reasoningReplayMaxAge": 86400000
}2
3
4
5
6
7
8
9
10
11
12
省略 maxTokens(或将其留为 null)时,Zuno 使用逐模型的 limit.output;只有非空值才覆盖模型声明。
移除过期的 responsesTextBlocks: "single" 选项:Zuno 的通用兼容模式会插入一个空行,那会改变当前 provider 的确切投影。文本与非文本块混合、且 Kiro 无法保留其顺序时,仍然会失败即拒绝。如果纯文本仍然产生旧的错误,请确认 Zed 确实连到了新构建的 provider 进程。
chunkTimeout 刻意超过 kiro-provider 配置的 300 秒流空闲超时。网关返回类型化失败后 recovery window 才开始计时,因此仍有空间执行两次有界替代尝试。
reasoningReplay: "encrypted" 让这条路由启用封装推理重放。ACP 会话最需要它,因为编辑器会驱动很长的多步骤轮次:没有它时网关会在每个请求上报告 reasoning_replay_locked: false,每个步骤都会在缺少上一步推理的情况下开始。包住这些选项的 provider 条目必须声明 "transport": "openai" 与 "surface": "responses",否则配置会带着出错键路径被拒绝:它的端点来自 baseURL,本身会解析成 Chat Completions。可以从 session.provider.request 事件的 replayedReasoningCapsules 确认,从一个会话的第二个请求起它就会大于零。
kiro-provider v0.5.0 及以后版本还会区分可重试的流失败与致命的协议失败。Zuno 只重试 upstream_stream_error、upstream_stream_incomplete、upstream_stream_idle_timeout、malformed_upstream_tool_arguments 和 request_deadline_exceeded;旧式的通用 upstream_error 不足以授权重试。provider 只能在模型已经完成工具调用、但参数载荷不是合法 JSON 时使用 malformed_upstream_tool_arguments;工具调用的结构性错误仍须使用永久失败的 invalid_upstream_tool_call。每次调用都记录在 session.provider.attempt.1 之下,并复用同一份持久会话亲和性。由于 ACP 无法撤回一个已追加的消息块,Zuno 只在该次尝试被打上检查点之后才提交 provider 文本、推理和待处理的工具行。一次失败的部分尝试会被丢弃,而不是与它的替代者拼接在一起。
10. 验收检查
配置完成之后:
- 在 Zed 中打开一个真实的项目文件夹并创建一个 Zuno Agent 线程;
- 选择
deep、目标模型,以及xhigh或max,然后确认该选择显示在会话控制项中; - 输入
/,确认/compact、/goal、/plan、/start-plan和/start-work各出现且仅出现一次; - 执行
/goal 验证 ACP 简写、/goal edit 验证 ACP action和/goal show,确认直接目标与显式 action 都作为 Agent 输出出现,而不是作为 Thinking; - 执行
/start-plan,确认 Zed 切换到 Plan,然后创建一个持久 plan 并执行/start-work; - 在积累了足够的对话历史之后,执行
/compact并确认摘要能在一次会话重新加载后存活; - 执行一个已配置的命令或一个无歧义的 Skill;
- 附加一张图像、一段选区和一份分支 diff,确认它们到达了该回合;
- 发送一个只读的仓库问题,确认已提交的推理、答案和待处理工具行各出现一次;注入一次可重试的流失败,确认失败的部分尝试不存在;
- 委派一个前台子级,并根据客户端能力确认得到的是协商后的子会话流,或者完整的稳定 task 卡片;
- 委派一个后台子级,关闭根线程,确认该 job 被取消且没有前台原生子级流;
- 在 ask 策略下请求一次文件编辑,确认 Zed 在权限请求、执行中和完成后均显示文件名, 位置由标准
locations提供,并保留类型化 diff; - 取消一个正在运行的提示词,确认会话回到空闲;
- 关闭并重新加载该会话,确认内容、question/task 卡片、子级历史、工具、plan 和用量都被重放且仅一次;
- 再次加载同一个已打开的会话,确认对话记录没有被重复。
仓库级的 ACP 验证是:
cargo test -p zuno-acp
cargo test -p zuno --test acp_stdio2