mirror of
https://github.com/wassname/pi-dynamic-context-pruning.git
synced 2026-06-27 18:05:36 +08:00
feat: auto-compaction when DCP blocks exceed threshold
- Add session_compact handler: deactivate all DCP blocks when pi compacts - Add context handler threshold: trigger ctx.compact() when DCP summary blocks occupy >= configurable fraction of context tokens (default 50%) - Add config.compact.autoCompactThreshold (0 = disabled, 0.5 = default) - Blocks are deactivated immediately on trigger to prevent re-triggering - Includes earlier fix: gap calculation scoped to requested range, clearer error text for overlap messages
This commit is contained in:
@@ -23,6 +23,9 @@ export interface DcpConfig {
|
||||
protectedTools: string[] // these tool outputs always protected from pruning
|
||||
protectUserMessages: boolean
|
||||
}
|
||||
compact: {
|
||||
autoCompactThreshold: number // 0-1, fraction of context that DCP blocks must occupy before triggering compaction (0 = disabled)
|
||||
}
|
||||
strategies: {
|
||||
deduplication: {
|
||||
enabled: boolean
|
||||
@@ -58,6 +61,9 @@ const DEFAULT_CONFIG: DcpConfig = {
|
||||
protectedTools: ["compress", "write", "edit"],
|
||||
protectUserMessages: false,
|
||||
},
|
||||
compact: {
|
||||
autoCompactThreshold: 0.5, // trigger compaction when DCP blocks occupy >= 50% of context tokens
|
||||
},
|
||||
strategies: {
|
||||
deduplication: {
|
||||
enabled: true,
|
||||
@@ -101,7 +107,10 @@ const DEFAULT_CONFIG_FILE_CONTENT = `{
|
||||
// "purgeErrors": { "enabled": true, "turns": 4, "protectedTools": [] }
|
||||
// },
|
||||
// "protectedFilePatterns": [],
|
||||
// "pruneNotification": "detailed"
|
||||
// "pruneNotification": "detailed",
|
||||
// "compact": {
|
||||
// "autoCompactThreshold": 0.5 // trigger pi compaction when DCP blocks >= 50% of context (0 = disabled)
|
||||
// }
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -201,49 +201,83 @@ export default function (pi: ExtensionAPI) {
|
||||
// In manual mode we still apply pruning strategies (if
|
||||
// automaticStrategies is on) but skip autonomous nudge injection.
|
||||
const usage = ctx.getContextUsage()
|
||||
if (usage && usage.tokens !== null && !state.manualMode) {
|
||||
const contextPercent = usage.tokens / usage.contextWindow
|
||||
if (usage && usage.tokens !== null) {
|
||||
// ── Auto-compaction: if DCP blocks exceed threshold, trigger pi compaction ──
|
||||
if (!state.manualMode && config.compact.autoCompactThreshold > 0) {
|
||||
const activeBlocks = state.compressionBlocks.filter((b) => b.active)
|
||||
const dcpBlockTokens = activeBlocks.reduce((sum, b) => sum + b.summaryTokenEstimate, 0)
|
||||
const blockFraction = dcpBlockTokens / usage.tokens
|
||||
|
||||
// Count tool calls since the last user message (used for iteration nudge).
|
||||
let toolCallsSinceLastUser = 0
|
||||
for (let i = prunedMessages.length - 1; i >= 0; i--) {
|
||||
const msg = prunedMessages[i] as any
|
||||
if (msg.role === "user") break
|
||||
if (msg.role === "toolResult") toolCallsSinceLastUser++
|
||||
if (blockFraction >= config.compact.autoCompactThreshold) {
|
||||
ctx.compact({
|
||||
customInstructions: "Include all DCP compression block summaries in the compaction summary.",
|
||||
})
|
||||
// Deactivate blocks immediately so we don't trigger again before compaction completes
|
||||
for (const block of activeBlocks) {
|
||||
block.active = false
|
||||
}
|
||||
saveState(pi, state)
|
||||
}
|
||||
}
|
||||
|
||||
const nudgeType = getNudgeType(
|
||||
contextPercent,
|
||||
state,
|
||||
config,
|
||||
toolCallsSinceLastUser,
|
||||
)
|
||||
if (!state.manualMode) {
|
||||
const contextPercent = usage.tokens / usage.contextWindow
|
||||
|
||||
if (nudgeType) {
|
||||
let nudgeText: string
|
||||
|
||||
if (nudgeType === "context-strong") {
|
||||
nudgeText = CONTEXT_LIMIT_NUDGE_STRONG
|
||||
} else if (nudgeType === "context-soft") {
|
||||
nudgeText = CONTEXT_LIMIT_NUDGE_SOFT
|
||||
} else if (nudgeType === "iteration") {
|
||||
nudgeText = ITERATION_NUDGE
|
||||
} else {
|
||||
// "turn"
|
||||
nudgeText = TURN_NUDGE
|
||||
// Count tool calls since the last user message (used for iteration nudge).
|
||||
let toolCallsSinceLastUser = 0
|
||||
for (let i = prunedMessages.length - 1; i >= 0; i--) {
|
||||
const msg = prunedMessages[i] as any
|
||||
if (msg.role === "user") break
|
||||
if (msg.role === "toolResult") toolCallsSinceLastUser++
|
||||
}
|
||||
|
||||
injectNudge(prunedMessages, nudgeText)
|
||||
state.nudgeCounter = 0
|
||||
} else {
|
||||
state.nudgeCounter++
|
||||
}
|
||||
}
|
||||
const nudgeType = getNudgeType(
|
||||
contextPercent,
|
||||
state,
|
||||
config,
|
||||
toolCallsSinceLastUser,
|
||||
)
|
||||
|
||||
if (nudgeType) {
|
||||
let nudgeText: string
|
||||
|
||||
if (nudgeType === "context-strong") {
|
||||
nudgeText = CONTEXT_LIMIT_NUDGE_STRONG
|
||||
} else if (nudgeType === "context-soft") {
|
||||
nudgeText = CONTEXT_LIMIT_NUDGE_SOFT
|
||||
} else if (nudgeType === "iteration") {
|
||||
nudgeText = ITERATION_NUDGE
|
||||
} else {
|
||||
// "turn"
|
||||
nudgeText = TURN_NUDGE
|
||||
}
|
||||
|
||||
injectNudge(prunedMessages, nudgeText)
|
||||
state.nudgeCounter = 0
|
||||
} else {
|
||||
state.nudgeCounter++
|
||||
}
|
||||
} // end !manualMode nudge block
|
||||
} // end usage check
|
||||
|
||||
return { messages: prunedMessages }
|
||||
})
|
||||
|
||||
// ── 11. agent_end: persist state after each agent run ────────────────────
|
||||
// ── 11. session_compact: deactivate all DCP blocks ───────────────────────
|
||||
// When pi's built-in compaction runs, it folds all prior context (including
|
||||
// DCP summary blocks) into a single compaction summary. All active DCP
|
||||
// blocks are now redundant — deactivate them.
|
||||
pi.on("session_compact", async (_event, _ctx) => {
|
||||
const activeBlocks = state.compressionBlocks.filter((b) => b.active)
|
||||
if (activeBlocks.length > 0) {
|
||||
for (const block of activeBlocks) {
|
||||
block.active = false
|
||||
}
|
||||
saveState(pi, state)
|
||||
}
|
||||
})
|
||||
|
||||
// ── 12. agent_end: persist state after each agent run ────────────────────
|
||||
pi.on("agent_end", async (_event, _ctx) => {
|
||||
saveState(pi, state)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user