| # Phoenix LiveView β Agent Console integration | |
| Clean extension: make LiveView the control surface. | |
| ## Supervisor | |
| `lib/perplexity_macro/application.ex` β already `ROM + Host`. Extend to `ROM + VM + TraceHub + Endpoint + ToolBroker + WASMBox`: | |
| ```elixir | |
| children = [ | |
| PerplexityMacro.ROM, | |
| PerplexityMacro.VM, # GenServer fetch/decode/execute | |
| PerplexityMacro.TraceHub, # ring 50k + PubSub | |
| PerplexityMacro.ResearchBroker, # search/fetch/... + Evidence TLV | |
| PerplexityMacro.ToolRouter, # capability router + CONFIRM | |
| PerplexityMacro.WASMBox, # Port to Pyodide/Worker or NIF | |
| PerplexityMacroWeb.Endpoint | |
| ] | |
| ``` | |
| ## Mailbox | |
| `lib/perplexity_macro/rom.ex:00 phase 01 fuel 02 cap 03 status, TLV @$46 CRC16 @$C6` β keep. Add `python.execute` as code cap (0x04) routed to WASMBox instead of Adapters.Code. | |
| ## Tool Router | |
| ```elixir | |
| defmodule PerplexityMacro.ToolRouter do | |
| # sensitive? from capability.ex β requires CONFIRM 0x2A + expiring token | |
| def route(%{capability: cap}=req) do | |
| if PerplexityMacro.Capability.sensitive?(cap) and not confirmed?(req) do | |
| {:error, :confirm_required} | |
| else | |
| case cap do | |
| :code when req.python? -> PerplexityMacro.WASMBox.exec(req) | |
| _ -> PerplexityMacro.ResearchBroker.request(req) | |
| end | |
| end | |
| end | |
| end | |
| ``` | |
| ## LiveView | |
| `lib/perplexity_macro_web/live/agent_console_live.ex`: | |
| ```elixir | |
| defmodule PerplexityMacroWeb.AgentConsoleLive do | |
| use Phoenix.LiveView | |
| def mount(_,_,socket) do | |
| if connected?(socket), do: Phoenix.PubSub.subscribe(PerplexityMacro.PubSub, "trace") | |
| {:ok, assign(socket, vm: PerplexityMacro.VM.snapshot(), py: PerplexityMacro.WASMBox.status(), tools: PerplexityMacro.ToolRouter.registry())} | |
| end | |
| def handle_info({:retired, trace}, socket), do: {:noreply, stream_insert(socket, :traces, trace, at: 0)} | |
| def handle_event("instruction", %{"capability"=>cap, "arguments"=>args}, socket) do | |
| VM.request(cap, args) # -> RQ -> ToolRouter -> VERIFY -> EVIDENCE -> TRACE | |
| {:noreply, socket} | |
| end | |
| def handle_event("python", %{"code"=>code}, socket) do | |
| VM.request(:code, %{python: true, code: code}) | |
| {:noreply, socket} | |
| end | |
| end | |
| ``` | |
| Template is `docs/agent-console/index.html` (same IDs: `#chatLog`, `#traceBody`, `#termOut`). JS hook `phx-hook="AgentConsole"` replaces the mock WS with real Phoenix socket. | |
| ## Static fallback (GitHub Pages) | |
| `docs/agent-console/` works without Phoenix β JS mocks broker + VM. When `ws://localhost:4000/socket/websocket` connects, it upgrades to real PubSub streaming (50k ring). Pyodide always runs in browser WASM, mediated through Capability Router in both modes. | |