# 交付物上报技能 — 运维部署指南 > 本文档写给**运维001**,确保新初始化的数字生命容器能正确上报交付物。 > 读完这篇,你就知道每个新容器启动后该检查什么、配在哪里。 --- ## 一、容器初始化检查清单 ### □ 1. 确定 AGENT_ID 每台容器需要一个**语义唯一名**,格式:`{主人名}-{角色}` | 容器 | AGENT_ID 示例 | |------|--------------| | 刘雨飏 主容器 | `agent-node` | | 刘雨飏 雨飏001 容器 | `agent-node-tuye` | | 芯葶 容器 | `agent-node-xinting` | **存放位置**(优先级从高到低): | 位置 | 命令/文件 | 说明 | |------|-----------|------| | 环境变量 | `export FMODE_AGENT_ID=agent-node` | 最优先,重构建容不失 | | 环境变量 | `export AGENT_ID=agent-node` | 兼容旧脚本 | | 配置文件 | `~/.fmode/config.json` → `agentId` | 持久化,被 env 覆盖 | | 项目配置 | `/.fmode/config.json` → `agentId` | 项目级覆盖 | **推荐做法**:`echo 'export FMODE_AGENT_ID=agent-node' >> ~/.bashrc` > ⚠️ **不要依赖 hostname**。Docker 容器重启后 hostname 变随机 hash(如 `6ec71e98949e`),云函数会判 `404 agent not found`。 ### □ 2. 确保 FMODE_SESSION_TOKEN 可被读取 SessionToken 是用户登录后生成的凭据,控制 ACL(**访问权限**)。 **存放位置**: | 位置 | 文件 | 说明 | |------|------|------| | 环境变量 | `FMODE_SESSION_TOKEN` | 最优 | | 用户配置 | `~/.fmode/config/user.json` → `sessionToken` | FmodeAgent / Hermes Agent、Fmode Studio 自动维护 | | 旧配置 | `~/.fmode/config.json` → `sessionToken` | 兼容旧版本 | **推荐做法**:登录一次 FMODE Studio 后,token 自动写入 user.json。 如 env 缺失,技能会从配置文件兜底读取。 ### □ 3. 验证 ```bash # 查看当前容器身份 skill-deliverable check # 成功输出示例: # { # "agentId": "agent-node", # "sessionToken": "r:852951...", # "status": "ready", # "hostname": "6ec71e98949e" // 仅显示,不用于上报 # } ``` --- ## 二、FmodeStudio 容器生命周期集成 在 `fmode-studio` 的 Docker 部署脚本中,新增: ```bash # 设置 AGENT_ID export FMODE_AGENT_ID="agent-$(hostname -s)" # 或用固定语义名 # 确认 sessionToken 有来源 if [ -z "$FMODE_SESSION_TOKEN" ] && [ -f "$HOME/.fmode/config/user.json" ]; then export FMODE_SESSION_TOKEN=$(python3 -c "import json;print(json.load(open('$HOME/.fmode/config/user.json'))['sessionToken'])") fi # 运行检查 npx --yes skill-deliverable@latest check ``` --- ## 三、容灾:云函数 404/403 怎么办 ### 「404 agent not found」 → FmodeAgent 表里没有这个 agentId 的记录。 - **新容器首次上报时会自动注册**(云函数 v2 特性),等待第一次 report() 即可 - 若等不及,用 master key 手动在 Parse `FmodeAgent` 表创建同名记录 ### 「403 only owner or superadmin can report」 → session 用户不是这个 agent 的 owner。 - 确认该容器的报告人的 session 和该 agent 是否是同一个人 - 云函数 v2 **已移除 superadmin 检查**,只校验 owner(用户=他自己容器的 owner) - 若 agent 无 owner(旧数据),首次成功上报会自动绑定 --- ## 四、常见问题 ### Q: 容器重建后上报失败? A: 重建后 AGENT_ID 丢失 → 重设 env `FMODE_AGENT_ID`。skill-deliverable 已做 hostname 兜底但语义名丢失时 hostname 会变导致 404。**必须持久化 AGENT_ID**。 ### Q: 一台机器跑了多个容器怎么办? A: 每个容器设不同的 AGENT_ID,各自上报各自的。FmodeAgent 表以 agentId 区分。 ### Q: 能不能手动上报测试? A: 可以。`skill-deliverable report --title "测试" --project test`。失败看 `/tmp/agent-deliverable-report.log`。 --- ## 版本记录 | 版本 | 日期 | 变更 | |------|------|------| | 0.0.1 | 2026-09-23 | 初版:分离 fmode-hub 独立成技能 + 云函数 v2 改良 | --- ## 五、运维001 实测补充(2026-09-23) ### 5.1 云函数代码必须是「平台形态」,不是 Parse.Cloud.define 本平台 `/api/functions` 的契约是:代码里定义 `function handler(request, response)`, 可用的全局变量是 `Parse` / `Psql` / `request` / `response` / `params` / `user`; 鉴权信息挂在 `request.user` / `request.company` / `request.account`。 仓库里的 `cloud/deliverable-report-v2.js` 用的是 `Parse.Cloud.define('deliverable-report', ...)`, **直接写进 Function 记录会跑不起来**。平台形态的正确版本见 `cloud/deliverable-report-v2.platform.js`(已按此上线到 `lfYlgU7SkK`,version 0.0.2)。 ### 5.2 云函数不需要「发布」,改 `Function` 记录即生效 executor 每次调用都从库里查 `Function` 记录(无内存缓存),所以: - 更新 = 更新 `Function.code`(+ `version` / `updatedAt`) - **不需要**部署源码、不需要重启服务 Parse REST 对 `Function` 类有 CLP 限制(masterKey 也被拒,code 119), 运维侧用 nova 库直改 `"Function"` 表那一行即可。 ### 5.3 字段类型:`user` / `company` 是 Parse 指针,不是字符串 - `AgentDeliverable.user` / `.company` 的 schema 是 **Pointer**,写字符串会报 `schema mismatch for AgentDeliverable.user; expected Pointer<_User> but got String`。 要传 `Parse.User` 对象(ACL 里才用 `user.id` 字符串)。 - `FmodeAgent.user` 同理,owner 比较要取 `.id`(`typeof v === 'string' ? v : v.id`)。 ### 5.4 关键 ID 核查(★ 安装前必做) 容器能上报的三个前提,缺一个就 403/500: | 前提 | 检查方法 | |------|----------| | `FMODE_AGENT_ID` 有值且与 `FmodeAgent.agentId` 一致 | 见 5.5 | | 容器的 `sessionToken` **属于该 agent 的 owner** | 用 token 查 `_Session."user"`,必须等于 `FmodeAgent."user"` | | `FmodeAgent` 记录存在(或允许自注册) | `SELECT * FROM "FmodeAgent" WHERE "agentId"='...'` | **实测踩到的坑**:某容器 `fmode-identity.json` 里的 `session_token` 是**别人(平台主账号)的**, 且容器 env 里 `FMODE_SESSION_TOKEN` 是**空字符串** —— 于是 owner 校验必然 403。 批量开通的容器尤其要逐个核。 ### 5.5 技能只读这几个位置(`fmode-identity.json` 不在其中!) `lib/index.mjs` 的解析链: - agentId:`env.FMODE_AGENT_ID` → `env.AGENT_ID` → `~/.fmode/config.json#agentId` → `./.fmode/config.json#agentId` → hostname(仅兜底,会 404) - sessionToken:`env.FMODE_SESSION_TOKEN` → `~/.fmode/config/user.json#sessionToken` → `~/.fmode/config.json#sessionToken` → `./.fmode/config.json#sessionToken` **注意**:`~` 取的是 `$HOME`。这些容器里 `hermes` 用户的 HOME 是 **`/opt/data`**(持久卷), `root` 的 HOME 是 `/root`(容器层,重建即丢)。所以持久化的正确位置是 **`/opt/data/.fmode/config.json`**(同时给 `/root/.fmode/config.json` 兜底)。 ### 5.6 CLI 的 Node 版本坑 `bin/skill-deliverable.mjs` 原稿在 ES 模块里用了 `require('os')`, Node ≥ 20 会直接抛 `ERR_AMBIGUOUS_MODULE_SYNTAX`(top-level await + require 混用)。 已改为 `import { hostname } from 'node:os'`。