pi-agents-switch 0.2.4 → 0.2.5
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/frontmatter.ts +0 -1
- package/index.ts +111 -5
- package/package.json +1 -1
- package/profile-manager.ts +1 -3
package/frontmatter.ts
CHANGED
|
@@ -171,7 +171,6 @@ export function serializeFrontmatter(fm: AgentFrontmatter): string {
|
|
|
171
171
|
writeArray("tools", fm.tools);
|
|
172
172
|
writeArray("excluded_tools", fm.excluded_tools);
|
|
173
173
|
|
|
174
|
-
|
|
175
174
|
writeArray("skills", fm.skills);
|
|
176
175
|
writeArray("excluded_skills", fm.excluded_skills ?? fm.noskills);
|
|
177
176
|
|
package/index.ts
CHANGED
|
@@ -769,9 +769,8 @@ export default function agentsSwitch(pi: ExtensionAPI) {
|
|
|
769
769
|
|
|
770
770
|
// Append skills section if agent has resolved skills
|
|
771
771
|
if (currentResolved && currentResolved.skillNames.length > 0) {
|
|
772
|
-
const loadedSkills = (
|
|
773
|
-
|
|
774
|
-
).skills;
|
|
772
|
+
const loadedSkills = (event.systemPromptOptions as { skills?: any[] })
|
|
773
|
+
.skills;
|
|
775
774
|
let selectedSkills: Array<{
|
|
776
775
|
name: string;
|
|
777
776
|
description: string;
|
|
@@ -802,12 +801,14 @@ export default function agentsSwitch(pi: ExtensionAPI) {
|
|
|
802
801
|
return { systemPrompt: newPrompt };
|
|
803
802
|
});
|
|
804
803
|
|
|
805
|
-
// ─── before_provider_request
|
|
804
|
+
// ─── before_provider_request: enforce tool set + sanitize ─
|
|
806
805
|
|
|
807
806
|
pi.on("before_provider_request", (event) => {
|
|
808
|
-
if (!currentResolved) return;
|
|
807
|
+
if (!currentResolved || currentAgent === PI_AGENT_NAME) return;
|
|
809
808
|
const payload = event.payload as Record<string, unknown>;
|
|
810
809
|
let changed = false;
|
|
810
|
+
|
|
811
|
+
// Temperature / topP
|
|
811
812
|
if (currentResolved.temperature !== undefined) {
|
|
812
813
|
payload.temperature = currentResolved.temperature;
|
|
813
814
|
changed = true;
|
|
@@ -816,6 +817,111 @@ export default function agentsSwitch(pi: ExtensionAPI) {
|
|
|
816
817
|
payload.top_p = currentResolved.topP;
|
|
817
818
|
changed = true;
|
|
818
819
|
}
|
|
820
|
+
|
|
821
|
+
// ---- Sanitization: prevent other extensions from contaminating ----
|
|
822
|
+
// Other extensions (e.g. pi-lens) may override our tools and prompt
|
|
823
|
+
// in before_agent_start (last-runner-wins). We re-apply our config
|
|
824
|
+
// here as the final defence before the API call.
|
|
825
|
+
|
|
826
|
+
// 1. Filter tools — only allow resolved tools
|
|
827
|
+
const allowedTools = new Set(currentResolved.tools);
|
|
828
|
+
const rawTools = payload.tools as
|
|
829
|
+
| Array<{ function?: { name?: string } }>
|
|
830
|
+
| undefined;
|
|
831
|
+
if (rawTools && rawTools.length > 0) {
|
|
832
|
+
const before = rawTools.length;
|
|
833
|
+
payload.tools = rawTools.filter((t) =>
|
|
834
|
+
allowedTools.has(t?.function?.name ?? ""),
|
|
835
|
+
);
|
|
836
|
+
if ((payload.tools as Array<any>).length !== before) changed = true;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// 2. Clean system message — replace contaminated sections
|
|
840
|
+
const messages = payload.messages as
|
|
841
|
+
| Array<{ role: string; content: string }>
|
|
842
|
+
| undefined;
|
|
843
|
+
if (messages && messages.length > 0 && messages[0]?.role === "system") {
|
|
844
|
+
let content: string = messages[0].content;
|
|
845
|
+
const originalLen = content.length;
|
|
846
|
+
|
|
847
|
+
// Strip the "Available tools:" block (other extensions may inject their own)
|
|
848
|
+
// Format: "Available tools:\n- name: desc\n..."
|
|
849
|
+
// We replace it with our clean version.
|
|
850
|
+
const toolsMarker = "\nAvailable tools:";
|
|
851
|
+
const toolsIdx = content.indexOf(toolsMarker);
|
|
852
|
+
if (toolsIdx >= 0) {
|
|
853
|
+
// Find where the tools block ends (next section or empty line before next section)
|
|
854
|
+
const nextSection = content.indexOf(
|
|
855
|
+
"\nIn addition to the tools above",
|
|
856
|
+
toolsIdx,
|
|
857
|
+
);
|
|
858
|
+
if (nextSection >= 0) {
|
|
859
|
+
// Build clean tools list
|
|
860
|
+
if (currentResolved.tools.length === 0) {
|
|
861
|
+
content =
|
|
862
|
+
content.substring(0, toolsIdx) +
|
|
863
|
+
"\nAvailable tools:\n(none)\n" +
|
|
864
|
+
content.substring(nextSection);
|
|
865
|
+
} else {
|
|
866
|
+
// Rebuild clean tools from tool snippets if available
|
|
867
|
+
const snippets = pi.getAllTools();
|
|
868
|
+
const cleanLines = currentResolved.tools
|
|
869
|
+
.map((name) => {
|
|
870
|
+
const tool = snippets.find(
|
|
871
|
+
(t) => t.name === name,
|
|
872
|
+
);
|
|
873
|
+
return tool
|
|
874
|
+
? `- ${tool.name}: ${tool.description ?? ""}`
|
|
875
|
+
: `- ${name}`;
|
|
876
|
+
})
|
|
877
|
+
.join("\n");
|
|
878
|
+
content =
|
|
879
|
+
content.substring(0, toolsIdx) +
|
|
880
|
+
`\nAvailable tools:\n${cleanLines}\n` +
|
|
881
|
+
content.substring(nextSection);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Strip the <available_skills> block (other extensions might add extra skills)
|
|
887
|
+
// Our before_agent_start handler manages this — any extras are contamination.
|
|
888
|
+
const skillsOpen = "\n<available_skills>";
|
|
889
|
+
const skillsIdx = content.indexOf(skillsOpen);
|
|
890
|
+
if (skillsIdx >= 0) {
|
|
891
|
+
const skillsEnd = content.indexOf("\n</available_skills>", skillsIdx);
|
|
892
|
+
if (skillsEnd >= 0) {
|
|
893
|
+
content =
|
|
894
|
+
content.substring(0, skillsIdx) +
|
|
895
|
+
content.substring(
|
|
896
|
+
skillsEnd + "\n</available_skills>".length,
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Strip "Pi documentation" block (pi-lens may re-add after our handler strips it)
|
|
902
|
+
const piDocsMarker =
|
|
903
|
+
"Pi documentation (read only when the user asks about pi itself";
|
|
904
|
+
const piDocsStart = content.indexOf(piDocsMarker);
|
|
905
|
+
if (piDocsStart >= 0) {
|
|
906
|
+
const currentDateMarker = "\nCurrent date:";
|
|
907
|
+
const currentDateStart = content.indexOf(
|
|
908
|
+
currentDateMarker,
|
|
909
|
+
piDocsStart,
|
|
910
|
+
);
|
|
911
|
+
if (currentDateStart >= 0) {
|
|
912
|
+
content =
|
|
913
|
+
content.substring(0, piDocsStart) +
|
|
914
|
+
content.substring(currentDateStart);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (content.length !== originalLen) {
|
|
919
|
+
messages[0].content = content;
|
|
920
|
+
changed = true;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Return payload only if we changed something (otherwise Pi uses default)
|
|
819
925
|
if (changed) return payload;
|
|
820
926
|
});
|
|
821
927
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agents-switch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Tab to switch primary agents in Pi — like OpenCode's agent switching. Each agent gets an isolated profile with its own AGENTS.md, extensions, skills, and settings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"pi": {
|
package/profile-manager.ts
CHANGED
|
@@ -480,9 +480,7 @@ export class ProfileManager {
|
|
|
480
480
|
private findSkillOnDisk(
|
|
481
481
|
name: string,
|
|
482
482
|
): { name: string; description: string; filePath: string } | undefined {
|
|
483
|
-
const locations = [
|
|
484
|
-
join(this.piAgentDir, "skills", name, "SKILL.md"),
|
|
485
|
-
];
|
|
483
|
+
const locations = [join(this.piAgentDir, "skills", name, "SKILL.md")];
|
|
486
484
|
|
|
487
485
|
if (this.cwd) {
|
|
488
486
|
locations.push(join(this.cwd, ".pi", "skills", name, "SKILL.md"));
|