@zhushanwen/pi-rename-session 0.5.4 → 0.6.0

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
@@ -42,10 +42,12 @@ pi install npm:@zhushanwen/pi-rename-session
42
42
 
43
43
  ## 开关优先级(重要)
44
44
 
45
- `enabled` 有两层来源,优先级从高到低:
45
+ `enabled` 有四层来源,优先级从高到低(`src/pure.ts` `loadRenameConfig`):
46
46
 
47
- 1. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):xyz-agent runtime 的开关契约——桌面端 SystemPage 开关、首启默认开启都写这个文件。**xyz-agent 用户请通过桌面端开关或 `/auto-rename` 命令管理,不要手改 JSON 的 `enabled`**(flag 存在时永远视为开,手改会被覆盖)。
48
- 2. **config `enabled` 字段**(默认 false):flag 不存在时生效,是原生 pi CLI 用户的开关。
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):以上三层均未设置时。
49
51
 
50
52
  ## 命令
51
53
 
@@ -79,9 +81,14 @@ rename 是 best-effort 副作用,任何失败静默跳过、绝不阻断 agent
79
81
  | 落库前发现已有手动名 | skip(name exists),不覆盖 |
80
82
  | 标题模型不可用 | 记日志静默跳过 |
81
83
 
82
- ### debug 证据链(`XYZ_AGENT_DEBUG=1`)
84
+ ### 日志通道(appendEntry 常开 + debug 文件日志)
83
85
 
84
- `console.warn` 输出,前缀 `[rename-session]`。下列 8 条 debug 日志的**文案字面值是 E2E 断言硬契约**(变更须同步 `e2e/` 场景脚本与单测):
86
+ 所有日志经 `@zhushanwen/pi-extension-logger` 输出,两个通道:
87
+
88
+ - **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
+ - **文件日志(`XYZ_AGENT_DEBUG=1` 时)**:`<agentDir>/logs/rename-session-<date>.log`。
90
+
91
+ 下列 8 条 debug 日志(仅 `XYZ_AGENT_DEBUG=1` 时经 `logger.warn` 发出)的**文案字面值是 E2E 断言硬契约**(断言对象 = appendEntry entry 的 message 内容;变更须同步 `e2e/` 场景脚本与单测):
85
92
 
86
93
  | # | 日志 | 发出侧 | 含义 |
87
94
  |---|---|---|---|
@@ -94,7 +101,7 @@ rename 是 best-effort 副作用,任何失败静默跳过、绝不阻断 agent
94
101
  | 7 | `LLM request messages: <JSON>` | llm | 传给 callLLM 的 messages 内省(role + text 的 head 200 码点 + … + tail 100 码点预览,截断单位与 truncateForTitle 统一为 Unicode 码点),在请求发起前打出 |
95
102
  | 8 | `rename with model <provider>/<id>` | llm | 成功路径模型记录(原常开日志;为避免污染 Pi 输入框改为 debug 输出,带 t=ISO 时间戳) |
96
103
 
97
- 另有两条**非 debug 常开**日志:`rename LLM call failed: <err>`(调用失败/超时;超时时 llm-shared callLLM 内部的 extractText 将空错误文本归一为 `unknown error`——extension 侧 `result.error ?? "unknown error"` 只兜 null/undefined,空串兜底发生在 llm-shared 层)、`model not available, skipping`(选模失败)。handler 侧日志带 `t=<ISO时间>` 与 `turnIndex`;llm 侧带 `t=<ISO时间>`、无 turnIndex。
104
+ 另有两条**非 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。
98
105
 
99
106
  ## E2E 验收
100
107
 
@@ -136,7 +143,7 @@ rename-session/
136
143
  ├── skills/rename-session-ext-config/SKILL.md # 配置指南(pi 内 agent 可发现)
137
144
  └── src/
138
145
  ├── index.ts # 工厂入口(注册 turn_end handler + /auto-rename 命令)
139
- ├── commands.ts # /auto-rename on|off|status 命令
146
+ ├── commands.ts # /auto-rename on|off|status 命令(enable/disable 别名)
140
147
  ├── llm.ts # callRenameLLM / 两段输入构造 / debug 内省 / 超时
141
148
  ├── pure.ts # 纯函数(配置 / 首轮计数 / cleanTitle)
142
149
  └── __tests__/ # 单测(pure / commands / llm mock / index 集成)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-rename-session",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "xyz-agent": {
@@ -32,8 +32,8 @@
32
32
  "pi-package"
33
33
  ],
34
34
  "dependencies": {
35
- "@zhushanwen/pi-extension-logger": "0.3.1",
36
- "@zhushanwen/pi-llm-shared": "0.4.2"
35
+ "@zhushanwen/pi-extension-logger": "0.4.0",
36
+ "@zhushanwen/pi-llm-shared": "0.5.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@vitest/coverage-v8": "^4.1.9",
@@ -114,11 +114,12 @@ subagent 子进程 session 不重命名(`isSubagentSession` 判定 session 目
114
114
 
115
115
  ## 开关优先级(重要)
116
116
 
117
- `enabled` 有三层来源,优先级从高到低:
117
+ `enabled` 有四层来源,优先级从高到低(`src/pure.ts` `loadRenameConfig`):
118
118
 
119
- 1. **环境变量 `PI_RENAME_ENABLED`**(最高优先级):适用于容器化部署、CI/CD 等场景。`true`/`false` 字符串,live 读取。覆盖 flag 文件和配置文件。
120
- 2. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):这是 xyz-agent runtime 的开关契约(SystemPage 开关 / 首启默认开启都写这个文件,live 检查每次 turn_end 生效)。**xyz-agent 用户不要手改 JSON 里的 enabled**——桌面端的开关状态存在 flag 文件里,手改 JSON 会被 flag 覆盖(flag 存在时永远视为开)。
121
- 3. **config 的 `enabled` 字段**(默认 false):flag 不存在时生效,是原生 pi CLI 用户的开关(手改 JSON 或 `/auto-rename on|off` 命令)。
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):以上三层均未设置时。
122
123
 
123
124
  `/auto-rename on` 只创建 flag;`/auto-rename off` 写 config.enabled=false + 删 flag(双写同步)。旧版升级用户:旧 flag 文件保留不动,仍作为开关生效,无需任何迁移操作。
124
125
 
@@ -36,7 +36,7 @@ import { callLLM, resolveModel } from "@zhushanwen/pi-llm-shared";
36
36
  import renameSessionExtension from "../index";
37
37
  import { type RenameSessionConfig, loadRenameConfig } from "../pure.js";
38
38
 
39
- // 每用例收尾统一还原:console spy 恢复 + stub 的 XYZ_AGENT_DEBUG 还原,
39
+ // 每用例收尾统一还原:logger mock 恢复 + stub 的 XYZ_AGENT_DEBUG 还原,
40
40
  // 防泄漏到后续用例(debug 开关 live 读 process.env,依赖 stubEnv/unstubAllEnvs 成对)
41
41
  afterEach(() => {
42
42
  vi.restoreAllMocks();
@@ -247,13 +247,13 @@ describe("renameSessionExtension", () => {
247
247
  vi.mocked(loadRenameConfig).mockReturnValue(ENABLED_CONFIG);
248
248
  vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
249
249
  // ok:false 即可满足断言需要(callLLM 被调 + 不落库),聚焦触发判定本身
250
- vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "by-design", recoverable: true });
250
+ vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "by-design" });
251
251
 
252
252
  const FINAL_TEXT = "最终回复:已修复登录超时";
253
253
  const stopMessage = { stopReason: "stop", content: [{ type: "text", text: FINAL_TEXT }] };
254
254
 
255
- // 组1(区分性数据):旧 countAssistantReplies=2 不触发 vs 新成功计数=1 触发——
256
- // index 若仍用旧计数函数(忘接线 wave-1 纯函数),本组红(接线回归防护)
255
+ // 组1(区分性数据):旧判定(数全部 assistant 回复)=2 不触发 vs 新成功计数(只数 stop)=1 触发——
256
+ // index 若仍用旧计数逻辑(不看 stopReason,忘接线 wave-1 纯函数),本组红(接线回归防护)
257
257
  const toolUseRound = [
258
258
  { type: "message", message: { role: "user", content: "帮我修复登录超时" } },
259
259
  { type: "message", message: { role: "assistant", stopReason: "toolUse", content: [] } },
@@ -322,7 +322,7 @@ describe("renameSessionExtension", () => {
322
322
  it("LTC8: callLLM 返回 {ok:false} → callRenameLLM 返回 null,不调 setSessionName", async () => {
323
323
  vi.mocked(loadRenameConfig).mockReturnValue(ENABLED_CONFIG);
324
324
  vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
325
- vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom", recoverable: true });
325
+ vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom" });
326
326
 
327
327
  await fire(setup, createMockCtx());
328
328
  // handler 内 callRenameLLM 是 detached promise(fire-and-forget),等其 settle 后断言
@@ -36,7 +36,7 @@ import {
36
36
  } from "../llm.js";
37
37
  import { type RenameSessionConfig } from "../pure.js";
38
38
 
39
- // 每用例收尾统一还原:console spy 恢复 + stub 的 XYZ_AGENT_DEBUG 还原,
39
+ // 每用例收尾统一还原:logger mock 恢复 + stub 的 XYZ_AGENT_DEBUG 还原,
40
40
  // 防泄漏到后续用例(debug 开关 live 读 process.env,依赖 stubEnv/unstubAllEnvs 成对)
41
41
  afterEach(() => {
42
42
  vi.restoreAllMocks();
@@ -324,7 +324,7 @@ describe("callRenameLLM", () => {
324
324
 
325
325
  it("callLLM 返回 {ok:false} → 返回 null(静默跳过,不抛错)", async () => {
326
326
  vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
327
- vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom", recoverable: true });
327
+ vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom" });
328
328
  const result = await callRenameLLM(createCtx(), BASE_CONFIG, FINAL_MESSAGE);
329
329
  expect(result).toBeNull();
330
330
  });
@@ -498,7 +498,7 @@ describe("callRenameLLM", () => {
498
498
 
499
499
  it("TC7: callLLM ok:false(超时/模型错误)→ 返回 null 不抛错(失败归一静默跳过)", async () => {
500
500
  vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
501
- vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "timeout", recoverable: true });
501
+ vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "timeout" });
502
502
  await expect(callRenameLLM(createCtx(), BASE_CONFIG, FINAL_MESSAGE)).resolves.toBeNull();
503
503
  });
504
504
 
@@ -558,7 +558,7 @@ describe("callRenameLLM A1 日志", () => {
558
558
  vi.clearAllMocks();
559
559
  });
560
560
 
561
- it("TC1: resolveModel null → console 输出 '[rename-session] model not available, skipping',返回 null", async () => {
561
+ it("TC1: resolveModel null → logger.warn('model not available, skipping')(前缀由真实 logger 补),返回 null", async () => {
562
562
  loggerMock.warn.mockClear();
563
563
  vi.mocked(resolveModel).mockReturnValue(null);
564
564
  const result = await callRenameLLM(createCtx(), BASE_CONFIG, FINAL_MESSAGE);
@@ -566,10 +566,10 @@ describe("callRenameLLM A1 日志", () => {
566
566
  expect(loggerMock.warn).toHaveBeenCalledWith("model not available, skipping");
567
567
  });
568
568
 
569
- it("TC2: callLLM {ok:false} → console 输出 '[rename-session] rename LLM call failed: <error>',返回 null", async () => {
569
+ it("TC2: callLLM {ok:false} → logger.warn('rename LLM call failed', {error})(前缀由真实 logger 补),返回 null", async () => {
570
570
  loggerMock.warn.mockClear();
571
571
  vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
572
- vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom", recoverable: true });
572
+ vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom" });
573
573
  const result = await callRenameLLM(createCtx(), BASE_CONFIG, FINAL_MESSAGE);
574
574
  expect(result).toBeNull();
575
575
  expect(loggerMock.warn).toHaveBeenCalledWith("rename LLM call failed", { error: "boom" });
@@ -578,7 +578,7 @@ describe("callRenameLLM A1 日志", () => {
578
578
  it("TC2b: callLLM {ok:false} → 不输出 'rename with model' 成功日志(B2 位置修正:成功日志已移到 result.ok 分支后),但仍输出 failed 日志", async () => {
579
579
  loggerMock.warn.mockClear();
580
580
  vi.mocked(resolveModel).mockReturnValue(STUB_MODEL);
581
- vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom", recoverable: true });
581
+ vi.mocked(callLLM).mockResolvedValue({ ok: false, error: "boom" });
582
582
  const result = await callRenameLLM(createCtx(), BASE_CONFIG, FINAL_MESSAGE);
583
583
  expect(result).toBeNull();
584
584
  // 失败分支仍输出 failed 日志
@@ -9,7 +9,6 @@ import { clearConfigCache, getConfigPath } from "@zhushanwen/pi-llm-shared";
9
9
  import {
10
10
  DEFAULT_RENAME_CONFIG,
11
11
  cleanTitle,
12
- countAssistantReplies,
13
12
  countSuccessfulAssistantReplies,
14
13
  loadRenameConfig,
15
14
  normalizeRenameConfig,
@@ -17,49 +16,6 @@ import {
17
16
  setAutoRenameSwitch,
18
17
  } from "../pure.js";
19
18
 
20
- // ────────────────────────────────────────────────────
21
- // countAssistantReplies(保留,不变)
22
- // ────────────────────────────────────────────────────
23
-
24
- describe("countAssistantReplies", () => {
25
- it("[user, assistant] → 1", () => {
26
- const entries = [
27
- { type: "message", message: { role: "user" } },
28
- { type: "message", message: { role: "assistant" } },
29
- ];
30
- expect(countAssistantReplies(entries)).toBe(1);
31
- });
32
-
33
- it("[user, assistant, user, assistant] → 2", () => {
34
- const entries = [
35
- { type: "message", message: { role: "user" } },
36
- { type: "message", message: { role: "assistant" } },
37
- { type: "message", message: { role: "user" } },
38
- { type: "message", message: { role: "assistant" } },
39
- ];
40
- expect(countAssistantReplies(entries)).toBe(2);
41
- });
42
-
43
- it("过滤非 message(thinkingLevelChange / modelChange)→ 1", () => {
44
- const entries = [
45
- { type: "thinkingLevelChange" },
46
- { type: "message", message: { role: "user" } },
47
- { type: "message", message: { role: "assistant" } },
48
- { type: "modelChange" },
49
- ];
50
- expect(countAssistantReplies(entries)).toBe(1);
51
- });
52
-
53
- it("[user, assistant, toolResult entry] → 1(非 message entry 不计)", () => {
54
- const entries = [
55
- { type: "message", message: { role: "user" } },
56
- { type: "message", message: { role: "assistant" } },
57
- { type: "toolResult" },
58
- ];
59
- expect(countAssistantReplies(entries)).toBe(1);
60
- });
61
- });
62
-
63
19
  // ────────────────────────────────────────────────────
64
20
  // countSuccessfulAssistantReplies(D6:触发判定改用的成功-turn 计数)
65
21
  // ────────────────────────────────────────────────────
package/src/commands.ts CHANGED
@@ -13,10 +13,10 @@ import { loadRenameConfig, saveRenameConfig, setAutoRenameSwitch } from "./pure.
13
13
  * - on → 只创建 flag(不写 config.enabled=true:避免 config 残留 true 导致旧 runtime 删 flag 后仍开启的错位)
14
14
  * - off → 先写 config.enabled=false(覆盖手写的 true),再删 flag(flag 是覆盖源,必须移除才真正关闭)
15
15
  *
16
- * 用法:
17
- * /auto-rename — 查看当前状态
18
- * /auto-rename on — 开启
19
- * /auto-rename off — 关闭
16
+ * 用法(enable/disable 是 on/off 的别名):
17
+ * /auto-rename — 查看当前状态
18
+ * /auto-rename on | enable — 开启
19
+ * /auto-rename off | disable — 关闭
20
20
  */
21
21
  export function executeAutoRenameCommand(args: string): string {
22
22
  const trimmed = args.trim().toLowerCase();
@@ -24,7 +24,7 @@ export function executeAutoRenameCommand(args: string): string {
24
24
  if (trimmed === "" || trimmed === "status") {
25
25
  const { enabled } = loadRenameConfig();
26
26
  const state = enabled ? "已开启 ✓" : "已关闭 ✗";
27
- return `自动重命名会话:${state}\n用法:/auto-rename on | off | status`;
27
+ return `自动重命名会话:${state}\n用法:/auto-rename on | off | status(别名 enable / disable)`;
28
28
  }
29
29
 
30
30
  if (trimmed === "on" || trimmed === "enable") {
@@ -38,18 +38,20 @@ export function executeAutoRenameCommand(args: string): string {
38
38
  return result.success ? "已关闭:自动重命名会话" : `设置失败:${result.error}`;
39
39
  }
40
40
 
41
- return `未知参数 "${args.trim()}"。\n用法:/auto-rename on | off | status`;
41
+ return `未知参数 "${args.trim()}"。\n用法:/auto-rename on | off | status(别名 enable / disable)`;
42
42
  }
43
43
 
44
44
  /** 注册 /auto-rename 命令。 */
45
45
  export function registerAutoRenameCommand(pi: ExtensionAPI): void {
46
46
  pi.registerCommand("auto-rename", {
47
- description: "控制自动重命名会话功能。/auto-rename [on|off|status]",
47
+ description: "控制自动重命名会话功能。/auto-rename [on|off|status](别名 enable/disable)",
48
48
  getArgumentCompletions(prefix: string) {
49
49
  const trimmed = prefix.trimStart().toLowerCase();
50
50
  const opts = [
51
51
  { label: "on", value: "on", description: "开启自动重命名" },
52
52
  { label: "off", value: "off", description: "关闭自动重命名" },
53
+ { label: "enable", value: "enable", description: "开启自动重命名(on 别名)" },
54
+ { label: "disable", value: "disable", description: "关闭自动重命名(off 别名)" },
53
55
  { label: "status", value: "status", description: "查看当前状态" },
54
56
  ];
55
57
  return trimmed === "" ? opts : opts.filter((o) => o.label.startsWith(trimmed));
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { getLogger } from "@zhushanwen/pi-extension-logger";
2
+ import { getLogger, setPiHandle } from "@zhushanwen/pi-extension-logger";
3
3
 
4
4
  import { registerAutoRenameCommand } from "./commands.js";
5
5
  import { callRenameLLM, debugLog as llmDebugLog, isSubagentSession } from "./llm.js";
@@ -26,6 +26,11 @@ interface TurnEndLikeEvent {
26
26
  * 新 session 首个成功 turn 完成后,用独立模型生成会话标题并 setSessionName 落库。
27
27
  */
28
28
  export default function renameSessionExtension(pi: ExtensionAPI): void {
29
+ // 日志通道注入(extension-logger 两阶段初始化:工厂拿 pi → setPiHandle,最早期调用)。
30
+ // 不注入则 logger.warn/error 只有文件日志通道(XYZ_AGENT_DEBUG=1),appendEntry
31
+ // (session entry,README「debug 证据链」的 E2E 断言数据源)不生效。
32
+ setPiHandle(pi);
33
+
29
34
  registerAutoRenameCommand(pi);
30
35
 
31
36
  pi.on("turn_end", async (event: TurnEndLikeEvent, ctx: ExtensionContext) => {
package/src/pure.ts CHANGED
@@ -268,24 +268,6 @@ interface EntryLike {
268
268
  message?: { role?: string; stopReason?: string };
269
269
  }
270
270
 
271
- /**
272
- * 数 session 中 assistant 回复数。用于判定首 turn(===1)。
273
- * 判定条件:entry.type === "message" && entry.message.role === "assistant"
274
- * (pi 内部 session-manager.ts 同款模式)。
275
- *
276
- * 注意:触发判定使用 countSuccessfulAssistantReplies(index.ts 已切换;不看 stopReason
277
- * 无法区分「iteration 结束」与「轮次结束」,见设计 D6)。本函数仅为兼容保留。
278
- */
279
- export function countAssistantReplies(entries: ReadonlyArray<EntryLike>): number {
280
- let count = 0;
281
- for (const entry of entries) {
282
- if (entry.type === "message" && entry.message?.role === "assistant") {
283
- count++;
284
- }
285
- }
286
- return count;
287
- }
288
-
289
271
  /**
290
272
  * 数 session 中「成功完成」的 assistant 回复数(stopReason === "stop"),触发判定用(===1 触发 rename)。
291
273
  *