API-契约.md 13 KB

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: <sessionToken>      # 有则带
Body:
  { "id": "<云函数 objectId>", "token": "<sessionToken>", "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/<key>
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<x<1=小数比例
heartbeatAt / updatedAt string(ISO) 心跳 / 更新时间
host string(JSON) 见下方结构
channels string(JSON) 见下方结构
balance string(JSON) 见下方结构
usage / usageSessions string(JSON) ❓ token 用量
user Pointer(_User) 归属用户(含 nickname/username/mobile/avatar)
company Pointer(Company) 租户

🔴 注意:host / channels / balance / usage 均为字符串形态的 JSON, 需兼容「已是对象」与「是字符串」两种形态后解析(存量 parseJsonLoose 的教训)。

// 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

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 } 同上
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/<id> 自改(存量反模式)。 未就绪前的降级:前端按服务端返回的 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/<id> 校正 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 <accessToken> Matrix 客户端 由 RP5Ht55NeX 签发,仅存内存 + localStorage,不入代码

四、Parse 数据表

类 字段 说明
_User objectId username nickname mobile company(Pointer) roles(relation) newapiToken roles 需单独查(R2)
_Role name 命名约定 <companyId>_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/<id>(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 含 '<companyId>_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