Skip to content

feat(mc): Pool B Health + Install Funnel ('auto-sync') + claude CLI prereq#28

Merged
aimerdoux merged 3 commits into
mainfrom
feat/mc-pool-b-health
May 19, 2026
Merged

feat(mc): Pool B Health + Install Funnel ('auto-sync') + claude CLI prereq#28
aimerdoux merged 3 commits into
mainfrom
feat/mc-pool-b-health

Conversation

@aimerdoux
Copy link
Copy Markdown
Owner

Summary

Two operator-visible additions on top of #27, plus one quality-of-life prereq cleanup.

1. Pool B Health + Install Funnel — new Mission Control subnav tab

The operator wants five questions answered at a glance. This adds a "Pool B" tab to MissionControlPage that surfaces all five:

Question Answer source
Is the Mac actually serving customers right now? os_devicesmax(usage_ledger.ran_at) — green dot if any device served within 5m
Which devices are paired + when did each last serve? Device table sorted online-first, then by last-inference
Are pillar-suggest chips populating in the chat onboarding? 24h success rate over Pool B usage_ledger rows
Are downloading customers following the right path? Pair → claim → first-inference funnel, 24h window
What's Pool B costing this week per subscription? Daily usage_ledger.cost_cents roll-up grouped by subscription_id, 14d

This is the "auto-sync" framing — one glance confirms both that the customer-facing chat onboarding's chips are alive AND that downloading users are not silently dropping off mid-pair.

Wiring

PoolBHealthWidget (UI, plugin)
   ↓ usePluginData
plugin worker.ts → ctx.data.register("pool-b-health-{recent,devices,pairings,spend,funnel}")
   ↓ localFetch to wavex-os-server
GET /api/pool-b-health/{recent,devices,pairings,spend,funnel}    ← board-gated
   ↓ assertBoard
mission-control/pool-b-health.ts   ← 30s in-memory cache, ?fresh=1 bypasses
   ↓ service-role Supabase client
wavex_os.{usage_ledger, os_devices, os_device_pairings}

Files

  • server: packages/wavex-os-server/src/mission-control/pool-b-health.ts — five query functions + 30s cache + service-role client
  • routes: packages/wavex-os-server/src/routes/pool-b-health.ts — five GET endpoints, board-gated, ?fresh=1 for live QA
  • plugin worker: five new ctx.data.register("pool-b-health-*") handlers in paperclip-plugin-wavex/src/worker.ts
  • plugin UI: paperclip-plugin-wavex/src/ui/PoolBHealthWidget.tsx — KPI row + 4 stacked sections + 30s auto-refresh + manual ↻ button
  • subnav: new "Pool B" tab in MissionControlPage.tsx (between Operations and Polish)

2. Claude CLI as a hard prereq in install.sh

The repo runs in BYOC mode — the tier-router T2 calls spawn the local claude binary. Without it, the dev server boots but T2 inference paths halt and the operator sees nothing come back from pillar-suggest / narrate / etc. install.sh was silently leaving this gap.

Now:

  • command -v claude check during prereq pass
  • Missing → npm install -g @anthropic-ai/claude-code (works on macOS + Linux uniformly, same path as the existing npm install -g pnpm@8)
  • WAVEX_SKIP_CLAUDE_CLI=1 env to bypass if you manage it elsewhere (asdf, system package)
  • Post-install message lists claude login as step 1 before wavex-os login at step 2

Why these belong together

The whole point of the Pool B Health tab is to answer "are chips still working?" — but if claude CLI isn't installed, chips can never work, and the dashboard will just show a flat zero forever. Making claude a hard prereq closes the loop: a fresh install either works end-to-end or surfaces the missing dep explicitly.

Verification

  • bash -n install.sh clean
  • TypeScript transpile on all 3 new files (server query module + route + plugin UI) — parsed ok
  • Plugin worker + MissionControlPage + ui/index re-exports — match the existing v0.15.0 conventions
  • CI / human review pass

Live QA after merge

Once landed + redeployed, hit the new endpoints directly to confirm wiring:

curl -H 'Authorization: Bearer <board-token>' \
  https://your-wavex-os-host/api/pool-b-health/funnel?fresh=1

# Expected (fresh install, no traffic):
# { "ok": true, "summary": {
#     "pairings_initiated_24h": 0, "pairings_claimed_24h": 0,
#     "devices_first_active_24h": 0, "devices_ever_active": 0,
#     "pillar_suggest_calls_24h": 0, "pillar_suggest_success_24h": 0
# } }

Then in MC: MissionControlPagePool B tab → should see "no devices paired" empty state. Once a customer runs wavex-os login + completes onboarding, the dots light up.

🤖 Generated with Claude Code

aimerdoux and others added 3 commits May 19, 2026 12:22
…e CLI prereq

Two operator-visible additions, plus one prereq cleanup:

1. **Pool B Health + Install Funnel** — new Mission Control subnav tab
   that answers the five questions the operator wants in their face:

   - Is the Mac actually serving customers right now?  (online dot)
   - Which devices are paired + when did each last serve?  (table)
   - Are pillar-suggest chips populating?  (24h success rate)
   - Are customers following the install path?
     (pair → claim → first-inference funnel)
   - What's Pool B costing this week per subscription?
     (daily roll-up, last 14d)

   The "auto-sync" framing — operator can glance and confirm both that
   the customer-facing chat onboarding's chips are alive AND that
   downloading users are not silently dropping off mid-pair.

   Added:
   - server: `packages/wavex-os-server/src/mission-control/pool-b-health.ts`
     (5 query functions, 30s in-memory cache, service-role Supabase
     client; queries `wavex_os.{usage_ledger, os_devices, os_device_pairings}`)
   - routes: `packages/wavex-os-server/src/routes/pool-b-health.ts`
     (5 GET endpoints under `/api/pool-b-health/*`, board-gated,
     `?fresh=1` bypasses cache for live QA)
   - plugin worker: 5 new `ctx.data.register("pool-b-health-*")` handlers
   - plugin UI: `PoolBHealthWidget.tsx` (KPI row + 4 stacked sections,
     30s auto-refresh, manual ↻ refresh button)
   - subnav: new "Pool B" tab in MissionControlPage (between Operations
     and the polish tab fallback)

2. **Claude CLI as a hard prereq in install.sh**

   The repo runs in BYOC (Bring Your Own Claude) mode — the tier-router
   T2 calls spawn the local `claude` binary. Without it, the dev server
   boots but T2 inference halts and the operator sees nothing useful
   come back from pillar-suggest / narrate / etc. install.sh was
   silently leaving this gap.

   Now installs `@anthropic-ai/claude-code` globally via npm during the
   prereq pass (works on both macOS + Linux uniformly — same path that
   `npm install -g pnpm@8` already uses). Bypass with
   `WAVEX_SKIP_CLAUDE_CLI=1` if you manage it elsewhere (asdf, system
   package, etc.). The post-install message now lists
   `claude login` as step 1 before `wavex-os login` at step 2.

Verification:
- bash -n install.sh → clean
- TypeScript transpile on all 3 new files → parsed ok
- Test CI runs on push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI on PR #28 failed because src/mission-control/pool-b-health.ts imports
@supabase/supabase-js but the package wasn't declared in wavex-os-server's
own package.json (vitest's resolver doesn't hoist transitive workspace
deps the way the runtime can). Pinned to ^2.46.1 to match the version
used by claude-code-proxy, cloud-client, inference-server, and
onboarding-ui — keeps the workspace consistent on a single supabase-js
major.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI runs `pnpm install --frozen-lockfile` by default. Adding
@supabase/supabase-js to wavex-os-server's package.json without
updating the lockfile makes that step fail with "specifiers in the
lockfile don't match specs in package.json".

Regenerated via `pnpm install --lockfile-only` so node_modules isn't
touched but the lockfile picks up the new specifier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@aimerdoux aimerdoux merged commit b311061 into main May 19, 2026
1 check passed
aimerdoux added a commit that referenced this pull request May 19, 2026
…29)

Two bugs that surfaced trying to build the wavex plugin locally after
#27 + #28. Neither one was caught by OSS CI because the plugin build
isn't run on the test workflow; the build is only exercised at dev
boot time via `pnpm dev:wavex-plugin:build`. Both bugs hard-fail the
build so the plugin's dist/ never refreshes — the Mission Control UI
loads a stale bundle that doesn't include any of the F1-F6 or Pool B
work.

1. **Hardcoded user path in build-ui.mjs**

   The esbuild import was pinned to
   `/Users/dylanriedweg/wavex-os/node_modules/.pnpm/esbuild@0.27.7/...`
   — works only on the original author's machine. esbuild isn't a
   direct dep of this package so a bare `import "esbuild"` also fails
   (pnpm's isolated store hides it). Replaced with a glob over the
   workspace root's `.pnpm` store that picks up whichever esbuild
   version is installed:

   ```js
   const pnpmStore = resolve(__dirname, "../../node_modules/.pnpm");
   const esbuildDir = readdirSync(pnpmStore).find((d) => /^esbuild@\d/.test(d));
   const esbuildMain = pathToFileURL(join(pnpmStore, esbuildDir, ...));
   const { build } = await import(esbuildMain.href);
   ```

   Robust across machines and esbuild minor-version bumps.

2. **`Record<View, number>` missing the new `"pool-b"` key**

   #28 added `"pool-b"` to the `View` type union but didn't update
   `countByTab`, which is typed `Record<View, number>`. tsc:

       MissionControlPage.tsx(94,9): error TS2741: Property '"pool-b"'
       is missing in type ... but required in type 'Record<View, number>'

   Added `"pool-b": 0` with a comment noting that Pool B Health
   doesn't have a backend-driven badge yet — the
   `mission-control-tab-counts` RPC predates it. A follow-up can
   surface `pillar_suggest_calls_24h - pillar_suggest_success_24h`
   as the badge value when chips are failing.

After both fixes, plugin builds to a fresh 235.8kb dist/ui/index.js
that includes the PoolBHealthWidget. Paperclip dev server now loads
the new "Pool B" subnav tab.

Co-authored-by: aimerdoux <aimerdoux94@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aimerdoux added a commit that referenced this pull request May 19, 2026
Bundles four operator-visible improvements to the Pool B inference
stack that together let WaveX run as a SaaS instead of a self-hosted
toy. Companion to #28 (Pool B Health widget + claude CLI prereq).

A. Composio managed mode (operator-managed key, not customer-managed)
─────────────────────────────────────────────────────────────────────

The directory modal at /<company>/connectors used to demand each
customer paste their own Composio API key. For a hosted WaveX deploy
that's wrong: the operator runs the Composio account, the customer
just gets the connectors as part of their subscription tier.

  - New env: WAVEX_COMPOSIO_MANAGED=1 on wavex-os-server
  - /api/connectors/setup-status now returns { managed: boolean }
  - ConnectorsSidebar's SetupScreen gate skips the key-entry modal
    when managed: true — falls through to the catalog (or degraded
    catalog when Composio is transiently down) instead of demanding
    a key the customer doesn't have

Self-hosted users keep BYOC by leaving WAVEX_COMPOSIO_MANAGED unset.

B. UUID→slug translation in plugin worker (fixes "0/0 ready" + fleet
   showing 0 agents even when 35 are alive)
─────────────────────────────────────────────────────────────────────

The plugin context gives the worker paperclip's company UUID, but
wavex-os-server's loadCompanyManifest() / getOnboardingDir() index by
the wavex slug (~/.wavex-os/instances/<slug>/). Every single
${base}/api/.../<uuid>/... call was silently returning [] because the
manifest lookup failed.

Diagnosed live:
  /api/companies/<uuid>/agents       → []
  /api/companies/<slug>/agents       → 35 agents
  /api/companies/<uuid>/agents on paperclip core → 35 agents

  - New helper resolveWavexSlug(uuid) in worker.ts that calls paperclip
    on first miss, caches the mapping in-process (immutable post-finalize)
  - Replaces every `const id = String(companyId ?? "")` with the async
    resolver (~30 sites mechanical, plus the inception-status handler's
    inline pattern)
  - Falls back to the raw input on lookup failure for partial-wiring
    test cases — degraded behavior is the same as before this change

Net effect: Inception Status sidebar now shows the real agent count,
Mission Control's Stream / Scoreboard / Map all hydrate correctly,
everything that hit ${base}/api/.../<id>/... starts working.

C. Inference life bar + throttle slider in Pool B Health
─────────────────────────────────────────────────────────────────────

Operator wants a glance check before triggering big customer flows —
"am I about to blow my Claude Max quota?" — plus a control surface
to throttle Pool B request rate to spread load.

  - New endpoint GET /api/pool-b-health/operator-quota returning
    {tokens_used_24h, tokens_used_7d, tokens_used_30d, cost_usd_*,
    requests_24h, requests_7d, last_inference_at}. Aggregates
    wavex_os.usage_ledger rows over the windows; 30s cache.
  - New worker data registration pool-b-health-operator-quota.
  - UI: life bar with green/yellow/red zones (cap at $5/day soft
    visual ramp), 24h/7d/30d numeric readout, throttle slider 1-120
    req/min persisted to localStorage.

Throttle ENFORCEMENT (queue/reject above cap on the inference-server
side) lands in a follow-up PR — for now the slider is a planning
surface. The life bar is fully live.

D. Supabase Edge Function: Pool B inference fallback
─────────────────────────────────────────────────────────────────────

When the operator's Mac is offline or rate-limited, the customer's
60s Realtime timeout currently fails silently (empty pillar
suggestions, hardcoded narrate copy). This PR adds an Edge Function
that the consumer-side cloudInference() can call as a fallback after
its existing timeout fires:

  POST /functions/v1/wavex-inference-fallback
  body: { prompt, max_output_tokens, purpose, model? }
  → { ok: true, content, usage, source: "fallback" }

  - Auth-gated: requires the caller's JWT + an active/trialing
    subscription. Free tier returns 402 — no anonymous Anthropic calls
    on the operator's dime.
  - Defaults to claude-haiku-4-5 (cheap; ~$0.005 per pillar-suggest).
    Whitelist also allows haiku-4-5-20251001 + sonnet-4-6.
  - Logs each call to wavex_os.usage_ledger with device_id=NULL so
    fallback usage shows up in the Pool B Health widget separately
    from Mac-served calls.
  - Returns 503 with structured error if ANTHROPIC_API_KEY is not set
    in Supabase project secrets — browser gracefully falls back to
    its existing failure mode.

Consumer-side cloudInference.ts update (browser calls this on
timeout) lands in a follow-up PR in wavex-experience-architect.

Deploy steps for D:
  1. Generate Anthropic API key at console.anthropic.com (separate
     from CLI session token — backend use only).
  2. supabase secrets set ANTHROPIC_API_KEY=sk-ant-...
  3. supabase functions deploy wavex-inference-fallback

Verification
─────────────────────────────────────────────────────────────────────

- pnpm --filter @wavex-os/paperclip-plugin-wavex build → 240.0kb dist
- packages/wavex-os-server tsc → clean
- Diff: 7 files, ~330 insertions, ~30 deletions
- Test CI runs on push

Co-authored-by: aimerdoux <aimerdoux94@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant