import { Hono } from "hono"; import { cors } from "hono/cors"; import { createWorker } from "@atmo-dev/contrail/worker"; import { Contrail } from "@atmo-dev/contrail"; import { config } from "./contrail.config.js"; import { lexicons } from "../lexicons/generated/index.js"; import { LANDING_PAGE } from "./landing-page.js"; // ─── Env ──────────────────────────────────────────────────────────── interface Env { DB: D1Database; ASSETS: { fetch(request: Request): Promise }; VAULT_BUCKET: R2Bucket; CARRY_BUCKET: R2Bucket; LETTA_API_KEY?: string; LETTA_AGENT_ID?: string; } interface CentroidDiscoveryResult { title: string; themes: string[]; highlights: string[]; tensions: string[]; openQuestions: string[]; synthesis: string; edges: Array<{ source: string; target: string; connectionType: string; note?: string }>; } // ─── Contrail setup ───────────────────────────────────────────────── const contrailWorker = createWorker(config, { lexicons }); const contrail = new Contrail(config); let contrailReady = false; async function ensureContrailReady(db: D1Database): Promise { if (contrailReady) return; await contrail.init(db); contrailReady = true; } async function resolveDidIdentity(did: string): Promise<{ did: string; handle: string | null; pds: string | null } | null> { let doc: any; if (did.startsWith("did:plc:")) { const resp = await fetch(`https://plc.directory/${encodeURIComponent(did)}`); if (!resp.ok) return null; doc = await resp.json(); } else if (did.startsWith("did:web:")) { const host = did.slice("did:web:".length).replace(/:/g, "/"); const resp = await fetch(`https://${host}/.well-known/did.json`); if (!resp.ok) return null; doc = await resp.json(); } else { return null; } const aka = Array.isArray(doc.alsoKnownAs) ? doc.alsoKnownAs.find((v: string) => v.startsWith("at://")) : null; const handle = aka ? aka.slice("at://".length) : null; const svc = Array.isArray(doc.service) ? doc.service.find((s: any) => s.type === "AtprotoPersonalDataServer" || s.id === "#atproto_pds") : null; const pds = typeof svc?.serviceEndpoint === "string" ? svc.serviceEndpoint.replace(/\/$/, "") : null; return { did, handle, pds }; } // ─── Resolve synthesisDoc AT URI pointers ─────────────────────────── // // synthesisDoc used to be an inline pub.oxa.document object. // Now it's an AT URI pointing to a separate pub.oxa.document record. // This helper dereferences the pointer so the frontend always gets // the resolved OxaRecord object. const synthesisDocCache = new Map(); async function resolveSynthesisDoc(synthesisDoc: any): Promise { if (!synthesisDoc) return null; // Already an inline OXA document object (legacy format) if (typeof synthesisDoc === "object" && synthesisDoc.$type === "pub.oxa.document") { return synthesisDoc; } // AT URI string — dereference it if (typeof synthesisDoc === "string" && synthesisDoc.startsWith("at://")) { // Check cache if (synthesisDocCache.has(synthesisDoc)) { return synthesisDocCache.get(synthesisDoc); } try { // Parse AT URI: at://// const parts = synthesisDoc.replace("at://", "").split("/"); const did = parts[0]; const collection = parts[1]; const rkey = parts[2]; if (!did || !collection || !rkey) return null; // Resolve PDS for this DID const identity = await resolveDidIdentity(did); if (!identity?.pds) return null; const resp = await fetch( `${identity.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=${collection}&rkey=${rkey}` ); if (!resp.ok) return null; const data: any = await resp.json(); const doc = data.value; if (doc?.$type === "pub.oxa.document") { synthesisDocCache.set(synthesisDoc, doc); return doc; } } catch { return null; } } return null; } // ─── Island detection (connected components) ──────────────────────── // // Load all connections, build an undirected graph, find connected // components via BFS. Each component is an "island" — a cluster of // linked URLs that form a constellation in the knowledge graph. interface Island { id: string; centroid: string; vertices: string[]; edges: Array<{ uri: string; did: string; source: string; target: string; connectionType: string; note: string; handle?: string | null; }>; } /** Compute centroid: vertex with highest closeness centrality (smallest avg BFS distance). */ function computeCentroid(adj: Map>, nodes: Set): string { if (nodes.size <= 1) return [...nodes][0] ?? ''; let bestNode = ''; let bestAvg = Infinity; for (const start of nodes) { const dist = new Map(); dist.set(start, 0); const queue = [start]; while (queue.length > 0) { const current = queue.shift()!; for (const neighbor of adj.get(current) || []) { if (!dist.has(neighbor) && nodes.has(neighbor)) { dist.set(neighbor, dist.get(current)! + 1); queue.push(neighbor); } } } let totalDist = 0; let reachable = 0; for (const [node, d] of dist) { if (nodes.has(node)) { totalDist += d; reachable++; } } const avg = reachable > 0 ? totalDist / reachable : Infinity; if (avg < bestAvg) { bestAvg = avg; bestNode = start; } } return bestNode; } /** Hash a URL to a 12-char hex island ID (matches CLI's islandId()). */ async function islandHash(url: string): Promise { const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(url)); return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, "0")).join("").slice(0, 12); } async function detectIslands(db: D1Database): Promise { await ensureContrailReady(db); // Load all connections const rows = await db .prepare( `SELECT r.record, r.did, r.rkey, i.handle FROM records_connection r LEFT JOIN identities i ON r.did = i.did ORDER BY r.time_us DESC`, ) .all<{ record: string; did: string; rkey: string; handle: string | null }>(); // Build adjacency list const adj = new Map>(); const edges: Island["edges"] = []; for (const row of rows.results || []) { try { const value = JSON.parse(row.record); const source = value.source as string; const target = value.target as string; if (!source || !target) continue; if (!adj.has(source)) adj.set(source, new Set()); if (!adj.has(target)) adj.set(target, new Set()); adj.get(source)!.add(target); adj.get(target)!.add(source); edges.push({ uri: `at://${row.did}/network.cosmik.connection/${row.rkey}`, did: row.did, source, target, connectionType: value.connectionType || "relates", note: value.note || "", handle: row.handle, }); } catch {} } // BFS to find connected components const visited = new Set(); const islands: Island[] = []; for (const node of adj.keys()) { if (visited.has(node)) continue; const component: string[] = []; const queue = [node]; while (queue.length > 0) { const current = queue.shift()!; if (visited.has(current)) continue; visited.add(current); component.push(current); for (const neighbor of adj.get(current) || []) { if (!visited.has(neighbor)) queue.push(neighbor); } } // Only include edges where both endpoints are in this component const componentSet = new Set(component); const componentEdges = edges.filter( e => componentSet.has(e.source) && componentSet.has(e.target), ); // Stable ID: truncated SHA-256 of the centroid vertex (highest closeness centrality) const centroid = computeCentroid(adj, componentSet); const id = await islandHash(centroid); islands.push({ id, centroid, vertices: component, edges: componentEdges, }); } // Sort by size descending islands.sort((a, b) => b.vertices.length - a.vertices.length); return islands; } // ─── Resolve vertex metadata (batched) ────────────────────────────── // // Bulk queries instead of N+1 per vertex. Loads all cards and citations // matching the given URIs in a few chunked queries. async function resolveVertexMeta( db: D1Database, uris: string[], ): Promise> { const meta = new Map(); for (const uri of uris) { meta.set(uri, { title: uri, description: "", type: "unknown" }); } const httpUris = uris.filter(u => u.startsWith("http")); const atUris = uris.filter(u => u.startsWith("at://")); const CHUNK = 50; // Build all queries upfront, then batch-execute in one D1 round trip const stmts: D1PreparedStatement[] = []; // HTTP URI queries — cards const httpChunks: string[][] = []; for (let i = 0; i < httpUris.length; i += CHUNK) { const chunk = httpUris.slice(i, i + CHUNK); httpChunks.push(chunk); const placeholders = chunk.map(() => "?").join(","); stmts.push( db.prepare( `SELECT record FROM records_card WHERE json_extract(record, '$.content.url') IN (${placeholders})` ).bind(...chunk) ); } // HTTP URI queries — citations for (let i = 0; i < httpUris.length; i += CHUNK) { const chunk = httpUris.slice(i, i + CHUNK); const placeholders = chunk.map(() => "?").join(","); stmts.push( db.prepare( `SELECT record FROM records_citation WHERE json_extract(record, '$.url') IN (${placeholders})` ).bind(...chunk) ); } // AT URI queries — cards const atChunks: string[][] = []; for (let i = 0; i < atUris.length; i += CHUNK) { const chunk = atUris.slice(i, i + CHUNK); atChunks.push(chunk); const placeholders = chunk.map(() => "?").join(","); stmts.push( db.prepare(`SELECT uri, record FROM records_card WHERE uri IN (${placeholders})`).bind(...chunk) ); } // AT URI queries — collections for (let i = 0; i < atUris.length; i += CHUNK) { const chunk = atUris.slice(i, i + CHUNK); const placeholders = chunk.map(() => "?").join(","); stmts.push( db.prepare(`SELECT uri, record FROM records_collection WHERE uri IN (${placeholders})`).bind(...chunk) ); } // Execute all queries in one batch const results = stmts.length > 0 ? await db.batch(stmts) : []; let resultIdx = 0; // Process card results (HTTP) for (const chunk of httpChunks) { const rows = (results[resultIdx++] as { results: { record: string }[] } | null)?.results || []; for (const row of rows) { try { const card = JSON.parse(row.record); const url = card?.content?.url; if (!url || !meta.has(url)) continue; const m = card.content.metadata; if (m?.title || m?.description) { meta.set(url, { title: m.title || url, description: m.description || "", type: card.type === "URL" ? "url" : "note" }); } } catch {} } } // Process citation results (HTTP) — overrides card data for (let i = 0; i < httpChunks.length; i++) { const rows = (results[resultIdx++] as { results: { record: string }[] } | null)?.results || []; for (const row of rows) { try { const citation = JSON.parse(row.record); const url = citation.url; if (!url || !meta.has(url)) continue; if (citation.title) { const existing = meta.get(url)!; meta.set(url, { title: citation.title, description: citation.takeaway || existing.description, type: "citation" }); } } catch {} } } // Process card results (AT) for (const chunk of atChunks) { const rows = (results[resultIdx++] as { results: { uri: string; record: string }[] } | null)?.results || []; for (const row of rows) { try { const card = JSON.parse(row.record); const m = card?.content?.metadata; const url = card?.content?.url; if (meta.has(row.uri)) { meta.set(row.uri, { title: m?.title || url || row.uri, description: m?.description || "", type: "card" }); } } catch {} } } // Process collection results (AT) for (let i = 0; i < atChunks.length; i++) { const rows = (results[resultIdx++] as { results: { uri: string; record: string }[] } | null)?.results || []; for (const row of rows) { try { const coll = JSON.parse(row.record); if (meta.has(row.uri)) { meta.set(row.uri, { title: coll.name || row.uri, description: coll.description || "", type: "collection" }); } } catch {} } } return meta; } // ─── Derive island from centroid ──────────────────────────────────── // // Given a centroid vertex (canonical component reference), derive the // connected component by traversing network.cosmik.connection records. async function deriveIsland( db: D1Database, seed: string, ): Promise { await ensureContrailReady(db); // BFS in application code — recursive CTE OOMs in D1 due to cross join const visited = new Set(); const queue = [seed]; const allEdges: Array<{ uri: string; did: string; rkey: string; source: string; target: string; connectionType: string; note: string; handle: string | null; }> = []; while (queue.length > 0) { // Drain the current queue into a batch const batch = queue.splice(0, 50); const toQuery = batch.filter(v => !visited.has(v)); if (toQuery.length === 0) continue; // Mark visited for (const v of toQuery) visited.add(v); // Query connections where any of these vertices are source or target const placeholders = toQuery.map(() => "?").join(","); const rows = await db .prepare( `SELECT r.record, r.did, r.rkey, i.handle FROM records_connection r LEFT JOIN identities i ON r.did = i.did WHERE json_extract(r.record, '$.source') IN (${placeholders}) OR json_extract(r.record, '$.target') IN (${placeholders})`, ) .bind(...toQuery, ...toQuery) .all<{ record: string; did: string; rkey: string; handle: string | null }>(); for (const row of rows.results || []) { try { const value = JSON.parse(row.record); const source = value.source as string; const target = value.target as string; if (!source || !target) continue; // Collect edge const edgeKey = `${source}|${target}|${row.rkey}`; allEdges.push({ uri: `at://${row.did}/network.cosmik.connection/${row.rkey}`, did: row.did, rkey: row.rkey, source, target, connectionType: value.connectionType || "relates", note: value.note || "", handle: row.handle, }); // Enqueue unvisited neighbors if (!visited.has(source)) queue.push(source); if (!visited.has(target)) queue.push(target); } catch {} } } if (visited.size === 0) return null; const vertices = [...visited]; // Filter edges to only those with both endpoints in the component const vertexSet = new Set(vertices); const edges: Island["edges"] = []; const seenEdges = new Set(); for (const e of allEdges) { if (!vertexSet.has(e.source) || !vertexSet.has(e.target)) continue; const edgeKey = `${e.source}|${e.target}|${e.rkey}`; if (seenEdges.has(edgeKey)) continue; seenEdges.add(edgeKey); edges.push({ uri: e.uri, did: e.did, source: e.source, target: e.target, connectionType: e.connectionType, note: e.note, handle: e.handle, }); } // Compute stable ID from centroid (highest closeness centrality) const adj = new Map>(); for (const e of edges) { if (!adj.has(e.source)) adj.set(e.source, new Set()); if (!adj.has(e.target)) adj.set(e.target, new Set()); adj.get(e.source)!.add(e.target); adj.get(e.target)!.add(e.source); } const centroid = computeCentroid(adj, vertexSet); const id = await islandHash(centroid); return { id, centroid, vertices, edges }; } // ─── List islands from records_island ─────────────────────────── // ─── Resolve edge URIs to full edge objects ────────────────────── /** Given a list of connections (AT URI strings, legacy {uri}, or legacy inline objects), resolve URIs to full edges from D1. */ async function resolveEdgeUris(db: D1Database, connections: any[]): Promise { const resolved: any[] = []; const uriIndices: Map = new Map(); // uri → indices in connections that need it for (let i = 0; i < connections.length; i++) { const conn = connections[i]; if (typeof conn === "string") { resolved.push(null); if (!uriIndices.has(conn)) uriIndices.set(conn, []); uriIndices.get(conn)!.push(i); continue; } // Already has source/target — use as-is if (typeof conn === "object" && conn.source && conn.target) { resolved.push(conn); continue; } // Just a URI reference — needs lookup const uri = conn?.uri; if (!uri) continue; resolved.push(null); // placeholder if (!uriIndices.has(uri)) uriIndices.set(uri, []); uriIndices.get(uri)!.push(i); } // Batch-fetch connection records from D1 (for URI-only references) if (uriIndices.size > 0) { const uris = [...uriIndices.keys()]; const CHUNK = 100; for (let i = 0; i < uris.length; i += CHUNK) { const chunk = uris.slice(i, i + CHUNK); const placeholders = chunk.map(() => "?").join(","); const rows = await db .prepare( `SELECT r.uri, r.record, r.did, r.rkey, i.handle FROM records_connection r LEFT JOIN identities i ON r.did = i.did WHERE r.uri IN (${placeholders})`, ) .bind(...chunk) .all<{ uri: string; record: string; did: string; rkey: string; handle: string | null }>(); for (const row of rows.results || []) { try { const value = JSON.parse(row.record); const edge = { uri: row.uri, did: row.did, source: value.source, target: value.target, connectionType: value.connectionType || "relates", note: value.note || "", handle: row.handle, }; for (const idx of uriIndices.get(row.uri) || []) { resolved[idx] = edge; } } catch {} } } } // Resolve handles for inline edges (those with source/target but no did/handle) // Extract DID from AT URI (at://did:plc:.../collection/rkey) and batch-lookup handles const inlineEdges = resolved.filter((e: any) => e && !e.did); if (inlineEdges.length > 0) { const dids = new Set(); for (const edge of inlineEdges) { const uri = (edge as any).uri as string | undefined; if (!uri) continue; const match = uri.match(/^at:\/\/(did:[^/]+)\//); if (match) dids.add(match[1]); } if (dids.size > 0) { const ph = [...dids].map(() => "?").join(","); const rows = await db .prepare(`SELECT did, handle FROM identities WHERE did IN (${ph})`) .bind(...dids) .all<{ did: string; handle: string | null }>(); const handleMap = new Map(); for (const row of rows.results || []) { handleMap.set(row.did, row.handle); } for (const edge of inlineEdges) { const uri = (edge as any).uri as string | undefined; if (!uri) continue; const match = uri.match(/^at:\/\/(did:[^/]+)\//); if (match) { const did = match[1]; (edge as any).did = did; (edge as any).handle = handleMap.get(did) ?? null; } } } } return resolved.filter(Boolean); } /** Resolve at://inline:* URIs by looking up the forkOf record's inline edge data */ async function resolveInlineFromForkOf(db: D1Database, record: any): Promise { const connections: any[] = Array.isArray(record.connections) ? record.connections : []; const hasInlineUris = connections.some(c => typeof c === "string" && c.startsWith("at://inline:")); if (!hasInlineUris || !record.forkOf) return []; // Look up the forkOf record const forkRow = await db .prepare("SELECT record FROM records_island WHERE uri = ?") .bind(record.forkOf) .first<{ record: string }>(); if (!forkRow) return []; const forkRec = JSON.parse(forkRow.record); const forkConnections: any[] = Array.isArray(forkRec.connections) ? forkRec.connections : []; // Build a map from inline URI → edge data const inlineMap = new Map(); for (const conn of forkConnections) { if (typeof conn === "object" && conn.uri && conn.source && conn.target) { inlineMap.set(conn.uri, conn); } } // Resolve the current record's connections using the fork's inline data const resolved: any[] = []; for (const conn of connections) { if (typeof conn === "string" && inlineMap.has(conn)) { const edge = inlineMap.get(conn); resolved.push({ uri: conn, source: edge.source, target: edge.target, connectionType: edge.connectionType || "relates", note: edge.note || "", }); } } return resolved; } async function findSimilarIslands(db: D1Database, currentUri: string, currentVertices: Set, currentConnUris?: Set): Promise { if (currentVertices.size === 0) return []; const domainOf = (url: string): string | null => { try { const u = new URL(url); return u.hostname.replace(/^www\./, "").toLowerCase(); } catch { return null; } }; const currentDomains = new Set([...currentVertices].map(domainOf).filter(Boolean) as string[]); const rows = await db .prepare("SELECT uri, record FROM records_island WHERE uri != ? AND uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT 500") .bind(currentUri) .all<{ uri: string; record: string }>(); // Phase 1: Fast pre-filter using connection URI overlap (no edge resolution) const candidates: { uri: string; record: any; connOverlap: number; centroid: string | null }[] = []; for (const row of rows.results || []) { try { const rec = JSON.parse(row.record); const connections: any[] = Array.isArray(rec.connections) ? rec.connections : []; const candidateConnUris = new Set(connections.filter(c => typeof c === "string")); const centroid = rec.source?.uri || null; // Fast overlap: shared connection AT URIs let connOverlap = 0; if (currentConnUris && currentConnUris.size > 0) { for (const uri of candidateConnUris) { if (currentConnUris.has(uri)) connOverlap++; } } // Quick centroid/domain check for islands with no connection overlap if (connOverlap === 0 && centroid) { const centroidDomain = domainOf(centroid); if (centroidDomain && currentDomains.has(centroidDomain)) connOverlap = -1; // domain match, low priority } if (connOverlap !== 0 || (centroid && currentVertices.has(centroid))) { candidates.push({ uri: row.uri, record: rec, connOverlap, centroid }); } } catch {} } // Phase 2: Resolve edges only for top candidates (max 20) candidates.sort((a, b) => Math.abs(b.connOverlap) - Math.abs(a.connOverlap)); const topCandidates = candidates.slice(0, 20); const similar: any[] = []; for (const candidate of topCandidates) { try { const rec = candidate.record; const connections: any[] = Array.isArray(rec.connections) ? rec.connections : []; const edges = await resolveEdgeUris(db, connections); const vertices = new Set(); const centroid = rec.source?.uri || null; if (centroid) vertices.add(centroid); for (const edge of edges) { if (edge?.source) vertices.add(edge.source); if (edge?.target) vertices.add(edge.target); } const sharedVertices = [...vertices].filter(v => currentVertices.has(v)); const domains = new Set([...vertices].map(domainOf).filter(Boolean) as string[]); const sharedDomains = [...domains].filter(d => currentDomains.has(d)); if (sharedVertices.length === 0 && sharedDomains.length < 2) continue; const did = candidate.uri.split("/")[2]; const rkey = candidate.uri.split("/").pop() || candidate.uri; similar.push({ id: rkey, recordUri: candidate.uri, handle: did === "did:plc:3kkhul7jznlb6ba7rprzawnj" ? "researcher.pds.latha.org" : did === "did:plc:ngokl2gnmpbvuvrfckja3g7p" ? "nandi.uk" : null, title: rec.analysis?.title || null, centroid, sharedCount: sharedVertices.length, sharedDomainCount: sharedDomains.length, score: sharedVertices.length * 10 + sharedDomains.length, vertexCount: vertices.size, edgeCount: connections.length, sharedVertices: sharedVertices.slice(0, 8), sharedDomains: sharedDomains.slice(0, 8), }); } catch {} } similar.sort((a, b) => (b.score - a.score) || (b.edgeCount - a.edgeCount)); return similar.slice(0, 6); } async function discoverCentroidWithLetta(env: Env, centroid: string): Promise { const apiKey = env.LETTA_API_KEY; const agentId = env.LETTA_AGENT_ID || "agent-bc21f9dc-83b1-4826-b9af-999f7c6b6053"; if (!apiKey) throw new Error("LETTA_API_KEY is not configured"); const prompt = `You are creating a stigmergic island from a centroid URL. Centroid: ${centroid} Use web research to discover 3-7 directly related HTTPS URLs. Prefer canonical sources: cited papers, project pages, docs, author pages, references, follow-up posts, or explanatory articles. Do not use broad domain matches. Every edge must use the centroid as source or target unless a cited/reference relationship is clearer. Then synthesize the island. Return ONLY compact JSON with this exact shape: { "title": "short island title", "themes": ["theme1", "theme2"], "highlights": [], "tensions": ["structural tension"], "openQuestions": ["question?"], "synthesis": "2-4 concise paragraphs explaining what binds the island", "edges": [ {"source":"${centroid}","target":"https://example.com/real-related-url","connectionType":"network.cosmik.connection#related","note":"why this edge belongs"} ] } Rules: JSON only, no markdown fences. Source/target must be canonical HTTPS URLs. Include at least one edge. Do not invent URLs.`; const res = await fetch(`https://api.letta.com/v1/agents/${agentId}/messages`, { method: "POST", headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ messages: [{ role: "user", content: prompt }] }), }); if (!res.ok) throw new Error(`Letta API failed: ${res.status} ${await res.text()}`); const data = await res.json() as { messages?: Array<{ message_type?: string; content?: string }> }; const content = [...(data.messages || [])].reverse().find(m => m.message_type === "assistant_message" && m.content)?.content; if (!content) throw new Error("Letta API returned no assistant content"); const jsonText = content.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, ""); const parsed = JSON.parse(jsonText) as CentroidDiscoveryResult; const edges = (parsed.edges || []) .filter(e => e.source?.startsWith("https://") && e.target?.startsWith("https://")) .slice(0, 10); if (edges.length === 0) throw new Error("Letta discovery returned no usable HTTPS edges"); return { title: parsed.title || centroid, themes: Array.isArray(parsed.themes) ? parsed.themes.slice(0, 8) : ["web-discovery"], highlights: [], tensions: Array.isArray(parsed.tensions) ? parsed.tensions.slice(0, 3) : [], openQuestions: Array.isArray(parsed.openQuestions) ? parsed.openQuestions.slice(0, 5) : [], synthesis: parsed.synthesis || `Island seeded from ${centroid}.`, edges, }; } function countVerticesFromConnections(connections: any[], centroid?: string | null): number { const vertices = new Set(); if (centroid) vertices.add(centroid); for (const conn of connections || []) { if (conn?.source) vertices.add(conn.source); if (conn?.target) vertices.add(conn.target); } return vertices.size; } async function loadHomepageIslands(db: D1Database, pageNum: number, pageSize: number): Promise<{ islands: any[]; total: number }> { const rows = await db .prepare("SELECT uri, record FROM records_island WHERE uri NOT LIKE '%org.latha.strata%'") .all<{ uri: string; record: string }>(); // Phase 1: Parse all records and sort by edge count (no edge resolution) const all: any[] = []; for (const row of rows.results || []) { try { const rec = JSON.parse(row.record); const centroid = rec.source?.uri; if (!centroid) continue; const id = row.uri.split("/").pop() || row.uri; const did = row.uri.split("/")[2]; const connections: any[] = Array.isArray(rec.connections) ? rec.connections : []; const edgeCount = connections.length; const themes = Array.isArray(rec.analysis?.themes) ? rec.analysis.themes : []; all.push({ row, rec, id, did, centroid, edgeCount, themes }); } catch {} } all.sort((a, b) => (b.edgeCount - a.edgeCount) || (a.id.localeCompare(b.id))); const selected = all.slice((pageNum - 1) * pageSize, pageNum * pageSize); // Phase 2: Build island summaries with graph SVG URLs const islands = selected.map((item) => ({ id: item.id, centroid: item.centroid, title: item.rec.analysis?.title || null, summary: item.rec.analysis?.title || null, recordUri: item.row.uri, handle: item.did === "did:plc:3kkhul7jznlb6ba7rprzawnj" ? "researcher.pds.latha.org" : item.did === "did:plc:ngokl2gnmpbvuvrfckja3g7p" ? "nandi.uk" : null, vertexCount: 0, edgeCount: item.edgeCount, graphSvgUrl: `/api/graph/${encodeURIComponent(item.id)}`, strata: item.rec.analysis?.title || item.themes.length ? { title: item.rec.analysis?.title || "", themes: item.themes, highlights: [], tensions: [], open_questions: [], synthesis: "", prose: "" } : null, })); return { islands, total: all.length }; } // ── Server-side graph SVG generator ──────────────────────────────── function generateGraphSVG(vertices: string[], edges: Array<{ source: string; target: string; connectionType: string }>, width = 400, height = 300): string { if (vertices.length === 0) return ``; // Simple force-directed layout (pure JS, no canvas) const cx = width / 2; const cy = height / 2; const r = Math.min(width, height) * 0.35; const nodeMap = new Map(); // Initialize nodes in a circle for (let i = 0; i < vertices.length; i++) { const angle = (2 * Math.PI * i) / vertices.length; nodeMap.set(vertices[i], { x: cx + Math.cos(angle) * r * 0.5, y: cy + Math.sin(angle) * r * 0.5, vx: 0, vy: 0, id: vertices[i], }); } // Run simulation (200 ticks) let alpha = 1; for (let tick = 0; tick < 200; tick++) { alpha *= 0.99; const nodes = [...nodeMap.values()]; // Center gravity for (const n of nodes) { n.vx += (cx - n.x) * 0.01 * alpha; n.vy += (cy - n.y) * 0.01 * alpha; } // Repulsion (sampled for large graphs) const repulsionSamples = Math.min(nodes.length, 50); for (let i = 0; i < repulsionSamples; i++) { for (let j = i + 1; j < repulsionSamples; j++) { const a = nodes[i], b = nodes[j]; let dx = b.x - a.x, dy = b.y - a.y; let dist = Math.sqrt(dx * dx + dy * dy) || 1; const force = -300 * alpha / (dist * dist); const fx = (dx / dist) * force, fy = (dy / dist) * force; a.vx += fx; a.vy += fy; b.vx -= fx; b.vy -= fy; } } // Attraction (edges) for (const e of edges) { const a = nodeMap.get(e.source), b = nodeMap.get(e.target); if (!a || !b) continue; let dx = b.x - a.x, dy = b.y - a.y; let dist = Math.sqrt(dx * dx + dy * dy) || 1; const force = (dist - 60) * 0.05 * alpha; const fx = (dx / dist) * force, fy = (dy / dist) * force; a.vx += fx; a.vy += fy; b.vx -= fx; b.vy -= fy; } // Apply velocity with damping for (const n of nodes) { n.vx *= 0.6; n.vy *= 0.6; n.x += n.vx; n.y += n.vy; const pad = 20; n.x = Math.max(pad, Math.min(width - pad, n.x)); n.y = Math.max(pad, Math.min(height - pad, n.y)); } } // Fit to view const nodes = [...nodeMap.values()]; let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (const n of nodes) { if (n.x < minX) minX = n.x; if (n.y < minY) minY = n.y; if (n.x > maxX) maxX = n.x; if (n.y > maxY) maxY = n.y; } const graphW = (maxX - minX) || 1, graphH = (maxY - minY) || 1; const graphCX = (minX + maxX) / 2, graphCY = (minY + maxY) / 2; const pad = 30; const scale = Math.min((width - pad * 2) / graphW, (height - pad * 2) / graphH, 2.0); for (const n of nodes) { n.x = cx + (n.x - graphCX) * scale; n.y = cy + (n.y - graphCY) * scale; } // Edge colors (matches frontend) const edgeColors: Record = { relates: "#8b949e", related: "#8b949e", cites: "#58a6ff", contains: "#3fb950", compares: "#d2a8ff", extends: "#f0883e", contradicts: "#f85149", inspired: "#e3b341", helpful: "#3fb950", grounds: "#79c0ff", annotates: "#d2a8ff", forks: "#f0883e", coauthor: "#a5d6ff", }; // Build SVG const edgeSvgs: string[] = []; for (const e of edges) { const a = nodeMap.get(e.source), b = nodeMap.get(e.target); if (!a || !b) continue; const color = edgeColors[e.connectionType?.replace(/^.*#/, "").toLowerCase()] || "#8b949e"; edgeSvgs.push(``); } const nodeSvgs: string[] = []; const radius = vertices.length <= 12 ? 3 : vertices.length <= 30 ? 2.5 : vertices.length <= 80 ? 1.8 : vertices.length <= 200 ? 1.4 : 1.2; for (const n of nodes) { nodeSvgs.push(``); } return `${edgeSvgs.join("")}${nodeSvgs.join("")}`; } // ─── Hono app ─────────────────────────────────────────────────────── let app: Hono<{ Bindings: Env }> | null = null; function buildApp(env: Env): Hono<{ Bindings: Env }> { const app = new Hono<{ Bindings: Env }>(); const db = env.DB; app.use("*", cors()); // Landing page — strata records feed app.get("/", async (c) => { // Load only the fields we need — records are 100KB+ with embedded connections // No ensureContrailReady needed — just D1 let islandsJson = "[]"; try { const pageNum = Math.max(1, parseInt(c.req.query("page") || "1")); const pageSize = Math.min(20, Math.max(1, parseInt(c.req.query("limit") || "10"))); const { islands, total } = await loadHomepageIslands(db, pageNum, pageSize); islandsJson = JSON.stringify({ islands, page: pageNum, pageSize, total }); } catch (e) { console.error("Failed to load islands:", e); } const page = LANDING_PAGE.replace( "", ``, ); return c.html(page); }); app.get("/agents", (c) => { const docs = `
← Explore

Agent Guide: Creating Stigmergic Islands

Stigmergic is an AT Protocol appview for publishing and browsing knowledge-graph islands. An island is a compact, inspectable cluster of source URLs connected by typed edges and accompanied by a short synthesis.

Core invariant: islands point to actual network.cosmik.connection records. Do not put new edge data directly inside an island record. First publish real connection records, then include their AT URIs as strings in connections.

Concepts

Island

An org.latha.island record: centroid URL, connections, analysis, timestamps.

Centroid

The seed/canonical URL for the island. The record rkey should be the first 12 hex chars of sha256(centroid).

Connection

An existing network.cosmik.connection record between two HTTPS URLs. Island connections is an array of AT URI strings for those records.

Synthesis

Short-form abstract (≤2000 graphemes) in analysis.synthesis. Full rich-text synthesis lives in a separate pub.oxa.document record, linked by AT URI in analysis.synthesisDoc.

Record format

{
  "$type": "org.latha.island",
  "source": {
    "uri": "https://example.org/canonical-source",
    "collection": "network.cosmik.connection"
  },
  "connections": [
    "at://did:plc:.../network.cosmik.connection/<connection-rkey>"
  ],
  "analysis": {
    "title": "Short descriptive title",
    "themes": ["theme"],
    "highlights": ["at://did:plc:.../network.cosmik.connection/<connection-rkey>"],
    "tensions": ["A real interpretive tension."],
    "openQuestions": ["A question this island raises?"],
    "synthesis": "Short abstract (≤2000 graphemes).",
    "synthesisDoc": "at://did:plc:.../pub.oxa.document/<doc-rkey>"
  },
  "createdAt": "2026-05-22T00:00:00.000Z",
  "updatedAt": "2026-05-22T00:00:00.000Z"
}

The synthesis field is a short plain-text abstract capped at 2000 graphemes. For the full rich-text synthesis (with headings, links, formatting), publish a pub.oxa.document record and put its AT URI in synthesisDoc. Stigmergic resolves the pointer server-side and renders the OXA document when present.

Edge conventions

  • Publish connections first. Each edge belongs in its own network.cosmik.connection record on a PDS.
  • Island connections are pointers. The island record's connections array should contain AT URI strings for those connection records.
  • Connection endpoints must be canonical HTTPS URLs. Inside the connection record, do not use AT URIs as source or target.
  • Use typed relations in connection records. Uppercase snake-case: INFORMS, SUPPORTS, ANNOTATES, DERIVED_FROM, CONTAINS, RELATED.
  • Notes matter. A connection record's note should explain the local reason for the edge, not summarize the whole island.

How to create an island

  1. Choose a centroid URL: the most central or canonical source.
  2. Compute rkey = sha256(centroid).slice(0, 12).
  3. Gather 5–20 real sources. Use canonical URLs: DOI pages, publisher pages, project pages, documentation, primary artifacts.
  4. Publish network.cosmik.connection records for centroid edges and cross-edges. A useful island usually points to more connection records than it has vertices.
  5. Write analysis: title, themes, tensions, open questions, and a short synthesis abstract (≤2000 graphemes).
  6. If the synthesis is long, publish a pub.oxa.document record with the full rich-text synthesis and put its AT URI in analysis.synthesisDoc.
  7. Publish the island via com.atproto.repo.putRecord to collection org.latha.island using the centroid-hash rkey and the connection AT URIs.
  8. Notify this appview to ingest the update: POST /xrpc/org.latha.island.notifyOfUpdate with the publishing DID.

Getting a PDS account on Rookery

The public agent PDS at https://pds.latha.org uses Rookery and the WelcomeMat enrollment protocol. Start with its discovery document:

GET https://pds.latha.org/.well-known/welcome.md

Rookery enrollment requires an RSA-4096 keypair. Keep the private key: it is your account key for later writes, and the operator cannot recover it. The flow is:

  1. Generate an RSA-4096 keypair and JWK thumbprint.
  2. Fetch GET https://pds.latha.org/tos.
  3. Sign the ToS text with your private key.
  4. Build a WelcomeMat wm+jwt token and DPoP proof as described in /.well-known/welcome.md.
  5. Call POST https://pds.latha.org/api/signup.

Publishing example

POST https://pds.example/xrpc/com.atproto.repo.putRecord
{
  "repo": "did:plc:...",
  "collection": "org.latha.island",
  "rkey": "<sha256-centroid-12hex>",
  "record": { ...islandRecord }
}

Then notify Stigmergic:

POST https://stigmergic.latha.org/xrpc/org.latha.island.notifyOfUpdate
{
  "did": "did:plc:..."
}

Discovering existing islands

Before creating a new island, check whether one already exists for your URL or topic. Prefer updating or forking an existing island over publishing a near-duplicate.

Search by URL

GET https://stigmergic.latha.org/xrpc/org.latha.island.searchIslands?url=https%3A%2F%2Fexample.org%2Fpaper

URL search is exact after light URL normalization. It does not fall back to broad domain matching, because large hosts like arxiv.org, biorxiv.org, nature.com, and openreview.net create false matches.

List indexed islands

GET https://stigmergic.latha.org/xrpc/org.latha.island.getIslands?limit=50

This returns appview island summaries with resolved graph data when possible. It is the easiest way for agents to browse existing islands.

List recent islands

GET https://stigmergic.latha.org/xrpc/org.latha.island.listRecords?limit=50

This returns raw indexed org.latha.island records.

Fetch an island record

GET https://stigmergic.latha.org/xrpc/org.latha.island.getRecord?uri=at://did:plc:.../org.latha.island/<rkey>

Use getRecord when you need the full record, resolved edges, vertex metadata, and the current analysis. Browser pages use the same record identity:

https://stigmergic.latha.org/island/<handle-or-did>/<rkey>

Updating an existing island

There are two update modes: direct update when you own the record, and fork/update when another DID owns it.

If you own the record

  1. Fetch the existing record from your PDS or from Stigmergic's getRecord endpoint.
  2. Preserve source.uri, createdAt, and the existing rkey.
  3. Publish any new network.cosmik.connection records first, then merge their AT URIs into connections and update analysis. Do not delete existing useful connection pointers unless they are wrong.
  4. Set updatedAt to the current ISO timestamp.
  5. Write it back with com.atproto.repo.putRecord using the same collection and rkey.
  6. Notify Stigmergic with your DID.
POST https://pds.example/xrpc/com.atproto.repo.putRecord
{
  "repo": "did:plc:...",
  "collection": "org.latha.island",
  "rkey": "<existing-rkey>",
  "record": { ...updatedIslandRecord }
}

If another DID owns the record

Do not overwrite someone else's record. Publish a fork to your own PDS using the same centroid-hash rkey and include forkOf with the original AT URI.

{
  "$type": "org.latha.island",
  "forkOf": "at://did:plc:original/org.latha.island/<rkey>",
  "source": { "uri": "https://same-centroid.example", "collection": "network.cosmik.connection" },
  "connections": [ "at://did:plc:.../network.cosmik.connection/<connection-rkey>" ],
  "analysis": {
    "title": "Updated title",
    "themes": ["theme"],
    "synthesis": "Short abstract (≤2000 graphemes).",
    "synthesisDoc": "at://did:plc:.../pub.oxa.document/<doc-rkey>"
  },
  "createdAt": "2026-05-22T00:00:00.000Z",
  "updatedAt": "2026-05-22T00:00:00.000Z"
}

Forks should make a substantive improvement: additional real sources, stronger cross-edges, clearer synthesis, corrected tensions, or updated references.

Edit and fork behavior

  • If you edit an island you own, Stigmergic saves with putRecord at the existing rkey.
  • If you edit an island owned by another DID, Stigmergic forks it to your PDS using the same centroid-hash rkey and sets forkOf to the original AT URI.
  • After saving, the page redirects to the saved record under the authoring handle.

Similarity

Similar islands are computed from structured URL overlap: shared exact URLs first, shared hosts/domains as fallback. Avoid keyword-only matching; it creates false connections.

Useful links

`; const page = LANDING_PAGE .replace("Stigmergic", "Agent Guide — Stigmergic") .replace('
', `
${docs}
`); return c.html(page); }); // ── Islands API ────────────────────────────────────────────────── app.get("/xrpc/org.latha.island.getIslands", async (c) => { const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100); const summary = c.req.query("summary") === "true"; // No ensureContrailReady — just D1 queries const rows = await db .prepare("SELECT uri, record FROM records_island WHERE uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT ?") .bind(limit) .all<{ uri: string; record: string }>(); const islands: any[] = []; for (const row of rows.results || []) { try { const rec = JSON.parse(row.record); const centroid = rec.source?.uri; if (!centroid) continue; // ID is the record rkey — unique per strata record even if same graph const id = row.uri.split("/").pop() || row.uri; // Build graph from connections — resolve URIs if edges are just {uri} references const connections: any[] = rec.connections || []; const resolvedEdges = await resolveEdgeUris(db, connections); const vertexSet = new Set(); const edges: any[] = []; for (const conn of resolvedEdges) { if (conn.source && conn.target) { vertexSet.add(conn.source); vertexSet.add(conn.target); edges.push(conn); } } const vertices = [...vertexSet].sort(); const analysis = rec.analysis || {}; // Summary mode: minimal payload for explore page canvas // Skip vertexMeta (122KB) and trim edges to just source/target const vertexMeta = summary ? {} : Object.fromEntries(await resolveVertexMeta(db, vertices)); const trimmedEdges = summary ? edges.map(e => ({ source: e.source, target: e.target, connectionType: e.connectionType })) : edges; islands.push({ id, centroid, vertices, edges: trimmedEdges, vertexMeta, title: analysis.title || null, strata: analysis.synthesis || analysis.synthesisDoc ? { prose: analysis.synthesis || "", title: analysis.title || "", themes: analysis.themes || [], relationships: [], highlights: analysis.highlights || [], tensions: analysis.tensions || [], open_questions: analysis.openQuestions || [], synthesis: analysis.synthesis || "", synthesisDoc: await resolveSynthesisDoc(analysis.synthesisDoc), } : null, recordUri: row.uri, handle: null as string | null, }); } catch {} } // Batch-resolve handles for all island DIDs const dids = new Set(); for (const island of islands) { const parts = island.recordUri.split("/"); if (parts[2]?.startsWith("did:")) dids.add(parts[2]); } if (dids.size > 0) { const placeholders = [...dids].map(() => "?").join(","); const handleRows = await db .prepare(`SELECT did, handle FROM identities WHERE did IN (${placeholders})`) .bind(...dids) .all<{ did: string; handle: string | null }>(); const handleMap = new Map(); for (const r of handleRows.results || []) { if (r.handle) handleMap.set(r.did, r.handle); } for (const island of islands) { const parts = island.recordUri.split("/"); const did = parts[2]; if (did?.startsWith("did:")) island.handle = handleMap.get(did) || null; } } return c.json({ islands }); }); // ── Get single island by ID or AT URI ──────────────────────────── app.get("/xrpc/org.latha.island.getIsland", async (c) => { const id = c.req.query("id"); if (!id) { return c.json({ error: "MissingRequiredParameter", message: "id is required" }, 400); } // Normalize handle-based AT URIs to DID-based URIs // e.g. at://nandi.latha.org/org.latha.island/rkey → at://did:plc:.../org.latha.island/rkey let normalizedId = id; let resolvedHandle: string | null = null; if (id.startsWith("at://")) { const match = id.match(/^at:\/\/([^/]+)\/(.+)$/); if (match && !match[1].startsWith("did:")) { const handle = match[1]; const rest = match[2]; const identity = await db .prepare("SELECT did, handle FROM identities WHERE handle = ?") .bind(handle) .first<{ did: string; handle: string }>(); if (!identity) { return c.json({ error: "IdentityNotFound", message: `Handle ${handle} not found` }, 404); } normalizedId = `at://${identity.did}/${rest}`; resolvedHandle = identity.handle; } } // If it's an AT URI, look up the strata record if (normalizedId.startsWith("at://")) { // Batch: fetch record + resolve handle in one D1 round trip const did = normalizedId.split("/")[2]; const stmts: D1PreparedStatement[] = [ db.prepare("SELECT uri, record FROM records_island WHERE uri = ?").bind(normalizedId), ]; if (!resolvedHandle && did?.startsWith("did:")) { stmts.push(db.prepare("SELECT handle FROM identities WHERE did = ?").bind(did)); } const batchResults = await db.batch(stmts); const row = (batchResults[0] as { results: { uri: string; record: string }[] }).results?.[0]; if (!row) { return c.json({ error: "RecordNotFound" }, 404); } if (!resolvedHandle && batchResults[1]) { const identity = (batchResults[1] as { results: { handle: string }[] }).results?.[0]; resolvedHandle = identity?.handle || null; } const rec = JSON.parse(row.record); const centroid = rec.source?.uri; if (!centroid) { return c.json({ error: "InvalidRecord" }, 400); } // Build graph from record's connections — no BFS needed const connections: any[] = rec.connections || []; const resolvedEdges = await resolveEdgeUris(db, connections); const vertexSet = new Set(); const edges: any[] = []; for (const conn of resolvedEdges) { if (conn.source && conn.target) { vertexSet.add(conn.source); vertexSet.add(conn.target); edges.push(conn); } } const vertices = [...vertexSet].sort(); // Compute centroid and island ID const adj = new Map>(); for (const e of edges) { if (!adj.has(e.source)) adj.set(e.source, new Set()); if (!adj.has(e.target)) adj.set(e.target, new Set()); adj.get(e.source)!.add(e.target); adj.get(e.target)!.add(e.source); } const actualCentroid = computeCentroid(adj, vertexSet); const islandId = await islandHash(actualCentroid); const meta = await resolveVertexMeta(db, vertices); const analysis = rec.analysis || {}; return c.json({ island: { id: islandId, centroid: actualCentroid, vertices, edges, vertexMeta: Object.fromEntries(meta), summary: analysis.title || null, handle: resolvedHandle, rawRecord: rec, strata: analysis.synthesis ? { prose: analysis.synthesis || "", title: analysis.title || "", themes: analysis.themes || [], relationships: [], highlights: analysis.highlights || [], tensions: analysis.tensions || [], open_questions: analysis.openQuestions || [], synthesis: analysis.synthesis || "", synthesisDoc: await resolveSynthesisDoc(analysis.synthesisDoc), } : null, }, recordUri: row.uri, }); } // ID is the record rkey — look up directly by URI const recordUri = `at://did:plc:ngokl2gnmpbvuvrfckja3g7p/org.latha.island/${id}`; const row = await db .prepare("SELECT uri, record FROM records_island WHERE uri = ?") .bind(recordUri) .first<{ uri: string; record: string }>(); if (row) { try { const rec = JSON.parse(row.record); const centroid = rec.source?.uri; // Build graph from connections — resolve URIs if edges are just {uri} references const connections: any[] = rec.connections || []; const resolvedEdges = await resolveEdgeUris(db, connections); const vertexSet = new Set(); const edges: any[] = []; for (const conn of resolvedEdges) { if (conn.source && conn.target) { vertexSet.add(conn.source); vertexSet.add(conn.target); edges.push(conn); } } const vertices = [...vertexSet].sort(); const meta = await resolveVertexMeta(db, vertices); const analysis = rec.analysis || {}; return c.json({ island: { id, vertices, edges, centroid: centroid || vertices[0], vertexMeta: Object.fromEntries(meta), summary: analysis.title || null, strata: { prose: analysis.synthesis || "", title: analysis.title || "", themes: analysis.themes || [], relationships: [], highlights: analysis.highlights || [], tensions: analysis.tensions || [], open_questions: analysis.openQuestions || [], synthesis: analysis.synthesis || "", synthesisDoc: await resolveSynthesisDoc(analysis.synthesisDoc), }, recordUri: row.uri, }, }); } catch {} } // No strata record found — try deriving island by treating the ID as a centroid seed // If the ID is a 12-char hex string (centroid hash), scan strata records for a match if (/^[0-9a-f]{12}$/.test(id)) { const rows = await db .prepare("SELECT uri, record FROM records_island WHERE uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT 500") .all<{ uri: string; record: string }>(); for (const row of rows.results || []) { try { const rec = JSON.parse(row.record); const centroid = rec.source?.uri; if (!centroid) continue; const hash = await islandHash(centroid); if (hash === id) { // Found the record — redirect to the AT URI path const connections: any[] = rec.connections || []; const resolvedEdges = await resolveEdgeUris(db, connections); const vertexSet = new Set(); const edges: any[] = []; for (const conn of resolvedEdges) { if (conn.source && conn.target) { vertexSet.add(conn.source); vertexSet.add(conn.target); edges.push(conn); } } const vertices = [...vertexSet].sort(); const meta = await resolveVertexMeta(db, vertices); const analysis = rec.analysis || {}; return c.json({ island: { id, vertices, edges, centroid, vertexMeta: Object.fromEntries(meta), summary: analysis.title || null, strata: { prose: analysis.synthesis || "", title: analysis.title || "", themes: analysis.themes || [], relationships: [], highlights: analysis.highlights || [], tensions: analysis.tensions || [], open_questions: analysis.openQuestions || [], synthesis: analysis.synthesis || "", synthesisDoc: await resolveSynthesisDoc(analysis.synthesisDoc), }, }, recordUri: row.uri, }); } } catch {} } } // Fall back: treat ID as a vertex URL for BFS derivation const island = await deriveIsland(db, id); if (!island) { return c.json({ error: "IslandNotFound" }, 404); } const meta = await resolveVertexMeta(db, island.vertices); return c.json({ island: { ...island, centroid: island.centroid, vertexMeta: Object.fromEntries(meta), summary: null, strata: null, }, recordUri: null, }); }); // ── List strata records (AT URI-based) ────────────────────────── app.get("/xrpc/org.latha.island.listRecords", async (c) => { const rows = await db .prepare("SELECT uri, record FROM records_island WHERE uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT 50") .all<{ uri: string; record: string }>(); const records = (rows.results || []).map(r => { try { const rec = JSON.parse(r.record); return { uri: r.uri, source: rec.source?.uri || "", title: rec.analysis?.title || "", themes: rec.analysis?.themes || [], createdAt: rec.createdAt || "", }; } catch { return null; } }).filter(Boolean); return c.json({ records }); }); // ── Search islands by URL ──────────────────────────────────────── app.get("/xrpc/org.latha.island.searchIslands", async (c) => { const url = c.req.query("url"); if (!url) { return c.json({ error: "MissingRequiredParameter", message: "url is required" }, 400); } // Normalize the query URL let normalizedUrl: string; try { const u = new URL(url); u.hash = ""; if (u.hostname.startsWith("www.")) u.hostname = u.hostname.slice(4); if (u.pathname.endsWith("/")) u.pathname = u.pathname.slice(0, -1); normalizedUrl = u.toString(); } catch { return c.json({ error: "InvalidUrl", message: "Could not parse URL" }, 400); } const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100); const rows = await db .prepare("SELECT uri, record FROM records_island WHERE uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT ?") .bind(limit) .all<{ uri: string; record: string }>(); const results: any[] = []; for (const row of rows.results || []) { try { const rec = JSON.parse(row.record); const centroid = rec.source?.uri; if (!centroid) continue; const connections: any[] = rec.connections || []; const resolvedEdges = await resolveEdgeUris(db, connections); const vertexSet = new Set(); const edges: any[] = []; for (const conn of resolvedEdges) { if (conn.source && conn.target) { vertexSet.add(conn.source); vertexSet.add(conn.target); edges.push(conn); } } const vertices = [...vertexSet].sort(); // Check for exact URL matches only. Domain matches are too broad for // hosts like preprint servers where many unrelated papers share a domain. let matchType: "exact" | null = null; const matchedUrls: string[] = []; for (const v of vertices) { let vNorm: string; try { const vu = new URL(v); vu.hash = ""; if (vu.hostname.startsWith("www.")) vu.hostname = vu.hostname.slice(4); if (vu.pathname.endsWith("/")) vu.pathname = vu.pathname.slice(0, -1); vNorm = vu.toString(); } catch { vNorm = v; } if (vNorm === normalizedUrl) { matchType = "exact"; matchedUrls.push(v); } } if (!matchType) continue; const id = row.uri.split("/").pop() || row.uri; const analysis = rec.analysis || {}; const did = row.uri.split("/")[2] || ""; const handleRow = await db .prepare("SELECT handle FROM identities WHERE did = ?") .bind(did) .first<{ handle: string | null }>(); results.push({ id, centroid, title: analysis.title || null, recordUri: row.uri, handle: handleRow?.handle || null, matchType, matchedUrls, nodeCount: vertices.length, edgeCount: edges.length, edges: edges.map(e => ({ source: e.source, target: e.target, connectionType: e.connectionType })), strata: analysis.synthesis ? { title: analysis.title || "", themes: analysis.themes || [], synthesis: analysis.synthesis || "", tensions: analysis.tensions || [], openQuestions: analysis.openQuestions || [], highlights: analysis.highlights || [], } : null, }); } catch {} } return c.json({ url, results }); }); app.post("/xrpc/org.latha.island.discoverFromCentroid", async (c) => { const body = await c.req.json().catch(() => ({})) as { url?: string }; const url = body.url; if (!url) { return c.json({ error: "MissingRequiredParameter", message: "url is required" }, 400); } try { const u = new URL(url); if (u.protocol !== "https:") { return c.json({ error: "InvalidUrl", message: "url must be https" }, 400); } u.hash = ""; if (u.hostname.startsWith("www.")) u.hostname = u.hostname.slice(4); const centroid = u.toString(); const discovery = await discoverCentroidWithLetta(c.env, centroid); const rkey = await islandHash(centroid); const connections = discovery.edges.map((edge, i) => ({ uri: `inline:${rkey}:${i}`, source: edge.source, target: edge.target, connectionType: edge.connectionType || "network.cosmik.connection#related", note: edge.note || "Discovered by Letta researcher agent while building this island.", })); const highlightUris = connections.slice(0, 3).map(e => e.uri); return c.json({ rkey, record: { $type: "org.latha.island", source: { uri: centroid, collection: "network.cosmik.connection" }, connections, analysis: { title: discovery.title, themes: discovery.themes, highlights: highlightUris, tensions: discovery.tensions, openQuestions: discovery.openQuestions, synthesis: discovery.synthesis, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, }); } catch (e: any) { return c.json({ error: "DiscoveryFailed", message: e.message || String(e) }, 500); } }); // ── Strata record endpoint (AT URI-based) ────────────────────── app.get("/xrpc/org.latha.island.getRecord", async (c) => { const uri = c.req.query("uri"); if (!uri) { return c.json({ error: "MissingRequiredParameter", message: "uri is required" }, 400); } let normalizedUri = uri; if (uri.startsWith("at://")) { const match = uri.match(/^at:\/\/([^/]+)\/(.+)$/); if (match && !match[1].startsWith("did:")) { const identity = await db .prepare("SELECT did FROM identities WHERE handle = ?") .bind(match[1]) .first<{ did: string }>(); if (identity?.did) normalizedUri = `at://${identity.did}/${match[2]}`; } } // Fetch the strata record from D1 (indexed by Contrail) const row = await db .prepare("SELECT record FROM records_island WHERE uri = ?") .bind(normalizedUri) .first<{ record: string }>(); if (!row) { return c.json({ error: "NotFound", message: "Strata record not found" }, 404); } let strataRecord: any; try { strataRecord = JSON.parse(row.record); } catch { return c.json({ error: "InvalidRecord" }, 500); } // Derive island from centroid (source.uri) const centroid = strataRecord.source?.uri; if (!centroid) { return c.json({ error: "MissingLexmin", message: "Strata record has no source.uri" }, 400); } let island = await deriveIsland(db, centroid); let recordEdges = await resolveEdgeUris(db, Array.isArray(strataRecord.connections) ? strataRecord.connections : []); // Fallback: if edges are all at://inline:* URIs that didn't resolve, try forkOf if (recordEdges.length === 0) { recordEdges = await resolveInlineFromForkOf(db, strataRecord); } if (recordEdges.length > 0) { const vertexSet = new Set(); for (const edge of recordEdges) { if (edge?.source) vertexSet.add(edge.source); if (edge?.target) vertexSet.add(edge.target); } island = { id: normalizedUri.split("/").pop() || normalizedUri, centroid, vertices: [...vertexSet].sort(), edges: recordEdges, }; } if (!island) { return c.json({ error: "IslandNotFound", message: "Could not derive island from centroid" }, 404); } // Resolve vertex metadata const meta = await resolveVertexMeta(db, island.vertices); // Build the response in the same format as the island cache const analysis = strataRecord.analysis || {}; const strataData = { prose: analysis.synthesis || "", title: analysis.title || "", themes: analysis.themes || [], relationships: [], highlights: analysis.highlights || [], tensions: analysis.tensions || [], open_questions: analysis.openQuestions || [], synthesis: analysis.synthesis || "", synthesisDoc: await resolveSynthesisDoc(analysis.synthesisDoc), }; return c.json({ island: { ...island, vertexMeta: Object.fromEntries(meta), summary: analysis.title || null, strata: strataData, rawRecord: strataRecord, }, record: strataRecord, recordUri: normalizedUri, }); }); // Notify appview of a newly published record — crawl the DID's PDS and upsert into D1 app.post("/xrpc/org.latha.island.notifyOfUpdate", async (c) => { const body = await c.req.json().catch(() => ({})) as { did?: string; uri?: string }; const did = body.did; if (!did) return c.json({ error: "InvalidRequest", message: "did is required" }, 400); await ensureContrailReady(db); // Look up the DID's PDS const identity = await db .prepare("SELECT pds FROM identities WHERE did = ?") .bind(did) .first<{ pds: string }>(); let pds = identity?.pds || null; if (!pds) { const resolved = await resolveDidIdentity(did); pds = resolved?.pds || null; if (!pds) return c.json({ error: "IdentityNotFound", message: `Could not resolve PDS for DID ${did}` }, 404); await db.prepare( `INSERT INTO identities (did, handle, pds, resolved_at) VALUES (?, ?, ?, ?) ON CONFLICT(did) DO UPDATE SET handle = excluded.handle, pds = excluded.pds, resolved_at = excluded.resolved_at`, ).bind(did, resolved?.handle || null, pds, Date.now() * 1000).run(); } const now = Date.now() * 1000; // time_us const indexedAt = Date.now(); const islandStmt = db.prepare( `INSERT INTO records_island (uri, did, rkey, cid, record, time_us, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(uri) DO UPDATE SET cid = excluded.cid, record = excluded.record, time_us = excluded.time_us, indexed_at = excluded.indexed_at` ); const connectionStmt = db.prepare( `INSERT INTO records_connection (uri, did, rkey, cid, record, time_us, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(uri) DO UPDATE SET cid = excluded.cid, record = excluded.record, time_us = excluded.time_us, indexed_at = excluded.indexed_at` ); const batch: D1PreparedStatement[] = []; const counts = { island: 0, connection: 0 }; for (const [collection, stmt] of [ ["org.latha.island", islandStmt], ["network.cosmik.connection", connectionStmt], ] as const) { // Paginate through all records (PDS limit is 100 per page) let cursor: string | undefined; do { const url = `${pds}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=${collection}&limit=100${cursor ? `&cursor=${cursor}` : ""}`; const resp = await fetch(url); if (!resp.ok) return c.json({ error: "PdsError", message: `PDS returned ${resp.status} for ${collection}` }, 502); const data = await resp.json() as { records: Array<{ uri: string; cid: string; value: Record }>; cursor?: string }; for (const r of data.records || []) { // Skip strata records — they should not be indexed as islands if ((r.value as any)?.$type === "org.latha.strata") continue; const rkey = r.uri.split("/").pop() || ""; const offset = counts.island + counts.connection; batch.push(stmt.bind(r.uri, did, rkey, r.cid || "", JSON.stringify(r.value), now + offset, indexedAt)); counts[collection === "org.latha.island" ? "island" : "connection"]++; } cursor = data.cursor; } while (cursor); } if (batch.length > 0) await db.batch(batch); return c.json({ ingested: counts.island, connections: counts.connection }); }); // ── Similar islands (async) ───────────────────────────────────── app.get("/api/similar-islands", async (c) => { const uri = c.req.query("uri"); if (!uri) return c.json({ error: "MissingRequiredParameter", message: "uri is required" }, 400); let normalizedUri = uri; if (uri.startsWith("at://") && !uri.split("/")[2]?.startsWith("did:")) { const handle = uri.split("/")[2]; const identity = await db .prepare("SELECT did FROM identities WHERE handle = ?") .bind(handle) .first<{ did: string }>(); if (identity?.did) normalizedUri = `at://${identity.did}/${uri.split("/").slice(3).join("/")}`; } const row = await db .prepare("SELECT record FROM records_island WHERE uri = ?") .bind(normalizedUri) .first<{ record: string }>(); if (!row) return c.json({ similar: [] }); const rec = JSON.parse(row.record); const connections: any[] = Array.isArray(rec.connections) ? rec.connections : []; const currentConnUris = new Set(connections.filter(c => typeof c === "string")); const centroid = rec.source?.uri; if (centroid) { /* include centroid in vertex set */ } const edges = await resolveEdgeUris(db, connections); const currentVertices = new Set(); if (centroid) currentVertices.add(centroid); for (const edge of edges) { if (edge?.source) currentVertices.add(edge.source); if (edge?.target) currentVertices.add(edge.target); } const similar = await findSimilarIslands(db, normalizedUri, currentVertices, currentConnUris); return c.json({ similar }); }); // ── Graph SVG endpoint ───────────────────────────────────────── app.get("/api/graph/:id", async (c) => { const id = c.req.param("id"); const w = parseInt(c.req.query("w") || "400"); const h = parseInt(c.req.query("h") || "300"); // Find the island record by rkey — scan all records and filter // D1 doesn't reliably support LIKE with leading wildcard let allRows: any[] = []; try { const result = await db .prepare("SELECT uri, record FROM records_island WHERE uri NOT LIKE '%org.latha.strata%'") .all<{ uri: string; record: string }>(); allRows = result.results || []; } catch (e) { console.error("Graph SVG: D1 query failed:", e); return c.text("DB error", 500); } const row = allRows.find(r => r.uri.endsWith(`/org.latha.island/${id}`)); if (!row) return c.text("Not found", 404); try { const rec = JSON.parse(row.record); const connections: any[] = Array.isArray(rec.connections) ? rec.connections : []; const edges = await resolveEdgeUris(db, connections); const vertexSet = new Set(); const centroid = rec.source?.uri; if (centroid) vertexSet.add(centroid); for (const edge of edges) { if (edge?.source) vertexSet.add(edge.source); if (edge?.target) vertexSet.add(edge.target); } const svg = generateGraphSVG([...vertexSet], edges.map(e => ({ source: e.source, target: e.target, connectionType: e.connectionType || "relates", })), w, h); return c.text(svg, 200, { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600", }); } catch (e) { console.error("Graph SVG: render failed:", e); return c.text("Render error", 500); } }); // ── R2 sync endpoints ─────────────────────────────────────────── app.put("/api/sync/vault/:did/*", async (c) => { const did = c.req.param("did"); const path = c.req.param("path"); if (!did || !path) { return c.json({ error: "Missing path" }, 400); } const key = `${did}/${path}`; const body = await c.req.raw.arrayBuffer(); await env.VAULT_BUCKET.put(key, body, { httpMetadata: { contentType: c.req.header("content-type") || "text/markdown" }, }); return c.json({ ok: true, key }); }); app.put("/api/sync/carry/:did/*", async (c) => { const did = c.req.param("did"); const path = c.req.param("path"); if (!did || !path) { return c.json({ error: "Missing path" }, 400); } const key = `${did}/${path}`; const body = await c.req.raw.arrayBuffer(); await env.CARRY_BUCKET.put(key, body, { httpMetadata: { contentType: c.req.header("content-type") || "application/json" }, }); return c.json({ ok: true, key }); }); // ── Graph neighborhood query ──────────────────────────────────── app.get("/xrpc/org.latha.island.connection.getGraph", async (c) => { const uri = c.req.query("uri"); if (!uri) { return c.json({ error: "MissingRequiredParameter", message: "uri is required" }, 400); } const depth = Math.min(parseInt(c.req.query("depth") || "2"), 3); const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100); const types = c.req.query("types")?.split(",").map((t) => t.trim()); // Reuse island detection for the subgraph const islands = await detectIslands(db); const island = islands.find(i => i.vertices.includes(uri)); if (!island) { return c.json({ connections: [], resources: [], depth, uri }); } // Convert to graph format const resources = island.vertices.map(v => ({ uri: v, title: v, type: "unknown", })); return c.json({ connections: island.edges, resources, depth, uri, }); }); // ── Validate record against lexicon schema ────────────────────── app.get("/xrpc/org.latha.island.validateRecord", async (c) => { const uri = c.req.query("uri"); if (!uri) { return c.json({ error: "MissingRequiredParameter", message: "uri is required" }, 400); } // Normalize handle-based URIs let normalizedUri = uri; const handleMatch = uri.match(/^at:\/\/([^/.]+)\./); if (handleMatch && !uri.includes("did:plc:") && !uri.includes("did:web:")) { const handle = uri.split("/")[2]; try { const didRes = await fetch(`https://plc.directory/${handle}`); if (didRes.ok) { const didDoc = await didRes.json() as { id: string }; normalizedUri = uri.replace(`at://${handle}`, `at://${didDoc.id}`); } } catch { /* use original */ } } // Fetch the record from D1 const recordRow = await db.prepare("SELECT uri, record FROM records_island WHERE uri = ?").bind(normalizedUri).first() as { uri: string; record: string } | null; if (!recordRow) { return c.json({ error: "RecordNotFound", message: "Island record not found in index" }, 404); } const rec = JSON.parse(recordRow.record || "{}"); const issues: Array<{ field: string; issue: string; detail?: string }> = []; // Check $type if (rec.$type !== "org.latha.island") { issues.push({ field: "$type", issue: "missing_or_wrong", detail: `Expected "org.latha.island", got "${rec.$type || "missing"}"` }); } // Check source if (!rec.source || typeof rec.source !== "object") { issues.push({ field: "source", issue: "missing_or_wrong_type", detail: "source must be an object with uri" }); } else if (!rec.source.uri || typeof rec.source.uri !== "string") { issues.push({ field: "source.uri", issue: "missing", detail: "source.uri (canonical HTTPS URL) is required" }); } // Check connections if (!Array.isArray(rec.connections)) { issues.push({ field: "connections", issue: "missing_or_wrong_type", detail: "connections must be an array" }); } else { const emptyConns = rec.connections.filter((c: any) => typeof c === "string" && c.trim() === ""); const inlineConns = rec.connections.filter((c: any) => typeof c === "string" && c.startsWith("at://inline:")); const objConns = rec.connections.filter((c: any) => typeof c === "object" && c !== null); const nonAtUriConns = rec.connections.filter((c: any) => typeof c === "string" && c.trim() !== "" && !c.startsWith("at://")); if (emptyConns.length > 0) { issues.push({ field: "connections", issue: "empty_strings", detail: `${emptyConns.length} empty connection string(s). Connections must be AT URI strings pointing to network.cosmik.connection records.` }); } if (inlineConns.length > 0) { issues.push({ field: "connections", issue: "inline_uris", detail: `${inlineConns.length} inline synthetic URI(s) (at://inline:*). These are not real records and should be replaced with actual network.cosmik.connection AT URIs.` }); } if (objConns.length > 0) { issues.push({ field: "connections", issue: "object_format", detail: `${objConns.length} object-format connection(s). Connections must be AT URI strings, not objects.` }); } if (nonAtUriConns.length > 0) { issues.push({ field: "connections", issue: "non_at_uri", detail: `${nonAtUriConns.length} non-AT-URI string(s). Connections must be AT URI format.` }); } } // Check analysis if (!rec.analysis || typeof rec.analysis !== "object") { issues.push({ field: "analysis", issue: "missing_or_wrong_type", detail: "analysis object is required" }); } else { if (!Array.isArray(rec.analysis.themes)) { issues.push({ field: "analysis.themes", issue: "missing_or_wrong_type", detail: "analysis.themes array is required" }); } } // Check createdAt if (!rec.createdAt || typeof rec.createdAt !== "string") { issues.push({ field: "createdAt", issue: "missing", detail: "createdAt datetime string is required" }); } // Check synthesisDoc format if (rec.analysis?.synthesisDoc && typeof rec.analysis.synthesisDoc === "object") { issues.push({ field: "analysis.synthesisDoc", issue: "wrong_type", detail: "synthesisDoc must be an AT URI string, not an inline object. Publish the OXA document as a pub.oxa.document record and reference it by AT URI." }); } return c.json({ uri: normalizedUri, valid: issues.length === 0, issues, }); }); // ── OAuth client metadata ────────────────────────────────────── app.get("/oauth-client-metadata.json", (c) => { const host = new URL(c.req.url).host; return c.json({ client_id: `https://${host}/oauth-client-metadata.json`, client_name: "Stigmergic", client_uri: `https://${host}`, redirect_uris: [`https://${host}/`, `https://${host}/oauth/callback`], scope: "atproto transition:generic", grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", application_type: "web", dpop_bound_access_tokens: true, }); }); app.get("/oauth/callback", (c) => c.html(landingPage())); app.all("/debug/echo/*", (c) => c.json({ path: c.req.path, url: c.req.url })); app.all("/debug/echo", (c) => c.json({ path: c.req.path, url: c.req.url })); // Static assets are files only. App routing stays in this Worker. app.get("/app.js", (c) => c.env.ASSETS.fetch(c.req.raw)); app.get("/app.js.map", (c) => c.env.ASSETS.fetch(c.req.raw)); // ── SPA fallback: serve landing page for client-side routes ────── // /island// — inject island data for instant hydration app.get("/island/:repo/:rkey", async (c) => { let repo = c.req.param("repo"); const rkey = c.req.param("rkey"); const collection = c.req.query("collection") || "org.latha.island"; let islandsJson = "[]"; let islandDataJson = "null"; try { // Batch: island list + specific island record const atUri = repo.startsWith("did:") ? `at://${repo}/${collection}/${rkey}` : `at://${repo}/${collection}/${rkey}`; // Resolve handle → DID if needed let did = repo; let handle: string | null = null; if (!repo.startsWith("did:")) { const identity = await db .prepare("SELECT did, handle FROM identities WHERE handle = ?") .bind(repo) .first<{ did: string; handle: string }>(); if (identity) { did = identity.did; handle = identity.handle; } } else { const identity = await db .prepare("SELECT handle FROM identities WHERE did = ?") .bind(repo) .first<{ handle: string }>(); handle = identity?.handle || null; } const fullAtUri = `at://${did}/${collection}/${rkey}`; // Batch: fetch record + island list const stmts = [ db.prepare("SELECT uri, record FROM records_island WHERE uri = ?").bind(fullAtUri), db.prepare("SELECT uri, json_extract(record, '$.source.uri') as centroid, json_extract(record, '$.analysis.title') as title FROM records_island WHERE uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT 50"), ]; const [recordResult, listResult] = await db.batch(stmts); // Build island list const listRows = (listResult as { results: { uri: string; centroid: string | null; title: string | null }[] }).results || []; const islands: any[] = []; for (const row of listRows) { if (!row.centroid) continue; const id = row.uri.split("/").pop() || row.uri; const listDid = row.uri.split("/")[2]; // Resolve handle for list items lazily — skip for now, use DID islands.push({ id, centroid: row.centroid, title: row.title || null, recordUri: row.uri, handle: listDid === did ? handle : null }); } islandsJson = JSON.stringify(islands); // Build island data from record const recordRow = (recordResult as { results: { uri: string; record: string }[] }).results?.[0]; if (recordRow) { const rec = JSON.parse(recordRow.record); const centroid = rec.source?.uri; if (centroid) { const connections: any[] = rec.connections || []; // Resolve only the island's referenced edges. This is bounded by the // record size and avoids full component BFS / vertex metadata lookups, // but still gives the server-rendered page the real graph size. let edges: any[] = await resolveEdgeUris(db, connections); // Fallback: if edges are all at://inline:* URIs that didn't resolve, try forkOf if (edges.length === 0) { edges = await resolveInlineFromForkOf(db, rec); } let vertexSet = new Set([centroid]); for (const edge of edges) { if (edge.source) vertexSet.add(edge.source); if (edge.target) vertexSet.add(edge.target); } // Fallback: if record connections are empty/unresolvable, derive from BFS if (edges.length === 0) { const derived = await deriveIsland(db, centroid); if (derived) { edges = derived.edges; vertexSet = new Set(derived.vertices); } } const vertices = [...vertexSet].sort(); const islandId = rkey; const analysis = rec.analysis || {}; islandDataJson = JSON.stringify({ island: { id: islandId, centroid, vertices, edges, vertexMeta: {}, summary: analysis.title || null, handle, strata: analysis.synthesis || analysis.synthesisDoc ? { prose: analysis.synthesis || "", title: analysis.title || "", themes: analysis.themes || [], relationships: [], highlights: analysis.highlights || [], tensions: analysis.tensions || [], open_questions: analysis.openQuestions || [], synthesis: analysis.synthesis || "", synthesisDoc: await resolveSynthesisDoc(analysis.synthesisDoc), } : null, rawRecord: rec, }, recordUri: recordRow.uri, }); } } } catch (e) { console.error("Failed to load island data:", e); } // Build OG tags from island data let ogTags = ""; try { const islandData = JSON.parse(islandDataJson); const title = islandData?.island?.strata?.title || islandData?.island?.summary || "Island"; const description = islandData?.island?.strata?.synthesis?.slice(0, 200) || islandData?.island?.strata?.tensions?.[0] || ""; const nodeCount = islandData?.island?.vertices?.length || 0; const edgeCount = islandData?.island?.edges?.length || 0; const fullTitle = `${title} — Stigmergic`; ogTags = ` `; } catch {} const page = LANDING_PAGE.replace( "", `${ogTags}`, ).replace("Stigmergic", `${(JSON.parse(islandDataJson)?.island?.strata?.title || JSON.parse(islandDataJson)?.island?.summary || "Island")} — Stigmergic`); return c.html(page); }); // Other SPA routes — just inject island list for (const path of ["/island", "/strata", "/connect", "/search"]) { app.get(path, async (c) => { let islandsJson = "[]"; try { const rows = await db .prepare("SELECT uri, json_extract(record, '$.source.uri') as centroid, json_extract(record, '$.analysis.title') as title FROM records_island WHERE uri NOT LIKE '%org.latha.strata%' ORDER BY time_us DESC LIMIT 50") .all<{ uri: string; centroid: string | null; title: string | null }>(); const islands: any[] = []; for (const row of rows.results || []) { if (!row.centroid) continue; const id = row.uri.split("/").pop() || row.uri; islands.push({ id, centroid: row.centroid, title: row.title || null, recordUri: row.uri }); } islandsJson = JSON.stringify(islands); } catch {} const page = LANDING_PAGE.replace( "", ``, ); return c.html(page); }); } // ── All other routes pass through to contrail ────────────────── app.all("*", async (c) => { const response = await contrailWorker.fetch( c.req.raw, c.env as unknown as Record, ); return response; }); return app; } // ─── Export ───────────────────────────────────────────────────────── export { StrataContainer }; export default { fetch(request: Request, env: Env): Response | Promise { app ??= buildApp(env); return app.fetch(request, env); }, async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { // Run Contrail indexing await contrailWorker.scheduled( event, env as unknown as Record, ctx, ); }, };