pi-ui-extend 1.0.41 → 1.0.45
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 +50 -13
- package/dist/app/app.d.ts +13 -0
- package/dist/app/app.js +158 -8
- package/dist/app/cli/install.js +2 -2
- package/dist/app/commands/command-controller.d.ts +4 -0
- package/dist/app/commands/command-controller.js +11 -0
- package/dist/app/commands/command-git-actions.d.ts +25 -0
- package/dist/app/commands/command-git-actions.js +381 -0
- package/dist/app/commands/command-host.d.ts +4 -0
- package/dist/app/commands/command-host.js +5 -2
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +57 -35
- package/dist/app/commands/command-navigation-actions.d.ts +1 -0
- package/dist/app/commands/command-navigation-actions.js +16 -3
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +15 -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 +70 -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/input/autocomplete-controller.js +61 -34
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/input/input-controller.d.ts +2 -1
- package/dist/app/input/input-controller.js +7 -2
- package/dist/app/input/prompt-enhancer-controller.js +53 -35
- package/dist/app/input/voice-controller.d.ts +41 -45
- package/dist/app/input/voice-controller.js +351 -419
- package/dist/app/model/model-usage-controller.js +17 -7
- package/dist/app/model/model-usage-status.d.ts +4 -1
- package/dist/app/model/model-usage-status.js +270 -46
- package/dist/app/popup/menu-items-controller.d.ts +13 -3
- package/dist/app/popup/menu-items-controller.js +37 -21
- package/dist/app/popup/popup-action-controller.d.ts +12 -2
- package/dist/app/popup/popup-action-controller.js +77 -24
- package/dist/app/popup/popup-menu-controller.d.ts +36 -14
- package/dist/app/popup/popup-menu-controller.js +239 -69
- package/dist/app/rendering/dcp-stats.d.ts +9 -4
- package/dist/app/rendering/dcp-stats.js +40 -425
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
- package/dist/app/rendering/popup-menu-renderer.js +42 -37
- package/dist/app/rendering/render-controller.js +23 -2
- package/dist/app/rendering/status-line-renderer.d.ts +5 -0
- package/dist/app/rendering/status-line-renderer.js +41 -5
- package/dist/app/rendering/tab-line-renderer.js +26 -20
- package/dist/app/runtime.d.ts +12 -1
- package/dist/app/runtime.js +120 -12
- package/dist/app/screen/mouse-controller.d.ts +2 -0
- package/dist/app/screen/mouse-controller.js +17 -7
- package/dist/app/screen/status-controller.d.ts +4 -0
- package/dist/app/screen/status-controller.js +5 -0
- package/dist/app/session/lazy-session-manager.js +34 -0
- package/dist/app/session/session-event-controller.d.ts +1 -0
- package/dist/app/session/session-event-controller.js +10 -1
- package/dist/app/session/session-history.d.ts +1 -0
- package/dist/app/session/session-history.js +12 -1
- package/dist/app/session/session-lifecycle-controller.d.ts +7 -1
- package/dist/app/session/session-lifecycle-controller.js +16 -1
- package/dist/app/session/tabs-controller.d.ts +26 -1
- package/dist/app/session/tabs-controller.js +454 -147
- 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/subagents/subagents-widget-controller.d.ts +1 -0
- package/dist/app/subagents/subagents-widget-controller.js +6 -0
- package/dist/app/types.d.ts +16 -1
- package/dist/app/workspace/workspace-actions-controller.js +10 -2
- package/dist/app/workspace/workspace-undo.d.ts +1 -0
- package/dist/app/workspace/workspace-undo.js +1 -0
- package/dist/bundled-extensions/question/index.js +9 -1
- package/dist/bundled-extensions/question/remote.d.ts +4 -0
- package/dist/bundled-extensions/question/remote.js +33 -0
- package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
- package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
- package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
- package/dist/bundled-extensions/telegram-connector/config.js +87 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
- package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
- package/dist/bundled-extensions/telegram-connector/index.js +195 -0
- package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
- package/dist/bundled-extensions/terminal-bell/index.js +0 -76
- package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
- package/dist/bundled-extensions/workspace-undo/index.js +191 -0
- package/dist/config.d.ts +21 -2
- package/dist/config.js +159 -32
- package/dist/default-pix-config.js +25 -5
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +88 -62
- package/dist/schemas/pi-tools-suite-schema.js +54 -83
- package/dist/schemas/pix-schema.d.ts +19 -2
- package/dist/schemas/pix-schema.js +47 -5
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +9 -1
- package/docs/desktop-mvp.md +22 -6
- package/docs/desktop-task-manager.md +148 -84
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +197 -84
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +47 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +48 -30
- package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
- package/external/pi-tools-suite/package.json +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
- package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
- package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
- package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
- 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/agents/ui-qa/backends/browser.mjs +346 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
- package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +15 -71
- 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 -5
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +160 -327
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +21 -5
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +40 -26
- 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/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +16 -9
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -5
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +55 -1
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +77 -3
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +222 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +206 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +7 -7
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +16 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +128 -49
- package/external/pi-tools-suite/src/dcp/commands.ts +32 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +177 -46
- 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/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +114 -17
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
- package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
- package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
- package/external/pi-tools-suite/src/dcp/index.ts +338 -66
- package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
- 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 +93 -3
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
- package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -0
- package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
- package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +78 -22
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/lib/project.ts +36 -1
- package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
- package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
- package/external/pi-tools-suite/src/resource-registry/index.ts +2551 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/todo/index.ts +21 -0
- package/external/pi-tools-suite/src/todo/todo.ts +19 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +47 -19
- package/package.json +10 -9
- package/schemas/pi-tools-suite.json +466 -287
- package/schemas/pix.json +129 -11
- package/schemas/tasks.json +131 -0
- package/skills/simplify/SKILL.md +33 -5
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -177
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- 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/playwright-cli/SKILL.md +0 -420
- package/skills/playwright-cli/references/element-attributes.md +0 -23
- package/skills/playwright-cli/references/playwright-tests.md +0 -50
- package/skills/playwright-cli/references/request-mocking.md +0 -87
- package/skills/playwright-cli/references/running-code.md +0 -241
- package/skills/playwright-cli/references/session-management.md +0 -273
- package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
- package/skills/playwright-cli/references/storage-state.md +0 -290
- package/skills/playwright-cli/references/test-generation.md +0 -142
- package/skills/playwright-cli/references/tracing.md +0 -154
- package/skills/playwright-cli/references/video-recording.md +0 -147
- package/skills/spec-lite/SKILL.md +0 -140
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
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 {
|
|
4
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
5
|
+
import { projectAgentsDir, readAgentDefinitionsFromDir, readProjectAgentDefinitions, type AgentDefinition } from "./agents-dir.js";
|
|
6
|
+
import { LEGACY_BROWSER_QA_TYPE, UI_QA_TYPE } from "./browser-qa.js";
|
|
8
7
|
import type { AgentTask, RetryConfig } from "./types.js";
|
|
9
8
|
|
|
10
9
|
export interface ModelByParentEntry {
|
|
11
10
|
/** Model ref to use when the parent model matches the entry's pattern. */
|
|
12
11
|
model: string;
|
|
13
12
|
/** Ordered fallbacks used when this entry's model hits quota/rate limits; replaces the normal fallback chain. */
|
|
14
|
-
fallbackModels
|
|
13
|
+
fallbackModels: string[];
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export interface SubagentTypeConfig {
|
|
@@ -31,15 +30,24 @@ export interface SubagentTypeConfig {
|
|
|
31
30
|
/**
|
|
32
31
|
* Parent-model-aware model selection. Keys are glob model refs (e.g. "zai/*")
|
|
33
32
|
* matched against the current parent model; the first matching key wins.
|
|
34
|
-
*
|
|
33
|
+
* Raw values may be a model ref string or { model, fallbackModels? };
|
|
34
|
+
* normalized entries always carry an explicit fallbackModels array.
|
|
35
35
|
* Ordinary roles: explicit task/forced model and preset models take priority.
|
|
36
36
|
* Oracle alone keeps parent-aware selection ahead of presets.
|
|
37
37
|
*/
|
|
38
38
|
modelByParent?: Record<string, ModelByParentEntry>;
|
|
39
|
+
/**
|
|
40
|
+
* Optional parent-model allow-list. When present, this role is exposed only
|
|
41
|
+
* when the current parent model matches at least one pattern.
|
|
42
|
+
*/
|
|
43
|
+
forParentModels?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Optional parent-model deny-list. A match hides the role even when
|
|
46
|
+
* forParentModels also matches.
|
|
47
|
+
*/
|
|
48
|
+
notForParentModels?: string[];
|
|
39
49
|
thinking?: string;
|
|
40
50
|
tools?: string[];
|
|
41
|
-
/** Explicit skill files loaded after disabling normal skill discovery. */
|
|
42
|
-
isolatedSkills?: string[];
|
|
43
51
|
extraArgs?: string[];
|
|
44
52
|
/** Extra prompt text appended after the generated or overridden prompt. */
|
|
45
53
|
promptAppend?: string;
|
|
@@ -125,18 +133,9 @@ 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[];
|
|
138
|
-
/** Explicit skill files loaded with normal skill discovery disabled. */
|
|
139
|
-
isolatedSkills: string[];
|
|
140
139
|
/** Ordered model fallbacks for the resolved model. Current-process exhausted models are skipped before spawning. */
|
|
141
140
|
fallbackModels: string[];
|
|
142
141
|
profile?: SubagentTypeConfig;
|
|
@@ -192,92 +191,73 @@ export const DEFAULT_ROUTING_CONFIG: ResolvedSubagentRoutingConfig = {
|
|
|
192
191
|
fallbackModels: ["openai-codex/gpt-5.6-luna"],
|
|
193
192
|
maxTaskChars: 1200,
|
|
194
193
|
maxTokens: 512,
|
|
195
|
-
maxRetries:
|
|
196
|
-
timeoutMs:
|
|
194
|
+
maxRetries: 1,
|
|
195
|
+
timeoutMs: 12_000,
|
|
197
196
|
debug: false,
|
|
198
197
|
};
|
|
199
198
|
|
|
200
199
|
const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "agents");
|
|
200
|
+
const BUILTIN_PRESETS_FILE = path.join(BUILTIN_AGENTS_DIR, "presets.jsonc");
|
|
201
|
+
const DEFAULT_BLIND_MODEL_PATTERNS = [
|
|
202
|
+
"zai/glm-4.5*", "glm-4.5*", "*/glm-4.5*",
|
|
203
|
+
"zai/glm-5-turbo*", "glm-5-turbo*", "*/glm-5-turbo*",
|
|
204
|
+
"zai/glm-5.3", "glm-5.3", "*/glm-5.3",
|
|
205
|
+
];
|
|
201
206
|
|
|
202
207
|
const BUILTIN_CONFIG: SubagentConfig = {
|
|
203
208
|
defaultType: "research",
|
|
204
209
|
maxConcurrent: DEFAULT_MAX_CONCURRENT,
|
|
210
|
+
maxResultBytes: 100_000,
|
|
205
211
|
routing: { ...DEFAULT_ROUTING_CONFIG },
|
|
212
|
+
vision: { blindModelPatterns: DEFAULT_BLIND_MODEL_PATTERNS },
|
|
213
|
+
presets: readPresetConfigFile(BUILTIN_PRESETS_FILE).presets,
|
|
206
214
|
types: normalizeAgentDefinitions(readAgentDefinitionsFromDir(BUILTIN_AGENTS_DIR)),
|
|
207
215
|
};
|
|
208
216
|
|
|
209
217
|
export function loadSubagentConfig(cwd: string, env: NodeJS.ProcessEnv = process.env): SubagentConfig {
|
|
210
218
|
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
|
-
}
|
|
219
|
+
mergeConfig(config, projectPresetConfig(cwd));
|
|
220
|
+
// Project-local agent definitions (.pi/agents/*.md) are the only project
|
|
221
|
+
// source of role/profile configuration and are loaded fresh on every call.
|
|
222
|
+
mergeConfig(config, projectAgentTypes(cwd));
|
|
223
223
|
applyEnvModelOverrides(config, env);
|
|
224
224
|
applyEnvRoutingOverrides(config, env);
|
|
225
|
+
applyEnvRuntimeOverrides(config, env);
|
|
225
226
|
return config;
|
|
226
227
|
}
|
|
227
228
|
|
|
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
229
|
export function getBuiltinSubagentDefinitionsDir(): string {
|
|
252
230
|
return BUILTIN_AGENTS_DIR;
|
|
253
231
|
}
|
|
254
232
|
|
|
255
|
-
export function
|
|
256
|
-
return
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function existingSubagentConfigFiles(cwd: string, env: NodeJS.ProcessEnv = process.env): string[] {
|
|
260
|
-
const explicit = explicitSubagentConfigPath(cwd, env);
|
|
261
|
-
if (explicit) return fs.existsSync(explicit) ? [explicit] : [];
|
|
262
|
-
return piToolsSuiteConfigFiles(cwd, env).filter(hasPiToolsSuiteSubagentConfig);
|
|
233
|
+
export function getBuiltinSubagentPresetsPath(): string {
|
|
234
|
+
return BUILTIN_PRESETS_FILE;
|
|
263
235
|
}
|
|
264
236
|
|
|
265
|
-
function
|
|
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));
|
|
237
|
+
export function getProjectSubagentPresetsPath(cwd: string): string {
|
|
238
|
+
return path.join(projectAgentsDir(cwd) ?? path.join(path.resolve(cwd), ".pi", "agents"), "presets.jsonc");
|
|
271
239
|
}
|
|
272
240
|
|
|
273
241
|
/** Normalize `.pi/agents/*.md` definitions through the shared type-profile path. */
|
|
274
242
|
function projectAgentTypes(cwd: string): Partial<SubagentConfig> {
|
|
275
243
|
const definitions = readProjectAgentDefinitions(cwd);
|
|
244
|
+
const legacyUiQa = definitions[LEGACY_BROWSER_QA_TYPE];
|
|
245
|
+
if (legacyUiQa) {
|
|
246
|
+
if (!definitions[UI_QA_TYPE]) definitions[UI_QA_TYPE] = legacyUiQa;
|
|
247
|
+
delete definitions[LEGACY_BROWSER_QA_TYPE];
|
|
248
|
+
}
|
|
276
249
|
const types = normalizeAgentDefinitions(definitions);
|
|
277
250
|
if (Object.keys(types).length === 0) return {};
|
|
278
251
|
return { types };
|
|
279
252
|
}
|
|
280
253
|
|
|
254
|
+
function projectPresetConfig(cwd: string): Partial<SubagentConfig> {
|
|
255
|
+
const dir = projectAgentsDir(cwd);
|
|
256
|
+
if (!dir) return {};
|
|
257
|
+
const file = path.join(dir, "presets.jsonc");
|
|
258
|
+
return fs.existsSync(file) ? readPresetConfigFile(file) : {};
|
|
259
|
+
}
|
|
260
|
+
|
|
281
261
|
function normalizeAgentDefinitions(definitions: Record<string, AgentDefinition>): Record<string, SubagentTypeConfig> {
|
|
282
262
|
const types: Record<string, SubagentTypeConfig> = {};
|
|
283
263
|
for (const [name, definition] of Object.entries(definitions)) {
|
|
@@ -286,42 +266,6 @@ function normalizeAgentDefinitions(definitions: Record<string, AgentDefinition>)
|
|
|
286
266
|
return types;
|
|
287
267
|
}
|
|
288
268
|
|
|
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
269
|
export function resolveAgentTaskConfig(
|
|
326
270
|
task: AgentTask,
|
|
327
271
|
config: SubagentConfig,
|
|
@@ -395,7 +339,6 @@ export function resolveAgentTaskConfig(
|
|
|
395
339
|
return {
|
|
396
340
|
profile,
|
|
397
341
|
extraArgs,
|
|
398
|
-
isolatedSkills: arrayOfStrings(profile?.isolatedSkills) ?? [],
|
|
399
342
|
fallbackModels,
|
|
400
343
|
retry: resolveRetryConfig(config.retry, profile?.retry),
|
|
401
344
|
maxResultBytes: profile?.maxResultBytes ?? config.maxResultBytes,
|
|
@@ -418,7 +361,7 @@ export function resolveSubagentRoutingConfig(config: SubagentConfig): ResolvedSu
|
|
|
418
361
|
}
|
|
419
362
|
|
|
420
363
|
export function defaultSubagentType(config: SubagentConfig): string | undefined {
|
|
421
|
-
const configured =
|
|
364
|
+
const configured = normalizeSubagentType(config.defaultType, config);
|
|
422
365
|
if (configured) return Object.prototype.hasOwnProperty.call(config.types, configured) ? configured : undefined;
|
|
423
366
|
return Object.keys(config.types).find((name) => trimString(name));
|
|
424
367
|
}
|
|
@@ -445,10 +388,11 @@ export function shouldForceCurrentSubagentModel(env: NodeJS.ProcessEnv = process
|
|
|
445
388
|
}
|
|
446
389
|
|
|
447
390
|
export function currentModelRef(model: unknown): string | undefined {
|
|
391
|
+
if (typeof model === "string") return trimString(model);
|
|
448
392
|
if (!isRecord(model)) return undefined;
|
|
449
|
-
const id = trimString(model.id);
|
|
393
|
+
const id = trimString(model.modelId) || trimString(model.id) || trimString(model.model) || trimString(model.name);
|
|
450
394
|
if (!id) return undefined;
|
|
451
|
-
const provider = trimString(model.provider);
|
|
395
|
+
const provider = trimString(model.provider) || trimString(model.providerId);
|
|
452
396
|
return provider && !id.includes("/") ? `${provider}/${id}` : id;
|
|
453
397
|
}
|
|
454
398
|
|
|
@@ -457,93 +401,76 @@ export function isBlindModelRef(modelRef: string | undefined, config: SubagentCo
|
|
|
457
401
|
return matchesAnyModelPattern(modelRef, config.vision?.blindModelPatterns ?? []);
|
|
458
402
|
}
|
|
459
403
|
|
|
404
|
+
/** Whether one role is available to the current parent model. Deny wins. */
|
|
405
|
+
export function isSubagentTypeAvailableForParent(
|
|
406
|
+
profile: SubagentTypeConfig,
|
|
407
|
+
parentModelRef: string | undefined,
|
|
408
|
+
): boolean {
|
|
409
|
+
const included = profile.forParentModels;
|
|
410
|
+
if (included !== undefined) {
|
|
411
|
+
if (!parentModelRef || !matchesAnyModelPattern(parentModelRef, included)) return false;
|
|
412
|
+
}
|
|
413
|
+
const excluded = profile.notForParentModels;
|
|
414
|
+
if (parentModelRef && excluded !== undefined && matchesAnyModelPattern(parentModelRef, excluded)) return false;
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Return the effective config visible to a particular parent model. Only the
|
|
420
|
+
* role catalog is filtered; presets, routing policy, retry defaults, etc. are
|
|
421
|
+
* preserved unchanged.
|
|
422
|
+
*/
|
|
423
|
+
export function filterSubagentConfigForParentModel(
|
|
424
|
+
config: SubagentConfig,
|
|
425
|
+
parentModelRef: string | undefined,
|
|
426
|
+
): SubagentConfig {
|
|
427
|
+
const types = Object.fromEntries(
|
|
428
|
+
Object.entries(config.types).filter(([, profile]) => isSubagentTypeAvailableForParent(profile, parentModelRef)),
|
|
429
|
+
);
|
|
430
|
+
return Object.keys(types).length === Object.keys(config.types).length ? config : { ...config, types };
|
|
431
|
+
}
|
|
432
|
+
|
|
460
433
|
export function selectSubagentType(task: AgentTask, config: SubagentConfig): string | undefined {
|
|
461
|
-
const explicit =
|
|
434
|
+
const explicit = normalizeSubagentType(task.subagentType, config);
|
|
462
435
|
if (explicit) return explicit;
|
|
463
436
|
return defaultSubagentType(config);
|
|
464
437
|
}
|
|
465
438
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
if (!
|
|
470
|
-
|
|
439
|
+
/** Preserve explicit legacy browser-qa calls after the built-in role was broadened to ui-qa. */
|
|
440
|
+
export function normalizeSubagentType(value: string | undefined, config: SubagentConfig): string | undefined {
|
|
441
|
+
const requested = trimString(value);
|
|
442
|
+
if (!requested) return undefined;
|
|
443
|
+
if (Object.prototype.hasOwnProperty.call(config.types, requested)) return requested;
|
|
444
|
+
if (requested === LEGACY_BROWSER_QA_TYPE && Object.prototype.hasOwnProperty.call(config.types, UI_QA_TYPE)) return UI_QA_TYPE;
|
|
445
|
+
return requested;
|
|
471
446
|
}
|
|
472
447
|
|
|
473
|
-
function
|
|
448
|
+
function readPresetConfigFile(file: string): Partial<SubagentConfig> {
|
|
474
449
|
const raw = fs.readFileSync(file, "utf-8");
|
|
475
450
|
const parsed = parseJsonc(raw) as unknown;
|
|
476
|
-
if (!isRecord(parsed))
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const edits = modify(original, ["asyncSubagents"], config, {
|
|
492
|
-
formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" },
|
|
493
|
-
});
|
|
494
|
-
const updated = applyEdits(original, edits);
|
|
495
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
496
|
-
fs.writeFileSync(file, updated.endsWith("\n") ? updated : `${updated}\n`, "utf-8");
|
|
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);
|
|
451
|
+
if (!isRecord(parsed)) throw new Error(`Subagent presets file must contain an object: ${file}`);
|
|
452
|
+
return { presets: normalizePresetMap(parsed, file, "preset file") };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function normalizePresetMap(value: unknown, file: string, owner: string): Record<string, SubagentPreset> {
|
|
456
|
+
if (!isRecord(value)) throw new Error(`Subagent ${owner} must be an object: ${file}`);
|
|
457
|
+
const presets: Record<string, SubagentPreset> = {};
|
|
458
|
+
for (const [name, rawPreset] of Object.entries(value)) {
|
|
459
|
+
if (!isRecord(rawPreset)) throw new Error(`Subagent preset "${name}" must be an object: ${file}`);
|
|
460
|
+
const models = normalizeModels(rawPreset.models, `preset "${name}"`, file);
|
|
461
|
+
if (models === undefined) throw new Error(`Subagent preset "${name}" must define models: ${file}`);
|
|
462
|
+
presets[name] = {
|
|
463
|
+
description: trimString(rawPreset.description),
|
|
464
|
+
models,
|
|
465
|
+
};
|
|
538
466
|
}
|
|
539
|
-
|
|
540
|
-
return output;
|
|
467
|
+
return presets;
|
|
541
468
|
}
|
|
542
469
|
|
|
543
470
|
/**
|
|
544
471
|
* Normalize one raw sub-agent type profile (`SubagentTypeConfig` shape) from
|
|
545
|
-
*
|
|
546
|
-
*
|
|
472
|
+
* an agent Markdown definition. Bundled and project files share the same
|
|
473
|
+
* validation and trimming path.
|
|
547
474
|
*/
|
|
548
475
|
export function normalizeSubagentTypeProfile(
|
|
549
476
|
rawProfile: Record<string, unknown>,
|
|
@@ -551,16 +478,19 @@ export function normalizeSubagentTypeProfile(
|
|
|
551
478
|
file: string,
|
|
552
479
|
): SubagentTypeConfig {
|
|
553
480
|
const models = normalizeModels(rawProfile.models, `type "${name}"`, file);
|
|
481
|
+
const model = models === undefined ? trimString(rawProfile.model) : undefined;
|
|
482
|
+
const fallbackModels = models === undefined ? modelList(rawProfile.fallbackModels, rawProfile.fallbackModel) : undefined;
|
|
554
483
|
return {
|
|
555
484
|
description: trimString(rawProfile.description),
|
|
556
485
|
icon: trimString(rawProfile.icon),
|
|
557
486
|
models,
|
|
558
|
-
model
|
|
559
|
-
fallbackModels: models === undefined
|
|
560
|
-
modelByParent: models === undefined ? normalizeModelByParent(rawProfile.modelByParent, name, file) : undefined,
|
|
487
|
+
model,
|
|
488
|
+
fallbackModels: models === undefined && (model || fallbackModels !== undefined) ? fallbackModels ?? [] : undefined,
|
|
489
|
+
modelByParent: models === undefined ? normalizeModelByParent(rawProfile.modelByParent, name, file, fallbackModels ?? []) : undefined,
|
|
490
|
+
forParentModels: normalizeParentModelPatterns(rawProfile.forParentModels, "forParentModels", name, file),
|
|
491
|
+
notForParentModels: normalizeParentModelPatterns(rawProfile.notForParentModels, "notForParentModels", name, file),
|
|
561
492
|
thinking: trimString(rawProfile.thinking),
|
|
562
493
|
tools: arrayOfStrings(rawProfile.tools),
|
|
563
|
-
isolatedSkills: arrayOfStrings(rawProfile.isolatedSkills),
|
|
564
494
|
extraArgs: arrayOfStrings(rawProfile.extraArgs),
|
|
565
495
|
promptAppend: textBlock(rawProfile.promptAppend),
|
|
566
496
|
promptOverride: textBlock(rawProfile.promptOverride),
|
|
@@ -571,13 +501,6 @@ export function normalizeSubagentTypeProfile(
|
|
|
571
501
|
}
|
|
572
502
|
|
|
573
503
|
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
504
|
for (const [name, profile] of Object.entries(source.types ?? {})) {
|
|
582
505
|
target.types[name] = mergeTypeProfile(target.types[name] ?? {}, profile);
|
|
583
506
|
}
|
|
@@ -585,17 +508,12 @@ function mergeConfig(target: SubagentConfig, source: Partial<SubagentConfig>): v
|
|
|
585
508
|
target.presets = target.presets ?? {};
|
|
586
509
|
const previous = target.presets[name] ?? {};
|
|
587
510
|
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
|
-
}
|
|
511
|
+
delete merged.model;
|
|
512
|
+
delete merged.fallbackModels;
|
|
513
|
+
delete merged.types;
|
|
514
|
+
delete merged.thinking;
|
|
515
|
+
delete merged.extraArgs;
|
|
516
|
+
delete merged.timeoutMs;
|
|
599
517
|
target.presets[name] = merged;
|
|
600
518
|
}
|
|
601
519
|
}
|
|
@@ -618,36 +536,6 @@ function mergeTypeProfile(base: SubagentTypeConfig, source: SubagentTypeConfig):
|
|
|
618
536
|
return merged;
|
|
619
537
|
}
|
|
620
538
|
|
|
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
539
|
function matchesAnyModelPattern(modelRef: string, patterns: string[]): boolean {
|
|
652
540
|
return patterns.some((pattern) => modelPatternRegExp(pattern).test(modelRef));
|
|
653
541
|
}
|
|
@@ -665,9 +553,10 @@ function compactProfile(profile: SubagentTypeConfig): SubagentTypeConfig {
|
|
|
665
553
|
if (profile.model) compact.model = profile.model;
|
|
666
554
|
if (profile.fallbackModels) compact.fallbackModels = profile.fallbackModels;
|
|
667
555
|
if (profile.modelByParent) compact.modelByParent = profile.modelByParent;
|
|
556
|
+
if (profile.forParentModels !== undefined) compact.forParentModels = profile.forParentModels;
|
|
557
|
+
if (profile.notForParentModels !== undefined) compact.notForParentModels = profile.notForParentModels;
|
|
668
558
|
if (profile.thinking) compact.thinking = profile.thinking;
|
|
669
559
|
if (profile.tools && profile.tools.length > 0) compact.tools = profile.tools;
|
|
670
|
-
if (profile.isolatedSkills && profile.isolatedSkills.length > 0) compact.isolatedSkills = profile.isolatedSkills;
|
|
671
560
|
if (profile.extraArgs && profile.extraArgs.length > 0) compact.extraArgs = profile.extraArgs;
|
|
672
561
|
if (profile.promptAppend) compact.promptAppend = profile.promptAppend;
|
|
673
562
|
if (profile.promptOverride) compact.promptOverride = profile.promptOverride;
|
|
@@ -690,34 +579,6 @@ function compactPreset(preset: SubagentPreset): SubagentPreset {
|
|
|
690
579
|
return compact;
|
|
691
580
|
}
|
|
692
581
|
|
|
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
582
|
function normalizeModels(value: unknown, owner: string, file: string): string[] | undefined {
|
|
722
583
|
if (value === undefined) return undefined;
|
|
723
584
|
if (!Array.isArray(value) || value.some((ref) => typeof ref !== "string" || !/^[^\s/*]+\/[^\s*]+$/.test(ref.trim()))) {
|
|
@@ -726,7 +587,25 @@ function normalizeModels(value: unknown, owner: string, file: string): string[]
|
|
|
726
587
|
return [...new Set(value.map((ref: string) => ref.trim()))];
|
|
727
588
|
}
|
|
728
589
|
|
|
729
|
-
function
|
|
590
|
+
function normalizeParentModelPatterns(
|
|
591
|
+
value: unknown,
|
|
592
|
+
field: "forParentModels" | "notForParentModels",
|
|
593
|
+
typeName: string,
|
|
594
|
+
file: string,
|
|
595
|
+
): string[] | undefined {
|
|
596
|
+
if (value === undefined) return undefined;
|
|
597
|
+
if (!Array.isArray(value) || value.some((pattern) => typeof pattern !== "string" || !pattern.trim())) {
|
|
598
|
+
throw new Error(`Subagent type "${typeName}" ${field} must be an array of non-empty model patterns: ${file}`);
|
|
599
|
+
}
|
|
600
|
+
return [...new Set(value.map((pattern: string) => pattern.trim()))];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function normalizeModelByParent(
|
|
604
|
+
value: unknown,
|
|
605
|
+
typeName: string,
|
|
606
|
+
file: string,
|
|
607
|
+
defaultFallbackModels: string[] = [],
|
|
608
|
+
): Record<string, ModelByParentEntry> | undefined {
|
|
730
609
|
if (value === undefined || value === null) return undefined;
|
|
731
610
|
if (!isRecord(value)) throw new Error(`Subagent type "${typeName}" modelByParent must be an object: ${file}`);
|
|
732
611
|
const out: Record<string, ModelByParentEntry> = {};
|
|
@@ -735,13 +614,20 @@ function normalizeModelByParent(value: unknown, typeName: string, file: string):
|
|
|
735
614
|
if (!pat) continue;
|
|
736
615
|
if (typeof raw === "string") {
|
|
737
616
|
const model = trimString(raw);
|
|
738
|
-
if (model) out[pat] = { model };
|
|
617
|
+
if (model) out[pat] = { model, fallbackModels: [...defaultFallbackModels] };
|
|
739
618
|
continue;
|
|
740
619
|
}
|
|
741
620
|
if (isRecord(raw)) {
|
|
742
621
|
const model = trimString(raw.model);
|
|
743
622
|
if (!model) throw new Error(`Subagent type "${typeName}" modelByParent["${pat}"].model must be a non-empty string: ${file}`);
|
|
744
|
-
|
|
623
|
+
const hasFallbackOverride = Object.prototype.hasOwnProperty.call(raw, "fallbackModels")
|
|
624
|
+
|| Object.prototype.hasOwnProperty.call(raw, "fallbackModel");
|
|
625
|
+
out[pat] = {
|
|
626
|
+
model,
|
|
627
|
+
fallbackModels: hasFallbackOverride
|
|
628
|
+
? modelList(raw.fallbackModels, raw.fallbackModel) ?? []
|
|
629
|
+
: [...defaultFallbackModels],
|
|
630
|
+
};
|
|
745
631
|
continue;
|
|
746
632
|
}
|
|
747
633
|
throw new Error(`Subagent type "${typeName}" modelByParent["${pat}"] must be a string or object: ${file}`);
|
|
@@ -806,55 +692,23 @@ function applyEnvRoutingOverrides(config: SubagentConfig, env: NodeJS.ProcessEnv
|
|
|
806
692
|
config.routing = routing;
|
|
807
693
|
}
|
|
808
694
|
|
|
809
|
-
function
|
|
810
|
-
|
|
695
|
+
function applyEnvRuntimeOverrides(config: SubagentConfig, env: NodeJS.ProcessEnv): void {
|
|
696
|
+
const maxConcurrent = finiteEnvNumber(env.PI_SUBAGENTS_MAX_CONCURRENT || env.ASYNC_SUBAGENTS_MAX_CONCURRENT);
|
|
697
|
+
if (maxConcurrent !== undefined) config.maxConcurrent = Math.max(0, Math.round(maxConcurrent));
|
|
698
|
+
const maxResultBytes = finiteEnvNumber(env.PI_SUBAGENTS_MAX_RESULT_BYTES || env.ASYNC_SUBAGENTS_MAX_RESULT_BYTES);
|
|
699
|
+
if (maxResultBytes !== undefined) config.maxResultBytes = Math.max(0, Math.round(maxResultBytes));
|
|
700
|
+
const timeoutMs = finiteEnvNumber(env.PI_SUBAGENTS_TIMEOUT_MS || env.ASYNC_SUBAGENTS_TIMEOUT_MS);
|
|
701
|
+
if (timeoutMs !== undefined) config.timeoutMs = Math.max(1, Math.round(timeoutMs));
|
|
811
702
|
}
|
|
812
703
|
|
|
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");
|
|
704
|
+
function typeEnvKey(typeName: string): string {
|
|
705
|
+
return typeName.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
848
706
|
}
|
|
849
707
|
|
|
850
708
|
function cloneConfig(config: SubagentConfig): SubagentConfig {
|
|
851
709
|
return JSON.parse(JSON.stringify(config)) as SubagentConfig;
|
|
852
710
|
}
|
|
853
711
|
|
|
854
|
-
function expandHome(value: string): string {
|
|
855
|
-
return value === "~" || value.startsWith("~/") ? path.join(os.homedir(), value.slice(2)) : value;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
712
|
function trimString(value: unknown): string | undefined {
|
|
859
713
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
860
714
|
}
|
|
@@ -874,10 +728,6 @@ function finiteEnvNumber(value: unknown): number | undefined {
|
|
|
874
728
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
875
729
|
}
|
|
876
730
|
|
|
877
|
-
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
878
|
-
return error instanceof Error && "code" in error;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
731
|
function arrayOfStrings(value: unknown): string[] | undefined {
|
|
882
732
|
if (!Array.isArray(value)) return undefined;
|
|
883
733
|
const items = value.filter((item): item is string => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
@@ -899,23 +749,6 @@ function modelList(...values: unknown[]): string[] | undefined {
|
|
|
899
749
|
return models.length > 0 || values.some(Array.isArray) ? models : undefined;
|
|
900
750
|
}
|
|
901
751
|
|
|
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
752
|
function textBlock(value: unknown): string | undefined {
|
|
920
753
|
if (typeof value === "string") return trimString(value);
|
|
921
754
|
const lines = arrayOfStrings(value);
|