pi-agents-switch 0.3.0 → 0.3.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/README.md +4 -0
- package/index.ts +33 -12
- package/package.json +1 -1
- package/profile-manager.ts +68 -35
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ pi install ~/project/agents-switch
|
|
|
12
12
|
|
|
13
13
|
Then `/reload` in pi.
|
|
14
14
|
|
|
15
|
+
> **Requires Pi ≥ v0.x** (tested with the version shipping `before_agent_start` + `before_provider_request` events).
|
|
16
|
+
> If Pi's system prompt format changes, agent identity will be prepended instead of replacing the default role line —
|
|
17
|
+
> check the Pi console for a `[agents-switch]` warning if this happens.
|
|
18
|
+
|
|
15
19
|
## Usage
|
|
16
20
|
|
|
17
21
|
| Action | How |
|
package/index.ts
CHANGED
|
@@ -752,6 +752,12 @@ export default function agentsSwitch(pi: ExtensionAPI) {
|
|
|
752
752
|
section +
|
|
753
753
|
newPrompt.substring(roleEnd);
|
|
754
754
|
} else {
|
|
755
|
+
// Pi version mismatch — role line changed. Fall back to prepend.
|
|
756
|
+
console.warn(
|
|
757
|
+
"[agents-switch] Default role line not found in system prompt. " +
|
|
758
|
+
"Pi may have changed its prompt format. " +
|
|
759
|
+
"Agent identity will be prepended instead of replacing the role line.",
|
|
760
|
+
);
|
|
755
761
|
newPrompt = section + "\n\n" + newPrompt;
|
|
756
762
|
}
|
|
757
763
|
}
|
|
@@ -760,20 +766,35 @@ export default function agentsSwitch(pi: ExtensionAPI) {
|
|
|
760
766
|
if (currentResolved && currentResolved.skillNames.length > 0) {
|
|
761
767
|
const loadedSkills = (event.systemPromptOptions as { skills?: any[] })
|
|
762
768
|
.skills;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
description: string;
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (loadedSkills
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
769
|
+
const skillMap = new Map<
|
|
770
|
+
string,
|
|
771
|
+
{ name: string; description: string; filePath: string }
|
|
772
|
+
>();
|
|
773
|
+
|
|
774
|
+
// Collect from Pi's loaded skills first (richest data)
|
|
775
|
+
if (loadedSkills) {
|
|
776
|
+
for (const s of loadedSkills) {
|
|
777
|
+
skillMap.set(s.name, {
|
|
778
|
+
name: s.name,
|
|
779
|
+
description: s.description ?? "",
|
|
780
|
+
filePath: s.filePath ?? "",
|
|
781
|
+
});
|
|
782
|
+
}
|
|
775
783
|
}
|
|
776
784
|
|
|
785
|
+
// Fill gaps from disk (agent-specific skills not in Pi's loaded list)
|
|
786
|
+
for (const name of currentResolved.skillNames) {
|
|
787
|
+
if (!skillMap.has(name)) {
|
|
788
|
+
const found = pm.getSkillObjects([name]);
|
|
789
|
+
if (found.length > 0) skillMap.set(found[0].name, found[0]);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Filter to only resolved names, preserving order
|
|
794
|
+
const selectedSkills = currentResolved.skillNames
|
|
795
|
+
.map((name) => skillMap.get(name))
|
|
796
|
+
.filter((s): s is NonNullable<typeof s> => s != null);
|
|
797
|
+
|
|
777
798
|
if (selectedSkills.length > 0) {
|
|
778
799
|
const skillsBlock = formatSkillsBlock(selectedSkills);
|
|
779
800
|
const currentDateMarker = "\nCurrent date:";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agents-switch",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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
|
@@ -478,12 +478,10 @@ export class ProfileManager {
|
|
|
478
478
|
* Look up full skill objects by name from Pi's standard skill directories.
|
|
479
479
|
* Returns name, description, and filePath for each skill found.
|
|
480
480
|
*/
|
|
481
|
-
/** Get
|
|
481
|
+
/** Get all indexed skill names (global + extension + project). */
|
|
482
482
|
getPISkillNames(): string[] {
|
|
483
483
|
if (!this.skillCache) this.rebuildSkillIndex();
|
|
484
|
-
return [...this.skillCache!.
|
|
485
|
-
.filter(([, v]) => v.source !== "agent")
|
|
486
|
-
.map(([k]) => k);
|
|
484
|
+
return [...this.skillCache!.keys()];
|
|
487
485
|
}
|
|
488
486
|
|
|
489
487
|
/**
|
|
@@ -562,17 +560,76 @@ export class ProfileManager {
|
|
|
562
560
|
if (!this.skillCache) {
|
|
563
561
|
this.rebuildSkillIndex();
|
|
564
562
|
}
|
|
565
|
-
|
|
563
|
+
const cached = this.skillCache!.get(name);
|
|
564
|
+
if (cached) return cached;
|
|
565
|
+
|
|
566
|
+
// Fallback: agent-specific skills are not indexed globally.
|
|
567
|
+
// Check each agent's skills/ dir on demand.
|
|
568
|
+
return this.findAgentSkillOnDisk(name);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Look up a single agent-specific skill by checking each agent's
|
|
573
|
+
* skills/<name>/SKILL.md path directly.
|
|
574
|
+
*/
|
|
575
|
+
private findAgentSkillOnDisk(
|
|
576
|
+
name: string,
|
|
577
|
+
): { name: string; description: string; filePath: string } | undefined {
|
|
578
|
+
const read = (
|
|
579
|
+
filePath: string,
|
|
580
|
+
): { name: string; description: string; filePath: string } | undefined => {
|
|
581
|
+
if (!existsSync(filePath)) return undefined;
|
|
582
|
+
try {
|
|
583
|
+
const content = readFileSync(filePath, "utf8");
|
|
584
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
585
|
+
return {
|
|
586
|
+
name: frontmatter.name ?? name,
|
|
587
|
+
description: frontmatter.description ?? "",
|
|
588
|
+
filePath,
|
|
589
|
+
};
|
|
590
|
+
} catch {
|
|
591
|
+
return undefined;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// User-level agents
|
|
596
|
+
const userDir = join(homedir(), ".pi", "agents");
|
|
597
|
+
if (existsSync(userDir)) {
|
|
598
|
+
try {
|
|
599
|
+
for (const agent of readdirSync(userDir)) {
|
|
600
|
+
const found = read(join(userDir, agent, "skills", name, "SKILL.md"));
|
|
601
|
+
if (found) return found;
|
|
602
|
+
}
|
|
603
|
+
} catch {}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Project-level agents
|
|
607
|
+
if (this.cwd) {
|
|
608
|
+
const projDir = join(this.cwd, ".pi", "agents");
|
|
609
|
+
if (existsSync(projDir)) {
|
|
610
|
+
try {
|
|
611
|
+
for (const agent of readdirSync(projDir)) {
|
|
612
|
+
const found = read(
|
|
613
|
+
join(projDir, agent, "skills", name, "SKILL.md"),
|
|
614
|
+
);
|
|
615
|
+
if (found) return found;
|
|
616
|
+
}
|
|
617
|
+
} catch {}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return undefined;
|
|
566
622
|
}
|
|
567
623
|
|
|
568
624
|
/**
|
|
569
|
-
* Rebuild the skill index by scanning
|
|
625
|
+
* Rebuild the skill index by scanning known skill directories.
|
|
626
|
+
* Agent-specific skills are NOT indexed here — they are looked up
|
|
627
|
+
* on demand via findSkillOnDisk → scanForAgentSkill().
|
|
570
628
|
*
|
|
571
629
|
* Locations scanned:
|
|
572
630
|
* 1. ~/.pi/agent/skills/<name>/SKILL.md (flat, user-installed)
|
|
573
631
|
* 2. ~/.pi/agent/npm/node_modules/<ext>/skills/ (extension skills)
|
|
574
|
-
* 3.
|
|
575
|
-
* 4. <cwd>/.pi/skills/ (project skills, flat)
|
|
632
|
+
* 3. <cwd>/.pi/skills/ (project skills, flat)
|
|
576
633
|
*/
|
|
577
634
|
rebuildSkillIndex(): void {
|
|
578
635
|
this.skillCache = new Map();
|
|
@@ -580,7 +637,6 @@ export class ProfileManager {
|
|
|
580
637
|
const scanDir = (dir: string, maxDepth: number, source: string): void => {
|
|
581
638
|
if (maxDepth <= 0) return;
|
|
582
639
|
|
|
583
|
-
// Check for SKILL.md in this directory
|
|
584
640
|
const skillMd = join(dir, "SKILL.md");
|
|
585
641
|
if (existsSync(skillMd)) {
|
|
586
642
|
try {
|
|
@@ -610,7 +666,7 @@ export class ProfileManager {
|
|
|
610
666
|
// 1. Global Pi skills
|
|
611
667
|
scanDir(join(this.piAgentDir, "skills"), 2, "global");
|
|
612
668
|
|
|
613
|
-
// 2. Extension skills from npm packages
|
|
669
|
+
// 2. Extension skills from npm packages
|
|
614
670
|
const npmDir = join(this.piAgentDir, "npm", "node_modules");
|
|
615
671
|
if (existsSync(npmDir)) {
|
|
616
672
|
for (const ext of readdirSync(npmDir)) {
|
|
@@ -620,35 +676,12 @@ export class ProfileManager {
|
|
|
620
676
|
if (statSync(extSkills).isDirectory()) {
|
|
621
677
|
scanDir(extSkills, 3, "extension");
|
|
622
678
|
}
|
|
623
|
-
} catch {
|
|
624
|
-
// Skip
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// 3. Agent-specific skills (nested: agents/<agent>/skills/<name>/SKILL.md)
|
|
631
|
-
const agentsDir = join(homedir(), ".pi", "agents");
|
|
632
|
-
if (existsSync(agentsDir)) {
|
|
633
|
-
try {
|
|
634
|
-
for (const agent of readdirSync(agentsDir)) {
|
|
635
|
-
const agentSkills = join(agentsDir, agent, "skills");
|
|
636
|
-
if (existsSync(agentSkills)) {
|
|
637
|
-
try {
|
|
638
|
-
if (statSync(agentSkills).isDirectory()) {
|
|
639
|
-
scanDir(agentSkills, 3, "agent");
|
|
640
|
-
}
|
|
641
|
-
} catch {
|
|
642
|
-
// Skip
|
|
643
|
-
}
|
|
644
|
-
}
|
|
679
|
+
} catch {}
|
|
645
680
|
}
|
|
646
|
-
} catch {
|
|
647
|
-
// Skip unreadable agents dir
|
|
648
681
|
}
|
|
649
682
|
}
|
|
650
683
|
|
|
651
|
-
//
|
|
684
|
+
// 3. Project-level skills
|
|
652
685
|
if (this.cwd) {
|
|
653
686
|
const projSkillsDir = join(this.cwd, ".pi", "skills");
|
|
654
687
|
if (existsSync(projSkillsDir)) {
|