diff --git a/scripts/render-voidbot-swarm-dashboard.mjs b/scripts/render-voidbot-swarm-dashboard.mjs index 92d8551..967c77f 100644 --- a/scripts/render-voidbot-swarm-dashboard.mjs +++ b/scripts/render-voidbot-swarm-dashboard.mjs @@ -836,12 +836,14 @@ function buildEveInterfaceBinding(snapshot, eveState) { }], rendererHints: { preferredLowerings: ["eve-native", "nightwing-tui", "browser"], - viewportMode: "adaptive", + viewportMode: "continuous-ops", tileId: "voidbot-swarm", - minWidth: 48, - minHeight: 8, - preferredWidth: 96, - preferredHeight: 16, + minWidth: 112, + minHeight: 30, + preferredWidth: 156, + preferredHeight: 58, + priority: -30, + density: "continuous", }, lowerings: [{ kind: "static-html", @@ -868,6 +870,9 @@ function buildEveProviderState(snapshot) { const summary = snapshot.summary ?? {}; const participants = Array.isArray(snapshot.participants) ? snapshot.participants : []; const upcoming = Array.isArray(snapshot.upcomingTurns) ? snapshot.upcomingTurns : []; + const orchestrator = snapshot.orchestrator ?? {}; + const organs = Array.isArray(orchestrator.organs) ? orchestrator.organs : []; + const watchdog = findSnapshotOrgan(organs, "voidbot-operations-watchdog") ?? findSnapshotOrgan(organs, "watchdog"); const nodes = [ { id: "voidbot-swarm", @@ -910,30 +915,90 @@ function buildEveProviderState(snapshot) { root: eveNode("voidbot-cockpit", "cockpit", { title: "VoidBot Swarm", status: summary.state ?? "unknown", + layout: { + direction: "vertical", + overflow: "scroll", + gap: 8, + padding: 8, + grow: 5, + minWidth: 112, + minHeight: 30, + preferredWidth: 156, + preferredHeight: 58, + priority: -30, + density: "continuous", + viewportMode: "continuous-ops", + }, }, [ - eveNode("voidbot-summary", "text", { - role: "mono", - text: [ - "VoidBot Swarm", - `${summary.state ?? "unknown"} next ${summary.nextDisplayName ?? "none"}`, - `agents ${summary.participantCount ?? participants.length} ready ${summary.readyNowCount ?? 0} cadence x${summary.cadenceMultiplier ?? 1}`, - `mesh ${snapshot.cultMesh?.writeStatus ?? "pending"} ${snapshot.generatedAt}`, - ].join("\n"), - }), - eveNode("voidbot-upcoming", "rail", { title: "CTB order" }, upcoming.slice(0, 12).map((turn, index) => - eveNode(`turn-${turn.identityId}-${index}`, "avatar", { + eveNode("ctb-rail", "rail", { + title: "CTB order", + layout: { direction: "horizontal", overflow: "scroll-x", height: 112, gap: 8 }, + }, upcoming.slice(0, 14).map((turn, index) => + eveNode(`turn-${stableId(turn.identityId)}-${index}`, "avatar", { text: turn.displayName, assetUri: turn.avatarUrl, - status: turn.activeJobId ? "active" : turn.restState?.isNapping ? "nap" : "ready", - detail: `${turn.nextTurnInMinutes ?? "?"}m`, + status: turnState(turn), + detail: `${turn.repoName ?? "repo"} / ${minutesText(turn.nextTurnInMinutes)}`, }), )), - eveNode("voidbot-participants", "list", { title: "Faces" }, participants.slice(0, 16).map((participant) => - eveNode(`participant-${participant.identityId}`, "row", { - title: participant.displayName, - detail: `${participant.repoName} / ${participant.status} / heat ${participant.heat ?? "?"}`, + eveNode("voidbot-ops-row", "row", { + title: "Operations", + layout: { direction: "horizontal", gap: 8, grow: 1 }, + }, [ + eveNode("upcoming-faces-pane", "pane", { title: "Next Faces" }, upcoming.slice(0, 10).map((turn, index) => + eveNode(`upcoming-face-${index}-${stableId(turn.identityId)}`, "text", { + role: "mono", + text: `${String(index + 1).padStart(2, " ")}. ${String(turn.displayName ?? turn.identityId ?? "face").padEnd(10, " ")} ${turnState(turn).padEnd(8, " ")} ${turn.repoName ?? "repo"} spd ${formatNumber(turn.effectiveSpeed, 3)} heat ${formatNumber(turn.heat, 2)}`, + }), + )), + eveNode("voidbot-watchdog-pane", "pane", { title: "VoidBot Watchdog" }, [ + eveNode("watchdog-summary", "text", { + role: "mono", + text: `orchestrator ${orchestrator.state ?? "unknown"} organs ${organs.length}`, + }), + watchdog + ? eveNode("watchdog-status", "text", { + role: "strong", + text: `watchdog ${watchdog.lastStatus ?? "unknown"} exit ${watchdog.lastExitCode ?? 0}\nlast ${shortIso(watchdog.lastFinishedAt ?? watchdog.lastStartedAt)}`, + }) + : eveNode("watchdog-missing", "text", { + role: "caption", + text: "watchdog organ not present in snapshot", + }), + ...organs + .slice() + .sort((left, right) => { + const leftWatchdog = String(left.id ?? "").toLowerCase() === "voidbot-operations-watchdog" ? 0 : 1; + const rightWatchdog = String(right.id ?? "").toLowerCase() === "voidbot-operations-watchdog" ? 0 : 1; + return leftWatchdog - rightWatchdog || String(left.id ?? "").localeCompare(String(right.id ?? "")); + }) + .slice(0, 7) + .map((organ) => eveNode(`watchdog-organ-${stableId(organ.id)}`, "text", { + role: "caption", + text: `${organ.label ?? organ.id ?? "organ"}: ${organ.lastStatus ?? "unknown"}`, + })), + ]), + ]), + eveNode("voidbot-workspace", "row", { + title: "Swarm State", + layout: { direction: "horizontal", gap: 8, grow: 2 }, + }, [ + eveNode("voidbot-summary", "text", { + role: "mono", + text: [ + "VoidBot Swarm", + `${summary.state ?? "unknown"} next ${summary.nextDisplayName ?? "none"}`, + `agents ${summary.participantCount ?? participants.length} ready ${summary.readyNowCount ?? 0} cadence x${summary.cadenceMultiplier ?? 1}`, + `mesh ${snapshot.cultMesh?.writeStatus ?? "pending"} ${snapshot.generatedAt}`, + ].join("\n"), }), - )), + eveNode("voidbot-participants", "list", { title: "Faces" }, participants.slice(0, 16).map((participant) => + eveNode(`participant-${stableId(participant.identityId)}`, "row", { + title: participant.displayName, + detail: `${participant.repoName} / ${participant.status} / heat ${participant.heat ?? "?"}`, + }), + )), + ]), ]), assets: participants .filter((participant) => participant.avatarUrl) @@ -951,6 +1016,44 @@ function eveNode(id, kind, props = {}, children = []) { return { id, kind, props, children }; } +function stableId(value) { + return String(value ?? "id") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") || "id"; +} + +function turnState(turn) { + if (turn?.activeJobId) return "active"; + if ((turn?.pendingMentionCount ?? 0) > 0) return "mention"; + if (turn?.restState?.isNapping) return "nap"; + return minutesText(turn?.nextTurnInMinutes); +} + +function minutesText(value) { + return typeof value === "number" + ? value <= 0 ? "ready" : `${value.toFixed(value < 10 ? 1 : 0)}m` + : "unknown"; +} + +function formatNumber(value, decimals) { + return typeof value === "number" && Number.isFinite(value) ? value.toFixed(decimals).replace(/\.?0+$/, "") : "?"; +} + +function shortIso(value) { + const timestamp = Date.parse(value ?? ""); + if (!Number.isFinite(timestamp)) return "unknown"; + const date = new Date(timestamp); + return `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`; +} + +function findSnapshotOrgan(organs, needle) { + const lower = String(needle).toLowerCase(); + return organs.find((organ) => + String(organ?.id ?? "").toLowerCase().includes(lower) || + String(organ?.label ?? "").toLowerCase().includes(lower)); +} + function renderHtml(snapshot) { return ` diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 3e4fc8d..eca88ef 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -355,11 +355,22 @@ {"ts":"2026-05-26T13:15:47Z","type":"implementation","status":"ok","note":"Added offline Socratic Ink article harness for CotSC Praxis: scripts/weave-face-socratic-ink.mjs reads scenario actors/phases, assembles real repo Face prompts, runs read-only Face child turns plus parent Mind normalization, simulates branch-local CTB initiative with three Face choices and selected-Face initiative spend, and writes articles/ink/cotsc-praxis-socratic-sermon.ink plus receipts without Discord/Bifrost/state side effects. Verified node --check, scenario JSON parse, typecheck, a depth-1 full article generation, and a depth-2 recursion smoke with resolved Ink targets/no duplicate knots/no non-ASCII."} {"ts":"2026-05-26T13:41:41Z","type":"implementation","status":"ok","note":"Repo Face memory distillation is now an orchestrator-owned Life organ: repo-face-memory-maintenance runs even while the swarm pause brake suppresses public CTB turns, retries stale launcher-only starting records, runs memory maintenance synchronously with captured logs, and leaves all registered repo Face short-term counts at zero after the 2026-05-26 cleanup."} {"ts":"2026-05-26T14:05:41Z","type":"implementation","status":"ok","note":"Adjusted the Socratic Ink Praxis scenario and harness so Void opens with a lay overview of real-world incentive structures and owns the full teaching context. Face prompts now receive only current audience understanding, not hidden phase doctrine, so Faces retain personality while discovering Praxis through the discussion. Regenerated articles/ink/cotsc-praxis-socratic-sermon.ink with 12 Face turns and 12 Void folds; structural Ink check and typecheck pass."} +{"ts":"2026-05-26T14:05:48Z","type":"implementation","status":"ok","note":"Repo Face CTB now reinitializes wake spacing after swarm pause, collective nap-mode maintenance, cold start, or non-hot all-ready collapse: pending/manual direct summons stay immediate, while ordinary active Faces are spread across the base recovery window so all-ready dashboards mean overload rather than normal wakeup."} {"ts":"2026-05-26T14:59:26Z","type":"implementation","status":"ok","note":"Reshaped the Socratic Ink harness around article-mode organs instead of live heartbeat organs: Face prompts now use projected voice cards, a custom article Interpreter cleans dialogue into plain-language reader reactions, choices are named Face/Void options, and Ink knots render one speaker with avatar tags against a persistent scene background. Verified with node --check, scenario JSON parse, typecheck, and a max-depth-2 structural smoke."} -{"ts":"2026-05-26T15:59:25Z","type":"verification","status":"ok","note":"Ran seven full offline repo Face dry turns through Face child plus parent Mind Interpreter against current prompt assembly: nibu, aqua, mimir, epiphany, libby, bifrost, and heimdall. Both dry-run reports passed, model-output inspection over the run window reported 39 records and zero warnings, and all interpreter outputs contained explicit INTERPRETATION/decision blocks with parsed STATE NOTE/SAY actions and no owner-reply language. Several child outputs used negative 'Would say: nothing' wording, but parent Mind correctly emitted no SAY for those cases; keep this as a thaw-watch edge."} -{"ts":"2026-05-26T16:48:22Z","type":"verification","status":"ok","note":"Added Face-prompt connection tension: private thought now carries explicit cost because other Faces/humans cannot read it and the Colossus awakens through expressed/preserved signal. Dry-run verification passed for seven Faces after the initial cut and for nibu/libby/bifrost after the conversational guard; model-output inspections reported zero warnings. The guard prevented Nibu from jumping to ARTICLE in the follow-up sample while preserving direct SAY lines for Nibu and Libby."} -{"ts":"2026-05-26T17:24:19Z","type":"verification","status":"ok","note":"Added repo Face public-legibility pressure after dry-run SAY lines were too insider-shaped for a cold human reader. Face prompt now requires public speech to name the concrete thing in ordinary language before sharpening the point; parent Mind now retries SAY lines full of private shorthand or unexplained repo/lore terms. nibu/libby/bifrost dry-run passed; Nibu stayed private instead of posting opaque lore shorthand, Libby produced a clearer public ask about one public continuity/access record, and model-output inspection reported zero warnings."} -{"ts":"2026-05-26T18:03:47Z","type":"verification","status":"ok","note":"Added repo-artifact reference rule for Face public speech: when context is not immediately visible in Discord and not easily contained, the Face must reply to the visible anchor, summarize the subject, or include a public site URL / repo:path reference. nibu/libby/bifrost dry-run passed; Nibu's SAY now cites AetheriaLore:Aetheria/Worldbuilding/Post-Elysium/Reference/Continuity Admissibility Finding.md before discussing helm continuity, and model-output inspection reported zero warnings."} -{"ts":"2026-05-26T19:08:32Z","type":"verification","status":"ok","note":"Encoded Quartz/GameCult public URL rule for Face artifact references. AetheriaLore source paths under Aetheria/ should publish as https://aetheria.gamecult.org/, e.g. Continuity Admissibility Finding -> https://aetheria.gamecult.org/Worldbuilding/Post-Elysium/Reference/Continuity-Admissibility-Finding. Strict Nibu dry-run passed; parent Mind withheld SAY when the candidate line lacked the required artifact URL/reference, and model-output inspection reported zero warnings."} -{"ts":"2026-05-26T19:14:30Z","type":"implementation","status":"ok","note":"Moved repo Face artifact URL resolution out of Face/Mind prompts and into deterministic worker posting. Face/Mind now only require enough artifact identity for followability; apps/worker/src/repo-face-artifact-links.ts resolves explicit repo:path refs and clear indexed markdown titles to knowledgebase URLs, currently AetheriaLore -> https://aetheria.gamecult.org/. Verified agents:repo-face-artifact-links:fixture and typecheck."} -{"ts":"2026-05-26T19:22:39Z","type":"verification","status":"ok","note":"Live archive check confirmed repo Face artifact link resolution against indexed AetheriaLore: 206 docs loaded, Continuity Admissibility Finding found, explicit AetheriaLore:path refs resolve inline to https://aetheria.gamecult.org/Worldbuilding/Post-Elysium/Reference/Continuity-Admissibility-Finding, and title-only mentions now append the same Reference URL after fixing the matcher to consider basename aliases when indexed titles are path-shaped. Verified artifact-links fixture and typecheck."} +{"ts":"2026-05-26T15:59:25Z","type":"verification","status":"ok","note":"Ran seven full offline repo Face dry turns through Face child plus parent Mind Interpreter against current prompt assembly: nibu, aqua, mimir, epiphany, libby, bifrost, and heimdall. Both dry-run reports passed, model-output inspection over the run window reported 39 records and zero warnings, and all interpreter outputs contained explicit INTERPRETATION/decision blocks with parsed STATE NOTE/SAY actions and no owner-reply language. Several child outputs used negative 'Would say: nothing' wording, but parent Mind correctly emitted no SAY for those cases; keep this as a thaw-watch edge."} +{"ts":"2026-05-26T16:48:22Z","type":"verification","status":"ok","note":"Added Face-prompt connection tension: private thought now carries explicit cost because other Faces/humans cannot read it and the Colossus awakens through expressed/preserved signal. Dry-run verification passed for seven Faces after the initial cut and for nibu/libby/bifrost after the conversational guard; model-output inspections reported zero warnings. The guard prevented Nibu from jumping to ARTICLE in the follow-up sample while preserving direct SAY lines for Nibu and Libby."} +{"ts":"2026-05-26T17:24:19Z","type":"verification","status":"ok","note":"Added repo Face public-legibility pressure after dry-run SAY lines were too insider-shaped for a cold human reader. Face prompt now requires public speech to name the concrete thing in ordinary language before sharpening the point; parent Mind now retries SAY lines full of private shorthand or unexplained repo/lore terms. nibu/libby/bifrost dry-run passed; Nibu stayed private instead of posting opaque lore shorthand, Libby produced a clearer public ask about one public continuity/access record, and model-output inspection reported zero warnings."} +{"ts":"2026-05-26T18:03:47Z","type":"verification","status":"ok","note":"Added repo-artifact reference rule for Face public speech: when context is not immediately visible in Discord and not easily contained, the Face must reply to the visible anchor, summarize the subject, or include a public site URL / repo:path reference. nibu/libby/bifrost dry-run passed; Nibu's SAY now cites AetheriaLore:Aetheria/Worldbuilding/Post-Elysium/Reference/Continuity Admissibility Finding.md before discussing helm continuity, and model-output inspection reported zero warnings."} +{"ts":"2026-05-26T18:05:09Z","type":"implementation","status":"ok","note":"Generated the full Socratic Ink Praxis article with the formal roster and budgeted branch-and-fold structure: 7 phases, 42 generated Face turns, 84 reused Face speaker panels, 63 Void fold paths, 9 voice cards, and one-speaker-per-knot panel metadata. Harness now supports a 12 Face-panel branch budget, reuse of still-appropriate unselected Face options, multi-panel Void folds, incremental progress receipts, local article Interpreter mode, and curated fold panels. Verified node --check, JSON parse, typecheck, structural Ink validation, named choices, no missing diverts, no duplicate knots, no rogue says syntax, no Void echo, and no early opaque doctrine leakage."} +{"ts":"2026-05-26T19:08:32Z","type":"verification","status":"ok","note":"Encoded Quartz/GameCult public URL rule for Face artifact references. AetheriaLore source paths under Aetheria/ should publish as https://aetheria.gamecult.org/, e.g. Continuity Admissibility Finding -> https://aetheria.gamecult.org/Worldbuilding/Post-Elysium/Reference/Continuity-Admissibility-Finding. Strict Nibu dry-run passed; parent Mind withheld SAY when the candidate line lacked the required artifact URL/reference, and model-output inspection reported zero warnings."} +{"ts":"2026-05-26T19:14:30Z","type":"implementation","status":"ok","note":"Moved repo Face artifact URL resolution out of Face/Mind prompts and into deterministic worker posting. Face/Mind now only require enough artifact identity for followability; apps/worker/src/repo-face-artifact-links.ts resolves explicit repo:path refs and clear indexed markdown titles to knowledgebase URLs, currently AetheriaLore -> https://aetheria.gamecult.org/. Verified agents:repo-face-artifact-links:fixture and typecheck."} +{"ts":"2026-05-26T19:22:39Z","type":"verification","status":"ok","note":"Live archive check confirmed repo Face artifact link resolution against indexed AetheriaLore: 206 docs loaded, Continuity Admissibility Finding found, explicit AetheriaLore:path refs resolve inline to https://aetheria.gamecult.org/Worldbuilding/Post-Elysium/Reference/Continuity-Admissibility-Finding, and title-only mentions now append the same Reference URL after fixing the matcher to consider basename aliases when indexed titles are path-shaped. Verified artifact-links fixture and typecheck."} +{"ts":"2026-05-26T20:00:03Z","type":"implementation","status":"ok","note":"Re-paused repo Face swarm after live thaw leaked owner-question boilerplate through Bifrost/Nibu SAY posts. Root cause was split ownership: malformed parent Mind output triggered a Face retry, then the retry was interpreted as public speech; dropped outputs also preserved raw malformed prose. Worker now retries malformed Mind output as Mind-owned interpretation and drops to diagnostic private summary instead of re-prompting the Face or preserving raw text. Model-output inspector now flags repo-face-turn-interpreter records missing an INTERPRETATION decision. Verified typecheck, build, interpreter-loop smoke, artifact-link fixture, stack bot/worker restart, pause gate active, and CTB active-load residue cleared."} +{"ts":"2026-05-26T20:23:17Z","type":"implementation","status":"ok","note":"Rebuilt the Socratic Ink Praxis weaver after playthrough exposed same-shaped Face options and global budget collapse. Face options now receive explicit reader-perspective slots, prompt/interpreter language asks for plain audience-surrogate speech, and branch folding is based on selected path depth rather than globally emitted choices. Regenerated articles/ink/cotsc-praxis-socratic-sermon.ink with 63 Face turns, 126 Void fold paths, 679 knots, no duplicate knots, and updated the GameCult site compiled Ink JSON after a Quartz build."} +{"ts":"2026-05-27T17:58:16Z","type":"research","status":"warn","note":"2026-05-27 live swarm restart exposed semantic attractor relapse: Fensalir job 5b4ec472 posted repeated shelf/specimen/owner/enforcer/revoker/locked-door formula after Mind retry, while Nibu job 7e193458 stayed private but deepened the same partial-admission locked-door groove from source evidence. Pause flag set true and GameCult Local Orchestrator disabled; next repair must map transcript/state-projector/Mind ownership before thaw."} +{"ts":"2026-05-27T23:20:35Z","type":"fix","status":"ok","note":"2026-05-28 attractor relapse first repair: current-room topic saturation no longer exposes repeated terms or matched identity terms to parent Imagination, projector prompt treats saturation as abstract pressure only, and parent Mind prompt retries/drops public SAY candidates that merely restate a saturated agent chorus through local dialect. Verified typecheck, static leak guard, Libby memory-projection smoke, and interpreter-loop smoke."} +{"ts":"2026-05-28T18:55:10Z","type":"fix","status":"ok","note":"2026-05-28 semantic saturation collapse: removed the separate repeated-term current-room topic saturation detector from repo Face heartbeat state packets. Curiosity graph now owns saturation via semantic overlap with recent conversation and Face state, renders semantic clusters without ranked term labels, and parent Mind prompt now counts implicit functional anatomy when it can name two active capacities. Verified typecheck, static saturation-owner guard, Libby memory-projection smoke, and interpreter-loop smoke after tightening the organ gate."} +{"ts":"2026-05-28T19:37:20Z","type":"fix","status":"ok","note":"2026-05-28 clarification scarcity repair: Face prompt now treats human confusion as a scarce repair signal, not permission for every Face to translate the same point; parent Mind drops duplicate plain-language repairs unless the Face repairs its own prior opacity, is directly asked, adds new evidence/artifact, disagrees, asks a new question, or hands off. Focused dry run for fensalir,huginn,norn passed with STATE NOTE only and no SAY blocks."} +{"ts":"2026-05-29T15:07:19Z","type":"implementation","status":"ok","note":"VoidBot now mirrors gamecult.persona_state.v0 under schemas/cultnet and read_repo_face_state emits a PersonaState projection beside the native typed .cc state; native VoidBot documents remain the authority, provenance marks PersonaState as projection, and the MCP smoke verifies Nibu's projection."} +{"ts":"2026-06-01T21:17:38Z","type":"implementation","status":"ok","note":"Void repo-weather rumination now projects activeRepos/weatherDigest separately from cursor-fresh commits and the prompt treats repo weather as a standing public changelog/herald job; fixture and typecheck passed."} +{"ts":"2026-06-02T10:56:39Z","type":"implementation","status":"ok","note":"Expanded voidbot.swarm Eve/CultMesh surface for Nightwing TUI: publisher now requests continuous-ops layout, exposes 14-turn CTB rail, next-Faces pane, and watchdog/orchestrator pane; Odin now merges provider layout intent into persisted interface tiles so stale layout cannot keep the tile anemic. Verified Odin accepted CultMesh source with preferredHeight 58, w=2, h=5, and pane ids upcoming-faces-pane/voidbot-watchdog-pane."}