commit 1d13b5065dd6053194386c62a97be05a7761af2a
Author: Coder Agent <coder@agents.omni>
Date: Mon Feb 16 20:01:46 2026
Centralize web routing under Omni.Web
Use Omni.Web as the single Warp router for /tasks, /news, and /files.
Task-Id: t-615
diff --git a/Omni/Ava/Core.hs b/Omni/Ava/Core.hs
index 0704e259..6c7b8e32 100755
--- a/Omni/Ava/Core.hs
+++ b/Omni/Ava/Core.hs
@@ -38,9 +38,9 @@ import qualified Data.Time as Time
import qualified Omni.Agent.AuditLog as AuditLog
import qualified Omni.Agent.Memory as Memory
import qualified Omni.Ava.Telegram.Bot as Telegram
-import qualified Omni.Ava.Web as Web
import qualified Omni.Cli as Cli
import qualified Omni.Test as Test
+import qualified Omni.Web as Web
import qualified System.Directory as Dir
import qualified System.Environment as Environment
import qualified System.IO as IO
diff --git a/Omni/Ava/Web.hs b/Omni/Ava/Web.hs
index a9aada05..7096e682 100644
--- a/Omni/Ava/Web.hs
+++ b/Omni/Ava/Web.hs
@@ -25,6 +25,7 @@
-- : dep http-conduit
module Omni.Ava.Web
( startWebServer,
+ initWebDb,
app,
defaultPort,
main,
@@ -59,12 +60,16 @@ main = putText "Use Omni.Ava for the main entry point"
defaultPort :: Int
defaultPort = 8079
-startWebServer :: Int -> FilePath -> IO ()
-startWebServer port dbPath = do
- putText <| "Starting Ava web server on port " <> tshow port
+initWebDb :: FilePath -> IO ()
+initWebDb dbPath =
SQL.withConnection dbPath <| \conn -> do
Trace.initTraceDb conn
Agent.initAgentDb conn
+
+startWebServer :: Int -> FilePath -> IO ()
+startWebServer port dbPath = do
+ putText <| "Starting Ava web server on port " <> tshow port
+ initWebDb dbPath
Warp.run port (app dbPath)
-- -----------------------------------------------------------------------------
diff --git a/Omni/Dev/Beryllium/Ava.nix b/Omni/Dev/Beryllium/Ava.nix
index 4519e981..0fe2b4cd 100644
--- a/Omni/Dev/Beryllium/Ava.nix
+++ b/Omni/Dev/Beryllium/Ava.nix
@@ -53,7 +53,7 @@
# Note: Tailscale Funnel for beryllium ingress is configured via:
# tailscale funnel --bg 80
- # Caddy then routes /files, /news, and /tasks (via ava on 8079).
+ # Caddy then proxies all requests to Omni.Web on 8079.
# This persists in tailscaled config and doesn't need a systemd service.
# URL: https://beryllium.oryx-ide.ts.net/
}
diff --git a/Omni/Dev/Beryllium/Caddy.nix b/Omni/Dev/Beryllium/Caddy.nix
index 41f7bd7c..8aa3a370 100644
--- a/Omni/Dev/Beryllium/Caddy.nix
+++ b/Omni/Dev/Beryllium/Caddy.nix
@@ -1,31 +1,16 @@
-# Unified ingress for beryllium services.
+# Unified ingress for beryllium web.
#
-# Caddy listens on port 80 and routes by path prefix:
-# /news/ → newsreader (8071)
-# /files/ → serve (8070)
-# all else (including /tasks) → ava web (8079)
-#
-# /news and /files run with --base-path so generated HTML links
-# include their prefixes. Caddy strips those prefixes before proxying.
+# Caddy stays thin and proxies everything to Omni.Web on port 8079.
+# Omni.Web performs all app routing (/tasks, /news, /files).
{...}: {
services.caddy = {
enable = true;
globalConfig = ''
admin localhost:2019
'';
- # Single-host config: match all requests, route by path
+ # Single-host config: proxy all traffic to Omni.Web
virtualHosts.":80" = {
extraConfig = ''
- # Redirect /news and /files (no slash) to /news/ and /files/
- redir /news /news/ 308
- redir /files /files/ 308
-
- handle_path /news/* {
- reverse_proxy localhost:8071
- }
- handle_path /files/* {
- reverse_proxy localhost:8070
- }
handle {
reverse_proxy localhost:8079
}
diff --git a/Omni/Serve.hs b/Omni/Serve.hs
index 077a92d1..6c7297d8 100755
--- a/Omni/Serve.hs
+++ b/Omni/Serve.hs
@@ -26,6 +26,7 @@
-- : dep time
module Omni.Serve
( main,
+ app,
test,
)
where
diff --git a/Omni/Web.hs b/Omni/Web.hs
new file mode 100644
index 00000000..476c37ab
--- /dev/null
+++ b/Omni/Web.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Unified web server for beryllium.
+--
+-- Routes all web surfaces in a single Warp process:
+-- - /tasks and Ava control plane via 'Omni.Ava.Web'
+-- - /news via 'Omni.Newsreader.Web'
+-- - /files via 'Omni.Serve'
+--
+-- This keeps routing logic centralized under Omni/Web.
+--
+-- : dep bytestring
+-- : dep sqlite-simple
+-- : dep wai
+-- : dep warp
+module Omni.Web
+ ( startWebServer,
+ defaultPort,
+ app,
+ )
+where
+
+import Alpha
+import qualified Data.ByteString as ByteString
+import qualified Data.Text.Encoding as TextEncoding
+import qualified Database.SQLite.Simple as SQL
+import qualified Network.Wai as Wai
+import qualified Network.Wai.Handler.Warp as Warp
+import qualified Omni.Ava.Web as AvaWeb
+import qualified Omni.Newsreader.Db as NewsDb
+import qualified Omni.Newsreader.Web as NewsWeb
+import qualified Omni.Serve as Serve
+import qualified System.Environment as Env
+
+defaultPort :: Int
+defaultPort = AvaWeb.defaultPort
+
+startWebServer :: Int -> FilePath -> IO ()
+startWebServer port avaDbPath = do
+ putText <| "Starting unified Omni.Web server on port " <> tshow port
+ AvaWeb.initWebDb avaDbPath
+ NewsDb.initDb
+ newsDbPath <- NewsDb.getDbPath
+ filesRoot <- Env.lookupEnv "SERVE_ROOT" /> fromMaybe "/home/ben/ava"
+ SQL.withConnection newsDbPath <| \newsConn ->
+ Warp.run port (app avaDbPath newsConn filesRoot)
+
+app :: FilePath -> SQL.Connection -> FilePath -> Wai.Application
+app avaDbPath newsConn filesRoot req respond =
+ case Wai.pathInfo req of
+ "news" : _ ->
+ NewsWeb.app "/news" newsConn (stripPrefixRequest "news" req) respond
+ "files" : _ ->
+ Serve.app "/files" filesRoot (stripPrefixRequest "files" req) respond
+ _ ->
+ AvaWeb.app avaDbPath req respond
+
+stripPrefixRequest :: Text -> Wai.Request -> Wai.Request
+stripPrefixRequest prefix req =
+ let strippedPath = case Wai.pathInfo req of
+ _ : rest -> rest
+ [] -> []
+ raw = Wai.rawPathInfo req
+ prefixBytes = "/" <> TextEncoding.encodeUtf8 prefix
+ strippedRaw
+ | raw == prefixBytes = "/"
+ | prefixBytes `ByteString.isPrefixOf` raw =
+ let rest = ByteString.drop (ByteString.length prefixBytes) raw
+ in if ByteString.null rest then "/" else rest
+ | otherwise = raw
+ in req {Wai.pathInfo = strippedPath, Wai.rawPathInfo = strippedRaw}