@chankov/agent-skills 0.1.0 → 0.2.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/.pi/extensions/agent-skills-update-check/README.md +58 -0
- package/.pi/extensions/agent-skills-update-check/index.ts +161 -0
- package/.pi/extensions/agent-skills-update-check/package.json +6 -0
- package/.versions/0.2.0/.claude/commands/build.md +18 -0
- package/.versions/0.2.0/.claude/commands/code-simplify.md +22 -0
- package/.versions/0.2.0/.claude/commands/design-agent.md +14 -0
- package/.versions/0.2.0/.claude/commands/doctor.md +13 -0
- package/.versions/0.2.0/.claude/commands/plan.md +16 -0
- package/.versions/0.2.0/.claude/commands/prime.md +22 -0
- package/.versions/0.2.0/.claude/commands/review.md +16 -0
- package/.versions/0.2.0/.claude/commands/setup.md +19 -0
- package/.versions/0.2.0/.claude/commands/ship.md +17 -0
- package/.versions/0.2.0/.claude/commands/spec.md +15 -0
- package/.versions/0.2.0/.claude/commands/test.md +19 -0
- package/.versions/0.2.0/.opencode/commands/as-build.md +17 -0
- package/.versions/0.2.0/.opencode/commands/as-code-simplify.md +16 -0
- package/.versions/0.2.0/.opencode/commands/as-design-agent.md +15 -0
- package/.versions/0.2.0/.opencode/commands/as-doctor.md +11 -0
- package/.versions/0.2.0/.opencode/commands/as-plan.md +16 -0
- package/.versions/0.2.0/.opencode/commands/as-prime.md +22 -0
- package/.versions/0.2.0/.opencode/commands/as-review.md +15 -0
- package/.versions/0.2.0/.opencode/commands/as-setup.md +11 -0
- package/.versions/0.2.0/.opencode/commands/as-ship.md +16 -0
- package/.versions/0.2.0/.opencode/commands/as-spec.md +16 -0
- package/.versions/0.2.0/.opencode/commands/as-test.md +21 -0
- package/.versions/0.2.0/.pi/agents/agent-chain.yaml +49 -0
- package/.versions/0.2.0/.pi/agents/bowser.md +19 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/agent-expert.md +98 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/cli-expert.md +41 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/config-expert.md +63 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/ext-expert.md +43 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/keybinding-expert.md +134 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/pi-orchestrator.md +57 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/prompt-expert.md +70 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/skill-expert.md +42 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/theme-expert.md +40 -0
- package/.versions/0.2.0/.pi/agents/pi-pi/tui-expert.md +85 -0
- package/.versions/0.2.0/.pi/agents/teams.yaml +31 -0
- package/.versions/0.2.0/.pi/damage-control-rules.yaml +278 -0
- package/.versions/0.2.0/.pi/extensions/agent-skills-update-check/README.md +58 -0
- package/.versions/0.2.0/.pi/extensions/agent-skills-update-check/index.ts +161 -0
- package/.versions/0.2.0/.pi/extensions/agent-skills-update-check/package.json +6 -0
- package/.versions/0.2.0/.pi/extensions/chrome-devtools-mcp/README.md +39 -0
- package/.versions/0.2.0/.pi/extensions/chrome-devtools-mcp/index.ts +61 -0
- package/.versions/0.2.0/.pi/extensions/chrome-devtools-mcp/package.json +6 -0
- package/.versions/0.2.0/.pi/extensions/compact-and-continue/README.md +42 -0
- package/.versions/0.2.0/.pi/extensions/compact-and-continue/index.ts +120 -0
- package/.versions/0.2.0/.pi/extensions/compact-and-continue/package.json +6 -0
- package/.versions/0.2.0/.pi/extensions/mcp-bridge/README.md +46 -0
- package/.versions/0.2.0/.pi/extensions/mcp-bridge/index.ts +206 -0
- package/.versions/0.2.0/.pi/extensions/mcp-bridge/package.json +6 -0
- package/.versions/0.2.0/.pi/extensions/package-lock.json +1143 -0
- package/.versions/0.2.0/.pi/extensions/package.json +9 -0
- package/.versions/0.2.0/.pi/harnesses/agent-chain/README.md +37 -0
- package/.versions/0.2.0/.pi/harnesses/agent-chain/index.ts +795 -0
- package/.versions/0.2.0/.pi/harnesses/agent-chain/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/agent-team/README.md +38 -0
- package/.versions/0.2.0/.pi/harnesses/agent-team/index.ts +732 -0
- package/.versions/0.2.0/.pi/harnesses/agent-team/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/coms/README.md +36 -0
- package/.versions/0.2.0/.pi/harnesses/coms/index.ts +1595 -0
- package/.versions/0.2.0/.pi/harnesses/coms/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/coms-net/README.md +46 -0
- package/.versions/0.2.0/.pi/harnesses/coms-net/index.ts +1637 -0
- package/.versions/0.2.0/.pi/harnesses/coms-net/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/damage-control/README.md +38 -0
- package/.versions/0.2.0/.pi/harnesses/damage-control/index.ts +207 -0
- package/.versions/0.2.0/.pi/harnesses/damage-control/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/damage-control-continue/README.md +37 -0
- package/.versions/0.2.0/.pi/harnesses/damage-control-continue/index.ts +234 -0
- package/.versions/0.2.0/.pi/harnesses/damage-control-continue/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/minimal/README.md +27 -0
- package/.versions/0.2.0/.pi/harnesses/minimal/index.ts +32 -0
- package/.versions/0.2.0/.pi/harnesses/minimal/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/package-lock.json +35 -0
- package/.versions/0.2.0/.pi/harnesses/package.json +9 -0
- package/.versions/0.2.0/.pi/harnesses/pi-pi/README.md +39 -0
- package/.versions/0.2.0/.pi/harnesses/pi-pi/index.ts +631 -0
- package/.versions/0.2.0/.pi/harnesses/pi-pi/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/purpose-gate/README.md +27 -0
- package/.versions/0.2.0/.pi/harnesses/purpose-gate/index.ts +82 -0
- package/.versions/0.2.0/.pi/harnesses/purpose-gate/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/session-replay/README.md +28 -0
- package/.versions/0.2.0/.pi/harnesses/session-replay/index.ts +214 -0
- package/.versions/0.2.0/.pi/harnesses/session-replay/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/subagent-widget/README.md +36 -0
- package/.versions/0.2.0/.pi/harnesses/subagent-widget/index.ts +479 -0
- package/.versions/0.2.0/.pi/harnesses/subagent-widget/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/system-select/README.md +39 -0
- package/.versions/0.2.0/.pi/harnesses/system-select/index.ts +165 -0
- package/.versions/0.2.0/.pi/harnesses/system-select/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/tilldone/README.md +35 -0
- package/.versions/0.2.0/.pi/harnesses/tilldone/index.ts +724 -0
- package/.versions/0.2.0/.pi/harnesses/tilldone/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/tool-counter/README.md +31 -0
- package/.versions/0.2.0/.pi/harnesses/tool-counter/index.ts +100 -0
- package/.versions/0.2.0/.pi/harnesses/tool-counter/package.json +6 -0
- package/.versions/0.2.0/.pi/harnesses/tool-counter-widget/README.md +27 -0
- package/.versions/0.2.0/.pi/harnesses/tool-counter-widget/index.ts +66 -0
- package/.versions/0.2.0/.pi/harnesses/tool-counter-widget/package.json +6 -0
- package/.versions/0.2.0/.pi/prompts/build.md +24 -0
- package/.versions/0.2.0/.pi/prompts/code-simplify.md +22 -0
- package/.versions/0.2.0/.pi/prompts/doctor.md +13 -0
- package/.versions/0.2.0/.pi/prompts/plan.md +16 -0
- package/.versions/0.2.0/.pi/prompts/review.md +16 -0
- package/.versions/0.2.0/.pi/prompts/setup.md +19 -0
- package/.versions/0.2.0/.pi/prompts/ship.md +17 -0
- package/.versions/0.2.0/.pi/prompts/spec.md +15 -0
- package/.versions/0.2.0/.pi/prompts/test.md +19 -0
- package/.versions/0.2.0/.pi/skills/bowser/SKILL.md +114 -0
- package/.versions/0.2.0/.version +1 -0
- package/.versions/0.2.0/agents/builder.md +6 -0
- package/.versions/0.2.0/agents/code-reviewer.md +93 -0
- package/.versions/0.2.0/agents/documenter.md +6 -0
- package/.versions/0.2.0/agents/plan-reviewer.md +22 -0
- package/.versions/0.2.0/agents/planner.md +6 -0
- package/.versions/0.2.0/agents/scout.md +6 -0
- package/.versions/0.2.0/agents/security-auditor.md +97 -0
- package/.versions/0.2.0/agents/test-engineer.md +89 -0
- package/.versions/0.2.0/hooks/SIMPLIFY-IGNORE.md +90 -0
- package/.versions/0.2.0/hooks/hooks.json +14 -0
- package/.versions/0.2.0/hooks/session-start.sh +74 -0
- package/.versions/0.2.0/hooks/simplify-ignore-test.sh +247 -0
- package/.versions/0.2.0/hooks/simplify-ignore.sh +302 -0
- package/.versions/0.2.0/references/accessibility-checklist.md +159 -0
- package/.versions/0.2.0/references/performance-checklist.md +121 -0
- package/.versions/0.2.0/references/prompting-patterns.md +380 -0
- package/.versions/0.2.0/references/security-checklist.md +134 -0
- package/.versions/0.2.0/references/testing-patterns.md +236 -0
- package/.versions/0.2.0/skills/api-and-interface-design/SKILL.md +294 -0
- package/.versions/0.2.0/skills/browser-testing-with-devtools/SKILL.md +335 -0
- package/.versions/0.2.0/skills/ci-cd-and-automation/SKILL.md +390 -0
- package/.versions/0.2.0/skills/code-review-and-quality/SKILL.md +347 -0
- package/.versions/0.2.0/skills/code-simplification/SKILL.md +331 -0
- package/.versions/0.2.0/skills/context-engineering/SKILL.md +291 -0
- package/.versions/0.2.0/skills/debugging-and-error-recovery/SKILL.md +300 -0
- package/.versions/0.2.0/skills/deprecation-and-migration/SKILL.md +206 -0
- package/.versions/0.2.0/skills/designing-agents/SKILL.md +394 -0
- package/.versions/0.2.0/skills/designing-agents/pi-harness-authoring.md +213 -0
- package/.versions/0.2.0/skills/documentation-and-adrs/SKILL.md +278 -0
- package/.versions/0.2.0/skills/frontend-ui-engineering/SKILL.md +322 -0
- package/.versions/0.2.0/skills/git-workflow-and-versioning/SKILL.md +316 -0
- package/.versions/0.2.0/skills/guided-workspace-setup/SKILL.md +293 -0
- package/.versions/0.2.0/skills/idea-refine/SKILL.md +178 -0
- package/.versions/0.2.0/skills/idea-refine/examples.md +238 -0
- package/.versions/0.2.0/skills/idea-refine/frameworks.md +99 -0
- package/.versions/0.2.0/skills/idea-refine/refinement-criteria.md +113 -0
- package/.versions/0.2.0/skills/idea-refine/scripts/idea-refine.sh +15 -0
- package/.versions/0.2.0/skills/incremental-implementation/SKILL.md +279 -0
- package/.versions/0.2.0/skills/performance-optimization/SKILL.md +350 -0
- package/.versions/0.2.0/skills/planning-and-task-breakdown/SKILL.md +237 -0
- package/.versions/0.2.0/skills/security-and-hardening/SKILL.md +349 -0
- package/.versions/0.2.0/skills/shipping-and-launch/SKILL.md +309 -0
- package/.versions/0.2.0/skills/source-driven-development/SKILL.md +194 -0
- package/.versions/0.2.0/skills/spec-driven-development/SKILL.md +237 -0
- package/.versions/0.2.0/skills/test-driven-development/SKILL.md +379 -0
- package/.versions/0.2.0/skills/using-agent-skills/SKILL.md +176 -0
- package/CHANGELOG.md +36 -0
- package/bin/cli.js +42 -7
- package/bin/lib/update-notifier.js +195 -0
- package/docs/npm-install.md +60 -0
- package/hooks/session-start.sh +66 -12
- package/package.json +1 -1
- package/skills/guided-workspace-setup/SKILL.md +1 -1
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Chain — Sequential pipeline orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Runs opinionated, repeatable agent workflows. Chains are defined in
|
|
5
|
+
* .pi/agents/agent-chain.yaml — each chain is a sequence of agent steps
|
|
6
|
+
* with prompt templates. The user's original prompt flows into step 1,
|
|
7
|
+
* the output becomes $INPUT for step 2's prompt template, and so on.
|
|
8
|
+
* $ORIGINAL is always the user's original prompt.
|
|
9
|
+
*
|
|
10
|
+
* The primary Pi agent has NO codebase tools — it can ONLY kick off the
|
|
11
|
+
* pipeline via the `run_chain` tool. On boot you select a chain; the
|
|
12
|
+
* agent decides when to run it based on the user's prompt.
|
|
13
|
+
*
|
|
14
|
+
* Agents maintain session context within a Pi session — re-running the
|
|
15
|
+
* chain lets each agent resume where it left off.
|
|
16
|
+
*
|
|
17
|
+
* Commands:
|
|
18
|
+
* /chain — switch active chain
|
|
19
|
+
* /chain-list — list all available chains
|
|
20
|
+
*
|
|
21
|
+
* Usage: pi -e extensions/agent-chain.ts
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
25
|
+
import { Type } from "@sinclair/typebox";
|
|
26
|
+
import { Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
27
|
+
import { spawn } from "child_process";
|
|
28
|
+
import { readFileSync, existsSync, readdirSync, mkdirSync, unlinkSync } from "fs";
|
|
29
|
+
import { join, resolve } from "path";
|
|
30
|
+
|
|
31
|
+
// ── Types ────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
interface ChainStep {
|
|
34
|
+
agent: string;
|
|
35
|
+
prompt: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ChainDef {
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
steps: ChainStep[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface AgentDef {
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
tools: string;
|
|
48
|
+
systemPrompt: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface StepState {
|
|
52
|
+
agent: string;
|
|
53
|
+
status: "pending" | "running" | "done" | "error";
|
|
54
|
+
elapsed: number;
|
|
55
|
+
lastWork: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Display Name Helper ──────────────────────────
|
|
59
|
+
|
|
60
|
+
function displayName(name: string): string {
|
|
61
|
+
return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Chain YAML Parser ────────────────────────────
|
|
65
|
+
|
|
66
|
+
function parseChainYaml(raw: string): ChainDef[] {
|
|
67
|
+
const chains: ChainDef[] = [];
|
|
68
|
+
let current: ChainDef | null = null;
|
|
69
|
+
let currentStep: ChainStep | null = null;
|
|
70
|
+
|
|
71
|
+
for (const line of raw.split("\n")) {
|
|
72
|
+
// Chain name: top-level key
|
|
73
|
+
const chainMatch = line.match(/^(\S[^:]*):$/);
|
|
74
|
+
if (chainMatch) {
|
|
75
|
+
if (current && currentStep) {
|
|
76
|
+
current.steps.push(currentStep);
|
|
77
|
+
currentStep = null;
|
|
78
|
+
}
|
|
79
|
+
current = { name: chainMatch[1].trim(), description: "", steps: [] };
|
|
80
|
+
chains.push(current);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Chain description
|
|
85
|
+
const descMatch = line.match(/^\s+description:\s+(.+)$/);
|
|
86
|
+
if (descMatch && current && !currentStep) {
|
|
87
|
+
let desc = descMatch[1].trim();
|
|
88
|
+
if ((desc.startsWith('"') && desc.endsWith('"')) ||
|
|
89
|
+
(desc.startsWith("'") && desc.endsWith("'"))) {
|
|
90
|
+
desc = desc.slice(1, -1);
|
|
91
|
+
}
|
|
92
|
+
current.description = desc;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// "steps:" label — skip
|
|
97
|
+
if (line.match(/^\s+steps:\s*$/) && current) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Step agent line
|
|
102
|
+
const agentMatch = line.match(/^\s+-\s+agent:\s+(.+)$/);
|
|
103
|
+
if (agentMatch && current) {
|
|
104
|
+
if (currentStep) {
|
|
105
|
+
current.steps.push(currentStep);
|
|
106
|
+
}
|
|
107
|
+
currentStep = { agent: agentMatch[1].trim(), prompt: "" };
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Step prompt line
|
|
112
|
+
const promptMatch = line.match(/^\s+prompt:\s+(.+)$/);
|
|
113
|
+
if (promptMatch && currentStep) {
|
|
114
|
+
let prompt = promptMatch[1].trim();
|
|
115
|
+
if ((prompt.startsWith('"') && prompt.endsWith('"')) ||
|
|
116
|
+
(prompt.startsWith("'") && prompt.endsWith("'"))) {
|
|
117
|
+
prompt = prompt.slice(1, -1);
|
|
118
|
+
}
|
|
119
|
+
prompt = prompt.replace(/\\n/g, "\n");
|
|
120
|
+
currentStep.prompt = prompt;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (current && currentStep) {
|
|
126
|
+
current.steps.push(currentStep);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return chains;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Frontmatter Parser ───────────────────────────
|
|
133
|
+
|
|
134
|
+
function parseAgentFile(filePath: string): AgentDef | null {
|
|
135
|
+
try {
|
|
136
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
137
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
138
|
+
if (!match) return null;
|
|
139
|
+
|
|
140
|
+
const frontmatter: Record<string, string> = {};
|
|
141
|
+
for (const line of match[1].split("\n")) {
|
|
142
|
+
const idx = line.indexOf(":");
|
|
143
|
+
if (idx > 0) {
|
|
144
|
+
frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!frontmatter.name) return null;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
name: frontmatter.name,
|
|
152
|
+
description: frontmatter.description || "",
|
|
153
|
+
tools: frontmatter.tools || "read,grep,find,ls",
|
|
154
|
+
systemPrompt: match[2].trim(),
|
|
155
|
+
};
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function scanAgentDirs(cwd: string): Map<string, AgentDef> {
|
|
162
|
+
const dirs = [
|
|
163
|
+
join(cwd, "agents"),
|
|
164
|
+
join(cwd, ".claude", "agents"),
|
|
165
|
+
join(cwd, ".pi", "agents"),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const agents = new Map<string, AgentDef>();
|
|
169
|
+
|
|
170
|
+
for (const dir of dirs) {
|
|
171
|
+
if (!existsSync(dir)) continue;
|
|
172
|
+
try {
|
|
173
|
+
for (const file of readdirSync(dir)) {
|
|
174
|
+
if (!file.endsWith(".md")) continue;
|
|
175
|
+
const fullPath = resolve(dir, file);
|
|
176
|
+
const def = parseAgentFile(fullPath);
|
|
177
|
+
if (def && !agents.has(def.name.toLowerCase())) {
|
|
178
|
+
agents.set(def.name.toLowerCase(), def);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return agents;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Extension ────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
export default function (pi: ExtensionAPI) {
|
|
190
|
+
let allAgents: Map<string, AgentDef> = new Map();
|
|
191
|
+
let chains: ChainDef[] = [];
|
|
192
|
+
let activeChain: ChainDef | null = null;
|
|
193
|
+
let widgetCtx: any;
|
|
194
|
+
let sessionDir = "";
|
|
195
|
+
const agentSessions: Map<string, string | null> = new Map();
|
|
196
|
+
|
|
197
|
+
// Per-step state for the active chain
|
|
198
|
+
let stepStates: StepState[] = [];
|
|
199
|
+
let pendingReset = false;
|
|
200
|
+
|
|
201
|
+
function loadChains(cwd: string) {
|
|
202
|
+
sessionDir = join(cwd, ".pi", "agent-sessions");
|
|
203
|
+
if (!existsSync(sessionDir)) {
|
|
204
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
allAgents = scanAgentDirs(cwd);
|
|
208
|
+
|
|
209
|
+
agentSessions.clear();
|
|
210
|
+
for (const [key] of allAgents) {
|
|
211
|
+
const sessionFile = join(sessionDir, `chain-${key}.json`);
|
|
212
|
+
agentSessions.set(key, existsSync(sessionFile) ? sessionFile : null);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const chainPath = join(cwd, ".pi", "agents", "agent-chain.yaml");
|
|
216
|
+
if (existsSync(chainPath)) {
|
|
217
|
+
try {
|
|
218
|
+
chains = parseChainYaml(readFileSync(chainPath, "utf-8"));
|
|
219
|
+
} catch {
|
|
220
|
+
chains = [];
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
chains = [];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function activateChain(chain: ChainDef) {
|
|
228
|
+
activeChain = chain;
|
|
229
|
+
stepStates = chain.steps.map(s => ({
|
|
230
|
+
agent: s.agent,
|
|
231
|
+
status: "pending" as const,
|
|
232
|
+
elapsed: 0,
|
|
233
|
+
lastWork: "",
|
|
234
|
+
}));
|
|
235
|
+
// Skip widget re-registration if reset is pending — let before_agent_start handle it
|
|
236
|
+
if (!pendingReset) {
|
|
237
|
+
updateWidget();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Card Rendering ──────────────────────────
|
|
242
|
+
|
|
243
|
+
function renderCard(state: StepState, colWidth: number, theme: any): string[] {
|
|
244
|
+
const w = colWidth - 2;
|
|
245
|
+
const truncate = (s: string, max: number) => s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
246
|
+
|
|
247
|
+
const statusColor = state.status === "pending" ? "dim"
|
|
248
|
+
: state.status === "running" ? "accent"
|
|
249
|
+
: state.status === "done" ? "success" : "error";
|
|
250
|
+
const statusIcon = state.status === "pending" ? "○"
|
|
251
|
+
: state.status === "running" ? "●"
|
|
252
|
+
: state.status === "done" ? "✓" : "✗";
|
|
253
|
+
|
|
254
|
+
const name = displayName(state.agent);
|
|
255
|
+
const nameStr = theme.fg("accent", theme.bold(truncate(name, w)));
|
|
256
|
+
const nameVisible = Math.min(name.length, w);
|
|
257
|
+
|
|
258
|
+
const statusStr = `${statusIcon} ${state.status}`;
|
|
259
|
+
const timeStr = state.status !== "pending" ? ` ${Math.round(state.elapsed / 1000)}s` : "";
|
|
260
|
+
const statusLine = theme.fg(statusColor, statusStr + timeStr);
|
|
261
|
+
const statusVisible = statusStr.length + timeStr.length;
|
|
262
|
+
|
|
263
|
+
const workRaw = state.lastWork || "";
|
|
264
|
+
const workText = workRaw ? truncate(workRaw, Math.min(50, w - 1)) : "";
|
|
265
|
+
const workLine = workText ? theme.fg("muted", workText) : theme.fg("dim", "—");
|
|
266
|
+
const workVisible = workText ? workText.length : 1;
|
|
267
|
+
|
|
268
|
+
const top = "┌" + "─".repeat(w) + "┐";
|
|
269
|
+
const bot = "└" + "─".repeat(w) + "┘";
|
|
270
|
+
const border = (content: string, visLen: number) =>
|
|
271
|
+
theme.fg("dim", "│") + content + " ".repeat(Math.max(0, w - visLen)) + theme.fg("dim", "│");
|
|
272
|
+
|
|
273
|
+
return [
|
|
274
|
+
theme.fg("dim", top),
|
|
275
|
+
border(" " + nameStr, 1 + nameVisible),
|
|
276
|
+
border(" " + statusLine, 1 + statusVisible),
|
|
277
|
+
border(" " + workLine, 1 + workVisible),
|
|
278
|
+
theme.fg("dim", bot),
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function updateWidget() {
|
|
283
|
+
if (!widgetCtx) return;
|
|
284
|
+
|
|
285
|
+
widgetCtx.ui.setWidget("agent-chain", (_tui: any, theme: any) => {
|
|
286
|
+
const text = new Text("", 0, 1);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
render(width: number): string[] {
|
|
290
|
+
if (!activeChain || stepStates.length === 0) {
|
|
291
|
+
text.setText(theme.fg("dim", "No chain active. Use /chain to select one."));
|
|
292
|
+
return text.render(width);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const arrowWidth = 5; // " ──▶ "
|
|
296
|
+
const cols = stepStates.length;
|
|
297
|
+
const totalArrowWidth = arrowWidth * (cols - 1);
|
|
298
|
+
const colWidth = Math.max(12, Math.floor((width - totalArrowWidth) / cols));
|
|
299
|
+
const arrowRow = 2; // middle of 5-line card (0-indexed)
|
|
300
|
+
|
|
301
|
+
const cards = stepStates.map(s => renderCard(s, colWidth, theme));
|
|
302
|
+
const cardHeight = cards[0].length;
|
|
303
|
+
const outputLines: string[] = [];
|
|
304
|
+
|
|
305
|
+
for (let line = 0; line < cardHeight; line++) {
|
|
306
|
+
let row = cards[0][line];
|
|
307
|
+
for (let c = 1; c < cols; c++) {
|
|
308
|
+
if (line === arrowRow) {
|
|
309
|
+
row += theme.fg("dim", " ──▶ ");
|
|
310
|
+
} else {
|
|
311
|
+
row += " ".repeat(arrowWidth);
|
|
312
|
+
}
|
|
313
|
+
row += cards[c][line];
|
|
314
|
+
}
|
|
315
|
+
outputLines.push(row);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
text.setText(outputLines.join("\n"));
|
|
319
|
+
return text.render(width);
|
|
320
|
+
},
|
|
321
|
+
invalidate() {
|
|
322
|
+
text.invalidate();
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ── Run Agent (subprocess) ──────────────────
|
|
329
|
+
|
|
330
|
+
function runAgent(
|
|
331
|
+
agentDef: AgentDef,
|
|
332
|
+
task: string,
|
|
333
|
+
stepIndex: number,
|
|
334
|
+
ctx: any,
|
|
335
|
+
): Promise<{ output: string; exitCode: number; elapsed: number }> {
|
|
336
|
+
const model = ctx.model
|
|
337
|
+
? `${ctx.model.provider}/${ctx.model.id}`
|
|
338
|
+
: "openrouter/google/gemini-3-flash-preview";
|
|
339
|
+
|
|
340
|
+
const agentKey = agentDef.name.toLowerCase().replace(/\s+/g, "-");
|
|
341
|
+
const agentSessionFile = join(sessionDir, `chain-${agentKey}.json`);
|
|
342
|
+
const hasSession = agentSessions.get(agentKey);
|
|
343
|
+
|
|
344
|
+
const args = [
|
|
345
|
+
"--mode", "json",
|
|
346
|
+
"-p",
|
|
347
|
+
"--no-extensions",
|
|
348
|
+
"--model", model,
|
|
349
|
+
"--tools", agentDef.tools,
|
|
350
|
+
"--thinking", "off",
|
|
351
|
+
"--append-system-prompt", agentDef.systemPrompt,
|
|
352
|
+
"--session", agentSessionFile,
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
if (hasSession) {
|
|
356
|
+
args.push("-c");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
args.push(task);
|
|
360
|
+
|
|
361
|
+
const textChunks: string[] = [];
|
|
362
|
+
const startTime = Date.now();
|
|
363
|
+
const state = stepStates[stepIndex];
|
|
364
|
+
|
|
365
|
+
return new Promise((resolve) => {
|
|
366
|
+
const proc = spawn("pi", args, {
|
|
367
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
368
|
+
env: { ...process.env },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const timer = setInterval(() => {
|
|
372
|
+
state.elapsed = Date.now() - startTime;
|
|
373
|
+
updateWidget();
|
|
374
|
+
}, 1000);
|
|
375
|
+
|
|
376
|
+
let buffer = "";
|
|
377
|
+
|
|
378
|
+
proc.stdout!.setEncoding("utf-8");
|
|
379
|
+
proc.stdout!.on("data", (chunk: string) => {
|
|
380
|
+
buffer += chunk;
|
|
381
|
+
const lines = buffer.split("\n");
|
|
382
|
+
buffer = lines.pop() || "";
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
if (!line.trim()) continue;
|
|
385
|
+
try {
|
|
386
|
+
const event = JSON.parse(line);
|
|
387
|
+
if (event.type === "message_update") {
|
|
388
|
+
const delta = event.assistantMessageEvent;
|
|
389
|
+
if (delta?.type === "text_delta") {
|
|
390
|
+
textChunks.push(delta.delta || "");
|
|
391
|
+
const full = textChunks.join("");
|
|
392
|
+
const last = full.split("\n").filter((l: string) => l.trim()).pop() || "";
|
|
393
|
+
state.lastWork = last;
|
|
394
|
+
updateWidget();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch {}
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
proc.stderr!.setEncoding("utf-8");
|
|
402
|
+
proc.stderr!.on("data", () => {});
|
|
403
|
+
|
|
404
|
+
proc.on("close", (code) => {
|
|
405
|
+
if (buffer.trim()) {
|
|
406
|
+
try {
|
|
407
|
+
const event = JSON.parse(buffer);
|
|
408
|
+
if (event.type === "message_update") {
|
|
409
|
+
const delta = event.assistantMessageEvent;
|
|
410
|
+
if (delta?.type === "text_delta") textChunks.push(delta.delta || "");
|
|
411
|
+
}
|
|
412
|
+
} catch {}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
clearInterval(timer);
|
|
416
|
+
const elapsed = Date.now() - startTime;
|
|
417
|
+
state.elapsed = elapsed;
|
|
418
|
+
const output = textChunks.join("");
|
|
419
|
+
state.lastWork = output.split("\n").filter((l: string) => l.trim()).pop() || "";
|
|
420
|
+
|
|
421
|
+
if (code === 0) {
|
|
422
|
+
agentSessions.set(agentKey, agentSessionFile);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
resolve({ output, exitCode: code ?? 1, elapsed });
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
proc.on("error", (err) => {
|
|
429
|
+
clearInterval(timer);
|
|
430
|
+
resolve({
|
|
431
|
+
output: `Error spawning agent: ${err.message}`,
|
|
432
|
+
exitCode: 1,
|
|
433
|
+
elapsed: Date.now() - startTime,
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── Run Chain (sequential pipeline) ─────────
|
|
440
|
+
|
|
441
|
+
async function runChain(
|
|
442
|
+
task: string,
|
|
443
|
+
ctx: any,
|
|
444
|
+
): Promise<{ output: string; success: boolean; elapsed: number }> {
|
|
445
|
+
if (!activeChain) {
|
|
446
|
+
return { output: "No chain active", success: false, elapsed: 0 };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const chainStart = Date.now();
|
|
450
|
+
|
|
451
|
+
// Reset all steps to pending
|
|
452
|
+
stepStates = activeChain.steps.map(s => ({
|
|
453
|
+
agent: s.agent,
|
|
454
|
+
status: "pending" as const,
|
|
455
|
+
elapsed: 0,
|
|
456
|
+
lastWork: "",
|
|
457
|
+
}));
|
|
458
|
+
updateWidget();
|
|
459
|
+
|
|
460
|
+
let input = task;
|
|
461
|
+
const originalPrompt = task;
|
|
462
|
+
|
|
463
|
+
for (let i = 0; i < activeChain.steps.length; i++) {
|
|
464
|
+
const step = activeChain.steps[i];
|
|
465
|
+
stepStates[i].status = "running";
|
|
466
|
+
updateWidget();
|
|
467
|
+
|
|
468
|
+
const resolvedPrompt = step.prompt
|
|
469
|
+
.replace(/\$INPUT/g, input)
|
|
470
|
+
.replace(/\$ORIGINAL/g, originalPrompt);
|
|
471
|
+
|
|
472
|
+
const agentDef = allAgents.get(step.agent.toLowerCase());
|
|
473
|
+
if (!agentDef) {
|
|
474
|
+
stepStates[i].status = "error";
|
|
475
|
+
stepStates[i].lastWork = `Agent "${step.agent}" not found`;
|
|
476
|
+
updateWidget();
|
|
477
|
+
return {
|
|
478
|
+
output: `Error at step ${i + 1}: Agent "${step.agent}" not found. Available: ${Array.from(allAgents.keys()).join(", ")}`,
|
|
479
|
+
success: false,
|
|
480
|
+
elapsed: Date.now() - chainStart,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const result = await runAgent(agentDef, resolvedPrompt, i, ctx);
|
|
485
|
+
|
|
486
|
+
if (result.exitCode !== 0) {
|
|
487
|
+
stepStates[i].status = "error";
|
|
488
|
+
updateWidget();
|
|
489
|
+
return {
|
|
490
|
+
output: `Error at step ${i + 1} (${step.agent}): ${result.output}`,
|
|
491
|
+
success: false,
|
|
492
|
+
elapsed: Date.now() - chainStart,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
stepStates[i].status = "done";
|
|
497
|
+
updateWidget();
|
|
498
|
+
|
|
499
|
+
input = result.output;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { output: input, success: true, elapsed: Date.now() - chainStart };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ── run_chain Tool ──────────────────────────
|
|
506
|
+
|
|
507
|
+
pi.registerTool({
|
|
508
|
+
name: "run_chain",
|
|
509
|
+
label: "Run Chain",
|
|
510
|
+
description: "Execute the active agent chain pipeline. Each step runs sequentially — output from one step feeds into the next. Agents maintain session context across runs.",
|
|
511
|
+
parameters: Type.Object({
|
|
512
|
+
task: Type.String({ description: "The task/prompt for the chain to process" }),
|
|
513
|
+
}),
|
|
514
|
+
|
|
515
|
+
async execute(_toolCallId, params, _signal, onUpdate, ctx) {
|
|
516
|
+
const { task } = params as { task: string };
|
|
517
|
+
|
|
518
|
+
if (onUpdate) {
|
|
519
|
+
onUpdate({
|
|
520
|
+
content: [{ type: "text", text: `Starting chain: ${activeChain?.name}...` }],
|
|
521
|
+
details: { chain: activeChain?.name, task, status: "running" },
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const result = await runChain(task, ctx);
|
|
526
|
+
|
|
527
|
+
const truncated = result.output.length > 8000
|
|
528
|
+
? result.output.slice(0, 8000) + "\n\n... [truncated]"
|
|
529
|
+
: result.output;
|
|
530
|
+
|
|
531
|
+
const status = result.success ? "done" : "error";
|
|
532
|
+
const summary = `[chain:${activeChain?.name}] ${status} in ${Math.round(result.elapsed / 1000)}s`;
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
content: [{ type: "text", text: `${summary}\n\n${truncated}` }],
|
|
536
|
+
details: {
|
|
537
|
+
chain: activeChain?.name,
|
|
538
|
+
task,
|
|
539
|
+
status,
|
|
540
|
+
elapsed: result.elapsed,
|
|
541
|
+
fullOutput: result.output,
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
renderCall(args, theme) {
|
|
547
|
+
const task = (args as any).task || "";
|
|
548
|
+
const preview = task.length > 60 ? task.slice(0, 57) + "..." : task;
|
|
549
|
+
return new Text(
|
|
550
|
+
theme.fg("toolTitle", theme.bold("run_chain ")) +
|
|
551
|
+
theme.fg("accent", activeChain?.name || "?") +
|
|
552
|
+
theme.fg("dim", " — ") +
|
|
553
|
+
theme.fg("muted", preview),
|
|
554
|
+
0, 0,
|
|
555
|
+
);
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
renderResult(result, options, theme) {
|
|
559
|
+
const details = result.details as any;
|
|
560
|
+
if (!details) {
|
|
561
|
+
const text = result.content[0];
|
|
562
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (options.isPartial || details.status === "running") {
|
|
566
|
+
return new Text(
|
|
567
|
+
theme.fg("accent", `● ${details.chain || "chain"}`) +
|
|
568
|
+
theme.fg("dim", " running..."),
|
|
569
|
+
0, 0,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const icon = details.status === "done" ? "✓" : "✗";
|
|
574
|
+
const color = details.status === "done" ? "success" : "error";
|
|
575
|
+
const elapsed = typeof details.elapsed === "number" ? Math.round(details.elapsed / 1000) : 0;
|
|
576
|
+
const header = theme.fg(color, `${icon} ${details.chain}`) +
|
|
577
|
+
theme.fg("dim", ` ${elapsed}s`);
|
|
578
|
+
|
|
579
|
+
if (options.expanded && details.fullOutput) {
|
|
580
|
+
const output = details.fullOutput.length > 4000
|
|
581
|
+
? details.fullOutput.slice(0, 4000) + "\n... [truncated]"
|
|
582
|
+
: details.fullOutput;
|
|
583
|
+
return new Text(header + "\n" + theme.fg("muted", output), 0, 0);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return new Text(header, 0, 0);
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// ── Commands ─────────────────────────────────
|
|
591
|
+
|
|
592
|
+
pi.registerCommand("chain", {
|
|
593
|
+
description: "Switch active chain",
|
|
594
|
+
handler: async (_args, ctx) => {
|
|
595
|
+
widgetCtx = ctx;
|
|
596
|
+
if (chains.length === 0) {
|
|
597
|
+
ctx.ui.notify("No chains defined in .pi/agents/agent-chain.yaml", "warning");
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const options = chains.map(c => {
|
|
602
|
+
const steps = c.steps.map(s => displayName(s.agent)).join(" → ");
|
|
603
|
+
const desc = c.description ? ` — ${c.description}` : "";
|
|
604
|
+
return `${c.name}${desc} (${steps})`;
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const choice = await ctx.ui.select("Select Chain", options);
|
|
608
|
+
if (choice === undefined) return;
|
|
609
|
+
|
|
610
|
+
const idx = options.indexOf(choice);
|
|
611
|
+
activateChain(chains[idx]);
|
|
612
|
+
const flow = chains[idx].steps.map(s => displayName(s.agent)).join(" → ");
|
|
613
|
+
ctx.ui.setStatus("agent-chain", `Chain: ${chains[idx].name} (${chains[idx].steps.length} steps)`);
|
|
614
|
+
ctx.ui.notify(
|
|
615
|
+
`Chain: ${chains[idx].name}\n${chains[idx].description}\n${flow}`,
|
|
616
|
+
"info",
|
|
617
|
+
);
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
pi.registerCommand("chain-list", {
|
|
622
|
+
description: "List all available chains",
|
|
623
|
+
handler: async (_args, ctx) => {
|
|
624
|
+
widgetCtx = ctx;
|
|
625
|
+
if (chains.length === 0) {
|
|
626
|
+
ctx.ui.notify("No chains defined in .pi/agents/agent-chain.yaml", "warning");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const list = chains.map(c => {
|
|
631
|
+
const desc = c.description ? ` ${c.description}` : "";
|
|
632
|
+
const steps = c.steps.map((s, i) =>
|
|
633
|
+
` ${i + 1}. ${displayName(s.agent)}`
|
|
634
|
+
).join("\n");
|
|
635
|
+
return `${c.name}:${desc ? "\n" + desc : ""}\n${steps}`;
|
|
636
|
+
}).join("\n\n");
|
|
637
|
+
|
|
638
|
+
ctx.ui.notify(list, "info");
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// ── System Prompt Override ───────────────────
|
|
643
|
+
|
|
644
|
+
pi.on("before_agent_start", async (_event, _ctx) => {
|
|
645
|
+
// Force widget reset on first turn after /new
|
|
646
|
+
if (pendingReset && activeChain) {
|
|
647
|
+
pendingReset = false;
|
|
648
|
+
widgetCtx = _ctx;
|
|
649
|
+
stepStates = activeChain.steps.map(s => ({
|
|
650
|
+
agent: s.agent,
|
|
651
|
+
status: "pending" as const,
|
|
652
|
+
elapsed: 0,
|
|
653
|
+
lastWork: "",
|
|
654
|
+
}));
|
|
655
|
+
updateWidget();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (!activeChain) return {};
|
|
659
|
+
|
|
660
|
+
const flow = activeChain.steps.map(s => displayName(s.agent)).join(" → ");
|
|
661
|
+
const desc = activeChain.description ? `\n${activeChain.description}` : "";
|
|
662
|
+
|
|
663
|
+
// Build pipeline steps summary
|
|
664
|
+
const steps = activeChain.steps.map((s, i) => {
|
|
665
|
+
const agentDef = allAgents.get(s.agent.toLowerCase());
|
|
666
|
+
const agentDesc = agentDef?.description || "";
|
|
667
|
+
return `${i + 1}. **${displayName(s.agent)}** — ${agentDesc}`;
|
|
668
|
+
}).join("\n");
|
|
669
|
+
|
|
670
|
+
// Build full agent catalog (like agent-team.ts)
|
|
671
|
+
const seen = new Set<string>();
|
|
672
|
+
const agentCatalog = activeChain.steps
|
|
673
|
+
.filter(s => {
|
|
674
|
+
const key = s.agent.toLowerCase();
|
|
675
|
+
if (seen.has(key)) return false;
|
|
676
|
+
seen.add(key);
|
|
677
|
+
return true;
|
|
678
|
+
})
|
|
679
|
+
.map(s => {
|
|
680
|
+
const agentDef = allAgents.get(s.agent.toLowerCase());
|
|
681
|
+
if (!agentDef) return `### ${displayName(s.agent)}\nAgent not found.`;
|
|
682
|
+
return `### ${displayName(agentDef.name)}\n${agentDef.description}\n**Tools:** ${agentDef.tools}\n**Role:** ${agentDef.systemPrompt}`;
|
|
683
|
+
})
|
|
684
|
+
.join("\n\n");
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
systemPrompt: `You are an agent with a sequential pipeline called "${activeChain.name}" at your disposal.${desc}
|
|
688
|
+
You have full access to your own tools AND the run_chain tool to delegate to your team.
|
|
689
|
+
|
|
690
|
+
## Active Chain: ${activeChain.name}
|
|
691
|
+
Flow: ${flow}
|
|
692
|
+
|
|
693
|
+
${steps}
|
|
694
|
+
|
|
695
|
+
## Agent Details
|
|
696
|
+
|
|
697
|
+
${agentCatalog}
|
|
698
|
+
|
|
699
|
+
## When to Use run_chain
|
|
700
|
+
- Significant work: new features, refactors, multi-file changes, anything non-trivial
|
|
701
|
+
- Tasks that benefit from the full pipeline: planning, building, reviewing
|
|
702
|
+
- When you want structured, multi-agent collaboration on a problem
|
|
703
|
+
|
|
704
|
+
## When to Work Directly
|
|
705
|
+
- Simple one-off commands: reading a file, checking status, listing contents
|
|
706
|
+
- Quick lookups, small edits, answering questions about the codebase
|
|
707
|
+
- Anything you can handle in a single step without needing the pipeline
|
|
708
|
+
|
|
709
|
+
## How run_chain Works
|
|
710
|
+
- Pass a clear task description to run_chain
|
|
711
|
+
- Each step's output feeds into the next step as $INPUT
|
|
712
|
+
- Agents maintain session context — they remember previous work within this session
|
|
713
|
+
- You can run the chain multiple times with different tasks if needed
|
|
714
|
+
- After the chain completes, review the result and summarize for the user
|
|
715
|
+
|
|
716
|
+
## Guidelines
|
|
717
|
+
- Use your judgment — if it's quick, just do it; if it's real work, run the chain
|
|
718
|
+
- Keep chain tasks focused and clearly described
|
|
719
|
+
- You can mix direct work and chain runs in the same conversation`,
|
|
720
|
+
};
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// ── Session Start ───────────────────────────
|
|
724
|
+
|
|
725
|
+
pi.on("session_start", async (_event, _ctx) => {
|
|
726
|
+
// Clear widget with both old and new ctx — one of them will be valid
|
|
727
|
+
if (widgetCtx) {
|
|
728
|
+
widgetCtx.ui.setWidget("agent-chain", undefined);
|
|
729
|
+
}
|
|
730
|
+
_ctx.ui.setWidget("agent-chain", undefined);
|
|
731
|
+
widgetCtx = _ctx;
|
|
732
|
+
|
|
733
|
+
// Reset execution state — widget re-registration deferred to before_agent_start
|
|
734
|
+
stepStates = [];
|
|
735
|
+
activeChain = null;
|
|
736
|
+
pendingReset = true;
|
|
737
|
+
|
|
738
|
+
// Wipe chain session files — reset agent context on /new and launch
|
|
739
|
+
const sessDir = join(_ctx.cwd, ".pi", "agent-sessions");
|
|
740
|
+
if (existsSync(sessDir)) {
|
|
741
|
+
for (const f of readdirSync(sessDir)) {
|
|
742
|
+
if (f.startsWith("chain-") && f.endsWith(".json")) {
|
|
743
|
+
try { unlinkSync(join(sessDir, f)); } catch {}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Reload chains + clear agentSessions map (all agents start fresh)
|
|
749
|
+
loadChains(_ctx.cwd);
|
|
750
|
+
|
|
751
|
+
if (chains.length === 0) {
|
|
752
|
+
_ctx.ui.notify("No chains found in .pi/agents/agent-chain.yaml", "warning");
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Default to first chain — use /chain to switch
|
|
757
|
+
activateChain(chains[0]);
|
|
758
|
+
|
|
759
|
+
// run_chain is registered as a tool — available alongside all default tools
|
|
760
|
+
|
|
761
|
+
const flow = activeChain!.steps.map(s => displayName(s.agent)).join(" → ");
|
|
762
|
+
_ctx.ui.setStatus("agent-chain", `Chain: ${activeChain!.name} (${activeChain!.steps.length} steps)`);
|
|
763
|
+
_ctx.ui.notify(
|
|
764
|
+
`Chain: ${activeChain!.name}\n${activeChain!.description}\n${flow}\n\n` +
|
|
765
|
+
`/chain Switch chain\n` +
|
|
766
|
+
`/chain-list List all chains`,
|
|
767
|
+
"info",
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Footer: model | chain name | context bar
|
|
771
|
+
_ctx.ui.setFooter((_tui, theme, _footerData) => ({
|
|
772
|
+
dispose: () => {},
|
|
773
|
+
invalidate() {},
|
|
774
|
+
render(width: number): string[] {
|
|
775
|
+
const model = _ctx.model?.id || "no-model";
|
|
776
|
+
const usage = _ctx.getContextUsage();
|
|
777
|
+
const pct = usage ? usage.percent : 0;
|
|
778
|
+
const filled = Math.round(pct / 10);
|
|
779
|
+
const bar = "#".repeat(filled) + "-".repeat(10 - filled);
|
|
780
|
+
|
|
781
|
+
const chainLabel = activeChain
|
|
782
|
+
? theme.fg("accent", activeChain.name)
|
|
783
|
+
: theme.fg("dim", "no chain");
|
|
784
|
+
|
|
785
|
+
const left = theme.fg("dim", ` ${model}`) +
|
|
786
|
+
theme.fg("muted", " · ") +
|
|
787
|
+
chainLabel;
|
|
788
|
+
const right = theme.fg("dim", `[${bar}] ${Math.round(pct)}% `);
|
|
789
|
+
const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
|
|
790
|
+
|
|
791
|
+
return [truncateToWidth(left + pad + right, width)];
|
|
792
|
+
},
|
|
793
|
+
}));
|
|
794
|
+
});
|
|
795
|
+
}
|