Tags: pifydev/compact
Tags
fix(compact): a synchronous compact throw no longer wedges auto-compa…
…ction (audit)
ctx.compact() can throw synchronously (not only via onError); that left the
module-level `compacting` flag stuck true, permanently disabling auto-compaction,
and in the input-preflight path left the promise unresolved so the turn stalled.
New src/dispatch.ts (dispatchCompact) try/catches ctx.compact() and routes a
synchronous throw through onError, which resets the flag and (in compactAndWait)
resolves so the input hook still returns {action:"continue"}.
dispatch unit tests + two extension integration tests. 49 pass, typecheck clean.
feat(compact): anchor injection, next-turn preflight, reserve-aware t… …rigger Improvements distilled from the pi compaction docs + tmustier/Chasen-Liao/capyup/ loka1 pi-auto-compact and sting8k/pi-vcc. All stay on @pify/compact's stance: delegate summarization to pi's LLM via ctx.compact(), act between turns, spend no tokens of its own, zero runtime deps. - Anchor injection (src/anchors.ts): deterministically extract the task + scope change, files modified/read, preferences, commits, and open blockers from the branch (regex/parse, no model call) and append them to the compaction instructions as "preserve these exact facts", so pi's summary keeps the load-bearing details instead of generalizing them away. Extraction patterns adapted from pi-vcc. Toggle: anchors (default on). - Preflight (input event): a big paste can overflow the next turn, which agent_settled can't foresee. On a fresh idle prompt, project current tokens + the prompt's estimated cost (text + images) and compact BEFORE sending if it would cross the threshold. Toggle: preflight (default on). - Reserve-aware ceiling: also compact at contextWindow − reserveTokens (from pi's own getCompactionReserveTokens()), so a response always has room — matters on small windows where a flat 80% leaves less than the reserve. Hard safety, bypasses the growth gate. Toggle: reserveAware (default on). - Sharper instructions: "don't make unfinished work sound finished; keep every pending check, tentative finding, blocker, and open question." - Hot-reload .pi/compact.json each agent_settled (keep last-good on parse error). - Soft-error handling: "Nothing to compact"/"Already compacted" report as info, not a warning. Tests: anchors (8), reserve-ceiling + settings cases. 42 pass, typecheck clean.
feat(compact): degeneration guard + growth-gated compaction Two long-session survival features, both zero-dep and zero-token, learned from ranxianglei/billion-context-pi. Degeneration guard (src/degenerate.ts, on the `context` hook, always on even while dormant): collapse long single-codepoint runs in outbound assistant text/thinking and, while the degenerate turn is the most recent one, append a one-shot recovery notice. Fixes a real abort-loop death — pi replays prior thinking to the provider every request, so a poisoned tail (observed: 4655× 「【」) biases the model to continue the run and each turn aborts the next. Persisted history is never touched; a clean context returns the same array reference so the prompt cache holds. Codepoint-safe, regex pre-screened, idempotent, fail-safe. Tunable via degenerationGuard / degenerationMinRun. Growth-gated trigger (decide.ts, opt-in via minGrowthTokens, default 0=off): the percentage trigger holds off until the context has grown by a real amount since the last compaction, stopping the thrash where a compaction frees little and usage stays near the threshold. The absolute ceiling ignores it — a hard ceiling is a safety and is never held back. Baseline resets to the post-compaction size after each run. Tests: degenerate.test.ts (13) + growth-gate and settings cases. 30 pass, typecheck clean.
feat: @pify/compact — proactive auto-compaction between turns When context usage crosses a threshold, compact so a long session never stalls at the wall or needs a restart. Fills the suite's #1 gap. Triggers on agent_settled (run fully settled — no retry/compaction/queued continuation), so ctx.compact() runs while the agent is IDLE and never aborts a live run — which sidesteps the spurious 'operation aborted' message and the fragile message-rewrite that mid-run compaction requires. The next turn just goes out smaller, current task preserved via customInstructions. Stays DORMANT unless pi's built-in compaction is off (SettingsManager. getCompactionEnabled === false), erring to dormant when unknown, so the two never double-compact. Never acts on pi's null 'unknown' usage reading, so it can't loop. Pure decision in src/decide.ts (6 tests) + settings (5 tests); threshold 1-99 via .pi/compact.json or PIFY_COMPACT_THRESHOLD. /autocompact status|now|on|off. ctx.compact takes no model override, so the deferred @pify/models isn't needed here — validated during the build. Zero runtime deps, no model calls of its own. Idea from HenryQW/pi-auto-compact (simplified: idle-only trigger, no abort rewrite).