pi-ui-extend 1.0.18 → 1.0.20
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/external/pi-tools-suite/README.md +4 -3
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +23 -3
- package/external/pi-tools-suite/package.json +1 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +22 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -0
- package/external/pi-tools-suite/src/todo/state/replay.ts +28 -10
- package/external/pi-tools-suite/src/todo/todo.ts +53 -4
- package/package.json +3 -1
|
@@ -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
|
|
@@ -251,8 +251,9 @@ The built-in `browser-qa` role runs on `antigravity/gemini-3-flash-preview`, wit
|
|
|
251
251
|
`openai-codex/gpt-5.4-mini` as its fallback. Its browser workflow is an explicit
|
|
252
252
|
private skill under `src/async-subagents/private-skills/`, outside normal Pi skill
|
|
253
253
|
discovery. The role's first-class `isolatedSkills` setting launches the child with
|
|
254
|
-
`--no-skills` plus
|
|
255
|
-
|
|
254
|
+
`--no-skills` plus the private workflow and the packaged `skills/playwright-cli`
|
|
255
|
+
skill. Both are mandatory even when configuration adds other isolated skills;
|
|
256
|
+
parent and ordinary sub-agent sessions do not discover the private workflow.
|
|
256
257
|
|
|
257
258
|
Keep named dev/staging auth profiles in project `.pi/qa_auth.jsonc` (there is no
|
|
258
259
|
`/qa-auth` command). The private runner supports `form`, `cookie`, `localStorage`,
|
|
@@ -17,6 +17,9 @@ evidence. The role uses `antigravity/gemini-3-flash-preview`, falling back to
|
|
|
17
17
|
Antigravity-backed role unavailable.
|
|
18
18
|
- A type profile may declare `isolatedSkills`. Spawning that profile adds
|
|
19
19
|
`--no-skills` followed by one explicit `--skill` per configured path.
|
|
20
|
+
- The `browser-qa` profile always loads both its private workflow and the
|
|
21
|
+
packaged `skills/playwright-cli` skill. Configuration may append isolated
|
|
22
|
+
skills but cannot remove either mandatory browser skill.
|
|
20
23
|
- Other sub-agent profiles and the parent session must not discover the private
|
|
21
24
|
skill automatically.
|
|
22
25
|
|
|
@@ -66,9 +69,10 @@ evidence. The role uses `antigravity/gemini-3-flash-preview`, falling back to
|
|
|
66
69
|
|
|
67
70
|
## Acceptance criteria
|
|
68
71
|
|
|
69
|
-
1. `browser-qa` resolves to the intended model/fallback
|
|
70
|
-
its isolated child process can register
|
|
71
|
-
|
|
72
|
+
1. `browser-qa` resolves to the intended model/fallback, private workflow, and
|
|
73
|
+
packaged `playwright-cli` skill, and its isolated child process can register
|
|
74
|
+
the configured Antigravity model.
|
|
75
|
+
2. Spawn args contain `--no-skills` and both mandatory explicit skills for this
|
|
72
76
|
profile; ordinary profiles retain existing skill discovery behavior.
|
|
73
77
|
3. Auth profile listing and all error output are redacted; model-authored input
|
|
74
78
|
cannot execute code in the credential-bearing process.
|
|
@@ -79,3 +83,19 @@ evidence. The role uses `antigravity/gemini-3-flash-preview`, falling back to
|
|
|
79
83
|
5. Completed test runs report clickable screenshot, video, and trace links
|
|
80
84
|
whenever those artifacts exist.
|
|
81
85
|
6. Suite tests/typecheck, host checks, and suite sync pass.
|
|
86
|
+
|
|
87
|
+
## Real-browser regression test
|
|
88
|
+
|
|
89
|
+
The repository includes a local mock-page E2E that launches real Chromium and
|
|
90
|
+
asserts PNG screenshots, WebM video, sanitized trace output, and absolute
|
|
91
|
+
path/`file:` URI metadata:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx playwright install chromium
|
|
95
|
+
npm run test:browser-qa-e2e
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Normal suite tests keep this case skipped; the Publish workflow runs it on
|
|
99
|
+
Linux after installing Chromium. Explicit runs retain the latest artifacts in
|
|
100
|
+
`.pi/qa-runs/browser-qa-e2e/latest/` and print a clickable link for every file.
|
|
101
|
+
Set `BROWSER_QA_KEEP_EVIDENCE=0` to clean up without publishing evidence.
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"smoke:tools": "PI_OFFLINE=1 pi --no-session -p \"ping\"",
|
|
23
23
|
"smoke": "npm run smoke:explicit && npm run smoke:auto && npm run smoke:tools",
|
|
24
24
|
"test": "bun test test",
|
|
25
|
+
"test:browser-qa-e2e": "BROWSER_QA_RUNNER_E2E=1 bun test test/async-subagents/browser-qa-runner.e2e.test.ts",
|
|
25
26
|
"test:async-subagents-e2e": "ASYNC_SUBAGENTS_E2E=1 ASYNC_SUBAGENTS_DEBUG_LOGS=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/async-subagents",
|
|
26
27
|
"test:async-subagents-selection-e2e": "ASYNC_SUBAGENTS_SELECTION_E2E=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/async-subagents/selection-e2e.test.ts",
|
|
27
28
|
"test:prompt-evals:tool-selection": "PROMPT_EVAL_E2E=1 bun test --concurrent --max-concurrency=10 test/tool-selection-e2e.test.ts",
|
|
@@ -288,6 +288,21 @@ export function getBrowserQaSkillPath(): string {
|
|
|
288
288
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "private-skills", "browser-qa", "SKILL.md");
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
export function getPlaywrightCliSkillPath(): string {
|
|
292
|
+
const bundledPath = path.resolve(
|
|
293
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
294
|
+
"../../../../..",
|
|
295
|
+
"skills",
|
|
296
|
+
"playwright-cli",
|
|
297
|
+
"SKILL.md",
|
|
298
|
+
);
|
|
299
|
+
const installedPaths = [
|
|
300
|
+
path.join(os.homedir(), ".agents", "skills", "playwright-cli", "SKILL.md"),
|
|
301
|
+
path.join(os.homedir(), ".pi", "agent", "skills", "playwright-cli", "SKILL.md"),
|
|
302
|
+
];
|
|
303
|
+
return [bundledPath, ...installedPaths].find((candidate) => fs.existsSync(candidate)) ?? bundledPath;
|
|
304
|
+
}
|
|
305
|
+
|
|
291
306
|
export function getSubagentConfigInitTargetPath(cwd: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
292
307
|
return explicitSubagentConfigPath(cwd, env) ?? getDefaultSubagentConfigPath();
|
|
293
308
|
}
|
|
@@ -381,7 +396,7 @@ export function resolveAgentTaskConfig(
|
|
|
381
396
|
return {
|
|
382
397
|
profile,
|
|
383
398
|
extraArgs,
|
|
384
|
-
isolatedSkills:
|
|
399
|
+
isolatedSkills: resolveIsolatedSkills(selectedType, profile),
|
|
385
400
|
fallbackModels,
|
|
386
401
|
retry: resolveRetryConfig(config.retry, profile?.retry),
|
|
387
402
|
maxResultBytes: profile?.maxResultBytes ?? config.maxResultBytes,
|
|
@@ -399,6 +414,12 @@ export function resolveAgentTaskConfig(
|
|
|
399
414
|
};
|
|
400
415
|
}
|
|
401
416
|
|
|
417
|
+
function resolveIsolatedSkills(selectedType: string | undefined, profile: SubagentTypeConfig | undefined): string[] {
|
|
418
|
+
const configured = arrayOfStrings(profile?.isolatedSkills) ?? [];
|
|
419
|
+
if (selectedType !== "browser-qa") return configured;
|
|
420
|
+
return [...new Set([getBrowserQaSkillPath(), getPlaywrightCliSkillPath(), ...configured])];
|
|
421
|
+
}
|
|
422
|
+
|
|
402
423
|
export function resolveSubagentRoutingConfig(config: SubagentConfig): ResolvedSubagentRoutingConfig {
|
|
403
424
|
return { ...DEFAULT_ROUTING_CONFIG, ...(config.routing ?? {}) };
|
|
404
425
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { TaskDetails } from "../tool/types.js";
|
|
2
2
|
import { EMPTY_STATE, type TaskState } from "./state.js";
|
|
3
3
|
|
|
4
|
+
export const TODO_STATE_ENTRY_TYPE = "pi-tools-suite:todo-state";
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Discriminator for `details` envelopes that match the persisted `TaskDetails`
|
|
6
8
|
* shape. Defensive — branch entries from older or corrupt sessions are
|
|
@@ -12,10 +14,29 @@ export function isTaskDetails(value: unknown): value is TaskDetails {
|
|
|
12
14
|
return Array.isArray(v.tasks) && typeof v.nextId === "number";
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
function taskDetailsFromEntry(entry: unknown): TaskDetails | undefined {
|
|
18
|
+
const candidate = entry as {
|
|
19
|
+
type?: string;
|
|
20
|
+
customType?: string;
|
|
21
|
+
data?: unknown;
|
|
22
|
+
message?: { role?: string; toolName?: string; details?: unknown };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (candidate.type === "custom" && candidate.customType === TODO_STATE_ENTRY_TYPE) {
|
|
26
|
+
return isTaskDetails(candidate.data) ? candidate.data : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const message = candidate.message;
|
|
30
|
+
if (candidate.type !== "message" || message?.role !== "toolResult" || message.toolName !== "todo") return undefined;
|
|
31
|
+
return isTaskDetails(message.details) ? message.details : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
15
34
|
/**
|
|
16
|
-
* Walk the current branch in chronological order; the LAST
|
|
17
|
-
*
|
|
18
|
-
* (last-write-wins).
|
|
35
|
+
* Walk the current branch in chronological order; the LAST valid snapshot
|
|
36
|
+
* from either a `todo` tool result or a todo custom state entry wins
|
|
37
|
+
* (last-write-wins). Custom entries persist slash-command mutations, which do
|
|
38
|
+
* not otherwise produce tool results. When no matching entry exists, returns
|
|
39
|
+
* `EMPTY_STATE`.
|
|
19
40
|
*
|
|
20
41
|
* Pure of module state — `index.ts` writes the returned snapshot into the
|
|
21
42
|
* store after this returns. The function explicitly does NOT touch the store
|
|
@@ -24,14 +45,11 @@ export function isTaskDetails(value: unknown): value is TaskDetails {
|
|
|
24
45
|
export function replayFromBranch(ctx: { sessionManager: { getBranch(): Iterable<unknown> } }): TaskState {
|
|
25
46
|
let result: TaskState = { tasks: [...EMPTY_STATE.tasks], nextId: EMPTY_STATE.nextId };
|
|
26
47
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
const msg = e.message;
|
|
30
|
-
if (!msg || msg.role !== "toolResult" || msg.toolName !== "todo") continue;
|
|
31
|
-
if (!isTaskDetails(msg.details)) continue;
|
|
48
|
+
const details = taskDetailsFromEntry(entry);
|
|
49
|
+
if (!details) continue;
|
|
32
50
|
result = {
|
|
33
|
-
tasks:
|
|
34
|
-
nextId:
|
|
51
|
+
tasks: details.tasks.map((t) => ({ ...t })),
|
|
52
|
+
nextId: details.nextId,
|
|
35
53
|
};
|
|
36
54
|
}
|
|
37
55
|
return result;
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
syncPersistedPlan,
|
|
24
24
|
} from "./state/persistence.js";
|
|
25
25
|
import { AUTO_CLEAR_COMPLETED_MESSAGE, autoClearCompletedTodos } from "./state/auto-clear.js";
|
|
26
|
-
import { replayFromBranch } from "./state/replay.js";
|
|
26
|
+
import { replayFromBranch, TODO_STATE_ENTRY_TYPE } from "./state/replay.js";
|
|
27
27
|
import { isTaskBlocked, selectTasksByStatus, selectTodoCounts } from "./state/selectors.js";
|
|
28
28
|
import { applyTaskMutation } from "./state/state-reducer.js";
|
|
29
29
|
import { activateStateScope, commitState, getState, replaceState } from "./state/store.js";
|
|
@@ -49,6 +49,7 @@ const SECTION_DEFERRED = "── Deferred ──";
|
|
|
49
49
|
const SECTION_COMPLETED = "── Completed ──";
|
|
50
50
|
const PERSIST_COMMAND_NAME = "todos-persist";
|
|
51
51
|
const SCOPE_COMMAND_NAME = "todos-scope";
|
|
52
|
+
const CLEAR_COMMAND_NAME = "todos-clear";
|
|
52
53
|
export const TODO_STATE_EVENT = "pi-tools-suite:todo:state";
|
|
53
54
|
|
|
54
55
|
type CommandCompletion = { value: string; label: string; description?: string };
|
|
@@ -59,6 +60,7 @@ const TODOS_ARGUMENT_COMPLETIONS: CommandCompletion[] = [
|
|
|
59
60
|
{ value: "persist off", label: "persist off", description: "Disable persistence and remove the project plan file" },
|
|
60
61
|
{ value: "persist clear", label: "persist clear", description: "Alias for persist off" },
|
|
61
62
|
{ value: "scope ", label: "scope <id...>", description: "Keep selected items active and defer out-of-scope items" },
|
|
63
|
+
{ value: "clear", label: "clear", description: "Clear all todos" },
|
|
62
64
|
{ value: "--active", label: "--active", description: "Show pending/in_progress tasks" },
|
|
63
65
|
{ value: "--ready", label: "--ready", description: "Show pending tasks whose blockers are completed" },
|
|
64
66
|
{ value: "--blocked", label: "--blocked", description: "Show tasks with blockers" },
|
|
@@ -96,6 +98,7 @@ interface TodoToolRegistrationOptions extends TodoToolHooks {
|
|
|
96
98
|
|
|
97
99
|
type TodoStateEventContext = { sessionManager?: { getSessionFile?: () => unknown; getSessionId?: () => unknown } };
|
|
98
100
|
type TodoStateEventEmitter = { events?: { emit?: (channel: string, data: unknown) => void } };
|
|
101
|
+
type TodoStateEntryWriter = { appendEntry<T = unknown>(customType: string, data?: T): void };
|
|
99
102
|
|
|
100
103
|
interface TodosCommandOptions {
|
|
101
104
|
status?: TaskStatus;
|
|
@@ -236,6 +239,20 @@ export function publishTodoState(
|
|
|
236
239
|
});
|
|
237
240
|
}
|
|
238
241
|
|
|
242
|
+
function appendTodoStateSnapshot(
|
|
243
|
+
pi: TodoStateEntryWriter,
|
|
244
|
+
action: TaskAction,
|
|
245
|
+
params: Record<string, unknown>,
|
|
246
|
+
): void {
|
|
247
|
+
const state = getState();
|
|
248
|
+
pi.appendEntry(TODO_STATE_ENTRY_TYPE, {
|
|
249
|
+
action,
|
|
250
|
+
params,
|
|
251
|
+
tasks: state.tasks,
|
|
252
|
+
nextId: state.nextId,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
239
256
|
function handlePersistCommand(args: unknown, ctx: { cwd?: string; hasUI?: boolean; ui?: { notify?: (message: string, level?: NotifyLevel) => void } }): boolean {
|
|
240
257
|
const [command, subcommand = "status"] = getCommandTokens(args);
|
|
241
258
|
if (command !== "persist") return false;
|
|
@@ -307,6 +324,20 @@ function handleScopeCommand(
|
|
|
307
324
|
return true;
|
|
308
325
|
}
|
|
309
326
|
|
|
327
|
+
function clearTodos(
|
|
328
|
+
pi: TodoStateEventEmitter & TodoStateEntryWriter,
|
|
329
|
+
ctx: { cwd?: string; hasUI?: boolean; ui?: { notify?: (message: string, level?: NotifyLevel) => void } },
|
|
330
|
+
): void {
|
|
331
|
+
const result = applyTaskMutation(getState(), "clear", { action: "clear" });
|
|
332
|
+
if (result.op.kind !== "clear") return;
|
|
333
|
+
commitState(result.state);
|
|
334
|
+
appendTodoStateSnapshot(pi, "clear", { action: "clear" });
|
|
335
|
+
publishTodoState(pi, ctx, "clear", { action: "clear" });
|
|
336
|
+
const sync = syncPersistedPlan(ctx.cwd, result.state);
|
|
337
|
+
const persistedText = sync?.completed ? `\nProject todo plan removed: ${sync.path}` : "";
|
|
338
|
+
notifyCommand(ctx, `Cleared ${result.op.count} todos.${persistedText}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
310
341
|
function filterCommandTasks(tasks: readonly Task[], options: TodosCommandOptions): Task[] {
|
|
311
342
|
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
312
343
|
let view = [...tasks];
|
|
@@ -429,12 +460,19 @@ export function registerTodoTool(pi: ExtensionAPI, hooks: TodoToolRegistrationOp
|
|
|
429
460
|
|
|
430
461
|
export function registerTodosCommand(pi: ExtensionAPI): void {
|
|
431
462
|
pi.registerCommand(COMMAND_NAME, {
|
|
432
|
-
description: "Show todos on the current branch. Flags: --active, --ready, --blocked, --tree, --status <status>, --export [json|markdown]. Commands: persist on|off|clear|status, scope <id
|
|
463
|
+
description: "Show todos on the current branch. Flags: --active, --ready, --blocked, --tree, --status <status>, --export [json|markdown]. Commands: persist on|off|clear|status, scope <id...>, clear",
|
|
433
464
|
getArgumentCompletions: (prefix) => completeCommandArguments(String(prefix ?? ""), TODOS_ARGUMENT_COMPLETIONS),
|
|
434
465
|
handler: async (args, ctx) => {
|
|
435
466
|
activateTodoStateScope(ctx);
|
|
436
467
|
if (handlePersistCommand(args, ctx)) return;
|
|
437
|
-
if (handleScopeCommand(args, ctx, () =>
|
|
468
|
+
if (handleScopeCommand(args, ctx, () => {
|
|
469
|
+
appendTodoStateSnapshot(pi, "update", { command: "scope" });
|
|
470
|
+
publishTodoState(pi as TodoStateEventEmitter, ctx);
|
|
471
|
+
})) return;
|
|
472
|
+
if (getCommandTokens(args)[0] === "clear") {
|
|
473
|
+
clearTodos(pi, ctx);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
438
476
|
if (!ctx.hasUI) {
|
|
439
477
|
console.error(ERR_REQUIRES_INTERACTIVE);
|
|
440
478
|
return;
|
|
@@ -510,7 +548,18 @@ export function registerTodosCommand(pi: ExtensionAPI): void {
|
|
|
510
548
|
description: "Select todo ids to continue from a persisted plan; pending/in_progress items outside the scope become deferred.",
|
|
511
549
|
handler: async (args, ctx) => {
|
|
512
550
|
activateTodoStateScope(ctx);
|
|
513
|
-
handleScopeCommand(`scope ${getCommandText(args)}`, ctx, () =>
|
|
551
|
+
handleScopeCommand(`scope ${getCommandText(args)}`, ctx, () => {
|
|
552
|
+
appendTodoStateSnapshot(pi, "update", { command: "scope" });
|
|
553
|
+
publishTodoState(pi as TodoStateEventEmitter, ctx);
|
|
554
|
+
});
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
pi.registerCommand(CLEAR_COMMAND_NAME, {
|
|
559
|
+
description: "Clear all todos on the current branch and remove the persisted project plan if enabled.",
|
|
560
|
+
handler: async (_args, ctx) => {
|
|
561
|
+
activateTodoStateScope(ctx);
|
|
562
|
+
clearTodos(pi, ctx);
|
|
514
563
|
},
|
|
515
564
|
});
|
|
516
565
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ui-extend",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Pix: a workspace-first terminal UI for Pi with tabs, readable tool activity, voice input, and bundled agent tools.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"test": "mise exec node@24.16.0 -- npm run test:inner",
|
|
59
59
|
"test:inner": "node --import tsx --test \"tests/**/*.test.ts\"",
|
|
60
60
|
"test:tools-suite": "npm --prefix external/pi-tools-suite test",
|
|
61
|
+
"test:browser-qa-e2e": "npm --prefix external/pi-tools-suite run test:browser-qa-e2e",
|
|
61
62
|
"sync:pi-tools-suite": "node scripts/sync-pi-tools-suite.mjs",
|
|
62
63
|
"sync:pi-tools-suite:check": "node scripts/sync-pi-tools-suite.mjs --check",
|
|
63
64
|
"sync:sdk-pin": "node scripts/sync-sdk-pin.mjs",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
89
|
"@types/node": "24.12.4",
|
|
90
|
+
"playwright": "1.62.1",
|
|
89
91
|
"tsx": "4.22.1",
|
|
90
92
|
"typescript": "5.9.3"
|
|
91
93
|
},
|