@henryqw/pi-herdr-tools 1.4.0 → 1.4.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 +2 -2
- package/extensions/index.ts +4 -2
- package/internal/btw.ts +3 -36
- package/internal/rename.ts +3 -38
- package/internal/task-models.ts +48 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,7 +152,7 @@ Normal cleanup does these steps. It checks whether a tab in another Herdr worksp
|
|
|
152
152
|
- The consumer-owned `pi-herdr-btw/btw` task defaults to `fast`.
|
|
153
153
|
- Before the pane starts, `/btw` selects the first authenticated viable effective profile route.
|
|
154
154
|
|
|
155
|
-
`~/.pi/agent/config/pi-task-models/config.json` is shared, and `@henryqw/pi-task-models` owns it. A task entry is an explicit user override. Routes resolve before the pane starts. If this file is missing,
|
|
155
|
+
`~/.pi/agent/config/pi-task-models/config.json` is shared, and `@henryqw/pi-task-models` owns it. A task entry is an explicit user override. Routes resolve before the pane starts. If this file is missing or unreadable, or a task profile is not configured, the package shows one warning one time per session.
|
|
156
156
|
|
|
157
157
|
#### Merge delivery
|
|
158
158
|
|
|
@@ -253,7 +253,7 @@ A large Main context can be more than the context limit of the side thread. Make
|
|
|
253
253
|
|
|
254
254
|
Display titles are natural task phrases. They usually have two or three words, and they always have a maximum of four words and 20 characters. Models that answer in another language still make English display titles.
|
|
255
255
|
|
|
256
|
-
If the shared task-model config is missing, the
|
|
256
|
+
If the shared task-model config is missing, the package shows one warning one time per session at session start. Run `/task-models` to configure rename routing. If no route is viable, display titles do not change. `/rename` shows a warning if the session has no user text to rename.
|
|
257
257
|
|
|
258
258
|
Herdr and Git synchronization failures show as warnings. Correct the reported problem. Then run `/rename` to try again. When a newer rename cancels an older rename, the extension shows no message. Older titles get no migration.
|
|
259
259
|
|
package/extensions/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import registerBtwExtension from "../internal/btw.ts";
|
|
2
|
+
import registerBtwExtension, { BTW_TASK } from "../internal/btw.ts";
|
|
3
3
|
import herdrCloneExtension from "../internal/clone-tab.ts";
|
|
4
4
|
import herdrDoneExtension from "../internal/done.ts";
|
|
5
5
|
import herdrHandoffExtension from "../internal/handoff.ts";
|
|
6
|
-
import herdrRenameExtension from "../internal/rename.ts";
|
|
6
|
+
import herdrRenameExtension, { RENAME_TASK } from "../internal/rename.ts";
|
|
7
|
+
import { registerTaskModelsWarning } from "../internal/task-models.ts";
|
|
7
8
|
|
|
8
9
|
export default async function herdrToolsExtension(pi: ExtensionAPI): Promise<void> {
|
|
10
|
+
registerTaskModelsWarning(pi, [RENAME_TASK, BTW_TASK]);
|
|
9
11
|
herdrRenameExtension(pi);
|
|
10
12
|
await registerBtwExtension(pi);
|
|
11
13
|
herdrCloneExtension(pi);
|
package/internal/btw.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { createHerdrClient, hasHerdrErrorCode, startPiAgent } from "@henryqw/pi-herdr";
|
|
4
|
-
import {
|
|
5
|
-
loadTaskModelsConfig,
|
|
6
|
-
registerModelTask,
|
|
7
|
-
resolveConfiguredTaskRoutes,
|
|
8
|
-
type ModelTask,
|
|
9
|
-
type ResolvedTaskRoute,
|
|
10
|
-
type TaskRouteError,
|
|
11
|
-
} from "@henryqw/pi-task-models";
|
|
4
|
+
import { registerModelTask, type ModelTask, type ResolvedTaskRoute } from "@henryqw/pi-task-models";
|
|
12
5
|
import {
|
|
13
6
|
buildSessionContext,
|
|
14
7
|
convertToLlm,
|
|
@@ -51,6 +44,7 @@ import {
|
|
|
51
44
|
type MergeRequest,
|
|
52
45
|
} from "./merge.ts";
|
|
53
46
|
import { HELP_TEXT, parseBtwCommand } from "./router.ts";
|
|
47
|
+
import { configuredTaskRoutes } from "./task-models.ts";
|
|
54
48
|
|
|
55
49
|
export const BTW_TASK = {
|
|
56
50
|
id: "pi-herdr-btw/btw",
|
|
@@ -97,21 +91,6 @@ function sameStringArray(a: string[], b: string[]): boolean {
|
|
|
97
91
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
function configuredBtwRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
|
|
101
|
-
try {
|
|
102
|
-
return resolveConfiguredTaskRoutes(ctx, BTW_TASK);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
const { taskRouteCode, profileName } = error as TaskRouteError;
|
|
105
|
-
throw new Error(
|
|
106
|
-
taskRouteCode === "profile-missing"
|
|
107
|
-
? `BTW task profile ${profileName} is not configured. Run /task-models.`
|
|
108
|
-
: taskRouteCode === "no-route"
|
|
109
|
-
? `BTW task profile ${profileName} has no available route. Run /task-models.`
|
|
110
|
-
: "Couldn't read task model config. Run /task-models.",
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
94
|
/**
|
|
116
95
|
* Decide whether the child can replay the parent's exact request prefix
|
|
117
96
|
* (system prompt, tools, model, thinking) for provider prompt-cache reuse.
|
|
@@ -401,7 +380,6 @@ export async function registerBtwExtension(
|
|
|
401
380
|
| Pick<ExtensionCommandContext, "sessionManager" | "isIdle" | "model" | "modelRegistry">
|
|
402
381
|
| undefined;
|
|
403
382
|
let sessionGeneration = 0;
|
|
404
|
-
let missingTaskModelsConfigWarningSessionId: string | undefined;
|
|
405
383
|
// Notifications need a UI context; route them through the last known ctx.
|
|
406
384
|
let notifyFn: ((message: string, type: "info" | "warning" | "error") => void) | undefined;
|
|
407
385
|
const coordinator = new MergeCoordinator(store, {
|
|
@@ -454,17 +432,6 @@ export async function registerBtwExtension(
|
|
|
454
432
|
}
|
|
455
433
|
|
|
456
434
|
pi.on("session_start", (_event, ctx) => {
|
|
457
|
-
const sessionId = ctx.sessionManager.getSessionId();
|
|
458
|
-
let taskModelsConfig: ReturnType<typeof loadTaskModelsConfig> | undefined;
|
|
459
|
-
try {
|
|
460
|
-
taskModelsConfig = loadTaskModelsConfig();
|
|
461
|
-
} catch {
|
|
462
|
-
ctx.ui.notify("Couldn't read task model config. Run /task-models.", "warning");
|
|
463
|
-
}
|
|
464
|
-
if (missingTaskModelsConfigWarningSessionId !== sessionId && taskModelsConfig?.source === "missing") {
|
|
465
|
-
missingTaskModelsConfigWarningSessionId = sessionId;
|
|
466
|
-
ctx.ui.notify("Task model config is missing; run /task-models to configure it.", "warning");
|
|
467
|
-
}
|
|
468
435
|
sessionGeneration += 1;
|
|
469
436
|
sessionCtx = ctx;
|
|
470
437
|
notifyFn = (message, type) => ctx.ui.notify(message, type);
|
|
@@ -599,7 +566,7 @@ export async function registerBtwExtension(
|
|
|
599
566
|
const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null;
|
|
600
567
|
const thinkingLevel = pi.getThinkingLevel();
|
|
601
568
|
let launchRoute: ResolvedTaskRoute | undefined;
|
|
602
|
-
for (const route of
|
|
569
|
+
for (const route of configuredTaskRoutes(ctx, BTW_TASK, "BTW")) {
|
|
603
570
|
try {
|
|
604
571
|
const authenticated = (await ctx.modelRegistry.getApiKeyAndHeaders(route.model)).ok;
|
|
605
572
|
if (launchGeneration !== sessionGeneration) return;
|
package/internal/rename.ts
CHANGED
|
@@ -4,15 +4,8 @@ import {
|
|
|
4
4
|
type ExtensionContext,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { createHerdrClient, withWorktreeLock } from "@henryqw/pi-herdr";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
loadTaskModelsConfig,
|
|
10
|
-
registerModelTask,
|
|
11
|
-
resolveConfiguredTaskRoutes,
|
|
12
|
-
type ModelTask,
|
|
13
|
-
type ResolvedTaskRoute,
|
|
14
|
-
type TaskRouteError,
|
|
15
|
-
} from "@henryqw/pi-task-models";
|
|
7
|
+
import { executeTaskRoutes, registerModelTask, type ModelTask, type ResolvedTaskRoute } from "@henryqw/pi-task-models";
|
|
8
|
+
import { configuredTaskRoutes } from "./task-models.ts";
|
|
16
9
|
|
|
17
10
|
const WIDGET_KEY = "pi-herdr-rename";
|
|
18
11
|
const WIDGET_RESULT_MS = 2_000;
|
|
@@ -39,21 +32,6 @@ type GeneratedTitle = { display: string; branch: string };
|
|
|
39
32
|
|
|
40
33
|
class RenameModelError extends Error {}
|
|
41
34
|
|
|
42
|
-
function configuredRenameRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
|
|
43
|
-
try {
|
|
44
|
-
return resolveConfiguredTaskRoutes(ctx, RENAME_TASK);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const { taskRouteCode, profileName } = error as TaskRouteError;
|
|
47
|
-
throw new RenameModelError(
|
|
48
|
-
taskRouteCode === "profile-missing"
|
|
49
|
-
? `Rename task profile ${profileName} is not configured. Run /task-models.`
|
|
50
|
-
: taskRouteCode === "no-route"
|
|
51
|
-
? `Rename task profile ${profileName} has no available route. Run /task-models.`
|
|
52
|
-
: "Couldn't read task model config. Run /task-models.",
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
35
|
function validSubject(subject: string): boolean {
|
|
58
36
|
return /^[a-z0-9]+(?: [a-z0-9]+)*$/.test(subject)
|
|
59
37
|
&& subject.length <= DISPLAY_MAX_CHARS
|
|
@@ -192,7 +170,7 @@ async function generateTitle(text: string, ctx: ExtensionContext, signal: AbortS
|
|
|
192
170
|
};
|
|
193
171
|
|
|
194
172
|
return executeTaskRoutes(
|
|
195
|
-
|
|
173
|
+
configuredTaskRoutes(ctx, RENAME_TASK, "Rename"),
|
|
196
174
|
async (route) => {
|
|
197
175
|
const firstMessages = [{ role: "user" as const, content: prompt, timestamp: Date.now() }];
|
|
198
176
|
const response = await complete(route, firstMessages);
|
|
@@ -358,19 +336,6 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
358
336
|
sequence++;
|
|
359
337
|
automaticPending = false;
|
|
360
338
|
latestUserText = latestSessionUserText(ctx);
|
|
361
|
-
try {
|
|
362
|
-
const taskModels = loadTaskModelsConfig();
|
|
363
|
-
if (taskModels.source === "missing") {
|
|
364
|
-
ctx.ui.notify("Task model config is missing; run /task-models to configure rename routing.", "warning");
|
|
365
|
-
} else {
|
|
366
|
-
const profileName = taskModels.value.tasks[RENAME_TASK.id] ?? RENAME_TASK.defaultProfile;
|
|
367
|
-
if (!taskModels.value.profiles[profileName]) {
|
|
368
|
-
ctx.ui.notify(`Configure rename task profile ${profileName} with /task-models.`, "warning");
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
} catch {
|
|
372
|
-
ctx.ui.notify("Couldn't read task model config. Run /task-models.", "warning");
|
|
373
|
-
}
|
|
374
339
|
const title = pi.getSessionName();
|
|
375
340
|
automaticStarted = Boolean(title || latestUserText);
|
|
376
341
|
const saved = savedTitle(ctx);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
loadTaskModelsConfig,
|
|
4
|
+
resolveConfiguredTaskRoutes,
|
|
5
|
+
type ModelTask,
|
|
6
|
+
type ResolvedTaskRoute,
|
|
7
|
+
type TaskRouteError,
|
|
8
|
+
} from "@henryqw/pi-task-models";
|
|
9
|
+
|
|
10
|
+
// Pi reloads extensions without a module cache, so process state keeps the warning once per session.
|
|
11
|
+
const WARNED_SESSION = Symbol.for("pi-herdr-tools/task-models-warned-session");
|
|
12
|
+
|
|
13
|
+
/** Resolve routes for `task`; profile errors name the feature, config errors keep pi-task-models' message. */
|
|
14
|
+
export function configuredTaskRoutes(ctx: ExtensionContext, task: ModelTask, label: string): ResolvedTaskRoute[] {
|
|
15
|
+
try {
|
|
16
|
+
return resolveConfiguredTaskRoutes(ctx, task);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
const { taskRouteCode, profileName } = error as TaskRouteError;
|
|
19
|
+
if (taskRouteCode === "profile-missing") throw new Error(`${label} task profile ${profileName} is not configured. Run /task-models.`);
|
|
20
|
+
if (taskRouteCode === "no-route") throw new Error(`${label} task profile ${profileName} has no available route. Run /task-models.`);
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Warn at most once per session when the shared config cannot route `tasks`. */
|
|
26
|
+
export function registerTaskModelsWarning(pi: ExtensionAPI, tasks: readonly ModelTask[]): void {
|
|
27
|
+
pi.on("session_start", (_event, ctx) => {
|
|
28
|
+
const state = globalThis as { [WARNED_SESSION]?: string };
|
|
29
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
30
|
+
if (state[WARNED_SESSION] === sessionId) return;
|
|
31
|
+
let message: string | undefined;
|
|
32
|
+
try {
|
|
33
|
+
const config = loadTaskModelsConfig();
|
|
34
|
+
if (config.source === "missing") {
|
|
35
|
+
message = "Task model config is missing. Run /task-models to configure task routes.";
|
|
36
|
+
} else {
|
|
37
|
+
const missing = [...new Set(tasks.map((task) => config.value.tasks[task.id] ?? task.defaultProfile))]
|
|
38
|
+
.filter((profile) => !config.value.profiles[profile]);
|
|
39
|
+
if (missing.length) message = `Task model profile ${missing.join(", ")} is not configured. Run /task-models.`;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
message = "Couldn't read task model config. Run /task-models.";
|
|
43
|
+
}
|
|
44
|
+
if (!message) return;
|
|
45
|
+
state[WARNED_SESSION] = sessionId;
|
|
46
|
+
ctx.ui.notify(message, "warning");
|
|
47
|
+
});
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-herdr-tools",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Clone, hand off, name, and finish Pi conversations in Herdr, with mergeable side questions.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "herdr", "session", "clone", "side-thread", "title"],
|
|
6
6
|
"type": "module",
|