@monotykamary/pi-vcc 0.7.1 → 0.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monotykamary/pi-vcc",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Algorithmic conversation compactor for pi - transcript-preserving structured summaries, no LLM calls",
5
5
  "main": "index.ts",
6
6
  "keywords": [
@@ -0,0 +1,26 @@
1
+ export const CODEX_OUTPUT_LIMIT_COMPACT_INSTRUCTION = "__pi_vcc_codex_output_limit__";
2
+
3
+ /** Identify the Codex provider error used when a response reaches its output cap. */
4
+ export const isCodexOutputLimitError = (message: unknown): boolean => {
5
+ if (!message || typeof message !== "object") return false;
6
+
7
+ const candidate = message as {
8
+ role?: unknown;
9
+ provider?: unknown;
10
+ api?: unknown;
11
+ stopReason?: unknown;
12
+ errorMessage?: unknown;
13
+ };
14
+
15
+ const isCodexMessage =
16
+ candidate.provider === "openai-codex" ||
17
+ candidate.api === "openai-codex-responses";
18
+
19
+ return (
20
+ candidate.role === "assistant" &&
21
+ isCodexMessage &&
22
+ candidate.stopReason === "error" &&
23
+ typeof candidate.errorMessage === "string" &&
24
+ /maximum output token limit/i.test(candidate.errorMessage)
25
+ );
26
+ };
@@ -1,6 +1,10 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { writeFileSync } from "fs";
3
3
  import { compile, type CompileInput } from "../core/summarize";
4
+ import {
5
+ CODEX_OUTPUT_LIMIT_COMPACT_INSTRUCTION,
6
+ isCodexOutputLimitError,
7
+ } from "../core/codex-output-limit";
4
8
  import { loadSettings, type PiVccSettings } from "../core/settings";
5
9
  import { triggerInvisibleContinue } from "../core/invisible-continue";
6
10
  import { countPiVccCompactionsFromSession, ordinalSuffix } from "../core/compaction-count";
@@ -16,6 +20,7 @@ export interface CompactionStats {
16
20
 
17
21
  let lastStats: CompactionStats | null = null;
18
22
  let lastCompactWasPiVcc = false;
23
+ let lastCompactWasCodexOutputLimit = false;
19
24
  let lastCompactHandledByVcc = false;
20
25
  export const getLastCompactionStats = () => lastStats;
21
26
 
@@ -387,6 +392,20 @@ const REASON_MESSAGES: Record<OwnCutCancelReason, string> = {
387
392
  too_few_live_messages: "pi-vcc: Too few messages to compact",
388
393
  };
389
394
 
395
+ export const shouldResumeAfterCompaction = (
396
+ lastMsg: unknown,
397
+ allowCodexOutputLimit = false,
398
+ ): boolean => {
399
+ if (!lastMsg || typeof lastMsg !== "object") return false;
400
+ const message = lastMsg as { role?: unknown; stopReason?: unknown };
401
+ if (message.role !== "assistant") return false;
402
+ if (message.stopReason === "stop" || message.stopReason === "aborted") return false;
403
+ if (message.stopReason === "error") {
404
+ return allowCodexOutputLimit && isCodexOutputLimitError(lastMsg);
405
+ }
406
+ return true;
407
+ };
408
+
390
409
  export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
391
410
  pi.on("session_before_compact", (event, ctx) => {
392
411
  const { preparation, branchEntries, customInstructions } = event;
@@ -409,10 +428,13 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
409
428
  // per-model threshold's perspective, but this is preferable to blocking
410
429
  // an explicit user action (/compact).
411
430
  const isPiVcc = customInstructions === PI_VCC_COMPACT_INSTRUCTION;
431
+ const isCodexOutputLimitCompaction =
432
+ customInstructions === CODEX_OUTPUT_LIMIT_COMPACT_INSTRUCTION;
433
+ const isPiVccHandled = isPiVcc || isCodexOutputLimitCompaction;
412
434
 
413
- // Always handle explicit /pi-vcc marker.
435
+ // Always handle explicit /pi-vcc and Codex output-limit markers.
414
436
  // Otherwise, only handle when user opted in via settings.
415
- if (!isPiVcc && !settings.overrideDefaultCompaction) return;
437
+ if (!isPiVccHandled && !settings.overrideDefaultCompaction) return;
416
438
 
417
439
  // Budget the kept tail so that summary + kept + system/tools + the model's
418
440
  // OUTPUT budget (maxTokens) all fit in the window. Without reserving
@@ -582,6 +604,7 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
582
604
  };
583
605
 
584
606
  lastCompactWasPiVcc = isPiVcc;
607
+ lastCompactWasCodexOutputLimit = isCodexOutputLimitCompaction;
585
608
  lastCompactHandledByVcc = true;
586
609
 
587
610
  // Signal to neuralwatt-mcr that pi-vcc is handling compaction
@@ -610,7 +633,9 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
610
633
  pi.on("session_compact", (event, ctx) => {
611
634
  // Only act when pi-vcc drove the compaction
612
635
  if (!lastCompactHandledByVcc) return;
636
+ const wasCodexOutputLimitCompaction = lastCompactWasCodexOutputLimit;
613
637
  lastCompactHandledByVcc = false;
638
+ lastCompactWasCodexOutputLimit = false;
614
639
 
615
640
  // Fire success toast for /compact path only (delayed to let UI settle).
616
641
  // /pi-vcc path uses its own onComplete callback in the command handler.
@@ -641,12 +666,14 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
641
666
  // - Last message is user/toolResult (agent can continue naturally)
642
667
  // - Last message is assistant with stopReason=stop (task finished)
643
668
  // - Last message is assistant with stopReason=aborted (user cancelled)
644
- // - Last message is assistant with stopReason=error (pi-retry handles
645
- // retry via its agent_end handler — avoid duplicate triggerInvisibleContinue)
669
+ // - Last message is assistant with stopReason=error, unless this was
670
+ // the pi-vcc Codex maximum-output recovery compaction
646
671
  //
647
672
  // We DO continue when:
648
673
  // - Last message is assistant with stopReason=toolUse (mid-tool cycle)
649
674
  // - Last message is assistant with stopReason=length (hit max tokens)
675
+ // - Last message is the known Codex maximum-output error from the
676
+ // pi-vcc recovery compaction
650
677
  // - Compact-all (firstKeptEntryId="") — context is just the summary,
651
678
  // the agent needs to re-enter the loop to continue the task
652
679
  try {
@@ -660,21 +687,10 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
660
687
  break;
661
688
  }
662
689
  }
663
- if (!lastMsg || lastMsg.role !== "assistant") return;
664
-
665
- // Agent completed its turn cleanly — no continuation needed
666
- if (lastMsg.stopReason === "stop") return;
667
-
668
- // Agent was aborted by user — don't auto-continue
669
- if (lastMsg.stopReason === "aborted") return;
670
-
671
- // Agent hit an error — pi-retry handles this via its agent_end handler.
672
- // If we also fire triggerInvisibleContinue, both extensions race
673
- // to call prompt([]), causing "Agent is already processing" (wasteful)
674
- // or a duplicate continuation. Let pi-retry own error retries.
675
- if (lastMsg.stopReason === "error") return;
690
+ if (!shouldResumeAfterCompaction(lastMsg, wasCodexOutputLimitCompaction)) return;
676
691
 
677
- // Agent was mid-task (toolUse or length) — needs to continue.
692
+ // Agent was mid-task (toolUse or length). The Codex recovery marker
693
+ // additionally permits its maximum-output error to continue.
678
694
  triggerInvisibleContinue();
679
695
  } catch {
680
696
  // Non-critical — if context inspection fails, don't block compaction
@@ -1,6 +1,17 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ CODEX_OUTPUT_LIMIT_COMPACT_INSTRUCTION,
4
+ isCodexOutputLimitError,
5
+ } from "../core/codex-output-limit";
2
6
  import { loadSettings, getModelThreshold, resolveTriggerTokens } from "../core/settings";
3
7
 
8
+ type ProactiveContext = {
9
+ model?: any;
10
+ getContextUsage?: () => any;
11
+ compact?: (options?: any) => void;
12
+ ui?: any;
13
+ };
14
+
4
15
  const formatTokens = (n: number): string => {
5
16
  if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
6
17
  return String(n);
@@ -36,7 +47,7 @@ export const resetProactiveState = () => {
36
47
  * if so. Safe to call from multiple event handlers — cooldown prevents
37
48
  * double-triggering.
38
49
  */
39
- const checkAndTrigger = (ctx: { model?: any; getContextUsage?: () => any; compact?: () => void; ui?: any }, source: string) => {
50
+ const checkAndTrigger = (ctx: ProactiveContext, source: string) => {
40
51
  const settings = loadSettings();
41
52
  const threshold = getModelThreshold(settings, ctx.model);
42
53
 
@@ -78,6 +89,32 @@ const checkAndTrigger = (ctx: { model?: any; getContextUsage?: () => any; compac
78
89
  ctx.compact?.();
79
90
  };
80
91
 
92
+ /** Force compaction for Codex responses that report an output limit as an error. */
93
+ const triggerCodexOutputLimitCompaction = (ctx: ProactiveContext) => {
94
+ if (isCoolingDown()) return;
95
+
96
+ try {
97
+ ctx?.ui?.notify?.(
98
+ "pi-vcc: Codex reached its maximum output token limit. Compacting...",
99
+ "info",
100
+ );
101
+ } catch {}
102
+
103
+ setCooldown();
104
+ proactiveTriggerActive = true;
105
+ ctx.compact?.({ customInstructions: CODEX_OUTPUT_LIMIT_COMPACT_INSTRUCTION });
106
+ };
107
+
108
+ const lastAssistantMessage = (event: unknown): unknown => {
109
+ const messages = (event as any)?.messages;
110
+ if (!Array.isArray(messages)) return undefined;
111
+
112
+ for (let i = messages.length - 1; i >= 0; i--) {
113
+ if (messages[i]?.role === "assistant") return messages[i];
114
+ }
115
+ return undefined;
116
+ };
117
+
81
118
  /**
82
119
  * Registers proactive configured compaction thresholds.
83
120
  *
@@ -105,8 +142,14 @@ const checkAndTrigger = (ctx: { model?: any; getContextUsage?: () => any; compac
105
142
  * "Compacting..." then "Skipped compaction" notifications.
106
143
  */
107
144
  export const registerProactiveThresholdHook = (pi: ExtensionAPI) => {
108
- // Proactive compaction after each agent run
109
- pi.on("agent_end", (_event, ctx) => {
145
+ // Codex reports some output-limit responses as errors instead of the
146
+ // standard "length" stop reason. The error has no usable context usage,
147
+ // so pi-core cannot discover the need to compact from its normal checks.
148
+ pi.on("agent_end", (event, ctx) => {
149
+ if (isCodexOutputLimitError(lastAssistantMessage(event))) {
150
+ triggerCodexOutputLimitCompaction(ctx);
151
+ return;
152
+ }
110
153
  checkAndTrigger(ctx, "auto");
111
154
  });
112
155