@zhushanwen/pi-rename-session 0.9.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 +1 -1
- package/package.json +3 -2
- package/skills/rename-session-ext-config/SKILL.md +1 -1
- package/src/llm.ts +20 -33
- package/src/pure.ts +37 -35
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ pi install npm:@zhushanwen/pi-rename-session
|
|
|
53
53
|
2. **config 的 `enabled` 字段**(默认 false):flag 不存在时生效,是原生 pi CLI 用户的开关。
|
|
54
54
|
3. **默认值**(false):以上两层均未设置时。
|
|
55
55
|
|
|
56
|
-
> [HISTORICAL] 原最高优先级的 `PI_RENAME_*` 环境变量覆盖层已删(全仓 0 生产 setter,4 键中 3 键从未被用过)——预置该前缀的环境变量不再有任何效果。rename-session
|
|
56
|
+
> [HISTORICAL] 原最高优先级的 `PI_RENAME_*` 环境变量覆盖层已删(全仓 0 生产 setter,4 键中 3 键从未被用过)——预置该前缀的环境变量不再有任何效果。rename-session 相关精简项裁决见原设计文档 rename-session-three-modes.md 附录 A(已删除,git 可追溯)。
|
|
57
57
|
|
|
58
58
|
## 命令
|
|
59
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-rename-session",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"xyz-agent": {
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
"pi-package"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@zhushanwen/pi-ext-guards": "0.4.0",
|
|
36
37
|
"@zhushanwen/pi-extension-logger": "0.6.0",
|
|
37
|
-
"@zhushanwen/pi-llm-shared": "0.
|
|
38
|
+
"@zhushanwen/pi-llm-shared": "0.8.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@vitest/coverage-v8": "^4.1.9",
|
|
@@ -102,7 +102,7 @@ interface RenameSessionConfig {
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
> [HISTORICAL] 原最高优先级的 `PI_RENAME_*` 环境变量覆盖层已删(全仓 0 生产 setter)——预置该前缀的环境变量不再有任何效果。rename-session
|
|
105
|
+
> [HISTORICAL] 原最高优先级的 `PI_RENAME_*` 环境变量覆盖层已删(全仓 0 生产 setter)——预置该前缀的环境变量不再有任何效果。rename-session 相关精简项裁决见原设计文档 rename-session-three-modes.md 附录 A(已删除,git 可追溯)。
|
|
106
106
|
|
|
107
107
|
## 配置生效时机
|
|
108
108
|
|
package/src/llm.ts
CHANGED
|
@@ -2,10 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import type { AssistantMessage, Message, Usage } from "@earendil-works/pi-ai";
|
|
4
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { isRecord } from "@zhushanwen/pi-ext-guards";
|
|
5
6
|
import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
6
|
-
import { callLLM, resolveModel } from "@zhushanwen/pi-llm-shared";
|
|
7
|
+
import { callLLM, joinTextBlocks, resolveModel } from "@zhushanwen/pi-llm-shared";
|
|
7
8
|
|
|
8
|
-
import { type RenameSessionConfig, cleanTitle } from "./pure.js";
|
|
9
|
+
import { type RenameSessionConfig, cleanTitle, truncateCodePoints } from "./pure.js";
|
|
9
10
|
|
|
10
11
|
const logger = getLogger("rename-session");
|
|
11
12
|
|
|
@@ -36,7 +37,7 @@ export const RENAME_INSTRUCTION =
|
|
|
36
37
|
* sessionDir 路径含 subagents 段 → 是 subagent 子进程 session,跳过 rename。
|
|
37
38
|
*
|
|
38
39
|
* 跨包路径耦合(constraints.json C-ext-21):`subagents` 目录布局由 xyz-agent 的
|
|
39
|
-
* `packages/subagent-core/src/execution/path-encoding.ts`(getSubagentSessionDir /
|
|
40
|
+
* `packages/subagent-core/src/execution/assembly/path-encoding.ts`(getSubagentSessionDir /
|
|
40
41
|
* encodeCwd)定义并写入。本包 role=universal,不 import subagent-core
|
|
41
42
|
* (import 会把 universal 包绑死在 xyz-agent 体系上),只按路径形态守卫——
|
|
42
43
|
* path-encoding.ts 侧有对向注释互指。改 subagent 目录布局必须双侧同步改。
|
|
@@ -53,23 +54,10 @@ interface EntryLike {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// ──────────────────────── 标题输入构造(两段信号:首条 user prompt / 触发 turn 最终回复) ────────────────────────
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** 拼接 content 内 type==="text" blocks 的文本(join(" "),跳过非 text block 与非 string text)。 */
|
|
63
|
-
function joinTextBlocks(content: unknown): string {
|
|
64
|
-
if (!Array.isArray(content)) return "";
|
|
65
|
-
const texts: string[] = [];
|
|
66
|
-
for (const block of content) {
|
|
67
|
-
if (isRecord(block) && block.type === "text" && typeof block.text === "string") {
|
|
68
|
-
texts.push(block.text);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return texts.join(" ");
|
|
72
|
-
}
|
|
57
|
+
//
|
|
58
|
+
// isRecord / joinTextBlocks 均为共享导出(ext-simplify-17 D3/D7):isRecord 用 ext-guards
|
|
59
|
+
// 排数组严版(数组 message 输入下三消费点 continue/return "" 殊途同归,等价论证见设计
|
|
60
|
+
// 文档 §3.1 D3);joinTextBlocks 用 llm-shared unknown 安全内核(不 trim,trim 是调用方策略)。
|
|
73
61
|
|
|
74
62
|
/**
|
|
75
63
|
* 取 session entries 中首条 user message 的 prompt 文本(标题输入信号之一)。
|
|
@@ -116,13 +104,11 @@ const MAX_TITLE_INPUT_CODE_POINTS = 4000;
|
|
|
116
104
|
|
|
117
105
|
/**
|
|
118
106
|
* 按 Unicode 码点截断标题输入段(两段信号各 4000 码点,中文场景约 4k token/段,成本可控)。
|
|
119
|
-
*
|
|
120
|
-
*
|
|
107
|
+
* 截断内核见 pure.ts truncateCodePoints(ext-simplify-17 D13 收口);超长才追加 '…' 后缀,
|
|
108
|
+
* ≤ 上限(含恰好等于)原样返回。
|
|
121
109
|
*/
|
|
122
110
|
export function truncateForTitle(text: string): string {
|
|
123
|
-
|
|
124
|
-
if (chars.length <= MAX_TITLE_INPUT_CODE_POINTS) return text;
|
|
125
|
-
return chars.slice(0, MAX_TITLE_INPUT_CODE_POINTS).join("") + "…";
|
|
111
|
+
return truncateCodePoints(text, MAX_TITLE_INPUT_CODE_POINTS, "…");
|
|
126
112
|
}
|
|
127
113
|
|
|
128
114
|
/**
|
|
@@ -195,16 +181,17 @@ const PREVIEW_TAIL_CODE_POINTS = 100;
|
|
|
195
181
|
|
|
196
182
|
/**
|
|
197
183
|
* debug 日志的文本预览(≤300 码点直接全文;超长输出 head 200 码点 + 字面 … + tail 100 码点
|
|
198
|
-
* (head/tail 双段支撑 E2E 对长 prompt
|
|
199
|
-
*
|
|
184
|
+
* (head/tail 双段支撑 E2E 对长 prompt 首尾片段的断言)。截断内核见 pure.ts truncateCodePoints
|
|
185
|
+
* (ext-simplify-17 D13 收口,双段形态经 head/keepTail 参数表达——300/200/100 是 15 号 §6.4
|
|
186
|
+
* D4 裁决的三个独立契约数字);e2e/harness.mjs 的 rebuildPreview 是同构实现,两处必须同步改。
|
|
200
187
|
*/
|
|
201
188
|
function previewText(text: string): string {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
189
|
+
return truncateCodePoints(
|
|
190
|
+
text,
|
|
191
|
+
PREVIEW_MAX_CODE_POINTS,
|
|
192
|
+
"…",
|
|
193
|
+
PREVIEW_HEAD_CODE_POINTS,
|
|
194
|
+
PREVIEW_TAIL_CODE_POINTS,
|
|
208
195
|
);
|
|
209
196
|
}
|
|
210
197
|
|
package/src/pure.ts
CHANGED
|
@@ -4,8 +4,11 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
6
6
|
|
|
7
|
+
import { isEnoentError } from "@zhushanwen/pi-ext-guards";
|
|
7
8
|
import {
|
|
9
|
+
isThinkingLevel,
|
|
8
10
|
loadConfig,
|
|
11
|
+
normalizeModelSelector,
|
|
9
12
|
saveConfig,
|
|
10
13
|
type ModelSelector,
|
|
11
14
|
} from "@zhushanwen/pi-llm-shared";
|
|
@@ -44,17 +47,6 @@ export interface RenameSessionConfig {
|
|
|
44
47
|
thinkingLevel: ModelThinkingLevel;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
/** 合法 thinking 级别清单(与 pi-ai ModelThinkingLevel 一致;normalize 校验用)。Set 免 as 断言。 */
|
|
48
|
-
const THINKING_LEVELS: ReadonlySet<string> = new Set([
|
|
49
|
-
"off",
|
|
50
|
-
"minimal",
|
|
51
|
-
"low",
|
|
52
|
-
"medium",
|
|
53
|
-
"high",
|
|
54
|
-
"xhigh",
|
|
55
|
-
"max",
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
50
|
// ──────────────────────── 枚举校验 ────────────────────────
|
|
59
51
|
|
|
60
52
|
/** 合法触发模式清单(与 RenameMode 一致;normalize 校验用)。Set 免 as 断言。 */
|
|
@@ -68,14 +60,6 @@ function isRenameMode(raw: unknown): raw is RenameMode {
|
|
|
68
60
|
return typeof raw === "string" && RENAME_MODES.has(raw);
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
/**
|
|
72
|
-
* 类型谓词:unknown 是否为合法 thinking 级别(normalizeRenameConfig 校验用,单点断言)。
|
|
73
|
-
* Set.has 运行时兜底 + 类型收窄,调用方无需再断言。
|
|
74
|
-
*/
|
|
75
|
-
function isThinkingLevel(raw: unknown): raw is ModelThinkingLevel {
|
|
76
|
-
return typeof raw === "string" && THINKING_LEVELS.has(raw);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
63
|
/** 默认配置:关闭、空 ref(跟随会话主模型,见 llm.ts 的空 ref fallback)、first-stop 触发、标题上限 50、不启用 thinking。 */
|
|
80
64
|
export const DEFAULT_RENAME_CONFIG: RenameSessionConfig = {
|
|
81
65
|
enabled: false,
|
|
@@ -128,8 +112,7 @@ export function setAutoRenameSwitch(enabled: boolean): void {
|
|
|
128
112
|
rmSync(flagPath);
|
|
129
113
|
} catch (e: unknown) {
|
|
130
114
|
// flag 不存在视为已关(吞 ENOENT);其他错误(如权限)如实抛出,不静默
|
|
131
|
-
|
|
132
|
-
if (code !== "ENOENT") throw e;
|
|
115
|
+
if (!isEnoentError(e)) throw e;
|
|
133
116
|
}
|
|
134
117
|
}
|
|
135
118
|
}
|
|
@@ -158,6 +141,7 @@ export function normalizeRenameConfig(raw: unknown): RenameSessionConfig {
|
|
|
158
141
|
? obj.maxTitleLength
|
|
159
142
|
: DEFAULT_RENAME_CONFIG.maxTitleLength;
|
|
160
143
|
|
|
144
|
+
// normalizeModelSelector:llm-shared 导出(ext-simplify-17 D6 收口)
|
|
161
145
|
const model = normalizeModelSelector(obj.model) ?? DEFAULT_RENAME_CONFIG.model;
|
|
162
146
|
|
|
163
147
|
const thinkingLevel = isThinkingLevel(obj.thinkingLevel)
|
|
@@ -167,16 +151,6 @@ export function normalizeRenameConfig(raw: unknown): RenameSessionConfig {
|
|
|
167
151
|
return { enabled, model, mode, maxTitleLength, thinkingLevel };
|
|
168
152
|
}
|
|
169
153
|
|
|
170
|
-
/** 校验 ModelSelector:只支持 ref 精确指定,其余形式非法返回 null。 */
|
|
171
|
-
function normalizeModelSelector(raw: unknown): ModelSelector | null {
|
|
172
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
173
|
-
const obj = raw as Record<string, unknown>;
|
|
174
|
-
if (obj.type === "ref" && typeof obj.ref === "string") {
|
|
175
|
-
return { type: "ref", ref: obj.ref };
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
154
|
/**
|
|
181
155
|
* 加载配置(mtime+size 缓存;文件缺失/损坏返回默认,不抛错)。
|
|
182
156
|
*
|
|
@@ -258,6 +232,36 @@ export function countSuccessfulAssistantReplies(entries: ReadonlyArray<EntryLike
|
|
|
258
232
|
|
|
259
233
|
// ──────────────────────── 标题清洗 ────────────────────────
|
|
260
234
|
|
|
235
|
+
/**
|
|
236
|
+
* 按 Unicode 码点截断(ext-simplify-17 D13:收口包内三处同构 `Array.from` 骨架——
|
|
237
|
+
* cleanTitle / truncateForTitle / previewText,三处差异全部参数化)。
|
|
238
|
+
*
|
|
239
|
+
* Array.from 按码点切分,星面字符(emoji 等,占 2 个 UTF-16 码元)不会被劈成半个代理对。
|
|
240
|
+
* 码点数 ≤ max(含恰好等于)原样返回;超长返回「前 head 码点 + tail + 原文最后 keepTail 码点」。
|
|
241
|
+
*
|
|
242
|
+
* @param max 判定阈值:码点数 ≤ max 原样返回(与 head 独立——previewText 的 300/200/100 是
|
|
243
|
+
* 15 号 §6.4 D4 裁决的三个独立契约数字,不得由 head+keepTail 推导)
|
|
244
|
+
* @param tail 超长时追加在 head 段后的字面中缀(如 "…";单段形态传 "")
|
|
245
|
+
* @param head 超长时保留的头部码点数(默认 = max,单段形态下阈值即截断长度)
|
|
246
|
+
* @param keepTail 超长时保留的原文尾部码点数(默认 0 = 单段形态;双段形态的调用方
|
|
247
|
+
* 自负 head/keepTail 不重叠——本包内 previewText 的 300/200/100 在超长区间恒不重叠)
|
|
248
|
+
*/
|
|
249
|
+
export function truncateCodePoints(
|
|
250
|
+
text: string,
|
|
251
|
+
max: number,
|
|
252
|
+
tail: string,
|
|
253
|
+
head = max,
|
|
254
|
+
keepTail = 0,
|
|
255
|
+
): string {
|
|
256
|
+
const chars = Array.from(text);
|
|
257
|
+
if (chars.length <= max) return text;
|
|
258
|
+
return (
|
|
259
|
+
chars.slice(0, head).join("") +
|
|
260
|
+
tail +
|
|
261
|
+
(keepTail > 0 ? chars.slice(-keepTail).join("") : "")
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
261
265
|
/**
|
|
262
266
|
* rename 专属后处理:去首尾成对引号(单/双/中文)+ markdown 强调标记(* ** ` _)+ 尾部标点,按 Unicode 码点截断。
|
|
263
267
|
*
|
|
@@ -281,8 +285,6 @@ export function cleanTitle(content: string, maxLength: number): string {
|
|
|
281
285
|
.trim();
|
|
282
286
|
if (!cleaned) return "";
|
|
283
287
|
|
|
284
|
-
// 按 Unicode
|
|
285
|
-
|
|
286
|
-
if (chars.length <= maxLength) return cleaned;
|
|
287
|
-
return chars.slice(0, maxLength).join("");
|
|
288
|
+
// 按 Unicode 码点截断(避免截断多字节字符;无尾部后缀的单段形态)
|
|
289
|
+
return truncateCodePoints(cleaned, maxLength, "");
|
|
288
290
|
}
|