mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
/**
|
|
* 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.',
|
|
}
|
|
case 'cancelled':
|
|
return { kind: 'error', text: 'Compaction cancelled.' }
|
|
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 {
|
|
const result = await ctx.compact.compactNow(invocation.agent, invocation.signal)
|
|
if (result === null) return { kind: 'success', text: 'No compactable history yet.' }
|
|
return {
|
|
kind: 'success',
|
|
text: `Compacted ${result.shadowedSeqs.length} history items (~${result.shadowedTokenCount} tokens).`,
|
|
sourceEventSeq: result.summarySeq,
|
|
}
|
|
} 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 {
|
|
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')
|
|
}
|