From cff36e6c1274474347f5c9ebcaf4d4a05d72413f Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:03:33 -0400 Subject: [PATCH 1/4] =?UTF-8?q?feat(cockpit):=20Lane=202=20=E2=80=94=20emb?= =?UTF-8?q?ed=20client-vue=20+=20sovereign=20runtime=20config=20injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Composition plan Lane 2 (docs/cockpit-composition-plan.md). Makes the SocioProphet cockpit shippable inside the .app, offline-first, in sovereign mode. - scripts/build-cockpit.sh — compiles client-vue → Contents/Resources/cockpit/ (reproducible: clones SocioProphet/socioprophet@ref, or COCKPIT_SRC local checkout; asserts the runtime resolver is present), then injects the config loader into index.html. - runtime/cockpit-config.js — sets window.__COCKPIT_CONFIG__ = { mode: 'sovereign', bases: {...loopback...} }, loaded BEFORE the app bundle. Consumed by client-vue's runtime resolver (socioprophet #468). BearBrowser rewrites the ephemeral sidecar ports into it at launch; committed values are the sovereign default. - docs/cockpit-spec.md — §3/§7/§8 app-vue → client-vue (the stale 'client-vue mocked' decision, now that client-vue is canonical). Verified: staged the built client-vue dist + injected the config + served it headless — window.__COCKPIT_CONFIG__ loads as sovereign with loopback bases, app boots clean, 0 errors. The resolver (Lane 1) consumes it. Follow-ups: per-service sovereign loopback map (mode semantics, plan gap #3); profile newtab wiring to the cockpit origin; Lane 3 agent-machine sidecar (bun --compile). --- docs/cockpit-spec.md | 24 ++++++++++----- runtime/cockpit-config.js | 29 +++++++++++++++++ scripts/build-cockpit.sh | 65 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 runtime/cockpit-config.js create mode 100755 scripts/build-cockpit.sh diff --git a/docs/cockpit-spec.md b/docs/cockpit-spec.md index 7005e26..ea158e6 100644 --- a/docs/cockpit-spec.md +++ b/docs/cockpit-spec.md @@ -45,9 +45,17 @@ what policy did about it. ## 3. Cockpit shell — Vue, shipped inside the .app, offline-first -**Base.** Fork `socioprophet-web/app-vue` (the real SPA — carries the Studio -design language + Carbon tokens). *Not* `client-vue` (mocked) and *not* the React -marketing shell. Product UI is Vue. +**Base.** Embed `socioprophet-web/client-vue` — the canonical, real cockpit (54 +surfaces, live `/svc` backends, shipped to GKE via prophet-platform #882). *Not* the +React marketing shell. Product UI is Vue. + +> **Decision update (2026-07-19, see `docs/cockpit-composition-plan.md`):** this was +> `app-vue`, with `client-vue` dismissed as "mocked." That's stale — `client-vue` +> became the canonical, real cockpit (the app-vue Studio surfaces were migrated into +> it). Embedding `client-vue` gives ONE codebase across embedded + GKE-hosted + +> Firebase. The build step is `scripts/build-cockpit.sh`; the sovereign runtime config +> is `runtime/cockpit-config.js`, consumed by client-vue's runtime resolver +> (`src/config/cockpitRuntime.ts`, socioprophet #468). **Embedding — no network for the shell.** Built assets ship inside the app bundle and load from a local origin, never HTTP: @@ -149,7 +157,7 @@ Physical actions are governed by the **same engine** as browser actions. ``` ┌──────────────────────────── BearBrowser.app (Gecko) ────────────────────────────┐ - │ Cockpit (Vue app-vue) origin: resource://bearbrowser-cockpit CSP: self+loopback │ + │ Cockpit (Vue client-vue) origin: resource://bearbrowser-cockpit CSP: self+loopback │ │ │ fetch / WS (127.0.0.1 only) │ │ ├───────────────► SearXNG sidecar / hosted → federated search + receipts │ │ ├───────────────► iot-sidecar (Rust, loopback) │ @@ -170,9 +178,11 @@ Physical actions are governed by the **same engine** as browser actions. 1. **Gecko binary.** Linux via `scripts/gcp-build-linux.sh` → GCS → `packaging/linux/binary-source.env`; macOS via `.github/workflows/nightly-dmg.yml` (macos-15). human-secure builds on `latest`/150. -2. **Cockpit assets.** Add a build step that compiles `app-vue` → static bundle → - `Contents/Resources/cockpit/`, and a branding/omni hook that maps the internal - origin. Register the service worker. +2. **Cockpit assets.** `scripts/build-cockpit.sh` compiles `client-vue` → static bundle + → `Contents/Resources/cockpit/` and injects `runtime/cockpit-config.js` (sets + `window.__COCKPIT_CONFIG__` → sovereign loopback bases, read by client-vue's runtime + resolver). A branding/omni hook maps the internal origin; register the service worker. + BearBrowser rewrites the sidecar ports into `cockpit-config.js` at launch. 3. **Profile wiring.** Point `browser.newtab.url` + `browser.startup.homepage` at the cockpit origin in each profile's `user.js`; keep hardening intact. 4. **Search default.** Add the SearXNG OpenSearch descriptor + set `Default` in diff --git a/runtime/cockpit-config.js b/runtime/cockpit-config.js new file mode 100644 index 0000000..c505c44 --- /dev/null +++ b/runtime/cockpit-config.js @@ -0,0 +1,29 @@ +// cockpit-config.js — sovereign runtime config for the embedded SocioProphet cockpit. +// +// build-cockpit.sh copies this into Contents/Resources/cockpit/ and injects a +// }' "$OUT/index.html" + log "injected cockpit-config.js loader into index.html" +fi + +log "done: embedded cockpit at $OUT ($(find "$OUT" -type f | wc -l | tr -d ' ') files)" From cbeef13018970ccd49c8e2cd2ff62cbdced98ad7 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:13:16 -0400 Subject: [PATCH 2/4] macos(nightly-dmg): cbindgen v0.29.1 (FF 150's min = build version); 0.29.4 regressed COUNT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'd used cbindgen 0.29.4 (latest 0.29.x, from a newer Firefox) — it emits the broken webrender_ffi macOS COUNT. FF 150.0.1's stated minimum (= its build version) is v0.29.1, before that codegen regression. Install from the git tag with --locked (crates.io 0.29.1 fails to build against current serde; the tag's Cargo.lock pins a compatible one). --- .github/workflows/nightly-dmg.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/nightly-dmg.yml b/.github/workflows/nightly-dmg.yml index 9d85808..48c4c40 100644 --- a/.github/workflows/nightly-dmg.yml +++ b/.github/workflows/nightly-dmg.yml @@ -111,19 +111,18 @@ jobs: # and 21 (Xcode) both hit the same webrender COUNT error, so keep the # simpler Xcode clang. # - lld: Firefox's macOS linker (Xcode doesn't ship it). - # - cbindgen: Mozilla's fork @ b826cb8 (0.29.4). This BUILDS and lets the - # compile run fully, but it emits a webrender_ffi macOS-only block - # (`#if defined(XP_MACOSX)` — hence Linux is unaffected) that references - # an undeclared COUNT, failing at ~27min. 🔴 KNOWN REMAINING BLOCKER — - # see docs note below. (0.28.0 is rejected as too old; crates.io 0.29.1 - # won't even compile against current serde; both clang 19 and 21 hit the - # identical COUNT, so it is NOT the compiler.) The real fix is to build - # with Mozilla's ACTUAL pinned toolchain bundle (what `mach bootstrap` - # fetches, which fails on this fork) rather than piecing it from brew. + # - cbindgen: PINNED to v0.29.1 (git tag eef1776) — FF 150.0.1's stated + # minimum, i.e. its build version. v0.29.4 (b826cb8, the LATEST 0.29.x, + # from a newer Firefox) emits a webrender_ffi macOS-only block + # (`#if defined(XP_MACOSX)` — hence Linux is fine) referencing an + # undeclared COUNT — a codegen regression after 0.29.1, failing ~27min + # in. Install from the git TAG with --locked (crates.io 0.29.1 won't + # compile against current serde; the tag's own Cargo.lock pins a + # compatible one). Clang is NOT the cause (19 and 21 fail identically). brew install nasm lld echo "$(brew --prefix lld)/bin" >> "$GITHUB_PATH" cargo install --git https://github.com/mozilla/cbindgen \ - --rev b826cb8911488fe8a209d2b693492c0c673e8cca cbindgen + --tag v0.29.1 --locked cbindgen echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" { echo "" From 8ae914b51351cb2745820147306050fbd61cd43e Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:14:06 -0400 Subject: [PATCH 3/4] =?UTF-8?q?feat(cockpit):=20Lane=203=20=E2=80=94=20age?= =?UTF-8?q?nt-machine=20as=20a=20bun-compiled=20loopback=20sidecar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Composition plan Lane 3. Packages @noetica/agent-machine (the local sovereign brain, 823 /api/* routes) into a single self-contained binary — no host Node, no node_modules. - scripts/build-agent-machine-sidecar.sh — bun build --compile server.ts → one 68M executable, then smoke-boots it and asserts /api/status 200 on loopback. Reproducible source (clone noetica@ref or AGENT_MACHINE_SRC local); COMPILES the engine, never edits it (Noetica lane: package-only). Installs deps only when node_modules is missing, so it doesn't touch an existing checkout's lockfile. - scripts/bearbrowser-agent-machine — launcher mirroring bearbrowser-sidecar-server: loopback-only, sovereign default (NOETICA_OFFLINE=1, no off-device egress), and it rewrites the live port into the embedded cockpit's cockpit-config.js so client-vue's resolver targets this sidecar — closing the Lane 1↔2↔3 loop. Verified for real: bun --compile → 68M arm64 binary that BOOTS, binds 127.0.0.1 ONLY by default, and serves /api/status 200 with NOETICA_OFFLINE=1 (no network). The build script's own smoke gate passes. bundleability de-risk → done. Follow-up: Homebrew/nix formula wiring (install the launcher + build step alongside bearbrowser-sidecar-server); Lane 4 governance via agent-control-bridge. --- scripts/bearbrowser-agent-machine | 45 +++++++++++++++++ scripts/build-agent-machine-sidecar.sh | 70 ++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100755 scripts/bearbrowser-agent-machine create mode 100755 scripts/build-agent-machine-sidecar.sh diff --git a/scripts/bearbrowser-agent-machine b/scripts/bearbrowser-agent-machine new file mode 100755 index 0000000..7063e55 --- /dev/null +++ b/scripts/bearbrowser-agent-machine @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# bearbrowser-agent-machine — launch the bundled Noetica agent-machine as a +# loopback-only sovereign sidecar, and point the embedded cockpit at it. +# +# Companion to bearbrowser-sidecar-server (the resolution sidecar) and the Rust +# iot-sidecar. Lane 3 of docs/cockpit-composition-plan.md. The agent-machine binary +# (built by build-agent-machine-sidecar.sh) already binds 127.0.0.1 only and reads +# NOETICA_AM_PORT; this wrapper enforces the sovereign defaults and writes the live +# port into the cockpit's runtime config so client-vue's resolver +# (window.__COCKPIT_CONFIG__) targets this sidecar. +# +# NOETICA_AM_PORT listen port (default 8080) +# NOETICA_OFFLINE sovereign default 1 (no off-device egress); set 0 to allow cloud models +# COCKPIT_DIR the embedded cockpit dir to update (default: sibling Resources/cockpit) +set -euo pipefail + +SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RES_DIR="$(cd "$SELF_DIR/.." && pwd)" # …/Contents/Resources when installed in the .app + +# Locate the compiled sidecar binary (installed path, then dev/staging fallbacks). +BIN="" +for cand in \ + "$RES_DIR/sidecars/bearbrowser-agent-machine-bin" \ + "$RES_DIR/build/sidecars/bearbrowser-agent-machine-bin" \ + "$SELF_DIR/../build/sidecars/bearbrowser-agent-machine-bin"; do + [ -x "$cand" ] && { BIN="$cand"; break; } +done +[ -n "$BIN" ] || { echo "[agent-machine] ERROR: sidecar binary not found (run build-agent-machine-sidecar.sh)"; exit 1; } + +PORT="${NOETICA_AM_PORT:-8080}" +export NOETICA_AM_PORT="$PORT" +export NOETICA_OFFLINE="${NOETICA_OFFLINE:-1}" # sovereign default: no off-device egress + +# Point the embedded cockpit at this sidecar: rewrite the loopback bases in +# cockpit-config.js to our live port (the "host injects the ports" step). +COCKPIT_DIR="${COCKPIT_DIR:-$RES_DIR/cockpit}" +CFG="$COCKPIT_DIR/cockpit-config.js" +if [ -f "$CFG" ]; then + # portable in-place edit (BSD/GNU sed differ on -i): write via tmp + sed -E "s#http://127\.0\.0\.1:[0-9]+#http://127.0.0.1:$PORT#g" "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG" + echo "[agent-machine] cockpit config → 127.0.0.1:$PORT ($CFG)" +fi + +echo "[agent-machine] starting loopback sidecar on 127.0.0.1:$PORT (offline=$NOETICA_OFFLINE)" +exec "$BIN" diff --git a/scripts/build-agent-machine-sidecar.sh b/scripts/build-agent-machine-sidecar.sh new file mode 100755 index 0000000..ba8e67f --- /dev/null +++ b/scripts/build-agent-machine-sidecar.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# build-agent-machine-sidecar.sh — compile @noetica/agent-machine into a single +# self-contained loopback-sidecar binary (bun --compile), staged into the .app. +# +# Lane 3 of docs/cockpit-composition-plan.md. The agent-machine is the local +# sovereign brain (server.ts, 823 /api/* routes: graph, pipelines, devspace, +# knowledge, chat). It's pure JS/TS (verified: @socioprophet/hellgraph is pure TS, +# no native addons), so `bun build --compile` collapses it to one executable — no +# node_modules, no host Node required. The heavy LLM runtime (Ollama) is NOT bundled; +# agent-machine downloads it on demand (scripts/provision-runtime.ts) or uses a cloud +# key, and boots + serves the non-chat surfaces without it. +# +# Source is reproducible: clone the noetica repo at a pinned ref, or point +# AGENT_MACHINE_SRC at a local checkout (dev/CI cache). NOTE: this only COMPILES the +# Noetica engine — it never edits it (Noetica lane: package-only). +# +# AGENT_MACHINE_SRC=/path/to/noetica # optional: skip the clone, use a local repo +# AM_REF= # default: main +# OUT=/path/to/Contents/Resources/sidecars # default: build/sidecars staging +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +AM_REF="${AM_REF:-main}" +OUT="${OUT:-$REPO_ROOT/build/sidecars}" +WORK="$REPO_ROOT/build/.agent-machine-src" +BIN_NAME="bearbrowser-agent-machine-bin" +AM_SUBDIR="agent-machine" + +log() { printf '[build-agent-machine] %s\n' "$*"; } + +command -v bun >/dev/null 2>&1 || { log "ERROR: bun is required (bun build --compile). Install: https://bun.sh"; exit 1; } + +# 1. Resolve the agent-machine source ----------------------------------------- +if [ -n "${AGENT_MACHINE_SRC:-}" ]; then + SRC="$AGENT_MACHINE_SRC/$AM_SUBDIR" + log "using local agent-machine source: $SRC" +else + log "cloning noetica@$AM_REF (shallow)" + rm -rf "$WORK" + git clone --depth 1 --branch "$AM_REF" \ + "${NOETICA_GIT_URL:-https://github.com/SocioProphet/noetica.git}" "$WORK" + SRC="$WORK/$AM_SUBDIR" +fi +[ -f "$SRC/server.ts" ] || { log "ERROR: agent-machine server.ts not found at $SRC"; exit 1; } + +# 2. Install deps (only if missing — don't mutate an existing checkout's lockfile) + compile +if [ ! -d "$SRC/node_modules" ]; then + log "bun install in $SRC (fresh checkout)" + ( cd "$SRC" && bun install ) +else + log "node_modules present — skipping install (avoids touching the Noetica lockfile)" +fi +mkdir -p "$OUT" +log "bun build --compile server.ts → $OUT/$BIN_NAME" +( cd "$SRC" && bun build ./server.ts --compile --outfile "$OUT/$BIN_NAME" ) +[ -x "$OUT/$BIN_NAME" ] || { log "ERROR: compile produced no executable"; exit 1; } + +# 3. Smoke: boot on a throwaway loopback port, assert it serves --------------- +PORT=8917 +NOETICA_AM_PORT="$PORT" NOETICA_OFFLINE=1 "$OUT/$BIN_NAME" >/tmp/am-sidecar-smoke.log 2>&1 & +PID=$! +for _ in $(seq 1 15); do + code="$(curl -s -m 2 -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT/api/status" 2>/dev/null || echo 000)" + [ "$code" = "200" ] && break + sleep 1 +done +kill "$PID" 2>/dev/null || true +[ "${code:-000}" = "200" ] || { log "ERROR: sidecar binary did not serve /api/status (got $code)"; cat /tmp/am-sidecar-smoke.log; exit 1; } +log "smoke OK: /api/status 200 on 127.0.0.1:$PORT" +log "done: $OUT/$BIN_NAME ($(du -h "$OUT/$BIN_NAME" | cut -f1))" From 959cc34690b62f8fafc0350b139700deb6a4bab8 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:58:39 -0400 Subject: [PATCH 4/4] cockpit: document the sovereign capability subset (mode-semantics gap #3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against agent-machine's route table: the local brain serves graph (76 /api/graph/* routes) + the agent surfaces + partial studio, but ZERO routes for reasoner / entity-resolution / IE / algo / sherlock — those are separate GKE services with no local equivalent. So they're intentionally UNSET in the sovereign config: the resolver falls back to /svc/* (no offline proxy) and the surfaces degrade gracefully, by design. Registering (connected mode) enables them. This resolves gap #3: it's a real capability subset, not a mapping to be filled. --- runtime/cockpit-config.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/runtime/cockpit-config.js b/runtime/cockpit-config.js index c505c44..c880cf9 100644 --- a/runtime/cockpit-config.js +++ b/runtime/cockpit-config.js @@ -16,14 +16,19 @@ window.__COCKPIT_CONFIG__ = { // 823 /api/* routes (graph, pipelines, devspace, knowledge, chat) on 127.0.0.1. // BearBrowser injects the live port; 8080 is the agent-machine default. agentMachine: 'http://127.0.0.1:8080', - // The graph + studio surfaces are served by the same local brain in sovereign - // mode (agent-machine's /api/graph/*, /api/studio/*), so they point at it too. + // The graph surface is served by the same local brain in sovereign mode + // (agent-machine serves 76 /api/graph/* routes), so it points at it. studio is + // partial locally (/api/studio); the full notebook/compute plane is cloud + // (lattice-studio), so studio degrades to read-only-ish offline. hellgraph: 'http://127.0.0.1:8080', studio: 'http://127.0.0.1:8080', - // ie / algo / sherlock / reason / er are the CONNECTED cloud decomposition - // (separate GKE services). In sovereign mode they either resolve into the - // agent-machine's /api or degrade until the per-service loopback map lands - // (mode-semantics follow-up — composition-plan gap #3). Left unset here so the - // resolver's fallback governs, rather than pointing at a port that 404s. + // CAPABILITY TIERING (verified against agent-machine's route table): + // reason / er / ie / algo / sherlock are CONNECTED-ONLY — the local agent-machine + // serves ZERO routes for them (they're separate GKE services: owl-reasoner, + // entity-resolution, ie-engine, algo-engine, sherlock-engine). There is no local + // backend to map them to, so they are intentionally UNSET here: the resolver falls + // back to /svc/* (no proxy offline) and the surfaces degrade gracefully — they're + // built to handle an unavailable backend. Registering (connected mode) is what + // enables them. This is the sovereign capability subset, not a gap to be filled. }, };