# Agent Studio Mini > 一个面向内部培训、可直接运行的最小 AI Agent Harness。 Agent Studio Mini 使用 Pi Coding Agent SDK 驱动 Agent Loop,通过 Fastify + SSE 暴露受控协议,并以 assistant-ui 呈现聊天、Tool 调用、人工审批、图片附件和完整事件时间线。

Agent Studio Mini 桌面端演示界面

桌面端演示界面 · 点击图片查看原图

## 核心能力 - **完整 Agent Loop**:流式对话、Tool 生命周期、取消、超时与错误处理。 - **Human-in-the-loop**:敏感 Tool 执行前审批,支持批准、拒绝和超时。 - **Skills 渐进加载**:按需发现并读取 Skill,避免一次性注入全部上下文。 - **多模态附件**:支持 JPEG、PNG、WebP 上传、清洗、预览与模型能力校验。 - **可观测事件流**:SSE 重放、断线恢复、事件去重与完整 Timeline。 - **安全边界示例**:Tool allowlist、路径守卫、凭证加密、SSRF 防护与日志脱敏。 **文档导航:** [架构](#架构) · [快速开始](#快速开始) · [模型配置](#模型配置) · [Skills](#skills-与-progressive-disclosure) · [Tools 与审批](#tools-与-approval) · [图片链路](#图片多模态链路) · [安全边界](#安全边界) · [生产化路线](#已知限制与生产化) ## 架构 ```mermaid flowchart LR UI[assistant-ui\nComposer / Thread / Tool UI] -->|HTTP + SSE| API[Fastify API] API --> REG[SessionRegistry] REG --> PI[Pi AgentSession] API --> STORE[EventStore / ApprovalBroker] PI --> LOADER[DefaultResourceLoader / Tools] STORE --> VIEW[Timeline / Approval UI] LOADER --> RES[Skills / Workspace / ModelRuntime] ``` ```mermaid sequenceDiagram participant U as User participant W as Web + assistant-ui participant H as Harness participant P as Pi AgentSession U->>W: 文本 + 图片 W->>H: POST message (立即 202) H->>P: prompt(text, { images }) P-->>H: stream / tool events H-->>W: HarnessEvent over SSE P->>H: save_report_draft preflight H-->>W: approval.required U->>H: Approve / Deny H-->>P: allow / block P-->>H: tool result + completion ``` 职责边界很明确:Pi `AgentSession` 是 Agent Loop、模型调用和 Tool 生命周期的事实来源;Harness 管理 Session、事件序列、安全守卫、附件、模型凭证和审批;assistant-ui 只把 Harness 状态投影为聊天和 Tool UI,不直接调用模型,也不在浏览器执行服务端 Tool。 ## 目录 ```text apps/server Fastify HTTP/SSE 组合层 apps/web React、assistant-ui 与三栏界面 packages/harness Session、Skills、Tools、Approval、安全边界 packages/shared 浏览器/服务端共享的 Zod 协议 .agents/skills ads-analysis 示例 Skill .pi/skills report-writer 示例 Skill fixtures 离线广告账户数据 workspace/sessions 运行时临时 Session(被 gitignore) docs 架构、映射、安全、课堂脚本与路线图 ``` ## 快速开始 ### 环境要求 - Node.js `>=22.19.0`(Pi 0.84.3 的 package engine 要求) - pnpm 11 ### 本地运行 ```bash cp .env.example .env # 至少填写 AGENT_PROVIDER 和 AGENT_MODEL;API Key 也可启动后在 Settings 中提交 pnpm install pnpm dev ``` Server 默认监听 `127.0.0.1:3001`,Vite Web 默认监听 `127.0.0.1:5173`,并代理 `/api` 与 `/health`。启动后访问 **http://127.0.0.1:5173**。 常用质量检查命令: ```bash pnpm lint pnpm typecheck pnpm test pnpm build ``` ## 模型配置 服务器环境变量优先级低于当前 Web Session 的内存设置: ```dotenv OPENAI_API_KEY= ANTHROPIC_API_KEY= MODEL_CREDENTIAL_ENCRYPTION_KEY= AGENT_PROVIDER= AGENT_MODEL= AGENT_DISPLAY_NAME= AGENT_SUPPORTS_IMAGES=false AGENT_CONTEXT_WINDOW= AGENT_MAX_TOKENS= LLM_BASE_URL= LLM_API=openai-responses ALLOW_PRIVATE_LLM_BASE_URLS=false SESSION_RUN_TIMEOUT_MS=300000 APPROVAL_TIMEOUT_MS=60000 MAX_IMAGE_BYTES=5242880 MAX_IMAGES_PER_MESSAGE=4 ``` `SESSION_RUN_TIMEOUT_MS` 是运行无活动超时:每次模型增量、Tool 生命周期或 Pi 生命周期事件都会重新计时,长链路不会仅因累计时长被中断。 服务启动时会从项目 `.env` 严格加载并校验上述默认模型字段;`AGENT_PROVIDER` 与 `AGENT_MODEL` 必须同时存在。当前 Web Session 的 Settings 覆盖环境默认值;未覆盖时新 Session 自动使用 `.env`,无需每次启动重新配置。也可在顶栏 Settings 配置 Provider ID、`openai-responses` / `openai-completions` / `anthropic-messages`、Base URL、Model ID、Vision 与 API Key。Preset 只是可编辑起点,不保证模型存在。没有显式模型或凭证时返回 `MODEL_NOT_CONFIGURED` / `MODEL_CREDENTIAL_NOT_CONFIGURED`,绝不选择“第一个可用模型”。 Provider、API 类型、Base URL、Model ID、Display Name、Vision、context/max token 等**非敏感模型配置**会以版本化 strict Zod 结构保存到浏览器 `localStorage`。刷新或 Reset 创建新 Session 后,在服务器没有环境模型配置时自动恢复这些字段;损坏、超限或旧格式数据会安全丢弃。Settings 中的“忘记本地配置”只清除这份浏览器配置,不影响当前 Session。 API Key 可直接由 `.env` 的 `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` 提供。若在前端提交,则服务端使用 `MODEL_CREDENTIAL_ENCRYPTION_KEY` 做 AES-256-GCM 认证加密,密文写入 gitignored 的 `workspace/model-credentials.enc.json`;下次启动会按 `.env` 默认模型的连接 identity 自动恢复。浏览器仍不会把 Key 写入 Web Storage、IndexedDB 或 Service Worker cache。刷新或 Reset 后无需重复输入。读取配置只返回 `configured`、安全模型信息、末四位 `keyHint` 与存储类型。Clear Credentials 会同时清除当前 Runtime credential 和对应持久密文;换模型会销毁既有 Pi Session,下一次 Run 按新配置创建。密钥不会写入 Pi 全局 `auth.json` / `models.json`、URL、SSE 或日志。 生成 master key:`openssl rand -base64 32`。该值必须稳定保存在服务器 `.env` 或 secret manager 中;丢失或更换后旧密文会 fail closed、无法解密。生产环境应迁移到 KMS/HSM 并设计密钥轮换。 自定义 Base URL 使用 WHATWG `URL`、协议/userinfo/fragment/query 检查与 DNS 解析;默认拒绝 loopback、private、link-local 和 metadata 地址。开发环境只放行显式 loopback HTTP;企业内网需明确设置 `ALLOW_PRIVATE_LLM_BASE_URLS=true`。模型 Provider SDK 的重定向策略不是本课堂 Harness 可控的公开扩展点,生产版应在出口代理处禁用跨主机重定向并重新校验每一跳。 连接测试只执行有界的本地配置校验并返回“首次运行验证”,不创建 Agent Run、不记录模型响应,也不伪造远端成功。 ## Skills 与 progressive disclosure 启动时 `DefaultResourceLoader.reload()` 发现 `.agents/skills`、`.pi/skills` 和 `AGENT_SKILLS_DIRS`(使用操作系统 `path.delimiter`)。额外目录会 canonicalize、去重、验证;错误进入 Catalog diagnostics,不拖垮服务。`GET /api/skills` 只返回名称、描述、来源、安全展示路径和 diagnostics。 Harness 不在启动时读取并拼接所有 `SKILL.md` 正文。Pi 仅先看到 Catalog,匹配任务后通过 `read` 按需加载正文。`skill.requested` / `skill.loaded` **不是 Pi 原生事件**:Harness 以已发现 Skill 的 canonical `SKILL.md` 路径索引,从成功的 `read` 请求/结果可靠推导;读取失败不会误报 loaded。 示例: - `ads-analysis`:要求先调用离线 `mock_ads_metrics`,不声称有真实平台数据。 - `report-writer`:生成结构化中文报告,并请求 `save_report_draft`。 ## Session、Events 与 SSE 每个 Web Session 有随机 ID、`workspace/sessions/` 独立 cwd、`SessionManager.inMemory(cwd)`、事件 sequence、运行锁、AbortController、审批、附件与内存凭证。为避免无配置时触发 Pi 的模型 fallback,Web Session 先建立,Pi `AgentSession` 在首次已配置 Run 时惰性创建;每个 Web Session 至多对应一个独立 Pi Session。 消息 API 接受后立即返回 `202 + runId`;同 Session 并发返回 `409 SESSION_BUSY`。用户取消与系统超时分别记录。删除顺序是 abort、取消审批、关闭订阅/SSE、dispose、清凭证/附件、校验目录边界、删除临时 cwd。 EventStore 原子分配单 Session 单调 sequence,保留最近 1000 条。SSE 使用 `id/event/data`、15 秒 heartbeat、`Last-Event-ID` 优先于 `afterSequence`、历史重放、窗口 gap 控制事件、慢客户端断开策略与连接清理。前端 reducer 按 eventId/sequence 去重并合并高频 delta。 完整映射见 [docs/event-mapping.md](docs/event-mapping.md)。 ## Tools 与 Approval 显式 allowlist 只有 `read`、`grep`、`find`、`ls`、`mock_ads_metrics` 和 `save_report_draft`。默认不注册 bash/powershell/edit/write;未知 Tool deny。 - `mock_ads_metrics` 用当前 Pi `defineTool` + TypeBox,从 `fixtures/ads-account.json` 读取 1–30 天离线数据,计算除零安全、定点精度的指标。 - `save_report_draft` 只接受标题/正文,只能原子写当前 Session 的固定 `report.md`。 Pi extension 的 `tool_call` preflight 在执行前阻塞 `save_report_draft`。服务器 `ApprovalBroker` 是唯一权威;Approve 才继续,Deny/Timeout/Abort 均 block,且生成 `approval.resolved`。重复相同决定幂等,冲突决定返回 409。前端卡片和 modal 只提交决定并等待 SSE 权威状态。 ## 图片多模态链路 Composer 支持选择、拖拽、粘贴、上传状态、移除与对话框预览。自定义 assistant-ui `AttachmentAdapter` 立即流式上传,React 状态只保留 attachmentId 和临时 object URL;移除/卸载会 revoke URL。 服务端只接受 JPEG、PNG、WebP,并同时校验扩展名、声明 MIME 与 magic bytes。默认单图 5 MiB、单消息 4 张,并限制像素;Sharp 自动旋转、移除 EXIF/ICC 等元数据并重新编码。随机文件名只进入 Session 的 `attachments/`。SVG/GIF/PDF/视频、伪 MIME、跨 Session ID 与解压炸弹会被拒绝。 发送时服务器验证归属与 ready 状态,按已解析 Pi Model 的 image capability 拦截 text-only 模型,然后把规范化图片映射为安装版 Pi `ImageContent[]` 并调用 `session.prompt(text, { images })`。base64 只存在于这一步的服务端瞬时调用,不进入事件或 React 全局状态。 ## Demo 点击“填入 Demo Prompt”(不会自动执行),或粘贴: > 请使用 ads-analysis Skill,分析账户 demo-account 最近 7 天数据,找出异常 Campaign,并输出账户总结、关键指标和 3 条优化建议。最后使用 report-writer Skill 生成一份报告草稿。 可附一张广告素材/后台截图并补充: > 请同时观察我上传的图片,将可见内容作为辅助信号;不要根据图片编造图片中不存在的数据,数值指标以 mock_ads_metrics 返回的数据为准。 预期路径:Skill requested/loaded → mock metrics Tool 卡片 → 分析 → report-writer → save Tool 卡片与审批提醒。Approve 后只写 Session `report.md`;Deny/超时不写文件,聊天仍可呈现正文。10–15 分钟完整讲稿见 [docs/classroom-demo.md](docs/classroom-demo.md)。 ## 安全边界 **Pi 没有内置 Sandbox。Project Trust 是项目资源加载防线,不是执行隔离。** Pi、Extension 和 Tool 继承 Node 进程权限。本项目通过最小 Tool allowlist、固定 Session cwd、canonical/realpath 路径守卫、符号链接拒绝、protected-path preflight、固定写入目标、审批、SSRF 检查、图片重编码和日志/事件脱敏降低风险,但这些都不等于 OS Sandbox。 不要把不可信 Skill、脚本、仓库或无人值守任务直接交给本课堂进程。生产环境必须放入 Docker、VM、micro-VM 或远程 Sandbox,并配置受控网络出口。更多边界见 [docs/security-boundaries.md](docs/security-boundaries.md)。 ## 版本与 API 核对 编码前核对了 Pi 的 [SDK](https://pi.dev/docs/latest/sdk)、[Skills](https://pi.dev/docs/latest/skills)、[Extensions](https://pi.dev/docs/latest/extensions)、[Security](https://pi.dev/docs/latest/security)、[Models](https://pi.dev/docs/latest/models)、[Providers](https://pi.dev/docs/latest/providers),以及 assistant-ui 的 [Custom Runtime](https://www.assistant-ui.com/docs/runtimes/custom/external-store)、[Tools](https://www.assistant-ui.com/docs/tools) 与 [Attachments](https://www.assistant-ui.com/docs/guides/attachments)。安装后再以 `node_modules` 的公开 `.d.ts` 和可验证行为为准。 | 依赖 | 实装版本 | 使用的公开接口 | | --- | --- | --- | | Pi Coding Agent / pi-ai | 0.84.3 | `createAgentSession`, `DefaultResourceLoader`, `SettingsManager.inMemory`, `SessionManager.inMemory`, `ModelRuntime`, `InMemoryCredentialStore`, `defineTool`, `isToolCallEventType` | | TypeBox | 1.3.7 | Pi Tool 参数 schema | | assistant-ui React | 0.15.17 | `useExternalStoreRuntime`, Attachment Adapter, Toolkit + `Tools`, Thread/Composer primitives | | Fastify | 5.12.1 | HTTP、multipart、SSE | | React / Vite | 19.2.8 / 7.3.6 | Web UI / build | | Zod | 4.4.3 | HTTP、事件、模型与消息边界 | 发现的实际差异:Pi 文档示例中的图片结构仍展示嵌套 `source`,但 0.84.3 导出的稳定 `ImageContent` 是 `{ type: "image", data, mimeType }`,本实现服从安装包类型。Pi 0.84.3 也要求 Node `>=22.19.0`。assistant-ui 包仍导出部分历史 helper,但本项目没有使用弃用的 `makeAssistantTool` / `useAssistantToolUI`,采用稳定 External Store Runtime 和 Toolkit renderers;没有使用 `unstable_*` API。 ## 测试与当前结果 默认测试使用 fake `AgentSessionPort`,不需要 API Key、不会访问模型或外网。覆盖 shared schemas、Skill discovery/diagnostics、canonical Skill 事件、Tools、安全路径、Approval 全状态、Session 并发/取消/清理、模型无 fallback/SSRF/脱敏、JPEG/PNG/WebP、伪 MIME/大小/跨 Session、SSE replay/heartbeat/cleanup、assistant-ui reducer/Tool UI/Settings/附件与可访问性。 2026-08-28 本机验证: - `pnpm lint`:通过,0 warning。 - `pnpm typecheck`:4 个 workspace package 全部通过。 - `pnpm test`:8 个 test files、88 个 tests 全部通过;其中包含 `.env` 默认模型严格加载、环境 API Key 脱敏、加密凭证按默认模型跨重启恢复、模型元数据恢复、AES-256-GCM 凭证跨 Session 恢复/清除/错误 master key fail-closed、API Key 不进入 Web Storage、Session 初始化竞态、真实重复 Tool/Approval 事件链去重与 UI error fallback,以及使用精确自定义模型、关闭模型网络刷新后创建并 dispose 真实 Pi `AgentSession` 的测试。 - `pnpm build`:shared、harness、server、web 全部通过;Vite 提示单一 JS chunk 约 603 kB,可在生产化时拆包,但不影响构建。 - `PORT=3010 pnpm --filter @agent-studio/server start`:编译产物启动成功,`GET /health` 返回 200,随后 SIGINT 优雅关闭。 人工浏览器验收:桌面三栏、无模型禁发、Settings password 显隐/脱敏回显、Vision badge、Demo Prompt 只填不发、SSE Timeline、390px 移动端 Tabs 与无横向溢出均通过;应用控制台无 error/warn。浏览器自动化环境拒绝设置本地 file chooser,因此上传点击的人工自动化未完成,实际 multipart 上传、附件 adapter、图片预览/移除和 text-only 拦截由通过的 Server/Web 测试覆盖。 ## 已知限制与生产化 课堂版服务端仍是单进程:重启会丢 Session/Event/Approval,但模型 API Key 可通过本地 AES-256-GCM 密文恢复;浏览器只保留非敏感模型元数据。当前文件 vault 没有多用户/租户隔离、远程 KMS、密钥轮换或分布式锁。SSE 无跨实例协调;没有用户身份、CSRF、分布式 rate limit、持久审计、对象存储/KMS 或 OS Sandbox。自定义 provider 的 Vision 声明会被服务器注册成精确 Pi Model 并按该模型检查,但课堂版不会主动发远端 capability probe。 生产化路线见 [docs/production-roadmap.md](docs/production-roadmap.md):Redis/Postgres、对象存储、KMS、身份鉴权与 CSRF、速率/配额、容器/VM Sandbox、出口代理、不可变审计日志、队列/worker、分布式 SSE,以及完整可观测性。 ## SW Ads 图片日报 在服务器 `.env` 配置 `SWADS_MCP_URL`(默认 `https://ads.mincode.cn/mcp`)、`SWADS_MCP_TOKEN` 和可选 `SWADS_MCP_TIMEOUT_MS`(默认 30000,范围 100–120000)。Token 只在服务端保存,勿粘贴到聊天或提交 Git。URL 启动时校验;缺少 Token 不影响现有 mock ads、附件和报告草稿功能,首次 SW Ads 调用返回 `SWADS_NOT_CONFIGURED`。 发送 `/swads-daily-report`,或 `/swads-daily-report 账户 demo-account,日期 2026-09-06`。该内置 Skill 仅显式调用,保留原始用户消息。首版仅支持 TikTok GMV Max:默认当前账户最近完整业务日,之前七个完整业务日作比较;账户时区、币种和账户范围来自真实 Gateway。查询前探测线上 `tools/list`,要求只读 whoami、metrics catalog、semantic query 及实际 TikTok 指标能力;能力缺失、鉴权失败或空数据时停止生成。 Agent 先输出简洁 Markdown 分析,再生成 PNG 长图,图片可点击预览;Tool 卡提供 PNG、HTML、JSON 下载。服务器通过固定 Chrome CLI 参数和 Sharp 截图检查生成图片,不需要 Playwright/Puppeteer。可用 `CHROME_BIN` 指定绝对可执行路径,否则尝试固定系统 Chrome/Chromium 路径。浏览器缺失、超时或达到画布上限仍不完整时保留 HTML/JSON,并显示 `CHROME_UNAVAILABLE`、`PNG_TIMEOUT`、`PNG_RENDER_FAILED` 或 `PNG_TRUNCATED`。 产物原子保存于 `workspace/reports////`,不随 Session reset/delete 清理,不进入 Git。报告 URL 没有目录列表,PNG inline,HTML/JSON attachment。当前版本没有用户认证,随机 ID 只是内部/本地部署下的非枚举地址;持有 URL 即可访问。公网多用户部署需要另行实现认证、授权与租户隔离。存储保留与容量清理由部署方管理。 `swads_cli` 是服务端受控 CLI facade:Agent 不接收 Gateway 的逐项 MCP Tools,也不能执行 shell,但 facade 仍使用官方 MCP Streamable HTTP client 连接 MCP Gateway。该功能不执行任何平台写操作;所有预算、状态、目标和发布建议都列入人工确认。