pi-ui-extend 1.0.40 → 1.0.42
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 +10 -2
- package/dist/app/app.d.ts +3 -0
- package/dist/app/app.js +26 -0
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +6 -1
- package/dist/app/commands/command-registry.js +3 -3
- package/dist/app/commands/command-session-actions.d.ts +0 -1
- package/dist/app/commands/command-session-actions.js +30 -15
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +59 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- 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 +17 -8
- package/dist/app/session/lazy-session-manager.js +12 -1
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +6 -0
- package/dist/app/session/tabs-controller.d.ts +3 -5
- package/dist/app/session/tabs-controller.js +31 -22
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +15 -1
- package/dist/app/subagents/subagents-model.js +52 -17
- package/dist/app/types.d.ts +7 -0
- package/dist/bundled-extensions/session-title/config.js +1 -1
- package/dist/default-pix-config.js +10 -0
- package/dist/markdown-format.js +27 -9
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +106 -71
- package/dist/schemas/pi-tools-suite-schema.js +68 -88
- package/dist/schemas/pix-schema.d.ts +7 -0
- package/dist/schemas/pix-schema.js +14 -0
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +6 -0
- package/docs/desktop-mvp.md +12 -1
- package/docs/desktop-task-manager.md +93 -82
- package/external/pi-tools-suite/README.md +192 -72
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +50 -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 +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +40 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +120 -0
- package/external/pi-tools-suite/package.json +3 -0
- 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/frontier-review.md +23 -0
- 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/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +20 -0
- package/external/pi-tools-suite/src/async-subagents/agents/verify.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +20 -72
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +42 -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 +499 -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 +284 -417
- 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 +65 -45
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +18 -6
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +77 -3
- package/external/pi-tools-suite/src/async-subagents/lib.ts +12 -9
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +54 -22
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +6 -4
- package/external/pi-tools-suite/src/async-subagents/types.ts +3 -0
- package/external/pi-tools-suite/src/config.ts +42 -2
- package/external/pi-tools-suite/src/context-gateway/accounting.ts +151 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +120 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +214 -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 +502 -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 +158 -0
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +106 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +892 -136
- package/external/pi-tools-suite/src/dcp/commands.ts +82 -139
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +533 -74
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +488 -94
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +122 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +70 -0
- package/external/pi-tools-suite/src/dcp/config.ts +86 -68
- 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 +83 -189
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +189 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +299 -79
- 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 +23 -15
- 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 +28 -136
- 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 +36 -214
- package/external/pi-tools-suite/src/index.ts +16 -1
- 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/resource-registry/index.ts +2290 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +189 -43
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +41 -38
- package/external/pi-tools-suite/src/truncation-metadata-normalizer/index.ts +17 -0
- package/package.json +6 -3
- package/schemas/pi-tools-suite.json +540 -312
- package/schemas/pix.json +24 -0
- package/schemas/tasks.json +132 -0
- package/skills/spec-wiki/SKILL.md +55 -0
- package/skills/spec-wiki/evals/evals.json +132 -0
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
- package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
- package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
- package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
- package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
- package/skills/spec-wiki/evals/trigger-evals.json +94 -0
- package/skills/spec-wiki/references/bootstrap.md +100 -0
- package/skills/spec-wiki/references/focused-spec.md +117 -0
- package/skills/spec-wiki/references/index-schema.md +330 -0
- package/skills/spec-wiki/references/maintenance.md +134 -0
- package/skills/spec-wiki/references/query.md +74 -0
- package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -270
- 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/skill-installer/index.ts +0 -333
- package/skills/spec-lite/SKILL.md +0 -140
- /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
|
@@ -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, formatSubagentLastActivity, 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,18 +106,20 @@ 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
|
-
const
|
|
113
|
+
const activity = formatSubagentLastActivity(agent.lastActivity);
|
|
114
|
+
const elapsed = formatElapsedSince(agent.startedAt, now);
|
|
115
|
+
const suffix = activity ? ` ${activity} ${elapsed}` : ` ${elapsed}`;
|
|
114
116
|
const taskWidth = Math.max(8, rowWidth - stringDisplayWidth(prefix) - stringDisplayWidth(suffix));
|
|
115
117
|
const taskText = ellipsizeDisplay(task, taskWidth);
|
|
116
118
|
const text = `${prefix}${taskText}${suffix}`;
|
|
117
119
|
lines.push({
|
|
118
120
|
text: padOrTrimPlain(text, width),
|
|
119
121
|
colorOverride: colors.muted,
|
|
120
|
-
segments: subagentPanelLineSegments({ text, icon, agentId: agent.id, model, taskText, prefix, runLabel, status: agent.status }, colors),
|
|
122
|
+
segments: subagentPanelLineSegments({ text, icon, agentId: agent.id, model, taskText, prefix, runLabel, activity, elapsed, status: agent.status }, colors),
|
|
121
123
|
target,
|
|
122
124
|
});
|
|
123
125
|
}
|
|
@@ -126,20 +128,27 @@ export function renderSubagentsPanel(state, expanded, width, colors) {
|
|
|
126
128
|
lines.push({ text: padOrTrimPlain(`+${hidden} more`, width), variant: "muted", target });
|
|
127
129
|
return lines;
|
|
128
130
|
}
|
|
131
|
+
function singleLinePreview(value) {
|
|
132
|
+
return value ? sanitizeText(value).replace(/\s+/gu, " ").trim() : "";
|
|
133
|
+
}
|
|
129
134
|
function subagentPanelLineSegments(input, colors) {
|
|
130
|
-
const iconStart = input.text.indexOf(input.icon);
|
|
131
135
|
const runLabelStart = input.runLabel ? input.text.indexOf(input.runLabel) : -1;
|
|
136
|
+
const iconStart = input.runLabel.length;
|
|
132
137
|
const nameStart = input.text.indexOf(input.agentId, iconStart + input.icon.length);
|
|
133
138
|
const modelStart = input.text.indexOf(input.model, nameStart + input.agentId.length);
|
|
134
139
|
const taskStart = input.prefix.length;
|
|
135
140
|
const suffixStart = taskStart + input.taskText.length;
|
|
141
|
+
const elapsedStart = input.text.length - input.elapsed.length;
|
|
142
|
+
const activityStart = input.activity ? elapsedStart - input.activity.length - 1 : -1;
|
|
136
143
|
return [
|
|
137
144
|
...(runLabelStart >= 0 ? [{ start: runLabelStart, end: runLabelStart + input.runLabel.length, foreground: colors.warning }] : []),
|
|
138
145
|
{ start: iconStart, end: iconStart + input.icon.length, foreground: subagentStatusColor(input.status, colors), bold: true },
|
|
139
146
|
{ start: nameStart, end: nameStart + input.agentId.length, foreground: colors.accent, bold: true },
|
|
140
147
|
{ start: modelStart, end: modelStart + input.model.length, foreground: colors.info },
|
|
141
148
|
{ start: taskStart, end: suffixStart, foreground: colors.muted },
|
|
142
|
-
{ start: suffixStart, end: input.text.length, foreground: colors.muted },
|
|
149
|
+
{ start: suffixStart, end: input.activity ? activityStart : input.text.length, foreground: colors.muted },
|
|
150
|
+
...(input.activity ? [{ start: activityStart, end: activityStart + input.activity.length, foreground: colors.info }] : []),
|
|
151
|
+
...(input.activity ? [{ start: activityStart + input.activity.length, end: input.text.length, foreground: colors.muted }] : []),
|
|
143
152
|
].filter((segment) => segment.start >= 0 && segment.end > segment.start);
|
|
144
153
|
}
|
|
145
154
|
function subagentStatusColor(status, colors) {
|
|
@@ -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) {
|
|
@@ -27,6 +27,7 @@ export type AppSessionLifecycleHost = {
|
|
|
27
27
|
handleExtensionError(error: ExtensionError): void;
|
|
28
28
|
handleSessionEvent(event: AgentSessionEvent): void;
|
|
29
29
|
bindAgentPause(session: AgentSession): void;
|
|
30
|
+
contextInventoryText?(runtime: AgentSessionRuntime, heading: string): string;
|
|
30
31
|
addEntry(entry: Entry): void;
|
|
31
32
|
setStatus(status: string): void;
|
|
32
33
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
@@ -44,6 +44,12 @@ export class AppSessionLifecycleController {
|
|
|
44
44
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
45
45
|
});
|
|
46
46
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
47
|
+
if (runtime.session.messages.length === 0) {
|
|
48
|
+
await this.awaitCurrentSessionExtensions(runtime);
|
|
49
|
+
const text = this.host.contextInventoryText?.(runtime, `Empty tab ready. cwd=${runtime.cwd}`);
|
|
50
|
+
if (text)
|
|
51
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
52
|
+
}
|
|
47
53
|
if (runtime.modelFallbackMessage) {
|
|
48
54
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
49
55
|
}
|
|
@@ -43,6 +43,7 @@ export type AppTabsControllerHost = {
|
|
|
43
43
|
restoreAutoUserMessages?(messages: readonly SubmittedUserMessage[]): void;
|
|
44
44
|
captureDeferredUserMessages?(): readonly SubmittedUserMessage[];
|
|
45
45
|
restoreDeferredUserMessages?(messages: readonly SubmittedUserMessage[]): void;
|
|
46
|
+
contextInventoryText?(runtime: AgentSessionRuntime, heading: string): string;
|
|
46
47
|
addEntry(entry: Entry): void;
|
|
47
48
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
48
49
|
render(): void;
|
|
@@ -184,13 +185,10 @@ export declare class AppTabsController {
|
|
|
184
185
|
private prewarmTabs;
|
|
185
186
|
private cleanupOldProjectSessions;
|
|
186
187
|
/**
|
|
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:
|
|
188
|
+
* Unlink a project session file. Everything here is best-effort:
|
|
191
189
|
* session retention must never interrupt the terminal UI.
|
|
192
190
|
*/
|
|
193
|
-
private
|
|
191
|
+
private unlinkSessionFile;
|
|
194
192
|
private preservedSessionPaths;
|
|
195
193
|
private maxProjectSessions;
|
|
196
194
|
}
|
|
@@ -350,6 +350,12 @@ export class AppTabsController {
|
|
|
350
350
|
this.scheduleProjectSessionRetention();
|
|
351
351
|
this.scheduleTabPrewarm();
|
|
352
352
|
await this.loadActiveSessionHistory(restoredRuntime, generation);
|
|
353
|
+
if (restoredRuntime.session.messages.length === 0) {
|
|
354
|
+
await this.host.awaitCurrentSessionExtensions?.(restoredRuntime);
|
|
355
|
+
const text = this.host.contextInventoryText?.(restoredRuntime, `Opened an empty tab. cwd=${restoredRuntime.cwd}`);
|
|
356
|
+
if (text)
|
|
357
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
358
|
+
}
|
|
353
359
|
this.scheduleRestoredTabTitleRefresh(restoredSessionPaths, generation);
|
|
354
360
|
}
|
|
355
361
|
openNewTab() {
|
|
@@ -436,6 +442,7 @@ export class AppTabsController {
|
|
|
436
442
|
this.host.render();
|
|
437
443
|
try {
|
|
438
444
|
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
445
|
+
await this.host.awaitCurrentSessionExtensions?.(newRuntime);
|
|
439
446
|
}
|
|
440
447
|
catch (error) {
|
|
441
448
|
if (this.pendingActiveTabId === targetTab.id)
|
|
@@ -477,7 +484,12 @@ export class AppTabsController {
|
|
|
477
484
|
this.scheduleProjectSessionRetention();
|
|
478
485
|
this.host.resetSessionView();
|
|
479
486
|
this.restoreDeferredUserMessages(targetTab.id);
|
|
480
|
-
this.host.addEntry({
|
|
487
|
+
this.host.addEntry({
|
|
488
|
+
id: createId("system"),
|
|
489
|
+
kind: "system",
|
|
490
|
+
text: this.host.contextInventoryText?.(newRuntime, `Opened a new tab. cwd=${newRuntime.cwd}`)
|
|
491
|
+
?? `Opened a new tab. cwd=${newRuntime.cwd}`,
|
|
492
|
+
});
|
|
481
493
|
if (newRuntime.modelFallbackMessage)
|
|
482
494
|
this.host.addEntry({ id: createId("system"), kind: "system", text: newRuntime.modelFallbackMessage });
|
|
483
495
|
for (const diag of newRuntime.diagnostics ?? []) {
|
|
@@ -571,6 +583,7 @@ export class AppTabsController {
|
|
|
571
583
|
this.host.render();
|
|
572
584
|
try {
|
|
573
585
|
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
586
|
+
await this.host.awaitCurrentSessionExtensions?.(newRuntime);
|
|
574
587
|
}
|
|
575
588
|
catch {
|
|
576
589
|
this.pendingActiveTabId = undefined;
|
|
@@ -615,6 +628,11 @@ export class AppTabsController {
|
|
|
615
628
|
void this.saveTabs();
|
|
616
629
|
this.scheduleTabPrewarm();
|
|
617
630
|
await this.loadActiveSessionHistory(newRuntime, generation);
|
|
631
|
+
if (newRuntime.session.messages.length === 0) {
|
|
632
|
+
const text = this.host.contextInventoryText?.(newRuntime, `Opened an empty tab. cwd=${newRuntime.cwd}`);
|
|
633
|
+
if (text)
|
|
634
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
635
|
+
}
|
|
618
636
|
return true;
|
|
619
637
|
}
|
|
620
638
|
forkSessionEntryInNewTab(entryId) {
|
|
@@ -1953,7 +1971,12 @@ export class AppTabsController {
|
|
|
1953
1971
|
for (const session of sessions) {
|
|
1954
1972
|
if (keep.has(session.path))
|
|
1955
1973
|
continue;
|
|
1956
|
-
|
|
1974
|
+
// Lifecycle state can change while retention is scanning. Re-check the
|
|
1975
|
+
// current runtime/open tabs immediately before destructive work so a
|
|
1976
|
+
// session that became live after the initial snapshot is not removed.
|
|
1977
|
+
if (this.preservedSessionPaths().has(resolve(session.path)))
|
|
1978
|
+
continue;
|
|
1979
|
+
await this.unlinkSessionFile(session.path);
|
|
1957
1980
|
}
|
|
1958
1981
|
}
|
|
1959
1982
|
catch {
|
|
@@ -1964,31 +1987,17 @@ export class AppTabsController {
|
|
|
1964
1987
|
}
|
|
1965
1988
|
}
|
|
1966
1989
|
/**
|
|
1967
|
-
* Unlink a project session file
|
|
1968
|
-
* The sidecar path is derived from the session id in the first line of the
|
|
1969
|
-
* `.jsonl` (mirrors the DCP module's `safeSessionFileName`), so retention
|
|
1970
|
-
* never leaves orphan sidecars behind. Everything here is best-effort:
|
|
1990
|
+
* Unlink a project session file. Everything here is best-effort:
|
|
1971
1991
|
* session retention must never interrupt the terminal UI.
|
|
1972
1992
|
*/
|
|
1973
|
-
async
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
if (header?.type === "session" && typeof header.id === "string" && header.id) {
|
|
1977
|
-
sidecarPath = join(dirname(sessionPath), "dcp-state", header.id.replace(/[^a-zA-Z0-9._-]/g, "_") + ".json");
|
|
1978
|
-
}
|
|
1993
|
+
async unlinkSessionFile(sessionPath, unlinkFile = unlink) {
|
|
1994
|
+
if (this.preservedSessionPaths().has(resolve(sessionPath)))
|
|
1995
|
+
return;
|
|
1979
1996
|
try {
|
|
1980
|
-
await
|
|
1997
|
+
await unlinkFile(sessionPath);
|
|
1981
1998
|
}
|
|
1982
1999
|
catch {
|
|
1983
|
-
|
|
1984
|
-
}
|
|
1985
|
-
if (sidecarPath) {
|
|
1986
|
-
try {
|
|
1987
|
-
await unlink(sidecarPath);
|
|
1988
|
-
}
|
|
1989
|
-
catch {
|
|
1990
|
-
// Sidecar removal is best-effort; never interrupt the terminal UI.
|
|
1991
|
-
}
|
|
2000
|
+
return;
|
|
1992
2001
|
}
|
|
1993
2002
|
}
|
|
1994
2003
|
preservedSessionPaths() {
|