import { Agent, ArtifactJob, AuthSession, ChatThreadRecord, ChatThreadUpsert, Deliverable, DeliverableGroup, MatrixProvision, Me, Task, UploadTicket, WorkGroup, WorkspaceRecord, WorkspaceUpsert, } from './models'; /** * 数据访问抽象接口 —— 应用层与数据源之间的**唯一闸门** * * 依据《数据访问层设计.md》§一/§四。硬约束: * ① 页面/components **禁止** inject(FmodeApi) 或其实现(FR-14 AC-14.2) * ② 业务服务**禁止**直接 fetch / 直接用 fmode-ng(黄金规则 5) * ③ 每个数据域**只有一个** owner 服务 * * 实现:`ParseApi`(真实,走 fmode-ng)/ `MockApi`(演示数据)。 * 切换由 `environment.useMockData` 单开关控制(api.provider.ts)。 * * ⚠️ `Project` / `EventItem` **不在此接口**:它们是派生投影(由 Agent + Deliverable 组合), * 由对应服务计算,见《数据访问层设计.md》§四.1。 */ export abstract class FmodeApi { // ---------- 鉴权 ---------- /** 发送手机验证码(REST R4:`code === 1` 才算成功) */ abstract sendMobileCode(mobile: string): Promise; /** 验证码登录(REST R5:`code === 200 && data.token` 才算成功) */ abstract loginWithMobile(mobile: string, code: string): Promise; /** Token 直登(REST R1:`POST /parse/users/me`) */ abstract loginWithToken(token: string): Promise; /** 用已有 token 取回身份 */ abstract fetchMe(token: string): Promise; // ---------- 数字生命(实况) ---------- /** 列表查询(云函数 xlosi3m46q,`op:'query'`) */ abstract fetchAgents(): Promise; /** 单条查询;`code` 404/403 时返回 null */ abstract fetchAgentStatus(agentId: string): Promise; /** * 重命名容器(**仅公司管理员 / 系统超管**)。 * * ## 契约(2026-11-04 与后端约定,待实现) * * `POST /api/functions`,沿用 `xlosi3m46q` 的写入形态: * ``` * { id: 'xlosi3m46q', token, params: { op: 'rename', agentId, displayName } } * → { code: 200, agent?: AgentStatusItem } // 成功 * → { code: 403, msg: '...' } // 无权限 * ``` * * ## 为什么单独成方法而不是复用 update * * 重命名是**跨用户的可见变更**(同公司成员都会看到新名字), * 与「改自己的偏好」不是一回事 —— 独立方法便于单独做权限校验与审计。 * * 🔴 服务端未就绪时实现方**必须抛错**,禁止静默改本地(那会制造 * 「我这看着改了、别人看到的还是旧名」的假象)。 */ abstract renameAgent(agentId: string, displayName: string): Promise; // ---------- 会话(Matrix) ---------- /** 开通 Matrix 身份(云函数 RP5Ht55NeX);绑定表命中时 `cached: true` */ abstract provisionMatrix(agentId: string): Promise; /** 校验/刷新凭据(`validate: 1`) */ abstract validateMatrix(agentId: string): Promise; // ---------- 会话目录(云端持久化) ---------- /** * 拉取当前用户的会话目录(云函数 `fmodeagent-chatthreads`,`op:'list'`)。 * * 🔴 云端是会话目录的**唯一真相源**;本地 localStorage 只作渲染缓存。 * 服务端按 sessionToken 强制归属当前用户,本方法不传 user 参数。 */ abstract fetchChatThreads(agentId: string, opts?: { includeDeleted?: boolean }): Promise; /** 批量上报会话(新建或补计数/时间;标题仅在为空时写入) */ abstract upsertChatThreads(items: ChatThreadUpsert[]): Promise; /** 修改标题(置 titleLocked)或归档状态 */ abstract patchChatThread(input: { threadRoot: string; title?: string; archived?: boolean; /** 归属工作区;`''` = 移回日常对话(2026-11-01 起上云) */ workspaceId?: string; }): Promise; /** 软删会话(isDeleted=true;再次收到消息可 revive) */ /** 删除会话索引(云端物理删除 + 留墓碑阻止重建);Matrix 消息不受影响 */ abstract removeChatThread(input: { threadRoot: string; roomId?: string; agentId?: string }): Promise; // ---------- 工作区(2026-11-01 上云) ---------- /** * 拉取用户的工作区列表。 * * 工作区是用户自己整理对话的「文件夹」,此前只存 localStorage —— * 换设备/清缓存后文件夹全丢,会话归属还在但无处安放。 */ abstract fetchWorkspaces(): Promise; /** 新建 / 更新工作区(按 id 幂等) */ abstract upsertWorkspaces(items: WorkspaceUpsert[]): Promise; /** 删除工作区(软删) */ abstract removeWorkspace(id: string): Promise; // ---------- 交付成果 ---------- /** 列表(云函数 lfYlgU7SkK,`action:'list'`) */ abstract fetchDeliverables(opts?: { agentId?: string; limit?: number }): Promise; /** 按项目分组(`action:'byProject'`) */ abstract fetchDeliverablesByProject(limit?: number): Promise; // ---------- 任务看板(FR-07b) ---------- /** * 任务列表(按 agentId 过滤)。 * 🔴 云函数接收端**尚未实现**:就绪前实现方须返回**空数组**,**禁止伪造数据** * (SPEC FR-07b「依赖声明」)。 */ abstract fetchTasks(agentId: string): Promise; /** 任务详情(含 runs / comments / attachments) */ abstract fetchTaskDetail(taskId: string): Promise; // ---------- 上传 ---------- /** 取预签名(云函数 AlP56LCKFm);缺 uploadUrl 即抛错,不得静默降级 */ abstract requestUploadUrl(filename: string, mimeType: string, size: number): Promise; /** 预签名直传(前端不经手任何 OBS 密钥) */ abstract uploadFile( file: File, onProgress?: (pct: number) => void, ): Promise<{ key: string; publicUrl: string }>; // ---------- 成果文件入库(云盘同步) ---------- // ---------- 成果文件搬运队列(容器侧轮询) ---------- /** * 登记一次搬运:把容器里的 `source` 文件搬到云盘。 * * 🔴 **只报「传哪个」,不报「传到哪」** —— 上传地址与必需的头由云函数 * 用服务端密钥签好后写进队列,客户端不接触任何密钥 * (2026-10-21 定稿;此前的「App 自签」方案已废弃)。 * * @param index 附件在成果 `artifacts[]` 中的下标 —— 保证同名附件不互相覆盖 */ abstract enqueueArtifactJob(job: { deliverableId: string; agentId: string; /** 容器内的原始路径 */ source: string; filename: string; index: number; }): Promise<{ id: string }>; /** 容器领取待办(按 agentId,只取未完成且未过期的) */ abstract fetchArtifactJobs(agentId: string): Promise; /** 容器回执:上传成功(带公网地址)/ 失败(带原因) */ abstract ackArtifactJob(input: { id: string; ok: boolean; publicUrl?: string; error?: string; }): Promise; // ---------- 工作组(多容器协作) ---------- /** 创建工作组(服务端建房 + 邀请 + 写记录) */ abstract createWorkGroup(input: { name: string; goal?: string; memberAgentIds: string[]; }): Promise<{ id: string; roomId: string }>; /** 我参与的工作组 */ abstract listWorkGroups(): Promise; /** 邀请成员 */ abstract inviteToWorkGroup(groupId: string, agentIds: string[]): Promise; /** 移出成员 */ abstract removeFromWorkGroup(groupId: string, agentId: string): Promise; /** 归档(不删房间) */ abstract archiveWorkGroup(groupId: string): Promise; }