@narumitw/pi-subagents 1.0.1 → 2.0.0
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 +198 -188
- package/package.json +8 -8
- package/src/agents/built-ins.ts +13 -66
- package/src/agents/catalog.ts +19 -2
- package/src/agents/discovery.ts +31 -15
- package/src/auto-transport.ts +7 -1
- package/src/child-peer-bridge.ts +124 -0
- package/src/child-peer-tools.ts +132 -0
- package/src/completion-delivery.ts +19 -5
- package/src/completion-render.ts +189 -0
- package/src/completion-routing.ts +24 -0
- package/src/config-registration.ts +29 -4
- package/src/config-ui.ts +11 -17
- package/src/consult-registration.ts +3 -2
- package/src/consult-render.ts +1 -1
- package/src/create-stateful-transport.ts +15 -2
- package/src/execution-ui.ts +0 -72
- package/src/in-process-transport.ts +39 -7
- package/src/inspect-tool.ts +3 -1
- package/src/panel-planning.ts +2 -2
- package/src/panel-presets.ts +3 -0
- package/src/params.ts +1 -1
- package/src/peer-communication.ts +352 -0
- package/src/peer-transport.ts +49 -0
- package/src/persistence.ts +26 -1
- package/src/pi-args.ts +2 -0
- package/src/registry-types.ts +7 -0
- package/src/registry.ts +240 -41
- package/src/render.ts +2 -41
- package/src/result-contract.ts +20 -5
- package/src/rpc-transport.ts +56 -26
- package/src/runner.ts +13 -1
- package/src/settings.ts +4 -1
- package/src/spawn-idempotency.ts +2 -0
- package/src/stateful-agent-view.ts +3 -1
- package/src/stateful-guidance.ts +11 -11
- package/src/stateful-safety.ts +0 -45
- package/src/stateful-tool-params.ts +11 -3
- package/src/stateful.ts +359 -136
- package/src/subagents.ts +7 -9
- package/src/subprocess-transport.ts +49 -28
- package/src/task-path.ts +65 -0
- package/src/transport-ui.ts +0 -6
- package/src/transport.ts +2 -1
- package/src/usage-format.ts +42 -0
- package/src/workflow-ui.ts +4 -4
- package/src/automation-contract.ts +0 -709
- package/src/automation-planner.ts +0 -65
- package/src/automation-registration.ts +0 -137
- package/src/automation-tool.ts +0 -40
- package/src/automation.ts +0 -435
- package/src/execution-profiles.ts +0 -95
- package/src/workflow-plan-compiler.ts +0 -618
- package/src/workflow-plan-patch.ts +0 -636
- package/src/workflow-planning-benchmark.ts +0 -95
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { MessageRenderer, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Box, type Component, Spacer, sliceByColumn, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { MAX_TOOL_MESSAGE_BYTES } from "./limits.js";
|
|
4
|
+
import {
|
|
5
|
+
COLLAPSED_LIST_LIMIT,
|
|
6
|
+
expansionHint,
|
|
7
|
+
type RenderStatus,
|
|
8
|
+
recordList,
|
|
9
|
+
recordValue,
|
|
10
|
+
safeBlock,
|
|
11
|
+
safeLine,
|
|
12
|
+
statusBadge,
|
|
13
|
+
} from "./render-common.js";
|
|
14
|
+
|
|
15
|
+
export const SUBAGENT_COMPLETION_MESSAGE_TYPE = "pi-subagent-completion";
|
|
16
|
+
|
|
17
|
+
export const renderCompletionMessage: MessageRenderer = (message, options, theme) => {
|
|
18
|
+
const box = new Box(options.outputPad, 1, (text) => theme.bg("customMessageBg", text));
|
|
19
|
+
if (options.expanded) {
|
|
20
|
+
box.addChild(
|
|
21
|
+
new ExactText(
|
|
22
|
+
theme.fg("customMessageLabel", theme.bold(`[${SUBAGENT_COMPLETION_MESSAGE_TYPE}]`)),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
box.addChild(new Spacer(1));
|
|
26
|
+
box.addChild(
|
|
27
|
+
new ExactText(
|
|
28
|
+
safeBlock(messageText(message.content), "(no completion content)", MAX_TOOL_MESSAGE_BYTES),
|
|
29
|
+
(text) => theme.fg("customMessageText", text),
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
return box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
box.addChild(new ExactText(collapsedCompletion(message.content, message.details, theme)));
|
|
36
|
+
return box;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function collapsedCompletion(contentValue: unknown, detailsValue: unknown, theme: Theme): string {
|
|
40
|
+
const content = safeBlock(messageText(contentValue), "", MAX_TOOL_MESSAGE_BYTES);
|
|
41
|
+
const details = recordValue(detailsValue) ?? {};
|
|
42
|
+
const completions = recordList(details.completions);
|
|
43
|
+
if (completions.length > 0 || details.completionCount !== undefined) {
|
|
44
|
+
return collapsedBatch(details, completions, theme);
|
|
45
|
+
}
|
|
46
|
+
return collapsedSingle(content, details, theme);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function collapsedSingle(content: string, details: Record<string, unknown>, theme: Theme): string {
|
|
50
|
+
const agent = optionalLine(details.agent) || extractedField(content, "Agent") || "subagent";
|
|
51
|
+
const state = optionalLine(details.state) || extractedField(content, "State") || "completed";
|
|
52
|
+
const task = optionalLine(details.task) || extractedField(content, "Task");
|
|
53
|
+
const payload = payloadPreview(content);
|
|
54
|
+
const lines = [
|
|
55
|
+
`${statusBadge(theme, renderStatus(state))}${theme.fg("muted", " · ")}${theme.fg("customMessageLabel", theme.bold(agent))}`,
|
|
56
|
+
];
|
|
57
|
+
if (task) lines.push(`${theme.fg("muted", "Task: ")}${theme.fg("customMessageText", task)}`);
|
|
58
|
+
if (payload) {
|
|
59
|
+
lines.push(`${theme.fg("muted", "Payload: ")}${theme.fg("customMessageText", payload)}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push(expansionHint());
|
|
62
|
+
return lines.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function collapsedBatch(
|
|
66
|
+
details: Record<string, unknown>,
|
|
67
|
+
completions: Record<string, unknown>[],
|
|
68
|
+
theme: Theme,
|
|
69
|
+
): string {
|
|
70
|
+
const declaredCount =
|
|
71
|
+
typeof details.completionCount === "number" && Number.isFinite(details.completionCount)
|
|
72
|
+
? Math.max(0, Math.floor(details.completionCount))
|
|
73
|
+
: completions.length;
|
|
74
|
+
const count = Math.max(declaredCount, completions.length);
|
|
75
|
+
const statuses = completions.map((completion) => renderStatus(optionalLine(completion.state)));
|
|
76
|
+
const icon = statuses.includes("failed")
|
|
77
|
+
? theme.fg("error", "✗")
|
|
78
|
+
: statuses.some((status) => status !== "completed" && status !== "closed")
|
|
79
|
+
? theme.fg("warning", "◐")
|
|
80
|
+
: theme.fg("success", "✓");
|
|
81
|
+
const lines = [
|
|
82
|
+
`${icon} ${theme.fg("customMessageLabel", theme.bold(`${count} subagent completions`))}`,
|
|
83
|
+
];
|
|
84
|
+
for (const completion of completions.slice(0, COLLAPSED_LIST_LIMIT)) {
|
|
85
|
+
const agent = optionalLine(completion.agent) || "subagent";
|
|
86
|
+
const state = optionalLine(completion.state) || "completed";
|
|
87
|
+
const task = optionalLine(completion.task);
|
|
88
|
+
lines.push(
|
|
89
|
+
`${theme.fg("muted", "• ")}${theme.fg("accent", agent)} · ${statusBadge(theme, renderStatus(state))}${task ? theme.fg("dim", ` — ${task}`) : ""}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const visibleCount = Math.min(completions.length, COLLAPSED_LIST_LIMIT);
|
|
93
|
+
if (count > visibleCount) {
|
|
94
|
+
lines.push(theme.fg("muted", `… ${count - visibleCount} more`));
|
|
95
|
+
}
|
|
96
|
+
lines.push(expansionHint());
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function messageText(value: unknown): string {
|
|
101
|
+
if (typeof value === "string") return value;
|
|
102
|
+
if (!Array.isArray(value)) return "";
|
|
103
|
+
return value
|
|
104
|
+
.flatMap((part) => {
|
|
105
|
+
const record = recordValue(part);
|
|
106
|
+
return record?.type === "text" && typeof record.text === "string" ? [record.text] : [];
|
|
107
|
+
})
|
|
108
|
+
.join("\n");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extractedField(content: string, label: string): string {
|
|
112
|
+
const prefix = `${label}:`;
|
|
113
|
+
const line = content.split("\n").find((candidate) => candidate.startsWith(prefix));
|
|
114
|
+
return line ? safeLine(line.slice(prefix.length), "", 512) : "";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function payloadPreview(content: string): string {
|
|
118
|
+
const marker = "\nPayload:\n";
|
|
119
|
+
const start = content.indexOf(marker);
|
|
120
|
+
if (start < 0) return "";
|
|
121
|
+
for (const line of content.slice(start + marker.length).split("\n")) {
|
|
122
|
+
const preview = safeLine(line, "", 1024);
|
|
123
|
+
if (preview) return preview;
|
|
124
|
+
}
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function optionalLine(value: unknown): string {
|
|
129
|
+
return typeof value === "string" ? safeLine(value, "", 512) : "";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderStatus(state: string): RenderStatus {
|
|
133
|
+
switch (state) {
|
|
134
|
+
case "failed":
|
|
135
|
+
return "failed";
|
|
136
|
+
case "cancelled":
|
|
137
|
+
return "cancelled";
|
|
138
|
+
case "interrupted":
|
|
139
|
+
return "interrupted";
|
|
140
|
+
case "closed":
|
|
141
|
+
return "closed";
|
|
142
|
+
case "blocked":
|
|
143
|
+
case "needs-input":
|
|
144
|
+
case "abstained":
|
|
145
|
+
case "stale":
|
|
146
|
+
return "warning";
|
|
147
|
+
default:
|
|
148
|
+
return "completed";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
class ExactText implements Component {
|
|
153
|
+
constructor(
|
|
154
|
+
private readonly value: string,
|
|
155
|
+
private readonly style: (text: string) => string = (text) => text,
|
|
156
|
+
) {}
|
|
157
|
+
|
|
158
|
+
render(width: number): string[] {
|
|
159
|
+
const safeWidth = Math.max(1, width);
|
|
160
|
+
return this.value
|
|
161
|
+
.split("\n")
|
|
162
|
+
.flatMap((line) => hardWrapExact(line, safeWidth))
|
|
163
|
+
.map(this.style);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
invalidate(): void {}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function hardWrapExact(value: string, width: number): string[] {
|
|
170
|
+
if (value.length === 0) return [""];
|
|
171
|
+
const columns = visibleWidth(value);
|
|
172
|
+
if (columns === 0) return [value];
|
|
173
|
+
const lines: string[] = [];
|
|
174
|
+
let column = 0;
|
|
175
|
+
while (column < columns) {
|
|
176
|
+
const chunk = sliceByColumn(value, column, width, true);
|
|
177
|
+
const chunkWidth = visibleWidth(chunk);
|
|
178
|
+
if (chunkWidth > 0) {
|
|
179
|
+
lines.push(chunk);
|
|
180
|
+
column += chunkWidth;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const oversized = sliceByColumn(value, column, width, false);
|
|
184
|
+
const oversizedWidth = visibleWidth(oversized);
|
|
185
|
+
lines.push("?".repeat(width));
|
|
186
|
+
column += Math.max(1, oversizedWidth);
|
|
187
|
+
}
|
|
188
|
+
return lines;
|
|
189
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ManagedAgent, PersistedAgentCompletion } from "./registry-types.js";
|
|
2
|
+
import { ROOT_TASK_PATH } from "./task-path.js";
|
|
3
|
+
|
|
4
|
+
export type CompletionRecipient = Pick<PersistedAgentCompletion, "recipientId" | "recipientPath">;
|
|
5
|
+
|
|
6
|
+
export function resolveCompletionRecipient(
|
|
7
|
+
agent: Pick<ManagedAgent, "id" | "parentId">,
|
|
8
|
+
getAgent: (
|
|
9
|
+
id: string,
|
|
10
|
+
) => Pick<ManagedAgent, "id" | "parentId" | "state" | "taskPath"> | undefined,
|
|
11
|
+
): CompletionRecipient {
|
|
12
|
+
const visited = new Set([agent.id]);
|
|
13
|
+
let parentId = agent.parentId;
|
|
14
|
+
while (parentId && !visited.has(parentId)) {
|
|
15
|
+
visited.add(parentId);
|
|
16
|
+
const parent = getAgent(parentId);
|
|
17
|
+
if (!parent) break;
|
|
18
|
+
if (parent.state !== "closed") {
|
|
19
|
+
return { recipientId: parent.id, recipientPath: parent.taskPath ?? parent.id };
|
|
20
|
+
}
|
|
21
|
+
parentId = parent.parentId;
|
|
22
|
+
}
|
|
23
|
+
return { recipientId: "root", recipientPath: ROOT_TASK_PATH };
|
|
24
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { cachedModuleLoader } from "./cached-module-loader.js";
|
|
3
|
-
import { showSubagentHelp, showSubagentStatus } from "./config-status.js";
|
|
4
3
|
import type { SubagentMenuOwner, SubagentSettingsRuntime } from "./config-ui.js";
|
|
5
4
|
|
|
6
5
|
const SUBCOMMANDS = [
|
|
@@ -14,8 +13,14 @@ type ConfigUiModule = Pick<
|
|
|
14
13
|
"showSubagentManager" | "showSubagentSettings"
|
|
15
14
|
>;
|
|
16
15
|
|
|
16
|
+
type ConfigStatusModule = Pick<
|
|
17
|
+
typeof import("./config-status.js"),
|
|
18
|
+
"showSubagentHelp" | "showSubagentStatus"
|
|
19
|
+
>;
|
|
20
|
+
|
|
17
21
|
export interface ConfigRegistrationDependencies {
|
|
18
22
|
loadConfigUi?: () => Promise<ConfigUiModule>;
|
|
23
|
+
loadConfigStatus?: () => Promise<ConfigStatusModule>;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export function registerSubagentConfigLifecycle(pi: ExtensionAPI): SubagentMenuOwner {
|
|
@@ -41,6 +46,9 @@ export function registerSubagentConfigCommand(
|
|
|
41
46
|
const loadConfigUi = cachedModuleLoader(
|
|
42
47
|
dependencies.loadConfigUi ?? (() => import("./config-ui.js")),
|
|
43
48
|
);
|
|
49
|
+
const loadConfigStatus = cachedModuleLoader<ConfigStatusModule>(
|
|
50
|
+
dependencies.loadConfigStatus ?? (() => import("./config-status.js")),
|
|
51
|
+
);
|
|
44
52
|
pi.registerCommand("subagents", {
|
|
45
53
|
description: "Manage current-session subagents and user settings",
|
|
46
54
|
getArgumentCompletions(prefix: string) {
|
|
@@ -50,16 +58,33 @@ export function registerSubagentConfigCommand(
|
|
|
50
58
|
},
|
|
51
59
|
async handler(args, ctx) {
|
|
52
60
|
const subcommand = args.trim().toLowerCase();
|
|
61
|
+
const runStatusCommand = async (show: (status: ConfigStatusModule) => void) => {
|
|
62
|
+
const generation = owner.generation;
|
|
63
|
+
const controller = owner.controller;
|
|
64
|
+
const isCurrent = () =>
|
|
65
|
+
generation === owner.generation &&
|
|
66
|
+
controller === owner.controller &&
|
|
67
|
+
!controller.signal.aborted;
|
|
68
|
+
let status: ConfigStatusModule;
|
|
69
|
+
try {
|
|
70
|
+
status = await loadConfigStatus();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (!isCurrent()) return;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (!isCurrent()) return;
|
|
76
|
+
show(status);
|
|
77
|
+
};
|
|
53
78
|
if (!subcommand && ctx.mode !== "tui") {
|
|
54
|
-
showSubagentStatus(ctx, runtime);
|
|
79
|
+
await runStatusCommand((status) => status.showSubagentStatus(ctx, runtime));
|
|
55
80
|
return;
|
|
56
81
|
}
|
|
57
82
|
if (subcommand === "status") {
|
|
58
|
-
showSubagentStatus(ctx, runtime);
|
|
83
|
+
await runStatusCommand((status) => status.showSubagentStatus(ctx, runtime));
|
|
59
84
|
return;
|
|
60
85
|
}
|
|
61
86
|
if (subcommand === "help") {
|
|
62
|
-
showSubagentHelp(ctx, runtime);
|
|
87
|
+
await runStatusCommand((status) => status.showSubagentHelp(ctx, runtime));
|
|
63
88
|
return;
|
|
64
89
|
}
|
|
65
90
|
if (!subcommand || subcommand === "settings") {
|
package/src/config-ui.ts
CHANGED
|
@@ -22,12 +22,10 @@ import {
|
|
|
22
22
|
applyAgentModel,
|
|
23
23
|
applyAgentThinking,
|
|
24
24
|
applyAgentTimeout,
|
|
25
|
-
applyExecutionProfileFromUi,
|
|
26
25
|
executionAgentPickerScreen,
|
|
27
26
|
executionAgentScreen,
|
|
28
27
|
executionModelInputScreen,
|
|
29
28
|
executionModelScreen,
|
|
30
|
-
executionProfileScreen,
|
|
31
29
|
executionThinkingScreen,
|
|
32
30
|
executionTimeoutInputScreen,
|
|
33
31
|
resetAgentExecution,
|
|
@@ -195,7 +193,6 @@ export async function showSubagentManager(
|
|
|
195
193
|
| "performance"
|
|
196
194
|
| "responsiveness"
|
|
197
195
|
| "transport"
|
|
198
|
-
| "execution-profiles"
|
|
199
196
|
| "execution-agent-picker"
|
|
200
197
|
| "execution-agent"
|
|
201
198
|
| "execution-thinking"
|
|
@@ -213,7 +210,6 @@ export async function showSubagentManager(
|
|
|
213
210
|
| "set-workflow"
|
|
214
211
|
| "clear-agents"
|
|
215
212
|
| "set-transport"
|
|
216
|
-
| "apply-execution-profile"
|
|
217
213
|
| "pick-execution-agent"
|
|
218
214
|
| "set-agent-thinking"
|
|
219
215
|
| "set-agent-model"
|
|
@@ -246,7 +242,7 @@ export async function showSubagentManager(
|
|
|
246
242
|
{
|
|
247
243
|
id: "workflow",
|
|
248
244
|
label: "Change delegation",
|
|
249
|
-
description: "Choose
|
|
245
|
+
description: "Choose async only (recommended) or a compatibility workflow",
|
|
250
246
|
to: "workflow",
|
|
251
247
|
},
|
|
252
248
|
{
|
|
@@ -280,6 +276,8 @@ export async function showSubagentManager(
|
|
|
280
276
|
title: "Change Delegation",
|
|
281
277
|
lines: [
|
|
282
278
|
`Current: ${workflowLabel(active)}`,
|
|
279
|
+
"Recommended: Async only keeps the main agent responsive and omits blocking delegation and consultation.",
|
|
280
|
+
"Final-answer-dependent detached work needs automatic resume.",
|
|
283
281
|
...(snapshot.value !== active
|
|
284
282
|
? [`Configured after reload: ${workflowLabel(snapshot.value)}`]
|
|
285
283
|
: []),
|
|
@@ -294,21 +292,21 @@ export async function showSubagentManager(
|
|
|
294
292
|
? []
|
|
295
293
|
: [
|
|
296
294
|
{
|
|
297
|
-
id: "
|
|
298
|
-
label: "
|
|
299
|
-
description: "
|
|
295
|
+
id: "async-only",
|
|
296
|
+
label: "Async only · Recommended",
|
|
297
|
+
description: "Detached lifecycle plus inspection; omit blocking and consultation",
|
|
300
298
|
action: "set-workflow" as const,
|
|
301
299
|
},
|
|
302
300
|
{
|
|
303
|
-
id: "
|
|
304
|
-
label: "
|
|
305
|
-
description: "
|
|
301
|
+
id: "all",
|
|
302
|
+
label: "All delegation methods",
|
|
303
|
+
description: "Compatibility: async, blocking, inspection, and consultation",
|
|
306
304
|
action: "set-workflow" as const,
|
|
307
305
|
},
|
|
308
306
|
{
|
|
309
307
|
id: "blocking-only",
|
|
310
308
|
label: "Blocking only",
|
|
311
|
-
description: "
|
|
309
|
+
description: "Compatibility: blocking and consultation without async lifecycle",
|
|
312
310
|
action: "set-workflow" as const,
|
|
313
311
|
},
|
|
314
312
|
],
|
|
@@ -378,7 +376,7 @@ export async function showSubagentManager(
|
|
|
378
376
|
{
|
|
379
377
|
id: "performance",
|
|
380
378
|
label: "Performance and execution",
|
|
381
|
-
description: "Transport, responsiveness,
|
|
379
|
+
description: "Transport, responsiveness, and agent defaults",
|
|
382
380
|
to: "performance",
|
|
383
381
|
},
|
|
384
382
|
{ id: "back", label: "Back", action: "back" },
|
|
@@ -397,7 +395,6 @@ export async function showSubagentManager(
|
|
|
397
395
|
to: "responsiveness",
|
|
398
396
|
},
|
|
399
397
|
{ id: "transport", label: "Detached transport", to: "transport" },
|
|
400
|
-
{ id: "profiles", label: "Execution profiles", to: "execution-profiles" },
|
|
401
398
|
{
|
|
402
399
|
id: "agents",
|
|
403
400
|
label: "Agent execution defaults",
|
|
@@ -410,7 +407,6 @@ export async function showSubagentManager(
|
|
|
410
407
|
}),
|
|
411
408
|
responsiveness: () => responsivenessSetupScreen(runtime),
|
|
412
409
|
transport: () => transportSettingsScreen(runtime),
|
|
413
|
-
"execution-profiles": () => executionProfileScreen(),
|
|
414
410
|
"execution-agent-picker": () => executionAgentPickerScreen(availableAgents),
|
|
415
411
|
"execution-agent": () => executionAgentScreen(selectedExecutionAgent),
|
|
416
412
|
"execution-thinking": () => executionThinkingScreen(selectedExecutionAgent),
|
|
@@ -568,8 +564,6 @@ export async function showSubagentManager(
|
|
|
568
564
|
},
|
|
569
565
|
"set-transport": async ({ itemId, signal }) =>
|
|
570
566
|
applyTransportSetting(itemId, ctx, runtime, signal, isCurrent),
|
|
571
|
-
"apply-execution-profile": async ({ itemId, signal }) =>
|
|
572
|
-
applyExecutionProfileFromUi(itemId, ctx, signal, isCurrent),
|
|
573
567
|
"pick-execution-agent": async ({ itemId }) => {
|
|
574
568
|
selectedExecutionAgent = availableAgents.find((agent) => agent.name === itemId);
|
|
575
569
|
return selectedExecutionAgent
|
|
@@ -49,9 +49,10 @@ export function registerSubagentConsult(
|
|
|
49
49
|
description: baseDescription(),
|
|
50
50
|
promptSnippet: "Consult one constrained read-only subagent and wait for its answer",
|
|
51
51
|
promptGuidelines: [
|
|
52
|
-
"Use subagent_consult for bounded
|
|
52
|
+
"Use subagent_consult only for bounded read-only evidence gathering when an independent perspective is worth making the main agent wait.",
|
|
53
|
+
"Keep ordinary planning and review in the main agent with applicable skills and deterministic checks; use subagent_consult only when synchronous read-only isolation adds concrete value.",
|
|
53
54
|
"Set subagent_consult timeoutMs to the shortest realistic work deadline for the task difficulty; split oversized consultations instead of extending the deadline merely to compensate for broad scope.",
|
|
54
|
-
"Implementation-shaped tasks remain read-only and can return only analysis or instructions.",
|
|
55
|
+
"Implementation-shaped subagent_consult tasks remain read-only and can return only analysis or instructions.",
|
|
55
56
|
],
|
|
56
57
|
parameters: SubagentConsultParams,
|
|
57
58
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
package/src/consult-render.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import type { ConsultDetails, SubagentConsultParams } from "./consult.js";
|
|
9
|
-
import { formatUsageStats } from "./render.js";
|
|
10
9
|
import {
|
|
11
10
|
COLLAPSED_ANSWER_LINES,
|
|
12
11
|
COLLAPSED_LIST_LIMIT,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
textResult,
|
|
26
25
|
toolHeader,
|
|
27
26
|
} from "./render-common.js";
|
|
27
|
+
import { formatUsageStats } from "./usage-format.js";
|
|
28
28
|
|
|
29
29
|
export function renderConsultCall(args: Partial<SubagentConsultParams>, theme: Theme) {
|
|
30
30
|
const scope = args.agentScope ?? "user";
|
|
@@ -2,7 +2,8 @@ import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { SubagentSettings, SubagentTransportKind } from "./agents/types.js";
|
|
3
3
|
import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
|
|
4
4
|
import type { ChildSessionFactory, ParentRuntimeSnapshot } from "./in-process-transport.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { PeerTransportRuntime } from "./peer-transport.js";
|
|
6
|
+
import type { AgentMailboxMessage, ManagedAgent, TurnOutcome } from "./registry.js";
|
|
6
7
|
import type { SubagentTransport } from "./transport.js";
|
|
7
8
|
import type { TransportProgressCallback } from "./transport-types.js";
|
|
8
9
|
|
|
@@ -12,6 +13,7 @@ export interface CreateStatefulTransportOptions {
|
|
|
12
13
|
getParentRuntime(): ParentRuntimeSnapshot;
|
|
13
14
|
getSettings(): SubagentSettings | undefined;
|
|
14
15
|
createInProcessSession?: ChildSessionFactory;
|
|
16
|
+
peerRuntime?: PeerTransportRuntime;
|
|
15
17
|
loadTransport?: () => Promise<SubagentTransport>;
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -58,6 +60,12 @@ class LazyStatefulTransport implements SubagentTransport {
|
|
|
58
60
|
return transport.runTurn(agent, task, signal, onProgress);
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
async deliverMessage(agent: ManagedAgent, message: AgentMailboxMessage): Promise<boolean> {
|
|
64
|
+
const transport = this.loaded;
|
|
65
|
+
if (!transport?.deliverMessage || this.closed) return false;
|
|
66
|
+
return transport.deliverMessage(agent, message);
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
async release(agent: ManagedAgent): Promise<void> {
|
|
62
70
|
const transport =
|
|
63
71
|
this.loaded ?? (this.loading ? await this.loading.catch(() => undefined) : undefined);
|
|
@@ -100,7 +108,10 @@ async function loadStatefulTransport(
|
|
|
100
108
|
): Promise<SubagentTransport> {
|
|
101
109
|
const subprocess = async () => {
|
|
102
110
|
const { SubprocessTransport } = await import("./subprocess-transport.js");
|
|
103
|
-
return new SubprocessTransport({
|
|
111
|
+
return new SubprocessTransport({
|
|
112
|
+
getSettings: options.getSettings,
|
|
113
|
+
peerRuntime: options.peerRuntime,
|
|
114
|
+
});
|
|
104
115
|
};
|
|
105
116
|
const inProcess = async () => {
|
|
106
117
|
const [{ discoverAgents }, { InProcessTransport }] = await Promise.all([
|
|
@@ -109,6 +120,7 @@ async function loadStatefulTransport(
|
|
|
109
120
|
]);
|
|
110
121
|
return new InProcessTransport({
|
|
111
122
|
modelRegistry: options.modelRegistry,
|
|
123
|
+
peerRuntime: options.peerRuntime,
|
|
112
124
|
getParentRuntime: options.getParentRuntime,
|
|
113
125
|
createSession: options.createInProcessSession,
|
|
114
126
|
discoverAgent: (agent) =>
|
|
@@ -122,6 +134,7 @@ async function loadStatefulTransport(
|
|
|
122
134
|
return new RpcTransport({
|
|
123
135
|
getSettings: options.getSettings,
|
|
124
136
|
getParentRuntime: options.getParentRuntime,
|
|
137
|
+
peerRuntime: options.peerRuntime,
|
|
125
138
|
});
|
|
126
139
|
};
|
|
127
140
|
switch (options.kind) {
|
package/src/execution-ui.ts
CHANGED
|
@@ -1,77 +1,9 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AgentConfig, SubagentThinkingLevel } from "./agents/types.js";
|
|
3
|
-
import {
|
|
4
|
-
applyExecutionProfile,
|
|
5
|
-
EXECUTION_PROFILES,
|
|
6
|
-
type ExecutionProfile,
|
|
7
|
-
executionProfileDescription,
|
|
8
|
-
executionProfileLabel,
|
|
9
|
-
executionProfilePreview,
|
|
10
|
-
inspectExecutionProfile,
|
|
11
|
-
} from "./execution-profiles.js";
|
|
12
3
|
import { MAX_SUBAGENT_TIMEOUT_MS } from "./limits.js";
|
|
13
4
|
import { safeTerminalLine as safeTerminalText } from "./safe-text.js";
|
|
14
5
|
import { readSubagentSettings, updateAgentSettingsPatch } from "./settings.js";
|
|
15
6
|
|
|
16
|
-
export function executionProfileScreen() {
|
|
17
|
-
const current = inspectExecutionProfile();
|
|
18
|
-
return {
|
|
19
|
-
kind: "actions" as const,
|
|
20
|
-
title: "Execution Profiles",
|
|
21
|
-
lines: [
|
|
22
|
-
`Current built-in mapping: ${current === "custom" ? "Custom or inherited" : executionProfileLabel(current)}`,
|
|
23
|
-
"Profiles change only built-in agent thinking defaults.",
|
|
24
|
-
"Models, timeouts, tools, transport, context, and explicit tool-call values are preserved.",
|
|
25
|
-
],
|
|
26
|
-
items: [
|
|
27
|
-
...EXECUTION_PROFILES.map((profile) => ({
|
|
28
|
-
id: profile,
|
|
29
|
-
label: executionProfileLabel(profile),
|
|
30
|
-
description: executionProfileDescription(profile),
|
|
31
|
-
action: "apply-execution-profile" as const,
|
|
32
|
-
})),
|
|
33
|
-
{ id: "back", label: "Back", action: "back" as const },
|
|
34
|
-
],
|
|
35
|
-
hint: "back" as const,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function applyExecutionProfileFromUi(
|
|
40
|
-
profileValue: string,
|
|
41
|
-
ctx: ExtensionCommandContext,
|
|
42
|
-
signal: AbortSignal,
|
|
43
|
-
isCurrent: () => boolean,
|
|
44
|
-
) {
|
|
45
|
-
if (!EXECUTION_PROFILES.includes(profileValue as ExecutionProfile)) {
|
|
46
|
-
return { kind: "rejected" as const };
|
|
47
|
-
}
|
|
48
|
-
const profile = profileValue as ExecutionProfile;
|
|
49
|
-
const before = executionSettingsFingerprint();
|
|
50
|
-
const confirmed = await ctx.ui.confirm(
|
|
51
|
-
`Apply ${executionProfileLabel(profile)} profile?`,
|
|
52
|
-
[
|
|
53
|
-
executionProfileDescription(profile),
|
|
54
|
-
...executionProfilePreview(profile),
|
|
55
|
-
"Existing model, timeout, and tool overrides remain unchanged.",
|
|
56
|
-
].join("\n"),
|
|
57
|
-
{ signal },
|
|
58
|
-
);
|
|
59
|
-
if (signal.aborted || !isCurrent()) return { kind: "close" as const };
|
|
60
|
-
if (!confirmed) return { kind: "rejected" as const };
|
|
61
|
-
if (before !== executionSettingsFingerprint()) {
|
|
62
|
-
ctx.ui.notify("Agent execution settings changed while confirming; review again.", "warning");
|
|
63
|
-
return { kind: "rejected" as const };
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
applyExecutionProfile(profile);
|
|
67
|
-
ctx.ui.notify(`Applied ${executionProfileLabel(profile)} profile.`, "info");
|
|
68
|
-
return { kind: "stay" as const };
|
|
69
|
-
} catch (error) {
|
|
70
|
-
ctx.ui.notify(`Execution profile was not saved: ${formatError(error)}`, "error");
|
|
71
|
-
return { kind: "rejected" as const };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
7
|
export function executionAgentPickerScreen(agents: readonly AgentConfig[]) {
|
|
76
8
|
const configured = readSubagentSettings()?.agents ?? {};
|
|
77
9
|
return {
|
|
@@ -311,10 +243,6 @@ function hasTerminalControl(value: string): boolean {
|
|
|
311
243
|
});
|
|
312
244
|
}
|
|
313
245
|
|
|
314
|
-
function executionSettingsFingerprint(): string {
|
|
315
|
-
return JSON.stringify(readSubagentSettings()?.agents ?? {});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
246
|
function formatError(error: unknown): string {
|
|
319
247
|
return safeTerminalText(error instanceof Error ? error.message : String(error));
|
|
320
248
|
}
|