@zhushanwen/pi-rename-session 0.7.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,17 @@
1
1
  # @zhushanwen/pi-rename-session
2
2
 
3
- Pi rename-session 扩展 — session 首个成功 round 完成后,自动生成 slug 式会话标题并落库(`setSessionName`),让 session 列表摆脱默认的日期/序号占位,一眼可辨。
3
+ Pi rename-session 扩展 — 三种触发模式下自动/自主为会话生成 slug 式标题并落库(`setSessionName`),让 session 列表摆脱默认的日期/序号占位,一眼可辨。
4
4
 
5
5
  ## 功能
6
6
 
7
- - session 的**首个成功 round 末**自动生成 slug 式标题(名词/动名词词组,非完整句子;英文小写 kebab-case;跟随对话语言)
8
- - **触发时机**:只在 round 的最终 turn(`stopReason === "stop"`)评估——工具中间轮 / error / aborted / length 轮不评估,error 轮延迟到下一个成功轮命名
9
- - **两段输入**:`[user(首条 prompt), assistant(最终回复)]` 两段信号(各截断 4000 码点),不含 toolCall/toolResult 过程数据,token 成本不随工具数增长
10
- - **独立选模**:标题生成用独立的 `ModelSelector` 配置(仅支持 `ref` 精确指定 provider/model),不搭便车主 session 的昂贵模型
11
- - **可靠性行为**:固定 30s 超时;落库前重查手动名(防覆盖 LLM 调用窗口内的竞态);任何失败静默跳过保留原 label,绝不阻断 agent 循环
7
+ - **三种触发模式**(`mode` 配置,默认 `first-stop` 零行为迁移):
8
+ - `first-stop`(默认):新 session 的**首个成功 round 末**自动生成 slug 式标题
9
+ - `first-prompt`:**首条 user 消息发出即命名**(不等回复)——标题只基于 prompt 本身;pi 事件链 await handler,rename LLM 请求构造性早于首条 assistant 回复开始
10
+ - `agent-tool`:**不自动生成**——注册 `rename_session` 工具,由 agent 在对话中自主改名(零额外 rename LLM 调用);工具改名允许覆盖任何既有名(agent 显式调用语义等同手动 rename)
11
+ - **触发时机(first-stop 路径)**:只在 round 的最终 turn(`stopReason === "stop"`)评估——工具中间轮 / error / aborted / length 轮不评估,error 轮延迟到下一个成功轮命名
12
+ - **两段输入(自动路径)**:`[user(首条 prompt), assistant(最终回复)]` 两段信号(各截断 4000 码点),不含 toolCall/toolResult 过程数据,token 成本不随工具数增长;first-prompt 模式无 assistant 段(降级两条)
13
+ - **独立选模**:标题生成用独立的 `ModelSelector` 配置(仅支持 `ref` 精确指定 provider/model);**未配置(空 ref)时跟随会话主模型**(`ctx.model`),开箱即用
14
+ - **可靠性行为**:固定 30s 超时;落库前重查手动名(防覆盖 LLM 调用窗口内的竞态,agent-tool 工具路径除外——显式改名允许覆盖);任何失败静默跳过保留原 label,绝不阻断 agent 循环
12
15
  - 标题直接 `setSessionName` 落库,不进 session history(不污染对话记录)
13
16
  - **子 session 自动排除**:subagent 子进程 session 不触发 rename(避免给临时产物起名)
14
17
 
@@ -26,6 +29,7 @@ pi install npm:@zhushanwen/pi-rename-session
26
29
  {
27
30
  "enabled": true,
28
31
  "model": { "type": "ref", "ref": "deepseek/deepseek-chat" },
32
+ "mode": "first-stop",
29
33
  "maxTitleLength": 50,
30
34
  "thinkingLevel": "off"
31
35
  }
@@ -34,20 +38,22 @@ pi install npm:@zhushanwen/pi-rename-session
34
38
  | 字段 | 类型 | 默认 | 说明 |
35
39
  |---|---|---|---|
36
40
  | `enabled` | `boolean` | `false` | 自动重命名开关(受 flag 文件覆盖,见下) |
37
- | `model` | `ModelSelector` | `{ "type": "ref", "ref": "" }` | 标题生成模型,仅支持精确指定 `{type:"ref", ref:"provider/modelId"}` |
41
+ | `model` | `ModelSelector` | `{ "type": "ref", "ref": "" }` | 标题生成模型,仅支持精确指定 `{type:"ref", ref:"provider/modelId"}`;**空 ref 跟随会话主模型**(开箱即用),非空但解析失败(配错)才静默跳过 |
42
+ | `mode` | `"first-prompt" \| "first-stop" \| "agent-tool"` | `"first-stop"` | 触发模式(三值互斥):首条请求即命名 / 首个成功 round 末命名(现状)/ agent 自主经 `rename_session` 工具命名。事件面每次事件 live 读(GUI 切换对活跃 session 的自动命名即时生效);工具注册面只在 pi 进程启动加载 extension 时求值一次(切换后已存活 session 的工具清单不回溯,残留工具由 execute 内 live mode 守卫拒绝并给恢复指引) |
38
43
  | `maxTitleLength` | `number` | `50` | 标题最大长度(Unicode 码点数,须正整数) |
39
44
  | `thinkingLevel` | `ModelThinkingLevel` | `"off"` | 标题 LLM 的 thinking 级别(`off` = 不传 reasoning,provider 默认) |
40
45
 
41
- 文件缺失/坏 JSON 返回默认值,不抛错。改完保存即生效(mtime 读时刷新,每个 `turn_end` 重新 load)。
46
+ 文件缺失/坏 JSON 返回默认值,不抛错。改完保存即生效(mtime 读时刷新,每个 `turn_end` / `message_end` 重新 load)。
42
47
 
43
48
  ## 开关优先级(重要)
44
49
 
45
- `enabled` 有四层来源,优先级从高到低(`src/pure.ts` `loadRenameConfig`):
50
+ `enabled` 有三层来源,优先级从高到低(`src/pure.ts` `loadRenameConfig`):
46
51
 
47
- 1. **`PI_RENAME_*` 环境变量**(最高,live 读取):`PI_RENAME_ENABLED=true/false` 显式设置时最终生效,flag 文件也被覆盖(仅显式设置该变量时压制 flag)。适用于容器化部署、CI/CD 等场景。
48
- 2. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):xyz-agent runtime 的开关契约——桌面端 SystemPage 开关、首启默认开启都写这个文件。**xyz-agent 用户请通过桌面端开关或 `/auto-rename` 命令管理,不要手改 JSON 的 `enabled`**(flag 存在时视为开,手改会被覆盖)。
49
- 3. **config 的 `enabled` 字段**(默认 false):环境变量未设 `PI_RENAME_ENABLED` 且 flag 不存在时生效,是原生 pi CLI 用户的开关。
50
- 4. **默认值**(false):以上三层均未设置时。
52
+ 1. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):xyz-agent runtime 的开关契约——桌面端 SystemPage 开关、首启默认开启都写这个文件。**xyz-agent 用户请通过桌面端开关或 `/auto-rename` 命令管理,不要手改 JSON 的 `enabled`**(flag 存在时视为开,手改会被覆盖)。
53
+ 2. **config 的 `enabled` 字段**(默认 false):flag 不存在时生效,是原生 pi CLI 用户的开关。
54
+ 3. **默认值**(false):以上两层均未设置时。
55
+
56
+ > [HISTORICAL] 原最高优先级的 `PI_RENAME_*` 环境变量覆盖层已删(全仓 0 生产 setter,4 键中 3 键从未被用过)——预置该前缀的环境变量不再有任何效果。rename-session 相关精简项裁决见原设计文档 rename-session-three-modes.md 附录 A(已删除,git 可追溯)。
51
57
 
52
58
  ## 命令
53
59
 
@@ -59,13 +65,16 @@ pi install npm:@zhushanwen/pi-rename-session
59
65
 
60
66
  ## 工作原理
61
67
 
62
- 1. **监听 `turn_end`**:pi 每个 iteration 结束都发一次 turn_end(工具中间轮、最终轮、异常轮各一次)。
63
- 2. **开关 + subagent 过滤**:开关关闭(flag 不存在且 `enabled=false`)直接返回;session 路径含 `subagents` 段视为子进程 session,跳过。
64
- 3. **O(1) 快速路径**:只有 `stopReason === "stop"` turn 才继续——**rename 一定在 round 末触发**(最终 turn message 即最终 assistant 回复,final text 零遍历可得),不会在首个 iteration 中途命名。
65
- 4. **首 round 判定**:session entries 中成功(stop)assistant 回复数 === 1 才触发(后续 round 不重复 rename;error 轮的 assistant 回复不计数,延迟到下一个成功轮)。
66
- 5. **两段输入构造**:`[user(首条 prompt), assistant(最终回复文本), user(instruction)]`——任务意图 + 轮次结论恰好与标题语义对齐,不含 toolCall/toolResult 过程数据;两段文本各截断 4000 Unicode 码点(中文场景约 4k token/段,成本可控且不随工具数增长)。assistant 段为空(纯工具结束的 round)时降级为两条。
67
- 6. **LLM 生成 slug 标题**:独立精简 system prompt(<200 字符的 slug 词组约束,非整个 agent prompt)+ instruction(正反例 few-shot,作为追加 user message 发送)+ `tools: []` + `maxTokens: 64`,按 `config.model` 独立选模发起一次 LLM 调用;固定 30s 超时(超时归一为失败,走静默跳过)。
68
+ 三个入口共用同一条落库管道(`callRenameLLM` 防覆盖重查 `setSessionName`),按 `mode` 分派:
69
+
70
+ 1. **入口分派(事件面 live 读 mode)**:`message_end` 入口(first-prompt)过滤 `role === "user"`;`turn_end` 入口(first-stop)按下方流程;`agent-tool` 模式不激活自动命名逻辑(两 handler 常驻注册、命中即静默返回),改为 extension load 时注册 `rename_session` 工具。
71
+ 2. **开关 + subagent 过滤**(自动路径共用):开关关闭(flag 不存在且 `enabled=false`)直接返回;session 路径含 `subagents` 段视为子进程 session,跳过。
72
+ 3. **O(1) 快速路径(first-stop)**:只有 `stopReason === "stop"` 的 turn 才继续——**rename 一定在 round 末触发**(最终 turn 的 message 即最终 assistant 回复,final text 零遍历可得),不会在首个 iteration 中途命名。first-prompt 入口的对应守卫:首条 user 判定 = handler 执行时 `getEntries()` user message 计数 === 0(pi extension handler 先于该条 message 的 entries append 执行,本条即 session 首条 user;steering/follow-up 消息到达时首条已入 entries,天然不重复触发)。
73
+ 4. **首 round 判定(first-stop)**:session entries 中成功(stop)assistant 回复数 === 1 才触发(后续 round 不重复 rename;error 轮的 assistant 回复不计数,延迟到下一个成功轮)。
74
+ 5. **两段输入构造**:`[user(首条 prompt), assistant(最终回复文本), user(instruction)]`——任务意图 + 轮次结论恰好与标题语义对齐,不含 toolCall/toolResult 过程数据;两段文本各截断 4000 Unicode 码点(中文场景约 4k token/段,成本可控且不随工具数增长)。assistant 段为空(first-prompt 模式 / 纯工具结束的 round)时降级为两条——标题主信号本就是 prompt。
75
+ 6. **LLM 生成 slug 标题**:独立精简 system prompt(<200 字符的 slug 词组约束,非整个 agent prompt)+ instruction(正反例 few-shot,作为追加 user message 发送)+ `tools: []` + `maxTokens: 64`,按 `config.model` 独立选模(空 ref 跟随会话主模型)发起一次 LLM 调用;固定 30s 超时(超时归一为失败,走静默跳过)。
68
76
  7. **落库**:cleanTitle 清洗(去首尾引号 / markdown 强调标记 / 句尾标点、空白归一、按码点截断)后 `setSessionName` 写入。**落库前重查** `pi.getSessionName()`——LLM 调用窗口(2-30s)内用户手动命名的竞态由此兜住,已有名则 skip 不覆盖。**不**写入 session history,对话记录不受影响。
77
+ 8. **`rename_session` 工具(agent-tool)**:execute 内守卫链依次为——先拒绝 subagent session(子会话是临时产物,subagent 排除守卫覆盖 message_end / turn_end / 工具三入口,C-ext-21);再 live 读 config 守卫——mode 已切走时拒绝(错误文案含恢复指引:切回 agent-tool / 手动改名);title 经 cleanTitle 清洗,空值拒绝;非空直接 `setSessionName` **不走防覆盖守卫**(agent 显式调用 = 代表用户的意图,允许覆盖任何既有名,含自动名/语义名)。
69
78
 
70
79
  ### 可靠性行为
71
80
 
@@ -76,9 +85,12 @@ rename 是 best-effort 副作用,任何失败静默跳过、绝不阻断 agent
76
85
  | 中间 iteration(工具轮)的 turn_end | skip(stopReason=toolUse),round 末才评估 |
77
86
  | error / aborted / length 轮 | skip(stopReason=<X>),error 上下文不用于命名,延迟到下一个成功轮 |
78
87
  | 非 round-1(成功回复数 ≠ 1) | skip(count=N),一次性语义 |
88
+ | 非 first-prompt 模式收到 user message_end | skip(mode=<mode>),事件面 live 分派 |
89
+ | 非 session 首条 user message(first-prompt) | skip(userCount=N),一次性语义 |
79
90
  | LLM 调用失败 / 超过 30s | 记录失败日志,保留原 label(不做重试;用户可手动命名) |
80
91
  | 标题清洗后为空 | skip(title empty) |
81
- | 落库前发现已有手动名 | skip(name exists),不覆盖 |
92
+ | 落库前发现已有手动名(自动路径) | skip(name exists),不覆盖 |
93
+ | `rename_session` 工具被调用但 mode 已切走 | execute 守卫拒绝(isError 文案含恢复指引) |
82
94
  | 标题模型不可用 | 记日志静默跳过 |
83
95
 
84
96
  ### 日志通道(appendEntry 常开 + debug 文件日志)
@@ -88,28 +100,32 @@ rename 是 best-effort 副作用,任何失败静默跳过、绝不阻断 agent
88
100
  - **appendEntry(session entry,常开)**:`logger.warn` / `logger.error` 写入 session JSONL 的 custom entry(`type: "custom"`、`customType: "rename-session:log"`),不进 LLM 上下文、不显 TUI。`data.message` 由 logger 自动补 `[rename-session]` 前缀,格式为 `msg + 结构化 data`(error 等详情在 entry `data.data` 字段,不冒号拼接进 message)。
89
101
  - **文件日志(`XYZ_AGENT_DEBUG=1` 时)**:`<agentDir>/logs/rename-session-<date>.log`。
90
102
 
91
- 下列 8 条 debug 日志(仅 `XYZ_AGENT_DEBUG=1` 时经 `logger.warn` 发出)的**文案字面值是 E2E 断言硬契约**(断言对象 = appendEntry entry 的 message 内容;变更须同步 `e2e/` 场景脚本与单测):
103
+ 下列 debug 日志(仅 `XYZ_AGENT_DEBUG=1` 时经 `logger.warn` 发出)的**文案字面值是 E2E 断言硬契约**(断言对象 = appendEntry entry 的 message 内容;变更须同步 `e2e/` 场景脚本与单测):
92
104
 
93
105
  | # | 日志 | 发出侧 | 含义 |
94
106
  |---|---|---|---|
95
- | 1 | `skip: stopReason=<r>` | handler(带 `turnIndex=<n>`) | 快速路径拦截(toolUse/error/aborted/length) |
96
- | 2 | `skip: count=<n>` | handler(带 turnIndex) | 非首成功 round |
97
- | 3 | `skip: name exists` | handler(带 turnIndex) | 落库前防覆盖命中 |
98
- | 4 | `renamed to "<title>"` | handler(带 turnIndex | 标题生成并落库成功(index.ts `.then()` `setSessionName` 之后打出;竞态命中时只打 #3,无此条) |
107
+ | 1 | `skip: stopReason=<r>` | turn_end handler(带 `turnIndex=<n>`) | 快速路径拦截(toolUse/error/aborted/length) |
108
+ | 2 | `skip: count=<n>` | turn_end handler(带 turnIndex) | 非首成功 round |
109
+ | 3 | `skip: name exists` | handler `.then()`(turn_end 原样 / message_end 带 `firstPrompt` 前缀) | 落库前防覆盖命中 |
110
+ | 4 | `renamed to "<title>"` | turn_end handler(带 turnIndex)/ message_end handler(`firstPrompt renamed to "..."`) | 标题生成并落库成功(`setSessionName` 之后打出;竞态命中时只打 #3,无此条) |
99
111
  | 5 | `skip: no user prompt` | llm | session 无 user message(理论不发生) |
100
112
  | 6 | `skip: title empty` | llm | cleanTitle 清洗后为空 |
101
113
  | 7 | `LLM request messages: <JSON>` | llm | 传给 callLLM 的 messages 内省(role + text 的 head 200 码点 + … + tail 100 码点预览,截断单位与 truncateForTitle 统一为 Unicode 码点),在请求发起前打出 |
102
114
  | 8 | `rename with model <provider>/<id>` | llm | 成功路径模型记录(原常开日志;为避免污染 Pi 输入框改为 debug 输出,带 t=ISO 时间戳) |
115
+ | 9 | `skip: mode=<mode>` | turn_end handler(`turnIndex=<n>`)/ message_end handler(`firstPrompt skip: mode=...`) | mode 分派拦截:本入口对当前 mode 不负责(事件面 live 读,切走即停) |
116
+ | 10 | `skip: userCount=<n>` | message_end handler(`firstPrompt skip: userCount=...`) | 非 session 首条 user message(first-prompt 一次性语义) |
117
+ | 11 | `skip: empty prompt` | message_end handler(`firstPrompt skip: empty prompt`) | user message 载荷无文本(理论不发生) |
118
+ | 12 | `tool renamed to "<title>"` | rename_session 工具 execute | agent-tool 路径落库成功(`setSessionName` 之后打出,无防覆盖前缀) |
103
119
 
104
120
  另有两条**非 debug 常开**日志(无条件经 `logger.warn` 落 appendEntry entry):`rename LLM call failed`(`logger.warn(msg, { error })` 形态,error 详情在结构化 data 字段;超时时 llm-shared callLLM 内部的 extractText 将空错误文本归一为 `unknown error`——extension 侧 `result.error ?? "unknown error"` 只兜 null/undefined,空串兜底发生在 llm-shared 层)、`model not available, skipping`(选模失败)。handler 侧日志 message 带 `t=<ISO时间>` 与 `turnIndex`;llm 侧带 `t=<ISO时间>`、无 turnIndex。
105
121
 
106
122
  ## E2E 验收
107
123
 
108
- E2E 是本地人工触发的验收资产(真实 pi 进程 + 真实模型,不进常规 CI),覆盖五个场景:A1 触发时机证据链(流序/内容匹配/负向/行序/结构五重——结构断言 = 仅一条 LLM request + [user,assistant,user] 三元组)、A2 slug 风格 ×3、A3 防覆盖(静态/竞态/一次性)、A4 error 轮两阶段(`--session` 续跑)、A5 超时兜底(hang provider)。
124
+ E2E 是本地人工触发的验收资产(真实 pi 进程 + 真实模型,不进常规 CI),覆盖七个场景:A1 触发时机证据链(流序/内容匹配/负向/行序/结构五重——结构断言 = 仅一条 LLM request + [user,assistant,user] 三元组)、A2 slug 风格 ×3、A3 防覆盖(静态/竞态/一次性)、A4 error 轮两阶段(`--session` 续跑)、A5 超时兜底(hang provider)、A6 first-prompt 模式(触发时点 = 首条 assistant `message_start` 前 rename LLM 请求已发出 + 标题仅基于 prompt + 后续 round 不再改名;完成序不做 gate)、A7 agent-tool 模式(工具改名即时落库覆盖既有名 + first-stop 对照进程零 `rename_session` toolCall entry)。
109
125
 
110
126
  ```bash
111
127
  cd extensions/universal/rename-session
112
- node e2e/run-a1.mjs # 单场景独立可跑(run-a1 ~ run-a5
128
+ node e2e/run-a1.mjs # 单场景独立可跑(run-a1 ~ run-a7
113
129
  node e2e/run-all.mjs # 顺序全跑:单场景失败不阻断后续,汇总表 + exit code(任一失败(含 KEBAB_NON_COMPLIANT)→ 1)
114
130
  ```
115
131
 
@@ -135,14 +151,14 @@ rename-session/
135
151
  │ ├── README.md # 探针结论 + 运行指南
136
152
  │ ├── harness.mjs # pi 进程/RPC/交错时间轴/断言纯函数
137
153
  │ ├── harness.test.mjs # 断言纯函数单测(随 vitest 跑)
138
- │ ├── run-a1.mjs ~ run-a5.mjs # A1-A5 场景脚本
154
+ │ ├── run-a1.mjs ~ run-a7.mjs # A1-A7 场景脚本
139
155
  │ ├── run-all.mjs # 总结 runner(汇总 + exit code)
140
- │ ├── scenarios.test.mjs # A1-A5 的 vitest 包装(仅 e2e config 收录,不进常规 CI)
156
+ │ ├── scenarios.test.mjs # A1-A7 的 vitest 包装(仅 e2e config 收录,不进常规 CI)
141
157
  │ ├── vitest.e2e.config.ts # E2E 专用 vitest 入口(--config 显式指定,include 含 scenarios.test.mjs)
142
158
  │ └── RESULTS.md # A2 标题记录 + 人工抽查表
143
159
  ├── skills/rename-session-ext-config/SKILL.md # 配置指南(pi 内 agent 可发现)
144
160
  └── src/
145
- ├── index.ts # 工厂入口(注册 turn_end handler + /auto-rename 命令)
161
+ ├── index.ts # 工厂入口(message_end/turn_end handler 按模式分派 + rename_session 工具注册 + /auto-rename 命令)
146
162
  ├── commands.ts # /auto-rename on|off|status 命令(enable/disable 别名)
147
163
  ├── llm.ts # callRenameLLM / 两段输入构造 / debug 内省 / 超时
148
164
  ├── pure.ts # 纯函数(配置 / 首轮计数 / cleanTitle)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-rename-session",
3
- "version": "0.7.0",
3
+ "version": "0.9.1",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "xyz-agent": {
@@ -14,6 +14,7 @@
14
14
  "type": "ref",
15
15
  "ref": ""
16
16
  },
17
+ "mode": "first-stop",
17
18
  "maxTitleLength": 50,
18
19
  "thinkingLevel": "off"
19
20
  }
@@ -32,8 +33,9 @@
32
33
  "pi-package"
33
34
  ],
34
35
  "dependencies": {
35
- "@zhushanwen/pi-extension-logger": "0.4.1",
36
- "@zhushanwen/pi-llm-shared": "0.6.0"
36
+ "@zhushanwen/pi-ext-guards": "0.4.0",
37
+ "@zhushanwen/pi-extension-logger": "0.6.0",
38
+ "@zhushanwen/pi-llm-shared": "0.8.0"
37
39
  },
38
40
  "devDependencies": {
39
41
  "@vitest/coverage-v8": "^4.1.9",
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: rename-session-ext-config
3
- description: "配置 @zhushanwen/pi-rename-session(会话自动重命名)时加载。含配置文件路径、RenameSessionConfig schema、ModelSelector ref 精确指定、触发时机(首 turn)、maxTitleLength 约束、默认值、示例、生效时机、开关优先级(flag 覆盖)。触发词:配置重命名、rename 配置、自动标题、rename-session config、auto-rename 设置、首 turn、触发时机、开关不生效。"
3
+ description: "配置 @zhushanwen/pi-rename-session(会话自动重命名)时加载。含配置文件路径、RenameSessionConfig schema、ModelSelector ref 精确指定、触发模式三选一(first-prompt 首条请求 / first-stop 首 round 末 / agent-tool 工具自主)、maxTitleLength 约束、默认值、示例、生效时机、开关优先级(flag 覆盖)。触发词:配置重命名、rename 配置、自动标题、rename-session config、auto-rename 设置、触发时机、首 turn、first-prompt、agent-tool、开关不生效。"
4
4
  ---
5
5
 
6
6
  # rename-session 配置指南
7
7
 
8
- > @zhushanwen/pi-rename-session:新 session 首个成功 round 完成后,用独立小模型生成会话标题(不搭便车主 session 的昂贵模型)。
8
+ > @zhushanwen/pi-rename-session:按触发模式为会话生成标题——自动模式(first-prompt / first-stop)用独立小模型或会话主模型生成,agent-tool 模式注册 `rename_session` 工具由 agent 自主改名。
9
9
 
10
10
  ## 配置文件位置
11
11
 
@@ -15,23 +15,34 @@ description: "配置 @zhushanwen/pi-rename-session(会话自动重命名)时
15
15
  - 走 llm-shared 泛型 config(config/ 子目录 + getAgentDir 派生 + mtime+size 缓存 + 原子写)
16
16
  - 文件缺失/坏 JSON 返回默认值,不抛错
17
17
 
18
- ## 何时触发重命名(重要)
18
+ ## 触发模式(重要)
19
19
 
20
- **仅在新 session 的首个成功 round 完成后触发一次**(判定条件:round 最终 turn 的 `stopReason === "stop"`,且 session 内成功(stop)assistant 回复数 === 1)。
20
+ `mode` 三值互斥,默认 `first-stop`(现状行为,零迁移):
21
21
 
22
- - 已存在的多 turn session **不会回溯重命名**——开启 `enabled` 后只对之后新建的 session 生效
23
- - 每个 session 最多重命名一次(首个成功 round 后不再触发)
24
- - 工具中间轮(`stopReason === "toolUse"`)不评估;error/aborted/length 轮延迟到下一个成功轮再命名
25
- - 若首个成功 round LLM 调用失败,静默跳过保留原标题,不重试
22
+ | mode | 触发时机 | 标题来源 |
23
+ |---|---|---|
24
+ | `first-prompt` | 首条 user 消息发出即命名(不等回复) | 只基于 prompt 本身的 LLM 生成 |
25
+ | `first-stop`(默认) | 新 session 首个成功 round 末(round 最终 turn `stopReason === "stop"` 且成功 assistant 回复数 === 1) | prompt + 最终回复的 LLM 生成 |
26
+ | `agent-tool` | 不自动生成——注册 `rename_session` 工具,agent 在对话中自主调用 | agent 给定的标题(零额外 rename LLM 调用) |
27
+
28
+ 行为边界:
29
+
30
+ - **自动模式一次性语义**:每个 session 最多自动重命名一次;已过触发窗口的存量 session 不会回溯重命名——开启后只对之后新建的 session 生效
31
+ - 工具中间轮(`stopReason === "toolUse"`)不评估;error/aborted/length 轮延迟到下一个成功轮再命名(first-stop)
32
+ - 首个成功 round 时 LLM 调用失败 → 静默跳过保留原标题,不重试(first-stop 有 error 轮延迟语义;first-prompt 触发窗口唯一,错过即无自动机会)
33
+ - **mode 求值时点**:事件面(自动命名分派)每次事件 live 读——切换对活跃 session 的自动命名即时生效;`rename_session` 工具注册面只在 pi 进程启动加载 extension 时求值一次——切换后已存活 session 的工具清单不回溯,残留工具被调用时 execute 内 live 守卫拒绝(错误文案含恢复指引)
34
+ - `rename_session` 工具改名**不走防覆盖守卫**——agent 显式调用等同手动 rename,允许覆盖任何既有名(含自动名/语义名)
35
+ - steering/follow-up 队列消息不满足「session 首条 user」判定,不会误触发(first-prompt)
26
36
 
27
- > 改完配置「没看到 session 被重命名」的常见原因:当前 session 已过首个成功 round。新建一个 session 测试。
37
+ > 改完配置「没看到 session 被重命名」的常见原因:当前 session 已过触发窗口(首条 user 已发出 / 首个成功 round 已完成)。新建一个 session 测试。
28
38
 
29
39
  ## Schema
30
40
 
31
41
  ```ts
32
42
  interface RenameSessionConfig {
33
43
  enabled: boolean; // 自动重命名开关,默认 false
34
- model: ModelSelector; // 标题生成模型,默认 { type: "ref", ref: "" }(未配置则解析不到,跳过 rename)
44
+ model: ModelSelector; // 标题生成模型,默认 { type: "ref", ref: "" }(空 ref 跟随会话主模型)
45
+ mode: "first-prompt" | "first-stop" | "agent-tool"; // 触发模式,默认 "first-stop"
35
46
  maxTitleLength: number; // 标题最大长度(Unicode 码点),默认 50
36
47
  thinkingLevel: ModelThinkingLevel; // 标题 LLM 的 thinking 级别,默认 "off"
37
48
  }
@@ -45,6 +56,8 @@ interface RenameSessionConfig {
45
56
 
46
57
  不再支持 `fallback` / `available` / `scoped`。需要自动选模时请在调用方(如 permission 的 `"auto"`)自行基于 `ctx.modelRegistry` 实现。
47
58
 
59
+ **空 ref 语义(重要)**:`ref: ""` = 未配置 → **跟随会话主模型**(`ctx.model`,开箱即用);非空但解析失败(无效 provider/model)才静默跳过 rename(日志 `model not available, skipping`)。
60
+
48
61
  ### maxTitleLength 约束
49
62
 
50
63
  必须是**正整数**(`Number.isInteger && > 0`)。传小数(`50.5`)、0、负数、非数字都会回落默认值 50。截断按 Unicode 码点(不会截断多字节字符)。
@@ -56,57 +69,44 @@ interface RenameSessionConfig {
56
69
  ## 默认值
57
70
 
58
71
  ```json
59
- { "enabled": false, "model": { "type": "ref", "ref": "" }, "maxTitleLength": 50, "thinkingLevel": "off" }
60
- ```
61
-
62
- ## 环境变量覆盖(容器化部署/CI-CD)
63
-
64
- 支持通过环境变量覆盖配置,适用于容器化部署、CI/CD 等场景。环境变量优先级最高,覆盖配置文件和 flag 文件。
65
-
66
- | 环境变量 | 说明 | 示例值 |
67
- |---|---|---|
68
- | `PI_RENAME_ENABLED` | 自动重命名开关 | `true` / `false` |
69
- | `PI_RENAME_MODEL` | 模型引用(`provider/model` 格式,映射为 `{type:"ref", ref:"provider/model"}`) | `deepseek/chat` |
70
- | `PI_RENAME_MAX_TITLE_LENGTH` | 标题最大长度(正整数) | `30` |
71
- | `PI_RENAME_THINKING_LEVEL` | thinking 级别 | `minimal` / `high` |
72
-
73
- **注意事项:**
74
- - 环境变量值无效时静默忽略,回落到配置文件或默认值
75
- - 环境变量优先级最高,即使 flag 文件存在,`PI_RENAME_ENABLED=false` 也会禁用重命名
76
- - 环境变量每次调用时 live 读取,修改后无需重启进程(下一个 `turn_end` 生效)
77
- - 环境变量只支持简单 `provider/model` 覆盖;ModelSelector 本身仅支持 ref 精确指定
78
-
79
- **使用示例:**
80
- ```bash
81
- # 容器化部署:启用重命名 + 指定便宜模型
82
- PI_RENAME_ENABLED=true PI_RENAME_MODEL=deepseek/chat node app.js
83
-
84
- # CI/CD 禁用重命名
85
- PI_RENAME_ENABLED=false npm test
86
-
87
- # 开发环境:使用轻量 thinking
88
- PI_RENAME_ENABLED=true PI_RENAME_THINKING_LEVEL=minimal npm run dev
72
+ { "enabled": false, "model": { "type": "ref", "ref": "" }, "mode": "first-stop", "maxTitleLength": 50, "thinkingLevel": "off" }
89
73
  ```
90
74
 
91
75
  ## 配置示例
92
76
 
93
- 固定用便宜模型生成标题:
77
+ 固定用便宜模型生成标题(first-stop):
94
78
  ```json
95
79
  {
96
80
  "enabled": true,
97
81
  "model": { "type": "ref", "ref": "deepseek/deepseek-chat" },
82
+ "mode": "first-stop",
98
83
  "maxTitleLength": 50,
99
84
  "thinkingLevel": "off"
100
85
  }
101
86
  ```
102
87
 
103
- > 必须精确指定 `ref`;未配置或 ref 为空时解析不到模型,rename 会静默跳过。
88
+ 首条请求即命名(不指定模型,跟随会话主模型):
89
+ ```json
90
+ {
91
+ "enabled": true,
92
+ "model": { "type": "ref", "ref": "" },
93
+ "mode": "first-prompt"
94
+ }
95
+ ```
96
+
97
+ 交给 agent 自主命名:
98
+ ```json
99
+ {
100
+ "enabled": true,
101
+ "mode": "agent-tool"
102
+ }
103
+ ```
104
104
 
105
- ## 配置生效时机
105
+ > [HISTORICAL] 原最高优先级的 `PI_RENAME_*` 环境变量覆盖层已删(全仓 0 生产 setter)——预置该前缀的环境变量不再有任何效果。rename-session 相关精简项裁决见原设计文档 rename-session-three-modes.md 附录 A(已删除,git 可追溯)。
106
106
 
107
- 配置走 mtime+size 读时刷新(每个 `turn_end` 都重新 load)。改完 JSON 保存后,**下一个新 session 的首个成功 round** 即按新配置触发(已过首个成功 round 的 session 不受影响)。
107
+ ## 配置生效时机
108
108
 
109
- **环境变量生效时机:** 环境变量每次调用时 live 读取(`process.env`),修改后无需重启进程,下一个 `turn_end` 即按新环境变量生效。
109
+ 配置走 mtime+size 读时刷新(每个 `turn_end` / `message_end` 都重新 load)。改完 JSON 保存后,**下一个新 session 的触发窗口** 即按新配置生效(已过触发窗口的 session 不受影响)。`mode` 切换的存量 session 边界见上文「触发模式」行为边界。
110
110
 
111
111
  ## 排除项
112
112
 
@@ -114,19 +114,18 @@ subagent 子进程 session 不重命名(`isSubagentSession` 判定 session 目
114
114
 
115
115
  ## 开关优先级(重要)
116
116
 
117
- `enabled` 有四层来源,优先级从高到低(`src/pure.ts` `loadRenameConfig`):
117
+ `enabled` 有三层来源,优先级从高到低(`src/pure.ts` `loadRenameConfig`):
118
118
 
119
- 1. **环境变量 `PI_RENAME_ENABLED`**(最高优先级):适用于容器化部署、CI/CD 等场景。`true`/`false` 字符串,live 读取。显式设置时覆盖 flag 文件和配置文件(`PI_RENAME_ENABLED=false` 即使 flag 存在也禁用重命名)。
120
- 2. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):这是 xyz-agent runtime 的开关契约(SystemPage 开关 / 首启默认开启都写这个文件,live 检查每次 turn_end 生效)。**xyz-agent 用户不要手改 JSON 里的 enabled**——桌面端的开关状态存在 flag 文件里,手改 JSON 会被 flag 覆盖(环境变量未显式设置 `PI_RENAME_ENABLED` 时,flag 存在即视为开)。
121
- 3. **config 的 `enabled` 字段**(默认 false):环境变量未设置且 flag 不存在时生效,是原生 pi CLI 用户的开关(手改 JSON 或 `/auto-rename on|off` 命令)。
122
- 4. **默认值**(false):以上三层均未设置时。
119
+ 1. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):这是 xyz-agent runtime 的开关契约(SystemPage 开关 / 首启默认开启都写这个文件,live 检查每次事件生效)。**xyz-agent 用户不要手改 JSON 里的 enabled**——桌面端的开关状态存在 flag 文件里,手改 JSON 会被 flag 覆盖(flag 存在即视为开)。
120
+ 2. **config 的 `enabled` 字段**(默认 false):flag 不存在时生效,是原生 pi CLI 用户的开关(手改 JSON `/auto-rename on|off` 命令)。
121
+ 3. **默认值**(false):以上两层均未设置时。
123
122
 
124
123
  `/auto-rename on` 只创建 flag;`/auto-rename off` 写 config.enabled=false + 删 flag(双写同步)。旧版升级用户:旧 flag 文件保留不动,仍作为开关生效,无需任何迁移操作。
125
124
 
126
- ## LLM 调用特性
125
+ ## LLM 调用特性(自动模式)
127
126
 
128
- - 独立 model(不搭便车主 session 模型)
127
+ - 模型:空 ref 跟随会话主模型;非空 ref 独立选模(不搭便车主 session 模型)
129
128
  - 独立精简 system prompt(<200 字符的 slug 词组约束 + 正反例 few-shot,非整个 agent prompt)
130
129
  - 不传 tools(纯文本标题生成)
131
- - fire-and-forget(不阻塞 turn_end handler)
130
+ - fire-and-forget(不阻塞 turn_end / message_end handler)
132
131
  - model 不可用 → 静默跳过(日志 `[rename-session] model not available, skipping`),不阻断主对话