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 · Tools 与审批 · 图片链路 · 安全边界 · 生产化路线

架构

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]
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。

目录

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.0Pi 0.84.3 的 package engine 要求)
  • pnpm 11

本地运行

cp .env.example .env
# 至少填写 AGENT_PROVIDER 和 AGENT_MODELAPI Key 也可启动后在 Settings 中提交
pnpm install
pnpm dev

Server 默认监听 127.0.0.1:3001Vite Web 默认监听 127.0.0.1:5173,并代理 /api/health。启动后访问 http://127.0.0.1:5173

常用质量检查命令:

pnpm lint
pnpm typecheck
pnpm test
pnpm build

模型配置

服务器环境变量优先级低于当前 Web Session 的内存设置:

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_PROVIDERAGENT_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 可直接由 .envOPENAI_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 keyopenssl 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/skillsAGENT_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/<id> 独立 cwd、SessionManager.inMemory(cwd)、事件 sequence、运行锁、AbortController、审批、附件与内存凭证。为避免无配置时触发 Pi 的模型 fallbackWeb 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

Tools 与 Approval

显式 allowlist 只有 readgrepfindlsmock_ads_metricssave_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

安全边界

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

版本与 API 核对

编码前核对了 Pi 的 SDKSkillsExtensionsSecurityModelsProviders,以及 assistant-ui 的 Custom RuntimeToolsAttachments。安装后再以 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 typecheck4 个 workspace package 全部通过。
  • pnpm test8 个 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 buildshared、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.mdRedis/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,范围 100120000)。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_UNAVAILABLEPNG_TIMEOUTPNG_RENDER_FAILEDPNG_TRUNCATED

产物原子保存于 workspace/reports/<accountKey>/<YYYY-MM-DD>/<reportId>/,不随 Session reset/delete 清理,不进入 Git。报告 URL 没有目录列表,PNG inlineHTML/JSON attachment。当前版本没有用户认证,随机 ID 只是内部/本地部署下的非枚举地址;持有 URL 即可访问。公网多用户部署需要另行实现认证、授权与租户隔离。存储保留与容量清理由部署方管理。

swads_cli 是服务端受控 CLI facadeAgent 不接收 Gateway 的逐项 MCP Tools,也不能执行 shell,但 facade 仍使用官方 MCP Streamable HTTP client 连接 MCP Gateway。该功能不执行任何平台写操作;所有预算、状态、目标和发布建议都列入人工确认。

S
Description
No description provided
Readme
1.9 MiB
Languages
TypeScript 97.2%
CSS 2.5%
JavaScript 0.2%