From 9a34e0005e08f63d279f70090343566dfdff0559 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sun, 3 May 2026 11:24:53 +0200 Subject: [PATCH] Adding --- agents/gerhard-hermes/channel_directory.json | 2 +- agents/gerhard-hermes/gateway.lock | 2 +- agents/gerhard-hermes/gateway.pid | 2 +- agents/gerhard-hermes/gateway_state.json | 2 +- plugins/festinger/festinger/main.py | 215 ++++++++++++++++++- tunnel-entrypoint.sh | 1 + 6 files changed, 218 insertions(+), 6 deletions(-) diff --git a/agents/gerhard-hermes/channel_directory.json b/agents/gerhard-hermes/channel_directory.json index c75e04f..e4f5c56 100644 --- a/agents/gerhard-hermes/channel_directory.json +++ b/agents/gerhard-hermes/channel_directory.json @@ -1,5 +1,5 @@ { - "updated_at": "2026-05-03T05:59:27.815628", + "updated_at": "2026-05-03T07:49:54.497466", "platforms": { "telegram": [], "discord": [], diff --git a/agents/gerhard-hermes/gateway.lock b/agents/gerhard-hermes/gateway.lock index 45e53da..e4589db 100644 --- a/agents/gerhard-hermes/gateway.lock +++ b/agents/gerhard-hermes/gateway.lock @@ -1 +1 @@ -{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 57263427} \ No newline at end of file +{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 57925761} \ No newline at end of file diff --git a/agents/gerhard-hermes/gateway.pid b/agents/gerhard-hermes/gateway.pid index 45e53da..e4589db 100755 --- a/agents/gerhard-hermes/gateway.pid +++ b/agents/gerhard-hermes/gateway.pid @@ -1 +1 @@ -{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 57263427} \ No newline at end of file +{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 57925761} \ No newline at end of file diff --git a/agents/gerhard-hermes/gateway_state.json b/agents/gerhard-hermes/gateway_state.json index 149869b..c841412 100644 --- a/agents/gerhard-hermes/gateway_state.json +++ b/agents/gerhard-hermes/gateway_state.json @@ -1 +1 @@ -{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 57263427, "gateway_state": "running", "exit_reason": null, "restart_requested": false, "active_agents": 0, "platforms": {}, "updated_at": "2026-05-03T05:59:27.802648+00:00"} \ No newline at end of file +{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 57925761, "gateway_state": "running", "exit_reason": null, "restart_requested": false, "active_agents": 0, "platforms": {}, "updated_at": "2026-05-03T07:49:54.488441+00:00"} \ No newline at end of file diff --git a/plugins/festinger/festinger/main.py b/plugins/festinger/festinger/main.py index 7c8310e..8a8e914 100644 --- a/plugins/festinger/festinger/main.py +++ b/plugins/festinger/festinger/main.py @@ -1145,6 +1145,56 @@ async def _handle_ollama_generate(request: Request, agent_name: str = "") -> Res raise +# --------------------------------------------------------------------------- +# /chat — gnommoweb integration endpoint +# Accepts a conversation turn from gnommoweb and returns an agent reply. +# Currently a stub: echoes back a placeholder until full agent routing is wired. +# --------------------------------------------------------------------------- + +@app.post("/chat") +async def gnommoweb_chat(request: Request) -> dict: + """ + Entry point for gnommoweb agent chat. + + Expected body: + { + "agent_id": , + "conversation_id": , + "context_id": , + "user_id": , + "message": , + "history": [{"role": "user"|"assistant", "content": }] + } + + Returns: + { + "message": , + "pose": , + "context_id": + } + """ + data = await request.json() + agent_id = data.get("agent_id") + conversation_id = data.get("conversation_id") + context_id = data.get("context_id") + user_id = data.get("user_id") + message = data.get("message", "") + history = data.get("history", []) + + log.info( + "gnommoweb_chat agent_id=%s conv=%s user=%s msg_len=%d hist=%d", + agent_id, conversation_id, user_id, len(message), len(history), + ) + + # TODO: route to agent framework (Agent Zero, etc.) based on agent config + # For now return a stub so gnommoweb has a working endpoint to call + return { + "message": f"[festinger stub] agent_id={agent_id} received: {message[:80]}", + "pose": "neutral", + "context_id": context_id, + } + + @app.post("/api/chat") async def chat(request: Request) -> Response: return await _handle_ollama_chat(request) @@ -1940,13 +1990,95 @@ async def update_config(request: Request) -> dict: return {"error": "key is required"} async with pool.acquire() as conn: await conn.execute( - "UPDATE config SET value=$1, updated_at=now() WHERE key=$2", - value, key, + """INSERT INTO config (key, value, updated_at) + VALUES ($1, $2, now()) + ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = now()""", + key, value, ) log.info("config updated key=%s value=%s", key, value) return {"status": "ok", "key": key, "value": value} +# --------------------------------------------------------------------------- +# /test-chat — diagnostic: send a single message to upstream_openai and return +# the raw response + timing so the admin can verify the LLM connection works. +# --------------------------------------------------------------------------- + +@app.get("/test-chat/models") +async def test_chat_models(request: Request) -> dict: + """Return the list of models currently available in upstream_openai (Ollama).""" + cfg = request.app.state.yaml_config + upstream = cfg.get("upstream_openai", "") + if not upstream: + return {"ok": False, "error": "upstream_openai not configured", "models": []} + url = f"{upstream.rstrip('/')}/v1/models" + try: + async with httpx.AsyncClient(timeout=8.0) as client: + r = await client.get(url) + if not r.is_success: + return {"ok": False, "error": f"HTTP {r.status_code} from {url}", "models": []} + ids = sorted(m.get("id", "") for m in r.json().get("data", [])) + return {"ok": True, "models": ids, "upstream": upstream} + except httpx.ConnectError: + return {"ok": False, "error": f"Connection refused at {url} — is Ollama running?", "models": []} + except Exception as exc: + return {"ok": False, "error": str(exc), "models": []} + + +@app.post("/test-chat") +async def test_chat(request: Request) -> dict: + cfg = request.app.state.yaml_config + data = await request.json() + message = (data.get("message") or "Say hello in one sentence.").strip() + model = (data.get("model") or "").strip() + upstream = cfg.get("upstream_openai", "") + + if not upstream: + return {"ok": False, "error": "upstream_openai not configured"} + + # Always fetch the available model list so the UI can show it + available: list[str] = [] + try: + async with httpx.AsyncClient(timeout=8.0) as client: + mr = await client.get(f"{upstream.rstrip('/')}/v1/models") + if mr.is_success: + available = sorted(m.get("id", "") for m in mr.json().get("data", [])) + except Exception: + pass + + if not model: + if not available: + return {"ok": False, "error": f"No model specified and none found at {upstream}/v1/models", "available": []} + model = available[0] + + url = f"{upstream.rstrip('/')}/v1/chat/completions" + body = { + "model": model, + "stream": False, + "messages": [{"role": "user", "content": message}], + } + + t0 = time.perf_counter() + try: + async with httpx.AsyncClient(timeout=60.0) as client: + r = await client.post(url, json=body) + ms = int((time.perf_counter() - t0) * 1000) + if not r.is_success: + return {"ok": False, "error": f"HTTP {r.status_code}: {r.text[:400]}", "url": url, "model": model, "ms": ms, "available": available} + resp = r.json() + reply = resp.get("choices", [{}])[0].get("message", {}).get("content", "(empty)") + return {"ok": True, "reply": reply, "model": model, "url": url, "ms": ms, "available": available} + except httpx.ConnectError as exc: + ms = int((time.perf_counter() - t0) * 1000) + return {"ok": False, "error": f"Connection refused — is Ollama running at {upstream}? ({exc})", "url": url, "ms": ms, "available": available} + except httpx.TimeoutException: + ms = int((time.perf_counter() - t0) * 1000) + return {"ok": False, "error": f"Request timed out after {ms}ms", "url": url, "ms": ms, "available": available} + except Exception as exc: + ms = int((time.perf_counter() - t0) * 1000) + return {"ok": False, "error": str(exc), "url": url, "ms": ms, "available": available} + + # --------------------------------------------------------------------------- # /resolve/run — manually trigger resolution job # --------------------------------------------------------------------------- @@ -3274,6 +3406,22 @@ ADMIN_HTML = """
+

Test Chat

+

+ Send a message directly to upstream_openai (Ollama) to verify the connection end-to-end. +

+
+ Loading available models… +
+
+ + + +
+
+

World model stats

SOAS tokens
@@ -3446,6 +3594,68 @@ ADMIN_HTML = """ document.getElementById('feat-loop-detection').checked = isEnabled('feature_loop_detection'); }} + async function loadTestChatModels() {{ + const sel = document.getElementById('tc-model'); + const label = document.getElementById('tc-models-label'); + try {{ + const r = await fetch('/test-chat/models'); + const d = await r.json(); + if (d.ok && d.models.length) {{ + sel.innerHTML = d.models.map(m => ``).join(''); + label.innerHTML = `${{d.models.length}} model${{d.models.length===1?'':'s'}} available at ${{d.upstream}}: ${{d.models.join(', ')}}`; + label.style.color = '#2a7a2a'; + }} else {{ + sel.innerHTML = ''; + label.textContent = d.error || 'No models found'; + label.style.color = '#b00'; + }} + }} catch(e) {{ + sel.innerHTML = ''; + label.textContent = `Failed to reach /test-chat/models: ${{e}}`; + label.style.color = '#b00'; + }} + }} + + async function sendTestChat(btn) {{ + const model = document.getElementById('tc-model').value.trim(); + const message = document.getElementById('tc-msg').value.trim() || 'Say hello in one sentence.'; + const out = document.getElementById('tc-result'); + btn.disabled = true; + btn.textContent = 'Sending…'; + out.innerHTML = 'Waiting for response…'; + try {{ + const r = await fetch('/test-chat', {{ + method: 'POST', + headers: {{'Content-Type': 'application/json'}}, + body: JSON.stringify({{message, model: model || undefined}}), + }}); + const d = await r.json(); + // Refresh model dropdown if the response includes an updated list + if (d.available && d.available.length) {{ + const sel = document.getElementById('tc-model'); + const current = sel.value; + sel.innerHTML = d.available.map(m => ``).join(''); + document.getElementById('tc-models-label').innerHTML = + `${{d.available.length}} model${{d.available.length===1?'':'s'}} available: ${{d.available.join(', ')}}`; + document.getElementById('tc-models-label').style.color = '#2a7a2a'; + }} + if (d.ok) {{ + out.innerHTML = + `
${{d.model}} — ${{d.ms}}ms
` + + `
${{d.reply}}
`; + }} else {{ + out.innerHTML = + `
✗ ${{d.ms != null ? d.ms+'ms' : ''}}
` + + `
${{d.error}}
`; + }} + }} catch(e) {{ + out.innerHTML = `Request failed: ${{e}}`; + }} finally {{ + btn.disabled = false; + btn.textContent = 'Send'; + }} + }} + async function setFeature(key, enabled) {{ const val = enabled ? 'true' : 'false'; const r = await fetch('/config', {{method:'POST', headers:{{'Content-Type':'application/json'}}, body:JSON.stringify({{key, value:val}})}}); @@ -3846,6 +4056,7 @@ ADMIN_HTML = """ loadStats(); loadFeatures(); + loadTestChatModels(); loadConflicts(); loadLog(0); loadModels(); diff --git a/tunnel-entrypoint.sh b/tunnel-entrypoint.sh index 1c07d04..e9aed88 100644 --- a/tunnel-entrypoint.sh +++ b/tunnel-entrypoint.sh @@ -17,4 +17,5 @@ exec autossh -M 0 -N \ -R 0.0.0.0:50005:rind:80 \ -R 0.0.0.0:50006:abyssinthia:80 \ -R 0.0.0.0:50007:gerhard-dashboard:9119 \ + -R 0.0.0.0:11435:festinger:11434 \ tunnel@glitch.university