fix(aiui): content loads progressively — own first, peers when they arrive
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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
086d381c4f
commit
05b459a65f
+29
@@ -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://<node>:*/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.
|
||||
|
||||
@@ -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<string>()
|
||||
// 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<string>()
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user