# 云函数源码(随项目版本化) 平台把云函数存在 Parse 库的 **`Function`** 表(`objectId` 即前端调用的函数 id)。 本目录保存源码副本,便于 code review 与回滚;发布方式见下。 ## 已发布 | 文件 | Function.objectId | name | path | version | |---|---|---|---|---| | `fmodeagent-chatthreads.js` | `oR5O67i1Cm` | fmodeagent-chatthreads | /fmodeagent/chatthreads | **0.1.6(与线上一致,2026-11-06 对齐)** | | `fmodeagent-status.js` | `xlosi3m46q` | fmodeagent-status | /fmodeagent/status | **v0.0.9(余额口径修复,2026-11-11 已上线)** | | `fmode-file-hub.js` | `THXAOGKiPK` | fmode-file-hub | /fmodeagent/file-hub | **v0.2.0(群隔离,2026-11-11 已上线)** | | `fmode-file-read.js` | `tocXyQjyh6` | fmode-file-read | /fmodeagent/file-read | **v0.1.0(云盘文本代理,2026-11-11 新建)** | > **fmode-file-read v0.1.0(2026-11-11 新建)** > 云盘 `s3.fmode.cn` 是 public-read 但**没配 CORS**,浏览器 fetch 必被拦, > `.md` / `.txt` 在页内渲染不出来。本函数在服务端取正文再回传, > 浏览器只调本域(已配 `ACAO:*`)。三条安全约束:只放行云盘域名(防 SSRF)、 > 必须有效 session、2MB 上限。 > 实测:读取 `w-test.md` 返回 `{"code":200,"text":"测试\n"}`; > 非云盘域名 → 400;无 token → 401。 > **fmode-file-hub v0.2.0(2026-11-11 群隔离)** > 伪群聊下容器只在自己的 DM 房间工作、回报文件时**不知道群**。 > 新增字段 `groupId` / `groupIdSource` / `prevThreadRoot`,新增 action > `listByGroup` / `claimToGroup`。App 在派活前写一条**派发锚点** > (`mimeType = application/x-fmode-anchor`),容器产出登记时服务端按 > 「同 user + 同 agentId + 同 roomId + 30 分钟内」反查归属。 > **多群同时派活时不认(宁可漏,不归错)**;命中后 `threadRoot` 一并改写为群 id, > 保证一条文件只出现在一个页签。 > 回滚:`cloud/backup/THXAOGKiPK-fmode-file-hub-live-20261111-before-v0.2.0.json`。 > **v0.0.9 说明(2026-11-11)** > 余额口径修复:旧实现用 `fmodeApiToken` 调 `api.fmode.cn/api/usage/token/` 取 `total_available` 再自行换汇, > 而该令牌是 `unlimited_quota: true` → 线上 App 显示 **-76.12**,Studio 充值页实际 **948.09**。 > 新版改为与 Studio 同源:`POST /api/fmode/billing/query`(`data.balance.availableCny`)优先, > `POST /api/fmode/newapi/user`(`data.user.balance`)回落;不再自行换算;`unlimited_quota` 令牌被明确拒绝。 > 缓存键改为**按用户 objectId**(`__FM9_BAL_CACHE`)、TTL **60s**(原 300s 且不区分用户)。 > 上线前已本地仿真:新旧实现分别对真实接口取数 → 新实现 948.09 ✅ / 旧实现 -76.12 ❌。 > **回滚**:`cloud/backup/xlosi3m46q-fmodeagent-status-live-20261111-before-v0.0.9.json`(改前线上全文), > `PUT {code: <该文件 code>}` 即回到改前状态。 > 本文件以**线上当前代码**为基线(线上比 `-v0.1.2.patched.js` 新,别拿旧备份覆盖)。 > **v0.1.6 说明(2026-11-06)** > 群会话需要在会话目录里区分出来,靠两个字段:`kind`(`dm`/`group`)与 `memberIds`(成员容器 id)。 > 这两个字段此前**只存在于线上**,工作区源码仍停在 v0.1.5 —— 直接部署工作区这份会把线上功能回退掉。 > 本次已补齐 `opUpsert` 写入与 `opList` 返回,并用本地仿真验证 8 项语义(含「不带 kind 的后续 upsert 不得把群降级成单聊」)。 > **部署前务必先校验**:`node scripts/verify-cloud-fn.mjs`(对比线上返回的字段集)。 > > **v0.1.4**:工作区归属 `workspaceId`(`''` = 日常对话)。 ## 发布 / 热更 ```bash # 新建(objectId 自动生成) curl -X POST https://server.fmode.cn/parse/classes/Function \ -H "X-Parse-Application-Id: ncloudmaster" -H "X-Parse-Master-Key: " \ -H "Content-Type: application/json" -d '{"name":"...","code":"...","enabled":true,...}' # 热更(免重启) curl -X PUT https://server.fmode.cn/parse/classes/Function/ \ -H "X-Parse-Application-Id: ncloudmaster" -H "X-Parse-Master-Key: " \ -H "Content-Type: application/json" -d '{"code":"..."}' ``` ## 函数体约定(从现有函数反推,实测) - 入口:`async function handler(request, response)`;入参在**全局 `params`**。 - 身份:`request.user` 由平台按 `X-Parse-Session-Token` 解析后注入。 - 🔴 输出必须 `response.status(200).json(...)` **显式写出**;直接 `return 对象` 平台不回包(请求挂起)。 - token 只能放 `X-Parse-Session-Token` 请求头;放 body 的 `token` 字段会被平台鉴权层直接 401。