godot-daedalus_backend 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +101 -0
  2. package/bin/godot-daedalus-backend.js +4 -0
  3. package/bin/godot-daedalus-mcp.js +4 -0
  4. package/bin/godot-daedalus-terminal-mcp.js +4 -0
  5. package/bin/run-tsx-entry.js +26 -0
  6. package/package.json +54 -0
  7. package/scripts/deepseek-tokenizer-server.py +54 -0
  8. package/src/app-paths.ts +36 -0
  9. package/src/main.ts +21 -0
  10. package/src/mcp/content-length-protocol.ts +68 -0
  11. package/src/mcp/custom-mcp-config-store.ts +397 -0
  12. package/src/mcp/godot-diagnostics-bridge.ts +1298 -0
  13. package/src/mcp/godot-editor-bridge.ts +307 -0
  14. package/src/mcp/godot-mcp-server.ts +3484 -0
  15. package/src/mcp/godot-paths.ts +151 -0
  16. package/src/mcp/godot-project-settings.ts +233 -0
  17. package/src/mcp/godot-tool-registration.ts +46 -0
  18. package/src/mcp/mcp-config.ts +48 -0
  19. package/src/mcp/mcp-host.ts +393 -0
  20. package/src/mcp/mcp-session.ts +81 -0
  21. package/src/mcp/terminal-mcp-server.ts +576 -0
  22. package/src/mcp/tscn-tools.ts +302 -0
  23. package/src/mcp/types.ts +12 -0
  24. package/src/ping-client.ts +24 -0
  25. package/src/prompts/registry.ts +97 -0
  26. package/src/prompts/templates/backend-helper.md +25 -0
  27. package/src/prompts/templates/gdscript-reviewer.md +19 -0
  28. package/src/prompts/templates/godot-assistant.md +225 -0
  29. package/src/prompts/templates/scene-architect.md +15 -0
  30. package/src/prompts/templates/session-compressor.md +33 -0
  31. package/src/protocol/schema.ts +486 -0
  32. package/src/protocol/types.ts +77 -0
  33. package/src/providers/deepseek-agent.ts +1014 -0
  34. package/src/providers/deepseek-client.ts +114 -0
  35. package/src/providers/deepseek-dsml-tools.ts +90 -0
  36. package/src/providers/deepseek-loose-tools.ts +450 -0
  37. package/src/providers/provider-config-store.ts +164 -0
  38. package/src/server/client-session.ts +93 -0
  39. package/src/server/request-dispatcher.ts +74 -0
  40. package/src/server/response-helpers.ts +33 -0
  41. package/src/server/send-json.ts +8 -0
  42. package/src/server/websocket-server.ts +3997 -0
  43. package/src/session/session-compressor.ts +68 -0
  44. package/src/session/session-store.ts +669 -0
  45. package/src/skills/registry.ts +180 -0
  46. package/src/skills/templates/backend-helper.md +12 -0
  47. package/src/skills/templates/file-creator.md +14 -0
  48. package/src/skills/templates/gdscript-review.md +12 -0
  49. package/src/skills/templates/godot-project-init.md +29 -0
  50. package/src/skills/templates/scene-builder.md +12 -0
  51. package/src/tokens/deepseek-tokenizer-counter.ts +233 -0
  52. package/src/tokens/model-profiles.ts +38 -0
  53. package/src/tokens/token-counter-factory.ts +52 -0
  54. package/src/tokens/token-counter.ts +22 -0
  55. package/src/tools/approval-gateway.ts +111 -0
  56. package/src/tools/llm-tools.ts +1415 -0
  57. package/src/tools/tool-dispatcher.ts +147 -0
  58. package/src/tools/tool-event-describer.ts +387 -0
  59. package/src/tools/tool-idempotency.ts +373 -0
  60. package/src/tools/tool-policy-table.ts +61 -0
  61. package/src/tools/tool-policy.ts +73 -0
  62. package/src/workflow/llm-planner.ts +407 -0
  63. package/src/workflow/planner.ts +201 -0
  64. package/src/workflow/runner.ts +141 -0
  65. package/src/workflow/types.ts +69 -0
  66. package/src/workspace/registry.ts +104 -0
  67. package/src/workspace/types.ts +7 -0
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Godot Daedalus Backend
2
+
3
+ Godot Daedalus Backend is the TypeScript runtime service for the Godot Daedalus editor plugin. It provides the WebSocket/RPC backend, DeepSeek chat integration, approval-gated Godot project tools, session persistence, MCP host support, and Godot MCP servers.
4
+
5
+ This npm package is a source-runtime package. It publishes the TypeScript source and small JavaScript bin launchers, then runs the source through `tsx` at runtime. It does not publish compiled `dist/` output.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 20.19 or newer.
10
+ - npm.
11
+ - Godot 4.x for Godot validation and editor workflows.
12
+ - Optional Python 3 for the DeepSeek tokenizer helper.
13
+
14
+ The Python tokenizer bridge is included as `scripts/deepseek-tokenizer-server.py`. The tokenizer model files are intentionally not included in the npm package because they can be large. If you want exact tokenizer counting, install the Python dependency and point the backend at your tokenizer directory:
15
+
16
+ ```powershell
17
+ pip install tokenizers
18
+ $env:PYTHON_CMD = "python"
19
+ $env:DEEPSEEK_TOKENIZER_DIR = "D:\path\to\tokenizer"
20
+ ```
21
+
22
+ If Python or tokenizer files are unavailable, the backend can still run; token counting may fall back to the non-Python path used by the runtime.
23
+
24
+ ## Install
25
+
26
+ Global install:
27
+
28
+ ```powershell
29
+ npm install -g godot-daedalus_backend
30
+ ```
31
+
32
+ Local project install:
33
+
34
+ ```powershell
35
+ npm install godot-daedalus_backend
36
+ ```
37
+
38
+ ## Run The WebSocket Backend
39
+
40
+ ```powershell
41
+ $env:PORT = "8080"
42
+ godot-daedalus-backend
43
+ ```
44
+
45
+ The Godot plugin connects to this backend over WebSocket. Provider configuration, including the DeepSeek API key, is normally saved from the plugin settings UI.
46
+
47
+ ## Run The Godot MCP Server
48
+
49
+ The standalone Godot MCP server requires a Godot project path:
50
+
51
+ ```powershell
52
+ $env:GODOT_PROJECT_PATH = "D:\GodotProjects\example"
53
+ godot-daedalus-mcp
54
+ ```
55
+
56
+ ## Run The Terminal MCP Server
57
+
58
+ The terminal MCP server exposes guarded verification commands, including Godot headless checks. Configure the project and Godot executable first:
59
+
60
+ ```powershell
61
+ $env:GODOT_PROJECT_PATH = "D:\GodotProjects\example"
62
+ $env:GODOT_EXECUTABLE_PATH = "D:\Godot_v4.7-stable_win64.exe\Godot_v4.7-stable_win64.exe"
63
+ godot-daedalus-terminal-mcp
64
+ ```
65
+
66
+ ## Development
67
+
68
+ ```powershell
69
+ npm install
70
+ npm run typecheck
71
+ npm test
72
+ npm run pack:check
73
+ ```
74
+
75
+ Useful scripts:
76
+
77
+ - `npm start`: run the WebSocket backend from source.
78
+ - `npm run mcp`: run the Godot MCP server from source.
79
+ - `npm run terminal:mcp`: run the terminal MCP server from source.
80
+ - `npm run ping`: run the local ping client.
81
+ - `npm run pack:check`: inspect the npm package contents without publishing.
82
+
83
+ ## Published Files
84
+
85
+ The npm package uses the `files` whitelist in `package.json`. Runtime users receive:
86
+
87
+ - `bin/`
88
+ - `src/`
89
+ - `scripts/deepseek-tokenizer-server.py`
90
+ - `README.md`
91
+ - `package.json`
92
+
93
+ Tests, local agent instructions, generated tarballs, environment files, `node_modules/`, and tokenizer model files are not published.
94
+
95
+ Before publishing:
96
+
97
+ ```powershell
98
+ npm test
99
+ npm publish --dry-run
100
+ npm publish
101
+ ```
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runTsxEntry } from "./run-tsx-entry.js";
3
+
4
+ runTsxEntry("src/main.ts");
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runTsxEntry } from "./run-tsx-entry.js";
3
+
4
+ runTsxEntry("src/mcp/godot-mcp-server.ts");
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runTsxEntry } from "./run-tsx-entry.js";
3
+
4
+ runTsxEntry("src/mcp/terminal-mcp-server.ts");
@@ -0,0 +1,26 @@
1
+ import { spawn } from "node:child_process";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ export function runTsxEntry(entryRelativePath) {
6
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7
+ const entryPath = resolve(packageRoot, entryRelativePath);
8
+ const child = spawn(process.execPath, ["--import", "tsx", entryPath, ...process.argv.slice(2)], {
9
+ cwd: packageRoot,
10
+ env: process.env,
11
+ stdio: "inherit"
12
+ });
13
+
14
+ child.on("error", (error) => {
15
+ console.error(error instanceof Error ? error.message : String(error));
16
+ process.exit(1);
17
+ });
18
+
19
+ child.on("exit", (code, signal) => {
20
+ if (signal !== null) {
21
+ console.error(`Process exited from signal ${signal}`);
22
+ process.exit(1);
23
+ }
24
+ process.exit(code ?? 1);
25
+ });
26
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "godot-daedalus_backend",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript backend for the Godot Daedalus editor assistant plugin.",
5
+ "main": "./src/main.ts",
6
+ "bin": {
7
+ "godot-daedalus-backend": "bin/godot-daedalus-backend.js",
8
+ "godot-daedalus-mcp": "bin/godot-daedalus-mcp.js",
9
+ "godot-daedalus-terminal-mcp": "bin/godot-daedalus-terminal-mcp.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "src/",
14
+ "scripts/deepseek-tokenizer-server.py",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "start": "tsx src/main.ts",
19
+ "dev": "tsx src/main.ts",
20
+ "ping": "tsx src/ping-client.ts",
21
+ "mcp": "tsx src/mcp/godot-mcp-server.ts",
22
+ "terminal:mcp": "tsx src/mcp/terminal-mcp-server.ts",
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "npm run typecheck && node --import tsx --test \"tests/*.test.ts\"",
25
+ "pack:check": "npm pack --dry-run",
26
+ "prepublishOnly": "npm test"
27
+ },
28
+ "engines": {
29
+ "node": ">=20.19"
30
+ },
31
+ "keywords": [
32
+ "godot",
33
+ "editor-plugin",
34
+ "mcp",
35
+ "websocket",
36
+ "deepseek"
37
+ ],
38
+ "author": "LuYingYiLong",
39
+ "license": "MIT",
40
+ "type": "module",
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.29.0",
43
+ "keytar": "^7.9.0",
44
+ "openai": "^6.45.0",
45
+ "tsx": "^4.22.4",
46
+ "typescript": "^6.0.3",
47
+ "ws": "^8.21.0",
48
+ "zod": "^4.4.3"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^26.0.1",
52
+ "@types/ws": "^8.18.1"
53
+ }
54
+ }
@@ -0,0 +1,54 @@
1
+ """DeepSeek tokenizer JSON-line server.
2
+
3
+ Usage: python scripts/deepseek-tokenizer-server.py <tokenizer_dir>
4
+
5
+ Reads JSON objects from stdin, one per line: {"id": 1, "text": "..."}
6
+ Writes JSON responses to stdout: {"id": 1, "tokens": 42}
7
+ Sends {"ready": true} on startup.
8
+ """
9
+
10
+ import sys
11
+ import json
12
+ from pathlib import Path
13
+ from tokenizers import Tokenizer
14
+
15
+ if hasattr(sys.stdin, "reconfigure"):
16
+ sys.stdin.reconfigure(encoding="utf-8")
17
+ sys.stdout.reconfigure(encoding="utf-8")
18
+ sys.stderr.reconfigure(encoding="utf-8")
19
+
20
+ def main():
21
+ if len(sys.argv) < 2:
22
+ print(json.dumps({"error": "Usage: deepseek-tokenizer-server.py <tokenizer_dir>"}), flush=True)
23
+ sys.exit(1)
24
+
25
+ tokenizer_dir = Path(sys.argv[1])
26
+ tokenizer_path = tokenizer_dir / "tokenizer.json"
27
+ tokenizer = Tokenizer.from_file(str(tokenizer_path))
28
+ print(json.dumps({"ready": True}), flush=True)
29
+
30
+ for line in sys.stdin:
31
+ line = line.strip()
32
+ if not line:
33
+ continue
34
+
35
+ try:
36
+ request = json.loads(line)
37
+ except json.JSONDecodeError as e:
38
+ print(json.dumps({"error": f"Invalid JSON: {e}"}), flush=True)
39
+ continue
40
+
41
+ request_id = request.get("id")
42
+ text = request.get("text", "")
43
+ if not isinstance(text, str):
44
+ print(json.dumps({"id": request_id, "error": "text must be a string"}), flush=True)
45
+ continue
46
+
47
+ try:
48
+ encoded = tokenizer.encode(text)
49
+ print(json.dumps({"id": request_id, "tokens": len(encoded.ids)}), flush=True)
50
+ except Exception as e:
51
+ print(json.dumps({"id": request_id, "error": str(e)}), flush=True)
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,36 @@
1
+ import { join } from "node:path";
2
+
3
+ const APP_DIR_NAME: string = ".godot_daedalus";
4
+
5
+ export function getAppDataDir(): string {
6
+ const windowsAppData: string | undefined = process.env.APPDATA;
7
+ if (!windowsAppData || windowsAppData.trim().length === 0) {
8
+ throw new Error("APPDATA is not configured");
9
+ }
10
+
11
+ return join(windowsAppData, APP_DIR_NAME);
12
+ }
13
+
14
+ export function getDefaultWorkspaceConfigPath(): string {
15
+ return join(getAppDataDir(), "config", "workspaces.json");
16
+ }
17
+
18
+ export function getProviderConfigPath(): string {
19
+ return join(getAppDataDir(), "config", "provider.json");
20
+ }
21
+
22
+ export function getMcpServersConfigPath(): string {
23
+ return join(getAppDataDir(), "config", "mcp-servers.json");
24
+ }
25
+
26
+ export function getDefaultSessionsDir(): string {
27
+ return join(getAppDataDir(), "data", "sessions");
28
+ }
29
+
30
+ export function getDefaultArchivedSessionsDir(): string {
31
+ return join(getAppDataDir(), "data", "archived_sessions");
32
+ }
33
+
34
+ export function getToolExecutionLedgerPath(): string {
35
+ return join(getAppDataDir(), "data", "tool-executions.jsonl");
36
+ }
package/src/main.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { createServer } from "./server/websocket-server.js";
2
+ import { McpHost } from "./mcp/mcp-host.js";
3
+
4
+ const DEFAULT_PORT: number = 8080;
5
+ const portText: string = process.env.PORT ?? String(DEFAULT_PORT);
6
+ const port: number = Number.parseInt(portText, 10);
7
+
8
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
9
+ throw new Error(`Invalid PORT: ${portText}`);
10
+ }
11
+
12
+ const mcpHost: McpHost = new McpHost();
13
+
14
+ try {
15
+ await mcpHost.connectAll();
16
+ } catch (error: unknown) {
17
+ console.warn("MCP host failed to connect:", error instanceof Error ? error.message : error);
18
+ console.warn("Server will start without MCP support");
19
+ }
20
+
21
+ createServer(port, mcpHost);
@@ -0,0 +1,68 @@
1
+ import { Buffer } from "node:buffer";
2
+
3
+ const HEADER_SEPARATOR: string = "\r\n\r\n";
4
+
5
+ export function encodeContentLengthMessage(payload: unknown): Buffer {
6
+ const body: string = JSON.stringify(payload);
7
+ const bodyBuffer: Buffer = Buffer.from(body, "utf8");
8
+ const header: string = `Content-Length: ${bodyBuffer.byteLength}\r\n\r\n`;
9
+ return Buffer.concat([Buffer.from(header, "ascii"), bodyBuffer]);
10
+ }
11
+
12
+ export class ContentLengthMessageParser {
13
+ private buffer: Buffer = Buffer.alloc(0);
14
+
15
+ push(chunk: Buffer): unknown[] {
16
+ this.buffer = Buffer.concat([this.buffer, chunk]);
17
+ const messages: unknown[] = [];
18
+
19
+ while (true) {
20
+ const headerEnd: number = this.buffer.indexOf(HEADER_SEPARATOR);
21
+ if (headerEnd < 0) {
22
+ break;
23
+ }
24
+
25
+ const headerText: string = this.buffer.subarray(0, headerEnd).toString("ascii");
26
+ const contentLength: number | null = parseContentLength(headerText);
27
+ if (contentLength === null) {
28
+ throw new Error("Missing Content-Length header");
29
+ }
30
+
31
+ const bodyStart: number = headerEnd + Buffer.byteLength(HEADER_SEPARATOR, "ascii");
32
+ const bodyEnd: number = bodyStart + contentLength;
33
+ if (this.buffer.byteLength < bodyEnd) {
34
+ break;
35
+ }
36
+
37
+ const bodyText: string = this.buffer.subarray(bodyStart, bodyEnd).toString("utf8");
38
+ messages.push(JSON.parse(bodyText) as unknown);
39
+ this.buffer = this.buffer.subarray(bodyEnd);
40
+ }
41
+
42
+ return messages;
43
+ }
44
+ }
45
+
46
+ function parseContentLength(headerText: string): number | null {
47
+ const lines: string[] = headerText.split("\r\n");
48
+ for (const line of lines) {
49
+ const separatorIndex: number = line.indexOf(":");
50
+ if (separatorIndex < 0) {
51
+ continue;
52
+ }
53
+
54
+ const name: string = line.slice(0, separatorIndex).trim().toLowerCase();
55
+ if (name !== "content-length") {
56
+ continue;
57
+ }
58
+
59
+ const value: string = line.slice(separatorIndex + 1).trim();
60
+ if (!/^\d+$/.test(value)) {
61
+ throw new Error(`Invalid Content-Length header: ${value}`);
62
+ }
63
+
64
+ return Number.parseInt(value, 10);
65
+ }
66
+
67
+ return null;
68
+ }