--- 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 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 \ "<任务描述>" 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 --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 ### 手动部署线上(紧急) ```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 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 验证: ``` 读 /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 # 配置管理 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 /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 ```