Files
penguin-harness/packages/docs/content/server-api.zh.md
T
Yaowei Zheng 45bfae6e94 Initialize repository with harness code and assets
Initial import of all source code, config, and README assets: the
packages workspace (cli, core, server, web, docs, landing, skills),
build scripts, tooling config, and CI workflows.

Includes the data-layout revision made on this branch: the local data
root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the
installer keeps its binaries in ~/.penguin), and every Agent lives
under <project>/agents/<agent>/ — path helpers, the three
agent-enumeration scans, the system prompt, built-in Skills, tests
and docs all follow the new layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
2026-07-19 14:06:53 +08:00

237 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Server API
description: HTTP API 参考:认证机制、路由列表、SSE 流式协议与 DTO 类型导入。
---
PenguinHarness Server 提供一套同源 HTTP API,自带的 Web App 与其他 HTTP 客户端都通过它访问。本文是接口参考:认证机制、路由列表与 SSE 流式协议。服务启动方式见[快速开始](/quickstart)。
## 总览
- 技术栈:Hono + @hono/node-server,要求 Node >= 24;
- 存储:SQLite(内置 `node:sqlite`,WAL 模式)仅存放索引与聚合数据——用户、登录会话、Project 授权、Agent / Session 索引、用量、UI 偏好、错误记录与 Schedule 状态;Agent、Trace 与 Workspace 数据全部以文件形式存放在 `~/.penguin/data` 下,与 CLI / SDK 共享,见[配置参考](/configuration);
- 监听:默认 `127.0.0.1:7364`,可用环境变量 `PORT` / `HOST` 调整;
- 请求体:写请求仅接受 JSON(Content-Type 校验,CSRF 防线之一),上限 20MB;
- 错误响应统一为:
```text
{ "error": { "code": "<机器可读错误码>", "message": "<提示文案>" } }
```
## 目录结构
```text
packages/server/src
├── index.ts / config.ts / app.ts # 启动入口 · 环境变量配置 · Hono 组装(createApp 不绑端口,便于测试)
├── api/types.ts # 对外 DTO 契约(经 "./api" 子路径供前端 type-only 引用)
├── auth/ # scrypt 密码、admin 种子、cookie 会话、认证中间件
├── db/ # node:sqlite 连接、建表 SQL、每表一个 repo
├── http/ # 错误体、请求校验、SSE 适配、routes/ 全部路由
├── runtime/ # session-manager(运行时驱动)· channel(SSE 环形缓冲)
│ # approvals · usage-recorder · scheduler · title-generator
└── services/ # 授权规则、TOML/YAML 配置读写、Session/Trace/用量/快照服务
```
## 认证
- Cookie 会话:`penguin_session`(HttpOnly、SameSite=Lax),有效期 7 天,滑动续期;
- 密码以 scrypt 哈希存储;服务端只保存会话 Token 的 sha256,不落明文;
- 不开放注册:启动时种子化内置管理员 `admin` / `admin123`,其余账号由管理员创建;
- 仅限同源访问,未启用 CORS 中间件。
```bash
curl -c cookies.txt -H "Content-Type: application/json" \
-d '{"userId":"admin","password":"admin123"}' \
http://127.0.0.1:7364/api/auth/login
```
## 路由参考
### 认证与账户
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | /api/auth/login | 登录:`{userId, password}` → `{user}` |
| POST | /api/auth/logout | 退出登录,返回 204 |
| GET | /api/me | 当前用户信息 |
| PUT | /api/me/password | 修改密码:`{oldPassword, newPassword}` |
| GET | /api/me/prefs | 读取 UI 偏好 |
| PUT | /api/me/prefs | 写入 UI 偏好(浅合并) |
### 用户管理(仅管理员)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /api/admin/users | 用户列表 |
| POST | /api/admin/users | 创建用户:`{userId, password}` |
| POST | /api/admin/users/:userId/password | 重置密码(该用户全部登录会话失效) |
| DELETE | /api/admin/users/:userId | 删除用户 |
### Project 与成员
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /api/projects | 当前用户可见的 Project 列表 |
| POST | /api/projects | 创建 Project |
| DELETE | /api/projects/:projectId | 删除 Project |
| GET | /api/projects/:projectId/members | 成员列表 |
| POST | /api/projects/:projectId/members | 添加成员:`{userId}` |
| DELETE | /api/projects/:projectId/members/:userId | 移除成员 |
成员写操作仅限 Owner。
### 模型
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /api/projects/:projectId/models | 模型列表(api_key 掩码显示) |
| PUT | /api/projects/:projectId/models | 全表替换,条目以 `(provider, modelId)` 为键 |
| POST | /api/projects/:projectId/models/test | 连通性测试:`{provider, modelId, …}` → `{ok, latencyMs?, message?}` |
### Agent
以下路径均省略前缀 `/api/projects/:projectId`。
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET / POST | /agents | Agent 列表 / 创建 |
| DELETE | /agents/:agentId | 删除 Agent |
| GET / PUT | /agents/:agentId/config | 读写配置(AGENTS.md + system_config.yaml,PUT 保留 YAML 注释) |
| GET / PUT | /agents/:agentId/vault | Vault 环境变量(值掩码显示;PUT 全表替换) |
| GET | /agents/:agentId/export | 导出 Agent State 快照(tar.gz 下载) |
| POST | /agents/:agentId/import | 导入快照:`{dataBase64, confirm?}`;版本冲突且未确认时返回 409 |
| GET / POST | /agents/:agentId/skills | 已安装 Skill 列表 / 安装 |
| DELETE | /agents/:agentId/skills/:name | 卸载 Skill |
| GET | /agents/:agentId/benchmarks | Benchmark 评分数据(只读) |
### Schedule
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET / POST | /agents/:agentId/schedules | 定时任务列表 / 创建(重名返回 409) |
| GET / PUT / DELETE | /agents/:agentId/schedules/:name | 读取 / 更新 / 删除单个任务 |
Schedule 写操作仅限 Owner。
### Session 创建与目录浏览
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /agents/:agentId/sessions | Session 列表(含运行状态) |
| POST | /agents/:agentId/sessions | 创建 Session:`{modelId?, provider?, workspace?, approvalMode?}` → 201 |
| GET | /dirs?path= | 服务器端目录浏览(Workspace 选择器数据源) |
创建 Session 时,模型默认取 Project 默认模型,Workspace 默认自动创建临时目录,审批模式默认 `allow-all`。
### 用量与 Trace(Agent 级)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /usage | 用量统计,查询参数 `from`、`to`、`groupBy`、`agentId`、`provider`、`modelId` |
| GET | /agents/:agentId/traces | Trace 文件的日期 → Session 下钻结构 |
| GET | /agents/:agentId/traces/:sessionId/:index | 读取 Trace 事件(`offset` / `limit` 分页) |
| GET | /agents/:agentId/traces/:sessionId/:index/analysis | Trace 性能分析结果 |
### Session 级接口
以下路径均省略前缀 `/api/sessions/:sessionId`。Trace 与 Session 的存储模型见 [Session 与 Trace](/sessions-and-traces)。
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | / | Session 信息 |
| PATCH | / | 更新:`{approvalMode?, archived?, title?}` |
| DELETE | / | 删除 Session(连同 Trace 与暂存文件) |
| GET | /messages | 完整 OmniMessage 历史 |
| GET | /stream | SSE 事件流(见下节) |
| POST | /tasks | 发起 Task:`{input: TaskInputPart[]}` → 202 |
| POST | /approvals/:toolCallId | 审批决定:`{decision}` 取 `allow` 或 `deny` → 204 |
| POST | /abort | 中断当前 Task:已触发返回 202,无任务返回 204 |
| POST | /compact | 触发上下文压缩:202;无可压缩内容返回 409 `nothing_to_compact` |
| GET | /files?path= | 浏览 Workspace 目录 |
| GET | /files/content?path=&download= | 读取 Workspace 文件(`download=1` 时作为附件下载) |
| POST | /files/stat | 批量存在性检查:`{paths}` |
| PUT | /files/content?path= | 上传文件:`{dataBase64}`,上限 14MB |
| GET | /traces | 本 Session 的 Trace 文件列表 |
| GET | /traces/:index | 读取 Trace 事件(分页) |
| GET | /traces/:index/analysis | Trace 性能分析结果 |
| GET | /scratchpad/:fileName | 读取会话暂存文件(如输入图片) |
通用约定:无权访问的 Session 一律返回 404,不泄露其存在性;每个 Session 同时只允许一个 Task 或压缩在运行,冲突时返回 409(`task_in_progress` / `compacting`)。
关键请求体(明确键名):
```ts
// POST /api/sessions/:sessionId/tasks —— 发起一个 Task
interface TaskCreateRequest {
input: TaskInputPart[];
}
type TaskInputPart =
| { type: "text"; text: string }
| { type: "image_url"; imageUrl: string }; // 粘贴图片以 data URL 上送
// POST /api/sessions/:sessionId/approvals/:toolCallId
interface ApprovalDecisionRequest {
decision: "allow" | "deny";
}
```
## 流式接口(SSE)
实时通道采用 Server-Sent Events 而非 WebSocket,共两条(通道内承载的消息顺序语义见[消息流转与时序](/message-flow)):
| 通道 | 路径 | 内容 |
| --- | --- | --- |
| Session 级 | GET /api/sessions/:sessionId/stream | 该 Session 的消息流与运行事件 |
| 用户级 | GET /api/events | `hello` 握手与跨 Session 通知(schedule_fired / schedule_queued / session_created) |
### 传输格式
默认(未命名)SSE 事件承载原始 OmniMessage 信封(单行 JSON)——与 SDK 产出、Trace 落盘是同一套协议,见 [OmniMessage 协议](/omni-message);命名为 `server_event` 的事件承载 ServerEvent 联合类型:
```ts
export type ServerEvent =
| { type: "approval_request"; toolCall: OmniMessage<ToolCallPayload>; origin?: string[] }
| { type: "task_state"; state: "idle" | "running" | "compacting" }
| { type: "session_title"; sessionId: string; title: string }
| { type: "resync_required" }
| { type: "hello" }
| { type: "session_created"; projectId: string; agentId: string; sessionId: string; source: SessionSource }
| { type: "schedule_fired"; projectId: string; agentId: string; name: string; sessionId: string }
| { type: "schedule_queued"; projectId: string; agentId: string; name: string; sessionId: string };
```
| 事件 | 触发时机 |
| --- | --- |
| approval_request | 工具调用升级为人工审批时发出:always-ask 下的所有调用,以及 read-only 下 rw / 未知权限的调用;重连时未决审批会重发 |
| task_state | Session 运行状态翻转(idle / running / compacting) |
| session_title | 首轮后模型生成的标题已持久化 |
| resync_required | Last-Event-ID 已被缓冲区淘汰,客户端须重新拉取历史 |
| hello | 用户通道连接握手 |
| session_created | 新 Session 注册(如子 Agent 会话) |
| schedule_fired | 定时任务已触发并发送 |
| schedule_queued | 目标 Session 正在运行,本次触发已排队 |
### 投递保证
- 事件 id 按通道单调递增,形如 `<epoch>-<seq>`;
- 每通道维护有界重放缓冲(最近 1000 条事件或 2MB);
- 携带 `Last-Event-ID` 重连时,命中缓冲则补发缺口;未命中则先发 `resync_required`,客户端重新拉取 `/messages` 后继续消费;
- 每 20 秒写一条心跳注释行;
- 事件次序:带 `Last-Event-ID` 重连时,**补发的缺口(或 `resync_required`)最先送达**,随后才是初始事件——权威的 `task_state` 快照与未决的 approval_request,再进入实时流;全新连接(无 `Last-Event-ID`)不重放缓冲,首个事件即为 `task_state` 快照。
### 推荐客户端模式
自带 Web App 的接入顺序:
1. 先连接 `/stream` 并缓冲收到的事件;
2. 再 GET `/messages` 拉取完整历史;
3. 回放缓冲区并对重叠消息去重;
4. 转入实时消费。
## 类型导入
全部 DTO 类型可从服务端包的子路径 `@prismshadow/penguin-server/api` 以 type-only 方式导入:
```ts
import type { ServerEvent, SessionInfo } from "@prismshadow/penguin-server/api";
```