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.
- package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
- package/dist/schemas/pi-tools-suite-schema.js +1 -0
- package/external/pi-tools-suite/README.md +55 -2
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +81 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +11 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +19 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +28 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +76 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-auth.example.jsonc +87 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-flow.example.jsonc +19 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +926 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/vendor/fflate.LICENSE +21 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/vendor/fflate.mjs +2692 -0
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +6 -1
- package/external/pi-tools-suite/src/todo/todo.ts +28 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +10 -3
- package/package.json +1 -1
- package/schemas/pi-tools-suite.json +7 -0
|
@@ -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-
|
|
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");
|
|
@@ -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
|
+
}
|