pi-ui-extend 1.0.39 → 1.0.41
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 +1 -1
- package/dist/app/commands/command-registry.js +2 -2
- package/dist/app/commands/command-session-actions.d.ts +0 -1
- package/dist/app/commands/command-session-actions.js +22 -13
- package/dist/app/icons.d.ts +14 -0
- package/dist/app/icons.js +33 -0
- package/dist/app/rendering/conversation-tool-renderer.js +2 -2
- package/dist/app/rendering/dcp-stats.d.ts +6 -1
- package/dist/app/rendering/dcp-stats.js +214 -46
- package/dist/app/rendering/editor-panels.js +8 -5
- package/dist/app/session/lazy-session-manager.js +12 -1
- package/dist/app/session/tabs-controller.d.ts +2 -5
- package/dist/app/session/tabs-controller.js +12 -21
- package/dist/app/subagents/subagents-model.d.ts +14 -1
- package/dist/app/subagents/subagents-model.js +34 -15
- package/dist/app/types.d.ts +2 -0
- package/dist/bundled-extensions/session-title/config.js +1 -1
- package/dist/markdown-format.js +27 -9
- package/dist/schemas/pi-tools-suite-schema.d.ts +29 -16
- package/dist/schemas/pi-tools-suite-schema.js +46 -31
- package/external/pi-tools-suite/README.md +392 -52
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +31 -21
- package/external/pi-tools-suite/docs/context-gateway-p00-adr.md +216 -0
- package/external/pi-tools-suite/docs/context-gateway-p01n-gate-review.md +122 -0
- package/external/pi-tools-suite/docs/context-gateway-p01n-measurement.md +133 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-ra-evidence.md +111 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rb-evidence.md +100 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rc-evidence.md +69 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rd-evidence.md +100 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-re-evidence.md +74 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rf-evidence.md +153 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rg-evidence.md +235 -0
- package/external/pi-tools-suite/docs/evals.md +684 -0
- package/external/pi-tools-suite/docs/subagent-model-pools.md +109 -0
- package/external/pi-tools-suite/package.json +10 -3
- package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/scripts/browser-qa-runner.mjs +82 -1
- package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/SKILL.md → agents/browser-qa.md} +261 -12
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +20 -0
- package/external/pi-tools-suite/src/async-subagents/agents/oracle.md +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/agents/verify.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +27 -243
- package/external/pi-tools-suite/src/async-subagents/commands.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +41 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +13 -93
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +494 -0
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +9 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +200 -143
- package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +54 -0
- package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +7 -6
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +52 -45
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +12 -4
- package/external/pi-tools-suite/src/async-subagents/index.ts +11 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +46 -18
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +2 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +41 -142
- package/external/pi-tools-suite/src/config.ts +1 -22
- package/external/pi-tools-suite/src/context-gateway/accounting.ts +151 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +111 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +160 -0
- package/external/pi-tools-suite/src/context-gateway/metadata-normalization.ts +88 -0
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +89 -0
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +429 -0
- package/external/pi-tools-suite/src/context-gateway/test-output-parser.ts +326 -0
- package/external/pi-tools-suite/src/context-gateway/types.ts +152 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +106 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +810 -106
- package/external/pi-tools-suite/src/dcp/commands.ts +64 -139
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +369 -35
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +510 -64
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +113 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +70 -0
- package/external/pi-tools-suite/src/dcp/config.ts +36 -61
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +421 -0
- package/external/pi-tools-suite/src/dcp/debug-log.ts +7 -5
- package/external/pi-tools-suite/src/dcp/index.ts +617 -203
- package/external/pi-tools-suite/src/dcp/journal.ts +566 -0
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +244 -0
- package/external/pi-tools-suite/src/dcp/prompts.ts +10 -7
- package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +189 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +298 -78
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +173 -281
- package/external/pi-tools-suite/src/dcp/pruner-emergency.ts +2 -4
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +17 -5
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +11 -1
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +30 -82
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +22 -133
- package/external/pi-tools-suite/src/dcp/pruner.ts +18 -33
- package/external/pi-tools-suite/src/dcp/recovery.ts +129 -0
- package/external/pi-tools-suite/src/dcp/shadow-plan.ts +127 -0
- package/external/pi-tools-suite/src/dcp/state-transaction.ts +102 -0
- package/external/pi-tools-suite/src/dcp/state.ts +158 -580
- package/external/pi-tools-suite/src/dcp/ui.ts +1 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +55 -220
- package/external/pi-tools-suite/src/index.ts +9 -0
- package/external/pi-tools-suite/src/model-tools/index.ts +76 -42
- package/external/pi-tools-suite/src/repo-discovery/index.ts +84 -18
- package/external/pi-tools-suite/src/repo-discovery/native-compact.ts +458 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +189 -43
- package/external/pi-tools-suite/src/tool-descriptions.ts +43 -38
- package/external/pi-tools-suite/src/truncation-metadata-normalizer/index.ts +17 -0
- package/package.json +6 -6
- package/schemas/pi-tools-suite.json +159 -78
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/auth-scaffold-spec.md +0 -78
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +0 -223
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +0 -195
- /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.mjs +0 -0
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ Optional:
|
|
|
140
140
|
- an audio recorder for dictation: SoX (`rec`/`sox`), `ffmpeg`, or `arecord` on Linux
|
|
141
141
|
- `tmux` and `rsvg-convert` only if you want to regenerate README screenshots
|
|
142
142
|
- Ollama with web search enabled for the bundled `web_search` and `web_fetch` tools
|
|
143
|
-
- language servers for the LSP module
|
|
143
|
+
- language servers for the LSP module (see [LSP setup](external/pi-tools-suite/README.md#lsp-setup) for install commands and ready-to-paste configs for popular servers, including Svelte, Vue, Go, clangd, Lua, and bash)
|
|
144
144
|
|
|
145
145
|
### One-shot use
|
|
146
146
|
|
|
@@ -249,9 +249,9 @@ export function createSlashCommands(actions, host) {
|
|
|
249
249
|
},
|
|
250
250
|
{
|
|
251
251
|
name: "delete",
|
|
252
|
-
description: "Delete the current (or specified) session file
|
|
252
|
+
description: "Delete the current (or specified) session file",
|
|
253
253
|
kind: "builtin",
|
|
254
|
-
keywords: ["remove", "destroy", "purge", "session", "
|
|
254
|
+
keywords: ["remove", "destroy", "purge", "session", "dcp"],
|
|
255
255
|
allowArguments: true,
|
|
256
256
|
run: (argumentsText) => actions.runDeleteCommand(argumentsText),
|
|
257
257
|
},
|
|
@@ -15,7 +15,6 @@ export declare class SessionCommandActions {
|
|
|
15
15
|
runHotkeysCommand(): Promise<void>;
|
|
16
16
|
runReloadCommand(): Promise<void>;
|
|
17
17
|
runDeleteCommand(argumentsText: string): Promise<void>;
|
|
18
|
-
private removeDcpSidecarState;
|
|
19
18
|
runNewSessionCommand(): Promise<void>;
|
|
20
19
|
runCompactCommand(customInstructions?: string): Promise<void>;
|
|
21
20
|
private piPackageRoot;
|
|
@@ -342,7 +342,7 @@ export class SessionCommandActions {
|
|
|
342
342
|
{
|
|
343
343
|
value: true,
|
|
344
344
|
label: `Yes, delete ${isCurrent ? "the current session" : basename(targetSessionFile)}`,
|
|
345
|
-
description: "This permanently removes the session file
|
|
345
|
+
description: "This permanently removes the session file. This cannot be undone.",
|
|
346
346
|
variant: "error",
|
|
347
347
|
},
|
|
348
348
|
{ value: false, label: "Cancel" },
|
|
@@ -356,11 +356,28 @@ export class SessionCommandActions {
|
|
|
356
356
|
}
|
|
357
357
|
this.host.setStatus("deleting session");
|
|
358
358
|
this.host.render();
|
|
359
|
-
const sidecarRemoved = await this.removeDcpSidecarState(dirname(targetSessionFile), targetSessionId).catch(() => false);
|
|
360
|
-
await rm(targetSessionFile, { force: true }).catch(() => undefined);
|
|
361
359
|
const deleteCurrent = isCurrent || targetSessionFile === currentSessionFile;
|
|
362
360
|
if (deleteCurrent) {
|
|
361
|
+
// Do not race a final extension persistence operation with destructive
|
|
362
|
+
// session cleanup.
|
|
363
363
|
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
364
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
await rm(targetSessionFile, { force: true });
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
this.host.addEntry({
|
|
372
|
+
id: createId("system"),
|
|
373
|
+
kind: "system",
|
|
374
|
+
text: `Could not delete session file ${targetSessionFile}: ${error instanceof Error ? error.message : String(error)}`,
|
|
375
|
+
});
|
|
376
|
+
this.host.toast.error("Session deletion failed");
|
|
377
|
+
this.host.setSessionStatus(runtime.session);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (deleteCurrent) {
|
|
364
381
|
const result = await runtime.newSession();
|
|
365
382
|
if (!isCommandRuntimeActive(this.host, runtime))
|
|
366
383
|
return;
|
|
@@ -374,7 +391,7 @@ export class SessionCommandActions {
|
|
|
374
391
|
this.host.addEntry({
|
|
375
392
|
id: createId("system"),
|
|
376
393
|
kind: "system",
|
|
377
|
-
text: `Deleted session ${targetSessionId}.
|
|
394
|
+
text: `Deleted session ${targetSessionId}. Started a new session. cwd=${runtime.cwd}`,
|
|
378
395
|
});
|
|
379
396
|
if (runtime.modelFallbackMessage)
|
|
380
397
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
@@ -386,20 +403,12 @@ export class SessionCommandActions {
|
|
|
386
403
|
this.host.addEntry({
|
|
387
404
|
id: createId("system"),
|
|
388
405
|
kind: "system",
|
|
389
|
-
text: `Deleted session file ${targetSessionFile}
|
|
406
|
+
text: `Deleted session file ${targetSessionFile}.`,
|
|
390
407
|
});
|
|
391
408
|
this.host.setSessionStatus(runtime.session);
|
|
392
409
|
}
|
|
393
410
|
this.host.toast.success("Session deleted");
|
|
394
411
|
}
|
|
395
|
-
async removeDcpSidecarState(sessionDir, sessionId) {
|
|
396
|
-
if (!sessionId)
|
|
397
|
-
return false;
|
|
398
|
-
const safeName = `${sessionId.replace(/[^a-zA-Z0-9._-]/g, "_")}.json`;
|
|
399
|
-
const statePath = join(sessionDir, "dcp-state", safeName);
|
|
400
|
-
await rm(statePath, { force: true });
|
|
401
|
-
return true;
|
|
402
|
-
}
|
|
403
412
|
async runNewSessionCommand() {
|
|
404
413
|
const runtime = getIdleRuntime(this.host, "new");
|
|
405
414
|
if (!runtime)
|
package/dist/app/icons.d.ts
CHANGED
|
@@ -33,6 +33,20 @@ declare const NERD_FONT_ICONS: {
|
|
|
33
33
|
readonly toolPreviewTruncated: "⊞";
|
|
34
34
|
readonly up: "↑";
|
|
35
35
|
readonly down: "↓";
|
|
36
|
+
readonly agent: "";
|
|
37
|
+
readonly search: "";
|
|
38
|
+
readonly code: "";
|
|
39
|
+
readonly flask: "";
|
|
40
|
+
readonly globe: "";
|
|
41
|
+
readonly sparkles: "";
|
|
42
|
+
readonly brain: "";
|
|
43
|
+
readonly wrench: "";
|
|
44
|
+
readonly terminal: "";
|
|
45
|
+
readonly bug: "";
|
|
46
|
+
readonly book: "";
|
|
47
|
+
readonly eye: "";
|
|
48
|
+
readonly zap: "";
|
|
49
|
+
readonly rocket: "";
|
|
36
50
|
};
|
|
37
51
|
export type AppIconName = keyof typeof NERD_FONT_ICONS;
|
|
38
52
|
export type AppIconMap = Record<AppIconName, string>;
|
package/dist/app/icons.js
CHANGED
|
@@ -44,6 +44,22 @@ const NERD_FONT_ICONS = {
|
|
|
44
44
|
toolPreviewTruncated: "⊞",
|
|
45
45
|
up: "↑",
|
|
46
46
|
down: "↓",
|
|
47
|
+
// Sub-agent icons (agent .md frontmatter `icon:` names). Values are shared
|
|
48
|
+
// with the Pix Desktop lucide mapping in desktop/src/lib/agent-icons.ts.
|
|
49
|
+
agent: "\u{f06a9}", // nf-md-robot
|
|
50
|
+
search: "\u{f0349}", // nf-md-magnify
|
|
51
|
+
code: "\u{f0174}", // nf-md-code_tags
|
|
52
|
+
flask: "\u{f0093}", // nf-md-flask
|
|
53
|
+
globe: "\u{f01e7}", // nf-md-earth
|
|
54
|
+
sparkles: "\u{f0ae2}", // nf-md-star_four_points
|
|
55
|
+
brain: "\u{f09d1}", // nf-md-brain
|
|
56
|
+
wrench: "\u{f05b7}", // nf-md-wrench
|
|
57
|
+
terminal: "\u{f018d}", // nf-md-console
|
|
58
|
+
bug: "\u{f00e4}", // nf-md-bug
|
|
59
|
+
book: "\u{f00ba}", // nf-md-book
|
|
60
|
+
eye: "\u{f0208}", // nf-md-eye
|
|
61
|
+
zap: "\u{f0241}", // nf-md-flash
|
|
62
|
+
rocket: "\u{f0463}", // nf-md-rocket
|
|
47
63
|
};
|
|
48
64
|
const FALLBACK_ICONS = {
|
|
49
65
|
alert: "⚠",
|
|
@@ -78,6 +94,23 @@ const FALLBACK_ICONS = {
|
|
|
78
94
|
toolPreviewTruncated: "+",
|
|
79
95
|
up: "↑",
|
|
80
96
|
down: "↓",
|
|
97
|
+
// Sub-agent icon fallbacks (no icon-font codepoints; readable when the
|
|
98
|
+
// terminal font lacks Nerd Font glyphs). Emoji follow the existing fallback
|
|
99
|
+
// precedent (lightbulb, timerSand).
|
|
100
|
+
agent: "◇",
|
|
101
|
+
search: "⌕",
|
|
102
|
+
code: "{}",
|
|
103
|
+
flask: "∆",
|
|
104
|
+
globe: "⊕",
|
|
105
|
+
sparkles: "✦",
|
|
106
|
+
brain: "∞",
|
|
107
|
+
wrench: "⚙",
|
|
108
|
+
terminal: ">_",
|
|
109
|
+
bug: "🐛",
|
|
110
|
+
book: "▤",
|
|
111
|
+
eye: "◉",
|
|
112
|
+
zap: "↯",
|
|
113
|
+
rocket: "🚀",
|
|
81
114
|
};
|
|
82
115
|
export const APP_ICON_THEMES = {
|
|
83
116
|
nerdFont: NERD_FONT_ICONS,
|
|
@@ -4,7 +4,7 @@ import { renderToolDisplay } from "../../tool-renderers/index.js";
|
|
|
4
4
|
import { SUBAGENT_STATUSES, THINKING_TOOL_NAME, TODO_TOOL_NAME } from "../constants.js";
|
|
5
5
|
import { attachImageClickTargets } from "../screen/image-click-targets.js";
|
|
6
6
|
import { formatStructuredText } from "./message-content.js";
|
|
7
|
-
import { formatSubagentTimestamp, isSubagentRunRenderDetails, isSubagentsToolName,
|
|
7
|
+
import { formatSubagentTimestamp, isSubagentRunRenderDetails, isSubagentsToolName, subagentIcon, subagentRunName, taskPreviewMap, } from "../subagents/subagents-model.js";
|
|
8
8
|
import { formatTodoTaskLine, isTodoDetails, visibleTodoTasks } from "../todo/todo-model.js";
|
|
9
9
|
import { renderToolBlock } from "./tool-block-renderer.js";
|
|
10
10
|
export function renderConversationToolEntry(entry, width, options) {
|
|
@@ -202,7 +202,7 @@ function subagentsDetailsText(details) {
|
|
|
202
202
|
return lines.join("\n");
|
|
203
203
|
}
|
|
204
204
|
function formatSubagentToolLine(agent, preview) {
|
|
205
|
-
const parts = [
|
|
205
|
+
const parts = [subagentIcon(preview), agent.status, agent.id];
|
|
206
206
|
if (preview?.model)
|
|
207
207
|
parts.push(`model:${preview.model}`);
|
|
208
208
|
if (preview?.task)
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
2
|
+
type FormatDcpStatsOptions = {
|
|
3
|
+
manualModeBaseline?: boolean;
|
|
4
|
+
configPath?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function formatDcpStatsToast(session: AgentSession, options?: FormatDcpStatsOptions): string;
|
|
7
|
+
export {};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
4
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
3
5
|
import { normalizeToolName, parseArgsText } from "../../tool-renderers/utils.js";
|
|
4
6
|
const NUDGE_TYPES = ["turn", "iteration", "context-soft", "context-strong"];
|
|
5
|
-
export function formatDcpStatsToast(session) {
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
export function formatDcpStatsToast(session, options = {}) {
|
|
8
|
+
const branch = dcpStatsBranch(session);
|
|
9
|
+
const latestState = resolveJournalDcpState(branch);
|
|
10
|
+
const manualModeBaseline = options.manualModeBaseline ?? readDcpManualModeBaseline(session, options.configPath);
|
|
11
|
+
const stats = collectDcpSessionStats(session, latestState, branch, manualModeBaseline);
|
|
12
|
+
const nudgeStats = collectDcpNudgeStats(branch, latestState?.data);
|
|
9
13
|
const activeBlocks = stats.activeBlocks ?? 0;
|
|
10
14
|
const totalBlocks = stats.totalBlocks ?? stats.activeBlocks ?? 0;
|
|
11
|
-
const totalNudgeEvents = nudgeStats.emitted + nudgeStats.upgraded;
|
|
15
|
+
const totalNudgeEvents = nudgeStats.emitted + nudgeStats.reapplied + nudgeStats.upgraded;
|
|
12
16
|
const activeAnchors = NUDGE_TYPES.reduce((sum, type) => sum + nudgeStats.activeByType[type], 0);
|
|
13
17
|
const lines = [
|
|
14
18
|
"DCP Session Statistics:",
|
|
@@ -19,7 +23,7 @@ export function formatDcpStatsToast(session) {
|
|
|
19
23
|
` State source: ${formatStateSource(stats.stateSource)}`,
|
|
20
24
|
"",
|
|
21
25
|
"Nudge telemetry:",
|
|
22
|
-
` Sent: ${fmt(nudgeStats.emitted)} emitted, ${fmt(nudgeStats.upgraded)} upgraded`,
|
|
26
|
+
` Sent: ${fmt(nudgeStats.emitted)} emitted, ${fmt(nudgeStats.reapplied)} reapplied, ${fmt(nudgeStats.upgraded)} upgraded`,
|
|
23
27
|
` By type: ${NUDGE_TYPES.map((type) => `${type}=${fmt(nudgeStats.byType[type])}`).join(", ")}`,
|
|
24
28
|
` Active anchors: ${fmt(activeAnchors)}${activeAnchors > 0 ? ` (${NUDGE_TYPES.map((type) => `${type}=${fmt(nudgeStats.activeByType[type])}`).join(", ")})` : ""}`,
|
|
25
29
|
` Cleared after compress: ${fmt(nudgeStats.clearedEvents)} time${nudgeStats.clearedEvents === 1 ? "" : "s"} (${fmt(nudgeStats.clearedAnchors)} anchor${nudgeStats.clearedAnchors === 1 ? "" : "s"})`,
|
|
@@ -32,7 +36,7 @@ export function formatDcpStatsToast(session) {
|
|
|
32
36
|
];
|
|
33
37
|
return lines.join("\n");
|
|
34
38
|
}
|
|
35
|
-
function collectDcpSessionStats(session, latestState) {
|
|
39
|
+
function collectDcpSessionStats(session, latestState, branch, manualModeBaseline) {
|
|
36
40
|
const usage = session.getContextUsage();
|
|
37
41
|
const stats = {
|
|
38
42
|
runs: 0,
|
|
@@ -42,18 +46,16 @@ function collectDcpSessionStats(session, latestState) {
|
|
|
42
46
|
summaryTokens: 0,
|
|
43
47
|
prunedTools: 0,
|
|
44
48
|
stateSource: latestState?.source ?? "tool-results",
|
|
49
|
+
...(latestState ? { manualMode: manualModeBaseline } : {}),
|
|
45
50
|
...(usage?.tokens != null ? { contextTokens: usage.tokens } : {}),
|
|
46
51
|
...(usage?.contextWindow != null ? { contextWindow: usage.contextWindow } : {}),
|
|
47
52
|
...(usage?.percent != null ? { contextPercent: usage.percent } : {}),
|
|
48
53
|
};
|
|
49
|
-
const branch = session.sessionManager.getBranch();
|
|
50
54
|
if (latestState)
|
|
51
55
|
applyDcpStateStats(stats, latestState.data);
|
|
52
56
|
for (const entry of branch) {
|
|
53
57
|
if (entry.type !== "message")
|
|
54
58
|
continue;
|
|
55
|
-
if (latestState)
|
|
56
|
-
continue;
|
|
57
59
|
const message = entry.message;
|
|
58
60
|
if (message.role !== "toolResult")
|
|
59
61
|
continue;
|
|
@@ -64,18 +66,24 @@ function collectDcpSessionStats(session, latestState) {
|
|
|
64
66
|
const result = parseToolResultText(message.content);
|
|
65
67
|
if (!result)
|
|
66
68
|
continue;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
if (result.idempotentReplay !== true) {
|
|
70
|
+
stats.runs += 1;
|
|
71
|
+
stats.tokensSaved += numberValue(result.tokensSaved) ?? 0;
|
|
72
|
+
stats.items += numberValue(result.itemCount) ?? sumDefined(numberValue(result.ranges), numberValue(result.messages)) ?? 0;
|
|
73
|
+
stats.summaryTokens += numberValue(result.totalSummaryTokens) ?? 0;
|
|
74
|
+
}
|
|
75
|
+
if (!latestState) {
|
|
76
|
+
stats.totalPruneCount = Math.max(stats.totalPruneCount, numberValue(result.totalPruneCount) ?? 0);
|
|
77
|
+
const prunedTools = numberValue(result.prunedTools);
|
|
78
|
+
if (prunedTools != null)
|
|
79
|
+
stats.prunedTools = prunedTools;
|
|
80
|
+
const activeBlocks = numberValue(result.activeBlocks);
|
|
81
|
+
const totalBlocks = numberValue(result.totalBlocks);
|
|
82
|
+
if (activeBlocks != null)
|
|
83
|
+
stats.activeBlocks = activeBlocks;
|
|
84
|
+
if (totalBlocks != null)
|
|
85
|
+
stats.totalBlocks = totalBlocks;
|
|
86
|
+
}
|
|
79
87
|
const contextTokens = numberValue(result.contextTokens);
|
|
80
88
|
const contextWindow = numberValue(result.contextWindow);
|
|
81
89
|
const contextPercent = numberValue(result.contextPercent);
|
|
@@ -86,10 +94,17 @@ function collectDcpSessionStats(session, latestState) {
|
|
|
86
94
|
if (stats.contextPercent == null && contextPercent != null)
|
|
87
95
|
stats.contextPercent = contextPercent;
|
|
88
96
|
}
|
|
97
|
+
if (latestState) {
|
|
98
|
+
// Journal state owns durable block/prune counts; successful compress tool
|
|
99
|
+
// results own operation gain because the journal intentionally stores only
|
|
100
|
+
// projection decisions rather than derived accounting snapshots.
|
|
101
|
+
stats.totalPruneCount = Math.max(stats.totalPruneCount, (stats.totalBlocks ?? 0) + stats.prunedTools);
|
|
102
|
+
}
|
|
89
103
|
return stats;
|
|
90
104
|
}
|
|
91
105
|
function applyDcpStateStats(stats, data) {
|
|
92
106
|
stats.tokensSaved = numberValue(data.tokensSaved) ?? stats.tokensSaved;
|
|
107
|
+
stats.tokensSaved += numberValue(data.prunedTokensSaved) ?? 0;
|
|
93
108
|
stats.totalPruneCount = numberValue(data.totalPruneCount) ?? stats.totalPruneCount;
|
|
94
109
|
const blocks = Array.isArray(data.compressionBlocks) ? data.compressionBlocks : undefined;
|
|
95
110
|
if (blocks) {
|
|
@@ -101,16 +116,16 @@ function applyDcpStateStats(stats, data) {
|
|
|
101
116
|
if (typeof data.manualMode === "boolean")
|
|
102
117
|
stats.manualMode = data.manualMode;
|
|
103
118
|
}
|
|
104
|
-
function collectDcpNudgeStats(
|
|
119
|
+
function collectDcpNudgeStats(branch, latestState) {
|
|
105
120
|
const stats = {
|
|
106
121
|
emitted: 0,
|
|
122
|
+
reapplied: 0,
|
|
107
123
|
upgraded: 0,
|
|
108
124
|
clearedEvents: 0,
|
|
109
125
|
clearedAnchors: 0,
|
|
110
126
|
byType: { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 },
|
|
111
127
|
activeByType: { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 },
|
|
112
128
|
};
|
|
113
|
-
const branch = session.sessionManager.getBranch();
|
|
114
129
|
if (latestState)
|
|
115
130
|
applyActiveAnchorStats(stats, latestState);
|
|
116
131
|
for (const entry of branch) {
|
|
@@ -118,9 +133,11 @@ function collectDcpNudgeStats(session, latestState) {
|
|
|
118
133
|
if (!data)
|
|
119
134
|
continue;
|
|
120
135
|
const event = data.event;
|
|
121
|
-
if ((event === "emitted" || event === "upgraded") && isNudgeType(data.type)) {
|
|
136
|
+
if ((event === "emitted" || event === "reapplied" || event === "upgraded") && isNudgeType(data.type)) {
|
|
122
137
|
if (event === "emitted")
|
|
123
138
|
stats.emitted += 1;
|
|
139
|
+
else if (event === "reapplied")
|
|
140
|
+
stats.reapplied += 1;
|
|
124
141
|
else
|
|
125
142
|
stats.upgraded += 1;
|
|
126
143
|
stats.byType[data.type] += 1;
|
|
@@ -142,6 +159,95 @@ function collectDcpNudgeStats(session, latestState) {
|
|
|
142
159
|
}
|
|
143
160
|
return stats;
|
|
144
161
|
}
|
|
162
|
+
function readDcpManualModeBaseline(session, configPath = join(homedir(), ".config", "pi", "pi-tools-suite.jsonc")) {
|
|
163
|
+
let enabled = false;
|
|
164
|
+
if (!existsSync(configPath))
|
|
165
|
+
return enabled;
|
|
166
|
+
try {
|
|
167
|
+
const parsed = parseJsonc(readFileSync(configPath, "utf8"));
|
|
168
|
+
if (!isRecord(parsed))
|
|
169
|
+
return enabled;
|
|
170
|
+
const dcp = parsed.dcp;
|
|
171
|
+
if (!isRecord(dcp))
|
|
172
|
+
return enabled;
|
|
173
|
+
const manualMode = dcp.manualMode;
|
|
174
|
+
if (isRecord(manualMode) && typeof manualMode.enabled === "boolean")
|
|
175
|
+
enabled = manualMode.enabled;
|
|
176
|
+
const overrides = dcp.modelOverrides;
|
|
177
|
+
if (!isRecord(overrides))
|
|
178
|
+
return enabled;
|
|
179
|
+
for (const override of matchingModelOverrides(overrides, dcpModelKeys(session))) {
|
|
180
|
+
const overrideManualMode = override.manualMode;
|
|
181
|
+
if (isRecord(overrideManualMode) && typeof overrideManualMode.enabled === "boolean") {
|
|
182
|
+
enabled = overrideManualMode.enabled;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
return enabled;
|
|
190
|
+
}
|
|
191
|
+
function dcpModelKeys(session) {
|
|
192
|
+
const model = session.model;
|
|
193
|
+
const provider = normalizeModelKey(model?.provider);
|
|
194
|
+
const id = normalizeModelKey(model?.id);
|
|
195
|
+
return [provider && id ? `${provider}/${id}` : undefined, id].filter((key) => key !== undefined);
|
|
196
|
+
}
|
|
197
|
+
function matchingModelOverrides(overrides, modelKeys) {
|
|
198
|
+
const candidates = modelKeys.map(normalizeModelKey).filter((key, index, all) => key !== undefined && all.indexOf(key) === index);
|
|
199
|
+
const exact = new Map();
|
|
200
|
+
const wildcard = [];
|
|
201
|
+
for (const [rawKey, value] of Object.entries(overrides)) {
|
|
202
|
+
const key = normalizeModelKey(rawKey);
|
|
203
|
+
if (!key || !isRecord(value))
|
|
204
|
+
continue;
|
|
205
|
+
if (key.includes("*") || key.includes("?"))
|
|
206
|
+
wildcard.push([key, value]);
|
|
207
|
+
else
|
|
208
|
+
exact.set(key, value);
|
|
209
|
+
}
|
|
210
|
+
const bare = candidates.filter((candidate) => !candidate.includes("/"));
|
|
211
|
+
const full = candidates.filter((candidate) => candidate.includes("/"));
|
|
212
|
+
const matches = [];
|
|
213
|
+
for (const candidate of bare)
|
|
214
|
+
for (const [pattern, value] of wildcard)
|
|
215
|
+
if (!pattern.includes("/") && globMatches(pattern, candidate))
|
|
216
|
+
matches.push(value);
|
|
217
|
+
for (const candidate of bare) {
|
|
218
|
+
const value = exact.get(candidate);
|
|
219
|
+
if (value)
|
|
220
|
+
matches.push(value);
|
|
221
|
+
}
|
|
222
|
+
for (const candidate of full)
|
|
223
|
+
for (const [pattern, value] of wildcard)
|
|
224
|
+
if (pattern.includes("/") && globMatches(pattern, candidate))
|
|
225
|
+
matches.push(value);
|
|
226
|
+
for (const candidate of full) {
|
|
227
|
+
const value = exact.get(candidate);
|
|
228
|
+
if (value)
|
|
229
|
+
matches.push(value);
|
|
230
|
+
}
|
|
231
|
+
return matches;
|
|
232
|
+
}
|
|
233
|
+
function normalizeModelKey(value) {
|
|
234
|
+
if (typeof value !== "string")
|
|
235
|
+
return undefined;
|
|
236
|
+
const trimmed = value.trim();
|
|
237
|
+
return trimmed || undefined;
|
|
238
|
+
}
|
|
239
|
+
function globMatches(pattern, candidate) {
|
|
240
|
+
let source = "^";
|
|
241
|
+
for (const char of pattern) {
|
|
242
|
+
if (char === "*")
|
|
243
|
+
source += ".*";
|
|
244
|
+
else if (char === "?")
|
|
245
|
+
source += ".";
|
|
246
|
+
else
|
|
247
|
+
source += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
248
|
+
}
|
|
249
|
+
return new RegExp(`${source}$`).test(candidate);
|
|
250
|
+
}
|
|
145
251
|
function applyActiveAnchorStats(stats, data) {
|
|
146
252
|
stats.activeByType = { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 };
|
|
147
253
|
const anchors = Array.isArray(data.nudgeAnchors) ? data.nudgeAnchors : [];
|
|
@@ -161,33 +267,95 @@ function applyActiveAnchorStats(stats, data) {
|
|
|
161
267
|
};
|
|
162
268
|
}
|
|
163
269
|
}
|
|
164
|
-
function
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
270
|
+
function resolveJournalDcpState(branch) {
|
|
271
|
+
const blocks = new Map();
|
|
272
|
+
const pruned = new Map();
|
|
273
|
+
let initialized = false;
|
|
274
|
+
let manualMode;
|
|
275
|
+
let nudgeAnchors = [];
|
|
276
|
+
let lastNudge;
|
|
277
|
+
for (const entry of branch) {
|
|
278
|
+
const operation = customEntryData(entry, "dcp-journal");
|
|
279
|
+
if (!operation || operation.schemaVersion !== 1)
|
|
280
|
+
continue;
|
|
281
|
+
if (operation.kind === "init") {
|
|
282
|
+
initialized = true;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (!initialized || operation.kind !== "delta")
|
|
286
|
+
continue;
|
|
287
|
+
if (Array.isArray(operation.blocks)) {
|
|
288
|
+
for (const raw of operation.blocks) {
|
|
289
|
+
if (!isRecord(raw) || typeof raw.id !== "number")
|
|
290
|
+
continue;
|
|
291
|
+
blocks.set(raw.id, { ...raw });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (Array.isArray(operation.blockStates)) {
|
|
295
|
+
for (const raw of operation.blockStates) {
|
|
296
|
+
if (!isRecord(raw) || typeof raw.id !== "number")
|
|
297
|
+
continue;
|
|
298
|
+
const block = blocks.get(raw.id);
|
|
299
|
+
if (!block)
|
|
300
|
+
continue;
|
|
301
|
+
if (typeof raw.active === "boolean")
|
|
302
|
+
block.active = raw.active;
|
|
303
|
+
if (typeof raw.deactivatedReason === "string")
|
|
304
|
+
block.deactivatedReason = raw.deactivatedReason;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (Array.isArray(operation.prunedTools)) {
|
|
308
|
+
for (const raw of operation.prunedTools) {
|
|
309
|
+
if (!isRecord(raw) || typeof raw.toolCallId !== "string")
|
|
310
|
+
continue;
|
|
311
|
+
pruned.set(raw.toolCallId, {
|
|
312
|
+
...(typeof raw.reason === "string" ? { reason: raw.reason } : {}),
|
|
313
|
+
tokenEstimate: Math.max(0, numberValue(raw.tokenEstimate) ?? 0),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (typeof operation.manualMode === "boolean")
|
|
318
|
+
manualMode = operation.manualMode;
|
|
319
|
+
if (Array.isArray(operation.nudgeAnchors))
|
|
320
|
+
nudgeAnchors = operation.nudgeAnchors;
|
|
321
|
+
if (Object.prototype.hasOwnProperty.call(operation, "lastNudge"))
|
|
322
|
+
lastNudge = operation.lastNudge ?? undefined;
|
|
323
|
+
}
|
|
324
|
+
if (!initialized)
|
|
175
325
|
return undefined;
|
|
326
|
+
return {
|
|
327
|
+
source: "journal",
|
|
328
|
+
data: {
|
|
329
|
+
compressionBlocks: [...blocks.values()],
|
|
330
|
+
prunedToolIds: [...pruned.keys()],
|
|
331
|
+
prunedTokensSaved: [...pruned.values()].reduce((sum, item) => sum + item.tokenEstimate, 0),
|
|
332
|
+
manualMode,
|
|
333
|
+
nudgeAnchors,
|
|
334
|
+
lastNudge,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function dcpStatsBranch(session) {
|
|
339
|
+
const manager = session.sessionManager;
|
|
176
340
|
try {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
341
|
+
const full = manager.readFullBranchEntriesSync?.();
|
|
342
|
+
if (Array.isArray(full))
|
|
343
|
+
return full;
|
|
180
344
|
}
|
|
181
345
|
catch {
|
|
182
|
-
|
|
346
|
+
// Fall back to the manager's currently materialized branch below.
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const branch = manager.getBranch();
|
|
350
|
+
return Array.isArray(branch) ? branch : [];
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return [];
|
|
183
354
|
}
|
|
184
|
-
}
|
|
185
|
-
function safeSessionFileName(sessionId) {
|
|
186
|
-
return `${sessionId.replace(/[^a-zA-Z0-9._-]/g, "_")}.json`;
|
|
187
355
|
}
|
|
188
356
|
function formatStateSource(source) {
|
|
189
|
-
if (source === "
|
|
190
|
-
return "
|
|
357
|
+
if (source === "journal")
|
|
358
|
+
return "session journal";
|
|
191
359
|
return "compress tool results";
|
|
192
360
|
}
|
|
193
361
|
function customEntryData(entry, customType) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { stringDisplayWidth } from "../../terminal-width.js";
|
|
2
2
|
import { SUBAGENTS_WIDGET_MAX_ROWS } from "../constants.js";
|
|
3
|
-
import { ellipsizeDisplay, padOrTrimPlain, wrapLine } from "./render-text.js";
|
|
3
|
+
import { ellipsizeDisplay, padOrTrimPlain, sanitizeText, wrapLine } from "./render-text.js";
|
|
4
4
|
import { thinkingLevelThemeColor } from "./status-line-renderer.js";
|
|
5
|
-
import { activeSubagentStates, formatElapsedSince, formatSubagentsPanelStats, subagentModelThinkingLabel, subagentRunName,
|
|
5
|
+
import { activeSubagentStates, formatElapsedSince, formatSubagentsPanelStats, subagentIcon, subagentModelThinkingLabel, subagentRunName, taskPreviewMap, } from "../subagents/subagents-model.js";
|
|
6
6
|
import { formatTodoPanelStats, formatTodoTaskLine, hasOpenTodoTasks, shiftSegmentsToSlice, todoTaskLineSegments, visibleTodoTaskRows, visibleTodoTasks, } from "../todo/todo-model.js";
|
|
7
7
|
export function renderTodoPanel(details, expanded, width, colors) {
|
|
8
8
|
if (!details)
|
|
@@ -106,8 +106,8 @@ export function renderSubagentsPanel(state, expanded, width, colors) {
|
|
|
106
106
|
for (const visibleRun of visibleAgents) {
|
|
107
107
|
const { agent, preview } = visibleRun;
|
|
108
108
|
const model = subagentModelThinkingLabel(preview);
|
|
109
|
-
const task = preview?.task
|
|
110
|
-
const icon =
|
|
109
|
+
const task = singleLinePreview(preview?.task) || singleLinePreview(preview?.scope) || "task unavailable";
|
|
110
|
+
const icon = subagentIcon(preview);
|
|
111
111
|
const runLabel = visibleRun.showRunLabel ? `${subagentRunName(visibleRun.runDir)} ` : "";
|
|
112
112
|
const prefix = `${runLabel}${icon} ${agent.id} ${model} `;
|
|
113
113
|
const suffix = ` ${formatElapsedSince(agent.startedAt, now)}`;
|
|
@@ -126,9 +126,12 @@ export function renderSubagentsPanel(state, expanded, width, colors) {
|
|
|
126
126
|
lines.push({ text: padOrTrimPlain(`+${hidden} more`, width), variant: "muted", target });
|
|
127
127
|
return lines;
|
|
128
128
|
}
|
|
129
|
+
function singleLinePreview(value) {
|
|
130
|
+
return value ? sanitizeText(value).replace(/\s+/gu, " ").trim() : "";
|
|
131
|
+
}
|
|
129
132
|
function subagentPanelLineSegments(input, colors) {
|
|
130
|
-
const iconStart = input.text.indexOf(input.icon);
|
|
131
133
|
const runLabelStart = input.runLabel ? input.text.indexOf(input.runLabel) : -1;
|
|
134
|
+
const iconStart = input.runLabel.length;
|
|
132
135
|
const nameStart = input.text.indexOf(input.agentId, iconStart + input.icon.length);
|
|
133
136
|
const modelStart = input.text.indexOf(input.model, nameStart + input.agentId.length);
|
|
134
137
|
const taskStart = input.prefix.length;
|
|
@@ -145,6 +145,14 @@ class LazySessionManager {
|
|
|
145
145
|
const entries = await readAllSessionEntries(this.sessionFilePath);
|
|
146
146
|
return branchEntries(entries, this.leafId ?? entries.at(-1)?.id);
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Explicit synchronous full-branch escape hatch for user-triggered diagnostics
|
|
150
|
+
* such as DCP statistics. Normal transcript rendering stays lazy; callers that
|
|
151
|
+
* opt into this method accept one full session hydration for exact branch data.
|
|
152
|
+
*/
|
|
153
|
+
readFullBranchEntriesSync() {
|
|
154
|
+
return this.hydrate().getBranch();
|
|
155
|
+
}
|
|
148
156
|
async readFullSessionEntries() {
|
|
149
157
|
if (this.hydrated)
|
|
150
158
|
return this.hydrated.getEntries();
|
|
@@ -320,10 +328,13 @@ class LazySessionManager {
|
|
|
320
328
|
}
|
|
321
329
|
}
|
|
322
330
|
appendEntry(entry) {
|
|
331
|
+
// Persist first. If the append fails, the in-memory branch must remain
|
|
332
|
+
// unchanged; otherwise extensions that treat append success as a commit
|
|
333
|
+
// boundary can observe a phantom entry that never reached the JSONL file.
|
|
334
|
+
appendFileSync(this.sessionFilePath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
323
335
|
this.entries.push(entry);
|
|
324
336
|
this.byId.set(entry.id, entry);
|
|
325
337
|
this.leafId = entry.id;
|
|
326
|
-
appendFileSync(this.sessionFilePath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
327
338
|
return entry.id;
|
|
328
339
|
}
|
|
329
340
|
newEntry(type, payload) {
|
|
@@ -184,13 +184,10 @@ export declare class AppTabsController {
|
|
|
184
184
|
private prewarmTabs;
|
|
185
185
|
private cleanupOldProjectSessions;
|
|
186
186
|
/**
|
|
187
|
-
* Unlink a project session file
|
|
188
|
-
* The sidecar path is derived from the session id in the first line of the
|
|
189
|
-
* `.jsonl` (mirrors the DCP module's `safeSessionFileName`), so retention
|
|
190
|
-
* never leaves orphan sidecars behind. Everything here is best-effort:
|
|
187
|
+
* Unlink a project session file. Everything here is best-effort:
|
|
191
188
|
* session retention must never interrupt the terminal UI.
|
|
192
189
|
*/
|
|
193
|
-
private
|
|
190
|
+
private unlinkSessionFile;
|
|
194
191
|
private preservedSessionPaths;
|
|
195
192
|
private maxProjectSessions;
|
|
196
193
|
}
|