import type { HandlerResult, ToolContext, ToolCallDelegate } from '../types.js'; import type { ReadOnlyGuard } from './ReadOnlyGuard.js'; import type { Tool, ServerContext } from "@modelcontextprotocol/server"; import type { EditorToolExecutor } from './EditorToolExecutor.js'; import { type ElicitFn } from './elicit.js'; import { HealthMonitor } from './health-monitor.js'; import type { AgentContextManager } from './agent-context.js'; import { type ProgressEmitter } from './progress.js'; /** Known profile names for IDE autocomplete. Unknown strings fall through to resolveProfile(). */ type KnownProfile = 'full' | 'basic' | 'lite' | 'minimal' | 'bridge_dev' | '3d_dev'; export interface DispatcherOptions { readOnly: boolean; mode: KnownProfile | string; connectionMode: 'headless' | 'editor'; noFallback: boolean; readOnlyGuard: ReadOnlyGuard; editorExecutor?: EditorToolExecutor; opsScript: string; findGodot: (projectPath?: string) => Promise; toolCallDelegate: (fn: ToolCallDelegate | null) => void; agentContext?: AgentContextManager; /** CRITICAL(2026-07-13 安全): out-of-band 用户确认函数(堵 AI 自确认 token)。默认 createElicitFn()。 */ elicitFn?: ElicitFn; } export declare class ToolDispatcher { private readonly options; private readonly readOnlyGuard; private connectionMode; private editorExecutor; private readonly ctx; private _editorFallback; private _editorFallbackWarned; private healthMonitor; private readonly middleware; /** CRITICAL(2026-07-13 安全): out-of-band elicitation — confirm_and_execute 强制用户确认(堵 AI 自确认)。 */ private readonly elicitFn; /** Deferred mode switch — applied at the start of the next handleCall. Prevents * editor disconnect callbacks from switching mode mid-request (C-01). */ private _pendingModeSwitch; constructor(options: DispatcherOptions); getHealthMonitor(): HealthMonitor; getFilteredTools(): Tool[]; handleCall(request: { params: { name: string; arguments?: Record; }; }, srvCtx?: ServerContext, clientTasksCapable?: boolean): Promise; private executeToolCall; private buildMiddleware; /** Schedule a connection mode change. Applied at the start of the next handleCall * to prevent mid-request mode switches from editor disconnect callbacks (C-01). */ setConnectionMode(mode: 'headless' | 'editor'): void; /** Schedule an executor change. Destroys the old executor immediately to release * resources (WebSocket listeners, etc.), but defers the instance assignment to the * next handleCall entry (C-01). This ensures a running handleCall keeps its snapshot * executor reference stable throughout the async operation. */ setEditorExecutor(executor: EditorToolExecutor | null): void; /** I-04: Atomically degrade to headless mode. Avoids two separate calls to * setConnectionMode + setEditorExecutor racing on _pendingModeSwitch. */ degradeToHeadless(): void; /** Get the effective executor: pending switch takes precedence over current instance. */ private _resolvePendingExecutor; /** Apply any deferred mode switch. Called at the top of handleCall, outside of any await. */ private _applyPendingModeSwitch; /** 标记 editor fallback 状态(由 GodotServer.run() 调用) */ markEditorFallback(): void; /** I-05: Convert camelCase arg keys to snake_case, recursively for nested plain objects. */ private static readonly MAX_NORMALIZE_DEPTH; private normalizeArgs; /** Validate common arg types (project_path, action). Returns error ToolResult or null. */ private validateCommonArgs; /** * A-10 (advisory): 仅校验根级路径字段(project_path/search_dir)是否在 ALLOWED_PROJECT_PATHS。 * 其余路径参数(file_path/script_path/scene_path 等)语义多样(res://、项目内相对、绝对路径), * 由各工具自行调 resolveWithinRoot 校验——**新增工具须确保其路径参数经过 resolveWithinRoot**, * 否则绕过根限制。未做通用扩展因 file_path 等字段语义不一,通用 isPathInAllowedRoots 会误伤。 */ private validatePathArgs; /** * CR-1/CR-2: 基于 args 计算本次调用的 findGodot override。 * - 有 godot_path → 校验绝对路径 + Godot 二进制后返回固定值 * - 无 godot_path → 返回项目感知 findGodot(基于 project_path) * 抽取为独立方法以便 executeToolCall 入口和 confirm_and_execute 分支 * (后者须基于 pending.args 而非 confirm_and_execute 自身 args)各自调用。 */ private resolveFindGodotOverride; /** * P0-2 MRTR: confirm_and_execute 的执行段(从 executeToolCall 提取)。 * 第一轮(inputRequired 返回)和第二轮(用户确认后)共用此方法。 * 含二次 guard 检查 + 路径校验 + findGodotOverride + editor/headless 分支。 */ private _confirmExecute; /** B-1 修复(审查):confirm_and_execute 真实执行后补审计。 * _confirmExecute → dispatchTool 绕过 executeMiddleware,audit middleware 接不到; * 且外层 confirm_and_execute 的 ctx.action='' 会被 audit 跳过。故此处用 pending 的 * 真实 tool/action/risk + 真实执行结果显式补一条审计(details.confirmed=true 标记)。 */ private _auditConfirmedExecution; /** * editor/headless dispatch 共用逻辑(executeToolCall 与 _confirmExecute 复用,消除重复)。 * editor 模式:currentExecutor.execute;-32601 unknown method 自动回退 headless(P1-1: * command_handler 只认扁平 method,TS 工具 (tool,action) 命名转发落 -32601 静默失效, * 检测到 -32601 回退让非编辑器原生工具在 editor 模式仍可用;isError 前置避免误判)。 * headless 模式:dispatchTool(findGodotOverride 必传,CR-1)。 */ private _dispatchEditorOrHeadless; private dispatchTool; /** P1-1 (2026-07-06 review): 检测 editor 返回是否 -32601 Unknown method * (command_handler 不认此 method — TS (tool,action) 工具转发后常见)。 */ private _isUnknownMethod; /** * P1-2 (2026-07-06 review): 经 WS 调编辑器 guard, 返回是否阻塞写。 * - editorExecutor 不可用(null) → 放行(headless 无编辑器状态可守) * - 编辑器返回 -32009(状态冲突: 打开的脚本/缓存 Resource/打开的场景) → 阻塞 * - 其他(guard 放行 ok / -32003 guards 不可用 / 连接错误) → 放行(不静默吞, 调用方据 blocked 决定) */ private _checkEditorGuard; private attachFallbackWarning; /** Parse content blocks and check for logical failure. * * Phase 1 升级:改用 response-format.ts 的 isErrorText,覆盖多种 error shape。 * 旧实现只检测 {success:false} 一种,漏判 {ok:false} / {error:string} / {error:{message}} / * {error_code, message} 形态的逻辑失败(对标 unity-mcp-server response-format.js:31-41)。 */ private checkJsonSuccessFalse; } /** * 构建每次工具调用的 ctx 副本(覆盖 findGodot)。 * * 必须用 Object.create(继承 this.ctx 原型),而非 spread {...this.ctx}:ctx 上的 * runningProcess/outputBuffer/processStartTime/projectDir 是连 process-state 模块的 getter, * spread 会把它们展平成调用时刻的快照。dispatch 入口 _runningProcess=null,spread 后 * perCallCtx.runningProcess 被冻结成 null —— 即便 run_project 内 setRunningProcess(proc) 改了 * 模块 state,perCallCtx.runningProcess 仍为 null,isCancelled(runningProcess !== proc)永远 true, * 导致 wait_for_bridge 误报 "process exited during probe"。Object.create 让 perCallCtx 继承 getter, * runningProcess 实时反映模块 state。 */ export declare function buildPerCallCtx(baseCtx: ToolContext, findGodotOverride?: (projectPath?: string) => Promise, progressEmitter?: ProgressEmitter, taskAugmented?: boolean): ToolContext; export {};