diff --git a/interfaces/admin/routes.py b/interfaces/admin/routes.py index 7ca23c33..b996563e 100644 --- a/interfaces/admin/routes.py +++ b/interfaces/admin/routes.py @@ -1974,8 +1974,14 @@ def build_admin_router() -> APIRouter: enable_mcp_tools = emcp_raw not in ("0", "false", "no", "off") epl_raw = str(store.get_setting("AIA_ENABLE_PLUGIN_TOOLS") or "").strip().lower() enable_plugin_tools = epl_raw in ("1", "true", "yes", "on") - erc_raw = str(store.get_setting("AIA_ENABLE_RUN_COMMAND") or "").strip().lower() - enable_run_command = erc_raw not in ("0", "false", "no", "off") + # Absent DB row must read as OFF: matches LocalAdapter.run_command (no row → env only; no env → disabled). + # Previously `str(None or "")` made the UI show ON while execution stayed disabled (confusing on new machines). + erc_sv = store.get_setting("AIA_ENABLE_RUN_COMMAND") + if erc_sv is None: + enable_run_command = False + else: + erc_raw = str(erc_sv).strip().lower() + enable_run_command = erc_raw not in ("0", "false", "no", "off") tctx_raw = str(store.get_setting("AIA_TOOL_CONTEXT_TRUNCATE_ENABLED") or "").strip().lower() tool_context_truncate_enabled = tctx_raw not in ("0", "false", "no", "off") ttft_raw = str(store.get_setting("AIA_CHAT_SHOW_TTFT_DEBUG") or "").strip().lower() diff --git a/runtime/tools/public/local_sdk.py b/runtime/tools/public/local_sdk.py index 19bce43f..d18ba6dd 100644 --- a/runtime/tools/public/local_sdk.py +++ b/runtime/tools/public/local_sdk.py @@ -99,9 +99,23 @@ class LocalAdapter: raw_env = str(os.getenv("AIA_ENABLE_RUN_COMMAND") or "").strip() enabled = _truthy(raw_env) if raw_env else False if not enabled: - return {"ok": False, "error_code": "disabled", "error": "disabled"} + return { + "ok": False, + "error_code": "disabled", + "error": "disabled", + "hint": ( + "run_command is off: save Admin → Tool policy with run_command enabled (writes AIA_ENABLE_RUN_COMMAND " + "into the same SQLite the gateway uses), or set env AIA_ENABLE_RUN_COMMAND=1. " + "Check OPS_ASSISTANT_DB_PATH / db_path() if another machine uses a different DB file." + ), + } except Exception: - return {"ok": False, "error_code": "disabled", "error": "disabled"} + return { + "ok": False, + "error_code": "disabled", + "error": "disabled", + "hint": "run_command gate failed unexpectedly; check gateway logs and SQLite app_setting access.", + } try: timeout_s = max(1, min(int(timeout or 300), 600)) # run_command never follows adapter cd state. diff --git a/tests/test_admin_tool_policy_oclaw_retry_codes.py b/tests/test_admin_tool_policy_oclaw_retry_codes.py index d36ec6c5..17add3e2 100644 --- a/tests/test_admin_tool_policy_oclaw_retry_codes.py +++ b/tests/test_admin_tool_policy_oclaw_retry_codes.py @@ -51,6 +51,7 @@ class AdminToolPolicyOclawRetryCodesTests(unittest.TestCase): self.assertEqual(r1.status_code, 200) b1 = r1.json() self.assertTrue(b1.get("ok")) + self.assertFalse(bool(b1.get("enable_run_command"))) self.assertTrue(str(b1.get("oclaw_retryable_error_codes") or "").strip()) self.assertFalse(bool(b1.get("oclaw_retry_codes_strict_mode")))