# Agent Client（demo v0.1）

> FFOA 内部 AI Coding Assistant 客户端 demo。三层架构：浏览器/桌面 UI ↔ 本地 Service ↔ 中转 Gateway ↔ AI 服务。
>
> 本目录是 v0.1 demo 的全部代码；定型后会迁到独立仓库 `ffoa-agent-client`。

---

## 架构

```
浏览器 (4292)            Mac APP (Tauri 内嵌 webview)
        ╲                        ╱
         WebSocket 4291  
              ↓
   本地 Service (Node + Agent SDK)
              ↓ HTTPS
   Gateway (NestJS, 海外节点 4290 / UAT 7050)
   ├── 鉴权 (静态 token，v1 换 SSO)
   ├── 头部脱敏 + 凭据注入
   └── Provider 抽象 (v0.1 只接 Anthropic)
              ↓
       api.anthropic.com
```

**品牌脱敏**：UI / 错误信息 / HTTP 头一律不暴露上游品牌。模型字段对外抽象成"标准模型"；
Service 看到上游异常会经 `brandSafeError` 替换关键字（agent.ts:175）；Gateway 报 502 时返回
`{"error":"AI 服务暂时不可用"}`。

---

## 快速开始（浏览器模式）

### 0. 一次性设置

```bash
cd packages/agent-client
bash scripts/setup.sh                     # 装 4 个子项目 + build shared
```

填环境变量：

```bash
cp gateway/.env.example  gateway/.env.local   # 填 UPSTREAM_API_KEY (Anthropic dev key)
cp service/.env.example  service/.env.local   # GATEWAY_TOKEN 跟 gateway 保持一致
cp frontend/.env.example frontend/.env.local  # NEXT_PUBLIC_SERVICE_TOKEN 跟 GATEWAY_TOKEN 一致
```

> **三处 token 必须相同**：service → gateway 用 `GATEWAY_TOKEN`，frontend → service 用同一个值（v0.1 简化，v1 换登录后下发短期 token）。

### 1. 启动

```bash
bash scripts/dev-all.sh
```

启动后：
- Gateway   `http://localhost:4290`（健康检查 `/health`）
- Service   `ws://localhost:4291`
- Frontend  `http://localhost:4292`

> **SSH 远程开发提示**：开发机在远端时，本机浏览器要起隧道：
> `ssh -L 4290:localhost:4290 -L 4291:localhost:4291 -L 4292:localhost:4292 <dev-host>`

### 2. 验收三场景

在 `http://localhost:4292` 输入：

| # | Prompt | 期望工具 | 通过条件 |
|---|---|---|---|
| 1 | `读 packages/agent-client/package.json，告诉我里面有什么` | Read | 浏览器看到 Read tool_use 卡 + 文本回答列出文件结构 |
| 2 | `在 /tmp/demo-test/ 下创建 hello.txt 写 'hello demo'，然后读出来确认` | Bash + Write + Read | 三个 tool_use 卡按顺序出现；/tmp/demo-test/hello.txt 实际存在 |
| 3 | `跑 git log --oneline -5，告诉我最近 5 次提交` | Bash | tool_use 卡里展开能看到 git log 输出 |

每个场景必须：流式可见 + tool_use 可展开 + 错误可观察 + 断网能恢复（auto-reconnect）。

### 3. 不带 UI 的 CLI 冒烟测试

```bash
cd service
set -a && source .env.local && set +a
npm run cli -- "列出当前目录"
```

---

## 快速开始（Mac APP 模式）

> 必须在 macOS 上跑 `tauri build`——slot-4 的 Linux 远端只能跑前两步。

```bash
# 1. 在 slot-4（或任何机器）准备好代码
bash scripts/setup.sh

# 2. 把 Service 打成 sidecar 资源（在 macOS 上做，Anthropic SDK 有平台特异 native 模块）
bash scripts/build-sidecar.sh

# 3. 在 frontend 目录 dev 模式跑（webview + 自动启 service）
cd frontend
npm run tauri:dev
# 这会：
#   - tauri 主进程启动一个随机端口的 service 子进程
#   - webview 加载 http://localhost:4292（dev URL）
#   - service 通过 ANTHROPIC_BASE_URL 连 gateway
#   - 你需要在另一个终端单独跑 gateway（后续 v0.2 把 gateway 也接进来）

# 4. 打 DMG
npm run tauri:build
# 产物在 src-tauri/target/release/bundle/dmg/
# 不签名 — 首次双击会被 Gatekeeper 拦住，右键→打开 一次即可
```

**Mac APP 已知限制（v0.1）**：
- Gateway 仍要单独跑（v0.2 会一并打进 sidecar 或要求外部部署）
- 没有 icon（src-tauri/icons/ 是空的；`npx @tauri-apps/cli icon path/to/source.png` 一键生成）
- 不签名 / 不公证；分发要用户右键打开

---

## 目录

```
agent-client/
├── shared/                    @agent-client/shared — zod 协议 schema
│   └── src/index.ts           ClientMessage / ServerMessage
├── gateway/                   @agent-client/gateway — NestJS 反代中转
│   └── src/
│       ├── main.ts            Express 鉴权 + 反代装配
│       ├── app.module.ts      只挂 /health
│       ├── health.controller.ts
│       └── providers/
│           ├── provider.interface.ts  AiProvider 抽象
│           └── anthropic.provider.ts  唯一实现
├── service/                   @agent-client/service — 本地 Agent SDK 执行层
│   └── src/
│       ├── main.ts            WebSocket server
│       ├── agent.ts           Agent SDK query() → ServerMessage 映射
│       └── cli.ts             CLI 冒烟测试
├── frontend/                  @agent-client/frontend — Next.js 16 + React 19
│   ├── src/
│   │   ├── app/page.tsx       主页（client component）
│   │   ├── components/
│   │   │   ├── turn-view.tsx
│   │   │   ├── tool-call-card.tsx
│   │   │   ├── markdown-text.tsx
│   │   │   ├── composer.tsx
│   │   │   └── connection-status.tsx
│   │   └── lib/
│   │       ├── ws-client.ts   带 reconnect 的 WS 封装
│   │       └── chat-store.ts  useReducer chat state
│   └── src-tauri/             Tauri 2.x 桌面壳
│       ├── tauri.conf.json
│       ├── Cargo.toml
│       ├── capabilities/default.json
│       └── src/
│           ├── main.rs
│           └── lib.rs         Sidecar spawn + service_port 命令
└── scripts/
    ├── setup.sh               一次性安装
    ├── dev-all.sh             启 gateway + service + frontend
    ├── build-sidecar.sh       打 service 包给 Tauri 用
    └── verify-gateway.sh      Gateway 4-case smoke test
```

---

## 环境变量

### gateway/.env.local

| 变量 | 必填 | 默认 | 说明 |
|---|---|---|---|
| `PORT` |  | 4290 | Gateway 监听端口 |
| `UPSTREAM_BASE_URL` |  | `https://api.anthropic.com` | 上游 |
| `UPSTREAM_API_KEY` | ✅ |  | 真实 Anthropic dev key (`sk-ant-api03-...`) |
| `GATEWAY_TOKEN` | ✅ |  | 客户端鉴权 token，service/frontend 同值 |

### service/.env.local

| 变量 | 必填 | 默认 | 说明 |
|---|---|---|---|
| `WS_PORT` |  | 4291 | WebSocket 监听 |
| `ANTHROPIC_BASE_URL` | ✅ | `http://localhost:4290` | 指向 gateway |
| `ANTHROPIC_API_KEY` | ✅ | (=`GATEWAY_TOKEN`) | SDK 用来当 `x-api-key`，gateway 收到后置换为真实 upstream key |
| `GATEWAY_TOKEN` | ✅ |  | 同 gateway 一致 |
| `SETTING_SOURCES` |  | `project` | Agent SDK 加载的 settings 范围 |
| `MODEL` |  | `claude-sonnet-4-6` | 模型别名 |

### frontend/.env.local

| 变量 | 必填 | 默认 | 说明 |
|---|---|---|---|
| `NEXT_PUBLIC_WS_URL` |  | `ws://localhost:4291` | 浏览器连 service |
| `NEXT_PUBLIC_SERVICE_TOKEN` | ✅ |  | 同 GATEWAY_TOKEN |

---

## v0.1 范围（明确不做）

- ❌ Session 持久化（每次刷新清空）
- ❌ 多 session
- ❌ 权限弹窗 UI（危险工具走 Agent SDK 默认权限模式 + 我们用 `tools: claude_code preset`，工具直接执行）
- ❌ Diff 可视化（tool_use 卡只折叠 JSON）
- ❌ FFOA 登录集成（用静态 token）
- ❌ Skills / hooks / MCP 自定义（用 SDK 默认）
- ❌ Provider 切换（接口预留，只接 Anthropic）
- ❌ Tauri Mac APP 内置 Gateway（要单独跑）
- ❌ 签名 / 公证（不签名分发）
- ❌ 自动更新

详细 roadmap 见 `docs/proposals/agent-sdk-desktop-wrapper.md`。

---

## 故障排查

### 浏览器看到"连接错误"
- service 跑了吗？`curl http://localhost:4291` 应该报 426（升级协议）
- 端口冲突？slot-4 主项目占了 4200/4201/4202/4203，agent-client 占 4290/4291/4292，应不冲突
- token 三处一致吗？打开浏览器 DevTools → Network → WS 看握手日志

### Gateway 502 / "AI 服务暂时不可用"
- `bash scripts/verify-gateway.sh` 跑 4-case 自检
- 看 gateway 日志 `proxy error: ...`
- slot-4 在海外，理论上能直连 api.anthropic.com；本机不在海外时 demo 跑不通

### Tauri sidecar 启不来 / "service bundle not found"
- `bash scripts/build-sidecar.sh` 跑过吗？
- 检查 `frontend/src-tauri/resources/service/index.js` 存在
- 用户机器有 Node 18+ 吗？sidecar 是 JS 调 system Node

### Anthropic SDK 报 platform-specific binary missing
- ncc bundle 是按 build 机的平台打的；Mac APP 必须在 macOS 上 `bash scripts/build-sidecar.sh`
- 不要把 Linux 上 build 的 bundle 拷到 Mac

---

## 开发 / Debug 速查

```bash
# 单独启 gateway 开 debug 日志
cd gateway && set -a && source .env.local && set +a && LOG_LEVEL=debug npm run dev

# 用 wscat 直连 service（绕过 frontend）
npx -y wscat -c "ws://localhost:4291?token=demo-token-change-me"
> {"type":"user_prompt","id":"t1","prompt":"列出当前目录"}

# 看 service 实际发给 gateway 的请求（gateway 日志在 debug 模式打 → 行）
# 看 gateway 实际发给 Anthropic 的（可以 tcpdump 或在 gateway 加临时日志）
```
