@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.
- package/README.md +19 -18
- package/agents/code-reviewer.md +52 -104
- package/agents/oracle.md +26 -52
- package/agents/planner.md +7 -7
- package/agents/quality-reviewer.md +90 -131
- package/agents/scout.md +3 -2
- package/agents/worker.md +8 -2
- package/extension/agent-discovery.ts +791 -0
- package/extension/bootstrap-session.ts +131 -0
- package/extension/index.ts +63 -0
- package/extension/integration/register-renderers.ts +77 -0
- package/extension/integration/register-tools.ts +39 -0
- package/extension/integration/tool-presentation.ts +50 -0
- package/extension/integration/tools/crew-abort.ts +126 -0
- package/extension/integration/tools/crew-done.ts +46 -0
- package/extension/integration/tools/crew-list.ts +92 -0
- package/extension/integration/tools/crew-respond.ts +59 -0
- package/extension/integration/tools/crew-spawn.ts +87 -0
- package/extension/integration/tools/tool-deps.ts +16 -0
- package/extension/integration.ts +13 -0
- package/extension/runtime/crew-runtime.ts +426 -0
- package/extension/runtime/delivery-coordinator.ts +131 -0
- package/extension/runtime/overflow-recovery.ts +211 -0
- package/extension/runtime/subagent-registry.ts +78 -0
- package/extension/runtime/subagent-state.ts +59 -0
- package/extension/status-widget.ts +107 -0
- package/extension/subagent-messages.ts +124 -0
- package/extension/tool-registry.ts +19 -0
- package/package.json +14 -14
- package/prompts/pi-crew-plan.md +46 -37
- package/prompts/pi-crew-review.md +3 -1
- package/skills/pi-crew/SKILL.md +129 -0
- package/dist/agent-discovery.d.ts +0 -29
- package/dist/agent-discovery.js +0 -527
- package/dist/bootstrap-session.d.ts +0 -21
- package/dist/bootstrap-session.js +0 -74
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -46
- package/dist/integration/register-command.d.ts +0 -3
- package/dist/integration/register-command.js +0 -51
- package/dist/integration/register-renderers.d.ts +0 -2
- package/dist/integration/register-renderers.js +0 -59
- package/dist/integration/register-tools.d.ts +0 -3
- package/dist/integration/register-tools.js +0 -25
- package/dist/integration/tool-presentation.d.ts +0 -27
- package/dist/integration/tool-presentation.js +0 -29
- package/dist/integration/tools/crew-abort.d.ts +0 -2
- package/dist/integration/tools/crew-abort.js +0 -79
- package/dist/integration/tools/crew-done.d.ts +0 -2
- package/dist/integration/tools/crew-done.js +0 -28
- package/dist/integration/tools/crew-list.d.ts +0 -2
- package/dist/integration/tools/crew-list.js +0 -74
- package/dist/integration/tools/crew-respond.d.ts +0 -2
- package/dist/integration/tools/crew-respond.js +0 -32
- package/dist/integration/tools/crew-spawn.d.ts +0 -2
- package/dist/integration/tools/crew-spawn.js +0 -48
- package/dist/integration/tools/tool-deps.d.ts +0 -9
- package/dist/integration/tools/tool-deps.js +0 -1
- package/dist/integration.d.ts +0 -3
- package/dist/integration.js +0 -8
- package/dist/runtime/crew-runtime.d.ts +0 -62
- package/dist/runtime/crew-runtime.js +0 -285
- package/dist/runtime/delivery-coordinator.d.ts +0 -26
- package/dist/runtime/delivery-coordinator.js +0 -86
- package/dist/runtime/overflow-recovery.d.ts +0 -3
- package/dist/runtime/overflow-recovery.js +0 -155
- package/dist/runtime/subagent-registry.d.ts +0 -14
- package/dist/runtime/subagent-registry.js +0 -58
- package/dist/runtime/subagent-state.d.ts +0 -35
- package/dist/runtime/subagent-state.js +0 -32
- package/dist/status-widget.d.ts +0 -3
- package/dist/status-widget.js +0 -84
- package/dist/subagent-messages.d.ts +0 -37
- package/dist/subagent-messages.js +0 -68
- package/dist/tool-registry.d.ts +0 -5
- package/dist/tool-registry.js +0 -13
- package/docs/architecture.md +0 -187
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AgentSession,
|
|
3
|
+
createAgentSession,
|
|
4
|
+
DefaultResourceLoader,
|
|
5
|
+
type ModelRegistry,
|
|
6
|
+
SessionManager,
|
|
7
|
+
SettingsManager,
|
|
8
|
+
} from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
10
|
+
import type { AgentConfig } from "./agent-discovery.js";
|
|
11
|
+
import { SUPPORTED_TOOL_NAMES, type SupportedToolName } from "./tool-registry.js";
|
|
12
|
+
|
|
13
|
+
function resolveTools(agentConfig: AgentConfig): SupportedToolName[] {
|
|
14
|
+
return [...(agentConfig.tools ?? SUPPORTED_TOOL_NAMES)];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveModel(agentConfig: AgentConfig, ctx: BootstrapContext): { model: Model<Api> | undefined; warnings: string[] } {
|
|
18
|
+
const warnings: string[] = [];
|
|
19
|
+
const model = ctx.model;
|
|
20
|
+
if (!agentConfig.parsedModel) return { model, warnings };
|
|
21
|
+
|
|
22
|
+
const found = ctx.modelRegistry.find(
|
|
23
|
+
agentConfig.parsedModel.provider,
|
|
24
|
+
agentConfig.parsedModel.modelId,
|
|
25
|
+
);
|
|
26
|
+
if (found) return { model: found, warnings };
|
|
27
|
+
|
|
28
|
+
warnings.push(
|
|
29
|
+
`Model "${agentConfig.model}" not found, using current session model`,
|
|
30
|
+
);
|
|
31
|
+
return { model, warnings };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSkillWarnings(
|
|
35
|
+
agentConfig: AgentConfig,
|
|
36
|
+
resourceLoader: DefaultResourceLoader,
|
|
37
|
+
): string[] {
|
|
38
|
+
const warnings: string[] = [];
|
|
39
|
+
if (!agentConfig.skills) return warnings;
|
|
40
|
+
|
|
41
|
+
const availableSkillNames = new Set(
|
|
42
|
+
resourceLoader.getSkills().skills.map((skill) => skill.name),
|
|
43
|
+
);
|
|
44
|
+
for (const skillName of agentConfig.skills) {
|
|
45
|
+
if (!availableSkillNames.has(skillName)) {
|
|
46
|
+
warnings.push(
|
|
47
|
+
`Unknown skill "${skillName}" in subagent config, skipping`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return warnings;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BootstrapContext {
|
|
55
|
+
model: Model<Api> | undefined;
|
|
56
|
+
modelRegistry: ModelRegistry;
|
|
57
|
+
agentDir: string;
|
|
58
|
+
parentSessionFile?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface BootstrapOptions {
|
|
62
|
+
agentConfig: AgentConfig;
|
|
63
|
+
cwd: string;
|
|
64
|
+
ctx: BootstrapContext;
|
|
65
|
+
extensionResolvedPath: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BootstrapResult {
|
|
69
|
+
session: AgentSession;
|
|
70
|
+
warnings: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function bootstrapSession(
|
|
74
|
+
opts: BootstrapOptions,
|
|
75
|
+
): Promise<BootstrapResult> {
|
|
76
|
+
const warnings: string[] = [];
|
|
77
|
+
const { agentConfig, cwd, ctx, extensionResolvedPath } = opts;
|
|
78
|
+
|
|
79
|
+
const authStorage = ctx.modelRegistry.authStorage;
|
|
80
|
+
const modelRegistry = ctx.modelRegistry;
|
|
81
|
+
const { model, warnings: modelWarnings } = resolveModel(agentConfig, ctx);
|
|
82
|
+
warnings.push(...modelWarnings);
|
|
83
|
+
const tools = resolveTools(agentConfig);
|
|
84
|
+
|
|
85
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
86
|
+
cwd,
|
|
87
|
+
agentDir: ctx.agentDir,
|
|
88
|
+
extensionsOverride: (base) => ({
|
|
89
|
+
...base,
|
|
90
|
+
extensions: base.extensions.filter(
|
|
91
|
+
(ext) => !ext.resolvedPath.startsWith(extensionResolvedPath),
|
|
92
|
+
),
|
|
93
|
+
}),
|
|
94
|
+
skillsOverride: agentConfig.skills
|
|
95
|
+
? (base) => ({
|
|
96
|
+
skills: base.skills.filter((skill) =>
|
|
97
|
+
agentConfig.skills!.includes(skill.name),
|
|
98
|
+
),
|
|
99
|
+
diagnostics: base.diagnostics,
|
|
100
|
+
})
|
|
101
|
+
: undefined,
|
|
102
|
+
appendSystemPromptOverride: (base) =>
|
|
103
|
+
agentConfig.systemPrompt.trim()
|
|
104
|
+
? [...base, agentConfig.systemPrompt]
|
|
105
|
+
: base,
|
|
106
|
+
});
|
|
107
|
+
await resourceLoader.reload();
|
|
108
|
+
warnings.push(...getSkillWarnings(agentConfig, resourceLoader));
|
|
109
|
+
|
|
110
|
+
const settingsManager = SettingsManager.inMemory({
|
|
111
|
+
compaction: { enabled: agentConfig.compaction ?? true },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const sessionManager = SessionManager.create(cwd);
|
|
115
|
+
sessionManager.newSession({ parentSession: ctx.parentSessionFile });
|
|
116
|
+
|
|
117
|
+
const result = await createAgentSession({
|
|
118
|
+
cwd,
|
|
119
|
+
agentDir: ctx.agentDir,
|
|
120
|
+
model,
|
|
121
|
+
thinkingLevel: agentConfig.thinking,
|
|
122
|
+
tools,
|
|
123
|
+
resourceLoader,
|
|
124
|
+
sessionManager,
|
|
125
|
+
settingsManager,
|
|
126
|
+
authStorage,
|
|
127
|
+
modelRegistry,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return { session: result.session, warnings };
|
|
131
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { crewRuntime } from "./runtime/crew-runtime.js";
|
|
5
|
+
import { registerCrewIntegration } from "./integration.js";
|
|
6
|
+
import { updateWidget } from "./status-widget.js";
|
|
7
|
+
|
|
8
|
+
const extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
// Process-level cleanup for subagents on exit
|
|
11
|
+
const processHooksSetupKey = Symbol.for("pi-crew.processHooksSetup");
|
|
12
|
+
const globalWithProcessHooks = globalThis as typeof globalThis & Record<
|
|
13
|
+
symbol,
|
|
14
|
+
boolean | undefined
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
function setupProcessHooks() {
|
|
18
|
+
if (globalWithProcessHooks[processHooksSetupKey]) return;
|
|
19
|
+
globalWithProcessHooks[processHooksSetupKey] = true;
|
|
20
|
+
|
|
21
|
+
process.once('SIGINT', () => {
|
|
22
|
+
crewRuntime.abortAll();
|
|
23
|
+
process.exit(130);
|
|
24
|
+
});
|
|
25
|
+
process.on('beforeExit', () => crewRuntime.abortAll());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function (pi: ExtensionAPI) {
|
|
29
|
+
let currentCtx: ExtensionContext | undefined;
|
|
30
|
+
|
|
31
|
+
setupProcessHooks();
|
|
32
|
+
|
|
33
|
+
const refreshWidget = () => {
|
|
34
|
+
if (currentCtx) updateWidget(currentCtx, crewRuntime);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const activateSession = (ctx: ExtensionContext) => {
|
|
38
|
+
currentCtx = ctx;
|
|
39
|
+
crewRuntime.activateSession(
|
|
40
|
+
{
|
|
41
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
42
|
+
isIdle: () => ctx.isIdle(),
|
|
43
|
+
sendMessage: pi.sendMessage.bind(pi),
|
|
44
|
+
},
|
|
45
|
+
refreshWidget,
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
pi.on("session_start", (_event, ctx) => {
|
|
50
|
+
activateSession(ctx);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
pi.on("session_shutdown", (event, ctx) => {
|
|
54
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
55
|
+
crewRuntime.deactivateSession(sessionId);
|
|
56
|
+
|
|
57
|
+
if (event.reason === "quit") {
|
|
58
|
+
crewRuntime.abortAll();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
registerCrewIntegration(pi, crewRuntime, extensionDir);
|
|
63
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExtensionAPI,
|
|
3
|
+
getMarkdownTheme,
|
|
4
|
+
} from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import { Box, Markdown, Text } from "@mariozechner/pi-tui";
|
|
6
|
+
import {
|
|
7
|
+
type CrewResultMessageDetails,
|
|
8
|
+
STATUS_ICON,
|
|
9
|
+
getCrewResultTitle,
|
|
10
|
+
} from "../subagent-messages.js";
|
|
11
|
+
|
|
12
|
+
type MessageRenderer = Parameters<ExtensionAPI["registerMessageRenderer"]>[1];
|
|
13
|
+
type MessageRendererTheme = Parameters<MessageRenderer>[2];
|
|
14
|
+
|
|
15
|
+
function getStatusColor(status: CrewResultMessageDetails["status"]): "success" | "error" | "warning" | "muted" {
|
|
16
|
+
switch (status) {
|
|
17
|
+
case "done":
|
|
18
|
+
return "success";
|
|
19
|
+
case "error":
|
|
20
|
+
case "aborted":
|
|
21
|
+
return "error";
|
|
22
|
+
case "running":
|
|
23
|
+
case "waiting":
|
|
24
|
+
return "warning";
|
|
25
|
+
default:
|
|
26
|
+
return "muted";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderWarningMessage(content: unknown, theme: MessageRendererTheme): Box {
|
|
31
|
+
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
32
|
+
box.addChild(new Text(theme.fg("warning", String(content ?? "")), 0, 0));
|
|
33
|
+
return box;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function registerCrewMessageRenderers(pi: ExtensionAPI): void {
|
|
37
|
+
pi.registerMessageRenderer("crew-result", (message, { expanded }, theme) => {
|
|
38
|
+
const details = message.details as CrewResultMessageDetails | undefined;
|
|
39
|
+
const title = details ? getCrewResultTitle(details) : "Subagent update";
|
|
40
|
+
const icon = details
|
|
41
|
+
? theme.fg(getStatusColor(details.status), STATUS_ICON[details.status])
|
|
42
|
+
: theme.fg("muted", "ℹ");
|
|
43
|
+
const header = `${icon} ${theme.fg("toolTitle", theme.bold(title))}`;
|
|
44
|
+
const body = details?.body ?? (!details && message.content ? String(message.content) : undefined);
|
|
45
|
+
|
|
46
|
+
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
47
|
+
box.addChild(new Text(header, 0, 0));
|
|
48
|
+
|
|
49
|
+
if (details?.sessionFile) {
|
|
50
|
+
box.addChild(new Text(theme.fg("muted", `📁 ${details.sessionFile}`), 0, 0));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (body) {
|
|
54
|
+
if (expanded) {
|
|
55
|
+
box.addChild(new Text("", 0, 0));
|
|
56
|
+
box.addChild(new Markdown(body, 0, 0, getMarkdownTheme()));
|
|
57
|
+
} else {
|
|
58
|
+
const lines = body.split("\n");
|
|
59
|
+
const preview = lines.slice(0, 5).join("\n");
|
|
60
|
+
box.addChild(new Text(theme.fg("dim", preview), 0, 0));
|
|
61
|
+
if (lines.length > 5) {
|
|
62
|
+
box.addChild(new Text(theme.fg("muted", "(Ctrl+O to expand)"), 0, 0));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return box;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
pi.registerMessageRenderer("crew-remaining", (message, _options, theme) => {
|
|
71
|
+
return renderWarningMessage(message.content, theme);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
pi.registerMessageRenderer("crew-list-warning", (message, _options, theme) => {
|
|
75
|
+
return renderWarningMessage(message.content, theme);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
} from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import { type AgentDiscoveryWarning } from "../agent-discovery.js";
|
|
6
|
+
import type { CrewRuntime } from "../runtime/crew-runtime.js";
|
|
7
|
+
import { registerCrewAbortTool } from "./tools/crew-abort.js";
|
|
8
|
+
import { registerCrewDoneTool } from "./tools/crew-done.js";
|
|
9
|
+
import { registerCrewListTool } from "./tools/crew-list.js";
|
|
10
|
+
import { registerCrewRespondTool } from "./tools/crew-respond.js";
|
|
11
|
+
import { registerCrewSpawnTool } from "./tools/crew-spawn.js";
|
|
12
|
+
|
|
13
|
+
export function registerCrewTools(
|
|
14
|
+
pi: ExtensionAPI,
|
|
15
|
+
crew: CrewRuntime,
|
|
16
|
+
extensionDir: string,
|
|
17
|
+
): void {
|
|
18
|
+
const shownDiscoveryWarnings = new Set<string>();
|
|
19
|
+
|
|
20
|
+
const notifyDiscoveryWarnings = (
|
|
21
|
+
ctx: ExtensionContext,
|
|
22
|
+
warnings: AgentDiscoveryWarning[],
|
|
23
|
+
) => {
|
|
24
|
+
if (!ctx.hasUI) return;
|
|
25
|
+
for (const warning of warnings) {
|
|
26
|
+
const key = `${warning.filePath}:${warning.message}`;
|
|
27
|
+
if (shownDiscoveryWarnings.has(key)) continue;
|
|
28
|
+
shownDiscoveryWarnings.add(key);
|
|
29
|
+
ctx.ui.notify(`${warning.message} (${warning.filePath})`, "error");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const deps = { pi, crew, extensionDir, notifyDiscoveryWarnings };
|
|
34
|
+
registerCrewListTool(deps);
|
|
35
|
+
registerCrewSpawnTool(deps);
|
|
36
|
+
registerCrewAbortTool(deps);
|
|
37
|
+
registerCrewRespondTool(deps);
|
|
38
|
+
registerCrewDoneTool(deps);
|
|
39
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { Box, Text } from "@mariozechner/pi-tui";
|
|
4
|
+
|
|
5
|
+
export type ToolTheme = Parameters<Exclude<Parameters<ExtensionAPI["registerTool"]>[0]["renderCall"], undefined>>[1];
|
|
6
|
+
export type ToolResult = AgentToolResult<unknown>;
|
|
7
|
+
|
|
8
|
+
export function toolError(text: string) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text" as const, text }],
|
|
11
|
+
isError: true,
|
|
12
|
+
details: { error: true },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toolSuccess(
|
|
17
|
+
text: string,
|
|
18
|
+
details: Record<string, unknown> = {},
|
|
19
|
+
options: { terminate?: boolean } = {},
|
|
20
|
+
) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text" as const, text }],
|
|
23
|
+
details,
|
|
24
|
+
...(options.terminate ? { terminate: true } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function renderCrewCall(
|
|
29
|
+
theme: ToolTheme,
|
|
30
|
+
name: string,
|
|
31
|
+
id: string,
|
|
32
|
+
preview?: string,
|
|
33
|
+
): Box {
|
|
34
|
+
const box = new Box(1, 1);
|
|
35
|
+
box.addChild(new Text(theme.fg("toolTitle", theme.bold(`${name} `)) + theme.fg("accent", id), 0, 0));
|
|
36
|
+
if (preview) {
|
|
37
|
+
box.addChild(new Text(theme.fg("dim", preview), 0, 0));
|
|
38
|
+
}
|
|
39
|
+
return box;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function renderCrewResult(
|
|
43
|
+
result: ToolResult,
|
|
44
|
+
theme: ToolTheme,
|
|
45
|
+
): Text {
|
|
46
|
+
const text = result.content[0];
|
|
47
|
+
const details = result.details as { error?: boolean } | undefined;
|
|
48
|
+
const content = text?.type === "text" && text.text ? text.text : "(no output)";
|
|
49
|
+
return new Text(details?.error ? theme.fg("error", content) : theme.fg("success", content), 0, 0);
|
|
50
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import {
|
|
3
|
+
renderCrewCall,
|
|
4
|
+
renderCrewResult,
|
|
5
|
+
toolError,
|
|
6
|
+
toolSuccess,
|
|
7
|
+
} from "../tool-presentation.js";
|
|
8
|
+
import type { CrewToolDeps } from "./tool-deps.js";
|
|
9
|
+
|
|
10
|
+
function formatAbortToolMessage(result: {
|
|
11
|
+
abortedIds: string[];
|
|
12
|
+
missingIds: string[];
|
|
13
|
+
foreignIds: string[];
|
|
14
|
+
}): string {
|
|
15
|
+
const parts: string[] = [];
|
|
16
|
+
|
|
17
|
+
if (result.abortedIds.length > 0) {
|
|
18
|
+
parts.push(`Aborted ${result.abortedIds.length} subagent(s): ${result.abortedIds.join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
if (result.missingIds.length > 0) {
|
|
21
|
+
parts.push(`Not found or already finished: ${result.missingIds.join(", ")}`);
|
|
22
|
+
}
|
|
23
|
+
if (result.foreignIds.length > 0) {
|
|
24
|
+
parts.push(`Belong to a different session: ${result.foreignIds.join(", ")}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return parts.join("\n");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function registerCrewAbortTool({ pi, crew }: CrewToolDeps): void {
|
|
31
|
+
pi.registerTool({
|
|
32
|
+
name: "crew_abort",
|
|
33
|
+
label: "Abort Crew",
|
|
34
|
+
description:
|
|
35
|
+
"Abort one, many, or all active subagents owned by the current session.",
|
|
36
|
+
parameters: Type.Object({
|
|
37
|
+
subagent_id: Type.Optional(
|
|
38
|
+
Type.String({ description: "Single subagent ID to abort" }),
|
|
39
|
+
),
|
|
40
|
+
subagent_ids: Type.Optional(
|
|
41
|
+
Type.Array(Type.String(), {
|
|
42
|
+
minItems: 1,
|
|
43
|
+
description: "Multiple subagent IDs to abort",
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
all: Type.Optional(
|
|
47
|
+
Type.Boolean({
|
|
48
|
+
description: "Abort all active subagents owned by the current session",
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
}),
|
|
52
|
+
promptSnippet: "Abort one, many, or all active subagents from this session.",
|
|
53
|
+
promptGuidelines: [
|
|
54
|
+
"crew_abort: Abort one, many, or all active subagents owned by this session.",
|
|
55
|
+
"crew_abort: Provide exactly one mode: subagent_id, subagent_ids, or all=true.",
|
|
56
|
+
"crew_abort: Use only when delegated work is obsolete, wrong, or explicitly cancelled.",
|
|
57
|
+
],
|
|
58
|
+
|
|
59
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
60
|
+
const callerSessionId = ctx.sessionManager.getSessionId();
|
|
61
|
+
const modeCount = Number(Boolean(params.subagent_id))
|
|
62
|
+
+ Number(Boolean(params.subagent_ids?.length))
|
|
63
|
+
+ Number(params.all === true);
|
|
64
|
+
|
|
65
|
+
if (modeCount !== 1) {
|
|
66
|
+
return toolError(
|
|
67
|
+
"Provide exactly one of: subagent_id, subagent_ids, or all=true.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (params.all) {
|
|
72
|
+
const abortedIds = crew.abortAllOwned(callerSessionId, {
|
|
73
|
+
reason: "Aborted by tool request",
|
|
74
|
+
});
|
|
75
|
+
if (abortedIds.length === 0) {
|
|
76
|
+
return toolError("No active subagents in the current session.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return toolSuccess(
|
|
80
|
+
`Aborted ${abortedIds.length} subagent(s): ${abortedIds.join(", ")}`,
|
|
81
|
+
{ ids: abortedIds },
|
|
82
|
+
{ terminate: true },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ids = params.subagent_id
|
|
87
|
+
? [params.subagent_id]
|
|
88
|
+
: (params.subagent_ids ?? []);
|
|
89
|
+
const result = crew.abortOwned(ids, callerSessionId, {
|
|
90
|
+
reason: "Aborted by tool request",
|
|
91
|
+
});
|
|
92
|
+
const message = formatAbortToolMessage(result);
|
|
93
|
+
|
|
94
|
+
if (result.abortedIds.length === 0) {
|
|
95
|
+
return toolError(message || "No subagents were aborted.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return toolSuccess(
|
|
99
|
+
message,
|
|
100
|
+
{
|
|
101
|
+
ids: result.abortedIds,
|
|
102
|
+
missing_ids: result.missingIds,
|
|
103
|
+
foreign_ids: result.foreignIds,
|
|
104
|
+
},
|
|
105
|
+
{ terminate: true },
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
renderCall(args, theme, _context) {
|
|
110
|
+
if (args.all) {
|
|
111
|
+
return renderCrewCall(theme, "crew_abort", "all");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (args.subagent_id) {
|
|
115
|
+
return renderCrewCall(theme, "crew_abort", args.subagent_id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const count = Array.isArray(args.subagent_ids) ? args.subagent_ids.length : 0;
|
|
119
|
+
return renderCrewCall(theme, "crew_abort", `${count} ids`);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
renderResult(result, _options, theme, _context) {
|
|
123
|
+
return renderCrewResult(result, theme);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import {
|
|
3
|
+
renderCrewCall,
|
|
4
|
+
renderCrewResult,
|
|
5
|
+
toolError,
|
|
6
|
+
toolSuccess,
|
|
7
|
+
} from "../tool-presentation.js";
|
|
8
|
+
import type { CrewToolDeps } from "./tool-deps.js";
|
|
9
|
+
|
|
10
|
+
export function registerCrewDoneTool({ pi, crew }: CrewToolDeps): void {
|
|
11
|
+
pi.registerTool({
|
|
12
|
+
name: "crew_done",
|
|
13
|
+
label: "Done with Crew",
|
|
14
|
+
description:
|
|
15
|
+
"Close an interactive subagent session. Use when you no longer need to interact with the subagent.",
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
subagent_id: Type.String({ description: "ID of the subagent to close" }),
|
|
18
|
+
}),
|
|
19
|
+
promptSnippet: "Close an interactive subagent session when done.",
|
|
20
|
+
promptGuidelines: [
|
|
21
|
+
"crew_done: Close a waiting interactive subagent owned by this session.",
|
|
22
|
+
"crew_done: Use only when no further follow-up is needed; otherwise use crew_respond.",
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
26
|
+
const callerSessionId = ctx.sessionManager.getSessionId();
|
|
27
|
+
const { error } = crew.done(params.subagent_id, callerSessionId);
|
|
28
|
+
if (error) return toolError(error);
|
|
29
|
+
|
|
30
|
+
return toolSuccess(
|
|
31
|
+
`Subagent ${params.subagent_id} closed.`,
|
|
32
|
+
{
|
|
33
|
+
id: params.subagent_id,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
renderCall(args, theme, _context) {
|
|
39
|
+
return renderCrewCall(theme, "crew_done", args.subagent_id || "...");
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
renderResult(result, _options, theme, _context) {
|
|
43
|
+
return renderCrewResult(result, theme);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { discoverAgents } from "../../agent-discovery.js";
|
|
4
|
+
import { STATUS_ICON, sendCrewListActiveWarning } from "../../subagent-messages.js";
|
|
5
|
+
import type { CrewToolDeps } from "./tool-deps.js";
|
|
6
|
+
|
|
7
|
+
export function registerCrewListTool({
|
|
8
|
+
pi,
|
|
9
|
+
crew,
|
|
10
|
+
notifyDiscoveryWarnings,
|
|
11
|
+
}: CrewToolDeps): void {
|
|
12
|
+
pi.registerTool({
|
|
13
|
+
name: "crew_list",
|
|
14
|
+
label: "List Crew",
|
|
15
|
+
description:
|
|
16
|
+
"List available subagent definitions and currently running subagents with their status. Use only to discover which subagents exist or to get a one-time status snapshot. Do NOT call this repeatedly to check if a subagent has finished — results are delivered automatically as steering messages.",
|
|
17
|
+
parameters: Type.Object({}),
|
|
18
|
+
promptSnippet: "List subagent definitions and active subagents",
|
|
19
|
+
promptGuidelines: [
|
|
20
|
+
"crew_list: List available subagents and active subagents owned by this session.",
|
|
21
|
+
"crew_list: Use before crew_spawn to discover names, descriptions, and interactive status.",
|
|
22
|
+
"crew_list: Use only for discovery or a requested status snapshot; do not poll for completion.",
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
26
|
+
const { agents, warnings } = discoverAgents(ctx.cwd);
|
|
27
|
+
notifyDiscoveryWarnings(ctx, warnings);
|
|
28
|
+
const callerSessionId = ctx.sessionManager.getSessionId();
|
|
29
|
+
const running = crew.getActiveSummariesForOwner(callerSessionId);
|
|
30
|
+
|
|
31
|
+
const lines: string[] = [];
|
|
32
|
+
|
|
33
|
+
lines.push("## Available Subagents");
|
|
34
|
+
if (agents.length === 0) {
|
|
35
|
+
lines.push(
|
|
36
|
+
"No valid subagent definitions found. Add `.md` files to `<cwd>/.pi/agents/` or `~/.pi/agent/agents/`.",
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
for (const agent of agents) {
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push(`name: ${agent.name}`);
|
|
42
|
+
lines.push(`description: ${agent.description}`);
|
|
43
|
+
lines.push(`interactive: ${agent.interactive ? "true" : "false"}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (warnings.length > 0) {
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push("## Ignored subagent definitions");
|
|
50
|
+
for (const warning of warnings) {
|
|
51
|
+
lines.push(`- ${warning.message} (${warning.filePath})`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lines.push("");
|
|
56
|
+
lines.push("## Active Subagents");
|
|
57
|
+
if (running.length === 0) {
|
|
58
|
+
lines.push("No subagents currently active.");
|
|
59
|
+
} else {
|
|
60
|
+
for (const agent of running) {
|
|
61
|
+
const icon = STATUS_ICON[agent.status] ?? "❓";
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push(`id: ${agent.id}`);
|
|
64
|
+
lines.push(`name: ${agent.agentName}`);
|
|
65
|
+
lines.push(`status: ${icon} ${agent.status}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const text = lines.join("\n");
|
|
70
|
+
|
|
71
|
+
if (running.length > 0) {
|
|
72
|
+
Promise.resolve().then(() => {
|
|
73
|
+
sendCrewListActiveWarning(pi.sendMessage.bind(pi), {
|
|
74
|
+
isIdle: ctx.isIdle(),
|
|
75
|
+
triggerTurn: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { content: [{ type: "text", text }], details: {} };
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
renderCall(_args, theme, _context) {
|
|
84
|
+
return new Text(theme.fg("toolTitle", theme.bold("crew_list")), 0, 0);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
renderResult(result, _options, _theme, _context) {
|
|
88
|
+
const text = result.content[0];
|
|
89
|
+
return new Text(text?.type === "text" ? text.text : "(no output)", 0, 0);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import {
|
|
3
|
+
renderCrewCall,
|
|
4
|
+
renderCrewResult,
|
|
5
|
+
toolError,
|
|
6
|
+
toolSuccess,
|
|
7
|
+
} from "../tool-presentation.js";
|
|
8
|
+
import type { CrewToolDeps } from "./tool-deps.js";
|
|
9
|
+
|
|
10
|
+
export function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void {
|
|
11
|
+
pi.registerTool({
|
|
12
|
+
name: "crew_respond",
|
|
13
|
+
label: "Respond to Crew",
|
|
14
|
+
description:
|
|
15
|
+
"Send a follow-up message to an interactive subagent that is waiting for a response.",
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
subagent_id: Type.String({
|
|
18
|
+
description:
|
|
19
|
+
"ID of the waiting subagent (from crew_list or crew_spawn result)",
|
|
20
|
+
}),
|
|
21
|
+
message: Type.String({ description: "Message to send to the subagent" }),
|
|
22
|
+
}),
|
|
23
|
+
promptSnippet:
|
|
24
|
+
"Send a follow-up message to a waiting interactive subagent.",
|
|
25
|
+
promptGuidelines: [
|
|
26
|
+
"crew_respond: Send a complete follow-up message to a waiting interactive subagent.",
|
|
27
|
+
"crew_respond: Use the waiting subagent ID from crew_spawn results or crew_list.",
|
|
28
|
+
"crew_respond: The response arrives as a steering message; do not poll crew_list.",
|
|
29
|
+
],
|
|
30
|
+
|
|
31
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
32
|
+
const callerSessionId = ctx.sessionManager.getSessionId();
|
|
33
|
+
const { error } = crew.respond(
|
|
34
|
+
params.subagent_id,
|
|
35
|
+
params.message,
|
|
36
|
+
callerSessionId,
|
|
37
|
+
);
|
|
38
|
+
if (error) return toolError(error);
|
|
39
|
+
|
|
40
|
+
return toolSuccess(
|
|
41
|
+
`Message sent to subagent ${params.subagent_id}. Response will be delivered as a steering message.`,
|
|
42
|
+
{ id: params.subagent_id, message: params.message },
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
renderCall(args, theme, _context) {
|
|
47
|
+
return renderCrewCall(
|
|
48
|
+
theme,
|
|
49
|
+
"crew_respond",
|
|
50
|
+
args.subagent_id || "...",
|
|
51
|
+
args.message,
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
renderResult(result, _options, theme, _context) {
|
|
56
|
+
return renderCrewResult(result, theme);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|