Just for Fun
dsh-godot-tool
fromlan/dsh-godot-tool · ★ 3
Godot Agent RPC — lets an AI agent drive the Godot editor.
安装
dsh plugin --profile web add github:fromlan/dsh-godot-toolGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
- Contents
- Quick start
- 1. Install the Godot addon
- 2. Install the harness plugin
- 3. Point the addon at the harness endpoint
- 4. Enable the tools you want
- 5. Verify the connection
- How it works
- Wire protocol
- Transport
- Message shapes
- Endpoint file
- Method roster (27 RPCs + ping)
- Timeout ladder
- Playback error collection
- Security model
- Glossary (EN ↔ 中文)
- Godot addon (agent_rpc)
- What the addon folder ships
- Installation
- Endpoint configuration
- Handshake
- Troubleshooting
- Harness plugin (dsh-godot-tool)
- What it does
- Configuration
- Tool gate
- Export shape
- Model experience
- Known limitations
- Godot mode (agent preset)
- What it is
- Installation
- Bridge lifecycle and mode switching
- Maintenance
- Development
- Repository layout
- Running the harness-plugin tests
- Smoke-testing against a real Godot editor
- Keeping the docs in sync
- Compatibility
- License
English | 中文
A pair of plugins plus a Godot mode (agent preset) that let an AI agent drive a Godot 4.x editor over loopback TCP JSON-lines: open / reload scenes, run / stop the current or main scene, watch playback errors, inspect scene trees and scripts, set project settings, lint GDScript, list project files, and export builds.
| Path | What it is | Runs in |
|---|---|---|
addons/agent_rpc/ |
Godot editor addon (EditorPlugin + EditorDebuggerPlugin) — TCP client, message dispatch, play-error ring buffer |
Godot editor process |
dsh-godot-tool/ |
DeepSeek Harness plugin (@deepseek-ai/dsh-godot-tool) — loopback TCP server (GodotRpcBridge) + 27 godot_* model-facing tools |
DeepSeek Harness |
agent-presets/godot/ |
Godot mode agent preset — assembles the two halves into a lean Godot-dev agent (persona, shell/fs/jobs, compaction, subagents, goals, plan mode) | DeepSeek Harness |
The two halves speak one wire protocol: newline-delimited JSON over TCP on 127.0.0.1:8765 (fallback 8765–8774), token-authenticated handshake, loopback-only. The Godot mode assembles the two halves into a ready-to-use Godot-dev agent on the harness side. This document is the single reference — quick start first, then the wire protocol, then each half's and the mode's manual.
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ Godot 4.x editor │ TCP │ DeepSeek Harness │
│ │ JSON-L │ │
│ addons/agent_rpc (client) │◄────────►│ dsh-godot-tool (server) │
│ EditorPlugin + debugger │ 8765 │ GodotRpcBridge + 27 tools │
└──────────────┬──────────────┘ └──────────────┬───────────────┘
│ polls endpoint file (1 s) │ publishes
▼ ▼
~/.pi/agent/x-agent-godot-rpc.json ~/.dsh/godot/endpoint.json
(addon default) (harness plugin default)
Godot mode (agent-presets/godot/) assembles the dsh-godot-tool/ plugin into a lean Godot-dev agent on the harness side — see Godot mode.
Contents
- Quick start
- How it works
- Wire protocol
- Godot addon (
agent_rpc) - Harness plugin (
dsh-godot-tool) - Godot mode (agent preset)
- Development
- Compatibility · License
Quick start
1. Install the Godot addon
Copy addons/agent_rpc/ into your Godot project's addons/ folder, then enable Agent RPC in Project Settings → Plugins.
2. Install the harness plugin
Two options:
- From this repo (source): point the Harness at
dsh-godot-tool/src/index.tsvia a--patchoverlay or a project plugin row, and adddsh-toolsto your composition. - From the harness workspace: if you build inside the deepseek-harness repository, the package is
packages/extensions/tool-godot(@deepseek-ai/dsh-godot-tool); this copy stays in sync with it.
3. Point the addon at the harness endpoint
The addon polls an endpoint file every second. The default is ~/.pi/agent/x-agent-godot-rpc.json; the harness plugin publishes $DSH_HOME/godot/endpoint.json by default. Point one at the other:
- Set the environment variable
AGENT_RPC_ENDPOINT(recommended), or - Set the ProjectSettings key
agent_rpc/endpoint_file, or - Configure the harness plugin's
endpointPathto the addon's file.
4. Enable the tools you want
Every godot_* tool is off by default (enabledTools is empty in the harness plugin config). Opt in explicitly:
Not needed when you use Godot mode — the preset already carries the
tool-godotrow (enabledTools= all 27 tools). The snippet below is only for bare deployments without agent presets (CLI/TUI,--patchsource installs).
# dsh cordis.yml
- id: tool-godot
name: '@deepseek-ai/dsh-godot-tool'
config:
enabledTools: ['godot_editor_info', 'godot_run_scene', 'godot_play_errors']
Then start the harness, open the Godot project, and the agent can drive the editor.
5. Verify the connection
- The addon prints
Agent RPC: connected to …in the Godot Output panel within ~1 s of the bridge listening. - From the agent side, call
godot_editor_info— it returns{ godotVersion, projectPath, editedScene, playing }when the wire path works end to end. If it fails, check the troubleshooting table.
How it works
Two processes, one loopback socket, one endpoint file that joins them:
- Harness starts first (or Godot — either order works). The
dsh-godot-toolplugin binds127.0.0.1:8765(walking up to8774if busy), generates a 32-hex token, and atomically writes the endpoint file{ host, port, token }for the addon to find. - The addon polls that file every second and reconnects automatically whenever it changes or disappears. The file is resolved from
AGENT_RPC_ENDPOINT, thenagent_rpc/endpoint_file, then the legacy~/.pi/...default. - On the first TCP connect the addon sends
editor_readycarrying its token andaddonVersion; the bridge authenticates it, records itsprojectPath, and assigns aclientId. - The agent calls a
godot_*tool → the bridge validates (allowlist, tool gate, param hygiene, cross-project routing) → sends a JSON-lines request → the addon acts on the editor → replies withok:true/ok:false→ the bridge resolves the tool call. - During playback the addon pushes
play_errorevents into the bridge's ring buffer (max 50);godot_play_errorsreads them. The addon never auto-stops playback — the agent callsgodot_stop_scene.
Wire protocol
Pinned to addon 0.6.3, protocol generations 1.0 + 1.2 + 1.3 (27 RPC methods). If you change either half, update this section in the same commit.
Transport
| Property | Value |
|---|---|
| Address | Loopback only — 127.0.0.1 |
| Port | 8765, falling back to 8765–8774 when busy |
| Encoding | UTF-8, newline-delimited JSON (one object per line, no length prefix, no binary) |
| Auth | 32-hex token presented by the addon on editor_ready |
Message shapes
// Request — server → addon
type GodotRpcRequest = {
id: string; // randomUUID from the caller
method: string; // see the roster below
...params // method-specific
};
// Response — addon → server (paired by id)
type GodotRpcResponse =
| { id: string; ok: true; result: unknown; routedTo?: string }
| { id: string; ok: false; error: string; routedTo?: string };
// Event — addon → server (no 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 };
Framing rule (0.6.3 fix): disambiguate request vs event by data.has("method"), never by data.has("type"). list_project_files accepts a type parameter for filtering, and a request carrying both method and type is silently dropped by the older heuristic.
Endpoint file
The server publishes { host, port, token } to a JSON file the addon polls every second (_endpoint_config_path() in plugin.gd); the addon reconnects automatically when the file changes.
| Field | Type | Notes |
|---|---|---|
host |
string | Must be 127.0.0.1 or localhost on reuse; anything else is rejected and a fresh token is generated |
port |
number | 1–65535; outside this range falls back |
token |
string | 32-hex (/^[0-9a-f]{32}$/i); mismatch falls back |
version |
number | Wire format version; current = 1 |
updatedAt |
string | ISO timestamp written on every successful listen |
Written atomically (tmp + rename) and deliberately not deleted on server stop, so the next start reuses the token and skips handshake churn.
Method roster (27 RPCs + ping)
| Tool (when enabled) | RPC method | Protocol gen |
|---|---|---|
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 (+ optional paths) |
1.0 |
godot_play_errors |
get_play_errors (+ optional 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? xor 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 is a protocol-level health check with no user-facing tool. The allowlist lives in dsh-godot-tool/src/protocol.ts (GODOT_RPC_ALLOWED_METHODS) and the tool-to-method gate map in GODOT_RPC_METHOD_TOOL; requests outside the allowlist are rejected before they reach the bridge.
Timeout ladder
| Constant | Value | Used by |
|---|---|---|
GODOT_RPC_DEFAULT_PORT |
8765 | listen |
GODOT_RPC_FALLBACK_PORT_END |
8774 | listen fallback |
GODOT_RPC_DEFAULT_WAIT_MS |
3000 | play-error window |
GODOT_RPC_MAX_WAIT_MS |
15000 | play-error window cap |
GODOT_RPC_BASE_TIMEOUT_MS |
8000 | default request timeout |
GODOT_RPC_EXPORT_TIMEOUT_MS |
5 × 60 000 | export_project hard kill |
GODOT_RPC_EXPORT_GRACE_MS |
15 000 | slack after export returns |
GODOT_RPC_GRACE_PERIOD_MS |
8000 | disconnect grace window |
GODOT_LIST_FILES_DEFAULT_LIMIT / MAX_LIMIT |
500 / 5000 | list_project_files pagination |
GODOT_WAIT_DEFAULT_TIMEOUT_MS / MAX |
30 000 / 60 000 | wait_for_import_done |
Playback error collection
run_current_scene / play_main_scene clear the buffer, start playback, and return after a configurable window (default ~3 s, max 15 s):
{
"started": true,
"playing": true,
"waitMs": 3000,
"playMethod": "play_current_scene",
"errors": [{ "severity": "error", "message": "..." }]
}
Sources (rpc_debugger.gd): the Output dock's ERROR / WARN messages, the Debugger Errors page, breakpoint hit reasons. The addon never auto-stops playback on error — the caller must invoke stop_scene.
Security model
| Gate | Where | What it does |
|---|---|---|
| Server loopback bind | bridge.ts — server.listen(port, '127.0.0.1') |
The kernel refuses non-loopback binds |
| Token handshake | editor_ready — 32-hex compare |
Failures counted as missing_token (addon < 0.2.0) / bad_token (stale token) |
| Method allowlist | protocol.ts — GODOT_RPC_ALLOWED_METHODS |
Anything outside is rejected before the bridge sees it |
| Tool gate (double layer) | registration + dispatch | Only enabledTools are registered (model schema never contains a disabled tool), and the bridge re-checks the gating tool per wire method |
| Param hygiene | protocol.ts — checkHygiene |
Strings ≤ 4096, arrays ≤ 512, nested strings ≤ 4096 |
set_project_setting denylist |
protocol.ts |
Forbids autoload/*, input/*, editor_plugins/enabled, debug logging/warnings/shapes/colors, TLS certificate-bundle override, project_settings_override/* |
| Cross-project routing | bridge.ts |
A request only reaches a client whose projectPath shares the calling session's cwd |
| Disconnect rejection | bridge.ts |
In-flight requests routed to a disconnected client fail with client disconnected |
Glossary (EN ↔ 中文)
| English | 中文 | One-line definition |
|---|---|---|
| addon | 插件 | Godot editor extension; lives in <project>/addons/<name>/; declared in project.godot [editor_plugins] |
EditorPlugin |
编辑器插件基类 | Godot base class for code that runs in the editor process |
EditorDebuggerPlugin |
编辑器调试器插件基类 | Hooks ScriptEditorDebugger signals to capture runtime errors without spawning a process |
EditorInterface |
编辑器接口单例 | Static accessor for opening scenes, controlling playback, listing open scenes, etc. |
ProjectSettings |
项目设置 | Godot's project-level configuration store; keys use /-separated paths like autoload/Foo |
| autoload | 自动加载 | Singleton scripts registered under autoload/* keys. Denylisted for set_project_setting |
| endpoint file | 端点文件 | The bridge's published {host, port, token} JSON file the addon polls every second |
| handshake | 握手 | The editor_ready event sent on first TCP connect, carrying the addon-supplied token and addonVersion |
| token | 令牌 | 32-hex shared secret that authorises the addon to talk to the bridge |
addonVersion |
插件版本 | The addon's plugin.cfg version, surfaced on editor_ready so the server can warn about protocol mismatches |
routedTo |
路由目标 | The clientId actually used when a request omits one and the bridge auto-routes to the first authenticated client |
| tool gate | 工具闸 | The double-layer (registration + dispatch) check that a tool is enabled before an RPC is admitted |
| JSON-lines | JSON 行协议 | UTF-8 stream where each \n-delimited line is one JSON object |
| play error | 播放期错误 | Runtime error / warning captured during a run_current_scene / play_main_scene session |
| ping | 健康检查 | The single RPC method that does not correspond to a user-facing tool |
| ring buffer | 环形缓冲 | Bounded buffer (max 50) holding recent play_error events |
Godot addon (agent_rpc)
The client half — a TCP JSON-lines bridge that lets an AI agent drive the editor: loopback-only transport, token handshake, default-off tool gate.
What the addon folder ships
addons/agent_rpc/
plugin.cfg # plugin manifest (name, version="0.6.3", entry script)
plugin.gd # EditorPlugin — TCP client, message dispatch, play-error ring buffer
rpc_debugger.gd # EditorDebuggerPlugin — hooks ScriptEditorDebugger output / debug_data / breaked
No autoloads, no scene files — everything runs in the editor process (@tool).
Installation
- Copy
addons/agent_rpc/into your Godot project'saddons/folder. - In Godot: Project Settings → Plugins → enable "Agent RPC".
- Start your agent (e.g. DeepSeek Harness with
dsh-godot-tool). It listens on127.0.0.1:8765, falling back to8765–8774, and publishes an endpoint file the addon polls. - The addon connects within ~1 s and prints
Agent RPC: connected to …in the Godot Output panel.
After upgrading the addon, re-install it and reload the project or restart Godot.
Endpoint configuration
The addon polls an endpoint file every second and reconnects automatically. The file is resolved in this order:
- Environment variable
AGENT_RPC_ENDPOINT— e.g. point it at$DSH_HOME/godot/endpoint.jsonwhen the DeepSeek Harnessdsh-godot-toolplugin publishes there. (Recommended.) - ProjectSetting key
agent_rpc/endpoint_file— an explicit per-project path. - Legacy default —
~/.pi/agent/x-agent-godot-rpc.json.
The environment variable wins; both override the legacy default. The file itself must contain { "host": "127.0.0.1", "port": 8765, "token": "<32-hex>", "version": 1 }.
Handshake
On the first TCP connect the addon sends an editor_ready event carrying its token and addonVersion. Reusing the previous endpoint file's token makes "start Godot first, then start the agent" land in roughly one second without re-installing anything.
Troubleshooting
| Symptom | Likely cause | First check |
|---|---|---|
No connection, missing_token handshake failure |
Addon predates 0.2.0 (no token on editor_ready) |
plugin.cfg version; re-install the addon |
No connection, bad_token handshake failure |
Addon holds a stale token; server wrote a new one | Confirm both ends read the same endpoint file; re-install the addon to force a re-read |
run_current_scene returns empty errors despite a script error |
EditorDebuggerPlugin did not hook ScriptEditorDebugger (debugger UI not yet built, or Godot version drift) |
Open Godot's Debugger panel and check the plugin is active |
Playback stuck after run_current_scene |
wait_ms elapsed but the addon never auto-stops |
This is by design — call stop_scene |
export_project hangs past 5 minutes |
Headless Godot blocked on a missing export template or a long script startup | Bridge kills the process at the export timeout; check Godot's output |
set_project_setting rejected with "forbidden prefix" |
Wrote to autoload/*, input/*, editor_plugins/enabled, etc. |
The denylist in the security model is final |
list_project_files always times out |
Server used the older data.has("type") framing heuristic |
The framing rule must be data.has("method") — see message shapes |
| Editor freezes ~30 s when linting fails | --check-only ran on the main thread |
Use the threaded worker pattern in plugin.gd; _exit_tree must wait for the thread |
Harness plugin (dsh-godot-tool)
The server half — @deepseek-ai/dsh-godot-tool, a loopback TCP JSON-lines server plus 27 godot_* model-facing tools.
What it does
GodotRpcBridge(src/bridge.ts) — anode:netserver bound to loopback that speaks the addon's wire protocol: newline-delimited JSON, token handshake (editor_ready), per-connectionclientId, request/response correlation by id, andplay_error/scene_changedevent capture.- 27 tools (
src/tools.ts) — onedefineToolper wire method, each dispatching through the bridge. The complete roster matches the method roster.
The plugin publishes an endpoint file the addon polls every second ($DSH_HOME/godot/endpoint.json by default, or Config.endpointPath), so "start the harness first, then open Godot" lands in roughly a second without reinstalling the addon.
Configuration
| Field | Default | Meaning |
|---|---|---|
port |
8765 |
Preferred loopback listen port; the bridge walks up to fallbackPortEnd. |
fallbackPortEnd |
8774 |
Last port tried before reporting port exhaustion. |
token |
auto-generated 32-hex | Shared secret the addon must present on editor_ready. |
endpointPath |
~/.dsh/godot/endpoint.json |
Endpoint file the addon polls; point it at the same file the addon reads. |
enabledTools |
[] |
Tool names to register. Default empty — the model sees no godot tool until the deployment opts in. |
The endpoint file is written atomically (tmp + rename) and deliberately retained on plugin unload: the next start reuses the token and avoids handshake churn.
Tool gate
enabledTools is the double-layer switch from the reference desktop implementation, collapsed into one plugin:
- Registration layer — only opted-in tools are registered, so the model's function-call schema never contains a disabled tool.
- Dispatch layer — the bridge re-checks the gating tool for every wire method (
GODOT_RPC_METHOD_TOOL), so even a direct bridge caller cannot reach a disabled method.
Export shape
A function/namespace plugin: it exports name / inject / Config / apply and NO default. A stray export default would collapse the module via the Loader's unwrapExports and drop inject — the same failure mode as the harness postmortem 0001-acp-default-export-drops-inject.
Model experience
Tool schemas — the model sees the generated godot_* schemas for the tools listed in Config.enabledTools only; descriptions name the wire method's parameters and any context-size caps (scene tree serialization budget, list pagination). Fixed schema cost on every request for each enabled tool; prefix-stable while the tool set is unchanged.
Tool-call results — each successful call returns the addon's result verbatim as the canonical JSON value (e.g. get_editor_info → { godotVersion, projectPath, editedScene, playing }). A wire-level ok:false or a local refusal (method not allowed, tool disabled, param hygiene, unknown client, cross-project route, timeout, client disconnected) surfaces as Error: <message>. Result tokens scale with the addon's response, bounded by its serialization budgets (5000-node scene tree, 500/5000 file pagination, 50-error ring).
Known limitations
- Wire protocol pinned to the reference addon — the 27-method allowlist, timeout ladder, and denylist mirror
agent_rpc0.6.x; a newer addon protocol generation requires extendingsrc/protocol.ts. - No condition expressions for breakpoints — the Godot 4 breakpoint API ignores conditions;
set_breakpointreportsconditionIgnoredand the model must rely on line breakpoints only. play_errorcapture depends on editor debugger UI timing —EditorDebuggerPluginhooks theScriptEditorDebugger; on some Godot versions the hook attaches only after the Debugger panel is built. Verify with a real editor before relying on error collection.- Single active client routing only — when several Godot instances connect, an unspecified call routes to the first authenticated client;
clientIdselection is not yet exposed as a tool parameter.
Godot mode (agent preset)
The third half — assembles the two halves above into a ready-to-use Godot-dev agent. Two pieces in this repo: agent-presets/godot/ (the preset itself) and examples/web-profile.cordis.patch.yml (optional host-plane wiring for deployments without presets).
What it is
A DeepSeek Harness agent preset: preset.yml declares the mode metadata (name, description, order) and agent.cordis.yml is the agent-plane composition. It assembles the repo's two halves into a lean agent for Godot game development:
- Persona — a Godot 4.x dev agent: GDScript, scenes (.tscn), resources (.tres), shaders, editor plugins, export builds; drive the editor through the
godot_*tools first, use PowerShell for headless runs and anything outside the editor bridge. - Tools — shell (PowerShell on Windows), file read/write/search, background jobs (
tool-jobs), compaction (long sessions), subagent delegation, goals, plan mode, web search (fetch disabled), todo, ask-user. - Deliberately absent — workflow orchestration, ralph loops, skill loading, and other heavy machinery.
- Editor driving — the
godot_*tools are registered by thetool-godotrow inside the preset (thedsh-godot-tool/plugin in this repo). The preset's standing mount is created once per process, so there is still exactly one bridge and one endpoint file (~/.dsh/godot/endpoint.json) for the Godotagent_rpcaddon to poll; the bridge starts when Godot mode is first used. The row registers into this preset's scope layer, so only Godot-mode sessions see thegodot_*tools — the catalogs of standard/code/other modes stay clean.
Installation
- Preset — copy
agent-presets/godot/to~/.dsh/.agent-presets/godot/(the harness user-preset directory), then select Godot模式 in the GUI. - No host wiring needed — the
tool-godotrow is already inside the preset (enabledTools= all 27 tools); trim the list to tighten the tool set. Do not also registertool-godotin the web profile'scordis.patch.yml— back on the host plane, thegodot_*tools become globally visible in every mode. Only deployments without agent presets (CLI/TUI,--patchsource installs) wire it manually perexamples/web-profile.cordis.patch.yml. - Open the Godot project, start the harness, and the agent drives the editor in Godot mode.
Bridge lifecycle and mode switching
- The bridge starts with the preset's standing mount on the first session that picks Godot mode and stays up for the harness process lifetime; switching a session away from Godot mode (or deleting it) does not stop the bridge — that is the preset architecture's standing-mount behavior. Tool visibility follows the scope, though: after the switch the session no longer sees the
godot_*tools, and other modes are unaffected throughout. - Do not duplicate this preset with the GUI's "copy preset" to make a second mode — the copy would mount a second bridge that fights this one for the port and overwrites the endpoint file.
Maintenance
agent.cordis.ymlcomments mark deployment assumptions (Windows shell,tool-godotowned by the preset); adjust them per the comments when moving to another deployment.
Development
Repository layout
addons/agent_rpc/ # Godot editor addon (client half), GDScript
plugin.cfg / plugin.gd / rpc_debugger.gd
dsh-godot-tool/ # Harness plugin (server half), TypeScript
src/bridge.ts # GodotRpcBridge — loopback TCP server, handshake, routing
src/protocol.ts # method allowlist, timeout ladder, hygiene, denylist
src/tools.ts # the 27 godot_* tool definitions
src/endpoint.ts # endpoint-file publish / read / remove
src/index.ts # plugin entry (name / inject / Config / apply)
tests/ # vitest suites (bridge, protocol, tools, loader-composition, preset-scope)
agent-presets/godot/ # Godot mode agent preset (preset.yml + agent.cordis.yml)
examples/ # optional host-plane wiring for non-preset deployments (web-profile.cordis.patch.yml)
Running the harness-plugin tests
The TypeScript side is tested with vitest; tests/fake-addon.ts fakes the Godot addon over a real loopback socket, so the suites exercise the actual wire protocol without a Godot editor:
cd dsh-godot-tool
pnpm exec vitest run
Suites: bridge.spec.ts (transport, handshake, routing, timeouts), protocol.spec.ts (allowlist, hygiene, denylist, endpoint schema), tools.spec.ts (tool gate), loader-composition.spec.ts (export shape), preset-scope.spec.ts (scope layering: a preset mount registers the godot_* tools only in that scope's layer while the host global catalog stays clean).
Smoke-testing against a real Godot editor
.smoke-test/ is a minimal throwaway Godot 4 project (main scene prints SMOKE_OK from Godot 4.7) with the addon installed under its own addons/. It is excluded from git; use it to sanity-check the wire path by hand:
- Open
.smoke-test/project.godotin the Godot editor (the addon plugin is enabled in that project). - Start the harness with
tool-godotenabled; the bridge publishes its endpoint file. - Run the scene (F6): the Output panel shows
Agent RPC: connected to …and the print line; then drive it from the agent withgodot_editor_info/godot_run_scene/godot_play_errors.
Keeping the docs in sync
The README pins the wire protocol to the addon version and protocol generations. If you change either half (a new method, a timeout constant, a config field), update the same commit:
dsh-godot-tool/src/protocol.ts— allowlist, timeout ladder, hygiene limits, denylist, endpoint schema.- The method roster, timeout ladder, and security model tables above.
- The addon's
ADDON_VERSION(plugin.gd) andversioninplugin.cfgwhen the addon changes. - When the Godot mode changes, sync the Godot mode section.
Compatibility
Addon 0.6.3, harness plugin 0.1.0-rc.5 (@deepseek-ai/dsh-godot-tool), protocol generations 1.0 + 1.2 + 1.3 (27 RPCs). Tested against Godot 4.4 stable and 4.7 stable (Windows). The addon reads AGENT_RPC_ENDPOINT / agent_rpc/endpoint_file since this repository's patch; the legacy ~/.pi/... default still works. Newer addon versions may grow methods, but the 1.0 set stays intact. The Godot mode (agent-presets/godot/) is maintained in the same repo and tracks the same version lock.
License
MIT — see LICENSE.