| 路径 | 内容 |
|---|---|
| docs/security.md | §1 第 1/2 层各一段 R-TITLE 补记:纯函数与数据面只读两条约束的唯一例外及其边界(规则 9,已先行) |
| ROUNDS.md | 功能边界第二次修订(规则 8 例外)· 进度表新行 · 「R-TITLE」拆解段 |
| apps/api/agent/migrations/009_session_title.up.sql | sessions.title_source 列 · NOLOGIN 角色 agent_title + 列级授权 · tool_config 种 session_rename(enabled = TRUE) |
| apps/api/agent/title-db.ts | 标题写通道:SET LOCAL ROLE agent_title + statement_timeout,与 ro-db.ts 同构但不是 READ ONLY 事务 |
| apps/api/agent/tools.ts | 会话绑定工具注册表 SESSION_TOOL_REGISTRY + session_rename 实现 + sanitizeTitle + buildSessionTools() |
| apps/api/agent/store.ts | sessionNeedsTitle()(冷启动判据) |
| apps/api/agent/runtime.ts | 冷启动按「是否还需要命名」决定注册集合;系统提示按实际注册到的工具生成 |
| apps/api/agent/title.test.ts | 本轮验收项本身:列级授权 · 只命名一次 · sanitize · 闭包绑定 |
| apps/api/agent/sandbox.test.ts | 随注册表结构调整(第 1 层用例覆盖新增的会话绑定工具) |
不交付:前端任何改动(标题在会话列表里本来就渲染,每轮结束的 refreshSessions() 会带回新标题)。
| # | 检查 | 命令 / 期望 | 结果 |
|---|---|---|---|
| 1 | 新会话首轮拿到模型起的标题 | 开一轮真实对话,GET /agent/sessions 的 title 不等于首条消息首行截断 | ✅ 生产实跑通过(R11,2026-09-02):title_source=agent,标题由模型调 session_rename 起,见 round-11 任务卡「全链路验收」;本机验不了(本地库没有 LLM provider,/agent/ask 一律 503) |
| 2 | 命名过程在右侧视图可见 | 一轮命名里 tool_execution_start / tool_call / tool_result / tool_execution_end 四个事件到达观测者,toolName = session_rename,入参预览就是那个标题 | ✅ faux 探针实跑,四个事件全到(事件清单见「实测」);agent/events.ts 的白名单本就含 toolName 与 argsPreview / inputPreview |
| 3 | 一个会话只命名一次 | 已命名的会话冷启动不再注册该工具;直接写库也改不动(WHERE title_source='derived' 命中 0 行) | ✅ title.test.ts「只命名一次」段 |
| 4 | 写面被 Postgres 限死 | 以 agent_title 改 sessions.last_active_at / visitor_id / 写 messages / DELETE FROM sessions / 读 llm_config / 读 notes_chapters / SELECT created_at 全部 permission denied;只有改 title / title_source 成功 | ✅ title.test.ts「agent_title 角色」段 |
| 5 | 会话 id 不可由模型指定 | 工具 parameters 只有 title 且 additionalProperties: false;给会话 A 构建的工具即便被塞进 sessionId: B 也只改 A | ✅ title.test.ts「工具行为」段 |
| 6 | sanitize 生效 | 多行 / 引号 / 尾部标点(半角与全角)/ 引号与标点互相嵌套 / 控制字符 / 超长 / 全空白 逐条断言 | ✅ title.test.ts「sanitizeTitle」段(互相嵌套那条是探针抓出来的真 bug,见「实测」) |
| 7 | 可关停 | 经 MCP tool_config_set session_rename enabled=false 后新会话不注册该工具,标题回落首行截断 | ✅ 逻辑侧用例(loadEnabledTools 丢弃 / buildSessionTools 不产出)+ 所有者在生产经 MCP 实调验过(2026-09-03 确认) |
| 8 | 无回归 | dev.ps1 check + dev.ps1 test 全绿;dev.ps1 gen 后 api-client.ts 无接口面变化 | ✅ 12 文件 211 用例全过;check 通过;gen 只有已知的 app slug 噪音(BACKLOG R3/R6),接口面零变化,已还原该文件 |
第 1 轮(1 条 findings:0×P1 · 1×P2)
| # | 级别 | findings | 处置 |
|---|---|---|---|
| 1 | P2 | 命名成功后没从活着的会话里撤掉 session_rename:title_source 已是 agent,但会话手里的工具白名单与「本次会话还没有标题」这句系统提示都在 createAgentSession 时定格,热路径又因配置指纹没变而直接复用该会话、不再走 sessionNeedsTitle。模型可能每轮重复调用,白花 provider 往返、token 与轨迹事件(尽管 SQL 最终拒绝改名) | 采纳,按最小改动整改 |
整改内容与为什么不按 findings 的字面做:
第 2 轮(1 条 findings:0×P1 · 1×P2)
| # | 级别 | findings | 处置 |
|---|---|---|---|
| 1 | P2 | sanitizeTitle 只去尾部那一小撮标点,模型给一串 : / —— / … / () 时它们都不在字符类里,于是「纯标点」原样活下来、被当成合法标题写库并把 title_source 翻成 agent;而命名只有一次,那个没法用的标题就永久钉在会话列表上 —— 与工具自己「纯标点要拒绝」的契约相抵 | 采纳整改 |
整改内容(仍是最小改动:改判断,不新增机制):
第 3 轮(1 条 findings:1×P1)
| # | 级别 | findings | 处置 |
|---|---|---|---|
| 1 | P1 | 写一次的命名被钉在第一轮,在「hi 开场」场景上失效:首句是 hi / 你好 时模型手里没有任何任务信息,而系统提示逼它当场命名 —— 得到「打招呼」这种标题,且因为只能命名一次,之后访客说出真正来意的那一轮修不回来 | 所有者裁定不采纳(2026-09-02);我先按「措辞整改」做了一版,所有者看后要求回滚 |
处置过程要如实记:
第 4 轮(1 条 findings:1×P1)—— 随第 3 轮回滚作废
| # | 级别 | findings | 处置 |
|---|---|---|---|
| 1 | P1 | 工具自己的 description 里还留着「整个会话只需在第一轮调用一次」,与第 3 轮改过的系统提示互相打架 | 作废:它只因第 3 轮那版整改而存在;回滚后 description / promptSnippet / 系统提示三处又回到同一口径(第一轮命名),已恢复原文 |
第 5 轮(零 findings):在第 4 轮状态上发起、回滚前已在跑。原文:「No actionable correctness, security, or Encore-specific defects were identified in the current changes. The TypeScript compilation check also completed successfully.」它扫的是回滚前的代码;回滚后的代码 = 第 3 轮扫过的那份。两份状态各被全量扫过一次, 除所有者裁定不采纳的那条 P1 外均零 findings。
同一验收项针对性整改后连续 2 次验证仍不过 → 写 rounds/round-title/BLOCKED.md,停下呼人。禁止放宽验收标准自我通过。
本轮的止损很便宜:回退面是一条迁移加一个工具;线上真出问题时经 MCP tool_config_set session_rename enabled=false 即可当场停用,不需要发版、不需要回滚镜像。
用 pi-ai 的 faux provider 扮演模型发一次 session_rename 的 tool call —— 全程不需要 LLM key。 到达观测者的四个事件(顺序即实测顺序):
库里:title = 排查 SSE 断流、title_source = agent。 右侧面板要的东西齐了 —— agent/events.ts 的白名单本就带 toolName 与 argsPreview/inputPreview, 所以 Timeline 上会出现一行 tool_call · session_rename,入参预览就是那个标题,与截图里 tool_call · notes_get_chapte 是同一种呈现。
探针没有提交:它的 import 走的是 pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/faux.js 这条深路径(穿过另一个包的私有 node_modules),pi 升级时会以「整个测试文件 import 失败」的方式炸掉整个套件 —— 与 R7 的裁定一致。要复现照 rounds/round-07/round-07.md「闭环验证怎么复现」那一节重建。
结果(2026-09-03):R11 跳过了 130,两条都在生产验掉——#1 R11 当日(title_source=agent),#7 所有者确认。本段保留为交接记录。
沿用 R-VISITOR 的先例(「本机验不了的在 130 上验掉」):