Agent Studio Mini
Agent Studio Mini 是一个用于内部培训的、可运行的最小 AI Agent Harness。它用 Pi Coding Agent SDK 驱动 Agent Loop,以 Fastify + SSE 暴露受控协议,用 assistant-ui 展示聊天、Tool、审批、图片附件与完整事件时间线。
当前仓库未附产品截图;启动后,http://127.0.0.1:5173 即是桌面三栏和移动端 Tabs 的真实演示界面。
架构
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 架构、映射、安全、课堂脚本与路线图
环境与启动
Pi 0.84.3 的实际 package engine 要求 Node.js >=22.19.0,因此本仓库采用该版本,而不是需求草案中的 Node 20 下限。需要 pnpm 11。
cp .env.example .env
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。根命令还包括:
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=120000
APPROVAL_TIMEOUT_MS=60000
MAX_IMAGE_BYTES=5242880
MAX_IMAGES_PER_MESSAGE=4
服务启动时会从项目 .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/<id> 独立 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。
Tools 与 Approval
显式 allowlist 只有 read、grep、find、ls、mock_ads_metrics 和 save_report_draft。默认不注册 bash/powershell/edit/write;未知 Tool deny。
mock_ads_metrics用当前 PidefineTool+ 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 的 SDK、Skills、Extensions、Security、Models、Providers,以及 assistant-ui 的 Custom Runtime、Tools 与 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 真实 PiAgentSession的测试。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:Redis/Postgres、对象存储、KMS、身份鉴权与 CSRF、速率/配额、容器/VM Sandbox、出口代理、不可变审计日志、队列/worker、分布式 SSE,以及完整可观测性。