mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
chore: fix 154 f-strings, simplify getattr/URL patterns, remove dead code (#3119)
Three categories of cleanup, all zero-behavioral-change:
1. F-strings without placeholders (154 fixes across 29 files)
- Converted f'...' to '...' where no {expression} was present
- Heaviest files: run_agent.py (24), cli.py (20), honcho_integration/cli.py (34)
2. Simplify defensive patterns in run_agent.py
- Added explicit self._is_anthropic_oauth = False in __init__ (before
the api_mode branch that conditionally sets it)
- Replaced 7x getattr(self, '_is_anthropic_oauth', False) with direct
self._is_anthropic_oauth (attribute always initialized now)
- Added _is_openrouter_url() and _is_anthropic_url() helper methods
- Replaced 3 inline 'openrouter' in self._base_url_lower checks
3. Remove dead code in small files
- hermes_cli/claw.py: removed unused 'total' computation
- tools/fuzzy_match.py: removed unused strip_indent() function and
pattern_stripped variable
Full test suite: 6184 passed, 0 failures
E2E PTY: banner clean, tool calls work, zero garbled ANSI
This commit is contained in:
parent
08d3be0412
commit
cbf195e806
30 changed files with 173 additions and 170 deletions
|
|
@ -666,7 +666,7 @@ class InsightsEngine:
|
||||||
cost_cell = " N/A"
|
cost_cell = " N/A"
|
||||||
lines.append(f" {model_name:<30} {m['sessions']:>8} {m['total_tokens']:>12,} {cost_cell}")
|
lines.append(f" {model_name:<30} {m['sessions']:>8} {m['total_tokens']:>12,} {cost_cell}")
|
||||||
if o.get("models_without_pricing"):
|
if o.get("models_without_pricing"):
|
||||||
lines.append(f" * Cost N/A for custom/self-hosted models")
|
lines.append(" * Cost N/A for custom/self-hosted models")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Platform breakdown
|
# Platform breakdown
|
||||||
|
|
|
||||||
40
cli.py
40
cli.py
|
|
@ -2414,13 +2414,13 @@ class HermesCLI:
|
||||||
print(f" ✅ Restored {file_path} from checkpoint {result['restored_to']}: {result['reason']}")
|
print(f" ✅ Restored {file_path} from checkpoint {result['restored_to']}: {result['reason']}")
|
||||||
else:
|
else:
|
||||||
print(f" ✅ Restored to checkpoint {result['restored_to']}: {result['reason']}")
|
print(f" ✅ Restored to checkpoint {result['restored_to']}: {result['reason']}")
|
||||||
print(f" A pre-rollback snapshot was saved automatically.")
|
print(" A pre-rollback snapshot was saved automatically.")
|
||||||
|
|
||||||
# Also undo the last conversation turn so the agent's context
|
# Also undo the last conversation turn so the agent's context
|
||||||
# matches the restored filesystem state
|
# matches the restored filesystem state
|
||||||
if self.conversation_history:
|
if self.conversation_history:
|
||||||
self.undo_last()
|
self.undo_last()
|
||||||
print(f" Chat turn undone to match restored file state.")
|
print(" Chat turn undone to match restored file state.")
|
||||||
else:
|
else:
|
||||||
print(f" ❌ {result['error']}")
|
print(f" ❌ {result['error']}")
|
||||||
|
|
||||||
|
|
@ -3067,15 +3067,15 @@ class HermesCLI:
|
||||||
print(f" endpoint: {custom_url}")
|
print(f" endpoint: {custom_url}")
|
||||||
if is_active:
|
if is_active:
|
||||||
print(f" model: {self.model} ← current")
|
print(f" model: {self.model} ← current")
|
||||||
print(f" (use hermes model to change)")
|
print(" (use hermes model to change)")
|
||||||
else:
|
else:
|
||||||
print(f" (use hermes model to change)")
|
print(" (use hermes model to change)")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if unauthed:
|
if unauthed:
|
||||||
names = ", ".join(p["label"] for p in unauthed)
|
names = ", ".join(p["label"] for p in unauthed)
|
||||||
print(f" Not configured: {names}")
|
print(f" Not configured: {names}")
|
||||||
print(f" Run: hermes setup")
|
print(" Run: hermes setup")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print(" To change model or provider, use: hermes model")
|
print(" To change model or provider, use: hermes model")
|
||||||
|
|
@ -3099,9 +3099,9 @@ class HermesCLI:
|
||||||
self.system_prompt = new_prompt
|
self.system_prompt = new_prompt
|
||||||
self.agent = None # Force re-init
|
self.agent = None # Force re-init
|
||||||
if save_config_value("agent.system_prompt", new_prompt):
|
if save_config_value("agent.system_prompt", new_prompt):
|
||||||
print(f"(^_^)b System prompt set (saved to config)")
|
print("(^_^)b System prompt set (saved to config)")
|
||||||
else:
|
else:
|
||||||
print(f"(^_^) System prompt set (session only)")
|
print("(^_^) System prompt set (session only)")
|
||||||
print(f" \"{new_prompt[:60]}{'...' if len(new_prompt) > 60 else ''}\"")
|
print(f" \"{new_prompt[:60]}{'...' if len(new_prompt) > 60 else ''}\"")
|
||||||
else:
|
else:
|
||||||
# Show current prompt
|
# Show current prompt
|
||||||
|
|
@ -3642,7 +3642,7 @@ class HermesCLI:
|
||||||
elif self._pending_title:
|
elif self._pending_title:
|
||||||
_cprint(f" Title (pending): {self._pending_title}")
|
_cprint(f" Title (pending): {self._pending_title}")
|
||||||
else:
|
else:
|
||||||
_cprint(f" No title set. Usage: /title <your session title>")
|
_cprint(" No title set. Usage: /title <your session title>")
|
||||||
else:
|
else:
|
||||||
_cprint(" Session database not available.")
|
_cprint(" Session database not available.")
|
||||||
elif canonical == "new":
|
elif canonical == "new":
|
||||||
|
|
@ -3701,7 +3701,7 @@ class HermesCLI:
|
||||||
plugins = mgr.list_plugins()
|
plugins = mgr.list_plugins()
|
||||||
if not plugins:
|
if not plugins:
|
||||||
print("No plugins installed.")
|
print("No plugins installed.")
|
||||||
print(f"Drop plugin directories into ~/.hermes/plugins/ to get started.")
|
print("Drop plugin directories into ~/.hermes/plugins/ to get started.")
|
||||||
else:
|
else:
|
||||||
print(f"Plugins ({len(plugins)}):")
|
print(f"Plugins ({len(plugins)}):")
|
||||||
for p in plugins:
|
for p in plugins:
|
||||||
|
|
@ -3894,7 +3894,7 @@ class HermesCLI:
|
||||||
|
|
||||||
_cprint(f" 🔄 Background task #{task_num} started: \"{prompt[:60]}{'...' if len(prompt) > 60 else ''}\"")
|
_cprint(f" 🔄 Background task #{task_num} started: \"{prompt[:60]}{'...' if len(prompt) > 60 else ''}\"")
|
||||||
_cprint(f" Task ID: {task_id}")
|
_cprint(f" Task ID: {task_id}")
|
||||||
_cprint(f" You can continue chatting — results will appear when done.\n")
|
_cprint(" You can continue chatting — results will appear when done.\n")
|
||||||
|
|
||||||
turn_route = self._resolve_turn_agent_config(prompt)
|
turn_route = self._resolve_turn_agent_config(prompt)
|
||||||
|
|
||||||
|
|
@ -4104,7 +4104,7 @@ class HermesCLI:
|
||||||
print(f" ⚠ Chrome launched but port {_port} isn't responding yet")
|
print(f" ⚠ Chrome launched but port {_port} isn't responding yet")
|
||||||
print(" You may need to close existing Chrome windows first and retry")
|
print(" You may need to close existing Chrome windows first and retry")
|
||||||
else:
|
else:
|
||||||
print(f" ⚠ Could not auto-launch Chrome")
|
print(" ⚠ Could not auto-launch Chrome")
|
||||||
# Show manual instructions as fallback
|
# Show manual instructions as fallback
|
||||||
sys_name = _plat.system()
|
sys_name = _plat.system()
|
||||||
if sys_name == "Darwin":
|
if sys_name == "Darwin":
|
||||||
|
|
@ -4161,7 +4161,7 @@ class HermesCLI:
|
||||||
elif sub == "status":
|
elif sub == "status":
|
||||||
print()
|
print()
|
||||||
if current:
|
if current:
|
||||||
print(f"🌐 Browser: connected to live Chrome via CDP")
|
print("🌐 Browser: connected to live Chrome via CDP")
|
||||||
print(f" Endpoint: {current}")
|
print(f" Endpoint: {current}")
|
||||||
|
|
||||||
_port = 9222
|
_port = 9222
|
||||||
|
|
@ -4175,9 +4175,9 @@ class HermesCLI:
|
||||||
s.settimeout(1)
|
s.settimeout(1)
|
||||||
s.connect(("127.0.0.1", _port))
|
s.connect(("127.0.0.1", _port))
|
||||||
s.close()
|
s.close()
|
||||||
print(f" Status: ✓ reachable")
|
print(" Status: ✓ reachable")
|
||||||
except (OSError, Exception):
|
except (OSError, Exception):
|
||||||
print(f" Status: ⚠ not reachable (Chrome may not be running)")
|
print(" Status: ⚠ not reachable (Chrome may not be running)")
|
||||||
elif os.environ.get("BROWSERBASE_API_KEY"):
|
elif os.environ.get("BROWSERBASE_API_KEY"):
|
||||||
print("🌐 Browser: Browserbase (cloud)")
|
print("🌐 Browser: Browserbase (cloud)")
|
||||||
else:
|
else:
|
||||||
|
|
@ -4210,13 +4210,13 @@ class HermesCLI:
|
||||||
current = get_active_skin_name()
|
current = get_active_skin_name()
|
||||||
skins = list_skins()
|
skins = list_skins()
|
||||||
print(f"\n Current skin: {current}")
|
print(f"\n Current skin: {current}")
|
||||||
print(f" Available skins:")
|
print(" Available skins:")
|
||||||
for s in skins:
|
for s in skins:
|
||||||
marker = " ●" if s["name"] == current else " "
|
marker = " ●" if s["name"] == current else " "
|
||||||
source = f" ({s['source']})" if s["source"] == "user" else ""
|
source = f" ({s['source']})" if s["source"] == "user" else ""
|
||||||
print(f" {marker} {s['name']}{source} — {s['description']}")
|
print(f" {marker} {s['name']}{source} — {s['description']}")
|
||||||
print(f"\n Usage: /skin <name>")
|
print("\n Usage: /skin <name>")
|
||||||
print(f" Custom skins: drop a YAML file in ~/.hermes/skins/\n")
|
print(" Custom skins: drop a YAML file in ~/.hermes/skins/\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
new_skin = parts[1].strip().lower()
|
new_skin = parts[1].strip().lower()
|
||||||
|
|
@ -4413,7 +4413,7 @@ class HermesCLI:
|
||||||
)
|
)
|
||||||
elapsed = format_duration_compact((datetime.now() - self.session_start).total_seconds())
|
elapsed = format_duration_compact((datetime.now() - self.session_start).total_seconds())
|
||||||
|
|
||||||
print(f" 📊 Session Token Usage")
|
print(" 📊 Session Token Usage")
|
||||||
print(f" {'─' * 40}")
|
print(f" {'─' * 40}")
|
||||||
print(f" Model: {agent.model}")
|
print(f" Model: {agent.model}")
|
||||||
print(f" Input tokens: {input_tokens:>10,}")
|
print(f" Input tokens: {input_tokens:>10,}")
|
||||||
|
|
@ -5549,7 +5549,7 @@ class HermesCLI:
|
||||||
# But if it does (race condition), don't interrupt.
|
# But if it does (race condition), don't interrupt.
|
||||||
if self._clarify_state or self._clarify_freetext:
|
if self._clarify_state or self._clarify_freetext:
|
||||||
continue
|
continue
|
||||||
print(f"\n⚡ New message detected, interrupting...")
|
print("\n⚡ New message detected, interrupting...")
|
||||||
# Signal TTS to stop on interrupt
|
# Signal TTS to stop on interrupt
|
||||||
if stop_event is not None:
|
if stop_event is not None:
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
@ -5762,7 +5762,7 @@ class HermesCLI:
|
||||||
else:
|
else:
|
||||||
duration_str = f"{seconds}s"
|
duration_str = f"{seconds}s"
|
||||||
|
|
||||||
print(f"Resume this session with:")
|
print("Resume this session with:")
|
||||||
print(f" hermes --resume {self.session_id}")
|
print(f" hermes --resume {self.session_id}")
|
||||||
print()
|
print()
|
||||||
print(f"Session: {self.session_id}")
|
print(f"Session: {self.session_id}")
|
||||||
|
|
|
||||||
|
|
@ -2885,7 +2885,7 @@ class GatewayRunner:
|
||||||
else:
|
else:
|
||||||
preview = prompt[:50] + "..." if len(prompt) > 50 else prompt
|
preview = prompt[:50] + "..." if len(prompt) > 50 else prompt
|
||||||
lines.append(f"• `{name}` — {preview}")
|
lines.append(f"• `{name}` — {preview}")
|
||||||
lines.append(f"\nUsage: `/personality <name>`")
|
lines.append("\nUsage: `/personality <name>`")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _resolve_prompt(value):
|
def _resolve_prompt(value):
|
||||||
|
|
|
||||||
|
|
@ -2012,7 +2012,7 @@ def _login_openai_codex(args, pconfig: ProviderConfig) -> None:
|
||||||
config_path = _update_config_for_provider("openai-codex", creds.get("base_url", DEFAULT_CODEX_BASE_URL))
|
config_path = _update_config_for_provider("openai-codex", creds.get("base_url", DEFAULT_CODEX_BASE_URL))
|
||||||
print()
|
print()
|
||||||
print("Login successful!")
|
print("Login successful!")
|
||||||
print(f" Auth state: ~/.hermes/auth.json")
|
print(" Auth state: ~/.hermes/auth.json")
|
||||||
print(f" Config updated: {config_path} (model.provider=openai-codex)")
|
print(f" Config updated: {config_path} (model.provider=openai-codex)")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2056,9 +2056,9 @@ def _codex_device_code_login() -> Dict[str, Any]:
|
||||||
|
|
||||||
# Step 2: Show user the code
|
# Step 2: Show user the code
|
||||||
print("To continue, follow these steps:\n")
|
print("To continue, follow these steps:\n")
|
||||||
print(f" 1. Open this URL in your browser:")
|
print(" 1. Open this URL in your browser:")
|
||||||
print(f" \033[94m{issuer}/codex/device\033[0m\n")
|
print(f" \033[94m{issuer}/codex/device\033[0m\n")
|
||||||
print(f" 2. Enter this code:")
|
print(" 2. Enter this code:")
|
||||||
print(f" \033[94m{user_code}\033[0m\n")
|
print(f" \033[94m{user_code}\033[0m\n")
|
||||||
print("Waiting for sign-in... (press Ctrl+C to cancel)")
|
print("Waiting for sign-in... (press Ctrl+C to cancel)")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ def _cmd_migrate(args):
|
||||||
print()
|
print()
|
||||||
print_error(f"OpenClaw directory not found: {source_dir}")
|
print_error(f"OpenClaw directory not found: {source_dir}")
|
||||||
print_info("Make sure your OpenClaw installation is at the expected path.")
|
print_info("Make sure your OpenClaw installation is at the expected path.")
|
||||||
print_info(f"You can specify a custom path: hermes claw migrate --source /path/to/.openclaw")
|
print_info("You can specify a custom path: hermes claw migrate --source /path/to/.openclaw")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Find the migration script
|
# Find the migration script
|
||||||
|
|
@ -206,7 +206,6 @@ def _print_migration_report(report: dict, dry_run: bool):
|
||||||
skipped = summary.get("skipped", 0)
|
skipped = summary.get("skipped", 0)
|
||||||
conflicts = summary.get("conflict", 0)
|
conflicts = summary.get("conflict", 0)
|
||||||
errors = summary.get("error", 0)
|
errors = summary.get("error", 0)
|
||||||
total = migrated + skipped + conflicts + errors
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
if dry_run:
|
if dry_run:
|
||||||
|
|
@ -240,7 +239,7 @@ def _print_migration_report(report: dict, dry_run: bool):
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if conflict_items:
|
if conflict_items:
|
||||||
print(color(f" ⚠ Conflicts (skipped — use --overwrite to force):", Colors.YELLOW))
|
print(color(" ⚠ Conflicts (skipped — use --overwrite to force):", Colors.YELLOW))
|
||||||
for item in conflict_items:
|
for item in conflict_items:
|
||||||
kind = item.get("kind", "unknown")
|
kind = item.get("kind", "unknown")
|
||||||
reason = item.get("reason", "already exists")
|
reason = item.get("reason", "already exists")
|
||||||
|
|
@ -248,7 +247,7 @@ def _print_migration_report(report: dict, dry_run: bool):
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if skipped_items:
|
if skipped_items:
|
||||||
print(color(f" ─ Skipped:", Colors.DIM))
|
print(color(" ─ Skipped:", Colors.DIM))
|
||||||
for item in skipped_items:
|
for item in skipped_items:
|
||||||
kind = item.get("kind", "unknown")
|
kind = item.get("kind", "unknown")
|
||||||
reason = item.get("reason", "")
|
reason = item.get("reason", "")
|
||||||
|
|
@ -256,7 +255,7 @@ def _print_migration_report(report: dict, dry_run: bool):
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if error_items:
|
if error_items:
|
||||||
print(color(f" ✗ Errors:", Colors.RED))
|
print(color(" ✗ Errors:", Colors.RED))
|
||||||
for item in error_items:
|
for item in error_items:
|
||||||
kind = item.get("kind", "unknown")
|
kind = item.get("kind", "unknown")
|
||||||
reason = item.get("reason", "unknown error")
|
reason = item.get("reason", "unknown error")
|
||||||
|
|
|
||||||
|
|
@ -705,7 +705,7 @@ def run_doctor(args):
|
||||||
_honcho_cfg_path = resolve_config_path()
|
_honcho_cfg_path = resolve_config_path()
|
||||||
|
|
||||||
if not _honcho_cfg_path.exists():
|
if not _honcho_cfg_path.exists():
|
||||||
check_warn("Honcho config not found", f"run: hermes honcho setup")
|
check_warn("Honcho config not found", "run: hermes honcho setup")
|
||||||
elif not hcfg.enabled:
|
elif not hcfg.enabled:
|
||||||
check_info(f"Honcho disabled (set enabled: true in {_honcho_cfg_path} to activate)")
|
check_info(f"Honcho disabled (set enabled: true in {_honcho_cfg_path} to activate)")
|
||||||
elif not hcfg.api_key:
|
elif not hcfg.api_key:
|
||||||
|
|
|
||||||
|
|
@ -1332,9 +1332,9 @@ def _setup_standard_platform(platform: dict):
|
||||||
|
|
||||||
# Allowlist fields get special handling for the deny-by-default security model
|
# Allowlist fields get special handling for the deny-by-default security model
|
||||||
if var.get("is_allowlist"):
|
if var.get("is_allowlist"):
|
||||||
print_info(f" The gateway DENIES all users by default for security.")
|
print_info(" The gateway DENIES all users by default for security.")
|
||||||
print_info(f" Enter user IDs to create an allowlist, or leave empty")
|
print_info(" Enter user IDs to create an allowlist, or leave empty")
|
||||||
print_info(f" and you'll be asked about open access next.")
|
print_info(" and you'll be asked about open access next.")
|
||||||
value = prompt(f" {var['prompt']}", password=False)
|
value = prompt(f" {var['prompt']}", password=False)
|
||||||
if value:
|
if value:
|
||||||
cleaned = value.replace(" ", "")
|
cleaned = value.replace(" ", "")
|
||||||
|
|
@ -1351,7 +1351,7 @@ def _setup_standard_platform(platform: dict):
|
||||||
parts.append(uid)
|
parts.append(uid)
|
||||||
cleaned = ",".join(parts)
|
cleaned = ",".join(parts)
|
||||||
save_env_value(var["name"], cleaned)
|
save_env_value(var["name"], cleaned)
|
||||||
print_success(f" Saved — only these users can interact with the bot.")
|
print_success(" Saved — only these users can interact with the bot.")
|
||||||
allowed_val_set = cleaned
|
allowed_val_set = cleaned
|
||||||
else:
|
else:
|
||||||
# No allowlist — ask about open access vs DM pairing
|
# No allowlist — ask about open access vs DM pairing
|
||||||
|
|
@ -1380,7 +1380,7 @@ def _setup_standard_platform(platform: dict):
|
||||||
print_warning(f" Skipped — {label} won't work without this.")
|
print_warning(f" Skipped — {label} won't work without this.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
print_info(f" Skipped (can configure later)")
|
print_info(" Skipped (can configure later)")
|
||||||
|
|
||||||
# If an allowlist was set and home channel wasn't, offer to reuse
|
# If an allowlist was set and home channel wasn't, offer to reuse
|
||||||
# the first user ID (common for Telegram DMs).
|
# the first user ID (common for Telegram DMs).
|
||||||
|
|
@ -1556,7 +1556,7 @@ def _setup_signal():
|
||||||
print_success("Signal configured!")
|
print_success("Signal configured!")
|
||||||
print_info(f" URL: {url}")
|
print_info(f" URL: {url}")
|
||||||
print_info(f" Account: {account}")
|
print_info(f" Account: {account}")
|
||||||
print_info(f" DM auth: via SIGNAL_ALLOWED_USERS + DM pairing")
|
print_info(" DM auth: via SIGNAL_ALLOWED_USERS + DM pairing")
|
||||||
print_info(f" Groups: {'enabled' if get_env_value('SIGNAL_GROUP_ALLOWED_USERS') else 'disabled'}")
|
print_info(f" Groups: {'enabled' if get_env_value('SIGNAL_GROUP_ALLOWED_USERS') else 'disabled'}")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@ def _session_browse_picker(sessions: list) -> Optional[str]:
|
||||||
return sessions[idx]["id"]
|
return sessions[idx]["id"]
|
||||||
print(f" Invalid selection. Enter 1-{len(sessions)} or q to cancel.")
|
print(f" Invalid selection. Enter 1-{len(sessions)} or q to cancel.")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print(f" Invalid input. Enter a number or q to cancel.")
|
print(" Invalid input. Enter a number or q to cancel.")
|
||||||
except (KeyboardInterrupt, EOFError):
|
except (KeyboardInterrupt, EOFError):
|
||||||
print()
|
print()
|
||||||
return None
|
return None
|
||||||
|
|
@ -2038,8 +2038,8 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
|
||||||
else:
|
else:
|
||||||
model_list = _PROVIDER_MODELS.get(provider_id, [])
|
model_list = _PROVIDER_MODELS.get(provider_id, [])
|
||||||
if model_list:
|
if model_list:
|
||||||
print(f" ⚠ Could not auto-detect models from API — showing defaults.")
|
print(" ⚠ Could not auto-detect models from API — showing defaults.")
|
||||||
print(f" Use \"Enter custom model name\" if you don't see your model.")
|
print(" Use \"Enter custom model name\" if you don't see your model.")
|
||||||
# else: no defaults either, will fall through to raw input
|
# else: no defaults either, will fall through to raw input
|
||||||
|
|
||||||
if model_list:
|
if model_list:
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,10 @@ def _cmd_approve(store, platform: str, code: str):
|
||||||
name = result.get("user_name", "")
|
name = result.get("user_name", "")
|
||||||
display = f"{name} ({uid})" if name else uid
|
display = f"{name} ({uid})" if name else uid
|
||||||
print(f"\n Approved! User {display} on {platform} can now use the bot~")
|
print(f"\n Approved! User {display} on {platform} can now use the bot~")
|
||||||
print(f" They'll be recognized automatically on their next message.\n")
|
print(" They'll be recognized automatically on their next message.\n")
|
||||||
else:
|
else:
|
||||||
print(f"\n Code '{code}' not found or expired for platform '{platform}'.")
|
print(f"\n Code '{code}' not found or expired for platform '{platform}'.")
|
||||||
print(f" Run 'hermes pairing list' to see pending codes.\n")
|
print(" Run 'hermes pairing list' to see pending codes.\n")
|
||||||
|
|
||||||
|
|
||||||
def _cmd_revoke(store, platform: str, user_id: str):
|
def _cmd_revoke(store, platform: str, user_id: str):
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@ def cmd_list() -> None:
|
||||||
dirs = sorted(d for d in plugins_dir.iterdir() if d.is_dir())
|
dirs = sorted(d for d in plugins_dir.iterdir() if d.is_dir())
|
||||||
if not dirs:
|
if not dirs:
|
||||||
console.print("[dim]No plugins installed.[/dim]")
|
console.print("[dim]No plugins installed.[/dim]")
|
||||||
console.print(f"[dim]Install with:[/dim] hermes plugins install owner/repo")
|
console.print("[dim]Install with:[/dim] hermes plugins install owner/repo")
|
||||||
return
|
return
|
||||||
|
|
||||||
table = Table(title="Installed Plugins", show_lines=False)
|
table = Table(title="Installed Plugins", show_lines=False)
|
||||||
|
|
|
||||||
|
|
@ -548,9 +548,9 @@ def _prompt_api_key(var: dict):
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
save_env_value(var["name"], value)
|
save_env_value(var["name"], value)
|
||||||
print_success(f" ✓ Saved")
|
print_success(" ✓ Saved")
|
||||||
else:
|
else:
|
||||||
print_warning(f" Skipped (configure later with 'hermes setup')")
|
print_warning(" Skipped (configure later with 'hermes setup')")
|
||||||
|
|
||||||
|
|
||||||
def _print_setup_summary(config: dict, hermes_home):
|
def _print_setup_summary(config: dict, hermes_home):
|
||||||
|
|
@ -725,9 +725,9 @@ def _print_setup_summary(config: dict, hermes_home):
|
||||||
f" {color('hermes config edit', Colors.GREEN)} Open config in your editor"
|
f" {color('hermes config edit', Colors.GREEN)} Open config in your editor"
|
||||||
)
|
)
|
||||||
print(f" {color('hermes config set <key> <value>', Colors.GREEN)}")
|
print(f" {color('hermes config set <key> <value>', Colors.GREEN)}")
|
||||||
print(f" Set a specific value")
|
print(" Set a specific value")
|
||||||
print()
|
print()
|
||||||
print(f" Or edit the files directly:")
|
print(" Or edit the files directly:")
|
||||||
print(f" {color(f'nano {get_config_path()}', Colors.DIM)}")
|
print(f" {color(f'nano {get_config_path()}', Colors.DIM)}")
|
||||||
print(f" {color(f'nano {get_env_path()}', Colors.DIM)}")
|
print(f" {color(f'nano {get_env_path()}', Colors.DIM)}")
|
||||||
print()
|
print()
|
||||||
|
|
@ -755,13 +755,13 @@ def _prompt_container_resources(config: dict):
|
||||||
print_info(" Persistent filesystem keeps files between sessions.")
|
print_info(" Persistent filesystem keeps files between sessions.")
|
||||||
print_info(" Set to 'no' for ephemeral sandboxes that reset each time.")
|
print_info(" Set to 'no' for ephemeral sandboxes that reset each time.")
|
||||||
persist_str = prompt(
|
persist_str = prompt(
|
||||||
f" Persist filesystem across sessions? (yes/no)", persist_label
|
" Persist filesystem across sessions? (yes/no)", persist_label
|
||||||
)
|
)
|
||||||
terminal["container_persistent"] = persist_str.lower() in ("yes", "true", "y", "1")
|
terminal["container_persistent"] = persist_str.lower() in ("yes", "true", "y", "1")
|
||||||
|
|
||||||
# CPU
|
# CPU
|
||||||
current_cpu = terminal.get("container_cpu", 1)
|
current_cpu = terminal.get("container_cpu", 1)
|
||||||
cpu_str = prompt(f" CPU cores", str(current_cpu))
|
cpu_str = prompt(" CPU cores", str(current_cpu))
|
||||||
try:
|
try:
|
||||||
terminal["container_cpu"] = float(cpu_str)
|
terminal["container_cpu"] = float(cpu_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -769,7 +769,7 @@ def _prompt_container_resources(config: dict):
|
||||||
|
|
||||||
# Memory
|
# Memory
|
||||||
current_mem = terminal.get("container_memory", 5120)
|
current_mem = terminal.get("container_memory", 5120)
|
||||||
mem_str = prompt(f" Memory in MB (5120 = 5GB)", str(current_mem))
|
mem_str = prompt(" Memory in MB (5120 = 5GB)", str(current_mem))
|
||||||
try:
|
try:
|
||||||
terminal["container_memory"] = int(mem_str)
|
terminal["container_memory"] = int(mem_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -777,7 +777,7 @@ def _prompt_container_resources(config: dict):
|
||||||
|
|
||||||
# Disk
|
# Disk
|
||||||
current_disk = terminal.get("container_disk", 51200)
|
current_disk = terminal.get("container_disk", 51200)
|
||||||
disk_str = prompt(f" Disk in MB (51200 = 50GB)", str(current_disk))
|
disk_str = prompt(" Disk in MB (51200 = 50GB)", str(current_disk))
|
||||||
try:
|
try:
|
||||||
terminal["container_disk"] = int(disk_str)
|
terminal["container_disk"] = int(disk_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -3441,9 +3441,9 @@ def _run_quick_setup(config: dict, hermes_home):
|
||||||
value = prompt(f" {var.get('prompt', var['name'])}")
|
value = prompt(f" {var.get('prompt', var['name'])}")
|
||||||
if value:
|
if value:
|
||||||
save_env_value(var["name"], value)
|
save_env_value(var["name"], value)
|
||||||
print_success(f" ✓ Saved")
|
print_success(" ✓ Saved")
|
||||||
else:
|
else:
|
||||||
print_warning(f" Skipped")
|
print_warning(" Skipped")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Handle missing config fields
|
# Handle missing config fields
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ def show_status(args):
|
||||||
)
|
)
|
||||||
is_active = result.stdout.strip() == "active"
|
is_active = result.stdout.strip() == "active"
|
||||||
print(f" Status: {check_mark(is_active)} {'running' if is_active else 'stopped'}")
|
print(f" Status: {check_mark(is_active)} {'running' if is_active else 'stopped'}")
|
||||||
print(f" Manager: systemd (user)")
|
print(" Manager: systemd (user)")
|
||||||
|
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == 'darwin':
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|
@ -299,10 +299,10 @@ def show_status(args):
|
||||||
)
|
)
|
||||||
is_loaded = result.returncode == 0
|
is_loaded = result.returncode == 0
|
||||||
print(f" Status: {check_mark(is_loaded)} {'loaded' if is_loaded else 'not loaded'}")
|
print(f" Status: {check_mark(is_loaded)} {'loaded' if is_loaded else 'not loaded'}")
|
||||||
print(f" Manager: launchd")
|
print(" Manager: launchd")
|
||||||
else:
|
else:
|
||||||
print(f" Status: {color('N/A', Colors.DIM)}")
|
print(f" Status: {color('N/A', Colors.DIM)}")
|
||||||
print(f" Manager: (not supported on this platform)")
|
print(" Manager: (not supported on this platform)")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Cron Jobs
|
# Cron Jobs
|
||||||
|
|
@ -320,9 +320,9 @@ def show_status(args):
|
||||||
enabled_jobs = [j for j in jobs if j.get("enabled", True)]
|
enabled_jobs = [j for j in jobs if j.get("enabled", True)]
|
||||||
print(f" Jobs: {len(enabled_jobs)} active, {len(jobs)} total")
|
print(f" Jobs: {len(enabled_jobs)} active, {len(jobs)} total")
|
||||||
except Exception:
|
except Exception:
|
||||||
print(f" Jobs: (error reading jobs file)")
|
print(" Jobs: (error reading jobs file)")
|
||||||
else:
|
else:
|
||||||
print(f" Jobs: 0")
|
print(" Jobs: 0")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Sessions
|
# Sessions
|
||||||
|
|
@ -338,9 +338,9 @@ def show_status(args):
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
print(f" Active: {len(data)} session(s)")
|
print(f" Active: {len(data)} session(s)")
|
||||||
except Exception:
|
except Exception:
|
||||||
print(f" Active: (error reading sessions file)")
|
print(" Active: (error reading sessions file)")
|
||||||
else:
|
else:
|
||||||
print(f" Active: 0")
|
print(" Active: 0")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Deep checks
|
# Deep checks
|
||||||
|
|
|
||||||
|
|
@ -659,7 +659,7 @@ def _configure_tool_category(ts_key: str, cat: dict, config: dict):
|
||||||
# Multiple providers - let user choose
|
# Multiple providers - let user choose
|
||||||
print()
|
print()
|
||||||
# Use custom title if provided (e.g. "Select Search Provider")
|
# Use custom title if provided (e.g. "Select Search Provider")
|
||||||
title = cat.get("setup_title", f"Choose a provider")
|
title = cat.get("setup_title", "Choose a provider")
|
||||||
print(color(f" --- {icon} {name} - {title} ---", Colors.CYAN))
|
print(color(f" --- {icon} {name} - {title} ---", Colors.CYAN))
|
||||||
if cat.get("setup_note"):
|
if cat.get("setup_note"):
|
||||||
_print_info(f" {cat['setup_note']}")
|
_print_info(f" {cat['setup_note']}")
|
||||||
|
|
@ -768,9 +768,9 @@ def _configure_provider(provider: dict, config: dict):
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
save_env_value(var["key"], value)
|
save_env_value(var["key"], value)
|
||||||
_print_success(f" Saved")
|
_print_success(" Saved")
|
||||||
else:
|
else:
|
||||||
_print_warning(f" Skipped")
|
_print_warning(" Skipped")
|
||||||
all_configured = False
|
all_configured = False
|
||||||
|
|
||||||
# Run post-setup hooks if needed
|
# Run post-setup hooks if needed
|
||||||
|
|
@ -834,9 +834,9 @@ def _configure_simple_requirements(ts_key: str):
|
||||||
value = _prompt(f" {var}", password=True)
|
value = _prompt(f" {var}", password=True)
|
||||||
if value and value.strip():
|
if value and value.strip():
|
||||||
save_env_value(var, value.strip())
|
save_env_value(var, value.strip())
|
||||||
_print_success(f" Saved")
|
_print_success(" Saved")
|
||||||
else:
|
else:
|
||||||
_print_warning(f" Skipped")
|
_print_warning(" Skipped")
|
||||||
|
|
||||||
|
|
||||||
def _reconfigure_tool(config: dict):
|
def _reconfigure_tool(config: dict):
|
||||||
|
|
@ -924,7 +924,7 @@ def _reconfigure_provider(provider: dict, config: dict):
|
||||||
_print_success(f" Browser cloud provider set to: {bp}")
|
_print_success(f" Browser cloud provider set to: {bp}")
|
||||||
else:
|
else:
|
||||||
config.get("browser", {}).pop("cloud_provider", None)
|
config.get("browser", {}).pop("cloud_provider", None)
|
||||||
_print_success(f" Browser set to local mode")
|
_print_success(" Browser set to local mode")
|
||||||
|
|
||||||
# Set web search backend in config if applicable
|
# Set web search backend in config if applicable
|
||||||
if provider.get("web_backend"):
|
if provider.get("web_backend"):
|
||||||
|
|
@ -946,9 +946,9 @@ def _reconfigure_provider(provider: dict, config: dict):
|
||||||
value = _prompt(f" {var.get('prompt', var['key'])} (Enter to keep current)", password=not default_val)
|
value = _prompt(f" {var.get('prompt', var['key'])} (Enter to keep current)", password=not default_val)
|
||||||
if value and value.strip():
|
if value and value.strip():
|
||||||
save_env_value(var["key"], value.strip())
|
save_env_value(var["key"], value.strip())
|
||||||
_print_success(f" Updated")
|
_print_success(" Updated")
|
||||||
else:
|
else:
|
||||||
_print_info(f" Kept current")
|
_print_info(" Kept current")
|
||||||
|
|
||||||
|
|
||||||
def _reconfigure_simple_requirements(ts_key: str):
|
def _reconfigure_simple_requirements(ts_key: str):
|
||||||
|
|
@ -970,9 +970,9 @@ def _reconfigure_simple_requirements(ts_key: str):
|
||||||
value = _prompt(f" {var} (Enter to keep current)", password=True)
|
value = _prompt(f" {var} (Enter to keep current)", password=True)
|
||||||
if value and value.strip():
|
if value and value.strip():
|
||||||
save_env_value(var, value.strip())
|
save_env_value(var, value.strip())
|
||||||
_print_success(f" Updated")
|
_print_success(" Updated")
|
||||||
else:
|
else:
|
||||||
_print_info(f" Kept current")
|
_print_info(" Kept current")
|
||||||
|
|
||||||
|
|
||||||
# ─── Main Entry Point ─────────────────────────────────────────────────────────
|
# ─── Main Entry Point ─────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ def run_uninstall(args):
|
||||||
log_info("No wrapper script found")
|
log_info("No wrapper script found")
|
||||||
|
|
||||||
# 4. Remove installation directory (code)
|
# 4. Remove installation directory (code)
|
||||||
log_info(f"Removing installation directory...")
|
log_info("Removing installation directory...")
|
||||||
|
|
||||||
# Check if we're running from within the install dir
|
# Check if we're running from within the install dir
|
||||||
# We need to be careful here
|
# We need to be careful here
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ def cmd_setup(args) -> None:
|
||||||
|
|
||||||
# Memory mode
|
# Memory mode
|
||||||
current_mode = hermes_host.get("memoryMode") or cfg.get("memoryMode", "hybrid")
|
current_mode = hermes_host.get("memoryMode") or cfg.get("memoryMode", "hybrid")
|
||||||
print(f"\n Memory mode options:")
|
print("\n Memory mode options:")
|
||||||
print(" hybrid — write to both Honcho and local MEMORY.md (default)")
|
print(" hybrid — write to both Honcho and local MEMORY.md (default)")
|
||||||
print(" honcho — Honcho only, skip MEMORY.md writes")
|
print(" honcho — Honcho only, skip MEMORY.md writes")
|
||||||
new_mode = _prompt("Memory mode", default=current_mode)
|
new_mode = _prompt("Memory mode", default=current_mode)
|
||||||
|
|
@ -152,7 +152,7 @@ def cmd_setup(args) -> None:
|
||||||
|
|
||||||
# Write frequency
|
# Write frequency
|
||||||
current_wf = str(hermes_host.get("writeFrequency") or cfg.get("writeFrequency", "async"))
|
current_wf = str(hermes_host.get("writeFrequency") or cfg.get("writeFrequency", "async"))
|
||||||
print(f"\n Write frequency options:")
|
print("\n Write frequency options:")
|
||||||
print(" async — background thread, no token cost (recommended)")
|
print(" async — background thread, no token cost (recommended)")
|
||||||
print(" turn — sync write after every turn")
|
print(" turn — sync write after every turn")
|
||||||
print(" session — batch write at session end only")
|
print(" session — batch write at session end only")
|
||||||
|
|
@ -166,7 +166,7 @@ def cmd_setup(args) -> None:
|
||||||
# Recall mode
|
# Recall mode
|
||||||
_raw_recall = hermes_host.get("recallMode") or cfg.get("recallMode", "hybrid")
|
_raw_recall = hermes_host.get("recallMode") or cfg.get("recallMode", "hybrid")
|
||||||
current_recall = "hybrid" if _raw_recall not in ("hybrid", "context", "tools") else _raw_recall
|
current_recall = "hybrid" if _raw_recall not in ("hybrid", "context", "tools") else _raw_recall
|
||||||
print(f"\n Recall mode options:")
|
print("\n Recall mode options:")
|
||||||
print(" hybrid — auto-injected context + Honcho tools available (default)")
|
print(" hybrid — auto-injected context + Honcho tools available (default)")
|
||||||
print(" context — auto-injected context only, Honcho tools hidden")
|
print(" context — auto-injected context only, Honcho tools hidden")
|
||||||
print(" tools — Honcho tools only, no auto-injected context")
|
print(" tools — Honcho tools only, no auto-injected context")
|
||||||
|
|
@ -176,7 +176,7 @@ def cmd_setup(args) -> None:
|
||||||
|
|
||||||
# Session strategy
|
# Session strategy
|
||||||
current_strat = hermes_host.get("sessionStrategy") or cfg.get("sessionStrategy", "per-directory")
|
current_strat = hermes_host.get("sessionStrategy") or cfg.get("sessionStrategy", "per-directory")
|
||||||
print(f"\n Session strategy options:")
|
print("\n Session strategy options:")
|
||||||
print(" per-directory — one session per working directory (default)")
|
print(" per-directory — one session per working directory (default)")
|
||||||
print(" per-session — new Honcho session each run, named by Hermes session ID")
|
print(" per-session — new Honcho session each run, named by Hermes session ID")
|
||||||
print(" per-repo — one session per git repository (uses repo root name)")
|
print(" per-repo — one session per git repository (uses repo root name)")
|
||||||
|
|
@ -203,7 +203,7 @@ def cmd_setup(args) -> None:
|
||||||
print(f"FAILED\n Error: {e}")
|
print(f"FAILED\n Error: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"\n Honcho is ready.")
|
print("\n Honcho is ready.")
|
||||||
print(f" Session: {hcfg.resolve_session_name()}")
|
print(f" Session: {hcfg.resolve_session_name()}")
|
||||||
print(f" Workspace: {hcfg.workspace_id}")
|
print(f" Workspace: {hcfg.workspace_id}")
|
||||||
print(f" Peer: {hcfg.peer_name}")
|
print(f" Peer: {hcfg.peer_name}")
|
||||||
|
|
@ -213,17 +213,17 @@ def cmd_setup(args) -> None:
|
||||||
_mode_str = f"{hcfg.memory_mode} (peers: {overrides})"
|
_mode_str = f"{hcfg.memory_mode} (peers: {overrides})"
|
||||||
print(f" Mode: {_mode_str}")
|
print(f" Mode: {_mode_str}")
|
||||||
print(f" Frequency: {hcfg.write_frequency}")
|
print(f" Frequency: {hcfg.write_frequency}")
|
||||||
print(f"\n Honcho tools available in chat:")
|
print("\n Honcho tools available in chat:")
|
||||||
print(f" honcho_context — ask Honcho a question about you (LLM-synthesized)")
|
print(" honcho_context — ask Honcho a question about you (LLM-synthesized)")
|
||||||
print(f" honcho_search — semantic search over your history (no LLM)")
|
print(" honcho_search — semantic search over your history (no LLM)")
|
||||||
print(f" honcho_profile — your peer card, key facts (no LLM)")
|
print(" honcho_profile — your peer card, key facts (no LLM)")
|
||||||
print(f" honcho_conclude — persist a user fact to Honcho memory (no LLM)")
|
print(" honcho_conclude — persist a user fact to Honcho memory (no LLM)")
|
||||||
print(f"\n Other commands:")
|
print("\n Other commands:")
|
||||||
print(f" hermes honcho status — show full config")
|
print(" hermes honcho status — show full config")
|
||||||
print(f" hermes honcho mode — show or change memory mode")
|
print(" hermes honcho mode — show or change memory mode")
|
||||||
print(f" hermes honcho tokens — show or set token budgets")
|
print(" hermes honcho tokens — show or set token budgets")
|
||||||
print(f" hermes honcho identity — seed or show AI peer identity")
|
print(" hermes honcho identity — seed or show AI peer identity")
|
||||||
print(f" hermes honcho map <name> — map this directory to a session name\n")
|
print(" hermes honcho map <name> — map this directory to a session name\n")
|
||||||
|
|
||||||
|
|
||||||
def cmd_status(args) -> None:
|
def cmd_status(args) -> None:
|
||||||
|
|
@ -253,7 +253,7 @@ def cmd_status(args) -> None:
|
||||||
api_key = hcfg.api_key or ""
|
api_key = hcfg.api_key or ""
|
||||||
masked = f"...{api_key[-8:]}" if len(api_key) > 8 else ("set" if api_key else "not set")
|
masked = f"...{api_key[-8:]}" if len(api_key) > 8 else ("set" if api_key else "not set")
|
||||||
|
|
||||||
print(f"\nHoncho status\n" + "─" * 40)
|
print("\nHoncho status\n" + "─" * 40)
|
||||||
print(f" Enabled: {hcfg.enabled}")
|
print(f" Enabled: {hcfg.enabled}")
|
||||||
print(f" API key: {masked}")
|
print(f" API key: {masked}")
|
||||||
print(f" Workspace: {hcfg.workspace_id}")
|
print(f" Workspace: {hcfg.workspace_id}")
|
||||||
|
|
@ -265,7 +265,7 @@ def cmd_status(args) -> None:
|
||||||
print(f" Recall mode: {hcfg.recall_mode}")
|
print(f" Recall mode: {hcfg.recall_mode}")
|
||||||
print(f" Memory mode: {hcfg.memory_mode}")
|
print(f" Memory mode: {hcfg.memory_mode}")
|
||||||
if hcfg.peer_memory_modes:
|
if hcfg.peer_memory_modes:
|
||||||
print(f" Per-peer modes:")
|
print(" Per-peer modes:")
|
||||||
for peer, mode in hcfg.peer_memory_modes.items():
|
for peer, mode in hcfg.peer_memory_modes.items():
|
||||||
print(f" {peer}: {mode}")
|
print(f" {peer}: {mode}")
|
||||||
print(f" Write freq: {hcfg.write_frequency}")
|
print(f" Write freq: {hcfg.write_frequency}")
|
||||||
|
|
@ -345,12 +345,12 @@ def cmd_peer(args) -> None:
|
||||||
ai = hermes.get('aiPeer') or cfg.get('aiPeer') or HOST
|
ai = hermes.get('aiPeer') or cfg.get('aiPeer') or HOST
|
||||||
lvl = hermes.get("dialecticReasoningLevel") or cfg.get("dialecticReasoningLevel") or "low"
|
lvl = hermes.get("dialecticReasoningLevel") or cfg.get("dialecticReasoningLevel") or "low"
|
||||||
max_chars = hermes.get("dialecticMaxChars") or cfg.get("dialecticMaxChars") or 600
|
max_chars = hermes.get("dialecticMaxChars") or cfg.get("dialecticMaxChars") or 600
|
||||||
print(f"\nHoncho peers\n" + "─" * 40)
|
print("\nHoncho peers\n" + "─" * 40)
|
||||||
print(f" User peer: {user}")
|
print(f" User peer: {user}")
|
||||||
print(f" Your identity in Honcho. Messages you send build this peer's card.")
|
print(" Your identity in Honcho. Messages you send build this peer's card.")
|
||||||
print(f" AI peer: {ai}")
|
print(f" AI peer: {ai}")
|
||||||
print(f" Hermes' identity in Honcho. Seed with 'hermes honcho identity <file>'.")
|
print(" Hermes' identity in Honcho. Seed with 'hermes honcho identity <file>'.")
|
||||||
print(f" Dialectic calls ask this peer questions to warm session context.")
|
print(" Dialectic calls ask this peer questions to warm session context.")
|
||||||
print()
|
print()
|
||||||
print(f" Dialectic reasoning: {lvl} ({', '.join(REASONING_LEVELS)})")
|
print(f" Dialectic reasoning: {lvl} ({', '.join(REASONING_LEVELS)})")
|
||||||
print(f" Dialectic cap: {max_chars} chars\n")
|
print(f" Dialectic cap: {max_chars} chars\n")
|
||||||
|
|
@ -394,11 +394,11 @@ def cmd_mode(args) -> None:
|
||||||
or cfg.get("memoryMode")
|
or cfg.get("memoryMode")
|
||||||
or "hybrid"
|
or "hybrid"
|
||||||
)
|
)
|
||||||
print(f"\nHoncho memory mode\n" + "─" * 40)
|
print("\nHoncho memory mode\n" + "─" * 40)
|
||||||
for m, desc in MODES.items():
|
for m, desc in MODES.items():
|
||||||
marker = " ←" if m == current else ""
|
marker = " ←" if m == current else ""
|
||||||
print(f" {m:<8} {desc}{marker}")
|
print(f" {m:<8} {desc}{marker}")
|
||||||
print(f"\n Set with: hermes honcho mode [hybrid|honcho]\n")
|
print("\n Set with: hermes honcho mode [hybrid|honcho]\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
if mode_arg not in MODES:
|
if mode_arg not in MODES:
|
||||||
|
|
@ -423,18 +423,18 @@ def cmd_tokens(args) -> None:
|
||||||
ctx_tokens = hermes.get("contextTokens") or cfg.get("contextTokens") or "(Honcho default)"
|
ctx_tokens = hermes.get("contextTokens") or cfg.get("contextTokens") or "(Honcho default)"
|
||||||
d_chars = hermes.get("dialecticMaxChars") or cfg.get("dialecticMaxChars") or 600
|
d_chars = hermes.get("dialecticMaxChars") or cfg.get("dialecticMaxChars") or 600
|
||||||
d_level = hermes.get("dialecticReasoningLevel") or cfg.get("dialecticReasoningLevel") or "low"
|
d_level = hermes.get("dialecticReasoningLevel") or cfg.get("dialecticReasoningLevel") or "low"
|
||||||
print(f"\nHoncho budgets\n" + "─" * 40)
|
print("\nHoncho budgets\n" + "─" * 40)
|
||||||
print()
|
print()
|
||||||
print(f" Context {ctx_tokens} tokens")
|
print(f" Context {ctx_tokens} tokens")
|
||||||
print(f" Raw memory retrieval. Honcho returns stored facts/history about")
|
print(" Raw memory retrieval. Honcho returns stored facts/history about")
|
||||||
print(f" the user and session, injected directly into the system prompt.")
|
print(" the user and session, injected directly into the system prompt.")
|
||||||
print()
|
print()
|
||||||
print(f" Dialectic {d_chars} chars, reasoning: {d_level}")
|
print(f" Dialectic {d_chars} chars, reasoning: {d_level}")
|
||||||
print(f" AI-to-AI inference. Hermes asks Honcho's AI peer a question")
|
print(" AI-to-AI inference. Hermes asks Honcho's AI peer a question")
|
||||||
print(f" (e.g. \"what were we working on?\") and Honcho runs its own model")
|
print(" (e.g. \"what were we working on?\") and Honcho runs its own model")
|
||||||
print(f" to synthesize an answer. Used for first-turn session continuity.")
|
print(" to synthesize an answer. Used for first-turn session continuity.")
|
||||||
print(f" Level controls how much reasoning Honcho spends on the answer.")
|
print(" Level controls how much reasoning Honcho spends on the answer.")
|
||||||
print(f"\n Set with: hermes honcho tokens [--context N] [--dialectic N]\n")
|
print("\n Set with: hermes honcho tokens [--context N] [--dialectic N]\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
|
|
@ -523,7 +523,7 @@ def cmd_identity(args) -> None:
|
||||||
print(f" Seeded AI peer identity from {p.name} into session '{session_key}'")
|
print(f" Seeded AI peer identity from {p.name} into session '{session_key}'")
|
||||||
print(f" Honcho will incorporate this into {hcfg.ai_peer}'s representation over time.\n")
|
print(f" Honcho will incorporate this into {hcfg.ai_peer}'s representation over time.\n")
|
||||||
else:
|
else:
|
||||||
print(f" Failed to seed identity. Check logs for details.\n")
|
print(" Failed to seed identity. Check logs for details.\n")
|
||||||
|
|
||||||
|
|
||||||
def cmd_migrate(args) -> None:
|
def cmd_migrate(args) -> None:
|
||||||
|
|
@ -623,7 +623,7 @@ def cmd_migrate(args) -> None:
|
||||||
print()
|
print()
|
||||||
print(" If you want to migrate them now without starting a session:")
|
print(" If you want to migrate them now without starting a session:")
|
||||||
for f in user_files:
|
for f in user_files:
|
||||||
print(f" hermes honcho migrate — this step handles it interactively")
|
print(" hermes honcho migrate — this step handles it interactively")
|
||||||
if has_key:
|
if has_key:
|
||||||
answer = _prompt(" Upload user memory files to Honcho now?", default="y")
|
answer = _prompt(" Upload user memory files to Honcho now?", default="y")
|
||||||
if answer.lower() in ("y", "yes"):
|
if answer.lower() in ("y", "yes"):
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ class MiniSWERunner:
|
||||||
# Tool definition
|
# Tool definition
|
||||||
self.tools = [TERMINAL_TOOL_DEFINITION]
|
self.tools = [TERMINAL_TOOL_DEFINITION]
|
||||||
|
|
||||||
print(f"🤖 Mini-SWE Runner initialized")
|
print("🤖 Mini-SWE Runner initialized")
|
||||||
print(f" Model: {self.model}")
|
print(f" Model: {self.model}")
|
||||||
print(f" Environment: {self.env_type}")
|
print(f" Environment: {self.env_type}")
|
||||||
if self.env_type != "local":
|
if self.env_type != "local":
|
||||||
|
|
@ -233,7 +233,7 @@ class MiniSWERunner:
|
||||||
cwd=self.cwd,
|
cwd=self.cwd,
|
||||||
timeout=self.command_timeout
|
timeout=self.command_timeout
|
||||||
)
|
)
|
||||||
print(f"✅ Environment ready")
|
print("✅ Environment ready")
|
||||||
|
|
||||||
def _cleanup_env(self):
|
def _cleanup_env(self):
|
||||||
"""Cleanup the execution environment."""
|
"""Cleanup the execution environment."""
|
||||||
|
|
@ -365,7 +365,7 @@ class MiniSWERunner:
|
||||||
except (json.JSONDecodeError, AttributeError):
|
except (json.JSONDecodeError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
tool_response = f"<tool_response>\n"
|
tool_response = "<tool_response>\n"
|
||||||
tool_response += json.dumps({
|
tool_response += json.dumps({
|
||||||
"tool_call_id": tool_msg.get("tool_call_id", ""),
|
"tool_call_id": tool_msg.get("tool_call_id", ""),
|
||||||
"name": msg["tool_calls"][len(tool_responses)]["function"]["name"] \
|
"name": msg["tool_calls"][len(tool_responses)]["function"]["name"] \
|
||||||
|
|
@ -505,7 +505,7 @@ Complete the user's task step by step."""
|
||||||
|
|
||||||
# Check for task completion signal
|
# Check for task completion signal
|
||||||
if "MINI_SWE_AGENT_FINAL_OUTPUT" in result["output"]:
|
if "MINI_SWE_AGENT_FINAL_OUTPUT" in result["output"]:
|
||||||
print(f" ✅ Task completion signal detected!")
|
print(" ✅ Task completion signal detected!")
|
||||||
completed = True
|
completed = True
|
||||||
|
|
||||||
# Add tool response
|
# Add tool response
|
||||||
|
|
@ -530,7 +530,7 @@ Complete the user's task step by step."""
|
||||||
"content": final_response
|
"content": final_response
|
||||||
})
|
})
|
||||||
completed = True
|
completed = True
|
||||||
print(f"🎉 Agent finished (no more tool calls)")
|
print("🎉 Agent finished (no more tool calls)")
|
||||||
break
|
break
|
||||||
|
|
||||||
if api_call_count >= self.max_iterations:
|
if api_call_count >= self.max_iterations:
|
||||||
|
|
|
||||||
77
run_agent.py
77
run_agent.py
|
|
@ -524,7 +524,7 @@ class AIAgent:
|
||||||
# Pre-warm OpenRouter model metadata cache in a background thread.
|
# Pre-warm OpenRouter model metadata cache in a background thread.
|
||||||
# fetch_model_metadata() is cached for 1 hour; this avoids a blocking
|
# fetch_model_metadata() is cached for 1 hour; this avoids a blocking
|
||||||
# HTTP request on the first API response when pricing is estimated.
|
# HTTP request on the first API response when pricing is estimated.
|
||||||
if self.provider == "openrouter" or "openrouter" in self._base_url_lower:
|
if self.provider == "openrouter" or self._is_openrouter_url():
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=lambda: fetch_model_metadata(),
|
target=lambda: fetch_model_metadata(),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
|
|
@ -574,7 +574,7 @@ class AIAgent:
|
||||||
# Anthropic prompt caching: auto-enabled for Claude models via OpenRouter.
|
# Anthropic prompt caching: auto-enabled for Claude models via OpenRouter.
|
||||||
# Reduces input costs by ~75% on multi-turn conversations by caching the
|
# Reduces input costs by ~75% on multi-turn conversations by caching the
|
||||||
# conversation prefix. Uses system_and_3 strategy (4 breakpoints).
|
# conversation prefix. Uses system_and_3 strategy (4 breakpoints).
|
||||||
is_openrouter = "openrouter" in self._base_url_lower
|
is_openrouter = self._is_openrouter_url()
|
||||||
is_claude = "claude" in self.model.lower()
|
is_claude = "claude" in self.model.lower()
|
||||||
is_native_anthropic = self.api_mode == "anthropic_messages"
|
is_native_anthropic = self.api_mode == "anthropic_messages"
|
||||||
self._use_prompt_caching = (is_openrouter and is_claude) or is_native_anthropic
|
self._use_prompt_caching = (is_openrouter and is_claude) or is_native_anthropic
|
||||||
|
|
@ -694,6 +694,7 @@ class AIAgent:
|
||||||
# raw_codex=True because the main agent needs direct responses.stream()
|
# raw_codex=True because the main agent needs direct responses.stream()
|
||||||
# access for Codex Responses API streaming.
|
# access for Codex Responses API streaming.
|
||||||
self._anthropic_client = None
|
self._anthropic_client = None
|
||||||
|
self._is_anthropic_oauth = False
|
||||||
|
|
||||||
if self.api_mode == "anthropic_messages":
|
if self.api_mode == "anthropic_messages":
|
||||||
from agent.anthropic_adapter import build_anthropic_client, resolve_anthropic_token
|
from agent.anthropic_adapter import build_anthropic_client, resolve_anthropic_token
|
||||||
|
|
@ -1178,6 +1179,14 @@ class AIAgent:
|
||||||
url = (base_url or self._base_url_lower).lower()
|
url = (base_url or self._base_url_lower).lower()
|
||||||
return "api.openai.com" in url and "openrouter" not in url
|
return "api.openai.com" in url and "openrouter" not in url
|
||||||
|
|
||||||
|
def _is_openrouter_url(self) -> bool:
|
||||||
|
"""Return True when the base URL targets OpenRouter."""
|
||||||
|
return "openrouter" in self._base_url_lower
|
||||||
|
|
||||||
|
def _is_anthropic_url(self) -> bool:
|
||||||
|
"""Return True when the base URL targets Anthropic (native or /anthropic proxy path)."""
|
||||||
|
return "api.anthropic.com" in self._base_url_lower or self._base_url_lower.rstrip("/").endswith("/anthropic")
|
||||||
|
|
||||||
def _max_tokens_param(self, value: int) -> dict:
|
def _max_tokens_param(self, value: int) -> dict:
|
||||||
"""Return the correct max tokens kwarg for the current provider.
|
"""Return the correct max tokens kwarg for the current provider.
|
||||||
|
|
||||||
|
|
@ -1731,7 +1740,7 @@ class AIAgent:
|
||||||
while j < len(messages) and messages[j]["role"] == "tool":
|
while j < len(messages) and messages[j]["role"] == "tool":
|
||||||
tool_msg = messages[j]
|
tool_msg = messages[j]
|
||||||
# Format tool response with XML tags
|
# Format tool response with XML tags
|
||||||
tool_response = f"<tool_response>\n"
|
tool_response = "<tool_response>\n"
|
||||||
|
|
||||||
# Try to parse tool content as JSON if it looks like JSON
|
# Try to parse tool content as JSON if it looks like JSON
|
||||||
tool_content = tool_msg["content"]
|
tool_content = tool_msg["content"]
|
||||||
|
|
@ -2064,7 +2073,7 @@ class AIAgent:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug("Failed to propagate interrupt to child agent: %s", e)
|
logger.debug("Failed to propagate interrupt to child agent: %s", e)
|
||||||
if not self.quiet_mode:
|
if not self.quiet_mode:
|
||||||
print(f"\n⚡ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else ""))
|
print("\n⚡ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else ""))
|
||||||
|
|
||||||
def clear_interrupt(self) -> None:
|
def clear_interrupt(self) -> None:
|
||||||
"""Clear any pending interrupt request and the global tool interrupt signal."""
|
"""Clear any pending interrupt request and the global tool interrupt signal."""
|
||||||
|
|
@ -4257,7 +4266,7 @@ class AIAgent:
|
||||||
tools=self.tools,
|
tools=self.tools,
|
||||||
max_tokens=self.max_tokens,
|
max_tokens=self.max_tokens,
|
||||||
reasoning_config=self.reasoning_config,
|
reasoning_config=self.reasoning_config,
|
||||||
is_oauth=getattr(self, "_is_anthropic_oauth", False),
|
is_oauth=self._is_anthropic_oauth,
|
||||||
preserve_dots=self._anthropic_preserve_dots(),
|
preserve_dots=self._anthropic_preserve_dots(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -4378,7 +4387,7 @@ class AIAgent:
|
||||||
|
|
||||||
extra_body = {}
|
extra_body = {}
|
||||||
|
|
||||||
_is_openrouter = "openrouter" in self._base_url_lower
|
_is_openrouter = self._is_openrouter_url()
|
||||||
_is_github_models = (
|
_is_github_models = (
|
||||||
"models.github.ai" in self._base_url_lower
|
"models.github.ai" in self._base_url_lower
|
||||||
or "api.githubcopilot.com" in self._base_url_lower
|
or "api.githubcopilot.com" in self._base_url_lower
|
||||||
|
|
@ -4747,7 +4756,7 @@ class AIAgent:
|
||||||
tool_calls = assistant_msg.tool_calls
|
tool_calls = assistant_msg.tool_calls
|
||||||
elif self.api_mode == "anthropic_messages" and not _aux_available:
|
elif self.api_mode == "anthropic_messages" and not _aux_available:
|
||||||
from agent.anthropic_adapter import normalize_anthropic_response as _nar_flush
|
from agent.anthropic_adapter import normalize_anthropic_response as _nar_flush
|
||||||
_flush_msg, _ = _nar_flush(response, strip_tool_prefix=getattr(self, '_is_anthropic_oauth', False))
|
_flush_msg, _ = _nar_flush(response, strip_tool_prefix=self._is_anthropic_oauth)
|
||||||
if _flush_msg and _flush_msg.tool_calls:
|
if _flush_msg and _flush_msg.tool_calls:
|
||||||
tool_calls = _flush_msg.tool_calls
|
tool_calls = _flush_msg.tool_calls
|
||||||
elif hasattr(response, "choices") and response.choices:
|
elif hasattr(response, "choices") and response.choices:
|
||||||
|
|
@ -5548,10 +5557,10 @@ class AIAgent:
|
||||||
from agent.anthropic_adapter import build_anthropic_kwargs as _bak, normalize_anthropic_response as _nar
|
from agent.anthropic_adapter import build_anthropic_kwargs as _bak, normalize_anthropic_response as _nar
|
||||||
_ant_kw = _bak(model=self.model, messages=api_messages, tools=None,
|
_ant_kw = _bak(model=self.model, messages=api_messages, tools=None,
|
||||||
max_tokens=self.max_tokens, reasoning_config=self.reasoning_config,
|
max_tokens=self.max_tokens, reasoning_config=self.reasoning_config,
|
||||||
is_oauth=getattr(self, '_is_anthropic_oauth', False),
|
is_oauth=self._is_anthropic_oauth,
|
||||||
preserve_dots=self._anthropic_preserve_dots())
|
preserve_dots=self._anthropic_preserve_dots())
|
||||||
summary_response = self._anthropic_messages_create(_ant_kw)
|
summary_response = self._anthropic_messages_create(_ant_kw)
|
||||||
_msg, _ = _nar(summary_response, strip_tool_prefix=getattr(self, '_is_anthropic_oauth', False))
|
_msg, _ = _nar(summary_response, strip_tool_prefix=self._is_anthropic_oauth)
|
||||||
final_response = (_msg.content or "").strip()
|
final_response = (_msg.content or "").strip()
|
||||||
else:
|
else:
|
||||||
summary_response = self._ensure_primary_openai_client(reason="iteration_limit_summary").chat.completions.create(**summary_kwargs)
|
summary_response = self._ensure_primary_openai_client(reason="iteration_limit_summary").chat.completions.create(**summary_kwargs)
|
||||||
|
|
@ -5579,11 +5588,11 @@ class AIAgent:
|
||||||
elif self.api_mode == "anthropic_messages":
|
elif self.api_mode == "anthropic_messages":
|
||||||
from agent.anthropic_adapter import build_anthropic_kwargs as _bak2, normalize_anthropic_response as _nar2
|
from agent.anthropic_adapter import build_anthropic_kwargs as _bak2, normalize_anthropic_response as _nar2
|
||||||
_ant_kw2 = _bak2(model=self.model, messages=api_messages, tools=None,
|
_ant_kw2 = _bak2(model=self.model, messages=api_messages, tools=None,
|
||||||
is_oauth=getattr(self, '_is_anthropic_oauth', False),
|
is_oauth=self._is_anthropic_oauth,
|
||||||
max_tokens=self.max_tokens, reasoning_config=self.reasoning_config,
|
max_tokens=self.max_tokens, reasoning_config=self.reasoning_config,
|
||||||
preserve_dots=self._anthropic_preserve_dots())
|
preserve_dots=self._anthropic_preserve_dots())
|
||||||
retry_response = self._anthropic_messages_create(_ant_kw2)
|
retry_response = self._anthropic_messages_create(_ant_kw2)
|
||||||
_retry_msg, _ = _nar2(retry_response, strip_tool_prefix=getattr(self, '_is_anthropic_oauth', False))
|
_retry_msg, _ = _nar2(retry_response, strip_tool_prefix=self._is_anthropic_oauth)
|
||||||
final_response = (_retry_msg.content or "").strip()
|
final_response = (_retry_msg.content or "").strip()
|
||||||
else:
|
else:
|
||||||
summary_kwargs = {
|
summary_kwargs = {
|
||||||
|
|
@ -5845,7 +5854,7 @@ class AIAgent:
|
||||||
if self._interrupt_requested:
|
if self._interrupt_requested:
|
||||||
interrupted = True
|
interrupted = True
|
||||||
if not self.quiet_mode:
|
if not self.quiet_mode:
|
||||||
self._safe_print(f"\n⚡ Breaking out of tool loop due to interrupt...")
|
self._safe_print("\n⚡ Breaking out of tool loop due to interrupt...")
|
||||||
break
|
break
|
||||||
|
|
||||||
api_call_count += 1
|
api_call_count += 1
|
||||||
|
|
@ -6074,7 +6083,7 @@ class AIAgent:
|
||||||
if response_invalid:
|
if response_invalid:
|
||||||
# Stop spinner before printing error messages
|
# Stop spinner before printing error messages
|
||||||
if thinking_spinner:
|
if thinking_spinner:
|
||||||
thinking_spinner.stop(f"(´;ω;`) oops, retrying...")
|
thinking_spinner.stop("(´;ω;`) oops, retrying...")
|
||||||
thinking_spinner = None
|
thinking_spinner = None
|
||||||
if self.thinking_callback:
|
if self.thinking_callback:
|
||||||
self.thinking_callback("")
|
self.thinking_callback("")
|
||||||
|
|
@ -6356,7 +6365,7 @@ class AIAgent:
|
||||||
except Exception as api_error:
|
except Exception as api_error:
|
||||||
# Stop spinner before printing error messages
|
# Stop spinner before printing error messages
|
||||||
if thinking_spinner:
|
if thinking_spinner:
|
||||||
thinking_spinner.stop(f"(╥_╥) error, retrying...")
|
thinking_spinner.stop("(╥_╥) error, retrying...")
|
||||||
thinking_spinner = None
|
thinking_spinner = None
|
||||||
if self.thinking_callback:
|
if self.thinking_callback:
|
||||||
self.thinking_callback("")
|
self.thinking_callback("")
|
||||||
|
|
@ -6764,7 +6773,7 @@ class AIAgent:
|
||||||
elif self.api_mode == "anthropic_messages":
|
elif self.api_mode == "anthropic_messages":
|
||||||
from agent.anthropic_adapter import normalize_anthropic_response
|
from agent.anthropic_adapter import normalize_anthropic_response
|
||||||
assistant_message, finish_reason = normalize_anthropic_response(
|
assistant_message, finish_reason = normalize_anthropic_response(
|
||||||
response, strip_tool_prefix=getattr(self, "_is_anthropic_oauth", False)
|
response, strip_tool_prefix=self._is_anthropic_oauth
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assistant_message = response.choices[0].message
|
assistant_message = response.choices[0].message
|
||||||
|
|
@ -6952,7 +6961,7 @@ class AIAgent:
|
||||||
if tc.function.name not in self.valid_tool_names:
|
if tc.function.name not in self.valid_tool_names:
|
||||||
content = f"Tool '{tc.function.name}' does not exist. Available tools: {available}"
|
content = f"Tool '{tc.function.name}' does not exist. Available tools: {available}"
|
||||||
else:
|
else:
|
||||||
content = f"Skipped: another tool call in this turn used an invalid name. Please retry this tool call."
|
content = "Skipped: another tool call in this turn used an invalid name. Please retry this tool call."
|
||||||
messages.append({
|
messages.append({
|
||||||
"role": "tool",
|
"role": "tool",
|
||||||
"tool_call_id": tc.id,
|
"tool_call_id": tc.id,
|
||||||
|
|
@ -7547,20 +7556,20 @@ def main(
|
||||||
toolset = get_toolset_for_tool(tool_name)
|
toolset = get_toolset_for_tool(tool_name)
|
||||||
print(f" 📌 {tool_name} (from {toolset})")
|
print(f" 📌 {tool_name} (from {toolset})")
|
||||||
|
|
||||||
print(f"\n💡 Usage Examples:")
|
print("\n💡 Usage Examples:")
|
||||||
print(f" # Use predefined toolsets")
|
print(" # Use predefined toolsets")
|
||||||
print(f" python run_agent.py --enabled_toolsets=research --query='search for Python news'")
|
print(" python run_agent.py --enabled_toolsets=research --query='search for Python news'")
|
||||||
print(f" python run_agent.py --enabled_toolsets=development --query='debug this code'")
|
print(" python run_agent.py --enabled_toolsets=development --query='debug this code'")
|
||||||
print(f" python run_agent.py --enabled_toolsets=safe --query='analyze without terminal'")
|
print(" python run_agent.py --enabled_toolsets=safe --query='analyze without terminal'")
|
||||||
print(f" ")
|
print(" ")
|
||||||
print(f" # Combine multiple toolsets")
|
print(" # Combine multiple toolsets")
|
||||||
print(f" python run_agent.py --enabled_toolsets=web,vision --query='analyze website'")
|
print(" python run_agent.py --enabled_toolsets=web,vision --query='analyze website'")
|
||||||
print(f" ")
|
print(" ")
|
||||||
print(f" # Disable toolsets")
|
print(" # Disable toolsets")
|
||||||
print(f" python run_agent.py --disabled_toolsets=terminal --query='no command execution'")
|
print(" python run_agent.py --disabled_toolsets=terminal --query='no command execution'")
|
||||||
print(f" ")
|
print(" ")
|
||||||
print(f" # Run with trajectory saving enabled")
|
print(" # Run with trajectory saving enabled")
|
||||||
print(f" python run_agent.py --save_trajectories --query='your question here'")
|
print(" python run_agent.py --save_trajectories --query='your question here'")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse toolset selection arguments
|
# Parse toolset selection arguments
|
||||||
|
|
@ -7576,9 +7585,9 @@ def main(
|
||||||
print(f"🚫 Disabled toolsets: {disabled_toolsets_list}")
|
print(f"🚫 Disabled toolsets: {disabled_toolsets_list}")
|
||||||
|
|
||||||
if save_trajectories:
|
if save_trajectories:
|
||||||
print(f"💾 Trajectory saving: ENABLED")
|
print("💾 Trajectory saving: ENABLED")
|
||||||
print(f" - Successful conversations → trajectory_samples.jsonl")
|
print(" - Successful conversations → trajectory_samples.jsonl")
|
||||||
print(f" - Failed conversations → failed_trajectories.jsonl")
|
print(" - Failed conversations → failed_trajectories.jsonl")
|
||||||
|
|
||||||
# Initialize agent with provided parameters
|
# Initialize agent with provided parameters
|
||||||
try:
|
try:
|
||||||
|
|
@ -7620,7 +7629,7 @@ def main(
|
||||||
print(f"💬 Messages: {len(result['messages'])}")
|
print(f"💬 Messages: {len(result['messages'])}")
|
||||||
|
|
||||||
if result['final_response']:
|
if result['final_response']:
|
||||||
print(f"\n🎯 FINAL RESPONSE:")
|
print("\n🎯 FINAL RESPONSE:")
|
||||||
print("-" * 30)
|
print("-" * 30)
|
||||||
print(result['final_response'])
|
print(result['final_response'])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1073,7 +1073,7 @@ def browser_navigate(url: str, task_id: Optional[str] = None) -> str:
|
||||||
_run_browser_command(effective_task_id, "open", ["about:blank"], timeout=10)
|
_run_browser_command(effective_task_id, "open", ["about:blank"], timeout=10)
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Blocked: redirect landed on a private/internal address",
|
"error": "Blocked: redirect landed on a private/internal address",
|
||||||
})
|
})
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
|
|
|
||||||
|
|
@ -505,7 +505,7 @@ class CheckpointManager:
|
||||||
# Get the hash of the commit at the cutoff point
|
# Get the hash of the commit at the cutoff point
|
||||||
ok, cutoff_hash, _ = _run_git(
|
ok, cutoff_hash, _ = _run_git(
|
||||||
["rev-list", "--reverse", "HEAD", "--skip=0",
|
["rev-list", "--reverse", "HEAD", "--skip=0",
|
||||||
f"--max-count=1"],
|
"--max-count=1"],
|
||||||
shadow_repo, working_dir,
|
shadow_repo, working_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -542,7 +542,7 @@ def format_checkpoint_list(checkpoints: List[Dict], directory: str) -> str:
|
||||||
|
|
||||||
lines.append(f" {i}. {cp['short_hash']} {ts} {cp['reason']}{stat}")
|
lines.append(f" {i}. {cp['short_hash']} {ts} {cp['reason']}{stat}")
|
||||||
|
|
||||||
lines.append(f"\n /rollback <N> restore to checkpoint N")
|
lines.append("\n /rollback <N> restore to checkpoint N")
|
||||||
lines.append(f" /rollback diff <N> preview changes since checkpoint N")
|
lines.append(" /rollback diff <N> preview changes since checkpoint N")
|
||||||
lines.append(f" /rollback <N> <file> restore a single file from checkpoint N")
|
lines.append(" /rollback <N> <file> restore a single file from checkpoint N")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
|
||||||
|
|
@ -187,11 +187,6 @@ def _strategy_indentation_flexible(content: str, pattern: str) -> List[Tuple[int
|
||||||
|
|
||||||
Strips all leading whitespace from lines before matching.
|
Strips all leading whitespace from lines before matching.
|
||||||
"""
|
"""
|
||||||
def strip_indent(s):
|
|
||||||
return '\n'.join(line.lstrip() for line in s.split('\n'))
|
|
||||||
|
|
||||||
pattern_stripped = strip_indent(pattern)
|
|
||||||
|
|
||||||
content_lines = content.split('\n')
|
content_lines = content.split('\n')
|
||||||
content_stripped_lines = [line.lstrip() for line in content_lines]
|
content_stripped_lines = [line.lstrip() for line in content_lines]
|
||||||
pattern_lines = [line.lstrip() for line in pattern.split('\n')]
|
pattern_lines = [line.lstrip() for line in pattern.split('\n')]
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ async def _redirect_to_browser(auth_url: str) -> None:
|
||||||
try:
|
try:
|
||||||
if _can_open_browser():
|
if _can_open_browser():
|
||||||
webbrowser.open(auth_url)
|
webbrowser.open(auth_url)
|
||||||
print(f" Opened browser for authorization...")
|
print(" Opened browser for authorization...")
|
||||||
else:
|
else:
|
||||||
print(f"\n Open this URL to authorize:\n {auth_url}\n")
|
print(f"\n Open this URL to authorize:\n {auth_url}\n")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -466,7 +466,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Show current configuration
|
# Show current configuration
|
||||||
config = get_moa_configuration()
|
config = get_moa_configuration()
|
||||||
print(f"\n⚙️ Current Configuration:")
|
print("\n⚙️ Current Configuration:")
|
||||||
print(f" 🤖 Reference models ({len(config['reference_models'])}): {', '.join(config['reference_models'])}")
|
print(f" 🤖 Reference models ({len(config['reference_models'])}): {', '.join(config['reference_models'])}")
|
||||||
print(f" 🧠 Aggregator model: {config['aggregator_model']}")
|
print(f" 🧠 Aggregator model: {config['aggregator_model']}")
|
||||||
print(f" 🌡️ Reference temperature: {config['reference_temperature']}")
|
print(f" 🌡️ Reference temperature: {config['reference_temperature']}")
|
||||||
|
|
@ -506,7 +506,7 @@ if __name__ == "__main__":
|
||||||
print(f" - Optimized temperatures: {REFERENCE_TEMPERATURE} for reference models, {AGGREGATOR_TEMPERATURE} for aggregation")
|
print(f" - Optimized temperatures: {REFERENCE_TEMPERATURE} for reference models, {AGGREGATOR_TEMPERATURE} for aggregation")
|
||||||
print(" - Token-efficient: only returns final aggregated response")
|
print(" - Token-efficient: only returns final aggregated response")
|
||||||
print(" - Resilient: continues with partial model failures")
|
print(" - Resilient: continues with partial model failures")
|
||||||
print(f" - Configurable: easy to modify models and settings at top of file")
|
print(" - Configurable: easy to modify models and settings at top of file")
|
||||||
print(" - State-of-the-art results on challenging benchmarks")
|
print(" - State-of-the-art results on challenging benchmarks")
|
||||||
|
|
||||||
print("\nDebug mode:")
|
print("\nDebug mode:")
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,7 @@ async def _monitor_training_run(run_state: RunState):
|
||||||
|
|
||||||
if run_state.api_process and run_state.api_process.poll() is not None:
|
if run_state.api_process and run_state.api_process.poll() is not None:
|
||||||
run_state.status = "failed"
|
run_state.status = "failed"
|
||||||
run_state.error_message = f"API server exited unexpectedly"
|
run_state.error_message = "API server exited unexpectedly"
|
||||||
_stop_training_run(run_state)
|
_stop_training_run(run_state)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -1233,11 +1233,11 @@ async def rl_test_inference(
|
||||||
print(f"\n ❌ Error: {model_results['error']}")
|
print(f"\n ❌ Error: {model_results['error']}")
|
||||||
# Print last few lines of stderr for debugging
|
# Print last few lines of stderr for debugging
|
||||||
if stderr_lines:
|
if stderr_lines:
|
||||||
print(f" Last errors:")
|
print(" Last errors:")
|
||||||
for line in stderr_lines[-5:]:
|
for line in stderr_lines[-5:]:
|
||||||
print(f" {line}")
|
print(f" {line}")
|
||||||
else:
|
else:
|
||||||
print(f"\n ✅ Process completed successfully")
|
print("\n ✅ Process completed successfully")
|
||||||
print(f" Output file: {output_file}")
|
print(f" Output file: {output_file}")
|
||||||
print(f" File exists: {output_file.exists()}")
|
print(f" File exists: {output_file.exists()}")
|
||||||
|
|
||||||
|
|
@ -1272,7 +1272,7 @@ async def rl_test_inference(
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
model_results["error"] = "Process timed out after 10 minutes"
|
model_results["error"] = "Process timed out after 10 minutes"
|
||||||
print(f" Timeout!")
|
print(" Timeout!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
model_results["error"] = str(e)
|
model_results["error"] = str(e)
|
||||||
print(f" Error: {e}")
|
print(f" Error: {e}")
|
||||||
|
|
|
||||||
|
|
@ -2021,8 +2021,8 @@ class LobeHubSource(SkillSource):
|
||||||
"metadata:",
|
"metadata:",
|
||||||
" hermes:",
|
" hermes:",
|
||||||
f" tags: [{', '.join(str(t) for t in tag_list)}]",
|
f" tags: [{', '.join(str(t) for t in tag_list)}]",
|
||||||
f" lobehub:",
|
" lobehub:",
|
||||||
f" source: lobehub",
|
" source: lobehub",
|
||||||
"---",
|
"---",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1252,7 +1252,7 @@ if __name__ == "__main__":
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
|
|
||||||
config = _get_env_config()
|
config = _get_env_config()
|
||||||
print(f"\nCurrent Configuration:")
|
print("\nCurrent Configuration:")
|
||||||
print(f" Environment type: {config['env_type']}")
|
print(f" Environment type: {config['env_type']}")
|
||||||
print(f" Docker image: {config['docker_image']}")
|
print(f" Docker image: {config['docker_image']}")
|
||||||
print(f" Modal image: {config['modal_image']}")
|
print(f" Modal image: {config['modal_image']}")
|
||||||
|
|
|
||||||
|
|
@ -634,7 +634,7 @@ def check_command_security(command: str) -> dict:
|
||||||
logger.warning("tirith timed out after %ds", timeout)
|
logger.warning("tirith timed out after %ds", timeout)
|
||||||
if fail_open:
|
if fail_open:
|
||||||
return {"action": "allow", "findings": [], "summary": f"tirith timed out ({timeout}s)"}
|
return {"action": "allow", "findings": [], "summary": f"tirith timed out ({timeout}s)"}
|
||||||
return {"action": "block", "findings": [], "summary": f"tirith timed out (fail-closed)"}
|
return {"action": "block", "findings": [], "summary": "tirith timed out (fail-closed)"}
|
||||||
|
|
||||||
# Map exit code to action
|
# Map exit code to action
|
||||||
exit_code = result.returncode
|
exit_code = result.returncode
|
||||||
|
|
|
||||||
|
|
@ -797,7 +797,7 @@ if __name__ == "__main__":
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"\nProvider availability:")
|
print("\nProvider availability:")
|
||||||
print(f" Edge TTS: {'installed' if _check(_import_edge_tts, 'edge') else 'not installed (pip install edge-tts)'}")
|
print(f" Edge TTS: {'installed' if _check(_import_edge_tts, 'edge') else 'not installed (pip install edge-tts)'}")
|
||||||
print(f" ElevenLabs: {'installed' if _check(_import_elevenlabs, 'el') else 'not installed (pip install elevenlabs)'}")
|
print(f" ElevenLabs: {'installed' if _check(_import_elevenlabs, 'el') else 'not installed (pip install elevenlabs)'}")
|
||||||
print(f" API Key: {'set' if os.getenv('ELEVENLABS_API_KEY') else 'not set'}")
|
print(f" API Key: {'set' if os.getenv('ELEVENLABS_API_KEY') else 'not set'}")
|
||||||
|
|
|
||||||
|
|
@ -1665,7 +1665,7 @@ if __name__ == "__main__":
|
||||||
print(" # - Final processed results")
|
print(" # - Final processed results")
|
||||||
print(" # Logs saved to: ./logs/web_tools_debug_UUID.json")
|
print(" # Logs saved to: ./logs/web_tools_debug_UUID.json")
|
||||||
|
|
||||||
print(f"\n📝 Run 'python test_web_tools_llm.py' to test LLM processing capabilities")
|
print("\n📝 Run 'python test_web_tools_llm.py' to test LLM processing capabilities")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ def print_distribution_info(distribution_name: str) -> None:
|
||||||
|
|
||||||
print(f"\n📊 Distribution: {distribution_name}")
|
print(f"\n📊 Distribution: {distribution_name}")
|
||||||
print(f" Description: {dist['description']}")
|
print(f" Description: {dist['description']}")
|
||||||
print(f" Toolsets:")
|
print(" Toolsets:")
|
||||||
for toolset, prob in sorted(dist["toolsets"].items(), key=lambda x: x[1], reverse=True):
|
for toolset, prob in sorted(dist["toolsets"].items(), key=lambda x: x[1], reverse=True):
|
||||||
print(f" • {toolset:15} : {prob:3}% chance")
|
print(f" • {toolset:15} : {prob:3}% chance")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -558,7 +558,7 @@ if __name__ == "__main__":
|
||||||
print("\nMultiple Toolset Resolution:")
|
print("\nMultiple Toolset Resolution:")
|
||||||
print("-" * 40)
|
print("-" * 40)
|
||||||
combined = resolve_multiple_toolsets(["web", "vision", "terminal"])
|
combined = resolve_multiple_toolsets(["web", "vision", "terminal"])
|
||||||
print(f" Combining ['web', 'vision', 'terminal']:")
|
print(" Combining ['web', 'vision', 'terminal']:")
|
||||||
print(f" Result: {', '.join(sorted(combined))}")
|
print(f" Result: {', '.join(sorted(combined))}")
|
||||||
|
|
||||||
print("\nCustom Toolset Creation:")
|
print("\nCustom Toolset Creation:")
|
||||||
|
|
@ -570,6 +570,6 @@ if __name__ == "__main__":
|
||||||
includes=["terminal", "vision"]
|
includes=["terminal", "vision"]
|
||||||
)
|
)
|
||||||
custom_info = get_toolset_info("my_custom")
|
custom_info = get_toolset_info("my_custom")
|
||||||
print(f" Created 'my_custom' toolset:")
|
print(" Created 'my_custom' toolset:")
|
||||||
print(f" Description: {custom_info['description']}")
|
print(f" Description: {custom_info['description']}")
|
||||||
print(f" Resolved tools: {', '.join(custom_info['resolved_tools'])}")
|
print(f" Resolved tools: {', '.join(custom_info['resolved_tools'])}")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue