Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions .github/workflows/nightly-dmg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
24 changes: 17 additions & 7 deletions docs/cockpit-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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) │
Expand All @@ -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
Expand Down
34 changes: 34 additions & 0 deletions runtime/cockpit-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// cockpit-config.js — sovereign runtime config for the embedded SocioProphet cockpit.
//
// build-cockpit.sh copies this into Contents/Resources/cockpit/ and injects a
// <script src="./cockpit-config.js"> into index.html so it runs BEFORE the app
// bundle. The client-vue runtime resolver (src/config/cockpitRuntime.ts) reads
// window.__COCKPIT_CONFIG__ at boot; this file is what puts the cockpit in
// SOVEREIGN mode against loopback sidecars, with no off-device egress.
//
// The ports here are the sovereign DEFAULTS. BearBrowser rewrites this file at
// launch with the live sidecar ports (they're ephemeral — see the sidecar
// launchers), so the committed values are just a usable pre-rewrite fallback.
window.__COCKPIT_CONFIG__ = {
mode: 'sovereign',
bases: {
// The Noetica agent-machine loopback sidecar — the local brain. server.ts serves
// 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 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',
// 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.
},
};
45 changes: 45 additions & 0 deletions scripts/bearbrowser-agent-machine
Original file line number Diff line number Diff line change
@@ -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"
70 changes: 70 additions & 0 deletions scripts/build-agent-machine-sidecar.sh
Original file line number Diff line number Diff line change
@@ -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=<git 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))"
65 changes: 65 additions & 0 deletions scripts/build-cockpit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# build-cockpit.sh — compile the SocioProphet cockpit (client-vue) into a static
# bundle embedded in the .app at Contents/Resources/cockpit/, offline-first.
#
# Lane 2 of docs/cockpit-composition-plan.md. The embedded cockpit is the SAME
# client-vue that ships to prophet-platform (GKE) and Firebase — one codebase, three
# surfaces. What makes it modular is the runtime resolver (client-vue
# src/config/cockpitRuntime.ts): it reads window.__COCKPIT_CONFIG__ at boot, so the
# embed runs in SOVEREIGN mode against loopback sidecars with no off-device egress.
#
# Source is reproducible: clone SocioProphet/socioprophet at a pinned ref, or point
# COCKPIT_SRC at a local checkout (dev/CI cache).
#
# COCKPIT_SRC=/path/to/socioprophet # optional: skip the clone, use a local repo
# COCKPIT_REF=<git ref> # default: master
# OUT=/path/to/Contents/Resources/cockpit # default: build/cockpit staging
#
# Idempotent; no network needed when COCKPIT_SRC is set.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COCKPIT_REF="${COCKPIT_REF:-master}"
OUT="${OUT:-$REPO_ROOT/build/cockpit}"
WORK="$REPO_ROOT/build/.cockpit-src"
CLIENT_VUE_SUBDIR="socioprophet-web/client-vue"

log() { printf '[build-cockpit] %s\n' "$*"; }

# 1. Resolve the cockpit source ------------------------------------------------
if [ -n "${COCKPIT_SRC:-}" ]; then
SRC="$COCKPIT_SRC/$CLIENT_VUE_SUBDIR"
log "using local cockpit source: $SRC"
else
log "cloning SocioProphet/socioprophet@$COCKPIT_REF (shallow)"
rm -rf "$WORK"
git clone --depth 1 --branch "$COCKPIT_REF" \
"${COCKPIT_GIT_URL:-https://github.com/SocioProphet/socioprophet.git}" "$WORK"
SRC="$WORK/$CLIENT_VUE_SUBDIR"
fi
[ -d "$SRC" ] || { log "ERROR: client-vue not found at $SRC"; exit 1; }
grep -q "cockpitRuntime" "$SRC/src/config/cockpitRuntime.ts" 2>/dev/null \
|| { log "ERROR: cockpit source lacks the runtime resolver (needs socioprophet #468 landed)"; exit 1; }

# 2. Build the static bundle ---------------------------------------------------
log "npm ci + build in $SRC"
( cd "$SRC" && npm ci --no-audit --no-fund && npm run build )
[ -f "$SRC/dist/index.html" ] || { log "ERROR: build produced no dist/index.html"; exit 1; }

# 3. Stage into the cockpit resource dir --------------------------------------
log "staging → $OUT"
rm -rf "$OUT"; mkdir -p "$OUT"
cp -R "$SRC/dist/." "$OUT/"

# 4. Inject the runtime config loader BEFORE the app bundle -------------------
# cockpit-config.js sets window.__COCKPIT_CONFIG__ for sovereign (loopback) mode.
# BearBrowser rewrites the sidecar ports into this file at launch (they're ephemeral);
# the committed template is the sovereign default so the cockpit is usable pre-rewrite.
cp "$REPO_ROOT/runtime/cockpit-config.js" "$OUT/cockpit-config.js"
if ! grep -q 'cockpit-config.js' "$OUT/index.html"; then
# insert as the first <head> child so it runs before the module bundle
perl -0pi -e 's{(<head[^>]*>)}{$1\n <script src="./cockpit-config.js"></script>}' "$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)"
Loading