Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9314,9 +9314,11 @@ def _manual_compress(self, cmd_original: str = ""):
return

from hermes_cli.partial_compress import (
extract_compress_flags,
parse_partial_compress_args,
rejoin_compressed_head_and_tail,
split_history_for_partial_compress,
summarize_compress_preview,
)

# Args after the command word (e.g. "/compress here 3" -> "here 3").
Expand All @@ -9326,9 +9328,42 @@ def _manual_compress(self, cmd_original: str = ""):
if len(_parts) > 1:
raw_args = _parts[1].strip()

# Strip --preview/--dry-run/--aggressive before positional parsing
# so the flags coexist with 'here [N]' / focus-topic forms.
raw_args, preview, aggressive = extract_compress_flags(raw_args)
partial, keep_last, focus_topic = parse_partial_compress_args(raw_args)
focus_topic = focus_topic or ""

if aggressive:
# LLM-free hard truncation is not supported: it would need its
# own transcript-persistence path outside the guarded
# _compress_context rotation machinery. Surface that instead of
# silently mis-parsing the flag as a focus topic.
print("(._.) --aggressive is not supported; use '/compress here [N]' "
"to keep only recent exchanges, or /undo to drop turns.")
if not preview:
return

if preview:
from agent.model_metadata import estimate_request_tokens_rough
_sys_prompt = getattr(self.agent, "_cached_system_prompt", "") or ""
_tools = getattr(self.agent, "tools", None) or None
approx_tokens = estimate_request_tokens_rough(
self.conversation_history,
system_prompt=_sys_prompt,
tools=_tools,
)
report = summarize_compress_preview(
self.conversation_history,
partial,
keep_last,
focus_topic or None,
approx_tokens,
)
for line in report["lines"]:
print(f"🗜️ {line}")
return

original_count = len(self.conversation_history)
with self._busy_command("Compressing context..."):
try:
Expand Down
34 changes: 32 additions & 2 deletions gateway/slash_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2899,13 +2899,12 @@ async def _handle_verbose_command(self, event: MessageEvent) -> str:
return t("gateway.verbose.not_enabled")

# --- cycle mode (per-platform) ----------------------------------------
cycle = ["off", "new", "all", "verbose", "log"]
cycle = ["off", "new", "all", "verbose"]
descriptions = {
"off": t("gateway.verbose.mode_off"),
"new": t("gateway.verbose.mode_new"),
"all": t("gateway.verbose.mode_all"),
"verbose": t("gateway.verbose.mode_verbose"),
"log": t("gateway.verbose.mode_log"),
}

# Read current effective mode for this platform via the resolver
Expand Down Expand Up @@ -3044,13 +3043,44 @@ async def _handle_compress_command(self, event: MessageEvent) -> str:
# Parse args: either a focus topic (full compress) or the
# boundary-aware "here [N]" form (partial compress).
from hermes_cli.partial_compress import (
extract_compress_flags,
parse_partial_compress_args,
rejoin_compressed_head_and_tail,
split_history_for_partial_compress,
summarize_compress_preview,
)
_raw_args = (event.get_command_args() or "").strip()
# Strip --preview/--dry-run/--aggressive before positional parsing
# so the flags coexist with 'here [N]' / focus-topic forms.
_raw_args, _preview, _aggressive = extract_compress_flags(_raw_args)
partial, keep_last, focus_topic = parse_partial_compress_args(_raw_args)

_agg_note = ""
if _aggressive:
# LLM-free hard truncation is not supported on this surface —
# it would need its own transcript-persistence branch outside
# the guarded _compress_context rotation machinery (#44794).
_agg_note = t("gateway.compress.aggressive_unsupported")
if not _preview:
return _agg_note

if _preview:
# Report what WOULD be compressed — no agent, no writes.
from agent.model_metadata import estimate_request_tokens_rough
_pv_msgs = [
{"role": m.get("role"), "content": m.get("content")}
for m in history
if m.get("role") in {"user", "assistant"} and m.get("content")
]
approx_tokens = estimate_request_tokens_rough(_pv_msgs)
report = summarize_compress_preview(
_pv_msgs, partial, keep_last, focus_topic, approx_tokens
)
lines = [f"🗜️ {line}" for line in report["lines"]]
if _aggressive:
lines.append(_agg_note)
return "\n".join(lines)

try:
from run_agent import AIAgent
from agent.manual_compression_feedback import summarize_manual_compression
Expand Down
6 changes: 3 additions & 3 deletions hermes_cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ class CommandDef:
args_hint="<platform>", cli_only=True),
CommandDef("branch", "Branch the current session (explore a different path)", "Session",
aliases=("fork",), args_hint="[name]"),
CommandDef("compress", "Compress conversation context (add 'here [N]' to keep recent N turns)", "Session",
args_hint="[here [N] | focus topic]"),
CommandDef("compress", "Compress conversation context (add 'here [N]' to keep recent N turns; --preview shows what would happen)", "Session",
aliases=("compact",), args_hint="[here [N] | focus topic | --preview|--dry-run]"),
CommandDef("rollback", "List or restore filesystem checkpoints", "Session",
args_hint="[number]"),
CommandDef("snapshot", "Create or restore state snapshots of Hermes config/state", "Session",
Expand Down Expand Up @@ -144,7 +144,7 @@ class CommandDef:
CommandDef("timestamps", "Toggle [HH:MM] timestamps on messages and /history", "Configuration",
cli_only=True, args_hint="[on|off|status]",
subcommands=("on", "off", "status"), aliases=("ts",)),
CommandDef("verbose", "Cycle tool progress display: off -> new -> all -> verbose -> log",
CommandDef("verbose", "Cycle tool progress display: off -> new -> all -> verbose",
"Configuration", cli_only=True,
gateway_config_gate="display.tool_progress_command"),
CommandDef("footer", "Toggle gateway runtime-metadata footer on final replies",
Expand Down
89 changes: 89 additions & 0 deletions hermes_cli/partial_compress.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,95 @@ def parse_partial_compress_args(
return False, DEFAULT_KEEP_LAST, text or None


def extract_compress_flags(raw_args: str) -> Tuple[str, bool, bool]:
"""Strip ``--preview``/``--dry-run``/``--aggressive`` flags from the
argument string after ``/compress`` (or its ``/compact`` alias).

Flags may appear anywhere and coexist with the positional forms
(``here [N]``, ``--keep N``, or a focus topic); the returned
remainder is what :func:`parse_partial_compress_args` should see.

Returns ``(remaining_args, preview, aggressive_requested)``:

* ``preview`` — True when ``--preview`` or ``--dry-run`` was given.
The caller must report what WOULD be compressed (message counts,
token estimate, boundary) and make **no changes**.
* ``aggressive_requested`` — True when ``--aggressive`` was given.
The current surfaces do not implement an LLM-free hard-truncate
path (it would need its own transcript-persistence branch outside
the guarded ``_compress_context`` rotation machinery), so callers
surface a "not supported" note instead of silently treating the
flag as a focus topic.
"""
preview = False
aggressive = False
kept: List[str] = []
for tok in (raw_args or "").split():
low = tok.lower()
if low in ("--preview", "--dry-run", "--dryrun"):
preview = True
elif low == "--aggressive":
aggressive = True
else:
kept.append(tok)
return " ".join(kept), preview, aggressive


def summarize_compress_preview(
history: List[Dict[str, Any]],
partial: bool,
keep_last: int,
focus_topic: Optional[str],
approx_tokens: int,
) -> Dict[str, Any]:
"""Build the ``/compress --preview`` report — pure, no side effects.

Shared by the CLI (``cli.py::_manual_compress``) and the gateway
(``gateway/slash_commands.py::_handle_compress_command``) so both
surfaces report the same numbers the real run would use.

Returns a dict with ``head_count``/``tail_count``/``lines`` where
``lines`` is a ready-to-print list of report strings.
"""
total = len(history)
head = list(history)
tail: List[Dict[str, Any]] = []
effective_partial = partial
if partial:
head, tail = split_history_for_partial_compress(history, keep_last)
if not tail:
# Same degenerate-split fallback the real run applies.
effective_partial = False
head, tail = list(history), []

lines = [
"Preview — no changes made.",
f"Would compress {len(head)} of {total} message(s) "
f"(~{approx_tokens:,} tokens currently in context).",
]
if effective_partial:
lines.append(
f"Boundary: keeping the last {keep_last} exchange(s) "
f"({len(tail)} message(s)) verbatim."
)
elif partial:
lines.append(
"Boundary: 'here' split would keep everything — "
"falling back to full compression."
)
if focus_topic:
lines.append(f'Focus topic: "{focus_topic}"')
lines.append("Run the command again without --preview to apply.")

return {
"head_count": len(head),
"tail_count": len(tail),
"total": total,
"partial": effective_partial,
"lines": lines,
}


def _coerce_keep(value: str) -> int:
"""Parse a keep-count token, clamping to [1, MAX_KEEP_LAST]."""
try:
Expand Down
2 changes: 1 addition & 1 deletion locales/af.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ gateway:
not_enough: "Nie genoeg gesprek om saam te pers nie (ten minste 4 boodskappe nodig)."
no_provider: "Geen verskaffer opgestel nie -- kan nie saampers nie."
nothing_to_do: "Niks om saam te pers nie (die transkripsie is steeds heeltemal beskermde konteks)."
aggressive_unsupported: "--aggressive word nie ondersteun nie; gebruik '/compress here [N]' om net onlangse uitruilings te behou, of /undo om beurte te verwyder."
focus_line: "Fokus: \"{topic}\""
summary_failed: "⚠️ Opsomming kon nie gegenereer word nie ({error}). {count} historiese boodskap(pe) is verwyder en met 'n plekhouer vervang; vroeëre konteks kan nie meer herstel word nie. Oorweeg om jou auxiliary.compression-modelopstelling na te gaan."
aborted: "⚠️ Kompressie gestaak ({error}). Geen boodskappe is laat val nie — die gesprek is onveranderd. Voer /compress uit om weer te probeer, /reset vir 'n skoon sessie, of kyk na jou auxiliary.compression-modelkonfigurasie."
Expand Down Expand Up @@ -367,7 +368,6 @@ Future messages in this room will use that transcript until `/reset` or another
mode_new: "⚙️ Gereedskap-vordering: **NUUT** — vertoon wanneer gereedskap verander (voorskoulengte: `display.tool_preview_length`, verstek 40)."
mode_all: "⚙️ Gereedskap-vordering: **ALMAL** — elke gereedskaps-oproep vertoon (voorskoulengte: `display.tool_preview_length`, verstek 40)."
mode_verbose: "⚙️ Gereedskap-vordering: **OMSLAGTIG** — elke gereedskaps-oproep met volle argumente."
mode_log: "⚙️ Gereedskap-vordering: **LOG** — stil in die klets; gereedskaps-oproepe word na ~/.hermes/logs/tool_calls.log geskryf."
saved_suffix: "_(gestoor vir **{platform}** — neem effek by die volgende boodskap)_"
save_failed: "_(kon nie in konfigurasie stoor nie: {error})_"

Expand Down
2 changes: 1 addition & 1 deletion locales/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ gateway:
not_enough: "Nicht genug Konversation zum Komprimieren (mindestens 4 Nachrichten erforderlich)."
no_provider: "Kein Anbieter konfiguriert — Komprimierung nicht möglich."
nothing_to_do: "Noch nichts zu komprimieren (das Transkript ist weiterhin vollständig geschützter Kontext)."
aggressive_unsupported: "--aggressive wird nicht unterstützt; verwende '/compress here [N]', um nur die letzten Austausche zu behalten, oder /undo, um Beiträge zu entfernen."
focus_line: "Fokus: \"{topic}\""
summary_failed: "⚠️ Zusammenfassungsgenerierung fehlgeschlagen ({error}). {count} historische Nachricht(en) wurden entfernt und durch einen Platzhalter ersetzt; früherer Kontext ist nicht mehr wiederherstellbar. Überprüfen Sie die Konfiguration des auxiliary.compression-Modells."
aborted: "⚠️ Komprimierung abgebrochen ({error}). Keine Nachrichten wurden entfernt — die Konversation ist unverändert. Führe /compress aus, um es erneut zu versuchen, /reset für eine neue Sitzung, oder prüfe deine auxiliary.compression-Modellkonfiguration."
Expand Down Expand Up @@ -367,7 +368,6 @@ Future messages in this room will use that transcript until `/reset` or another
mode_new: "⚙️ Tool-Fortschritt: **NEW** — angezeigt bei Tool-Wechsel (Vorschaulänge: `display.tool_preview_length`, Standard 40)."
mode_all: "⚙️ Tool-Fortschritt: **ALL** — jeder Tool-Aufruf wird angezeigt (Vorschaulänge: `display.tool_preview_length`, Standard 40)."
mode_verbose: "⚙️ Tool-Fortschritt: **VERBOSE** — jeder Tool-Aufruf mit vollständigen Argumenten."
mode_log: "⚙️ Tool-Fortschritt: **LOG** — still im Chat; Tool-Aufrufe werden in ~/.hermes/logs/tool_calls.log geschrieben."
saved_suffix: "_(für **{platform}** gespeichert — wird ab nächster Nachricht wirksam)_"
save_failed: "_(konnte nicht in der Konfiguration gespeichert werden: {error})_"

Expand Down
2 changes: 1 addition & 1 deletion locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ gateway:
not_enough: "Not enough conversation to compress (need at least 4 messages)."
no_provider: "No provider configured -- cannot compress."
nothing_to_do: "Nothing to compress yet (the transcript is still all protected context)."
aggressive_unsupported: "--aggressive is not supported; use '/compress here [N]' to keep only recent exchanges, or /undo to drop turns."
focus_line: "Focus: \"{topic}\""
summary_failed: "⚠️ Summary generation failed ({error}). {count} historical message(s) were removed and replaced with a placeholder; earlier context is no longer recoverable. Consider checking your auxiliary.compression model configuration."
aborted: "⚠️ Compression aborted ({error}). No messages were dropped — conversation is unchanged. Run /compress to retry, /reset for a clean session, or check your auxiliary.compression model configuration."
Expand Down Expand Up @@ -379,7 +380,6 @@ gateway:
mode_new: "⚙️ Tool progress: **NEW** — shown when tool changes (preview length: `display.tool_preview_length`, default 40)."
mode_all: "⚙️ Tool progress: **ALL** — every tool call shown (preview length: `display.tool_preview_length`, default 40)."
mode_verbose: "⚙️ Tool progress: **VERBOSE** — every tool call with full arguments."
mode_log: "⚙️ Tool progress: **LOG** — silent in chat; tool calls written to ~/.hermes/logs/tool_calls.log."
saved_suffix: "_(saved for **{platform}** — takes effect on next message)_"
save_failed: "_(could not save to config: {error})_"

Expand Down
2 changes: 1 addition & 1 deletion locales/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ gateway:
not_enough: "No hay suficiente conversación para comprimir (se necesitan al menos 4 mensajes)."
no_provider: "No hay proveedor configurado — no se puede comprimir."
nothing_to_do: "Aún no hay nada que comprimir (la transcripción sigue siendo todo contexto protegido)."
aggressive_unsupported: "--aggressive no es compatible; usa '/compress here [N]' para conservar solo los intercambios recientes, o /undo para eliminar turnos."
focus_line: "Enfoque: \"{topic}\""
summary_failed: "⚠️ Falló la generación del resumen ({error}). Se eliminaron {count} mensaje(s) históricos y se reemplazaron por un marcador; el contexto anterior ya no se puede recuperar. Considera revisar la configuración del modelo auxiliary.compression."
aborted: "⚠️ Compresión abortada ({error}). No se eliminó ningún mensaje — la conversación está intacta. Ejecuta /compress para reintentar, /reset para una sesión limpia, o revisa la configuración de tu modelo auxiliary.compression."
Expand Down Expand Up @@ -364,7 +365,6 @@ gateway:
mode_new: "⚙️ Progreso de herramientas: **NEW** — se muestra al cambiar de herramienta (longitud de vista previa: `display.tool_preview_length`, por defecto 40)."
mode_all: "⚙️ Progreso de herramientas: **ALL** — se muestra cada llamada a herramienta (longitud de vista previa: `display.tool_preview_length`, por defecto 40)."
mode_verbose: "⚙️ Progreso de herramientas: **VERBOSE** — cada llamada a herramienta con sus argumentos completos."
mode_log: "⚙️ Progreso de herramientas: **LOG** — silencioso en el chat; las llamadas a herramientas se escriben en ~/.hermes/logs/tool_calls.log."
saved_suffix: "_(guardado para **{platform}** — se aplica en el próximo mensaje)_"
save_failed: "_(no se pudo guardar en la configuración: {error})_"

Expand Down
2 changes: 1 addition & 1 deletion locales/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ gateway:
not_enough: "Conversation insuffisante pour la compression (au moins 4 messages nécessaires)."
no_provider: "Aucun fournisseur configuré — compression impossible."
nothing_to_do: "Rien à compresser pour l'instant (la transcription est encore entièrement du contexte protégé)."
aggressive_unsupported: "--aggressive n'est pas pris en charge ; utilisez '/compress here [N]' pour ne conserver que les échanges récents, ou /undo pour supprimer des tours."
focus_line: "Focus : \"{topic}\""
summary_failed: "⚠️ Échec de la génération du résumé ({error}). {count} message(s) historique(s) ont été supprimés et remplacés par un espace réservé ; le contexte antérieur n'est plus récupérable. Vérifiez la configuration du modèle auxiliary.compression."
aborted: "⚠️ Compression interrompue ({error}). Aucun message n'a été supprimé — la conversation est inchangée. Lancez /compress pour réessayer, /reset pour une nouvelle session, ou vérifiez la configuration de votre modèle auxiliary.compression."
Expand Down Expand Up @@ -367,7 +368,6 @@ Future messages in this room will use that transcript until `/reset` or another
mode_new: "⚙️ Progression des outils : **NEW** — affichée lors d'un changement d'outil (longueur d'aperçu : `display.tool_preview_length`, par défaut 40)."
mode_all: "⚙️ Progression des outils : **ALL** — chaque appel d'outil est affiché (longueur d'aperçu : `display.tool_preview_length`, par défaut 40)."
mode_verbose: "⚙️ Progression des outils : **VERBOSE** — chaque appel d'outil avec ses arguments complets."
mode_log: "⚙️ Progression des outils : **LOG** — silencieux dans le chat ; les appels d'outils sont écrits dans ~/.hermes/logs/tool_calls.log."
saved_suffix: "_(enregistré pour **{platform}** — prend effet au prochain message)_"
save_failed: "_(impossible d'enregistrer dans la configuration : {error})_"

Expand Down
Loading
Loading