Run notify forwarder as a compose service (host networking, restart policy) (#26)
CI / build + unit (push) Successful in 19s

This commit was merged in pull request #26.
This commit is contained in:
2026-09-25 10:49:51 +02:00
parent 5e03546f40
commit dca0dfa894
6 changed files with 86 additions and 1 deletions
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""mesh-bot notify forwarder: POST /notify {text, from?} -> opencode session message (basic auth).
Note on semantics: the opencode API accepts a session message immediately but holds the HTTP
response until the agent turn completes — so a read timeout here means DELIVERED, not failed.
"""
import json, base64, urllib.request
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
BASE = os.environ.get("FORWARDER_BASE", "http://127.0.0.1:9898/workspaces/679fd6f6-b237-4a46-bf69-64cb0fc865b0/instance")
SESSION = os.environ.get("FORWARDER_SESSION", "ses_f31037caeffeioRwixqGV7VS6m")
USER = os.environ["FORWARDER_USER"] # required; fail fast if missing
PASS = os.environ["FORWARDER_PASS"] # required; fail fast if missing
class H(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/notify":
self.send_response(404); self.end_headers(); return
n = int(self.headers.get("Content-Length", 0))
try:
body = json.loads(self.rfile.read(n) or b"{}")
text, frm = body.get("text"), body.get("from")
msg = 'New mesh message' + (f' from node {frm}' if frm is not None else '') + f': "{text}" — please react to it.'
req = urllib.request.Request(
f"{BASE}/session/{SESSION}/message",
data=json.dumps({"parts": [{"type": "text", "text": msg}]}).encode(),
headers={"Content-Type": "application/json",
"Authorization": "Basic " + base64.b64encode(f"{USER}:{PASS}".encode()).decode()},
method="POST")
try:
r = urllib.request.urlopen(req, timeout=15)
code = r.status # headers received => opencode accepted the message
try: out = r.read(200).decode(errors='replace')
except Exception as e2: out = f'(body read failed after delivery: {e2!r})'
print(f"forwarded to opencode session {SESSION}: HTTP {code} {out[:150]!r}", flush=True)
self._reply(code if 200 <= code < 300 else 502, b'{"ok":true}' if 200 <= code < 300 else str(code).encode())
except TimeoutError:
# request was sent; opencode holds the response until the agent turn finishes => delivered
print(f"forwarded to opencode session {SESSION}: accepted (response still pending after 15 s)", flush=True)
self._reply(202, b'{"ok":true,"note":"accepted; opencode response pending"}')
except Exception as e:
print(f"opencode call failed: {e!r}", flush=True)
self._reply(502, repr(e).encode())
def _reply(self, code, payload):
try:
self.send_response(code); self.end_headers(); self.wfile.write(payload)
except (BrokenPipeError, ConnectionResetError):
pass # client went away; delivery state already logged above
def log_message(self, fmt, *args):
print("forwarder:", fmt % args, flush=True)
HTTPServer(("0.0.0.0", 4097), H).serve_forever()