@zhushanwen/pi-rename-session 0.5.3 → 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 +14 -7
- package/package.json +20 -6
- package/skills/rename-session-ext-config/SKILL.md +5 -4
- package/src/__tests__/index.test.ts +5 -5
- package/src/__tests__/llm.test.ts +7 -7
- package/src/__tests__/pure.test.ts +0 -44
- package/src/__tests__/startup-config-declaration.test.ts +34 -0
- package/src/commands.ts +9 -7
- package/src/index.ts +6 -1
- package/src/pure.ts +4 -19
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.
|
|
48
|
-
2.
|
|
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
|
|
84
|
+
### 日志通道(appendEntry 常开 + debug 文件日志)
|
|
83
85
|
|
|
84
|
-
|
|
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
|
|
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,10 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-rename-session",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"xyz-agent": {
|
|
7
|
-
"role": "universal"
|
|
7
|
+
"role": "universal",
|
|
8
|
+
"startupConfig": [
|
|
9
|
+
{
|
|
10
|
+
"path": "config/rename-session-ext-config.json",
|
|
11
|
+
"content": {
|
|
12
|
+
"enabled": false,
|
|
13
|
+
"model": {
|
|
14
|
+
"type": "ref",
|
|
15
|
+
"ref": ""
|
|
16
|
+
},
|
|
17
|
+
"maxTitleLength": 50,
|
|
18
|
+
"thinkingLevel": "off"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
8
22
|
},
|
|
9
23
|
"pi": {
|
|
10
24
|
"extensions": [
|
|
@@ -18,8 +32,8 @@
|
|
|
18
32
|
"pi-package"
|
|
19
33
|
],
|
|
20
34
|
"dependencies": {
|
|
21
|
-
"@zhushanwen/pi-extension-logger": "0.
|
|
22
|
-
"@zhushanwen/pi-llm-shared": "0.
|
|
35
|
+
"@zhushanwen/pi-extension-logger": "0.4.0",
|
|
36
|
+
"@zhushanwen/pi-llm-shared": "0.5.0"
|
|
23
37
|
},
|
|
24
38
|
"devDependencies": {
|
|
25
39
|
"@vitest/coverage-v8": "^4.1.9",
|
|
@@ -32,8 +46,8 @@
|
|
|
32
46
|
"vitest.config.ts"
|
|
33
47
|
],
|
|
34
48
|
"peerDependencies": {
|
|
35
|
-
"@earendil-works/pi-coding-agent": "^0.84.
|
|
36
|
-
"@earendil-works/pi-ai": "^0.84.
|
|
49
|
+
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
50
|
+
"@earendil-works/pi-ai": "^0.84.4",
|
|
37
51
|
"typebox": "*"
|
|
38
52
|
},
|
|
39
53
|
"peerDependenciesMeta": {
|
|
@@ -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
|
|
120
|
-
2. **`<agentDir>/auto-rename-enabled` flag 文件**(存在 = 开):这是 xyz-agent runtime 的开关契约(SystemPage 开关 / 首启默认开启都写这个文件,live 检查每次 turn_end 生效)。**xyz-agent 用户不要手改 JSON 里的 enabled**——桌面端的开关状态存在 flag 文件里,手改 JSON 会被 flag
|
|
121
|
-
3. **config 的 `enabled` 字段**(默认 false
|
|
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
|
-
// 每用例收尾统一还原:
|
|
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"
|
|
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
|
|
256
|
-
// index
|
|
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"
|
|
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
|
-
// 每用例收尾统一还原:
|
|
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"
|
|
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"
|
|
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 →
|
|
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} →
|
|
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"
|
|
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"
|
|
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
|
// ────────────────────────────────────────────────────
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* startupConfig 声明守护测试:断言 package.json `xyz-agent.startupConfig` 声明的
|
|
3
|
+
* content 与代码 DEFAULT_RENAME_CONFIG 常量深相等,防止两侧任一改动未同步(漂移)。
|
|
4
|
+
* 机制:runtime 启动序列统一 ensure(extension-startup-config.ts),声明即首建内容。
|
|
5
|
+
*
|
|
6
|
+
* 运行:cd extensions/universal/rename-session && npx vitest run src/__tests__/startup-config-declaration.test.ts
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
|
|
14
|
+
import { DEFAULT_RENAME_CONFIG } from "../pure.js";
|
|
15
|
+
|
|
16
|
+
/** package.json `xyz-agent.startupConfig` 声明条目的最小形状(守护断言用)。 */
|
|
17
|
+
interface StartupConfigEntry {
|
|
18
|
+
path: string;
|
|
19
|
+
content: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pkg = JSON.parse(
|
|
23
|
+
readFileSync(join(fileURLToPath(import.meta.url), "../../../package.json"), "utf-8"),
|
|
24
|
+
) as { "xyz-agent"?: { startupConfig?: StartupConfigEntry[] } };
|
|
25
|
+
|
|
26
|
+
describe("startupConfig 声明守护", () => {
|
|
27
|
+
it("声明 content 与代码 DEFAULT_RENAME_CONFIG 深相等", () => {
|
|
28
|
+
const entry = pkg["xyz-agent"]?.startupConfig?.find(
|
|
29
|
+
(e: StartupConfigEntry) => e.path === "config/rename-session-ext-config.json",
|
|
30
|
+
);
|
|
31
|
+
expect(entry).toBeDefined();
|
|
32
|
+
expect(entry?.content).toEqual(DEFAULT_RENAME_CONFIG);
|
|
33
|
+
});
|
|
34
|
+
});
|
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
|
@@ -53,6 +53,9 @@ const THINKING_LEVELS: ReadonlySet<string> = new Set([
|
|
|
53
53
|
/** 环境变量前缀(避免与其他配置冲突)。 */
|
|
54
54
|
const ENV_PREFIX = "PI_RENAME_";
|
|
55
55
|
|
|
56
|
+
/** "provider/model" 格式按 "/" 拆分后的合法段数(modelEnv 校验用)。 */
|
|
57
|
+
const MODEL_REF_PART_COUNT = 2;
|
|
58
|
+
|
|
56
59
|
/**
|
|
57
60
|
* 从环境变量读取配置覆盖值(live 读取,每次调用查 process.env)。
|
|
58
61
|
*
|
|
@@ -84,7 +87,7 @@ function getEnvOverrides(): Partial<RenameSessionConfig> {
|
|
|
84
87
|
if (modelEnv !== undefined && typeof modelEnv === "string") {
|
|
85
88
|
// 支持 "provider/model" 格式(最常用场景)
|
|
86
89
|
const parts = modelEnv.split("/");
|
|
87
|
-
if (parts.length ===
|
|
90
|
+
if (parts.length === MODEL_REF_PART_COUNT && parts[0] && parts[1]) {
|
|
88
91
|
overrides.model = { type: "ref", ref: modelEnv };
|
|
89
92
|
}
|
|
90
93
|
// 其他格式静默忽略(复杂 ModelSelector 请用配置文件)
|
|
@@ -265,24 +268,6 @@ interface EntryLike {
|
|
|
265
268
|
message?: { role?: string; stopReason?: string };
|
|
266
269
|
}
|
|
267
270
|
|
|
268
|
-
/**
|
|
269
|
-
* 数 session 中 assistant 回复数。用于判定首 turn(===1)。
|
|
270
|
-
* 判定条件:entry.type === "message" && entry.message.role === "assistant"
|
|
271
|
-
* (pi 内部 session-manager.ts 同款模式)。
|
|
272
|
-
*
|
|
273
|
-
* 注意:触发判定使用 countSuccessfulAssistantReplies(index.ts 已切换;不看 stopReason
|
|
274
|
-
* 无法区分「iteration 结束」与「轮次结束」,见设计 D6)。本函数仅为兼容保留。
|
|
275
|
-
*/
|
|
276
|
-
export function countAssistantReplies(entries: ReadonlyArray<EntryLike>): number {
|
|
277
|
-
let count = 0;
|
|
278
|
-
for (const entry of entries) {
|
|
279
|
-
if (entry.type === "message" && entry.message?.role === "assistant") {
|
|
280
|
-
count++;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return count;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
271
|
/**
|
|
287
272
|
* 数 session 中「成功完成」的 assistant 回复数(stopReason === "stop"),触发判定用(===1 触发 rename)。
|
|
288
273
|
*
|