pi-ui-extend 1.0.41 → 1.0.42
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 +10 -2
- package/dist/app/app.d.ts +3 -0
- package/dist/app/app.js +26 -0
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +6 -1
- package/dist/app/commands/command-registry.js +1 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +59 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +6 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +19 -1
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- package/dist/app/types.d.ts +5 -0
- package/dist/default-pix-config.js +10 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +80 -58
- package/dist/schemas/pi-tools-suite-schema.js +43 -78
- package/dist/schemas/pix-schema.d.ts +7 -0
- package/dist/schemas/pix-schema.js +14 -0
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +6 -0
- package/docs/desktop-mvp.md +12 -1
- package/docs/desktop-task-manager.md +93 -82
- package/external/pi-tools-suite/README.md +37 -50
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +19 -0
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +40 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +38 -27
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +14 -70
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -3
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +122 -312
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +15 -2
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +7 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -4
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/config.ts +41 -1
- package/external/pi-tools-suite/src/context-gateway/config.ts +11 -2
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +73 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +3 -3
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +9 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +96 -44
- package/external/pi-tools-suite/src/dcp/commands.ts +18 -0
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +170 -45
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/config.ts +56 -13
- package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +1 -1
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +13 -9
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/resource-registry/index.ts +2290 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +3 -4
- package/package.json +4 -2
- package/schemas/pi-tools-suite.json +427 -280
- package/schemas/pix.json +24 -0
- package/schemas/tasks.json +132 -0
- package/skills/spec-wiki/SKILL.md +55 -0
- package/skills/spec-wiki/evals/evals.json +132 -0
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
- package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
- package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
- package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
- package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
- package/skills/spec-wiki/evals/trigger-evals.json +94 -0
- package/skills/spec-wiki/references/bootstrap.md +100 -0
- package/skills/spec-wiki/references/focused-spec.md +117 -0
- package/skills/spec-wiki/references/index-schema.md +330 -0
- package/skills/spec-wiki/references/maintenance.md +134 -0
- package/skills/spec-wiki/references/query.md +74 -0
- package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- package/skills/spec-lite/SKILL.md +0 -140
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
import { readAgentDefinitionsFromDir, readProjectAgentDefinitions, type AgentDefinition } from "./agents-dir.js";
|
|
7
|
-
import { ensurePiToolsSuiteUserConfig, getPiToolsSuiteUserConfigPath } from "../../config.js";
|
|
4
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
5
|
+
import { projectAgentsDir, readAgentDefinitionsFromDir, readProjectAgentDefinitions, type AgentDefinition } from "./agents-dir.js";
|
|
8
6
|
import type { AgentTask, RetryConfig } from "./types.js";
|
|
9
7
|
|
|
10
8
|
export interface ModelByParentEntry {
|
|
@@ -36,6 +34,16 @@ export interface SubagentTypeConfig {
|
|
|
36
34
|
* Oracle alone keeps parent-aware selection ahead of presets.
|
|
37
35
|
*/
|
|
38
36
|
modelByParent?: Record<string, ModelByParentEntry>;
|
|
37
|
+
/**
|
|
38
|
+
* Optional parent-model allow-list. When present, this role is exposed only
|
|
39
|
+
* when the current parent model matches at least one pattern.
|
|
40
|
+
*/
|
|
41
|
+
forParentModels?: string[];
|
|
42
|
+
/**
|
|
43
|
+
* Optional parent-model deny-list. A match hides the role even when
|
|
44
|
+
* forParentModels also matches.
|
|
45
|
+
*/
|
|
46
|
+
notForParentModels?: string[];
|
|
39
47
|
thinking?: string;
|
|
40
48
|
tools?: string[];
|
|
41
49
|
/** Explicit skill files loaded after disabling normal skill discovery. */
|
|
@@ -125,13 +133,6 @@ export interface SubagentConfig {
|
|
|
125
133
|
timeoutMs?: number;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
export interface CopySubagentConfigSampleResult {
|
|
129
|
-
copied: boolean;
|
|
130
|
-
targetPath: string;
|
|
131
|
-
samplePath: string;
|
|
132
|
-
existingFiles: string[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
136
|
export interface ResolvedAgentTaskConfig {
|
|
136
137
|
task: AgentTask;
|
|
137
138
|
extraArgs: string[];
|
|
@@ -192,82 +193,51 @@ export const DEFAULT_ROUTING_CONFIG: ResolvedSubagentRoutingConfig = {
|
|
|
192
193
|
fallbackModels: ["openai-codex/gpt-5.6-luna"],
|
|
193
194
|
maxTaskChars: 1200,
|
|
194
195
|
maxTokens: 512,
|
|
195
|
-
maxRetries:
|
|
196
|
-
timeoutMs:
|
|
196
|
+
maxRetries: 1,
|
|
197
|
+
timeoutMs: 12_000,
|
|
197
198
|
debug: false,
|
|
198
199
|
};
|
|
199
200
|
|
|
200
201
|
const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "agents");
|
|
202
|
+
const BUILTIN_PRESETS_FILE = path.join(BUILTIN_AGENTS_DIR, "presets.jsonc");
|
|
203
|
+
const DEFAULT_BLIND_MODEL_PATTERNS = [
|
|
204
|
+
"zai/glm-4.5*", "glm-4.5*", "*/glm-4.5*",
|
|
205
|
+
"zai/glm-5-turbo*", "glm-5-turbo*", "*/glm-5-turbo*",
|
|
206
|
+
"zai/glm-5.3", "glm-5.3", "*/glm-5.3",
|
|
207
|
+
];
|
|
201
208
|
|
|
202
209
|
const BUILTIN_CONFIG: SubagentConfig = {
|
|
203
210
|
defaultType: "research",
|
|
204
211
|
maxConcurrent: DEFAULT_MAX_CONCURRENT,
|
|
212
|
+
maxResultBytes: 100_000,
|
|
205
213
|
routing: { ...DEFAULT_ROUTING_CONFIG },
|
|
214
|
+
vision: { blindModelPatterns: DEFAULT_BLIND_MODEL_PATTERNS },
|
|
215
|
+
presets: readPresetConfigFile(BUILTIN_PRESETS_FILE).presets,
|
|
206
216
|
types: normalizeAgentDefinitions(readAgentDefinitionsFromDir(BUILTIN_AGENTS_DIR)),
|
|
207
217
|
};
|
|
208
218
|
|
|
209
219
|
export function loadSubagentConfig(cwd: string, env: NodeJS.ProcessEnv = process.env): SubagentConfig {
|
|
210
220
|
const config = cloneConfig(BUILTIN_CONFIG);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Project-local agent definitions (.pi/agents/*.md) win name collisions
|
|
216
|
-
// over user/project JSONC config; loaded fresh on every call so edits and
|
|
217
|
-
// /reload are respected without a restart.
|
|
218
|
-
mergeConfig(config, projectAgentTypes(cwd));
|
|
219
|
-
}
|
|
220
|
-
for (const file of configFiles(cwd, env)) {
|
|
221
|
-
mergeConfig(config, readConfigFile(file));
|
|
222
|
-
}
|
|
221
|
+
mergeConfig(config, projectPresetConfig(cwd));
|
|
222
|
+
// Project-local agent definitions (.pi/agents/*.md) are the only project
|
|
223
|
+
// source of role/profile configuration and are loaded fresh on every call.
|
|
224
|
+
mergeConfig(config, projectAgentTypes(cwd));
|
|
223
225
|
applyEnvModelOverrides(config, env);
|
|
224
226
|
applyEnvRoutingOverrides(config, env);
|
|
227
|
+
applyEnvRuntimeOverrides(config, env);
|
|
225
228
|
return config;
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
export function configFiles(cwd: string, env: NodeJS.ProcessEnv = process.env): string[] {
|
|
229
|
-
const explicit = explicitSubagentConfigPath(cwd, env);
|
|
230
|
-
if (explicit) {
|
|
231
|
-
if (!fs.existsSync(explicit)) throw new Error(`Subagent config not found: ${explicit}`);
|
|
232
|
-
return [explicit];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return existingSubagentConfigFiles(cwd, env);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function explicitSubagentConfigPath(cwd: string, env: NodeJS.ProcessEnv): string | undefined {
|
|
239
|
-
const explicit = trimString(env.ASYNC_SUBAGENTS_CONFIG || env.PI_SUBAGENTS_CONFIG);
|
|
240
|
-
return explicit ? path.resolve(cwd, expandHome(explicit)) : undefined;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function getDefaultSubagentConfigPath(): string {
|
|
244
|
-
return getPiToolsSuiteUserConfigPath();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function getSubagentConfigSamplePath(): string {
|
|
248
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "async-subagents.sample.jsonc");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
231
|
export function getBuiltinSubagentDefinitionsDir(): string {
|
|
252
232
|
return BUILTIN_AGENTS_DIR;
|
|
253
233
|
}
|
|
254
234
|
|
|
255
|
-
export function
|
|
256
|
-
return
|
|
235
|
+
export function getBuiltinSubagentPresetsPath(): string {
|
|
236
|
+
return BUILTIN_PRESETS_FILE;
|
|
257
237
|
}
|
|
258
238
|
|
|
259
|
-
export function
|
|
260
|
-
|
|
261
|
-
if (explicit) return fs.existsSync(explicit) ? [explicit] : [];
|
|
262
|
-
return piToolsSuiteConfigFiles(cwd, env).filter(hasPiToolsSuiteSubagentConfig);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function piToolsSuiteConfigFiles(cwd: string, env: NodeJS.ProcessEnv): string[] {
|
|
266
|
-
return [
|
|
267
|
-
getPiToolsSuiteUserConfigPath(),
|
|
268
|
-
env.PI_CONFIG_DIR ? path.join(env.PI_CONFIG_DIR, "pi-tools-suite.jsonc") : undefined,
|
|
269
|
-
findProjectPiToolsSuiteConfig(cwd),
|
|
270
|
-
].filter((file): file is string => typeof file === "string" && fs.existsSync(file));
|
|
239
|
+
export function getProjectSubagentPresetsPath(cwd: string): string {
|
|
240
|
+
return path.join(projectAgentsDir(cwd) ?? path.join(path.resolve(cwd), ".pi", "agents"), "presets.jsonc");
|
|
271
241
|
}
|
|
272
242
|
|
|
273
243
|
/** Normalize `.pi/agents/*.md` definitions through the shared type-profile path. */
|
|
@@ -278,6 +248,13 @@ function projectAgentTypes(cwd: string): Partial<SubagentConfig> {
|
|
|
278
248
|
return { types };
|
|
279
249
|
}
|
|
280
250
|
|
|
251
|
+
function projectPresetConfig(cwd: string): Partial<SubagentConfig> {
|
|
252
|
+
const dir = projectAgentsDir(cwd);
|
|
253
|
+
if (!dir) return {};
|
|
254
|
+
const file = path.join(dir, "presets.jsonc");
|
|
255
|
+
return fs.existsSync(file) ? readPresetConfigFile(file) : {};
|
|
256
|
+
}
|
|
257
|
+
|
|
281
258
|
function normalizeAgentDefinitions(definitions: Record<string, AgentDefinition>): Record<string, SubagentTypeConfig> {
|
|
282
259
|
const types: Record<string, SubagentTypeConfig> = {};
|
|
283
260
|
for (const [name, definition] of Object.entries(definitions)) {
|
|
@@ -286,42 +263,6 @@ function normalizeAgentDefinitions(definitions: Record<string, AgentDefinition>)
|
|
|
286
263
|
return types;
|
|
287
264
|
}
|
|
288
265
|
|
|
289
|
-
function findProjectPiToolsSuiteConfig(startDir: string): string | undefined {
|
|
290
|
-
let dir = path.resolve(startDir);
|
|
291
|
-
const root = path.parse(dir).root;
|
|
292
|
-
while (true) {
|
|
293
|
-
const candidate = path.join(dir, ".pi", "pi-tools-suite.jsonc");
|
|
294
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
295
|
-
if (dir === root) return undefined;
|
|
296
|
-
const parent = path.dirname(dir);
|
|
297
|
-
if (parent === dir) return undefined;
|
|
298
|
-
dir = parent;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function copySubagentConfigSample(cwd: string, env: NodeJS.ProcessEnv = process.env): CopySubagentConfigSampleResult {
|
|
303
|
-
const samplePath = getSubagentConfigSamplePath();
|
|
304
|
-
const existingFiles = existingSubagentConfigFiles(cwd, env);
|
|
305
|
-
if (existingFiles.length > 0) return { copied: false, targetPath: existingFiles[0], samplePath, existingFiles };
|
|
306
|
-
|
|
307
|
-
const explicit = explicitSubagentConfigPath(cwd, env);
|
|
308
|
-
const targetPath = getSubagentConfigInitTargetPath(cwd, env);
|
|
309
|
-
if (explicit) {
|
|
310
|
-
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
311
|
-
try {
|
|
312
|
-
fs.copyFileSync(samplePath, targetPath, fs.constants.COPYFILE_EXCL);
|
|
313
|
-
} catch (error) {
|
|
314
|
-
if (isNodeError(error) && error.code === "EEXIST") {
|
|
315
|
-
return { copied: false, targetPath, samplePath, existingFiles: [targetPath] };
|
|
316
|
-
}
|
|
317
|
-
throw error;
|
|
318
|
-
}
|
|
319
|
-
} else {
|
|
320
|
-
writePiToolsSuiteSubagentConfig(targetPath, readConfigFile(samplePath));
|
|
321
|
-
}
|
|
322
|
-
return { copied: true, targetPath, samplePath, existingFiles: [] };
|
|
323
|
-
}
|
|
324
|
-
|
|
325
266
|
export function resolveAgentTaskConfig(
|
|
326
267
|
task: AgentTask,
|
|
327
268
|
config: SubagentConfig,
|
|
@@ -445,10 +386,11 @@ export function shouldForceCurrentSubagentModel(env: NodeJS.ProcessEnv = process
|
|
|
445
386
|
}
|
|
446
387
|
|
|
447
388
|
export function currentModelRef(model: unknown): string | undefined {
|
|
389
|
+
if (typeof model === "string") return trimString(model);
|
|
448
390
|
if (!isRecord(model)) return undefined;
|
|
449
|
-
const id = trimString(model.id);
|
|
391
|
+
const id = trimString(model.modelId) || trimString(model.id) || trimString(model.model) || trimString(model.name);
|
|
450
392
|
if (!id) return undefined;
|
|
451
|
-
const provider = trimString(model.provider);
|
|
393
|
+
const provider = trimString(model.provider) || trimString(model.providerId);
|
|
452
394
|
return provider && !id.includes("/") ? `${provider}/${id}` : id;
|
|
453
395
|
}
|
|
454
396
|
|
|
@@ -457,93 +399,67 @@ export function isBlindModelRef(modelRef: string | undefined, config: SubagentCo
|
|
|
457
399
|
return matchesAnyModelPattern(modelRef, config.vision?.blindModelPatterns ?? []);
|
|
458
400
|
}
|
|
459
401
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
402
|
+
/** Whether one role is available to the current parent model. Deny wins. */
|
|
403
|
+
export function isSubagentTypeAvailableForParent(
|
|
404
|
+
profile: SubagentTypeConfig,
|
|
405
|
+
parentModelRef: string | undefined,
|
|
406
|
+
): boolean {
|
|
407
|
+
const included = profile.forParentModels;
|
|
408
|
+
if (included !== undefined) {
|
|
409
|
+
if (!parentModelRef || !matchesAnyModelPattern(parentModelRef, included)) return false;
|
|
410
|
+
}
|
|
411
|
+
const excluded = profile.notForParentModels;
|
|
412
|
+
if (parentModelRef && excluded !== undefined && matchesAnyModelPattern(parentModelRef, excluded)) return false;
|
|
413
|
+
return true;
|
|
464
414
|
}
|
|
465
415
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Return the effective config visible to a particular parent model. Only the
|
|
418
|
+
* role catalog is filtered; presets, routing policy, retry defaults, etc. are
|
|
419
|
+
* preserved unchanged.
|
|
420
|
+
*/
|
|
421
|
+
export function filterSubagentConfigForParentModel(
|
|
422
|
+
config: SubagentConfig,
|
|
423
|
+
parentModelRef: string | undefined,
|
|
424
|
+
): SubagentConfig {
|
|
425
|
+
const types = Object.fromEntries(
|
|
426
|
+
Object.entries(config.types).filter(([, profile]) => isSubagentTypeAvailableForParent(profile, parentModelRef)),
|
|
427
|
+
);
|
|
428
|
+
return Object.keys(types).length === Object.keys(config.types).length ? config : { ...config, types };
|
|
471
429
|
}
|
|
472
430
|
|
|
473
|
-
function
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const section = parsed.asyncSubagents ?? parsed["async-subagents"] ?? parsed.subagents;
|
|
478
|
-
return isRecord(section) ? normalizeConfig(section, file) : {};
|
|
431
|
+
export function selectSubagentType(task: AgentTask, config: SubagentConfig): string | undefined {
|
|
432
|
+
const explicit = trimString(task.subagentType);
|
|
433
|
+
if (explicit) return explicit;
|
|
434
|
+
return defaultSubagentType(config);
|
|
479
435
|
}
|
|
480
436
|
|
|
481
|
-
function
|
|
437
|
+
function readPresetConfigFile(file: string): Partial<SubagentConfig> {
|
|
482
438
|
const raw = fs.readFileSync(file, "utf-8");
|
|
483
439
|
const parsed = parseJsonc(raw) as unknown;
|
|
484
|
-
if (!isRecord(parsed))
|
|
485
|
-
return
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
function normalizeConfig(value: Record<string, unknown>, file: string): Partial<SubagentConfig> {
|
|
500
|
-
const output: Partial<SubagentConfig> = {};
|
|
501
|
-
if (typeof value.defaultType === "string") output.defaultType = value.defaultType.trim();
|
|
502
|
-
if (typeof value.routing === "boolean") output.routing = { enabled: value.routing };
|
|
503
|
-
else if (isRecord(value.routing)) output.routing = normalizeRoutingConfig(value.routing);
|
|
504
|
-
const vision = normalizeVisionConfig(value);
|
|
505
|
-
if (vision) output.vision = vision;
|
|
506
|
-
const maxConcurrent = finiteNumber(value.maxConcurrent);
|
|
507
|
-
if (maxConcurrent !== undefined) output.maxConcurrent = Math.max(0, Math.round(maxConcurrent));
|
|
508
|
-
const maxResultBytes = finiteNumber(value.maxResultBytes);
|
|
509
|
-
if (maxResultBytes !== undefined) output.maxResultBytes = Math.max(0, Math.round(maxResultBytes));
|
|
510
|
-
const timeoutMs = positiveMilliseconds(value.timeoutMs);
|
|
511
|
-
if (timeoutMs !== undefined) output.timeoutMs = timeoutMs;
|
|
512
|
-
if (isRecord(value.retry)) output.retry = normalizeRetryConfig(value.retry);
|
|
513
|
-
if (value.presets !== undefined) {
|
|
514
|
-
if (!isRecord(value.presets)) throw new Error(`Subagent config "presets" must be an object: ${file}`);
|
|
515
|
-
const presets: Record<string, SubagentPreset> = {};
|
|
516
|
-
for (const [name, rawPreset] of Object.entries(value.presets)) {
|
|
517
|
-
if (!isRecord(rawPreset)) throw new Error(`Subagent preset "${name}" must be an object: ${file}`);
|
|
518
|
-
presets[name] = {
|
|
519
|
-
description: trimString(rawPreset.description),
|
|
520
|
-
models: normalizeModels(rawPreset.models, `preset "${name}"`, file),
|
|
521
|
-
model: trimString(rawPreset.model),
|
|
522
|
-
fallbackModels: modelList(rawPreset.fallbackModels, rawPreset.fallbackModel),
|
|
523
|
-
thinking: trimString(rawPreset.thinking),
|
|
524
|
-
extraArgs: arrayOfStrings(rawPreset.extraArgs),
|
|
525
|
-
timeoutMs: positiveMilliseconds(rawPreset.timeoutMs),
|
|
526
|
-
types: normalizePresetTypeOverrides(rawPreset.types, file, name),
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
output.presets = presets;
|
|
530
|
-
}
|
|
531
|
-
if (value.types === undefined) return output;
|
|
532
|
-
if (!isRecord(value.types)) throw new Error(`Subagent config "types" must be an object: ${file}`);
|
|
533
|
-
|
|
534
|
-
const types: Record<string, SubagentTypeConfig> = {};
|
|
535
|
-
for (const [name, rawProfile] of Object.entries(value.types)) {
|
|
536
|
-
if (!isRecord(rawProfile)) throw new Error(`Subagent type "${name}" must be an object: ${file}`);
|
|
537
|
-
types[name] = normalizeSubagentTypeProfile(rawProfile, name, file);
|
|
440
|
+
if (!isRecord(parsed)) throw new Error(`Subagent presets file must contain an object: ${file}`);
|
|
441
|
+
return { presets: normalizePresetMap(parsed, file, "preset file") };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function normalizePresetMap(value: unknown, file: string, owner: string): Record<string, SubagentPreset> {
|
|
445
|
+
if (!isRecord(value)) throw new Error(`Subagent ${owner} must be an object: ${file}`);
|
|
446
|
+
const presets: Record<string, SubagentPreset> = {};
|
|
447
|
+
for (const [name, rawPreset] of Object.entries(value)) {
|
|
448
|
+
if (!isRecord(rawPreset)) throw new Error(`Subagent preset "${name}" must be an object: ${file}`);
|
|
449
|
+
const models = normalizeModels(rawPreset.models, `preset "${name}"`, file);
|
|
450
|
+
if (models === undefined) throw new Error(`Subagent preset "${name}" must define models: ${file}`);
|
|
451
|
+
presets[name] = {
|
|
452
|
+
description: trimString(rawPreset.description),
|
|
453
|
+
models,
|
|
454
|
+
};
|
|
538
455
|
}
|
|
539
|
-
|
|
540
|
-
return output;
|
|
456
|
+
return presets;
|
|
541
457
|
}
|
|
542
458
|
|
|
543
459
|
/**
|
|
544
460
|
* Normalize one raw sub-agent type profile (`SubagentTypeConfig` shape) from
|
|
545
|
-
*
|
|
546
|
-
*
|
|
461
|
+
* an agent Markdown definition. Bundled and project files share the same
|
|
462
|
+
* validation and trimming path.
|
|
547
463
|
*/
|
|
548
464
|
export function normalizeSubagentTypeProfile(
|
|
549
465
|
rawProfile: Record<string, unknown>,
|
|
@@ -558,6 +474,8 @@ export function normalizeSubagentTypeProfile(
|
|
|
558
474
|
model: models === undefined ? trimString(rawProfile.model) : undefined,
|
|
559
475
|
fallbackModels: models === undefined ? modelList(rawProfile.fallbackModels, rawProfile.fallbackModel) : undefined,
|
|
560
476
|
modelByParent: models === undefined ? normalizeModelByParent(rawProfile.modelByParent, name, file) : undefined,
|
|
477
|
+
forParentModels: normalizeParentModelPatterns(rawProfile.forParentModels, "forParentModels", name, file),
|
|
478
|
+
notForParentModels: normalizeParentModelPatterns(rawProfile.notForParentModels, "notForParentModels", name, file),
|
|
561
479
|
thinking: trimString(rawProfile.thinking),
|
|
562
480
|
tools: arrayOfStrings(rawProfile.tools),
|
|
563
481
|
isolatedSkills: arrayOfStrings(rawProfile.isolatedSkills),
|
|
@@ -571,13 +489,6 @@ export function normalizeSubagentTypeProfile(
|
|
|
571
489
|
}
|
|
572
490
|
|
|
573
491
|
function mergeConfig(target: SubagentConfig, source: Partial<SubagentConfig>): void {
|
|
574
|
-
if (source.defaultType) target.defaultType = source.defaultType;
|
|
575
|
-
if (source.routing) target.routing = { ...(target.routing ?? {}), ...source.routing };
|
|
576
|
-
if (source.vision) target.vision = { ...(target.vision ?? {}), ...compactVisionConfig(source.vision) };
|
|
577
|
-
if (source.maxConcurrent !== undefined) target.maxConcurrent = source.maxConcurrent;
|
|
578
|
-
if (source.maxResultBytes !== undefined) target.maxResultBytes = source.maxResultBytes;
|
|
579
|
-
if (source.timeoutMs !== undefined) target.timeoutMs = source.timeoutMs;
|
|
580
|
-
if (source.retry) target.retry = { ...(target.retry ?? {}), ...source.retry };
|
|
581
492
|
for (const [name, profile] of Object.entries(source.types ?? {})) {
|
|
582
493
|
target.types[name] = mergeTypeProfile(target.types[name] ?? {}, profile);
|
|
583
494
|
}
|
|
@@ -585,17 +496,12 @@ function mergeConfig(target: SubagentConfig, source: Partial<SubagentConfig>): v
|
|
|
585
496
|
target.presets = target.presets ?? {};
|
|
586
497
|
const previous = target.presets[name] ?? {};
|
|
587
498
|
const merged = { ...previous, ...compactPreset(preset) };
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
delete merged.extraArgs;
|
|
595
|
-
delete merged.timeoutMs;
|
|
596
|
-
} else if (preset.model || preset.fallbackModels !== undefined || preset.types) {
|
|
597
|
-
delete merged.models;
|
|
598
|
-
}
|
|
499
|
+
delete merged.model;
|
|
500
|
+
delete merged.fallbackModels;
|
|
501
|
+
delete merged.types;
|
|
502
|
+
delete merged.thinking;
|
|
503
|
+
delete merged.extraArgs;
|
|
504
|
+
delete merged.timeoutMs;
|
|
599
505
|
target.presets[name] = merged;
|
|
600
506
|
}
|
|
601
507
|
}
|
|
@@ -618,36 +524,6 @@ function mergeTypeProfile(base: SubagentTypeConfig, source: SubagentTypeConfig):
|
|
|
618
524
|
return merged;
|
|
619
525
|
}
|
|
620
526
|
|
|
621
|
-
function normalizeRoutingConfig(value: Record<string, unknown>): SubagentRoutingConfig {
|
|
622
|
-
const routing: SubagentRoutingConfig = {};
|
|
623
|
-
if (typeof value.enabled === "boolean") routing.enabled = value.enabled;
|
|
624
|
-
if (typeof value.model === "string" && value.model.trim()) routing.model = value.model.trim();
|
|
625
|
-
const fallbackModels = modelList(value.fallbackModels, value.fallbackModel);
|
|
626
|
-
if (fallbackModels) routing.fallbackModels = fallbackModels;
|
|
627
|
-
if (typeof value.debug === "boolean") routing.debug = value.debug;
|
|
628
|
-
const maxTaskChars = finiteNumber(value.maxTaskChars);
|
|
629
|
-
if (maxTaskChars !== undefined) routing.maxTaskChars = Math.max(100, Math.round(maxTaskChars));
|
|
630
|
-
const maxTokens = finiteNumber(value.maxTokens);
|
|
631
|
-
if (maxTokens !== undefined) routing.maxTokens = Math.max(8, Math.round(maxTokens));
|
|
632
|
-
const maxRetries = finiteNumber(value.maxRetries);
|
|
633
|
-
if (maxRetries !== undefined) routing.maxRetries = Math.max(0, Math.round(maxRetries));
|
|
634
|
-
const timeoutMs = finiteNumber(value.timeoutMs);
|
|
635
|
-
if (timeoutMs !== undefined) routing.timeoutMs = Math.max(1000, Math.round(timeoutMs));
|
|
636
|
-
return routing;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function normalizeVisionConfig(value: Record<string, unknown>): SubagentVisionConfig | undefined {
|
|
640
|
-
const rawVision = value.vision;
|
|
641
|
-
const output: SubagentVisionConfig = {};
|
|
642
|
-
if (isRecord(rawVision)) {
|
|
643
|
-
const patterns = patternList(rawVision.blindModelPatterns, rawVision.blindModelPattern, rawVision.blindModels, rawVision.blindModelMasks, rawVision.blindModelMask);
|
|
644
|
-
if (patterns) output.blindModelPatterns = patterns;
|
|
645
|
-
}
|
|
646
|
-
const topLevelPatterns = patternList(value.blindModelPatterns, value.blindModelPattern, value.blindModels, value.blindModelMasks, value.blindModelMask);
|
|
647
|
-
if (topLevelPatterns) output.blindModelPatterns = topLevelPatterns;
|
|
648
|
-
return output.blindModelPatterns !== undefined ? output : undefined;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
527
|
function matchesAnyModelPattern(modelRef: string, patterns: string[]): boolean {
|
|
652
528
|
return patterns.some((pattern) => modelPatternRegExp(pattern).test(modelRef));
|
|
653
529
|
}
|
|
@@ -665,6 +541,8 @@ function compactProfile(profile: SubagentTypeConfig): SubagentTypeConfig {
|
|
|
665
541
|
if (profile.model) compact.model = profile.model;
|
|
666
542
|
if (profile.fallbackModels) compact.fallbackModels = profile.fallbackModels;
|
|
667
543
|
if (profile.modelByParent) compact.modelByParent = profile.modelByParent;
|
|
544
|
+
if (profile.forParentModels !== undefined) compact.forParentModels = profile.forParentModels;
|
|
545
|
+
if (profile.notForParentModels !== undefined) compact.notForParentModels = profile.notForParentModels;
|
|
668
546
|
if (profile.thinking) compact.thinking = profile.thinking;
|
|
669
547
|
if (profile.tools && profile.tools.length > 0) compact.tools = profile.tools;
|
|
670
548
|
if (profile.isolatedSkills && profile.isolatedSkills.length > 0) compact.isolatedSkills = profile.isolatedSkills;
|
|
@@ -690,34 +568,6 @@ function compactPreset(preset: SubagentPreset): SubagentPreset {
|
|
|
690
568
|
return compact;
|
|
691
569
|
}
|
|
692
570
|
|
|
693
|
-
function compactVisionConfig(vision: SubagentVisionConfig): SubagentVisionConfig {
|
|
694
|
-
const compact: SubagentVisionConfig = {};
|
|
695
|
-
if (vision.blindModelPatterns) compact.blindModelPatterns = vision.blindModelPatterns;
|
|
696
|
-
return compact;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function normalizePresetTypeOverrides(value: unknown, file: string, presetName: string): Record<string, SubagentPresetTypeOverride> | undefined {
|
|
700
|
-
if (value === undefined) return undefined;
|
|
701
|
-
if (!isRecord(value)) throw new Error(`Subagent preset "${presetName}" types must be an object: ${file}`);
|
|
702
|
-
const types: Record<string, SubagentPresetTypeOverride> = {};
|
|
703
|
-
for (const [name, rawOverride] of Object.entries(value)) {
|
|
704
|
-
if (!isRecord(rawOverride)) throw new Error(`Subagent preset "${presetName}" type override "${name}" must be an object: ${file}`);
|
|
705
|
-
const override: SubagentPresetTypeOverride = {};
|
|
706
|
-
const model = trimString(rawOverride.model);
|
|
707
|
-
const fallbackModels = modelList(rawOverride.fallbackModels, rawOverride.fallbackModel);
|
|
708
|
-
const thinking = trimString(rawOverride.thinking);
|
|
709
|
-
const extraArgs = arrayOfStrings(rawOverride.extraArgs);
|
|
710
|
-
const timeoutMs = positiveMilliseconds(rawOverride.timeoutMs);
|
|
711
|
-
if (model) override.model = model;
|
|
712
|
-
if (fallbackModels) override.fallbackModels = fallbackModels;
|
|
713
|
-
if (thinking) override.thinking = thinking;
|
|
714
|
-
if (extraArgs && extraArgs.length > 0) override.extraArgs = extraArgs;
|
|
715
|
-
if (timeoutMs !== undefined) override.timeoutMs = timeoutMs;
|
|
716
|
-
if (override.model || override.fallbackModels || override.thinking || override.extraArgs || override.timeoutMs !== undefined) types[name] = override;
|
|
717
|
-
}
|
|
718
|
-
return Object.keys(types).length > 0 ? types : undefined;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
571
|
function normalizeModels(value: unknown, owner: string, file: string): string[] | undefined {
|
|
722
572
|
if (value === undefined) return undefined;
|
|
723
573
|
if (!Array.isArray(value) || value.some((ref) => typeof ref !== "string" || !/^[^\s/*]+\/[^\s*]+$/.test(ref.trim()))) {
|
|
@@ -726,6 +576,19 @@ function normalizeModels(value: unknown, owner: string, file: string): string[]
|
|
|
726
576
|
return [...new Set(value.map((ref: string) => ref.trim()))];
|
|
727
577
|
}
|
|
728
578
|
|
|
579
|
+
function normalizeParentModelPatterns(
|
|
580
|
+
value: unknown,
|
|
581
|
+
field: "forParentModels" | "notForParentModels",
|
|
582
|
+
typeName: string,
|
|
583
|
+
file: string,
|
|
584
|
+
): string[] | undefined {
|
|
585
|
+
if (value === undefined) return undefined;
|
|
586
|
+
if (!Array.isArray(value) || value.some((pattern) => typeof pattern !== "string" || !pattern.trim())) {
|
|
587
|
+
throw new Error(`Subagent type "${typeName}" ${field} must be an array of non-empty model patterns: ${file}`);
|
|
588
|
+
}
|
|
589
|
+
return [...new Set(value.map((pattern: string) => pattern.trim()))];
|
|
590
|
+
}
|
|
591
|
+
|
|
729
592
|
function normalizeModelByParent(value: unknown, typeName: string, file: string): Record<string, ModelByParentEntry> | undefined {
|
|
730
593
|
if (value === undefined || value === null) return undefined;
|
|
731
594
|
if (!isRecord(value)) throw new Error(`Subagent type "${typeName}" modelByParent must be an object: ${file}`);
|
|
@@ -806,55 +669,23 @@ function applyEnvRoutingOverrides(config: SubagentConfig, env: NodeJS.ProcessEnv
|
|
|
806
669
|
config.routing = routing;
|
|
807
670
|
}
|
|
808
671
|
|
|
809
|
-
function
|
|
810
|
-
|
|
672
|
+
function applyEnvRuntimeOverrides(config: SubagentConfig, env: NodeJS.ProcessEnv): void {
|
|
673
|
+
const maxConcurrent = finiteEnvNumber(env.PI_SUBAGENTS_MAX_CONCURRENT || env.ASYNC_SUBAGENTS_MAX_CONCURRENT);
|
|
674
|
+
if (maxConcurrent !== undefined) config.maxConcurrent = Math.max(0, Math.round(maxConcurrent));
|
|
675
|
+
const maxResultBytes = finiteEnvNumber(env.PI_SUBAGENTS_MAX_RESULT_BYTES || env.ASYNC_SUBAGENTS_MAX_RESULT_BYTES);
|
|
676
|
+
if (maxResultBytes !== undefined) config.maxResultBytes = Math.max(0, Math.round(maxResultBytes));
|
|
677
|
+
const timeoutMs = finiteEnvNumber(env.PI_SUBAGENTS_TIMEOUT_MS || env.ASYNC_SUBAGENTS_TIMEOUT_MS);
|
|
678
|
+
if (timeoutMs !== undefined) config.timeoutMs = Math.max(1, Math.round(timeoutMs));
|
|
811
679
|
}
|
|
812
680
|
|
|
813
|
-
function
|
|
814
|
-
|
|
815
|
-
let inString = false;
|
|
816
|
-
let quote = "";
|
|
817
|
-
let escaped = false;
|
|
818
|
-
for (let i = 0; i < input.length; i++) {
|
|
819
|
-
const ch = input[i];
|
|
820
|
-
const next = input[i + 1];
|
|
821
|
-
if (inString) {
|
|
822
|
-
output += ch;
|
|
823
|
-
if (escaped) escaped = false;
|
|
824
|
-
else if (ch === "\\") escaped = true;
|
|
825
|
-
else if (ch === quote) inString = false;
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
if (ch === '"' || ch === "'") {
|
|
829
|
-
inString = true;
|
|
830
|
-
quote = ch;
|
|
831
|
-
output += ch;
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
if (ch === "/" && next === "/") {
|
|
835
|
-
while (i < input.length && input[i] !== "\n") i++;
|
|
836
|
-
output += "\n";
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
if (ch === "/" && next === "*") {
|
|
840
|
-
i += 2;
|
|
841
|
-
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
842
|
-
i++;
|
|
843
|
-
continue;
|
|
844
|
-
}
|
|
845
|
-
output += ch;
|
|
846
|
-
}
|
|
847
|
-
return output.replace(/,\s*([}\]])/g, "$1");
|
|
681
|
+
function typeEnvKey(typeName: string): string {
|
|
682
|
+
return typeName.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
848
683
|
}
|
|
849
684
|
|
|
850
685
|
function cloneConfig(config: SubagentConfig): SubagentConfig {
|
|
851
686
|
return JSON.parse(JSON.stringify(config)) as SubagentConfig;
|
|
852
687
|
}
|
|
853
688
|
|
|
854
|
-
function expandHome(value: string): string {
|
|
855
|
-
return value === "~" || value.startsWith("~/") ? path.join(os.homedir(), value.slice(2)) : value;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
689
|
function trimString(value: unknown): string | undefined {
|
|
859
690
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
860
691
|
}
|
|
@@ -874,10 +705,6 @@ function finiteEnvNumber(value: unknown): number | undefined {
|
|
|
874
705
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
875
706
|
}
|
|
876
707
|
|
|
877
|
-
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
878
|
-
return error instanceof Error && "code" in error;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
708
|
function arrayOfStrings(value: unknown): string[] | undefined {
|
|
882
709
|
if (!Array.isArray(value)) return undefined;
|
|
883
710
|
const items = value.filter((item): item is string => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
@@ -899,23 +726,6 @@ function modelList(...values: unknown[]): string[] | undefined {
|
|
|
899
726
|
return models.length > 0 || values.some(Array.isArray) ? models : undefined;
|
|
900
727
|
}
|
|
901
728
|
|
|
902
|
-
function patternList(...values: unknown[]): string[] | undefined {
|
|
903
|
-
let sawArray = false;
|
|
904
|
-
const seen = new Set<string>();
|
|
905
|
-
const patterns: string[] = [];
|
|
906
|
-
for (const value of values) {
|
|
907
|
-
if (Array.isArray(value)) sawArray = true;
|
|
908
|
-
const items = Array.isArray(value) ? value : [value];
|
|
909
|
-
for (const item of items) {
|
|
910
|
-
const pattern = trimString(item);
|
|
911
|
-
if (!pattern || seen.has(pattern)) continue;
|
|
912
|
-
seen.add(pattern);
|
|
913
|
-
patterns.push(pattern);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
return sawArray || patterns.length > 0 ? patterns : undefined;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
729
|
function textBlock(value: unknown): string | undefined {
|
|
920
730
|
if (typeof value === "string") return trimString(value);
|
|
921
731
|
const lines = arrayOfStrings(value);
|
|
@@ -4,6 +4,7 @@ import type { AgentTask } from "./types.js";
|
|
|
4
4
|
import {
|
|
5
5
|
currentModelRef,
|
|
6
6
|
defaultSubagentType,
|
|
7
|
+
filterSubagentConfigForParentModel,
|
|
7
8
|
resolveSubagentRoutingConfig,
|
|
8
9
|
type ResolvedSubagentRoutingConfig,
|
|
9
10
|
type SubagentConfig,
|
|
@@ -45,7 +46,7 @@ export class SubagentRoutingError extends Error {
|
|
|
45
46
|
const ROUTER_SYSTEM_PROMPT = [
|
|
46
47
|
"You route Pi async sub-agent tasks to the best configured subagentType.",
|
|
47
48
|
"Choose exactly one allowed type for each task. Use the allowed type descriptions as the source of truth.",
|
|
48
|
-
"Prefer a matching project specialist. Otherwise use research for reading/review
|
|
49
|
+
"Prefer a matching project specialist. Otherwise use research for reading/evidence or focused review questions, implement for code/docs/tests/UI changes, verify for running checks, browser-qa for real-browser testing, and frontier-review for an independent post-implementation code review when that type is allowed. Oracle is a deliberate strong second opinion, not routine code review or the default for difficult work.",
|
|
49
50
|
"Return only strict JSON with this shape: {\"routes\":[{\"id\":\"task-id\",\"subagentType\":\"type\"}]}",
|
|
50
51
|
"Do not include markdown, comments, explanations, or unknown types.",
|
|
51
52
|
].join("\n");
|
|
@@ -61,11 +62,23 @@ export async function routeSubagentTasks(
|
|
|
61
62
|
tasks = tasks.map((task) => hasText(task.subagentType) && task.subagentType !== task.subagentType.trim()
|
|
62
63
|
? { ...task, subagentType: task.subagentType.trim() }
|
|
63
64
|
: task);
|
|
65
|
+
const parentModel = currentModelRef(ctx.model);
|
|
66
|
+
const effectiveConfig = filterSubagentConfigForParentModel(config, parentModel);
|
|
64
67
|
const invalidTasks = tasks.filter((task) => hasText(task.subagentType)
|
|
65
68
|
&& !Object.prototype.hasOwnProperty.call(config.types, task.subagentType));
|
|
66
69
|
if (invalidTasks.length > 0) {
|
|
67
|
-
throw routingError(`Unknown subagentType: ${invalidTasks.map((task) => `${task.id}=${JSON.stringify(task.subagentType)}`).join(", ")}.`, invalidTasks,
|
|
70
|
+
throw routingError(`Unknown subagentType: ${invalidTasks.map((task) => `${task.id}=${JSON.stringify(task.subagentType)}`).join(", ")}.`, invalidTasks, effectiveConfig);
|
|
71
|
+
}
|
|
72
|
+
const unavailableTasks = tasks.filter((task) => hasText(task.subagentType)
|
|
73
|
+
&& !Object.prototype.hasOwnProperty.call(effectiveConfig.types, task.subagentType));
|
|
74
|
+
if (unavailableTasks.length > 0) {
|
|
75
|
+
throw routingError(
|
|
76
|
+
`subagentType unavailable for parent model ${parentModel ?? "(unknown)"}: ${unavailableTasks.map((task) => `${task.id}=${JSON.stringify(task.subagentType)}`).join(", ")}.`,
|
|
77
|
+
unavailableTasks,
|
|
78
|
+
effectiveConfig,
|
|
79
|
+
);
|
|
68
80
|
}
|
|
81
|
+
config = effectiveConfig;
|
|
69
82
|
const autoTasks = tasks.filter((task) => !hasText(task.subagentType));
|
|
70
83
|
if (autoTasks.length === 0) return { tasks, usedLlm: false, routes: {}, warnings: [] };
|
|
71
84
|
|
|
@@ -696,10 +696,14 @@ function compactRpcEventForProgress(event: RpcEventRecord): Record<string, unkno
|
|
|
696
696
|
toolName: typeof event.toolName === "string" ? event.toolName : undefined,
|
|
697
697
|
});
|
|
698
698
|
}
|
|
699
|
-
if (event.type === "message_end") {
|
|
699
|
+
if (event.type === "message_start" || event.type === "message_end") {
|
|
700
700
|
return stripUndefined({
|
|
701
701
|
type: event.type,
|
|
702
|
-
role:
|
|
702
|
+
role: typeof event.role === "string"
|
|
703
|
+
? event.role
|
|
704
|
+
: isRecord(event.message) && typeof event.message.role === "string"
|
|
705
|
+
? event.message.role
|
|
706
|
+
: undefined,
|
|
703
707
|
stopReason: isRecord(event.message) && typeof event.message.stopReason === "string" ? event.message.stopReason : undefined,
|
|
704
708
|
});
|
|
705
709
|
}
|