Phase 12: recalculate pending on each compact — SPEC §16.6.
Every session_compact replans pendingReinject in defer mode; skipped reinject clears the queue unless manual compaction is waiting for the next user prompt. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+15
-13
@@ -16,8 +16,8 @@ import {
|
|||||||
} from "./compaction.js";
|
} from "./compaction.js";
|
||||||
import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js";
|
import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js";
|
||||||
import {
|
import {
|
||||||
|
applyPendingReinjectAfterCompact,
|
||||||
clearPendingReinjectOnUserPrompt,
|
clearPendingReinjectOnUserPrompt,
|
||||||
enqueueDeferredReinjectFromCompact,
|
|
||||||
planReinject,
|
planReinject,
|
||||||
sendImmediateReinjectAllFollowUp,
|
sendImmediateReinjectAllFollowUp,
|
||||||
sendImmediateReinjectIdle,
|
sendImmediateReinjectIdle,
|
||||||
@@ -91,34 +91,36 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
|
|
||||||
function handleSessionCompact(event: SessionCompactEvent, ctx: ExtensionContext): void {
|
function handleSessionCompact(event: SessionCompactEvent, ctx: ExtensionContext): void {
|
||||||
const settings = readSettings(ctx);
|
const settings = readSettings(ctx);
|
||||||
|
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
|
||||||
|
const planned = planReinject(state, settings, ctx, event, skills);
|
||||||
const shouldReinject = consumeCompactionOnSessionCompact(
|
const shouldReinject = consumeCompactionOnSessionCompact(
|
||||||
compactionRuntime,
|
compactionRuntime,
|
||||||
state,
|
state,
|
||||||
state.sessionOverride,
|
state.sessionOverride,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
|
const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride);
|
||||||
|
|
||||||
|
if (deliveryMode === "defer") {
|
||||||
|
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
||||||
|
persistState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
||||||
if (!shouldReinject) {
|
if (!shouldReinject) {
|
||||||
persistState();
|
persistState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
|
if (planned.length === 0) {
|
||||||
const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride);
|
|
||||||
if (deliveryMode === "defer") {
|
|
||||||
enqueueDeferredReinjectFromCompact(state, settings, ctx, event, skills);
|
|
||||||
persistState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const skillNames = planReinject(state, settings, ctx, event, skills);
|
|
||||||
if (skillNames.length === 0) {
|
|
||||||
persistState();
|
persistState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ctx.isIdle()) {
|
if (ctx.isIdle()) {
|
||||||
sendImmediateReinjectIdle(pi, skillNames, state, settings, skills, ctx);
|
sendImmediateReinjectIdle(pi, planned, state, settings, skills, ctx);
|
||||||
} else {
|
} else {
|
||||||
sendImmediateReinjectAllFollowUp(pi, skillNames, state, settings, skills, ctx);
|
sendImmediateReinjectAllFollowUp(pi, planned, state, settings, skills, ctx);
|
||||||
}
|
}
|
||||||
persistState();
|
persistState();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,25 @@ export function enqueueDeferredReinjectFromCompact(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update pending queue after session_compact; each compact recalculates or clears (SPEC §16.6).
|
||||||
|
* Manual compaction with default settings keeps stale pending until the next user prompt.
|
||||||
|
*/
|
||||||
|
export function applyPendingReinjectAfterCompact(
|
||||||
|
state: ExtensionState,
|
||||||
|
compactionRuntime: CompactionRuntime,
|
||||||
|
shouldReinject: boolean,
|
||||||
|
planned: readonly string[],
|
||||||
|
): void {
|
||||||
|
if (shouldReinject) {
|
||||||
|
state.pendingReinject = [...planned];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!compactionRuntime.clearPendingReinjectOnNextUserInput) {
|
||||||
|
state.pendingReinject = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Warn when re-injecting many skills; unlimited by default with soft warn above 3 (SPEC §15). */
|
/** Warn when re-injecting many skills; unlimited by default with soft warn above 3 (SPEC §15). */
|
||||||
export function maybeWarnManySkills(
|
export function maybeWarnManySkills(
|
||||||
skillCount: number,
|
skillCount: number,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { createCompactionRuntime } from "../src/compaction";
|
||||||
|
import { applyPendingReinjectAfterCompact } from "../src/reinject";
|
||||||
|
import { createInitialState } from "../src/state";
|
||||||
|
|
||||||
|
describe("applyPendingReinjectAfterCompact", () => {
|
||||||
|
it("replaces pending with a new plan on each compact", () => {
|
||||||
|
const runtime = createCompactionRuntime();
|
||||||
|
const state = createInitialState();
|
||||||
|
state.pendingReinject = ["alpha"];
|
||||||
|
|
||||||
|
applyPendingReinjectAfterCompact(state, runtime, true, ["beta", "gamma"]);
|
||||||
|
|
||||||
|
expect(state.pendingReinject).toEqual(["beta", "gamma"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears pending when reinject is skipped", () => {
|
||||||
|
const runtime = createCompactionRuntime();
|
||||||
|
const state = createInitialState();
|
||||||
|
state.pendingReinject = ["alpha"];
|
||||||
|
|
||||||
|
applyPendingReinjectAfterCompact(state, runtime, false, ["beta"]);
|
||||||
|
|
||||||
|
expect(state.pendingReinject).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps stale pending when manual compaction scheduled a user-prompt clear", () => {
|
||||||
|
const runtime = createCompactionRuntime();
|
||||||
|
runtime.clearPendingReinjectOnNextUserInput = true;
|
||||||
|
const state = createInitialState();
|
||||||
|
state.pendingReinject = ["alpha"];
|
||||||
|
|
||||||
|
applyPendingReinjectAfterCompact(state, runtime, false, ["beta"]);
|
||||||
|
|
||||||
|
expect(state.pendingReinject).toEqual(["alpha"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user