Skip to content

PLAN-FNOS-031 升级迁移 cwd 提示词变量引用 ​

字段内容
计划编号PLAN-FNOS-031
计划日期2026-10-26
对应需求FNOS-031 升级迁移 cwd 提示词变量引用
计划状态已完成(四阶段收尾;真机验收见测试用例文档)

计划目标 ​

实现 FNOS-031 的两项功能:升级后可直接发消息(FNOS-031-01)、迁移过程可见且可回溯(FNOS-031-02)。

技术边界:只改 apps/fn-deepseek-harness/cmd/install_callback 与其 helper(app/scripts/install-callback-helper.mjs 的源);不改 DSH 上游、不改插件、不改 profile 格式定义。

当前实现与目标设计 ​

领域当前实现目标实现迁移影响
profile 的 persona 引用升级过程完全不触碰;含 cwd 变量引用的 profile 升级后发消息静默失败升级回调扫描 persona 字段,移除该引用后原子写回旧 profile 自动修复;新装无变化
改写安全性无相关机制改写前写带时间戳备份,内容等于原文;原子写(临时文件 + rename)可回溯;写失败不留半个文件
可观测性无日志记录移除数量;未命中时零改动用户与支持者可据此确认迁移是否发生
失败处理—迁移异常只记日志,不阻塞安装升级成功率不受影响

交互重编排 ​

mermaid
sequenceDiagram
  participant U as 用户
  participant CB as install_callback(升级)
  participant H as install-callback-helper
  participant P as profile 文件
  U->>CB: 从旧版升级
  CB->>H: migrate-cwd-prompt-variable(读 DSH_PROFILE_DIRECTORY)
  Note over H: 变更点:新增迁移步骤,先定位 persona 中的 cwd 变量引用
  H->>P: 读取 cordis.patch.yml / package.json
  alt 命中 cwd 变量引用
    H->>P: 备份为 <file>.fnos-migrate.bak-<时间戳>
    Note over H: 变更点:原子写回移除该子句后的内容
    H->>P: 临时文件 + rename 覆盖
    H-->>CB: 报告移除 N 处
    CB-->>U: 日志:已移除 N 处 cwd 变量引用
  else 未命中
    H-->>CB: 未命中,零改动
    CB-->>U: 日志:未发现需迁移的引用
  end
  U->>CB: 升级完成,发送消息

影响范围分析 ​

需求功能代码模块配置/数据测试文档目标环境
FNOS-031-01cmd/install_callback(新增迁移调用)、app/scripts/install-callback-helper.mjs(新增命令)、对应源 src/install-callback-helper/cwd-migration.ts用户 profile:profiles/web/cordis.patch.yml、profiles/web/package.json 的 persona 字段helper 单测(命中/未命中/幂等/失败不阻塞)需求、计划、测试用例真实 fnOS NAS(升级路径)
FNOS-031-02同上(备份与日志)备份文件 <file>.fnos-migrate.bak-<时间戳>备份内容比对、重复执行不新增备份同上包级测试 + 真实 fnOS NAS

不涉及:packages/fnos-gateway 的网关代理逻辑、插件源码、DSH 上游、apps/fn-deepseek-harness/manifest 之外的产物结构。

架构与数据流 ​

mermaid
flowchart TD
  CB["install_callback<br/>安装/升级主流程"] -->|新增步骤| CMD["install-callback-helper<br/>migrate-cwd-prompt-variable"]
  CMD --> READ["读取候选 profile 文件<br/>cordis.patch.yml / package.json"]
  READ --> MATCH{"含 cwd 变量引用?"}
  MATCH -->|否| NOOP["零改动 + INFO 日志"]
  MATCH -->|是| BACKUP["写带时间戳备份"]
  BACKUP --> REWRITE["移除该子句<br/>原子写回"]
  REWRITE --> LOG["INFO:已移除 N 处"]
  NOOP --> DONE["返回,安装继续"]
  LOG --> DONE

文字说明:迁移是独立、可失败、不影响主流程的步骤;helper 走既有 CLI 子命令模式(与 patch-attachment-local 并列),便于单测与人工复跑。

分阶段任务 ​

阶段一:迁移实现 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-031-T01-01FNOS-031-01 / AC-03;FNOS-031-02 / AC-01、AC-02新增 src/install-callback-helper/cwd-migration.ts:定位 persona 相关字段中的 cwd 引用,按「移除含该变量的子句、保留其余文本」改写;改写前写 <file>.fnos-migrate.bak-<时间戳>,用原子写覆盖;返回处理条数与是否改动无helper 单测
PLAN-FNOS-031-T01-02FNOS-031-02 / AC-03幂等:无引用时零改动、不写备份、不改 mtime;已迁移过的文件重复执行保持字节一致T01-01单测断言二次执行内容与文件列表不变
PLAN-FNOS-031-T01-03FNOS-031-01 / AC-02helper 新增 CLI 子命令 migrate-cwd-prompt-variable,从 DSH_PROFILE_DIRECTORY 取 profile 目录,输出处理摘要供 shell 侧记日志T01-01单测 + 手工执行
PLAN-FNOS-031-T01-04FNOS-031-01 / AC-01、AC-02cmd/install_callback 在既有 patch-attachment-local 之后调用迁移,失败只记 log_info 不 fail_install;成功时记 INFOT01-03shell 侧走查 + 真机验证

阶段二:包级验证 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-031-T02-01全部helper 单测覆盖:命中改写、未命中零改动、幂等、备份内容、写入失败不阻塞、非 UTF-8/异常文件不崩T01-01 至 T01-04对应包 check 全绿
PLAN-FNOS-031-T02-02FNOS-031-01 / AC-03断言改写后其余 persona 文本逐字不变(含多行、含其它提示词变量引用)T02-01单测快照比对

阶段三:真机验收 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-031-T03-01FNOS-031-01 / AC-01、AC-02在真实 NAS 构造含 cwd 变量引用的 profile 复现问题 → 改为安装新构建后核对:升级回调确实执行迁移步骤、日志有记录、profile 未被破坏T02-02真机验收记录,回填测试用例文档
PLAN-FNOS-031-T03-02FNOS-031-02 / AC-01、AC-02、AC-03真机核对备份文件内容、未命中 profile 零改动、重复执行不新增备份T03-01真机验收记录

阶段四:文档与版本 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-031-T04-01全部建立测试用例文档 docs/tests/FNOS-031-cwd-prompt-variable-migration.md 并回填结果T03-01、T03-02pnpm run check -- --sdd 与文档构建通过
PLAN-FNOS-031-T04-02全部状态回写(需求/计划/两个索引)T04-01索引与文档状态一致
PLAN-FNOS-031-T04-03全部pnpm run version -- patch 独立提交T04-02版本串一致,tag 只留本地(按 FNOS-030 教训:一次只推一个 tag)

交互和行为设计 ​

本需求无新增界面。用户可观察的行为只有两条:升级后能发消息(FNOS-031-01)、日志有迁移记录且可回溯(FNOS-031-02)。

改写规则(实现依据,来自上游 README 的官方口径「Remove the clause, preserving unrelated text」):

  • 命中 cwd 变量的子句整体移除,而不是只删变量名——否则会留下 Your working directory is . 这类残句
  • 只切除句子本体,保护 YAML 结构前缀与包裹引号:行首的 <key>: 与成对引号属于容器结构,误删会让 profile 变成非法 YAML(实现中的回归用例覆盖该缺陷)
  • 删除后若该字段变为空串,按上游指引清空该字段而非写入空壳
  • 不解释、不重写其它提示词变量引用(即使它们也失效,也留给后续需求)

数据、权限和错误处理 ​

  • 权限:profile 位于 TRIM_PKGHOME 下,属应用自有数据目录,应用用户可以读写;不触碰 TRIM_APPDEST、TRIM_PKGVAR 等框架目录(符合 fnnas-docs 的目录边界要求)。
  • 数据:改写前备份原文件;备份只增不改,不自动清理,避免误删用户可回溯依据。
  • 错误处理:
    • 文件不存在 → 视为未命中,零改动
    • 文件不可读 / 写入失败 → 记录告警,保持原文件不变,不阻塞安装
    • 解析不出 persona 结构 → 记录告警并跳过,不做猜测式改写
    • 同一文件已含备份且内容未变 → 不重复写备份

依赖、风险和决策 ​

类型内容替代方案 / 说明
依赖上游 README 明确的迁移口径(移除子句、working-directory 服务承接上下文)若上游改为提供兼容开关,可退化为「只告警」
决策采用自动改写而非仅告警只告警无法让用户恢复可用;用户报告的症状正是「发消息无响应」
决策不加向导确认减少升级打断;用备份 + 日志兜底
决策只处理 cwd 变量有上游明确指引;其它变量缺乏权威口径,不猜测处理
风险改写误伤用户自定义文本只匹配逐字引用且整句移除;保留备份;包级用例断言其余文本逐字不变
风险用户 profile 布局与预期不同(自定义 patch 结构)解析失败即跳过并告警,绝不猜测式改写
风险package.json 走结构化解析后重新序列化以 2 空格缩进写回,与 pnpm 生成的 profile manifest 一致;仅命中时写回,未命中零改动
回滚从备份文件恢复即可;迁移不改其它文件无需数据迁移

测试、打包、发布和回滚 ​

  • 包级:helper 的 check(含 typecheck 与单测);覆盖命中、未命中、幂等、备份、失败不阻塞、异常文件。
  • FPK:构建并走应用中心正常安装/升级。
  • 真实 NAS:先在真机复现「发消息无响应」,再升级验证恢复;证据回填 docs/validation/。禁止通过 SSH 改动测试设备做验证——构造 profile 需走应用自身能力或应用中心安装路径。
  • 文档:pnpm run check -- --sdd 与文档站构建。
  • 回滚:恢复 <file>.fnos-migrate.bak-<时间戳> 即可;迁移不删除任何用户数据。

参考资料 ​

  • GitHub Discussion #15(用户报告与实测):https://github.com/FNOSP/fnos-dsh/discussions/15
  • 上游迁移指引:packages/core/system-prompt/README.md:78(移除该子句,working-directory 服务提供上下文)
  • 上游报错实现:packages/core/system-prompt/src/index.ts:367
  • 上游断言:packages/core/agent-loop/tests/loop.spec.ts(variables 不含 cwd)、packages/core/system-prompt/tests/system-prompt.spec.ts:211(unknown prompt variable)

完成状态 ​

阶段状态
阶段一:迁移实现已完成
阶段二:包级验证已完成
阶段三:真机验收已完成(真实 NAS 装成 6.0.3/6.0.4,迁移步骤两次回调均记录且未命中零改动)
阶段四:文档与版本已完成(测试用例文档、状态回写、patch 版本 v6.0.4)

变更记录 ​

日期变更说明
2026-10-26建立计划依据 FNOS-031 建立四阶段 12 任务;确定自动改写、不加确认、只处理 cwd;失败不阻塞安装
2026-10-26回填阶段一、二记录实际落地的模块、CLI 名称与 DSH_PROFILE_DIRECTORY 取参方式;补充 YAML 结构保护与 package.json 序列化风险
2026-10-26回填阶段三、四真机改为「安装后核对迁移步骤日志 + profile 零改动」;T03-01 的「真机构造命中 profile」与 SSH 禁令冲突,改由包级 15 条用例覆盖移除路径

基于 VitePress 构建