私有部署与观测

ACP

千手大师实现了完整的 ACP(Agent Client Protocol)agent 端,可被 Zed、Cursor 等支持该协议的编辑器通过 stdin/stdout 的 NDJSON 流直接驱动。 需要远程或浏览器接入时,再用 acp-link 代理服务器把 WebSocket 客户端桥接到 ACP agent。

  • Zed / Cursor
  • acp-link
  • 会话恢复

这是什么

ACP(Agent Client Protocol)是一种标准化的 stdio 协议:编辑器 / IDE 作为客户端, 通过进程的 stdin / stdout 收发 NDJSON 消息来驱动一个 AI agent。 千手大师实现了完整的 agent 端,因此任何支持 ACP 的客户端(Zed、Cursor 等)都能直接调用它。

入口是一条快速路径:当首个参数为 --acp 时,CLI 跳过完整 REPL 启动流程, 直接加载 runAcpAgent()。读取端把 Node 流包成 ACP 的 Stream, 再用 AgentSideConnection 驱动 AcpAgent 实现。

文件 职责
src/services/acp/entry.ts 入口:创建 stdio → NDJSON 流,启动 AgentSideConnection;把 console 重定向到 stderr
src/services/acp/agent.ts Barrel,装配 AcpAgent 类(拆分为 agent/ 子模块)
src/services/acp/bridge.ts 把内部 SDKMessage 转换为 ACP SessionUpdate(文本 / 思考 / 工具 / 用量 / 编辑 diff)
src/services/acp/permissions.ts 权限桥接:ACP requestPermission() ↔ 内部 CanUseToolFn
src/services/acp/utils.ts Pushable / 流转换 / 权限模式解析 / session fingerprint / 路径显示

AcpAgent 的角色是 ACP Agent 接口的实现体,负责会话 CRUD (新建 / 恢复 / 加载 / 分叉 / 关闭 / 列出)、发送 prompt、取消、以及运行时切换权限模式与模型。 它在会话内部复用千手大师的 QueryEngine 完成真正的对话执行。

i
特性开关

ACP 由 feature flag ACP 控制(build 与 dev 模式默认启用)。 该 flag 未启用时,--acp 快速路径不会生效。

前置条件

  • 已在 Zed、Cursor 或自建 ACP 客户端中配置好以子进程方式启动 qsdashi --acp 的能力。
  • 已通过 /login 配置好 API 供应商,或准备了 QS_BASE_URL / QS_AUTH_TOKEN 等凭证。
  • 运行 ACP agent 的机器上已安装千手大师(qsdashi,英文命令别名 qs-master)。
  • 使用 acp-link 远程接入时,需能运行仓库内 packages/acp-link 或已安装其 acp-link 可执行文件。

ACP agent 启动时会调用 applySafeConfigEnvironmentVariables(), 自动把 settings.json 中的环境变量(QS_BASE_URL、QS_AUTH_TOKEN、模型覆盖等) 注入 process.env。这一点很关键:由 Zed 拉起的进程默认拿不到这些变量。

安装与启用

1直接以 ACP 模式启动

标准输入输出即协议通道,不要在其中混入其他输出。

Terminal
qsdashi --acp

2在 Zed 中注册为 Agent Server

编辑 Zed 的 settings.json(Cmd+, → Open Settings)。

Zed settings.json
{
  "agent_servers": {
    "qsdashi": {
      "type": "custom",
      "command": "qsdashi",
      "args": ["--acp"]
    }
  }
}

3可选:用 acp-link 提供 WebSocket 入口

让远程 / 浏览器客户端也能接入同一个 ACP agent。

Terminal
# 源码目录内运行(monorepo)
bun packages/acp-link/src/cli/bin.ts qsdashi -- --acp

# 或使用安装后的 acp-link 可执行文件,指定端口与主机
acp-link --port 9000 --host 0.0.0.0 qsdashi -- --acp
*
参数分隔:--

acp-link 之后的第一个位置参数是要启动的 agent 命令,再往后的参数需用 -- 分隔才能传给 agent 本身。例如 acp-link qsdashi -- --acp。

配置

acp-link 命令行参数

参数 默认值 说明
--port 9315 监听端口
--host localhost 绑定主机;远程访问用 0.0.0.0
--debug 关闭 开启调试日志并写入文件
--no-auth 关闭 关闭认证(危险,仅开发用)
--https 关闭 使用自动生成的自签名证书启用 HTTPS
--manager 关闭 仅启动管理 Web UI,不启动代理;浏览器访问 http://localhost:<port>
--group 无 RCS 注册用的频道组 ID(仅字母、数字、连字符、下划线)

环境变量

变量 作用域 说明
ACP_AUTH_TOKEN acp-link 固定认证 token;未设置时启动会随机生成
ACP_PERMISSION_MODE acp-link / agent 默认权限模式的兜底来源
ACP_RCS_URL acp-link RCS 服务器地址,设置后启用 RCS 集成
ACP_RCS_TOKEN acp-link RCS API token
ACP_RCS_GROUP acp-link RCS 频道组 ID;--group 优先于该变量

权限模式与 fallback 链

支持的权限模式:default、auto、acceptEdits、plan、dontAsk、bypassPermissions。当客户端未显式传入 permissionMode 时,按以下顺序兜底:

fallback 优先级
# 客户端传值 > config.permissionMode > ACP_PERMISSION_MODE 环境变量
ACP_PERMISSION_MODE=auto acp-link qsdashi -- --acp
!
bypassPermissions 需要双重满足

在 acp-link 层,客户端要请求 bypassPermissions,必须先令 ACP_PERMISSION_MODE=bypassPermissions,否则会报 bypassPermissions requires local ACP_PERMISSION_MODE=bypassPermissions。 在 agent 层,该模式还会做可用性检测,仅在非 root 或 sandbox 环境中启用。

认证传递

默认连接端点为 ws://localhost:9315/ws,token 不放在 URL 中。 无法发送 Authorization 头的 WebSocket 客户端,需通过 rcs.auth.<base64url-token> 子协议传递 token。

常用命令与参数

命令 行为
qsdashi --acp 以 ACP agent 模式启动,stdin/stdout 走 NDJSON
acp-link qsdashi -- --acp 启动 WebSocket → stdio 代理(默认 localhost:9315)
acp-link --https qsdashi -- --acp 启用自签名 HTTPS
acp-link --manager 仅启动 Manager Web UI,可创建 / 停止 / 删除多个 acp-link 实例并查看日志

ACP 协议方法支持

方法 说明
initialize返回 agent 信息与能力
authenticate自托管场景无需认证
newSession / resumeSession / loadSession新建 / 恢复 / 加载会话,后两者含历史回放
listSessions / forkSession / closeSession列出 / 分叉 / 关闭会话
prompt / cancel发送消息(支持排队)与取消
setSessionMode / setSessionModel / setSessionConfigOption运行时切换权限模式 / 模型 / 配置

SessionUpdate 类型(节选):agent_message_chunk、agent_thought_chunk、user_message_chunk(历史回放)、tool_call、tool_call_update、usage_update、plan、available_commands_update、current_mode_update、config_option_update。

实战示例

带环境变量在 Zed 中启动(进程由 Zed 拉起、拿不到 shell 环境时):

Zed settings.json
{
  "agent_servers": {
    "qsdashi": {
      "command": "qsdashi",
      "args": ["--acp"],
      "env": {
        "QS_BASE_URL": "https://api.example.com/v1",
        "QS_AUTH_TOKEN": "sk-xxx"
      }
    }
  }
}

接入 RCS,通过 Web UI 远程操控:

Terminal
ACP_RCS_URL=http://localhost:3000 \
ACP_RCS_TOKEN=sk-rcs-your-key \
acp-link qsdashi -- --acp

注册分两步:先通过 REST POST /v1/environments/bridge 向 RCS 注册环境并拿到 agentId,再建立 WebSocket 后发送 identify 消息(携带 agentId)完成标识。RCS 的 ACP WebSocket 不接受 URL query token, acp-link 会用 rcs.auth.<base64url-token> 子协议发送 ACP_RCS_TOKEN。

Plan 可视化:当会话使用 TodoWrite 时,bridge 会把待办映射为 ACP 的 session/update 中 sessionUpdate: "plan" 消息, 每个条目带 content、归一化后的 status 与 priority。 支持 ACP Plan 视图的客户端会直接渲染为带进度与状态图标的清单。

自建 ACP 客户端(用 @agentclientprotocol/sdk):

client.ts
import { ClientSideConnection, ndJsonStream } from '@agentclientprotocol/sdk'

// 将 qsdashi --acp 作为子进程启动,接通 stdio
const child = spawn('qsdashi', ['--acp'])
const stream = ndJsonStream(
  Writable.toWeb(child.stdin),
  Readable.toWeb(child.stdout),
)
const client = new ClientSideConnection(stream)

await client.initialize({ clientCapabilities: {} })
const { sessionId } = await client.newSession({ cwd: '/path/to/project' })
await client.prompt({
  sessionId,
  prompt: [{ type: 'text', text: '解释一下这个项目' }],
})

常见问题排错

!
Zed 拉起的进程未认证 / 连不上 API

由 IDE 拉起的进程默认没有 shell 里的环境变量。ACP 启动时会读取 settings.json 中的环境变量,因此先确保已通过 /login 配置; 或在 agent_servers 的 env 中显式传入 QS_BASE_URL / QS_AUTH_TOKEN。

!
WebSocket 连不上 acp-link

token 不能放在 URL 中。能发 header 的客户端请用 Authorization; 否则通过 rcs.auth.<base64url-token> 子协议传递。绑定远程地址时记得 --host 0.0.0.0,并优先用 ACP_AUTH_TOKEN 固定 token 而非 --no-auth。

!
请求 bypassPermissions 被拒

这属于安全设计。acp-link 侧需要 ACP_PERMISSION_MODE=bypassPermissions; agent 侧还需要非 root 或 sandbox 环境。两个条件缺一不可。

!
stdout 被其他输出污染

ACP 用 stdout 传协议消息。agent 启动时已把 console.log/info/warn/debug 重定向到 stderr;自定义代码也不要往 stdout 打印。否则 NDJSON 流会被破坏。

*
端口冲突

默认端口为 9315。被占用时用 --port 指定其他端口即可。