@zhushanwen/pi-rename-session 0.12.1 → 0.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-rename-session",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "taiji": {
@@ -35,6 +35,7 @@
35
35
  "pi-package"
36
36
  ],
37
37
  "dependencies": {
38
+ "@zhushanwen/extension-protocol": "0.21.0",
38
39
  "@zhushanwen/pi-ext-guards": "0.5.0",
39
40
  "@zhushanwen/pi-extension-logger": "0.6.2",
40
41
  "@zhushanwen/pi-llm-shared": "0.11.3"
@@ -52,6 +53,7 @@
52
53
  "peerDependencies": {
53
54
  "@earendil-works/pi-coding-agent": "^1.0.0",
54
55
  "@earendil-works/pi-ai": "^1.0.0",
56
+ "@earendil-works/pi-tui": "^1.0.0",
55
57
  "typebox": "1.3.27"
56
58
  },
57
59
  "peerDependenciesMeta": {
@@ -61,6 +63,9 @@
61
63
  "@earendil-works/pi-ai": {
62
64
  "optional": true
63
65
  },
66
+ "@earendil-works/pi-tui": {
67
+ "optional": true
68
+ },
64
69
  "typebox": {
65
70
  "optional": true
66
71
  }
@@ -786,7 +786,10 @@ interface RegisteredRenameTool {
786
786
  signal: undefined,
787
787
  onUpdate: undefined,
788
788
  ctx: ExtensionContext,
789
- ) => Promise<unknown>;
789
+ ) => Promise<{
790
+ content: Array<{ type: string; text: string }>;
791
+ details: { summary: string };
792
+ }>;
790
793
  }
791
794
 
792
795
  describe("rename_session 工具注册与 execute 守卫(D3)", () => {
@@ -846,9 +849,19 @@ describe("rename_session 工具注册与 execute 守卫(D3)", () => {
846
849
  setupWithMode(AGENT_TOOL_CONFIG);
847
850
  const tool = setup.registeredTool as RegisteredRenameTool;
848
851
 
849
- await tool.execute("call-1", { title: " **重构配置加载** " }, undefined, undefined, createMockCtx());
852
+ const result = await tool.execute(
853
+ "call-1",
854
+ { title: " **重构配置加载** " },
855
+ undefined,
856
+ undefined,
857
+ createMockCtx(),
858
+ );
850
859
 
851
860
  expect(setup.setSessionNameMock).toHaveBeenCalledWith("重构配置加载");
861
+ // execute 组装线:details.summary 恒写入(index.ts:123 buildRenameSessionSummary;
862
+ // title 用清洗后落库值,与 content 文案同源——组装行丢失时此处红)
863
+ expect(result.details.summary).toBe('Renamed session to "重构配置加载"');
864
+ expect(result.content[0].text).toBe("已命名:重构配置加载");
852
865
  });
853
866
 
854
867
  it("TC-T6: execute 时 title 清洗后为空(纯标点)→ isError(throw),不落库", async () => {
@@ -0,0 +1,76 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import {
5
+ buildRenameSessionSummary,
6
+ renderRenameSessionResult,
7
+ } from "../render";
8
+
9
+ // ── mock theme(fg 直通,便于断言纯文本)────────────
10
+
11
+ const mockTheme = {
12
+ fg: (_color: string, text: string) => text,
13
+ bg: (_color: string, text: string) => text,
14
+ bold: (text: string) => text,
15
+ italic: (text: string) => text,
16
+ underline: (text: string) => text,
17
+ inverse: (text: string) => text,
18
+ strikethrough: (text: string) => text,
19
+ getFgAnsi: (_color: string) => "",
20
+ getBgAnsi: (_color: string) => "",
21
+ getColorMode: () => "truecolor" as const,
22
+ getThinkingBorderColor: () => (text: string) => text,
23
+ getBashModeBorderColor: () => (text: string) => text,
24
+ } as unknown as Theme;
25
+
26
+ /** Text 实例的渲染行(width 足够宽避免 wrap;每行被 pad 到宽度,断言用 trim 后全等) */
27
+ function renderedText(result: unknown, expanded = false): string {
28
+ return renderRenameSessionResult(result, { expanded }, mockTheme)
29
+ .render(400)
30
+ .map((line) => line.trimEnd())
31
+ .join("\n");
32
+ }
33
+
34
+ // ── buildRenameSessionSummary ───────────────────────
35
+
36
+ describe("buildRenameSessionSummary", () => {
37
+ it("动作 + 清洗后标题,一行英文摘要", () => {
38
+ expect(buildRenameSessionSummary({ title: "修复登录超时" })).toBe(
39
+ 'Renamed session to "修复登录超时"',
40
+ );
41
+ expect(buildRenameSessionSummary({ title: "refactor-config-loader" })).toBe(
42
+ 'Renamed session to "refactor-config-loader"',
43
+ );
44
+ });
45
+ });
46
+
47
+ // ── renderRenameSessionResult ───────────────────────
48
+
49
+ describe("renderRenameSessionResult", () => {
50
+ const result = {
51
+ content: [{ type: "text", text: "已命名:修复登录超时" }],
52
+ details: { summary: 'Renamed session to "修复登录超时"' },
53
+ };
54
+
55
+ it("折叠态 → details.summary 一行摘要", () => {
56
+ expect(renderedText(result, false)).toBe('Renamed session to "修复登录超时"');
57
+ });
58
+
59
+ it("展开态 → content 纯文本", () => {
60
+ expect(renderedText(result, true)).toBe("已命名:修复登录超时");
61
+ });
62
+
63
+ it("无 details(旧版本持久化 entry)→ 折叠态回退 content 文本", () => {
64
+ const legacy = { content: [{ type: "text", text: "已命名:legacy" }] };
65
+ expect(renderedText(legacy, false)).toBe("已命名:legacy");
66
+ });
67
+
68
+ it("content 非 text 类型 → 折叠态兜底 Renamed", () => {
69
+ const odd = { content: [{ type: "image" }], details: {} };
70
+ expect(renderedText(odd, false)).toBe("Renamed");
71
+ });
72
+
73
+ it("result 非对象(防御)→ 兜底 Renamed 不抛错", () => {
74
+ expect(renderedText(undefined, false)).toBe("Renamed");
75
+ });
76
+ });
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import type {
2
2
  AgentToolResult,
3
3
  ExtensionAPI,
4
4
  ExtensionContext,
5
+ Theme,
5
6
  } from "@earendil-works/pi-coding-agent";
6
7
  import { getLogger, setPiHandle } from "@zhushanwen/pi-extension-logger";
7
8
  import { Type } from "typebox";
@@ -9,6 +10,11 @@ import { Type } from "typebox";
9
10
  import { registerAutoRenameCommand } from "./commands.js";
10
11
  import { landTitle } from "./landing.js";
11
12
  import { debugLog as llmDebugLog, extractMessageText, isSubagentSession } from "./llm.js";
13
+ import {
14
+ buildRenameSessionSummary,
15
+ renderRenameSessionResult,
16
+ type RenameSessionDetails,
17
+ } from "./render.js";
12
18
  import {
13
19
  cleanTitle,
14
20
  countSuccessfulAssistantReplies,
@@ -94,7 +100,7 @@ function registerRenameSessionTool(pi: ExtensionAPI): void {
94
100
  _signal: AbortSignal | undefined,
95
101
  _onUpdate: undefined,
96
102
  ctx: ExtensionContext,
97
- ): Promise<AgentToolResult<Record<string, never>>> {
103
+ ): Promise<AgentToolResult<RenameSessionDetails>> {
98
104
  // subagent session 守卫先于 mode 守卫:subagent 拒绝与 mode 无关(即便 mode 仍为
99
105
  // agent-tool 也不该命名临时子会话),且文案归因到 subagent 而非误导性的「模式已切换」
100
106
  if (isSubagentSession(ctx.sessionManager.getSessionDir())) {
@@ -112,7 +118,14 @@ function registerRenameSessionTool(pi: ExtensionAPI): void {
112
118
  }
113
119
  pi.setSessionName(title);
114
120
  llmDebugLog(`tool renamed to "${title}"`);
115
- return { content: [{ type: "text", text: `已命名:${title}` }], details: {} };
121
+ return {
122
+ content: [{ type: "text", text: `已命名:${title}` }],
123
+ details: { summary: buildRenameSessionSummary({ title }) },
124
+ };
125
+ },
126
+
127
+ renderResult(result: unknown, options: { expanded: boolean }, theme: Theme) {
128
+ return renderRenameSessionResult(result, options, theme);
116
129
  },
117
130
  });
118
131
  }
package/src/render.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * rename_session 工具渲染层(收起态一行摘要)。
3
+ *
4
+ * summary 是「做了什么」的一行英文摘要:GUI 侧读 tool result 的 details.summary 展示,
5
+ * TUI renderResult 折叠态渲染同一份文本(dim 染色),展开态回退 content 纯文本。
6
+ */
7
+
8
+ import type { Theme } from "@earendil-works/pi-coding-agent";
9
+ import { Text } from "@earendil-works/pi-tui";
10
+ import { firstContentText } from "@zhushanwen/extension-protocol";
11
+
12
+ /** rename_session 工具 result 的 details 形状(summary 由 buildRenameSessionSummary 产出)。 */
13
+ export interface RenameSessionDetails { // oe-exempt:20261017:framework:details 数据形状窄 view(单实现消费,非抽象接口)
14
+ summary: string;
15
+ }
16
+
17
+ /** 收起态一行摘要:动作 + 新标题。title 用清洗后落库值(execute 传入),与 content 文案同源。 */
18
+ export function buildRenameSessionSummary(params: { title: string }): string {
19
+ return `Renamed session to "${params.title}"`;
20
+ }
21
+
22
+ /** unknown 值按字段名安全取值(index-signature 收窄,替代全可选结构断言)。 */
23
+ function fieldValue(v: unknown, key: string): unknown {
24
+ if (typeof v !== "object" || v === null) return undefined;
25
+ return (v as Record<string, unknown>)[key];
26
+ }
27
+
28
+ /** 从 tool result 的 details 安全读取 summary(容错历史 entry 与非对象形态)。 */
29
+ function readDetailsSummary(result: unknown): string | undefined {
30
+ const summary = fieldValue(fieldValue(result, "details"), "summary");
31
+ return typeof summary === "string" && summary !== "" ? summary : undefined;
32
+ }
33
+
34
+ /** content[0] 的 text 纯文本(content 缺失或非 text 类型时兜底空串)。 */
35
+ function contentText(result: unknown): string {
36
+ const content = fieldValue(result, "content");
37
+ if (!Array.isArray(content)) return "";
38
+ return firstContentText({ content });
39
+ }
40
+
41
+ /**
42
+ * TUI renderResult:折叠态(expanded=false)= details.summary 一行(dim);展开态 =
43
+ * content 纯文本。summary 缺失(旧版本持久化 entry)时折叠态回退 content 文本。
44
+ */
45
+ export function renderRenameSessionResult(
46
+ result: unknown,
47
+ options: { expanded: boolean },
48
+ theme: Theme,
49
+ ): Text {
50
+ const summary = readDetailsSummary(result);
51
+ const collapsed = summary ?? (contentText(result) || "Renamed");
52
+ const text = options.expanded ? contentText(result) : collapsed;
53
+ return new Text(theme.fg("dim", text), 0, 0);
54
+ }