feat: initial commit

This commit is contained in:
Jeffrey Wu
2026-09-07 09:57:33 +08:00
commit f39f6ac881
66 changed files with 9859 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# Architecture
## Component map
```mermaid
flowchart TB
subgraph Browser
C[assistant-ui Composer]
T[Thread + Tool UI]
E[Event Timeline]
S[HarnessClientStore]
C --> S
S --> T
S --> E
end
subgraph Server
F[Fastify protocol layer]
R[SessionRegistry]
ES[EventStore]
AB[ApprovalBroker]
AS[AttachmentStore]
MS[ModelConnectionStore]
P[Pi AgentSession]
L[DefaultResourceLoader]
W[SafeWorkspace]
end
S -->|JSON / multipart| F
F -->|SSE| S
F --> R
F --> AS
F --> MS
R --> P
R --> ES
P --> L
P --> AB
P --> W
```
`apps/server` 只做请求校验、错误映射、SSE framing 与对象组合。`packages/harness` 拥有 Agent 生命周期和所有核心状态接口。`packages/shared` 不依赖 Node,定义前后端唯一协议。`apps/web` 不拥有另一套 Agent 状态机,只用 sequence reducer 投影 EventStore。
## Session creation and run
1. Web 创建 Harness Session;服务器建立独立 cwd、事件序列、附件和凭证命名空间。
2. 服务启动时严格加载并缓存 `.env` 默认模型;新 Session 优先使用该配置。没有环境配置时,Web 可把 localStorage 中通过 strict Zod 校验的非敏感模型元数据恢复到该 Session。API Key 不参与浏览器持久化,来自环境变量或服务器 AES-256-GCM 凭证库。
3. 首次 Run 使用精确模型创建独立 Pi `AgentSession``SessionManager.inMemory(cwd)` 与安全 extension。
4. message 立即得到 202;后台调用 `prompt`Pi 事件由单一 adapter 映射并写 EventStore。
5. SSE 将历史和实时事件交给 `HarnessClientStore`assistant-ui External Store Runtime 只负责呈现。
## Storage replacement seams
`EventStore``ApprovalBroker``AttachmentStore``ModelConnectionStore``SessionRegistry` 都有接口。内存/本地临时文件实现可分别替换为 Redis streams、Postgres、对象存储、KMS 与分布式 worker,而不改变 Web 协议。
## Failure and shutdown
Run timeout 和用户 cancel 使用不同 reasonApproval wait 监听同一 AbortSignal。删除先 abort,再 cancel pending approval,关闭订阅与 SSE,调用 Pi `dispose()`,清凭证/附件,最后在 canonical sessions root 校验后删除 cwd。单个 SSE 断开不取消 Run。
+37
View File
@@ -0,0 +1,37 @@
# 1015 minute classroom demo
## 0:002:00 — boundaries
启动 `pnpm dev`,展示三栏:左侧只显示 Skill catalog,不显示正文;中间由 assistant-ui 渲染;右侧是 EventStore 的审计时间线。强调 Pi 是 Agent Loop 事实来源,assistant-ui 不直接访问模型。
## 2:004:00 — model connection
打开 Settings,比较 OpenAI、Anthropic、OpenAI-compatible preset。说明模型元数据会保存在 localStorage,但 key 不回填、不进 Web Storage;刷新后字段自动恢复,必须重输 Key,Composer 才会启用。课堂无需真实调用时可只演示表单和测试返回的“首次运行验证”。
## 4:008:00 — main path
点击 Demo Prompt(它只填入,不自动执行):
> 请使用 ads-analysis Skill,分析账户 demo-account 最近 7 天数据,找出异常 Campaign,并输出账户总结、关键指标和 3 条优化建议。最后使用 report-writer Skill 生成一份报告草稿。
有可用课堂模型时发送,观察:`run.started``skill.requested``skill.loaded``mock_ads_metrics` Tool 卡片 → streaming delta。指出指标来自 fixture,不是真实广告平台。
## 8:0011:00 — approval
`save_report_draft` 出现时,展示上下文 Tool 卡片与 modal。先讲清 preflight 尚未写文件。
- Approve 路径:点击“批准一次”,等待 SSE 权威状态,再查看当前 Session 的 `report.md`
- Deny 路径:重开一个 Session,再拒绝;确认没有文件但 Agent 可在聊天给出正文。
- Timeout 可将 `APPROVAL_TIMEOUT_MS` 临时缩短后演示;最终状态以服务器事件为准,不以浏览器倒计时为准。
## 11:0013:00 — image path
选择/拖拽/粘贴一张 JPEG、PNG 或 WebP,打开缩略图 Dialog,再附加:
> 请同时观察我上传的图片,将可见内容作为辅助信号;不要根据图片编造图片中不存在的数据,数值指标以 mock_ads_metrics 返回的数据为准。
说明 browser object URL、流式 upload、Sharp 重编码、attachmentId、服务端 ImageContent 的链路。切换到 text-only 配置,演示发送前阻止图片。
## 13:0015:00 — failure and production boundary
停止一个 Run,查看 `run.cancelled`。断开/刷新页面说明 SSE 重放与去重。最后强调:Pi Project Trust 不是 Sandbox;不可信任务需要容器/VM/micro-VM,课堂内存 Store 也不是生产持久层。
+25
View File
@@ -0,0 +1,25 @@
# Event mapping
Pi 原生事件到 HarnessEvent 的转换集中在 `packages/harness/src/pi-event-adapter.ts`。未识别事件安全忽略,不会让 Run 崩溃。
| Pi / Harness fact | HarnessEvent | assistant-ui / UI state |
| --- | --- | --- |
| message text delta | `assistant.delta` | 按 messageId 合并文本,status running |
| assistant message end | `assistant.completed` | message status completepolite live announcement |
| extension `tool_call` preflight | `tool.requested` | Tool part running / approval pending |
| tool execution success | `tool.completed` | Tool result success |
| tool execution error | `tool.failed` | Tool result error |
| canonical Skill `read` request | `skill.requested` | Catalog 高亮 requested |
| 同一 read 成功结果 | `skill.loaded` | Catalog 高亮 loaded |
| ApprovalBroker request | `approval.required` | Tool card + modal pending |
| broker decision/timeout/cancel | `approval.resolved` | approved/denied/expired/cancelled |
| registry accepts message | `user.message`, `run.started` | user message + running |
| prompt resolves | `run.completed` | run idle |
| abort by user/system | `run.cancelled` | incomplete,显示 reason |
| prompt/create failure | `run.failed` | incomplete/error |
Skill requested/loaded 是 Harness 推导事件,不是 Pi 原生事件。匹配依据是 `DefaultResourceLoader` 已发现的 canonical `SKILL.md` 索引;仅文件名相同不算命中。
`tool.requested` 在 extension preflight 写入,后续 Pi `tool_execution_start` 按 toolCallId 去重。读取类 Tool 的 completed event 只暴露安全摘要,避免把 Skill 正文或文件内容复制到 Timeline。参数会限长、敏感字段脱敏,绝对路径转换为项目相对路径或 `<protected-path>`
SSE 事件 id 是 eventId,事件名是 HarnessEvent type。`Last-Event-ID` 比 query `afterSequence` 优先;若 cursor 早于 1000 条保留窗口,先发送 `stream.gap`,客户端重新建立可识别边界,而不是静默丢事件。
+33
View File
@@ -0,0 +1,33 @@
# Production roadmap
## Persistence and distribution
1. EventStore 迁移到 Redis Streams 或 Kafka,并把长期审计写 Postgres/不可变对象存储。
2. Session/Approval 元数据进入 Postgres,运行锁、timeout、SSE fan-out 使用 Redis。
3. AttachmentStore 使用带生命周期策略的对象存储、短期签名 URL、恶意内容扫描和租户配额。
4. 把 Agent Run 放入 durable queue/worker,支持幂等恢复、worker lease 与跨实例取消。
## Secrets and identity
1. KMS/Vault envelope encryption,短期 scoped credential,不在长期 Store 保存明文。
2. OIDC/SSO、租户/RBAC/ABAC、每个 Session 所有权校验。
3. CSRF 防护、严格 same-site cookie、CSP、Origin 检查、rate limit、配额与 abuse detection。
4. 管理员批准策略、双人审批和不可抵赖审计。
## Isolation
1. 每 Run 使用无特权容器、VM 或 micro-VM;只读输入、最小可写输出。
2. seccomp/AppArmor、PID/CPU/内存/磁盘/time limits、无 host socket。
3. egress allowlist proxyDNS pinning,重定向逐跳校验,metadata 永久 deny。
4. Skill 签名、来源策略、版本锁与离线扫描。
## Reliability and observability
1. 分布式 SSE/WebSocket gatewaycursor 持久化、backpressure 和 gap recovery。
2. OpenTelemetry traceHTTP → run → model → tool → approval;指标不含 prompt/key/image body。
3. Provider circuit breaker、预算、token/cost accounting、重试分类和 dead-letter queue。
4. chaos tests、断电恢复、跨版本 event schema migration 和数据保留策略。
## Frontend scale
代码分割 assistant-ui/设置/附件预览,虚拟化长 Timeline 与 Thread;增加离线恢复、上传续传和生产级国际化。保持 HarnessClientStore 是唯一投影状态,不在 Runtime 中复制 Agent 状态机。
+41
View File
@@ -0,0 +1,41 @@
# Security boundaries
## Trust model
Pi 没有内置 Sandbox。Project Trust 只控制项目资源加载,不隔离进程、文件系统或网络。Pi、Extension 与 Tool 都继承 Node 进程权限。本项目是安全教学基线,不是恶意代码隔离器。
## Credentials and logs
- API Key 只经同源 body 进入服务端;Pi 运行时仍使用 Session 专属 `InMemoryCredentialStore`
- 前端提交的 API Key 使用 `MODEL_CREDENTIAL_ENCRYPTION_KEY` 提供的 32-byte master key 做 AES-256-GCM 加密,采用随机 96-bit IV、认证标签和模型连接 identity AAD;密文文件使用 `0600` 权限与临时文件原子 rename。master key 只来自 `.env`/进程环境,绝不写入密文文件。
- 浏览器刷新创建新 Session 时,只提交非敏感模型 identity;服务端按 identity 解密恢复对应凭证。删除 Session 不删除持久凭证,只有 Clear Credentials 会删除。
- 浏览器 localStorage 只保存白名单模型元数据;保存前主动移除 `apiKey` 并用不接受额外字段的 Zod schema 校验。损坏或超限数据会删除。
- 不写 Pi 全局 auth/models 文件,不执行 `!command`,不接受自定义 headers。
- 响应仅回传配置状态和末四位 hint;SSE、事件、日志和测试快照禁止原始 key。
- Fastify 只记录安全元数据并对 Authorization/API Key 路径 redaction;生产错误不返回 stack/绝对路径。
## Filesystem
- 每个 Session 独立 canonical cwdresolve 后必须仍在 root 内。
- `realpath` 验证父目录,拒绝 `..`、绝对路径逃逸与符号链接路径。
- `.env``.git`、SSH、用户主目录和系统路径受到 preflight 保护。
- `save_report_draft` 目标固定为 `report.md`,临时排他创建后原子 rename;审批不能替代路径检查。
- 默认不注册 shell、write、edit。
课堂实现使用 Node 文件 API,无法完全消除恶意同机进程造成的 TOCTOU。生产版应把每个任务放进独立 mount namespace / micro-VM,并使用只读输入卷和受控输出卷。
## Images
multipart 流有字节上限;扩展名、MIME、magic bytes 三重匹配。Sharp 限制像素、解码并重新编码,自动旋转且移除多余 metadata。随机文件名仅写 Session attachments,预览不提供任意路径静态服务。事件仅记录 ID、MIME、大小和尺寸。
## Model Base URL and SSRF
默认 HTTPS;拒绝 URL credentials、fragment、危险 credential query、畸形 URL、metadata hostname,以及 DNS 解析得到的 private/loopback/link-local 地址。开发环境可使用 loopback HTTP,企业内网必须显式 opt-in。DNS rebinding、重定向逐跳策略和出口 DNS 固定应由生产 egress proxy 强制执行。
## Approval
审批发生在 Pi `tool_call` preflightTool 尚未执行。Broker 校验当前 Session/Run、响应 abort、释放 timer/Promise,并将 approve/deny/expired/cancelled 作为服务器事实发送。未知工具 deny,未注册高风险工具无法被模型调用。
## Required production isolation
不可信 Skill、仓库、脚本、附件解析或无人值守 Agent 必须在 Docker(配合 seccomp/AppArmor 和无特权用户)、VM、micro-VM 或远程 Sandbox 运行,并施加 CPU/内存/磁盘/时间/网络配额。Project Trust 不能替代这些措施。