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.
- package/README.md +21 -0
- package/dist/app/app.js +0 -1
- package/dist/app/commands/command-host.d.ts +1 -2
- package/dist/app/commands/command-model-actions.js +5 -5
- package/dist/app/constants.d.ts +0 -1
- package/dist/app/constants.js +0 -34
- package/dist/app/popup/menu-items-controller.d.ts +1 -3
- package/dist/app/popup/menu-items-controller.js +10 -32
- package/dist/app/rendering/popup-menu-renderer.js +1 -1
- package/dist/app/runtime.js +1 -3
- package/dist/app/screen/file-link-opener.js +15 -49
- package/external/pi-tools-suite/README.md +29 -7
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +24 -6
- package/external/pi-tools-suite/docs/session-recovery.md +84 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +13 -9
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +23 -4
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +40 -4
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +19 -0
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +444 -13
- package/external/pi-tools-suite/src/coding-discipline/index.ts +12 -2
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +6 -6
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +674 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +42 -0
- package/package.json +4 -2
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/package_skill.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
- 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,
|
|
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
|
-
: " -
|
|
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 = [" -
|
|
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
|
|
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(
|
|
212
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: "
|
|
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
|
}
|
package/dist/app/constants.d.ts
CHANGED
|
@@ -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;
|
package/dist/app/constants.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
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
|
|
196
|
-
|
|
197
|
-
if (!scopedModels.length)
|
|
170
|
+
const runtime = this.host.runtime();
|
|
171
|
+
if (!runtime)
|
|
198
172
|
return [];
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
104
|
+
text: this.host.session ? " No matching available models" : " Model menu unavailable",
|
|
105
105
|
variant: "muted",
|
|
106
106
|
});
|
|
107
107
|
}
|
package/dist/app/runtime.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
43
|
-
|
|
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
|
|
67
|
-
const
|
|
68
|
-
|
|
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 `
|
|
251
|
-
`
|
|
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`;
|
|
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
|
|
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": "
|
|
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 `
|
|
8
|
-
`
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|