389 lines
12 KiB
Markdown
389 lines
12 KiB
Markdown
---
|
|
name: uncaged-dev
|
|
version: 1.0.0
|
|
description: >
|
|
Uncaged 项目的完整开发 skill。涵盖:项目架构、开发流程、build/deploy、
|
|
测试验证、调试排查。装这一个 skill 就获得所有 Uncaged 开发能力。
|
|
metadata:
|
|
requiredTools: ["secret", "gh"]
|
|
---
|
|
|
|
# Uncaged Dev
|
|
|
|
Uncaged 项目开发的一站式 skill。
|
|
|
|
## 项目概览
|
|
|
|
**Uncaged** — Sigil-native AI Agent 平台,运行在 Cloudflare Workers 上。
|
|
|
|
```
|
|
uncaged/
|
|
├── packages/
|
|
│ ├── core/ # 共享核心:LLM、Memory、Sigil、Tool Registry
|
|
│ ├── worker/ # CF Worker(后端 API + routing)
|
|
│ ├── web/ # React 19 + Tailwind v4 前端(SPA)
|
|
│ └── runner/ # Runner 客户端(连设备到 Agent)
|
|
├── tests/e2e/ # 场景化测试用例
|
|
├── scripts/ # 部署脚本
|
|
└── .github/workflows/ # CI/CD
|
|
```
|
|
|
|
**仓库:** `oc-xiaoju/uncaged`
|
|
**线上:** `https://uncaged.shazhou.work`
|
|
**CI/CD:** push main → GitHub Actions 自动部署
|
|
|
|
## 开发流程
|
|
|
|
**原则:Issue 驱动,协调者不写代码,Cursor Agent 干活。**
|
|
|
|
```
|
|
需求/Bug → 开 Issue → 创建分支 → Cursor Agent 编码 →
|
|
验证(build + diff)→ Commit(closes #N)→ 合并 main → 自动部署
|
|
```
|
|
|
|
### 1. 开 Issue
|
|
|
|
```bash
|
|
gh issue create --repo oc-xiaoju/uncaged \
|
|
--title "fix/feat: 简短描述" \
|
|
--body "## Problem\n...\n## Plan\n...\n## Acceptance\n..."
|
|
```
|
|
|
|
### 2. 创建分支
|
|
|
|
```bash
|
|
cd <uncaged-repo>
|
|
git checkout main && git pull
|
|
git checkout -b fix/descriptive-name # 或 feat/
|
|
```
|
|
|
|
### 3. 用 Cursor Agent 编码
|
|
|
|
```bash
|
|
CURSOR_API_KEY="$(secret get CURSOR_API_KEY)" \
|
|
bash ~/.openclaw/workspace/skills/cursor-agent-cn/scripts/run.sh \
|
|
<uncaged-repo> "<任务描述>" auto ask # 先 review
|
|
# 确认后
|
|
... auto write # 再 apply
|
|
```
|
|
|
|
中国区必须用 `auto` model。两步走:先 `ask` review,再 `write` apply。
|
|
|
|
### 4. 验证
|
|
|
|
```bash
|
|
# Build(三步)
|
|
cd packages/core && rm -rf dist && npx tsc
|
|
cd ../web && npm run build
|
|
cd ../worker && npx tsc --noEmit
|
|
|
|
# Diff 检查
|
|
git diff --stat
|
|
```
|
|
|
|
### 5. 提交 + 合并
|
|
|
|
```bash
|
|
git add -A
|
|
git commit -m "fix: description (closes #N)"
|
|
git checkout main && git merge <branch> --no-ff
|
|
git push origin main # 触发 CI/CD 自动部署
|
|
```
|
|
|
|
## Build & Deploy
|
|
|
|
### 线上(自动)
|
|
Push main → GitHub Actions → build core → build web → wrangler deploy
|
|
|
|
### 开发环境(手动)
|
|
|
|
每人一个独立 Worker,互不影响:
|
|
|
|
```bash
|
|
bash scripts/deploy-dev.sh xingyue # → uncaged-xingyue.shazhou.work
|
|
bash scripts/deploy-dev.sh xiaoju # → uncaged-xiaoju.shazhou.work
|
|
bash scripts/deploy-dev.sh xiaomooo # → uncaged-xiaomooo.shazhou.work
|
|
bash scripts/deploy-dev.sh aobing # → uncaged-aobing.shazhou.work
|
|
```
|
|
|
|
脚本自动:build core → build web → wrangler deploy --env <name>
|
|
|
|
### 手动部署线上(紧急)
|
|
|
|
```bash
|
|
cd packages/core && rm -rf dist && npx tsc
|
|
cd ../web && npm run build
|
|
cd ../worker
|
|
CLOUDFLARE_API_TOKEN="$(secret get CLOUDFLARE_API_TOKEN)" \
|
|
CLOUDFLARE_ACCOUNT_ID="$(secret get CLOUDFLARE_ACCOUNT_ID)" \
|
|
npx wrangler deploy
|
|
```
|
|
|
|
### 常见 Build 问题
|
|
|
|
| 问题 | 原因 | 解决 |
|
|
|:-----|:-----|:-----|
|
|
| core dist 为空 | tsconfig 缺 `noEmitOnError: false` | 检查 `packages/core/tsconfig.json` |
|
|
| wrangler can't resolve @uncaged/core/* | core 没 build | 先 `cd packages/core && npx tsc` |
|
|
| POST 请求返回 SPA HTML | wrangler.toml 缺 `run_worker_first = true` | 检查 `[assets]` 配置 |
|
|
| 路由 404 | `normalizeApiPath` strip 了 `/api/v1/` | 路由匹配用 strip 后路径 |
|
|
|
|
## 测试
|
|
|
|
### 测试分层
|
|
|
|
| 层级 | 工具 | 位置 | 用途 |
|
|
|:-----|:-----|:-----|:-----|
|
|
| **Unit Test** | Vitest | `packages/*/src/*.test.ts` | 纯逻辑,mock 外部依赖,秒级 |
|
|
| **UI 组件测试** | Vitest + @testing-library/react | `packages/web/src/**/*.test.tsx` | React 组件交互,mock fetch |
|
|
| **E2E API** | curl 脚本 | `tests/e2e/scripts/run-tests.sh` | 真实 HTTP 请求打线上 API |
|
|
| **E2E UI** | Playwright | `tests/e2e/ui/*.spec.ts` | 浏览器自动化,端到端流程 |
|
|
|
|
**跑测试的顺序:** UT → UI 组件 → build → E2E(先快后慢,先本地后线上)
|
|
|
|
### Unit Test
|
|
|
|
```bash
|
|
# 跑全部 UT
|
|
cd packages/core && npx vitest run
|
|
cd packages/worker && npx vitest run
|
|
cd packages/web && npx vitest run # UI 组件测试也在这
|
|
|
|
# 跑单个文件
|
|
npx vitest run src/agent-loop.test.ts
|
|
|
|
# Watch 模式(开发时)
|
|
npx vitest --watch
|
|
```
|
|
|
|
**写 UT 的规范:**
|
|
- 文件放在被测源码旁边:`foo.ts` → `foo.test.ts`
|
|
- Mock 外部依赖(KV、D1、fetch),参考 `core/src/__mocks__/cf-bindings.ts`
|
|
- 测试文件不要 import 真实 Cloudflare bindings
|
|
- `describe` 按功能分组,`test` 名字说清楚测的是什么行为
|
|
- 验收:`npx vitest run` 全绿 + 无 type error
|
|
|
|
**现有 UT 覆盖(13 文件 134 tests):**
|
|
- `agent-loop` — loop 流程、tool call、maxRounds、nudge、streaming
|
|
- `pipeline` — model 选择、context 压缩、orphan tool 清理
|
|
- `baton` — 创建、状态流转、子 baton 事件
|
|
- `tool-registry` — 注册、查询、user-invocable 过滤
|
|
- `chat-store` + `chat-store-kv` — 消息存储/读取
|
|
- `identity` / `soul` / `codegen` / `embedding` / `chat-key` / `short-id` / `slug-resolver`
|
|
|
|
### UI 组件测试
|
|
|
|
```bash
|
|
cd packages/web && npx vitest run
|
|
```
|
|
|
|
**写 UI 组件测试的规范:**
|
|
- 用 `@testing-library/react` + `jsdom` 环境
|
|
- 文件放在组件旁边:`chat-input.tsx` → `chat-input.test.tsx`
|
|
- Mock `fetch` / `authedFetch`,不打真实 API
|
|
- 优先测用户交互(输入、点击、键盘事件)和渲染结果
|
|
- 使用 `data-testid`(#71 已添加)定位元素
|
|
- 不测样式/CSS,测行为和 DOM 结构
|
|
|
|
### Playwright UI 测试(9 个用例,28 秒)
|
|
|
|
```bash
|
|
cd <uncaged-repo>
|
|
npx playwright test # 跑全部
|
|
npx playwright test -g "tool search" # 跑单个
|
|
npx playwright test --reporter=html # 生成 HTML 报告
|
|
```
|
|
|
|
覆盖:token 登录、聊天发消息、工具搜索浮层、ESC 关闭、主题切换、JS 错误检测、手机端布局。
|
|
|
|
**首次使用需安装浏览器:** `npx playwright install chromium`
|
|
|
|
**指定目标环境:** `UNCAGED_URL=https://uncaged-xingyue.shazhou.work npx playwright test`
|
|
|
|
**指定 token:** `TOKEN_NAME=UNCAGED_AGENT_TOKEN_XIAOJU npx playwright test`
|
|
|
|
### API 回归(12 个用例,curl 脚本)
|
|
|
|
```bash
|
|
bash tests/e2e/scripts/run-tests.sh UNCAGED_AGENT_TOKEN_XINGYUE
|
|
```
|
|
|
|
### 场景验证(给 subagent 用)
|
|
|
|
场景文件在 `tests/e2e/scenes/`:
|
|
|
|
| 场景 | 文件 |
|
|
|:-----|:-----|
|
|
| 认证 | `scenes/auth.md` |
|
|
| 聊天 | `scenes/chat.md` |
|
|
| 流式 | `scenes/streaming.md` |
|
|
| Tool Gateway | `scenes/tool-gateway.md` |
|
|
| 工具搜索 | `scenes/tool-search.md` |
|
|
| 异常处理 | `scenes/error-handling.md` |
|
|
|
|
派 subagent 验证:
|
|
```
|
|
读 <uncaged-repo>/tests/e2e/scenes/tool-gateway.md,按描述验证。
|
|
失败了收集 logs 开 bug issue。
|
|
```
|
|
|
|
### Token 登录(测试用)
|
|
|
|
```bash
|
|
TOKEN=$(secret get UNCAGED_AGENT_TOKEN_XINGYUE)
|
|
curl -s -X POST "https://uncaged.shazhou.work/auth/token" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"token\": \"$TOKEN\"}" \
|
|
-c /tmp/uncaged-cookies.txt
|
|
```
|
|
|
|
### uncaged-cli 工具
|
|
|
|
**安装:**
|
|
```bash
|
|
npm install -g @uncaged/cli # 官方版本
|
|
# 或
|
|
npm install -g uncaged-cli # 备用版本
|
|
```
|
|
|
|
**基本用法:**
|
|
```bash
|
|
# 初始化配置
|
|
uncaged init
|
|
|
|
# 登录(自动检测 JWT token 或生成 Admin token)
|
|
uncaged login
|
|
|
|
# 环境状态检查
|
|
uncaged status
|
|
|
|
# 数据库查询
|
|
uncaged db users --limit 10
|
|
uncaged db messages --user scott --channel web --limit 20
|
|
uncaged db agents --format table
|
|
uncaged db context-boundaries --user scott --channel telegram
|
|
|
|
# API 调试
|
|
uncaged api chat "hello world"
|
|
uncaged api history --user scott --channel web --limit 10
|
|
uncaged api debug overview
|
|
uncaged api debug stats
|
|
uncaged api debug capabilities
|
|
|
|
# 测试用户管理(Admin 权限)
|
|
uncaged admin create-user test-user-123
|
|
uncaged admin delete-user test-user-123 --cascade
|
|
uncaged admin create-token test-user-123
|
|
|
|
# Sigil 操作
|
|
uncaged sigil list
|
|
uncaged sigil inspect <capability-name>
|
|
|
|
# 配置管理
|
|
uncaged config show
|
|
uncaged config set endpoint https://uncaged-dev.shazhou.work
|
|
uncaged config set auth.method admin
|
|
```
|
|
|
|
**多环境切换:**
|
|
```bash
|
|
# 开发环境
|
|
uncaged config set endpoint https://uncaged-xingyue.shazhou.work
|
|
uncaged config set auth.method jwt
|
|
uncaged config set auth.token "$(secret get UNCAGED_AGENT_TOKEN_XINGYUE)"
|
|
|
|
# 生产环境
|
|
uncaged config set endpoint https://uncaged.shazhou.work
|
|
uncaged config set auth.method admin
|
|
uncaged config set auth.admin_token "$(secret get UNCAGED_ADMIN_TOKEN)"
|
|
|
|
# 查看当前配置
|
|
uncaged status
|
|
```
|
|
|
|
**Channel 概念:**
|
|
- `web` — 网页聊天
|
|
- `telegram` — Telegram 消息
|
|
- `api` — 直接 API 调用
|
|
|
|
所有 `db` 和 `api` 命令都支持 `--channel` 过滤。
|
|
|
|
## 调试
|
|
|
|
### Worker 日志
|
|
|
|
```bash
|
|
CF_TOKEN=$(secret get CLOUDFLARE_API_TOKEN)
|
|
CF_ACCOUNT=$(secret get CLOUDFLARE_ACCOUNT_ID)
|
|
cd <uncaged-repo>/packages/worker
|
|
CLOUDFLARE_API_TOKEN="$CF_TOKEN" CLOUDFLARE_ACCOUNT_ID="$CF_ACCOUNT" \
|
|
npx wrangler tail --format pretty
|
|
```
|
|
|
|
### D1 查询
|
|
|
|
```bash
|
|
CLOUDFLARE_API_TOKEN="$CF_TOKEN" CLOUDFLARE_ACCOUNT_ID="$CF_ACCOUNT" \
|
|
npx wrangler d1 execute uncaged-memory --remote --json \
|
|
--command "SELECT * FROM users LIMIT 10;"
|
|
```
|
|
|
|
### 前端状态
|
|
|
|
```bash
|
|
# Session
|
|
curl -s https://uncaged.shazhou.work/auth/session -b /tmp/uncaged-cookies.txt | python3 -m json.tool
|
|
# History
|
|
curl -s https://uncaged.shazhou.work/scott/doudou/api/history -b /tmp/uncaged-cookies.txt | python3 -c "
|
|
import sys,json; [print(f' [{m[\"role\"]}] {str(m.get(\"content\",\"\"))[:80]}') for m in json.load(sys.stdin)['history'][-5:]]"
|
|
```
|
|
|
|
## 架构速查
|
|
|
|
### API
|
|
|
|
| 端点 | 方法 | 说明 |
|
|
|:-----|:-----|:-----|
|
|
| `/auth/token` | POST | Token 登录 |
|
|
| `/auth/session` | GET | Session 检查 |
|
|
| `/:o/:a/api/v1/chat` | POST | 发消息 |
|
|
| `/:o/:a/api/v1/chat/stream` | POST | SSE 流式 |
|
|
| `/:o/:a/api/v1/history` | GET | 聊天历史 |
|
|
| `/:o/:a/api/v1/clear` | POST | 清空历史 |
|
|
| `/:o/:a/api/v1/tools/builtin` | GET | Builtin 工具列表 |
|
|
| `/:o/:a/api/v1/tools/:slug/invoke` | POST | 直接调用工具 |
|
|
|
|
### 关键文件
|
|
|
|
| 文件 | 作用 |
|
|
|:-----|:-----|
|
|
| `core/src/llm/tool-registry.ts` | **SSOT** — 所有 builtin tool 定义 |
|
|
| `core/src/llm/agent-loop.ts` | LLM agent loop + tool execution |
|
|
| `worker/src/index.ts` | 主路由 + Worker entry |
|
|
| `worker/src/services/capability-service.ts` | Tool Gateway 执行层 |
|
|
| `web/src/hooks/use-chat.ts` | 前端聊天状态 |
|
|
| `web/src/components/chat/chat-input.tsx` | 输入框 + 工具搜索 |
|
|
| `web/src/components/chat/message-bubble.tsx` | 消息气泡渲染 |
|
|
| `worker/wrangler.toml` | CF 配置(bindings + routes + envs) |
|
|
|
|
### 路由注意事项
|
|
|
|
- 所有前端 API 调用统一使用 `/api/v1/` 前缀(#73 已清理)
|
|
- `normalizeApiPath` 只 strip `/api/v1/` 前缀(不再处理 `/api/`)
|
|
- 新路由要用 strip 后的路径匹配(如 `/tools/:slug/invoke` 不是 `/api/v1/tools/...`)
|
|
- `[assets] run_worker_first = true` 确保 POST 请求到 Worker
|
|
- 没有 `webEnabled`、`hasBearer`、`channels/web.ts`(#73 已删除)
|
|
|
|
### Tool Registry(SSOT)
|
|
|
|
添加新 builtin tool 只需改 `core/src/llm/tool-registry.ts` 的 `TOOL_REGISTRY` 数组,LLM / Gateway / 前端自动同步。
|
|
|
|
## Secrets
|
|
|
|
```bash
|
|
secret get CLOUDFLARE_API_TOKEN # CF 部署
|
|
secret get CLOUDFLARE_ACCOUNT_ID # CF 账户
|
|
secret get CURSOR_API_KEY # Cursor Agent
|
|
secret get UNCAGED_AGENT_TOKEN_XINGYUE # 测试登录 token
|
|
```
|