pi-ui-extend 1.0.42 → 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 +41 -12
- package/dist/app/app.d.ts +10 -0
- package/dist/app/app.js +132 -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 +3 -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 +51 -34
- 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 +14 -0
- package/dist/app/commands/reload-context-inventory.js +12 -1
- 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/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 +6 -1
- package/dist/app/session/session-lifecycle-controller.js +10 -1
- package/dist/app/session/tabs-controller.d.ts +25 -1
- package/dist/app/session/tabs-controller.js +442 -153
- 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 +11 -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 +20 -10
- package/dist/schemas/pi-tools-suite-schema.d.ts +8 -4
- package/dist/schemas/pi-tools-suite-schema.js +11 -5
- package/dist/schemas/pix-schema.d.ts +12 -2
- package/dist/schemas/pix-schema.js +37 -9
- package/dist/schemas/tasks-schema.js +1 -1
- package/docs/concurrency.md +3 -1
- package/docs/desktop-mvp.md +10 -5
- package/docs/desktop-task-manager.md +81 -28
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +165 -39
- package/external/pi-tools-suite/docs/session-recovery.md +7 -0
- package/external/pi-tools-suite/docs/subagent-model-pools.md +12 -5
- 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/implement.md +1 -1
- 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 +1 -1
- 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 +0 -2
- 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 +40 -17
- 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 +7 -4
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +34 -24
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/lib.ts +9 -1
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +0 -1
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +14 -0
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +66 -1
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +123 -3
- package/external/pi-tools-suite/src/context-gateway/index.ts +137 -4
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +5 -5
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +2 -2
- package/external/pi-tools-suite/src/context-gateway/types.ts +8 -1
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +41 -14
- package/external/pi-tools-suite/src/dcp/commands.ts +14 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +8 -2
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +58 -4
- 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/pruner-candidates.ts +92 -2
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- 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 +68 -16
- 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 +282 -21
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -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 +44 -15
- package/package.json +7 -8
- package/schemas/pi-tools-suite.json +44 -12
- package/schemas/pix.json +109 -15
- package/schemas/tasks.json +1 -2
- package/skills/simplify/SKILL.md +33 -5
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -196
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- 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-wiki/SKILL.md +0 -55
- package/skills/spec-wiki/evals/evals.json +0 -132
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +0 -14
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +0 -4
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +0 -9
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +0 -17
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +0 -12
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +0 -11
- package/skills/spec-wiki/evals/files/focused/src/session.ts +0 -5
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +0 -17
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +0 -2
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +0 -3
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +0 -18
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +0 -5
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +0 -24
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +0 -7
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +0 -7
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +0 -17
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +0 -3
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +0 -3
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +0 -4
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +0 -9
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +0 -15
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +0 -6
- package/skills/spec-wiki/evals/maintenance_regression.py +0 -311
- package/skills/spec-wiki/evals/retrieval-eval.md +0 -37
- package/skills/spec-wiki/evals/retrieval-evals.json +0 -37
- package/skills/spec-wiki/evals/trigger-evals.json +0 -94
- package/skills/spec-wiki/references/bootstrap.md +0 -100
- package/skills/spec-wiki/references/focused-spec.md +0 -117
- package/skills/spec-wiki/references/index-schema.md +0 -330
- package/skills/spec-wiki/references/maintenance.md +0 -134
- package/skills/spec-wiki/references/query.md +0 -74
- package/skills/spec-wiki/scripts/spec_wiki.py +0 -2076
- /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
|
@@ -3,13 +3,14 @@ import * as path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
5
5
|
import { projectAgentsDir, readAgentDefinitionsFromDir, readProjectAgentDefinitions, type AgentDefinition } from "./agents-dir.js";
|
|
6
|
+
import { LEGACY_BROWSER_QA_TYPE, UI_QA_TYPE } from "./browser-qa.js";
|
|
6
7
|
import type { AgentTask, RetryConfig } from "./types.js";
|
|
7
8
|
|
|
8
9
|
export interface ModelByParentEntry {
|
|
9
10
|
/** Model ref to use when the parent model matches the entry's pattern. */
|
|
10
11
|
model: string;
|
|
11
12
|
/** Ordered fallbacks used when this entry's model hits quota/rate limits; replaces the normal fallback chain. */
|
|
12
|
-
fallbackModels
|
|
13
|
+
fallbackModels: string[];
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface SubagentTypeConfig {
|
|
@@ -29,7 +30,8 @@ export interface SubagentTypeConfig {
|
|
|
29
30
|
/**
|
|
30
31
|
* Parent-model-aware model selection. Keys are glob model refs (e.g. "zai/*")
|
|
31
32
|
* matched against the current parent model; the first matching key wins.
|
|
32
|
-
*
|
|
33
|
+
* Raw values may be a model ref string or { model, fallbackModels? };
|
|
34
|
+
* normalized entries always carry an explicit fallbackModels array.
|
|
33
35
|
* Ordinary roles: explicit task/forced model and preset models take priority.
|
|
34
36
|
* Oracle alone keeps parent-aware selection ahead of presets.
|
|
35
37
|
*/
|
|
@@ -46,8 +48,6 @@ export interface SubagentTypeConfig {
|
|
|
46
48
|
notForParentModels?: string[];
|
|
47
49
|
thinking?: string;
|
|
48
50
|
tools?: string[];
|
|
49
|
-
/** Explicit skill files loaded after disabling normal skill discovery. */
|
|
50
|
-
isolatedSkills?: string[];
|
|
51
51
|
extraArgs?: string[];
|
|
52
52
|
/** Extra prompt text appended after the generated or overridden prompt. */
|
|
53
53
|
promptAppend?: string;
|
|
@@ -136,8 +136,6 @@ export interface SubagentConfig {
|
|
|
136
136
|
export interface ResolvedAgentTaskConfig {
|
|
137
137
|
task: AgentTask;
|
|
138
138
|
extraArgs: string[];
|
|
139
|
-
/** Explicit skill files loaded with normal skill discovery disabled. */
|
|
140
|
-
isolatedSkills: string[];
|
|
141
139
|
/** Ordered model fallbacks for the resolved model. Current-process exhausted models are skipped before spawning. */
|
|
142
140
|
fallbackModels: string[];
|
|
143
141
|
profile?: SubagentTypeConfig;
|
|
@@ -243,6 +241,11 @@ export function getProjectSubagentPresetsPath(cwd: string): string {
|
|
|
243
241
|
/** Normalize `.pi/agents/*.md` definitions through the shared type-profile path. */
|
|
244
242
|
function projectAgentTypes(cwd: string): Partial<SubagentConfig> {
|
|
245
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
|
+
}
|
|
246
249
|
const types = normalizeAgentDefinitions(definitions);
|
|
247
250
|
if (Object.keys(types).length === 0) return {};
|
|
248
251
|
return { types };
|
|
@@ -336,7 +339,6 @@ export function resolveAgentTaskConfig(
|
|
|
336
339
|
return {
|
|
337
340
|
profile,
|
|
338
341
|
extraArgs,
|
|
339
|
-
isolatedSkills: arrayOfStrings(profile?.isolatedSkills) ?? [],
|
|
340
342
|
fallbackModels,
|
|
341
343
|
retry: resolveRetryConfig(config.retry, profile?.retry),
|
|
342
344
|
maxResultBytes: profile?.maxResultBytes ?? config.maxResultBytes,
|
|
@@ -359,7 +361,7 @@ export function resolveSubagentRoutingConfig(config: SubagentConfig): ResolvedSu
|
|
|
359
361
|
}
|
|
360
362
|
|
|
361
363
|
export function defaultSubagentType(config: SubagentConfig): string | undefined {
|
|
362
|
-
const configured =
|
|
364
|
+
const configured = normalizeSubagentType(config.defaultType, config);
|
|
363
365
|
if (configured) return Object.prototype.hasOwnProperty.call(config.types, configured) ? configured : undefined;
|
|
364
366
|
return Object.keys(config.types).find((name) => trimString(name));
|
|
365
367
|
}
|
|
@@ -429,11 +431,20 @@ export function filterSubagentConfigForParentModel(
|
|
|
429
431
|
}
|
|
430
432
|
|
|
431
433
|
export function selectSubagentType(task: AgentTask, config: SubagentConfig): string | undefined {
|
|
432
|
-
const explicit =
|
|
434
|
+
const explicit = normalizeSubagentType(task.subagentType, config);
|
|
433
435
|
if (explicit) return explicit;
|
|
434
436
|
return defaultSubagentType(config);
|
|
435
437
|
}
|
|
436
438
|
|
|
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;
|
|
446
|
+
}
|
|
447
|
+
|
|
437
448
|
function readPresetConfigFile(file: string): Partial<SubagentConfig> {
|
|
438
449
|
const raw = fs.readFileSync(file, "utf-8");
|
|
439
450
|
const parsed = parseJsonc(raw) as unknown;
|
|
@@ -467,18 +478,19 @@ export function normalizeSubagentTypeProfile(
|
|
|
467
478
|
file: string,
|
|
468
479
|
): SubagentTypeConfig {
|
|
469
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;
|
|
470
483
|
return {
|
|
471
484
|
description: trimString(rawProfile.description),
|
|
472
485
|
icon: trimString(rawProfile.icon),
|
|
473
486
|
models,
|
|
474
|
-
model
|
|
475
|
-
fallbackModels: models === undefined
|
|
476
|
-
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,
|
|
477
490
|
forParentModels: normalizeParentModelPatterns(rawProfile.forParentModels, "forParentModels", name, file),
|
|
478
491
|
notForParentModels: normalizeParentModelPatterns(rawProfile.notForParentModels, "notForParentModels", name, file),
|
|
479
492
|
thinking: trimString(rawProfile.thinking),
|
|
480
493
|
tools: arrayOfStrings(rawProfile.tools),
|
|
481
|
-
isolatedSkills: arrayOfStrings(rawProfile.isolatedSkills),
|
|
482
494
|
extraArgs: arrayOfStrings(rawProfile.extraArgs),
|
|
483
495
|
promptAppend: textBlock(rawProfile.promptAppend),
|
|
484
496
|
promptOverride: textBlock(rawProfile.promptOverride),
|
|
@@ -545,7 +557,6 @@ function compactProfile(profile: SubagentTypeConfig): SubagentTypeConfig {
|
|
|
545
557
|
if (profile.notForParentModels !== undefined) compact.notForParentModels = profile.notForParentModels;
|
|
546
558
|
if (profile.thinking) compact.thinking = profile.thinking;
|
|
547
559
|
if (profile.tools && profile.tools.length > 0) compact.tools = profile.tools;
|
|
548
|
-
if (profile.isolatedSkills && profile.isolatedSkills.length > 0) compact.isolatedSkills = profile.isolatedSkills;
|
|
549
560
|
if (profile.extraArgs && profile.extraArgs.length > 0) compact.extraArgs = profile.extraArgs;
|
|
550
561
|
if (profile.promptAppend) compact.promptAppend = profile.promptAppend;
|
|
551
562
|
if (profile.promptOverride) compact.promptOverride = profile.promptOverride;
|
|
@@ -589,7 +600,12 @@ function normalizeParentModelPatterns(
|
|
|
589
600
|
return [...new Set(value.map((pattern: string) => pattern.trim()))];
|
|
590
601
|
}
|
|
591
602
|
|
|
592
|
-
function normalizeModelByParent(
|
|
603
|
+
function normalizeModelByParent(
|
|
604
|
+
value: unknown,
|
|
605
|
+
typeName: string,
|
|
606
|
+
file: string,
|
|
607
|
+
defaultFallbackModels: string[] = [],
|
|
608
|
+
): Record<string, ModelByParentEntry> | undefined {
|
|
593
609
|
if (value === undefined || value === null) return undefined;
|
|
594
610
|
if (!isRecord(value)) throw new Error(`Subagent type "${typeName}" modelByParent must be an object: ${file}`);
|
|
595
611
|
const out: Record<string, ModelByParentEntry> = {};
|
|
@@ -598,13 +614,20 @@ function normalizeModelByParent(value: unknown, typeName: string, file: string):
|
|
|
598
614
|
if (!pat) continue;
|
|
599
615
|
if (typeof raw === "string") {
|
|
600
616
|
const model = trimString(raw);
|
|
601
|
-
if (model) out[pat] = { model };
|
|
617
|
+
if (model) out[pat] = { model, fallbackModels: [...defaultFallbackModels] };
|
|
602
618
|
continue;
|
|
603
619
|
}
|
|
604
620
|
if (isRecord(raw)) {
|
|
605
621
|
const model = trimString(raw.model);
|
|
606
622
|
if (!model) throw new Error(`Subagent type "${typeName}" modelByParent["${pat}"].model must be a non-empty string: ${file}`);
|
|
607
|
-
|
|
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
|
+
};
|
|
608
631
|
continue;
|
|
609
632
|
}
|
|
610
633
|
throw new Error(`Subagent type "${typeName}" modelByParent["${pat}"] must be a string or object: ${file}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import { isBlindModelRef, SubagentModelSelectionError, type ResolvedAgentTaskConfig, type SubagentConfig } from "./config.js";
|
|
3
|
+
import { isUiQaType } from "./browser-qa.js";
|
|
3
4
|
import { isSessionModelUnavailable } from "./model-fallback.js";
|
|
4
5
|
|
|
5
6
|
export interface SubagentModelRegistry {
|
|
@@ -17,7 +18,7 @@ export async function selectAvailableAgentModels(
|
|
|
17
18
|
signal?: AbortSignal,
|
|
18
19
|
): Promise<ResolvedAgentTaskConfig> {
|
|
19
20
|
if (signal?.aborted) throw new Error("Aborted");
|
|
20
|
-
const needsImages = Boolean(resolved.task.imagePaths?.length) || resolved.task.subagentType
|
|
21
|
+
const needsImages = Boolean(resolved.task.imagePaths?.length) || isUiQaType(resolved.task.subagentType);
|
|
21
22
|
if (!registry && !needsImages) return resolved; // Narrow offline/test callers.
|
|
22
23
|
const candidates = [resolved.task.model, ...resolved.fallbackModels].filter((ref): ref is string => Boolean(ref));
|
|
23
24
|
let registeredAvailable: Set<string> | undefined;
|
|
@@ -4,6 +4,15 @@ import { getRunRoot, hasAgentPrompt, hasLaunchedAgentPrompt, isDir, resolveRunDi
|
|
|
4
4
|
|
|
5
5
|
export const SUBAGENT_REGISTRY_FILE = "registry.json";
|
|
6
6
|
|
|
7
|
+
const REGISTRY_LOCK_RETRY_MS = 10;
|
|
8
|
+
const REGISTRY_LOCK_TIMEOUT_MS = 2_000;
|
|
9
|
+
const REGISTRY_LOCK_STALE_MS = 30_000;
|
|
10
|
+
|
|
11
|
+
interface RegistryLockOwner {
|
|
12
|
+
pid: number;
|
|
13
|
+
token: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export interface SubagentRegistryRun {
|
|
8
17
|
runId: string;
|
|
9
18
|
runDir: string;
|
|
@@ -62,45 +71,44 @@ export function loadSubagentRegistry(cwd: string): SubagentRegistry {
|
|
|
62
71
|
|
|
63
72
|
export function saveSubagentRegistry(cwd: string, registry: SubagentRegistry): void {
|
|
64
73
|
const registryPath = getSubagentRegistryPath(cwd);
|
|
65
|
-
|
|
66
|
-
fs.writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
|
|
74
|
+
withRegistryLock(registryPath, () => writeRegistryAtomically(registryPath, registry));
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
export function recordSubagentRun(cwd: string, runDir: string, agentIds: string[]): SubagentRegistry {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
return updateSubagentRegistry(cwd, (registry) => {
|
|
79
|
+
const resolvedRunDir = path.resolve(runDir);
|
|
80
|
+
const runId = path.basename(resolvedRunDir);
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
const uniqueAgentIds = [...new Set(agentIds.filter((id) => id.trim().length > 0))];
|
|
83
|
+
const previous = registry.runs[runId];
|
|
84
|
+
registry.runs[runId] = {
|
|
85
|
+
runId,
|
|
86
|
+
runDir: resolvedRunDir,
|
|
87
|
+
agentIds: uniqueAgentIds,
|
|
88
|
+
createdAt: previous?.createdAt ?? now,
|
|
89
|
+
updatedAt: now,
|
|
90
|
+
};
|
|
91
|
+
registry.latestRunId = runId;
|
|
92
|
+
registry.latestRunDir = resolvedRunDir;
|
|
93
|
+
for (const agentId of uniqueAgentIds) {
|
|
94
|
+
registry.agents[agentId] = { agentId, runId, runDir: resolvedRunDir, updatedAt: now };
|
|
95
|
+
}
|
|
96
|
+
return registry;
|
|
97
|
+
});
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
export function removeSubagentRunsFromRegistry(cwd: string, runDirs: string[]): SubagentRegistry {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
return updateSubagentRegistry(cwd, (registry) => {
|
|
102
|
+
const removed = new Set(runDirs.map((runDir) => normalizePath(runDir)));
|
|
103
|
+
for (const [runId, run] of Object.entries(registry.runs)) {
|
|
104
|
+
if (removed.has(normalizePath(run.runDir))) delete registry.runs[runId];
|
|
105
|
+
}
|
|
106
|
+
for (const [agentId, agent] of Object.entries(registry.agents)) {
|
|
107
|
+
if (removed.has(normalizePath(agent.runDir))) delete registry.agents[agentId];
|
|
108
|
+
}
|
|
109
|
+
refreshLatestRun(registry);
|
|
110
|
+
return registry;
|
|
111
|
+
});
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
export function resolveSubagentRunDir(cwd: string, runDir?: string): string {
|
|
@@ -157,6 +165,123 @@ function refreshLatestRun(registry: SubagentRegistry): void {
|
|
|
157
165
|
delete registry.latestRunDir;
|
|
158
166
|
}
|
|
159
167
|
|
|
168
|
+
function updateSubagentRegistry(cwd: string, update: (registry: SubagentRegistry) => SubagentRegistry): SubagentRegistry {
|
|
169
|
+
const registryPath = getSubagentRegistryPath(cwd);
|
|
170
|
+
return withRegistryLock(registryPath, () => {
|
|
171
|
+
const registry = loadSubagentRegistry(cwd);
|
|
172
|
+
const updated = update(registry);
|
|
173
|
+
writeRegistryAtomically(registryPath, updated);
|
|
174
|
+
return updated;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function withRegistryLock<T>(registryPath: string, operation: () => T): T {
|
|
179
|
+
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
|
180
|
+
const lockPath = `${registryPath}.lock`;
|
|
181
|
+
const owner = registryLockOwner();
|
|
182
|
+
const deadline = Date.now() + REGISTRY_LOCK_TIMEOUT_MS;
|
|
183
|
+
while (!createRegistryLock(lockPath, owner)) {
|
|
184
|
+
recoverStaleRegistryLock(lockPath, owner);
|
|
185
|
+
if (Date.now() >= deadline) {
|
|
186
|
+
throw new Error(`Timed out waiting for sub-agent registry lock at ${lockPath}.`);
|
|
187
|
+
}
|
|
188
|
+
sleep(REGISTRY_LOCK_RETRY_MS);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return operation();
|
|
192
|
+
} finally {
|
|
193
|
+
releaseRegistryLock(lockPath, owner);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function writeRegistryAtomically(registryPath: string, registry: SubagentRegistry): void {
|
|
198
|
+
const temporaryPath = `${registryPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
199
|
+
try {
|
|
200
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(registry, null, 2)}\n`, { encoding: "utf-8", mode: 0o600, flag: "wx" });
|
|
201
|
+
fs.renameSync(temporaryPath, registryPath);
|
|
202
|
+
} finally {
|
|
203
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function registryLockOwner(): RegistryLockOwner {
|
|
208
|
+
return { pid: process.pid, token: `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}` };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function createRegistryLock(lockPath: string, owner: RegistryLockOwner): boolean {
|
|
212
|
+
try {
|
|
213
|
+
fs.writeFileSync(lockPath, JSON.stringify(owner), { encoding: "utf-8", mode: 0o600, flag: "wx" });
|
|
214
|
+
return true;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (errorCode(error) === "EEXIST") return false;
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function recoverStaleRegistryLock(lockPath: string, owner: RegistryLockOwner): void {
|
|
222
|
+
let lockStat: fs.Stats;
|
|
223
|
+
try {
|
|
224
|
+
lockStat = fs.statSync(lockPath);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (errorCode(error) === "ENOENT") return;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
const existingOwner = readRegistryLockOwner(lockPath);
|
|
230
|
+
const lockAgeMs = Date.now() - lockStat.mtimeMs;
|
|
231
|
+
if (existingOwner && isProcessAlive(existingOwner.pid)) return;
|
|
232
|
+
if (!existingOwner && lockAgeMs <= REGISTRY_LOCK_STALE_MS) return;
|
|
233
|
+
|
|
234
|
+
const quarantinePath = `${lockPath}.stale-${owner.token}`;
|
|
235
|
+
try {
|
|
236
|
+
fs.renameSync(lockPath, quarantinePath);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (errorCode(error) === "ENOENT") return;
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const movedStat = fs.statSync(quarantinePath);
|
|
243
|
+
if (movedStat.dev !== lockStat.dev || movedStat.ino !== lockStat.ino) return;
|
|
244
|
+
fs.rmSync(quarantinePath, { force: true });
|
|
245
|
+
} finally {
|
|
246
|
+
if (fs.existsSync(quarantinePath) && !fs.existsSync(lockPath)) {
|
|
247
|
+
fs.renameSync(quarantinePath, lockPath);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function releaseRegistryLock(lockPath: string, owner: RegistryLockOwner): void {
|
|
253
|
+
if (readRegistryLockOwner(lockPath)?.token === owner.token) fs.rmSync(lockPath, { force: true });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function readRegistryLockOwner(lockPath: string): RegistryLockOwner | undefined {
|
|
257
|
+
try {
|
|
258
|
+
const parsed: unknown = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
259
|
+
if (!isRecord(parsed) || typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.token !== "string") return undefined;
|
|
260
|
+
return { pid: parsed.pid, token: parsed.token };
|
|
261
|
+
} catch {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isProcessAlive(pid: number): boolean {
|
|
267
|
+
try {
|
|
268
|
+
process.kill(pid, 0);
|
|
269
|
+
return true;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return errorCode(error) !== "ESRCH";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function errorCode(error: unknown): string | undefined {
|
|
276
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
277
|
+
? error.code
|
|
278
|
+
: undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function sleep(ms: number): void {
|
|
282
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
283
|
+
}
|
|
284
|
+
|
|
160
285
|
function looksLikeRunDir(runDir: string): boolean {
|
|
161
286
|
if (isDir(path.join(runDir, "prompts"))) return true;
|
|
162
287
|
try {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
currentModelRef,
|
|
6
6
|
defaultSubagentType,
|
|
7
7
|
filterSubagentConfigForParentModel,
|
|
8
|
+
normalizeSubagentType,
|
|
8
9
|
resolveSubagentRoutingConfig,
|
|
9
10
|
type ResolvedSubagentRoutingConfig,
|
|
10
11
|
type SubagentConfig,
|
|
@@ -46,7 +47,7 @@ export class SubagentRoutingError extends Error {
|
|
|
46
47
|
const ROUTER_SYSTEM_PROMPT = [
|
|
47
48
|
"You route Pi async sub-agent tasks to the best configured subagentType.",
|
|
48
49
|
"Choose exactly one allowed type for each task. Use the allowed type descriptions as the source of truth.",
|
|
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,
|
|
50
|
+
"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, ui-qa for real user-interface testing across browsers, TUIs, and desktop GUIs, 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.",
|
|
50
51
|
"Return only strict JSON with this shape: {\"routes\":[{\"id\":\"task-id\",\"subagentType\":\"type\"}]}",
|
|
51
52
|
"Do not include markdown, comments, explanations, or unknown types.",
|
|
52
53
|
].join("\n");
|
|
@@ -59,9 +60,11 @@ export async function routeSubagentTasks(
|
|
|
59
60
|
): Promise<RoutedSubagentTasks> {
|
|
60
61
|
if (signal?.aborted) throw new Error("Aborted");
|
|
61
62
|
// Validate even the fast path: an explicit typo must not bypass profiles.
|
|
62
|
-
tasks = tasks.map((task) =>
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
tasks = tasks.map((task) => {
|
|
64
|
+
if (!hasText(task.subagentType)) return task;
|
|
65
|
+
const subagentType = normalizeSubagentType(task.subagentType, config);
|
|
66
|
+
return subagentType !== task.subagentType ? { ...task, subagentType } : task;
|
|
67
|
+
});
|
|
65
68
|
const parentModel = currentModelRef(ctx.model);
|
|
66
69
|
const effectiveConfig = filterSubagentConfigForParentModel(config, parentModel);
|
|
67
70
|
const invalidTasks = tasks.filter((task) => hasText(task.subagentType)
|
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { selectSuitableToolsForModel } from "../../lib/tool-args.js";
|
|
6
|
-
import { BROWSER_QA_RUNNER_ENV, getBrowserQaRunnerPath } from "./browser-qa.js";
|
|
6
|
+
import { BROWSER_QA_RUNNER_ENV, getBrowserQaRunnerPath, getUiQaRunnerPath, isUiQaType, UI_QA_RUNNER_ENV } from "./browser-qa.js";
|
|
7
7
|
import { validateBasename } from "./paths.js";
|
|
8
8
|
import { getPiInvocation } from "./pi-invocation.js";
|
|
9
9
|
import { writePromptFile } from "./prompt.js";
|
|
@@ -20,12 +20,11 @@ export interface SpawnAgentOptions {
|
|
|
20
20
|
parentSession?: string;
|
|
21
21
|
timeoutMs?: number;
|
|
22
22
|
maxResultBytes?: number;
|
|
23
|
-
/** Explicit skills to load after disabling normal skill discovery. */
|
|
24
|
-
isolatedSkills?: string[];
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
export const DEFAULT_AGENT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
28
26
|
const BROWSER_QA_WORKSPACE_DIR = "browser-qa";
|
|
27
|
+
const UI_QA_WORKSPACE_DIR = "ui-qa";
|
|
29
28
|
const SUBAGENT_AGENT_DIR_ENV = "PI_SUBAGENT_AGENT_DIR";
|
|
30
29
|
const AGENT_TIMEOUT_EXIT_CODE = 124;
|
|
31
30
|
const AGENT_TIMEOUT_KILL_GRACE_MS = 5_000;
|
|
@@ -50,7 +49,7 @@ export function spawnAgent(
|
|
|
50
49
|
validateBasename(task.id, "task.id");
|
|
51
50
|
const agentDir = path.join(runDir, task.id);
|
|
52
51
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
53
|
-
|
|
52
|
+
prepareUiQaWorkspace(agentDir, task.subagentType);
|
|
54
53
|
|
|
55
54
|
// Clean previous state when reusing a run directory/agent id.
|
|
56
55
|
for (const f of [
|
|
@@ -89,10 +88,9 @@ export function spawnAgent(
|
|
|
89
88
|
// detached subprocesses do not depend on persisted local pi settings.
|
|
90
89
|
const persistSessions = shouldPersistSubagentSessions();
|
|
91
90
|
const sessionDir = persistSessions ? getAgentSessionDir(agentDir) : undefined;
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
const
|
|
95
|
-
const forwardedExtraArgs = isolateSkills ? withoutSkillArgs(extraArgs) : extraArgs;
|
|
91
|
+
// Sub-agent roles are self-contained. Never inherit/discover skills, and do
|
|
92
|
+
// not allow profile/user extra args to re-enable or inject them.
|
|
93
|
+
const forwardedExtraArgs = withoutSkillArgs(extraArgs);
|
|
96
94
|
if (sessionDir) fs.mkdirSync(sessionDir, { recursive: true });
|
|
97
95
|
const piArgs: string[] = ["--mode", "rpc"];
|
|
98
96
|
if (sessionDir) piArgs.push("--session-dir", sessionDir);
|
|
@@ -101,16 +99,15 @@ export function spawnAgent(
|
|
|
101
99
|
piArgs.push("--extension", getModelToolsExtensionPath());
|
|
102
100
|
// Preserve `--no-extensions` unless this invocation explicitly selects an
|
|
103
101
|
// Antigravity model. Environment/default models do not opt the provider in.
|
|
104
|
-
|
|
102
|
+
const configuredModel = task.model || getEnvModel();
|
|
103
|
+
const selectedModel = resolveSelectedModel(configuredModel, forwardedExtraArgs);
|
|
104
|
+
const explicitModel = resolveSelectedModel(task.model, forwardedExtraArgs);
|
|
105
|
+
if (explicitModel?.startsWith("antigravity/") && explicitModel.length > "antigravity/".length) {
|
|
105
106
|
piArgs.push("--extension", getAntigravityAuthExtensionPath());
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
const envModel = task.model || getEnvModel();
|
|
112
|
-
if (envModel) piArgs.push("--model", envModel);
|
|
113
|
-
const selectedTools = task.tools ? filterSubagentTools(selectSuitableToolsForModel(envModel, task.tools)) : undefined;
|
|
108
|
+
piArgs.push("--no-skills");
|
|
109
|
+
if (configuredModel) piArgs.push("--model", configuredModel);
|
|
110
|
+
const selectedTools = task.tools ? filterSubagentTools(selectSuitableToolsForModel(selectedModel, task.tools)) : undefined;
|
|
114
111
|
if (selectedTools) {
|
|
115
112
|
if (selectedTools.length > 0) piArgs.push("--tools", selectedTools.join(","));
|
|
116
113
|
else piArgs.push("--no-tools");
|
|
@@ -119,6 +116,10 @@ export function spawnAgent(
|
|
|
119
116
|
|
|
120
117
|
// User-supplied extra args (e.g. --thinking high)
|
|
121
118
|
piArgs.push(...forwardedExtraArgs);
|
|
119
|
+
// The child always runs one explicitly selected model. Override persisted
|
|
120
|
+
// enabledModels (and any extra --models value) so isolated children do not
|
|
121
|
+
// resolve unrelated provider patterns before their limited extensions load.
|
|
122
|
+
if (selectedModel) piArgs.push("--models", selectedModel);
|
|
122
123
|
// Keep recursive/interactive parent-only tools disabled even if explicit
|
|
123
124
|
// sub-agent extraArgs load additional extensions or override --tools.
|
|
124
125
|
piArgs.push("--extension", getSubagentToolGuardExtensionPath());
|
|
@@ -151,7 +152,7 @@ export function spawnAgent(
|
|
|
151
152
|
|
|
152
153
|
const proc = spawn(invocation.command, invocation.args, {
|
|
153
154
|
cwd,
|
|
154
|
-
env: subagentEnvironment(process.env, task.subagentType
|
|
155
|
+
env: subagentEnvironment(process.env, isUiQaType(task.subagentType) ? agentDir : undefined),
|
|
155
156
|
stdio: ["pipe", "pipe", "pipe"],
|
|
156
157
|
detached: process.platform !== "win32",
|
|
157
158
|
});
|
|
@@ -477,8 +478,8 @@ function withoutSkillArgs(args: string[]): string[] {
|
|
|
477
478
|
return filtered;
|
|
478
479
|
}
|
|
479
480
|
|
|
480
|
-
function
|
|
481
|
-
let model =
|
|
481
|
+
function resolveSelectedModel(configuredModel: string | undefined, extraArgs: string[]): string | undefined {
|
|
482
|
+
let model = configuredModel?.trim() || undefined;
|
|
482
483
|
for (let index = 0; index < extraArgs.length; index += 1) {
|
|
483
484
|
const arg = extraArgs[index];
|
|
484
485
|
if (arg === "--model" || arg === "-m") {
|
|
@@ -490,7 +491,7 @@ function usesAntigravityModel(taskModel: string | undefined, extraArgs: string[]
|
|
|
490
491
|
model = arg.slice("--model=".length).trim() || undefined;
|
|
491
492
|
}
|
|
492
493
|
}
|
|
493
|
-
return
|
|
494
|
+
return model;
|
|
494
495
|
}
|
|
495
496
|
|
|
496
497
|
function getModelToolsExtensionPath(): string {
|
|
@@ -821,21 +822,30 @@ function subagentEnvironment(env: NodeJS.ProcessEnv, agentDir?: string): NodeJS.
|
|
|
821
822
|
};
|
|
822
823
|
delete result[SUBAGENT_AGENT_DIR_ENV];
|
|
823
824
|
delete result[BROWSER_QA_RUNNER_ENV];
|
|
825
|
+
delete result[UI_QA_RUNNER_ENV];
|
|
824
826
|
if (agentDir) {
|
|
825
827
|
result[SUBAGENT_AGENT_DIR_ENV] = fs.realpathSync(agentDir);
|
|
826
828
|
result[BROWSER_QA_RUNNER_ENV] = getBrowserQaRunnerPath();
|
|
829
|
+
result[UI_QA_RUNNER_ENV] = getUiQaRunnerPath();
|
|
827
830
|
}
|
|
828
831
|
return result;
|
|
829
832
|
}
|
|
830
833
|
|
|
831
|
-
function
|
|
832
|
-
const workspace = path.join(agentDir,
|
|
834
|
+
function prepareUiQaWorkspace(agentDir: string, subagentType: string | undefined): void {
|
|
835
|
+
const workspace = path.join(agentDir, UI_QA_WORKSPACE_DIR);
|
|
836
|
+
const browserWorkspace = path.join(agentDir, BROWSER_QA_WORKSPACE_DIR);
|
|
833
837
|
fs.rmSync(workspace, { recursive: true, force: true });
|
|
834
|
-
|
|
835
|
-
|
|
838
|
+
fs.rmSync(browserWorkspace, { recursive: true, force: true });
|
|
839
|
+
if (!isUiQaType(subagentType)) return;
|
|
840
|
+
const flows = path.join(browserWorkspace, "flows");
|
|
841
|
+
const uiFlows = path.join(workspace, "flows");
|
|
842
|
+
fs.mkdirSync(workspace, { recursive: true, mode: 0o700 });
|
|
843
|
+
fs.mkdirSync(uiFlows, { recursive: true, mode: 0o700 });
|
|
836
844
|
fs.mkdirSync(flows, { recursive: true, mode: 0o700 });
|
|
837
845
|
if (process.platform !== "win32") {
|
|
838
846
|
fs.chmodSync(workspace, 0o700);
|
|
847
|
+
fs.chmodSync(uiFlows, 0o700);
|
|
848
|
+
fs.chmodSync(browserWorkspace, 0o700);
|
|
839
849
|
fs.chmodSync(flows, 0o700);
|
|
840
850
|
}
|
|
841
851
|
}
|