feat(coding-agent): added async compaction and in-place handoff

- Added compaction.asyncEnabled (Async Compaction, default on): when
  context enters the pre-threshold band [threshold - lead, threshold)
  with lead = clamp(threshold * 0.125, 8192, 32000), maintenance
  speculatively summarizes in the background off a branch snapshot
  (first configured LLM-backed method: remote, handoff, or soft) using
  a side session id isolated from the live turn. Crossing the threshold
  splices the armed result in instantly instead of blocking on a
  summarization round-trip. Armed results are invalidated by branch
  changes, reset boundaries, model switches that strand provider-native
  replay payloads, and context growth past keepRecentTokens (which
  re-speculates); extensions registering session_before_compact keep
  exact blocking semantics (speculation disabled).
- Reworked handoff to commit in place: /handoff and the auto handoff
  method now write the generated document as a regular compaction entry
  on the current session (summary = document + <files> tag, cut from
  prepareCompaction) instead of starting a new session. SessionHandoff
  shrank to a document generator; session_before_switch/session_switch
  no longer fire with reason "handoff"; mid-turn maintenance no longer
  suppresses the handoff preference; overflow recovery can apply an
  armed handoff result.
- Extracted the shared auto-compaction commit tail
  (#commitAutoCompactionResult / #commitCompactionEntry) used by the
  blocking production path, the armed speculative apply, manual
  compaction, and manual handoff.
- Status line pulses the auto-compact icon while a speculation runs and
  holds it in accent once a result is armed.
- Exported remotePreserveReusable from pi-agent-core/compaction for
  apply-time validation of speculative remote results.
This commit is contained in:
can1357
2026-08-20 03:45:44 +02:00
parent eced7ab08a
commit 2f54e760c1
26 changed files with 1218 additions and 1043 deletions
@@ -1412,7 +1412,8 @@ export class CommandController {
this.ctx.ui.requestRender();
try {
// Handoff generation runs as a oneshot request; the new session is shown after it completes.
// Handoff generation runs as a oneshot request; the document is then
// committed as a compaction entry on this session.
const result = await this.ctx.session.handoff(customInstructions);
if (!result) {
@@ -1420,7 +1421,7 @@ export class CommandController {
return;
}
// Rebuild chat from the new session (which now contains the handoff document).
// Rebuild chat from the session, which now shows the handoff compaction divider.
this.ctx.clearTransientSessionUi();
await this.ctx.renderInitialMessages();
this.ctx.statusLine.invalidate();
@@ -1429,7 +1430,11 @@ export class CommandController {
this.ctx.present([
new Spacer(1),
new Text(`${theme.fg("accent", `${theme.status.success} New session started with handoff context`)}`, 1, 1),
new Text(
`${theme.fg("accent", `${theme.status.success} Context handed off and compacted in place`)}`,
1,
1,
),
]);
if (result.savedPath) {
this.ctx.showStatus(`Handoff document saved to: ${result.savedPath}`);