Files
oh-my-pi/packages/coding-agent/src/session/compact-modes.ts
T
can1357 8c35861d96 feat(coding-agent): implemented ordered compaction fallback and settings
- Replaced legacy `compaction.strategy` and `remoteEnabled` settings with `compaction.methodOrder` across session maintenance, schema, and tests.
- Added automatic fallback mechanism to try subsequent compaction methods upon failure or unsupported model capabilities.
- Added mouse drag-and-drop reordering support and click handlers to multi-select settings submenus.
- Updated documentation and test suites to reflect ordered compaction strategy preferences and fallback chains.
2026-08-20 02:59:49 +02:00

98 lines
3.6 KiB
TypeScript

/**
* Manual `/compact` subcommands. Kept in a dependency-free leaf module so the
* slash-command registry, the interactive controllers, and `AgentSession`
* can all import the mode metadata + parser without pulling in the heavy
* `agent-session` module graph (which would form an import cycle through the
* slash-command registry) — same rationale as `shake-types.ts`.
*
* Each mode is a one-off override layered on top of the configured
* `compaction.*` settings for a single invocation; it never mutates settings.
* Adding a mode is a single entry here: the command surface (autocomplete +
* ACP hint), the parser, and the engine override all read this table.
*/
import type { CompactionMethod } from "./compaction-methods";
/** Subcommand selecting a one-off compaction mode for manual `/compact`. */
export type CompactMode = "soft" | "remote" | "snapcompact";
/**
* Per-invocation ordered methods merged over the configured
* `compaction.methodOrder` for this run.
*/
export interface CompactionOverride {
methodOrder?: CompactionMethod[];
}
export interface CompactModeDef {
readonly name: CompactMode;
/** One-line description surfaced in autocomplete + help. */
readonly description: string;
/** Settings overrides applied on top of `compaction.*` for this run. */
readonly overrides: CompactionOverride;
/**
* When true, the mode produces no LLM summary, so trailing focus text is
* meaningless and rejected by the parser (snapcompact archives history into
* images without a directed summary).
*/
readonly rejectsFocus?: boolean;
}
export const COMPACT_MODES: readonly CompactModeDef[] = [
{
name: "soft",
description: "Summarize locally with the active model (skip server compaction)",
overrides: { methodOrder: ["soft"] },
},
{
name: "remote",
description: "Summarize via OpenAI-compatible server compaction, then fall back to a local summary",
overrides: { methodOrder: ["remote", "soft"] },
},
{
name: "snapcompact",
description: "Archive history onto dense bitmap images the model reads back (no LLM call)",
overrides: { methodOrder: ["snapcompact"] },
rejectsFocus: true,
},
];
/** Resolve a subcommand token (case-insensitive) to its mode definition. */
export function findCompactMode(name: string): CompactModeDef | undefined {
const key = name.trim().toLowerCase();
return COMPACT_MODES.find(mode => mode.name === key);
}
/** Parsed `/compact` arguments: an optional mode plus optional focus text. */
export interface ParsedCompactArgs {
mode?: CompactMode;
instructions?: string;
}
/**
* Split `/compact` args into a leading mode subcommand + focus instructions.
*
* Backward compatible: when the first token is not a known mode, the entire
* argument string is treated as focus instructions (the historical behavior).
* A recognized mode with `rejectsFocus` and trailing text is an error.
*/
export function parseCompactArgs(args: string): ParsedCompactArgs | { error: string } {
const trimmed = args.trim();
if (!trimmed) return {};
const spaceIndex = trimmed.search(/\s/);
const firstToken = spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex);
const mode = findCompactMode(firstToken);
if (!mode) {
// No recognized mode prefix — keep the whole thing as focus instructions.
return { instructions: trimmed };
}
const focus = spaceIndex === -1 ? "" : trimmed.slice(spaceIndex + 1).trim();
if (mode.rejectsFocus && focus) {
return {
error: `/compact ${mode.name} does not take focus instructions (it archives history without an LLM summary).`,
};
}
return { mode: mode.name, instructions: focus || undefined };
}