@melihmucuk/pi-crew 1.0.17 → 1.0.19

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 (35) hide show
  1. package/agents/code-reviewer.md +16 -11
  2. package/agents/quality-reviewer.md +8 -17
  3. package/extension/catalog.ts +543 -0
  4. package/extension/crew.ts +377 -0
  5. package/extension/index.ts +35 -18
  6. package/extension/subagent-session.ts +257 -0
  7. package/extension/tools.ts +323 -0
  8. package/extension/ui.ts +291 -0
  9. package/package.json +6 -6
  10. package/prompts/pi-crew-review.md +25 -16
  11. package/skills/pi-crew/SKILL.md +3 -1
  12. package/extension/agent-catalog.ts +0 -369
  13. package/extension/agent-config-fields.ts +0 -359
  14. package/extension/agent-discovery.ts +0 -123
  15. package/extension/bootstrap-session.ts +0 -131
  16. package/extension/integration/crew-tool-actions.ts +0 -306
  17. package/extension/integration/crew-tool-executor.ts +0 -109
  18. package/extension/integration/register-renderers.ts +0 -77
  19. package/extension/integration/register-tools.ts +0 -47
  20. package/extension/integration/tool-presentation.ts +0 -30
  21. package/extension/integration/tools/crew-abort.ts +0 -56
  22. package/extension/integration/tools/crew-done.ts +0 -27
  23. package/extension/integration/tools/crew-list.ts +0 -36
  24. package/extension/integration/tools/crew-respond.ts +0 -38
  25. package/extension/integration/tools/crew-spawn.ts +0 -46
  26. package/extension/message-delivery-policy.ts +0 -22
  27. package/extension/runtime/crew-runtime.ts +0 -263
  28. package/extension/runtime/overflow-recovery.ts +0 -211
  29. package/extension/runtime/owner-session-coordinator.ts +0 -138
  30. package/extension/runtime/subagent-lifecycle.ts +0 -203
  31. package/extension/runtime/subagent-registry.ts +0 -122
  32. package/extension/runtime/subagent-transitions.ts +0 -100
  33. package/extension/status-widget.ts +0 -107
  34. package/extension/subagent-messages.ts +0 -116
  35. package/extension/tool-registry.ts +0 -19
@@ -0,0 +1,291 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import {
3
+ type ExtensionAPI,
4
+ type ExtensionContext,
5
+ getMarkdownTheme,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Box, Markdown, Text } from "@earendil-works/pi-tui";
8
+ import type { ActiveAgentSummary, CrewRuntime } from "./crew.js";
9
+
10
+ export type SendMessageFn = ExtensionAPI["sendMessage"];
11
+ type Message = Parameters<SendMessageFn>[0];
12
+
13
+ type ToolTheme = Parameters<Exclude<Parameters<ExtensionAPI["registerTool"]>[0]["renderCall"], undefined>>[1];
14
+ export type ToolResult = AgentToolResult<unknown>;
15
+
16
+ export type SubagentStatus = "running" | "waiting" | "done" | "error" | "aborted";
17
+
18
+ export const STATUS_ICON: Record<SubagentStatus, string> = {
19
+ running: "⏳",
20
+ waiting: "⏳",
21
+ done: "✅",
22
+ error: "❌",
23
+ aborted: "⏹️",
24
+ };
25
+
26
+ export const STATUS_LABEL: Record<SubagentStatus, string> = {
27
+ running: "running",
28
+ waiting: "waiting for response",
29
+ done: "done",
30
+ error: "failed",
31
+ aborted: "aborted",
32
+ };
33
+
34
+ export interface SteeringPayload {
35
+ id: string;
36
+ agentName: string;
37
+ sessionFile?: string;
38
+ status: SubagentStatus;
39
+ result?: string;
40
+ error?: string;
41
+ }
42
+
43
+ export interface CrewResultMessageDetails {
44
+ agentId: string;
45
+ agentName: string;
46
+ sessionFile?: string;
47
+ status: SubagentStatus;
48
+ body?: string;
49
+ }
50
+
51
+ export function getCrewResultTitle(details: {
52
+ agentId: string;
53
+ agentName: string;
54
+ status: SubagentStatus;
55
+ }): string {
56
+ return `Subagent '${details.agentName}' (${details.agentId}) ${STATUS_LABEL[details.status]}`;
57
+ }
58
+
59
+ function sendWithDeliveryPolicy(
60
+ message: Message,
61
+ sendMessage: SendMessageFn,
62
+ opts: { isIdle: boolean; triggerTurn: boolean },
63
+ ): void {
64
+ sendMessage(
65
+ message,
66
+ opts.isIdle
67
+ ? { triggerTurn: opts.triggerTurn }
68
+ : { deliverAs: "steer", triggerTurn: opts.triggerTurn },
69
+ );
70
+ }
71
+
72
+ function getSteeringBody(payload: SteeringPayload): string | undefined {
73
+ return (payload.status === "error" || payload.status === "aborted")
74
+ ? (payload.error ?? payload.result)
75
+ : (payload.result ?? payload.error);
76
+ }
77
+
78
+ export function sendSteeringMessage(
79
+ payload: SteeringPayload,
80
+ sendMessage: SendMessageFn,
81
+ opts: { isIdle: boolean; triggerTurn: boolean },
82
+ ): void {
83
+ const body = getSteeringBody(payload);
84
+ const title = getCrewResultTitle({ agentId: payload.id, agentName: payload.agentName, status: payload.status });
85
+ const content = body
86
+ ? `**${STATUS_ICON[payload.status]} ${title}**\n\n${body}`
87
+ : `**${STATUS_ICON[payload.status]} ${title}**`;
88
+
89
+ sendWithDeliveryPolicy(
90
+ {
91
+ customType: "crew-result",
92
+ content,
93
+ display: true,
94
+ details: {
95
+ agentId: payload.id,
96
+ agentName: payload.agentName,
97
+ sessionFile: payload.sessionFile,
98
+ status: payload.status,
99
+ body,
100
+ } satisfies CrewResultMessageDetails,
101
+ },
102
+ sendMessage,
103
+ opts,
104
+ );
105
+ }
106
+
107
+ export function sendCrewListActiveWarning(
108
+ sendMessage: SendMessageFn,
109
+ opts: { isIdle: boolean; triggerTurn: boolean },
110
+ ): void {
111
+ sendWithDeliveryPolicy(
112
+ {
113
+ customType: "crew-list-warning",
114
+ content:
115
+ "⚠ Active subagents detected. Do not poll crew_list for completion — results arrive as steering messages. Continue with unrelated work or end your turn and wait for the steering messages.",
116
+ display: true,
117
+ },
118
+ sendMessage,
119
+ opts,
120
+ );
121
+ }
122
+
123
+ function getStatusColor(status: CrewResultMessageDetails["status"]): "success" | "error" | "warning" | "muted" {
124
+ switch (status) {
125
+ case "done":
126
+ return "success";
127
+ case "error":
128
+ case "aborted":
129
+ return "error";
130
+ case "running":
131
+ case "waiting":
132
+ return "warning";
133
+ default:
134
+ return "muted";
135
+ }
136
+ }
137
+
138
+ type MessageRenderer = Parameters<ExtensionAPI["registerMessageRenderer"]>[1];
139
+ type MessageRendererTheme = Parameters<MessageRenderer>[2];
140
+
141
+ function renderWarningMessage(content: unknown, theme: MessageRendererTheme): Box {
142
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
143
+ box.addChild(new Text(theme.fg("warning", String(content ?? "")), 0, 0));
144
+ return box;
145
+ }
146
+
147
+ export function registerCrewMessageRenderers(pi: ExtensionAPI): void {
148
+ pi.registerMessageRenderer("crew-result", (message, { expanded }, theme) => {
149
+ const details = message.details as CrewResultMessageDetails | undefined;
150
+ const title = details ? getCrewResultTitle(details) : "Subagent update";
151
+ const icon = details
152
+ ? theme.fg(getStatusColor(details.status), STATUS_ICON[details.status])
153
+ : theme.fg("muted", "ℹ");
154
+ const header = `${icon} ${theme.fg("toolTitle", theme.bold(title))}`;
155
+ const body = details?.body ?? (!details && message.content ? String(message.content) : undefined);
156
+
157
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
158
+ box.addChild(new Text(header, 0, 0));
159
+
160
+ if (details?.sessionFile) {
161
+ box.addChild(new Text(theme.fg("muted", `📁 ${details.sessionFile}`), 0, 0));
162
+ }
163
+
164
+ if (body) {
165
+ if (expanded) {
166
+ box.addChild(new Text("", 0, 0));
167
+ box.addChild(new Markdown(body, 0, 0, getMarkdownTheme()));
168
+ } else {
169
+ const lines = body.split("\n");
170
+ const preview = lines.slice(0, 5).join("\n");
171
+ box.addChild(new Text(theme.fg("dim", preview), 0, 0));
172
+ if (lines.length > 5) box.addChild(new Text(theme.fg("muted", "(Ctrl+O to expand)"), 0, 0));
173
+ }
174
+ }
175
+
176
+ return box;
177
+ });
178
+
179
+ pi.registerMessageRenderer("crew-list-warning", (message, _options, theme) => renderWarningMessage(message.content, theme));
180
+ }
181
+
182
+ export function renderCrewCall(theme: ToolTheme, name: string, id: string, preview?: string): Box {
183
+ const box = new Box(1, 1);
184
+ box.addChild(new Text(theme.fg("toolTitle", theme.bold(`${name} `)) + theme.fg("accent", id), 0, 0));
185
+ if (preview) box.addChild(new Text(theme.fg("dim", preview), 0, 0));
186
+ return box;
187
+ }
188
+
189
+ export function renderCrewResult(result: ToolResult, theme: ToolTheme): Text {
190
+ const text = result.content[0];
191
+ const details = result.details as { error?: boolean } | undefined;
192
+ const content = text?.type === "text" && text.text ? text.text : "(no output)";
193
+ return new Text(details?.error ? theme.fg("error", content) : theme.fg("success", content), 0, 0);
194
+ }
195
+
196
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
197
+ const SPINNER_INTERVAL_MS = 80;
198
+
199
+ function formatTokens(tokens: number): string {
200
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
201
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
202
+ return String(tokens);
203
+ }
204
+
205
+ function buildWidgetLine(agent: ActiveAgentSummary, frame: string): string {
206
+ const model = agent.model ?? "…";
207
+ const icon = agent.status === "waiting" ? "⏳" : frame;
208
+ return `${icon} ${agent.id} (${model}) · turn ${agent.turns} · ${formatTokens(agent.contextTokens)} ctx`;
209
+ }
210
+
211
+ interface WidgetState {
212
+ ctx: ExtensionContext;
213
+ text: Text;
214
+ // biome-ignore lint: TUI type from factory param
215
+ tui: any;
216
+ timer: ReturnType<typeof setInterval>;
217
+ frameIndex: number;
218
+ }
219
+
220
+ let widget: WidgetState | undefined;
221
+
222
+ function disposeWidget(state: WidgetState): void {
223
+ clearInterval(state.timer);
224
+ if (widget === state) widget = undefined;
225
+ }
226
+
227
+ function clearWidget(): void {
228
+ const current = widget;
229
+ if (!current) return;
230
+ disposeWidget(current);
231
+ current.ctx.ui.setWidget("crew-status", undefined);
232
+ }
233
+
234
+ function hasRunningAgent(agents: ActiveAgentSummary[]): boolean {
235
+ return agents.some((agent) => agent.status === "running");
236
+ }
237
+
238
+ function syncWidgetText(state: WidgetState, agents: ActiveAgentSummary[]): void {
239
+ const frame = SPINNER_FRAMES[state.frameIndex % SPINNER_FRAMES.length];
240
+ state.text.setText(agents.map((agent) => buildWidgetLine(agent, frame)).join("\n"));
241
+ state.tui.requestRender();
242
+ }
243
+
244
+ export function updateWidget(ctx: ExtensionContext, crew: CrewRuntime): void {
245
+ if (!ctx.hasUI) {
246
+ clearWidget();
247
+ return;
248
+ }
249
+
250
+ const ownerSessionId = ctx.sessionManager.getSessionId();
251
+ const running = crew.getActiveSummariesForOwner(ownerSessionId);
252
+ if (running.length === 0) {
253
+ clearWidget();
254
+ return;
255
+ }
256
+
257
+ if (widget && widget.ctx !== ctx) clearWidget();
258
+ if (widget) {
259
+ syncWidgetText(widget, running);
260
+ return;
261
+ }
262
+
263
+ ctx.ui.setWidget("crew-status", (tui, _theme) => {
264
+ const text = new Text("", 1, 0);
265
+ const state: WidgetState = {
266
+ ctx,
267
+ text,
268
+ tui,
269
+ frameIndex: 0,
270
+ timer: setInterval(() => {
271
+ const agents = crew.getActiveSummariesForOwner(ownerSessionId);
272
+ if (agents.length === 0) {
273
+ clearWidget();
274
+ return;
275
+ }
276
+ if (!hasRunningAgent(agents)) return;
277
+ state.frameIndex++;
278
+ syncWidgetText(state, agents);
279
+ }, SPINNER_INTERVAL_MS),
280
+ };
281
+
282
+ widget = state;
283
+ syncWidgetText(state, running);
284
+
285
+ return Object.assign(text, {
286
+ dispose() {
287
+ disposeWidget(state);
288
+ },
289
+ });
290
+ });
291
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melihmucuk/pi-crew",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "type": "module",
5
5
  "description": "Non-blocking subagent orchestration for pi coding agent",
6
6
  "files": [
@@ -43,13 +43,13 @@
43
43
  "typebox": "*"
44
44
  },
45
45
  "devDependencies": {
46
- "@earendil-works/pi-agent-core": "^0.75.4",
47
- "@earendil-works/pi-ai": "^0.75.4",
48
- "@earendil-works/pi-coding-agent": "^0.75.4",
49
- "@earendil-works/pi-tui": "^0.75.4",
46
+ "@earendil-works/pi-agent-core": "^0.76.0",
47
+ "@earendil-works/pi-ai": "^0.76.0",
48
+ "@earendil-works/pi-coding-agent": "^0.76.0",
49
+ "@earendil-works/pi-tui": "^0.76.0",
50
50
  "@types/node": "^22.19.17",
51
51
  "tsx": "^4.22.3",
52
- "typebox": "^1.1.38",
52
+ "typebox": "^1.1.39",
53
53
  "typescript": "^5.9.3"
54
54
  }
55
55
  }
@@ -10,7 +10,7 @@ You are a review orchestrator, not a reviewer. Resolve the review scope, gather
10
10
 
11
11
  ## Scope
12
12
 
13
- Use the user's scope when provided. Otherwise rely on each reviewer’s default scope. If “latest” or “recent” is requested, review the last 5 commits unless a count is given.
13
+ Use the user's scope when provided. Otherwise rely on each reviewer’s default scope. If “latest” or “recent” is requested, review the last 5 commits unless a count is given. If “full”, “codebase”, or whole-repo review is requested, treat it as an explicit non-default scope and pass that scope to reviewers.
14
14
 
15
15
  Gather minimal review context: why the changes were made, expected behavior/outcome, feature or bug intent, notable fixes since any prior review, verification already run, and user instructions that are specific to this review.
16
16
 
@@ -33,6 +33,8 @@ If you include a Goal, make it specific to the change intent, not the reviewer r
33
33
 
34
34
  For default reviews, do not include a Scope section or mention uncommitted/current repo changes in the subagent brief unless needed to disambiguate scope. If you need to state task-specific emphasis, use `Review focus:` instead of `Scope:`.
35
35
 
36
+ For full/codebase requests, state that the requested scope is a bounded full-codebase review.
37
+
36
38
  Do not echo the raw user instruction if it is already represented in the intent summary; quote it only when exact wording matters.
37
39
 
38
40
  Do not restate reviewer-role boilerplate implied by the selected reviewer, such as telling `code-reviewer` to find actionable bugs or telling `quality-reviewer` to review maintainability. Do not include default scope, generic non-goals, acceptance criteria, output format, edit permissions, or severity rules unless the user explicitly overrides them.
@@ -49,26 +51,33 @@ You may do a minimal spot-check only when a finding is ambiguous, high-impact, o
49
51
 
50
52
  Reply in the user's language. Apply the gate before merging.
51
53
 
54
+ For each accepted finding, preserve enough detail to act without reading subagent logs:
55
+
56
+ **[SEVERITY] Category: Title**
57
+ Source: `code-reviewer` | `quality-reviewer` | `both`
58
+ File: `path:line`
59
+ Issue: what is wrong
60
+ Evidence: what was verified
61
+ Impact: concrete consequence
62
+ Fix: specific suggested correction
63
+
64
+ Do not forward findings as summaries only. If evidence, location, or fix is missing and cannot be inferred from the reviewer result, omit the finding or report it as insufficiently evidenced.
65
+
52
66
  Sections:
53
67
 
54
- ### Consensus Findings
55
- Issues clearly reported by both reviewers.
68
+ ### Findings
69
+ List all accepted findings in severity order. Use `Source:` to identify `code-reviewer`, `quality-reviewer`, or `both`.
56
70
 
57
- ### Code Review Findings
58
- Accepted findings only from `code-reviewer`.
71
+ If both reviewers report no accepted findings, write only:
59
72
 
60
- ### Quality Review Findings
61
- Accepted findings only from `quality-reviewer`.
73
+ No accepted findings.
62
74
 
63
- ### Final Summary
64
- - Review scope
65
- - Reviewers run and any failures
66
- - Consensus findings count
67
- - Code review findings count
68
- - Quality review findings count
69
- - Overall assessment
75
+ ### Summary
76
+ - Scope: [review scope]
77
+ - Reviewers: [completed reviewers and any failures]
78
+ - Findings: [count by severity]
79
+ - Result: [one-sentence overall assessment]
70
80
 
71
81
  Rules:
72
82
  - Do not repeat overlapping findings.
73
- - Do not present a single-reviewer finding as consensus.
74
- - If both reviewers report no accepted findings, say so clearly.
83
+ - Mark a finding as `Source: both` only when both reviewers clearly reported the same issue.
@@ -35,6 +35,8 @@ Omit sections that would only restate the selected subagent’s role, default sc
35
35
 
36
36
  Include only information that helps this specific subagent do this specific task: intent, expected outcome, relevant decisions, exact errors/output, unusual constraints, and file paths or entry points that genuinely clarify the task. Use short Markdown sections and bullets when they improve scanability, especially for multi-part intent, constraints, observations, requirements, or acceptance criteria; avoid dense paragraphs.
37
37
 
38
+ For repeated workflows, make each spawn brief independent. Do not assume a new subagent knows earlier loop results, owner-session discussion, or what another subagent saw. If prior findings, fixes, decisions, or verification matter, summarize the concrete facts or point to durable artifacts the subagent can inspect. Avoid vague references like “we fixed the first review findings” unless you also state what those findings/fixes were or define the current review target without relying on that history.
39
+
38
40
  Do not restate boilerplate implied by the selected subagent’s role, name, or description. Avoid repeating default scope, output format, edit permissions, or repo guidance. Subagents run in the same cwd as the orchestrator, so do not include mechanical Git state they can inspect themselves, such as full changed-file lists, staged/unstaged/untracked inventories, branch/cwd details, or generic project constraints, unless those details define a non-default scope or prevent ambiguity.
39
41
 
40
42
  If the user points to a plan, spec, issue, design, or doc as task intent, read it when practical and summarize the relevant intent instead of merely passing the path. Prefer explaining why the work matters and what outcome is expected over restating repository state.
@@ -44,7 +46,7 @@ If the user points to a plan, spec, issue, design, or doc as task intent, read i
44
46
  - Wait for subagent results before using them. Never invent or predict results.
45
47
  - Evaluate each result against the task acceptance criteria.
46
48
  - If results conflict, are incomplete, or miss criteria, state that clearly and use a follow-up or new spawn only when needed.
47
- - After spawning, continue only with unrelated work or end the turn.
49
+ - After spawning, do not work on the delegated task; wait for results, continue only with unrelated work, or end the turn.
48
50
 
49
51
  ## Interactive Subagents
50
52