From 05b459a65fe6f8429f825c0830a09915e16dccf4 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 7 Aug 2026 03:08:03 -0400 Subject: [PATCH] =?UTF-8?q?fix(aiui):=20content=20loads=20progressively=20?= =?UTF-8?q?=E2=80=94=20own=20first,=20peers=20when=20they=20arrive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression from wiring the peers scope: requestArchyAllContent awaited all three scopes together, so the grid waited on the slowest. `peers` browses every federated node over FIPS (Tor fallback) and routinely takes tens of seconds or times out when a peer is offline. On-device that read as "content(peers) failed: Content request timed out" plus an AIUI that felt very slow to open — with nothing rendered meanwhile, even though local content was ready immediately. Now `own` paints as soon as it lands and `owned`/`peers` fold in as they arrive. A scope that times out costs only its own results. Also records the operator's console findings as tasks: the `files` context timeout, the web-search CSP block (13-09, now firing on every query), the strfry icon 404, IndeeHub's relay.nostr.band socket, and the ask that `!archy` over mesh be able to action container commands with text responses. Co-Authored-By: Claude Opus 5 (1M context) --- ...ead-of-the-content-and-context-surfaces.md | 29 ++++++ aiui/packages/app/src/composables/useArchy.ts | 94 +++++++++++-------- 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md b/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md index 8db8e83b..8c83f7cb 100644 --- a/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md +++ b/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md @@ -108,3 +108,32 @@ touched only `appgate/mod.rs`; `routstr.rs` has no reference to appgate). Per `RESUME-2026-08-06-media-loop.md`: fix → build → deploy to archi-dev-box → test live → verify the shipped bundle changed → commit → push. No item is done without a live check on the node. + +## Added 2026-08-07 — operator console evidence + +- **`content(peers) timed out` + AIUI slow to open** — CAUSED by the first version of + `requestArchyAllContent` awaiting all three scopes together, so the grid waited on the + slowest. `peers` browses every federated node over FIPS (Tor fallback) and routinely takes + tens of seconds. **FIXED**: progressive — `own` paints immediately, `owned`/`peers` fold in + as they land, a timeout costs only its own scope. +- **`files fetch failed: Context request timed out: files`** — the `files` CONTEXT category + (not content) is slow enough to hit the broker's deadline. Separate from the above; needs + its own look at what `sanitizeFiles()` does. +- **Web search blocked by CSP** — `connect-src http://:*/aiui/` blocks + `/api/web-search`. Already recorded as 13-09's decision (the web-search setting must drive + the CSP node-side); now reproduced on every single query, so it is noisy as well as broken. +- **`strfry.png` / `strfry.svg` 404** — missing app icon. +- **`wss://relay.nostr.band` closed before established** — IndeeHub's own relay connection, + from its page. Separate from the local relay fix. +- **Operator ask: `!archy` over mesh must action commands.** The mesh AI should be able to + restart/stop/install containers and everything else that makes sense, with text responses — + i.e. the same tool surface the embedded assistant has, reachable from a mesh message. + +## Assistant tool coverage (root cause of "I don't have a tool for that") + +`content_list` mapped ONLY to `content.list-mine`, so asking about peer films got an honest +"I have no tool" — the model was right. Now takes `scope`: own | peers | purchased | films, +dispatching to content.list-mine / content.browse-all-peers / content.owned-list / +content.indeehub-projects. Peer browsing rides FIPS (`PeerRequest::new(fips_npub, onion, +"/content")`, 6s FIPS fast-fail then Tor), which is what the operator meant by "we use FIPS +for that" — the onion is the peer's identity, FIPS is the transport. diff --git a/aiui/packages/app/src/composables/useArchy.ts b/aiui/packages/app/src/composables/useArchy.ts index 647976b7..83ec51a7 100644 --- a/aiui/packages/app/src/composables/useArchy.ts +++ b/aiui/packages/app/src/composables/useArchy.ts @@ -270,55 +270,73 @@ export function useArchy() { */ async function requestArchyAllContent() { if (!isInitialized.value) return - const scopes: Array<'own' | 'owned' | 'peers'> = ['own', 'owned', 'peers'] - const results = await Promise.all( - scopes.map((s) => - archyBridge - .requestArchyContent('all', s) - .then((res) => { - // Log the denial per scope. A silent null here is indistinguishable - // from "the node genuinely has no content", which is exactly the - // ambiguity that made an ungranted Media/File permission look like - // a broken films search on-device. - if (!res.permitted) { - console.warn( - `[AIUI Archy] content(${s}): not permitted — enable Media/File access in Archy Settings`, - ) - return null - } - return res - }) - .catch((err) => { - console.warn(`[AIUI Archy] content(${s}) failed:`, (err as Error)?.message ?? err) - return null - }), - ), - ) - - const seen = new Set() + // PROGRESSIVE, not all-at-once. The first version awaited all three scopes + // together, so the grid waited on the slowest — and `peers` browses every + // federated node over FIPS (falling back to Tor), which routinely takes + // tens of seconds or times out entirely when a peer is offline. On-device + // that showed up as "content(peers) failed: Content request timed out" and + // an AIUI that felt very slow to open, with NOTHING rendered in the + // meantime even though local content was ready immediately. + // + // So: paint `own` as soon as it lands, then fold in the slower sources as + // they arrive. A scope that times out costs only its own results. const films: Film[] = [] const podcasts: Podcast[] = [] - for (const res of results) { - if (!res) continue + const seen = new Set() + + const merge = (res: { films?: unknown; podcasts?: unknown } | null) => { + if (!res) return false + let added = false for (const f of (res.films ?? []) as Film[]) { - const key = `film:${f.id}` - if (seen.has(key)) continue - seen.add(key) - films.push(f) + const k = `film:${f.id}` + if (seen.has(k)) continue + seen.add(k); films.push(f); added = true } for (const p of (res.podcasts ?? []) as Podcast[]) { - const key = `pod:${p.id}` - if (seen.has(key)) continue - seen.add(key) - podcasts.push(p) + const k = `pod:${p.id}` + if (seen.has(k)) continue + seen.add(k); podcasts.push(p); added = true } + return added } - const panel = useContentPanel() - panel.setArchyContent({ films, songs: panel.panelSongs.value, podcasts }) + const paint = () => { + const panel = useContentPanel() + panel.setArchyContent({ films: [...films], songs: panel.panelSongs.value, podcasts: [...podcasts] }) + } + + const fetchScope = (scope: 'own' | 'owned' | 'peers') => + archyBridge + .requestArchyContent('all', scope) + .then((res) => { + if (!res.permitted) { + console.warn(`[AIUI Archy] content(${scope}): not permitted — enable Media/File access in Archy Settings`) + return null + } + return res + }) + .catch((err) => { + // A slow or absent peer is ordinary, not exceptional. + console.warn(`[AIUI Archy] content(${scope}) failed:`, (err as Error)?.message ?? err) + return null + }) + + // Local content first — it is fast and is what the operator sees instantly. + if (merge(await fetchScope('own'))) paint() + + // Then the slower sources, each painting as it lands rather than blocking + // the others or the initial render. + await Promise.all( + (['owned', 'peers'] as const).map((scope) => + fetchScope(scope).then((res) => { + if (merge(res)) paint() + }), + ), + ) } + /** * Request the node's real music library (13-11 — the D-13 wave) and hand * it to the same `setArchyContent` sink `requestArchyContent` uses, so