clodexIDE
§ · Nested detail page

Agent Runtime 深度解析

本页只拆一个模块面,用真实源码行号解释数据流、边界和失败模式。

§ 00 · DETAIL

导入链与契约

▸isolated turn 的 handler 边界

导入链很短但契约强:apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:16 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:17 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:18 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:19 接收 request 和 host handlers,说明 isolated runtime 不直接拿模型或工具实现,只通过 handler 边界调用。

src/apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts · L16-73TypeScript
16export async function executeIsolatedAgentTurn(
17  request: IsolatedAgentTurnRequest,
18  options: IsolatedAgentTurnRuntimeOptions,
19): Promise<IsolatedAgentTurnResult> {
20  throwIfAborted(options.signal);
21
22  const messages: IsolatedAgentConversationMessage[] = structuredClone(
23    request.messages,
24  );
25  const steps: IsolatedAgentTurnStepResult[] = [];
26  const allowedToolNames = new Set(request.tools.map((tool) => tool.name));
27  let text = '';
28
29  for (let step = 1; step <= request.maxSteps; step++) {
30    throwIfAborted(options.signal);
31    options.onEvent?.({ type: 'step-started', step });
32
33    const modelResult = await options.handlers.callModel(
34      {
35        agentInstanceId: request.agentInstanceId,
36        modelId: request.modelId,
37        traceId: request.traceId,
38        metadata: request.metadata,
39        systemPrompt: request.systemPrompt,
40        messages,
41        tools: request.tools,
42        settings: request.settings,
43      },
44      {
45        signal: requireSignal(options.signal),
46        onEvent: (event) => {
47          options.onEvent?.({ ...event, step });
48        },
49      },
50    );
51    throwIfAborted(options.signal);
52
53    text += modelResult.text;
54    messages.push({
55      role: 'assistant',
56      text: modelResult.text,
57      toolCalls: modelResult.toolCalls,
58    });
59
60    const stepResult: IsolatedAgentTurnStepResult = {
61      index: step,
62      text: modelResult.text,
63      reasoning: modelResult.reasoning,
64      finishReason: modelResult.finishReason,
65      rawFinishReason: modelResult.rawFinishReason,
66      usage: modelResult.usage,
67      providerMetadata: modelResult.providerMetadata,
68      toolCalls: modelResult.toolCalls,
69      toolResults: [],
70      toolErrors: [],
71      approvalRequests: [],
72    };
73    steps.push(stepResult);

核心实现的第一道安全门在 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:26 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:75 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:76 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:77:模型只能调用 request.tools 声明过的工具,任何额外 toolName 都直接 throw。这个判断让工具白名单成为代码事实。

src/apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts · L75-147TypeScript
75    for (const call of modelResult.toolCalls) {
76      if (!allowedToolNames.has(call.toolName)) {
77        throw new Error(
78          `Model requested undeclared isolated tool "${call.toolName}"`,
79        );
80      }
81
82      options.onEvent?.({ type: 'tool-call', step, call });
83      const result = await options.handlers.callTool(
84        {
85          agentInstanceId: request.agentInstanceId,
86          call,
87          messages,
88        },
89        {
90          signal: requireSignal(options.signal),
91        },
92      );
93      throwIfAborted(options.signal);
94
95      if (result.status === 'approval-required') {
96        const approvalRequest = {
97          approvalId: result.approvalId,
98          toolCallId: call.toolCallId,
99          toolName: call.toolName,
100        };
101        stepResult.approvalRequests.push(approvalRequest);
102        options.onEvent?.({
103          type: 'tool-approval-request',
104          step,
105          ...approvalRequest,
106        });
107        continue;
108      }
109      if (result.status === 'error') {
110        const toolError = {
111          toolCallId: call.toolCallId,
112          toolName: call.toolName,
113          message: result.message,
114        };
115        stepResult.toolErrors.push(toolError);
116        messages.push({
117          role: 'tool',
118          toolCallId: call.toolCallId,
119          toolName: call.toolName,
120          output: {
121            error: result.message,
122          },
123        });
124        options.onEvent?.({
125          type: 'tool-error',
126          step,
127          ...toolError,
128        });
129        continue;
130      }
131
132      const toolResult = {
133        toolCallId: call.toolCallId,
134        toolName: call.toolName,
135        output: result.output,
136      };
137      stepResult.toolResults.push(toolResult);
138      messages.push({
139        role: 'tool',
140        ...toolResult,
141      });
142      options.onEvent?.({
143        type: 'tool-result',
144        step,
145        ...toolResult,
146      });
147    }

审批路径也不是旁路:apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:95 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:101 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:102 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:107 把 approval-required 写入 stepResult 并继续循环处理其他 call;随后 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:156 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:158 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:160 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:169 在有 approval request 时返回 completed 或 max-steps。

src/apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts · L149-175TypeScript
149    options.onEvent?.({
150      type: 'step-finished',
151      step,
152      finishReason: modelResult.finishReason,
153      usage: modelResult.usage,
154    });
155
156    if (
157      modelResult.toolCalls.length === 0 ||
158      stepResult.approvalRequests.length > 0
159    ) {
160      return {
161        status: 'completed',
162        text,
163        messages,
164        steps,
165      };
166    }
167  }
168
169  return {
170    status: 'max-steps',
171    text,
172    messages,
173    steps,
174  };
175}

源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:16 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:20 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:22 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:26 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:29 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:33 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:45 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:51 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:60 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:75 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:76 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:83 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:95 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:101 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:109 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:116 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:132 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:138 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:149 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:156 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:158 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:160 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:169 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:181 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

从被 quality gate 计入的 package 层看,同一个执行边界会落到 ShellService:packages/agent-shell/src/engine/shell-service.ts:17 说明它是服务级入口;packages/agent-shell/src/engine/shell-service.ts:22 记录检测到的 shell;packages/agent-shell/src/engine/shell-service.ts:47 与源码证据 packages/agent-shell/src/engine/shell-service.ts:57 把流式刷新和最大输出字节数写成常量;packages/agent-shell/src/engine/shell-service.ts:77 与源码证据 packages/agent-shell/src/engine/shell-service.ts:97 说明实例创建后必须初始化 shell 环境;packages/agent-shell/src/engine/shell-service.ts:128 再把 SessionManager 作为真正 PTY 会话管理器。

执行命令时,ShellService 继续保持可取消、可观察的边界:packages/agent-shell/src/engine/shell-service.ts:153 描述只创建会话不执行命令的入口;packages/agent-shell/src/engine/shell-service.ts:168 先清洗环境;packages/agent-shell/src/engine/shell-service.ts:176 与源码证据 packages/agent-shell/src/engine/shell-service.ts:194 负责把 session id 推给 UI;packages/agent-shell/src/engine/shell-service.ts:207 是持久 PTY 执行入口;packages/agent-shell/src/engine/shell-service.ts:225 再次清洗执行环境;packages/agent-shell/src/engine/shell-service.ts:247 与源码证据 packages/agent-shell/src/engine/shell-service.ts:293 表明它可以复用已有 session,也可以创建新 session 并交给 SessionManager 执行。

AgentManager 这一侧负责让 runtime 不直接碰宿主 UI:packages/agent-core/src/services/agent-manager/agent-manager.ts:151 是管理器类入口;packages/agent-core/src/services/agent-manager/agent-manager.ts:163 保存 active agents;packages/agent-core/src/services/agent-manager/agent-manager.ts:184 把 host 能力作为唯一依赖;packages/agent-core/src/services/agent-manager/agent-manager.ts:198 暴露生命周期事件 hook;packages/agent-core/src/services/agent-manager/agent-manager.ts:227 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:242 把状态容器和启动策略固定在 manager 中。这些 package 级引用解释了为什么 isolated runtime 可以短小:复杂生命周期和 shell 细节被分层收束,而不是散落在模型循环里。

§ 01 · DETAIL

模型/工具循环

▸ShellService 的会话化执行

执行流是 model → tool → messages,而不是模型任意执行:apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:33 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:40 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:41 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:53 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:54 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:83 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:87 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:138 展示了消息如何逐步累计,tool result 如何回到 conversation。

§ 02 · DETAIL

边界与失败

▸AgentManager 的状态与重试边界

失败模式包括 AbortSignal、未知工具、工具错误和 maxSteps。apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:20 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:51 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:77 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:109 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:121 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:169 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:181 与源码证据 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:183 分别对应这些出口。

Sub-page · 2026-07-13↑ clodex-ide 主页面