@melihmucuk/pi-crew 1.0.13 → 1.0.15

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 (77) hide show
  1. package/README.md +19 -18
  2. package/agents/code-reviewer.md +52 -104
  3. package/agents/oracle.md +26 -52
  4. package/agents/planner.md +7 -7
  5. package/agents/quality-reviewer.md +90 -131
  6. package/agents/scout.md +3 -2
  7. package/agents/worker.md +8 -2
  8. package/extension/agent-discovery.ts +791 -0
  9. package/extension/bootstrap-session.ts +131 -0
  10. package/extension/index.ts +63 -0
  11. package/extension/integration/register-renderers.ts +77 -0
  12. package/extension/integration/register-tools.ts +39 -0
  13. package/extension/integration/tool-presentation.ts +50 -0
  14. package/extension/integration/tools/crew-abort.ts +126 -0
  15. package/extension/integration/tools/crew-done.ts +46 -0
  16. package/extension/integration/tools/crew-list.ts +92 -0
  17. package/extension/integration/tools/crew-respond.ts +59 -0
  18. package/extension/integration/tools/crew-spawn.ts +87 -0
  19. package/extension/integration/tools/tool-deps.ts +16 -0
  20. package/extension/integration.ts +13 -0
  21. package/extension/runtime/crew-runtime.ts +426 -0
  22. package/extension/runtime/delivery-coordinator.ts +131 -0
  23. package/extension/runtime/overflow-recovery.ts +211 -0
  24. package/extension/runtime/subagent-registry.ts +78 -0
  25. package/extension/runtime/subagent-state.ts +59 -0
  26. package/extension/status-widget.ts +107 -0
  27. package/extension/subagent-messages.ts +124 -0
  28. package/extension/tool-registry.ts +19 -0
  29. package/package.json +14 -14
  30. package/prompts/pi-crew-plan.md +46 -37
  31. package/prompts/pi-crew-review.md +3 -1
  32. package/skills/pi-crew/SKILL.md +129 -0
  33. package/dist/agent-discovery.d.ts +0 -29
  34. package/dist/agent-discovery.js +0 -527
  35. package/dist/bootstrap-session.d.ts +0 -21
  36. package/dist/bootstrap-session.js +0 -74
  37. package/dist/index.d.ts +0 -2
  38. package/dist/index.js +0 -46
  39. package/dist/integration/register-command.d.ts +0 -3
  40. package/dist/integration/register-command.js +0 -51
  41. package/dist/integration/register-renderers.d.ts +0 -2
  42. package/dist/integration/register-renderers.js +0 -59
  43. package/dist/integration/register-tools.d.ts +0 -3
  44. package/dist/integration/register-tools.js +0 -25
  45. package/dist/integration/tool-presentation.d.ts +0 -27
  46. package/dist/integration/tool-presentation.js +0 -29
  47. package/dist/integration/tools/crew-abort.d.ts +0 -2
  48. package/dist/integration/tools/crew-abort.js +0 -79
  49. package/dist/integration/tools/crew-done.d.ts +0 -2
  50. package/dist/integration/tools/crew-done.js +0 -28
  51. package/dist/integration/tools/crew-list.d.ts +0 -2
  52. package/dist/integration/tools/crew-list.js +0 -74
  53. package/dist/integration/tools/crew-respond.d.ts +0 -2
  54. package/dist/integration/tools/crew-respond.js +0 -32
  55. package/dist/integration/tools/crew-spawn.d.ts +0 -2
  56. package/dist/integration/tools/crew-spawn.js +0 -48
  57. package/dist/integration/tools/tool-deps.d.ts +0 -9
  58. package/dist/integration/tools/tool-deps.js +0 -1
  59. package/dist/integration.d.ts +0 -3
  60. package/dist/integration.js +0 -8
  61. package/dist/runtime/crew-runtime.d.ts +0 -62
  62. package/dist/runtime/crew-runtime.js +0 -285
  63. package/dist/runtime/delivery-coordinator.d.ts +0 -26
  64. package/dist/runtime/delivery-coordinator.js +0 -86
  65. package/dist/runtime/overflow-recovery.d.ts +0 -3
  66. package/dist/runtime/overflow-recovery.js +0 -155
  67. package/dist/runtime/subagent-registry.d.ts +0 -14
  68. package/dist/runtime/subagent-registry.js +0 -58
  69. package/dist/runtime/subagent-state.d.ts +0 -35
  70. package/dist/runtime/subagent-state.js +0 -32
  71. package/dist/status-widget.d.ts +0 -3
  72. package/dist/status-widget.js +0 -84
  73. package/dist/subagent-messages.d.ts +0 -37
  74. package/dist/subagent-messages.js +0 -68
  75. package/dist/tool-registry.d.ts +0 -5
  76. package/dist/tool-registry.js +0 -13
  77. package/docs/architecture.md +0 -187
@@ -1,32 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- export function generateId(name, existingIds) {
3
- for (let i = 0; i < 10; i++) {
4
- const id = `${name}-${randomBytes(4).toString("hex")}`;
5
- if (!existingIds.has(id))
6
- return id;
7
- }
8
- return `${name}-${randomBytes(8).toString("hex")}`;
9
- }
10
- // Status may change externally via abort(). Standalone function avoids TS narrowing.
11
- export function isAborted(state) {
12
- return state.status === "aborted";
13
- }
14
- export function isAbortableStatus(status) {
15
- return status === "running" || status === "waiting";
16
- }
17
- export function buildActiveAgentSummary(state) {
18
- return {
19
- id: state.id,
20
- agentName: state.agentConfig.name,
21
- status: state.status,
22
- turns: state.turns,
23
- contextTokens: state.contextTokens,
24
- model: state.model,
25
- };
26
- }
27
- export function buildAbortableAgentSummary(state) {
28
- return {
29
- id: state.id,
30
- agentName: state.agentConfig.name,
31
- };
32
- }
@@ -1,3 +0,0 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
- import type { CrewRuntime } from "./runtime/crew-runtime.js";
3
- export declare function updateWidget(ctx: ExtensionContext, crew: CrewRuntime): void;
@@ -1,84 +0,0 @@
1
- import { Text } from "@mariozechner/pi-tui";
2
- const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
3
- const SPINNER_INTERVAL_MS = 80;
4
- function formatTokens(tokens) {
5
- if (tokens >= 1_000_000)
6
- return `${(tokens / 1_000_000).toFixed(1)}M`;
7
- if (tokens >= 1_000)
8
- return `${(tokens / 1_000).toFixed(1)}k`;
9
- return String(tokens);
10
- }
11
- function buildLine(agent, frame) {
12
- const model = agent.model ?? "…";
13
- const icon = agent.status === "waiting" ? "⏳" : frame;
14
- return `${icon} ${agent.id} (${model}) · turn ${agent.turns} · ${formatTokens(agent.contextTokens)} ctx`;
15
- }
16
- let widget;
17
- function disposeWidget(state) {
18
- clearInterval(state.timer);
19
- if (widget === state) {
20
- widget = undefined;
21
- }
22
- }
23
- function clearWidget() {
24
- const current = widget;
25
- if (!current)
26
- return;
27
- disposeWidget(current);
28
- current.ctx.ui.setWidget("crew-status", undefined);
29
- }
30
- function hasRunningAgent(agents) {
31
- return agents.some((agent) => agent.status === "running");
32
- }
33
- function syncWidgetText(state, agents) {
34
- const frame = SPINNER_FRAMES[state.frameIndex % SPINNER_FRAMES.length];
35
- const lines = agents.map((agent) => buildLine(agent, frame));
36
- state.text.setText(lines.join("\n"));
37
- state.tui.requestRender();
38
- }
39
- export function updateWidget(ctx, crew) {
40
- if (!ctx.hasUI) {
41
- clearWidget();
42
- return;
43
- }
44
- const ownerSessionId = ctx.sessionManager.getSessionId();
45
- const running = crew.getActiveSummariesForOwner(ownerSessionId);
46
- if (running.length === 0) {
47
- clearWidget();
48
- return;
49
- }
50
- if (widget && widget.ctx !== ctx) {
51
- clearWidget();
52
- }
53
- if (widget) {
54
- syncWidgetText(widget, running);
55
- return;
56
- }
57
- ctx.ui.setWidget("crew-status", (tui, _theme) => {
58
- const text = new Text("", 1, 0);
59
- const state = {
60
- ctx,
61
- text,
62
- tui,
63
- frameIndex: 0,
64
- timer: setInterval(() => {
65
- const agents = crew.getActiveSummariesForOwner(ownerSessionId);
66
- if (agents.length === 0) {
67
- clearWidget();
68
- return;
69
- }
70
- if (!hasRunningAgent(agents))
71
- return;
72
- state.frameIndex++;
73
- syncWidgetText(state, agents);
74
- }, SPINNER_INTERVAL_MS),
75
- };
76
- widget = state;
77
- syncWidgetText(state, running);
78
- return Object.assign(text, {
79
- dispose() {
80
- disposeWidget(state);
81
- },
82
- });
83
- });
84
- }
@@ -1,37 +0,0 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- export type SubagentStatus = "running" | "waiting" | "done" | "error" | "aborted";
3
- export type SendMessageFn = ExtensionAPI["sendMessage"];
4
- export declare const STATUS_ICON: Record<SubagentStatus, string>;
5
- export declare const STATUS_LABEL: Record<SubagentStatus, string>;
6
- export interface SteeringPayload {
7
- id: string;
8
- agentName: string;
9
- sessionFile?: string;
10
- status: SubagentStatus;
11
- result?: string;
12
- error?: string;
13
- }
14
- export interface CrewResultMessageDetails {
15
- agentId: string;
16
- agentName: string;
17
- sessionFile?: string;
18
- status: SubagentStatus;
19
- body?: string;
20
- }
21
- export declare function getCrewResultTitle(details: {
22
- agentId: string;
23
- agentName: string;
24
- status: SubagentStatus;
25
- }): string;
26
- export declare function sendSteeringMessage(payload: SteeringPayload, sendMessage: SendMessageFn, opts: {
27
- isIdle: boolean;
28
- triggerTurn: boolean;
29
- }): void;
30
- export declare function sendRemainingNote(remainingCount: number, sendMessage: SendMessageFn, opts: {
31
- isIdle: boolean;
32
- triggerTurn: boolean;
33
- }): void;
34
- export declare function sendCrewListActiveWarning(sendMessage: SendMessageFn, opts: {
35
- isIdle: boolean;
36
- triggerTurn: boolean;
37
- }): void;
@@ -1,68 +0,0 @@
1
- export const STATUS_ICON = {
2
- running: "⏳",
3
- waiting: "⏳",
4
- done: "✅",
5
- error: "❌",
6
- aborted: "⏹️",
7
- };
8
- export const STATUS_LABEL = {
9
- running: "running",
10
- waiting: "waiting for response",
11
- done: "done",
12
- error: "failed",
13
- aborted: "aborted",
14
- };
15
- export function getCrewResultTitle(details) {
16
- return `Subagent '${details.agentName}' (${details.agentId}) ${STATUS_LABEL[details.status]}`;
17
- }
18
- function getSteeringBody(payload) {
19
- return (payload.status === "error" || payload.status === "aborted")
20
- ? (payload.error ?? payload.result)
21
- : (payload.result ?? payload.error);
22
- }
23
- export function sendSteeringMessage(payload, sendMessage, opts) {
24
- const body = getSteeringBody(payload);
25
- const title = getCrewResultTitle({
26
- agentId: payload.id,
27
- agentName: payload.agentName,
28
- status: payload.status,
29
- });
30
- const content = body
31
- ? `**${STATUS_ICON[payload.status]} ${title}**\n\n${body}`
32
- : `**${STATUS_ICON[payload.status]} ${title}**`;
33
- const message = {
34
- customType: "crew-result",
35
- content,
36
- display: true,
37
- details: {
38
- agentId: payload.id,
39
- agentName: payload.agentName,
40
- sessionFile: payload.sessionFile,
41
- status: payload.status,
42
- body,
43
- },
44
- };
45
- sendMessage(message, opts.isIdle
46
- ? { triggerTurn: opts.triggerTurn }
47
- : { deliverAs: "steer", triggerTurn: opts.triggerTurn });
48
- }
49
- export function sendRemainingNote(remainingCount, sendMessage, opts) {
50
- if (remainingCount <= 0)
51
- return;
52
- sendMessage({
53
- customType: "crew-remaining",
54
- content: `⏳ ${remainingCount} subagent(s) still running`,
55
- display: true,
56
- }, opts.isIdle
57
- ? { triggerTurn: opts.triggerTurn }
58
- : { deliverAs: "steer", triggerTurn: opts.triggerTurn });
59
- }
60
- export function sendCrewListActiveWarning(sendMessage, opts) {
61
- sendMessage({
62
- customType: "crew-list-warning",
63
- content: "⚠ 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.",
64
- display: true,
65
- }, opts.isIdle
66
- ? { triggerTurn: opts.triggerTurn }
67
- : { deliverAs: "steer", triggerTurn: opts.triggerTurn });
68
- }
@@ -1,5 +0,0 @@
1
- declare const SUPPORTED_TOOL_NAMES_LITERAL: readonly ["read", "bash", "edit", "write", "grep", "find", "ls"];
2
- export type SupportedToolName = (typeof SUPPORTED_TOOL_NAMES_LITERAL)[number];
3
- export declare const SUPPORTED_TOOL_NAMES: readonly ("read" | "bash" | "edit" | "write" | "grep" | "find" | "ls")[];
4
- export declare function isSupportedToolName(name: string): name is SupportedToolName;
5
- export {};
@@ -1,13 +0,0 @@
1
- const SUPPORTED_TOOL_NAMES_LITERAL = [
2
- "read",
3
- "bash",
4
- "edit",
5
- "write",
6
- "grep",
7
- "find",
8
- "ls",
9
- ];
10
- export const SUPPORTED_TOOL_NAMES = Object.freeze([...SUPPORTED_TOOL_NAMES_LITERAL]);
11
- export function isSupportedToolName(name) {
12
- return SUPPORTED_TOOL_NAMES.includes(name);
13
- }
@@ -1,187 +0,0 @@
1
- # pi-crew Architecture
2
-
3
- This document explains the technical architecture of `@melihmucuk/pi-crew`, focusing on what makes this extension unique.
4
-
5
- For pi fundamentals, see pi docs: `extensions.md`, `sdk.md`, `session.md`. Project-level guardrails are in `AGENTS.md`.
6
-
7
- ## 1. What pi-crew adds
8
-
9
- `pi-crew` is a non-blocking subagent orchestration extension. It lets one pi session delegate work to isolated subagent sessions without blocking the caller. Results are delivered back as `crew-result` custom messages.
10
-
11
- Primary components:
12
-
13
- - `extension/runtime/crew-runtime.ts` - Process-level singleton owning all subagent state
14
- - `extension/runtime/subagent-registry.ts` - In-memory subagent registry
15
- - `extension/runtime/delivery-coordinator.ts` - Owner-based result routing
16
- - `extension/runtime/overflow-recovery.ts` - Context overflow retry tracking for subagent prompts
17
- - `extension/bootstrap-session.ts` - Subagent session construction with extension filtering
18
- - `extension/agent-discovery.ts` - Subagent definition discovery and validation
19
-
20
- ## 2. Core runtime components
21
-
22
- ### 2.1 CrewRuntime singleton
23
-
24
- `CrewRuntime` is a process-level singleton that survives pi runtime replacement (`/resume`, `/new`, `/fork`, `/reload`). When pi discards an old extension instance and creates a new one, the new instance reconnects to the same `crewRuntime` and picks up existing subagent state.
25
-
26
- Responsibilities:
27
-
28
- - Create subagent state records
29
- - Bootstrap isolated subagent sessions
30
- - Run subagent prompt cycles with overflow recovery
31
- - Transition subagents between states
32
- - Deliver results to owner sessions
33
-
34
- ### 2.2 Delivery coordinator
35
-
36
- Routes subagent results to the correct session at the correct time. Key behaviors:
37
-
38
- - Tracks active session via `ActiveRuntimeBinding` (set on `session_start`, cleared on `session_shutdown`)
39
- - Queues results when owner session is inactive
40
- - Flushes queued results when owner session activates on any `session_start`; resume/fork are the important replacement paths because subagents survive runtime replacement within the same process
41
- - Uses `triggerTurn: false/true` split to preserve ordering between `crew-result` and `crew-remaining`
42
-
43
- Underlying delivery: see pi's `sendMessage({ deliverAs, triggerTurn })` in extensions.md.
44
-
45
- `crew_list` uses the same idle/streaming delivery rules for its `crew-list-warning` custom message when active subagents exist. The warning is separate from tool output so the list remains a one-time snapshot while anti-polling guidance is delivered as a visible message.
46
-
47
- ### 2.3 Overflow recovery
48
-
49
- Subagent prompt cycles are wrapped by overflow recovery tracking. The tracker observes `agent_end`, `compaction_start`, `compaction_end`, `auto_retry_start`, and `auto_retry_end` events to distinguish normal completion from context-overflow compaction and retry.
50
-
51
- Outcomes:
52
-
53
- - No overflow observed → prompt outcome is based on the final assistant message.
54
- - Overflow compaction completes with retry and the retry reaches a terminal `agent_end` → recovered; prompt outcome is based on the final assistant message.
55
- - Overflow handling times out, is cancelled, or compaction does not retry → failed; the subagent settles as `error` unless the final assistant message already reported an error.
56
-
57
- ### 2.4 Subagent registry
58
-
59
- In-memory, process-scoped: `Map<subagentId, SubagentState>`
60
-
61
- - Owner session filtering
62
- - Runtime ID generation (`<name>-<hex>`)
63
-
64
- Does not persist across process restarts. Subagent session files remain for post-hoc inspection.
65
-
66
- ## 3. Session bootstrapping
67
-
68
- When `crew_spawn` executes:
69
-
70
- 1. Resolve subagent definition from discovery sources
71
- 2. Resolve model (fallback to caller session model if invalid)
72
- 3. Resolve tools, skills
73
- 4. Create `DefaultResourceLoader` with `extensionsOverride` that excludes `pi-crew`
74
- 5. Call `sessionManager.newSession({ parentSession })` for parent-child linkage
75
- 6. Create `AgentSession` with resolved configuration
76
- 7. Send task prompt asynchronously
77
-
78
- **Extension filtering:** Subagent sessions must not load `pi-crew` again. Prevents recursive orchestration loops.
79
-
80
- ## 4. Delivery model
81
-
82
- ### 4.1 Owner-based routing
83
-
84
- Results belong to the session that spawned the subagent. Owner identity uses `getSessionId()`, not file path (in-memory sessions have undefined paths).
85
-
86
- ### 4.2 Idle vs streaming
87
-
88
- Check owner session state before delivery:
89
-
90
- - **Idle (`isIdle() = true`):** Send with `triggerTurn: true`
91
- - **Streaming (`isIdle() = false`):** Send with `deliverAs: "steer"` and `triggerTurn: true`
92
-
93
- Critical: `deliverAs: "steer"` to an idle session leaves the message unprocessed (no active turn loop).
94
-
95
- ### 4.3 Deferred flush
96
-
97
- Pending message flush after `session_start` is deferred to next macrotask. Synchronous delivery loses custom message persistence (pi-core emits `session_start` before reconnecting agent listener during resume). While a flush is scheduled, new deliveries for that owner are queued so ordering is preserved.
98
-
99
- ### 4.4 TTL cleanup
100
-
101
- Pending messages older than 24 hours are discarded during `flushPending`.
102
-
103
- ## 5. Subagent state lifecycle
104
-
105
- ### 5.1 States
106
-
107
- - `running` - Actively processing
108
- - `waiting` - Interactive subagent awaiting `crew_respond` or `crew_done`
109
- - `done` - Completed successfully
110
- - `error` - Failed with error
111
- - `aborted` - Cancelled
112
-
113
- ### 5.2 State transitions
114
-
115
- After prompt cycle completion, inspect assistant stop reason:
116
-
117
- - `stopReason: "error"` → status `error`
118
- - `stopReason: "aborted"` → status `aborted`
119
- - Normal completion + `interactive: true` → status `waiting`
120
- - Normal completion + non-interactive → status `done`
121
-
122
- ### 5.3 Interactive subagents
123
-
124
- `interactive: true` subagents enter `waiting` after each response. They accept follow-up messages via `crew_respond` until explicitly closed with `crew_done`. Closing does NOT emit a duplicate `crew-result`.
125
-
126
- ### 5.4 Tool completion behavior
127
-
128
- `crew_respond` returns immediately and delivers the subagent response asynchronously. Successful `crew_abort` results terminate the current tool turn after aborting owned subagents.
129
-
130
- ## 6. Ownership and isolation
131
-
132
- Invariants:
133
-
134
- 1. `crew_list`, `crew_abort`, `crew_respond`, `crew_done`, status widget: session-scoped. Only owner sees/controls.
135
- 2. `/pi-crew-abort`: cross-session emergency escape hatch.
136
- 3. `session_shutdown` always deactivates delivery binding. On replacement paths (`reload`, `new`, `resume`, `fork`), subagents continue running. On `quit`, the extension aborts all running subagents. `SIGINT` also aborts via a process hook, and `beforeExit` remains a fallback.
137
-
138
- ## 7. Subagent definition model
139
-
140
- Discovery priority:
141
-
142
- 1. Project: `<cwd>/.pi/agents/*.md`
143
- 2. User global: `~/.pi/agent/agents/*.md`
144
- 3. Bundled: `agents/` in package
145
-
146
- Higher priority wins. Same-name duplicates in same directory produce warning.
147
-
148
- Frontmatter: `name`, `description`, `model`, `thinking`, `tools`, `skills`, `compaction`, `interactive`
149
-
150
- Tools/skills semantics:
151
-
152
- - **Omitted:** Use full supported allowlist
153
- - **Empty list (`tools: []`):** Grant none
154
-
155
- JSON overrides: `~/.pi/agent/pi-crew.json` (global), `<cwd>/.pi/pi-crew.json` (project). Project wins.
156
-
157
- ## 8. Behavioral invariants
158
-
159
- 1. Spawned subagent must not block caller session.
160
- 2. Results route to owning session (by ID), not currently active session.
161
- 3. Subagent sessions must not load `pi-crew`.
162
- 4. `crew_respond` returns immediately; result delivered asynchronously.
163
- 5. `crew_done` cleans up only; no duplicate result message.
164
- 6. Queued results flush when owner session becomes active.
165
- 7. `crew-result` messages appear before `crew-remaining` notes (ordering via `triggerTurn` split).
166
- 8. `crew-list-warning` is delivered as a separate custom message when `crew_list` is called while the owner has active subagents.
167
- 9. Pending messages preserved for inactive sessions; TTL (24h) prevents memory leak.
168
- 10. Active subagent state survives runtime replacement within same process.
169
- 11. Graceful quit aborts subagents through `session_shutdown.reason === "quit"`; replacement paths do not.
170
-
171
- ## 9. Reading guide
172
-
173
- 1. `README.md` - Product surface
174
- 2. `AGENTS.md` - Architecture guardrails
175
- 3. `extension/index.ts` - Session event wiring
176
- 4. `extension/runtime/crew-runtime.ts` - Orchestration, state transitions
177
- 5. `extension/runtime/delivery-coordinator.ts` - Owner routing, queueing
178
- 6. `extension/bootstrap-session.ts` - Session construction
179
- 7. `extension/agent-discovery.ts` - Definition validation
180
- 8. `extension/integration/` - Tools, command, renderers
181
-
182
- ## 10. Verification
183
-
184
- ```bash
185
- npm run typecheck
186
- npm run build
187
- ```