# @xicode/pi-roleplay

## 0.5.0

### Minor Changes

- e50dbeb: 升级 Pi 构建基线到 0.84.1，并适配其 API 变化。

  - 全部包的构建与测试基线从 0.81.1 提升到 0.84.1，`peerDependencies` 同步更新为
    `>=0.84.1`，安装 pi 0.84.1 及以上版本即满足依赖。
  - pi-codex-pat：`check`/`resolve` 现在接受并遵守调用方传入的 abort signal（0.84 起的
    auth 契约要求），whoami 请求把调用方 signal 与自身超时合并；宿主版本基线更新为 0.84.1。
    打包的 Codex transport 改为 0.84.1 源码上的补丁副本。
  - pi-tool-display：pi 0.84.1 起 bash 工具自带 PI\_\* 环境变量说明（promptGuidelines），
    上游测试的过时断言改为跳过并守护，适配层行为不变。

## 0.4.0

### Minor Changes

- 89fbe07: 收紧角色激活、手动状态命令和剧情提交的一致性，并优化长会话归并性能。

  - 显式角色 id 现在必须精确命中；`--role`、锁定角色和 `/rp reload` 都不会在目标资产缺失时静默切换到
    第一张角色卡。重载后会重新同步当前角色的运行时状态，角色不匹配的 commit 也会留下诊断。
  - `/rp state`、`event`、`memory`、`turn repair` 与 `review amend` 的 JSON 输入在执行前统一通过运行时
    schema 校验，避免不完整或类型错误的 payload 写入 canon。
  - 状态路径只在根节点阻止保留域，同时继续在任意层级拒绝原型污染片段。模型可合法更新名为 `tools`
    的物品；已知外貌字段的点号写法会归一到结构化路径，避免同时出现 `hair` 与 `hair.length` 两套状态。
  - `/rp review` 的纯数字选择器只表示一基序号，越界时直接报错；提交别名统一要求 `#alias`，消除数字
    hash 被误判为序号后绕过审批的歧义。
  - 未显式声明 id 的世界条目继续使用文件名，避免既有 Checkpoint 的资产快照意外漂移；跨世界检索按
    「世界 id + 条目 id」去重，预算估算统一只计算实际注入的 Markdown 正文。
  - 分支 reducer 为事件、记忆、commit id 与当前状态哈希建立单次归并索引，避免每个 commit 都对全部
    历史记录重复稳定序列化。长会话 E2E 新增 200 commits / 800 events 的性能回归门禁。

## 0.3.0

### Minor Changes

- fc31536: 补齐资产加载的静默失败诊断，并新增 `/rp validate` 校验命令。

  此前 `loadCatalog` 只在 `catch` 里产生 diagnostics，所有"内容写错"的失败路径都是零提示的：
  世界书不生效、卡片不出现在 `/rp list`、budgets 悄悄回到默认值，作者只能靠猜。

  - **剥离 UTF-8 BOM。** 带 BOM 的文件会让 Pi 的 `extractFrontmatter` 判定失败，整块 YAML 被
    当作正文注入 system prompt，且不抛任何异常。修复覆盖角色卡、世界书与世界条目三类资产。
  - **六条静默路径全部补上诊断**，每条都给出具体文件路径、实际后果和修复动作：frontmatter 只有
    开头 `---` 没有闭合、角色/世界目录缺少 `CHARACTER.md` 或 `WORLD.md`、同一根目录内 id 冲突
    （给出两个路径并说明哪个生效）、世界条目 id 冲突、`budgets` 未知键（带拼写建议）、`budgets`
    字段不是有限正数。跨根目录的同 id 覆盖是文档承诺的行为，不计入告警。
    缺少入口文件的诊断会跳过 `.` / `_` 前缀目录、`node_modules` 以及递归下去不含任何 `.md` 的目录，
    避免 `.git`、`.stfolder`、`@eaDir`、`_templates` 这类目录每次 `session_start` 都弹一条红字。
  - **`worlds` 交叉校验。** 角色卡引用了不存在的世界 id 时报错并给出最接近的候选。
    `/rp status` 与 `/rp inspect world` 改为逐个标注解析状态（`astra ✓(3 条目) / astara ✗(未找到)`），
    不再把拼错的 id 显示成"已关联"。
  - **新增 `/rp validate [<id>]`。** 输出按错误 / 警告 / 提示分级；不带参数检查全部资产，带 id 只
    检查那张卡及其关联世界。额外覆盖只在显式校验时才值得跑的检查：`examples` 指向的文件缺失、
    世界没有任何条目、条目既无 keys 又非 constant（永远不会命中）、单条超出 `worldEntries` 预算
    （永远进不了上下文）。即使一切正常也会给出"N 个角色、M 个世界、K 个条目，未发现问题"。
    该命令读取磁盘当前内容，不需要先 `/rp reload`，也不会改动当前会话已选择的角色。
  - **加载期诊断按级别提示。** `error` 用 error 级通知，`warning` 用 warning 级，不再一律 warning。
  - 资产快照比对的两条提示由裸英文改为中文，并补上文件路径、后果和建议动作。

  行为变更（`minor`）：

  - **世界条目的默认 id 从文件名改为相对 `entryRoot` 的路径（去掉 `.md`）**，例如 `npc/guard`。
    这从根本上消除了 `npc/guard.md` 与 `rules/guard.md` 在检索期被静默去重的问题。
    影响面：扁平布局（`entries/*.md`）的相对路径等于文件名，行为不变；显式写了 `id:` 的条目不受
    影响，包内示例资产全部显式声明 id。只有"嵌套目录 + 未声明 id"的条目 id 会改变，而 id 仅用于
    检索去重、`<world_entry id=...>` 显示和资产快照，**不参与 keys 匹配**，因此检索命中结果不变。
    唯一可见影响是这类资产的世界快照哈希会变化一次，已有 Checkpoint 的会话会收到一条
    "世界书内容自 Checkpoint 之后已改变"提示，运行 `/rp checkpoint` 即可重新对齐。
  - **`budgets.recentMessages` 写成 0 或负数时会被夹到 1。** 检索走 `messages.slice(-recentMessages)`，
    而 `-0 === 0`，所以 `0` 实际等于"取整段历史"，负数等于"丢掉最旧的几条"——两者都把**更多**消息灌
    进检索 query，与作者的意图完全相反。这个字段不存在"关闭"语义，保留原值等于让作者确信自己关掉了
    检索、实际却放大了它，因此这里破例改值并在诊断中说明。其余字段的 0 与负数仍按原样保留。
  - `budgets` 其余字段的 0 **仍按原样生效**，只是不再静默：把 `maxEntries: 0` 悄悄改回默认值 8 会
    制造新的意外，因此按"作者有意关闭该功能"处理，同时告警说明具体关掉了什么。诊断文案已逐条对照
    消费端代码核实：`currentState: 0` 会被 `formatCurrentView` 夹到 1 token，`<current_state>` 块
    **仍然注入**；`examples` 为负数走 `slice(0, n * 4)`，是**从末尾截断**而非关闭。
  - `AssetCatalog` 新增 `issues` 字段（带级别与归属的结构化问题列表）；`diagnostics` 语义不变。

- dd9ab02: 角色激活改为显式 opt-in，并新增 `/rp off` 与 `--no-role` 关闭入口。

  **默认行为变更（breaking-ish）**：从前只要资产目录里存在任意一张角色卡，每个 Pi 会话
  （包括纯编码会话）都会自动激活角色——整张角色卡前置进 system prompt、`roleplay_finalize_turn`
  及其 4 条 promptGuidelines 常驻、每轮注入世界书与当前状态，而且全包没有任何关闭入口。
  跑一次 `/rp init global` 试玩的代价是此后所有会话。现在只有下列任一条件成立才会激活：

  - 启动时带了 `--role <id>`；
  - 当前 branch 已有记录了 `characterId` 的 `pi-roleplay-state` entry；
  - 当前 branch 已被 Commit、Checkpoint 等结构化状态记录锁定身份；
  - 本次会话跑过 `/rp use <id>`。

  依赖「装了卡就自动进入角色」的用户需要改为显式 `/rp use <id>`，或启动时带 `--role <id>`。

  **向后兼容保证**：分支上已有选择记录或结构化状态记录的旧会话，恢复后仍然激活原角色，
  进行中的剧情不会失去人格。为此 `resolveSessionRoleBinding` 改为先扫完整条分支上的结构化
  状态记录，再判断 assistant 消息——Commit 永远写在角色回复之后，此前在第一条 assistant
  消息处提前返回会让只靠 Commit 记录身份的分支识别不出角色。

  - 新增 `/rp off`：停用当前 Session 的角色，回到纯编码模式。这个决定同时记在两处——一条带
    `disabled: true` 的 `pi-roleplay-state` entry 写进当前 branch（跟着路径走，跨重启存活），
    外加本次会话的进程内意图（跟着会话走，跨分支存活）。两者都必要：只靠分支记录的话，一次
    `/tree` 导航、或编辑重试一条更早的 user 消息，都会落到不含停用记录的路径上，角色被
    `locked` / `selection` 原样装回来；只靠进程内意图则关不过重启。停用同样必须是一条读得
    出来的记录，`{characterId: undefined}` 与「从未选择」同形，一样会被重新激活。该 entry
    刻意不带 `characterId`，绑定归属仍属于用户真实的那条选择记录，已锁定的剧情 `/rp off`
    之后仍绑定原角色，`/rp use <同一角色>` 即可续上，不丢任何剧情记录。
  - 新增 `--no-role` flag：本次启动强制不激活，优先级高于分支上恢复的选择。
  - `session_tree` 的严格程度低于 `session_start`：目标分支没有任何角色记录时，若本进程当前
    已处于角色模式则保持当前角色。用 `/tree` 导航到首条选择记录之前的早期节点是剧情中的常见
    操作，角色在那里凭空消失是突兀的；而从没进入过角色模式的纯编码会话 `activeCharacter`
    始终为空，走不到这条保持规则。`/rp off` 与 `--no-role` 的关闭意图在所有导航路径上都优先。
  - `session_start` 不再在全新会话里自动写 `pi-roleplay-state` entry。自动激活顺手落盘会让
    「插件替你选的」在下次恢复时伪装成「用户自己选的」；只有 `--role` 这类显式意图才落盘。
  - 修复纯编码会话被锁死在用户从没选过的角色上的问题。此前身份解析会用 catalog 默认角色兜底，
    于是任何出现过 assistant 消息的会话都被判定为已绑定，用户随后 `/rp use <id>` 会被拒绝。
  - `/rp status` 在「有角色卡但未激活」时不再错误显示「没有可用角色卡」，改为说明当前处于
    关闭状态、是哪一种关闭（默认关闭 / `/rp off` / `--no-role`）、列出可用角色并给出
    `/rp use <id>` 入口；这个状态不再报成 warning。
  - `/rp init` 明确提示安装示例资产不会自动开启角色模式，且不再顺手把默认角色顶上来。
  - `/rp` 参数补全新增 `off`。

- 9194dea: 修复了会导致回合状态不可恢复丢失的缺陷，并补齐 `/rp turn repair` 的恢复链路。

  三个缺口叠加会让一轮剧情的结构化状态变化被静默丢弃且无法找回，现在一起修复：

  - 命令参数不再被空白折叠。此前整条输入按 `split(/\s+/)` 拆开再用空格拼回，JSON 字符串里的
    连续空格和换行会被悄悄改写，而 JSON 依然解析成功：`/rp state set` 的 value、`/rp event add`
    与 `/rp memory add` 的 summary 都会被改写后逐字注入模型上下文，`/rp turn repair` 更是彻底不可用
    ——evidence.quote 要求与本轮原文逐字一致，多段落的角色回复必然含换行，用户无论怎么正确复制
    原文都不可能匹配上。现在 JSON 部分按原始字节解析；字面换行会给出显式错误，并指出出错行列和
    「换行写成 `\n`」的改法。
  - 修复失败不再消耗掉该回合唯一的修复机会，也不再谎报成功。此前无论校验结果如何都会写下一条
    「已修复」记录并把该回合永久排除，照着旧 README 的空 proposal 示例做一次，就等于不可逆地放弃
    这一轮。现在判据是「实际落盘了什么」：失败的尝试只留审计记录，并以 warning 逐条列出被拒理由，
    改好后可以重试。这同时解锁了旧 session 里被空记录锁死的回合。
  - 只产生待审项的修复如实报告「尚未写入任何内容」，不再自相矛盾地说「已修复 … Revision：0」。
    这些待审项被 `/rp review reject` 全部拒绝后，这一轮一个字都没落盘，修复机会会归还。
  - 模型调用 finalize 但所有实质变化被证据校验拒绝的回合，现在会当场以 warning 通知用户、在状态栏
    标出，并纳入 `/rp turn repair` 的可修复集合。此前这种最常见的失败模式（模型记错引用）既没有
    任何用户可见信号，也没有任何补救入口。只剩 `uncertainties` 的空 commit 同样归入这一类——此前
    它被记成 `finalized-change`，用户有告警却没有补救入口。
  - `/rp turn repair` 不带参数时展示待修复回合的 user / assistant 完整原文、可复制的引文候选与
    proposal 骨架，而不是只抛一句通用用法串。骨架里的 kind、summary 和 quote 都是占位符，占位引文
    不可能是任何原文的子串，因此原样提交必然被拒：不写入任何内容，也不消耗修复机会。
  - 证据校验失败的提示改为明确说明「引文必须与原文逐字一致」并回显实际收到的 quote。逐字匹配这条
    核心安全属性本身没有放宽。
  - 命令参数中 JSON 文档外侧的空白会被裁掉，用中文输入法打出的全角空格（U+3000）作分隔符不再让命令
    失败；它出现在 JSON 内部时错误信息会点名指出。

  新增 turn status `repair-failed`，不改变 `pi-roleplay-turn-status` 的 entry 结构；旧 session 的
  v1 entry 仍按既有内存迁移读取，不改写任何旧 entry。

- 6f400f7: 让状态审批闸门在 UI 层真正可见、可读、可批量处理。

  relationship / knowledge / goals / questFlags 四类变化一直会进待审队列，但队列此前
  在用户侧完全没有出口：产生待审项时零通知，唯一痕迹是模型可见的工具结果文本。用户的
  实际体感是"角色没有记忆、态度不会变"——这个包的核心增量能力被静默冻结了。

  - 待审队列常驻可见。产生待审项时主动 notify 一条 warning 并列出每条变化；状态栏
    常驻 `rp:review N`；`/rp status` 增加一行待审计数。状态项跟随 `runtimeState`
    重算，队列清空或 `/tree` 切分支后会正确归零，不会留一个永远亮着的假信号。
  - 选择器与确认框可读化。`ctx.ui.select` 此前直接把 43 字符 UUID 丢给用户，只能退
    出去跑 `/rp review list` 再回来手抄；现在列表、选择器、确认框共用同一套格式化，
    形如 `[high] 信任 bob: 0.7 → 0.3 · "你根本不该来"`。确认框补上 reason、原值
    （提案未声明时回落到 Current State 上的真实值）、置信度、来源回合与逐字 evidence。
  - reject 也走确认框。此前只有 accept 有确认，拒绝直接执行且没有任何预览。
  - 新增 `/rp review accept all` 与 `/rp review reject all`，支持 `--risk=medium|high`
    按风险过滤。批量确认框一次性列出全部变更行，避免退化成无脑确认。批量操作逐条走
    既有的 `decidePendingReview`，保持 append-only 语义与 branch 回滚正确性。
  - reviewId 支持短别名（前 8 位）和列表序号，并纳入命令补全。补全表此前连
    `review accept` / `review reject` 都没有，输入 `review a` 只会补出 `review amend`。
    短别名匹配到多条时报错并列出可读候选。
  - 审批文本在渲染前统一折叠控制字符与 ANSI 转义。待审行里每一处可变文本都由模型产生
    （`stateChange.value` 是 `Type.Any()`，各类 id 只限长度不限字符集，canonical path
    也只转义 `~` 和 `/`），不清洗的话一条待审项就能在确认框里伪造出额外的变更行、连序号
    都能和真实条目撞上，或者用 ANSI 转义重绘选择器。
  - 待审项与批量参数不再混用。`/rp review accept 3 --risk=high` 此前会同时解析出 selector
    和 risk，而调用方只看批量标记，于是静默接受全部 high 项而不是第 3 条；无 UI 时更是
    连确认框都没有。多余的位置参数也不再静默丢弃。
  - 批量操作补上两道闸门：没有交互 UI 时中止并列出全部待处理条目，要求显式 `--yes`；
    一次超过 20 条时同样中止，要求先用 `--risk` 收窄或分批。队列是跨回合累加的，长剧情
    攒到上百条很正常，而上百行的确认框等于没有确认；这里宁可拒绝也不截断列表——截断
    意味着用户批准的正是自己没看到的那部分。

  审批策略本身没有变化：哪些路径需要用户确认与此前完全一致。

- ede2f98: 修复部分模型在 `tool_choice=auto` 下忽略 `roleplay_finalize_turn`，导致剧情状态不推进、灰色摘要
  缺失且 footer 显示内部状态 `role:incomplete` 的问题。

  - 在角色模式最终 system prompt 的末尾加入强制回合完成协议，并同步加强工具 description 与
    prompt guidelines：主模型必须在自然语言角色回复后调用 finalize 恰好一次，无变化也不能跳过。
  - 主模型漏调 finalize 时，`agent_settled` 会复用当前模型与认证发起隔离 sidecar 提取。该调用关闭
    thinking、只暴露 finalize schema 并强制选择该工具，输入仅包含本轮最终 user/assistant 原文和
    Current State。
  - sidecar 结果继续经过原有 schema、evidence、path、confidence、revision 与 hash 校验，以
    branch-local `pi-roleplay-sidecar` custom entry 原子保存，不注入伪造的 user/assistant 消息。
  - sidecar entry 复用 inline tool 的灰色回合摘要 renderer；无变化时仍保持隐藏。
  - 新增 branch-local `pi-roleplay-audit`，记录 inline 调用、主模型漏调、sidecar
    成功/失败、模型、耗时与结果计数；`/rp status` 展示本轮路径与分支累计指标，
    `/rp inspect audit` 展示聚合数据及最近明细。审计不保存提示词、认证或完整参数。
  - 为全部 `/rp` 静态子命令以及角色、待审项等动态候选增加中文补全描述。
  - `/rp status` 新增剧情账本 revision 与内容计数；`/rp inspect state` 展开事件、状态变化、记忆、
    存疑、来源 revision 和 evidence，并解释 `rev` 的版本语义与 Checkpoint 历史边界。
  - `/rp inspect` 继续使用可编辑报告面板，但打开时将初始光标和视口定位到文档顶部。
  - sidecar 失败才回退为可手工修复的 `incomplete`。footer 改用「剧情未同步」「剧情记录失败」
    等用户可读状态，不再与角色状态并列显示两个 `role:*` 内部标签。

- 5231651: 给 `roleplay_finalize_turn` 注册渲染器，把每轮的机器协议方框换成一行安静的摘要。

  - 此前该工具没有注册任何 renderer，Pi 只能退回 `createResultFallback()`，把
    `formatCommitContent` 产出的 `<roleplay_turn_commit>` XML（含 commit id 与 JSON Pointer）
    原样打印给玩家。该 fallback 不做截断，也完全忽略 `expanded` 参数，所以 `ctrl+o` 折叠对它
    无效——每读一句角色台词，后面就跟着一个看不懂又关不掉的方框。
  - 新增 `renderResult`：有实质变化时只显示 `爱丽丝 · 2 事件 · 1 状态变化 · rev 47` 这样一行
    次要色摘要文本；`ctrl+o` 展开才显示事件 kind/summary、状态变化的 `path → value`、记忆与
    存疑项。（Pi 的工具外壳会在有内容的行前固定插入一个空行，所以屏幕上实际占两行。）
  - 待审变化（relationship、knowledge、goals、questFlags）以告警色单独提示
    `⚠ N 项状态变化待确认（/rp review）`。这是此前用户完全感知不到的信息：不提示，这些变化
    就一直悬在队列里没人处理。被拒变化同样单独提示，并指向 `/rp inspect state`。
  - 工具执行失败时以红色显示失败原因且永不隐藏，覆盖没有启用角色、模型写坏参数、按 `ESC`
    打断等路径。Pi 对失败结果给的 `details` 是 `{}`，若不看 `isError` 会被误判成「无变化」
    而整行隐藏、把错误静音——那会是相对无 renderer 时的回退。
  - 本轮无变化时整行隐藏。此前即使什么都没发生也会输出一个
    `<roleplay_turn_commit status="no-change">` 方框，而角色扮演里绝大多数回合都是无变化的。
    实现上通过 `renderShell: "self"` + 零行组件达成——默认外壳的 hideComponent 分支对注册过
    renderer 的工具永远不成立。
  - 新增 `renderCall`：仅在模型流式输出工具参数期间显示一个安静的占位符，结果落定后让位给
    结果摘要，避免自绘外壳下叠出两行。
  - 进入渲染层的模型与用户自由文本一律剥离 ANSI 转义序列、把控制字符折成空格。内嵌换行会让
    声称的「一行」在终端实际打成两行而 pi-tui 仍按一行记账，导致重绘错位；内嵌 ANSI 则能提前
    终止上色，甚至伪造出看起来像插件自己打出来的告警行。
  - 摘要计算抽成 `src/render.ts` 里的纯函数，不依赖 TUI，可被单元测试直接覆盖；渲染器只负责
    裁剪宽度并上色。上色发生在按显示宽度裁剪之后，避免切坏 ANSI 转义序列。

  仅改变显示层。工具的返回值结构、`formatCommitContent` 的输出、写入会话的 Commit 内容以及
  模型看到的工具结果都没有变化。

## 0.2.0

### Minor Changes

- 77a3b03: 修复发布包中的资产安装失败，并加固运行时降级行为。

  - 修复 `/rp init` 在发布包中必然失败的问题。示例资产路径此前按源码布局解析，构建产物的
    入口层级不同，导致路径落到包外。这是新用户唯一的上手路径。
  - 资产读取失败不再中断整个 `context` 注入。世界书条目读不到时，当前状态视图仍会注入；
    降级信息通过一次通知和 `/rp status` 暴露，不再静默丢失。
  - `formatCurrentView` 超出预算时改为值级截断并标注 `truncated`，不再整键丢弃。此前一段
    较长的 `appearance` 会连带吃掉 `location` 等后续字段的预算，极端情况下输出空状态。
  - `roleplay_finalize_turn` 改为按当前角色同步激活状态，不再在没有角色卡的会话中占用
    系统提示与工具位。
  - `peerDependencies` 下界提升到 `0.80.4`，与实际使用的 `agent_settled` API 一致。此前
    声明支持 0.79.x，但在那些版本上周期性 checkpoint 会静默失效。
  - 演示证据文件移出发布内容，并清除其中的本机路径。

后续版本条目由 Changesets 在版本 PR 中维护。

## 0.1.0

首个 `@xicode` 发行版本。面向 Pi 的 Markdown-first 角色扮演扩展。

### 资产与人格

- Markdown + YAML frontmatter 的角色卡（`CHARACTER.md`）、世界核心（`WORLD.md`）与世界条目
  （`entries/**/*.md`）；格式见 `docs/FORMAT.md`。
- 全局（`~/.pi/agent/roleplay/`）与项目（`<cwd>/.pi/roleplay/`）资产合并，项目同 ID 覆盖全局。
- `before_agent_start` 将角色人格前置到 system prompt，同时保留 Pi 工具与项目指令。
- `/rp init global|project` 安装包内示例资产；`/rp status`、`/rp list`、`/rp use`、`/rp reload`、
  `/rp path` 管理当前角色。
- `/rp inspect [character|world|system]` 在编辑器中预览实际注入内容，不作为聊天消息发送。

### 世界书检索

- 启动时只读取条目 frontmatter，正文命中关键词后才加载。
- 按 `constant`、匹配分与 `priority` 排序，受 `maxEntries` 与 token 近似预算约束。
- 世界正文以非持久化方式注入最新 user 消息，不写入 session。

### 剧情状态

- `roleplay_finalize_turn` inline tool：模型在最终回复后提交 Event、State Change、Memory 与
  Uncertainty；插件执行 schema、evidence 逐字引用、路径白名单、confidence、revision 与 state hash 校验。
- Turn Commit 以 Pi `toolResult.details` 原子持久化，沿当前 session branch 确定性恢复，恢复时不调用 LLM。
- 低风险的 appearance、location、inventory、conditions 自动接受；relationship、knowledge、goals、
  questFlags 进入待审队列，由 `/rp review list|accept|reject|amend` 处理。
- `/rp state set|correct`、`/rp event add`、`/rp memory add` 提供无需 LLM 的显式写入；
  `/rp state correct` 追加带 `correctsCommitId` 与 reason 的 Correction Commit。
- `agent_settled` 检测启用状态工具却未 finalize 的回合并标记 incomplete，`/rp turn repair` 用同一
  evidence validator 补交。
- 角色卡与世界 canon 始终只读，不会被剧情或状态工具回写。

### 会话、分支与恢复

- 单角色 Session Identity：首次角色回复或结构化状态产生后锁定角色，不支持同一 Session 内热切换。
- 服从 Pi 原生 Session Tree：`/tree`、`/fork`、`/clone` 负责历史导航与分叉，插件不另建历史栈。
- 角色模式下从瞬时 LLM context 移除 Pi `branchSummary`，避免被放弃分支污染当前 canon；
  Session entry 本身不修改。
- `/rp checkpoint` 追加带 state hash 的 Checkpoint；每 50 个 Commit 与 compaction 后自动创建
  branch-local Checkpoint，compaction 前后以 character、revision、state hash 三重校验。
- `/rp archive` 生成按正式 ID 去重的完整 Event/Memory 归档，原 Commit 保留。
- 内部持久化 schema v2；Reducer 在内存中迁移 v1 Commit/Review/Turn/Checkpoint，不改写旧 entry。
- `/rp inspect state` 查看当前 Session ID、leaf entry、revision、state、events、memories 与
  reducer diagnostics。

### 模型适配

- DeepSeek V4 `before_provider_request` 适配：仅在最终 payload 的第一条 user 消息末尾追加沉浸指令，
  不写入 session。

### 已知限制

`budgets.contextTotal` 当前只约束世界书，不约束 Current View；自动 sidecar repair、custom entry 的
TUI 卡片渲染、世界条目 `parents`/`recursiveDepth` 递归激活与资产 `spec` 版本校验均未实现。
完整清单见 `docs/SESSION-ROADMAP.md` 的「未完成项汇总」。
