@melihmucuk/pi-crew 1.0.14 → 1.0.16

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 (33) hide show
  1. package/README.md +19 -18
  2. package/agents/code-reviewer.md +31 -153
  3. package/agents/oracle.md +23 -55
  4. package/agents/planner.md +34 -119
  5. package/agents/quality-reviewer.md +42 -168
  6. package/agents/scout.md +19 -35
  7. package/agents/worker.md +27 -66
  8. package/extension/agent-discovery.ts +2 -2
  9. package/extension/bootstrap-session.ts +2 -2
  10. package/extension/index.ts +9 -11
  11. package/extension/integration/register-renderers.ts +2 -2
  12. package/extension/integration/register-tools.ts +1 -1
  13. package/extension/integration/tool-presentation.ts +3 -3
  14. package/extension/integration/tools/crew-abort.ts +5 -0
  15. package/extension/integration/tools/crew-done.ts +4 -0
  16. package/extension/integration/tools/crew-list.ts +4 -3
  17. package/extension/integration/tools/crew-respond.ts +3 -1
  18. package/extension/integration/tools/crew-spawn.ts +72 -73
  19. package/extension/integration/tools/tool-deps.ts +1 -1
  20. package/extension/integration.ts +1 -3
  21. package/extension/runtime/crew-runtime.ts +12 -12
  22. package/extension/runtime/overflow-recovery.ts +1 -1
  23. package/extension/runtime/subagent-registry.ts +2 -9
  24. package/extension/runtime/subagent-state.ts +36 -50
  25. package/extension/status-widget.ts +2 -2
  26. package/extension/subagent-messages.ts +1 -1
  27. package/package.json +15 -12
  28. package/prompts/pi-crew-plan.md +35 -130
  29. package/prompts/pi-crew-review.md +37 -115
  30. package/skills/pi-crew/REFERENCE.md +70 -0
  31. package/skills/pi-crew/SKILL.md +55 -0
  32. package/docs/architecture.md +0 -186
  33. package/extension/integration/register-command.ts +0 -59
@@ -1,88 +1,87 @@
1
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
3
  import { discoverAgents } from "../../agent-discovery.js";
4
4
  import {
5
- renderCrewCall,
6
- renderCrewResult,
7
- toolError,
8
- toolSuccess,
5
+ renderCrewCall,
6
+ renderCrewResult,
7
+ toolError,
8
+ toolSuccess,
9
9
  } from "../tool-presentation.js";
10
10
  import type { CrewToolDeps } from "./tool-deps.js";
11
11
 
12
12
  export function registerCrewSpawnTool({
13
- pi,
14
- crew,
15
- extensionDir,
16
- notifyDiscoveryWarnings,
13
+ pi,
14
+ crew,
15
+ extensionDir,
16
+ notifyDiscoveryWarnings,
17
17
  }: CrewToolDeps): void {
18
- pi.registerTool({
19
- name: "crew_spawn",
20
- label: "Spawn Crew",
21
- description:
22
- "Spawn a non-blocking subagent that runs in an isolated session. The subagent works independently while your session stays interactive. Results are delivered back to your session as steering messages.",
23
- parameters: Type.Object({
24
- subagent: Type.String({ description: "Subagent name from crew_list" }),
25
- task: Type.String({ description: "Task to delegate to the subagent" }),
26
- }),
27
- promptSnippet:
28
- "Spawn a non-blocking subagent. Use crew_list first to see available subagents.",
29
- promptGuidelines: [
30
- "crew_spawn: The subagent runs in isolation with no access to your session. Include file paths, requirements, and known locations directly in the task parameter.",
31
- "crew_spawn: DELEGATE means OWNERSHIP TRANSFER. Once you spawn a subagent for a task, that task is exclusively theirs. If you also work on it, you waste the subagent's effort and create conflicting results. After spawning, work on an UNRELATED task or end your turn.",
32
- "crew_spawn: To avoid duplication, gather only enough context to write a useful task (key files, entry points). Do not pre-investigate the full problem.",
33
- "crew_spawn: Results arrive asynchronously as steering messages. Do not predict or fabricate results. Wait for all crew-result messages before acting on them.",
34
- "crew_spawn: Never use crew_list as a completion polling loop. Results arrive as steering messages. Continue with unrelated work or end your turn and wait for the steering messages.",
35
- "crew_spawn: Interactive subagents stay alive after responding. Use crew_respond to continue or crew_done to close when finished.",
36
- ],
18
+ pi.registerTool({
19
+ name: "crew_spawn",
20
+ label: "Spawn Crew",
21
+ description:
22
+ "Spawn a non-blocking subagent that runs in an isolated session. The subagent works independently while your session stays interactive. Results are delivered back to your session as steering messages.",
23
+ parameters: Type.Object({
24
+ subagent: Type.String({ description: "Subagent name from crew_list" }),
25
+ task: Type.String({ description: "Task to delegate to the subagent" }),
26
+ }),
27
+ promptSnippet:
28
+ "Spawn a non-blocking subagent. Use crew_list first to see available subagents.",
29
+ promptGuidelines: [
30
+ "crew_spawn: Spawn a discovered subagent for one clearly delegated, self-contained task.",
31
+ "crew_spawn: Include only needed context: constraints, relevant files, acceptance criteria, and expected output.",
32
+ "crew_spawn: After spawning, ownership transfers to the subagent; do not work on that task yourself.",
33
+ "crew_spawn: Results arrive as steering messages; do not poll crew_list or fabricate results.",
34
+ "crew_spawn: Use the bundled pi-crew skill for detailed delegation patterns.",
35
+ ],
37
36
 
38
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
39
- const { agents, warnings } = discoverAgents(ctx.cwd);
40
- notifyDiscoveryWarnings(ctx, warnings);
41
- const subagent = agents.find(
42
- (candidate) => candidate.name === params.subagent,
43
- );
37
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
38
+ const { agents, warnings } = discoverAgents(ctx.cwd);
39
+ notifyDiscoveryWarnings(ctx, warnings);
40
+ const subagent = agents.find(
41
+ (candidate) => candidate.name === params.subagent,
42
+ );
44
43
 
45
- if (!subagent) {
46
- const available =
47
- agents.map((candidate) => candidate.name).join(", ") || "none";
48
- return toolError(
49
- `Unknown subagent: "${params.subagent}". Available: ${available}`,
50
- );
51
- }
44
+ if (!subagent) {
45
+ const available =
46
+ agents.map((candidate) => candidate.name).join(", ") || "none";
47
+ return toolError(
48
+ `Unknown subagent: "${params.subagent}". Available: ${available}`,
49
+ );
50
+ }
52
51
 
53
- const ownerSessionId = ctx.sessionManager.getSessionId();
54
- const id = crew.spawn(
55
- subagent,
56
- params.task,
57
- ctx.cwd,
58
- ownerSessionId,
59
- {
60
- model: ctx.model,
61
- modelRegistry: ctx.modelRegistry,
62
- agentDir: getAgentDir(),
63
- parentSessionFile: ctx.sessionManager.getSessionFile(),
64
- onWarning: (msg) => ctx.ui.notify(msg, "warning"),
65
- },
66
- extensionDir,
67
- );
52
+ const ownerSessionId = ctx.sessionManager.getSessionId();
53
+ const id = crew.spawn(
54
+ subagent,
55
+ params.task,
56
+ ctx.cwd,
57
+ ownerSessionId,
58
+ {
59
+ model: ctx.model,
60
+ modelRegistry: ctx.modelRegistry,
61
+ agentDir: getAgentDir(),
62
+ parentSessionFile: ctx.sessionManager.getSessionFile(),
63
+ onWarning: (msg) => ctx.ui.notify(msg, "warning"),
64
+ },
65
+ extensionDir,
66
+ );
68
67
 
69
- return toolSuccess(
70
- `Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`,
71
- { id, agentName: subagent.name, task: params.task },
72
- );
73
- },
68
+ return toolSuccess(
69
+ `Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`,
70
+ { id, agentName: subagent.name, task: params.task },
71
+ );
72
+ },
74
73
 
75
- renderCall(args, theme, _context) {
76
- return renderCrewCall(
77
- theme,
78
- "crew_spawn",
79
- args.subagent || "...",
80
- args.task,
81
- );
82
- },
74
+ renderCall(args, theme, _context) {
75
+ return renderCrewCall(
76
+ theme,
77
+ "crew_spawn",
78
+ args.subagent || "...",
79
+ args.task,
80
+ );
81
+ },
83
82
 
84
- renderResult(result, _options, theme, _context) {
85
- return renderCrewResult(result, theme);
86
- },
87
- });
83
+ renderResult(result, _options, theme, _context) {
84
+ return renderCrewResult(result, theme);
85
+ },
86
+ });
88
87
  }
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
- } from "@mariozechner/pi-coding-agent";
4
+ } from "@earendil-works/pi-coding-agent";
5
5
  import type { AgentDiscoveryWarning } from "../../agent-discovery.js";
6
6
  import type { CrewRuntime } from "../../runtime/crew-runtime.js";
7
7
 
@@ -1,6 +1,5 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { CrewRuntime } from "./runtime/crew-runtime.js";
3
- import { registerCrewCommand } from "./integration/register-command.js";
4
3
  import { registerCrewMessageRenderers } from "./integration/register-renderers.js";
5
4
  import { registerCrewTools } from "./integration/register-tools.js";
6
5
 
@@ -10,6 +9,5 @@ export function registerCrewIntegration(
10
9
  extensionDir: string,
11
10
  ): void {
12
11
  registerCrewTools(pi, crew, extensionDir);
13
- registerCrewCommand(pi, crew);
14
12
  registerCrewMessageRenderers(pi);
15
13
  }
@@ -1,6 +1,6 @@
1
- import type { AgentMessage } from "@mariozechner/pi-agent-core";
2
- import type { Api, AssistantMessage, Model } from "@mariozechner/pi-ai";
3
- import type { AgentSession, ModelRegistry } from "@mariozechner/pi-coding-agent";
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { Api, AssistantMessage, Model } from "@earendil-works/pi-ai";
3
+ import type { AgentSession, ModelRegistry } from "@earendil-works/pi-coding-agent";
4
4
  import type { AgentConfig } from "../agent-discovery.js";
5
5
  import type { BootstrapContext } from "../bootstrap-session.js";
6
6
  import { bootstrapSession } from "../bootstrap-session.js";
@@ -9,7 +9,6 @@ import { type ActiveRuntimeBinding, DeliveryCoordinator } from "./delivery-coord
9
9
  import { runPromptWithOverflowRecovery } from "./overflow-recovery.js";
10
10
  import { SubagentRegistry } from "./subagent-registry.js";
11
11
  import {
12
- type AbortableAgentSummary,
13
12
  type ActiveAgentSummary,
14
13
  type SubagentState,
15
14
  isAbortableStatus,
@@ -17,7 +16,6 @@ import {
17
16
  } from "./subagent-state.js";
18
17
 
19
18
  export type {
20
- AbortableAgentSummary,
21
19
  ActiveAgentSummary,
22
20
  } from "./subagent-state.js";
23
21
 
@@ -403,24 +401,26 @@ class CrewRuntime {
403
401
  }
404
402
 
405
403
  /**
406
- * Abort all running subagents during shutdown cleanup.
404
+ * Abort all abortable subagents during shutdown cleanup.
407
405
  * Called from SIGINT, session_shutdown(reason="quit"), and beforeExit fallback paths.
408
406
  */
409
407
  abortAll(): void {
410
- const allAgents = this.registry.getAllRunning();
408
+ const allAgents = this.registry.getAllAbortable();
411
409
  for (const state of allAgents) {
412
410
  this.abort(state.id, { reason: "Aborted during shutdown" });
413
411
  }
414
412
  }
415
413
 
416
- getAbortableAgents(): AbortableAgentSummary[] {
417
- return this.registry.getAbortableAgents();
418
- }
419
-
420
414
  getActiveSummariesForOwner(ownerSessionId: string): ActiveAgentSummary[] {
421
415
  return this.registry.getActiveSummariesForOwner(ownerSessionId);
422
416
  }
423
417
  }
424
418
 
425
- export const crewRuntime = new CrewRuntime();
419
+ const crewRuntimeKey = Symbol.for("pi-crew.runtime");
420
+ const globalWithCrewRuntime = globalThis as typeof globalThis & Record<
421
+ symbol,
422
+ CrewRuntime | undefined
423
+ >;
424
+
425
+ export const crewRuntime = globalWithCrewRuntime[crewRuntimeKey] ??= new CrewRuntime();
426
426
  export type { CrewRuntime };
@@ -1,4 +1,4 @@
1
- import type { AgentSession, AgentSessionEvent } from "@mariozechner/pi-coding-agent";
1
+ import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  const OVERFLOW_RECOVERY_TIMEOUT_MS = 120_000;
4
4
 
@@ -1,7 +1,6 @@
1
1
  import type { AgentConfig } from "../agent-discovery.js";
2
- import type { AbortableAgentSummary, ActiveAgentSummary, SubagentState } from "./subagent-state.js";
2
+ import type { ActiveAgentSummary, SubagentState } from "./subagent-state.js";
3
3
  import {
4
- buildAbortableAgentSummary,
5
4
  buildActiveAgentSummary,
6
5
  generateId,
7
6
  isAbortableStatus,
@@ -54,12 +53,6 @@ export class SubagentRegistry {
54
53
  return count;
55
54
  }
56
55
 
57
- getAbortableAgents(): AbortableAgentSummary[] {
58
- return Array.from(this.activeAgents.values())
59
- .filter((state) => isAbortableStatus(state.status))
60
- .map(buildAbortableAgentSummary);
61
- }
62
-
63
56
  getActiveSummariesForOwner(ownerSessionId: string): ActiveAgentSummary[] {
64
57
  return Array.from(this.activeAgents.values())
65
58
  .filter(
@@ -77,7 +70,7 @@ export class SubagentRegistry {
77
70
  .map((state) => state.id);
78
71
  }
79
72
 
80
- getAllRunning(): SubagentState[] {
73
+ getAllAbortable(): SubagentState[] {
81
74
  return Array.from(this.activeAgents.values()).filter((state) =>
82
75
  isAbortableStatus(state.status),
83
76
  );
@@ -1,73 +1,59 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
2
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
3
3
  import type { AgentConfig } from "../agent-discovery.js";
4
4
  import type { SubagentStatus } from "../subagent-messages.js";
5
5
 
6
6
  export interface SubagentState {
7
- id: string;
8
- agentConfig: AgentConfig;
9
- task: string;
10
- status: SubagentStatus;
11
- ownerSessionId: string;
12
- session: AgentSession | null;
13
- turns: number;
14
- contextTokens: number;
15
- model: string | undefined;
16
- error?: string;
17
- result?: string;
18
- promptAbortController?: AbortController;
19
- unsubscribe?: () => void;
7
+ id: string;
8
+ agentConfig: AgentConfig;
9
+ task: string;
10
+ status: SubagentStatus;
11
+ ownerSessionId: string;
12
+ session: AgentSession | null;
13
+ turns: number;
14
+ contextTokens: number;
15
+ model: string | undefined;
16
+ error?: string;
17
+ result?: string;
18
+ promptAbortController?: AbortController;
19
+ unsubscribe?: () => void;
20
20
  }
21
21
 
22
22
  export interface ActiveAgentSummary {
23
- id: string;
24
- agentName: string;
25
- status: SubagentStatus;
26
- turns: number;
27
- contextTokens: number;
28
- model: string | undefined;
29
- }
30
-
31
- export interface AbortableAgentSummary {
32
- id: string;
33
- agentName: string;
23
+ id: string;
24
+ agentName: string;
25
+ status: SubagentStatus;
26
+ turns: number;
27
+ contextTokens: number;
28
+ model: string | undefined;
34
29
  }
35
30
 
36
31
  export function generateId(name: string, existingIds: Set<string>): string {
37
- for (let i = 0; i < 10; i++) {
38
- const id = `${name}-${randomBytes(4).toString("hex")}`;
39
- if (!existingIds.has(id)) return id;
40
- }
41
- return `${name}-${randomBytes(8).toString("hex")}`;
32
+ for (let i = 0; i < 10; i++) {
33
+ const id = `${name}-${randomBytes(4).toString("hex")}`;
34
+ if (!existingIds.has(id)) return id;
35
+ }
36
+ return `${name}-${randomBytes(8).toString("hex")}`;
42
37
  }
43
38
 
44
39
  // Status may change externally via abort(). Standalone function avoids TS narrowing.
45
40
  export function isAborted(state: SubagentState): boolean {
46
- return state.status === "aborted";
41
+ return state.status === "aborted";
47
42
  }
48
43
 
49
44
  export function isAbortableStatus(status: SubagentStatus): boolean {
50
- return status === "running" || status === "waiting";
45
+ return status === "running" || status === "waiting";
51
46
  }
52
47
 
53
48
  export function buildActiveAgentSummary(
54
- state: SubagentState,
49
+ state: SubagentState,
55
50
  ): ActiveAgentSummary {
56
- return {
57
- id: state.id,
58
- agentName: state.agentConfig.name,
59
- status: state.status,
60
- turns: state.turns,
61
- contextTokens: state.contextTokens,
62
- model: state.model,
63
- };
64
- }
65
-
66
- export function buildAbortableAgentSummary(
67
- state: SubagentState,
68
- ): AbortableAgentSummary {
69
- return {
70
- id: state.id,
71
- agentName: state.agentConfig.name,
72
- };
51
+ return {
52
+ id: state.id,
53
+ agentName: state.agentConfig.name,
54
+ status: state.status,
55
+ turns: state.turns,
56
+ contextTokens: state.contextTokens,
57
+ model: state.model,
58
+ };
73
59
  }
@@ -1,5 +1,5 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
- import { Text } from "@mariozechner/pi-tui";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { Text } from "@earendil-works/pi-tui";
3
3
  import type { ActiveAgentSummary } from "./runtime/crew-runtime.js";
4
4
  import type { CrewRuntime } from "./runtime/crew-runtime.js";
5
5
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  export type SubagentStatus = "running" | "waiting" | "done" | "error" | "aborted";
4
4
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@melihmucuk/pi-crew",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "type": "module",
5
5
  "description": "Non-blocking subagent orchestration for pi coding agent",
6
6
  "files": [
7
7
  "extension/",
8
8
  "agents/",
9
- "prompts/",
10
- "docs/architecture.md"
9
+ "skills/",
10
+ "prompts/"
11
11
  ],
12
12
  "author": "Melih Mucuk",
13
13
  "license": "MIT",
@@ -23,6 +23,9 @@
23
23
  "extensions": [
24
24
  "./extension/index.ts"
25
25
  ],
26
+ "skills": [
27
+ "./skills"
28
+ ],
26
29
  "prompts": [
27
30
  "./prompts"
28
31
  ],
@@ -32,19 +35,19 @@
32
35
  "typecheck": "tsc --noEmit"
33
36
  },
34
37
  "peerDependencies": {
35
- "@mariozechner/pi-agent-core": "*",
36
- "@mariozechner/pi-ai": "*",
37
- "@mariozechner/pi-coding-agent": "*",
38
- "@mariozechner/pi-tui": "*",
38
+ "@earendil-works/pi-agent-core": "*",
39
+ "@earendil-works/pi-ai": "*",
40
+ "@earendil-works/pi-coding-agent": "*",
41
+ "@earendil-works/pi-tui": "*",
39
42
  "typebox": "*"
40
43
  },
41
44
  "devDependencies": {
42
- "@mariozechner/pi-agent-core": "^0.70.2",
43
- "@mariozechner/pi-ai": "^0.70.2",
44
- "@mariozechner/pi-coding-agent": "^0.70.2",
45
- "@mariozechner/pi-tui": "^0.70.2",
45
+ "@earendil-works/pi-agent-core": "^0.74.0",
46
+ "@earendil-works/pi-ai": "^0.74.0",
47
+ "@earendil-works/pi-coding-agent": "^0.74.0",
48
+ "@earendil-works/pi-tui": "^0.74.0",
46
49
  "@types/node": "^22.19.17",
47
- "typebox": "^1.1.33",
50
+ "typebox": "^1.1.38",
48
51
  "typescript": "^5.9.3"
49
52
  }
50
53
  }