核心实现在 ui.html 的 runAgentLoop(userMessage) 函数(约 1388 行)。流程如下:
纯机械命令(/save、/load、/status、开关配图等)直接调 apiCall('mud_action', ...) 走 GameEngine,不经过 LLM,避免浪费 token 和延迟。
登录用户的消息会先调 fetchMemoryContext(userMessage),从 MySQL/JSONL 中检索相关记忆(语义搜索 + 用户画像 + 当前场景),拼入 system prompt。
buildSystemPrompt() 根据登录/访客、MUD 模式、MUD 开关状态、当前游戏状态动态组装 prompt,包含文件目录规则、工具列表、EDIT_PRIORITY、AGENT_DRIFT_GUARD、MUD 叙事规则等。
while (loopCount < MAX_LOOPS) { // MAX_LOOPS = 10
// 4a. 上下文压缩(非 MUD 模式下,token 超阈值则调用 LLM 做 compactContext)
// 4b. 调 proxyRequest 请求 LLM(deepseek-v4-flash),带 tools 参数
// 4c. LLM 返回 message.tool_calls → 执行工具 → 结果追加到 messages → 继续循环
// 4d. LLM 返回纯文本(无 tool_calls)→ 展示给用户 → break
}
EnhancedToolExecutor.executeToolCalls() 遍历所有 tool_calls,调用对应的 handler(web_search、read、write、edit、mud_action、image_generation 等 12 个工具),结果截断至 50KB 后回传给 LLM。
proxyRequest(provider, path, body) 构造 {_token, _provider, _path, ...body},POST 到 proxy.php,后者做 CORS 校验、Token 验证、多 Key 轮换、curl 转发到 MiniMax/OpenCode-Go。遇到 429 自动换下一个 Key,全部耗尽返回 proxy_all_keys_exhausted。
_mudPendingImage/_mudPendingVoice(配图/语音)triggerMemoryExtraction 提取记忆commandQueue / processQueue)用户消息
│
▼
┌─────────────────────┐
│ MUD 命令预拦截 │ → 纯机械命令 → apiCall('mud_action') → GameEngine
└─────────────────────┘
│ 非机械命令
▼
┌─────────────────────┐
│ 记忆注入 │ → fetchMemoryContext() → MySQL/JSONL 语义搜索
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 构建 System Prompt │ → buildSystemPrompt() → 动态组装 prompt
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Agent Loop │ ← MAX_LOOPS = 10
│ ├─ 上下文压缩 │
│ ├─ proxyRequest() │ → LLM (deepseek-v4-flash)
│ ├─ tool_calls │ → EnhancedToolExecutor → 12 个工具 handler
│ └─ 纯文本 │ → 展示给用户 → break
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 后续处理 │
│ ├─ MUD: 配图/语音 │
│ ├─ 非MUD: 记忆提取 │
│ └─ 命令队列 │
└─────────────────────┘
| 决策 | 原因 |
|---|---|
| MUD 命令不经过 LLM | 避免浪费 token 和延迟 |
| MAX_LOOPS = 10 | 防止无限循环 |
| 工具结果截断至 50KB | 防止上下文溢出 |
| 429 自动换 Key | 提高可用性 |
| 记忆注入在构建 prompt 前 | 确保 LLM 有足够上下文 |