Fromlan
dsh-godot-tool
Drive the Godot 4.x editor from an AI agent: Godot agent_rpc addon + DeepSeek Harness dsh-tool-godot plugin (loopback TCP JSON-lines bridge, 27 godot_* tools)
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
Godot Agent RPC —— 让 AI agent 驱动 Godot 编辑器
English | 中文
一对插件,让 AI agent 通过回环 TCP JSON-lines 驱动 Godot 4.x 编辑器:打开/重新加载场景、运行/停止当前或主场景、观察播放错误、检查场景树和脚本、设置项目设置、lint GDScript、列出项目文件以及导出构建。
| 路径 | 内容 | 运行于 |
|---|---|---|
addons/agent_rpc/ | Godot 编辑器插件(EditorPlugin + EditorDebuggerPlugin)——TCP 客户端、消息分发、播放错误环形缓冲 | Godot 编辑器进程 |
dsh-godot-tool/ | DeepSeek Harness 插件(@deepseek-ai/dsh-godot-tool)——回环 TCP 服务端(GodotRpcBridge)+ 27 个 godot_* 面向模型工具 | DeepSeek Harness |
两半使用同一种线上协议:TCP 127.0.0.1:8765(回退 8765–8774)上的换行分隔 JSON、令牌认证握手、仅回环。本文档是唯一参考——先快速开始,再讲线上协议,然后是两半各自的手册。
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ Godot 4.x 编辑器 │ TCP │ DeepSeek Harness │
│ │ JSON-L │ │
│ addons/agent_rpc(客户端) │◄────────►│ dsh-godot-tool(服务端) │
│ EditorPlugin + 调试器 │ 8765 │ GodotRpcBridge + 27 工具 │
└──────────────┬──────────────┘ └──────────────┬───────────────┘
│ 每秒轮询端点文件 │ 发布
▼ ▼
~/.pi/agent/x-agent-godot-rpc.json ~/.dsh/godot/endpoint.json
(插件默认) (harness 插件默认)
目录
快速开始
1. 安装 Godot 插件
把 addons/agent_rpc/ 复制到 Godot 项目的 addons/ 文件夹,然后在 项目设置 → 插件 中启用 Agent RPC。
2. 安装 harness 插件
两种方式:
- 从本仓库(源码):通过
--patchoverlay 或项目插件行把 Harness 指向dsh-godot-tool/src/index.ts,并在组合中加入dsh-tools。 - 从 harness workspace:如果你在 deepseek-harness 仓库内构建,包位于
packages/extensions/tool-godot(@deepseek-ai/dsh-godot-tool);本副本与其保持同步。
3. 让插件指向 harness 端点
插件每秒轮询端点文件。默认是 ~/.pi/agent/x-agent-godot-rpc.json;harness 插件默认发布 $DSH_HOME/godot/endpoint.json。把一边指向另一边:
- 设置环境变量
AGENT_RPC_ENDPOINT(推荐),或 - 设置 ProjectSettings 键
agent_rpc/endpoint_file,或 - 把 harness 插件的
endpointPath配置为插件的文件路径。
4. 启用你想要的工具
每个 godot_* 工具默认关闭(harness 插件配置中 enabledTools 为空)。显式选择启用:
# dsh cordis.yml
- id: tool-godot
name: '@deepseek-ai/dsh-godot-tool'
config:
enabledTools: ['godot_editor_info', 'godot_run_scene', 'godot_play_errors']
然后启动 harness、打开 Godot 项目,agent 就可以驱动编辑器了。
5. 验证连接
- 桥开始监听后约 1 秒内,插件会在 Godot Output 面板打印
Agent RPC: connected to …。 - 从 agent 一侧调用
godot_editor_info——端到端通路正常时返回{ godotVersion, projectPath, editedScene, playing }。若失败,对照故障排查表检查。
工作原理
两个进程、一个回环 socket、一个把它们连起来的端点文件:
- 先启动 harness(或先启动 Godot——两种顺序都行)。
dsh-godot-tool插件绑定127.0.0.1:8765(忙时向上尝试至8774),生成 32 位十六进制令牌,并原子写入端点文件{ host, port, token }供插件发现。 - 插件每秒轮询该文件,文件变化或消失时自动重连。文件按
AGENT_RPC_ENDPOINT→agent_rpc/endpoint_file→ 旧默认~/.pi/...的顺序解析。 - 首次 TCP 连接时插件发送
editor_ready,携带令牌和addonVersion;桥认证后记录其projectPath并分配clientId。 - agent 调用
godot_*工具 → 桥校验(白名单、工具闸、参数卫生、跨项目路由)→ 发送 JSON 行请求 → 插件对编辑器执行操作 → 以ok:true/ok:false应答 → 桥解析工具调用结果。 - 播放期间插件推送
play_error事件进入桥的环形缓冲(上限 50);godot_play_errors读取它们。插件绝不会自动停止播放——由 agent 调用godot_stop_scene。
线上协议
锁定插件 0.6.3、协议世代 1.0 + 1.2 + 1.3(27 个 RPC 方法)。改动任何一半时,请在同一提交内更新本节。
传输
| 属性 | 值 |
|---|---|
| 地址 | 仅回环 —— 127.0.0.1 |
| 端口 | 8765,忙时回退到 8765–8774 |
| 编码 | UTF-8、换行分隔 JSON(每行一个对象,无长度前缀,无二进制) |
| 认证 | 插件在 editor_ready 时出示的 32 位十六进制令牌 |
消息形态
// 请求 —— 服务端 → 插件
type GodotRpcRequest = {
id: string; // 调用方的 randomUUID
method: string; // 见下方名单
...params // 方法专属参数
};
// 响应 —— 插件 → 服务端(按 id 配对)
type GodotRpcResponse =
| { id: string; ok: true; result: unknown; routedTo?: string }
| { id: string; ok: false; error: string; routedTo?: string };
// 事件 —— 插件 → 服务端(无 id)
type GodotRpcEvent =
| { type: "editor_ready"; godotVersion: string; projectPath: string;
token?: string; addonVersion?: string; clientId?: string }
| { type: "scene_changed"; path: string; clientId?: string }
| { type: "play_error"; severity: string; message: string; clientId?: string }
| { type: "disconnected"; clientId?: string };
分帧规则(0.6.3 修复):用 data.has("method") 区分请求与事件,绝不要用 data.has("type")。list_project_files 接受 type 参数用于过滤,同时携带 method 和 type 的请求会被旧启发式静默丢弃。
端点文件
服务端把 { host, port, token } 发布到插件每秒轮询的 JSON 文件(plugin.gd 中的 _endpoint_config_path());文件变化时插件自动重连。
| 字段 | 类型 | 说明 |
|---|---|---|
host | string | 复用时必须是 127.0.0.1 或 localhost;其他值被拒绝并重新生成令牌 |
port | number | 1–65535;超出范围则回退 |
token | string | 32 位十六进制(/^[0-9a-f]{32}$/i);不匹配则回退 |
version | number | 线路格式版本;当前 = 1 |
updatedAt | string | 每次成功监听时写入的 ISO 时间戳 |
文件原子写入(tmp + 重命名),服务端停止时故意不删除,以便下次启动复用令牌、跳过握手抖动。
方法名单(27 个 RPC + ping)
| 工具(启用时) | RPC 方法 | 协议世代 |
|---|---|---|
godot_editor_info | get_editor_info | 1.0 |
godot_open_scenes | get_open_scenes | 1.0 |
godot_edited_scene | get_edited_scene | 1.0 |
godot_open_scene | open_scene | 1.0 |
godot_reload_scene | reload_scene | 1.0 |
godot_run_scene | run_current_scene(+ wait_ms) | 1.0 |
godot_run_main_scene | play_main_scene(+ wait_ms,≡ F5) | 1.0 |
godot_import_resources | import_resources(+ 可选 paths) | 1.0 |
godot_play_errors | get_play_errors(+ 可选 clear) | 1.0 |
godot_stop_scene | stop_scene | 1.0 |
godot_get_scene_tree | get_scene_tree(+ max_depth) | 1.0 |
godot_get_node_properties | get_node_properties | 1.0 |
godot_get_debugger_state | get_debugger_state | 1.2 |
godot_set_breakpoint | set_breakpoint(+ condition?、remove?) | 1.2 |
godot_find_unused_resources | find_unused_resources(+ root?) | 1.2 |
godot_get_project_setting | get_project_setting | 1.2 |
godot_set_project_setting | set_project_setting | 1.2 |
godot_lint_scripts | lint_scripts | 1.2 |
godot_export_project | export_project(+ preset、output_dir、debug?) | 1.2 |
godot_list_project_files | list_project_files(+ type?、pattern?、limit?、cursor?) | 1.3 |
godot_resolve_uid | resolve_uid(uid? 异或 path?) | 1.3 |
godot_wait_for_import_done | wait_for_import_done(+ timeout_ms?) | 1.3 |
godot_list_global_classes | list_global_classes | 1.3 |
godot_find_class_name_conflicts | find_class_name_conflicts(+ include_addons?) | 1.3 |
godot_inspect_script | inspect_script | 1.3 |
godot_list_export_presets | list_export_presets | 1.3 |
godot_check_export_templates | check_export_templates | 1.3 |
ping 是协议级健康检查,没有对应的用户工具。白名单位于 dsh-godot-tool/src/protocol.ts(GODOT_RPC_ALLOWED_METHODS),工具到方法的门控映射在 GODOT_RPC_METHOD_TOOL;白名单之外的请求在到达桥之前即被拒绝。
超时阶梯
| 常量 | 值 | 用途 |
|---|---|---|
GODOT_RPC_DEFAULT_PORT | 8765 | 监听 |
GODOT_RPC_FALLBACK_PORT_END | 8774 | 监听回退 |
GODOT_RPC_DEFAULT_WAIT_MS | 3000 | 播放错误窗口 |
GODOT_RPC_MAX_WAIT_MS | 15000 | 播放错误窗口上限 |
GODOT_RPC_BASE_TIMEOUT_MS | 8000 | 默认请求超时 |
GODOT_RPC_EXPORT_TIMEOUT_MS | 5 × 60 000 | export_project 硬杀 |
GODOT_RPC_EXPORT_GRACE_MS | 15 000 | 导出返回后的宽限 |
GODOT_RPC_GRACE_PERIOD_MS | 8000 | 断连宽限窗口 |
GODOT_LIST_FILES_DEFAULT_LIMIT / MAX_LIMIT | 500 / 5000 | list_project_files 分页 |
GODOT_WAIT_DEFAULT_TIMEOUT_MS / MAX | 30 000 / 60 000 | wait_for_import_done |
播放错误收集
run_current_scene / play_main_scene 清空缓冲、开始播放,并在可配置窗口(默认约 3 秒,上限 15 秒)后返回目前捕获到的错误:
{
"started": true,
"playing": true,
"waitMs": 3000,
"playMethod": "play_current_scene",
"errors": [{ "severity": "error", "message": "..." }]
}
来源(rpc_debugger.gd):Output 面板的 ERROR / WARN 消息、调试器错误页、断点命中原因。插件绝不会在出错时自动停止播放——调用方必须调用 stop_scene。
安全模型
| 闸门 | 位置 | 作用 |
|---|---|---|
| 服务端回环绑定 | bridge.ts —— server.listen(port, '127.0.0.1') | 内核拒绝非回环绑定 |
| 令牌握手 | editor_ready —— 32 位十六进制比对 | 失败计为 missing_token(插件 < 0.2.0)/ bad_token(令牌过期) |
| 方法白名单 | protocol.ts —— GODOT_RPC_ALLOWED_METHODS | 白名单之外在到达桥之前即被拒绝 |
| 工具闸(双层) | 注册 + 分发 | 只注册 enabledTools(模型 schema 永不包含被禁用工具),且桥对每个线上方法重新检查门控工具 |
| 参数卫生 | protocol.ts —— checkHygiene | 字符串 ≤ 4096、数组 ≤ 512、嵌套字符串 ≤ 4096 |
set_project_setting 拒绝列表 | protocol.ts | 禁止 autoload/*、input/*、editor_plugins/enabled、调试日志/警告/形状/颜色、TLS 证书包覆盖、project_settings_override/* |
| 跨项目路由 | bridge.ts | 请求只到达 projectPath 与调用会话 cwd 共享的客户端 |
| 断连拒绝 | bridge.ts | 路由到已断连客户端的在途请求以 client disconnected 失败 |
词汇表(中英对照)
| English | 中文 | 一句话定义 |
|---|---|---|
| addon | 插件 | Godot 编辑器扩展;位于 <project>/addons/<name>/;在 project.godot [editor_plugins] 中声明 |
EditorPlugin | 编辑器插件基类 | 在编辑器进程中运行的代码所继承的 Godot 基类 |
EditorDebuggerPlugin | 编辑器调试器插件基类 | 钩住 ScriptEditorDebugger 信号,无需派生子进程即可捕获运行时错误 |
EditorInterface | 编辑器接口单例 | 打开场景、控制播放、列出打开场景等的静态访问器 |
ProjectSettings | 项目设置 | Godot 的项目级配置存储;键使用 / 分隔路径,如 autoload/Foo |
| autoload | 自动加载 | 在 autoload/* 键下注册的单例脚本。已列入 set_project_setting 拒绝列表 |
| endpoint file | 端点文件 | 桥发布的 {host, port, token} JSON 文件,插件每秒轮询 |
| handshake | 握手 | 首次 TCP 连接时发送的 editor_ready 事件,携带插件提供的 token 和 addonVersion |
| token | 令牌 | 授权插件与桥通信的 32 位十六进制共享密钥 |
addonVersion | 插件版本 | 插件的 plugin.cfg 版本,在 editor_ready 时上报,便于服务端警告协议不匹配 |
routedTo | 路由目标 | 请求未指定客户端时桥自动路由到的 clientId |
| tool gate | 工具闸 | 双层(注册 + 分发)检查:RPC 被受理前必须启用对应工具 |
| JSON-lines | JSON 行协议 | 每行一个 JSON 对象的 UTF-8 流,以 \n 分隔 |
| play error | 播放期错误 | run_current_scene / play_main_scene 会话期间捕获的运行时错误/警告 |
| ping | 健康检查 | 唯一没有对应用户工具的 RPC 方法 |
| ring buffer | 环形缓冲 | 保存最近 play_error 事件的有界缓冲(上限 50) |
Godot 插件(agent_rpc)
客户端一半——让 AI agent 驱动编辑器的 TCP JSON-lines 桥:仅回环传输、令牌握手、默认关闭的工具闸。
插件文件夹内容
addons/agent_rpc/
plugin.cfg # 插件清单(名称、version="0.6.3"、入口脚本)
plugin.gd # EditorPlugin —— TCP 客户端、消息分发、播放错误环形缓冲
rpc_debugger.gd # EditorDebuggerPlugin —— 钩住 ScriptEditorDebugger output / debug_data / breaked
没有自动加载、没有场景文件——一切都在编辑器进程中运行(@tool)。
安装
- 把
addons/agent_rpc/复制到 Godot 项目的addons/文件夹。 - 在 Godot 中:项目设置 → 插件 → 启用 "Agent RPC"。
- 启动你的 agent(如带
dsh-godot-tool的 DeepSeek Harness)。它监听127.0.0.1:8765,忙时回退到8765–8774,并发布插件轮询的端点文件。 - 插件约 1 秒内连接成功,Godot Output 面板打印
Agent RPC: connected to …。
升级插件后请重新安装,并重新加载项目或重启 Godot。
端点配置
插件每秒轮询端点文件并自动重连。文件按以下顺序解析:
- 环境变量
AGENT_RPC_ENDPOINT——例如当 DeepSeek Harnessdsh-godot-tool插件发布到$DSH_HOME/godot/endpoint.json时指向该路径。(推荐。) - ProjectSetting 键
agent_rpc/endpoint_file——显式的项目内路径。 - 旧默认值——
~/.pi/agent/x-agent-godot-rpc.json。
环境变量优先;两者都覆盖旧默认值。文件本身必须包含 { "host": "127.0.0.1", "port": 8765, "token": "<32-hex>", "version": 1 }。
握手
首次 TCP 连接时,插件发送携带 token 和 addonVersion 的 editor_ready 事件。复用上一个端点文件的令牌,"先启动 Godot、再启动 agent"约 1 秒即可就绪,无需重新安装任何东西。
故障排查
| 症状 | 可能原因 | 先查什么 |
|---|---|---|
无连接,握手失败 missing_token | 插件早于 0.2.0(editor_ready 不带令牌) | plugin.cfg 版本;重装插件 |
无连接,握手失败 bad_token | 插件持有过期令牌;服务端已写入新令牌 | 确认两端读取同一个端点文件;重装插件强制重读 |
run_current_scene 有脚本错误却返回空 errors | EditorDebuggerPlugin 未钩住 ScriptEditorDebugger(调试器 UI 尚未构建,或 Godot 版本差异) | 打开 Godot 调试器面板,确认插件已激活 |
run_current_scene 后播放卡住 | wait_ms 已到但插件从不自动停止 | 这是设计行为——调用 stop_scene |
export_project 超过 5 分钟仍挂起 | 无头 Godot 卡在缺失的导出模板或脚本启动 | 桥在导出超时时杀进程;检查 Godot 输出 |
set_project_setting 被拒 "forbidden prefix" | 写入 autoload/*、input/*、editor_plugins/enabled 等 | 安全模型中的拒绝列表是最终的 |
list_project_files 总是超时 | 服务端用了旧的 data.has("type") 分帧启发式 | 分帧规则必须是 data.has("method")——见消息形态 |
| lint 失败时编辑器冻结约 30 秒 | --check-only 在主线程运行 | 使用 plugin.gd 中的线程 worker 模式;_exit_tree 必须等待线程 |
Harness 插件(dsh-godot-tool)
服务端一半——@deepseek-ai/dsh-godot-tool:回环 TCP JSON-lines 服务端外加 27 个 godot_* 面向模型工具。
它做什么
GodotRpcBridge(src/bridge.ts)——绑定到回环地址的node:net服务端,使用插件协议:换行分隔 JSON、令牌握手(editor_ready)、按连接分配clientId、按 id 关联请求响应,以及捕获play_error/scene_changed事件。- 27 个工具(
src/tools.ts)——每个线上方法一个defineTool,全部通过桥分发。完整名单见方法名单。
插件发布插件每秒轮询的端点文件(默认 $DSH_HOME/godot/endpoint.json,或 Config.endpointPath),因此"先启动 harness,再打开 Godot"大约一秒钟即可就绪,无需重新安装插件。
配置
| 字段 | 默认值 | 含义 |
|---|---|---|
port | 8765 | 首选回环监听端口;桥会向上尝试至 fallbackPortEnd。 |
fallbackPortEnd | 8774 | 报告端口耗尽前尝试的最后一个端口。 |
token | 自动生成的 32 位十六进制 | 插件必须在 editor_ready 时出示的共享密钥。 |
endpointPath | ~/.dsh/godot/endpoint.json | 插件轮询的端点文件;指向与插件读取的相同文件。 |
enabledTools | [] | 要注册的工具名。默认空——部署选择启用之前,模型看不到任何 godot 工具。 |
端点文件原子写入(tmp + 重命名),并在插件卸载时故意保留:下次启动复用令牌,避免握手抖动。
工具闸
enabledTools 是参考桌面实现的双层开关,折叠进一个插件:
- 注册层——只注册选择启用的工具,因此模型的功能调用 schema 永远不会包含被禁用的工具。
- 分发层——桥对每个线上方法重新检查门控工具(
GODOT_RPC_METHOD_TOOL),因此即使直接调用桥也无法触达被禁用的方法。
导出形态
函数/命名空间插件:导出 name / inject / Config / apply,没有 export default。多余的 export default 会经 Loader 的 unwrapExports 折叠模块并丢弃 inject——与 harness postmortem 0001-acp-default-export-drops-inject 记录的同一种失败模式。
模型体验
工具 schema——模型只看到 Config.enabledTools 中列出的工具对应的 godot_* schema;描述会指明线上方法的参数以及任何上下文大小上限(场景树序列化预算、列表分页)。每个启用工具在每次请求中产生固定 schema 成本;工具集不变时前缀稳定。
工具调用结果——每次成功调用原样返回插件的 result 作为规范 JSON 值(例如 get_editor_info → { godotVersion, projectPath, editedScene, playing })。线上 ok:false 或本地拒绝(方法不允许、工具被禁用、参数卫生、未知客户端、跨项目路由、超时、客户端断连)以 Error: <message> 呈现。结果令牌随插件响应增长,受其序列化预算约束(5000 节点场景树、500/5000 文件分页、50 条错误环形缓冲)。
已知局限
- 线上协议锁定参考插件——27 方法白名单、超时阶梯和拒绝列表镜像
agent_rpc0.6.x;更新的插件协议世代需要同步扩展src/protocol.ts。 - 断点不支持条件表达式——Godot 4 断点 API 忽略条件;
set_breakpoint报告conditionIgnored,模型只能依赖行断点。 play_error捕获依赖编辑器调试器 UI 时序——EditorDebuggerPlugin钩住ScriptEditorDebugger;在部分 Godot 版本上,钩子只在调试器面板构建后挂接。依赖错误收集前请先用真实编辑器验证。- 仅单一活动客户端路由——多个 Godot 实例连接时,未指定的调用路由到第一个认证客户端;
clientId选择尚未暴露为工具参数。
开发
仓库布局
addons/agent_rpc/ # Godot 编辑器插件(客户端一半),GDScript
plugin.cfg / plugin.gd / rpc_debugger.gd
dsh-godot-tool/ # Harness 插件(服务端一半),TypeScript
src/bridge.ts # GodotRpcBridge —— 回环 TCP 服务端、握手、路由
src/protocol.ts # 方法白名单、超时阶梯、参数卫生、拒绝列表
src/tools.ts # 27 个 godot_* 工具定义
src/endpoint.ts # 端点文件发布 / 读取 / 删除
src/index.ts # 插件入口(name / inject / Config / apply)
tests/ # vitest 套件(bridge、protocol、tools、loader-composition)
运行 harness 插件测试
TypeScript 一侧用 vitest 测试;tests/fake-addon.ts 通过真实回环 socket 伪造 Godot 插件,因此套件无需 Godot 编辑器即可覆盖实际线上协议:
cd dsh-godot-tool
pnpm exec vitest run
套件:bridge.spec.ts(传输、握手、路由、超时)、protocol.spec.ts(白名单、卫生、拒绝列表、端点 schema)、tools.spec.ts(工具闸)、loader-composition.spec.ts(导出形态)。
用真实 Godot 编辑器做冒烟测试
.smoke-test/ 是一个最小的一次性 Godot 4 项目(主场景打印 SMOKE_OK from Godot 4.7),自带 addons/ 下安装的插件。它被排除在 git 之外;用于手工验证线上通路:
- 在 Godot 编辑器中打开
.smoke-test/project.godot(该项目的插件已启用)。 - 启动启用了
tool-godot的 harness;桥发布端点文件。 - 运行场景(F6):Output 面板显示
Agent RPC: connected to …和打印行;然后用godot_editor_info/godot_run_scene/godot_play_errors从 agent 侧驱动。
保持文档同步
README 把线上协议锁定到插件版本和协议世代。改动任何一半(新方法、超时常量、配置字段)时,请在同一提交内更新:
dsh-godot-tool/src/protocol.ts—— 白名单、超时阶梯、卫生上限、拒绝列表、端点 schema。- 上文方法名单、超时阶梯与安全模型表。
- 插件变化时同步
plugin.gd中的ADDON_VERSION与plugin.cfg的version。
兼容性
插件 0.6.3、harness 插件 0.1.0-rc.5(@deepseek-ai/dsh-godot-tool)、协议世代 1.0 + 1.2 + 1.3(27 个 RPC)。已在 Godot 4.4 stable 和 4.7 stable(Windows)上测试。自本仓库补丁起,插件读取 AGENT_RPC_ENDPOINT / agent_rpc/endpoint_file;旧的 ~/.pi/... 默认值仍可用。新插件版本可能增加方法,但 1.0 集合保持不变。
许可证
MIT —— 见 LICENSE。