diff --git a/.planning/RESUME-2026-08-07-aiui-surfaces.md b/.planning/RESUME-2026-08-07-aiui-surfaces.md new file mode 100644 index 00000000..942f3b9e --- /dev/null +++ b/.planning/RESUME-2026-08-07-aiui-surfaces.md @@ -0,0 +1,292 @@ +# RESUME — 2026-08-07 (afternoon). AIUI surfaces + demo prep. + +**Read this first.** Then `.planning/RESUME-2026-08-07-aiui-demo.md` (the previous +handoff, still the source for the traps list), then +`.planning/RESUME-2026-08-06-media-loop.md` (the fix→deploy→test loop). + +Branch `gsd/phase-13-aiui-functional-conversational-node-control-and-content-surf` +@ `b1c5d138`. Working tree: clean except `neode-ui/shot.tmp.mjs` (a throwaway +Playwright driver, see "Browser verification" below — delete or keep, it is not +committed). + +**Context: AIUI is being demoed soon and a clean ISO must be cut.** The operator's +priority order is #16, #15, #9, #17. + +**The operator's standing instruction for this work:** fix → test → check the +browser with screenshots → deploy → debug → fix, in a loop, without stopping to +ask questions. And: *"if you think you know better like the peer files you are +completely wrong"* — see "The peer-files correction" below. Do not re-derive the +old conclusion. + +--- + +## THE TASK LIST — rebuild this in the session task tool on resume + +`[x]` = done, deployed AND verified on the node. + +- [x] **1. Unify AI Data Access toggles with assistant tool grants** — `55155f2d` +- [ ] **2. Verify AI grants persist across refresh through the real UI path** +- [ ] **3. Add `app_install` / `app_uninstall` tools** (behind the 13-08 confirm gate) +- [ ] **4. `!archy` / `!ai` over mesh must action commands** with text responses +- [x] **5. Cap `content.browse-all-peers`** — `75919a20`, then rebuilt as Cloud's fan-out +- [~] **6. Settings link when a request needs an ungranted permission** — node + broker + + chrome banner all landed in `9abc1623`; **NOT yet seen in a browser** (needs a + turn that actually hits an ungranted category — try revoking `media` and asking + for content). Finish by confirming the banner renders and its button lands on + the AI Data Access section. +- [ ] **7. App lifecycle defects** — fedimint guardian installs but doesn't work; BTCPay + uninstall-with-wipe reinstalls with an account still enabled; Bitcoin Knots and other + apps disappeared; fedimint gateway died at 88%; reconciler `chown postgres-btcpay` + failures (same volume-ownership family as the IndeeHub relay fix) +- [ ] **8. LND UI + filebrowser 401s** — every `:18083/proxy/lnd/*` and + `/app/filebrowser/api/resources/`. Node-owned `*-ui` apps need session passthrough; + memory says that rides DISK manifests because the catalog refuses build-source. + **Untouched this session.** Note the 403s still in the browser console (below) may + be this same family — worth checking before assuming a separate cause. +- [~] **9. AIUI must answer with content + context surfaces, NOT JUST prose** — + *(operator correction: the prose answer stays, it is wanted; what was missing is + the surfaces alongside it)*. Largely delivered by the work below and verified in a + browser. Remaining: only `content_list`/`apps_list` are surface-producing tools, so + turns about system/network/bitcoin still answer in prose only. Decide whether those + deserve context surfaces too. +- [~] **10. AIUI slow background image + console noise** — web-search CSP spam and the + web-search 403 are FIXED (`b1c5d138`). Still open, all observed live in the console: + wavlake.com + itunes.apple.com blocked by CSP (song cover enrichment — this is the + known 13-09 CSP issue; memory says the decision is that the web-search setting + should drive the CSP, moved node-side), three 403s, two 402 Payment Required, a 502, + a 404, and the sw.js SSL registration failure (self-signed cert). The slow + background image itself was not investigated. +- [ ] **11. Cmd/Ctrl+K → AIUI must carry the query into the expanded chat** — I read the + whole path (`SpotlightSearch.vue:294` → `Chat.vue` `askedAt` watcher → `flushAsk` + → `chat:prefill` → `archyBridge.onPrefill` buffering → `ChatInput.vue:224`) and it + is **fully wired and looks correct**, including the cold-frame buffer. It + deliberately prefills-and-focuses rather than auto-sending. **Not reproduced, not + verified in a browser.** Do that before changing any code — the report may predate + the fix. +- [x] **12. Node certificate settings section** container/layout — `75919a20` +- [ ] **13. HTTPS dynamically on EVERY address** alongside Tailscale (LAN done; must + re-apply as addresses change, and the bare hostname must resolve) +- [ ] **14. Nostr signer + service worker over HTTPS** (operator: lower priority than AIUI) +- [x] **15. Content-surface header** — the stale-title half is DONE and browser-verified + ("Loading…" during the turn, "Nothing found" on an empty result, correct count + after). **The header-overlap half is NOT confirmed:** at 1600×950 the close button + (`absolute top-3 right-3`) does not collide with anything — the tab row already + carries `pr-12` (`ChatPage.vue:38`). I never reproduced the overlap. **Check a + narrow/mobile viewport** before editing CSS; that is the most likely place it bites. +- [x] **16. Populate the content surface for own shared content + rich chat previews** — + DONE and browser-verified. See below. +- [ ] **17. Cut a clean ISO for the demo** — `UNBUNDLED=1 bash + image-recipe/build-debian-iso.sh` (the default env silently builds the wrong + full-bundle variant); verify the frontend INSIDE the ISO. + **BLOCKER: rebuild the release binary first** — see "Binary drift" below. + +--- + +## What shipped this session + +| What | Commit | +|---|---| +| Content surface renders what the assistant found (4 defects) | `9abc1623` | +| SearXNG JSON 403 — AIUI web search never worked | `c810b514` | +| Node content outranks the prose surface; web-search path | `b1c5d138` | + +### `9abc1623` — the content surface, four separate defects, one symptom + +The symptom was always the same: a correct prose answer beside an empty grid. + +1. **The assistant's curated RPC bridge had an arm only for `content.list-mine`.** + `assistant/tools.rs` mapped the `peers`, `purchased` and `films` scopes onto + `content.browse-all-peers` / `content.owned-list` / `content.indeehub-projects` + — three real, dispatcher-registered handlers that `assistant_dispatch_tool` + (`api/rpc/assistant_chat.rs`) had never heard of. Every non-`own` scope died on + its catch-all with "no such handler". **The tool never ran.** Regression test + added: `every_content_scope_reaches_a_real_dispatch_handler`. +2. **`content.browse-all-peers` threw away completed work.** It wrapped the whole + fan-out in one `timeout(overall, ...).unwrap_or_default()`, which DISCARDED + every finished batch the moment the budget expired. One slow peer turned a + partly-successful browse into `peers_reached: 0, peers_unreachable: 16`. Now it + accumulates per batch and checks a deadline between batches, so partial results + always survive; budget 20s → 45s (two batches of 8 at a 10s per-peer timeout had + literally zero headroom). +3. **`assistant.chat` returned only `{ text }`.** The structured tool results were + dropped inside the loop. The turn now carries them through as `surfaces`, + captured RAW in `loop_::execute_tool`'s `Ok(v)` arm — deliberately BEFORE + `wrap_tool_result_if_untrusted`, because that boundary exists to stop peer text + being read as instructions by the MODEL, and this copy goes to a renderer that + treats every field as inert data and never re-enters the prompt. +4. **The adapter classified images as `'excluded'` and dropped them.** A node + sharing mostly photos rendered as an empty grid while AIUI's `panelImages` / + `ImageGrid` sat unused. Images now have a bucket end to end + (`archyContentAdapter` → `contextBroker` → `archyBridge` → `useArchy` → + `useContentPanel`), with the paid-lock and extension-fallback handling that + audio and video already had. + +Also in that commit: the "Loading…" / "Nothing found" panel headings; a system-prompt +paragraph telling the model to call the content tool and summarise rather than +re-list what the cards already show; and `refused_categories` on the chat response so +the trusted chrome can offer the AI settings screen (task 6). + +### `c810b514` — SearXNG JSON was 403, so AIUI web search never worked + +The operator asked whether "Web search via your private SearXNG instance" is true. +**It is true** — `/aiui/api/web-search` proxies to `127.0.0.1:8888/search`, the local +container, and nothing leaves via a third-party API. But SearXNG defaults to +`formats: [html]`, so its JSON API answered **403**, and JSON is the only thing AIUI +speaks. Both seed sites (`scripts/first-boot-containers.sh` and +`api/rpc/package/install.rs`) omitted `search.formats`. + +**Measured after the fix, live:** 28 results for "bitcoin halving", from Brave (20) +and DuckDuckGo (8). Google self-suspends ("access denied") and Startpage hits a +CAPTCHA — normal for a self-hosted instance, and it costs little because Brave runs +its own independent index. **The result quality is fine; the 403 was the whole +problem.** No case for replacing SearXNG on this evidence. + +**Existing nodes need manual repair** (the commit only fixes what new installs get): +add to `/var/lib/archipelago/searxng/settings.yml` +```yaml +search: + formats: + - html + - json +``` +then restart the app. **archi-dev-box is already repaired.** + +### `b1c5d138` — the prose surface was winning, and the web-search path was wrong + +- `setArchyContent` put the node's grids on the tab bar, then `updatePanelFromText` + **replaced** the bar with tabs inferred from the reply text. "show me my own shared + content" landed on an **"AI Brief"** — a prose restatement of the answer already on + the left — with the populated image grid no longer reachable. Guarding the `panel*` + arrays was not enough: they held the right data while the tab bar had discarded the + way to see it. Archy tabs now lead and the title follows the leading tab. +- Archy tabs are ordered by **bucket size**. A node with 13 photos and 2 tracks opened + on Songs and titled itself "2 Songs" for a 15-item answer. +- `searchWeb` hardcoded `/api/web-search` while every other call is built from + `BASE_URL`. Under `/aiui/` it asked the HOST for a path only the AIUI-scoped nginx + location serves → 403 from the node's API gate, plus a CSP refusal. +- The embedded path now skips client-side web search entirely: `streamViaArchy` sends + only the user's text, so the system prompt those results were folded into is never + transmitted. It was a round trip and a console error per turn whose output provably + reached no model. **Web search for the embedded path belongs node-side, next to the + other tools** — that is task #10's "web search through node chat" note. + +--- + +## The peer-files correction — READ THIS + +The previous session concluded: *"Your 16 federated peers are not serving content… +peers_reached: 0 is correct. No AIUI work will make peer films appear until those +peers answer. This is a fleet problem, not a UI one."* + +**That was wrong, and the operator said so.** Measured live on archi-dev-box: + +- A direct `content.browse-all-peers` RPC returned **real peer items** (a peer's + `Music/Architects of Tomorrow.mp3`) on one call and **0 reached / 16 unreachable** + on the very next — the discard-on-timeout bug in defect 2 above. +- Through the assistant after the fixes: **4 of 16 peers reached, 7 items from 2 + peers**, including paid tracks at 10,000 sats. + +The peers were serving content the whole time. Two code bugs (a missing dispatch arm +and a timeout that threw away completed work) produced a number that looked exactly +like a fleet outage. **Do not re-diagnose this as infrastructure.** + +--- + +## Browser verification — how it was done, and how to repeat it + +This box **is** archi-dev-box (`hostname` confirms; LAN `192.168.63.240`, Tailscale +`100.69.68.39`). Everything can be tested locally. + +**Authenticated RPC from the shell** — +`/tmp/claude-1000/.../scratchpad/rpc.mjs` (regenerate if the scratchpad is gone): +POST `https://192.168.63.240/rpc/v1`, `auth.login` with `{"password":"ThisIsWeb54321@"}`, +carry the `session` + `csrf_token` cookies and send `X-CSRF-Token`. +**The API is NOT on 7777 or 8101** — go through nginx on 443. + +**Playwright driver** — `neode-ui/shot.tmp.mjs` (uncommitted, run it from +`neode-ui/`, which is where `playwright` is installed; the aiui package has it only +under pnpm's store). It logs in, opens `/dashboard/chat`, dismisses the Remote +Companion modal (**Escape first — its button uses a curly apostrophe, so +`has-text("I've installed it")` never matches**), finds the `/aiui/` frame, types +into the real composer, and screenshots. It also installs an `addInitScript` probe +that logs every `chat:response` with its surface bucket counts — that probe is what +proved the pipeline end to end. + +**Latest verified run:** +``` +heading before: "16 Images" +heading during turn: "Loading…" +heading after: "13 Images" +[PROBE] chat:response success=true surfaces=1 + detail=[{"tool":"content_list","scope":"own","films":0,"songs":2,"images":13}] +``` +Screenshots in the scratchpad: `13-answered.png` shows prose on the left and a +populated Songs/Images grid on the right — the thing that was empty before. + +--- + +## Binary drift — DO THIS BEFORE THE ISO + +`core/target/release/archipelago` was built BEFORE the `install.rs` SearXNG change, +and the currently deployed `/usr/local/bin/archipelago` is that same binary. It has +all the surface/peer fixes (verified live) but **not** the SearXNG seed fix. + +``` +cd core && CARGO_INCREMENTAL=0 cargo build --release -p archipelago # ~9 min +``` +Confirm it took: `strings core/target/release/archipelago | grep -A2 "limiter: false"` +should now show the `formats` lines. Then deploy per the loop below and cut the ISO. + +## Deploy loop (archi-dev-box = this box) + +``` +# frontend +cd neode-ui && npm run build +sudo rsync -a --exclude 'aiui/' --exclude 'archipelago-runtime/' \ + web/dist/neode-ui/ /opt/archipelago/web-ui/ +# AIUI (build-aiui.sh HANGS after succeeding — wrap in `timeout 540 ... || true` +# and check dist/index.html's mtime; the dist is complete) +bash scripts/build-aiui.sh +sudo rsync -a --delete aiui/packages/app/dist/ /opt/archipelago/web-ui/aiui/ +# binary +sudo cp -f /usr/local/bin/archipelago /opt/archipelago/rollback/archipelago.bak +sudo install -m 755 core/target/release/archipelago /usr/local/bin/archipelago +sudo systemctl restart archipelago +``` + +## Test state + +- **Rust:** `cargo test --bin archipelago assistant::` → **122 passed, 0 failed.** + (`-p archipelago --lib` fails with "no library targets" — it is a bin crate.) +- **neode-ui:** adapter + broker + views → **71 passed, 0 failed.** I also fixed a + **pre-existing** failure in `toolConfirm.test.ts` (it asserted `rpcClient.call` was + never called, but `ContextBroker.start()` hydrates AI permissions over RPC; narrowed + to "no `assistant.*` call", which is the property actually under test). +- **AIUI:** **348 passed, 3 failed — all three PRE-EXISTING**, confirmed by stashing + my changes and re-running. They are `seed-conversations.test.ts` (seed-songs content + types), `seedExtraction.test.ts` (extracts 10 songs), and `useAI.test.ts` + (`webSearch` flag in the request body — this one may now be *related* to the + embedded-mode gate; re-check it, it was failing before but for a different reason). + +## Traps — do not repeat + +- **I removed a running container** by running `podman restart searxng` directly. The + orchestrator owns lifecycle: it saw "stopping" and the container vanished. Recover + with the RPC `container-start` and params **`{"app_id": "..."}`** (not `{"name":...}`). + Do not drive podman by hand for app containers. +- `es.json` reformatted wholesale when edited with `json.dump` (470-line diff). Insert + keys textually, preserving the file's own formatting. +- Verify on the NODE, not from source (nginx template vs `/etc/nginx/sites-enabled/`). +- rustls does NOT check key/cert pairing — the check in `appgate/tls.rs` is load-bearing. +- AIUI must build with `VITE_BASE_PATH=/aiui/` or you get a black page. +- Never `rm -rf /opt/archipelago/web-ui/*` — it destroys `aiui/`. +- Restarting `archipelago` does NOT kill app containers (they live in `/user.slice`). +- Release build is ~9 min. Budget for it. + +## Phase 13 GSD + +14/15. **13-15 only** (device-close, blocking human-verify). Operator verified checks +1, 3 and 4. **Check 2 was the holdout — "no film content exists on this node to +display".** That premise should be re-tested now: peer content demonstrably reaches +the surface, and the images bucket means the node's own catalogue renders too. Then +write `13-UAT.md`, fill `13-VALIDATION.md`, close.