跳到主要内容

数据截至 (上游 commit efde31963f6f)

第 4 章 · 面向 agent 的接口与可扩展性(附边界与局限)

前面三章讲的是「人 + 多设备」。本章讲「程序」怎么用上 dinotty:REST agent API、MCP server、插件系统、webhook,以及最后诚实的边界清单。


4.1 为什么 dinotty 适合给 agent 当「终端面」

前几章的两个机制,正好是程序化控制终端最需要的:

  • 服务端屏幕(第 1 章):程序随时能拿到「终端现在显示什么」的纯文本,不用自己解析字节流。
  • OSC 133 命令检测(第 1 章 1.6):程序发一条命令后,能可靠地等到「它跑完了 + 退出码」,而不是猜 sleep 多久。

REST agent API 和 MCP server 都是这两件事的直接封装。


4.2 REST agent API:run / send / read

三个端点(src/main.rs:525-527),都在 src/agent.rs:

端点处理器语义
POST /api/sessions/:pane_id/runsessions_run(src/agent.rs:203)发命令并等它跑完,返回退出码 + stdout
POST /api/sessions/:pane_id/sendsessions_send(src/agent.rs:405)只发命令,立即返回,不等结果
GET /api/sessions/:pane_id/readsessions_read(src/agent.rs:463)读当前屏幕(+可选滚动历史)的纯文本

门控:双重检查

sessions_run 开头有两道闸(src/agent.rs:208-227):

  1. 设置项 open_api.enabled 必须打开,否则 403 CAPABILITY_DENIED(src/agent.rs:210-218)。
  2. token 必须有 terminal:write capability(read 端点要 terminal:read,src/agent.rs:479)。

capability 的判定在 TokenInfo::has_capability:全局 token 全通,否则看集合里有没有这一项(src/token.rs:72-74);全部能力名枚举在 ALL_CAPABILITIES(src/token.rs:91-102,含 terminal:read/write/create/killworkspace:*plugin:exec 等)。这条合并路由挂的是专用的 sessions_token_middleware,支持「会话 cookie / 全局 Bearer / agent Bearer」三轨认证(src/main.rs:520-544)。

并发与超时:明确的数字

  • 并发上限:MAX_CONCURRENT_RUNS = 10(src/agent.rs:33),超过返回 429 RATE_LIMITED 并带 Retry-After: 5(src/agent.rs:248-260)。
  • 超时:默认 5 分钟(DEFAULT_TIMEOUT_MS = 300_000,src/agent.rs:35),请求可指定,上限 1 小时(MAX_TIMEOUT_MS = 3_600_000,src/agent.rs:37);超限直接 400(src/agent.rs:231-237)。

run 的内部:借 OSC 133 等命令结束

execute_command(src/agent.rs:294-401)的流程:

  1. begin_command_tracking 让 VirtualScreen 开始收集输出,把命令加换行写进 PTY(src/agent.rs:314-321)。
  2. 每 50ms 轮询一次屏幕:有 OSC 133 产生的 CommandResult 就取输出返回(src/agent.rs:342-361);没有 shell 集成时退化为「100ms 静默 + 提示符正则」检测(src/agent.rs:363-380)。
  3. 到超时则 finish_command_tracking 强制收尾,返回已收集的部分输出,method 记为 "timeout",退出码 -1(src/agent.rs:383-399)。

响应里的 method 字段(AgentRunResponse,src/agent.rs:73-80)如实告诉调用方结果是怎么判出来的:shell_integration / prompt_detection / timeout

read 端点直接复用第 1 章的纯文本快照:snapshot_plain() 读当前屏,snapshot_scrollback_plain 读历史(上限 10000 行),外加光标位置和 cwd(src/agent.rs:496-519)——这是「服务端屏幕」红利最直接的体现。

此外还有一条 WebSocket /ws/events:推送事件总线上的事件,并接受 run / subscribe / ping 消息,适合长连接 agent(src/agent.rs:539-688)。


4.3 MCP server:同一能力的工具化封装

dinotty 内嵌一个 MCP server(McpServer,src/mcp/server.rs:19-24,server name 就是 "dinotty"),走 SSE 传输:/mcp/sse + /mcp/message 两个端点(src/main.rs:536-537,处理器在 src/mcp/transport.rs:103src/mcp/transport.rs:125)。

McpTools::list_tools(src/mcp/tools.rs:44)暴露 9 个工具:

工具干什么
terminal_execute执行 shell 命令并等结束(带 cwd/timeout 参数)
terminal_read / terminal_send / terminal_list读屏 / 发输入 / 列会话
file_read / file_write / file_list工作区文件读写列
git_status / git_diff工作区 git 状态与 diff

工具标注也如实填写:例如 terminal_executedestructiveHint: trueopenWorldHint: true(src/mcp/tools.rs:47-63)。这意味着任何 MCP 客户端(比如另一个 AI agent)可以把 dinotty 当成「远程终端 + 工作区」工具集直接调用。


4.4 插件系统:JS 插件 + 宿主侧能力面

插件放在 ~/.dinotty/plugins,数据在 ~/.dinotty/plugin-data(PluginManager::new,src/plugin/manager.rs:83-84)。每个插件一个 plugin.json 清单(PluginManifest,src/plugin/types.rs:9-33),声明 id、版本、入口、permissions(权限列表)、events(订阅的事件)、bin(可选 CLI 二进制)等。

宿主侧给插件的能力面,从路由就能看出规模(src/main.rs:478-519):

  • 执行:/api/plugins/:id/exec 以子进程方式跑插件的 CLI bin,默认 30 秒超时、超时返回部分结果(plugin_exec,src/plugin/exec/mod.rs:103-161);还有受管进程 start/stop/list。
  • 存储:per-plugin 的 key-value storage。
  • 工作区文件:readDir/readFile/put/mkdir/delete/rename/move 一整套。
  • 事件订阅:subscribe/unsubscribe,插件可以监听 tab、会话、命令结束等事件。
  • crypto:hash / HMAC 代算。

热重载:PluginManager::watch_changes(src/plugin/manager.rs:355)监听插件目录,文件变化时按 plugin id 做增量重载,在启动时挂到事件上(src/main.rs:364)。


4.5 webhook:把事件外发给第三方

WebhookDispatcher(src/webhook.rs:80-178)订阅事件总线,把匹配的事件以 fire-and-forget 的 HTTP POST 发出去:

  • 可订阅的事件名包括 command_finishedsession_created/closedtab_created/closedfile_changedauth_login_failednotifyplugin_changed 等,也可用 * 全收(dispatch,src/webhook.rs:102-125)。
  • 配了 secret 时用 HMAC-SHA256 给 payload 签名,放进 X-Dinotty-Signature 头(src/webhook.rs:136-152);secret 单独存在 secrets.json(src/webhook.rs:38-61)。

⚠️ 一个诚实的现状:派发器本身完整,但 main.rs 目前装配的是空配置列表——注释写明「Webhook configs will be added to settings later; for now empty」(src/main.rs:308-312)。也就是说 as-of 这个 commit,webhook 通道已就绪但还没有配置入口。


4.6 边界与局限(诚实清单)

定位边界:

  • dinotty 不是 coding agent。它不内置任何 LLM 调用;Claude Code、opencode 等是它 PTY 里跑的普通进程(README 定位,「为 Coding Agent 场景打造的终端」,README.md:23-25)。它提供的是「跑 agent 的地方 + 观察/驱动 agent 的面」。

持久化边界:

  • 服务端重启 ≠ 进程复活。重启恢复的是「布局 + 新 shell + 原 cwd」,原来 shell 里跑的 agent 进程回不来(恢复快照只存布局与 cwd,src/session/restore.rs:51-105)。「断网不丢」指网络断开时 PTY 存活,不是服务端进程重启后的进程级恢复。
  • SSH tab 不参与重启恢复,需手动重连(src/session/restore.rs:28-29);且恢复上限 20 个 tab(src/session/restore.rs:21)。

协议/实现边界:

  • 重连快照存在已知的极小重影窗口:快照生成前已在 output_rx 里排队的字节,可能在快照之后再被广播,造成短暂重复绘制;代码注释明说完整去重需要 seq 系统,当前版本接受这个小概率瑕疵(src/session/mod.rs:449-451)。
  • agent run 的 stderr 恒为空串:AgentRunResponse 有 stderr 字段,但 execute_command 三条返回路径都填 String::new()(src/agent.rs:355)——PTY 本身合流 stdout/stderr,要区分得自行在命令里重定向。
  • prompt_detection 兜底不等于可靠:没有 shell 集成时,命令结束靠「100ms 静默 + 提示符正则」猜(src/vt_screen/screen.rs:79-136),对长静默任务可能误判。
  • 收割宽限 60s 内,无主会话仍占资源:这是防误杀的代价(第 2 章 2.3)。
  • webhook 尚无配置入口(见 4.5)。

4.7 本章小结

  • REST run/send/read = 服务端屏幕 + OSC 133 的直接封装;capability token 门控,并发 10、超时 5 分钟~1 小时。
  • MCP server 把同样能力包成 9 个工具,任何 MCP 客户端可用。
  • 插件系统提供执行/存储/工作区/事件订阅一整套宿主 API,支持热重载;webhook 派发器就绪但配置入口未接线。
  • 记住定位:dinotty 是 agent 的「终端基础设施」,不是 agent 本身;其持久化承诺覆盖断网,不覆盖服务端重启后的进程级恢复。

4.8 代码地图

主题文件路径符号名
run/send/readsrc/agent.rssessions_runsessions_sendsessions_readexecute_command
并发/超时参数src/agent.rsMAX_CONCURRENT_RUNSDEFAULT_TIMEOUT_MSMAX_TIMEOUT_MS
capabilitysrc/token.rsTokenInfo::has_capabilityALL_CAPABILITIES
agent 事件 WSsrc/agent.rshandle_agent_ws
MCP serversrc/mcp/server.rssrc/mcp/tools.rssrc/mcp/transport.rsMcpServerMcpTools::list_toolsmcp_sse_handler
插件管理src/plugin/manager.rsPluginManagerscanwatch_changes
插件清单src/plugin/types.rsPluginManifestBinConfig
插件执行src/plugin/exec/mod.rsplugin_exec
webhooksrc/webhook.rsWebhookDispatcherdispatchhmac_sha256
路由总装src/main.rsagent/MCP 合并路由(523-545 行)、插件路由(478-519 行)