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
@@ -0,0 +1,147 @@
1
+ import type { ChatCompletionMessageToolCall, ChatCompletionToolMessageParam } from "openai/resources/chat/completions";
2
+ import type { McpHost } from "../mcp/mcp-host.js";
3
+ import { type ApprovalGateway, type PendingApproval } from "./approval-gateway.js";
4
+ import { describeToolEvent, type ToolEventDisplay } from "./tool-event-describer.js";
5
+ import { executeLlmToolWithIdempotency } from "./tool-idempotency.js";
6
+
7
+ export type ToolEvent =
8
+ | { type: "ai.delta"; text: string }
9
+ | { type: "ai.thinking.delta"; text: string }
10
+ | { type: "ai.thinking.done" }
11
+ | ({ type: "tool.call"; step: number; toolCallId: string; toolName: string; args: Record<string, unknown> } & ToolEventDisplay)
12
+ | { type: "tool.result"; step: number; toolCallId: string; toolName: string; resultChars: number; truncated: boolean; cached?: boolean }
13
+ | { type: "tool.error"; step: number; toolCallId: string; toolName: string; message: string }
14
+ | ({ type: "tool.approval_required"; step: number; toolCallId: string; toolName: string; approvalId: string; reason: string; args: Record<string, unknown> } & ToolEventDisplay);
15
+
16
+ export type OnToolEvent = (event: ToolEvent) => void;
17
+
18
+ export class ToolApprovalRequiredError extends Error {
19
+ readonly pendingApproval: PendingApproval;
20
+
21
+ constructor(pendingApproval: PendingApproval) {
22
+ super(`Tool approval required: ${pendingApproval.approvalId}`);
23
+ this.name = "ToolApprovalRequiredError";
24
+ this.pendingApproval = pendingApproval;
25
+ }
26
+ }
27
+
28
+ async function executeSingleToolCall(
29
+ mcpHost: McpHost,
30
+ toolCall: ChatCompletionMessageToolCall,
31
+ step: number,
32
+ gateway: ApprovalGateway,
33
+ onEvent?: OnToolEvent
34
+ ): Promise<ChatCompletionToolMessageParam> {
35
+ if (toolCall.type !== "function") {
36
+ return {
37
+ role: "tool",
38
+ tool_call_id: toolCall.id,
39
+ content: "Error: Unsupported tool call type"
40
+ };
41
+ }
42
+
43
+ const functionName: string = toolCall.function.name;
44
+
45
+ let argsParsed: Record<string, unknown>;
46
+
47
+ try {
48
+ argsParsed = JSON.parse(toolCall.function.arguments) as Record<string, unknown>;
49
+ } catch {
50
+ const message: string = `Invalid JSON arguments: ${toolCall.function.arguments}`;
51
+ onEvent?.({ type: "tool.error", step, toolCallId: toolCall.id, toolName: functionName, message });
52
+ return {
53
+ role: "tool",
54
+ tool_call_id: toolCall.id,
55
+ content: `Error: ${message}`
56
+ };
57
+ }
58
+
59
+ const decision = await gateway.evaluate(functionName, argsParsed, toolCall.id);
60
+
61
+ if (decision.action === "deny") {
62
+ onEvent?.({ type: "tool.error", step, toolCallId: toolCall.id, toolName: functionName, message: decision.reason });
63
+ return {
64
+ role: "tool",
65
+ tool_call_id: toolCall.id,
66
+ content: `Error: ${decision.reason}`
67
+ };
68
+ }
69
+
70
+ if (decision.action === "request_approval") {
71
+ const pending = gateway.requestApproval(functionName, argsParsed, toolCall.id, decision.reason, mcpHost.getActiveWorkspaceId());
72
+ onEvent?.({
73
+ type: "tool.approval_required",
74
+ step,
75
+ toolCallId: toolCall.id,
76
+ toolName: functionName,
77
+ approvalId: pending.approvalId,
78
+ reason: decision.reason,
79
+ args: argsParsed,
80
+ ...describeToolEvent(functionName, argsParsed)
81
+ });
82
+
83
+ throw new ToolApprovalRequiredError(pending);
84
+ }
85
+
86
+ if (onEvent) {
87
+ onEvent({
88
+ type: "tool.call",
89
+ step,
90
+ toolCallId: toolCall.id,
91
+ toolName: functionName,
92
+ args: argsParsed,
93
+ ...describeToolEvent(functionName, argsParsed)
94
+ });
95
+ }
96
+
97
+ try {
98
+ const result = await executeLlmToolWithIdempotency(mcpHost, functionName, argsParsed);
99
+
100
+ if (onEvent) {
101
+ onEvent({
102
+ type: "tool.result",
103
+ step,
104
+ toolCallId: toolCall.id,
105
+ toolName: functionName,
106
+ resultChars: result.rawContentLength,
107
+ truncated: result.truncated,
108
+ cached: result.reused
109
+ });
110
+ }
111
+
112
+ return {
113
+ role: "tool",
114
+ tool_call_id: toolCall.id,
115
+ content: result.content
116
+ };
117
+ } catch (error: unknown) {
118
+ const message: string = error instanceof Error ? error.message : "MCP tool call failed";
119
+
120
+ if (onEvent) {
121
+ onEvent({ type: "tool.error", step, toolCallId: toolCall.id, toolName: functionName, message });
122
+ }
123
+
124
+ return {
125
+ role: "tool",
126
+ tool_call_id: toolCall.id,
127
+ content: `Error: ${message}`
128
+ };
129
+ }
130
+ }
131
+
132
+ export async function dispatchToolCalls(
133
+ mcpHost: McpHost,
134
+ toolCalls: ChatCompletionMessageToolCall[],
135
+ step: number,
136
+ gateway: ApprovalGateway,
137
+ onEvent?: OnToolEvent
138
+ ): Promise<ChatCompletionToolMessageParam[]> {
139
+ const results: ChatCompletionToolMessageParam[] = [];
140
+
141
+ for (const toolCall of toolCalls) {
142
+ const result = await executeSingleToolCall(mcpHost, toolCall, step, gateway, onEvent);
143
+ results.push(result);
144
+ }
145
+
146
+ return results;
147
+ }
@@ -0,0 +1,387 @@
1
+ import { getDynamicMcpToolMetadata, isDynamicMcpToolName } from "./llm-tools.js";
2
+
3
+ export type ToolEventCategory =
4
+ | "read"
5
+ | "write"
6
+ | "search"
7
+ | "terminal"
8
+ | "scene"
9
+ | "approval"
10
+ | "propose"
11
+ | "docs"
12
+ | "unknown";
13
+
14
+ export type ToolEventTarget = {
15
+ kind: "file" | "scene" | "command" | "query" | "approval" | "unknown";
16
+ path?: string;
17
+ line?: number;
18
+ label?: string;
19
+ };
20
+
21
+ export type ToolEventDisplay = {
22
+ serverId: string;
23
+ serverName: string;
24
+ category: ToolEventCategory;
25
+ title: string;
26
+ summary: string;
27
+ target: ToolEventTarget;
28
+ };
29
+
30
+ function getStringArg(args: Record<string, unknown>, key: string): string | undefined {
31
+ const value: unknown = args[key];
32
+ return typeof value === "string" && value.length > 0 ? value : undefined;
33
+ }
34
+
35
+ function parseOperationJson(args: Record<string, unknown>): Record<string, unknown> {
36
+ const operationJson: string | undefined = getStringArg(args, "operationJson");
37
+ if (operationJson === undefined) {
38
+ return {};
39
+ }
40
+
41
+ try {
42
+ const parsed: unknown = JSON.parse(operationJson);
43
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
44
+ ? parsed as Record<string, unknown>
45
+ : {};
46
+ } catch {
47
+ return {};
48
+ }
49
+ }
50
+
51
+ function createDisplay(
52
+ serverId: string,
53
+ serverName: string,
54
+ category: ToolEventCategory,
55
+ title: string,
56
+ summary: string,
57
+ target: ToolEventTarget
58
+ ): ToolEventDisplay {
59
+ return { serverId, serverName, category, title, summary, target };
60
+ }
61
+
62
+ export function describeToolEvent(toolName: string, args: Record<string, unknown>): ToolEventDisplay {
63
+ if (isDynamicMcpToolName(toolName)) {
64
+ const metadata = getDynamicMcpToolMetadata(toolName);
65
+ const serverId: string = metadata?.serverId ?? "custom";
66
+ const serverName: string = metadata?.serverName ?? "Custom MCP";
67
+ const originalToolName: string = metadata?.toolName ?? toolName;
68
+ return createDisplay(serverId, serverName, "write", "自定义 MCP 工具", `${serverName}: ${originalToolName}`, {
69
+ kind: "unknown",
70
+ label: originalToolName
71
+ });
72
+ }
73
+
74
+ if (toolName.startsWith("mcp_godot_editor_")) {
75
+ const scenePath: string | undefined = getStringArg(args, "scenePath");
76
+ const nodePath: string | undefined = getStringArg(args, "nodePath");
77
+ const targetLabel: string = nodePath ?? scenePath ?? "Godot Editor";
78
+
79
+ if (toolName.includes("get_context")) {
80
+ return createDisplay("godot_editor", "Godot Editor", "read", "读取编辑器上下文", "读取当前编辑器在线状态与场景上下文", {
81
+ kind: "unknown",
82
+ label: "Godot Editor"
83
+ });
84
+ }
85
+
86
+ if (toolName.includes("get_selected_nodes")) {
87
+ return createDisplay("godot_editor", "Godot Editor", "read", "读取选中节点", "读取当前编辑器选中的节点", {
88
+ kind: "scene",
89
+ label: "selected nodes"
90
+ });
91
+ }
92
+
93
+ if (toolName.includes("inspect_node")) {
94
+ const target: ToolEventTarget = scenePath === undefined ? {
95
+ kind: "scene",
96
+ label: targetLabel
97
+ } : {
98
+ kind: "scene",
99
+ path: scenePath,
100
+ label: targetLabel
101
+ };
102
+ return createDisplay("godot_editor", "Godot Editor", "read", "查看在线节点", `查看 ${targetLabel}`, {
103
+ ...target
104
+ });
105
+ }
106
+
107
+ if (toolName.includes("apply_scene_patch")) {
108
+ const target: ToolEventTarget = scenePath === undefined ? {
109
+ kind: "scene",
110
+ label: "当前场景"
111
+ } : {
112
+ kind: "scene",
113
+ path: scenePath,
114
+ label: scenePath
115
+ };
116
+ return createDisplay("godot_editor", "Godot Editor", "scene", "编辑在线场景", `编辑 ${scenePath ?? "当前场景"}`, {
117
+ ...target
118
+ });
119
+ }
120
+ }
121
+
122
+ if (toolName.startsWith("mcp_godot_")) {
123
+ const relativePath: string | undefined = getStringArg(args, "relativePath") ?? getStringArg(args, "scenePath");
124
+ const resourcePath: string | undefined = getStringArg(args, "resourcePath") ?? relativePath;
125
+ const settingKey: string | undefined = getStringArg(args, "key");
126
+
127
+ if (toolName.includes("lsp_get_status")) {
128
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "检查 LSP 状态", "探测 Godot GDScript LSP", {
129
+ kind: "unknown",
130
+ label: "Godot LSP"
131
+ });
132
+ }
133
+
134
+ if (toolName.includes("lsp_get_file_diagnostics")) {
135
+ const targetLabel: string = resourcePath ?? "script";
136
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "读取脚本诊断", `读取 ${targetLabel} 的 LSP 诊断`, {
137
+ kind: "file",
138
+ path: targetLabel,
139
+ label: targetLabel
140
+ });
141
+ }
142
+
143
+ if (toolName.includes("lsp_get_document_symbols")) {
144
+ const targetLabel: string = resourcePath ?? "script";
145
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "查看脚本符号", `查看 ${targetLabel} 的符号结构`, {
146
+ kind: "file",
147
+ path: targetLabel,
148
+ label: targetLabel
149
+ });
150
+ }
151
+
152
+ if (toolName.includes("lsp_hover")) {
153
+ const targetLabel: string = resourcePath ?? "script";
154
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "查看 Hover 信息", `查看 ${targetLabel} 的符号说明`, {
155
+ kind: "file",
156
+ path: targetLabel,
157
+ label: targetLabel
158
+ });
159
+ }
160
+
161
+ if (toolName.includes("lsp_goto_definition")) {
162
+ const targetLabel: string = resourcePath ?? "script";
163
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "查找定义", `查找 ${targetLabel} 中的定义`, {
164
+ kind: "file",
165
+ path: targetLabel,
166
+ label: targetLabel
167
+ });
168
+ }
169
+
170
+ if (toolName.includes("dap_get_status")) {
171
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "检查 DAP 状态", "探测 Godot DAP 调试会话", {
172
+ kind: "unknown",
173
+ label: "Godot DAP"
174
+ });
175
+ }
176
+
177
+ if (toolName.includes("dap_get_last_error")) {
178
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "读取运行错误", "读取 Godot DAP 最近运行错误", {
179
+ kind: "unknown",
180
+ label: "last runtime error"
181
+ });
182
+ }
183
+
184
+ if (toolName.includes("dap_get_stack_trace")) {
185
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "读取调用栈", "读取 Godot DAP 调用栈", {
186
+ kind: "unknown",
187
+ label: "stack trace"
188
+ });
189
+ }
190
+
191
+ if (toolName.includes("dap_get_variables")) {
192
+ const reference: string = String(args["variablesReference"] ?? "variables");
193
+ return createDisplay("godot_diagnostics", "Godot Diagnostics", "read", "读取变量", `读取变量引用 ${reference}`, {
194
+ kind: "unknown",
195
+ label: reference
196
+ });
197
+ }
198
+
199
+ if (toolName.includes("get_project_log_config")) {
200
+ return createDisplay("godot", "Godot", "read", "读取日志配置", "解析 Godot 项目日志路径", {
201
+ kind: "unknown",
202
+ label: "project log config"
203
+ });
204
+ }
205
+
206
+ if (toolName.includes("list_project_logs")) {
207
+ return createDisplay("godot", "Godot", "read", "列出项目日志", "列出 Godot 项目日志文件", {
208
+ kind: "file",
209
+ label: "project logs"
210
+ });
211
+ }
212
+
213
+ if (toolName.includes("read_project_log")) {
214
+ const fileName: string = getStringArg(args, "fileName") ?? "godot.log";
215
+ return createDisplay("godot", "Godot", "read", "读取项目日志", `读取 ${fileName}`, {
216
+ kind: "file",
217
+ label: fileName
218
+ });
219
+ }
220
+
221
+ if (toolName.includes("get_project_settings")) {
222
+ return createDisplay("godot", "Godot", "read", "读取项目设置", "读取 project.godot 设置", {
223
+ kind: "file",
224
+ path: "project.godot",
225
+ label: "project.godot"
226
+ });
227
+ }
228
+
229
+ if (toolName.includes("get_editor_config_summary")) {
230
+ return createDisplay("godot", "Godot", "read", "读取编辑器摘要", "读取 Godot 编辑器设置与项目编辑状态摘要", {
231
+ kind: "unknown",
232
+ label: "Godot editor config"
233
+ });
234
+ }
235
+
236
+ if (toolName.includes("get_editor_settings")) {
237
+ return createDisplay("godot", "Godot", "read", "读取编辑器设置", "读取 editor_settings 配置", {
238
+ kind: "file",
239
+ label: "editor_settings"
240
+ });
241
+ }
242
+
243
+ if (toolName.includes("list_editor_config_files")) {
244
+ return createDisplay("godot", "Godot", "read", "列出编辑器配置", "列出可读的 Godot 编辑器配置文件", {
245
+ kind: "file",
246
+ label: "editor config files"
247
+ });
248
+ }
249
+
250
+ if (toolName.includes("read_editor_config_file")) {
251
+ const fileId: string = getStringArg(args, "fileId") ?? getStringArg(args, "filePath") ?? "editor config";
252
+ return createDisplay("godot", "Godot", "read", "读取编辑器配置", `读取 ${fileId}`, {
253
+ kind: "file",
254
+ label: fileId
255
+ });
256
+ }
257
+
258
+ if (toolName.includes("get_editor_project_state")) {
259
+ return createDisplay("godot", "Godot", "read", "读取编辑器状态", "读取当前项目 .godot/editor 状态", {
260
+ kind: "file",
261
+ path: ".godot/editor",
262
+ label: ".godot/editor"
263
+ });
264
+ }
265
+
266
+ if (toolName.includes("get_recent_projects")) {
267
+ return createDisplay("godot", "Godot", "read", "读取最近项目", "读取 Godot 最近项目与目录", {
268
+ kind: "file",
269
+ label: "projects.cfg"
270
+ });
271
+ }
272
+
273
+ if (toolName.includes("propose_set_project_setting") || toolName.includes("propose_unset_project_setting")) {
274
+ const targetLabel: string = settingKey ?? "project setting";
275
+ return createDisplay("godot", "Godot", "propose", "预览项目设置修改", `预览 ${targetLabel}`, {
276
+ kind: "file",
277
+ path: "project.godot",
278
+ label: targetLabel
279
+ });
280
+ }
281
+
282
+ if (toolName.includes("set_project_setting") || toolName.includes("unset_project_setting")) {
283
+ const targetLabel: string = settingKey ?? "project setting";
284
+ return createDisplay("godot", "Godot", "write", "修改项目设置", `修改 ${targetLabel}`, {
285
+ kind: "file",
286
+ path: "project.godot",
287
+ label: targetLabel
288
+ });
289
+ }
290
+
291
+ if (toolName.includes("read_text_file")) {
292
+ const filePath: string = relativePath ?? "unknown file";
293
+ return createDisplay("godot", "Godot", "read", "读取文件", `读取 ${filePath}`, {
294
+ kind: "file",
295
+ path: filePath,
296
+ label: filePath
297
+ });
298
+ }
299
+
300
+ if (toolName.includes("search_text")) {
301
+ const query: string = getStringArg(args, "query") ?? "";
302
+ return createDisplay("godot", "Godot", "search", "搜索文本", `搜索 ${query}`, {
303
+ kind: "query",
304
+ label: query
305
+ });
306
+ }
307
+
308
+ if (toolName.includes("propose_")) {
309
+ const targetKind: ToolEventTarget["kind"] = toolName.includes("scene") || toolName.includes("scene_patch") || relativePath?.endsWith(".tscn")
310
+ ? "scene"
311
+ : "file";
312
+ const targetLabel: string = relativePath ?? (targetKind === "scene" ? "unknown scene" : "unknown file");
313
+ const title: string = targetKind === "scene" ? "预览场景修改" : "预览文件修改";
314
+ return createDisplay("godot", "Godot", "propose", title, `${title} ${targetLabel}`, {
315
+ kind: targetKind,
316
+ path: targetLabel,
317
+ label: targetLabel
318
+ });
319
+ }
320
+
321
+ if (toolName.includes("scene")) {
322
+ const scenePath: string = relativePath ?? "unknown scene";
323
+ const category: ToolEventCategory = toolName.includes("inspect") ? "read" : "scene";
324
+ const title: string = toolName.includes("inspect") ? "查看场景" : "编辑场景";
325
+ return createDisplay("godot", "Godot", category, title, `${title} ${scenePath}`, {
326
+ kind: "scene",
327
+ path: scenePath,
328
+ label: scenePath
329
+ });
330
+ }
331
+
332
+ if (toolName.includes("create_text_file") || toolName.includes("overwrite_text_file") || toolName.includes("replace_text_in_file") || toolName.includes("delete_file")) {
333
+ const filePath: string = relativePath ?? "unknown file";
334
+ return createDisplay("godot", "Godot", "write", "写入文件", `写入 ${filePath}`, {
335
+ kind: "file",
336
+ path: filePath,
337
+ label: filePath
338
+ });
339
+ }
340
+
341
+ return createDisplay("godot", "Godot", "unknown", "Godot 工具", toolName, {
342
+ kind: "unknown",
343
+ label: toolName
344
+ });
345
+ }
346
+
347
+ if (toolName === "mcp_terminal_run_godot_scene_script") {
348
+ const operation: Record<string, unknown> = parseOperationJson(args);
349
+ const scenePath: string = typeof operation.scene_path === "string"
350
+ ? operation.scene_path
351
+ : typeof operation.path === "string"
352
+ ? operation.path
353
+ : "scene operation";
354
+ return createDisplay("terminal", "Terminal", "scene", "执行 Godot 场景脚本", `场景操作 ${scenePath}`, {
355
+ kind: "scene",
356
+ path: scenePath,
357
+ label: scenePath
358
+ });
359
+ }
360
+
361
+ if (toolName.startsWith("mcp_terminal_")) {
362
+ const presetName: string = getStringArg(args, "presetName") ?? toolName;
363
+ const resourcePath: string | undefined = getStringArg(args, "resourcePath");
364
+ const label: string = resourcePath === undefined ? presetName : `${presetName} ${resourcePath}`;
365
+ const target: ToolEventTarget = resourcePath === undefined ? {
366
+ kind: "command",
367
+ label
368
+ } : {
369
+ kind: "command",
370
+ path: resourcePath,
371
+ label
372
+ };
373
+ return createDisplay("terminal", "Terminal", "terminal", "运行终端命令", label, target);
374
+ }
375
+
376
+ if (toolName.includes("context7") || toolName.includes("library") || toolName.includes("docs")) {
377
+ return createDisplay("context7", "Context7", "docs", "查询文档", toolName, {
378
+ kind: "query",
379
+ label: toolName
380
+ });
381
+ }
382
+
383
+ return createDisplay("unknown", "MCP", "unknown", "MCP 工具", toolName, {
384
+ kind: "unknown",
385
+ label: toolName
386
+ });
387
+ }