From 236c5af4a0425846088bf498f0165fb3c39e1217 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 05:45:07 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`devin/1?= =?UTF-8?q?778734310-stabilize-money-machine`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @Moeabdelaziz007. * https://github.com/Moeabdelaziz007/AlphaAxiom/pull/61#issuecomment-4447757058 The following files were modified: * `money-machine/src-python/engine/adapters/mt5.py` * `money-machine/src-python/engine/signal_generator.py` * `money-machine/src-python/engine/trading_core.py` * `money-machine/src-python/main.py` * `money-machine/src-python/tests/test_config_security.py` * `money-machine/src-python/tests/test_ipc_auth.py` * `money-machine/src-python/utils/config.py` * `money-machine/src-python/utils/ipc_server.py` * `money-machine/src-tauri/src/lib.rs` * `money-machine/src/app/layout.tsx` * `money-machine/src/app/page.tsx` * `money-machine/src/components/ControlPanel.tsx` * `money-machine/src/components/PnLWidget.tsx` * `money-machine/src/components/StatusWidget.tsx` * `money-machine/src/components/TradesTable.tsx` * `money-machine/src/lib/tauri.ts` --- .../src-python/engine/adapters/mt5.py | 11 ++++- .../src-python/engine/signal_generator.py | 9 +++- .../src-python/engine/trading_core.py | 34 +++++++++++++- money-machine/src-python/main.py | 19 +++++++- .../src-python/tests/test_config_security.py | 5 ++ .../src-python/tests/test_ipc_auth.py | 31 ++++++++++++ money-machine/src-python/utils/config.py | 42 +++++++++++++++-- money-machine/src-python/utils/ipc_server.py | 47 ++++++++++++++++++- money-machine/src-tauri/src/lib.rs | 13 +++++ money-machine/src/app/layout.tsx | 6 +++ money-machine/src/app/page.tsx | 8 ++++ money-machine/src/components/ControlPanel.tsx | 7 +++ money-machine/src/components/PnLWidget.tsx | 7 +++ money-machine/src/components/StatusWidget.tsx | 5 ++ money-machine/src/components/TradesTable.tsx | 9 ++++ money-machine/src/lib/tauri.ts | 19 +++++++- 16 files changed, 261 insertions(+), 11 deletions(-) diff --git a/money-machine/src-python/engine/adapters/mt5.py b/money-machine/src-python/engine/adapters/mt5.py index 5854419..57a86ad 100644 --- a/money-machine/src-python/engine/adapters/mt5.py +++ b/money-machine/src-python/engine/adapters/mt5.py @@ -742,7 +742,16 @@ def _result_from_response( request: OrderRequest, response: HttpResponse, ) -> OrderResult: - """Turn an HTTP response from the Worker into an OrderResult.""" + """ + Map an HTTP response from the relay Worker to an OrderResult representing the placement outcome. + + Parameters: + request (OrderRequest): Original order request used to populate client-facing fields. + response (HttpResponse): HTTP response returned by the relay Worker. + + Returns: + OrderResult: If the response status is 2xx, returns a `PENDING` result whose `venue_order_id` is taken directly from the parsed JSON `venue_order_id` field (may be missing or empty) and whose `metadata["relay_response"]` contains the parsed JSON dict. For non-2xx responses, returns a `REJECTED` result with `error` set to "relay HTTP {status}: {body_text}" where `body_text` is the response body decoded as UTF-8 with replacement and truncated to 256 bytes. + """ if 200 <= response.status < 300: parsed = _safe_parse_json_dict(response.body) venue_id = parsed.get("venue_order_id") diff --git a/money-machine/src-python/engine/signal_generator.py b/money-machine/src-python/engine/signal_generator.py index c867276..c97023d 100644 --- a/money-machine/src-python/engine/signal_generator.py +++ b/money-machine/src-python/engine/signal_generator.py @@ -177,7 +177,14 @@ def _parse_json_response( response_text: str, market_data: List[List] ) -> TradingSignal: - """Parse JSON response into a TradingSignal""" + """ + Convert a Gemini JSON response string into a TradingSignal for the given symbol. + + Parses `response_text` as JSON and maps fields into a `TradingSignal`. If `entry_price` is missing or falsy, the last candle close from `market_data` is used as the entry price. The parsed `amount_pct` is placed under `metadata["amount_pct"]`. On JSON parsing or value errors, returns a fallback `TradingSignal` with `action='HOLD'`, `confidence=0.3`, and a reasoning message indicating a parsing failure. + + Returns: + TradingSignal: The signal built from the parsed JSON or the fallback HOLD signal on error. + """ try: data = json.loads(response_text) diff --git a/money-machine/src-python/engine/trading_core.py b/money-machine/src-python/engine/trading_core.py index 386fa0c..87a6cb1 100644 --- a/money-machine/src-python/engine/trading_core.py +++ b/money-machine/src-python/engine/trading_core.py @@ -183,19 +183,49 @@ def is_connected(self) -> bool: return self._connected def get_uptime(self) -> float: + """ + Compute how many seconds have elapsed since the engine was started. + + Returns: + uptime_seconds (float): Seconds elapsed since the engine's start time. + """ return (datetime.now() - self.start_time).total_seconds() async def update_config(self, new_config: dict): - """Update safe runtime configuration on the fly.""" + """ + Apply validated runtime configuration updates to the engine's configuration. + + Parameters: + new_config (dict): Mapping of configuration keys to numeric values to update. Keys must be supported runtime config fields and values must be numeric (not boolean) and within the allowed ranges. + + Raises: + ValueError: If `new_config` is not a dict, contains unsupported keys, contains non-numeric or boolean values, or contains values outside the allowed ranges. + """ self.config.update(validate_config_update(new_config)) async def close(self): - """Cleanup resources""" + """ + Close any active exchange connection and free related resources. + + If an exchange client was initialized, close its connection. + """ if self.exchange: await self.exchange.close() def validate_config_update(new_config: dict) -> dict: + """ + Validate and normalize runtime configuration updates against CONFIG_LIMITS. + + Parameters: + new_config (dict): Mapping of configuration keys to numeric values to apply. + + Returns: + dict: A new dict containing the validated configuration keys with values converted to float. + + Raises: + ValueError: If `new_config` is not a dict, if a key is unsupported, if a value is not a numeric (non-boolean) type, or if a value falls outside the allowed range defined in `CONFIG_LIMITS`. + """ if not isinstance(new_config, dict): raise ValueError("config update must be an object") validated: dict = {} diff --git a/money-machine/src-python/main.py b/money-machine/src-python/main.py index 3cea205..ca10bf2 100644 --- a/money-machine/src-python/main.py +++ b/money-machine/src-python/main.py @@ -204,7 +204,24 @@ async def cmd_get_shadow_report(self, payload: dict) -> dict: return self.shadow_monitor.summary(window_minutes=window_minutes) def _record_shadow_decision(self, symbol: str, signal: dict) -> None: - """Store dry-run decision and compare against baseline strategy.""" + """ + Record an AI-generated (dry-run) trading decision and record a derived baseline decision for shadow-mode comparison. + + Creates and records a non-baseline ShadowDecision using the provided signal and current portfolio balance (position size = balance * metadata['amount_pct'] or 0.0). Then creates and records a baseline ShadowDecision that: + - uses "HOLD" when confidence < 0.55, otherwise uses the AI action, + - scales `size` and `risk` by 0.9, + - preserves timestamp, symbol, entry, exit, and confidence. + + Parameters: + symbol (str): Trading symbol for the decision (e.g., "BTC/USDT"). + signal (dict): Signal payload; expected keys include: + - "metadata" (dict) with optional "amount_pct" (interpreted as fraction of balance), + - "timestamp" (optional numeric), + - "entry_price", + - "take_profit", + - "action" (optional), + - "confidence" (optional numeric). + """ balance = float(self.engine.portfolio.get_balance()) metadata = signal.get("metadata") or {} risk_pct = float(metadata.get("amount_pct") or 0.0) diff --git a/money-machine/src-python/tests/test_config_security.py b/money-machine/src-python/tests/test_config_security.py index 6f30ca9..8ea4f7b 100644 --- a/money-machine/src-python/tests/test_config_security.py +++ b/money-machine/src-python/tests/test_config_security.py @@ -15,6 +15,11 @@ def test_save_config_redacts_nested_secrets(tmp_path, monkeypatch) -> None: + """ + Verifies that save_config persists a config.json with secret fields removed. + + Calls save_config with a nested configuration containing exchange.api_key, exchange.secret, and gemini_api_key and asserts the written config.json retains only non-secret fields (exchange.name and max_risk_per_trade). + """ fake_config_module_path = tmp_path / "utils" / "config.py" fake_config_module_path.parent.mkdir() fake_config_module_path.write_text("") diff --git a/money-machine/src-python/tests/test_ipc_auth.py b/money-machine/src-python/tests/test_ipc_auth.py index f5bf244..e7eee5a 100644 --- a/money-machine/src-python/tests/test_ipc_auth.py +++ b/money-machine/src-python/tests/test_ipc_auth.py @@ -59,6 +59,22 @@ async def _send_raw(host: str, port: int, raw: bytes) -> dict: async def _start_server( rate: float = 100.0, burst: float = 200.0, read_timeout_seconds: float = 5.0 ) -> Tuple[IPCServer, asyncio.Task, Tuple[str, int]]: + """ + Start a test IPCServer bound to an ephemeral loopback port and return the server instance, the background serve task, and the resolved (host, port) address. + + The server is configured for tests (uses the module's `_echo_handler` and `TEST_TOKEN`) and is started in a background task; the function waits until the server socket is bound before returning. + + Parameters: + rate (float): Token refill rate (tokens per second) for the server's rate limiter. + burst (float): Burst capacity (maximum tokens) for the server's rate limiter. + read_timeout_seconds (float): Number of seconds the server will wait for a request body before timing out. + + Returns: + Tuple[IPCServer, asyncio.Task, Tuple[str, int]]: A tuple containing + - the started IPCServer instance, + - the asyncio.Task running the server's serve loop, + - a (host, port) tuple for the bound ephemeral endpoint. + """ server = IPCServer( command_handler=_echo_handler, host="127.0.0.1", @@ -189,6 +205,11 @@ async def scenario() -> None: def test_rate_limit_returns_429_after_burst() -> None: + """ + Verifies the server enforces the configured token-bucket rate limit by allowing requests up to the burst size and returning a 429 error once the burst is exhausted. + + The test sends multiple authenticated requests: the first N requests (where N equals the burst limit) must succeed, and the subsequent request must receive a response with code 429 and an error message referencing rate limiting. + """ async def scenario() -> None: # Tiny bucket so we can exhaust it in a handful of requests. server, task, (host, port) = await _start_server(rate=1.0, burst=3.0) @@ -212,6 +233,11 @@ async def scenario() -> None: def test_oversized_ipc_body_returns_413() -> None: + """ + Verifies the IPC server returns a 413 error when the request body exceeds IPCServer.MAX_BODY_BYTES. + + Sends an authenticated request whose JSON body is one byte larger than MAX_BODY_BYTES and asserts the server responds with code 413. + """ async def scenario() -> None: server, task, (host, port) = await _start_server() try: @@ -226,6 +252,11 @@ async def scenario() -> None: def test_missing_body_times_out() -> None: + """ + Verifies the IPC server responds with code 408 when the request body is not received before the read timeout. + + Starts an IPCServer with a short read timeout, sends only the authentication header (no JSON body), and asserts the server returns `code == 408`. + """ async def scenario() -> None: server, task, (host, port) = await _start_server(read_timeout_seconds=0.05) try: diff --git a/money-machine/src-python/utils/config.py b/money-machine/src-python/utils/config.py index 90e3f63..a601b95 100644 --- a/money-machine/src-python/utils/config.py +++ b/money-machine/src-python/utils/config.py @@ -11,7 +11,14 @@ def load_config() -> Dict[str, Any]: - """Load configuration from environment variables and config file""" + """ + Builds the application configuration from environment variables and an optional config.json file. + + If a config.json file is present, its values are merged into the environment-based defaults after removing secret-bearing keys; if the file cannot be read or parsed a warning is printed and the environment defaults are used unchanged. + + Returns: + dict: Configuration dictionary with defaults from environment variables, updated by non-secret values from config.json when available. + """ config = { # Default values @@ -49,6 +56,15 @@ def load_config() -> Dict[str, Any]: def _deep_merge(base: Dict[str, Any], update: Dict[str, Any]) -> None: + """ + Recursively merge keys from `update` into `base`, mutating `base` in place. + + For keys present in both mappings whose values are dictionaries, their mappings are merged recursively; for all other keys the value from `update` replaces the value in `base`. + + Parameters: + base (Dict[str, Any]): The target mapping to be updated; this object is modified in place. + update (Dict[str, Any]): The source mapping whose keys and values are merged into `base`. + """ for key, value in update.items(): if isinstance(value, dict) and isinstance(base.get(key), dict): _deep_merge(base[key], value) @@ -57,7 +73,17 @@ def _deep_merge(base: Dict[str, Any], update: Dict[str, Any]) -> None: def _without_secrets(value: Any) -> Any: - """Return a copy with secret-bearing keys removed before disk use.""" + """ + Produce a copy of `value` with any dictionary entries whose key (case-insensitive) is in `SECRET_KEYS` removed. + + Recursively processes dictionaries and lists; non-dict/list values are returned unchanged. + + Parameters: + value (Any): The input structure (dict, list, or other) to cleanse of secret-bearing keys. + + Returns: + Any: The cleaned value with secret keys removed, preserving the input's structure types. + """ if isinstance(value, dict): cleaned: Dict[str, Any] = {} for key, item in value.items(): @@ -71,7 +97,17 @@ def _without_secrets(value: Any) -> Any: def save_config(config: Dict[str, Any]) -> bool: - """Save non-secret configuration to config file.""" + """ + Write the provided configuration to the project's config.json after removing secret keys. + + The file is written to the repository-level config.json (Path(__file__).parent.parent / "config.json"). Secret-bearing keys (case-insensitive matches against SECRET_KEYS) are removed from the data before it is persisted. + + Parameters: + config (Dict[str, Any]): Configuration mapping to save; secret fields will be stripped before writing. + + Returns: + bool: `True` if the file was written successfully, `False` otherwise. + """ config_path = Path(__file__).parent.parent / "config.json" try: diff --git a/money-machine/src-python/utils/ipc_server.py b/money-machine/src-python/utils/ipc_server.py index 1a64834..526979e 100644 --- a/money-machine/src-python/utils/ipc_server.py +++ b/money-machine/src-python/utils/ipc_server.py @@ -53,6 +53,18 @@ def __init__( burst_limit: Optional[float] = None, read_timeout_seconds: float = DEFAULT_READ_TIMEOUT_SECONDS, ): + """ + Initialize the IPC server with network, authentication, rate-limiting, and read timeout configuration. + + Parameters: + command_handler (CommandHandler): Async callable invoked as (command, payload) to handle requests. + host (str): IP address to bind the server to. + port (int): TCP port to listen on. + auth_token (Optional[str]): Hex authentication token to require from clients; if None a default token is resolved. + rate_limit (Optional[float]): Tokens-per-second rate for the global token bucket; uses DEFAULT_RATE when None. + burst_limit (Optional[float]): Capacity (burst size) for the token bucket; uses DEFAULT_BURST when None. + read_timeout_seconds (float): Per-read timeout in seconds applied when reading the auth header and JSON body. + """ self.host = host self.port = port self.command_handler = command_handler @@ -104,7 +116,17 @@ async def handle_client( logger.debug("Connection closed from %s", addr) async def _process_request(self, reader: asyncio.StreamReader) -> dict: - """Read auth header + JSON body, return a response dict.""" + """ + Process a single IPC request from the provided StreamReader using the two-line protocol: an auth header line followed by a JSON body line. + + Reads and validates the auth header, applies the global token-bucket rate limit after successful authentication, reads and parses the JSON request body, ensures a non-empty "command" field is present, and dispatches to the configured command handler. On protocol, authentication, rate-limit, or JSON-parsing errors, returns an error dictionary with keys "error" and "code". + + Parameters: + reader (asyncio.StreamReader): Stream reader to read the incoming request lines from. + + Returns: + dict: The response produced by the command handler, or an error dictionary containing "error" and "code". + """ auth_line_bytes, auth_error = await self._read_limited_line( reader, self.MAX_AUTH_LINE_BYTES, "auth header" ) @@ -152,6 +174,23 @@ async def _read_limited_line( limit: int, label: str, ) -> tuple[bytes, Optional[dict]]: + """ + Read a single newline-terminated line from `reader`, enforcing a per-read timeout and a maximum byte limit. + + Parameters: + reader (asyncio.StreamReader): Stream reader to read the line from. + limit (int): Maximum allowed bytes for the line (inclusive). Lines longer than this produce an error. + label (str): Human-readable label used in error messages (e.g., "auth header" or "request body"). + + Returns: + tuple[bytes, Optional[dict]]: A pair (line, error). On success `line` contains the bytes read (including the trailing newline) and `error` is `None`. On failure `line` is `b""` and `error` is a dict with keys `error` (message) and `code` (HTTP-like status code): + - Timeout reading the line → code 408. + - Line exceeds `limit` → code 413. + - Other read failures → code 400. + + Notes: + If the underlying read raises `asyncio.IncompleteReadError`, the function returns the partial bytes read as `line` (and no error) so the caller can decide how to handle an unterminated stream. + """ try: line = await asyncio.wait_for( reader.readuntil(b"\n"), @@ -171,7 +210,11 @@ async def _read_limited_line( return line, None async def stop(self) -> None: - """Stop the server.""" + """ + Close the running asyncio server and wait for it to finish closing. + + If the server was not started, this is a no-op. + """ if self.server: self.server.close() await self.server.wait_closed() diff --git a/money-machine/src-tauri/src/lib.rs b/money-machine/src-tauri/src/lib.rs index 23390a0..c4d8083 100644 --- a/money-machine/src-tauri/src/lib.rs +++ b/money-machine/src-tauri/src/lib.rs @@ -103,6 +103,19 @@ use std::sync::Mutex; static KEEP_AWAKE_HANDLE: Lazy>> = Lazy::new(|| Mutex::new(None)); +/// Enables the OS keep-alive handle to prevent system idle and sleep for the application. +/// +/// If a keep-alive handle is already active this returns `Ok("Keep-Alive already active")`. +/// On success it returns `Ok("Keep-Alive enabled")`. On failure it returns `Err` with a string describing the error (mutex lock failure or keep-awake creation failure). +/// +/// # Examples +/// +/// ``` +/// // Call and check that a keep-alive response was returned. +/// let res = enable_keep_alive(); +/// assert!(res.is_ok()); +/// assert!(res.unwrap().contains("Keep-Alive")); +/// ``` #[tauri::command] fn enable_keep_alive() -> Result { let mut handle = KEEP_AWAKE_HANDLE.lock().map_err(|e| e.to_string())?; diff --git a/money-machine/src/app/layout.tsx b/money-machine/src/app/layout.tsx index cc033bc..c96f24e 100644 --- a/money-machine/src/app/layout.tsx +++ b/money-machine/src/app/layout.tsx @@ -17,6 +17,12 @@ export const metadata: Metadata = { description: "AI-powered trading overlay for AlphaAxiom.", }; +/** + * Top-level HTML layout that applies global font variables and renders the app content. + * + * @param children - The page or route content to render inside the document body. + * @returns The root `` element containing a `` with global font classes and `children`. + */ export default function RootLayout({ children, }: Readonly<{ diff --git a/money-machine/src/app/page.tsx b/money-machine/src/app/page.tsx index dfb8cb8..d7bd296 100644 --- a/money-machine/src/app/page.tsx +++ b/money-machine/src/app/page.tsx @@ -8,6 +8,14 @@ import { ControlPanel } from '@/components/ControlPanel'; import { TradesTable } from '@/components/TradesTable'; import { useEnginePolling } from '@/hooks/useEnginePolling'; +/** + * Render the main dashboard UI for the Money Machine application. + * + * Initializes engine polling with a 5000ms interval and composes the header, metrics + * panels, control and status widgets, trades and PnL sections, and footer. + * + * @returns The React element for the application's main dashboard interface. + */ export default function Dashboard() { useEnginePolling(5000); return ( diff --git a/money-machine/src/components/ControlPanel.tsx b/money-machine/src/components/ControlPanel.tsx index f7934b8..12ff922 100644 --- a/money-machine/src/components/ControlPanel.tsx +++ b/money-machine/src/components/ControlPanel.tsx @@ -5,6 +5,13 @@ import { motion } from 'framer-motion'; import { useAppStore } from '@/store/useAppStore'; import { startTrading, stopTrading, setAlwaysOnTop, setIgnoreMouseEvents } from '@/lib/tauri'; +/** + * Render the Mission Control panel with trading and window controls. + * + * Displays connection status, a supervised "shadow trading" toggle, window behavior toggles (Pinned/Floating and Ghost/Interactive), and quick action buttons; interactive controls are disabled when the app is not connected. + * + * @returns A JSX element that renders the Mission Control UI with trading controls, window behavior toggles, and quick actions. + */ export function ControlPanel() { const { tradingActive, setTradingActive, connected } = useAppStore(); const [loading, setLoading] = useState(false); diff --git a/money-machine/src/components/PnLWidget.tsx b/money-machine/src/components/PnLWidget.tsx index b9ecf44..63777e2 100644 --- a/money-machine/src/components/PnLWidget.tsx +++ b/money-machine/src/components/PnLWidget.tsx @@ -3,6 +3,13 @@ import { motion } from 'framer-motion'; import { useAppStore } from '@/store/useAppStore'; +/** + * Render a styled P&L widget showing today's profit or loss, the portfolio balance, and the current mode. + * + * The widget conditionally styles its appearance based on whether the P&L is >= 0, formats numeric values to two decimal places, and includes entry/scale animations for the container and amount. + * + * @returns A JSX element displaying today's P&L (prefixing positive values with `+`), the portfolio balance, and the mode label. + */ export function PnLWidget() { const { portfolio } = useAppStore(); const pnl = portfolio.pnl; diff --git a/money-machine/src/components/StatusWidget.tsx b/money-machine/src/components/StatusWidget.tsx index 5cefc94..f5c659b 100644 --- a/money-machine/src/components/StatusWidget.tsx +++ b/money-machine/src/components/StatusWidget.tsx @@ -3,6 +3,11 @@ import { motion } from 'framer-motion'; import { useAppStore } from '@/store/useAppStore'; +/** + * Displays an animated engine status widget showing connection, trading state, skills loaded, formatted uptime, a fixed oracle host, and shadow report metrics (drift, error rate, and acceptance). + * + * @returns The JSX element for the status widget + */ export function StatusWidget() { const { connected, tradingActive, skillsLoaded, uptime, shadowReport } = useAppStore(); diff --git a/money-machine/src/components/TradesTable.tsx b/money-machine/src/components/TradesTable.tsx index d5aedbd..6f4a9ec 100644 --- a/money-machine/src/components/TradesTable.tsx +++ b/money-machine/src/components/TradesTable.tsx @@ -3,6 +3,15 @@ import { motion } from 'framer-motion'; import { useAppStore, Trade } from '@/store/useAppStore'; +/** + * Render a list of recent trades with animated rows and an empty-state for shadow mode. + * + * This component reads `trades` from the application store and displays each trade with side badge, + * symbol, amount/price, PnL (colored by sign), and a formatted time. If there are no trades, it + * shows a dashed empty-state card indicating that shadow mode is waiting for verified signals. + * + * @returns A React element displaying recent trades or an empty-state card when no trades are available. + */ export function TradesTable() { const { trades } = useAppStore(); diff --git a/money-machine/src/lib/tauri.ts b/money-machine/src/lib/tauri.ts index a1377b1..0b6f4df 100644 --- a/money-machine/src/lib/tauri.ts +++ b/money-machine/src/lib/tauri.ts @@ -34,7 +34,12 @@ export interface ShadowReport { export const isTauri = typeof window !== 'undefined' && '__TAURI__' in window; /** - * Send IPC command to Python engine via TCP + * Sends an IPC command to the backend, using the native Tauri bridge when available or an HTTP development proxy otherwise. + * + * @param command - Command name to send (will be lowercased for the Tauri bridge) + * @param payload - Command-specific payload + * @returns The backend response parsed as type `T` + * @throws Error when the development proxy responds with an `error` field */ async function sendIPCCommand(command: string, payload: Record = {}): Promise { if (isTauri) { @@ -77,6 +82,11 @@ export async function getStatus(): Promise { return await sendIPCCommand('GET_STATUS'); } +/** + * Checks whether the backend responds to a ping request. + * + * @returns `true` if the backend responded with `'pong'`, `false` otherwise (including when an error occurs) + */ export async function ping(): Promise { try { const result = await sendIPCCommand<{ status: string }>('PING'); @@ -86,6 +96,13 @@ export async function ping(): Promise { } } +/** + * Execute a named skill on the backend engine with optional parameters. + * + * @param skillName - The identifier of the skill to execute + * @param params - Optional key/value parameters passed to the skill + * @returns The raw response returned by the backend engine + */ export async function executeSkill(skillName: string, params: Record = {}): Promise { return await sendIPCCommand('EXECUTE_SKILL', { skill: skillName, params }); }