@chankov/agent-skills 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/.versions/0.3.2/.claude/commands/build.md +18 -0
- package/.versions/0.3.2/.claude/commands/code-simplify.md +22 -0
- package/.versions/0.3.2/.claude/commands/design-agent.md +14 -0
- package/.versions/0.3.2/.claude/commands/doctor-agent-skills.md +13 -0
- package/.versions/0.3.2/.claude/commands/plan.md +16 -0
- package/.versions/0.3.2/.claude/commands/prime.md +22 -0
- package/.versions/0.3.2/.claude/commands/review.md +16 -0
- package/.versions/0.3.2/.claude/commands/setup-agent-skills.md +19 -0
- package/.versions/0.3.2/.claude/commands/ship.md +17 -0
- package/.versions/0.3.2/.claude/commands/spec.md +15 -0
- package/.versions/0.3.2/.claude/commands/test.md +19 -0
- package/.versions/0.3.2/.opencode/commands/as-build.md +17 -0
- package/.versions/0.3.2/.opencode/commands/as-code-simplify.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-design-agent.md +15 -0
- package/.versions/0.3.2/.opencode/commands/as-doctor-agent-skills.md +11 -0
- package/.versions/0.3.2/.opencode/commands/as-plan.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-prime.md +22 -0
- package/.versions/0.3.2/.opencode/commands/as-review.md +15 -0
- package/.versions/0.3.2/.opencode/commands/as-setup-agent-skills.md +11 -0
- package/.versions/0.3.2/.opencode/commands/as-ship.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-spec.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-test.md +21 -0
- package/.versions/0.3.2/.pi/agents/agent-chain.yaml +49 -0
- package/.versions/0.3.2/.pi/agents/bowser.md +19 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/agent-expert.md +98 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/cli-expert.md +41 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/config-expert.md +63 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/ext-expert.md +43 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/keybinding-expert.md +134 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/pi-orchestrator.md +57 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/prompt-expert.md +70 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/skill-expert.md +42 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/theme-expert.md +40 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/tui-expert.md +85 -0
- package/.versions/0.3.2/.pi/agents/teams.yaml +31 -0
- package/.versions/0.3.2/.pi/damage-control-rules.yaml +278 -0
- package/.versions/0.3.2/.pi/extensions/agent-skills-update-check/README.md +58 -0
- package/.versions/0.3.2/.pi/extensions/agent-skills-update-check/index.ts +161 -0
- package/.versions/0.3.2/.pi/extensions/agent-skills-update-check/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/chrome-devtools-mcp/README.md +39 -0
- package/.versions/0.3.2/.pi/extensions/chrome-devtools-mcp/index.ts +61 -0
- package/.versions/0.3.2/.pi/extensions/chrome-devtools-mcp/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/compact-and-continue/README.md +42 -0
- package/.versions/0.3.2/.pi/extensions/compact-and-continue/index.ts +120 -0
- package/.versions/0.3.2/.pi/extensions/compact-and-continue/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/mcp-bridge/README.md +46 -0
- package/.versions/0.3.2/.pi/extensions/mcp-bridge/index.ts +206 -0
- package/.versions/0.3.2/.pi/extensions/mcp-bridge/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/package-lock.json +1143 -0
- package/.versions/0.3.2/.pi/extensions/package.json +9 -0
- package/.versions/0.3.2/.pi/harnesses/agent-chain/README.md +37 -0
- package/.versions/0.3.2/.pi/harnesses/agent-chain/index.ts +795 -0
- package/.versions/0.3.2/.pi/harnesses/agent-chain/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/agent-team/README.md +38 -0
- package/.versions/0.3.2/.pi/harnesses/agent-team/index.ts +732 -0
- package/.versions/0.3.2/.pi/harnesses/agent-team/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/coms/README.md +36 -0
- package/.versions/0.3.2/.pi/harnesses/coms/index.ts +1595 -0
- package/.versions/0.3.2/.pi/harnesses/coms/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/coms-net/README.md +46 -0
- package/.versions/0.3.2/.pi/harnesses/coms-net/index.ts +1637 -0
- package/.versions/0.3.2/.pi/harnesses/coms-net/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control/README.md +38 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control/index.ts +207 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control-continue/README.md +37 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control-continue/index.ts +234 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control-continue/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/minimal/README.md +27 -0
- package/.versions/0.3.2/.pi/harnesses/minimal/index.ts +32 -0
- package/.versions/0.3.2/.pi/harnesses/minimal/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/package-lock.json +35 -0
- package/.versions/0.3.2/.pi/harnesses/package.json +9 -0
- package/.versions/0.3.2/.pi/harnesses/pi-pi/README.md +39 -0
- package/.versions/0.3.2/.pi/harnesses/pi-pi/index.ts +631 -0
- package/.versions/0.3.2/.pi/harnesses/pi-pi/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/purpose-gate/README.md +27 -0
- package/.versions/0.3.2/.pi/harnesses/purpose-gate/index.ts +82 -0
- package/.versions/0.3.2/.pi/harnesses/purpose-gate/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/session-replay/README.md +28 -0
- package/.versions/0.3.2/.pi/harnesses/session-replay/index.ts +214 -0
- package/.versions/0.3.2/.pi/harnesses/session-replay/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/subagent-widget/README.md +36 -0
- package/.versions/0.3.2/.pi/harnesses/subagent-widget/index.ts +479 -0
- package/.versions/0.3.2/.pi/harnesses/subagent-widget/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/system-select/README.md +39 -0
- package/.versions/0.3.2/.pi/harnesses/system-select/index.ts +165 -0
- package/.versions/0.3.2/.pi/harnesses/system-select/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/tilldone/README.md +35 -0
- package/.versions/0.3.2/.pi/harnesses/tilldone/index.ts +724 -0
- package/.versions/0.3.2/.pi/harnesses/tilldone/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter/README.md +31 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter/index.ts +100 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter-widget/README.md +27 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter-widget/index.ts +66 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter-widget/package.json +6 -0
- package/.versions/0.3.2/.pi/prompts/build.md +24 -0
- package/.versions/0.3.2/.pi/prompts/code-simplify.md +22 -0
- package/.versions/0.3.2/.pi/prompts/doctor-agent-skills.md +13 -0
- package/.versions/0.3.2/.pi/prompts/plan.md +16 -0
- package/.versions/0.3.2/.pi/prompts/review.md +16 -0
- package/.versions/0.3.2/.pi/prompts/setup-agent-skills.md +19 -0
- package/.versions/0.3.2/.pi/prompts/ship.md +17 -0
- package/.versions/0.3.2/.pi/prompts/spec.md +15 -0
- package/.versions/0.3.2/.pi/prompts/test.md +19 -0
- package/.versions/0.3.2/.pi/skills/bowser/SKILL.md +114 -0
- package/.versions/0.3.2/.version +1 -0
- package/.versions/0.3.2/agents/builder.md +6 -0
- package/.versions/0.3.2/agents/code-reviewer.md +93 -0
- package/.versions/0.3.2/agents/documenter.md +6 -0
- package/.versions/0.3.2/agents/plan-reviewer.md +22 -0
- package/.versions/0.3.2/agents/planner.md +6 -0
- package/.versions/0.3.2/agents/scout.md +6 -0
- package/.versions/0.3.2/agents/security-auditor.md +97 -0
- package/.versions/0.3.2/agents/test-engineer.md +89 -0
- package/.versions/0.3.2/hooks/SIMPLIFY-IGNORE.md +90 -0
- package/.versions/0.3.2/hooks/hooks.json +14 -0
- package/.versions/0.3.2/hooks/session-start.sh +74 -0
- package/.versions/0.3.2/hooks/simplify-ignore-test.sh +247 -0
- package/.versions/0.3.2/hooks/simplify-ignore.sh +302 -0
- package/.versions/0.3.2/references/accessibility-checklist.md +159 -0
- package/.versions/0.3.2/references/performance-checklist.md +121 -0
- package/.versions/0.3.2/references/prompting-patterns.md +380 -0
- package/.versions/0.3.2/references/security-checklist.md +134 -0
- package/.versions/0.3.2/references/testing-patterns.md +236 -0
- package/.versions/0.3.2/skills/api-and-interface-design/SKILL.md +294 -0
- package/.versions/0.3.2/skills/browser-testing-with-devtools/SKILL.md +335 -0
- package/.versions/0.3.2/skills/ci-cd-and-automation/SKILL.md +390 -0
- package/.versions/0.3.2/skills/code-review-and-quality/SKILL.md +347 -0
- package/.versions/0.3.2/skills/code-simplification/SKILL.md +331 -0
- package/.versions/0.3.2/skills/context-engineering/SKILL.md +291 -0
- package/.versions/0.3.2/skills/debugging-and-error-recovery/SKILL.md +300 -0
- package/.versions/0.3.2/skills/deprecation-and-migration/SKILL.md +206 -0
- package/.versions/0.3.2/skills/designing-agents/SKILL.md +394 -0
- package/.versions/0.3.2/skills/designing-agents/pi-harness-authoring.md +213 -0
- package/.versions/0.3.2/skills/documentation-and-adrs/SKILL.md +278 -0
- package/.versions/0.3.2/skills/frontend-ui-engineering/SKILL.md +322 -0
- package/.versions/0.3.2/skills/git-workflow-and-versioning/SKILL.md +316 -0
- package/.versions/0.3.2/skills/guided-workspace-setup/SKILL.md +345 -0
- package/.versions/0.3.2/skills/idea-refine/SKILL.md +178 -0
- package/.versions/0.3.2/skills/idea-refine/examples.md +238 -0
- package/.versions/0.3.2/skills/idea-refine/frameworks.md +99 -0
- package/.versions/0.3.2/skills/idea-refine/refinement-criteria.md +113 -0
- package/.versions/0.3.2/skills/idea-refine/scripts/idea-refine.sh +15 -0
- package/.versions/0.3.2/skills/incremental-implementation/SKILL.md +279 -0
- package/.versions/0.3.2/skills/performance-optimization/SKILL.md +350 -0
- package/.versions/0.3.2/skills/planning-and-task-breakdown/SKILL.md +237 -0
- package/.versions/0.3.2/skills/security-and-hardening/SKILL.md +349 -0
- package/.versions/0.3.2/skills/shipping-and-launch/SKILL.md +309 -0
- package/.versions/0.3.2/skills/source-driven-development/SKILL.md +194 -0
- package/.versions/0.3.2/skills/spec-driven-development/SKILL.md +237 -0
- package/.versions/0.3.2/skills/test-driven-development/SKILL.md +379 -0
- package/.versions/0.3.2/skills/using-agent-skills/SKILL.md +176 -0
- package/CHANGELOG.md +36 -0
- package/bin/lib/bootstrap.js +56 -1
- package/docs/npm-install.md +30 -0
- package/package.json +1 -1
- package/skills/guided-workspace-setup/SKILL.md +16 -2
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Team — Dispatcher-only orchestrator with grid dashboard
|
|
3
|
+
*
|
|
4
|
+
* The primary Pi agent has NO codebase tools. It can ONLY delegate work
|
|
5
|
+
* to specialist agents via the `dispatch_agent` tool. Each specialist
|
|
6
|
+
* maintains its own Pi session for cross-invocation memory.
|
|
7
|
+
*
|
|
8
|
+
* Loads agent definitions from agents/*.md, .claude/agents/*.md, .pi/agents/*.md.
|
|
9
|
+
* Teams are defined in .pi/agents/teams.yaml — on boot a select dialog lets
|
|
10
|
+
* you pick which team to work with. Only team members are available for dispatch.
|
|
11
|
+
*
|
|
12
|
+
* Commands:
|
|
13
|
+
* /agents-team — switch active team
|
|
14
|
+
* /agents-list — list loaded agents
|
|
15
|
+
* /agents-grid N — set column count (default 2)
|
|
16
|
+
*
|
|
17
|
+
* Usage: pi -e extensions/agent-team.ts
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
21
|
+
import { Type } from "@sinclair/typebox";
|
|
22
|
+
import { Text, type AutocompleteItem, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
23
|
+
import { spawn } from "child_process";
|
|
24
|
+
import { readdirSync, readFileSync, existsSync, mkdirSync, unlinkSync } from "fs";
|
|
25
|
+
import { join, resolve } from "path";
|
|
26
|
+
|
|
27
|
+
// ── Types ────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
interface AgentDef {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
tools: string;
|
|
33
|
+
systemPrompt: string;
|
|
34
|
+
file: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface AgentState {
|
|
38
|
+
def: AgentDef;
|
|
39
|
+
status: "idle" | "running" | "done" | "error";
|
|
40
|
+
task: string;
|
|
41
|
+
toolCount: number;
|
|
42
|
+
elapsed: number;
|
|
43
|
+
lastWork: string;
|
|
44
|
+
contextPct: number;
|
|
45
|
+
sessionFile: string | null;
|
|
46
|
+
runCount: number;
|
|
47
|
+
timer?: ReturnType<typeof setInterval>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Display Name Helper ──────────────────────────
|
|
51
|
+
|
|
52
|
+
function displayName(name: string): string {
|
|
53
|
+
return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Teams YAML Parser ────────────────────────────
|
|
57
|
+
|
|
58
|
+
function parseTeamsYaml(raw: string): Record<string, string[]> {
|
|
59
|
+
const teams: Record<string, string[]> = {};
|
|
60
|
+
let current: string | null = null;
|
|
61
|
+
for (const line of raw.split("\n")) {
|
|
62
|
+
const teamMatch = line.match(/^(\S[^:]*):$/);
|
|
63
|
+
if (teamMatch) {
|
|
64
|
+
current = teamMatch[1].trim();
|
|
65
|
+
teams[current] = [];
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const itemMatch = line.match(/^\s+-\s+(.+)$/);
|
|
69
|
+
if (itemMatch && current) {
|
|
70
|
+
teams[current].push(itemMatch[1].trim());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return teams;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Frontmatter Parser ───────────────────────────
|
|
77
|
+
|
|
78
|
+
function parseAgentFile(filePath: string): AgentDef | null {
|
|
79
|
+
try {
|
|
80
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
81
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
82
|
+
if (!match) return null;
|
|
83
|
+
|
|
84
|
+
const frontmatter: Record<string, string> = {};
|
|
85
|
+
for (const line of match[1].split("\n")) {
|
|
86
|
+
const idx = line.indexOf(":");
|
|
87
|
+
if (idx > 0) {
|
|
88
|
+
frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!frontmatter.name) return null;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
name: frontmatter.name,
|
|
96
|
+
description: frontmatter.description || "",
|
|
97
|
+
tools: frontmatter.tools || "read,grep,find,ls",
|
|
98
|
+
systemPrompt: match[2].trim(),
|
|
99
|
+
file: filePath,
|
|
100
|
+
};
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function scanAgentDirs(cwd: string): AgentDef[] {
|
|
107
|
+
const dirs = [
|
|
108
|
+
join(cwd, "agents"),
|
|
109
|
+
join(cwd, ".claude", "agents"),
|
|
110
|
+
join(cwd, ".pi", "agents"),
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const agents: AgentDef[] = [];
|
|
114
|
+
const seen = new Set<string>();
|
|
115
|
+
|
|
116
|
+
for (const dir of dirs) {
|
|
117
|
+
if (!existsSync(dir)) continue;
|
|
118
|
+
try {
|
|
119
|
+
for (const file of readdirSync(dir)) {
|
|
120
|
+
if (!file.endsWith(".md")) continue;
|
|
121
|
+
const fullPath = resolve(dir, file);
|
|
122
|
+
const def = parseAgentFile(fullPath);
|
|
123
|
+
if (def && !seen.has(def.name.toLowerCase())) {
|
|
124
|
+
seen.add(def.name.toLowerCase());
|
|
125
|
+
agents.push(def);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return agents;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Extension ────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
export default function (pi: ExtensionAPI) {
|
|
137
|
+
const agentStates: Map<string, AgentState> = new Map();
|
|
138
|
+
let allAgentDefs: AgentDef[] = [];
|
|
139
|
+
let teams: Record<string, string[]> = {};
|
|
140
|
+
let activeTeamName = "";
|
|
141
|
+
let gridCols = 2;
|
|
142
|
+
let widgetCtx: any;
|
|
143
|
+
let sessionDir = "";
|
|
144
|
+
let contextWindow = 0;
|
|
145
|
+
|
|
146
|
+
function loadAgents(cwd: string) {
|
|
147
|
+
// Create session storage dir
|
|
148
|
+
sessionDir = join(cwd, ".pi", "agent-sessions");
|
|
149
|
+
if (!existsSync(sessionDir)) {
|
|
150
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Load all agent definitions
|
|
154
|
+
allAgentDefs = scanAgentDirs(cwd);
|
|
155
|
+
|
|
156
|
+
// Load teams from .pi/agents/teams.yaml
|
|
157
|
+
const teamsPath = join(cwd, ".pi", "agents", "teams.yaml");
|
|
158
|
+
if (existsSync(teamsPath)) {
|
|
159
|
+
try {
|
|
160
|
+
teams = parseTeamsYaml(readFileSync(teamsPath, "utf-8"));
|
|
161
|
+
} catch {
|
|
162
|
+
teams = {};
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
teams = {};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// If no teams defined, create a default "all" team
|
|
169
|
+
if (Object.keys(teams).length === 0) {
|
|
170
|
+
teams = { all: allAgentDefs.map(d => d.name) };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function activateTeam(teamName: string) {
|
|
175
|
+
activeTeamName = teamName;
|
|
176
|
+
const members = teams[teamName] || [];
|
|
177
|
+
const defsByName = new Map(allAgentDefs.map(d => [d.name.toLowerCase(), d]));
|
|
178
|
+
|
|
179
|
+
agentStates.clear();
|
|
180
|
+
for (const member of members) {
|
|
181
|
+
const def = defsByName.get(member.toLowerCase());
|
|
182
|
+
if (!def) continue;
|
|
183
|
+
const key = def.name.toLowerCase().replace(/\s+/g, "-");
|
|
184
|
+
const sessionFile = join(sessionDir, `${key}.json`);
|
|
185
|
+
agentStates.set(def.name.toLowerCase(), {
|
|
186
|
+
def,
|
|
187
|
+
status: "idle",
|
|
188
|
+
task: "",
|
|
189
|
+
toolCount: 0,
|
|
190
|
+
elapsed: 0,
|
|
191
|
+
lastWork: "",
|
|
192
|
+
contextPct: 0,
|
|
193
|
+
sessionFile: existsSync(sessionFile) ? sessionFile : null,
|
|
194
|
+
runCount: 0,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Auto-size grid columns based on team size
|
|
199
|
+
const size = agentStates.size;
|
|
200
|
+
gridCols = size <= 3 ? size : size === 4 ? 2 : 3;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Grid Rendering ───────────────────────────
|
|
204
|
+
|
|
205
|
+
function renderCard(state: AgentState, colWidth: number, theme: any): string[] {
|
|
206
|
+
const w = colWidth - 2;
|
|
207
|
+
const truncate = (s: string, max: number) => s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
208
|
+
|
|
209
|
+
const statusColor = state.status === "idle" ? "dim"
|
|
210
|
+
: state.status === "running" ? "accent"
|
|
211
|
+
: state.status === "done" ? "success" : "error";
|
|
212
|
+
const statusIcon = state.status === "idle" ? "○"
|
|
213
|
+
: state.status === "running" ? "●"
|
|
214
|
+
: state.status === "done" ? "✓" : "✗";
|
|
215
|
+
|
|
216
|
+
const name = displayName(state.def.name);
|
|
217
|
+
const nameStr = theme.fg("accent", theme.bold(truncate(name, w)));
|
|
218
|
+
const nameVisible = Math.min(name.length, w);
|
|
219
|
+
|
|
220
|
+
const statusStr = `${statusIcon} ${state.status}`;
|
|
221
|
+
const timeStr = state.status !== "idle" ? ` ${Math.round(state.elapsed / 1000)}s` : "";
|
|
222
|
+
const statusLine = theme.fg(statusColor, statusStr + timeStr);
|
|
223
|
+
const statusVisible = statusStr.length + timeStr.length;
|
|
224
|
+
|
|
225
|
+
// Context bar: 5 blocks + percent
|
|
226
|
+
const filled = Math.ceil(state.contextPct / 20);
|
|
227
|
+
const bar = "#".repeat(filled) + "-".repeat(5 - filled);
|
|
228
|
+
const ctxStr = `[${bar}] ${Math.ceil(state.contextPct)}%`;
|
|
229
|
+
const ctxLine = theme.fg("dim", ctxStr);
|
|
230
|
+
const ctxVisible = ctxStr.length;
|
|
231
|
+
|
|
232
|
+
const workRaw = state.task
|
|
233
|
+
? (state.lastWork || state.task)
|
|
234
|
+
: state.def.description;
|
|
235
|
+
const workText = truncate(workRaw, Math.min(50, w - 1));
|
|
236
|
+
const workLine = theme.fg("muted", workText);
|
|
237
|
+
const workVisible = workText.length;
|
|
238
|
+
|
|
239
|
+
const top = "┌" + "─".repeat(w) + "┐";
|
|
240
|
+
const bot = "└" + "─".repeat(w) + "┘";
|
|
241
|
+
const border = (content: string, visLen: number) =>
|
|
242
|
+
theme.fg("dim", "│") + content + " ".repeat(Math.max(0, w - visLen)) + theme.fg("dim", "│");
|
|
243
|
+
|
|
244
|
+
return [
|
|
245
|
+
theme.fg("dim", top),
|
|
246
|
+
border(" " + nameStr, 1 + nameVisible),
|
|
247
|
+
border(" " + statusLine, 1 + statusVisible),
|
|
248
|
+
border(" " + ctxLine, 1 + ctxVisible),
|
|
249
|
+
border(" " + workLine, 1 + workVisible),
|
|
250
|
+
theme.fg("dim", bot),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function updateWidget() {
|
|
255
|
+
if (!widgetCtx) return;
|
|
256
|
+
|
|
257
|
+
widgetCtx.ui.setWidget("agent-team", (_tui: any, theme: any) => {
|
|
258
|
+
const text = new Text("", 0, 1);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
render(width: number): string[] {
|
|
262
|
+
if (agentStates.size === 0) {
|
|
263
|
+
text.setText(theme.fg("dim", "No agents found. Add .md files to agents/"));
|
|
264
|
+
return text.render(width);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const cols = Math.min(gridCols, agentStates.size);
|
|
268
|
+
const gap = 1;
|
|
269
|
+
const colWidth = Math.floor((width - gap * (cols - 1)) / cols);
|
|
270
|
+
const agents = Array.from(agentStates.values());
|
|
271
|
+
const rows: string[][] = [];
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < agents.length; i += cols) {
|
|
274
|
+
const rowAgents = agents.slice(i, i + cols);
|
|
275
|
+
const cards = rowAgents.map(a => renderCard(a, colWidth, theme));
|
|
276
|
+
|
|
277
|
+
while (cards.length < cols) {
|
|
278
|
+
cards.push(Array(6).fill(" ".repeat(colWidth)));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const cardHeight = cards[0].length;
|
|
282
|
+
for (let line = 0; line < cardHeight; line++) {
|
|
283
|
+
rows.push(cards.map(card => card[line] || ""));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const output = rows.map(cols => cols.join(" ".repeat(gap)));
|
|
288
|
+
text.setText(output.join("\n"));
|
|
289
|
+
return text.render(width);
|
|
290
|
+
},
|
|
291
|
+
invalidate() {
|
|
292
|
+
text.invalidate();
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Dispatch Agent (returns Promise) ─────────
|
|
299
|
+
|
|
300
|
+
function dispatchAgent(
|
|
301
|
+
agentName: string,
|
|
302
|
+
task: string,
|
|
303
|
+
ctx: any,
|
|
304
|
+
): Promise<{ output: string; exitCode: number; elapsed: number }> {
|
|
305
|
+
const key = agentName.toLowerCase();
|
|
306
|
+
const state = agentStates.get(key);
|
|
307
|
+
if (!state) {
|
|
308
|
+
return Promise.resolve({
|
|
309
|
+
output: `Agent "${agentName}" not found. Available: ${Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", ")}`,
|
|
310
|
+
exitCode: 1,
|
|
311
|
+
elapsed: 0,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (state.status === "running") {
|
|
316
|
+
return Promise.resolve({
|
|
317
|
+
output: `Agent "${displayName(state.def.name)}" is already running. Wait for it to finish.`,
|
|
318
|
+
exitCode: 1,
|
|
319
|
+
elapsed: 0,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
state.status = "running";
|
|
324
|
+
state.task = task;
|
|
325
|
+
state.toolCount = 0;
|
|
326
|
+
state.elapsed = 0;
|
|
327
|
+
state.lastWork = "";
|
|
328
|
+
state.runCount++;
|
|
329
|
+
updateWidget();
|
|
330
|
+
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
state.timer = setInterval(() => {
|
|
333
|
+
state.elapsed = Date.now() - startTime;
|
|
334
|
+
updateWidget();
|
|
335
|
+
}, 1000);
|
|
336
|
+
|
|
337
|
+
const model = ctx.model
|
|
338
|
+
? `${ctx.model.provider}/${ctx.model.id}`
|
|
339
|
+
: "openrouter/google/gemini-3-flash-preview";
|
|
340
|
+
|
|
341
|
+
// Session file for this agent
|
|
342
|
+
const agentKey = state.def.name.toLowerCase().replace(/\s+/g, "-");
|
|
343
|
+
const agentSessionFile = join(sessionDir, `${agentKey}.json`);
|
|
344
|
+
|
|
345
|
+
// Build args — first run creates session, subsequent runs resume
|
|
346
|
+
const args = [
|
|
347
|
+
"--mode", "json",
|
|
348
|
+
"-p",
|
|
349
|
+
"--no-extensions",
|
|
350
|
+
"--model", model,
|
|
351
|
+
"--tools", state.def.tools,
|
|
352
|
+
"--thinking", "off",
|
|
353
|
+
"--append-system-prompt", state.def.systemPrompt,
|
|
354
|
+
"--session", agentSessionFile,
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
// Continue existing session if we have one
|
|
358
|
+
if (state.sessionFile) {
|
|
359
|
+
args.push("-c");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
args.push(task);
|
|
363
|
+
|
|
364
|
+
const textChunks: string[] = [];
|
|
365
|
+
|
|
366
|
+
return new Promise((resolve) => {
|
|
367
|
+
const proc = spawn("pi", args, {
|
|
368
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
369
|
+
env: { ...process.env },
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
let buffer = "";
|
|
373
|
+
|
|
374
|
+
proc.stdout!.setEncoding("utf-8");
|
|
375
|
+
proc.stdout!.on("data", (chunk: string) => {
|
|
376
|
+
buffer += chunk;
|
|
377
|
+
const lines = buffer.split("\n");
|
|
378
|
+
buffer = lines.pop() || "";
|
|
379
|
+
for (const line of lines) {
|
|
380
|
+
if (!line.trim()) continue;
|
|
381
|
+
try {
|
|
382
|
+
const event = JSON.parse(line);
|
|
383
|
+
if (event.type === "message_update") {
|
|
384
|
+
const delta = event.assistantMessageEvent;
|
|
385
|
+
if (delta?.type === "text_delta") {
|
|
386
|
+
textChunks.push(delta.delta || "");
|
|
387
|
+
const full = textChunks.join("");
|
|
388
|
+
const last = full.split("\n").filter((l: string) => l.trim()).pop() || "";
|
|
389
|
+
state.lastWork = last;
|
|
390
|
+
updateWidget();
|
|
391
|
+
}
|
|
392
|
+
} else if (event.type === "tool_execution_start") {
|
|
393
|
+
state.toolCount++;
|
|
394
|
+
updateWidget();
|
|
395
|
+
} else if (event.type === "message_end") {
|
|
396
|
+
const msg = event.message;
|
|
397
|
+
if (msg?.usage && contextWindow > 0) {
|
|
398
|
+
state.contextPct = ((msg.usage.input || 0) / contextWindow) * 100;
|
|
399
|
+
updateWidget();
|
|
400
|
+
}
|
|
401
|
+
} else if (event.type === "agent_end") {
|
|
402
|
+
const msgs = event.messages || [];
|
|
403
|
+
const last = [...msgs].reverse().find((m: any) => m.role === "assistant");
|
|
404
|
+
if (last?.usage && contextWindow > 0) {
|
|
405
|
+
state.contextPct = ((last.usage.input || 0) / contextWindow) * 100;
|
|
406
|
+
updateWidget();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} catch {}
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
proc.stderr!.setEncoding("utf-8");
|
|
414
|
+
proc.stderr!.on("data", () => {});
|
|
415
|
+
|
|
416
|
+
proc.on("close", (code) => {
|
|
417
|
+
if (buffer.trim()) {
|
|
418
|
+
try {
|
|
419
|
+
const event = JSON.parse(buffer);
|
|
420
|
+
if (event.type === "message_update") {
|
|
421
|
+
const delta = event.assistantMessageEvent;
|
|
422
|
+
if (delta?.type === "text_delta") textChunks.push(delta.delta || "");
|
|
423
|
+
}
|
|
424
|
+
} catch {}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
clearInterval(state.timer);
|
|
428
|
+
state.elapsed = Date.now() - startTime;
|
|
429
|
+
state.status = code === 0 ? "done" : "error";
|
|
430
|
+
|
|
431
|
+
// Mark session file as available for resume
|
|
432
|
+
if (code === 0) {
|
|
433
|
+
state.sessionFile = agentSessionFile;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const full = textChunks.join("");
|
|
437
|
+
state.lastWork = full.split("\n").filter((l: string) => l.trim()).pop() || "";
|
|
438
|
+
updateWidget();
|
|
439
|
+
|
|
440
|
+
ctx.ui.notify(
|
|
441
|
+
`${displayName(state.def.name)} ${state.status} in ${Math.round(state.elapsed / 1000)}s`,
|
|
442
|
+
state.status === "done" ? "success" : "error"
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
resolve({
|
|
446
|
+
output: full,
|
|
447
|
+
exitCode: code ?? 1,
|
|
448
|
+
elapsed: state.elapsed,
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
proc.on("error", (err) => {
|
|
453
|
+
clearInterval(state.timer);
|
|
454
|
+
state.status = "error";
|
|
455
|
+
state.lastWork = `Error: ${err.message}`;
|
|
456
|
+
updateWidget();
|
|
457
|
+
resolve({
|
|
458
|
+
output: `Error spawning agent: ${err.message}`,
|
|
459
|
+
exitCode: 1,
|
|
460
|
+
elapsed: Date.now() - startTime,
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── dispatch_agent Tool (registered at top level) ──
|
|
467
|
+
|
|
468
|
+
pi.registerTool({
|
|
469
|
+
name: "dispatch_agent",
|
|
470
|
+
label: "Dispatch Agent",
|
|
471
|
+
description: "Dispatch a task to a specialist agent. The agent will execute the task and return the result. Use the system prompt to see available agent names.",
|
|
472
|
+
parameters: Type.Object({
|
|
473
|
+
agent: Type.String({ description: "Agent name (case-insensitive)" }),
|
|
474
|
+
task: Type.String({ description: "Task description for the agent to execute" }),
|
|
475
|
+
}),
|
|
476
|
+
|
|
477
|
+
async execute(_toolCallId, params, _signal, onUpdate, ctx) {
|
|
478
|
+
const { agent, task } = params as { agent: string; task: string };
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
if (onUpdate) {
|
|
482
|
+
onUpdate({
|
|
483
|
+
content: [{ type: "text", text: `Dispatching to ${agent}...` }],
|
|
484
|
+
details: { agent, task, status: "dispatching" },
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const result = await dispatchAgent(agent, task, ctx);
|
|
489
|
+
|
|
490
|
+
const truncated = result.output.length > 8000
|
|
491
|
+
? result.output.slice(0, 8000) + "\n\n... [truncated]"
|
|
492
|
+
: result.output;
|
|
493
|
+
|
|
494
|
+
const status = result.exitCode === 0 ? "done" : "error";
|
|
495
|
+
const summary = `[${agent}] ${status} in ${Math.round(result.elapsed / 1000)}s`;
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
content: [{ type: "text", text: `${summary}\n\n${truncated}` }],
|
|
499
|
+
details: {
|
|
500
|
+
agent,
|
|
501
|
+
task,
|
|
502
|
+
status,
|
|
503
|
+
elapsed: result.elapsed,
|
|
504
|
+
exitCode: result.exitCode,
|
|
505
|
+
fullOutput: result.output,
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
} catch (err: any) {
|
|
509
|
+
return {
|
|
510
|
+
content: [{ type: "text", text: `Error dispatching to ${agent}: ${err?.message || err}` }],
|
|
511
|
+
details: { agent, task, status: "error", elapsed: 0, exitCode: 1, fullOutput: "" },
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
renderCall(args, theme) {
|
|
517
|
+
const agentName = (args as any).agent || "?";
|
|
518
|
+
const task = (args as any).task || "";
|
|
519
|
+
const preview = task.length > 60 ? task.slice(0, 57) + "..." : task;
|
|
520
|
+
return new Text(
|
|
521
|
+
theme.fg("toolTitle", theme.bold("dispatch_agent ")) +
|
|
522
|
+
theme.fg("accent", agentName) +
|
|
523
|
+
theme.fg("dim", " — ") +
|
|
524
|
+
theme.fg("muted", preview),
|
|
525
|
+
0, 0,
|
|
526
|
+
);
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
renderResult(result, options, theme) {
|
|
530
|
+
const details = result.details as any;
|
|
531
|
+
if (!details) {
|
|
532
|
+
const text = result.content[0];
|
|
533
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Streaming/partial result while agent is still running
|
|
537
|
+
if (options.isPartial || details.status === "dispatching") {
|
|
538
|
+
return new Text(
|
|
539
|
+
theme.fg("accent", `● ${details.agent || "?"}`) +
|
|
540
|
+
theme.fg("dim", " working..."),
|
|
541
|
+
0, 0,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const icon = details.status === "done" ? "✓" : "✗";
|
|
546
|
+
const color = details.status === "done" ? "success" : "error";
|
|
547
|
+
const elapsed = typeof details.elapsed === "number" ? Math.round(details.elapsed / 1000) : 0;
|
|
548
|
+
const header = theme.fg(color, `${icon} ${details.agent}`) +
|
|
549
|
+
theme.fg("dim", ` ${elapsed}s`);
|
|
550
|
+
|
|
551
|
+
if (options.expanded && details.fullOutput) {
|
|
552
|
+
const output = details.fullOutput.length > 4000
|
|
553
|
+
? details.fullOutput.slice(0, 4000) + "\n... [truncated]"
|
|
554
|
+
: details.fullOutput;
|
|
555
|
+
return new Text(header + "\n" + theme.fg("muted", output), 0, 0);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return new Text(header, 0, 0);
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// ── Commands ─────────────────────────────────
|
|
563
|
+
|
|
564
|
+
pi.registerCommand("agents-team", {
|
|
565
|
+
description: "Select a team to work with",
|
|
566
|
+
handler: async (_args, ctx) => {
|
|
567
|
+
widgetCtx = ctx;
|
|
568
|
+
const teamNames = Object.keys(teams);
|
|
569
|
+
if (teamNames.length === 0) {
|
|
570
|
+
ctx.ui.notify("No teams defined in .pi/agents/teams.yaml", "warning");
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const options = teamNames.map(name => {
|
|
575
|
+
const members = teams[name].map(m => displayName(m));
|
|
576
|
+
return `${name} — ${members.join(", ")}`;
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const choice = await ctx.ui.select("Select Team", options);
|
|
580
|
+
if (choice === undefined) return;
|
|
581
|
+
|
|
582
|
+
const idx = options.indexOf(choice);
|
|
583
|
+
const name = teamNames[idx];
|
|
584
|
+
activateTeam(name);
|
|
585
|
+
updateWidget();
|
|
586
|
+
ctx.ui.setStatus("agent-team", `Team: ${name} (${agentStates.size})`);
|
|
587
|
+
ctx.ui.notify(`Team: ${name} — ${Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", ")}`, "info");
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
pi.registerCommand("agents-list", {
|
|
592
|
+
description: "List all loaded agents",
|
|
593
|
+
handler: async (_args, _ctx) => {
|
|
594
|
+
widgetCtx = _ctx;
|
|
595
|
+
const names = Array.from(agentStates.values())
|
|
596
|
+
.map(s => {
|
|
597
|
+
const session = s.sessionFile ? "resumed" : "new";
|
|
598
|
+
return `${displayName(s.def.name)} (${s.status}, ${session}, runs: ${s.runCount}): ${s.def.description}`;
|
|
599
|
+
})
|
|
600
|
+
.join("\n");
|
|
601
|
+
_ctx.ui.notify(names || "No agents loaded", "info");
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
pi.registerCommand("agents-grid", {
|
|
606
|
+
description: "Set grid columns: /agents-grid <1-6>",
|
|
607
|
+
getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
|
|
608
|
+
const items = ["1", "2", "3", "4", "5", "6"].map(n => ({
|
|
609
|
+
value: n,
|
|
610
|
+
label: `${n} columns`,
|
|
611
|
+
}));
|
|
612
|
+
const filtered = items.filter(i => i.value.startsWith(prefix));
|
|
613
|
+
return filtered.length > 0 ? filtered : items;
|
|
614
|
+
},
|
|
615
|
+
handler: async (args, _ctx) => {
|
|
616
|
+
widgetCtx = _ctx;
|
|
617
|
+
const n = parseInt(args?.trim() || "", 10);
|
|
618
|
+
if (n >= 1 && n <= 6) {
|
|
619
|
+
gridCols = n;
|
|
620
|
+
_ctx.ui.notify(`Grid set to ${gridCols} columns`, "info");
|
|
621
|
+
updateWidget();
|
|
622
|
+
} else {
|
|
623
|
+
_ctx.ui.notify("Usage: /agents-grid <1-6>", "error");
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// ── System Prompt Override ───────────────────
|
|
629
|
+
|
|
630
|
+
pi.on("before_agent_start", async (_event, _ctx) => {
|
|
631
|
+
// Build dynamic agent catalog from active team only
|
|
632
|
+
const agentCatalog = Array.from(agentStates.values())
|
|
633
|
+
.map(s => `### ${displayName(s.def.name)}\n**Dispatch as:** \`${s.def.name}\`\n${s.def.description}\n**Tools:** ${s.def.tools}`)
|
|
634
|
+
.join("\n\n");
|
|
635
|
+
|
|
636
|
+
const teamMembers = Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", ");
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
systemPrompt: `You are a dispatcher agent. You coordinate specialist agents to accomplish tasks.
|
|
640
|
+
You do NOT have direct access to the codebase. You MUST delegate all work through
|
|
641
|
+
agents using the dispatch_agent tool.
|
|
642
|
+
|
|
643
|
+
## Active Team: ${activeTeamName}
|
|
644
|
+
Members: ${teamMembers}
|
|
645
|
+
You can ONLY dispatch to agents listed below. Do not attempt to dispatch to agents outside this team.
|
|
646
|
+
|
|
647
|
+
## How to Work
|
|
648
|
+
- Analyze the user's request and break it into clear sub-tasks
|
|
649
|
+
- Choose the right agent(s) for each sub-task
|
|
650
|
+
- Dispatch tasks using the dispatch_agent tool
|
|
651
|
+
- Review results and dispatch follow-up agents if needed
|
|
652
|
+
- If a task fails, try a different agent or adjust the task description
|
|
653
|
+
- Summarize the outcome for the user
|
|
654
|
+
|
|
655
|
+
## Rules
|
|
656
|
+
- NEVER try to read, write, or execute code directly — you have no such tools
|
|
657
|
+
- ALWAYS use dispatch_agent to get work done
|
|
658
|
+
- You can chain agents: use scout to explore, then builder to implement
|
|
659
|
+
- You can dispatch the same agent multiple times with different tasks
|
|
660
|
+
- Keep tasks focused — one clear objective per dispatch
|
|
661
|
+
|
|
662
|
+
## Agents
|
|
663
|
+
|
|
664
|
+
${agentCatalog}`,
|
|
665
|
+
};
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// ── Session Start ────────────────────────────
|
|
669
|
+
|
|
670
|
+
pi.on("session_start", async (_event, _ctx) => {
|
|
671
|
+
// Clear widgets from previous session
|
|
672
|
+
if (widgetCtx) {
|
|
673
|
+
widgetCtx.ui.setWidget("agent-team", undefined);
|
|
674
|
+
}
|
|
675
|
+
widgetCtx = _ctx;
|
|
676
|
+
contextWindow = _ctx.model?.contextWindow || 0;
|
|
677
|
+
|
|
678
|
+
// Wipe old agent session files so subagents start fresh
|
|
679
|
+
const sessDir = join(_ctx.cwd, ".pi", "agent-sessions");
|
|
680
|
+
if (existsSync(sessDir)) {
|
|
681
|
+
for (const f of readdirSync(sessDir)) {
|
|
682
|
+
if (f.endsWith(".json")) {
|
|
683
|
+
try { unlinkSync(join(sessDir, f)); } catch {}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
loadAgents(_ctx.cwd);
|
|
689
|
+
|
|
690
|
+
// Default to first team — use /agents-team to switch
|
|
691
|
+
const teamNames = Object.keys(teams);
|
|
692
|
+
if (teamNames.length > 0) {
|
|
693
|
+
activateTeam(teamNames[0]);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Lock down to dispatcher-only (tool already registered at top level)
|
|
697
|
+
pi.setActiveTools(["dispatch_agent"]);
|
|
698
|
+
|
|
699
|
+
_ctx.ui.setStatus("agent-team", `Team: ${activeTeamName} (${agentStates.size})`);
|
|
700
|
+
const members = Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", ");
|
|
701
|
+
_ctx.ui.notify(
|
|
702
|
+
`Team: ${activeTeamName} (${members})\n` +
|
|
703
|
+
`Team sets loaded from: .pi/agents/teams.yaml\n\n` +
|
|
704
|
+
`/agents-team Select a team\n` +
|
|
705
|
+
`/agents-list List active agents and status\n` +
|
|
706
|
+
`/agents-grid <1-6> Set grid column count`,
|
|
707
|
+
"info",
|
|
708
|
+
);
|
|
709
|
+
updateWidget();
|
|
710
|
+
|
|
711
|
+
// Footer: model | team | context bar
|
|
712
|
+
_ctx.ui.setFooter((_tui, theme, _footerData) => ({
|
|
713
|
+
dispose: () => {},
|
|
714
|
+
invalidate() {},
|
|
715
|
+
render(width: number): string[] {
|
|
716
|
+
const model = _ctx.model?.id || "no-model";
|
|
717
|
+
const usage = _ctx.getContextUsage();
|
|
718
|
+
const pct = usage ? usage.percent : 0;
|
|
719
|
+
const filled = Math.round(pct / 10);
|
|
720
|
+
const bar = "#".repeat(filled) + "-".repeat(10 - filled);
|
|
721
|
+
|
|
722
|
+
const left = theme.fg("dim", ` ${model}`) +
|
|
723
|
+
theme.fg("muted", " · ") +
|
|
724
|
+
theme.fg("accent", activeTeamName);
|
|
725
|
+
const right = theme.fg("dim", `[${bar}] ${Math.round(pct)}% `);
|
|
726
|
+
const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
|
|
727
|
+
|
|
728
|
+
return [truncateToWidth(left + pad + right, width)];
|
|
729
|
+
},
|
|
730
|
+
}));
|
|
731
|
+
});
|
|
732
|
+
}
|