@monotykamary/pi-vcc 0.7.4 → 0.8.0
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 +8 -2
- package/index.ts +2 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/core/invisible-continue.ts +27 -171
- package/src/hooks/before-compact.ts +115 -11
package/README.md
CHANGED
|
@@ -236,12 +236,18 @@ pi -e https://github.com/monotykamary/pi-vcc@tom
|
|
|
236
236
|
Once installed, pi-vcc registers a `session_before_compact` hook.
|
|
237
237
|
|
|
238
238
|
- Run `/pi-vcc` to trigger pi-vcc compaction manually.
|
|
239
|
-
- By default, pi-vcc handles all compaction paths (`/compact`, auto-threshold, `/pi-vcc`). Set `overrideDefaultCompaction: false` in the config to fall back to pi core's LLM-based compaction for `/compact` and auto-threshold.
|
|
239
|
+
- By default, pi-vcc handles all compaction paths (`/compact`, auto-threshold, `/pi-vcc`). Set `overrideDefaultCompaction: false` in the config to fall back to pi core's LLM-based compaction for `/compact` and auto-threshold. When pi-fabric's compaction engine is active, pi-vcc retains its threshold and recovery triggers but delegates non-`/pi-vcc` summary compilation to pi-fabric.
|
|
240
240
|
- To search older active-lineage history after compaction, use `vcc_recall`.
|
|
241
241
|
- To intentionally search across all lineages, pass `scope:"all"` to `vcc_recall` or run `/pi-vcc-recall <query> scope:all`.
|
|
242
242
|
- To search and feed results to agent yourself, run `/pi-vcc-recall <query> [page:N]`.
|
|
243
243
|
- Tip: type `/recall` and Pi will autocomplete to `/pi-vcc-recall`.
|
|
244
244
|
|
|
245
|
+
### pi-fabric interop
|
|
246
|
+
|
|
247
|
+
When `PI_FABRIC_COMPACTION_ENGINE=fabric`, pi-vcc defers non-explicit summary compilation to pi-fabric while preserving its proactive-threshold and Codex recovery trigger behavior. pi-fabric marks an event it has already claimed with `_fabricCompaction === true`, which prevents pi-vcc from replacing that result. An explicit `/pi-vcc` uses the `__pi_vcc__` instruction sentinel and always runs pi-vcc's deterministic compiler.
|
|
248
|
+
|
|
249
|
+
The precedence is: explicit `/pi-vcc` sentinel > pi-fabric engine > pi-vcc default override.
|
|
250
|
+
|
|
245
251
|
### How compaction works
|
|
246
252
|
|
|
247
253
|
Pi splits the conversation at the **last user message**. Everything after — the **kept tail** — stays intact and untouched. pi-vcc only summarizes the older portion before that cut point.
|
|
@@ -573,7 +579,7 @@ Config lives at `~/.pi/agent/pi-vcc-config.json` (auto-scaffolded on first load
|
|
|
573
579
|
}
|
|
574
580
|
```
|
|
575
581
|
|
|
576
|
-
- **`overrideDefaultCompaction`** *(default `true`)*: when `true` (default), pi-vcc handles all compaction paths (`/compact`, auto-threshold, `/pi-vcc`). Set `false` to let pi core handle `/compact` and auto-threshold compactions via its default LLM-based compaction.
|
|
582
|
+
- **`overrideDefaultCompaction`** *(default `true`)*: when `true` (default), pi-vcc handles all compaction paths (`/compact`, auto-threshold, `/pi-vcc`) unless the active pi-fabric engine owns summary compilation. Set `false` to let pi core handle `/compact` and auto-threshold compactions via its default LLM-based compaction. Explicit `/pi-vcc` always uses pi-vcc.
|
|
577
583
|
- **`debug`** *(default `false`)*: when `true`, each compaction writes detailed info to `/tmp/pi-vcc-debug.json` — message counts, cut boundary, summary preview, sections.
|
|
578
584
|
- **`modelThresholds`** *(default: none)*: per-model compaction thresholds. Keys match against `"provider/modelId"` (e.g., `"neuralwatt/zai-org/GLM-5.1-FP8"`) or just `"modelId"` (e.g., `"GLM-5.1"` — matched only when `provider/modelId` doesn't). Each value has:
|
|
579
585
|
- **`reserveTokens`**: tokens to reserve for the LLM response. Overrides pi-core's global `compaction.reserveTokens` for matching models. Controls *when* compaction triggers: `contextTokens > contextWindow − reserveTokens`. A higher value compacts earlier (more conservative); a lower value lets context grow larger. Takes precedence over `compactAtTokens` and `compactPercent` when multiple are set.
|
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/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-vcc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@monotykamary/pi-vcc",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.8.0",
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"knip": "6.14.1",
|
|
12
12
|
"typescript": "6.0.3"
|
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
|
}
|
|
@@ -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,7 +26,10 @@ 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;
|
|
31
|
+
let lastCompactDeferredToFabric = false;
|
|
32
|
+
let hasNotifiedFabricDeferral = false;
|
|
29
33
|
export const getLastCompactionStats = () => lastStats;
|
|
30
34
|
|
|
31
35
|
const formatTokens = (n: number): string => {
|
|
@@ -98,6 +102,19 @@ interface EntryWithMessage {
|
|
|
98
102
|
message: { role: string; content: unknown };
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
const isHiddenEmptyCustomMessage = (message: unknown): boolean => {
|
|
106
|
+
if (!message || typeof message !== "object") return false;
|
|
107
|
+
const candidate = message as {
|
|
108
|
+
role?: unknown;
|
|
109
|
+
content?: unknown;
|
|
110
|
+
display?: unknown;
|
|
111
|
+
};
|
|
112
|
+
if (candidate.role !== "custom" || candidate.display !== false) return false;
|
|
113
|
+
return candidate.content === "" || (
|
|
114
|
+
Array.isArray(candidate.content) && candidate.content.length === 0
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
101
118
|
export type OwnCutCancelReason =
|
|
102
119
|
| "no_live_messages"
|
|
103
120
|
| "too_few_live_messages";
|
|
@@ -265,7 +282,11 @@ export function buildOwnCut(branchEntries: any[], options?: { maxKeptTokens?: nu
|
|
|
265
282
|
for (let i = lastCompactionIdx + 1; i < branchEntries.length; i++) {
|
|
266
283
|
const e = branchEntries[i];
|
|
267
284
|
if (e.type === "compaction") continue;
|
|
268
|
-
if (
|
|
285
|
+
if (
|
|
286
|
+
e.type === "message" &&
|
|
287
|
+
e.message &&
|
|
288
|
+
!isHiddenEmptyCustomMessage(e.message)
|
|
289
|
+
) {
|
|
269
290
|
liveMessages.push({ entry: e, message: e.message });
|
|
270
291
|
}
|
|
271
292
|
}
|
|
@@ -275,7 +296,11 @@ export function buildOwnCut(branchEntries: any[], options?: { maxKeptTokens?: nu
|
|
|
275
296
|
if (!foundKept && e.id === lastKeptId) foundKept = true;
|
|
276
297
|
if (!foundKept) continue;
|
|
277
298
|
if (e.type === "compaction") continue;
|
|
278
|
-
if (
|
|
299
|
+
if (
|
|
300
|
+
e.type === "message" &&
|
|
301
|
+
e.message &&
|
|
302
|
+
!isHiddenEmptyCustomMessage(e.message)
|
|
303
|
+
) {
|
|
279
304
|
liveMessages.push({ entry: e, message: e.message });
|
|
280
305
|
}
|
|
281
306
|
}
|
|
@@ -413,8 +438,32 @@ export const shouldResumeAfterCompaction = (
|
|
|
413
438
|
return true;
|
|
414
439
|
};
|
|
415
440
|
|
|
441
|
+
export type CompactionCompletionMetadata = {
|
|
442
|
+
reason?: "manual" | "threshold" | "overflow";
|
|
443
|
+
willRetry?: boolean;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
export function shouldTriggerResumeForCompaction(
|
|
447
|
+
event: CompactionCompletionMetadata,
|
|
448
|
+
sessionIsIdle: boolean,
|
|
449
|
+
wasProactiveCompaction: boolean,
|
|
450
|
+
wasCodexRecoveryCompaction: boolean,
|
|
451
|
+
): boolean {
|
|
452
|
+
if (event.willRetry) return true;
|
|
453
|
+
if (wasCodexRecoveryCompaction || wasProactiveCompaction) return true;
|
|
454
|
+
if (event.reason === "manual" || event.reason === "overflow") return false;
|
|
455
|
+
if (event.reason === "threshold") return !sessionIsIdle;
|
|
456
|
+
|
|
457
|
+
// Pi before 0.79.10 did not expose compaction reason metadata. Be
|
|
458
|
+
// conservative there: an idle session may be manual or pre-prompt.
|
|
459
|
+
return !sessionIsIdle;
|
|
460
|
+
}
|
|
461
|
+
|
|
416
462
|
export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
417
463
|
pi.on("session_before_compact", (event, ctx) => {
|
|
464
|
+
// pi-fabric may run before pi-vcc and mark the shared event after claiming it.
|
|
465
|
+
if ((event as any)._fabricCompaction === true) return;
|
|
466
|
+
|
|
418
467
|
const { preparation, branchEntries, customInstructions } = event;
|
|
419
468
|
const settings = loadSettings();
|
|
420
469
|
|
|
@@ -451,6 +500,32 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
451
500
|
const isPiVccHandled =
|
|
452
501
|
isPiVcc || isCodexOutputLimitCompaction || isCodexContextOverflowCompaction;
|
|
453
502
|
|
|
503
|
+
// Explicit /pi-vcc takes precedence over the pi-fabric engine. For every
|
|
504
|
+
// other compaction, retain pi-vcc's trigger/recovery bookkeeping but let
|
|
505
|
+
// pi-fabric compile the summary.
|
|
506
|
+
if (!isPiVcc && process.env.PI_FABRIC_COMPACTION_ENGINE === "fabric") {
|
|
507
|
+
if (!isPiVccHandled && !settings.overrideDefaultCompaction) return;
|
|
508
|
+
if (isCodexContextOverflowCompaction) clearCodexContextOverflowPending();
|
|
509
|
+
|
|
510
|
+
lastCompactWasPiVcc = false;
|
|
511
|
+
lastCompactWasCodexRecovery =
|
|
512
|
+
isCodexOutputLimitCompaction || isCodexContextOverflowCompaction;
|
|
513
|
+
lastCompactWasProactive = isProactiveTriggerActive();
|
|
514
|
+
lastCompactHandledByVcc = true;
|
|
515
|
+
lastCompactDeferredToFabric = true;
|
|
516
|
+
|
|
517
|
+
if (settings.overrideDefaultCompaction && !hasNotifiedFabricDeferral) {
|
|
518
|
+
hasNotifiedFabricDeferral = true;
|
|
519
|
+
try {
|
|
520
|
+
ctx?.ui?.notify?.(
|
|
521
|
+
"pi-vcc: deferring compaction to pi-fabric engine (explicit /pi-vcc still uses pi-vcc)",
|
|
522
|
+
"info",
|
|
523
|
+
);
|
|
524
|
+
} catch {}
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
454
529
|
// Always handle explicit /pi-vcc and Codex recovery markers. Otherwise,
|
|
455
530
|
// only handle when the user opted in via settings.
|
|
456
531
|
if (!isPiVccHandled && !settings.overrideDefaultCompaction) return;
|
|
@@ -626,7 +701,9 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
626
701
|
lastCompactWasPiVcc = isPiVcc;
|
|
627
702
|
lastCompactWasCodexRecovery =
|
|
628
703
|
isCodexOutputLimitCompaction || isCodexContextOverflowCompaction;
|
|
704
|
+
lastCompactWasProactive = isProactiveTriggerActive();
|
|
629
705
|
lastCompactHandledByVcc = true;
|
|
706
|
+
lastCompactDeferredToFabric = false;
|
|
630
707
|
|
|
631
708
|
// Signal to neuralwatt-mcr that pi-vcc is handling compaction
|
|
632
709
|
// so it doesn't cancel the event. Without this flag, neuralwatt-mcr
|
|
@@ -644,6 +721,10 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
644
721
|
};
|
|
645
722
|
});
|
|
646
723
|
|
|
724
|
+
pi.on("session_start", () => {
|
|
725
|
+
hasNotifiedFabricDeferral = false;
|
|
726
|
+
});
|
|
727
|
+
|
|
647
728
|
// After compaction completes, check if the agent loop stalled and needs
|
|
648
729
|
// an invisible continue to resume. This handles threshold compaction
|
|
649
730
|
// where willRetry=false — pi-core doesn't auto-retry, and the agent loop
|
|
@@ -652,15 +733,20 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
652
733
|
// that isn't a clean end_turn), the agent was interrupted and should
|
|
653
734
|
// continue.
|
|
654
735
|
pi.on("session_compact", (event, ctx) => {
|
|
655
|
-
//
|
|
736
|
+
// Act when pi-vcc compiled the summary or tracked a compaction delegated
|
|
737
|
+
// to pi-fabric so proactive and Codex recovery continuation still works.
|
|
656
738
|
if (!lastCompactHandledByVcc) return;
|
|
657
739
|
const wasCodexRecoveryCompaction = lastCompactWasCodexRecovery;
|
|
740
|
+
const wasProactiveCompaction = lastCompactWasProactive;
|
|
741
|
+
const wasDeferredToFabric = lastCompactDeferredToFabric;
|
|
658
742
|
lastCompactHandledByVcc = false;
|
|
659
743
|
lastCompactWasCodexRecovery = false;
|
|
744
|
+
lastCompactWasProactive = false;
|
|
745
|
+
lastCompactDeferredToFabric = false;
|
|
660
746
|
|
|
661
|
-
// Fire success toast for /compact path only (delayed to let UI
|
|
662
|
-
// /pi-vcc
|
|
663
|
-
if (!lastCompactWasPiVcc) {
|
|
747
|
+
// Fire success toast for pi-vcc's /compact path only (delayed to let UI
|
|
748
|
+
// settle). /pi-vcc has its own callback, and pi-fabric owns delegated UI.
|
|
749
|
+
if (!lastCompactWasPiVcc && !wasDeferredToFabric) {
|
|
664
750
|
const stats = lastStats;
|
|
665
751
|
const count = countPiVccCompactionsFromSession(ctx?.sessionManager as any);
|
|
666
752
|
const compactionLabel = count > 0
|
|
@@ -708,11 +794,29 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
708
794
|
break;
|
|
709
795
|
}
|
|
710
796
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
797
|
+
// A core-owned retry also gets a marker. Agent.continue() drains the
|
|
798
|
+
// queued marker before checking the assistant-tail role, so no Agent
|
|
799
|
+
// prototype fallback is needed.
|
|
800
|
+
const completion = event as CompactionCompletionMetadata;
|
|
801
|
+
const legacyActiveCompaction =
|
|
802
|
+
completion.reason === undefined &&
|
|
803
|
+
completion.willRetry === undefined &&
|
|
804
|
+
!ctx.isIdle();
|
|
805
|
+
if (
|
|
806
|
+
!completion.willRetry &&
|
|
807
|
+
!legacyActiveCompaction &&
|
|
808
|
+
!shouldResumeAfterCompaction(lastMsg, wasCodexRecoveryCompaction)
|
|
809
|
+
) return;
|
|
810
|
+
if (!shouldTriggerResumeForCompaction(
|
|
811
|
+
completion,
|
|
812
|
+
ctx.isIdle(),
|
|
813
|
+
wasProactiveCompaction,
|
|
814
|
+
wasCodexRecoveryCompaction,
|
|
815
|
+
)) return;
|
|
816
|
+
|
|
817
|
+
// Queue through Pi's native follow-up path so a concurrent user prompt
|
|
818
|
+
// wins cleanly instead of racing a low-level Agent.prompt([]) call.
|
|
819
|
+
triggerInvisibleContinue(pi);
|
|
716
820
|
} catch {
|
|
717
821
|
// Non-critical — if context inspection fails, don't block compaction
|
|
718
822
|
}
|