feat: prepare Zhinian desktop client for pilot release
This commit is contained in:
243
docs/SERVER_CONTRACT_V0.md
Normal file
243
docs/SERVER_CONTRACT_V0.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# YINIAN Server Contract v0
|
||||
|
||||
> Updated: 2026-04-26
|
||||
> Status: Draft contract for M2 integration
|
||||
> Client switch: set `YINIAN_API_BASE_URL` to enable HTTP mode. Without it, desktop uses mock mode.
|
||||
|
||||
## 1. Transport
|
||||
|
||||
- Base URL: `YINIAN_API_BASE_URL`
|
||||
- Content type: `application/json`
|
||||
- Auth header: `Authorization: Bearer <accessToken>`
|
||||
- Desktop header: `X-YINIAN-App-Version: 0.1.0`
|
||||
- Timestamps: Unix milliseconds.
|
||||
- Field casing: server should prefer `camelCase`; desktop accepts key `snake_case` aliases for M1/M2 migration.
|
||||
|
||||
## 2. Error Shape
|
||||
|
||||
Non-2xx responses should return:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "SESSION_EXPIRED",
|
||||
"message": "Session expired"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The desktop currently surfaces `error.message` and falls back to `YINIAN API request failed: <status>`.
|
||||
|
||||
Recommended error codes:
|
||||
|
||||
- `INVALID_CREDENTIALS`
|
||||
- `INVALID_SMS_CODE`
|
||||
- `SESSION_EXPIRED`
|
||||
- `WORKSPACE_FORBIDDEN`
|
||||
- `CONFIG_UNAVAILABLE`
|
||||
- `SKILLS_MANIFEST_UNAVAILABLE`
|
||||
|
||||
## 3. Device Payload
|
||||
|
||||
Login requests include:
|
||||
|
||||
```json
|
||||
{
|
||||
"device": {
|
||||
"device_id": "dev_local_electron",
|
||||
"platform": "darwin",
|
||||
"app_version": "0.1.0",
|
||||
"machine_name": "YINIAN Desktop"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The exact `device_id` is a placeholder in M2 and should be replaced by a stable desktop installation id later.
|
||||
|
||||
## 4. Auth
|
||||
|
||||
### POST `/auth/login/sms`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"phone": "13800000000",
|
||||
"code": "123456",
|
||||
"device": {}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "access_demo",
|
||||
"refreshToken": "refresh_demo",
|
||||
"accessTokenExpiresAt": 1777188600000
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/auth/login/password`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"account": "ops@example.com",
|
||||
"password": "secret",
|
||||
"device": {}
|
||||
}
|
||||
```
|
||||
|
||||
Response shape is the same as SMS login.
|
||||
|
||||
### POST `/auth/refresh`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"refreshToken": "refresh_demo",
|
||||
"device": {}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "access_refreshed",
|
||||
"refreshToken": "refresh_rotated",
|
||||
"accessTokenExpiresAt": 1777189500000
|
||||
}
|
||||
```
|
||||
|
||||
M2 desktop persists only `refreshToken`; `accessToken` remains memory-only.
|
||||
|
||||
### GET `/auth/me`
|
||||
|
||||
Headers:
|
||||
|
||||
```http
|
||||
Authorization: Bearer access_demo
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": "user_ops_001",
|
||||
"name": "王管理员",
|
||||
"phone": "13800000000",
|
||||
"email": "ops@example.com",
|
||||
"avatar": "https://cdn.example.com/avatar.png"
|
||||
},
|
||||
"hotels": [
|
||||
{
|
||||
"id": "workspace_hangzhou_ops",
|
||||
"name": "智念企业组织空间",
|
||||
"brand": "智念"
|
||||
}
|
||||
],
|
||||
"currentHotelId": "workspace_hangzhou_ops",
|
||||
"accessTokenExpiresAt": 1777188600000
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Config Sync
|
||||
|
||||
### GET `/config/sync?hotel_id=<hotelId>`
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"serverTime": 1777188000000,
|
||||
"hotel": {
|
||||
"id": "workspace_hangzhou_ops",
|
||||
"name": "智念企业组织空间",
|
||||
"brand": "智念"
|
||||
},
|
||||
"entitlements": [
|
||||
{
|
||||
"skillId": "daily-report",
|
||||
"name": "日报生成助手",
|
||||
"version": "1.0.0",
|
||||
"enabled": true,
|
||||
"category": "reporting",
|
||||
"triggers": ["scheduled", "manual"],
|
||||
"lastRunAt": 1777184400000
|
||||
}
|
||||
],
|
||||
"notificationChannels": [
|
||||
{
|
||||
"id": "wechat_ops",
|
||||
"kind": "wecom",
|
||||
"label": "业务通知群",
|
||||
"recipient": "room_001",
|
||||
"enabled": true,
|
||||
"source": "nianxx"
|
||||
}
|
||||
],
|
||||
"featureFlags": {
|
||||
"skillsSync": true,
|
||||
"advancedSettings": false
|
||||
},
|
||||
"uiPolicy": {
|
||||
"defaultPage": "today",
|
||||
"showAdvancedSettings": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Skills Manifest
|
||||
|
||||
### GET `/skills/manifest?hotel_id=<hotelId>`
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"serverTime": 1777188000000,
|
||||
"hotelId": "workspace_hangzhou_ops",
|
||||
"manifestVersion": "2026.04.26.1",
|
||||
"skills": [
|
||||
{
|
||||
"skillId": "data-check",
|
||||
"name": "数据检查助手",
|
||||
"version": "1.2.0",
|
||||
"enabled": true,
|
||||
"bundleSha256": "sha256-demo",
|
||||
"bundleUrl": "https://cdn.example.com/skills/data-check-1.2.0.tgz"
|
||||
},
|
||||
{
|
||||
"skillId": "customer-reply-helper",
|
||||
"name": "客户回复助手",
|
||||
"version": "0.9.0",
|
||||
"enabled": false,
|
||||
"bundleSha256": "sha256-disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
M2 desktop consumes manifest metadata only. It does not download `bundleUrl`, verify signatures, or unpack bundles yet.
|
||||
|
||||
## 7. Client Normalization Rules
|
||||
|
||||
The desktop accepts:
|
||||
|
||||
- `camelCase` and `snake_case` aliases for known fields.
|
||||
- Missing `user.name` as `未命名用户`.
|
||||
- Missing hotel fields with safe empty/default values.
|
||||
- Unknown skill categories as `ops-automation`.
|
||||
- Unknown trigger values are ignored.
|
||||
- Missing `uiPolicy.defaultPage` defaults to `today`.
|
||||
- Missing arrays default to `[]`.
|
||||
|
||||
Malformed server responses should not break the shell, but required auth fields still fail fast:
|
||||
|
||||
- Missing `accessToken` after login or refresh throws `服务端未返回 access token`.
|
||||
- Authenticated config/skills calls without a session throw `请先登录`.
|
||||
Reference in New Issue
Block a user