diff --git a/api/server.py b/api/server.py index 6fcd1b5..85f99ea 100755 --- a/api/server.py +++ b/api/server.py @@ -65,7 +65,7 @@ # When set, all requests must include: Authorization: Bearer # Health and root endpoints are exempt. GATHM_API_KEY = os.environ.get("GATHM_API_KEY", "") -PUBLIC_PATHS = {"", "/", "/api", "/api/v1", "/api/v1/health"} +PUBLIC_PATHS = {"", "/", "/api", "/api/v1", "/api/v1/health", "/api/v1/ping"} # GUI static files are also public (any path not starting with /api/) import hashlib @@ -288,7 +288,13 @@ def do_GET(self): else: self._send_json({"error": f"Tool '{tool_name}' not found"}, 404) - # GET /api/v1/health + # GET /api/v1/ping — lightweight liveness probe used by the GUI. + # Unlike /health it does NOT shell out to the orchestrator, so it + # returns instantly and never makes the UI look "offline". + elif path == "/api/v1/ping": + self._send_json({"status": "ok", "service": "gathm-api"}) + + # GET /api/v1/health (full system health — checks every tool, slow) elif path == "/api/v1/health": result = run_agent_command("health", "all") self._send_json(result) @@ -434,7 +440,10 @@ def main(): else: i += 1 - server = http.server.HTTPServer((host, port), GathmAPIHandler) + # ThreadingHTTPServer so a slow request (e.g. the full /health sweep + # across all tools) never blocks the GUI from loading or other + # requests from being served concurrently. + server = http.server.ThreadingHTTPServer((host, port), GathmAPIHandler) print(f""" ╔══════════════════════════════════════════════════╗ ║ Gathm Enterprise API Server ║ diff --git a/gui/app.js b/gui/app.js index 6dd8bda..29a8454 100644 --- a/gui/app.js +++ b/gui/app.js @@ -24,8 +24,9 @@ let isOnline = false; async function checkConnectivity() { try { - const res = await fetch(`${API_BASE}/api/v1/health`, { - signal: AbortSignal.timeout(5000), + // /ping is instant — /health sweeps every tool and would time out. + const res = await fetch(`${API_BASE}/api/v1/ping`, { + signal: AbortSignal.timeout(4000), }); isOnline = res.ok; } catch { @@ -96,6 +97,34 @@ function hideTyping() { typingEl = null; } +// ── Render agent reply ───────────────────────────────────────── +// The /agent/ask endpoint is a tool ROUTER, not a chat LLM. It returns +// structured JSON; translate it into something human-readable instead of +// dumping raw JSON into the chat. +function formatAgentReply(data) { + // A tool was matched to the query + if (data.matched_tool && data.matched_tool !== 'null') { + const desc = data.description ? `\n\n${data.description}` : ''; + const action = data.action ? `\n\n→ ${data.action}` : `\n\n→ Run: gathm run ${data.matched_tool}`; + return `I can help with that using the “${data.matched_tool}” tool.${desc}${action}`; + } + + // No tool matched — give a friendly, useful nudge instead of an error blob + if (data.error && /no matching tool/i.test(data.error)) { + return "I'm a tool-running assistant, so I work best with task requests. " + + "Try things like:\n" + + " • weather in Tokyo\n" + + " • dns records for github.com\n" + + " • ip info 8.8.8.8\n" + + " • define serendipity\n" + + " • crypto price bitcoin"; + } + + // Any other shape: prefer real output, fall back to the error text + return data.raw_output || data.output || data.result || data.error + || JSON.stringify(data, null, 2); +} + // ── Send via API ─────────────────────────────────────────────── let isSending = false; @@ -125,9 +154,7 @@ async function sendMessage() { } const data = await res.json(); - const reply = data.raw_output || data.output || data.result - || JSON.stringify(data, null, 2); - addMessage(reply, 'bot'); + addMessage(formatAgentReply(data), 'bot'); } catch (err) { hideTyping(); diff --git a/gui/styles.css b/gui/styles.css index 1a312c8..8a01d4b 100644 --- a/gui/styles.css +++ b/gui/styles.css @@ -381,6 +381,8 @@ body { .message p { font-size: 14px; line-height: 1.55; + white-space: pre-wrap; /* preserve newlines / bullet lists in replies */ + word-break: break-word; } .bot-text { diff --git a/install b/install index 4ba772e..89d0076 100755 --- a/install +++ b/install @@ -1529,7 +1529,9 @@ main() { install_ollama "$platform" install_llmfit_and_select_model - install_engineer_deps + # TODO(engineer): temporarily disabled — re-enable when resuming the + # engineer agent. Uncomment the line below to restore engineer setup. + # install_engineer_deps install_pilot_deps install_playwright_browser setup_shortcuts "$platform"