2026-07-30 17:40:25 +08:00
/**
* Human-facing `/compact` command over the backend-independent compaction seam.
* @module @deepseek-ai/dsh-command-compact
*/
import type { Context } from 'cordis'
import { ManualCompactionError } from '@deepseek-ai/dsh-compact'
import type { CommandInvocation , CommandResult } from '@deepseek-ai/dsh-commands'
export const name = 'command-compact'
export const inject = [ 'commands' , 'compact' ]
const USAGE = 'Usage: /compact (no arguments)'
/** Fail loudly if a locally closed union gains an unhandled member. */
/* v8 ignore start -- closed-union backstop is unreachable without violating the TypeScript contract */
function assertNever ( value : never ) : never {
throw new TypeError ( ` unknown manual compaction error code: ${ String ( value ) } ` )
}
/* v8 ignore stop */
/** Convert expected capability failures into concise human-only outcomes. */
function expectedFailure ( error : ManualCompactionError ) : CommandResult {
switch ( error . code ) {
case 'busy' :
return {
kind : 'error' ,
text : 'Compaction is unavailable because this process has an active compaction, or the agent is not idle.' ,
}
2026-08-03 10:53:08 +08:00
case 'cancelled' :
return { kind : 'error' , text : 'Compaction cancelled.' }
2026-07-30 17:40:25 +08:00
case 'changed' :
return {
kind : 'error' ,
text : 'The history selected for compaction changed before it could be replaced. The conversation is unchanged; the attempt is recorded in the session log.' ,
}
case 'summary' :
return {
kind : 'error' ,
text : 'Compaction could not produce a useful summary. The conversation is unchanged; the attempt is recorded in the session log.' ,
}
case 'commit' :
return {
kind : 'error' ,
text : 'Compaction did not finish cleanly; some session history may have changed. Inspect the current session state before retrying.' ,
}
case 'persistence' :
return {
kind : 'error' ,
text : 'Compaction finished, but the session could not be saved.' ,
}
/* v8 ignore next 2 -- ManualCompactionErrorCode is closed and every member is handled above */
default : return assertNever ( error . code )
}
}
/** Execute one argument-free manual compaction request. */
async function executeCompact (
ctx : Context ,
invocation : CommandInvocation ,
) : Promise < CommandResult > {
if ( invocation . rawInput . trim ( ) . length > 0 ) {
return { kind : 'error' , text : USAGE }
}
try {
2026-08-09 15:48:07 +08:00
const result = await ctx . compact . compactNow ( invocation . agent , invocation . signal , invocation . commandId )
2026-07-30 17:40:25 +08:00
if ( result === null ) return { kind : 'success' , text : 'No compactable history yet.' }
return {
kind : 'success' ,
text : ` Compacted ${ result . shadowedSeqs . length } history items (~ ${ result . shadowedTokenCount } tokens). ` ,
2026-08-08 14:11:17 +08:00
sourceEventSeq : result.summarySeq ,
2026-07-30 17:40:25 +08:00
}
} catch ( error : unknown ) {
if ( invocation . signal . aborted ) return { kind : 'error' , text : 'Compaction cancelled.' }
if ( error instanceof ManualCompactionError ) return expectedFailure ( error )
throw error
}
}
/**
* Register `/compact` for every composed human-command adapter.
* @param ctx - context carrying the command registry and the compaction seam.
*/
export function apply ( ctx : Context ) : void {
2026-07-30 22:00:34 +08:00
const active = new Set < Promise < CommandResult > > ( )
const handler = ( invocation : CommandInvocation ) : Promise < CommandResult > = > {
const operation = executeCompact ( ctx , invocation )
active . add ( operation )
const retire = ( ) : void = > { active . delete ( operation ) }
// Both branches retire without rethrowing, so the derived observer promise
// cannot become an unhandled mirror of an expected handler rejection.
void operation . then ( retire , retire )
return operation
}
ctx . effect ( function * ( ) {
// Yield drain before registration: composite teardown is LIFO, so no new
// invocation can enter while already-started handler promises quiesce.
yield async ( ) = > { await Promise . allSettled ( active ) }
yield ctx . commands . register ( {
name : 'compact' ,
description : 'Compact older conversation history' ,
handler ,
} )
} , 'command-compact lifecycle' )
2026-07-30 17:40:25 +08:00
}