# 策展 · X (Twitter) 🔥

> 作者：LangChain JS (@LangChain_JS) · 平台：X (Twitter) · 日期：2026-03-27

> 原始來源：https://x.com/langchain_js/status/2037560951445266891

## 中文摘要

LangChain 推出 Deep Agent IDE 模式。

LangChain 新指南強調 coding Agent 應具備完整 IDE 體驗，包括沙盒檔案系統、即時檔案樹、diff 檢視、終端與聊天介面，而非僅限聊天框；透過 PatternEmbed 元件支援 React、Vue、Svelte、Angular 等框架，實現一致架構與即時檔案同步，讓 Agent 在沙盒中讀取、編輯、寫入與執行程式碼。

**PatternEmbed 元件架構**
PatternEmbed React 元件定義 props 為 `{pattern, theme, height, minHeight = 400, maxHeight = 700, className, defaultView = "preview", defaultSdk = "react", defaultLanguage = "js", showCodeTab = true, agentServer = "prod", onError, onReady}`。VALID_GUEST_TYPES Set 包含 `["READY", "RESIZE", "ERROR", "RUN_STARTED", "TRACE_URL", "THREAD_CLEARED"]`。

自訂 Zod-like schema 物件 `z` 支援 `safeParse`、`optional`、`min`、`url` 等驗證，`discriminatedUnion` 依 `data.type` 檢查 VALID_GUEST_TYPES。關鍵訊息 schema 包括：
- `SetThemeMessageSchema`：`z.object({type: z.literal("SET_THEME"), theme: z.enum(["light", "dark"])})`
- `SetPatternMessageSchema`：`z.object({type: z.literal("SET_PATTERN"), slug: z.string()})`
- `ResetMessageSchema`：`z.object({type: z.literal("RESET")})`
- `SetViewMessageSchema`：`z.object({type: z.literal("SET_VIEW"), view: z.enum(["preview", "code"])})`
- `SetLanguageMessageSchema`：`z.object({type: z.literal("SET_LANGUAGE"), language: z.enum(["js", "python"])})`
- `CodeFileSchema`：`z.object({filename: z.string(), content: z.string()})`
- `UpdateCodeMessageSchema`：`z.object({type: z.literal("UPDATE_CODE"), files: z.array(CodeFileSchema).min(1), entryFile: z.string()})`
- `TrackEventMessageSchema`：`z.object({type: z.literal("TRACK_EVENT"), name: z.string(), properties: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional()})`
- `HostToGuestMessageSchema`：`z.discriminatedUnion("type", [...上述 schema])`

GuestToHost 訊息包括 `ReadyMessageSchema`（framework: `["react", "vue", "angular", "svelte"]`）、`ResizeMessageSchema`、`ErrorMessageSchema` 等，`PreviewMessageSchema` 為兩者聯集。

`isOriginAllowed(origin, allowedOrigins)` 檢查 `allowedOrigins.includes("*")` 或 `allowedOrigins.includes(origin)`。`createPreviewHost(iframe, options)` 處理 iframe postMessage，`targetOrigins = options.targetOrigins ?? allowedOrigins`，使用 listeners Map 管理回呼。

Host API 提供 `setTheme(theme)`、`setPattern(slug)`、`setView(view)`、`setLanguage(language)`、`updateCode(files, entryFile)`、`reset()`、`trackEvent(name, properties)`；listener 如 `onReady`、`onResize`、`onError`、`onRunStarted`、`onTraceUrl`、`onThreadCleared`，並有 `destroy()` 清理。

常數定義：`PROD_BASE = "https://ui-patterns.langchain.com"`，`SDK_LABELS = {react: "React", vue: "Vue", svelte: "Svelte", angular: "Angular"}`，`SDK_LOCAL_HOSTS = {react: "http://localhost:4100", vue: "http://localhost:4200", svelte: "http://localhost:4300", angular: "http://localhost:4400"}`，`SDK_PROD_HOSTS = {react: `${PROD_BASE}/react`, ...}`。

**前端框架支援與快取機制**
支援 React、Vue、Svelte、Angular 的 SVG 圖示（width="14" height="14"），包含特定 viewBox 與 path。`PROD_AGENT_API_BASE = `${PROD_BASE}/api/langgraph``。

`normalizeAgentServerBase(agentServer, useLocalPreview)` 處理伺服器基底：若 `useLocalPreview` 為真，返回 `"http://localhost:2024"`；`"prod"` 或 `"local"` 皆返回 PROD_AGENT_API_BASE。

`isLocalhost()` 檢查 `window.location.hostname` 是否為 `"localhost"`、`"127.0.0.1"` 或 `"[::]"`。`detectPageTheme()` 偵測 `dark` 主題，透過 classList、`data-theme` 或 `colorScheme`。

快取金鑰：`CACHE_KEY = "__lcPlaygroundIframeCache"`、`SDK_CACHE_KEY = "__lcPlaygroundSdkCache"`、`LANG_CACHE_KEY = "__lcPlaygroundLangCache"`。`iframeCache`、`sdkCache`、`langCache` 使用 `globalThis` Map 初始化。

SVG 圖示包含 `VIEW_EYE_SVG`（眼睛）、`VIEW_CODE_SVG`（程式碼）、`CHEVRON_DOWN_SVG`（向下箭頭）、`TRACE_ICON_SVG`、`TRACE_SPINNER_SVG`（旋轉載入）、`EXTERNAL_LINK_SVG`、`DOWNLOAD_SVG`、`EXPAND_SVG`、`CLOSE_SVG`、`LANG_TS_SVG`（TypeScript）、`LANG_PYTHON_SVG`（Python）。

`SDK_OPTIONS` 為 `Object.keys(SDK_LABELS).map(k => [k, SDK_LABELS[k]])`。`EMBED_CSS` 定義主題樣式、邊框 `#B8DFFF`（dark: `#1A2740`）、背景 `#F2FAFF`（dark: `#030710`）等，響應式 max-width:639px。

**狀態管理與 iframe 生命週期**
使用 `useRef` 管理 `slotRef`、`cardRef` 等。`useLocalPreview = agentServer === "local" || (agentServer === "prod" && isLocalhost())`。`agentQuery = agentServer !== "local" && agentServer !== "prod" ? \`?agentServer=${encodeURIComponent(agentServer)}\` : ""`。

`sdkCacheKey = ${agentServer}|${pattern}`，`sdk` 從快取選最近 active 的 SDK。`langCacheKey` 同理管理 `agentLang`。`previewUrl` 依本地/生產切換。`ready`、`iframeHeight`、`error`、`activeView`、`traceUrl`、`traceLoading`、`expanded`、`pageTheme` 狀態從快取初始化。

`ResizeObserver` 監聽主題變化，動態注入 `<style id="lc-pe-css">` 以 `EMBED_CSS`。iframe 建立：
```javascript
const iframe2 = document.createElement("iframe");
iframe2.src = `${previewUrl}/${agentQuery}#/${patternRef.current}`;
iframe2.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
iframe2.setAttribute("allow", "clipboard-write");
iframe2.setAttribute("data-cache-key", cacheKey);
```
樣式：`position: fixed`、`border: none`、`visibility: hidden` 等。`createPreviewHost(iframe2, { allowedOrigins: [iframeOrigin] })`。

快取同步：若 `cached.ready`，設定 `ready=true`、`iframeHeight`，同步 `setTheme`、`setPattern` 等，顯示 iframe。`onReady` 更新 `lastActiveAt = Date.now()`。`onResize` 限制高度 `Math.min(maxHeight, Math.max(minHeight, h))`。`onError` 隱藏 iframe 並設定錯誤。

`onRunStarted` 啟動 trace loading，`onTraceUrl(url, runId)` 設定 URL，`onThreadCleared` 清除狀態。

`syncPosition()` 依 `slotRef` 位置調整 iframe，`ResizeObserver`、`scroll`、`resize` 事件監聽。展開模式使用 `wrapper`（`data-lc-pe`）、`backdrop`（點擊關閉），`Escape` 鍵關閉，`zIndex: 9999`。

`useEffect` 同步 `pattern`、`theme`、`agentLang`，`switchView(view)` 更新視圖並追蹤事件。SDK 下拉動態定位，點擊 `setSdk` 並 `trackEvent("sdk_switched", {sdk: value, pattern})`。

`handleReset()` 呼叫 `host.reset()` 並重設 pattern。下載按鈕生成 `${base}/api/download/${encodeURIComponent(pattern)}?sdk=${sdk}&lang=${agentLang}` 並儲存 `${pattern}.zip`。

**工具列與 UI 渲染**
Tab 類別：`tabBase = "lc-tab inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg border-none font-medium cursor-pointer transition-all duration-150"`，`tabActiveClass`、`tabInactiveClass`、`tabTraceActiveClass`、`tabTraceLoadingClass`。

工具列條件渲染 Preview/Code/Trace tabs，Trace 僅 `!useLocalPreview` 且有 `traceUrl` 時顯示。語言切換器支援 `js`/`python`，使用 `LANG_TS_SVG`、`LANG_PYTHON_SVG`。

右側 Expand/Collapse、`DOWNLOAD_SVG`、`SDK_LOGOS[sdk]`。Slot 依 `expanded` 調整高度，loading 用 `lc-spinner`，error 顯示 `Retry` 按鈕呼叫 `handleReset`。

**Deep Agent IDE 整體架構**
三層架構：
1. **Deep agent with sandbox backend**：Agent 自動獲 `read_file`、`write_file`、`edit_file`、`execute` tools。
2. **Custom API server**：Hono app 暴露檔案瀏覽端點，透過 `langgraph.json` 的 `http.app`。
3. **IDE frontend**：三面板（檔案樹、程式碼/diff、聊天），即時同步。

```mermaid
graph LR
  UI["IDE Frontend"]
  API["API Server"]
  AGENT["createDeepAgent()"]
  SANDBOX["Sandbox"]

  UI --"useStream()"--> AGENT
  UI --"/api/sandbox/:threadId/*"--> API
  AGENT --"read/write/execute"--> SANDBOX
  API --"ls / read"--> SANDBOX

  classDef blueHighlight fill:#DBEAFE,stroke:#2563EB,color:#1E3A8A;
  classDef greenHighlight fill:#DCFCE7,stroke:#16A34A,color:#14532D;
  classDef purpleHighlight fill:#EDE9FE,stroke:#7C3AED,color:#4C1D95;
  classDef orangeHighlight fill:#FEF3C7,stroke:#D97706,color:#92400E;
  class UI blueHighlight;
  class AGENT greenHighlight;
  class SANDBOX purpleHighlight;
  class API orangeHighlight;
```

使用範例：`<PatternEmbed pattern="deep-agent-ide" minHeight={700} />`，連至 ``./oss/javascript/deepagents/sandboxes``。

**沙盒生命週期策略**
- **Thread-scoped（推薦）**：每個 thread 獨立，ID 存 metadata，後端 factory 解析 `runtime.configurable.thread_id`；隔離對話、頁面重載持久、thread 刪除清理。

```mermaid
sequenceDiagram
    participant FE as Frontend
    participant LG as LangGraph API
    participant HTTP as API Server
    participant SB as Sandbox

    Note over FE: Page loads
    FE->>LG: POST /threads
    LG-->>FE: threadId

    FE->>HTTP: GET /api/sandbox/:threadId/tree
    HTTP->>LG: threads.get(threadId) → metadata.sandbox_id
    alt No sandbox yet
        HTTP->>SB: LangSmithSandbox.create()
        HTTP->>LG: threads.update(threadId, metadata.sandbox_id)
    else Existing sandbox
        HTTP->>SB: connect(sandbox_id)
    end
    HTTP-->>FE: file tree

    Note over FE: User sends message
    FE->>LG: POST /threads/:threadId/runs/stream
    LG->>LG: backend factory reads thread_id from runtime
    LG->>SB: connect to same sandbox
```

- **Agent-scoped**：助理共享，適合跨對話專案：
```ts
const sandboxBackendFactory = async (runtime: BackendRuntime) => {
  const assistantId = runtime.configurable?.assistant_id;
  return getOrCreateSandboxForAssistant(assistantId);
};
```
- **User-scoped**：使用者獨立，需自訂 auth。
- **Session-scoped**：前端 UUID，不跨 session。

指南以 thread-scoped 為主。

**Agent 設定與後端 factory**
選擇 sandbox provider 實現 `SandboxBackendProtocol`：
```ts
import { createDeepAgent, LangSmithSandbox } from "deepagents";

const sandbox = await LangSmithSandbox.create();

export const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-4-5",
  backend: sandbox,
  systemPrompt: "You are an expert developer working on a project in /app.",
});
```
自動 tools：`read_file`、`write_file`、`edit_file`、`ls`、`glob`、`grep`、`execute`。

Thread-scoped factory：
```ts
async function getOrCreateSandboxForThread(threadId: string): Promise<LangSmithSandbox> {
  const client = new Client({ apiUrl: "http://localhost:2024" });
  const thread = await client.threads.get(threadId);
  // 若 metadata.sandbox_id 存在，重新連線；否則 create + seed + update metadata
}
```
`sandboxBackendFactory` 檢查 `runtime.configurable?.thread_id`，回傳上述函數。Agent：`backend: sandboxBackendFactory`。

LangSmith sandbox 用 sandbox template (`/langsmith/sandbox-templates`)，`templateName: "my-template"`，運行時 `uploadFiles`。

`SEED_FILES` 範例：`"package.json": JSON.stringify({ name: "my-app", version: "1.0.0" }, null, 2)`，`"src/index.js": 'console.log("Hello");'`。編碼後 `sandbox.uploadFiles([...])`，執行 `sandbox.execute("cd /app && npm install")`。

**API 伺服器與檔案同步**
Hono app 暴露 `/api/sandbox/:threadId`，共用 `getOrCreateSandboxForThread`。

- `app.get("/api/sandbox/:threadId/tree")`：`rootPath = c.req.query("filePath") || "/app"`，執行 `find '${rootPath}' -printf '%y\\t%s\\t%p\\n' 2>/dev/null | sort -t$'\\t' -k3`，解析為 `{name, type, path, size}`。
- `app.get("/api/sandbox/:threadId/file")`：下載並解碼內容。

`langgraph.json`：`"node_version": "22"`，`"graphs": { "coding_agent": "./src/agents/my-agent.ts:agent" }`，`"env": ".env"`，`"http": { "app": "./src/api/app.ts:app" }`。

前端：`useStream({ apiUrl: "http://localhost:2024", assistantId: "coding_agent", threadId })`，`sessionStorage` 存 `THREAD_KEY = "sandbox-thread-id"`。`fetchTree`、`fetchFile` 呼叫 API，過濾 `node_modules`。

即時同步：監控 `stream.messages` 中 `FILE_MUTATING_TOOLS = new Set(["write_file", "edit_file", "execute"])`，建 `toolCallMap`，`ToolMessage` 完成時 `refreshSingleFile` 或 `refreshAllFiles`，用 `processedIds` Set 去重。React 用 `useEffect([stream.messages])`，Vue 用 `watch`，Svelte 用 `$effect`。

**Diff 檢視與三面板佈局**
`detectChanges(current, original)` 比較快照，返回變更路徑 Set。預設 diff view 顯示 Agent 修改。

Diff 庫：
| Framework | Library | Component |
|-----------|---------|-----------|
| React | [`@pierre/diffs`](https://diffs.com) | `<FileDiff>` with `parseDiffFromFile` |
| Vue | [`@git-diff-view/vue`](https://github.com/MrWangJustToDo/git-diff-view) | `<DiffView>` with `generateDiffFile` |
| Svelte | [`@git-diff-view/svelte`](https://github.com/MrWangJustToDo/git-diff-view) | `<DiffView>` with `generateDiffFile` |
| Angular | [`ngx-diff`](https://github.com/rars/ngx-diff) | `<ngx-unified-diff>` |

React 範例：`parseDiffFromFile({ name: fileName, contents: original }, { name: fileName, contents: current })`，`<FileDiff fileDiff={diff} options={{ theme: "github-dark", diffStyle: "unified", diffIndicators: "bars" }} />`。

`ChangedFilesSummary` 計算 `additions`/`deletions`，顯示 `{stats.length} Files Changed` 與 stats。

三面板：檔案樹（208px）、程式碼/diff（彈性）、聊天（320px）。檔案樹用 [`@iconify-json/vscode-icons`](https://www.npmjs.com/package/@iconify-json/vscode-icons)，變更標 amber dots。

**最佳實踐與使用情境**
- 生產用 thread-scoped，存 sandbox ID 於 metadata，後端/API 共享 factory，無 in-memory cache。
- `sessionStorage` 持久 threadId。
- 每 tool call 即同步檔案，讓 IDE 即時。
- 變更檔案預設 diff。
- Read-only 顯示 compact（如 `Read router.js L1-42`），mutating 顯示完整。
- Seed 真實專案，過濾 `node_modules`。

情境：視覺介面、code review（審核 diff）、tutorial、prototyping。

完整指南：[New Deep Agents guide](https://t.co/Fu0mivu354) [https://t.co/aCSrZx1MbY](https://t.co/aCSrZx1MbY)，沙盒文件：[https://t.co/BSmYJxDwWo](https://t.co/BSmYJxDwWo)，文件索引 [https://docs.langchain.com/llms.txt](https://docs.langchain.com/llms.txt)。編輯：[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/deepagents/frontend/sandbox.mdx)。

## 標籤

Agent, 開源專案, LangGraph
