pi-ui-extend 0.1.34 → 0.1.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 +20 -0
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +12 -2
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +32 -0
- package/dist/app/commands/command-navigation-actions.js +3 -0
- package/dist/app/commands/command-session-actions.js +2 -0
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +4 -0
- package/dist/app/input/input-controller.d.ts +5 -1
- package/dist/app/input/input-controller.js +122 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +21 -0
- package/dist/app/input/terminal-edit-shortcuts.js +92 -16
- package/dist/app/popup/popup-action-controller.d.ts +1 -0
- package/dist/app/popup/popup-action-controller.js +1 -0
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +78 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +7 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +4 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +1 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +5 -5
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +7 -5
|
@@ -68,12 +68,62 @@ DCP settings are stored only under `dcp` in the user shared config file `~/.conf
|
|
|
68
68
|
"nudgeFrequency": 1,
|
|
69
69
|
"iterationNudgeThreshold": 6,
|
|
70
70
|
"protectedTools": ["compress", "write", "edit", "subagents"]
|
|
71
|
+
},
|
|
72
|
+
"modelOverrides": {
|
|
73
|
+
"openai-codex/gpt-5.5": {
|
|
74
|
+
"compress": {
|
|
75
|
+
"minContextPercent": "28%",
|
|
76
|
+
"maxContextPercent": "48%"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"openai-codex/gpt-5.4-mini": {
|
|
80
|
+
"compress": {
|
|
81
|
+
"minContextPercent": "20%",
|
|
82
|
+
"maxContextPercent": "38%"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"zai/*": {
|
|
86
|
+
"compress": {
|
|
87
|
+
"minContextPercent": "16%",
|
|
88
|
+
"maxContextPercent": "30%"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"antigravity/*sonnet*": {
|
|
92
|
+
"compress": {
|
|
93
|
+
"minContextPercent": "22%",
|
|
94
|
+
"maxContextPercent": "40%"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"antigravity/gemini-3.1-pro*": {
|
|
98
|
+
"compress": {
|
|
99
|
+
"minContextPercent": "24%",
|
|
100
|
+
"maxContextPercent": "42%"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"antigravity/gemini-3-flash*": {
|
|
104
|
+
"compress": {
|
|
105
|
+
"minContextPercent": "18%",
|
|
106
|
+
"maxContextPercent": "34%"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"antigravity/gemini-2.5-flash*": {
|
|
110
|
+
"compress": {
|
|
111
|
+
"minContextPercent": "18%",
|
|
112
|
+
"maxContextPercent": "32%"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"antigravity/antigravity-claude-opus-4-6-thinking": {
|
|
116
|
+
"compress": {
|
|
117
|
+
"minContextPercent": "26%",
|
|
118
|
+
"maxContextPercent": "44%"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
71
121
|
}
|
|
72
122
|
}
|
|
73
123
|
}
|
|
74
124
|
```
|
|
75
125
|
|
|
76
|
-
`minContextPercent` / `maxContextPercent` accept legacy fractions (`0.25`), percent strings (`"25%"`), or absolute token counts when Pi knows the current model context window. `minContextLimit` / `maxContextLimit` and `modelMinContextLimits` / `modelMaxContextLimits` are explicit absolute-or-percent aliases. If `compress.protectUserMessages` is enabled, range compression appends selected user messages verbatim instead of rejecting the range; individual message compression still skips protected raw user messages. Protected tool outputs are copied into summaries for tools protected by name or `protectedFilePatterns`; protected `subagents` result reads also try to include the saved `result.md` artifact when available.
|
|
126
|
+
`minContextPercent` / `maxContextPercent` accept legacy fractions (`0.25`), percent strings (`"25%"`), or absolute token counts when Pi knows the current model context window. `minContextLimit` / `maxContextLimit` and `modelMinContextLimits` / `modelMaxContextLimits` are explicit absolute-or-percent aliases. `modelOverrides` and the `modelMin*` / `modelMax*` maps support exact model keys plus `*` / `?` wildcard patterns; matching is applied from generic to specific so exact bare-model matches override bare wildcards, and exact `provider/model` matches override provider wildcards. Array fields are union-merged, so model-specific `protectedTools` extend the defaults instead of replacing them. If `compress.protectUserMessages` is enabled, range compression appends selected user messages verbatim instead of rejecting the range; individual message compression still skips protected raw user messages. Protected tool outputs are copied into summaries for tools protected by name or `protectedFilePatterns`; protected `subagents` result reads also try to include the saved `result.md` artifact when available.
|
|
77
127
|
|
|
78
128
|
## LSP setup
|
|
79
129
|
|
|
@@ -271,6 +321,26 @@ npm run test:e2e
|
|
|
271
321
|
|
|
272
322
|
Supporting docs and historical standalone README content are kept in `docs/`; third-party license texts are kept in `licenses/`.
|
|
273
323
|
|
|
324
|
+
## SDK pin
|
|
325
|
+
|
|
326
|
+
This suite runs inside the Pi host process, so its `@earendil-works/*`
|
|
327
|
+
peerDependencies (`pi-ai`, `pi-coding-agent`, `pi-tui`) must match the host Pi
|
|
328
|
+
SDK version exactly. Otherwise npm can resolve a stale copy in this package's
|
|
329
|
+
own `node_modules` and cause a double-load (e.g. `0.75.4` here vs `0.79.4` in
|
|
330
|
+
the host).
|
|
331
|
+
|
|
332
|
+
The host repo keeps these aligned: `npm run sync:sdk-pin` rewrites these
|
|
333
|
+
peerDeps to the host version, and `npm run sync:sdk-pin:check` reports drift
|
|
334
|
+
(non-zero exit). When you bump the Pi SDK in the host `package.json`, the host
|
|
335
|
+
runs `sync:sdk-pin` and then you reinstall here:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npm install --ignore-scripts
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The suite deliberately does not bump its own `version` field for SDK changes;
|
|
342
|
+
its peerDeps carry the version.
|
|
343
|
+
|
|
274
344
|
## Third-party notices
|
|
275
345
|
|
|
276
346
|
Parts of this extension suite are based on or adapted from code by other vendors and projects. The corresponding license texts and notices are included in `licenses/`.
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"smoke:tools": "PI_OFFLINE=1 pi --no-session -p \"ping\"",
|
|
23
23
|
"smoke": "npm run smoke:explicit && npm run smoke:auto && npm run smoke:tools",
|
|
24
24
|
"test": "bun test test",
|
|
25
|
-
"test:async-subagents-e2e": "ASYNC_SUBAGENTS_E2E=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/async-subagents",
|
|
25
|
+
"test:async-subagents-e2e": "ASYNC_SUBAGENTS_E2E=1 ASYNC_SUBAGENTS_DEBUG_LOGS=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/async-subagents",
|
|
26
26
|
"test:async-subagents-selection-e2e": "ASYNC_SUBAGENTS_SELECTION_E2E=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/async-subagents/selection-e2e.test.ts",
|
|
27
27
|
"bench:locate": "PI_LOCATE_BENCH_ITERATIONS=5 PI_LOCATE_BENCH_FAKE_IDX=0 PI_LOCATE_BENCH_MODEL=zai/glm-5-turbo PI_LOCATE_BENCH_MODES=direct-read-grep,ast-structural,repo-search-hybrid,repo-discovery,subagent-search,unrestricted-suite node test/fixtures/hard-to-find-project/benchmark/run-locate-benchmark.mjs",
|
|
28
28
|
"bench:locate:analyze": "node test/fixtures/hard-to-find-project/benchmark/analyze-locate-benchmark.mjs",
|
|
29
29
|
"test:locate-benchmark-e2e": "PI_LOCATE_BENCH_E2E=1 PI_LOCATE_BENCH_MODEL=zai/glm-5-turbo bun test test/locate-benchmark-e2e.test.ts",
|
|
30
30
|
"test:tool-selection-e2e": "TOOL_SELECTION_E2E=1 TOOL_SELECTION_E2E_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=30 test/tool-selection-e2e.test.ts",
|
|
31
|
-
"test:e2e": "TOOL_SELECTION_E2E=1 TOOL_SELECTION_E2E_MODEL=zai/glm-5-turbo ASYNC_SUBAGENTS_E2E=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=5 test/tool-selection-e2e.test.ts test/async-subagents/e2e.test.ts test/async-subagents/selection-e2e.test.ts test/todo-persistence-e2e.test.ts",
|
|
31
|
+
"test:e2e": "TOOL_SELECTION_E2E=1 TOOL_SELECTION_E2E_MODEL=zai/glm-5-turbo ASYNC_SUBAGENTS_E2E=1 ASYNC_SUBAGENTS_DEBUG_LOGS=1 ASYNC_SUBAGENTS_MODEL=zai/glm-5-turbo bun test --concurrent --max-concurrency=5 test/tool-selection-e2e.test.ts test/async-subagents/e2e.test.ts test/async-subagents/selection-e2e.test.ts test/todo-persistence-e2e.test.ts",
|
|
32
32
|
"typecheck:async-subagents": "bash scripts/typecheck-source.sh",
|
|
33
33
|
"check": "npm run smoke"
|
|
34
34
|
},
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-ai": "
|
|
42
|
-
"@earendil-works/pi-coding-agent": "
|
|
43
|
-
"@earendil-works/pi-tui": "
|
|
41
|
+
"@earendil-works/pi-ai": "0.79.4",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "0.79.4",
|
|
43
|
+
"@earendil-works/pi-tui": "0.79.4",
|
|
44
44
|
"typebox": "*"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { ignoreStaleExtensionContextError } from "../context-usage.js";
|
|
4
5
|
import {
|
|
5
6
|
copySubagentConfigSample,
|
|
6
7
|
ensureSessionFileLink,
|
|
@@ -172,12 +173,17 @@ async function triggerOrchestrationPrompt(
|
|
|
172
173
|
? `${basePrompt}\n\nObjective:\n${objective}`
|
|
173
174
|
: basePrompt;
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
pi.sendUserMessage
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
try {
|
|
177
|
+
if (typeof pi.sendUserMessage === "function") {
|
|
178
|
+
pi.sendUserMessage(prompt);
|
|
179
|
+
} else if (typeof pi.sendMessage === "function") {
|
|
180
|
+
pi.sendMessage({ customType: `async-subagents-${modeName}`, content: prompt, display: false }, { triggerTurn: true, deliverAs: "followUp" });
|
|
181
|
+
} else {
|
|
182
|
+
ctx.ui.notify(`Cannot trigger /${modeName}: this Pi runtime does not expose sendUserMessage/sendMessage.`, "error");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
ignoreStaleExtensionContextError(error);
|
|
181
187
|
return;
|
|
182
188
|
}
|
|
183
189
|
|
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
getSubagentRegistryPath,
|
|
10
10
|
isBlindModelRef,
|
|
11
11
|
loadSubagentConfig,
|
|
12
|
-
|
|
12
|
+
listSubagentSessionRecords,
|
|
13
13
|
loadSubagentRegistry,
|
|
14
14
|
removeSubagentRunsFromRegistry,
|
|
15
15
|
stopAgents,
|
|
16
16
|
type AgentCompletionHandler,
|
|
17
17
|
type StopSignal,
|
|
18
|
+
type SubagentSessionRecord,
|
|
18
19
|
} from "./lib.js";
|
|
19
20
|
import { buildUltraworkPrompt, isUltraworkEnvEnabled, registerCommands } from "./commands.js";
|
|
20
21
|
import { agentStrategyPrompt, appendAgentStrategyPrompt } from "./core/agent-strategy.js";
|
|
@@ -46,6 +47,11 @@ interface ShutdownTarget {
|
|
|
46
47
|
agentIds?: string[];
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
interface ShutdownPlan {
|
|
51
|
+
targets: ShutdownTarget[];
|
|
52
|
+
runDirsToDelete: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
function createLiveStatePayload(
|
|
50
56
|
liveAgents: Map<string, Map<string, LiveAgent>>,
|
|
51
57
|
sessionFile: string | undefined,
|
|
@@ -81,6 +87,14 @@ function agentMatchesSession(agent: LiveAgent, sessionFile: string | undefined):
|
|
|
81
87
|
return pathsEqual(sessionFile, agent.parentSession);
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
function isStaleExtensionContextError(error: unknown): boolean {
|
|
91
|
+
return error instanceof Error && /ctx is stale|stale ctx|stale after session replacement|stale after.*reload/i.test(error.message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function ignoreStaleExtensionContextError(error: unknown): void {
|
|
95
|
+
if (!isStaleExtensionContextError(error)) throw error;
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
export default function (pi: ExtensionAPI) {
|
|
85
99
|
const liveAgents = new Map<string, Map<string, LiveAgent>>();
|
|
86
100
|
const subagentOverlay = new SubagentOverlay(liveAgents);
|
|
@@ -90,11 +104,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
104
|
publishSubagentPresetsStartupSection();
|
|
91
105
|
|
|
92
106
|
function refreshSubagentOverlay(): void {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
try {
|
|
108
|
+
reconcileLiveAgentCompletions();
|
|
109
|
+
const liveState = createLiveStatePayload(liveAgents, currentSessionFile);
|
|
110
|
+
pi.events?.emit?.(SUBAGENTS_LIVE_COUNT_EVENT, { count: liveState.count });
|
|
111
|
+
pi.events?.emit?.(SUBAGENTS_LIVE_STATE_EVENT, liveState);
|
|
112
|
+
updateCompletionWatcher();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
ignoreStaleExtensionContextError(error);
|
|
115
|
+
}
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
function removeLiveAgent(runDir: string, agentId: string): void {
|
|
@@ -147,10 +165,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
147
165
|
registerCommands(pi);
|
|
148
166
|
|
|
149
167
|
pi.on("session_start", async (_event, ctx) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
168
|
+
try {
|
|
169
|
+
sawAutoUltraworkCandidate = false;
|
|
170
|
+
currentSessionFile = sessionFileFromContext(ctx);
|
|
171
|
+
subagentOverlay.restoreRunningAgents(ctx.cwd, currentSessionFile);
|
|
172
|
+
refreshSubagentOverlay();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
ignoreStaleExtensionContextError(error);
|
|
175
|
+
}
|
|
154
176
|
});
|
|
155
177
|
|
|
156
178
|
pi.on("tool_execution_end", async (event) => {
|
|
@@ -207,18 +229,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
207
229
|
});
|
|
208
230
|
|
|
209
231
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
210
|
-
subagentOverlay.dispose();
|
|
211
|
-
if (completionWatchTimer) {
|
|
212
|
-
clearInterval(completionWatchTimer);
|
|
213
|
-
completionWatchTimer = undefined;
|
|
214
|
-
}
|
|
215
|
-
if (event?.reason === "reload" || event?.reason === "fork") return;
|
|
216
232
|
try {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
subagentOverlay.dispose();
|
|
234
|
+
if (completionWatchTimer) {
|
|
235
|
+
clearInterval(completionWatchTimer);
|
|
236
|
+
completionWatchTimer = undefined;
|
|
237
|
+
}
|
|
238
|
+
if (event?.reason === "reload" || event?.reason === "fork") return;
|
|
239
|
+
try {
|
|
240
|
+
const shutdownSessionFile = sessionFileFromContext(ctx) ?? currentSessionFile;
|
|
241
|
+
await cleanupProjectSubagentState(ctx.cwd, liveAgents, { parentSession: shutdownSessionFile });
|
|
242
|
+
liveAgents.clear();
|
|
243
|
+
refreshSubagentOverlay();
|
|
244
|
+
} catch {
|
|
245
|
+
// Shutdown cleanup is best-effort and must never block the main session from closing.
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
ignoreStaleExtensionContextError(error);
|
|
222
249
|
}
|
|
223
250
|
});
|
|
224
251
|
}
|
|
@@ -394,31 +421,100 @@ function selectedToolsInclude(event: unknown, toolName: string): boolean {
|
|
|
394
421
|
return !Array.isArray(selectedTools) || selectedTools.includes(toolName);
|
|
395
422
|
}
|
|
396
423
|
|
|
397
|
-
async function cleanupProjectSubagentState(
|
|
398
|
-
|
|
399
|
-
|
|
424
|
+
async function cleanupProjectSubagentState(
|
|
425
|
+
cwd: string,
|
|
426
|
+
liveAgents: Map<string, Map<string, LiveAgent>>,
|
|
427
|
+
options: { parentSession?: string } = {},
|
|
428
|
+
): Promise<void> {
|
|
429
|
+
const shutdownPlan = collectShutdownPlan(cwd, liveAgents, options.parentSession);
|
|
430
|
+
const signaled = signalShutdownTargets(shutdownPlan.targets, "SIGTERM");
|
|
400
431
|
if (signaled > 0) await sleep(SESSION_SHUTDOWN_KILL_GRACE_MS);
|
|
401
|
-
signalShutdownTargets(
|
|
432
|
+
signalShutdownTargets(shutdownPlan.targets, "SIGKILL");
|
|
402
433
|
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
removeSubagentRunsFromRegistry(cwd, runDirs);
|
|
434
|
+
for (const target of shutdownPlan.targets) stopRunBestEffort(target.runDir, target.agentIds, "SIGKILL");
|
|
435
|
+
deleteRunDirs(shutdownPlan.runDirsToDelete);
|
|
436
|
+
removeSubagentRunsFromRegistry(cwd, shutdownPlan.runDirsToDelete);
|
|
407
437
|
removeEmptySubagentState(cwd);
|
|
408
438
|
}
|
|
409
439
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
440
|
+
|
|
441
|
+
function collectShutdownPlan(
|
|
442
|
+
cwd: string,
|
|
443
|
+
liveAgents: Map<string, Map<string, LiveAgent>>,
|
|
444
|
+
parentSession: string | undefined,
|
|
445
|
+
): ShutdownPlan {
|
|
446
|
+
if (!parentSession) return collectLiveShutdownPlan(liveAgents);
|
|
447
|
+
|
|
448
|
+
const targets = new Map<string, Set<string>>();
|
|
449
|
+
const runDirsToDelete = new Set<string>();
|
|
450
|
+
const recordsByRun = groupRecordsByRun(listSubagentSessionRecords(cwd));
|
|
451
|
+
|
|
452
|
+
for (const [runDir, records] of recordsByRun) {
|
|
453
|
+
const matchingRecords = records.filter((record) => recordMatchesSession(record, parentSession));
|
|
454
|
+
if (matchingRecords.length === 0) continue;
|
|
455
|
+
mergeTargetIds(targets, runDir, matchingRecords.map((record) => record.agentId));
|
|
456
|
+
const liveRun = liveAgents.get(runDir);
|
|
457
|
+
if (records.every((record) => recordMatchesSession(record, parentSession)) && liveRunMatchesSession(liveRun, parentSession)) {
|
|
458
|
+
runDirsToDelete.add(runDir);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
413
462
|
for (const [runDir, liveRun] of liveAgents) {
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
463
|
+
const matchingIds = [...liveRun.values()]
|
|
464
|
+
.filter((agent) => liveAgentMatchesSession(agent, parentSession))
|
|
465
|
+
.map((agent) => agent.agentId);
|
|
466
|
+
if (matchingIds.length === 0) continue;
|
|
467
|
+
mergeTargetIds(targets, runDir, matchingIds);
|
|
468
|
+
const records = recordsByRun.get(runDir) ?? [];
|
|
469
|
+
if (records.every((record) => recordMatchesSession(record, parentSession)) && liveRunMatchesSession(liveRun, parentSession)) {
|
|
470
|
+
runDirsToDelete.add(runDir);
|
|
471
|
+
}
|
|
417
472
|
}
|
|
418
|
-
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
targets: targetMapToTargets(targets),
|
|
476
|
+
runDirsToDelete: [...runDirsToDelete],
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function collectLiveShutdownPlan(liveAgents: Map<string, Map<string, LiveAgent>>): ShutdownPlan {
|
|
481
|
+
const targets = [...liveAgents.entries()].map(([runDir, liveRun]) => ({
|
|
419
482
|
runDir,
|
|
420
|
-
|
|
483
|
+
agentIds: [...liveRun.keys()],
|
|
421
484
|
}));
|
|
485
|
+
return { targets, runDirsToDelete: targets.map((target) => target.runDir) };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function groupRecordsByRun(records: SubagentSessionRecord[]): Map<string, SubagentSessionRecord[]> {
|
|
489
|
+
const grouped = new Map<string, SubagentSessionRecord[]>();
|
|
490
|
+
for (const record of records) {
|
|
491
|
+
const existing = grouped.get(record.runDir) ?? [];
|
|
492
|
+
existing.push(record);
|
|
493
|
+
grouped.set(record.runDir, existing);
|
|
494
|
+
}
|
|
495
|
+
return grouped;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function recordMatchesSession(record: SubagentSessionRecord, sessionFile: string): boolean {
|
|
499
|
+
return Boolean(record.parentSession && pathsEqual(record.parentSession, sessionFile));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function liveAgentMatchesSession(agent: LiveAgent, sessionFile: string): boolean {
|
|
503
|
+
return Boolean(agent.parentSession && pathsEqual(agent.parentSession, sessionFile));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function liveRunMatchesSession(liveRun: Map<string, LiveAgent> | undefined, sessionFile: string): boolean {
|
|
507
|
+
return !liveRun || [...liveRun.values()].every((agent) => liveAgentMatchesSession(agent, sessionFile));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function mergeTargetIds(targets: Map<string, Set<string>>, runDir: string, agentIds: string[]): void {
|
|
511
|
+
const target = targets.get(runDir) ?? new Set<string>();
|
|
512
|
+
for (const agentId of agentIds) target.add(agentId);
|
|
513
|
+
targets.set(runDir, target);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function targetMapToTargets(targets: Map<string, Set<string>>): ShutdownTarget[] {
|
|
517
|
+
return [...targets.entries()].map(([runDir, agentIds]) => ({ runDir, agentIds: [...agentIds] }));
|
|
422
518
|
}
|
|
423
519
|
|
|
424
520
|
function signalShutdownTargets(targets: ShutdownTarget[], signal: StopSignal): number {
|
|
@@ -10,13 +10,18 @@ export interface ContextUsageProvider {
|
|
|
10
10
|
|
|
11
11
|
export function isStaleExtensionContextError(error: unknown): boolean {
|
|
12
12
|
if (!(error instanceof Error)) return false
|
|
13
|
-
return /ctx is stale|stale after session replacement|stale after.*reload/i.test(error.message)
|
|
13
|
+
return /ctx is stale|stale ctx|stale after session replacement|stale after.*reload/i.test(error.message)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function ignoreStaleExtensionContextError(error: unknown): void {
|
|
17
17
|
if (!isStaleExtensionContextError(error)) throw error
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function isAgentBusyRaceError(error: unknown): boolean {
|
|
21
|
+
if (!(error instanceof Error)) return false
|
|
22
|
+
return /Agent is already processing(?: a prompt)?\.|Wait for completion before continuing|Specify streamingBehavior/i.test(error.message)
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
// Fork/newSession/switchSession/reload can invalidate a runner while late UI or
|
|
21
26
|
// context events from the old runner are still unwinding. In that race,
|
|
22
27
|
// ctx.getContextUsage() throws the Pi stale-ctx guard; treat it as unavailable
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent"
|
|
2
2
|
import type { AutocompleteItem } from "@earendil-works/pi-tui"
|
|
3
3
|
import type { DcpState } from "./state.js"
|
|
4
|
-
import type
|
|
4
|
+
import { modelKeysFromContext, resolveModelConfig, type DcpConfig } from "./config.js"
|
|
5
5
|
import type { DcpNudgeType } from "./pruner-types.js"
|
|
6
6
|
import { isToolRecordProtected, markToolPruned } from "./pruner.js"
|
|
7
7
|
import { safeGetContextUsage } from "../context-usage.js"
|
|
@@ -540,6 +540,7 @@ export function registerCommands(
|
|
|
540
540
|
async handler(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
541
541
|
const parts = args.trim().split(/\s+/).filter(Boolean)
|
|
542
542
|
const sub = parts[0] ?? ""
|
|
543
|
+
const effectiveConfig = resolveModelConfig(config, modelKeysFromContext(ctx))
|
|
543
544
|
|
|
544
545
|
try {
|
|
545
546
|
switch (sub) {
|
|
@@ -559,7 +560,7 @@ export function registerCommands(
|
|
|
559
560
|
case "sweep": {
|
|
560
561
|
const rawN = parts[1] !== undefined ? parseInt(parts[1], 10) : 0
|
|
561
562
|
const n = isNaN(rawN) || rawN < 0 ? 0 : rawN
|
|
562
|
-
await handleSweep(ctx, state,
|
|
563
|
+
await handleSweep(ctx, state, effectiveConfig, n)
|
|
563
564
|
break
|
|
564
565
|
}
|
|
565
566
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Type } from "typebox"
|
|
6
6
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
7
7
|
import type { DcpState } from "./state.js"
|
|
8
|
-
import type
|
|
8
|
+
import { modelKeysFromContext, resolveModelConfig, type DcpConfig } from "./config.js"
|
|
9
9
|
import { clearDcpNudgeAnchors } from "./pruner.js"
|
|
10
10
|
import type { DcpCompressionVisualDetails } from "./ui.js"
|
|
11
11
|
import { normalizeDcpContextUsage } from "./ui.js"
|
|
@@ -149,6 +149,11 @@ export function registerCompressTool(
|
|
|
149
149
|
}),
|
|
150
150
|
|
|
151
151
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
152
|
+
const effectiveConfig = resolveModelConfig(config, modelKeysFromContext(ctx))
|
|
153
|
+
if (!effectiveConfig.enabled) {
|
|
154
|
+
throw new Error("DCP is disabled for the active model")
|
|
155
|
+
}
|
|
156
|
+
|
|
152
157
|
const newBlockIds: number[] = []
|
|
153
158
|
const ranges = Array.isArray(params.ranges) ? params.ranges : []
|
|
154
159
|
const messages = Array.isArray(params.messages) ? params.messages : []
|
|
@@ -215,7 +220,7 @@ export function registerCompressTool(
|
|
|
215
220
|
anchorMessageId: anchor.stableId,
|
|
216
221
|
createdByToolCallId: _toolCallId,
|
|
217
222
|
state,
|
|
218
|
-
config,
|
|
223
|
+
config: effectiveConfig,
|
|
219
224
|
mode: "range",
|
|
220
225
|
})
|
|
221
226
|
const block = created.block
|
|
@@ -260,7 +265,7 @@ export function registerCompressTool(
|
|
|
260
265
|
skippedMessageIssues.push({ kind: "non-finite", messageId })
|
|
261
266
|
continue
|
|
262
267
|
}
|
|
263
|
-
if (
|
|
268
|
+
if (effectiveConfig.compress.protectUserMessages && meta.role === "user") {
|
|
264
269
|
skippedMessageIssues.push({ kind: "protected-user", messageId })
|
|
265
270
|
continue
|
|
266
271
|
}
|
|
@@ -291,7 +296,7 @@ export function registerCompressTool(
|
|
|
291
296
|
anchorMessageId: anchor.stableId,
|
|
292
297
|
createdByToolCallId: _toolCallId,
|
|
293
298
|
state,
|
|
294
|
-
config,
|
|
299
|
+
config: effectiveConfig,
|
|
295
300
|
mode: "message",
|
|
296
301
|
validatePlaceholders: false,
|
|
297
302
|
expandPlaceholders: false,
|