@melihmucuk/pi-crew 1.0.15 → 1.0.17
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 +8 -8
- package/agents/code-reviewer.md +32 -102
- package/agents/oracle.md +23 -29
- package/agents/planner.md +35 -116
- package/agents/quality-reviewer.md +39 -124
- package/agents/scout.md +21 -38
- package/agents/worker.md +27 -72
- package/extension/agent-catalog.ts +369 -0
- package/extension/agent-config-fields.ts +359 -0
- package/extension/agent-discovery.ts +49 -717
- package/extension/bootstrap-session.ts +2 -2
- package/extension/index.ts +5 -3
- package/extension/integration/crew-tool-actions.ts +306 -0
- package/extension/integration/crew-tool-executor.ts +109 -0
- package/extension/integration/register-renderers.ts +2 -2
- package/extension/integration/register-tools.ts +11 -3
- package/extension/integration/tool-presentation.ts +3 -23
- package/extension/integration/tools/crew-abort.ts +14 -84
- package/extension/integration/tools/crew-done.ts +7 -26
- package/extension/integration/tools/crew-list.ts +5 -61
- package/extension/integration/tools/crew-respond.ts +8 -29
- package/extension/integration/tools/crew-spawn.ts +16 -57
- package/extension/message-delivery-policy.ts +22 -0
- package/extension/runtime/crew-runtime.ts +60 -223
- package/extension/runtime/overflow-recovery.ts +1 -1
- package/extension/runtime/{delivery-coordinator.ts → owner-session-coordinator.ts} +44 -37
- package/extension/runtime/subagent-lifecycle.ts +203 -0
- package/extension/runtime/subagent-registry.ts +50 -6
- package/extension/runtime/subagent-transitions.ts +100 -0
- package/extension/status-widget.ts +2 -2
- package/extension/subagent-messages.ts +9 -17
- package/package.json +13 -11
- package/prompts/pi-crew-plan.md +34 -137
- package/prompts/pi-crew-review.md +36 -112
- package/skills/pi-crew/REFERENCE.md +82 -0
- package/skills/pi-crew/SKILL.md +33 -104
- package/extension/integration/tools/tool-deps.ts +0 -16
- package/extension/integration.ts +0 -13
- package/extension/runtime/subagent-state.ts +0 -59
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { Text } from "@
|
|
1
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
4
|
-
import { STATUS_ICON, sendCrewListActiveWarning } from "../../subagent-messages.js";
|
|
5
|
-
import type { CrewToolDeps } from "./tool-deps.js";
|
|
3
|
+
import type { CrewToolDeps } from "../crew-tool-executor.js";
|
|
6
4
|
|
|
7
5
|
export function registerCrewListTool({
|
|
8
6
|
pi,
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
actions,
|
|
8
|
+
executor,
|
|
11
9
|
}: CrewToolDeps): void {
|
|
12
10
|
pi.registerTool({
|
|
13
11
|
name: "crew_list",
|
|
@@ -23,61 +21,7 @@ export function registerCrewListTool({
|
|
|
23
21
|
],
|
|
24
22
|
|
|
25
23
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
26
|
-
|
|
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: {} };
|
|
24
|
+
return executor.execute(ctx, (actionCtx) => actions.list(actionCtx));
|
|
81
25
|
},
|
|
82
26
|
|
|
83
27
|
renderCall(_args, theme, _context) {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { renderCrewCall } from "../tool-presentation.js";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
toolSuccess,
|
|
7
|
-
} from "../tool-presentation.js";
|
|
8
|
-
import type { CrewToolDeps } from "./tool-deps.js";
|
|
4
|
+
registerCrewActionTool,
|
|
5
|
+
type CrewToolDeps,
|
|
6
|
+
} from "../crew-tool-executor.js";
|
|
9
7
|
|
|
10
|
-
export function registerCrewRespondTool(
|
|
11
|
-
|
|
8
|
+
export function registerCrewRespondTool(deps: CrewToolDeps): void {
|
|
9
|
+
registerCrewActionTool<{ subagent_id: string; message: string }>(deps, {
|
|
12
10
|
name: "crew_respond",
|
|
13
11
|
label: "Respond to Crew",
|
|
14
12
|
description:
|
|
@@ -27,22 +25,7 @@ export function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void {
|
|
|
27
25
|
"crew_respond: Use the waiting subagent ID from crew_spawn results or crew_list.",
|
|
28
26
|
"crew_respond: The response arrives as a steering message; do not poll crew_list.",
|
|
29
27
|
],
|
|
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
|
-
|
|
28
|
+
action: (params, actionCtx) => deps.actions.respond(params, actionCtx),
|
|
46
29
|
renderCall(args, theme, _context) {
|
|
47
30
|
return renderCrewCall(
|
|
48
31
|
theme,
|
|
@@ -51,9 +34,5 @@ export function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void {
|
|
|
51
34
|
args.message,
|
|
52
35
|
);
|
|
53
36
|
},
|
|
54
|
-
|
|
55
|
-
renderResult(result, _options, theme, _context) {
|
|
56
|
-
return renderCrewResult(result, theme);
|
|
57
|
-
},
|
|
58
37
|
});
|
|
59
|
-
}
|
|
38
|
+
}
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import { getAgentDir } from "@
|
|
1
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
3
|
+
import { renderCrewCall } from "../tool-presentation.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
toolSuccess,
|
|
9
|
-
} from "../tool-presentation.js";
|
|
10
|
-
import type { CrewToolDeps } from "./tool-deps.js";
|
|
5
|
+
registerCrewActionTool,
|
|
6
|
+
type CrewToolDeps,
|
|
7
|
+
} from "../crew-tool-executor.js";
|
|
11
8
|
|
|
12
|
-
export function registerCrewSpawnTool({
|
|
13
|
-
|
|
14
|
-
crew,
|
|
15
|
-
extensionDir,
|
|
16
|
-
notifyDiscoveryWarnings,
|
|
17
|
-
}: CrewToolDeps): void {
|
|
18
|
-
pi.registerTool({
|
|
9
|
+
export function registerCrewSpawnTool(deps: CrewToolDeps): void {
|
|
10
|
+
registerCrewActionTool<{ subagent: string; task: string }>(deps, {
|
|
19
11
|
name: "crew_spawn",
|
|
20
12
|
label: "Spawn Crew",
|
|
21
13
|
description:
|
|
@@ -33,44 +25,15 @@ export function registerCrewSpawnTool({
|
|
|
33
25
|
"crew_spawn: Results arrive as steering messages; do not poll crew_list or fabricate results.",
|
|
34
26
|
"crew_spawn: Use the bundled pi-crew skill for detailed delegation patterns.",
|
|
35
27
|
],
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const available =
|
|
46
|
-
agents.map((candidate) => candidate.name).join(", ") || "none";
|
|
47
|
-
return toolError(
|
|
48
|
-
`Unknown subagent: "${params.subagent}". Available: ${available}`,
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
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
|
-
);
|
|
67
|
-
|
|
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
|
-
},
|
|
73
|
-
|
|
28
|
+
action: (params, actionCtx, ctx) =>
|
|
29
|
+
deps.actions.spawn(params, {
|
|
30
|
+
...actionCtx,
|
|
31
|
+
model: ctx.model,
|
|
32
|
+
modelRegistry: ctx.modelRegistry,
|
|
33
|
+
agentDir: getAgentDir(),
|
|
34
|
+
parentSessionFile: ctx.sessionManager.getSessionFile(),
|
|
35
|
+
onWarning: (msg) => ctx.ui.notify(msg, "warning"),
|
|
36
|
+
}),
|
|
74
37
|
renderCall(args, theme, _context) {
|
|
75
38
|
return renderCrewCall(
|
|
76
39
|
theme,
|
|
@@ -79,9 +42,5 @@ export function registerCrewSpawnTool({
|
|
|
79
42
|
args.task,
|
|
80
43
|
);
|
|
81
44
|
},
|
|
82
|
-
|
|
83
|
-
renderResult(result, _options, theme, _context) {
|
|
84
|
-
return renderCrewResult(result, theme);
|
|
85
|
-
},
|
|
86
45
|
});
|
|
87
46
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export type SendMessageFn = ExtensionAPI["sendMessage"];
|
|
4
|
+
type Message = Parameters<SendMessageFn>[0];
|
|
5
|
+
|
|
6
|
+
interface DeliveryOptions {
|
|
7
|
+
isIdle: boolean;
|
|
8
|
+
triggerTurn: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function sendWithDeliveryPolicy(
|
|
12
|
+
message: Message,
|
|
13
|
+
sendMessage: SendMessageFn,
|
|
14
|
+
opts: DeliveryOptions,
|
|
15
|
+
): void {
|
|
16
|
+
sendMessage(
|
|
17
|
+
message,
|
|
18
|
+
opts.isIdle
|
|
19
|
+
? { triggerTurn: opts.triggerTurn }
|
|
20
|
+
: { deliverAs: "steer", triggerTurn: opts.triggerTurn },
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type { AgentSession, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
1
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
4
3
|
import type { AgentConfig } from "../agent-discovery.js";
|
|
5
4
|
import type { BootstrapContext } from "../bootstrap-session.js";
|
|
6
|
-
import {
|
|
7
|
-
import type { SubagentStatus } from "../subagent-messages.js";
|
|
8
|
-
import { type ActiveRuntimeBinding, DeliveryCoordinator } from "./delivery-coordinator.js";
|
|
9
|
-
import { runPromptWithOverflowRecovery } from "./overflow-recovery.js";
|
|
10
|
-
import { SubagentRegistry } from "./subagent-registry.js";
|
|
5
|
+
import { type ActiveRuntimeBinding, OwnerSessionCoordinator } from "./owner-session-coordinator.js";
|
|
11
6
|
import {
|
|
12
7
|
type ActiveAgentSummary,
|
|
8
|
+
SubagentRegistry,
|
|
13
9
|
type SubagentState,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
} from "./subagent-registry.js";
|
|
11
|
+
import { SubagentLifecycle } from "./subagent-lifecycle.js";
|
|
12
|
+
import {
|
|
13
|
+
type SettledSubagentStatus,
|
|
14
|
+
canAbortSubagent,
|
|
15
|
+
settleSubagent,
|
|
16
|
+
startSubagentResponse,
|
|
17
|
+
validateSubagentDone,
|
|
18
|
+
} from "./subagent-transitions.js";
|
|
17
19
|
|
|
18
20
|
export type {
|
|
19
21
|
ActiveAgentSummary,
|
|
20
|
-
} from "./subagent-
|
|
22
|
+
} from "./subagent-registry.js";
|
|
21
23
|
|
|
22
24
|
export interface AbortOwnedResult {
|
|
23
25
|
abortedIds: string[];
|
|
@@ -46,63 +48,6 @@ function toBootstrapContext(ctx: SpawnContext): BootstrapContext {
|
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
interface PromptOutcome {
|
|
50
|
-
status: Extract<SubagentStatus, "done" | "waiting" | "error" | "aborted">;
|
|
51
|
-
result?: string;
|
|
52
|
-
error?: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getLastAssistantMessage(
|
|
56
|
-
messages: AgentMessage[],
|
|
57
|
-
): AssistantMessage | undefined {
|
|
58
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
59
|
-
const msg = messages[i];
|
|
60
|
-
if (msg.role === "assistant") {
|
|
61
|
-
return msg as AssistantMessage;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function getAssistantText(
|
|
68
|
-
message: AssistantMessage | undefined,
|
|
69
|
-
): string | undefined {
|
|
70
|
-
if (!message) return undefined;
|
|
71
|
-
|
|
72
|
-
const texts: string[] = [];
|
|
73
|
-
for (const part of message.content) {
|
|
74
|
-
if (part.type === "text") {
|
|
75
|
-
texts.push(part.text);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return texts.length > 0 ? texts.join("\n") : undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function getPromptOutcome(state: SubagentState): PromptOutcome {
|
|
83
|
-
const lastAssistant = getLastAssistantMessage(state.session!.messages);
|
|
84
|
-
const text = getAssistantText(lastAssistant);
|
|
85
|
-
|
|
86
|
-
if (lastAssistant?.stopReason === "error") {
|
|
87
|
-
return {
|
|
88
|
-
status: "error",
|
|
89
|
-
error: lastAssistant.errorMessage ?? text ?? "(no output)",
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (lastAssistant?.stopReason === "aborted") {
|
|
94
|
-
return {
|
|
95
|
-
status: "aborted",
|
|
96
|
-
error: lastAssistant.errorMessage ?? text ?? "(no output)",
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
status: state.agentConfig.interactive ? "waiting" : "done",
|
|
102
|
-
result: text ?? "(no output)",
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
51
|
/**
|
|
107
52
|
* Process-level singleton that owns all durable subagent state.
|
|
108
53
|
*
|
|
@@ -113,11 +58,26 @@ function getPromptOutcome(state: SubagentState): PromptOutcome {
|
|
|
113
58
|
*/
|
|
114
59
|
class CrewRuntime {
|
|
115
60
|
private readonly registry = new SubagentRegistry();
|
|
116
|
-
private readonly
|
|
61
|
+
private readonly ownerSessions: OwnerSessionCoordinator;
|
|
62
|
+
private readonly lifecycle: SubagentLifecycle;
|
|
117
63
|
|
|
118
64
|
// Per-session refresh callbacks, keyed by ownerSessionId
|
|
119
65
|
private readonly refreshCallbacks = new Map<string, () => void>();
|
|
120
66
|
|
|
67
|
+
constructor() {
|
|
68
|
+
this.ownerSessions = new OwnerSessionCoordinator({
|
|
69
|
+
countRunningForOwner: (ownerSessionId, excludeId) =>
|
|
70
|
+
this.registry.countRunningForOwner(ownerSessionId, excludeId),
|
|
71
|
+
onRefreshOwnerSession: (ownerSessionId) => this.refreshWidgetFor(ownerSessionId),
|
|
72
|
+
});
|
|
73
|
+
this.lifecycle = new SubagentLifecycle({
|
|
74
|
+
isCurrent: (state) => this.registry.hasState(state),
|
|
75
|
+
onProgress: (ownerSessionId) => this.ownerSessions.refresh(ownerSessionId),
|
|
76
|
+
onSettled: (state, status, outcome) =>
|
|
77
|
+
this.settleAgent(state, status, outcome),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
121
81
|
private refreshWidgetFor(sessionId: string): void {
|
|
122
82
|
this.refreshCallbacks.get(sessionId)?.();
|
|
123
83
|
}
|
|
@@ -129,16 +89,12 @@ class CrewRuntime {
|
|
|
129
89
|
if (refreshWidget) {
|
|
130
90
|
this.refreshCallbacks.set(binding.sessionId, refreshWidget);
|
|
131
91
|
}
|
|
132
|
-
this.
|
|
133
|
-
binding,
|
|
134
|
-
(ownerSessionId, excludeId) =>
|
|
135
|
-
this.registry.countRunningForOwner(ownerSessionId, excludeId),
|
|
136
|
-
);
|
|
92
|
+
this.ownerSessions.activateSession(binding);
|
|
137
93
|
refreshWidget?.();
|
|
138
94
|
}
|
|
139
95
|
|
|
140
96
|
deactivateSession(sessionId: string): void {
|
|
141
|
-
this.
|
|
97
|
+
this.ownerSessions.deactivateSession(sessionId);
|
|
142
98
|
this.refreshCallbacks.delete(sessionId);
|
|
143
99
|
}
|
|
144
100
|
|
|
@@ -151,57 +107,24 @@ class CrewRuntime {
|
|
|
151
107
|
extensionResolvedPath: string,
|
|
152
108
|
): string {
|
|
153
109
|
const state = this.registry.create(agentConfig, task, ownerSessionId);
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
state,
|
|
110
|
+
this.ownerSessions.refresh(ownerSessionId);
|
|
111
|
+
this.lifecycle.start(state, {
|
|
157
112
|
cwd,
|
|
158
|
-
ctx,
|
|
113
|
+
ctx: toBootstrapContext(ctx),
|
|
159
114
|
extensionResolvedPath,
|
|
160
|
-
|
|
161
|
-
return state.id;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private attachSessionListeners(
|
|
165
|
-
state: SubagentState,
|
|
166
|
-
session: AgentSession,
|
|
167
|
-
): void {
|
|
168
|
-
state.unsubscribe = session.subscribe((event) => {
|
|
169
|
-
if (event.type !== "turn_end") return;
|
|
170
|
-
|
|
171
|
-
state.turns++;
|
|
172
|
-
const msg = event.message;
|
|
173
|
-
if (msg.role === "assistant") {
|
|
174
|
-
const assistantMsg = msg as AssistantMessage;
|
|
175
|
-
state.contextTokens = assistantMsg.usage.totalTokens;
|
|
176
|
-
state.model = assistantMsg.model;
|
|
177
|
-
}
|
|
178
|
-
this.refreshWidgetFor(state.ownerSessionId);
|
|
115
|
+
onWarning: ctx.onWarning,
|
|
179
116
|
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
private attachSpawnedSession(
|
|
183
|
-
state: SubagentState,
|
|
184
|
-
session: AgentSession,
|
|
185
|
-
): boolean {
|
|
186
|
-
if (!this.registry.hasState(state)) {
|
|
187
|
-
session.dispose();
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
state.session = session;
|
|
192
|
-
return true;
|
|
117
|
+
return state.id;
|
|
193
118
|
}
|
|
194
119
|
|
|
195
120
|
private settleAgent(
|
|
196
121
|
state: SubagentState,
|
|
197
|
-
nextStatus:
|
|
122
|
+
nextStatus: SettledSubagentStatus,
|
|
198
123
|
opts: { result?: string; error?: string },
|
|
199
124
|
): void {
|
|
200
|
-
state
|
|
201
|
-
state.result = opts.result;
|
|
202
|
-
state.error = opts.error;
|
|
125
|
+
settleSubagent(state, nextStatus, opts);
|
|
203
126
|
|
|
204
|
-
this.
|
|
127
|
+
this.ownerSessions.deliver(
|
|
205
128
|
state.ownerSessionId,
|
|
206
129
|
{
|
|
207
130
|
id: state.id,
|
|
@@ -211,14 +134,12 @@ class CrewRuntime {
|
|
|
211
134
|
result: state.result,
|
|
212
135
|
error: state.error,
|
|
213
136
|
},
|
|
214
|
-
(ownerSessionId, excludeId) =>
|
|
215
|
-
this.registry.countRunningForOwner(ownerSessionId, excludeId),
|
|
216
137
|
);
|
|
217
138
|
|
|
218
139
|
if (state.status !== "waiting") {
|
|
219
140
|
this.disposeAgent(state);
|
|
220
141
|
} else {
|
|
221
|
-
this.
|
|
142
|
+
this.ownerSessions.refresh(state.ownerSessionId);
|
|
222
143
|
}
|
|
223
144
|
}
|
|
224
145
|
|
|
@@ -227,128 +148,44 @@ class CrewRuntime {
|
|
|
227
148
|
state.promptAbortController = undefined;
|
|
228
149
|
state.session?.dispose();
|
|
229
150
|
this.registry.delete(state.id);
|
|
230
|
-
this.
|
|
151
|
+
this.ownerSessions.refresh(state.ownerSessionId);
|
|
231
152
|
}
|
|
232
153
|
|
|
233
|
-
private async runPromptCycle(
|
|
234
|
-
state: SubagentState,
|
|
235
|
-
prompt: string,
|
|
236
|
-
): Promise<void> {
|
|
237
|
-
if (isAborted(state)) return;
|
|
238
|
-
|
|
239
|
-
const abortController = new AbortController();
|
|
240
|
-
state.promptAbortController = abortController;
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
const recovery = await runPromptWithOverflowRecovery(
|
|
244
|
-
state.session!,
|
|
245
|
-
prompt,
|
|
246
|
-
abortController.signal,
|
|
247
|
-
);
|
|
248
|
-
if (isAborted(state)) return;
|
|
249
|
-
|
|
250
|
-
const outcome = getPromptOutcome(state);
|
|
251
|
-
|
|
252
|
-
if (recovery === "failed" && outcome.status !== "error") {
|
|
253
|
-
this.settleAgent(state, "error", {
|
|
254
|
-
error: "Context overflow recovery failed",
|
|
255
|
-
});
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
this.settleAgent(state, outcome.status, outcome);
|
|
260
|
-
} catch (err) {
|
|
261
|
-
if (isAborted(state)) return;
|
|
262
|
-
|
|
263
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
264
|
-
this.settleAgent(state, "error", { error });
|
|
265
|
-
} finally {
|
|
266
|
-
state.promptAbortController = undefined;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private async spawnSession(
|
|
271
|
-
state: SubagentState,
|
|
272
|
-
cwd: string,
|
|
273
|
-
ctx: SpawnContext,
|
|
274
|
-
extensionResolvedPath: string,
|
|
275
|
-
): Promise<void> {
|
|
276
|
-
try {
|
|
277
|
-
if (isAborted(state)) return;
|
|
278
|
-
|
|
279
|
-
const { session, warnings } = await bootstrapSession({
|
|
280
|
-
agentConfig: state.agentConfig,
|
|
281
|
-
cwd,
|
|
282
|
-
ctx: toBootstrapContext(ctx),
|
|
283
|
-
extensionResolvedPath,
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// Emit bootstrap warnings to UI
|
|
287
|
-
for (const warning of warnings) {
|
|
288
|
-
ctx.onWarning?.(warning);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (!this.attachSpawnedSession(state, session)) return;
|
|
292
|
-
|
|
293
|
-
this.attachSessionListeners(state, session);
|
|
294
|
-
await this.runPromptCycle(state, state.task);
|
|
295
|
-
} catch (err) {
|
|
296
|
-
if (isAborted(state)) return;
|
|
297
|
-
|
|
298
|
-
if (state.status === "running") {
|
|
299
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
300
|
-
this.settleAgent(state, "error", { error });
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
154
|
|
|
305
155
|
respond(
|
|
306
156
|
id: string,
|
|
307
157
|
message: string,
|
|
308
158
|
callerSessionId: string,
|
|
309
159
|
): { error?: string } {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
return {
|
|
317
|
-
error: `Subagent "${id}" is not waiting for a response (status: ${state.status})`,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
if (!state.session)
|
|
321
|
-
return { error: `Subagent "${id}" has no active session` };
|
|
160
|
+
const transition = startSubagentResponse(
|
|
161
|
+
this.registry.get(id),
|
|
162
|
+
id,
|
|
163
|
+
callerSessionId,
|
|
164
|
+
);
|
|
165
|
+
if (!transition.ok) return { error: transition.error };
|
|
322
166
|
|
|
323
|
-
state.
|
|
324
|
-
this.
|
|
325
|
-
void this.runPromptCycle(state, message);
|
|
167
|
+
this.ownerSessions.refresh(transition.state.ownerSessionId);
|
|
168
|
+
this.lifecycle.respond(transition.state, message);
|
|
326
169
|
return {};
|
|
327
170
|
}
|
|
328
171
|
|
|
329
172
|
done(id: string, callerSessionId: string): { error?: string } {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
return { error: `Subagent "${id}" is not in waiting state` };
|
|
337
|
-
}
|
|
173
|
+
const transition = validateSubagentDone(
|
|
174
|
+
this.registry.get(id),
|
|
175
|
+
id,
|
|
176
|
+
callerSessionId,
|
|
177
|
+
);
|
|
178
|
+
if (!transition.ok) return { error: transition.error };
|
|
338
179
|
|
|
339
|
-
this.disposeAgent(state);
|
|
180
|
+
this.disposeAgent(transition.state);
|
|
340
181
|
return {};
|
|
341
182
|
}
|
|
342
183
|
|
|
343
184
|
abort(id: string, opts: AbortOptions): boolean {
|
|
344
185
|
const state = this.registry.get(id);
|
|
345
|
-
if (!
|
|
186
|
+
if (!canAbortSubagent(state)) return false;
|
|
346
187
|
|
|
347
|
-
|
|
348
|
-
state.promptAbortController = undefined;
|
|
349
|
-
state.session?.abortCompaction();
|
|
350
|
-
state.session?.abortRetry();
|
|
351
|
-
state.session?.abort().catch(() => {});
|
|
188
|
+
this.lifecycle.abortPrompt(state);
|
|
352
189
|
this.settleAgent(state, "aborted", { error: opts.reason });
|
|
353
190
|
return true;
|
|
354
191
|
}
|
|
@@ -369,7 +206,7 @@ class CrewRuntime {
|
|
|
369
206
|
|
|
370
207
|
for (const id of uniqueIds) {
|
|
371
208
|
const state = this.registry.get(id);
|
|
372
|
-
if (!
|
|
209
|
+
if (!canAbortSubagent(state)) {
|
|
373
210
|
result.missingIds.push(id);
|
|
374
211
|
continue;
|
|
375
212
|
}
|