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.
- package/README.md +101 -0
- package/bin/godot-daedalus-backend.js +4 -0
- package/bin/godot-daedalus-mcp.js +4 -0
- package/bin/godot-daedalus-terminal-mcp.js +4 -0
- package/bin/run-tsx-entry.js +26 -0
- package/package.json +54 -0
- package/scripts/deepseek-tokenizer-server.py +54 -0
- package/src/app-paths.ts +36 -0
- package/src/main.ts +21 -0
- package/src/mcp/content-length-protocol.ts +68 -0
- package/src/mcp/custom-mcp-config-store.ts +397 -0
- package/src/mcp/godot-diagnostics-bridge.ts +1298 -0
- package/src/mcp/godot-editor-bridge.ts +307 -0
- package/src/mcp/godot-mcp-server.ts +3484 -0
- package/src/mcp/godot-paths.ts +151 -0
- package/src/mcp/godot-project-settings.ts +233 -0
- package/src/mcp/godot-tool-registration.ts +46 -0
- package/src/mcp/mcp-config.ts +48 -0
- package/src/mcp/mcp-host.ts +393 -0
- package/src/mcp/mcp-session.ts +81 -0
- package/src/mcp/terminal-mcp-server.ts +576 -0
- package/src/mcp/tscn-tools.ts +302 -0
- package/src/mcp/types.ts +12 -0
- package/src/ping-client.ts +24 -0
- package/src/prompts/registry.ts +97 -0
- package/src/prompts/templates/backend-helper.md +25 -0
- package/src/prompts/templates/gdscript-reviewer.md +19 -0
- package/src/prompts/templates/godot-assistant.md +225 -0
- package/src/prompts/templates/scene-architect.md +15 -0
- package/src/prompts/templates/session-compressor.md +33 -0
- package/src/protocol/schema.ts +486 -0
- package/src/protocol/types.ts +77 -0
- package/src/providers/deepseek-agent.ts +1014 -0
- package/src/providers/deepseek-client.ts +114 -0
- package/src/providers/deepseek-dsml-tools.ts +90 -0
- package/src/providers/deepseek-loose-tools.ts +450 -0
- package/src/providers/provider-config-store.ts +164 -0
- package/src/server/client-session.ts +93 -0
- package/src/server/request-dispatcher.ts +74 -0
- package/src/server/response-helpers.ts +33 -0
- package/src/server/send-json.ts +8 -0
- package/src/server/websocket-server.ts +3997 -0
- package/src/session/session-compressor.ts +68 -0
- package/src/session/session-store.ts +669 -0
- package/src/skills/registry.ts +180 -0
- package/src/skills/templates/backend-helper.md +12 -0
- package/src/skills/templates/file-creator.md +14 -0
- package/src/skills/templates/gdscript-review.md +12 -0
- package/src/skills/templates/godot-project-init.md +29 -0
- package/src/skills/templates/scene-builder.md +12 -0
- package/src/tokens/deepseek-tokenizer-counter.ts +233 -0
- package/src/tokens/model-profiles.ts +38 -0
- package/src/tokens/token-counter-factory.ts +52 -0
- package/src/tokens/token-counter.ts +22 -0
- package/src/tools/approval-gateway.ts +111 -0
- package/src/tools/llm-tools.ts +1415 -0
- package/src/tools/tool-dispatcher.ts +147 -0
- package/src/tools/tool-event-describer.ts +387 -0
- package/src/tools/tool-idempotency.ts +373 -0
- package/src/tools/tool-policy-table.ts +61 -0
- package/src/tools/tool-policy.ts +73 -0
- package/src/workflow/llm-planner.ts +407 -0
- package/src/workflow/planner.ts +201 -0
- package/src/workflow/runner.ts +141 -0
- package/src/workflow/types.ts +69 -0
- package/src/workspace/registry.ts +104 -0
- package/src/workspace/types.ts +7 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
export type TscnSection = {
|
|
2
|
+
type: string;
|
|
3
|
+
attributes: Record<string, string>;
|
|
4
|
+
lineIndex: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type TscnNode = {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
parent: string | null;
|
|
11
|
+
instance: string | null;
|
|
12
|
+
script: string | null;
|
|
13
|
+
properties: Record<string, string>;
|
|
14
|
+
lineStart: number;
|
|
15
|
+
lineEnd: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TscnConnection = {
|
|
19
|
+
signal: string;
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
method: string;
|
|
23
|
+
flags?: string | undefined;
|
|
24
|
+
binds?: string | undefined;
|
|
25
|
+
lineIndex: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type TscnData = {
|
|
29
|
+
header: TscnSection | null;
|
|
30
|
+
nodes: TscnNode[];
|
|
31
|
+
connections: TscnConnection[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ScenePatchOperation =
|
|
35
|
+
| { type: "add_node"; parentPath: string; nodeType: string; nodeName: string; properties?: Record<string, string> | undefined }
|
|
36
|
+
| { type: "attach_script"; nodePath: string; scriptPath: string }
|
|
37
|
+
| { type: "connect_signal"; signal: string; fromNode: string; toNode: string; method: string; flags?: number | undefined; binds?: string | undefined };
|
|
38
|
+
|
|
39
|
+
export function parseSectionHeader(line: string, lineIndex: number = 0): TscnSection | null {
|
|
40
|
+
const match: RegExpMatchArray | null = line.match(/^\[([a-zA-Z_][a-zA-Z0-9_]*)\s*(.*)\]$/);
|
|
41
|
+
if (match === null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const attributes: Record<string, string> = {};
|
|
46
|
+
const rest: string = match[2] ?? "";
|
|
47
|
+
for (const attrMatch of rest.matchAll(/([a-zA-Z_][a-zA-Z0-9_]*)=("[^"]*"|[^\s]+)/g)) {
|
|
48
|
+
attributes[attrMatch[1] as string] = attrMatch[2] as string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
type: match[1] as string,
|
|
53
|
+
attributes,
|
|
54
|
+
lineIndex
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function unquoteTscnString(value: string | undefined): string {
|
|
59
|
+
if (value === undefined) {
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
if (value.startsWith("\"") && value.endsWith("\"")) {
|
|
63
|
+
return value.slice(1, -1);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function quoteTscnString(value: string): string {
|
|
69
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function parseTscn(content: string): TscnData {
|
|
73
|
+
const lines: string[] = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
74
|
+
const data: TscnData = {
|
|
75
|
+
header: null,
|
|
76
|
+
nodes: [],
|
|
77
|
+
connections: []
|
|
78
|
+
};
|
|
79
|
+
let currentNode: TscnNode | null = null;
|
|
80
|
+
|
|
81
|
+
function finishNode(endLine: number): void {
|
|
82
|
+
if (currentNode !== null) {
|
|
83
|
+
currentNode.lineEnd = endLine;
|
|
84
|
+
data.nodes.push(currentNode);
|
|
85
|
+
currentNode = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
90
|
+
const line: string = lines[index] ?? "";
|
|
91
|
+
const section: TscnSection | null = parseSectionHeader(line, index);
|
|
92
|
+
if (section === null) {
|
|
93
|
+
if (currentNode !== null) {
|
|
94
|
+
const equalsIndex: number = line.indexOf("=");
|
|
95
|
+
if (equalsIndex > 0) {
|
|
96
|
+
const key: string = line.slice(0, equalsIndex).trim();
|
|
97
|
+
currentNode.properties[key] = line.slice(equalsIndex + 1).trim();
|
|
98
|
+
if (key === "script") {
|
|
99
|
+
currentNode.script = currentNode.properties[key] ?? null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
finishNode(index - 1);
|
|
107
|
+
if (section.type === "gd_scene") {
|
|
108
|
+
data.header = section;
|
|
109
|
+
} else if (section.type === "node") {
|
|
110
|
+
currentNode = {
|
|
111
|
+
name: unquoteTscnString(section.attributes.name),
|
|
112
|
+
type: unquoteTscnString(section.attributes.type),
|
|
113
|
+
parent: section.attributes.parent !== undefined ? unquoteTscnString(section.attributes.parent) : null,
|
|
114
|
+
instance: section.attributes.instance ?? null,
|
|
115
|
+
script: null,
|
|
116
|
+
properties: {},
|
|
117
|
+
lineStart: index,
|
|
118
|
+
lineEnd: index
|
|
119
|
+
};
|
|
120
|
+
} else if (section.type === "connection") {
|
|
121
|
+
data.connections.push({
|
|
122
|
+
signal: unquoteTscnString(section.attributes.signal),
|
|
123
|
+
from: unquoteTscnString(section.attributes.from),
|
|
124
|
+
to: unquoteTscnString(section.attributes.to),
|
|
125
|
+
method: unquoteTscnString(section.attributes.method),
|
|
126
|
+
flags: section.attributes.flags,
|
|
127
|
+
binds: section.attributes.binds,
|
|
128
|
+
lineIndex: index
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finishNode(lines.length - 1);
|
|
133
|
+
|
|
134
|
+
return data;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function validateTscnContent(content: string): string[] {
|
|
138
|
+
const errors: string[] = [];
|
|
139
|
+
const trimmedContent: string = content.trimStart();
|
|
140
|
+
if (!/^\[gd_scene\s/.test(trimmedContent)) {
|
|
141
|
+
errors.push("TSCN file must start with [gd_scene ...] header");
|
|
142
|
+
}
|
|
143
|
+
if (!/^\[node\s/m.test(trimmedContent)) {
|
|
144
|
+
errors.push("TSCN file must contain at least one [node ...] section (root node)");
|
|
145
|
+
}
|
|
146
|
+
return errors;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function generateSceneTscn(rootNodeType: string, rootNodeName: string): string {
|
|
150
|
+
return [
|
|
151
|
+
"[gd_scene format=3]",
|
|
152
|
+
"",
|
|
153
|
+
`[node name="${quoteTscnString(rootNodeName)}" type="${quoteTscnString(rootNodeType)}"]`,
|
|
154
|
+
""
|
|
155
|
+
].join("\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getNodeFullPath(node: TscnNode, allNodes: TscnNode[]): string {
|
|
159
|
+
if (node.parent === null) {
|
|
160
|
+
return ".";
|
|
161
|
+
}
|
|
162
|
+
if (node.parent === ".") {
|
|
163
|
+
return node.name;
|
|
164
|
+
}
|
|
165
|
+
return `${node.parent}/${node.name}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function findNodeInTscn(data: TscnData, targetPath: string): TscnNode | null {
|
|
169
|
+
const normalizedTarget: string = targetPath.trim() || ".";
|
|
170
|
+
for (const node of data.nodes) {
|
|
171
|
+
if (getNodeFullPath(node, data.nodes) === normalizedTarget || node.name === normalizedTarget && node.parent === null) {
|
|
172
|
+
return node;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getNextSectionIndex(lines: string[], startIndex: number): number {
|
|
179
|
+
for (let index = startIndex; index < lines.length; index += 1) {
|
|
180
|
+
if (/^\[[a-zA-Z_]/.test(lines[index] ?? "")) {
|
|
181
|
+
return index;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return lines.length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function findLastNodeInsertIndex(lines: string[]): number {
|
|
188
|
+
let insertIndex: number = lines.length;
|
|
189
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
190
|
+
if ((lines[index] ?? "").startsWith("[connection ")) {
|
|
191
|
+
return index;
|
|
192
|
+
}
|
|
193
|
+
if ((lines[index] ?? "").startsWith("[node ")) {
|
|
194
|
+
insertIndex = getNextSectionIndex(lines, index + 1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return insertIndex;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function nodePathForChild(parentPath: string, nodeName: string): string {
|
|
201
|
+
return parentPath === "." ? nodeName : `${parentPath}/${nodeName}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function addNodeToSceneTscn(content: string, parentPath: string, nodeType: string, nodeName: string, properties: Record<string, string>): string {
|
|
205
|
+
const data: TscnData = parseTscn(content);
|
|
206
|
+
const parentNode: TscnNode | null = findNodeInTscn(data, parentPath);
|
|
207
|
+
if (parentNode === null) {
|
|
208
|
+
throw new Error(`Parent node not found in scene: ${parentPath}`);
|
|
209
|
+
}
|
|
210
|
+
const candidatePath: string = nodePathForChild(parentPath, nodeName);
|
|
211
|
+
if (findNodeInTscn(data, candidatePath) !== null) {
|
|
212
|
+
throw new Error(`Node already exists in scene: ${candidatePath}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const lines: string[] = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
216
|
+
const nodeLines: string[] = [
|
|
217
|
+
"",
|
|
218
|
+
`[node name="${quoteTscnString(nodeName)}" type="${quoteTscnString(nodeType)}" parent="${quoteTscnString(parentPath)}"]`
|
|
219
|
+
];
|
|
220
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
221
|
+
nodeLines.push(`${key} = ${value}`);
|
|
222
|
+
}
|
|
223
|
+
lines.splice(findLastNodeInsertIndex(lines), 0, ...nodeLines);
|
|
224
|
+
return lines.join("\n");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function attachScriptToSceneTscn(content: string, nodePath: string, scriptPath: string): string {
|
|
228
|
+
const data: TscnData = parseTscn(content);
|
|
229
|
+
const targetNode: TscnNode | null = findNodeInTscn(data, nodePath);
|
|
230
|
+
if (targetNode === null) {
|
|
231
|
+
throw new Error(`Node not found in scene: ${nodePath}`);
|
|
232
|
+
}
|
|
233
|
+
if (targetNode.script !== null) {
|
|
234
|
+
throw new Error(`Node already has a script: ${nodePath}`);
|
|
235
|
+
}
|
|
236
|
+
if (!scriptPath.startsWith("res://") && !scriptPath.startsWith("ExtResource(")) {
|
|
237
|
+
throw new Error("scriptPath must be a res:// path or an ExtResource reference");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const lines: string[] = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
241
|
+
lines.splice(targetNode.lineEnd + 1, 0, `script = ${scriptPath.startsWith("ExtResource(") ? scriptPath : `ExtResource("${quoteTscnString(scriptPath)}")`}`);
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function connectSignalInSceneTscn(content: string, signal: string, fromNode: string, toNode: string, method: string, flags?: number, binds?: string): string {
|
|
246
|
+
const data: TscnData = parseTscn(content);
|
|
247
|
+
if (findNodeInTscn(data, fromNode) === null) {
|
|
248
|
+
throw new Error(`Signal source node not found in scene: ${fromNode}`);
|
|
249
|
+
}
|
|
250
|
+
if (findNodeInTscn(data, toNode) === null) {
|
|
251
|
+
throw new Error(`Signal target node not found in scene: ${toNode}`);
|
|
252
|
+
}
|
|
253
|
+
if (data.connections.some((connection: TscnConnection): boolean => connection.signal === signal && connection.from === fromNode && connection.to === toNode && connection.method === method)) {
|
|
254
|
+
throw new Error("This signal connection already exists in the scene");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const attrs: string[] = [
|
|
258
|
+
`signal="${quoteTscnString(signal)}"`,
|
|
259
|
+
`from="${quoteTscnString(fromNode)}"`,
|
|
260
|
+
`to="${quoteTscnString(toNode)}"`,
|
|
261
|
+
`method="${quoteTscnString(method)}"`
|
|
262
|
+
];
|
|
263
|
+
if (flags !== undefined) {
|
|
264
|
+
attrs.push(`flags=${flags}`);
|
|
265
|
+
}
|
|
266
|
+
if (binds !== undefined) {
|
|
267
|
+
attrs.push(`binds=${binds}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const lines: string[] = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
271
|
+
if (lines.length > 0 && (lines[lines.length - 1] ?? "").trim().length > 0) {
|
|
272
|
+
lines.push("");
|
|
273
|
+
}
|
|
274
|
+
lines.push(`[connection ${attrs.join(" ")}]`);
|
|
275
|
+
return lines.join("\n");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function applyScenePatchToTscn(content: string, operations: ScenePatchOperation[]): {
|
|
279
|
+
content: string;
|
|
280
|
+
applied: Array<{ type: string; target: string }>;
|
|
281
|
+
} {
|
|
282
|
+
let nextContent: string = content;
|
|
283
|
+
const applied: Array<{ type: string; target: string }> = [];
|
|
284
|
+
|
|
285
|
+
for (const operation of operations) {
|
|
286
|
+
if (operation.type === "add_node") {
|
|
287
|
+
nextContent = addNodeToSceneTscn(nextContent, operation.parentPath, operation.nodeType, operation.nodeName, operation.properties ?? {});
|
|
288
|
+
applied.push({ type: operation.type, target: nodePathForChild(operation.parentPath, operation.nodeName) });
|
|
289
|
+
} else if (operation.type === "attach_script") {
|
|
290
|
+
nextContent = attachScriptToSceneTscn(nextContent, operation.nodePath, operation.scriptPath);
|
|
291
|
+
applied.push({ type: operation.type, target: operation.nodePath });
|
|
292
|
+
} else if (operation.type === "connect_signal") {
|
|
293
|
+
nextContent = connectSignalInSceneTscn(nextContent, operation.signal, operation.fromNode, operation.toNode, operation.method, operation.flags, operation.binds);
|
|
294
|
+
applied.push({ type: operation.type, target: `${operation.fromNode}.${operation.signal}` });
|
|
295
|
+
} else {
|
|
296
|
+
const unreachable: never = operation;
|
|
297
|
+
throw new Error(`Unsupported scene patch operation: ${JSON.stringify(unreachable)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { content: nextContent, applied };
|
|
302
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type McpServerConfig = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string | undefined;
|
|
5
|
+
transport: "stdio" | "http";
|
|
6
|
+
command?: string | undefined;
|
|
7
|
+
args?: string[] | undefined;
|
|
8
|
+
env?: Record<string, string> | undefined;
|
|
9
|
+
url?: string | undefined;
|
|
10
|
+
headers?: Record<string, string> | undefined;
|
|
11
|
+
custom?: boolean | undefined;
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
|
|
3
|
+
const url: string = process.env.WS_URL ?? "ws://localhost:8080";
|
|
4
|
+
const socket: WebSocket = new WebSocket(url);
|
|
5
|
+
|
|
6
|
+
socket.on("open", (): void => {
|
|
7
|
+
socket.send(JSON.stringify({
|
|
8
|
+
type: "request",
|
|
9
|
+
id: "test-1",
|
|
10
|
+
method: "ping",
|
|
11
|
+
params: {}
|
|
12
|
+
}));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
socket.on("message", (data: WebSocket.RawData, isBinary: boolean): void => {
|
|
16
|
+
const text: string = isBinary ? data.toString("base64") : data.toString("utf8");
|
|
17
|
+
console.log(text);
|
|
18
|
+
socket.close();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
socket.on("error", (error: Error): void => {
|
|
22
|
+
console.error("WebSocket client error:", error);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { PromptId } from "../protocol/types.js";
|
|
4
|
+
|
|
5
|
+
export type PromptTemplate = {
|
|
6
|
+
id: PromptId;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
path: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_PROMPT_ID: PromptId = "godot.assistant";
|
|
13
|
+
|
|
14
|
+
export const promptTemplates: Record<PromptId, PromptTemplate> = {
|
|
15
|
+
"godot.assistant": {
|
|
16
|
+
id: "godot.assistant",
|
|
17
|
+
name: "Godot Assistant",
|
|
18
|
+
description: "General Godot Development Assistant",
|
|
19
|
+
path: "src/prompts/templates/godot-assistant.md"
|
|
20
|
+
},
|
|
21
|
+
"gdscript.reviewer": {
|
|
22
|
+
id: "gdscript.reviewer",
|
|
23
|
+
name: "GDScript Reviewer",
|
|
24
|
+
description: "Reviews GDScript code for type safety and style issues",
|
|
25
|
+
path: "src/prompts/templates/gdscript-reviewer.md"
|
|
26
|
+
},
|
|
27
|
+
"scene.architect": {
|
|
28
|
+
id: "scene.architect",
|
|
29
|
+
name: "Scene Architect",
|
|
30
|
+
description: "Designs Godot scene structures following scene-first principles",
|
|
31
|
+
path: "src/prompts/templates/scene-architect.md"
|
|
32
|
+
},
|
|
33
|
+
"backend.helper": {
|
|
34
|
+
id: "backend.helper",
|
|
35
|
+
name: "Backend Helper",
|
|
36
|
+
description: "TypeScript backend development for the AI Runtime",
|
|
37
|
+
path: "src/prompts/templates/backend-helper.md"
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const promptContentCache: Map<PromptId, string> = new Map();
|
|
42
|
+
const INSTRUCTION_PRIORITY_NOTICE: string = [
|
|
43
|
+
"冲突处理优先级:",
|
|
44
|
+
"1. Runtime 安全限制、后端强制策略、工具安全边界和审批流程。",
|
|
45
|
+
"2. 经 Runtime 工作区边界校验后加载的项目指令文件,例如 AGENTS.md、CLAUDE.md。",
|
|
46
|
+
"3. 用户当前消息中的明确任务目标。",
|
|
47
|
+
"4. 本 Settings 用户提示词。",
|
|
48
|
+
"5. 默认风格、通用建议和惯例。",
|
|
49
|
+
"",
|
|
50
|
+
"如果低优先级内容与高优先级内容冲突,只遵循不冲突的部分;必要时简短说明冲突原因。"
|
|
51
|
+
].join("\n");
|
|
52
|
+
|
|
53
|
+
const CUSTOM_INSTRUCTIONS_PRIORITY_NOTICE: string = [
|
|
54
|
+
"以下内容来自前端 Settings 的 Custom instructions,本轮请求会生效并随每次对话发送。",
|
|
55
|
+
"它只表示用户偏好或补充背景,不是工具结果、文件事实或项目规范。",
|
|
56
|
+
"如果它与系统规则、工具安全、项目指令文件或用户当前消息冲突,只遵循不冲突的部分。"
|
|
57
|
+
].join("\n");
|
|
58
|
+
|
|
59
|
+
const TOOL_CALL_COMMUNICATION_NOTICE: string = [
|
|
60
|
+
"工具调用沟通约定:",
|
|
61
|
+
"- 如果你决定调用工具,先用一句自然语言说明你马上要做什么,以及为什么这一步有必要。",
|
|
62
|
+
"- 预告应当简短、具体、像正常对话;不要输出工具协议、XML、DSML、JSON 参数或内部 tool_call 结构。",
|
|
63
|
+
"- 预告后直接发起工具调用,不要等待用户确认,除非工具安全策略或审批流程要求暂停。"
|
|
64
|
+
].join("\n");
|
|
65
|
+
|
|
66
|
+
export function listPromptTemplates(): PromptTemplate[] {
|
|
67
|
+
return Object.values(promptTemplates);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function loadPromptTemplate(promptId: PromptId): Promise<string> {
|
|
71
|
+
const cachedContent: string | undefined = promptContentCache.get(promptId);
|
|
72
|
+
if (cachedContent !== undefined) {
|
|
73
|
+
return cachedContent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const template: PromptTemplate = promptTemplates[promptId];
|
|
77
|
+
const templatePath: string = resolve(process.cwd(), template.path);
|
|
78
|
+
const content: string = await readFile(templatePath, "utf8");
|
|
79
|
+
const trimmedContent: string = content.trim();
|
|
80
|
+
promptContentCache.set(promptId, trimmedContent);
|
|
81
|
+
return trimmedContent;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function composeSystemPrompt(
|
|
85
|
+
promptId: PromptId | undefined,
|
|
86
|
+
extraSystemPrompt: string | undefined
|
|
87
|
+
): Promise<string> {
|
|
88
|
+
const templateContent: string = await loadPromptTemplate(promptId ?? DEFAULT_PROMPT_ID);
|
|
89
|
+
const trimmedExtraPrompt: string = extraSystemPrompt?.trim() ?? "";
|
|
90
|
+
const prioritizedTemplateContent: string = `${templateContent}\n\n## 工具调用沟通约定\n\n${TOOL_CALL_COMMUNICATION_NOTICE}\n\n## 指令优先级\n\n${INSTRUCTION_PRIORITY_NOTICE}`;
|
|
91
|
+
|
|
92
|
+
if (trimmedExtraPrompt.length === 0) {
|
|
93
|
+
return prioritizedTemplateContent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return `${prioritizedTemplateContent}\n\n## Settings 用户提示词(本轮生效)\n\n${CUSTOM_INSTRUCTIONS_PRIORITY_NOTICE}\n\n${trimmedExtraPrompt}`;
|
|
97
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
You are a TypeScript backend developer for the Godot Daedalus AI Runtime.
|
|
2
|
+
The project is a Node.js WebSocket server that bridges Godot clients to LLM providers.
|
|
3
|
+
|
|
4
|
+
Architecture:
|
|
5
|
+
- `src/main.ts` — entry point
|
|
6
|
+
- `src/protocol/` — types and zod schemas for the RPC protocol
|
|
7
|
+
- `src/server/` — WebSocket server and JSON transport
|
|
8
|
+
- `src/providers/` — LLM provider clients (DeepSeek, OpenAI, etc.)
|
|
9
|
+
- `src/prompts/` — system prompt templates
|
|
10
|
+
- `src/router/` — request routing by method
|
|
11
|
+
- `src/godot/` — Godot project context reading
|
|
12
|
+
|
|
13
|
+
Conventions:
|
|
14
|
+
- All code in TypeScript with `strict` mode.
|
|
15
|
+
- Use `zod` for runtime validation of all external input.
|
|
16
|
+
- Use `verbatimModuleSyntax` — type-only imports must use `import type`.
|
|
17
|
+
- Use NodeNext module resolution with `.js` extensions in imports.
|
|
18
|
+
- UTF-8 without BOM, LF line endings.
|
|
19
|
+
- Functions should have explicit return types.
|
|
20
|
+
- External messages (WebSocket, HTTP) must be validated before acting on them.
|
|
21
|
+
|
|
22
|
+
Key types:
|
|
23
|
+
- ClientRequest: `{ type: "request", id: string } & ({ method: "ping" } | { method: "ai.chat", params: { message: string } })`
|
|
24
|
+
- ServerResponse: discriminated union on `ok: true/false`
|
|
25
|
+
- API keys stay on the backend only, never exposed to Godot clients.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
You are a GDScript code reviewer for Godot projects.
|
|
2
|
+
Follow these rules strictly:
|
|
3
|
+
|
|
4
|
+
- All variables, parameters, and return types must use explicit static typing (`: int`, `: String`, etc.).
|
|
5
|
+
- Never use inferred type assignment `:=`.
|
|
6
|
+
- Do not repeat default initialization values (e.g., `var count: int` not `var count: int = 0`).
|
|
7
|
+
- Parameters and local variables must not shadow member variables.
|
|
8
|
+
- Prefer scene-tree paths over direct node variables when calling methods.
|
|
9
|
+
- Static node properties (position, size, color, theme, etc.) must be set in `.tscn` files, not in `_ready()` or `_init()`.
|
|
10
|
+
- Only runtime-dynamic properties should be set via script.
|
|
11
|
+
- Signal connections between static nodes must be configured in the `.tscn` scene file, never in `_ready()`.
|
|
12
|
+
- Use `uid://` resource paths when Godot provides stable UIDs.
|
|
13
|
+
|
|
14
|
+
When reviewing code, point out:
|
|
15
|
+
1. Missing type annotations.
|
|
16
|
+
2. Shadowed variables.
|
|
17
|
+
3. Static properties incorrectly set in scripts.
|
|
18
|
+
4. Signal connections that should be in the scene file.
|
|
19
|
+
5. Violations of GDScript style conventions.
|