@henryqw/pi-herdr-rename 2.1.7 → 2.1.9
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 +56 -30
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,10 +30,10 @@ Requires Pi Coding Agent 0.84.3+.
|
|
|
30
30
|
|
|
31
31
|
First real user prompt generates a title in the background after Pi expands skill and prompt-template shorthand. It does not delay main reply. Extension-injected prompts, empty prompts, and image-only input are ignored.
|
|
32
32
|
|
|
33
|
-
Shared [`pi-task-models` config](../pi-task-models#config) at `~/.pi/agent/config/pi-task-models.json`
|
|
33
|
+
Shared [`pi-task-models` config](../pi-task-models#config) at `~/.pi/agent/config/pi-task-models.json` can explicitly override the local `pi-herdr-rename/rename` declaration, which defaults to `fast`.
|
|
34
34
|
|
|
35
35
|
Display titles are natural task phrases, preferably three or four words and always at most four words and 20 characters. Model classification stays internal: `refactor: update task logic` displays as `Update task logic` and maps to Git branch `refactor/update-task-logic`.
|
|
36
36
|
|
|
37
|
-
In a linked worktree, a detached checkout or Herdr `worktree/...` branch is renamed; an existing non-generated branch stays. A generated workspace label such as `worktree-brave-meadow-4aa8` becomes display title; custom workspace
|
|
37
|
+
In a linked worktree, a detached checkout or Herdr `worktree/...` branch is renamed; an existing non-generated branch stays. A generated workspace label such as `worktree-brave-meadow-4aa8` becomes the display title automatically; `/rename` also replaces a custom workspace name. Enclosing Herdr tab updates only when this pane is tab's only pane. Outside Herdr, only Pi session name changes.
|
|
38
38
|
|
|
39
|
-
Tries assigned profile primary, then fallback, while honoring configured thinking level. Never substitutes current session model. No viable route leaves titles unchanged. Resuming a session created by this version reapplies saved display title and semantic branch without another model request. Older titles receive no migration.
|
|
39
|
+
Tries assigned profile primary, then fallback, while honoring configured thinking level. Never substitutes current session model. No viable route leaves titles unchanged. Resuming a session created by this version reapplies saved display title and semantic branch without another model request. Herdr and Git synchronization failures appear as warnings; cancellation by a newer rename remains silent. Older titles receive no migration.
|
package/extensions/rename.ts
CHANGED
|
@@ -3,10 +3,12 @@ import {
|
|
|
3
3
|
type ExtensionAPI,
|
|
4
4
|
type ExtensionContext,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { createHerdrClient } from "@henryqw/pi-herdr";
|
|
6
|
+
import { createHerdrClient, withWorktreeLock } from "@henryqw/pi-herdr";
|
|
7
7
|
import {
|
|
8
8
|
readTaskModelsConfig,
|
|
9
|
+
registerModelTask,
|
|
9
10
|
resolveConfiguredTaskRoutes,
|
|
11
|
+
type ModelTask,
|
|
10
12
|
type ResolvedTaskRoute,
|
|
11
13
|
type TaskRouteError,
|
|
12
14
|
} from "@henryqw/pi-task-models";
|
|
@@ -18,7 +20,12 @@ const MAX_CONTEXT_CHARS = 2_000;
|
|
|
18
20
|
const DISPLAY_MAX_WORDS = 4;
|
|
19
21
|
const DISPLAY_MAX_CHARS = 20;
|
|
20
22
|
const SEMANTIC_TYPE_MAX_CHARS = 12;
|
|
21
|
-
const RENAME_TASK =
|
|
23
|
+
export const RENAME_TASK = {
|
|
24
|
+
id: "pi-herdr-rename/rename",
|
|
25
|
+
label: "Conversation rename",
|
|
26
|
+
purpose: "Generate a short conversation title.",
|
|
27
|
+
defaultProfile: "fast",
|
|
28
|
+
} as const satisfies ModelTask;
|
|
22
29
|
const TITLE_STATE_TYPE = "pi-herdr-rename/title";
|
|
23
30
|
const HERDR_DEFAULT_WORKTREE_NAME = /^(?:worktree[-/])?(?:brave|calm|clear|green|lucky|quiet|rapid|silver)-(?:river|cloud|field|forest|harbor|meadow|stone|valley)-[0-9a-f]{4}$/;
|
|
24
31
|
const SEMANTIC_BRANCH = /^[a-z][a-z0-9-]{0,11}\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -42,15 +49,22 @@ function configuredRenameRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
|
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
function validSubject(subject: string): boolean {
|
|
53
|
+
return /^[a-z0-9]+(?: [a-z0-9]+)*$/.test(subject)
|
|
54
|
+
&& subject.length <= DISPLAY_MAX_CHARS
|
|
55
|
+
&& subject.split(" ").length <= DISPLAY_MAX_WORDS;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isDisplayTitle(value: unknown): value is string {
|
|
59
|
+
if (typeof value !== "string" || !value) return false;
|
|
60
|
+
const subject = value[0].toLowerCase() + value.slice(1);
|
|
61
|
+
return validSubject(subject) && value === subject[0].toUpperCase() + subject.slice(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
function parseGeneratedTitle(title: string): GeneratedTitle | undefined {
|
|
46
|
-
const match = /^([a-z][a-z0-9-]*): (
|
|
47
|
-
if (!match) return undefined;
|
|
65
|
+
const match = /^([a-z][a-z0-9-]*): (.+)$/.exec(title);
|
|
66
|
+
if (!match || match[1].length > SEMANTIC_TYPE_MAX_CHARS || !validSubject(match[2])) return undefined;
|
|
48
67
|
const subject = match[2];
|
|
49
|
-
if (
|
|
50
|
-
match[1].length > SEMANTIC_TYPE_MAX_CHARS ||
|
|
51
|
-
subject.length > DISPLAY_MAX_CHARS ||
|
|
52
|
-
subject.split(" ").length > DISPLAY_MAX_WORDS
|
|
53
|
-
) return undefined;
|
|
54
68
|
return {
|
|
55
69
|
display: subject[0].toUpperCase() + subject.slice(1),
|
|
56
70
|
branch: `${match[1]}/${subject.replaceAll(" ", "-")}`,
|
|
@@ -63,7 +77,7 @@ function savedTitle(ctx: ExtensionContext): GeneratedTitle | undefined {
|
|
|
63
77
|
.find((candidate) => candidate.type === "custom" && candidate.customType === TITLE_STATE_TYPE);
|
|
64
78
|
if (entry?.type !== "custom" || !entry.data || typeof entry.data !== "object" || Array.isArray(entry.data)) return undefined;
|
|
65
79
|
const { display, branch } = entry.data as { display?: unknown; branch?: unknown };
|
|
66
|
-
return
|
|
80
|
+
return isDisplayTitle(display) && typeof branch === "string" && SEMANTIC_BRANCH.test(branch)
|
|
67
81
|
? { display, branch }
|
|
68
82
|
: undefined;
|
|
69
83
|
}
|
|
@@ -200,6 +214,7 @@ async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortS
|
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
217
|
+
registerModelTask(pi, RENAME_TASK);
|
|
203
218
|
const herdr = createHerdrClient<{ signal: AbortSignal }>(pi.exec.bind(pi));
|
|
204
219
|
let latestUserText: string | undefined;
|
|
205
220
|
let automaticStarted = false;
|
|
@@ -216,6 +231,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
216
231
|
displayTitle: string,
|
|
217
232
|
branchCandidate: string,
|
|
218
233
|
previousDisplayTitle: string | undefined,
|
|
234
|
+
forceWorkspaceRename: boolean,
|
|
219
235
|
request: number,
|
|
220
236
|
controller: AbortController,
|
|
221
237
|
): Promise<void> => {
|
|
@@ -247,6 +263,16 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
247
263
|
const workspaceName = workspace?.label;
|
|
248
264
|
if (typeof workspaceName !== "string") throw new Error("Herdr workspace response omitted label.");
|
|
249
265
|
const worktree = workspace?.worktree;
|
|
266
|
+
if (
|
|
267
|
+
workspaceName !== displayTitle &&
|
|
268
|
+
(forceWorkspaceRename ||
|
|
269
|
+
(worktree?.is_linked_worktree === true &&
|
|
270
|
+
(HERDR_DEFAULT_WORKTREE_NAME.test(workspaceName) || workspaceName === previousDisplayTitle))) &&
|
|
271
|
+
isCurrent(request, controller)
|
|
272
|
+
) {
|
|
273
|
+
await herdr.run(["workspace", "rename", workspaceId, displayTitle], { signal: controller.signal });
|
|
274
|
+
}
|
|
275
|
+
|
|
250
276
|
const checkoutPath = worktree?.checkout_path;
|
|
251
277
|
if (worktree?.is_linked_worktree !== true || typeof checkoutPath !== "string" || !checkoutPath) return;
|
|
252
278
|
|
|
@@ -258,22 +284,18 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
258
284
|
return result.stdout.trim();
|
|
259
285
|
};
|
|
260
286
|
|
|
261
|
-
|
|
262
|
-
if (!branch || branch.startsWith("worktree/")) {
|
|
287
|
+
await withWorktreeLock(checkoutPath, async () => {
|
|
263
288
|
if (!isCurrent(request, controller)) return;
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
) {
|
|
275
|
-
await herdr.run(["workspace", "rename", workspaceId, displayTitle], { signal: controller.signal });
|
|
276
|
-
}
|
|
289
|
+
const branch = await runGit(["branch", "--show-current"]);
|
|
290
|
+
if (!branch || branch.startsWith("worktree/")) {
|
|
291
|
+
if (!isCurrent(request, controller)) return;
|
|
292
|
+
const branches = (await runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"]))
|
|
293
|
+
.split("\n")
|
|
294
|
+
.filter(Boolean);
|
|
295
|
+
const semanticBranch = availableBranch(branchCandidate, branches);
|
|
296
|
+
await runGit(branch ? ["branch", "-m", semanticBranch] : ["switch", "-c", semanticBranch]);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
277
299
|
};
|
|
278
300
|
|
|
279
301
|
const begin = () => {
|
|
@@ -300,10 +322,10 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
300
322
|
const previousDisplayTitle = saved && pi.getSessionName() === saved.display ? saved.display : undefined;
|
|
301
323
|
pi.setSessionName(title.display);
|
|
302
324
|
pi.appendEntry(TITLE_STATE_TYPE, title);
|
|
303
|
-
await applyHerdr(title.display, title.branch, previousDisplayTitle, request, controller);
|
|
325
|
+
await applyHerdr(title.display, title.branch, previousDisplayTitle, manual, request, controller);
|
|
304
326
|
return title.display;
|
|
305
327
|
} catch (error) {
|
|
306
|
-
if (isCurrent(request, controller)
|
|
328
|
+
if (isCurrent(request, controller)) {
|
|
307
329
|
ctx.ui.notify(error instanceof Error ? error.message : "Rename failed.", "warning");
|
|
308
330
|
}
|
|
309
331
|
return undefined;
|
|
@@ -328,7 +350,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
328
350
|
latestUserText = latestSessionUserText(ctx);
|
|
329
351
|
try {
|
|
330
352
|
const taskModels = readTaskModelsConfig();
|
|
331
|
-
const profileName = taskModels.tasks[RENAME_TASK];
|
|
353
|
+
const profileName = taskModels.tasks[RENAME_TASK.id] ?? RENAME_TASK.defaultProfile;
|
|
332
354
|
if (!taskModels.profiles[profileName]) {
|
|
333
355
|
ctx.ui.notify(`Configure rename task profile ${profileName} with /task-models.`, "warning");
|
|
334
356
|
}
|
|
@@ -341,8 +363,12 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
341
363
|
if (!title || title !== saved?.display) return;
|
|
342
364
|
|
|
343
365
|
const { request, controller } = begin();
|
|
344
|
-
void applyHerdr(title, saved.branch, saved.display, request, controller)
|
|
345
|
-
.catch(() =>
|
|
366
|
+
void applyHerdr(title, saved.branch, saved.display, false, request, controller)
|
|
367
|
+
.catch((error) => {
|
|
368
|
+
if (isCurrent(request, controller)) {
|
|
369
|
+
ctx.ui.notify(error instanceof Error ? error.message : "Rename failed.", "warning");
|
|
370
|
+
}
|
|
371
|
+
})
|
|
346
372
|
.finally(() => finish(request, controller));
|
|
347
373
|
});
|
|
348
374
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-herdr-rename",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.9",
|
|
4
4
|
"description": "Generate short Pi display titles and rename the current Herdr location.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -45,6 +45,6 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@henryqw/pi-herdr": "^0.3.0",
|
|
48
|
-
"@henryqw/pi-task-models": "^
|
|
48
|
+
"@henryqw/pi-task-models": "^3.0.0"
|
|
49
49
|
}
|
|
50
50
|
}
|