@henryqw/pi-herdr-rename 0.1.0 → 0.2.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 +3 -3
- package/extensions/rename.ts +45 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,11 +23,11 @@ pi remove npm:@henryqw/pi-herdr-rename
|
|
|
23
23
|
|
|
24
24
|
## Manual rename
|
|
25
25
|
|
|
26
|
-
Run `/rename` to generate a title from up to the three most recent user/assistant rounds. It uses text only, caps each message at 1,000 characters and the complete context at 4,000 characters, and applies the same Pi and Herdr rules. The command warns without changing anything when no user text exists or generation fails. If requests overlap, the latest rename request wins.
|
|
26
|
+
Run `/rename` to generate a title from up to the three most recent user/assistant rounds. It uses text only, caps each message at 1,000 characters and the complete context at 4,000 characters, and applies the same Pi and Herdr rules. An animated widget shows `renaming...`, briefly changes to `renamed to <title>`, then disappears. The command warns without changing anything when no user text exists or generation fails. If requests overlap, the latest rename request wins.
|
|
27
27
|
|
|
28
28
|
## Rename model
|
|
29
29
|
|
|
30
|
-
Run `/rename-model` to choose
|
|
30
|
+
Run `/rename-model` to choose the model from Pi's available authenticated text models. Until a valid selection is saved, every session start prompts you to run the command and title generation remains disabled.
|
|
31
31
|
|
|
32
32
|
The selection is saved in:
|
|
33
33
|
|
|
@@ -39,7 +39,7 @@ The selection is saved in:
|
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Missing
|
|
42
|
+
Missing, malformed, or unavailable configuration never falls back to another model.
|
|
43
43
|
|
|
44
44
|
## Development
|
|
45
45
|
|
package/extensions/rename.ts
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import {
|
|
4
|
+
BorderedLoader,
|
|
4
5
|
getAgentDir,
|
|
5
6
|
type ExtensionAPI,
|
|
6
7
|
type ExtensionContext,
|
|
7
8
|
} from "@earendil-works/pi-coding-agent";
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
+
const WIDGET_KEY = "pi-herdr-rename";
|
|
11
|
+
const WIDGET_RESULT_MS = 2_000;
|
|
10
12
|
const MAX_MESSAGE_CHARS = 1_000;
|
|
11
13
|
const MAX_CONTEXT_CHARS = 4_000;
|
|
12
14
|
const TITLE_PROMPT =
|
|
13
15
|
"Return only a short chat title for the current conversation topic, prioritizing the most recent user intent: lowercase, at most five words, and at most 60 characters.";
|
|
14
16
|
const configPath = () => join(getAgentDir(), "config", "pi-herdr-rename.json");
|
|
15
17
|
|
|
16
|
-
async function configuredModel(): Promise<string> {
|
|
18
|
+
async function configuredModel(): Promise<string | undefined> {
|
|
17
19
|
try {
|
|
18
20
|
const config: unknown = JSON.parse(await readFile(configPath(), "utf8"));
|
|
19
21
|
if (config && typeof config === "object" && !Array.isArray(config)) {
|
|
20
22
|
const model = (config as { model?: unknown }).model;
|
|
21
23
|
if (typeof model === "string" && /^[^\s/]+\/\S+$/.test(model)) return model;
|
|
22
24
|
}
|
|
23
|
-
} catch {
|
|
24
|
-
// Missing or malformed config uses the default model.
|
|
25
|
-
}
|
|
26
|
-
return DEFAULT_MODEL;
|
|
25
|
+
} catch {}
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async function saveModel(model: string): Promise<void> {
|
|
@@ -87,6 +86,7 @@ function recentConversation(ctx: ExtensionContext, fallback?: string): string |
|
|
|
87
86
|
|
|
88
87
|
async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortSignal): Promise<string> {
|
|
89
88
|
const key = await configuredModel();
|
|
89
|
+
if (!key) throw new Error("Rename model is not configured. Run /rename-model.");
|
|
90
90
|
const separator = key.indexOf("/");
|
|
91
91
|
const provider = key.slice(0, separator);
|
|
92
92
|
const id = key.slice(separator + 1);
|
|
@@ -131,6 +131,8 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
131
131
|
let automaticStarted = false;
|
|
132
132
|
let sequence = 0;
|
|
133
133
|
let active: AbortController | undefined;
|
|
134
|
+
let widgetSequence = 0;
|
|
135
|
+
let widgetTimer: ReturnType<typeof setTimeout> | undefined;
|
|
134
136
|
|
|
135
137
|
const isCurrent = (request: number, controller: AbortController) =>
|
|
136
138
|
request === sequence && active === controller && !controller.signal.aborted;
|
|
@@ -169,27 +171,40 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
169
171
|
if (isCurrent(request, controller)) active = undefined;
|
|
170
172
|
};
|
|
171
173
|
|
|
172
|
-
const rename = async (text: string, ctx: ExtensionContext, manual: boolean): Promise<
|
|
174
|
+
const rename = async (text: string, ctx: ExtensionContext, manual: boolean): Promise<string | undefined> => {
|
|
173
175
|
const { request, controller } = begin();
|
|
174
176
|
try {
|
|
175
177
|
const title = await generateTitle(text, ctx, controller.signal);
|
|
176
178
|
if (!isCurrent(request, controller)) return;
|
|
177
179
|
pi.setSessionName(title);
|
|
178
180
|
await applyHerdr(title, request, controller);
|
|
181
|
+
return title;
|
|
179
182
|
} catch (error) {
|
|
180
183
|
if (manual && isCurrent(request, controller)) {
|
|
181
184
|
ctx.ui.notify(error instanceof Error ? error.message : "Rename failed.", "warning");
|
|
182
185
|
}
|
|
186
|
+
return undefined;
|
|
183
187
|
} finally {
|
|
184
188
|
finish(request, controller);
|
|
185
189
|
}
|
|
186
190
|
};
|
|
187
191
|
|
|
188
|
-
|
|
192
|
+
const clearWidget = (ctx: ExtensionContext) => {
|
|
193
|
+
widgetSequence++;
|
|
194
|
+
if (widgetTimer) clearTimeout(widgetTimer);
|
|
195
|
+
widgetTimer = undefined;
|
|
196
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
200
|
+
clearWidget(ctx);
|
|
189
201
|
active?.abort();
|
|
190
202
|
active = undefined;
|
|
191
203
|
sequence++;
|
|
192
204
|
latestUserText = latestSessionUserText(ctx);
|
|
205
|
+
if (!(await configuredModel())) {
|
|
206
|
+
ctx.ui.notify("Run /rename-model to configure chat title generation.", "warning");
|
|
207
|
+
}
|
|
193
208
|
const title = pi.getSessionName();
|
|
194
209
|
automaticStarted = Boolean(title || latestUserText);
|
|
195
210
|
if (!title) return;
|
|
@@ -210,7 +225,8 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
210
225
|
return { action: "continue" };
|
|
211
226
|
});
|
|
212
227
|
|
|
213
|
-
pi.on("session_shutdown", () => {
|
|
228
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
229
|
+
clearWidget(ctx);
|
|
214
230
|
active?.abort();
|
|
215
231
|
active = undefined;
|
|
216
232
|
sequence++;
|
|
@@ -224,7 +240,26 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
224
240
|
ctx.ui.notify("No user text is available to rename this chat.", "warning");
|
|
225
241
|
return;
|
|
226
242
|
}
|
|
227
|
-
|
|
243
|
+
if (widgetTimer) clearTimeout(widgetTimer);
|
|
244
|
+
widgetTimer = undefined;
|
|
245
|
+
const widgetRequest = ++widgetSequence;
|
|
246
|
+
ctx.ui.setWidget(
|
|
247
|
+
WIDGET_KEY,
|
|
248
|
+
(tui, theme) => new BorderedLoader(tui, theme, "renaming...", { cancellable: false }),
|
|
249
|
+
);
|
|
250
|
+
const title = await rename(context, ctx, true);
|
|
251
|
+
if (widgetRequest !== widgetSequence) return;
|
|
252
|
+
if (!title) {
|
|
253
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
ctx.ui.setWidget(WIDGET_KEY, [`renamed to ${title}`]);
|
|
257
|
+
widgetTimer = setTimeout(() => {
|
|
258
|
+
if (widgetRequest !== widgetSequence) return;
|
|
259
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
260
|
+
widgetTimer = undefined;
|
|
261
|
+
}, WIDGET_RESULT_MS);
|
|
262
|
+
widgetTimer.unref?.();
|
|
228
263
|
},
|
|
229
264
|
});
|
|
230
265
|
|