diff --git a/.planning/HANDOFF.json b/.planning/HANDOFF.json new file mode 100644 index 00000000..5b91b8e0 --- /dev/null +++ b/.planning/HANDOFF.json @@ -0,0 +1,54 @@ +{ + "version": "1.0", + "timestamp": "2026-08-07T10:02:11.548Z", + "phase": "13", + "phase_name": "aiui-functional-conversational-node-control-and-content-surf", + "phase_dir": ".planning/phases/13-aiui-functional-conversational-node-control-and-content-surf", + "plan": 15, + "task": 6, + "total_tasks": 17, + "status": "paused", + "note": "Task ids below are the OPERATOR's 17-item demo list (see .planning/RESUME-2026-08-07-aiui-surfaces.md), not 13-XX plan tasks. Phase 13's own GSD plans are 14/15 done with only 13-15 (device-close) open.", + "completed_tasks": [ + {"id": 1, "name": "Unify AI Data Access toggles with assistant tool grants", "status": "done", "commit": "55155f2d"}, + {"id": 5, "name": "Cap content.browse-all-peers, rebuilt as Cloud's fan-out", "status": "done", "commit": "75919a20"}, + {"id": 12, "name": "Node certificate settings section container/layout", "status": "done", "commit": "75919a20"}, + {"id": 16, "name": "Populate the content surface for own shared content + rich chat previews", "status": "done", "commit": "9abc1623,b1c5d138", "evidence": "browser: chat:response surfaces=1 songs:2 images:13; heading '13 Images'"}, + {"id": 15, "name": "Content-surface stale title", "status": "done", "commit": "9abc1623", "evidence": "browser: 'Loading...' during turn, '13 Images' after", "caveat": "header-OVERLAP half never reproduced at 1600x950; check a narrow/mobile viewport"}, + {"id": 6, "name": "Settings link when a permission is ungranted", "status": "in_progress", "commit": "9abc1623", "progress": "node refused_categories + broker event + Teleported chrome banner all landed and typecheck clean; NEVER seen in a browser"}, + {"id": 9, "name": "Answer with content + context surfaces, not JUST prose", "status": "in_progress", "commit": "b1c5d138", "progress": "content turns verified in browser; system/network/bitcoin turns still prose-only because only content_list/apps_list are surface-producing"}, + {"id": 10, "name": "AIUI slow background image + console noise", "status": "in_progress", "commit": "b1c5d138", "progress": "web-search CSP spam and its 403 fixed; wavlake/itunes CSP block, 3x403, 2x402, 502, 404, sw.js SSL all still present; slow background image not investigated"} + ], + "remaining_tasks": [ + {"id": 2, "name": "Verify AI grants persist across refresh through the real UI path", "status": "not_started"}, + {"id": 3, "name": "Add app_install / app_uninstall tools behind the confirm gate", "status": "not_started"}, + {"id": 4, "name": "!archy / !ai over mesh must action commands with text responses", "status": "not_started"}, + {"id": 7, "name": "App lifecycle defects (fedimint guardian, BTCPay wipe-reinstall, disappearing apps, chown postgres-btcpay)", "status": "not_started"}, + {"id": 8, "name": "LND UI + filebrowser 401s (session passthrough on node-owned *-ui apps)", "status": "not_started", "hint": "the 3x403 still in the AIUI console may be this same family"}, + {"id": 11, "name": "Cmd/Ctrl+K carries the query into the expanded chat", "status": "not_started", "hint": "whole path read and appears correctly wired incl. cold-frame buffer; REPRODUCE IN A BROWSER before editing code"}, + {"id": 13, "name": "Serve HTTPS dynamically on EVERY address alongside Tailscale", "status": "not_started"}, + {"id": 14, "name": "Nostr signer + service worker over HTTPS", "status": "not_started"}, + {"id": 17, "name": "Cut a clean ISO for the demo (UNBUNDLED=1)", "status": "blocked", "blocked_by": "release binary predates the install.rs SearXNG seed fix"} + ], + "blockers": [ + {"description": "core/target/release/archipelago and the deployed /usr/local/bin/archipelago were both built BEFORE the install.rs SearXNG seed fix (c810b514). They carry every surface/peer fix (verified live) but not the SearXNG one.", "type": "technical", "workaround": "cd core && CARGO_INCREMENTAL=0 cargo build --release -p archipelago (~9 min), verify with: strings core/target/release/archipelago | grep -A2 'limiter: false' should show the formats lines, then redeploy. MUST clear before task 17 (ISO)."}, + {"description": "Existing fleet nodes still have SearXNG JSON disabled — c810b514 only fixes what NEW installs get. Every existing node's AIUI web search returns 403.", "type": "external", "workaround": "Add search.formats [html, json] to /var/lib/archipelago/searxng/settings.yml on each node and restart the app. archi-dev-box is already repaired."}, + {"description": "A concurrent agent is committing to the same branch (1eb75a1e, a7368b8b, 9cf1c122 — a full AIUI security/mission assessment with 6 HIGH/MED findings S1-S6). Stage by path only; never git add -A.", "type": "external", "workaround": "Read .planning/phases/13-.../ASSESSMENT-FIX-PLAN-2026-08-07.md before the next wave; its findings partly overlap tasks 8 and 10."} + ], + "async_jobs": [], + "human_actions_pending": [ + {"action": "Operator device-close for 13-15 check 2 (film content displayed on the surface)", "context": "The old holdout reason was 'no film content exists on this node'. That premise is now stale: peer content demonstrably reaches the surface and the images bucket renders the node's own catalogue. Re-test before asking again.", "blocking": true}, + {"action": "Decide whether system/network/bitcoin turns deserve context surfaces (task 9 remainder)", "context": "Only content_list and apps_list are surface-producing tools today.", "blocking": false} + ], + "decisions": [ + {"decision": "Capture surfaces RAW in loop_::execute_tool's Ok(v) arm, BEFORE wrap_tool_result_if_untrusted", "rationale": "The untrusted boundary exists to stop peer-authored text being read as instructions by the MODEL. This copy goes to a renderer that treats every field as inert data and never re-enters the prompt; wrapping it would leave the UI parsing delimiter noise instead of JSON.", "phase": "13"}, + {"decision": "Gate chat surfaces on media/files in the broker as well as node-side", "rationale": "Mirrors handleContentRequest — this channel carries node data into the iframe, so it is a consent surface and is checked in the host rather than trusting the node's grant check to be the only one. Dropping surfaces never drops the prose answer.", "phase": "13"}, + {"decision": "Archy tabs outrank regex-inferred tabs, ordered by bucket size", "rationale": "setArchyContent put the node grids up and updatePanelFromText then replaced the bar, landing on an AI Brief with the real grid unreachable. Size ordering because a 13-photo/2-track answer opened on Songs and titled itself '2 Songs'.", "phase": "13"}, + {"decision": "Skip client-side web search entirely when embedded in Archy", "rationale": "streamViaArchy sends only the user's text, so the system prompt those results were folded into is never transmitted. It cost a round trip and a CSP console error per turn while its output provably reached no model. Web search for the embedded path belongs node-side with the other tools.", "phase": "13"}, + {"decision": "Keep SearXNG rather than replace it", "rationale": "Operator suspected poor results. Measured live after the JSON fix: 28 results for 'bitcoin halving' from Brave (20) + DuckDuckGo (8). Google self-suspends and Startpage CAPTCHAs, normal for self-hosted and cheap given Brave's independent index. The 403 was the whole problem.", "phase": "13"}, + {"decision": "browse-all-peers accumulates per batch with a between-batch deadline, no outer timeout", "rationale": "Every future is already bounded by PER_PEER_TIMEOUT, so an outer timeout can only discard completed work — which is the exact bug being fixed.", "phase": "13"} + ], + "uncommitted_files": ["neode-ui/shot.tmp.mjs (untracked Playwright driver — deliberately not committed; recreate from the recipe in the resume doc if deleted)"], + "next_action": "Rebuild the release binary (CARGO_INCREMENTAL=0 cargo build --release -p archipelago) to clear the ISO blocker, and while it builds verify task 6's permission banner in a browser by revoking the media grant and asking for content.", + "context_notes": "This box IS archi-dev-box, so everything is testable locally. The session's method was: read code, form a hypothesis, then PROVE it against the live node before changing anything — that is what overturned the previous session's 'peers have no content' conclusion (the operator flagged it as wrong, and they were right: two code bugs produced a number that looked like a fleet outage). Authenticated RPC goes through nginx on 443, NOT ports 7777/8101. The Playwright driver at neode-ui/shot.tmp.mjs installs an addInitScript probe that logs every chat:response with its bucket counts — that probe is what proved the surface pipeline end to end, and it is the fastest way to re-verify after any change." +} diff --git a/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/.continue-here.md b/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/.continue-here.md new file mode 100644 index 00000000..f6045ea1 --- /dev/null +++ b/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/.continue-here.md @@ -0,0 +1,213 @@ +--- +context: phase +phase: 13-aiui-functional-conversational-node-control-and-content-surf +task: 6 +total_tasks: 17 +status: in_progress +last_updated: 2026-08-07T10:02:11.548Z +--- + +# BLOCKING CONSTRAINTS — Read Before Anything Else + +> These are not suggestions. Each constraint below was discovered through failure +> in this session. Acknowledge each one explicitly before proceeding. + +- [ ] CONSTRAINT: **Do not re-diagnose peer content as a fleet problem.** The previous + session concluded "the 16 federated peers are not serving content, this is a fleet + problem, not a UI one." The operator said that was completely wrong, and it was. + Two code bugs produced a number identical to a fleet outage. Live proof after the + fix: 4 of 16 peers reached, 7 items from 2 peers, through the assistant. + **Mitigation: before attributing any empty result to infrastructure, call the RPC + directly twice and compare. Flapping between "real items" and "0 reached" is a + code bug, not a network.** +- [ ] CONSTRAINT: **Never drive podman directly for app containers.** I ran + `podman restart searxng` and the orchestrator, which owns lifecycle, saw + "stopping" and the container vanished from the node. + **Mitigation: use the RPC `container-start` / `container-stop` / `container-restart` + with params `{"app_id": ""}` — note `app_id`, not `name`.** +- [ ] CONSTRAINT: **A concurrent agent is committing to this same branch.** + `1eb75a1e`, `a7368b8b`, `9cf1c122` are not mine. + **Mitigation: stage explicitly by path (`git add `). Never `git add -A` or + `git commit -a`.** + +**Do not proceed until all boxes are checked.** + +## Critical Anti-Patterns + +| Pattern | Description | Severity | Prevention Mechanism | +|---------|-------------|----------|---------------------| +| Infrastructure-blaming a code bug | `content.browse-all-peers` wrapped its whole fan-out in `timeout(...).unwrap_or_default()`, discarding every COMPLETED batch on expiry. One slow peer turned a partial success into `0 reached / 16 unreachable`, which reads exactly like "the peers are down". | blocking | Call the RPC twice and compare before blaming the network. A result that flaps between real data and zero is code. | +| Direct podman control of app containers | `podman restart searxng` removed a healthy running app; the orchestrator reconciled it away. | blocking | Use RPC `container-*` with `{"app_id": ...}`. Never `podman start/stop/restart/rm` on an app container. | +| Trusting a tool-name→RPC mapping without a reachability test | `tools.rs` named three real dispatcher-registered methods that `assistant_dispatch_tool` had no arm for. Every non-`own` content scope died on the catch-all and the tool never ran. Schema validation passed; the failure was entirely downstream of it. | blocking | The regression test `every_content_scope_reaches_a_real_dispatch_handler` now asserts no scope returns "no such handler". Extend it when adding tools. | +| Editing JSON locale files with `json.dump` | Reformatted `es.json` wholesale — a 470-line diff for two keys. | advisory | Insert keys textually, preserving the file's own formatting. Verify with `git diff --stat`. | +| Declaring a UI fix done without a browser | Three defects this session were invisible from source: the tab bar being overwritten by the prose surface, the web-search path 403, and the images bucket being dropped. All only appeared when the real UI was driven. | blocking | Drive `neode-ui/shot.tmp.mjs` and read the screenshot before claiming any UI item complete. | + + +Phase 13's own GSD plans are 14/15 (only 13-15, the device-close human-verify, is open). +The active work is the operator's **17-item demo list**, not 13-XX tasks — the full list +with per-item status lives in `.planning/RESUME-2026-08-07-aiui-surfaces.md`. + +Branch `gsd/phase-13-aiui-functional-conversational-node-control-and-content-surf` +@ `bca18c03`, pushed to `gitea-ai`. Working tree clean except the untracked +`neode-ui/shot.tmp.mjs` (deliberate — throwaway Playwright driver). + +archi-dev-box (this box) is running the new frontends and a release binary that carries +every surface/peer fix but NOT the SearXNG seed fix. + +Three commits landed this session: `9abc1623`, `c810b514`, `b1c5d138`. + + + + +Operator list — done and browser-verified: +- Task 1: Unify AI Data Access toggles with assistant tool grants — `55155f2d` +- Task 5: Cap `content.browse-all-peers`, rebuilt as Cloud's fan-out — `75919a20` +- Task 12: Node certificate settings section container/layout — `75919a20` +- Task 16: Content surface populated for own shared content — `9abc1623` + `b1c5d138` +- Task 15: Stale-title half — "Loading…" during the turn, "13 Images" after + +In progress: +- Task 6: Ungranted-permission Settings offer — node `refused_categories` + broker + `aiui:permission-needed` event + Teleported chrome banner all landed in `9abc1623`, + typecheck clean, **never seen in a browser** +- Task 9: Surfaces alongside prose — content turns verified; system/network/bitcoin + turns still prose-only (only `content_list`/`apps_list` produce surfaces) +- Task 10: Console noise — web-search CSP spam and its 403 fixed; the rest still present + +What the three commits actually fixed (all four of these produced ONE symptom — a +correct prose answer beside an empty grid): +1. `assistant_dispatch_tool` had an arm only for `content.list-mine`; the `peers`, + `purchased` and `films` scopes named real handlers it had never heard of, so they + died on its catch-all. The tool never ran. +2. `content.browse-all-peers` discarded completed batches on timeout expiry. + Budget 20s → 45s (two batches of 8 at a 10s per-peer timeout had zero headroom). +3. `assistant.chat` returned only `{ text }`; tool results were dropped in the loop. + Now carried as `surfaces`, captured raw before the untrusted wrap. +4. The adapter classified images as `'excluded'`; a photo-heavy node rendered empty + while AIUI's `panelImages`/`ImageGrid` sat unused. +Plus: SearXNG's JSON API was 403 (default `formats: [html]`), so AIUI web search could +never have worked; and `searchWeb` used a host-absolute path instead of BASE_URL. + + + + +- Task 2: Verify AI grants persist across refresh through the real UI +- Task 3: Add `app_install` / `app_uninstall` tools behind the 13-08 confirm gate +- Task 4: `!archy` / `!ai` over mesh must action commands with text responses +- Task 6: Finish — verify the permission banner in a browser +- Task 7: App lifecycle defects (fedimint guardian, BTCPay wipe-reinstall, disappearing + apps, fedimint gateway 88%, reconciler `chown postgres-btcpay`) +- Task 8: LND UI + filebrowser 401s — untouched. The 3×403 still in the AIUI console + may be this same family; check before assuming a separate cause. +- Task 9: Decide whether system/network/bitcoin turns get context surfaces +- Task 10: wavlake/itunes CSP block (song covers), 3×403, 2×402, 502, 404, sw.js SSL, + and the slow background image (not yet investigated) +- Task 11: Cmd/Ctrl+K query carry — **reproduce before editing**; the whole path + (`SpotlightSearch.vue:294` → `Chat.vue` `askedAt` watcher → `flushAsk` → + `chat:prefill` → `archyBridge.onPrefill` buffer → `ChatInput.vue:224`) reads as + correct and complete, including the cold-frame buffer. It prefills-and-focuses by + design rather than auto-sending. +- Task 13: HTTPS on every address as addresses change +- Task 14: Nostr signer + service worker over HTTPS +- Task 15: Header-OVERLAP half — never reproduced at 1600×950 (the tab row already + carries `pr-12`, `ChatPage.vue:38`). Try a narrow/mobile viewport. +- Task 17: ISO — **blocked**, see Blockers + + + + +- Capture surfaces RAW, before `wrap_tool_result_if_untrusted`. The untrusted boundary + stops peer text being read as instructions **by the model**; this copy goes to a + renderer that treats every field as inert data and never re-enters the prompt. + Wrapping it would leave the UI parsing delimiter noise instead of JSON. +- Gate chat surfaces on media/files in the broker as well as node-side, mirroring + `handleContentRequest`. Dropping surfaces never drops the prose answer. +- Archy tabs outrank regex-inferred tabs, ordered by bucket size — a 13-photo/2-track + answer had been opening on Songs and titling itself "2 Songs". +- Skip client-side web search when embedded: `streamViaArchy` sends only the user's + text, so those results provably reached no model. Web search for the embedded path + belongs node-side. +- Keep SearXNG. Measured after the JSON fix: 28 results from Brave + DuckDuckGo. + Google self-suspends, Startpage CAPTCHAs — normal self-hosted, and cheap given + Brave's own index. The 403 was the whole problem, not result quality. +- `browse-all-peers` uses a between-batch deadline and no outer timeout: every future + is already bounded per-peer, so an outer timeout can only discard completed work. + + + +- **Binary drift (blocks task 17).** `core/target/release/archipelago` and the deployed + `/usr/local/bin/archipelago` were both built before the `install.rs` SearXNG fix. + Rebuild: `cd core && CARGO_INCREMENTAL=0 cargo build --release -p archipelago` (~9 min). + Verify: `strings core/target/release/archipelago | grep -A2 "limiter: false"` should + show the `formats` lines. Then redeploy and cut the ISO. +- **Fleet SearXNG repair.** `c810b514` only fixes new installs. Every existing node's + AIUI web search still 403s. Add to `/var/lib/archipelago/searxng/settings.yml`: + `search:` / ` formats:` / ` - html` / ` - json`, then restart the app. + archi-dev-box is already repaired. +- **Concurrent agent on this branch** — see the constraint above. + + +## Required Reading (in order) +1. `.planning/RESUME-2026-08-07-aiui-surfaces.md` — the full 17-item list with + per-item status, the peer-files correction, and the browser-verification recipe +2. `.planning/phases/13-.../ASSESSMENT-FIX-PLAN-2026-08-07.md` — another agent's + full-stack security/mission audit, S1–S6 (network_status leaks WAN IP + SSID against + a label promising "no IP addresses"; Claude key plaintext in localStorage; standalone + mode reachable on a node with unscreened egress; unauthenticated web-search proxy; + unredacted `app_logs`; G-B2 turn-minimality silently stripping prior user turns). + Overlaps tasks 8 and 10. +3. `.planning/RESUME-2026-08-06-media-loop.md` — the fix→deploy→test loop and deploy commands +4. `.planning/METHODOLOGY.md` (if it exists) — project analytical lenses + +## Critical Anti-Patterns (do NOT repeat these) +- [ANTI-PATTERN]: Blaming infrastructure for a flapping empty result → call the RPC + twice and compare before concluding anything about the network. +- [ANTI-PATTERN]: `podman restart ` → use RPC `container-*` with `{"app_id": ...}`. +- [ANTI-PATTERN]: Claiming a UI fix without driving the browser → run `shot.tmp.mjs` + and read the screenshot. +- [ANTI-PATTERN]: `git add -A` while another agent shares the tree → stage by path. + +## Infrastructure State +- **This box IS archi-dev-box** (`hostname`; LAN `192.168.63.240`, Tailscale `100.69.68.39`). + Everything is testable locally; no remote SSH needed. +- `archipelago.service`: active, running the newly deployed binary (restarted this session). +- Deployed: `/opt/archipelago/web-ui/` (neode-ui) and `/opt/archipelago/web-ui/aiui/` + both carry this session's builds. +- **Authenticated RPC goes through nginx on 443 — NOT ports 7777 or 8101.** + POST `https://192.168.63.240/rpc/v1`, `auth.login` `{"password":"ThisIsWeb54321@"}`, + carry `session` + `csrf_token` cookies and send `X-CSRF-Token`. +- SearXNG: running and healthy; `settings.yml` repaired with `formats: [html, json]`; + `format=json` verified 200. +- AI grants on this node: `apps=Y system=Y files=Y media=Y`, rest closed. +- Node has 18 own shared items (mostly photos + 2 mp3 + 3 APKs) and reaches ~4 of 16 peers. +- `neode-ui/shot.tmp.mjs` — uncommitted Playwright driver. **Run it from `neode-ui/`** + (that is where `playwright` is installed; the aiui package only has it under pnpm's + store). Dismiss the Remote Companion modal with **Escape first** — its button uses a + curly apostrophe so `has-text("I've installed it")` never matches. It installs an + `addInitScript` probe logging every `chat:response` with bucket counts. + +## Pre-Execution Critique Required +Not applicable — this is mid-execution, not a design/execute boundary. + + +The method that worked this session: read the code, form a hypothesis, then PROVE it +against the live node before changing anything. That is what overturned the previous +session's peer conclusion. Every claim in the commits is backed by a measurement taken +on archi-dev-box, and the three UI defects that mattered most were invisible from +source — they only appeared when the real browser drove the real node. + +The surface pipeline is now proven end to end: +`[PROBE] chat:response success=true surfaces=1 detail=[{"tool":"content_list", +"scope":"own","films":0,"songs":2,"images":13}]`, heading "Loading…" → "13 Images", +with the prose answer still on the left. That is the shape the operator asked for — +"not JUST prose", surfaces alongside it. + + + +Start with: `cd core && CARGO_INCREMENTAL=0 cargo build --release -p archipelago` to +clear the ISO blocker (~9 min, run it in the background). While it builds, verify +task 6 in a browser: revoke the `media` grant via `assistant.grants-set`, ask AIUI for +content, and confirm the permission banner renders and its button lands on the AI Data +Access section. Then re-check task 15's header overlap at a narrow viewport, and +task 11's ⌘K carry — both are read-only verifications that may need no code at all. +