@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.
- package/README.md +19 -18
- package/agents/code-reviewer.md +31 -153
- package/agents/oracle.md +23 -55
- package/agents/planner.md +34 -119
- package/agents/quality-reviewer.md +42 -168
- package/agents/scout.md +19 -35
- package/agents/worker.md +27 -66
- package/extension/agent-discovery.ts +2 -2
- package/extension/bootstrap-session.ts +2 -2
- package/extension/index.ts +9 -11
- package/extension/integration/register-renderers.ts +2 -2
- package/extension/integration/register-tools.ts +1 -1
- package/extension/integration/tool-presentation.ts +3 -3
- package/extension/integration/tools/crew-abort.ts +5 -0
- package/extension/integration/tools/crew-done.ts +4 -0
- package/extension/integration/tools/crew-list.ts +4 -3
- package/extension/integration/tools/crew-respond.ts +3 -1
- package/extension/integration/tools/crew-spawn.ts +72 -73
- package/extension/integration/tools/tool-deps.ts +1 -1
- package/extension/integration.ts +1 -3
- package/extension/runtime/crew-runtime.ts +12 -12
- package/extension/runtime/overflow-recovery.ts +1 -1
- package/extension/runtime/subagent-registry.ts +2 -9
- package/extension/runtime/subagent-state.ts +36 -50
- package/extension/status-widget.ts +2 -2
- package/extension/subagent-messages.ts +1 -1
- package/package.json +15 -12
- package/prompts/pi-crew-plan.md +35 -130
- package/prompts/pi-crew-review.md +37 -115
- package/skills/pi-crew/REFERENCE.md +70 -0
- package/skills/pi-crew/SKILL.md +55 -0
- package/docs/architecture.md +0 -186
- package/extension/integration/register-command.ts +0 -59
|
@@ -1,88 +1,87 @@
|
|
|
1
|
-
import { getAgentDir } from "@
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
pi,
|
|
14
|
+
crew,
|
|
15
|
+
extensionDir,
|
|
16
|
+
notifyDiscoveryWarnings,
|
|
17
17
|
}: CrewToolDeps): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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 "@
|
|
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
|
|
package/extension/integration.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
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 "@
|
|
2
|
-
import type { Api, AssistantMessage, Model } from "@
|
|
3
|
-
import type { AgentSession, ModelRegistry } from "@
|
|
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
|
|
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.
|
|
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
|
-
|
|
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,7 +1,6 @@
|
|
|
1
1
|
import type { AgentConfig } from "../agent-discovery.js";
|
|
2
|
-
import type {
|
|
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
|
-
|
|
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 "@
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
41
|
+
return state.status === "aborted";
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
export function isAbortableStatus(status: SubagentStatus): boolean {
|
|
50
|
-
|
|
45
|
+
return status === "running" || status === "waiting";
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
export function buildActiveAgentSummary(
|
|
54
|
-
|
|
49
|
+
state: SubagentState,
|
|
55
50
|
): ActiveAgentSummary {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 "@
|
|
2
|
-
import { Text } from "@
|
|
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
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melihmucuk/pi-crew",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
"
|
|
10
|
-
"
|
|
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
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
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
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
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.
|
|
50
|
+
"typebox": "^1.1.38",
|
|
48
51
|
"typescript": "^5.9.3"
|
|
49
52
|
}
|
|
50
53
|
}
|