pi-ui-extend 1.0.32 → 1.0.36

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.
Files changed (34) hide show
  1. package/README.md +21 -0
  2. package/dist/app/app.js +0 -1
  3. package/dist/app/commands/command-host.d.ts +1 -2
  4. package/dist/app/commands/command-model-actions.js +5 -5
  5. package/dist/app/constants.d.ts +0 -1
  6. package/dist/app/constants.js +0 -34
  7. package/dist/app/popup/menu-items-controller.d.ts +1 -3
  8. package/dist/app/popup/menu-items-controller.js +10 -32
  9. package/dist/app/rendering/popup-menu-renderer.js +1 -1
  10. package/dist/app/runtime.js +1 -3
  11. package/dist/app/screen/file-link-opener.js +15 -49
  12. package/external/pi-tools-suite/README.md +29 -7
  13. package/external/pi-tools-suite/docs/browser-qa-subagent.md +24 -6
  14. package/external/pi-tools-suite/docs/session-recovery.md +84 -0
  15. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +13 -9
  16. package/external/pi-tools-suite/src/async-subagents/core/config.ts +2 -2
  17. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +23 -4
  18. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +40 -4
  19. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +19 -0
  20. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +444 -13
  21. package/external/pi-tools-suite/src/coding-discipline/index.ts +12 -2
  22. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +6 -6
  23. package/external/pi-tools-suite/src/index.ts +1 -0
  24. package/external/pi-tools-suite/src/session-recovery/index.ts +674 -0
  25. package/external/pi-tools-suite/src/tool-descriptions.ts +42 -0
  26. package/package.json +4 -2
  27. package/skills/skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  28. package/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
  29. package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
  30. package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
  31. package/skills/skill-creator/scripts/__pycache__/package_skill.cpython-314.pyc +0 -0
  32. package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
  33. package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
  34. package/skills/skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
package/README.md CHANGED
@@ -401,6 +401,27 @@ Configurable areas include:
401
401
 
402
402
  Use `/settings` to inspect the effective settings summary and `/reload` after changing resources. Some command-driven settings update the relevant config file directly.
403
403
 
404
+ ### Ignoring legacy context files
405
+
406
+ If a legacy project already contains an `AGENTS.md` or `CLAUDE.md` that should not be loaded, the simplest project-local workaround for both Pi and Pix is an empty override file in the same directory:
407
+
408
+ ```bash
409
+ touch AGENTS.override.md
410
+ ```
411
+
412
+ Pi and Pix load `AGENTS.override.md` instead of `AGENTS.md`/`CLAUDE.md` from that directory. To keep this local without changing the repository:
413
+
414
+ ```bash
415
+ echo AGENTS.override.md >> .git/info/exclude
416
+ ```
417
+
418
+ To disable discovery of all context files, including files in parent directories:
419
+
420
+ - start Pi with `pi --no-context-files` (or `pi -nc`);
421
+ - in Pix, run `/no-context-files on`. Pix saves `"ignoreContextFiles": true` to `<workspace>/.pi/pix.jsonc`.
422
+
423
+ Start a new session or restart Pix after changing this setting. Use `/no-context-files off` to enable context-file loading again.
424
+
404
425
  ## Updates
405
426
 
406
427
  ```bash
package/dist/app/app.js CHANGED
@@ -533,7 +533,6 @@ export class PiUiExtendApp {
533
533
  getThinkingMenuItems: (query) => this.menuItems.getThinkingMenuItems(query),
534
534
  awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
535
535
  modelRef: (model) => this.menuItems.modelRef(model),
536
- getFavoriteScopedModels: () => this.menuItems.getFavoriteScopedModels(),
537
536
  setSessionStatus: (session) => this.setSessionStatus(session),
538
537
  queueUserMessage: (text) => {
539
538
  this.queuedMessages.deferUserMessage(this.queuedMessages.createSubmittedUserMessage(text, text, []));
@@ -1,7 +1,7 @@
1
1
  import type { AgentSession, AgentSessionRuntime, SessionInfo } from "@earendil-works/pi-coding-agent";
2
2
  import type { SessionSearchResult } from "../session/session-search.js";
3
3
  import type { AppRequestHistory } from "../session/request-history.js";
4
- import type { ActivePopupMenu, AppOptions, Entry, ModelMenuValue, PixMenuItem, PixMenuOptions, PopupMenuPlacement, ScopedSessionModel, SessionModel, ThinkingMenuValue } from "../types.js";
4
+ import type { ActivePopupMenu, AppOptions, Entry, ModelMenuValue, PixMenuItem, PixMenuOptions, PopupMenuPlacement, SessionModel, ThinkingMenuValue } from "../types.js";
5
5
  import type { ToastNotifier } from "../../ui.js";
6
6
  export type DirectPopupMenu = Exclude<ActivePopupMenu, "slash">;
7
7
  export type CommandControllerHost = {
@@ -27,7 +27,6 @@ export type CommandControllerHost = {
27
27
  getModelMenuItems(query: string): readonly PixMenuItem<ModelMenuValue>[];
28
28
  getThinkingMenuItems(query: string): readonly PixMenuItem<ThinkingMenuValue>[];
29
29
  modelRef(model: SessionModel): string;
30
- getFavoriteScopedModels(): ScopedSessionModel[];
31
30
  setSessionStatus(session: AgentSession | undefined): void;
32
31
  queueUserMessage(text: string): void;
33
32
  resetSessionView(): void;
@@ -18,7 +18,7 @@ export class ModelCommandActions {
18
18
  const enabledModels = settings.getEnabledModels();
19
19
  const scopedModelText = enabledModels && enabledModels.length > 0
20
20
  ? enabledModels.map((model) => ` - ${model}`).join("\n")
21
- : " - default favorites";
21
+ : " - all available models (no scope)";
22
22
  const text = [
23
23
  "Settings summary",
24
24
  `cwd: ${runtime.cwd}`,
@@ -191,7 +191,7 @@ export class ModelCommandActions {
191
191
  else if (sessionModels.length > 0)
192
192
  scopedModelLines = sessionModels;
193
193
  else
194
- scopedModelLines = [" - default favorites"];
194
+ scopedModelLines = [" - all available models (no scope)"];
195
195
  this.host.addEntry({
196
196
  id: createId("system"),
197
197
  kind: "system",
@@ -200,7 +200,7 @@ export class ModelCommandActions {
200
200
  ...scopedModelLines,
201
201
  "",
202
202
  "Usage: /scoped-models <provider/model[:thinking]> [...more]",
203
- "Use /scoped-models reset to restore the default favorites.",
203
+ "Use /scoped-models reset to use all available models.",
204
204
  ].join("\n"),
205
205
  });
206
206
  this.host.setSessionStatus(runtime.session);
@@ -208,8 +208,8 @@ export class ModelCommandActions {
208
208
  }
209
209
  if (["reset", "default", "clear"].includes(value.toLowerCase())) {
210
210
  runtime.services.settingsManager.setEnabledModels(undefined);
211
- runtime.session.setScopedModels(this.host.getFavoriteScopedModels());
212
- this.host.addEntry({ id: createId("system"), kind: "system", text: "Scoped models reset to default favorites." });
211
+ runtime.session.setScopedModels([]);
212
+ this.host.addEntry({ id: createId("system"), kind: "system", text: "Model scope reset to all available models." });
213
213
  this.host.setSessionStatus(runtime.session);
214
214
  return;
215
215
  }
@@ -1,7 +1,6 @@
1
1
  import type { ResolvedToolRule } from "../config.js";
2
2
  export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
3
3
  export declare const THINKING_MENU_MAX_ROWS: number;
4
- export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.6-sol", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.5", "groq/openai/gpt-oss-120b", "cerebras/gpt-oss-120b", "zai/glm-5.3", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
5
4
  export declare const SLASH_COMMAND_MENU_MAX_ROWS = 6;
6
5
  export declare const RESUME_MENU_MAX_ROWS = 20;
7
6
  export declare const RESUME_MENU_INITIAL_SESSION_ROWS = 30;
@@ -1,40 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
3
3
  export const THINKING_MENU_MAX_ROWS = THINKING_LEVELS.length + 1;
4
- export const PI_FAVORITE_MODEL_REFS = [
5
- "amazon-bedrock/us.anthropic.claude-opus-4-6-v1",
6
- "anthropic/claude-opus-4-8",
7
- "openai/gpt-5.4",
8
- "azure-openai-responses/gpt-5.4",
9
- "openai-codex/gpt-5.6-sol",
10
- "deepseek/deepseek-v4-pro",
11
- "google/gemini-3.1-pro-preview",
12
- "google-vertex/gemini-3.1-pro-preview",
13
- "github-copilot/gpt-5.4",
14
- "openrouter/moonshotai/kimi-k2.6",
15
- "vercel-ai-gateway/zai/glm-5.2",
16
- "xai/grok-4.5",
17
- "groq/openai/gpt-oss-120b",
18
- "cerebras/gpt-oss-120b",
19
- "zai/glm-5.3",
20
- "mistral/devstral-medium-latest",
21
- "minimax/MiniMax-M2.7",
22
- "minimax-cn/MiniMax-M2.7",
23
- "moonshotai/kimi-k2.6",
24
- "moonshotai-cn/kimi-k2.6",
25
- "huggingface/moonshotai/Kimi-K2.6",
26
- "fireworks/accounts/fireworks/models/kimi-k2p6",
27
- "together/moonshotai/Kimi-K2.6",
28
- "opencode/kimi-k2.6",
29
- "opencode-go/kimi-k2.6",
30
- "kimi-coding/kimi-for-coding",
31
- "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6",
32
- "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6",
33
- "xiaomi/mimo-v2.5-pro",
34
- "xiaomi-token-plan-cn/mimo-v2.5-pro",
35
- "xiaomi-token-plan-ams/mimo-v2.5-pro",
36
- "xiaomi-token-plan-sgp/mimo-v2.5-pro",
37
- ];
38
4
  export const SLASH_COMMAND_MENU_MAX_ROWS = 6;
39
5
  export const RESUME_MENU_MAX_ROWS = 20;
40
6
  export const RESUME_MENU_INITIAL_SESSION_ROWS = 30;
@@ -1,6 +1,6 @@
1
1
  import type { AgentSessionRuntime, SessionInfo } from "@earendil-works/pi-coding-agent";
2
2
  import type { PopupMenuItem } from "../../ui.js";
3
- import type { Entry, ModelMenuValue, QueueMessageMenuValue, ResumeMenuValue, ScopedSessionModel, SessionModel, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
3
+ import type { Entry, ModelMenuValue, QueueMessageMenuValue, ResumeMenuValue, SessionModel, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
4
4
  export type AppMenuItemsControllerHost = {
5
5
  runtime(): AgentSessionRuntime | undefined;
6
6
  getBuiltinSlashCommands(): readonly SlashCommand[];
@@ -18,7 +18,6 @@ export declare class AppMenuItemsController {
18
18
  getSlashCommandMatches(query: string, limit?: number): import("../../fuzzy.js").FuzzyMatch<SlashCommand>[];
19
19
  getSlashCommandMenuItems(query: string): PopupMenuItem<SlashCommand>[];
20
20
  modelRef(model: SessionModel): string;
21
- getFavoriteScopedModels(): ScopedSessionModel[];
22
21
  getModelMenuItems(query: string): PopupMenuItem<ModelMenuValue>[];
23
22
  getThinkingMenuItems(query: string): PopupMenuItem<ThinkingMenuValue>[];
24
23
  getUserMessageMenuItems(): PopupMenuItem<UserMessageMenuValue>[];
@@ -30,7 +29,6 @@ export declare class AppMenuItemsController {
30
29
  private getResumeMenuItemsLoader;
31
30
  private getAllSlashCommands;
32
31
  private isCurrentModel;
33
- private resolveScopedModelRefs;
34
32
  private getModelMenuModels;
35
33
  private thinkingLevelDescription;
36
34
  }
@@ -1,8 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { fuzzySearch } from "../../fuzzy.js";
3
- import { PI_FAVORITE_MODEL_REFS, THINKING_LEVELS } from "../constants.js";
3
+ import { THINKING_LEVELS } from "../constants.js";
4
4
  import { APP_ICONS } from "../icons.js";
5
- import { parseScopedModelRef } from "../model/model-ref.js";
6
5
  import { buildUserMessageJumpItems, createSessionInfoMenuItemsLoader, filterUserMessageJumpItems } from "./popup-menu-controller.js";
7
6
  import { getResourceSlashCommands, getSlashCommandMatches, parseSlashInput } from "../commands/slash-commands.js";
8
7
  import { isRecord } from "../guards.js";
@@ -38,11 +37,6 @@ export class AppMenuItemsController {
38
37
  modelRef(model) {
39
38
  return `${model.provider}/${model.id}`;
40
39
  }
41
- getFavoriteScopedModels() {
42
- const configuredRefs = this.host.runtime()?.services.settingsManager.getEnabledModels();
43
- const refs = configuredRefs && configuredRefs.length > 0 ? configuredRefs : PI_FAVORITE_MODEL_REFS;
44
- return this.resolveScopedModelRefs(refs);
45
- }
46
40
  getModelMenuItems(query) {
47
41
  const models = [...this.getModelMenuModels()].sort((left, right) => {
48
42
  const leftCurrent = this.isCurrentModel(left);
@@ -172,33 +166,17 @@ export class AppMenuItemsController {
172
166
  const current = this.host.runtime()?.session.model;
173
167
  return current?.provider === model.provider && current.id === model.id;
174
168
  }
175
- resolveScopedModelRefs(modelRefs) {
176
- const modelRuntime = this.host.runtime()?.services.modelRuntime;
177
- if (!modelRuntime)
178
- return [];
179
- const scopedModels = [];
180
- for (const modelRef of modelRefs) {
181
- const parsed = parseScopedModelRef(modelRef);
182
- if (!parsed)
183
- continue;
184
- const model = modelRuntime.getModel(parsed.provider, parsed.modelId);
185
- if (!model)
186
- continue;
187
- scopedModels.push({
188
- model,
189
- ...(parsed.thinkingLevel === undefined ? {} : { thinkingLevel: parsed.thinkingLevel }),
190
- });
191
- }
192
- return scopedModels;
193
- }
194
169
  getModelMenuModels() {
195
- const session = this.host.runtime()?.session;
196
- const scopedModels = session?.scopedModels.length ? session.scopedModels : this.getFavoriteScopedModels();
197
- if (!scopedModels.length)
170
+ const runtime = this.host.runtime();
171
+ if (!runtime)
198
172
  return [];
199
- const modelRuntime = this.host.runtime()?.services.modelRuntime;
200
- return scopedModels.map((scoped) => {
201
- const refreshed = modelRuntime?.getModel(scoped.model.provider, scoped.model.id);
173
+ const { session } = runtime;
174
+ const modelRuntime = runtime.services.modelRuntime;
175
+ if (!session.scopedModels.length) {
176
+ return [...modelRuntime.getAvailableSnapshot()];
177
+ }
178
+ return session.scopedModels.map((scoped) => {
179
+ const refreshed = modelRuntime.getModel(scoped.model.provider, scoped.model.id);
202
180
  return (refreshed ?? scoped.model);
203
181
  });
204
182
  }
@@ -101,7 +101,7 @@ export class PopupMenuRenderer {
101
101
  const visibleItems = menu.visibleItems();
102
102
  if (!this.hasPopupActionItems(menu.items)) {
103
103
  lines.push({
104
- text: this.host.session ? " No matching favorite models" : " Model menu unavailable",
104
+ text: this.host.session ? " No matching available models" : " Model menu unavailable",
105
105
  variant: "muted",
106
106
  });
107
107
  }
@@ -5,7 +5,6 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
7
7
  import { loadPixConfig, resolveDefaultModelRef } from "../config.js";
8
- import { PI_FAVORITE_MODEL_REFS } from "./constants.js";
9
8
  import { isThinkingLevel, parseModelRef, parseScopedModelRef } from "./model/model-ref.js";
10
9
  import { openLazySessionManager } from "./session/lazy-session-manager.js";
11
10
  const BUNDLED_QUESTION_EXTENSION_NAME = "question";
@@ -267,8 +266,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
267
266
  throw new Error(`Model not found: ${parsedModel.provider}/${parsedModel.modelId}`);
268
267
  }
269
268
  const enabledModelRefs = services.settingsManager.getEnabledModels();
270
- const favoriteModelRefs = enabledModelRefs && enabledModelRefs.length > 0 ? enabledModelRefs : PI_FAVORITE_MODEL_REFS;
271
- const scopedModels = favoriteModelRefs.flatMap((modelRef) => {
269
+ const scopedModels = (enabledModelRefs ?? []).flatMap((modelRef) => {
272
270
  const scoped = parseScopedModelRef(modelRef);
273
271
  if (!scoped)
274
272
  return [];
@@ -2,6 +2,11 @@ import { existsSync } from "node:fs";
2
2
  import { spawn } from "node:child_process";
3
3
  import { isAbsolute, posix, win32 } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ const MEDIA_EXTENSIONS = new Set([
6
+ ".3g2", ".3gp", ".aac", ".aiff", ".alac", ".apng", ".avi", ".avif", ".bmp", ".flac", ".gif", ".heic", ".heif",
7
+ ".ico", ".jpeg", ".jpg", ".m4a", ".m4v", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".oga", ".ogg",
8
+ ".ogv", ".opus", ".png", ".svg", ".tif", ".tiff", ".wav", ".webm", ".webp", ".wma", ".wmv",
9
+ ]);
5
10
  let deps = { existsSync, env: process.env, platform: process.platform, spawn };
6
11
  export function setFileLinkOpenerTestDeps(overrides) {
7
12
  const previous = deps;
@@ -16,9 +21,11 @@ export function openFileLink(link) {
16
21
  const filePath = link.filePath ?? filePathFromUrl(link.url);
17
22
  if (!filePath)
18
23
  return false;
19
- const editorLaunch = preferredEditorLaunch(filePath, link.line, link.column);
20
- if (editorLaunch && trySpawnCandidates(editorLaunch.candidates, editorLaunch.args))
21
- return true;
24
+ if (isZedTerminal(deps.env) && !isMediaFile(filePath)) {
25
+ const openedInZed = trySpawnCandidates(zedCommandCandidates(), [zedTarget(filePath, link.line, link.column)]);
26
+ if (openedInZed)
27
+ return true;
28
+ }
22
29
  return openPathWithSystemViewer(filePath);
23
30
  }
24
31
  function isWebUrl(url) {
@@ -39,45 +46,12 @@ function zedTarget(filePath, line, column) {
39
46
  return filePath;
40
47
  return column === undefined ? `${filePath}:${line}` : `${filePath}:${line}:${column}`;
41
48
  }
42
- function gotoTarget(filePath, line, column) {
43
- if (line === undefined)
44
- return filePath;
45
- return column === undefined ? `${filePath}:${line}` : `${filePath}:${line}:${column}`;
46
- }
47
- function preferredEditorLaunch(filePath, line, column) {
48
- switch (detectEditor(deps.env)) {
49
- case "cursor":
50
- return { args: ["--goto", gotoTarget(filePath, line, column)], candidates: commandCandidates(deps.env.CURSOR_CLI, "cursor") };
51
- case "jetbrains":
52
- return {
53
- args: jetbrainsTargetArgs(filePath, line),
54
- candidates: commandCandidates(deps.env.JETBRAINS_IDE_CLI, "idea", "idea64", "webstorm", "webstorm64", "pycharm", "pycharm64", "goland", "goland64", "clion", "clion64", "phpstorm", "phpstorm64", "rubymine", "rubymine64", "rider", "rider64"),
55
- };
56
- case "vscode":
57
- return { args: ["--goto", gotoTarget(filePath, line, column)], candidates: commandCandidates(deps.env.VSCODE_CLI, "code", "code-insiders") };
58
- case "windsurf":
59
- return { args: ["--goto", gotoTarget(filePath, line, column)], candidates: commandCandidates(deps.env.WINDSURF_CLI, "windsurf") };
60
- case "zed":
61
- return { args: [zedTarget(filePath, line, column)], candidates: zedCommandCandidates() };
62
- default:
63
- return undefined;
64
- }
49
+ function isZedTerminal(env) {
50
+ return env.TERM_PROGRAM?.trim().toLowerCase() === "zed" || Boolean(env.ZED_CLI);
65
51
  }
66
- function detectEditor(env) {
67
- const termProgram = env.TERM_PROGRAM?.trim().toLowerCase();
68
- const terminalEmulator = env.TERMINAL_EMULATOR?.trim().toLowerCase();
69
- const terminalProvider = env.TERMINAL_PROVIDER?.trim().toLowerCase();
70
- if (termProgram === "cursor" || env.CURSOR_TRACE_ID || env.CURSOR_TRACE)
71
- return "cursor";
72
- if (termProgram === "windsurf")
73
- return "windsurf";
74
- if (termProgram === "zed" || env.ZED_CLI)
75
- return "zed";
76
- if (termProgram === "vscode" || env.VSCODE_IPC_HOOK_CLI || env.VSCODE_GIT_IPC_HANDLE)
77
- return "vscode";
78
- if (terminalEmulator?.includes("jetbrains") || terminalProvider === "jetbrains")
79
- return "jetbrains";
80
- return undefined;
52
+ function isMediaFile(filePath) {
53
+ const pathApi = deps.platform === "win32" ? win32 : posix;
54
+ return MEDIA_EXTENSIONS.has(pathApi.extname(filePath).toLowerCase());
81
55
  }
82
56
  function zedCommandCandidates() {
83
57
  const candidates = [deps.env.ZED_CLI, "zed", "zeditor"];
@@ -85,14 +59,6 @@ function zedCommandCandidates() {
85
59
  candidates.push("/opt/homebrew/bin/zed", "/usr/local/bin/zed");
86
60
  return candidates.filter((candidate) => Boolean(candidate));
87
61
  }
88
- function commandCandidates(primary, ...rest) {
89
- return [primary, ...rest].filter((candidate) => Boolean(candidate));
90
- }
91
- function jetbrainsTargetArgs(filePath, line) {
92
- if (line === undefined)
93
- return [filePath];
94
- return ["--line", `${line}`, filePath];
95
- }
96
62
  function trySpawnCandidates(candidates, args) {
97
63
  for (const command of candidates) {
98
64
  if (!canRunCommand(command))
@@ -4,13 +4,14 @@ Local all-in-one Pi extension package.
4
4
 
5
5
  This package keeps shared Pi tools as ordinary source folders under `src/` and registers them through one entrypoint.
6
6
 
7
- - `src/coding-discipline` — injects a deduplicated silent-mode and quality-discipline block at the very top of the main-session per-turn system prompt for GLM main-session models only (`isGlmModel`) immediately before the LLM request; non-GLM models are left untouched; disabled for async sub-agents
7
+ - `src/coding-discipline` — injects a deduplicated silent-mode and quality-discipline block at the very top of the main-session per-turn system prompt for GLM main-session models only (`isGlmModel`) immediately before the LLM request; text-only GLM models get the `lookup` bridge while vision-capable `zai/glm-5.3-flash` inspects images directly; non-GLM models are left untouched; disabled for async sub-agents
8
8
  - `src/credential-firewall` — opt-in secret firewall for high-confidence outbound/session credential redaction; disabled by default
9
9
  - `src/ast-grep` — `ast_grep` / `ast_apply`
10
10
  - `src/async-subagents` — `subagents` tool and sub-agent slash commands, including oh-my-openagent-style `/ultrawork` (`/ulw`) and `/hyperplan` orchestration prompts, plus config-defined sub-agent model/thinking/args presets selected via `/subagent-preset` from `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`; includes the `frontend` profile for Gemini-friendly UI/UX and visual frontend work and the `oracle` profile for cross-provider second opinions; enforces a 30-minute per-agent execution timeout, project-wide `maxConcurrent` queueing, optional retry/backoff, and `result.json` structured metadata/chaining fields next to raw `result.md`; stores project-local run files and a registry under `.pi/subagents/` so result/status collection can recover after compaction or reload while the main session remains alive
11
11
  - `src/lsp` — shared LSP diagnostics hook/library that enriches mutating tool results with diagnostics and shuts down language servers on session shutdown
12
12
  - `src/comment-checker` — AI-slop comment guard that listens to the `tool_result` event for `write` / `edit` / `apply_patch` mutations, extracts net-new code comment lines, classifies them (filler phrasing, restating code, decorative separators, generic paraphrasing, or — under aggressive strictness — any non-valuable comment), and appends a short nudge to the tool result so the agent removes unnecessary comments on its next turn; TODO/FIXME, license headers, docstrings, pragmas, linter directives, shebangs, and decorators are never flagged; language-agnostic across `//` / `/* */` / `#` / `--` / `<!-- -->` / triple-quote comment styles; per-session deduplication (at most one nudge per 30 s) prevents fix/remark loops; configured via the `commentChecker` section (`enabled`, `strictness`: `conservative` | `balanced` | `aggressive`, default `balanced`) or `PI_COMMENT_CHECKER_ENABLED` / `PI_COMMENT_CHECKER_STRICTNESS`
13
13
  - `src/session-name` — `session_name` tool for reading or setting the current session title directly from tool calls, without relying on slash-command parsing
14
+ - `src/session-recovery` — branch- and compaction-aware `session_overview`, `session_read_section`, `session_search`, and `session_recovery_context` tools for bounded recovery from Pi's raw append-only session history
14
15
  - `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
16
  - `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
17
  - `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
@@ -24,7 +25,19 @@ This package keeps shared Pi tools as ordinary source folders under `src/` and r
24
25
 
25
26
  `index.ts` is intentionally only a thin auto-discovery shim that re-exports `src/index.ts`. There is no `pi.extensions` manifest here, so local Pi auto-discovery loads the suite once via `~/.pi/agent/extensions/pi-tools-suite/index.ts` and does not double-register tools.
26
27
 
27
- Registration order is preserved in `src/index.ts`: coding-discipline, ast-grep, async-subagents, lsp, comment-checker, session-name, repo-discovery command/tool gate, antigravity-auth provider, OpenCode import, todo, model-tools, usage, web-search, dcp, prompt-commands, skill-installer, credential-firewall, then codex-reasoning-fix. Tool metadata and active model-specific tool sets have two modes: standard and repo-aware. When `.indexer-cli` enables `repo_*`, those tools stay active ahead of overlapping lower-level aliases so the indexed discovery surface has priority.
28
+ Registration order is preserved in `src/index.ts`: coding-discipline, ast-grep, async-subagents, lsp, comment-checker, session-name, session-recovery, repo-discovery command/tool gate, antigravity-auth provider, OpenCode import, todo, model-tools, usage, web-search, dcp, prompt-commands, skill-installer, credential-firewall, then codex-reasoning-fix. Tool metadata and active model-specific tool sets have two modes: standard and repo-aware. When `.indexer-cli` enables `repo_*`, those tools stay active ahead of overlapping lower-level aliases so the indexed discovery surface has priority.
29
+
30
+ ## Session recovery
31
+
32
+ When context compaction obscures the task, start with `session_overview`, inspect a
33
+ relevant ID with `session_read_section`, and use `session_search` once a concrete
34
+ phrase, path, symbol, tool, or error is known. `session_recovery_context` is the
35
+ compact convenience view for original/latest user instructions, file evidence,
36
+ recent errors, pending calls, and the last meaningful action. All four tools read
37
+ through Pi's active `SessionManager`; they do not accept arbitrary session paths.
38
+ They default to the active branch, while `scope: "all"` includes abandoned branches.
39
+ See [`docs/session-recovery.md`](docs/session-recovery.md) for the full contract and
40
+ limits.
28
41
 
29
42
  ## Disabling modules
30
43
 
@@ -247,8 +260,8 @@ For an oh-my-openagent-style workflow, run `/ultrawork` or `/ulw` to ask the par
247
260
 
248
261
  ### Private browser QA and project auth
249
262
 
250
- The built-in `browser-qa` role runs on `openai-codex/gpt-5.6-luna`, with
251
- `antigravity/gemini-3-flash-preview` and then `zai/glm-5.3` as fallbacks. Its browser
263
+ The built-in `browser-qa` role runs on `zai/glm-5.3-flash`, with
264
+ `openai-codex/gpt-5.6-luna` as its fallback. Its browser
252
265
  workflow is an explicit private skill under `src/async-subagents/private-skills/`,
253
266
  outside normal Pi skill discovery. The role's first-class `isolatedSkills` setting launches the child with
254
267
  `--no-skills` plus one self-contained private workflow. It bundles the relevant
@@ -316,9 +329,17 @@ parent to ask the user for an update and rerun. See
316
329
  for complete profile shapes and `references/qa-flow.example.jsonc` beside it for
317
330
  the declarative, non-executable QA action/assertion format.
318
331
 
332
+ Browser QA videos automatically visualize pointer interactions. Clicks and
333
+ double-clicks show a transient cursor and pulse. Native drag/drop is replayed
334
+ for 450 ms with a large orange cursor, progressively drawn high-contrast path,
335
+ and green drop marker. The isolated, pointer-transparent layer is installed for
336
+ the whole browser context, including same-origin frames, declared popups, and
337
+ form-auth submission, and clears during the normal post-action stable interval
338
+ so screenshots and assertions remain state-focused.
339
+
319
340
  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.
320
341
 
321
- For blind-model screenshot/image inspection, use the main-session `coding-discipline` lookup tool. Async-subagents still supports `imagePaths` on tasks when a broader delegated track genuinely needs images, but it no longer ships a dedicated `vision` role. Dynamic provider capabilities can be missing or stale after switching models, so blind parent models can still be configured explicitly with case-insensitive `*` masks under `asyncSubagents.vision.blindModelPatterns` in `~/.config/pi/pi-tools-suite.jsonc`; this keeps guidance honest, not a sub-agent role.
342
+ For blind-model screenshot/image inspection, use the main-session `coding-discipline` lookup tool; the bundled default uses vision-capable `zai/glm-5.3-flash`. Async-subagents still supports `imagePaths` on tasks when a broader delegated track genuinely needs images, but it no longer ships a dedicated `vision` role. Dynamic provider capabilities can be missing or stale after switching models, so blind parent models can still be configured explicitly with case-insensitive `*` masks under `asyncSubagents.vision.blindModelPatterns` in `~/.config/pi/pi-tools-suite.jsonc`; do not include `zai/glm-5.3-flash` because it accepts image input. This keeps guidance honest, not a sub-agent role.
322
343
 
323
344
  When a task omits `subagentType`, async-subagents asks a lightweight router model to choose one configured type for each task from the task text/scope and the `types.<name>.description` metadata. Explicit task `subagentType` still wins. Keep type descriptions short, literal, and distinct because they are inserted into the router prompt for a small model. Router settings live under `asyncSubagents.routing` (`enabled`, `model`, `maxTaskChars`, `maxTokens`, `maxRetries`, `timeoutMs`, `debug`); the default router model is `zai/glm-4.5-air`. If the router is disabled, unavailable, aborted, or returns invalid JSON, omitted types fall back to `defaultType`.
324
345
 
@@ -337,10 +358,11 @@ Example shared async-subagents config section:
337
358
  },
338
359
  "presets": {
339
360
  "cheap": {
340
- "description": "Use GLM for text/code roles.",
361
+ "description": "Use GLM by role, including GLM-5.3 Flash for multimodal work.",
341
362
  "types": {
342
363
  "quick": { "model": "zai/glm-5.3", "thinking": "off" },
343
- "frontend": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["zai/glm-5.3"], "thinking": "medium" },
364
+ "frontend": { "model": "zai/glm-5.3-flash", "thinking": "medium" },
365
+ "browser-qa": { "model": "zai/glm-5.3-flash", "fallbackModels": ["openai-codex/gpt-5.6-luna"], "thinking": "low" },
344
366
  "review": { "model": "zai/glm-5.3", "thinking": "high" }
345
367
  }
346
368
  }
@@ -4,8 +4,8 @@
4
4
 
5
5
  Provide a cheap, fast `browser-qa` async-subagent that reproduces browser bugs
6
6
  and proves fixes with deterministic assertions plus screenshot, video, and trace
7
- evidence. The role uses `openai-codex/gpt-5.6-luna`, falling back to
8
- `antigravity/gemini-3-flash-preview` and then `zai/glm-5.3`.
7
+ evidence. The role uses `zai/glm-5.3-flash`, falling back to
8
+ `openai-codex/gpt-5.6-luna`.
9
9
 
10
10
  ## Private skill isolation
11
11
 
@@ -73,6 +73,20 @@ evidence. The role uses `openai-codex/gpt-5.6-luna`, falling back to
73
73
  screenshots, video finalization, and redacted result output. Before retaining
74
74
  a trace it removes network/non-image resource entries, redacts configured and
75
75
  runtime storage credentials, and verifies those values are absent.
76
+ - After every navigation or visible interaction, the runner waits for DOM
77
+ readiness, completion of requests started by the action, and disappearance of
78
+ common visible busy/spinner/skeleton markers. It requires a 500 ms stable
79
+ interval before the next action so recordings remain readable; a page that
80
+ stays busy through the flow timeout fails closed instead of being tested as a
81
+ loading shell. App-specific readiness still requires an explicit declarative
82
+ wait/assertion in the authored flow.
83
+ - Before any page is created, the runner installs a context-wide, isolated
84
+ interaction visualizer. Recorded clicks/double-clicks show a transient cursor
85
+ and pulse. Native drag/drop is replayed for 450 ms with a large orange cursor,
86
+ progressively drawn high-contrast path, and green drop marker. It also covers
87
+ same-origin frames, declared popups, and form-auth submission. The layer is
88
+ accessibility-hidden, pointer-transparent, never cancels application events,
89
+ and its bounded animations clear within the post-action stable interval.
76
90
  - Success and post-launch failure results include typed artifact groups. Every
77
91
  item has an absolute filesystem path and a `file:` URI; the sub-agent must
78
92
  present each item as a clickable Markdown link instead of reporting only the
@@ -112,7 +126,7 @@ evidence. The role uses `openai-codex/gpt-5.6-luna`, falling back to
112
126
 
113
127
  1. `browser-qa` resolves to the intended model/fallback and its self-contained
114
128
  private workflow, and its isolated child process can register the configured
115
- Antigravity model.
129
+ model provider.
116
130
  2. Spawn args contain `--no-skills` and the mandatory private skill for this
117
131
  profile; ordinary profiles retain existing skill discovery behavior.
118
132
  3. Auth profile listing and all error output are redacted; model-authored input
@@ -123,12 +137,16 @@ evidence. The role uses `openai-codex/gpt-5.6-luna`, falling back to
123
137
  flows, and successful redacted evidence creation.
124
138
  5. Browser QA flows/evidence live only inside the owning sub-agent directory;
125
139
  deleting the run removes them while persistent auth config/state remains.
126
- 6. Completed test runs report clickable screenshot, video, and trace links
140
+ 6. Runner tests prove that network activity and visible loading indicators are
141
+ awaited, persistent loading fails the flow, visible actions retain a stable
142
+ 500 ms video interval, and context-wide click/drag video visualization is
143
+ installed with bounded click pacing.
144
+ 7. Completed test runs report clickable screenshot, video, and trace links
127
145
  whenever those artifacts exist.
128
- 7. Timeout tests identify the last browser stage, launcher progress remains
146
+ 8. Timeout tests identify the last browser stage, launcher progress remains
129
147
  available when full RPC logging is disabled, and process-tree tests prove a
130
148
  descendant is terminated without signalling unrelated processes.
131
- 8. Suite tests/typecheck, host checks, and suite sync pass.
149
+ 9. Suite tests/typecheck, host checks, and suite sync pass.
132
150
 
133
151
  ## Real-browser regression test
134
152
 
@@ -0,0 +1,84 @@
1
+ # Session recovery tools
2
+
3
+ Status: implemented MVP contract (semantic search is intentionally deferred).
4
+
5
+ ## Goal
6
+
7
+ Let an agent recover the task, recent instructions, file activity, and useful raw
8
+ session history after context compaction. Recovery reads the session already
9
+ owned by Pi's `SessionManager`; it never opens an arbitrary session path.
10
+
11
+ ## Scope
12
+
13
+ The `session-recovery` module registers four read-only, headless tools:
14
+
15
+ - `session_overview` maps a session into stable, bounded sections.
16
+ - `session_read_section` renders one section by ID.
17
+ - `session_search` performs bounded lexical search over raw entries.
18
+ - `session_recovery_context` summarizes deterministic recovery signals.
19
+
20
+ Every tool defaults to the active root-to-leaf branch. `scope: "all"` includes
21
+ abandoned branches through `SessionManager.getEntries()`. Both modes use raw
22
+ append-only entries, not `buildContextEntries()`, so content hidden from the
23
+ active model context by compaction remains discoverable.
24
+
25
+ ## Contracts
26
+
27
+ ### Sections
28
+
29
+ A section starts at the first selected entry, a user message, a compaction, or a
30
+ branch summary. Its stable ID is derived from the start entry ID. The overview
31
+ reports bounded head and tail sections with entry ranges, counts, and compact
32
+ role/tool/error/file statistics. Labels are previews, not inferred decisions.
33
+
34
+ ### Reading and search
35
+
36
+ `session_read_section` requires a section ID produced for the same scope. It
37
+ renders message roles and text, tool calls and arguments, tool results,
38
+ compaction summaries, and branch summaries with per-entry and total output
39
+ limits.
40
+
41
+ `session_search` is case-insensitive by default and searches message text, tool
42
+ arguments/results, custom-message content, and compaction or branch summaries.
43
+ It returns entry and section IDs plus bounded snippets. Regex and semantic
44
+ search are out of scope for the MVP.
45
+
46
+ ### Recovery context
47
+
48
+ `session_recovery_context` reports only evidence that can be derived
49
+ deterministically: the original and latest user messages, recent tool errors,
50
+ unmatched tool calls, read and modified files, the last meaningful action, and
51
+ compaction count. It calls errors `recentErrors`; it does not claim that they
52
+ remain unresolved. It does not infer decisions. The currently executing
53
+ recovery tool call is excluded from unmatched-call reporting.
54
+
55
+ File activity comes from recognized tool calls and Pi-generated compaction or
56
+ branch-summary details. Read and modified paths remain separate, and unknown
57
+ tools are not guessed to be mutations.
58
+
59
+ ## Limits and edge cases
60
+
61
+ - Results use small defaults and hard caps for result count, entry body size,
62
+ and total text size.
63
+ - Empty or in-memory sessions return a normal explanatory result.
64
+ - Unknown or partially shaped entries are ignored or rendered conservatively.
65
+ - Concurrent sibling tool results might not yet be visible when recovery runs.
66
+ - A section ID is stable while its start entry ID is stable, but scope changes
67
+ can change section membership.
68
+ - Parent-session metadata is reported when Pi exposes it; parent files are not
69
+ traversed.
70
+
71
+ ## Verification
72
+
73
+ Deterministic tests cover active versus all branches, raw pre-compaction search,
74
+ stable section IDs, Unicode case-insensitive search, bounded output, empty
75
+ sessions, current-call exclusion, recent errors, file carry-forward details,
76
+ and conservative handling of unknown entries. Release verification runs the
77
+ suite typecheck/tests/smoke gate, host `npm run check`, and then syncs the suite
78
+ with `npm run sync:pi-tools-suite`.
79
+
80
+ ## Evidence
81
+
82
+ Evidence is recorded by the implementation tests in
83
+ `test/session-recovery.test.ts` and the verification commands reported with the
84
+ change.