pi-ui-extend 1.0.39 → 1.0.41
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 +1 -1
- package/dist/app/commands/command-registry.js +2 -2
- package/dist/app/commands/command-session-actions.d.ts +0 -1
- package/dist/app/commands/command-session-actions.js +22 -13
- package/dist/app/icons.d.ts +14 -0
- package/dist/app/icons.js +33 -0
- package/dist/app/rendering/conversation-tool-renderer.js +2 -2
- package/dist/app/rendering/dcp-stats.d.ts +6 -1
- package/dist/app/rendering/dcp-stats.js +214 -46
- package/dist/app/rendering/editor-panels.js +8 -5
- package/dist/app/session/lazy-session-manager.js +12 -1
- package/dist/app/session/tabs-controller.d.ts +2 -5
- package/dist/app/session/tabs-controller.js +12 -21
- package/dist/app/subagents/subagents-model.d.ts +14 -1
- package/dist/app/subagents/subagents-model.js +34 -15
- package/dist/app/types.d.ts +2 -0
- package/dist/bundled-extensions/session-title/config.js +1 -1
- package/dist/markdown-format.js +27 -9
- package/dist/schemas/pi-tools-suite-schema.d.ts +29 -16
- package/dist/schemas/pi-tools-suite-schema.js +46 -31
- package/external/pi-tools-suite/README.md +392 -52
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +31 -21
- package/external/pi-tools-suite/docs/context-gateway-p00-adr.md +216 -0
- package/external/pi-tools-suite/docs/context-gateway-p01n-gate-review.md +122 -0
- package/external/pi-tools-suite/docs/context-gateway-p01n-measurement.md +133 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-ra-evidence.md +111 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rb-evidence.md +100 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rc-evidence.md +69 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rd-evidence.md +100 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-re-evidence.md +74 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rf-evidence.md +153 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rg-evidence.md +235 -0
- package/external/pi-tools-suite/docs/evals.md +684 -0
- package/external/pi-tools-suite/docs/subagent-model-pools.md +109 -0
- package/external/pi-tools-suite/package.json +10 -3
- package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/scripts/browser-qa-runner.mjs +82 -1
- package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/SKILL.md → agents/browser-qa.md} +261 -12
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +20 -0
- package/external/pi-tools-suite/src/async-subagents/agents/oracle.md +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/agents/verify.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +27 -243
- package/external/pi-tools-suite/src/async-subagents/commands.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +41 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +13 -93
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +494 -0
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +9 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +200 -143
- package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +54 -0
- package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +7 -6
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +52 -45
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +12 -4
- package/external/pi-tools-suite/src/async-subagents/index.ts +11 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +46 -18
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +2 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +41 -142
- package/external/pi-tools-suite/src/config.ts +1 -22
- package/external/pi-tools-suite/src/context-gateway/accounting.ts +151 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +111 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +160 -0
- package/external/pi-tools-suite/src/context-gateway/metadata-normalization.ts +88 -0
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +89 -0
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +429 -0
- package/external/pi-tools-suite/src/context-gateway/test-output-parser.ts +326 -0
- package/external/pi-tools-suite/src/context-gateway/types.ts +152 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +106 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +810 -106
- package/external/pi-tools-suite/src/dcp/commands.ts +64 -139
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +369 -35
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +510 -64
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +113 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +70 -0
- package/external/pi-tools-suite/src/dcp/config.ts +36 -61
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +421 -0
- package/external/pi-tools-suite/src/dcp/debug-log.ts +7 -5
- package/external/pi-tools-suite/src/dcp/index.ts +617 -203
- package/external/pi-tools-suite/src/dcp/journal.ts +566 -0
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +244 -0
- package/external/pi-tools-suite/src/dcp/prompts.ts +10 -7
- package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +189 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +298 -78
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +173 -281
- package/external/pi-tools-suite/src/dcp/pruner-emergency.ts +2 -4
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +17 -5
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +11 -1
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +30 -82
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +22 -133
- package/external/pi-tools-suite/src/dcp/pruner.ts +18 -33
- package/external/pi-tools-suite/src/dcp/recovery.ts +129 -0
- package/external/pi-tools-suite/src/dcp/shadow-plan.ts +127 -0
- package/external/pi-tools-suite/src/dcp/state-transaction.ts +102 -0
- package/external/pi-tools-suite/src/dcp/state.ts +158 -580
- package/external/pi-tools-suite/src/dcp/ui.ts +1 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +55 -220
- package/external/pi-tools-suite/src/index.ts +9 -0
- package/external/pi-tools-suite/src/model-tools/index.ts +76 -42
- package/external/pi-tools-suite/src/repo-discovery/index.ts +84 -18
- package/external/pi-tools-suite/src/repo-discovery/native-compact.ts +458 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +189 -43
- package/external/pi-tools-suite/src/tool-descriptions.ts +43 -38
- package/external/pi-tools-suite/src/truncation-metadata-normalizer/index.ts +17 -0
- package/package.json +6 -6
- package/schemas/pi-tools-suite.json +159 -78
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/auth-scaffold-spec.md +0 -78
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +0 -223
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +0 -195
- /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.mjs +0 -0
|
@@ -29,10 +29,23 @@ export interface RoutedSubagentTasks {
|
|
|
29
29
|
warnings: string[];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** A recoverable selection error: the parent must correct the batch before spawn. */
|
|
33
|
+
export class SubagentRoutingError extends Error {
|
|
34
|
+
constructor(reason: string, readonly taskIds: string[], readonly allowedTypes: string[]) {
|
|
35
|
+
super([
|
|
36
|
+
reason,
|
|
37
|
+
`Tasks requiring a valid role: ${taskIds.join(", ")}.`,
|
|
38
|
+
"Set an explicit valid subagentType for these tasks and retry; defaultType is not an error fallback.",
|
|
39
|
+
`Available types: ${allowedTypes.join(", ") || "(none configured)"}.`,
|
|
40
|
+
].join("\n"));
|
|
41
|
+
this.name = "SubagentRoutingError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
const ROUTER_SYSTEM_PROMPT = [
|
|
33
46
|
"You route Pi async sub-agent tasks to the best configured subagentType.",
|
|
34
47
|
"Choose exactly one allowed type for each task. Use the allowed type descriptions as the source of truth.",
|
|
35
|
-
"Prefer
|
|
48
|
+
"Prefer a matching project specialist. Otherwise use research for reading/review and evidence, implement for code/docs/tests/UI changes, verify for running checks, browser-qa for real-browser testing. Oracle is a deliberate strong second opinion, not the default for difficult work.",
|
|
36
49
|
"Return only strict JSON with this shape: {\"routes\":[{\"id\":\"task-id\",\"subagentType\":\"type\"}]}",
|
|
37
50
|
"Do not include markdown, comments, explanations, or unknown types.",
|
|
38
51
|
].join("\n");
|
|
@@ -43,31 +56,37 @@ export async function routeSubagentTasks(
|
|
|
43
56
|
ctx: SubagentRoutingContext,
|
|
44
57
|
signal?: AbortSignal,
|
|
45
58
|
): Promise<RoutedSubagentTasks> {
|
|
46
|
-
|
|
59
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
60
|
+
// Validate even the fast path: an explicit typo must not bypass profiles.
|
|
61
|
+
tasks = tasks.map((task) => hasText(task.subagentType) && task.subagentType !== task.subagentType.trim()
|
|
62
|
+
? { ...task, subagentType: task.subagentType.trim() }
|
|
63
|
+
: task);
|
|
64
|
+
const invalidTasks = tasks.filter((task) => hasText(task.subagentType)
|
|
65
|
+
&& !Object.prototype.hasOwnProperty.call(config.types, task.subagentType));
|
|
66
|
+
if (invalidTasks.length > 0) {
|
|
67
|
+
throw routingError(`Unknown subagentType: ${invalidTasks.map((task) => `${task.id}=${JSON.stringify(task.subagentType)}`).join(", ")}.`, invalidTasks, config);
|
|
68
|
+
}
|
|
47
69
|
const autoTasks = tasks.filter((task) => !hasText(task.subagentType));
|
|
48
70
|
if (autoTasks.length === 0) return { tasks, usedLlm: false, routes: {}, warnings: [] };
|
|
49
71
|
|
|
50
72
|
const routing = resolveSubagentRoutingConfig(config);
|
|
51
73
|
if (!routing.enabled) {
|
|
52
|
-
|
|
74
|
+
throw routingError("LLM sub-agent routing is disabled.", autoTasks, config);
|
|
53
75
|
}
|
|
54
|
-
if (
|
|
76
|
+
if (Object.keys(config.types).length === 0) throw routingError("No sub-agent types are configured.", autoTasks, config);
|
|
55
77
|
|
|
56
78
|
try {
|
|
57
79
|
const candidates = await resolveRoutingModels(ctx, routing);
|
|
58
80
|
if (candidates.length === 0) {
|
|
59
|
-
|
|
60
|
-
notifyRoutingWarning(ctx, routing, warning);
|
|
61
|
-
return { tasks: fallbackTasks(), usedLlm: false, routes: {}, warnings: [warning] };
|
|
81
|
+
throw routingError(`LLM sub-agent routing model unavailable (${routing.model}).`, autoTasks, config);
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
const prompt = buildRoutingPrompt(autoTasks, config, routing);
|
|
65
85
|
const failures: string[] = [];
|
|
66
|
-
let response: RoutingResponse | undefined;
|
|
67
86
|
for (const candidate of candidates) {
|
|
68
87
|
if (signal?.aborted) throw new Error("Aborted");
|
|
69
88
|
try {
|
|
70
|
-
response = await completeWithModelRegistry(
|
|
89
|
+
const response = await completeWithModelRegistry(
|
|
71
90
|
ctx.modelRegistry,
|
|
72
91
|
candidate.model,
|
|
73
92
|
{
|
|
@@ -91,48 +110,38 @@ export async function routeSubagentTasks(
|
|
|
91
110
|
timeoutMs: routing.timeoutMs,
|
|
92
111
|
},
|
|
93
112
|
);
|
|
94
|
-
|
|
113
|
+
if (signal?.aborted || response.stopReason === "aborted") throw new Error("Aborted");
|
|
114
|
+
if (response.stopReason === "error") throw new Error(response.errorMessage || "Router provider returned an error.");
|
|
115
|
+
const routes = parseRoutingResponse(responseText(response), config, autoTasks);
|
|
116
|
+
const missingTasks = autoTasks.filter((task) => !Object.prototype.hasOwnProperty.call(routes, task.id));
|
|
117
|
+
if (missingTasks.length > 0) {
|
|
118
|
+
throw new Error(`Returned ${Object.keys(routes).length}/${autoTasks.length} valid route(s); missing: ${missingTasks.map((task) => task.id).join(", ")}.`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
usedLlm: true,
|
|
122
|
+
routes,
|
|
123
|
+
warnings: [],
|
|
124
|
+
tasks: tasks.map((task) => hasText(task.subagentType) ? task : { ...task, subagentType: routes[task.id] }),
|
|
125
|
+
};
|
|
95
126
|
} catch (error) {
|
|
96
127
|
if (signal?.aborted || isAbortError(error)) throw error;
|
|
97
128
|
failures.push(`${currentModelRef(candidate.model) ?? "(unknown)"}: ${errorMessage(error)}`);
|
|
98
129
|
}
|
|
99
130
|
}
|
|
100
131
|
|
|
101
|
-
|
|
102
|
-
const warning = `LLM sub-agent routing failed (${failures.join("; ")}); used defaultType fallback.`;
|
|
103
|
-
notifyRoutingWarning(ctx, routing, warning);
|
|
104
|
-
return { tasks: fallbackTasks(), usedLlm: false, routes: {}, warnings: [warning] };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const routes = parseRoutingResponse(responseText(response), config, autoTasks);
|
|
108
|
-
const warnings = Object.keys(routes).length === autoTasks.length
|
|
109
|
-
? []
|
|
110
|
-
: [`LLM sub-agent routing returned ${Object.keys(routes).length}/${autoTasks.length} valid route(s); missing tasks used defaultType fallback.`];
|
|
111
|
-
for (const warning of warnings) notifyRoutingWarning(ctx, routing, warning);
|
|
112
|
-
return {
|
|
113
|
-
usedLlm: true,
|
|
114
|
-
routes,
|
|
115
|
-
warnings,
|
|
116
|
-
tasks: tasks.map((task) => applyRoute(task, config, routes)),
|
|
117
|
-
};
|
|
132
|
+
throw routingError(`LLM sub-agent routing failed (${failures.join("; ")}).`, autoTasks, config);
|
|
118
133
|
} catch (error) {
|
|
119
134
|
if (signal?.aborted || isAbortError(error)) throw error;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
const failure = error instanceof SubagentRoutingError
|
|
136
|
+
? error
|
|
137
|
+
: routingError(`LLM sub-agent routing failed (${errorMessage(error)}).`, autoTasks, config);
|
|
138
|
+
notifyRoutingWarning(ctx, routing, failure.message);
|
|
139
|
+
throw failure;
|
|
123
140
|
}
|
|
124
141
|
}
|
|
125
142
|
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
const fallback = withFallbackType(task, config);
|
|
129
|
-
return routes[task.id] ? { ...fallback, subagentType: routes[task.id] } : fallback;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function withFallbackType(task: AgentTask, config: SubagentConfig): AgentTask {
|
|
133
|
-
if (hasText(task.subagentType)) return task;
|
|
134
|
-
const fallback = defaultSubagentType(config);
|
|
135
|
-
return fallback ? { ...task, subagentType: fallback } : task;
|
|
143
|
+
function routingError(reason: string, tasks: AgentTask[], config: SubagentConfig): SubagentRoutingError {
|
|
144
|
+
return new SubagentRoutingError(reason, tasks.map((task) => task.id), Object.keys(config.types).sort());
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
function buildRoutingPrompt(tasks: AgentTask[], config: SubagentConfig, routing: ResolvedSubagentRoutingConfig): string {
|
|
@@ -144,7 +153,7 @@ function buildRoutingPrompt(tasks: AgentTask[], config: SubagentConfig, routing:
|
|
|
144
153
|
return `- ${name}: ${profile.description ?? "No description; use only when the task explicitly names this type."}`;
|
|
145
154
|
}),
|
|
146
155
|
"",
|
|
147
|
-
`
|
|
156
|
+
`Preferred type only for genuinely ambiguous tasks: ${defaultSubagentType(config) ?? "none"}`,
|
|
148
157
|
"",
|
|
149
158
|
"Tasks:",
|
|
150
159
|
JSON.stringify(tasks.map((task) => ({
|
|
@@ -184,8 +193,6 @@ interface RoutingCandidate {
|
|
|
184
193
|
env?: Record<string, string>;
|
|
185
194
|
}
|
|
186
195
|
|
|
187
|
-
type RoutingResponse = Awaited<ReturnType<typeof completeWithModelRegistry>>;
|
|
188
|
-
|
|
189
196
|
async function resolveModelRef(ctx: SubagentRoutingContext, modelRef: string): Promise<{
|
|
190
197
|
model: Model<Api>;
|
|
191
198
|
apiKey?: string;
|
|
@@ -212,7 +219,7 @@ function parseRoutingResponse(raw: string, config: SubagentConfig, tasks: AgentT
|
|
|
212
219
|
const parsed = parseJsonObject(raw);
|
|
213
220
|
const allowedTypes = new Map(Object.keys(config.types).map((name) => [name.toLowerCase(), name]));
|
|
214
221
|
const taskIds = new Set(tasks.map((task) => task.id));
|
|
215
|
-
const routes: Record<string, string> =
|
|
222
|
+
const routes: Record<string, string> = Object.create(null);
|
|
216
223
|
if (Array.isArray(parsed)) collectRouteArray(parsed, taskIds, allowedTypes, routes);
|
|
217
224
|
else if (typeof parsed === "string" && tasks.length === 1) addRoute(routes, taskIds, allowedTypes, tasks[0]!.id, parsed);
|
|
218
225
|
else if (isRecord(parsed)) {
|
|
@@ -289,7 +296,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
289
296
|
}
|
|
290
297
|
|
|
291
298
|
function isAbortError(error: unknown): boolean {
|
|
292
|
-
return error instanceof Error && /abort/i.test(error.name || error.message);
|
|
299
|
+
return error instanceof Error && (/abort/i.test(error.name) || /^aborted$/i.test(error.message));
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
function errorMessage(error: unknown): string {
|
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { selectSuitableToolsForModel } from "../../lib/tool-args.js";
|
|
6
|
+
import { BROWSER_QA_RUNNER_ENV, getBrowserQaRunnerPath } from "./browser-qa.js";
|
|
6
7
|
import { validateBasename } from "./paths.js";
|
|
7
8
|
import { getPiInvocation } from "./pi-invocation.js";
|
|
8
9
|
import { writePromptFile } from "./prompt.js";
|
|
@@ -88,7 +89,10 @@ export function spawnAgent(
|
|
|
88
89
|
// detached subprocesses do not depend on persisted local pi settings.
|
|
89
90
|
const persistSessions = shouldPersistSubagentSessions();
|
|
90
91
|
const sessionDir = persistSessions ? getAgentSessionDir(agentDir) : undefined;
|
|
91
|
-
|
|
92
|
+
// QA instructions now live in the agent prompt, but normal skill discovery
|
|
93
|
+
// must stay disabled even when no additional skill is configured.
|
|
94
|
+
const isolateSkills = task.subagentType === "browser-qa" || Boolean(options.isolatedSkills?.length);
|
|
95
|
+
const forwardedExtraArgs = isolateSkills ? withoutSkillArgs(extraArgs) : extraArgs;
|
|
92
96
|
if (sessionDir) fs.mkdirSync(sessionDir, { recursive: true });
|
|
93
97
|
const piArgs: string[] = ["--mode", "rpc"];
|
|
94
98
|
if (sessionDir) piArgs.push("--session-dir", sessionDir);
|
|
@@ -100,9 +104,9 @@ export function spawnAgent(
|
|
|
100
104
|
if (usesAntigravityModel(task.model, forwardedExtraArgs)) {
|
|
101
105
|
piArgs.push("--extension", getAntigravityAuthExtensionPath());
|
|
102
106
|
}
|
|
103
|
-
if (
|
|
107
|
+
if (isolateSkills) {
|
|
104
108
|
piArgs.push("--no-skills");
|
|
105
|
-
for (const skillPath of options.isolatedSkills) piArgs.push("--skill", skillPath);
|
|
109
|
+
for (const skillPath of options.isolatedSkills ?? []) piArgs.push("--skill", skillPath);
|
|
106
110
|
}
|
|
107
111
|
const envModel = task.model || getEnvModel();
|
|
108
112
|
if (envModel) piArgs.push("--model", envModel);
|
|
@@ -812,7 +816,11 @@ function subagentEnvironment(env: NodeJS.ProcessEnv, agentDir?: string): NodeJS.
|
|
|
812
816
|
]),
|
|
813
817
|
};
|
|
814
818
|
delete result[SUBAGENT_AGENT_DIR_ENV];
|
|
815
|
-
|
|
819
|
+
delete result[BROWSER_QA_RUNNER_ENV];
|
|
820
|
+
if (agentDir) {
|
|
821
|
+
result[SUBAGENT_AGENT_DIR_ENV] = fs.realpathSync(agentDir);
|
|
822
|
+
result[BROWSER_QA_RUNNER_ENV] = getBrowserQaRunnerPath();
|
|
823
|
+
}
|
|
816
824
|
return result;
|
|
817
825
|
}
|
|
818
826
|
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "./lib.js";
|
|
20
20
|
import { buildUltraworkPrompt, isUltraworkEnvEnabled, registerCommands } from "./commands.js";
|
|
21
21
|
import { agentStrategyPrompt, appendAgentStrategyPrompt } from "./core/agent-strategy.js";
|
|
22
|
+
import { buildSubagentCatalogPrompt } from "./core/agent-catalog.js";
|
|
22
23
|
import {
|
|
23
24
|
bridgeImageAttachments,
|
|
24
25
|
removeImageAttachmentBridgeState,
|
|
@@ -190,9 +191,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
190
191
|
customPrompt: Boolean(event?.systemPromptOptions?.customPrompt),
|
|
191
192
|
});
|
|
192
193
|
const visionPrompt = visionCapabilityPrompt(event, ctx);
|
|
193
|
-
|
|
194
|
+
const catalogPrompt = selectedToolsInclude(event, "subagents")
|
|
195
|
+
? subagentCatalogPrompt((ctx as { cwd?: string } | undefined)?.cwd ?? process.cwd())
|
|
196
|
+
: undefined;
|
|
197
|
+
if (!strategyPrompt && !visionPrompt && !catalogPrompt) return undefined;
|
|
194
198
|
let systemPrompt = event.systemPrompt ?? "";
|
|
195
199
|
if (strategyPrompt) systemPrompt = appendAgentStrategyPrompt(systemPrompt, strategyPrompt);
|
|
200
|
+
if (catalogPrompt) systemPrompt = appendAgentStrategyPrompt(systemPrompt, catalogPrompt);
|
|
196
201
|
if (visionPrompt) systemPrompt = appendAgentStrategyPrompt(systemPrompt, visionPrompt);
|
|
197
202
|
return { systemPrompt };
|
|
198
203
|
});
|
|
@@ -281,6 +286,11 @@ function safeLoadSubagentConfig(cwd: string) {
|
|
|
281
286
|
}
|
|
282
287
|
}
|
|
283
288
|
|
|
289
|
+
function subagentCatalogPrompt(cwd: string): string | undefined {
|
|
290
|
+
const config = safeLoadSubagentConfig(cwd);
|
|
291
|
+
return config ? buildSubagentCatalogPrompt(config) : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
284
294
|
function startupSubagentPresetList(cwd = process.cwd()): string {
|
|
285
295
|
try {
|
|
286
296
|
const config = loadSubagentConfig(cwd);
|
|
@@ -23,7 +23,7 @@ export {
|
|
|
23
23
|
DEFAULT_ROUTING_CONFIG,
|
|
24
24
|
existingSubagentConfigFiles,
|
|
25
25
|
getDefaultSubagentConfigPath,
|
|
26
|
-
|
|
26
|
+
getBuiltinSubagentDefinitionsDir,
|
|
27
27
|
getSubagentConfigInitTargetPath,
|
|
28
28
|
getSubagentConfigSamplePath,
|
|
29
29
|
isBlindModelRef,
|
|
@@ -34,8 +34,12 @@ export {
|
|
|
34
34
|
selectSubagentType,
|
|
35
35
|
shouldForceCurrentSubagentModel,
|
|
36
36
|
} from "./core/config.js";
|
|
37
|
-
export { routeSubagentTasks } from "./core/routing.js";
|
|
37
|
+
export { routeSubagentTasks, SubagentRoutingError } from "./core/routing.js";
|
|
38
|
+
export { BROWSER_QA_RUNNER_ENV, getBrowserQaRunnerPath } from "./core/browser-qa.js";
|
|
38
39
|
export type { RoutedSubagentTasks, SubagentRoutingContext } from "./core/routing.js";
|
|
40
|
+
export { agentDefinitionFiles, agentNameFromFilename, parseAgentMarkdown, projectAgentDefinitionFiles, readAgentDefinitionsFromDir, readProjectAgentDefinitions } from "./core/agents-dir.js";
|
|
41
|
+
export type { AgentDefinition, ProjectAgentDefinition } from "./core/agents-dir.js";
|
|
42
|
+
export { buildSubagentCatalogPrompt } from "./core/agent-catalog.js";
|
|
39
43
|
export type { SubagentPresetSelectionState } from "./core/presets.js";
|
|
40
44
|
export { getActiveSubagentPresetName, getSessionSubagentPresetOverride, getSubagentPresetSelectionPath, loadSubagentPresetSelection, saveSubagentPresetSelection, setActiveSubagentPreset, setSessionSubagentPresetOverride } from "./core/presets.js";
|
|
41
45
|
export { isQuotaLimitCompletion, nextFallbackModel, rememberSessionModelFallback, resetSessionModelFallbacks, selectSessionModelWithFallback } from "./core/model-fallback.js";
|
|
@@ -4,6 +4,9 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import { Type } from "@earendil-works/pi-ai";
|
|
5
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
6
|
import { ASYNC_SUBAGENT_TOOL_DESCRIPTIONS } from "../../tool-descriptions.js";
|
|
7
|
+
import { SUBAGENT_TYPE_SELECTION_GUIDANCE } from "../core/agent-catalog.js";
|
|
8
|
+
import { selectAvailableAgentModels } from "../core/model-selection.js";
|
|
9
|
+
import { SubagentModelSelectionError } from "../core/config.js";
|
|
7
10
|
import type { AgentCompletionHandler, AgentTask, ResolvedAgentTaskConfig, Semaphore, SpawnedAgent } from "../lib.js";
|
|
8
11
|
import {
|
|
9
12
|
createRunDir,
|
|
@@ -19,6 +22,7 @@ import {
|
|
|
19
22
|
resolveAgentTaskConfig,
|
|
20
23
|
resolveRunDir,
|
|
21
24
|
routeSubagentTasks,
|
|
25
|
+
SubagentRoutingError,
|
|
22
26
|
selectSessionModelWithFallback,
|
|
23
27
|
shouldForceCurrentSubagentModel,
|
|
24
28
|
spawnAgent,
|
|
@@ -172,7 +176,7 @@ const AgentTaskSchema = Type.Object({
|
|
|
172
176
|
id: Type.Optional(Type.String({ description: "Short identifier for this agent (used as directory name). If omitted, the spawn action assigns agent-1, agent-2, etc." })),
|
|
173
177
|
task: Type.String({ description: "Focused task description for the sub-agent" }),
|
|
174
178
|
scope: Type.Optional(Type.String({ description: "Relevant files/areas for this task" })),
|
|
175
|
-
subagentType: Type.Optional(Type.String({ description:
|
|
179
|
+
subagentType: Type.Optional(Type.String({ description: SUBAGENT_TYPE_SELECTION_GUIDANCE })),
|
|
176
180
|
model: Type.Optional(Type.String({ description: "Explicit model override for this sub-agent. Prefer subagentType for reusable routing." })),
|
|
177
181
|
thinking: Type.Optional(Type.String({ description: "Per-agent thinking level override (off, minimal, low, medium, high, xhigh, max)." })),
|
|
178
182
|
promptAppend: Type.Optional(Type.String({ description: "Extra prompt instructions appended after the generated/type prompt." })),
|
|
@@ -209,10 +213,6 @@ export function registerSpawnTool(
|
|
|
209
213
|
const parentSession = typeof ctx.sessionManager?.getSessionFile === "function"
|
|
210
214
|
? ctx.sessionManager.getSessionFile()
|
|
211
215
|
: undefined;
|
|
212
|
-
const runDir = params.runDir
|
|
213
|
-
? resolveRunDir(ctx.cwd, params.runDir)
|
|
214
|
-
: createRunDir(ctx.cwd, params.slug);
|
|
215
|
-
|
|
216
216
|
const normalized = normalizeAgentTasks(params.tasks);
|
|
217
217
|
if (normalized.error) {
|
|
218
218
|
return {
|
|
@@ -240,21 +240,49 @@ export function registerSpawnTool(
|
|
|
240
240
|
isError: true,
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
let routed: Awaited<ReturnType<typeof routeSubagentTasks>>;
|
|
244
|
+
try {
|
|
245
|
+
routed = await routeSubagentTasks(normalized.tasks ?? [], config, ctx as any, signal ?? undefined);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (!(error instanceof SubagentRoutingError)) throw error;
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: "text", text: `No agents were launched. Correct the roles and resubmit the whole batch.\n${error.message}` }],
|
|
250
|
+
details: { error: "subagent_routing", taskIds: error.taskIds, allowedTypes: error.allowedTypes },
|
|
251
|
+
isError: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
244
254
|
const timeoutMs = timeoutMsFromSeconds(params.timeoutSeconds);
|
|
245
255
|
const parentModel = currentModelRef((ctx as { model?: unknown }).model);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
let resolvedTasks: ResolvedAgentTaskConfig[];
|
|
257
|
+
try {
|
|
258
|
+
resolvedTasks = await Promise.all(routed.tasks.map(async (task) => applySessionModelFallback(await selectAvailableAgentModels(
|
|
259
|
+
resolveAgentTaskConfig(task, config, {
|
|
260
|
+
preset: activePreset,
|
|
261
|
+
thinking: params.thinking,
|
|
262
|
+
extraArgs: Array.isArray(params.extraArgs) ? params.extraArgs : [],
|
|
263
|
+
forcedModel,
|
|
264
|
+
parentModel,
|
|
265
|
+
timeoutMs,
|
|
266
|
+
}), config, ctx.modelRegistry, signal,
|
|
267
|
+
))));
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (!(error instanceof SubagentModelSelectionError)) throw error;
|
|
270
|
+
return { content: [{ type: "text", text: error.message }], details: { error: "subagent_model_selection" }, isError: true };
|
|
271
|
+
}
|
|
256
272
|
const tasks: AgentTask[] = resolvedTasks.map((resolved) => resolved.task);
|
|
257
|
-
|
|
273
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
274
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
275
|
+
// Resolve the entire batch before creating run state or launching children.
|
|
276
|
+
const runDir = params.runDir
|
|
277
|
+
? resolveRunDir(ctx.cwd, params.runDir)
|
|
278
|
+
: createRunDir(ctx.cwd, params.slug);
|
|
279
|
+
const taskPreviews = toTaskPreviews(tasks).map((preview, index) => {
|
|
280
|
+
// tasks[i] === resolvedTasks[i].task (positional), so the resolved
|
|
281
|
+
// profile's icon rides along into every preview consumer (tool render
|
|
282
|
+
// details, live-state events, and the desktop subagents panel).
|
|
283
|
+
const icon = resolvedTasks[index]?.profile?.icon;
|
|
284
|
+
return icon ? { ...preview, icon } : preview;
|
|
285
|
+
});
|
|
258
286
|
const results: { id: string; pid: number; agentDir: string }[] = [];
|
|
259
287
|
const maxConcurrent = config.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
260
288
|
const semaphore = getProjectSemaphore(ctx.cwd, maxConcurrent);
|
|
@@ -365,7 +393,7 @@ export function registerSpawnTool(
|
|
|
365
393
|
|
|
366
394
|
renderResult(result, opts, theme) {
|
|
367
395
|
const details = result.details as SubagentRunRenderDetails | undefined;
|
|
368
|
-
if (!details) {
|
|
396
|
+
if (!details || ("isError" in result && result.isError === true)) {
|
|
369
397
|
const fallback = result.content[0] && result.content[0].type === "text" ? result.content[0].text : "(no output)";
|
|
370
398
|
return new Text(fallback, 0, 0);
|
|
371
399
|
}
|
|
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { Type } from "@earendil-works/pi-ai";
|
|
3
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { asyncSubagentToolDescriptions } from "../../tool-descriptions.js";
|
|
5
|
+
import { SUBAGENT_TYPE_SELECTION_GUIDANCE } from "../core/agent-catalog.js";
|
|
5
6
|
import { hasIndexedProjectRoot } from "../../lib/project.js";
|
|
6
7
|
import type { AgentCompletionHandler } from "../lib.js";
|
|
7
8
|
import { DEFAULT_SPAWN_WATCH_SECONDS, INLINE_RENDERING } from "../constants.js";
|
|
@@ -19,7 +20,7 @@ const AgentTaskSchema = Type.Object({
|
|
|
19
20
|
id: Type.Optional(Type.String({ description: "Short identifier for this agent (used as directory name). If omitted, assigns agent-1, agent-2, etc." })),
|
|
20
21
|
task: Type.String({ description: "Focused task description for the sub-agent" }),
|
|
21
22
|
scope: Type.Optional(Type.String({ description: "Relevant files/areas for this task" })),
|
|
22
|
-
subagentType: Type.Optional(Type.String({ description:
|
|
23
|
+
subagentType: Type.Optional(Type.String({ description: SUBAGENT_TYPE_SELECTION_GUIDANCE })),
|
|
23
24
|
model: Type.Optional(Type.String({ description: "Explicit model override for this sub-agent. Prefer subagentType for reusable routing." })),
|
|
24
25
|
thinking: Type.Optional(Type.String({ description: "Per-agent thinking level override (off, minimal, low, medium, high, xhigh, max)." })),
|
|
25
26
|
promptAppend: Type.Optional(Type.String({ description: "Extra prompt instructions appended after the generated/type prompt." })),
|
|
@@ -141,7 +142,7 @@ export function registerSubagentsTool(
|
|
|
141
142
|
|
|
142
143
|
renderResult(result, _opts, theme) {
|
|
143
144
|
const details = result.details as SubagentRunRenderDetails | undefined;
|
|
144
|
-
if (!details) return new Text(firstText(result), 0, 0);
|
|
145
|
+
if (!details || ("isError" in result && result.isError === true)) return new Text(firstText(result), 0, 0);
|
|
145
146
|
if (details.mode === "spawn" && Array.isArray(details.agents) && theme) {
|
|
146
147
|
return renderSubagentSpawnPrompts(details, _opts, theme);
|
|
147
148
|
}
|