# API 契约(fmode 平台云函数 + REST 端点) | 字段 | 值 | |---|---| | 版本 | v1.0 | | 最后更新日期 | 2026-10-14 | | 状态 | **生效(编码基线)**;标注 `❓` 者为推断,需服务端核对 | | 本次变更原因 | 初次创建。**补齐 CLAUDE.md「待确认项」指出的缺口**:云函数契约此前散见于存量 README 与调用点,**无集中文档** | | 事实来源 | 存量项目 `E:\work\fmode-app` 调用点逆向(子代理只读盘点)+ 存量 `docs/03-interface/hermes-agent-data-sources.md` | | 核对状态 | 请求参数与返回字段名 **已由 TS interface + 调用处校验双重确认**(标 ✅);仅少数语义为推断(标 ❓) | > **前端消费方式**:一律经 **`core/api/` 抽象层**;业务组件**禁止**直接调用 `FmodeApi` 或其实现。 > 抽象层定义见《数据访问层设计.md》。 --- ## 一、调用协议(✅ 确定) ### 1.1 云函数统一入口 ``` POST https://server.fmode.cn/api/functions Headers: Content-Type: application/json X-Parse-Application-Id: ncloudmaster X-Parse-Session-Token: # 有则带 Body: { "id": "<云函数 objectId>", "token": "", "params": { ... } } ``` **错误处理**:HTTP 200 但 `body.code` 为 `401` / `403` 时视为业务错误,须抛出。 错误文案取值顺序:`msg` → `mess` → `message`。 ### 1.2 常量 | 常量 | 值 | |---|---| | `PARSE_BASE` | `https://server.fmode.cn/parse` | | `FUNCTION_API` | `https://server.fmode.cn/api/functions` | | `PARSE_APP_ID` | `ncloudmaster` | | `LOGIN_COMPANY` | `E4KpGvTEto` | | OBS 公网前缀 | `https://s3.fmode.cn/` | | Matrix homeserver | `https://matrix.fmode.cn`,serverName `fmode.cn` | --- ## 二、云函数契约 ### 2.1 `xlosi3m46q` — 数字生命状态(AgentStatus)✅ **调用方**:实况服务(唯一轮询器) | 操作 | `params` | 返回 | |---|---|---| | 列表查询 | `{ op: 'query', scope: 'all' \| 'company' \| 'mine' }` | `{ code: 200, agents: AgentStatusItem[] }` | | 单条查询 | `{ op: 'query', scope, agentId }` | `{ code: 200, agent: AgentStatusItem }`;`code` 404/403 → 返回 `null` | | 写入性别 | `{ agentId, field: 'gender', value: 'male' \| 'female' }` | `{ code: 200 }` | > ⚠️ **`scope` 降级链**:存量按 `all → company → mine` 逐级降级取数。 > v2 处置:**降级链保留在服务端**,前端只按自身 ACL 期望值请求一次;失败即真实失败(不静默降级), > 避免「权限不足被伪装成空列表」(FR-20 AC-20.3)。 #### `AgentStatusItem`(✅ 除标注外) | 字段 | 类型 | 说明 | |---|---|---| | `objectId` / `agentId` | string | 实体 id | | `name` | string | 数字生命名称 | | `botName` / `botAvatar` | string | 展示名 / 头像 | | `gender` ❓ | `'male' \| 'female' \| 'unknown'` | 取值域推断 | | `status` | `'running' \| 'idle' \| 'error' \| 'offline' \| 'stopped'` | ✅ 枚举确定 | | `currentTask` | string | 当前任务(单值) | | `currentAction` | string | 当前动作描述 | | `progress` ❓ | number | 语义推断:整数=百分比,`0 🔴 **注意**:`host` / `channels` / `balance` / `usage` 均为**字符串形态的 JSON**, > 需兼容「已是对象」与「是字符串」两种形态后解析(存量 `parseJsonLoose` 的教训)。 ```ts // host(JSON 字符串) { cpuPercent?: number; memPercent?: number; diskPercent?: number } // channels(JSON 字符串) { wecom?: boolean; wecomBotId?: string; matrix?: boolean; homeserver?: string; matrixConfig?: { userId?: string; homeserver?: string; serverName?: string }; wecomConfig?: { botId?: string; corpName?: string } } // balance(JSON 字符串;兼容旧键 remainRmb) { remainCny?: number; remainRmb?: number; pageUrl?: string } ``` > 🔴 **死字段(禁止消费)**:`tasks[]` / `runningTasks[]` / `currentTasks[]` —— > 上报链路**从未实现**,永远为空。v2 任务数据改由 `AgentTask` 域承载(FR-07b)。 ### 2.2 `lfYlgU7SkK` — 交付成果 ✅ | 操作 | `params` | 返回 | |---|---|---| | 列表 | `{ action: 'list', agentId?, limit? }` | `{ code, count, deliverables: DeliverableItem[] }` | | 按项目分组 | `{ action: 'byProject', limit? }` | `{ code, groups: DeliverableGroup[] }` | | 上报(写) | `{ action: 'report', ... }` | 服务端存在,**v2 不调用** | **各调用点的 limit 基线(✅ 确定)**:成果页 200 / 项目页 200 / 事件派生 200 / 任务附件 12 / 详情状态面板 30 ```ts interface DeliverableItem { id?: string; objectId?: string; agentId: string; title: string; summary?: string; project?: string; projectName?: string; artifacts: { name: string; url: string; type: string }[]; tags?: string[]; deliveredAt: string; status?: string; // ❓ 取值:`delivered`=完成;含 `fail`/`error` 子串=失败 extra?: unknown; } interface DeliverableGroup { project?: string; projectName: string; count: number; items: DeliverableItem[]; } ``` ### 2.3 `RP5Ht55NeX` — Matrix 身份开通(provision)✅ | 操作 | `params` | 返回 | |---|---|---| | 开通 | `{ agentId }` 或 `{}` | `MatrixProvisionResult` | | 校验/刷新 | `{ agentId?, validate: 1 }` | 同上 | ```ts interface MatrixProvisionResult { code: number; cached?: boolean; // v3 P1:绑定表命中 = 幂等秒进 bindingId?: string; status?: string; userMxId: string | null; agentMxId: string | null; homeserver: string; accessToken: string; deviceId: string; roomId: string | null; userCreated?: boolean; } ``` **校验规则**:必须 `code === 200 && accessToken && userMxId`,否则抛错。 **消费流程**:本地绑定缓存优先 → 静默 `whoami` 探活 → 失效才 `validate`(避免每次进会话都开通)。 ### 2.4 `zl4yU8PAEB` — Token 同步 ✅(承载 FR-02 AC-02.4) | 操作 | `params` | 返回 | |---|---|---| | 回写 | `{ agentId, token }` | `{ code, mess? }` | **用途**:登录后把 SessionToken 回写到数字生命容器(`?reauth=1&agentId=` 自愈流程);失败**不阻塞**跳转。 > 🔴 **改造要求(FR-02 AC-02.4 / NFR-04)**:该函数应同时作为 **租户归属的权威判定方**。 > v2 处置:前端**只读**服务端返回的 `company`,**禁止** `PUT /users/` 自改(存量反模式)。 > **未就绪前的降级**:前端按服务端返回的 `company` 使用;若服务端仍返回旧值,前端不做纠正。 ### 2.5 `AlP56LCKFm` — 上传预签名 ✅ | 操作 | `params` | 返回 | |---|---|---| | 取签名 | `{ filename, mimeType, size }` | `{ code, uploadUrl, key, publicUrl, headers? }` | **上传链路**(✅ 确定): ``` 选文件 → uploadUrl(filename, mimeType, size) [云函数 AlP56LCKFm] → fetch(uploadUrl, { method:'PUT', headers, body:file }) [OBS 直传,前端不经手密钥] → 得 { key, publicUrl } → Matrix 发文件卡:m.text,body = JSON.stringify({ kind:'file', name, size, mimeType, url }) ``` **默认 headers 兜底**:`{ 'Content-Type': mimeType, 'x-obs-acl': 'public-read' }` **缺 `uploadUrl` 即抛错**(不得静默降级为本地 blob)。 ### 2.6 kanban 任务域(FR-07b)— ⬜ **接收端未实现** | 项 | 状态 | |---|---| | 数据源 | 容器 `/opt/data/kanban.db`(SQLite 只读)→ 采集脚本 `kanban-report.py` → 云函数 → Parse | | 表 | `AgentTask` / `AgentTaskRun` / `AgentLiveEvent` **已建**;**`AgentTaskComment` / `AgentTaskAttachment` 待建** | | 云函数接收端 | ⬜ **未实现** | | v2 处置 | 落**接口层 + 空态**,**禁止伪造数据**;字段形状按《ER-图-草案.md》§二.B,以 `AgentTask` 为真源 | **待核实的 6 组(见 `docs/03-interface/待问Agent-20261014-多任务接入核实.md`)**:`assignee` 取值域、 `block_kind` 枚举、`priority` 语义、`branchName` 来源、`projectId` 是否落库、`startedAt` 等时间字段口径。 --- ## 三、REST 端点(非云函数;登录与旧兼容路径) | # | 端点 | 用途 | 关键点 | |---|---|---|---| | R1 | `POST /parse/users/me` | Token 直登校验 + 取身份 | 头 `X-Parse-Session-Token` | | R2 | `GET /parse/roles?where={users:Pointer<_User>}&limit=50` | 补 `roles` 关系 | 🔴 `_User` JSON **不透出 relation**,必须单独查(存量 v0.3.3 著名坑:超管因缺 roles 被误判为 company-member → 列表空) | | R3 | `PUT /parse/users/` | 校正 `company` 指针 | ❌ **v2 禁止调用**(改为服务端权威,见 §2.4) | | R4 | `POST /api/apig/message` `{mobile, company}` | 发短信验证码 | 🔴 **`code === 1` 才算成功**(不是 200) | | R5 | `GET /api/fmode/mobile?mobile&code&company` | 验证码登录 | 🔴 `code === 200 && data.token` 才算成功;`data.newapiToken` 另存备用 | | R6 | `GET /api/fmodeagent/status` | 旧 REST 状态(降级路径) | v2 **不采用**(走云函数 `xlosi3m46q`) | | R7 | `GET /parse/classes/FmodeAgent?limit=200&include=user,company` | 旧直查兜底 | v2 **不采用** | ### 3.1 Token 形态(三套,别混) | 形态 | 用途 | 传递方式 | |---|---|---| | `r:` 前缀的 Fmode SessionToken | 业务会话令牌 | 登录页强校验 `r:` 前缀;存 `localStorage` | | Parse SessionToken | Parse REST / 云函数鉴权 | `X-Parse-Session-Token` 头 + body 的 `token` | | Matrix `Bearer ` | Matrix 客户端 | 由 `RP5Ht55NeX` 签发,**仅存内存 + localStorage**,不入代码 | --- ## 四、Parse 数据表 | 类 | 字段 | 说明 | |---|---|---| | `_User` | `objectId` `username` `nickname` `mobile` `company(Pointer)` `roles(relation)` `newapiToken` | `roles` 需单独查(R2) | | `_Role` | `name` | 命名约定 `_Admin`;超管 = `masters` / `FMODE_AGENT_SUPERADMIN` | | `Company` | `objectId` `name` | 登录公司常量 `E4KpGvTEto` | | `FmodeAgent` | 同 `AgentStatusItem` | 主路径**不直查**,走云函数 | | `Deliverable` | — | ❌ **代码中不存在**;成果全部走云函数 `lfYlgU7SkK` | | `AgentTask` / `AgentTaskRun` / `AgentLiveEvent` | 见 ER 草案 | 表已建;**前端一期不消费 `AgentLiveEvent`** | | `AgentTaskComment` / `AgentTaskAttachment` | 见 ER 草案 | ⬜ **待建** | ### 4.1 软删与租户隔离 — 实际情况说明 | 项 | 实情 | |---|---| | `isDeleted` | 🔴 **存量前端代码中 0 处出现**(全量 grep 证实)。过滤**完全依赖服务端**云函数与 CLP/ACL | | `company` 指针 | 存在于表结构;前端写路径仅 `PUT /users/`(v2 已禁止) | | 写操作总数 | **仅 2 处**:R3(company,已禁)与 `xlosi3m46q` 的 `gender` 写入 | > **v2 处置**:前端**不做**软删过滤与租户过滤(那是服务端 CLP 职责),但**必须**在类型与文档上标注该字段存在, > 且**不得新增**前端直写路径。黄金规则 6 的"业务表必须带 `company`"由服务端 Schema 保证。 --- ## 五、ACL 判定(服务端为准,前端只做展示) 判级顺序(✅ 确定): ``` roles 含 'masters' | 'FMODE_AGENT_SUPERADMIN' → superadmin username === 'FMODE_AGENT_SUPERADMIN' → superadmin company && roles 含 '_Admin' → company-admin 有 company → company-member 否则 → personal ``` 🔴 **FR-20 AC-20.2 硬约束**:前端代码中**无**基于 `roles` 字段的**授权分支**(判据:grep 命中 0)。 `roles` **只**用于:① 决定请求哪个 `scope`(期望值)② 界面提示。 **可见范围判定一律由服务端返回**;越权应返回 **403 语义**,不得返回空列表。 --- ## 六、待服务端核对清单 | # | 事项 | 影响 | 优先级 | |---|---|---|---| | 1 | `zl4yU8PAEB` 是否已改造为返回权威 `company` | FR-02 AC-02.4 / NFR-04 | 🔴 高 | | 2 | kanban 云函数接收端是否实现 | FR-07b 全模块 | 🔴 高 | | 3 | `AgentTaskComment` / `AgentTaskAttachment` 两表是否建 | FR-07b AC-07b.2 | 🔴 高 | | 4 | `xlosi3m46q` 的 `balance` / `usage` 字段名与结构 | FR-07 AC-07.1/07.2 | 🟡 中 | | 5 | `progress` 数值语义(百分比 or 比例) | 工作台进度条 | 🟡 中 | | 6 | 成果 `status` 取值域 | 成果状态徽标 | 🟡 中 | | 7 | 越权返回码是否确为 403(而非 200 + 空) | FR-20 AC-20.3 | 🟡 中 | | 8 | `gender` 取值域与是否需要保留该写操作 | 会话内性别询问流程 | ⬜ 低(建议 v2 移除) | --- ## 七、关联 - 《数据访问层设计.md》(抽象层与演示数据口径) - 《ER-图-草案.md》(实体关系) - 《待问Agent-20261014-多任务接入核实.md》(kanban 6 组未知) - 存量调用点:`E:\work\fmode-app\src\app\core\parse-client.ts`(只读参考) - SPEC:FR-02 · FR-04 · FR-05 · FR-07 · FR-07b · FR-08 · FR-20 · NFR-03 · NFR-04