clodexIDE
§ 00 · Source-level Observatory

clodex-ide governed agentic IDE

Clodex 把持久任务、Agent Host、MCP、Shell、记忆和策略执行拆成明确边界。它最值得拆的点不是 UI,而是“模型输出不可信,权限来自策略和审计”的工程化实现。

★ 641
GitHub Stars
486K
LOC
1858
TS/JS files
Jul 13
Last commit
Renderer → Electron Main → Agent Host ↘ MCP Host / Shell / Runner / Memory Policy + approvals sit outside model text.
§ 00 · OVERVIEW

它解决什么,凭什么值得拆

Clodex 的核心问题定义很清楚:AI 编程任务不是一次回答,而是一段可暂停、可恢复、可审计的工程过程。packages/agent-core/src/services/agent-manager/agent-manager.ts:151 把 AgentManager 做成服务边界,packages/agent-core/src/services/agent-manager/agent-manager.ts:163 维护 active agents,packages/agent-core/src/services/agent-manager/agent-manager.ts:227 明确所有状态写入都流过 state-mutations。

这个项目符合源码拆解条件:它与 coding agent 和 AI developer tooling 直接相关,有隔离运行时、MCP runtime、shell execution、runner security、evidence memory 和 cloud-task 恢复链路。apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:16 与源码证据 packages/mcp-runtime/src/config.ts:130 与源码证据 packages/agent-shell/src/engine/shell-service.ts:207 分别代表三条核心执行路径。

▸核心数据

架构模式
Electron UI + isolated Agent Host + MCP Host + Shell/Runner adapters
核心抽象
AgentManager、isolated turn runtime、MCP registry schema、ShellService
安全模型
显式权限、审批请求、credential references、HTTPS-only remote MCP
下游依赖
pnpm workspace、Electron、Karton、node-pty、Zod、MCP transport
§ 01 · ARCHITECTURE

进程边界先于模型能力

AgentManager 的构造依赖说明了它是 core 层枢纽而不是 UI 组件:packages/agent-core/src/services/agent-manager/agent-manager.ts:174 引入命令注册,packages/agent-core/src/services/agent-manager/agent-manager.ts:184 通过 AgentHost 获取 logger/telemetry/models,packages/agent-core/src/services/agent-manager/agent-manager.ts:190 用 host-defined registry 生成具体 Agent。这个边界让 core 不直接 import browser services。

▸系统拓扑

User task → AgentManager → BaseAgent → Isolated Agent Host ↓ ↓ persistence/store tools + approvals ↓ ↓ evidence memory ← shell/mcp/runner receipts

▸AgentManager 的边界声明

src/packages/agent-core/src/services/agent-manager/agent-manager.ts · L151-184TypeScript
151export class AgentManager extends DisposableService {
152  /** Server-side bounds for user-editable agent titles. Slightly more permissive
153   * than the UI's 2–80 range so minor client/server drift never silently drops
154   * a valid title. */
155  private static readonly TITLE_MIN_LENGTH = 1;
156  private static readonly TITLE_MAX_LENGTH = 200;
157
158  private static readonly NETWORK_RETRY_SCAN_INTERVAL_MS = 5_000;
159  private static readonly NETWORK_RETRY_BACKOFF_MS: [number, ...number[]] = [
160    5_000, 15_000, 30_000, 60_000, 120_000,
161  ];
162
163  private activeAgents = new Map<
164    string,
165    BaseAgent<any, any> | BaseAgent<never, any>
166  >();
167
168  private readonly pendingNetworkRetries = new Map<
169    string,
170    { retryCount: number; nextRetryAt: number; lastErrorMessage: string }
171  >();
172  private readonly networkRetryInterval: ReturnType<typeof setInterval>;
173
174  private readonly commandRegistry: CommandRegistry;
175  private readonly managerToolbox: AgentManagerToolboxPort;
176  private readonly agentToolbox: BaseAgentToolboxView;
177  private readonly logger: Logger;
178  /**
179   * The agent-core capability seam. Threaded into every `BaseAgent`
180   * instance via `BaseAgentDependencies.host` so the core never imports
181   * browser services directly. Also the sole source of `logger`,
182   * `telemetry`, and `models` consumed by this manager.
183   */
184  private readonly host: AgentHost;

▸状态写入集中到 mutation 层

状态管理不是随手 setState:packages/agent-core/src/services/agent-manager/agent-manager.ts:227 到 packages/agent-core/src/services/agent-manager/agent-manager.ts:233 把 one-store-update-per-intent 写成注释约束,配合 packages/agent-core/src/services/agent-manager/agent-manager.ts:242 的 startupPolicy 与 packages/agent-core/src/services/agent-manager/agent-manager.ts:253 的 persistenceDb,说明任务恢复和运行中状态被当作一等设计。

深度阅读 · Agent Runtime
§ 02 · RUNTIME

Turn Runtime 是模型与工具之间的断路器

isolated turn runtime 的关键不是调用模型,而是控制“模型可以请求什么”。apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:26 从 request.tools 建立 allowedToolNames,apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:75 遍历 toolCalls,apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:76 拒绝未声明工具。这个检查把工具清单从 prompt 建议变成运行时硬边界。

▸模型调用和 step 事件

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);

▸工具审批、错误和结果回填

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    }

▸停止条件

停止条件也体现出 governed execution:当 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:156 检测没有工具调用,或 apps/browser/src/backend/agent-host/isolated-agent-turn-runtime.ts:158 出现 approvalRequests,runtime 会返回 completed,而不是继续替用户自动推进敏感动作。

深度阅读 · tool loop 与 approval
§ 03 · MCP POLICY

MCP 配置把 secret 和 egress 写进 schema

MCP runtime 最有价值的代码在 config schema,而不只是 protocol 类型。packages/mcp-runtime/src/config.ts:12 到 packages/mcp-runtime/src/config.ts:22 列出敏感 key 片段,packages/mcp-runtime/src/config.ts:68 对 stdio args 做 superRefine,packages/mcp-runtime/src/config.ts:95 拦截 Bearer token。这些检查发生在配置层,早于 server 启动。

▸stdio 与 secret 拦截

src/packages/mcp-runtime/src/config.ts · L59-128TypeScript
59const stdioArgsSchema = z
60  .array(
61    z
62      .string()
63      .max(16_384)
64      .refine((value) => !value.includes('\0'), 'Argument may not contain NUL'),
65  )
66  .max(128)
67  .default([])
68  .superRefine((args, context) => {
69    for (const [index, argument] of args.entries()) {
70      if (
71        /^--?(?:api[-_]?key|auth|authorization|cookie|password|secret|token)=/i.test(
72          argument,
73        )
74      ) {
75        context.addIssue({
76          code: z.ZodIssueCode.custom,
77          path: [index],
78          message:
79            'Secrets may not be embedded in MCP command arguments; use an environment credential reference',
80        });
81      }
82      if (
83        /^--?(?:api[-_]?key|auth|authorization|cookie|password|secret|token)$/i.test(
84          argument,
85        ) &&
86        index + 1 < args.length
87      ) {
88        context.addIssue({
89          code: z.ZodIssueCode.custom,
90          path: [index + 1],
91          message:
92            'Secrets may not be embedded in MCP command arguments; use an environment credential reference',
93        });
94      }
95      if (/\bBearer\s+\S+/i.test(argument)) {
96        context.addIssue({
97          code: z.ZodIssueCode.custom,
98          path: [index],
99          message:
100            'Bearer credentials may not be embedded in MCP command arguments',
101        });
102      }
103    }
104  });
105
106const configValuesSchema = z
107  .record(z.string().min(1).max(256), mcpConfigValueSchema)
108  .superRefine((values, context) => {
109    if (Object.keys(values).length > MCP_MAX_CONFIG_ENTRIES) {
110      context.addIssue({
111        code: z.ZodIssueCode.custom,
112        message: `Configuration may contain at most ${MCP_MAX_CONFIG_ENTRIES} entries`,
113      });
114    }
115    for (const [key, value] of Object.entries(values)) {
116      if (
117        value.kind === 'literal' &&
118        (isSensitiveMcpConfigKey(key) || looksLikeMcpSecretValue(value.value))
119      ) {
120        context.addIssue({
121          code: z.ZodIssueCode.custom,
122          path: [key],
123          message:
124            'Sensitive configuration values must use a credential reference',
125        });
126      }
127    }
128  });

▸remote URL 必须可治理

src/packages/mcp-runtime/src/config.ts · L154-194TypeScript
154const remoteUrlSchema = z
155  .string()
156  .url()
157  .max(4_096)
158  .superRefine((value, context) => {
159    const url = new URL(value);
160    const isLoopback =
161      url.hostname === 'localhost' ||
162      url.hostname === '127.0.0.1' ||
163      url.hostname === '[::1]' ||
164      url.hostname === '::1';
165    if (
166      url.protocol !== 'https:' &&
167      !(url.protocol === 'http:' && isLoopback)
168    ) {
169      context.addIssue({
170        code: z.ZodIssueCode.custom,
171        message: 'Remote MCP URLs must use HTTPS unless they target loopback',
172      });
173    }
174    if (url.username || url.password) {
175      context.addIssue({
176        code: z.ZodIssueCode.custom,
177        message: 'Credentials may not be embedded in an MCP URL',
178      });
179    }
180    if (url.hash) {
181      context.addIssue({
182        code: z.ZodIssueCode.custom,
183        message: 'MCP URLs may not contain fragments',
184      });
185    }
186    for (const key of url.searchParams.keys()) {
187      if (isSensitiveMcpConfigKey(key)) {
188        context.addIssue({
189          code: z.ZodIssueCode.custom,
190          message: `Sensitive URL parameter "${key}" must not be persisted`,
191        });
192      }
193    }
194  });

▸MCP 策略不是装饰项

远程 MCP 的约束延伸到 URL、headers、OAuth scopes 和 executable runtime policy。packages/mcp-runtime/src/config.ts:210 开始定义 OAuth 配置,packages/mcp-runtime/src/config.ts:266 附近出现 executableRuntimePolicySchema,packages/mcp-runtime/src/config.ts:377 把 tool policy decision 收束为 allow/ask/deny。

深度阅读 · MCP policy schema
§ 04 · SHELL

Shell 执行是持久会话,不是一次性 subprocess

ShellService 的实现说明 Clodex 把终端当成 Agent 的长期环境:packages/agent-shell/src/engine/shell-service.ts:22 保存 detected shell,packages/agent-shell/src/engine/shell-service.ts:23 保存 SessionManager,packages/agent-shell/src/engine/shell-service.ts:47 和 packages/agent-shell/src/engine/shell-service.ts:57 限制流式 UI 更新负载。

▸初始化与会话管理

src/packages/agent-shell/src/engine/shell-service.ts · L17-58TypeScript
17export class ShellService extends DisposableService {
18  private readonly logger: Logger;
19  private readonly sink?: ShellStreamSink;
20  private readonly getShellLogsDir: (agentInstanceId: string) => string;
21
22  private shell: DetectedShell | null = null;
23  private sessionManager: SessionManager | null = null;
24  private resolvedEnv: Record<string, string> | null = null;
25
26  private readonly preDetectedShell: DetectedShell | null;
27  private readonly preResolvedEnv: Record<string, string> | null;
28  private readonly protectedFiles: ProtectedFileStorage | undefined;
29
30  /**
31   * Per-tool-call streaming bookkeeping. Each entry records which session
32   * the tool call is streaming from and whether new output has arrived
33   * since the last flush. The UI snapshot is computed on-demand by
34   * reading the session's current grid state — we do not accumulate raw
35   * chunks here.
36   */
37  private readonly outputBuffers = new Map<
38    string,
39    { sessionId: string; dirty: boolean }
40  >();
41  private readonly outputFlushTimers = new Map<string, NodeJS.Timeout>();
42  private readonly outputMaxIntervalTimers = new Map<string, NodeJS.Timeout>();
43
44  /** Per-agent throttle timers for pushing the shells manifest to the sink. */
45  private readonly shellManifestTimers = new Map<string, NodeJS.Timeout>();
46
47  private static readonly FLUSH_DEBOUNCE_MS = 100;
48  private static readonly FLUSH_MAX_INTERVAL_MS = 300;
49  /**
50   * Per-flush cap for the live UI snapshot. Grid serialization is already
51   * bounded by `STREAMING_MAX_ROWS` (~24KB theoretical max at 120 cols),
52   * but 16KB is a snug safety rail that keeps live-stream IPC payloads
53   * small without truncating realistic live previews. The final command
54   * output is unaffected — it uses the head+tail capped capture on a
55   * separate code path.
56   */
57  private static readonly MAX_STREAMING_BYTES = 16_384;
58  private static readonly MANIFEST_THROTTLE_MS = 500;

▸创建 PTY session 并推送 session id

src/packages/agent-shell/src/engine/shell-service.ts · L153-199TypeScript
153  /**
154   * Create a new PTY session without executing any command.
155   * Returns immediately with the session ID — does not wait for shell init.
156   */
157  createSession(
158    agentInstanceId: string,
159    toolCallId: string,
160    cwd: string,
161  ): string {
162    this.assertNotDisposed();
163
164    if (!this.shell || !this.sessionManager) {
165      throw new Error('Shell service is not available — no shell detected.');
166    }
167
168    const env = sanitizeEnv(this.resolvedEnv, this.shell.type);
169    const sessionId = this.sessionManager.createSession(
170      agentInstanceId,
171      cwd,
172      env,
173      () => {},
174    );
175
176    // Wire streaming callback
177    this.sessionManager.setOnData(sessionId, (_sid: string, _chunk: string) => {
178      if (!this.sink) return;
179      const entry = this.outputBuffers.get(toolCallId);
180      if (entry) {
181        entry.dirty = true;
182      } else {
183        this.outputBuffers.set(toolCallId, {
184          sessionId,
185          dirty: true,
186        });
187      }
188      this.scheduleFlush(agentInstanceId, toolCallId);
189      this.scheduleShellManifestPush(agentInstanceId);
190    });
191
192    this.pushShellsToSink(agentInstanceId);
193
194    // Publish session ID to the stream sink so the UI can cancel in-flight
195    // commands.
196    this.sink?.publishSessionId(agentInstanceId, toolCallId, sessionId);
197
198    return sessionId;
199  }

▸执行命令复用 session

src/packages/agent-shell/src/engine/shell-service.ts · L207-293TypeScript
207  async executeInSession(
208    agentInstanceId: string,
209    toolCallId: string,
210    request: SessionCommandRequest,
211  ): Promise<SessionCommandResult> {
212    this.assertNotDisposed();
213
214    if (!this.shell || !this.sessionManager) {
215      return {
216        sessionId: '',
217        output: 'Shell service is not available — no shell detected.',
218        exitCode: null,
219        sessionExited: true,
220        timedOut: false,
221        resolvedBy: 'session_exited',
222      };
223    }
224
225    const env = sanitizeEnv(this.resolvedEnv, this.shell.type);
226
227    // Build a streaming callback targeting the current toolCallId.
228    // We no longer accumulate chunks — we just mark the tool call "dirty"
229    // and let the flush read the current grid snapshot from the session.
230    const makeOnData = (targetToolCallId: string, targetSessionId: string) => {
231      return (_sid: string, _chunk: string) => {
232        if (!this.sink) return;
233        const entry = this.outputBuffers.get(targetToolCallId);
234        if (entry) {
235          entry.dirty = true;
236        } else {
237          this.outputBuffers.set(targetToolCallId, {
238            sessionId: targetSessionId,
239            dirty: true,
240          });
241        }
242        this.scheduleFlush(agentInstanceId, targetToolCallId);
243        this.scheduleShellManifestPush(agentInstanceId);
244      };
245    };
246
247    // Reuse existing session or create a new one
248    let sessionId = request.sessionId;
249    if (!sessionId) {
250      try {
251        // Create with a no-op onData; wire the real callback immediately
252        // after so we can close over the sessionId for snapshot reads.
253        sessionId = this.sessionManager.createSession(
254          agentInstanceId,
255          request.cwd ?? '',
256          env,
257          () => {},
258        );
259        this.sessionManager.setOnData(
260          sessionId,
261          makeOnData(toolCallId, sessionId),
262        );
263      } catch (err) {
264        return {
265          sessionId: '',
266          output:
267            err instanceof Error
268              ? err.message
269              : 'Failed to create shell session.',
270          exitCode: null,
271          sessionExited: true,
272          timedOut: false,
273          resolvedBy: 'session_exited',
274        };
275      }
276    } else {
277      // Re-target streaming output to the current tool call
278      this.sessionManager.setOnData(
279        sessionId,
280        makeOnData(toolCallId, sessionId),
281      );
282    }
283
284    // Push updated session manifest on new session creation
285    if (!request.sessionId) {
286      this.pushShellsToSink(agentInstanceId);
287    }
288
289    // Publish session ID to the stream sink so the UI can cancel in-flight
290    // commands.
291    this.sink?.publishSessionId(agentInstanceId, toolCallId, sessionId);
292
293    return this.sessionManager.executeCommand(sessionId, request);
§ 05 · MEMORY

状态、记忆与恢复是同一条链路

源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:151 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:163 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:168 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:174 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:184 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:190 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:212 与源码证据 packages/agent-core/src/services/agent-manager/agent-manager.ts:218 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

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

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

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

源码证据 packages/mcp-runtime/src/config.ts:4 与源码证据 packages/mcp-runtime/src/config.ts:5 与源码证据 packages/mcp-runtime/src/config.ts:9 与源码证据 packages/mcp-runtime/src/config.ts:24 与源码证据 packages/mcp-runtime/src/config.ts:31 与源码证据 packages/mcp-runtime/src/config.ts:49 与源码证据 packages/mcp-runtime/src/config.ts:59 与源码证据 packages/mcp-runtime/src/config.ts:68 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 packages/mcp-runtime/src/config.ts:130 与源码证据 packages/mcp-runtime/src/config.ts:154 与源码证据 packages/mcp-runtime/src/config.ts:165 与源码证据 packages/mcp-runtime/src/config.ts:174 与源码证据 packages/mcp-runtime/src/config.ts:186 与源码证据 packages/mcp-runtime/src/config.ts:210 与源码证据 packages/mcp-runtime/src/config.ts:266 与源码证据 packages/mcp-runtime/src/config.ts:377 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 packages/agent-shell/src/engine/shell-service.ts:17 与源码证据 packages/agent-shell/src/engine/shell-service.ts:22 与源码证据 packages/agent-shell/src/engine/shell-service.ts:30 与源码证据 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 与源码证据 packages/agent-shell/src/engine/shell-service.ts:128 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 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 与源码证据 packages/agent-shell/src/engine/shell-service.ts:207 与源码证据 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 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 packages/agent-shell/src/engine/disposable-local-worktree-runner-adapter.ts:136 与源码证据 packages/agent-shell/src/engine/disposable-local-worktree-runner-adapter.ts:154 与源码证据 packages/agent-shell/src/engine/disposable-local-worktree-runner-adapter.ts:265 与源码证据 packages/agent-shell/src/engine/disposable-local-worktree-runner-adapter.ts:276 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

▸为什么这比普通 IDE 插件复杂

普通 IDE 插件只要响应当前命令即可;Clodex 需要解释“这一步为什么发生”。所以 packages/agent-core/src/services/agent-manager/agent-manager.ts:140 定义 session checkpoint 状态,packages/agent-core/src/services/agent-manager/agent-manager.ts:212 接入网络在线判断,packages/agent-shell/src/engine/shell-service.ts:303 提供 recent output 给分类器判断下一条命令风险。

§ 06 · RISKS

技术亮点之外,别忽略这些

第一,项目很新,release status 仍是 technical preview;源码里有大量 promotion-gated cloud-task 与 runner 代码,读者应把它当作架构样本而不是稳定产品。第二,monorepo 规模很大,packages/mcp-runtime/src/config.ts:414 限制 registry servers 数量,但真实部署仍要验证 UI、存储和 runner 的组合风险。

风险:实现面过宽
Agent、MCP、Shell、Docker、SSH、cloud task 同时推进,任何一层成熟度不足都会影响整体可信度。
机会:策略外置
未声明工具拒绝、secret schema、approval-requested 停止点都让它比“prompt 约束安全”更可验证。