pi-prompt-template-model 0.8.0 → 0.9.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/CHANGELOG.md +28 -0
- package/README.md +2 -1
- package/index.ts +54 -3
- package/loop-utils.ts +10 -4
- package/package.json +8 -1
- package/prompt-loader.ts +42 -1
- package/skill-loaded-renderer.ts +1 -1
- package/skills/prompt-template-authoring/SKILL.md +189 -0
- package/tool-manager.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.0] - 2026-04-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added `boomerang: true` prompt frontmatter so non-chain templates, including looped templates, can run through prompt-template-model and then collapse their execution context back to the pre-run branch.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Migrated extension tool schemas from `@sinclair/typebox` to `typebox` 1.x so packaged installs follow Pi's current extension runtime contract.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Added `typebox` as a runtime dependency for packaged installs.
|
|
15
|
+
|
|
16
|
+
## [0.8.2] - 2026-04-21
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Added the packaged `prompt-template-authoring` skill for creating and maintaining prompt templates with this extension.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Tightened the shipped skill guide so it stays short, repo-specific, and aligned with the actual prompt-template runtime.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Corrected the shipped skill examples for model fallback vs rotation, argument substitution, deterministic handoff behavior, chain context wording, runtime flag syntax, and prompt discovery rules.
|
|
26
|
+
- Removed the emoji from the skill-loaded renderer so the UI copy matches the extension's plain-text style.
|
|
27
|
+
|
|
28
|
+
## [0.8.1] - 2026-04-21
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Added agent skill `prompt-template-authoring` for writing, managing, and running custom prompt templates. Registered in `package.json` under `pi.skills`.
|
|
32
|
+
|
|
5
33
|
## [0.8.0] - 2026-04-21
|
|
6
34
|
|
|
7
35
|
### Added
|
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ All fields are optional. Templates that don't use any extension features (no `mo
|
|
|
73
73
|
| `rotate` | `false` | When `true` and looping, cycle through models in the `model` list instead of using fallback semantics. Thinking levels can also be comma-separated to pair with each model. |
|
|
74
74
|
| `fresh` | `false` | When looping, collapse the conversation between iterations to a brief summary instead of carrying the full context forward. Saves tokens on long loops. |
|
|
75
75
|
| `converge` | `true` | When looping, stop early if an iteration makes no file changes. Set `false` to always run every iteration. |
|
|
76
|
+
| `boomerang` | `false` | After a non-chain prompt finishes, collapse its execution context back to the branch point with a brief summary. Works with loops, including `fresh` loop summaries. Useful for review prompts like `/double-check`. |
|
|
76
77
|
| `worktree` | `false` | When `true`, parallel delegated work runs in separate git worktrees. Valid on chain templates with `parallel()` steps, on delegated prompts with `parallel: N`, and on compare templates via `bestOfN.worktree`. |
|
|
77
78
|
|
|
78
79
|
### Delegation
|
|
@@ -291,7 +292,7 @@ This repo ships one example compare prompt under `examples/`:
|
|
|
291
292
|
- `examples/best-of-n.md` installs as `/best-of-n`, runs in the current repo, and shows mixed workers, mixed reviewers, and an optional final apply phase.
|
|
292
293
|
- Smoke test: `/best-of-n smoke test`.
|
|
293
294
|
|
|
294
|
-
Install
|
|
295
|
+
Install it manually from this repo checkout (or from the installed package directory):
|
|
295
296
|
|
|
296
297
|
```bash
|
|
297
298
|
PTM_DIR=/path/to/pi-prompt-template-model
|
package/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type SubagentOverride,
|
|
17
17
|
} from "./args.js";
|
|
18
18
|
import { parseChainSteps, parseChainDeclaration, type ChainStep, type ChainStepOrParallel, type ParallelChainStep } from "./chain-parser.js";
|
|
19
|
-
import { generateChainStepSummary, generateIterationSummary, didIterationMakeChanges, getIterationEntries, wasIterationAborted } from "./loop-utils.js";
|
|
19
|
+
import { generateBoomerangSummary, generateChainStepSummary, generateIterationSummary, didIterationMakeChanges, getIterationEntries, wasIterationAborted } from "./loop-utils.js";
|
|
20
20
|
import { selectModelCandidate } from "./model-selection.js";
|
|
21
21
|
import { notify, summarizePromptDiagnostics, diagnosticsFingerprint } from "./notifications.js";
|
|
22
22
|
import { preparePromptExecution, renderPromptForResolvedModel } from "./prompt-execution.js";
|
|
@@ -56,6 +56,12 @@ interface FreshCollapse {
|
|
|
56
56
|
totalIterations: number | null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
interface BoomerangCollapse {
|
|
60
|
+
targetId: string;
|
|
61
|
+
task: string;
|
|
62
|
+
previousSummaries: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
interface PendingSkillMessage {
|
|
60
66
|
customType: "skill-loaded";
|
|
61
67
|
content: string;
|
|
@@ -108,6 +114,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
108
114
|
let chainActive = false;
|
|
109
115
|
let loopState: LoopState | null = null;
|
|
110
116
|
let freshCollapse: FreshCollapse | null = null;
|
|
117
|
+
let boomerangCollapse: BoomerangCollapse | null = null;
|
|
111
118
|
let accumulatedSummaries: string[] = [];
|
|
112
119
|
let lastDiagnostics = "";
|
|
113
120
|
let storedCommandCtx: ExtensionCommandContext | null = null;
|
|
@@ -918,6 +925,26 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
918
925
|
}
|
|
919
926
|
}
|
|
920
927
|
|
|
928
|
+
async function collapseBoomerangPrompt(
|
|
929
|
+
ctx: ExtensionContext,
|
|
930
|
+
name: string,
|
|
931
|
+
targetId: string | null,
|
|
932
|
+
previousSummaries: string[] = [],
|
|
933
|
+
) {
|
|
934
|
+
if (!targetId) {
|
|
935
|
+
notify(ctx, `Cannot boomerang prompt \`${name}\`: no session entry to return to.`, "warning");
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
boomerangCollapse = { targetId, task: name, previousSummaries };
|
|
940
|
+
try {
|
|
941
|
+
const result = await ctx.navigateTree(targetId, { summarize: true });
|
|
942
|
+
if (result.cancelled) notify(ctx, `Boomerang cancelled for prompt \`${name}\``, "warning");
|
|
943
|
+
} finally {
|
|
944
|
+
boomerangCollapse = null;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
921
948
|
async function runPromptLoop(
|
|
922
949
|
name: string,
|
|
923
950
|
cleanedArgs: string,
|
|
@@ -942,10 +969,11 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
942
969
|
let currentThinking = savedThinking;
|
|
943
970
|
const shouldRestore = initialPrompt.restore;
|
|
944
971
|
const useFresh = freshFlag || initialPrompt.fresh === true;
|
|
972
|
+
const shouldBoomerang = initialPrompt.boomerang === true;
|
|
945
973
|
const effectiveMax = totalIterations ?? UNLIMITED_LOOP_CAP;
|
|
946
974
|
const isUnlimited = totalIterations === null;
|
|
947
975
|
const useConverge = converge && initialPrompt.converge !== false;
|
|
948
|
-
const anchorId = useFresh ? ctx.sessionManager.getLeafId() : null;
|
|
976
|
+
const anchorId = useFresh || shouldBoomerang ? ctx.sessionManager.getLeafId() : null;
|
|
949
977
|
|
|
950
978
|
loopState = { currentIteration: 1, totalIterations };
|
|
951
979
|
accumulatedSummaries = [];
|
|
@@ -955,6 +983,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
955
983
|
let loopErrorState: ExecutionErrorState = { hasError: false, error: undefined };
|
|
956
984
|
let lastDelegatedText: string | undefined;
|
|
957
985
|
let loopAborted = false;
|
|
986
|
+
let boomerangPreviousSummaries: string[] = [];
|
|
958
987
|
|
|
959
988
|
try {
|
|
960
989
|
for (let i = 0; i < effectiveMax; i++) {
|
|
@@ -1024,7 +1053,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1024
1053
|
break;
|
|
1025
1054
|
}
|
|
1026
1055
|
|
|
1027
|
-
if (anchorId && i < effectiveMax - 1) {
|
|
1056
|
+
if (useFresh && anchorId && i < effectiveMax - 1) {
|
|
1028
1057
|
freshCollapse = { targetId: anchorId, task: name, iteration: i + 1, totalIterations };
|
|
1029
1058
|
const result = await ctx.navigateTree(anchorId, { summarize: true });
|
|
1030
1059
|
freshCollapse = null;
|
|
@@ -1049,9 +1078,11 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1049
1078
|
"loop",
|
|
1050
1079
|
);
|
|
1051
1080
|
|
|
1081
|
+
boomerangPreviousSummaries = accumulatedSummaries;
|
|
1052
1082
|
loopState = null;
|
|
1053
1083
|
pendingSkillMessage = undefined;
|
|
1054
1084
|
freshCollapse = null;
|
|
1085
|
+
boomerangCollapse = null;
|
|
1055
1086
|
accumulatedSummaries = [];
|
|
1056
1087
|
updateLoopStatus(ctx);
|
|
1057
1088
|
|
|
@@ -1069,6 +1100,10 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1069
1100
|
await ctx.waitForIdle();
|
|
1070
1101
|
}
|
|
1071
1102
|
|
|
1103
|
+
if (!loopErrorState.hasError && !loopAborted && shouldBoomerang) {
|
|
1104
|
+
await collapseBoomerangPrompt(ctx, name, anchorId, boomerangPreviousSummaries);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1072
1107
|
if (loopErrorState.hasError) {
|
|
1073
1108
|
throw loopErrorState.error;
|
|
1074
1109
|
}
|
|
@@ -1402,6 +1437,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1402
1437
|
chainActive = false;
|
|
1403
1438
|
loopState = null;
|
|
1404
1439
|
freshCollapse = null;
|
|
1440
|
+
boomerangCollapse = null;
|
|
1405
1441
|
accumulatedSummaries = [];
|
|
1406
1442
|
updateLoopStatus(ctx);
|
|
1407
1443
|
if (ctx.hasUI) {
|
|
@@ -1549,6 +1585,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1549
1585
|
};
|
|
1550
1586
|
const savedModel = getCurrentModel(ctx);
|
|
1551
1587
|
const savedThinking = pi.getThinkingLevel();
|
|
1588
|
+
const boomerangTargetId = effectivePrompt.boomerang ? ctx.sessionManager.getLeafId() : null;
|
|
1552
1589
|
const stepResult = await executePromptStep(
|
|
1553
1590
|
effectivePrompt,
|
|
1554
1591
|
parseCommandArgs(argsWithoutSubagent),
|
|
@@ -1573,6 +1610,10 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1573
1610
|
previousThinking = savedThinking;
|
|
1574
1611
|
}
|
|
1575
1612
|
}
|
|
1613
|
+
|
|
1614
|
+
if (effectivePrompt.boomerang) {
|
|
1615
|
+
await collapseBoomerangPrompt(ctx, name, boomerangTargetId);
|
|
1616
|
+
}
|
|
1576
1617
|
}
|
|
1577
1618
|
|
|
1578
1619
|
function resetSessionScopedState(ctx: ExtensionContext) {
|
|
@@ -1581,6 +1622,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1581
1622
|
previousModel = undefined;
|
|
1582
1623
|
previousThinking = undefined;
|
|
1583
1624
|
runtimeModel = ctx.model;
|
|
1625
|
+
boomerangCollapse = null;
|
|
1584
1626
|
toolManager.clearQueue();
|
|
1585
1627
|
refreshPrompts(ctx.cwd, ctx);
|
|
1586
1628
|
}
|
|
@@ -1644,6 +1686,15 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
1644
1686
|
});
|
|
1645
1687
|
|
|
1646
1688
|
pi.on("session_before_tree", async (event) => {
|
|
1689
|
+
if (boomerangCollapse && event.preparation.targetId === boomerangCollapse.targetId) {
|
|
1690
|
+
const summary = generateBoomerangSummary(event.preparation.entriesToSummarize, boomerangCollapse.task);
|
|
1691
|
+
return {
|
|
1692
|
+
summary: {
|
|
1693
|
+
summary: [...boomerangCollapse.previousSummaries, summary].join("\n\n---\n\n"),
|
|
1694
|
+
},
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1647
1698
|
if (!freshCollapse) return;
|
|
1648
1699
|
if (event.preparation.targetId !== freshCollapse.targetId) return;
|
|
1649
1700
|
|
package/loop-utils.ts
CHANGED
|
@@ -88,7 +88,7 @@ function collectSummaryData(entries: SessionEntry[]): CollectedSummaryData {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function formatSummary(header: string, entries: SessionEntry[]): string {
|
|
91
|
+
function formatSummary(header: string, entries: SessionEntry[], preserveOutcome = false): string {
|
|
92
92
|
const { filesRead, filesWritten, commandCount, lastAssistantText } = collectSummaryData(entries);
|
|
93
93
|
|
|
94
94
|
let summary = header;
|
|
@@ -102,9 +102,11 @@ function formatSummary(header: string, entries: SessionEntry[]): string {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
if (lastAssistantText) {
|
|
105
|
-
const cleaned =
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const cleaned = preserveOutcome
|
|
106
|
+
? lastAssistantText.replace(/\r\n?/g, "\n").trim()
|
|
107
|
+
: lastAssistantText.replace(/\n+/g, " ").trim();
|
|
108
|
+
const outcome = preserveOutcome || cleaned.length <= 500 ? cleaned : `${cleaned.slice(0, 500)}...`;
|
|
109
|
+
summary += `\nOutcome: ${outcome}`;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
return summary;
|
|
@@ -117,6 +119,10 @@ export function generateIterationSummary(entries: SessionEntry[], task: string,
|
|
|
117
119
|
return formatSummary(header, entries);
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
export function generateBoomerangSummary(entries: SessionEntry[], task: string): string {
|
|
123
|
+
return formatSummary(`[Boomerang]\nTask: "${task}"`, entries, true);
|
|
124
|
+
}
|
|
125
|
+
|
|
120
126
|
export function generateChainStepSummary(entries: SessionEntry[], stepLabel: string, stepNumber: number): string {
|
|
121
127
|
return formatSummary(`Step ${stepNumber} — ${stepLabel}:`, entries);
|
|
122
128
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-prompt-template-model",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Prompt template model selector extension for pi coding agent",
|
|
6
6
|
"author": "Nico Bailon",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"tool-manager.ts",
|
|
41
41
|
"template-conditionals.ts",
|
|
42
42
|
"examples",
|
|
43
|
+
"skills",
|
|
43
44
|
"banner.png",
|
|
44
45
|
"README.md",
|
|
45
46
|
"CHANGELOG.md",
|
|
@@ -48,6 +49,9 @@
|
|
|
48
49
|
"scripts": {
|
|
49
50
|
"test": "tsx --test test/**/*.test.ts"
|
|
50
51
|
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"typebox": "^1.1.24"
|
|
54
|
+
},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@mariozechner/pi-agent-core": "^0.65.0",
|
|
53
57
|
"@mariozechner/pi-ai": "^0.65.0",
|
|
@@ -58,6 +62,9 @@
|
|
|
58
62
|
"pi": {
|
|
59
63
|
"extensions": [
|
|
60
64
|
"./index.ts"
|
|
65
|
+
],
|
|
66
|
+
"skills": [
|
|
67
|
+
"./skills"
|
|
61
68
|
]
|
|
62
69
|
}
|
|
63
70
|
}
|
package/prompt-loader.ts
CHANGED
|
@@ -74,6 +74,7 @@ export interface PromptWithModel {
|
|
|
74
74
|
fresh?: boolean;
|
|
75
75
|
loop?: number | null;
|
|
76
76
|
converge?: boolean;
|
|
77
|
+
boomerang?: boolean;
|
|
77
78
|
parallel?: number;
|
|
78
79
|
worktree?: boolean;
|
|
79
80
|
deterministic?: DeterministicStep;
|
|
@@ -303,6 +304,31 @@ function normalizeRotate(
|
|
|
303
304
|
return false;
|
|
304
305
|
}
|
|
305
306
|
|
|
307
|
+
function normalizeBoomerang(
|
|
308
|
+
value: unknown,
|
|
309
|
+
filePath: string,
|
|
310
|
+
source: PromptSource,
|
|
311
|
+
diagnostics: PromptLoaderDiagnostic[],
|
|
312
|
+
): boolean {
|
|
313
|
+
if (value === undefined) return false;
|
|
314
|
+
if (typeof value === "boolean") return value;
|
|
315
|
+
if (typeof value === "string") {
|
|
316
|
+
const normalized = value.trim().toLowerCase();
|
|
317
|
+
if (normalized === "true") return true;
|
|
318
|
+
if (normalized === "false") return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
diagnostics.push(
|
|
322
|
+
createDiagnostic(
|
|
323
|
+
"invalid-boomerang",
|
|
324
|
+
filePath,
|
|
325
|
+
source,
|
|
326
|
+
`Using default boomerang=false for ${filePath}: frontmatter field "boomerang" must be true or false.`,
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
306
332
|
function normalizeLoop(
|
|
307
333
|
value: unknown,
|
|
308
334
|
filePath: string,
|
|
@@ -1643,6 +1669,18 @@ function loadPromptsWithModelFromDir(
|
|
|
1643
1669
|
const fresh = normalizeFresh(frontmatter.fresh, fullPath, source, diagnostics);
|
|
1644
1670
|
const loop = normalizeLoop(frontmatter.loop, fullPath, source, diagnostics);
|
|
1645
1671
|
const converge = normalizeConverge(frontmatter.converge, fullPath, source, diagnostics);
|
|
1672
|
+
let boomerang = normalizeBoomerang(frontmatter.boomerang, fullPath, source, diagnostics);
|
|
1673
|
+
if (chain && boomerang) {
|
|
1674
|
+
diagnostics.push(
|
|
1675
|
+
createDiagnostic(
|
|
1676
|
+
"invalid-boomerang-chain",
|
|
1677
|
+
fullPath,
|
|
1678
|
+
source,
|
|
1679
|
+
`Ignoring boomerang in ${fullPath}: frontmatter fields "chain" and "boomerang" cannot be combined.`,
|
|
1680
|
+
),
|
|
1681
|
+
);
|
|
1682
|
+
boomerang = false;
|
|
1683
|
+
}
|
|
1646
1684
|
if (loop !== undefined && deterministic !== undefined) {
|
|
1647
1685
|
diagnostics.push(
|
|
1648
1686
|
createDiagnostic(
|
|
@@ -1695,6 +1733,7 @@ function loadPromptsWithModelFromDir(
|
|
|
1695
1733
|
fresh === true ||
|
|
1696
1734
|
loop !== undefined ||
|
|
1697
1735
|
converge === false ||
|
|
1736
|
+
boomerang === true ||
|
|
1698
1737
|
safeParallel !== undefined ||
|
|
1699
1738
|
deterministic !== undefined ||
|
|
1700
1739
|
hasLineup ||
|
|
@@ -1721,6 +1760,7 @@ function loadPromptsWithModelFromDir(
|
|
|
1721
1760
|
fresh: fresh || undefined,
|
|
1722
1761
|
loop: loop !== undefined ? loop : undefined,
|
|
1723
1762
|
converge: converge === false ? false : undefined,
|
|
1763
|
+
boomerang: boomerang || undefined,
|
|
1724
1764
|
parallel: safeParallel,
|
|
1725
1765
|
worktree: safeWorktree,
|
|
1726
1766
|
deterministic,
|
|
@@ -1821,6 +1861,7 @@ export function buildPromptCommandDescription(prompt: PromptWithModel): string {
|
|
|
1821
1861
|
const thinkingValue = prompt.thinkingLevels ? prompt.thinkingLevels.join(",") : prompt.thinking;
|
|
1822
1862
|
const thinkingLabel = thinkingValue ? ` ${thinkingValue}` : "";
|
|
1823
1863
|
const loopLabel = prompt.loop !== undefined ? ` loop:${prompt.loop === null ? "unlimited" : prompt.loop}` : "";
|
|
1864
|
+
const boomerangLabel = prompt.boomerang ? " boomerang" : "";
|
|
1824
1865
|
const subagentLabel = prompt.subagent ? ` subagent:${prompt.subagent === true ? "delegate" : prompt.subagent}` : "";
|
|
1825
1866
|
const parallelLabel = prompt.parallel !== undefined ? ` parallel:${prompt.parallel}` : "";
|
|
1826
1867
|
const deterministicLabel = prompt.deterministic ? ` deterministic-step:${prompt.deterministic.handoff}` : "";
|
|
@@ -1831,7 +1872,7 @@ export function buildPromptCommandDescription(prompt: PromptWithModel): string {
|
|
|
1831
1872
|
const inheritContextLabel = prompt.inheritContext ? " fork" : "";
|
|
1832
1873
|
const worktreeLabel = prompt.worktree ? " worktree" : "";
|
|
1833
1874
|
const details =
|
|
1834
|
-
`[${modelLabel}${rotateLabel}${thinkingLabel}${skillLabel}${loopLabel}${subagentLabel}${parallelLabel}${deterministicLabel}${workersLabel}${reviewersLabel}${finalApplierLabel}${cwdLabel}${inheritContextLabel}${worktreeLabel}] ${sourceLabel}`;
|
|
1875
|
+
`[${modelLabel}${rotateLabel}${thinkingLabel}${skillLabel}${loopLabel}${boomerangLabel}${subagentLabel}${parallelLabel}${deterministicLabel}${workersLabel}${reviewersLabel}${finalApplierLabel}${cwdLabel}${inheritContextLabel}${worktreeLabel}] ${sourceLabel}`;
|
|
1835
1876
|
return prompt.description ? `${prompt.description} ${details}` : details;
|
|
1836
1877
|
}
|
|
1837
1878
|
|
package/skill-loaded-renderer.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function renderSkillLoaded(
|
|
|
25
25
|
container.addChild(new Spacer(1));
|
|
26
26
|
|
|
27
27
|
const box = new Box(1, 1, (text: string) => theme.bg("toolSuccessBg", text));
|
|
28
|
-
box.addChild(new Text(theme.fg("toolTitle", theme.bold(
|
|
28
|
+
box.addChild(new Text(theme.fg("toolTitle", theme.bold(`Skill loaded: ${skillName}`)), 0, 0));
|
|
29
29
|
box.addChild(new Text(theme.fg("toolOutput", ` ${skillPath}`), 0, 0));
|
|
30
30
|
box.addChild(new Spacer(1));
|
|
31
31
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prompt-template-authoring
|
|
3
|
+
description: |
|
|
4
|
+
Write and run custom Pi prompt templates (slash commands) for this extension.
|
|
5
|
+
Use when creating templates with model selection, deterministic pre-steps,
|
|
6
|
+
loops, chains, subagents, or best-of-N compare flows.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Prompt Template Authoring
|
|
10
|
+
|
|
11
|
+
Use this skill when working on prompt templates for `pi-prompt-template-model`.
|
|
12
|
+
Templates are markdown files that register as slash commands.
|
|
13
|
+
|
|
14
|
+
## Where Templates Live
|
|
15
|
+
|
|
16
|
+
- `~/.pi/agent/prompts/` — user prompts (highest priority)
|
|
17
|
+
- `.pi/prompts/` inside a project — project-specific prompts
|
|
18
|
+
|
|
19
|
+
Extension `examples/` are reference files only. Copy them to a prompt directory to register them.
|
|
20
|
+
|
|
21
|
+
## Minimal Template
|
|
22
|
+
|
|
23
|
+
```markdown
|
|
24
|
+
---
|
|
25
|
+
model: claude-sonnet-4-20250514
|
|
26
|
+
---
|
|
27
|
+
Your prompt body here.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Save as `my-command.md`, restart Pi, run `/my-command`. Use `description:` for autocomplete text.
|
|
31
|
+
|
|
32
|
+
## Model Selection
|
|
33
|
+
|
|
34
|
+
Omit `model:` to inherit the current session model. Otherwise:
|
|
35
|
+
|
|
36
|
+
- `model: claude-sonnet-4-20250514` — specific model
|
|
37
|
+
- `model: claude-opus-4, gpt-5.4` — fallback order (tries first, falls back to second if unavailable)
|
|
38
|
+
- `model: claude-opus-4, gpt-5.4` + `rotate: true` — cycle through list on each loop iteration
|
|
39
|
+
|
|
40
|
+
## Argument Substitution
|
|
41
|
+
|
|
42
|
+
The prompt body can use placeholders:
|
|
43
|
+
|
|
44
|
+
- `$@` — all arguments passed to the command
|
|
45
|
+
- `$1`, `$2` — specific positional arguments
|
|
46
|
+
- `${@:1}` — argument 1 and everything after
|
|
47
|
+
|
|
48
|
+
## Deterministic Steps (Pre-LLM Execution)
|
|
49
|
+
|
|
50
|
+
Run a command or script before the LLM turn. The model only sees the output if you want it to.
|
|
51
|
+
|
|
52
|
+
Two equivalent forms. Don't mix them in the same prompt.
|
|
53
|
+
|
|
54
|
+
**Shorthand form** — top-level keys:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
---
|
|
58
|
+
run: git status --short
|
|
59
|
+
handoff: always
|
|
60
|
+
---
|
|
61
|
+
Summarize the repo state.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Nested form** — under `deterministic:`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
---
|
|
68
|
+
deterministic:
|
|
69
|
+
run: ./scripts/ship.sh
|
|
70
|
+
handoff: on-failure
|
|
71
|
+
timeout: 60000
|
|
72
|
+
---
|
|
73
|
+
Diagnose the failure and suggest a fix.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Handoff controls when the LLM sees the result:**
|
|
77
|
+
|
|
78
|
+
- `never` — run, show result, done (no LLM turn)
|
|
79
|
+
- `always` — always hand result to model
|
|
80
|
+
- `on-failure` — only hand off if command exits non-zero
|
|
81
|
+
- `on-success` — only hand off if command exits zero
|
|
82
|
+
|
|
83
|
+
**Execution forms:**
|
|
84
|
+
|
|
85
|
+
- `run: command string` — runs via `/bin/bash -lc`
|
|
86
|
+
- `run: {command: git, args: [status], shell: false}` — explicit args, optional shell
|
|
87
|
+
- `script: ./script.sh` or `script: {path: ./script.sh, args: [--fast]}` — run a file
|
|
88
|
+
|
|
89
|
+
**Constraints:**
|
|
90
|
+
- Only single prompt templates (no `chain`, `loop`, `subagent`, or `parallel`)
|
|
91
|
+
- Runtime flags `--loop`, `--subagent`, `--fork` are rejected for deterministic prompts
|
|
92
|
+
|
|
93
|
+
## Subagent Delegation
|
|
94
|
+
|
|
95
|
+
Delegate to another Pi agent instead of running inline:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
---
|
|
99
|
+
model: claude-sonnet-4-20250514
|
|
100
|
+
subagent: delegate # or true, or a specific agent name
|
|
101
|
+
inheritContext: true # fork conversation context (optional)
|
|
102
|
+
cwd: /absolute/path # working directory for the subagent (optional)
|
|
103
|
+
parallel: 3 # run 3 copies in parallel (optional)
|
|
104
|
+
---
|
|
105
|
+
$@
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Requires [pi-subagents](https://github.com/nicobailon/pi-subagents/) to be installed.
|
|
109
|
+
|
|
110
|
+
## Loops
|
|
111
|
+
|
|
112
|
+
Run the prompt multiple times:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
---
|
|
116
|
+
model: claude-sonnet-4-20250514
|
|
117
|
+
loop: 5 # run exactly 5 times
|
|
118
|
+
converge: true # stop early if no changes (default)
|
|
119
|
+
fresh: true # collapse context between iterations
|
|
120
|
+
---
|
|
121
|
+
$@
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Or at runtime: `/command --loop 5`, `/command --loop` (unlimited), or `/command --loop=5 --fresh`.
|
|
125
|
+
|
|
126
|
+
## Chains
|
|
127
|
+
|
|
128
|
+
Chain templates declare a reusable pipeline:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
---
|
|
132
|
+
chain: analyze -> fix -> test
|
|
133
|
+
chainContext: summary # pass step summaries to later delegated steps
|
|
134
|
+
---
|
|
135
|
+
$@
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or use `/chain-prompts analyze -> fix -> test` at runtime. Chain templates ignore the body and `model:` field.
|
|
139
|
+
|
|
140
|
+
## Model Conditionals
|
|
141
|
+
|
|
142
|
+
Show different content based on which model runs:
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
<if-model is="anthropic/*">
|
|
146
|
+
Use Claude-specific instructions.
|
|
147
|
+
<else>
|
|
148
|
+
Use default instructions.
|
|
149
|
+
</if-model>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Supports exact IDs, `provider/model-id` pairs, wildcards (`anthropic/*`), and comma-separated combinations.
|
|
153
|
+
|
|
154
|
+
## Best-of-N Compare
|
|
155
|
+
|
|
156
|
+
Run multiple workers, aggregate with reviewers, optionally apply final changes:
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
---
|
|
160
|
+
description: Best-of-N code review
|
|
161
|
+
bestOfN:
|
|
162
|
+
worktree: true # required if using finalApplier
|
|
163
|
+
workers:
|
|
164
|
+
- model: openai-codex/gpt-5.4-mini:low
|
|
165
|
+
count: 2
|
|
166
|
+
reviewers:
|
|
167
|
+
- model: anthropic/claude-sonnet-4-20250514:medium
|
|
168
|
+
finalApplier:
|
|
169
|
+
agent: delegate
|
|
170
|
+
model: anthropic/claude-sonnet-4-20250514:high
|
|
171
|
+
---
|
|
172
|
+
$@
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Runtime Flags
|
|
176
|
+
|
|
177
|
+
Override frontmatter at invocation:
|
|
178
|
+
|
|
179
|
+
- `--model=provider/model-id` — use this model instead
|
|
180
|
+
- `--subagent` / `--subagent=<name>` / `--subagent:<name>` — force delegation
|
|
181
|
+
- `--fork` — force delegation with context fork
|
|
182
|
+
- `--loop N` / `--loop=N` / `--loop` — override loop count (unlimited if bare)
|
|
183
|
+
- `--fresh` — collapse context between iterations
|
|
184
|
+
- `--no-converge` — run all iterations even if no changes
|
|
185
|
+
- `--cwd=/absolute/path` — working directory override when the prompt supports `cwd`
|
|
186
|
+
- `--chain-context` — pass summaries to later delegated chain steps
|
|
187
|
+
- `--worktree` — use git worktrees for parallel delegated work
|
|
188
|
+
|
|
189
|
+
When stuck, check `README.md` and `examples/best-of-n.md` in this extension.
|
package/tool-manager.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
5
|
-
import { Type } from "
|
|
5
|
+
import { Type } from "typebox";
|
|
6
6
|
import { notify } from "./notifications.js";
|
|
7
7
|
|
|
8
8
|
export interface ToolManagerDeps {
|