@aeondave/pi-skill-optimizer 3.1.0 → 3.1.2
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 +20 -0
- package/README.md +5 -1
- package/docs/ARCHITECTURE.md +19 -0
- package/package.json +2 -2
- package/src/index.ts +16 -7
- package/src/optimize.ts +19 -19
- package/src/skills.ts +1 -1
- package/src/usage.ts +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes are documented here.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 3.1.2 - 2026-10-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Handle Pi 1.2 custom-triggered runs as no-intent starts: fail open for permitted tools, ignore custom message text even if a stray prompt field is present, and preserve the previous human request for output extraction.
|
|
12
|
+
|
|
13
|
+
## 3.1.1 - 2026-10-08
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Optimize the native Pi transcript before provider conversion, keeping worker notifications, shell
|
|
18
|
+
output and summaries out of human-intent routing and prefetch insertion.
|
|
19
|
+
- Preserve structured system sections and tool declarations; respect provider scoping when the
|
|
20
|
+
request has no selected model.
|
|
21
|
+
- Label prefetch as catalog metadata, not loaded instructions or usage evidence. Emit its actual
|
|
22
|
+
catalog fingerprint and report selected skills only when an overlay was inserted.
|
|
23
|
+
- Recognize native Pi skill-activation calls without double-counting `skill_search` usage.
|
|
24
|
+
|
|
5
25
|
## 3.1.0 - 2026-10-03
|
|
6
26
|
|
|
7
27
|
### Breaking
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ That's enough to start. Optionally run `/skill-optimizer init` to generate routi
|
|
|
37
37
|
| Need | Command |
|
|
38
38
|
| --- | --- |
|
|
39
39
|
| Install only in this project | `pi install -l npm:@aeondave/pi-skill-optimizer` |
|
|
40
|
-
| Pin the current release | `pi install npm:@aeondave/pi-skill-optimizer@3.1.
|
|
40
|
+
| Pin the current release | `pi install npm:@aeondave/pi-skill-optimizer@3.1.2` |
|
|
41
41
|
| Update an unpinned install | `pi update npm:@aeondave/pi-skill-optimizer` |
|
|
42
42
|
|
|
43
43
|
Restart after updating. Pinned installs stay pinned until you explicitly install another version.
|
|
@@ -61,6 +61,10 @@ Say you have hundreds of skills, but you're asking Pi to fix a database migratio
|
|
|
61
61
|
|
|
62
62
|
AUTO keeps a compact, stable index of **all eligible names**, adds relevant descriptions beside your latest request, and exposes `skill_search` for loading complete instructions. The stable index does not change when your question changes.
|
|
63
63
|
|
|
64
|
+
Prefetch is a catalog, not loaded instructions or evidence that a skill was used. Worker updates,
|
|
65
|
+
extension notifications and summaries do not replace your request as the routing signal or receive
|
|
66
|
+
its overlay; there is no need to acknowledge the catalog.
|
|
67
|
+
|
|
64
68
|
| Area | What AUTO does | What stays available |
|
|
65
69
|
| --- | --- | --- |
|
|
66
70
|
| Skills | Shortens catalog descriptions; prefetches relevant ones | Every eligible name and exact full-instruction loading |
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -20,8 +20,27 @@ The index contains no filesystem paths. With the same catalog, profile, and conf
|
|
|
20
20
|
|
|
21
21
|
AUTO extracts intent only from genuine human text in Anthropic, OpenAI Chat and Responses, Gemini, Mistral, and Pi's structured `pi-messages` request shapes. A catalog may live in a system or developer block, but neither is treated as human intent. Assistant text, tool or function results, injected context, image metadata, and empty input cannot create a ranking signal.
|
|
22
22
|
|
|
23
|
+
At runtime, catalog optimization and usage extraction run in Pi's `context_with_system` hook,
|
|
24
|
+
before provider conversion erases custom-message provenance. Native `custom`, `bashExecution`,
|
|
25
|
+
`branchSummary`, `compactionSummary` and `toolResult` messages remain intact and cannot become
|
|
26
|
+
the query or overlay target. Structured system sections and tool declarations are preserved;
|
|
27
|
+
request-local clones do not modify persisted conversation entries.
|
|
28
|
+
|
|
29
|
+
Pi 1.2's `before_agent_start` event distinguishes user starts (`messageType: "user"` with `prompt`)
|
|
30
|
+
from custom-triggered starts (`messageType: "custom"` with `message`). Only a user prompt updates the
|
|
31
|
+
last human request and drives tool activation. A custom start has no routing signal, so optimizer
|
|
32
|
+
activation fails open across permitted named tools while retaining protected, used, and searched
|
|
33
|
+
tools; custom message text is ignored even if the event also carries a `prompt` property. The
|
|
34
|
+
previous human request remains available to output extraction. On Pi 1.0, the supported event has no
|
|
35
|
+
`messageType` discriminator, so its string `prompt` continues to identify user intent.
|
|
36
|
+
|
|
23
37
|
Ranking uses exact matching, BM25F, bounded name and alias typo recovery, reciprocal-rank fusion, diversity, profile evidence, and decayed usage evidence. It scores full source descriptions, then appends a bounded set of verbatim skill names and descriptions (not the full `SKILL.md` instructions) to the latest human block. The overlay is separate from the stable prefix, fingerprinted, idempotent, and omitted when there is no valid target.
|
|
24
38
|
|
|
39
|
+
The resolver labels prefetch as discovery metadata, not loaded instructions or usage evidence.
|
|
40
|
+
Diagnostics count only overlays actually inserted. Usage follows explicit human mentions and skill
|
|
41
|
+
activations; the `skill_search` executor records its own loads/resources, so transcript scanning
|
|
42
|
+
does not count those calls twice. Catalog selections and assistant acknowledgments are not usage.
|
|
43
|
+
|
|
25
44
|
Optional init-generated activation tags distinguish specialists, workflow methods
|
|
26
45
|
and behavioral discipline. An admitted specialist can bring one activity-matched
|
|
27
46
|
supporting skill into the ordinary prefetch budget; this does not recursively
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeondave/pi-skill-optimizer",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Less repeated context for Pi: local skill routing, on-demand tools, and recoverable output reduction.",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"extensions": [
|
|
82
82
|
"./src/index.ts"
|
|
83
83
|
],
|
|
84
|
-
"image": "https://raw.githubusercontent.com/AeonDave/pi-skill-optimizer/v3.1.
|
|
84
|
+
"image": "https://raw.githubusercontent.com/AeonDave/pi-skill-optimizer/v3.1.2/assets/banner.png"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/index.ts
CHANGED
|
@@ -1059,7 +1059,13 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1059
1059
|
});
|
|
1060
1060
|
|
|
1061
1061
|
pi.on("before_agent_start", (event, ctx) => {
|
|
1062
|
-
|
|
1062
|
+
// Pi 1.2 also starts runs from custom messages, which have no human prompt.
|
|
1063
|
+
// Keep their content out of routing and preserve the last real request for extraction.
|
|
1064
|
+
const customStart = "messageType" in event && event.messageType === "custom";
|
|
1065
|
+
const prompt = !customStart && "prompt" in event && typeof event.prompt === "string"
|
|
1066
|
+
? event.prompt
|
|
1067
|
+
: undefined;
|
|
1068
|
+
if (prompt !== undefined) latestPrompt = prompt;
|
|
1063
1069
|
if (isDisabled(ctx.cwd)) {
|
|
1064
1070
|
restorePermittedTools();
|
|
1065
1071
|
return;
|
|
@@ -1074,7 +1080,7 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1074
1080
|
try {
|
|
1075
1081
|
const skills = skillInputs(event.systemPromptOptions.skills ?? []);
|
|
1076
1082
|
registry = makeSkillRegistry(skills);
|
|
1077
|
-
initializeToolSelection(state,
|
|
1083
|
+
initializeToolSelection(state, prompt ?? "");
|
|
1078
1084
|
maybeShowInitNotice(ctx, state, skills);
|
|
1079
1085
|
updateStatus(ctx);
|
|
1080
1086
|
return;
|
|
@@ -1084,13 +1090,14 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1084
1090
|
}
|
|
1085
1091
|
});
|
|
1086
1092
|
|
|
1087
|
-
pi.on("
|
|
1093
|
+
pi.on("context_with_system", (event, ctx) => {
|
|
1088
1094
|
if (isDisabled(ctx.cwd)) return;
|
|
1089
1095
|
const config = getRuntimeConfig(ctx);
|
|
1090
1096
|
const providers = config.providers.length > 0 ? config.providers : undefined;
|
|
1091
|
-
if (providers && ctx.model
|
|
1097
|
+
if (providers && (!ctx.model || !providers.includes(ctx.model.provider))) return;
|
|
1092
1098
|
const state = ensureSnapshot(ctx);
|
|
1093
|
-
const
|
|
1099
|
+
const payload = { messages: event.messages };
|
|
1100
|
+
const transformed = optimizePayload(payload, {
|
|
1094
1101
|
...state.config,
|
|
1095
1102
|
profile: state.profile,
|
|
1096
1103
|
usagePrior: buildUsagePrior(state.usage, {
|
|
@@ -1109,7 +1116,7 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1109
1116
|
addPending(getStatsFilePath(ctx.cwd), { skills: transformed.removedChars, tools: 0, output: 0 });
|
|
1110
1117
|
}
|
|
1111
1118
|
if (registry) {
|
|
1112
|
-
const evidence = collectSkillUsageEvidence(
|
|
1119
|
+
const evidence = collectSkillUsageEvidence(payload, registry.skills.map((skill) => skill.name)).slice(-MAX_SEEN_EVIDENCE);
|
|
1113
1120
|
const names: string[] = [];
|
|
1114
1121
|
for (const item of evidence) {
|
|
1115
1122
|
if (seenEvidence.has(item.key)) continue;
|
|
@@ -1124,7 +1131,9 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1124
1131
|
}
|
|
1125
1132
|
maybeFlush(ctx);
|
|
1126
1133
|
updateStatus(ctx);
|
|
1127
|
-
return transformed.next ===
|
|
1134
|
+
return transformed.next === payload
|
|
1135
|
+
? undefined
|
|
1136
|
+
: { messages: (transformed.next as typeof payload).messages };
|
|
1128
1137
|
});
|
|
1129
1138
|
|
|
1130
1139
|
pi.on("context", (event, ctx) => {
|
package/src/optimize.ts
CHANGED
|
@@ -361,25 +361,24 @@ export function optimizePayload<T>(
|
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
let addedPrefetchChars = 0;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
364
|
+
let injectedSkills: string[] = [];
|
|
365
|
+
const prefetchPlan = planSkillPrefetch(catalogSkills, query, {
|
|
366
|
+
profile: config.profile,
|
|
367
|
+
usagePrior: config.usagePrior,
|
|
368
|
+
always: uniqueInOrder([
|
|
369
|
+
...(config.profile?.critical ?? []),
|
|
370
|
+
...(config.alwaysSkills ?? []),
|
|
371
|
+
]),
|
|
372
|
+
never: config.excludeSkills,
|
|
373
|
+
targetTopK: config.prefetchTarget,
|
|
374
|
+
minTopK: config.prefetchMin,
|
|
375
|
+
maxTopK: config.prefetchMax,
|
|
376
|
+
fullRenderBudgetChars: config.prefetchBudgetChars,
|
|
377
|
+
fuzzyCandidateLimit: config.fuzzyCandidateLimit,
|
|
378
|
+
});
|
|
379
|
+
const selectedSkills = prefetchPlan.selected;
|
|
379
380
|
if (selectedSkills.length > 0) {
|
|
380
|
-
const appendix = renderSkillPrefetch(
|
|
381
|
-
selected: selectedSkills,
|
|
382
|
-
} as Parameters<typeof renderSkillPrefetch>[0]);
|
|
381
|
+
const appendix = renderSkillPrefetch(prefetchPlan);
|
|
383
382
|
if (appendix) {
|
|
384
383
|
const withPrefetch = appendToLatestHumanInput(
|
|
385
384
|
next,
|
|
@@ -389,6 +388,7 @@ export function optimizePayload<T>(
|
|
|
389
388
|
if (withPrefetch !== next) {
|
|
390
389
|
addedPrefetchChars = serializedLength(withPrefetch) - serializedLength(next);
|
|
391
390
|
next = withPrefetch;
|
|
391
|
+
injectedSkills = selectedSkills.map((skill) => skill.name);
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
394
|
}
|
|
@@ -416,7 +416,7 @@ export function optimizePayload<T>(
|
|
|
416
416
|
removedChars: originalLength - finalLength,
|
|
417
417
|
baseRemovedChars,
|
|
418
418
|
addedPrefetchChars: Math.max(0, addedPrefetchChars),
|
|
419
|
-
selected:
|
|
419
|
+
selected: injectedSkills,
|
|
420
420
|
catalogNames: uniqueInOrder(catalogNames),
|
|
421
421
|
fingerprints: uniqueInOrder(fingerprints),
|
|
422
422
|
budget,
|
package/src/skills.ts
CHANGED
|
@@ -1058,7 +1058,7 @@ export function renderSkillPrefetch(plan: SkillPrefetchPlan): string {
|
|
|
1058
1058
|
if (plan.selected.length === 0) return "";
|
|
1059
1059
|
return [
|
|
1060
1060
|
`<skill_prefetch catalog_sha256="${plan.fingerprint}">`,
|
|
1061
|
-
" <skill_resolver>
|
|
1061
|
+
" <skill_resolver>Catalog metadata only, not loaded instructions or usage evidence. Load a relevant full skill with skill_search(action=load, name); no acknowledgment is needed.</skill_resolver>",
|
|
1062
1062
|
...plan.selected.map(renderFullSkill),
|
|
1063
1063
|
"</skill_prefetch>",
|
|
1064
1064
|
].join("\n");
|
package/src/usage.ts
CHANGED
|
@@ -91,16 +91,18 @@ export function collectSkillUsageEvidence(payload: unknown, selectedSkills: read
|
|
|
91
91
|
const rawMessage = rawMessages[messageIndex];
|
|
92
92
|
if (!rawMessage || typeof rawMessage !== "object") continue;
|
|
93
93
|
const message = rawMessage as Record<string, unknown>;
|
|
94
|
+
if (message.role !== "assistant") continue;
|
|
94
95
|
if (!Array.isArray(message.content)) continue;
|
|
95
96
|
for (let blockIndex = 0; blockIndex < message.content.length; blockIndex++) {
|
|
96
97
|
const rawBlock = message.content[blockIndex];
|
|
97
98
|
if (!rawBlock || typeof rawBlock !== "object") continue;
|
|
98
99
|
const block = rawBlock as Record<string, unknown>;
|
|
99
|
-
if (block.type !== "tool_use" || typeof block.name !== "string") continue;
|
|
100
|
+
if ((block.type !== "tool_use" && block.type !== "toolCall") || typeof block.name !== "string") continue;
|
|
100
101
|
const toolName = block.name.toLowerCase();
|
|
101
102
|
if (toolName !== "skill" && toolName !== "activate_skill") continue;
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const rawInput = block.type === "toolCall" ? block.arguments : block.input;
|
|
104
|
+
if (!rawInput || typeof rawInput !== "object") continue;
|
|
105
|
+
const input = rawInput as Record<string, unknown>;
|
|
104
106
|
let serialized = "";
|
|
105
107
|
try { serialized = JSON.stringify(input); } catch { serialized = String(input); }
|
|
106
108
|
const sourceId = objectId(block);
|