@johnnygreco/pizza-pi 0.1.1

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.
Files changed (27) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +82 -0
  3. package/extensions/context.ts +578 -0
  4. package/extensions/control.ts +1782 -0
  5. package/extensions/loop.ts +454 -0
  6. package/extensions/pizza-ui.ts +93 -0
  7. package/extensions/todos.ts +2066 -0
  8. package/node_modules/pi-interactive-subagents/.pi/settings.json +13 -0
  9. package/node_modules/pi-interactive-subagents/.pi/skills/release/SKILL.md +133 -0
  10. package/node_modules/pi-interactive-subagents/LICENSE +21 -0
  11. package/node_modules/pi-interactive-subagents/README.md +362 -0
  12. package/node_modules/pi-interactive-subagents/agents/planner.md +270 -0
  13. package/node_modules/pi-interactive-subagents/agents/reviewer.md +153 -0
  14. package/node_modules/pi-interactive-subagents/agents/scout.md +103 -0
  15. package/node_modules/pi-interactive-subagents/agents/spec.md +339 -0
  16. package/node_modules/pi-interactive-subagents/agents/visual-tester.md +202 -0
  17. package/node_modules/pi-interactive-subagents/agents/worker.md +104 -0
  18. package/node_modules/pi-interactive-subagents/package.json +34 -0
  19. package/node_modules/pi-interactive-subagents/pi-extension/session-artifacts/index.ts +252 -0
  20. package/node_modules/pi-interactive-subagents/pi-extension/subagents/cmux.ts +647 -0
  21. package/node_modules/pi-interactive-subagents/pi-extension/subagents/index.ts +1343 -0
  22. package/node_modules/pi-interactive-subagents/pi-extension/subagents/plan-skill.md +225 -0
  23. package/node_modules/pi-interactive-subagents/pi-extension/subagents/session.ts +124 -0
  24. package/node_modules/pi-interactive-subagents/pi-extension/subagents/subagent-done.ts +166 -0
  25. package/package.json +62 -0
  26. package/prompts/.gitkeep +0 -0
  27. package/skills/.gitkeep +0 -0
@@ -0,0 +1,225 @@
1
+ ---
2
+ name: plan
3
+ description: >
4
+ Planning workflow. Spawns a spec agent to clarify WHAT to build, then a
5
+ planner agent to figure out HOW. Use when asked to "plan", "brainstorm",
6
+ "I want to build X", or "let's design". Requires the subagents extension
7
+ and a supported multiplexer (cmux/tmux/zellij).
8
+ ---
9
+
10
+ # Plan
11
+
12
+ A planning workflow that separates WHAT (spec) from HOW (plan). First a spec agent clarifies intent and requirements with the user, then a planner figures out the technical approach and creates todos.
13
+
14
+ **Announce at start:** "Let me take a quick look, then I'll send a scout to map the codebase before we start the spec session."
15
+
16
+ ---
17
+
18
+ ## Tab Titles
19
+
20
+ Use `set_tab_title` to keep the user informed of progress in the multiplexer UI. Update the title at every phase transition.
21
+
22
+ | Phase | Title example |
23
+ | ------------- | -------------------------------------------------------------- |
24
+ | Assessment | `🔍 Assessing: <short task>` |
25
+ | Scouting | `🔍 Scouting: <short task>` |
26
+ | Spec | `📝 Spec: <short task>` |
27
+ | Planning | `💬 Planning: <short task>` |
28
+ | Review plan | `📋 Review: <short task>` |
29
+ | Executing | `🔨 Executing: 1/3 — <short task>` (update counter per worker) |
30
+ | Reviewing | `🔎 Reviewing: <short task>` |
31
+ | Done | `✅ Done: <short task>` |
32
+
33
+ Name subagents with context too:
34
+
35
+ - Scout: `"🔍 Scout"` (default is fine)
36
+ - Spec: `"📝 Spec"`
37
+ - Planner: `"💬 Planner"`
38
+ - Workers: `"🔨 Worker 1/3"`, `"🔨 Worker 2/3"`, etc.
39
+ - Reviewer: `"🔎 Reviewer"`
40
+
41
+ ---
42
+
43
+ ## The Flow
44
+
45
+ ```
46
+ Phase 1: Quick Assessment (main session — 30s orientation)
47
+
48
+ Phase 2: Scout (autonomous — deep codebase context)
49
+
50
+ Phase 3: Spawn Spec Agent (interactive — clarifies WHAT, has scout context)
51
+
52
+ Phase 4: Spawn Planner Agent (interactive — figures out HOW, has scout context)
53
+
54
+ Optional: Re-scout if spec/planner significantly changed scope
55
+
56
+ Phase 5: Review Plan & Todos (main session)
57
+
58
+ Phase 6: Execute Todos (workers — receive plan + scout context)
59
+
60
+ Phase 7: Review
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Phase 1: Quick Assessment
66
+
67
+ Quick orientation — just enough to give the scout a focused mission:
68
+
69
+ ```bash
70
+ ls -la
71
+ find . -type f -name "*.ts" | head -20 # or relevant extension
72
+ cat package.json 2>/dev/null | head -30
73
+ ```
74
+
75
+ Spend ~30 seconds. You're looking for: tech stack, project shape, and the area relevant to the user's request. This tells you what to ask the scout to focus on.
76
+
77
+ ---
78
+
79
+ ## Phase 2: Scout
80
+
81
+ **Always spawn a scout before spec/planner.** The scout's context feeds into both — it helps the spec agent ask better questions and helps the planner make better design decisions.
82
+
83
+ ```typescript
84
+ subagent({
85
+ name: "🔍 Scout",
86
+ agent: "scout",
87
+ task: "Analyze the codebase for [user's request area]. Map file structure, key modules, patterns, conventions, and existing code related to [feature area]. Focus on what a spec agent and planner would need to understand.",
88
+ });
89
+ ```
90
+
91
+ **Wait for the scout to finish.** Read the scout's context artifact — you'll pass it to both spec and planner.
92
+
93
+ ---
94
+
95
+ ## Phase 3: Spawn Spec Agent
96
+
97
+ Spawn the interactive spec agent with the scout's context. The `spec` agent clarifies intent, requirements, effort level, and success criteria (ISC) with the user.
98
+
99
+ ```typescript
100
+ subagent({
101
+ name: "📝 Spec",
102
+ agent: "spec",
103
+ interactive: true,
104
+ task: `Define spec: [what the user wants to build]
105
+
106
+ Scout context:
107
+ [paste scout findings here — file structure, conventions, patterns, relevant code]`,
108
+ });
109
+ ```
110
+
111
+ **The user works with the spec agent.** When done, they press Ctrl+D and the spec artifact path is returned.
112
+
113
+ ---
114
+
115
+ ## Phase 4: Spawn Planner Agent
116
+
117
+ Read the spec artifact, then spawn the planner. Pass both the spec AND the scout context. The planner takes these as input and figures out the technical approach — explores options, validates design, runs a premortem, writes the plan, and creates todos with mandatory code examples/references.
118
+
119
+ ```typescript
120
+ // Read the spec first
121
+ read_artifact({ name: "specs/YYYY-MM-DD-<name>.md" });
122
+
123
+ subagent({
124
+ name: "💬 Planner",
125
+ agent: "planner",
126
+ interactive: true,
127
+ task: `Plan implementation for spec: specs/YYYY-MM-DD-<name>.md
128
+
129
+ Scout context:
130
+ [paste scout findings here — same context from Phase 2]`,
131
+ });
132
+ ```
133
+
134
+ **The user works with the planner.** The planner will NOT re-clarify requirements — that's already done in the spec. It focuses on technical approach, design validation, premortem risk analysis, and creating well-scoped todos.
135
+
136
+ When done, the user presses Ctrl+D and the plan + todos are returned.
137
+
138
+ ### Optional: Re-scout after planning
139
+
140
+ If the spec or planner significantly changed scope (e.g. new subsystems, different approach than expected, areas the original scout didn't cover), spawn another scout targeting the new areas:
141
+
142
+ ```typescript
143
+ subagent({
144
+ name: "🔍 Scout (updated scope)",
145
+ agent: "scout",
146
+ task: "The plan changed scope. Gather context for [new areas]. Read the plan at [plan path]. Focus on [specific files/modules the planner identified that weren't in the original scout].",
147
+ });
148
+ ```
149
+
150
+ Fold the new context into the worker tasks.
151
+
152
+ ---
153
+
154
+ ## Phase 5: Review Plan & Todos
155
+
156
+ Once the planner closes, read the plan and todos:
157
+
158
+ ```typescript
159
+ todo({ action: "list" });
160
+ ```
161
+
162
+ Review with the user:
163
+
164
+ > "Here's what the planner produced: [brief summary]. Ready to execute, or anything to adjust?"
165
+
166
+ ---
167
+
168
+ ## Phase 6: Execute Todos
169
+
170
+ Spawn workers sequentially. Each worker gets the plan path and scout context:
171
+
172
+ ```typescript
173
+ // Workers execute todos sequentially — one at a time
174
+ subagent({
175
+ name: "🔨 Worker 1/N",
176
+ agent: "worker",
177
+ task: "Implement TODO-xxxx. Mark the todo as done. Plan: [plan path]\n\nScout context: [paste scout summary from Phase 2, plus any re-scout from Phase 4]",
178
+ });
179
+
180
+ // Check result, then next todo
181
+ subagent({
182
+ name: "🔨 Worker 2/N",
183
+ agent: "worker",
184
+ task: "Implement TODO-yyyy. Mark the todo as done. Plan: [plan path]\n\nScout context: [paste scout summary]",
185
+ });
186
+ ```
187
+
188
+ **Always run workers sequentially in the same git repo** — parallel workers will conflict on commits.
189
+
190
+ ---
191
+
192
+ ## Phase 7: Review
193
+
194
+ After all todos are complete:
195
+
196
+ ```typescript
197
+ subagent({
198
+ name: "Reviewer",
199
+ agent: "reviewer",
200
+ interactive: false,
201
+ task: "Review the recent changes. Plan: [plan path]",
202
+ });
203
+ ```
204
+
205
+ Triage findings:
206
+
207
+ - **P0** — Real bugs, security issues → fix now
208
+ - **P1** — Genuine traps, maintenance dangers → fix before merging
209
+ - **P2** — Minor issues → fix if quick, note otherwise
210
+ - **P3** — Nits → skip
211
+
212
+ Create todos for P0/P1, run workers to fix, re-review only if fixes were substantial.
213
+
214
+ ---
215
+
216
+ ## ⚠️ Completion Checklist
217
+
218
+ Before reporting done:
219
+
220
+ 1. ✅ Scout ran before spec/planner?
221
+ 2. ✅ Scout context was passed to spec and planner?
222
+ 3. ✅ All worker todos closed?
223
+ 4. ✅ Every todo has a polished commit (using the `commit` skill)?
224
+ 5. ✅ Reviewer has run?
225
+ 6. ✅ Reviewer findings triaged and addressed?
@@ -0,0 +1,124 @@
1
+ import { readFileSync, appendFileSync, copyFileSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { join } from "node:path";
4
+
5
+ export interface SessionEntry {
6
+ type: string;
7
+ id: string;
8
+ parentId?: string;
9
+ [key: string]: unknown;
10
+ }
11
+
12
+ export interface MessageEntry extends SessionEntry {
13
+ type: "message";
14
+ message: {
15
+ role: "user" | "assistant" | "toolResult";
16
+ content: Array<{ type: string; text?: string; [key: string]: unknown }>;
17
+ };
18
+ }
19
+
20
+ function readEntries(sessionFile: string): SessionEntry[] {
21
+ const raw = readFileSync(sessionFile, "utf8");
22
+ return raw
23
+ .split("\n")
24
+ .filter((line) => line.trim())
25
+ .map((line) => JSON.parse(line) as SessionEntry);
26
+ }
27
+
28
+ /**
29
+ * Return the id of the last entry in the session file (current branch point / leaf).
30
+ */
31
+ export function getLeafId(sessionFile: string): string | null {
32
+ const entries = readEntries(sessionFile);
33
+ return entries.length > 0 ? entries[entries.length - 1].id : null;
34
+ }
35
+
36
+ /**
37
+ * Return the number of non-empty lines (entries) in the session file.
38
+ */
39
+ export function getEntryCount(sessionFile: string): number {
40
+ const raw = readFileSync(sessionFile, "utf8");
41
+ return raw.split("\n").filter((line) => line.trim()).length;
42
+ }
43
+
44
+ /**
45
+ * Return entries added after `afterLine` (1-indexed count of existing entries).
46
+ */
47
+ export function getNewEntries(sessionFile: string, afterLine: number): SessionEntry[] {
48
+ const raw = readFileSync(sessionFile, "utf8");
49
+ const lines = raw.split("\n").filter((line) => line.trim());
50
+ return lines.slice(afterLine).map((line) => JSON.parse(line) as SessionEntry);
51
+ }
52
+
53
+ /**
54
+ * Find the last assistant message text in a list of entries.
55
+ */
56
+ export function findLastAssistantMessage(entries: SessionEntry[]): string | null {
57
+ for (let i = entries.length - 1; i >= 0; i--) {
58
+ const entry = entries[i];
59
+ if (entry.type !== "message") continue;
60
+ const msg = entry as MessageEntry;
61
+ if (msg.message.role !== "assistant") continue;
62
+
63
+ const texts = msg.message.content
64
+ .filter(
65
+ (block) =>
66
+ block.type === "text" && typeof block.text === "string" && block.text.trim() !== "",
67
+ )
68
+ .map((block) => block.text as string);
69
+
70
+ if (texts.length > 0 && texts.join("").trim()) return texts.join("\n");
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Append a branch_summary entry to the session file.
77
+ * Returns the new entry's id.
78
+ */
79
+ export function appendBranchSummary(
80
+ sessionFile: string,
81
+ branchPointId: string,
82
+ fromId: string | null,
83
+ summary: string,
84
+ ): string {
85
+ const id = randomBytes(4).toString("hex");
86
+ const entry = {
87
+ type: "branch_summary",
88
+ id,
89
+ parentId: branchPointId,
90
+ timestamp: new Date().toISOString(),
91
+ fromId: fromId ?? branchPointId,
92
+ summary,
93
+ };
94
+ appendFileSync(sessionFile, JSON.stringify(entry) + "\n", "utf8");
95
+ return id;
96
+ }
97
+
98
+ /**
99
+ * Copy the session file to destDir for parallel worker isolation.
100
+ * Returns the path of the copy.
101
+ */
102
+ export function copySessionFile(sessionFile: string, destDir: string): string {
103
+ const id = randomBytes(4).toString("hex");
104
+ const dest = join(destDir, `subagent-${id}.jsonl`);
105
+ copyFileSync(sessionFile, dest);
106
+ return dest;
107
+ }
108
+
109
+ /**
110
+ * Read new entries from sourceFile (after afterLine), append them to targetFile.
111
+ * Returns the appended entries.
112
+ */
113
+ export function mergeNewEntries(
114
+ sourceFile: string,
115
+ targetFile: string,
116
+ afterLine: number,
117
+ ): SessionEntry[] {
118
+ const entries = getNewEntries(sourceFile, afterLine);
119
+ for (const entry of entries) {
120
+ appendFileSync(targetFile, JSON.stringify(entry) + "\n", "utf8");
121
+ }
122
+ return entries;
123
+ }
124
+ const unused = "hello";
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Extension loaded into sub-agents.
3
+ * - Shows agent identity + available tools as a styled widget above the editor (toggle with Ctrl+J)
4
+ * - Provides a `subagent_done` tool for autonomous agents to self-terminate
5
+ */
6
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
7
+ import { Box, Text } from "@mariozechner/pi-tui";
8
+ import { Type } from "@sinclair/typebox";
9
+
10
+ export function shouldMarkUserTookOver(agentStarted: boolean): boolean {
11
+ return agentStarted;
12
+ }
13
+
14
+ export function shouldAutoExitOnAgentEnd(
15
+ userTookOver: boolean,
16
+ messages: any[] | undefined,
17
+ ): boolean {
18
+ if (userTookOver) return false;
19
+
20
+ if (messages) {
21
+ for (let i = messages.length - 1; i >= 0; i--) {
22
+ const msg = messages[i];
23
+ if (msg?.role === "assistant") {
24
+ return msg.stopReason !== "aborted";
25
+ }
26
+ }
27
+ }
28
+
29
+ return true;
30
+ }
31
+
32
+ export default function (pi: ExtensionAPI) {
33
+ let toolNames: string[] = [];
34
+ let denied: string[] = [];
35
+ let expanded = false;
36
+
37
+ // Read subagent identity from env vars (set by parent orchestrator)
38
+ const subagentName = process.env.PI_SUBAGENT_NAME ?? "";
39
+ const subagentAgent = process.env.PI_SUBAGENT_AGENT ?? "";
40
+
41
+ function renderWidget(ctx: { ui: { setWidget: Function } }, _theme: any) {
42
+ ctx.ui.setWidget(
43
+ "subagent-tools",
44
+ (_tui: any, theme: any) => {
45
+ const box = new Box(1, 0, (text: string) => theme.bg("toolSuccessBg", text));
46
+
47
+ const label = subagentAgent || subagentName;
48
+ const agentTag = label ? theme.bold(theme.fg("accent", `[${label}]`)) : "";
49
+
50
+ if (expanded) {
51
+ // Expanded: full tool list + denied
52
+ const countInfo = theme.fg("dim", ` — ${toolNames.length} available`);
53
+ const hint = theme.fg("muted", " (Ctrl+J to collapse)");
54
+
55
+ const toolList = toolNames
56
+ .map((name: string) => theme.fg("dim", name))
57
+ .join(theme.fg("muted", ", "));
58
+
59
+ let deniedLine = "";
60
+ if (denied.length > 0) {
61
+ const deniedList = denied
62
+ .map((name: string) => theme.fg("error", name))
63
+ .join(theme.fg("muted", ", "));
64
+ deniedLine = "\n" + theme.fg("muted", "denied: ") + deniedList;
65
+ }
66
+
67
+ const content = new Text(
68
+ `${agentTag}${countInfo}${hint}\n${toolList}${deniedLine}`,
69
+ 0,
70
+ 0,
71
+ );
72
+ box.addChild(content);
73
+ } else {
74
+ // Collapsed: one-line summary
75
+ const countInfo = theme.fg("dim", ` — ${toolNames.length} tools`);
76
+ const deniedInfo =
77
+ denied.length > 0
78
+ ? theme.fg("dim", " · ") + theme.fg("error", `${denied.length} denied`)
79
+ : "";
80
+ const hint = theme.fg("muted", " (Ctrl+J to expand)");
81
+
82
+ const content = new Text(`${agentTag}${countInfo}${deniedInfo}${hint}`, 0, 0);
83
+ box.addChild(content);
84
+ }
85
+
86
+ return box;
87
+ },
88
+ { placement: "aboveEditor" },
89
+ );
90
+ }
91
+
92
+ const autoExit = process.env.PI_SUBAGENT_AUTO_EXIT === "1";
93
+
94
+ // Show widget + status bar on session start
95
+ pi.on("session_start", (_event, ctx) => {
96
+ const tools = pi.getAllTools();
97
+ toolNames = tools.map((t) => t.name).sort();
98
+ denied = (process.env.PI_DENY_TOOLS ?? "")
99
+ .split(",")
100
+ .map((s) => s.trim())
101
+ .filter(Boolean);
102
+
103
+ renderWidget(ctx, null);
104
+ });
105
+
106
+ // Auto-exit: when the agent loop ends, shut down automatically.
107
+ // If the user interrupts (Escape) or sends any input, auto-exit is disabled
108
+ // for that cycle — the user wants to steer. Once they're done and the agent
109
+ // completes normally again, auto-exit re-engages.
110
+ // Enabled via `auto-exit: true` in agent frontmatter.
111
+ if (autoExit) {
112
+ let userTookOver = false;
113
+ let agentStarted = false;
114
+
115
+ pi.on("agent_start", () => {
116
+ agentStarted = true;
117
+ });
118
+
119
+ pi.on("input", () => {
120
+ // Ignore the initial task message that starts an autonomous subagent.
121
+ // Only inputs after the first agent run has started count as user takeover.
122
+ if (!shouldMarkUserTookOver(agentStarted)) return;
123
+ userTookOver = true;
124
+ });
125
+
126
+ pi.on("agent_end", (event, ctx) => {
127
+ const messages = (event as any).messages as any[] | undefined;
128
+ const shouldExit = shouldAutoExitOnAgentEnd(userTookOver, messages);
129
+ if (!shouldExit) {
130
+ // User sent input after the agent had started, or the run was interrupted
131
+ // with Escape. Reset takeover so auto-exit can re-engage on the next
132
+ // normal completion cycle.
133
+ userTookOver = false;
134
+ return;
135
+ }
136
+
137
+ ctx.shutdown();
138
+ });
139
+ }
140
+
141
+ // Toggle expand/collapse with Ctrl+J
142
+ pi.registerShortcut("ctrl+j", {
143
+ description: "Toggle subagent tools widget",
144
+ handler: (ctx) => {
145
+ expanded = !expanded;
146
+ renderWidget(ctx, null);
147
+ },
148
+ });
149
+
150
+ pi.registerTool({
151
+ name: "subagent_done",
152
+ label: "Subagent Done",
153
+ description:
154
+ "Call this tool when you have completed your task. " +
155
+ "It will close this session and return your results to the main session. " +
156
+ "Your LAST assistant message before calling this becomes the summary returned to the caller.",
157
+ parameters: Type.Object({}),
158
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
159
+ ctx.shutdown();
160
+ return {
161
+ content: [{ type: "text", text: "Shutting down subagent session." }],
162
+ details: {},
163
+ };
164
+ },
165
+ });
166
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@johnnygreco/pizza-pi",
3
+ "version": "0.1.1",
4
+ "description": "🍕 Pizza — Pi with toppings",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "keywords": [
8
+ "pi-package"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/johnnygreco/pizza.git"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org/"
17
+ },
18
+ "pi": {
19
+ "extensions": [
20
+ "extensions",
21
+ "node_modules/pi-interactive-subagents"
22
+ ],
23
+ "skills": [
24
+ "skills"
25
+ ],
26
+ "prompts": [
27
+ "prompts"
28
+ ]
29
+ },
30
+ "files": [
31
+ "extensions",
32
+ "skills",
33
+ "prompts"
34
+ ],
35
+ "bundledDependencies": [
36
+ "pi-interactive-subagents"
37
+ ],
38
+ "engines": {
39
+ "node": ">=20.6.0"
40
+ },
41
+ "scripts": {
42
+ "prepack": "rm -rf node_modules/pi-interactive-subagents/test",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "typecheck": "tsc --noEmit"
46
+ },
47
+ "dependencies": {
48
+ "pi-interactive-subagents": "github:HazAT/pi-interactive-subagents#bf4fb961c14567c949e010dca5ec01590b08289a"
49
+ },
50
+ "peerDependencies": {
51
+ "@mariozechner/pi-coding-agent": "~0.66.1",
52
+ "@mariozechner/pi-ai": "~0.66.1",
53
+ "@mariozechner/pi-tui": "~0.66.1",
54
+ "@sinclair/typebox": "~0.34.49"
55
+ },
56
+ "devDependencies": {
57
+ "@mariozechner/pi-coding-agent": "0.66.1",
58
+ "@types/node": "^22.0.0",
59
+ "typescript": "^5.7.0",
60
+ "vitest": "^3.0.0"
61
+ }
62
+ }
File without changes
File without changes