- AI 应用
- 人工智能
- AI Agent
- 本地部署
- 前端
- 后端
- 工作流自动化
【免费下载链接】hermes-studio
Ekko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web.
导读
在 Ekko Studio(本地优先的多智能体 AI 工作空间,同时支持桌面端与 Web)中,Claude Code、Codex、Pi、Grok、OpenCode 与 DSH 等无头(headless)编码代理运行时不具备终端交互能力,当任务遇到缺失的用户决策时往往只能猜测或卡死。本文基于仓库内变更记录 docs/chat-chain-changes/2026-09-17-coding-agent-mcp-clarification.md 并结合服务端与 MCP 网关源码,系统讲解ekko-studio-interactionMCP 服务器如何通过ekko_studio_clarify与ekko_studio_update_plan两个工具,让编码代理在 Studio/App 既有界面中向用户提问并等待回答、继续执行。读完本文,你将掌握该交互工具的传输范围与客户端分发矩阵、context_id绑定与指令注入机制、服务端澄清会话的生命周期与超时规则、四种显式reason语义,以及对应的测试覆盖与升级注意事项。
一、为什么需要"用户澄清":无头编码代理的交互缺口
编码代理(Coding Agent)在被 Studio 托管运行时,通常以无头方式启动,没有自己的交互式终端可供用户输入。传统上这类工具遇到"缺少关键决策"的场景(例如选择部署环境、确认删除路径、挑选实现方案)时,要么用默认值代替(可能产生错误结果),要么停在原地等待一个永远不会出现的输入。
该特性的核心思路是:复用 Studio/App 中已经存在的提问/任务卡片界面,把"向用户提问"封装为一个 MCP 工具,编码代理通过既有的 managed MCP 通道调用它,问题以clarify.requested事件呈现在界面中,用户在既有界面上回答后,答案以clarify.respond事件回流,ekko_studio_clarify的 HTTP 请求随之返回真实答案并显式给出reason。原文档将其影响概括为:"无头编码代理可以在现有 Studio/App 界面中提出问题,并在拿到用户回答后继续执行。"
二、传输与范围:一个重命名的共享 MCP 服务器
2.1ekko-studio-interaction:两个工具、一个服务器
原ekko-studio-plan(任务卡片)MCP 条目被重命名为ekko-studio-interaction,但不注册第二个服务器:改名而非新增,避免同一个启动命令被重复注册。改名后的服务器同时暴露两个直接工具:
ekko_studio_update_plan:创建或更新当前轮次的任务计划卡片(原ekko-studio-plan的能力,内部planlaunch 参数被保留以兼容既有配置);ekko_studio_clarify:向用户提出一个必要的澄清问题并等待回答。
从 packages/server/src/modules/hermes/services/mcp/studio-autoinject.ts 的MANAGED_SERVERS列表可以看到,ekko-studio-interaction的 toolset 为plan,与api、browser、devices、use并列;其旧名ekko-studio-plan出现在LEGACY_SERVER_NAMES中,用于识别并迁移存量配置(L10-L33)。在 packages/server/src/modules/coding-agents/services/mcp-manager.ts 的readServers()中有一行servers.delete('ekko-studio-plan')(L299),明确把旧条目从读取结果中剔除,再统一注入新名ekko-studio-interaction。
MCP 服务器本体实现于 bin/ekko-studio-mcp.mjs(stdio 协议,TOOLSETS = api/browser/devices/use/plan,按首个位置参数或HERMES_MCP_TOOLSET决定暴露哪个工具集)。plan工具集下同时注册ekko_studio_update_plan与ekko_studio_clarify,其中ekko_studio_clarify内部 POST 到/api/studio/clarifications/request,ekko_studio_update_plan内部 POST 到/api/studio/task-plans/update(L1959-L1966)。
2.2 客户端分发矩阵:谁拿到什么
原文档明确了各客户端的接收范围,这是一张值得完整保留的对照表:
| 客户端 | 接收ekko-studio-interaction | 可用工具 | 说明 |
|---|---|---|---|
| Claude Code / Codex / Pi / Grok / OpenCode / DSH | 是 | ekko_studio_update_plan+ekko_studio_clarify | 通过既有 managed 配置路径注入,无需新增客户端组件 |
| Hermes | 是 | 仅ekko_studio_update_plan | 澄清工具从 discovery 中隐藏、从指令中省略、直接调用被拒绝 |
| Ekko | 否 | — | 保留其原生工具(native tools),不接收该服务器 |
| Pi | 是 | 两个工具 | 同时保留其原生 RPC UI 支持 |
2.3 开关环境变量:HERMES_MCP_USER_CLARIFICATION
工具是否可见由一个环境变量控制,默认关闭:
- 默认值:off(未设置即不启用澄清);
- 只有 Coding Agent 注入路径显式设置
HERMES_MCP_USER_CLARIFICATION=1; - Hermes 的自动注入路径显式设置为
0。
在 packages/server/src/modules/coding-agents/services/index.ts 中,hermesMcpServerConfig()的 env 固定写入HERMES_MCP_USER_CLARIFICATION: '1'(L1194),而managedHermesMcpServerConfig()在 toolset 为plan时进一步确保该变量为'1'(L1213)。反观 Hermes 侧的 studio-autoinject.ts 的managedConfig(),env 中写入的是HERMES_MCP_USER_CLARIFICATION: '0'(L189)——从源码可以确认,这就是"只有 Coding Agent 注入开启、Hermes 显式关闭"的实现落点。
MCP 服务器侧在 bin/ekko-studio-mcp.mjs 用如下表达式计算开关(L70-L71):
const SHARED_TASK_PLAN_ENABLED = process.env.HERMES_MCP_NATIVE_TASK_PLAN !== '1' const USER_CLARIFICATION_ENABLED = SHARED_TASK_PLAN_ENABLED && process.env.HERMES_MCP_USER_CLARIFICATION === '1'并在activeToolsetTools()中过滤:(USER_CLARIFICATION_ENABLED || tool.name !== 'ekko_studio_clarify')(L1804-L1808)。也就是说,即使进程以plantoolset 启动,只要该变量不是'1',tools/list就不会暴露ekko_studio_clarify,tools/call也会拒绝调用——这正是"Hermes 端澄清被隐藏、被省略、被拒绝"的三重防护。serverInstructions()(L1822-L1826)在plantoolset 下只把澄清指令拼进系统提示当且仅当USER_CLARIFICATION_ENABLED为真,对应"从指令中省略"。
三、ekko_studio_clarify工具契约与参数校验
3.1 参数定义
工具接受context_id、question和可选的字符串数组choices;即使提供了choices,也仍然允许用户以自由文本回答。以下是 bin/ekko-studio-mcp.mjs 中ekko_studio_clarify的完整 input schema(L1013-L1021):
{ "context_id": { "type": "string", "description": "Current turn interaction context supplied by Studio." }, "question": { "type": "string", "minLength": 1, "maxLength": 4000 }, "choices": { "type": "array", "maxItems": 20, "items": { "type": "string", "minLength": 1, "maxLength": 500 } } }context_id与question为必填;choices可选。工具描述明确要求"只使用最新的 interaction context_id",并强调"超时、取消、关闭不等于同意(consent),先检查 reason 再继续"。
3.2 服务端校验边界
服务端 packages/server/src/modules/studio/services/clarification-runs.ts 的parseQuestion()(L14-L23)在显示任何问题之前做严格校验,任一不满足即抛ClarificationError(HTTP 400),且不会发出clarify.requested:
question必须是去空白后长度 1~4000 的字符串;choices若提供:必须是数组、最多 20 项、每项为去空白后长度 1~500 的非空字符串;重复项会被去重([...new Set(...)])。
测试 tests/server/clarification-runs.test.ts 的validates input before displaying a prompt用例覆盖了空问题、超长问题、非字符串选项、纯空白选项、超长选项、21 个选项等非法输入,并断言这些输入全部抛错且不产生clarify.requested事件。
四、交互绑定:context_id、studio_interaction_context与作用范围
4.1 capability id 与独立 interaction binding
每个交互式 coding-agent turn 的最新输入会附带一段studio_interaction_context指令。该指令使用的context_id与任务卡片(task card)共用同一个 capability id,但使用独立的 interaction binding(互不干扰)。从 packages/server/src/modules/studio/services/clarification-runs.ts 的clarificationTurnInstruction()(L113-L114)可以看到注入文本的完整内容,要点包括:
- 当缺失的用户决策实质性影响任务时,从同一个
ekko-studio-interactionMCP 服务器调用ekko_studio_clarify(与任务卡片同源),问题要简洁并可选提供选项; - 该工具会显示既有 Studio/App 提问界面并等待用户响应,用于替代无头模式下不可用的终端输入或原生交互工具;
- 若工具被延迟(deferred),应搜索
ekko-studio-interaction / clarify并使用发现到的工具名; - 超时、关闭、取消不是用户批准;
- 禁止在委托子代理(delegated subagents)或后台任务中使用;
- 指令末尾给出
Current turn context_id="...",并要求"只使用这个交互上下文,绝不要使用更早消息里的 id"。
4.2 注入时机与作用范围
在 packages/server/src/modules/studio/services/chat-run/handle-coding-agent-run.ts 中,interactionContext = mcpCapabilities.interaction ? data.interaction_context_id : undefined(L74),随后运行时输入被拼接为:
const runtimeInput = interactionContext ? `${plannedInput}\n\n${clarificationTurnInstruction(interactionContext)}` : plannedInput即指令只附加到该轮次最新输入上,而不是写进稳定的系统提示;context_id也不会出现在存储/展示的用户输入里。从 packages/server/src/modules/studio/sockets/chat-run.ts 的编排逻辑(L1709-L1714)可以确认作用范围:
const planContext = isCommand || !mcpCapabilities.interaction ? undefined : this.beginTaskPlanRun(data.session_id, profile) const interactionContext = planContext && source !== 'workflow' && data.session_source !== 'workflow' && source !== 'global_agent' && data.session_source !== 'global_agent' ? planContext : undefined if (interactionContext && data.session_id) { this.clarificationRuns.begin(interactionContext, data.session_id, profile, () => this.sessionMap.get(data.session_id!)) }据此可以总结出清晰的绑定范围:
| 场景 | 是否获得 interaction binding |
|---|---|
| 直接聊天(direct chat) | 是 |
| 群聊(group chat) | 是(经既有 manager relay 回答) |
| 工作流(workflow) | 否 |
| 全局后台代理(global background agent) | 否 |
| 独立终端(standalone terminal) | 否(无 binding) |
| 委托子代理 | 被指令明确禁止使用父级交互工具 |
4.3 会话与轮次解析:不信任调用方提供的 id
ekko_studio_clarify的 HTTP 请求体里只带context_id、question、choices,不携带 session/run id。Studio 从 binding(内存映射)解析出 session 与 history turn marker,而不是接受调用方提供的 session/run ids——这从根本上防止了跨会话、跨轮次的伪造或越权。绑定校验发生在 clarification-runs.ts 的active()(L37-L47):binding 不存在或 profile 不匹配返回 409("Interaction context is unavailable or has expired"),轮次状态不是 working、处于 aborting、没有活动 run marker、或 run id 与 binding 记录的 run id 不一致,均返回 409("Interaction context has no active turn")。
以下情况都会被拒绝:
- 其他 profile:binding 绑定到启动轮次时的 profile,
active()会比对binding.profile !== profile; - 过期 context:binding 不存在或已被清除;
- idle/aborting 轮次:
isWorking为假或isAborting为真; - 无效输入:见 3.2 节校验规则;
- 同一 session 内并发问题:
request()会检查 pending 表中是否已有同 session 的问题,存在则抛 409("Another clarification is already pending for this session")。
五、澄清会话的生命周期与事件流
5.1 状态机:begin → request → respond / 结算
ClarificationRuns维护两个内存 Map:bindings(contextId →{ sessionId, profile, resolve })与pending(clarifyId →{ contextId, sessionId, finish })。核心流程如下:
- begin(contextId, sessionId, profile, resolve):为新轮次注册绑定,同时调用
finishSession(sessionId)结算该 session 之前的旧等待; - request(contextId, profile, input, signal?):校验绑定与输入,生成
clarifyId = randomUUID(),注册 pending,发布clarify.requested,然后阻塞等待;CLARIFICATION_TIMEOUT_MS = 300_000(5 分钟,L8),定时器到期自动以timeout结算;AbortSignal触发时以cancelled结算; - respond(sessionId, clarifyId, response?):仅当 pending 存在且 session 匹配时结算——有回答文本 →
reason: 'response';空回答 →reason: 'dismissed';binding 失效 →reason: 'cancelled'; - finishSession(sessionId, contextId?):清除该 session 的 bindings,并以
cancelled结算所有 pending。
5.2 事件与 reason 语义
clarify.requested:沿用现有 question、choices、requested-at、timeout、remaining-time 字段(payload 含run_id、clarify_id、question、choices、timeout_ms、remaining_timeout_ms、requested_at);clarify.respond:直接聊天由客户端通过 socket 事件回答;群聊复用既有 manager relay;clarify.resolved:结算时向 session 广播,payload 含run_id、clarify_id、resolved: true、reason,并清除 pending replay 状态(即使答案来自另一个客户端也要同步清理);- HTTP 返回值:
{ clarify_id, response, reason },其中reason为四值枚举:response|dismissed|timeout|cancelled(clarification-runs.tsL5)。
一个必须强调的安全语义:"缺席的响应绝不等于批准"。超时、关闭、取消都会以非response的 reason 返回,Agent 若把这三种情况当成用户同意继续执行,将违反工具与指令的明确约束。
5.3 边界条件:哪些事件会使绑定失效、等待结算
原文档列出了完整的失效条件,对应源码中finishSession的调用点(chat-run.ts的beginTaskPlanRun、finishTaskPlanRun、以及各 socket 处理分支):
- 轮次完成(
run.completed)、失败(run.failed)、停止(abort.completed); - 被新轮次替换(
begin()会先finishSession旧 session); - session 处置(disposal);
- 服务器关闭;
- MCP 取消:
notifications/cancelled通知会 abort 对应请求(bin/ekko-studio-mcp.mjsL2199-L2201); - stdio 关闭:stdin close 时遍历 abort 所有 pending interactions(
L2243); - HTTP 断开:controller 中
ctx.res.once('close', ...)触发 abort(packages/server/src/modules/studio/controllers/clarifications.tsL13-L15)。
例外:单独的客户端 UI 断开不会取消问题——binding 保留,客户端重连/恢复后仍可继续回答。
由于 bindings 与 pending 都是内存态,服务器重启后不保留(有意设计):重启意味着所有等待以取消告终,这是可预期的、安全的默认行为。
六、超时预算与 HTTP 传输细节
澄清问题最长等待 5 分钟,因此整条链路的超时预算都必须覆盖这个业务期限:
MCP 工具级预算:所有受管 CLI 的 tool-call 预算至少 6 分钟(360 秒)。各 CLI 家族的字段名不同,从 packages/server/src/modules/coding-agents/services/index.ts(
L1215-L1216)与测试 tests/server/coding-agent-mcp-manager.test.ts(L86-L89)可看到映射:CLI 家族 预算字段 下限 claude-code / opencode timeout≥ 360_000 ms codex / grok tool_timeout_sec≥ 360 s pi requestTimeoutMs≥ 360_000 ms dsh toolCallTimeoutMs≥ 360_000 ms HTTP 传输级:
/api/studio/clarifications/request走的是自定义fetchMobileConsent(bin/ekko-studio-mcp.mjsL188-L213),使用330 秒(330_000 ms)的 deadline,而不是 fetch 默认的 300 秒 response-header deadline——因为用户交互可能在产生响应头之前就等待 5 分钟,330 秒给业务期限留了 30 秒缓冲,避免"业务未超时、传输先超时"的竞态。
七、升级与启用注意事项
原文档给出的运维要点,在部署时缺一不可:
- 升级后必须重启 Studio 与所有既有 coding-agent 进程,让它们重新加载更新后的 interaction MCP 工具目录(否则进程仍持有旧的
ekko-studio-plan工具集); - 工具是否真正被使用,取决于两个条件:
- Agent 是否遵循注入的
studio_interaction_context指令(模型需自行决定调用该工具,系统不保证每个模型都会调用); - managed interaction 服务器是否被启用(用户可通过 managed MCP 开关/override 禁用,例如测试
coding-agent-mcp-manager.test.ts中disabled: { codex: { default: ['ekko-studio-plan'] } }的用例展示了禁用 override 会跟随新名生效)。
- Agent 是否遵循注入的
此外,存量用户对ekko-studio-plan的 override 与禁用设置会自动跟随新名ekko-studio-interaction(mcp-overrides 与 grok 配置中的MANAGED_MCP_NAMES同时收录两个名字,packages/server/src/modules/coding-agents/services/grok/config.tsL8-L23),不需要用户手动迁移;内部planlaunch 参数继续保留,兼容旧配置。
八、验证体系:测试如何证明这条链路可靠
原文档列出了五类测试,仓库中均有对应实现:
- Service/controller 测试:tests/server/clarification-runs.test.ts 覆盖选项/自由文本回答、关闭(dismissal)、输入校验、超时、取消、profile/session 隔离、陈旧轮次(stale turn)被拒绝;tests/server/clarifications-controller.test.ts 覆盖 HTTP controller 路径;
- Socket 测试:tests/server/chat-run-bridge-readiness.test.ts 覆盖直接聊天与群聊两种
clarify.respond入口、pending replay 清理、abort 清理; - MCP 子进程测试:验证直接 discovery、阻塞直到 HTTP 回答返回、响应转发、取消通知(对应 bin/ekko-studio-mcp.mjs 中
notifications/cancelled→ abort →reason: cancelled的闭环); - 配置测试:tests/server/coding-agent-mcp-manager.test.ts 用
it.each(['claude-code', 'codex', 'pi', 'grok', 'opencode', 'dsh'])逐一断言六个 CLI family 都拿到ekko-studio-interaction、旧名ekko-studio-plan不再出现、managed为 true、预算字段 ≥ 360、HERMES_MCP_USER_CLARIFICATION为'1'; - 既有浏览器澄清测试:复用既有的提问界面组件,无需新增客户端组件。
需要明确的是:这些测试模拟 Agent 调用与用户回答,不依赖真实模型账号,也不会断言"每个模型都一定会调用该工具"——模型是否调用属于推理行为,不在服务端测试的承诺范围内。
九、总结
ekko-studio-interaction是 Ekko Studio 把"编码代理向用户提问"产品化的关键桥梁:通过一个重命名的共享 MCP 服务器、一个受环境变量严格控制的工具开关、一套以context_id为核心的内存绑定机制,以及"5 分钟业务超时 + 6 分钟工具预算 + 330 秒传输 deadline"的三级超时设计,让 Claude Code、Codex、Pi、Grok、OpenCode、DSH 在无头模式下也能安全地与用户在既有界面上交互。其设计中最值得借鉴的三点:binding 而非调用方自报 session/run id(杜绝跨会话越权)、显式reason而非布尔结果(让"缺席 ≠ 批准"成为可编程语义)、客户端 UI 断开与传输断开分离(支持重连恢复)。无论是排查澄清不生效、评估超时配置,还是为其他 Agent 家族接入同类交互能力,本文梳理的源码路径与测试用例都可以作为直接的入手点。
- AI 应用
- 人工智能
- AI Agent
- 本地部署
- 前端
- 后端
- 工作流自动化
【免费下载链接】hermes-studio
Ekko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web.
相关推荐
EEZ Studio 的 Agent 原生 CLI 工具链:cli-anything-eez-studio 实战指南
EEZ Studio 的 Agent 原生 CLI 工具链:cli anything eez studio 实战指南 cli anything eez stud
人工智能AI AgentAI 技能工具调用CLIMetabase SQL Agent 的澄清机制:`ask_for_sql_clarification` 工具的使用边界与底层实现
Metabase SQL Agent 的澄清机制: ask_for_sql_clarification 工具的使用边界与底层实现 Metabase 内置的 SQ
数据分析数据可视化后端数据库客户端企业应用mcp-agent Elicitation 实战指南:借助用户确认机制构建可交互的 MCP Server
mcp agent Elicitation 实战指南:借助用户确认机制构建可交互的 MCP Server 本文以仓库内示例 src/mcp_agent/data
人工智能AI AgentAgent 框架MCP ClientsAgent 工作流
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考