@henryqw/pi-herdr-rename 0.2.0 → 0.3.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 +6 -4
- package/extensions/rename.ts +34 -25
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ pi remove npm:@henryqw/pi-herdr-rename
|
|
|
17
17
|
## Behavior
|
|
18
18
|
|
|
19
19
|
- On the first real, non-empty text prompt in a new session, title generation starts in the background and does not delay the main Pi response. Extension-injected prompts, empty prompts, and image-only input are ignored.
|
|
20
|
-
- Successful titles are lowercase, at most
|
|
20
|
+
- Successful titles are lowercase and, by default, at most four words and 40 characters. The first 1,000 characters of user text are sent to the rename model; prompt content is never logged.
|
|
21
21
|
- A successful title updates the Pi session name and current Herdr pane. The enclosing Herdr tab is updated only when the current tab has one pane. Outside Herdr, only the Pi session name changes.
|
|
22
|
-
- Resuming a named session reapplies its saved title without another rename-model request. Automatic failures stay quiet
|
|
22
|
+
- Resuming a named session reapplies its saved title without another rename-model request. Automatic rename-model errors show a warning; other automatic failures stay quiet. Failures do not change labels, fall back locally, or retry.
|
|
23
23
|
|
|
24
24
|
## Manual rename
|
|
25
25
|
|
|
@@ -35,11 +35,13 @@ The selection is saved in:
|
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|
|
38
|
-
"model": "provider/model"
|
|
38
|
+
"model": "provider/model",
|
|
39
|
+
"maxWords": 4,
|
|
40
|
+
"maxChars": 40
|
|
39
41
|
}
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
Missing, malformed, or unavailable
|
|
44
|
+
`maxWords` and `maxChars` accept positive integers and default to 4 and 40. Invalid limits use their defaults. Missing, malformed, or unavailable model selection never falls back to another model.
|
|
43
45
|
|
|
44
46
|
## Development
|
|
45
47
|
|
package/extensions/rename.ts
CHANGED
|
@@ -6,29 +6,43 @@ import {
|
|
|
6
6
|
type ExtensionAPI,
|
|
7
7
|
type ExtensionContext,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { createHerdrClient } from "@henryqw/pi-herdr";
|
|
9
10
|
|
|
10
11
|
const WIDGET_KEY = "pi-herdr-rename";
|
|
11
12
|
const WIDGET_RESULT_MS = 2_000;
|
|
12
13
|
const MAX_MESSAGE_CHARS = 1_000;
|
|
13
14
|
const MAX_CONTEXT_CHARS = 4_000;
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const DEFAULT_MAX_WORDS = 4;
|
|
16
|
+
const DEFAULT_MAX_CHARS = 40;
|
|
16
17
|
const configPath = () => join(getAgentDir(), "config", "pi-herdr-rename.json");
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
type RenameConfig = { model?: string; maxWords: number; maxChars: number };
|
|
20
|
+
|
|
21
|
+
class RenameModelError extends Error {}
|
|
22
|
+
|
|
23
|
+
const positiveInteger = (value: unknown, fallback: number) =>
|
|
24
|
+
typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
25
|
+
|
|
26
|
+
async function configured(): Promise<RenameConfig> {
|
|
19
27
|
try {
|
|
20
28
|
const config: unknown = JSON.parse(await readFile(configPath(), "utf8"));
|
|
21
29
|
if (config && typeof config === "object" && !Array.isArray(config)) {
|
|
22
|
-
const
|
|
23
|
-
|
|
30
|
+
const values = config as { model?: unknown; maxWords?: unknown; maxChars?: unknown };
|
|
31
|
+
return {
|
|
32
|
+
model: typeof values.model === "string" && /^[^\s/]+\/\S+$/.test(values.model) ? values.model : undefined,
|
|
33
|
+
maxWords: positiveInteger(values.maxWords, DEFAULT_MAX_WORDS),
|
|
34
|
+
maxChars: positiveInteger(values.maxChars, DEFAULT_MAX_CHARS),
|
|
35
|
+
};
|
|
24
36
|
}
|
|
25
37
|
} catch {}
|
|
38
|
+
return { maxWords: DEFAULT_MAX_WORDS, maxChars: DEFAULT_MAX_CHARS };
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
async function saveModel(model: string): Promise<void> {
|
|
29
42
|
const path = configPath();
|
|
43
|
+
const { maxWords, maxChars } = await configured();
|
|
30
44
|
await mkdir(dirname(path), { recursive: true });
|
|
31
|
-
await writeFile(path, `${JSON.stringify({ model }, null, 2)}\n`, "utf8");
|
|
45
|
+
await writeFile(path, `${JSON.stringify({ model, maxWords, maxChars }, null, 2)}\n`, "utf8");
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
function messageText(content: unknown): string {
|
|
@@ -85,7 +99,7 @@ function recentConversation(ctx: ExtensionContext, fallback?: string): string |
|
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortSignal): Promise<string> {
|
|
88
|
-
const key = await
|
|
102
|
+
const { model: key, maxWords, maxChars } = await configured();
|
|
89
103
|
if (!key) throw new Error("Rename model is not configured. Run /rename-model.");
|
|
90
104
|
const separator = key.indexOf("/");
|
|
91
105
|
const provider = key.slice(0, separator);
|
|
@@ -98,11 +112,14 @@ async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortS
|
|
|
98
112
|
const response = await ctx.modelRegistry.complete(
|
|
99
113
|
model,
|
|
100
114
|
{
|
|
101
|
-
systemPrompt:
|
|
115
|
+
systemPrompt: `Return only a short chat title for the current conversation topic, prioritizing the most recent user intent: lowercase, at most ${maxWords} words, and at most ${maxChars} characters.`,
|
|
102
116
|
messages: [{ role: "user", content: text.slice(0, MAX_CONTEXT_CHARS), timestamp: Date.now() }],
|
|
103
117
|
},
|
|
104
118
|
{ signal, maxRetries: 0, maxTokens: 64 },
|
|
105
119
|
);
|
|
120
|
+
if (response.stopReason === "error") {
|
|
121
|
+
throw new RenameModelError(response.errorMessage || "Rename model failed.");
|
|
122
|
+
}
|
|
106
123
|
if (response.stopReason !== "stop") throw new Error("Rename model did not return a complete title.");
|
|
107
124
|
|
|
108
125
|
const title = response.content
|
|
@@ -112,21 +129,15 @@ async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortS
|
|
|
112
129
|
.trim()
|
|
113
130
|
.toLowerCase()
|
|
114
131
|
.replace(/\s+/g, " ");
|
|
115
|
-
if (!title || title.length >
|
|
132
|
+
if (!title || title.length > maxChars || title.split(" ").length > maxWords) {
|
|
116
133
|
throw new Error("Rename model returned an invalid title.");
|
|
117
134
|
}
|
|
118
135
|
return title;
|
|
119
136
|
}
|
|
120
137
|
|
|
121
|
-
async function herdr(pi: ExtensionAPI, args: string[], signal: AbortSignal): Promise<string> {
|
|
122
|
-
const result = await pi.exec("herdr", args, { signal });
|
|
123
|
-
if (result.code !== 0 || result.killed) {
|
|
124
|
-
throw new Error(`Herdr ${args[0]} failed: ${result.stderr.trim() || `exit code ${result.code}`}`);
|
|
125
|
-
}
|
|
126
|
-
return result.stdout;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
138
|
export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
139
|
+
const herdr = createHerdrClient<{ signal: AbortSignal }>((command, args, options) =>
|
|
140
|
+
pi.exec(command, [...args], options));
|
|
130
141
|
let latestUserText: string | undefined;
|
|
131
142
|
let automaticStarted = false;
|
|
132
143
|
let sequence = 0;
|
|
@@ -142,21 +153,19 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
142
153
|
if (!paneId) return;
|
|
143
154
|
|
|
144
155
|
if (!isCurrent(request, controller)) return;
|
|
145
|
-
await herdr(
|
|
156
|
+
await herdr.run(["pane", "rename", paneId, title], { signal: controller.signal });
|
|
146
157
|
if (!isCurrent(request, controller)) return;
|
|
147
158
|
|
|
148
|
-
const paneResponse: unknown =
|
|
149
|
-
await herdr(pi, ["pane", "get", paneId], controller.signal),
|
|
150
|
-
);
|
|
159
|
+
const paneResponse: unknown = await herdr.json(["pane", "get", paneId], { signal: controller.signal });
|
|
151
160
|
const tabId = (paneResponse as { result?: { pane?: { tab_id?: unknown } } }).result?.pane?.tab_id;
|
|
152
161
|
if (typeof tabId !== "string" || !tabId) throw new Error("Herdr pane response omitted tab_id.");
|
|
153
162
|
if (!isCurrent(request, controller)) return;
|
|
154
163
|
|
|
155
|
-
const tabResponse: unknown =
|
|
164
|
+
const tabResponse: unknown = await herdr.json(["tab", "get", tabId], { signal: controller.signal });
|
|
156
165
|
const paneCount = (tabResponse as { result?: { tab?: { pane_count?: unknown } } }).result?.tab?.pane_count;
|
|
157
166
|
if (typeof paneCount !== "number") throw new Error("Herdr tab response omitted pane_count.");
|
|
158
167
|
if (paneCount === 1 && isCurrent(request, controller)) {
|
|
159
|
-
await herdr(
|
|
168
|
+
await herdr.run(["tab", "rename", tabId, title], { signal: controller.signal });
|
|
160
169
|
}
|
|
161
170
|
};
|
|
162
171
|
|
|
@@ -180,7 +189,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
180
189
|
await applyHerdr(title, request, controller);
|
|
181
190
|
return title;
|
|
182
191
|
} catch (error) {
|
|
183
|
-
if (
|
|
192
|
+
if (isCurrent(request, controller) && (manual || error instanceof RenameModelError)) {
|
|
184
193
|
ctx.ui.notify(error instanceof Error ? error.message : "Rename failed.", "warning");
|
|
185
194
|
}
|
|
186
195
|
return undefined;
|
|
@@ -202,7 +211,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
202
211
|
active = undefined;
|
|
203
212
|
sequence++;
|
|
204
213
|
latestUserText = latestSessionUserText(ctx);
|
|
205
|
-
if (!(await
|
|
214
|
+
if (!(await configured()).model) {
|
|
206
215
|
ctx.ui.notify("Run /rename-model to configure chat title generation.", "warning");
|
|
207
216
|
}
|
|
208
217
|
const title = pi.getSessionName();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-herdr-rename",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Generate short Pi chat titles and rename the current Herdr location.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -42,5 +42,8 @@
|
|
|
42
42
|
"extensions": [
|
|
43
43
|
"./extensions"
|
|
44
44
|
]
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@henryqw/pi-herdr": "^0.1.0"
|
|
45
48
|
}
|
|
46
49
|
}
|