Phase 15: add mid-turn defer reinject via sendMessage steer

Deliver pending skills on session_compact when agent is not idle, and
skip before_agent_start consume when steer already ran for compaction.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-18 22:59:59 +07:00
parent 37dd2211d7
commit 24a3d35c06
2 changed files with 52 additions and 0 deletions
+6
View File
@@ -8,6 +8,10 @@ export interface CompactionRuntime {
clearPendingReinjectOnNextUserInput: boolean; clearPendingReinjectOnNextUserInput: boolean;
/** Last compact firstKeptEntryId for debug kept-window snapshots (Phase 14). */ /** Last compact firstKeptEntryId for debug kept-window snapshots (Phase 14). */
lastCompactionFirstKeptEntryId: string | null; lastCompactionFirstKeptEntryId: string | null;
/** Last compaction entry id on session_compact (Phase 15 / B-003 dedup). */
lastCompactionEntryId: string | null;
/** Compaction id when mid-turn steer already delivered reinject (Phase 15 / §6.5.1). */
deferredDeliveredForCompactionId: string | null;
} }
export function createCompactionRuntime(): CompactionRuntime { export function createCompactionRuntime(): CompactionRuntime {
@@ -15,6 +19,8 @@ export function createCompactionRuntime(): CompactionRuntime {
pendingCompactionSource: null, pendingCompactionSource: null,
clearPendingReinjectOnNextUserInput: false, clearPendingReinjectOnNextUserInput: false,
lastCompactionFirstKeptEntryId: null, lastCompactionFirstKeptEntryId: null,
lastCompactionEntryId: null,
deferredDeliveredForCompactionId: null,
}; };
} }
+46
View File
@@ -352,6 +352,46 @@ export function filterPendingReinjectForConsume(
return resolved; return resolved;
} }
/**
* Mid-turn defer delivery via sendMessage/steer (SPEC §6.5.1, Phase 15 / B-003).
* Clears pendingReinject and records compaction id so before_agent_start does not duplicate.
*/
export function deliverDeferredReinjectSteer(
pi: ExtensionAPI,
state: ExtensionState,
settings: SkillReinjectSettings,
registeredSkills: readonly Pick<Skill, "name" | "filePath" | "baseDir">[],
compactionRuntime: CompactionRuntime,
compactionEntryId: string,
ctx?: ExtensionContext,
): boolean {
if (state.pendingReinject.length === 0) {
return false;
}
const pendingNames = filterPendingReinjectForConsume(
state.pendingReinject,
state,
settings,
registeredSkills,
ctx,
);
const content = buildDeferredReinjectContent(pendingNames, state, settings, registeredSkills, ctx);
state.pendingReinject = [];
if (!content) {
return false;
}
pi.sendMessage(
{
customType: DEFERRED_REINJECT_CUSTOM_TYPE,
content,
display: true,
},
{ deliverAs: "steer" },
);
compactionRuntime.deferredDeliveredForCompactionId = compactionEntryId;
return true;
}
/** /**
* Defer path on before_agent_start: inject one combined message, then clear queue (SPEC §6.5.1). * Defer path on before_agent_start: inject one combined message, then clear queue (SPEC §6.5.1).
* Returns undefined when pendingReinject is empty or manual compaction scheduled a clear (SPEC §16.5). * Returns undefined when pendingReinject is empty or manual compaction scheduled a clear (SPEC §16.5).
@@ -366,6 +406,12 @@ export function tryConsumeDeferredReinject(
if (compactionRuntime?.clearPendingReinjectOnNextUserInput) { if (compactionRuntime?.clearPendingReinjectOnNextUserInput) {
return undefined; return undefined;
} }
if (
compactionRuntime?.deferredDeliveredForCompactionId &&
compactionRuntime.deferredDeliveredForCompactionId === compactionRuntime.lastCompactionEntryId
) {
return undefined;
}
if (state.pendingReinject.length === 0) { if (state.pendingReinject.length === 0) {
return undefined; return undefined;
} }