pi-ui-extend 1.0.16 → 1.0.19

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.
@@ -122,6 +122,7 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
122
122
  fallbackModels: Type.TOptional<Type.TArray<Type.TString>>;
123
123
  thinking: Type.TOptional<Type.TString>;
124
124
  tools: Type.TOptional<Type.TArray<Type.TString>>;
125
+ isolatedSkills: Type.TOptional<Type.TArray<Type.TString>>;
125
126
  extraArgs: Type.TOptional<Type.TArray<Type.TString>>;
126
127
  promptAppend: Type.TOptional<Type.TString>;
127
128
  promptOverride: Type.TOptional<Type.TString>;
@@ -153,6 +153,7 @@ const SubagentTypeConfig = Type.Object({
153
153
  fallbackModels: Type.Optional(Type.Array(Type.String(), { description: "Ordered model fallbacks." })),
154
154
  thinking: Type.Optional(Type.String({ description: "Thinking level." })),
155
155
  tools: Type.Optional(Type.Array(Type.String(), { description: "Enabled tools for this type." })),
156
+ isolatedSkills: Type.Optional(Type.Array(Type.String(), { description: "Explicit skill files loaded after disabling normal skill discovery for this type." })),
156
157
  extraArgs: Type.Optional(Type.Array(Type.String(), { description: "Extra CLI arguments." })),
157
158
  promptAppend: Type.Optional(Type.String({ description: "Extra prompt text appended after generated prompt." })),
158
159
  promptOverride: Type.Optional(Type.String({ description: "Full prompt replacement." })),
@@ -14,7 +14,7 @@ This package keeps shared Pi tools as ordinary source folders under `src/` and r
14
14
  - `src/repo-discovery` — `/idx-init`, `/idx-update`, and indexed-only `repo_architecture` / `repo_structure` / `repo_ast` / `repo_search` / `repo_explain` / `repo_deps`; tools register only when the launch project has `.indexer-cli`
15
15
  - `src/antigravity-auth` — `antigravity` custom provider with Google Antigravity OAuth login, startup account list, auth.json-only runtime account loading, `/antigravity-add-account` OAuth append into rotation, `/antigravity-account` status display, account rotation/failover, Antigravity plus Gemini CLI model registration, and streaming through the Cloud Code Assist unified gateway
16
16
  - `src/opencode-import` — `/opencode-import` for bounded migration of supported OpenCode OpenAI/Codex, GitHub Copilot, Z.ai, and Antigravity credentials into Pi; existing entries are preserved unless `--force` is passed
17
- - `src/todo` — `todo` tool, `/todos`, `/todos-persist`, and `/todos-scope`; supports parent/subtask hierarchy, blockers, ready-task filtering, deferred out-of-scope items, batch operations, JSON/Markdown import/export, automatic clearing when all visible todos are completed, and optional project persistence via `/todos persist on` or `/todos-persist on`; localization/i18n has been removed
17
+ - `src/todo` — `todo` tool, `/todos`, `/todos-persist`, `/todos-scope`, and `/todos-clear` (also `/todos clear`); supports parent/subtask hierarchy, blockers, ready-task filtering, deferred out-of-scope items, batch operations, JSON/Markdown import/export, automatic clearing when all visible todos are completed, and optional project persistence via `/todos persist on` or `/todos-persist on`; localization/i18n has been removed
18
18
  - `src/model-tools` — model-specific tool aliases such as Claude/GLM-style `Read` / `Edit` / `Write` / `Bash` / `Grep` / `Glob` / `LS`, GPT/Codex-style `shell`, and model-gated `apply_patch`
19
19
  - `src/usage` — `/usage` command and startup hint for read-only AI quota checks across OpenAI, Zhipu AI, Z.ai, and Google Antigravity, including Antigravity quota by model
20
20
  - `src/web-search` — `web_search` and `web_fetch` tools migrated from `@ollama/pi-web-search`; uses local Ollama by default or the official Ollama cloud API when an API key is configured, supports Tavily Search/Extract fallback, provides `/web-credentials` for secure user-level key storage, honors `OLLAMA_HOST`, supports request timeouts via `timeout_ms` / `PI_WEB_SEARCH_TIMEOUT_MS`, and reports provider-specific errors
@@ -243,7 +243,60 @@ Project-local overrides can be added in `.pi/pi-tools-suite.jsonc`; pi-tools-sui
243
243
 
244
244
  Sub-agent model routing normally follows task overrides, subagent type config, then `ASYNC_SUBAGENTS_MODEL` / `PI_SUBAGENTS_MODEL` fallbacks. Set `ASYNC_SUBAGENTS_FORCE_CURRENT_MODEL=1` (or `PI_SUBAGENTS_FORCE_CURRENT_MODEL=1`) to ignore task/config/env model choices and launch every sub-agent with the current parent session model. When this flag is enabled, any `--model` entries in sub-agent extra args are stripped so they cannot override the current model.
245
245
 
246
- For an oh-my-openagent-style workflow, run `/ultrawork` or `/ulw` to ask the parent agent to split broad work into configured async-subagents roles (`quick`, `scan`, `research`, `docs`, `frontend`, `implement`, `tests`, `review`, `deep`, `oracle`). Set `ULTRAWORK=1` before launching Pi to apply that compact routing prompt to normal non-slash user inputs automatically. Set `ULTRAWORK_AUTO=1` to ask the lightweight router model to classify only the first normal user input on non-GPT parent models: clear broad/parallel work is transformed into ultrawork, vague potentially-complex work gets a soft delegation hint, and narrow work is left unchanged. GPT-like parent models skip only this automatic transform; they can still use `/ultrawork` and `subagents` normally. `frontend` is for UI/UX, styling, layout, responsive behavior, and visual component polish; `review` covers security/performance/audit tracks; `implement` covers refactors; `deep` covers debugging/root-cause; `oracle` is for sparse cross-provider second opinions on high-stakes uncertainty. Run `/hyperplan` to pressure-test a plan before implementation.
246
+ For an oh-my-openagent-style workflow, run `/ultrawork` or `/ulw` to ask the parent agent to split broad work into configured async-subagents roles (`quick`, `scan`, `research`, `docs`, `frontend`, `browser-qa`, `implement`, `tests`, `review`, `deep`, `oracle`). Set `ULTRAWORK=1` before launching Pi to apply that compact routing prompt to normal non-slash user inputs automatically. Set `ULTRAWORK_AUTO=1` to ask the lightweight router model to classify only the first normal user input on non-GPT parent models: clear broad/parallel work is transformed into ultrawork, vague potentially-complex work gets a soft delegation hint, and narrow work is left unchanged. GPT-like parent models skip only this automatic transform; they can still use `/ultrawork` and `subagents` normally. `frontend` is for UI/UX, styling, layout, responsive behavior, and visual component polish; `browser-qa` reproduces browser bugs and proves fixes with deterministic assertions plus screenshot/video/trace evidence; `review` covers security/performance/audit tracks; `implement` covers refactors; `deep` covers debugging/root-cause; `oracle` is for sparse cross-provider second opinions on high-stakes uncertainty. Run `/hyperplan` to pressure-test a plan before implementation.
247
+
248
+ ### Private browser QA and project auth
249
+
250
+ The built-in `browser-qa` role runs on `antigravity/gemini-3-flash-preview`, with
251
+ `openai-codex/gpt-5.4-mini` as its fallback. Its browser workflow is an explicit
252
+ private skill under `src/async-subagents/private-skills/`, outside normal Pi skill
253
+ discovery. The role's first-class `isolatedSkills` setting launches the child with
254
+ `--no-skills` plus only that skill, so parent and ordinary sub-agent sessions do
255
+ not discover it.
256
+
257
+ Keep named dev/staging auth profiles in project `.pi/qa_auth.jsonc` (there is no
258
+ `/qa-auth` command). The private runner supports `form`, `cookie`, `localStorage`,
259
+ `sessionStorage`, `bearer`, and existing Playwright `storageState` auth. Every
260
+ profile must declare exact `allowedOrigins`; select the profile id explicitly when
261
+ spawning QA. On POSIX, keep the config at mode `0600`. During a run those origins
262
+ also form the fail-closed HTTP(S)/WebSocket allowlist and service workers are
263
+ blocked. Example:
264
+
265
+ ```jsonc
266
+ {
267
+ "profiles": {
268
+ "staging-admin": {
269
+ "description": "Staging administrator",
270
+ "traits": ["role:admin", "plan:enterprise"],
271
+ "baseUrl": "https://staging.example.test",
272
+ "allowedOrigins": ["https://staging.example.test"],
273
+ "auth": {
274
+ "type": "form",
275
+ "loginUrl": "https://staging.example.test/login",
276
+ "fields": [
277
+ { "selector": "input[name=email]", "value": "admin@example.test" },
278
+ { "selector": "input[name=password]", "value": "replace-me" }
279
+ ],
280
+ "submitSelector": "button[type=submit]",
281
+ "success": { "selector": "[data-testid=user-menu]" }
282
+ }
283
+ }
284
+ }
285
+ }
286
+ ```
287
+
288
+ Do not place credential values in prompts, QA flows, shell arguments, reports,
289
+ or evidence. The helper reads JSONC internally, emits only redacted statuses, and
290
+ caches generated storage state under `.pi/qa-auth-state`. Evidence is isolated by
291
+ run/profile under `.pi/qa-runs`; trace archives have network records and non-image
292
+ resources removed, then known configured/runtime credential values are redacted
293
+ and verified before the trace is retained. Missing, ambiguous, rejected, or
294
+ expired auth returns `QA_PROFILE_REQUIRED` or `QA_AUTH_UPDATE_REQUIRED`, naming
295
+ only the profile/file/reason needed for the parent to ask the user for an update
296
+ and rerun. See
297
+ `src/async-subagents/private-skills/browser-qa/references/qa-auth.example.jsonc`
298
+ for complete profile shapes and `references/qa-flow.example.jsonc` beside it for
299
+ the declarative, non-executable QA action/assertion format.
247
300
 
248
301
  Async-subagents also injects a lightweight oh-my-openagent-style system-prompt strategy by model: non-GPT parents get `parallel-first`, an orchestration-first hint that favors ultrawork/subagents for broad work, while GPT-like parents get `deep-work`, a direct deep-worker hint that uses subagents only when clearly useful. Explicit custom system prompts (`--system-prompt`, `SYSTEM.md`, custom templates) are respected and skip this injection by default. Disable it with `PI_AGENT_STRATEGY=off`; force a strategy with `PI_AGENT_STRATEGY=parallel-first` or `PI_AGENT_STRATEGY=deep-work`; set `PI_AGENT_STRATEGY_WITH_CUSTOM_PROMPT=1` to append it even when a custom prompt is present.
249
302
 
@@ -0,0 +1,81 @@
1
+ # Browser QA sub-agent specification
2
+
3
+ ## Goal
4
+
5
+ Provide a cheap, fast `browser-qa` async-subagent that reproduces browser bugs
6
+ and proves fixes with deterministic assertions plus screenshot, video, and trace
7
+ evidence. The role uses `antigravity/gemini-3-flash-preview`, falling back to
8
+ `openai-codex/gpt-5.4-mini`.
9
+
10
+ ## Private skill isolation
11
+
12
+ - The browser QA skill lives under `src/async-subagents/private-skills/`, outside
13
+ Pi's normal skill discovery roots.
14
+ - Sub-agent processes disable normal extension discovery, then always load the
15
+ suite's model-tools and Antigravity provider extensions explicitly, regardless
16
+ of the selected model. This keeps the process isolated without making any
17
+ Antigravity-backed role unavailable.
18
+ - A type profile may declare `isolatedSkills`. Spawning that profile adds
19
+ `--no-skills` followed by one explicit `--skill` per configured path.
20
+ - Other sub-agent profiles and the parent session must not discover the private
21
+ skill automatically.
22
+
23
+ ## Authentication contract
24
+
25
+ - Auth profiles live in project-local `.pi/qa_auth.jsonc` and are selected by
26
+ explicit id. The file must be a real project-local file with mode `0600` on
27
+ POSIX. Profile listings expose only `id`, description, and traits.
28
+ - When authenticated QA first needs credentials and that file is absent, the
29
+ runner creates a private empty template and returns `provide_credentials`.
30
+ The sub-agent must explicitly ask the user to fill the reported file and
31
+ rerun QA; it must not read or edit the credential values itself.
32
+ - Every profile requires one or more exact `allowedOrigins`. Secret-bearing auth
33
+ is applied only to those origins; all other HTTP(S)/WebSocket traffic and
34
+ service workers are blocked during QA.
35
+ - Supported auth types are `form`, `cookie`, `localStorage`, `sessionStorage`,
36
+ `bearer`, and existing Playwright `storageState`.
37
+ - The bundled runner reads secrets internally. Credentials must never be copied
38
+ into prompts, generated QA flows, shell arguments, transcripts, reports,
39
+ or QA evidence.
40
+ - Generated browser state is private cache under `.pi/qa-auth-state`; evidence
41
+ is written under `.pi/qa-runs`. Multiple profiles always use separate browser
42
+ contexts and evidence directories.
43
+ - Missing, ambiguous, rejected, or expired auth returns a machine-readable
44
+ update-required/profile-required status naming only the profile id, config
45
+ file, and redacted reason. The parent asks the user to update the file and
46
+ reruns; there is no `/qa-auth` command.
47
+
48
+ ## QA execution contract
49
+
50
+ - A model-authored QA flow is declarative JSONC, not executable JavaScript. The
51
+ trusted runner implements a bounded set of navigation, interaction,
52
+ assertion, screenshot, and auth-rejection actions and never gives the flow a
53
+ Playwright context or credential values.
54
+ - The runner owns browser lifecycle, origin checks, auth application, tracing,
55
+ screenshots, video finalization, and redacted result output. Before retaining
56
+ a trace it removes network/non-image resource entries, redacts configured and
57
+ runtime storage credentials, and verifies those values are absent.
58
+ - Success and post-launch failure results include typed artifact groups. Every
59
+ item has an absolute filesystem path and a `file:` URI; the sub-agent must
60
+ present each item as a clickable Markdown link instead of reporting only the
61
+ evidence directory.
62
+ - Success requires deterministic assertions. Visual inspection supplements,
63
+ but never replaces, explicit expected-state checks.
64
+ - Auth rejection discovered by a QA flow is reported through the
65
+ `authRejectedIf` action so the parent gets an update-required status.
66
+
67
+ ## Acceptance criteria
68
+
69
+ 1. `browser-qa` resolves to the intended model/fallback and private skill, and
70
+ its isolated child process can register the configured Antigravity model.
71
+ 2. Spawn args contain `--no-skills` and only the explicit private skill for this
72
+ profile; ordinary profiles retain existing skill discovery behavior.
73
+ 3. Auth profile listing and all error output are redacted; model-authored input
74
+ cannot execute code in the credential-bearing process.
75
+ 4. Runner tests cover explicit profile selection, all auth modes, fail-closed
76
+ origins, path/mode hardening, private empty-template creation, explicit
77
+ credential requests, non-executable flows, and successful redacted evidence
78
+ creation.
79
+ 5. Completed test runs report clickable screenshot, video, and trace links
80
+ whenever those artifacts exist.
81
+ 6. Suite tests/typecheck, host checks, and suite sync pass.
@@ -116,6 +116,7 @@
116
116
  "research": { "model": "zai/glm-5-turbo", "thinking": "low" },
117
117
  "docs": { "model": "zai/glm-4.5-air", "thinking": "low" },
118
118
  "frontend": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["zai/glm-5.3"], "thinking": "medium" },
119
+ "browser-qa": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["openai-codex/gpt-5.4-mini"], "thinking": "medium" },
119
120
  "tests": { "model": "zai/glm-5-turbo", "thinking": "medium" },
120
121
  "review": { "model": "zai/glm-5.3", "thinking": "high" },
121
122
  "implement": { "model": "zai/glm-5.3", "thinking": "high" },
@@ -131,6 +132,7 @@
131
132
  "research": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5-turbo"], "thinking": "low" },
132
133
  "docs": { "model": "openai-codex/gpt-5.6-luna", "fallbackModels": ["zai/glm-4.5-air"], "thinking": "low" },
133
134
  "frontend": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "medium" },
135
+ "browser-qa": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["openai-codex/gpt-5.4-mini"], "thinking": "medium" },
134
136
  "tests": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5-turbo"], "thinking": "medium" },
135
137
  "review": { "model": "openai-codex/gpt-5.6-sol", "fallbackModels": ["zai/glm-5.3"], "thinking": "high" },
136
138
  "implement": { "model": "openai-codex/gpt-5.6-sol", "fallbackModels": ["zai/glm-5.3"], "thinking": "high" },
@@ -146,6 +148,7 @@
146
148
  "research": { "model": "antigravity/gemini-3.1-pro-preview", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5-turbo"], "thinking": "medium" },
147
149
  "docs": { "model": "antigravity/gemini-2.5-flash", "fallbackModels": ["openai-codex/gpt-5.3-codex-spark", "zai/glm-4.5-air"], "thinking": "medium" },
148
150
  "frontend": { "model": "antigravity/gemini-3.1-pro-preview-customtools", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5.3"], "thinking": "low" },
151
+ "browser-qa": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["openai-codex/gpt-5.4-mini"], "thinking": "medium" },
149
152
  "tests": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5-turbo"], "thinking": "high" },
150
153
  "review": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.6-sol", "zai/glm-5.3"], "thinking": "high" },
151
154
  "implement": { "model": "openai-codex/gpt-5.6-sol", "fallbackModels": ["zai/glm-5.3"], "thinking": "high" },
@@ -194,6 +197,14 @@
194
197
  ]
195
198
  },
196
199
 
200
+ "browser-qa": {
201
+ "description": "Use for browser-based visual QA: reproduce UI bugs and verify fixes with deterministic assertions, screenshots, video, and traces.",
202
+ "model": "antigravity/gemini-3-flash-preview",
203
+ "fallbackModels": ["openai-codex/gpt-5.4-mini"],
204
+ "thinking": "medium",
205
+ "tools": ["read", "grep", "bash"]
206
+ },
207
+
197
208
  "tests": {
198
209
  "description": "Use for tests: locate coverage, find gaps, run/check targeted test commands, diagnose failing tests.",
199
210
  "model": "zai/glm-5-turbo",
@@ -27,6 +27,8 @@ export interface SubagentTypeConfig {
27
27
  modelByParent?: Record<string, ModelByParentEntry>;
28
28
  thinking?: string;
29
29
  tools?: string[];
30
+ /** Explicit skill files loaded after disabling normal skill discovery. */
31
+ isolatedSkills?: string[];
30
32
  extraArgs?: string[];
31
33
  /** Extra prompt text appended after the generated or overridden prompt. */
32
34
  promptAppend?: string;
@@ -118,6 +120,8 @@ export interface CopySubagentConfigSampleResult {
118
120
  export interface ResolvedAgentTaskConfig {
119
121
  task: AgentTask;
120
122
  extraArgs: string[];
123
+ /** Explicit skill files loaded with normal skill discovery disabled. */
124
+ isolatedSkills: string[];
121
125
  /** Ordered model fallbacks for the resolved model. Current-process exhausted models are skipped before spawning. */
122
126
  fallbackModels: string[];
123
127
  profile?: SubagentTypeConfig;
@@ -201,6 +205,14 @@ const BUILTIN_CONFIG: SubagentConfig = {
201
205
  "When no mockup exists, choose a clear aesthetic direction and explain it briefly. Verify with targeted build/lint/tests or screenshot-relevant checks when possible.",
202
206
  ].join("\n"),
203
207
  },
208
+ "browser-qa": {
209
+ description: "Use for browser-based visual QA: reproduce UI bugs and verify fixes with deterministic assertions, screenshots, video, and traces.",
210
+ model: "antigravity/gemini-3-flash-preview",
211
+ fallbackModels: ["openai-codex/gpt-5.4-mini"],
212
+ thinking: "medium",
213
+ tools: ["read", "grep", "bash"],
214
+ isolatedSkills: [getBrowserQaSkillPath()],
215
+ },
204
216
  implement: {
205
217
  description: "Use when the sub-agent should make or plan code changes for a feature, bug fix, or refactor.",
206
218
  thinking: "high",
@@ -272,6 +284,10 @@ export function getSubagentConfigSamplePath(): string {
272
284
  return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "async-subagents.sample.jsonc");
273
285
  }
274
286
 
287
+ export function getBrowserQaSkillPath(): string {
288
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "private-skills", "browser-qa", "SKILL.md");
289
+ }
290
+
275
291
  export function getSubagentConfigInitTargetPath(cwd: string, env: NodeJS.ProcessEnv = process.env): string {
276
292
  return explicitSubagentConfigPath(cwd, env) ?? getDefaultSubagentConfigPath();
277
293
  }
@@ -365,6 +381,7 @@ export function resolveAgentTaskConfig(
365
381
  return {
366
382
  profile,
367
383
  extraArgs,
384
+ isolatedSkills: arrayOfStrings(profile?.isolatedSkills) ?? [],
368
385
  fallbackModels,
369
386
  retry: resolveRetryConfig(config.retry, profile?.retry),
370
387
  maxResultBytes: profile?.maxResultBytes ?? config.maxResultBytes,
@@ -507,6 +524,7 @@ function normalizeConfig(value: Record<string, unknown>, file: string): Partial<
507
524
  modelByParent: normalizeModelByParent(rawProfile.modelByParent, name, file),
508
525
  thinking: trimString(rawProfile.thinking),
509
526
  tools: arrayOfStrings(rawProfile.tools),
527
+ isolatedSkills: arrayOfStrings(rawProfile.isolatedSkills),
510
528
  extraArgs: arrayOfStrings(rawProfile.extraArgs),
511
529
  promptAppend: textBlock(rawProfile.promptAppend),
512
530
  promptOverride: textBlock(rawProfile.promptOverride),
@@ -583,6 +601,7 @@ function compactProfile(profile: SubagentTypeConfig): SubagentTypeConfig {
583
601
  if (profile.modelByParent) compact.modelByParent = profile.modelByParent;
584
602
  if (profile.thinking) compact.thinking = profile.thinking;
585
603
  if (profile.tools && profile.tools.length > 0) compact.tools = profile.tools;
604
+ if (profile.isolatedSkills && profile.isolatedSkills.length > 0) compact.isolatedSkills = profile.isolatedSkills;
586
605
  if (profile.extraArgs && profile.extraArgs.length > 0) compact.extraArgs = profile.extraArgs;
587
606
  if (profile.promptAppend) compact.promptAppend = profile.promptAppend;
588
607
  if (profile.promptOverride) compact.promptOverride = profile.promptOverride;
@@ -19,6 +19,8 @@ export interface SpawnAgentOptions {
19
19
  parentSession?: string;
20
20
  timeoutMs?: number;
21
21
  maxResultBytes?: number;
22
+ /** Explicit skills to load after disabling normal skill discovery. */
23
+ isolatedSkills?: string[];
22
24
  }
23
25
 
24
26
  export const DEFAULT_AGENT_TIMEOUT_MS = 30 * 60 * 1000;
@@ -88,6 +90,13 @@ export function spawnAgent(
88
90
  else piArgs.push("--no-session");
89
91
  piArgs.push("--no-extensions");
90
92
  piArgs.push("--extension", getModelToolsExtensionPath());
93
+ // `--no-extensions` keeps sub-agents isolated, but the suite-owned provider
94
+ // is infrastructure: always restore it, regardless of the selected model.
95
+ piArgs.push("--extension", getAntigravityAuthExtensionPath());
96
+ if (options.isolatedSkills && options.isolatedSkills.length > 0) {
97
+ piArgs.push("--no-skills");
98
+ for (const skillPath of options.isolatedSkills) piArgs.push("--skill", skillPath);
99
+ }
91
100
  const envModel = task.model || getEnvModel();
92
101
  if (envModel) piArgs.push("--model", envModel);
93
102
  const selectedTools = task.tools ? filterSubagentTools(selectSuitableToolsForModel(envModel, task.tools)) : undefined;
@@ -98,7 +107,7 @@ export function spawnAgent(
98
107
  if (task.thinking) piArgs.push("--thinking", task.thinking);
99
108
 
100
109
  // User-supplied extra args (e.g. --thinking high)
101
- piArgs.push(...extraArgs);
110
+ piArgs.push(...(options.isolatedSkills?.length ? withoutSkillArgs(extraArgs) : extraArgs));
102
111
  // Keep recursive/interactive parent-only tools disabled even if explicit
103
112
  // sub-agent extraArgs load additional extensions or override --tools.
104
113
  piArgs.push("--extension", getSubagentToolGuardExtensionPath());
@@ -389,10 +398,28 @@ export function spawnAgent(
389
398
  return { pid, agentDir, process: proc };
390
399
  }
391
400
 
401
+ function withoutSkillArgs(args: string[]): string[] {
402
+ const filtered: string[] = [];
403
+ for (let index = 0; index < args.length; index += 1) {
404
+ const arg = args[index];
405
+ if (arg === "--skill") {
406
+ index += 1;
407
+ continue;
408
+ }
409
+ if (arg === "--no-skills" || arg.startsWith("--skill=")) continue;
410
+ filtered.push(arg);
411
+ }
412
+ return filtered;
413
+ }
414
+
392
415
  function getModelToolsExtensionPath(): string {
393
416
  return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "model-tools", "index.ts");
394
417
  }
395
418
 
419
+ function getAntigravityAuthExtensionPath(): string {
420
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "antigravity-auth", "index.ts");
421
+ }
422
+
396
423
  function terminateChildProcess(proc: ChildProcess, signal: NodeJS.Signals): void {
397
424
  if (proc.pid) {
398
425
  terminateProcess(proc.pid, signal as "SIGTERM" | "SIGINT" | "SIGKILL");
@@ -23,6 +23,7 @@ export {
23
23
  DEFAULT_ROUTING_CONFIG,
24
24
  existingSubagentConfigFiles,
25
25
  getDefaultSubagentConfigPath,
26
+ getBrowserQaSkillPath,
26
27
  getSubagentConfigInitTargetPath,
27
28
  getSubagentConfigSamplePath,
28
29
  isBlindModelRef,
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: browser-qa-private
3
+ description: Private workflow for deterministic browser bug reproduction and fix verification with redacted project auth and Playwright evidence.
4
+ ---
5
+
6
+ # Browser QA
7
+
8
+ Use the bundled runner; do not read, print, grep, copy, or edit credential values
9
+ from `.pi/qa_auth.jsonc` yourself.
10
+
11
+ ## Workflow
12
+
13
+ 1. Resolve `scripts/browser-qa-runner.mjs` relative to this skill.
14
+ 2. Run `node <runner> profiles`. Choose a profile only when the task names its
15
+ id or safe profile traits make the choice unambiguous. Otherwise stop with
16
+ `QA_PROFILE_REQUIRED` and list only ids, descriptions, and traits.
17
+ 3. Inspect the target code and write a declarative JSONC flow under
18
+ `.pi/qa-flows/`. Never put credentials or executable JavaScript in it.
19
+ 4. Run:
20
+ `node <runner> run --profile <id> --base-url <url> --flow <flow.jsonc>`.
21
+ Profile id, URL, and flow path are non-secret; never pass credentials as
22
+ arguments or environment variables.
23
+ 5. Report deterministic assertions and every artifact returned by the runner.
24
+ For each screenshot, video, or trace, emit a separate clickable Markdown
25
+ link using its `uri` and also show its absolute `path`. Do this for failed
26
+ runs too whenever `artifacts` is present; never report only `evidenceDir`.
27
+ Visual inspection supplements assertions; it does not replace them.
28
+
29
+ The flow is `{ "steps": [...] }` with at most 100 steps. Supported actions:
30
+
31
+ - navigation: `goto`, `reload`, `waitFor`, `waitForTimeout`
32
+ - interaction: `click`, `doubleClick`, `hover`, `fill`, `press`, `check`,
33
+ `uncheck`, `selectOption`
34
+ - assertions: `assertVisible`, `assertHidden`, `assertEnabled`,
35
+ `assertDisabled`, `assertChecked`, `assertUnchecked`, `assertText`,
36
+ `assertValue`, `assertCount`, `assertURL`
37
+ - evidence/auth: `screenshot`, `authRejectedIf`
38
+
39
+ Locators accept one of `testId`, `role` (plus optional `name`), `label`,
40
+ `placeholder`, `text`, or `css`; add `exact: true` where useful. String
41
+ assertions require exactly one of `equals` or `includes`.
42
+
43
+ ```jsonc
44
+ {
45
+ "steps": [
46
+ { "action": "goto", "path": "/settings" },
47
+ { "action": "authRejectedIf", "urlIncludes": "/login" },
48
+ {
49
+ "action": "assertVisible",
50
+ "locator": { "role": "heading", "name": "Settings", "exact": true }
51
+ },
52
+ { "action": "click", "locator": { "testId": "save-settings" } },
53
+ { "action": "assertText", "locator": { "testId": "toast" }, "includes": "Saved" }
54
+ ]
55
+ }
56
+ ```
57
+
58
+ The runner owns Playwright/browser lifecycle, strict network-origin boundaries,
59
+ auth application, assertions, screenshots, video, trace sanitization, and
60
+ cleanup. Do not start an additional shared/default browser session. For multiple
61
+ profiles, invoke the runner separately; each invocation gets an isolated context
62
+ and exclusive evidence directory.
63
+
64
+ If the runner returns `QA_AUTH_UPDATE_REQUIRED`, stop and explicitly report that
65
+ browser QA requires credentials or an auth-config update. Ask the user to fill
66
+ the reported file and rerun QA. If `templateCreated` is true, say that a private
67
+ empty template was created at that path. Relay only the runner's profile, file,
68
+ reason, action, and template-created state; never read the generated file or
69
+ attempt to recover by exposing or replaying credentials.
70
+
71
+ After any runner invocation that actually performed browser testing, include
72
+ all non-empty `artifacts.screenshots`, `artifacts.videos`, and
73
+ `artifacts.traces` groups in the final response. These links are mandatory so
74
+ the user can open the evidence directly.
75
+
76
+ See `references/qa-auth.example.jsonc` and `references/qa-flow.example.jsonc`.
@@ -0,0 +1,87 @@
1
+ {
2
+ // Copy to <project>/.pi/qa_auth.jsonc. This file is private and ignored by
3
+ // the host repository's default .gitignore rule for .pi/. On POSIX, run
4
+ // chmod 600 .pi/qa_auth.jsonc.
5
+ "profiles": {
6
+ "staging-admin": {
7
+ "description": "Staging administrator",
8
+ "traits": ["role:admin", "plan:enterprise"],
9
+ "baseUrl": "https://staging.example.test",
10
+ "allowedOrigins": ["https://staging.example.test"],
11
+ "auth": {
12
+ "type": "form",
13
+ "loginUrl": "https://staging.example.test/login",
14
+ "fields": [
15
+ { "selector": "input[name=email]", "value": "admin@example.test" },
16
+ { "selector": "input[name=password]", "value": "replace-me" }
17
+ ],
18
+ "submitSelector": "button[type=submit]",
19
+ "success": { "url": "**/dashboard", "selector": "[data-testid=user-menu]" }
20
+ }
21
+ },
22
+ "staging-subscriber": {
23
+ "description": "Staging paid subscriber",
24
+ "traits": ["role:user", "plan:paid"],
25
+ "baseUrl": "https://staging.example.test",
26
+ "allowedOrigins": ["https://staging.example.test"],
27
+ "auth": {
28
+ "type": "cookie",
29
+ "cookies": [
30
+ {
31
+ "name": "session",
32
+ "value": "replace-me",
33
+ "domain": "staging.example.test",
34
+ "path": "/",
35
+ "httpOnly": true,
36
+ "secure": true,
37
+ "sameSite": "Lax"
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ "staging-local-storage": {
43
+ "description": "Local-storage auth example",
44
+ "traits": ["role:user", "auth:localStorage"],
45
+ "baseUrl": "https://staging.example.test",
46
+ "allowedOrigins": ["https://staging.example.test"],
47
+ "auth": {
48
+ "type": "localStorage",
49
+ "origin": "https://staging.example.test",
50
+ "entries": { "access_token": "replace-me" }
51
+ }
52
+ },
53
+ "staging-session-storage": {
54
+ "description": "Session-storage auth example",
55
+ "traits": ["role:user", "auth:sessionStorage"],
56
+ "baseUrl": "https://staging.example.test",
57
+ "allowedOrigins": ["https://staging.example.test"],
58
+ "auth": {
59
+ "type": "sessionStorage",
60
+ "origin": "https://staging.example.test",
61
+ "entries": { "access_token": "replace-me" }
62
+ }
63
+ },
64
+ "staging-bearer": {
65
+ "description": "Bearer-token API auth example",
66
+ "traits": ["role:service", "auth:bearer"],
67
+ "baseUrl": "https://staging.example.test",
68
+ "allowedOrigins": ["https://staging.example.test"],
69
+ "auth": {
70
+ "type": "bearer",
71
+ "token": "replace-me",
72
+ "header": "Authorization",
73
+ "prefix": "Bearer "
74
+ }
75
+ },
76
+ "staging-storage-state": {
77
+ "description": "Existing Playwright storage-state example",
78
+ "traits": ["role:user", "auth:storageState"],
79
+ "baseUrl": "https://staging.example.test",
80
+ "allowedOrigins": ["https://staging.example.test"],
81
+ "auth": {
82
+ "type": "storageState",
83
+ "path": ".pi/qa-auth-state/imported-user.json"
84
+ }
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "timeoutMs": 15000,
3
+ "steps": [
4
+ { "action": "goto", "path": "/settings" },
5
+ { "action": "authRejectedIf", "urlIncludes": "/login" },
6
+ {
7
+ "action": "assertVisible",
8
+ "locator": { "role": "heading", "name": "Settings", "exact": true }
9
+ },
10
+ {
11
+ "action": "fill",
12
+ "locator": { "label": "Display name", "exact": true },
13
+ "value": "QA Example"
14
+ },
15
+ { "action": "click", "locator": { "testId": "save-settings" } },
16
+ { "action": "assertText", "locator": { "testId": "toast" }, "includes": "Saved" },
17
+ { "action": "screenshot", "name": "settings-saved" }
18
+ ]
19
+ }