#686/#661 night shift wave 4 (trigger + api): both -> 100% seedgo. trigger.py + api.py main() intercept <cmd> --help before dispatch (Subcommand_Help ->100). Output_Routing ->100: trigger (branch_log_events/errors/log_events/medic) + api (5 client/secrets modules) route status/error output via cli error()/success()/warning(). Test assertions updated to match routing (trigger 8 across 4 files). Suites re-run by devpulse: trigger 564, api 515, both green. aipass still in flight.

This commit is contained in:
AIOSAI
2026-07-11 02:46:51 -07:00
parent 80badb784a
commit 7fb65f0255
16 changed files with 128 additions and 105 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ PyPI version — not the changelog header.
`@cli` `success()/error()/warning()` helpers instead of raw `console.print`
markup. Owners self-audited and self-fixed their own branches; devpulse verified
each diff + re-ran each audit and committed per wave. Landed so far: spawn,
drone, flow, daemon, prax, ai_mail, backup, seedgo, memory. Remaining waves
following.
drone, flow, daemon, prax, ai_mail, backup, seedgo, memory, trigger, api.
Remaining waves following.
### Fixed
+13 -5
View File
@@ -149,11 +149,11 @@ def print_help():
console.print("[bold cyan]WHAT IS API?[/bold cyan]")
console.print()
console.print("API Branch provides:")
console.print(" [green]✓[/green] OpenRouter API client integration")
console.print(" [green]✓[/green] API key management and validation")
console.print(" [green]✓[/green] Model discovery and availability")
console.print(" [green]✓[/green] Usage tracking and statistics")
console.print(" [green]✓[/green] Connection testing and diagnostics")
console.print(" [cyan]•[/cyan] OpenRouter API client integration")
console.print(" [cyan]•[/cyan] API key management and validation")
console.print(" [cyan]•[/cyan] Model discovery and availability")
console.print(" [cyan]•[/cyan] Usage tracking and statistics")
console.print(" [cyan]•[/cyan] Connection testing and diagnostics")
console.print()
console.print("[bold cyan]AVAILABLE COMMANDS:[/bold cyan]")
@@ -287,6 +287,14 @@ def main():
command = args[0]
remaining_args = args[1:] if len(args) > 1 else []
# Subcommand --help guard
if remaining_args and remaining_args[0] in ["--help", "-h"]:
for module in modules:
if module.handle_command(command, ["--help"]):
return 0
print_help()
return 0
# Log api command attempt
json_handler.log_operation("api_command_attempted", {"command": command, "modules_discovered": len(modules)})
+1 -5
View File
@@ -323,9 +323,5 @@ if __name__ == "__main__":
if handle_command(command, remaining_args):
sys.exit(0)
else:
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands")
sys.exit(1)
+1 -5
View File
@@ -357,9 +357,5 @@ if __name__ == "__main__":
if handle_command(command, remaining_args):
sys.exit(0)
else:
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands")
sys.exit(1)
@@ -29,7 +29,7 @@ if sys.platform == "win32":
from typing import List
from aipass.prax.apps.modules.logger import system_logger as logger
from aipass.cli.apps.modules import console, header, success, error
from aipass.cli.apps.modules import console, header, success, error, warning
from aipass.api.apps.handlers.json import json_handler
from aipass.api.apps.handlers.auth import keys
from aipass.api.apps.handlers.openrouter import client, models
@@ -286,7 +286,7 @@ def check_status():
console.print(" [cyan]Key configured:[/cyan] [green]yes[/green]")
console.print(f" [cyan]Key:[/cyan] {masked}")
else:
console.print(" [cyan]Key configured:[/cyan] [red]no[/red]")
warning("API key not configured")
diagnosis = keys.diagnose_key("openrouter")
console.print(f" [cyan]Reason:[/cyan] {diagnosis}")
@@ -300,7 +300,7 @@ def check_status():
console.print(" [cyan]OpenAI SDK:[/cyan] [green]available[/green]")
except ImportError:
logger.warning("OpenAI SDK not installed")
console.print(" [cyan]OpenAI SDK:[/cyan] [red]missing[/red]")
warning("OpenAI SDK not installed")
# Client cache stats
cache_stats = client.get_cache_stats()
@@ -365,9 +365,5 @@ if __name__ == "__main__":
if handle_command(command, remaining_args):
sys.exit(0)
else:
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands")
sys.exit(1)
+2 -4
View File
@@ -32,7 +32,7 @@ if sys.platform == "win32":
from typing import Any, List, Optional, Union
from aipass.prax import logger # noqa: F401 — seedgo imports standard
from aipass.cli.apps.modules import console, header
from aipass.cli.apps.modules import console, header, error
from aipass.api.apps.handlers.json import json_handler
from aipass.api.apps.handlers.auth import secrets as _handler
@@ -156,7 +156,5 @@ if __name__ == "__main__":
print_help()
sys.exit(0)
console.print()
console.print(f"[red]Unknown command: {args[0]}[/red]")
console.print()
error(f"Unknown command: {args[0]}")
sys.exit(1)
+1 -5
View File
@@ -288,9 +288,5 @@ if __name__ == "__main__":
if handle_command(command, remaining_args):
sys.exit(0)
else:
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands")
sys.exit(1)
@@ -166,7 +166,7 @@ def handle_command(command: str, args: list) -> bool:
Returns:
True if command was handled, False otherwise
"""
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, success, error
# Handle module-name routing (drone @trigger branch_log_events <subcmd>)
if command == "branch_log_events":
@@ -195,15 +195,15 @@ def handle_command(command: str, args: list) -> bool:
if command == "start":
if start():
console.print("✅ Branch log watcher started")
success("Branch log watcher started")
console.print(f" Monitoring: {AIPASS_PKG_ROOT}/*/logs/*.log")
console.print(" Events: error_detected → AI_Mail error_handler")
else:
console.print("❌ Failed to start branch log watcher")
error("Failed to start branch log watcher")
console.print(" Check if watchdog package is installed")
elif command == "stop":
stop()
console.print("✅ Branch log watcher stopped")
success("Branch log watcher stopped")
elif command == "status":
info = status()
console.print("Branch Log Watcher Status")
@@ -213,7 +213,7 @@ def handle_command(command: str, args: list) -> bool:
console.print(f" AIPASS root: {info['aipass_root']}")
elif command == "reset":
reset_hashes()
console.print("✅ Error deduplication hashes cleared")
success("Error deduplication hashes cleared")
json_handler.log_operation("watcher_command", {"command": command})
return True
+22 -12
View File
@@ -323,14 +323,18 @@ def _cmd_detail(console, args: list) -> bool:
def _cmd_suppress(console, args: list) -> bool:
"""Mark error as suppressed with optional reason."""
from aipass.cli.apps.modules import error, warning
if not args:
console.print("[red]Missing error ID or fingerprint[/red]")
console.print("Usage: drone @trigger errors suppress <id_or_fingerprint> [reason]")
error(
"Missing error ID or fingerprint",
suggestion="Usage: drone @trigger errors suppress <id_or_fingerprint> [reason]",
)
return True
entry = _find_by_id_or_fp(args[0])
if not entry:
console.print(f"[red]Error not found:[/red] {args[0]}")
error(f"Error not found: {args[0]}")
return True
reason = " ".join(args[1:]) if len(args) > 1 else "No reason provided"
@@ -338,7 +342,7 @@ def _cmd_suppress(console, args: list) -> bool:
if update_status(fp, "suppressed", reason):
logger.info(f"[ERRORS] Suppressed {entry.get('id', '?')} ({fp[:12]}): {reason}")
console.print(f"[yellow]Suppressed[/yellow] error {entry.get('id', '?')} ({fp[:12]})")
warning(f"Suppressed error {entry.get('id', '?')} ({fp[:12]})")
console.print(f" Reason: {reason}")
# Phase 5: Source fix pipeline - notify source branch
@@ -352,28 +356,32 @@ def _cmd_suppress(console, args: list) -> bool:
update_source_fix_status(fp, "pending_fix")
console.print(" [dim]Source fix email could not be sent (status: pending_fix)[/dim]")
else:
console.print(f"[red]Failed to suppress error[/red] {args[0]}")
error(f"Failed to suppress error {args[0]}")
return True
def _cmd_resolve(console, args: list) -> bool:
"""Mark error as resolved."""
from aipass.cli.apps.modules import error, success
if not args:
console.print("[red]Missing error ID or fingerprint[/red]")
console.print("Usage: drone @trigger errors resolve <id_or_fingerprint>")
error(
"Missing error ID or fingerprint",
suggestion="Usage: drone @trigger errors resolve <id_or_fingerprint>",
)
return True
entry = _find_by_id_or_fp(args[0])
if not entry:
console.print(f"[red]Error not found:[/red] {args[0]}")
error(f"Error not found: {args[0]}")
return True
fp = entry.get("fingerprint", args[0])
if update_status(fp, "resolved"):
logger.info(f"[ERRORS] Resolved {entry.get('id', '?')} ({fp[:12]})")
console.print(f"[green]Resolved[/green] error {entry.get('id', '?')} ({fp[:12]})")
success(f"Resolved error {entry.get('id', '?')} ({fp[:12]})")
else:
console.print(f"[red]Failed to resolve error[/red] {args[0]}")
error(f"Failed to resolve error {args[0]}")
return True
@@ -433,6 +441,8 @@ def _cmd_stats(console, args: list) -> bool:
def _cmd_circuit_breaker(console, args: list) -> bool:
"""Show or reset the circuit breaker."""
from aipass.cli.apps.modules import error
if args and args[0] == "reset":
circuit_breaker_reset()
logger.info("[ERRORS] Circuit breaker manually reset to closed")
@@ -459,9 +469,9 @@ def _cmd_circuit_breaker(console, args: list) -> bool:
cooldown = cb.get("cooldown_seconds", 0)
if opened_at > 0:
remaining = max(0, cooldown - int(time.time() - opened_at))
console.print(f" [red]Dispatch paused[/red] - {remaining}s remaining until half-open")
error(f"Dispatch paused - {remaining}s remaining until half-open")
else:
console.print(" [red]Dispatch paused[/red]")
error("Dispatch paused")
console.print()
console.print(" [dim]Run 'drone @trigger errors circuit-breaker reset' to force close[/dim]")
elif cb_st == "half_open":
@@ -142,7 +142,7 @@ def handle_command(command: str, args: list) -> bool:
Returns:
True if command was handled, False otherwise
"""
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, success, error
# Handle module-name routing (drone @trigger log_events <subcmd>)
if command == "log_events":
@@ -163,13 +163,13 @@ def handle_command(command: str, args: list) -> bool:
if command == "start":
if start():
console.print("✅ Log watcher started")
success("Log watcher started")
console.print(f" Monitoring: {SYSTEM_LOGS_DIR}")
else:
console.print("❌ Failed to start log watcher")
error("Failed to start log watcher")
elif command == "stop":
stop()
console.print("✅ Log watcher stopped")
success("Log watcher stopped")
elif command == "status":
info = status()
console.print("Log Watcher Status")
+14 -8
View File
@@ -233,34 +233,38 @@ def print_help() -> None:
def _handle_mute(console, args: list) -> None:
"""Handle 'medic mute @branch'."""
from aipass.cli.apps.modules import error
if not args:
console.print("[red]Missing branch name[/red] - usage: medic mute @branch")
error("Missing branch name", suggestion="Usage: medic mute @branch")
return
branch_name = _extract_branch_name(args[0])
if not branch_name:
console.print("[red]Missing branch name[/red] - usage: medic mute @branch")
error("Missing branch name", suggestion="Usage: medic mute @branch")
return
if mute_branch(branch_name):
logger.info(f"[MEDIC] Muted branch: {branch_name}")
console.print(f" [yellow]Muted[/yellow] @{branch_name} — errors logged but not dispatched")
else:
console.print(f" [red]Failed to mute[/red] @{branch_name} — check trigger_config.json")
error(f"Failed to mute @{branch_name}", suggestion="Check trigger_config.json")
def _handle_unmute(console, args: list) -> None:
"""Handle 'medic unmute @branch'."""
from aipass.cli.apps.modules import error
if not args:
console.print("[red]Missing branch name[/red] - usage: medic unmute @branch")
error("Missing branch name", suggestion="Usage: medic unmute @branch")
return
branch_name = _extract_branch_name(args[0])
if not branch_name:
console.print("[red]Missing branch name[/red] - usage: medic unmute @branch")
error("Missing branch name", suggestion="Usage: medic unmute @branch")
return
if unmute_branch(branch_name):
logger.info(f"[MEDIC] Unmuted branch: {branch_name}")
console.print(f" [green]Unmuted[/green] @{branch_name} — dispatch resumed")
else:
console.print(f" [red]Failed to unmute[/red] @{branch_name} — check trigger_config.json")
error(f"Failed to unmute @{branch_name}", suggestion="Check trigger_config.json")
def _handle_status(console) -> None:
@@ -298,9 +302,10 @@ def _handle_status(console) -> None:
def _handle_on(console) -> None:
"""Handle 'medic on' — enable dispatch and start watcher."""
from rich.panel import Panel
from aipass.cli.apps.modules import error
if not set_enabled(True):
console.print("[red]Failed to enable Medic[/red] - check trigger_config.json")
error("Failed to enable Medic", suggestion="Check trigger_config.json")
return
logger.info("[MEDIC] Medic ENABLED - error dispatch active")
@@ -328,9 +333,10 @@ def _handle_on(console) -> None:
def _handle_off(console) -> None:
"""Handle 'medic off' — disable dispatch and stop watcher."""
from rich.panel import Panel
from aipass.cli.apps.modules import error
if not set_enabled(False):
console.print("[red]Failed to disable Medic[/red] - check trigger_config.json")
error("Failed to disable Medic", suggestion="Check trigger_config.json")
return
logger.info("[MEDIC] Medic DISABLED - error dispatch suppressed")
+9 -1
View File
@@ -217,6 +217,14 @@ def main():
command = args[0]
remaining_args = args[1:] if len(args) > 1 else []
# Subcommand --help guard
if remaining_args and remaining_args[0] in ["--help", "-h"]:
for module in modules:
if module.handle_command(command, ["--help"]):
return 0
print_help(modules)
return 0
# Route to modules
if route_command(command, remaining_args, modules):
return 0
@@ -236,5 +244,5 @@ if __name__ == "__main__":
sys.exit(0)
except Exception as e:
logger.error(f"TRIGGER entry point error: {e}", exc_info=True)
console.print(f"\n❌ Error: {e}")
error(f"Error: {e}")
sys.exit(1)
@@ -246,9 +246,10 @@ def test_handle_command_start_failure_prints_error():
watcher.start_branch_log_watcher.return_value = None
result = mod.handle_command("start", [])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
assert any("Failed to start" in s for s in printed), f"Expected failure message in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Failed to start" in s for s in err_args), f"Expected failure message in error() args: {err_args}"
def test_handle_command_stop():
+14 -11
View File
@@ -398,9 +398,10 @@ class TestHandleCommandCircuitBreaker:
result = handle_command("errors", ["circuit-breaker"])
assert result is True
printed_texts = [str(c) for c in mocks["console"].print.call_args_list]
has_paused = any("paused" in text.lower() for text in printed_texts)
assert has_paused, "Expected 'paused' in open circuit breaker output"
cli_modules = sys.modules["aipass.cli.apps.modules"]
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
has_paused = any("paused" in text.lower() for text in err_args)
assert has_paused, "Expected 'paused' in error() output"
def test_circuit_breaker_reset(self):
"""circuit-breaker reset calls reset and confirms CLOSED state in output."""
@@ -631,23 +632,25 @@ class TestHandleCommandResolve:
)
assert update_call[0][1] == "resolved"
# Verify confirmation message was printed
printed_texts = [str(c) for c in mocks["console"].print.call_args_list]
has_resolved = any("Resolved" in text and "e001" in text for text in printed_texts)
assert has_resolved, "Expected 'Resolved' confirmation with error ID in output"
# Verify confirmation message was routed through success()
cli_modules = sys.modules["aipass.cli.apps.modules"]
success_args = [str(a) for call in cli_modules.success.call_args_list for a in call.args]
has_resolved = any("Resolved" in text and "e001" in text for text in success_args)
assert has_resolved, "Expected 'Resolved' confirmation with error ID in success() output"
def test_resolve_no_id_prints_usage(self):
"""resolve with no ID prints a usage hint."""
from aipass.trigger.apps.modules.errors import handle_command
mocks = _mocks()
_mocks()
result = handle_command("errors", ["resolve"])
assert result is True
printed_texts = [str(c) for c in mocks["console"].print.call_args_list]
has_missing = any("missing" in text.lower() for text in printed_texts)
assert has_missing, "Expected 'missing' in resolve-no-id output"
cli_modules = sys.modules["aipass.cli.apps.modules"]
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
has_missing = any("missing" in text.lower() for text in err_args)
assert has_missing, "Expected 'missing' in error() output"
# ---------------------------------------------------------------------------
+4 -3
View File
@@ -210,9 +210,10 @@ def test_handle_command_start_failure_prints_error():
watcher.start_log_watcher.return_value = None
result = mod.handle_command("start", [])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
assert any("Failed to start" in s for s in printed), f"Expected failure message in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Failed to start" in s for s in err_args), f"Expected failure message in error() args: {err_args}"
def test_handle_command_stop():
+28 -24
View File
@@ -177,10 +177,10 @@ def test_handle_command_on_failure_prints_error():
result = medic.handle_command("on", [])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
expected_msg = "[red]Failed to enable Medic[/red] - check trigger_config.json"
assert expected_msg in printed, f"Expected exact error message '{expected_msg}' in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Failed to enable Medic" in s for s in err_args), f"Expected failure message in error() args: {err_args}"
# ---------------------------------------------------------------------------
@@ -226,10 +226,12 @@ def test_handle_command_off_failure_prints_error():
result = medic.handle_command("off", [])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
expected_msg = "[red]Failed to disable Medic[/red] - check trigger_config.json"
assert expected_msg in printed, f"Expected exact error message '{expected_msg}' in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Failed to disable Medic" in s for s in err_args), (
f"Expected failure message in error() args: {err_args}"
)
# ---------------------------------------------------------------------------
@@ -354,10 +356,10 @@ def test_handle_command_mute_failure_prints_error():
medic.handle_command("mute", ["@api"])
console = _get_console()
printed = _get_print_str_args(console)
expected = " [red]Failed to mute[/red] @api — check trigger_config.json"
assert expected in printed, f"Expected mute failure message '{expected}' in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Failed to mute" in s for s in err_args), f"Expected mute failure message in error() args: {err_args}"
def test_handle_command_mute_without_branch_name():
@@ -366,10 +368,10 @@ def test_handle_command_mute_without_branch_name():
result = medic.handle_command("mute", [])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
expected = "[red]Missing branch name[/red] - usage: medic mute @branch"
assert expected in printed, f"Expected usage error '{expected}' in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Missing branch name" in s for s in err_args), f"Expected usage error in error() args: {err_args}"
# Should NOT have called mute_branch
state = _get_medic_state()
state.mute_branch.assert_not_called()
@@ -410,10 +412,12 @@ def test_handle_command_unmute_already_unmuted():
result = medic.handle_command("unmute", ["@nonexistent"])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
expected = " [red]Failed to unmute[/red] @nonexistent — check trigger_config.json"
assert expected in printed, f"Expected unmute failure message '{expected}' in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Failed to unmute" in s for s in err_args), (
f"Expected unmute failure message in error() args: {err_args}"
)
def test_handle_command_unmute_without_branch_name():
@@ -422,10 +426,10 @@ def test_handle_command_unmute_without_branch_name():
result = medic.handle_command("unmute", [])
assert result is True
console = _get_console()
printed = _get_print_str_args(console)
expected = "[red]Missing branch name[/red] - usage: medic unmute @branch"
assert expected in printed, f"Expected usage error '{expected}' in printed args: {printed}"
cli_modules = sys.modules["aipass.cli.apps.modules"]
cli_modules.error.assert_called()
err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args]
assert any("Missing branch name" in s for s in err_args), f"Expected usage error in error() args: {err_args}"
state = _get_medic_state()
state.unmute_branch.assert_not_called()