@monotykamary/pi-vcc 0.7.3 → 0.7.5
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/index.ts +2 -5
- package/package.json +1 -1
- package/src/core/invisible-continue.ts +27 -171
- package/src/core/settings.ts +27 -1
- package/src/hooks/before-compact.ts +72 -7
- package/src/hooks/proactive-threshold.ts +13 -6
package/index.ts
CHANGED
|
@@ -5,17 +5,14 @@ import { registerProactiveThresholdHook } from "./src/hooks/proactive-threshold"
|
|
|
5
5
|
import { registerPiVccCommand } from "./src/commands/pi-vcc";
|
|
6
6
|
import { registerVccRecallCommand } from "./src/commands/vcc-recall";
|
|
7
7
|
import { registerRecallTool } from "./src/tools/recall";
|
|
8
|
-
import {
|
|
8
|
+
import { registerInvisibleContinue } from "./src/core/invisible-continue";
|
|
9
9
|
|
|
10
10
|
export default (pi: ExtensionAPI) => {
|
|
11
11
|
scaffoldSettings();
|
|
12
|
+
registerInvisibleContinue(pi);
|
|
12
13
|
registerBeforeCompactHook(pi);
|
|
13
14
|
registerProactiveThresholdHook(pi);
|
|
14
15
|
registerPiVccCommand(pi);
|
|
15
16
|
registerVccRecallCommand(pi);
|
|
16
17
|
registerRecallTool(pi);
|
|
17
|
-
|
|
18
|
-
pi.on("session_start", () => {
|
|
19
|
-
resetInvisibleContinue();
|
|
20
|
-
});
|
|
21
18
|
};
|
package/package.json
CHANGED
|
@@ -1,176 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* (willRetry=false, no queued messages).
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
let _agent: Agent | null = null;
|
|
17
|
-
let _continueInProgress = false;
|
|
18
|
-
|
|
19
|
-
// Timestamp of the last completed triggerInvisibleContinue().
|
|
20
|
-
// Used by the continue() monkey-patch to detect when triggerInvisibleContinue
|
|
21
|
-
// just ran (so it shouldn't fall back to prompt([]) again — the agent
|
|
22
|
-
// already continued). This prevents double continuation when both
|
|
23
|
-
// pi-retry and pi-vcc are installed.
|
|
24
|
-
let _lastInvisibleContinueTime = 0;
|
|
25
|
-
|
|
26
|
-
// Monkey-patch Agent.prototype.subscribe to capture the live instance.
|
|
27
|
-
// Chain the previous patch (if pi-retry already patched it) so both
|
|
28
|
-
// extensions can coexist.
|
|
29
|
-
const _prevSubscribe = Agent.prototype.subscribe;
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
Agent.prototype.subscribe = function (this: Agent, ...args: any[]) {
|
|
32
|
-
_agent = this;
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
-
return (_prevSubscribe as any).apply(this, args);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Monkey-patch continue() so that after compaction, when the rebuilt
|
|
38
|
-
// context ends with an assistant message, the agent loop can still
|
|
39
|
-
// continue. Without this patch, the session's continue() call throws
|
|
40
|
-
// "Cannot continue from message role: assistant" and the agent loop
|
|
41
|
-
// exits — leaving mid-task work unfinished.
|
|
42
|
-
//
|
|
43
|
-
// Note (pi 0.79+): Agent.continue() now drains queued steering/follow-up
|
|
44
|
-
// messages before throwing, so this throw path only fires when there are
|
|
45
|
-
// genuinely no queued messages — the prompt([]) fallback is still correct.
|
|
46
|
-
//
|
|
47
|
-
// The fix: when continue() would throw because the last message is an
|
|
48
|
-
// assistant, fall back to prompt([]) instead. prompt([]) doesn't check
|
|
49
|
-
// the last message role — it starts a fresh agent loop with the current
|
|
50
|
-
// context. This is equivalent to the invisible-continue mechanism
|
|
51
|
-
// (same call the extension fires from session_compact), but triggered
|
|
52
|
-
// from the session's own continue() path so the while loop stays alive.
|
|
53
|
-
//
|
|
54
|
-
// Chains the previous patch (pi-retry, pi-invisible-continue) so all
|
|
55
|
-
// mutexes are respected.
|
|
56
|
-
const _prevContinue = Agent.prototype.continue;
|
|
57
|
-
Agent.prototype.continue = function (this: Agent): Promise<void> {
|
|
58
|
-
const self = this;
|
|
59
|
-
return (async () => {
|
|
60
|
-
while (_continueInProgress) {
|
|
61
|
-
await new Promise(r => setTimeout(r, 10));
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
return await (_prevContinue as any).call(self);
|
|
65
|
-
} catch (e: unknown) {
|
|
66
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
67
|
-
|
|
68
|
-
// After compaction, the rebuilt context often ends with an assistant
|
|
69
|
-
// message. The original continue() throws "Cannot continue from
|
|
70
|
-
// message role: assistant". Instead of swallowing the error and
|
|
71
|
-
// letting the agent loop die (which leaves mid-task work unfinished),
|
|
72
|
-
// fall back to prompt([]). This restarts the agent loop with the
|
|
73
|
-
// compacted context — the LLM picks up where it left off.
|
|
74
|
-
//
|
|
75
|
-
// prompt([]) is safe regardless of last message role. The agent
|
|
76
|
-
// processes the current context and continues.
|
|
77
|
-
//
|
|
78
|
-
// We only do this when the agent is NOT currently processing
|
|
79
|
-
// (no other triggerInvisibleContinue in flight) to avoid races.
|
|
80
|
-
if (msg.includes("Cannot continue from message role") ||
|
|
81
|
-
msg.includes("Cannot continue from an assistant message")) {
|
|
82
|
-
// Check stopReason of the last message — only continue if
|
|
83
|
-
// the agent was mid-task (not a clean stop or user abort).
|
|
84
|
-
const lastMsg = self.state.messages[self.state.messages.length - 1];
|
|
85
|
-
if (lastMsg?.role === "assistant" &&
|
|
86
|
-
lastMsg.stopReason !== "stop" &&
|
|
87
|
-
lastMsg.stopReason !== "aborted") {
|
|
88
|
-
// Agent was mid-task — fall through to prompt([])
|
|
89
|
-
// (after the error-handling block)
|
|
90
|
-
} else {
|
|
91
|
-
// Agent finished cleanly or was aborted — don't continue.
|
|
92
|
-
// The session loop should exit (task complete).
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
// Fall back to prompt([]) to actually continue the agent.
|
|
96
|
-
// The while loop in _runAgentPrompt stays alive because prompt([])
|
|
97
|
-
// runs the agent (which emits events, updates _lastAssistantMessage,
|
|
98
|
-
// etc.).
|
|
99
|
-
//
|
|
100
|
-
// Guard: if triggerInvisibleContinue() just completed (within the
|
|
101
|
-
// last 500ms), the agent already continued — skip the fallback to
|
|
102
|
-
// avoid double continuation. This happens when both pi-retry and
|
|
103
|
-
// pi-vcc are installed: pi-retry's triggerInvisibleContinue runs
|
|
104
|
-
// first, then the session's continue() wrapper unblocks and would
|
|
105
|
-
// fall back to prompt([]) again.
|
|
106
|
-
if (!_continueInProgress && Date.now() - _lastInvisibleContinueTime > 500) {
|
|
107
|
-
_continueInProgress = true;
|
|
108
|
-
try {
|
|
109
|
-
await self.prompt([]);
|
|
110
|
-
} catch {
|
|
111
|
-
// Agent already processing or other transient error
|
|
112
|
-
} finally {
|
|
113
|
-
_continueInProgress = false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (msg.includes("Agent is already processing")) {
|
|
120
|
-
// Another extension is driving the agent — wait it out.
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
throw e;
|
|
124
|
-
}
|
|
125
|
-
})();
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
function sleep(ms: number): Promise<void> {
|
|
129
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export const VCC_RESUME_CUSTOM_TYPE = "pi-vcc:resume-after-compaction";
|
|
4
|
+
|
|
5
|
+
export function registerInvisibleContinue(pi: ExtensionAPI): void {
|
|
6
|
+
pi.on("context", (event) => {
|
|
7
|
+
const messages = event.messages.filter((message) => {
|
|
8
|
+
if (message.role !== "custom") return true;
|
|
9
|
+
return message.customType !== VCC_RESUME_CUSTOM_TYPE;
|
|
10
|
+
});
|
|
11
|
+
if (messages.length !== event.messages.length) return { messages };
|
|
12
|
+
});
|
|
130
13
|
}
|
|
131
14
|
|
|
132
15
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* MUST NOT be awaited — it schedules work that runs after the current
|
|
136
|
-
* session loop iteration finishes. Awaiting would deadlock inside the
|
|
137
|
-
* session's _handlePostAgentRun → _runAutoCompaction call stack.
|
|
16
|
+
* Queue a hidden follow-up through AgentSession. This keeps Pi's session-level
|
|
17
|
+
* busy state, retry, compaction, and native steer/follow-up queues coherent.
|
|
138
18
|
*/
|
|
139
|
-
export function triggerInvisibleContinue(): void {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
await sleep(50);
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
// Await so _continueInProgress stays true for the full cycle.
|
|
156
|
-
await _agent!.prompt([]);
|
|
157
|
-
} catch {
|
|
158
|
-
// Ignore — if prompt throws, something else is driving.
|
|
159
|
-
}
|
|
160
|
-
} finally {
|
|
161
|
-
_continueInProgress = false;
|
|
162
|
-
// Record completion time so the continue() monkey-patch can
|
|
163
|
-
// detect that an invisible continue just ran and avoid firing
|
|
164
|
-
// a duplicate prompt([]) (RC7: double continuation guard).
|
|
165
|
-
_lastInvisibleContinueTime = Date.now();
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
void run();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Reset state on new session. */
|
|
173
|
-
export function resetInvisibleContinue(): void {
|
|
174
|
-
_continueInProgress = false;
|
|
175
|
-
_lastInvisibleContinueTime = 0;
|
|
19
|
+
export function triggerInvisibleContinue(pi: ExtensionAPI): void {
|
|
20
|
+
pi.sendMessage(
|
|
21
|
+
{
|
|
22
|
+
customType: VCC_RESUME_CUSTOM_TYPE,
|
|
23
|
+
content: [],
|
|
24
|
+
display: false,
|
|
25
|
+
details: undefined,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
triggerTurn: true,
|
|
29
|
+
deliverAs: "followUp",
|
|
30
|
+
},
|
|
31
|
+
);
|
|
176
32
|
}
|
package/src/core/settings.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
2
|
import { dirname, join } from "path";
|
|
3
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
5
|
const settingsPath = (): string =>
|
|
6
6
|
process.env.PI_VCC_CONFIG_PATH ?? join(getAgentDir(), "pi-vcc-config.json");
|
|
@@ -181,6 +181,32 @@ const readJson = (path: string): Record<string, unknown> | null => {
|
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
const readPiCoreCompactionEnabled = (path: string): boolean | undefined => {
|
|
185
|
+
const parsed = readJson(path);
|
|
186
|
+
if (!parsed) return undefined;
|
|
187
|
+
const enabled = (parsed as { compaction?: { enabled?: unknown } }).compaction?.enabled;
|
|
188
|
+
return typeof enabled === "boolean" ? enabled : undefined;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Read pi-core's effective `compaction.enabled` setting.
|
|
193
|
+
*
|
|
194
|
+
* pi-core's `_checkCompaction` bails immediately when this is false, so neither
|
|
195
|
+
* overflow nor threshold auto-compaction fires. When that happens pi-vcc must
|
|
196
|
+
* drive compaction itself via `ctx.compact()` (the manual path skips the
|
|
197
|
+
* `enabled` gate). Mirrors pi-core's merge: project (`<cwd>/<CONFIG_DIR_NAME>/
|
|
198
|
+
* settings.json`) overrides global (`<agentDir>/settings.json`); defaults true.
|
|
199
|
+
*/
|
|
200
|
+
export function isPiCoreCompactionEnabled(projectCwd?: string): boolean {
|
|
201
|
+
const globalEnabled = readPiCoreCompactionEnabled(join(getAgentDir(), "settings.json"));
|
|
202
|
+
if (projectCwd) {
|
|
203
|
+
const projectEnabled = readPiCoreCompactionEnabled(join(projectCwd, CONFIG_DIR_NAME, "settings.json"));
|
|
204
|
+
if (typeof projectEnabled === "boolean") return projectEnabled;
|
|
205
|
+
}
|
|
206
|
+
if (typeof globalEnabled === "boolean") return globalEnabled;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
184
210
|
export function loadSettings(): PiVccSettings {
|
|
185
211
|
const parsed = readJson(settingsPath());
|
|
186
212
|
if (!parsed || typeof parsed !== "object") return { ...DEFAULT_SETTINGS };
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../core/codex-output-limit";
|
|
12
12
|
import { loadSettings, type PiVccSettings } from "../core/settings";
|
|
13
13
|
import { triggerInvisibleContinue } from "../core/invisible-continue";
|
|
14
|
+
import { isProactiveTriggerActive } from "./proactive-threshold";
|
|
14
15
|
import { countPiVccCompactionsFromSession, ordinalSuffix } from "../core/compaction-count";
|
|
15
16
|
import type { PiVccCompactionDetails } from "../details";
|
|
16
17
|
|
|
@@ -25,6 +26,7 @@ export interface CompactionStats {
|
|
|
25
26
|
let lastStats: CompactionStats | null = null;
|
|
26
27
|
let lastCompactWasPiVcc = false;
|
|
27
28
|
let lastCompactWasCodexRecovery = false;
|
|
29
|
+
let lastCompactWasProactive = false;
|
|
28
30
|
let lastCompactHandledByVcc = false;
|
|
29
31
|
export const getLastCompactionStats = () => lastStats;
|
|
30
32
|
|
|
@@ -98,6 +100,19 @@ interface EntryWithMessage {
|
|
|
98
100
|
message: { role: string; content: unknown };
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
const isHiddenEmptyCustomMessage = (message: unknown): boolean => {
|
|
104
|
+
if (!message || typeof message !== "object") return false;
|
|
105
|
+
const candidate = message as {
|
|
106
|
+
role?: unknown;
|
|
107
|
+
content?: unknown;
|
|
108
|
+
display?: unknown;
|
|
109
|
+
};
|
|
110
|
+
if (candidate.role !== "custom" || candidate.display !== false) return false;
|
|
111
|
+
return candidate.content === "" || (
|
|
112
|
+
Array.isArray(candidate.content) && candidate.content.length === 0
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
101
116
|
export type OwnCutCancelReason =
|
|
102
117
|
| "no_live_messages"
|
|
103
118
|
| "too_few_live_messages";
|
|
@@ -265,7 +280,11 @@ export function buildOwnCut(branchEntries: any[], options?: { maxKeptTokens?: nu
|
|
|
265
280
|
for (let i = lastCompactionIdx + 1; i < branchEntries.length; i++) {
|
|
266
281
|
const e = branchEntries[i];
|
|
267
282
|
if (e.type === "compaction") continue;
|
|
268
|
-
if (
|
|
283
|
+
if (
|
|
284
|
+
e.type === "message" &&
|
|
285
|
+
e.message &&
|
|
286
|
+
!isHiddenEmptyCustomMessage(e.message)
|
|
287
|
+
) {
|
|
269
288
|
liveMessages.push({ entry: e, message: e.message });
|
|
270
289
|
}
|
|
271
290
|
}
|
|
@@ -275,7 +294,11 @@ export function buildOwnCut(branchEntries: any[], options?: { maxKeptTokens?: nu
|
|
|
275
294
|
if (!foundKept && e.id === lastKeptId) foundKept = true;
|
|
276
295
|
if (!foundKept) continue;
|
|
277
296
|
if (e.type === "compaction") continue;
|
|
278
|
-
if (
|
|
297
|
+
if (
|
|
298
|
+
e.type === "message" &&
|
|
299
|
+
e.message &&
|
|
300
|
+
!isHiddenEmptyCustomMessage(e.message)
|
|
301
|
+
) {
|
|
279
302
|
liveMessages.push({ entry: e, message: e.message });
|
|
280
303
|
}
|
|
281
304
|
}
|
|
@@ -413,6 +436,27 @@ export const shouldResumeAfterCompaction = (
|
|
|
413
436
|
return true;
|
|
414
437
|
};
|
|
415
438
|
|
|
439
|
+
export type CompactionCompletionMetadata = {
|
|
440
|
+
reason?: "manual" | "threshold" | "overflow";
|
|
441
|
+
willRetry?: boolean;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
export function shouldTriggerResumeForCompaction(
|
|
445
|
+
event: CompactionCompletionMetadata,
|
|
446
|
+
sessionIsIdle: boolean,
|
|
447
|
+
wasProactiveCompaction: boolean,
|
|
448
|
+
wasCodexRecoveryCompaction: boolean,
|
|
449
|
+
): boolean {
|
|
450
|
+
if (event.willRetry) return true;
|
|
451
|
+
if (wasCodexRecoveryCompaction || wasProactiveCompaction) return true;
|
|
452
|
+
if (event.reason === "manual" || event.reason === "overflow") return false;
|
|
453
|
+
if (event.reason === "threshold") return !sessionIsIdle;
|
|
454
|
+
|
|
455
|
+
// Pi before 0.79.10 did not expose compaction reason metadata. Be
|
|
456
|
+
// conservative there: an idle session may be manual or pre-prompt.
|
|
457
|
+
return !sessionIsIdle;
|
|
458
|
+
}
|
|
459
|
+
|
|
416
460
|
export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
417
461
|
pi.on("session_before_compact", (event, ctx) => {
|
|
418
462
|
const { preparation, branchEntries, customInstructions } = event;
|
|
@@ -626,6 +670,7 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
626
670
|
lastCompactWasPiVcc = isPiVcc;
|
|
627
671
|
lastCompactWasCodexRecovery =
|
|
628
672
|
isCodexOutputLimitCompaction || isCodexContextOverflowCompaction;
|
|
673
|
+
lastCompactWasProactive = isProactiveTriggerActive();
|
|
629
674
|
lastCompactHandledByVcc = true;
|
|
630
675
|
|
|
631
676
|
// Signal to neuralwatt-mcr that pi-vcc is handling compaction
|
|
@@ -655,8 +700,10 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
655
700
|
// Only act when pi-vcc drove the compaction
|
|
656
701
|
if (!lastCompactHandledByVcc) return;
|
|
657
702
|
const wasCodexRecoveryCompaction = lastCompactWasCodexRecovery;
|
|
703
|
+
const wasProactiveCompaction = lastCompactWasProactive;
|
|
658
704
|
lastCompactHandledByVcc = false;
|
|
659
705
|
lastCompactWasCodexRecovery = false;
|
|
706
|
+
lastCompactWasProactive = false;
|
|
660
707
|
|
|
661
708
|
// Fire success toast for /compact path only (delayed to let UI settle).
|
|
662
709
|
// /pi-vcc path uses its own onComplete callback in the command handler.
|
|
@@ -708,11 +755,29 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
708
755
|
break;
|
|
709
756
|
}
|
|
710
757
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
758
|
+
// A core-owned retry also gets a marker. Agent.continue() drains the
|
|
759
|
+
// queued marker before checking the assistant-tail role, so no Agent
|
|
760
|
+
// prototype fallback is needed.
|
|
761
|
+
const completion = event as CompactionCompletionMetadata;
|
|
762
|
+
const legacyActiveCompaction =
|
|
763
|
+
completion.reason === undefined &&
|
|
764
|
+
completion.willRetry === undefined &&
|
|
765
|
+
!ctx.isIdle();
|
|
766
|
+
if (
|
|
767
|
+
!completion.willRetry &&
|
|
768
|
+
!legacyActiveCompaction &&
|
|
769
|
+
!shouldResumeAfterCompaction(lastMsg, wasCodexRecoveryCompaction)
|
|
770
|
+
) return;
|
|
771
|
+
if (!shouldTriggerResumeForCompaction(
|
|
772
|
+
completion,
|
|
773
|
+
ctx.isIdle(),
|
|
774
|
+
wasProactiveCompaction,
|
|
775
|
+
wasCodexRecoveryCompaction,
|
|
776
|
+
)) return;
|
|
777
|
+
|
|
778
|
+
// Queue through Pi's native follow-up path so a concurrent user prompt
|
|
779
|
+
// wins cleanly instead of racing a low-level Agent.prompt([]) call.
|
|
780
|
+
triggerInvisibleContinue(pi);
|
|
716
781
|
} catch {
|
|
717
782
|
// Non-critical — if context inspection fails, don't block compaction
|
|
718
783
|
}
|
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
isCodexOutputLimitError,
|
|
8
8
|
markCodexContextOverflowPending,
|
|
9
9
|
} from "../core/codex-output-limit";
|
|
10
|
-
import { loadSettings, getModelThreshold, resolveTriggerTokens } from "../core/settings";
|
|
10
|
+
import { loadSettings, getModelThreshold, isPiCoreCompactionEnabled, resolveTriggerTokens } from "../core/settings";
|
|
11
11
|
|
|
12
12
|
type ProactiveContext = {
|
|
13
13
|
model?: any;
|
|
14
14
|
getContextUsage?: () => any;
|
|
15
15
|
compact?: (options?: any) => void;
|
|
16
16
|
ui?: any;
|
|
17
|
+
sessionManager?: { getCwd?: () => string };
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const formatTokens = (n: number): string => {
|
|
@@ -182,11 +183,17 @@ export const registerProactiveThresholdHook = (pi: ExtensionAPI) => {
|
|
|
182
183
|
}
|
|
183
184
|
if (isCodexContextOverflowError(lastMessage)) {
|
|
184
185
|
markCodexContextOverflowPending();
|
|
185
|
-
// pi-core's overflow
|
|
186
|
-
// the current model identity.
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
186
|
+
// pi-core's overflow compaction only fires when compaction is enabled AND
|
|
187
|
+
// the assistant message carries the current model identity. If either
|
|
188
|
+
// is missing pi-core bails (or its LLM compaction would re-overflow), so
|
|
189
|
+
// pi-vcc must drive the recovery compaction itself via ctx.compact(),
|
|
190
|
+
// whose manual path skips the `enabled` gate and uses pi-vcc's static
|
|
191
|
+
// summary. When pi-core will handle it, defer to avoid a racing second
|
|
192
|
+
// compaction that would abort pi-core's own retry.
|
|
193
|
+
const piCoreWillHandle =
|
|
194
|
+
hasCurrentModelIdentity(lastMessage, ctx.model) &&
|
|
195
|
+
isPiCoreCompactionEnabled(ctx.sessionManager?.getCwd?.());
|
|
196
|
+
if (!piCoreWillHandle) {
|
|
190
197
|
triggerCodexContextOverflowCompaction(ctx);
|
|
191
198
|
}
|
|
192
199
|
return;
|