22 Commits

Author SHA1 Message Date
dajiaohuang 40affb83fe fix(ruvocal): keep hidden-tab streams responsive 2026-08-14 17:10:34 -04:00
rUv 67ff9898c0 fix: resolve current runtime and verification defects for v3.32.36 (#2850)
* fix(cli): make runtime status and learning signals truthful

* fix(metaharness): accept padded scan severities

* fix(codex): make core hooks and status skill native

* fix(security): make witness verification hermetic

* fix(metrics): expose grounded learning outcomes

* fix(hooks): deduplicate project and plugin events

* fix(verification): preserve security audit payloads

* fix(runtime): stabilize dual memory skills and MCP schemas

* fix(cli): harden helper signing and reflexion health

* test(release): align helper and container invariants

* chore(release): prepare v3.32.36

* test(funnel): align gates with cold-start seed pool

* fix(ci): document MCP environment precedence
2026-07-29 15:08:51 -04:00
rUv ae9bba523e feat(metaharness): learn + gepa integration — metaharness@0.3.0 / darwin@0.8.0 (15 MCP tools) (#2540)
* fix(mcp-bridge): streamable-HTTP session cleanup — DELETE + Mcp-Session-Id + 202 (#2425)

Closes #2425. Cherry-picked from djimit's commit b905c09f4 with
extensions: applied symmetrically to BOTH bridges (per ADR-166's
"both bridges move together" rule), and added regression coverage
in the ADR-166 security lock so this can't silently regress.

Streamable-HTTP clients (Codex/RMCP) send `DELETE /mcp` with an
`Mcp-Session-Id` header at shutdown. Previously the bridge returned
`Cannot DELETE /mcp` which surfaced as noisy client-side cleanup
failures after otherwise-successful MCP calls.

Changes (both bridges — ruflo/src/mcp-bridge/index.js AND
ruflo/src/ruvocal/mcp-bridge/index.js):

- Stable per-process `MCP_SESSION_ID = randomUUID()` echoed as the
  `Mcp-Session-Id` header on every `/mcp*` response
- `DELETE /mcp` and `DELETE /mcp/:group` → 204 No Content (idempotent)
- `DELETE` added to `Access-Control-Allow-Methods`; `Mcp-Session-Id`
  added to `Access-Control-Allow-Headers` (both compatible with the
  ADR-166 CORS allowlist wiring — allowlist controls Origin, not
  Method/Header)
- `notifications/initialized` responses now `202 Accepted` with empty
  body per the MCP streamable-HTTP spec (was returning a jsonrpc
  envelope which some clients rejected as unexpected)

Regression locks (extends ADR-166 §6 acceptance):
- test-security-lock.js — 2 new static checks (#7 delete handler,
  #8 Mcp-Session-Id header) × 2 bridges = 16/16
- test-runtime-security.mjs — 2 new runtime checks per bridge (R6
  DELETE→204 + header echoed; R7 notifications/initialized→202 empty)
  = 20/20 total green

Interaction with ADR-166 auth: DELETE flows through `app.use(requireAuth)`
same as the rest of `/mcp*`. When `MCP_AUTH_TOKEN` is set, DELETE without
Bearer returns 401 (verified in R2 by contrast; DELETE with Bearer
returns 204 verified in R6). Local-only deployments with no token remain
zero-friction (the middleware is a no-op).

Credit: @djimit for the original patch (branch
fix/mcp-session-cleanup-forkbase on djimit/claude-code-flow, commit
b905c09f42).

Co-Authored-By: djimit <djimit@users.noreply.github.com>
Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(metaharness): learn + gepa integration — metaharness@0.3.0 / darwin@0.8.0

Version pins (optionalDependencies, both @claude-flow/cli + ruflo wrapper):
- @metaharness/darwin  ~0.3.1 → ~0.8.0  (GEPA engine + cand-6 genome)
- metaharness          ~0.2.6 → ~0.2.8
- @metaharness/redblue ~0.1.1 → ~0.1.4  (+ versioned cache dir — pin bumps
  now invalidate stale installs instead of serving 0.1.1 forever)
- @metaharness/kernel  ~0.1.0 → ~0.1.2

New surfaces (13 → 15 MCP tools, 11 → 13 CLI subcommands):
- metaharness_learn / `ruflo metaharness learn` — upstream ADR-235 GEPA
  learning run; $0 dry-run default, --run to spend, structured
  checkout-required payload when the metaharness clone is absent
- metaharness_gepa / `ruflo metaharness gepa` — darwin GEPA library
  surface (genome|validate|render|analyze); gepaOptimize stays
  library-only (in-process evaluator can't cross subprocess boundary)
- skills: harness-learn, harness-gepa

Fixes found during validation:
- gepa.mjs treats ERR_PACKAGE_PATH_NOT_EXPORTED (stale pre-0.8.0 darwin
  in ancestor node_modules) as recoverable → falls back to versioned cache
- smoke.sh contract counts advanced (scripts 13→15, tools 13→15,
  subcommands 11→13, CLAUDE.md doc counts)

Validated: tsc clean; test-mcp-tools 171/171 across all 15 tools; darwin
0.8.0 evolve/bench/security-bench verbs proven through wrappers; redblue
0.1.4 attack preview proven via new cache path.

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: djimit <djimit@users.noreply.github.com>
2026-07-03 14:17:09 -04:00
rUv d00a0a40cd security(mcp-bridge): ADR-166 unauthenticated RCE — Phase 1-3 remediation + CI lock (#2521)
* docs(adr): ADR-166 — MCP bridge unauthenticated RCE remediation (coordinated disclosure)

Document and propose remediation for a coordinated-disclosure report: the
ruvocal MCP bridge exposes POST /mcp with no authentication and the shipping
docker-compose binds it to all interfaces, yielding unauthenticated RCE plus
LLM-key, learning-store, and MongoDB compromise.

Status: Proposed — no code changes. Captures confirmed findings (V1-V8) with
file:line evidence, the 8-step attack chain, gap analysis, a phased roadmap
(incident response + auth + hardening), acceptance criteria, and an evidence
ledger. Reporter identity, PoC callback domain, and target host are redacted.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(security): ADR-166 reframe — local-only default, public is opt-in (V1/V6/V8)

Maintainer decision (2026-06-30): MCP design contract is local-only by
default. Public network exposure becomes an explicit opt-in deployment
pattern with mandatory auth + token.

## Defaults flipped to loopback

- `ruflo/docker-compose.yml`: bridge `3001:3001` → `127.0.0.1:3001:3001`;
  Mongo `27017:27017` → `127.0.0.1:27017:27017`
- Both bridges (`ruflo/src/{ruvocal/,}mcp-bridge/index.js`):
  `app.listen(PORT)` → `app.listen(PORT, BIND_HOST)` with
  `BIND_HOST = process.env.MCP_BIND_HOST || '127.0.0.1'`
- `v3/@claude-flow/plugin-agent-federation/src/bin.ts`:
  default `bindHost` `'0.0.0.0'` → `'127.0.0.1'`

## Optional public deployment pattern

NEW `ruflo/docker-compose.public.yml` — explicit override composition.
Operator must set BOTH `MCP_BIND_HOST=0.0.0.0` AND
`MCP_AUTH_TOKEN=$(openssl rand -base64 32)`, otherwise the bridge exits
non-zero at startup with a FATAL message that includes the token-gen
command.

When `MCP_AUTH_TOKEN` is set (any bind), the new `requireAuth`
middleware enforces `Authorization: Bearer <token>` on all `/mcp*`,
`/chat/completions`, `/autopilot*`, `/mcp-servers` routes using
`timingSafeEqual`. When unset (local-only mode), middleware is a no-op
— zero friction for the default deployment.

## Why this framing

Per the review-thread pushback:
> "If someone has unauthorized access to your environment, you have
>  bigger problems. The MCP is local so unauthorized is the only
>  option."

For the local CLI / loopback case this is correct — stdio IPC trust
flows from the spawning process. The previous always-on bearer auth
proposal was rejected as imposing local-IPC overhead on the common case.

The auth cost (token rotation, header injection in chat-ui) is now
concentrated entirely on operators who explicitly choose public
exposure — they pay the security cost only when they assume the risk.

## ADR-166 amendments (6 surgical edits)

- §1.2 — adds the design-contract clause
- §6 Phase 1 — retitled; 1a now leads with loopback default, 1b is
  fail-closed-on-opt-in, 1c is auth-when-token-set, 1d unchanged
- §6 new "Optional public deployment pattern" subsection with the
  generate-token + compose -f override recipe
- §7 R1/R2 — reframed to acknowledge the UX cost is concentrated in
  the optional path
- §8 — "bind loopback only; skip auth" REJECTED → "loopback default;
  auth for opt-in" ACCEPTED
- §10 — maintainer decision 2026-06-30 cited

## Gates (all PASS)

- `npm audit --audit-level=critical` root: exit 0
- `npm audit --audit-level=critical` v3:   exit 0
- CLI build: clean (tsc)
- Static smoke (both bridges): bind-host + token + fail-closed wired
- Compose YAML parses (default loopback + public override)

## Follow-ons (in same release train)

1. Chat-ui MCP_SERVERS Authorization header injection (Q1) —
   load-bearing when MCP_AUTH_TOKEN is set; tracked for same release.
2. MCP_ENABLE_TERMINAL gate (1d) — separate commit before merge.
3. Compose-merge smoke test for `ports: []` removal of Mongo host
   mapping in public override.

Co-Authored-By: Dragan Spiridonov <dragan@spiridonov.dev>
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WbBa4nccx5aGhXphoHxcni

* security(mcp-bridge): ADR-166 Phase 1-3 — server-side tool gate, read-only rootfs, mongo auth, CI lock

Completes the ADR-166 remediation. Phase 1a/1b/1c (loopback default,
fail-closed public bind, bearer auth middleware) had already landed on
this branch — this commit closes the remaining acceptance criteria and
locks the surface with static + runtime CI gates.

Adds
- Server-side terminal gate (Phase 1d + 2a). `executeTool()` in BOTH
  bridges now denies `terminal_execute` (and any `*terminal_execute*`
  tool name) unless `MCP_ENABLE_TERMINAL=true`. Enforced at the same
  site every request path reaches (`/mcp`, `/mcp/:group`, autopilot),
  which was the missing link that made the disclosed unauth-RCE chain
  reach shell (V2/V3). Returns `{error, code: "TOOL_DISABLED"}`.
- Startup posture banner. Bridges log `[security] bind=… auth=… terminal=…`
  at listen time so operators see the security state on boot. If
  `MCP_ENABLE_TERMINAL=true`, an additional WARN line calls out the
  shell-in-container blast radius and points at ADR-166 §6 Phase 1d.
- MongoDB auth on by default (Phase 2b). `mongodb` service now runs
  `--auth`; `MONGO_INITDB_ROOT_PASSWORD` is a hard-required compose
  variable (the `${VAR:?…}` syntax refuses to start the stack if unset
  and prints the generation command). `chat-ui`'s MONGODB_URL is
  updated to include the credentials.
- Read-only rootfs on the bridge (Phase 2c). `read_only: true` +
  `tmpfs: /tmp` on the `mcp-bridge` service. Breaks step 7 of the
  disclosed PoC (the `/app/beacon.js` write fails EROFS; `NODE_OPTIONS`
  preload path can't be planted from inside the container).
- CORS allowlist wiring (Phase 3b). Both bridges honor
  `MCP_CORS_ORIGIN` (comma-separated). Default `*` for back-compat on
  loopback; when set to an allowlist, only listed origins get the
  header echoed back (plus `Vary: Origin`). Non-listed origins get
  nothing — browsers block the request.
- Anti-regression static-source lock (Phase §6 #9).
  `ruflo/src/mcp-bridge/test-security-lock.js` — 6 checks × 2 bridges,
  offline, zero-deps. Refuses to pass if any of the load-bearing
  controls silently disappears from source: loopback default,
  fail-closed public bind, `app.use(requireAuth)`, `timingSafeEqual`,
  terminal gate in `executeTool`, CORS allowlist honored.
- Runtime behavioral verification.
  `ruflo/src/mcp-bridge/test-runtime-security.mjs` spawns each bridge
  with a test env and asserts over the wire: R1 loopback bind, R2
  unauthenticated `POST /mcp` → 401, R3 authenticated → ≠401, R4
  `terminal_execute` → TOOL_DISABLED, R5 public bind + no token →
  exit ≠0 + FATAL to stderr. Complements the source-only lock.
- CI workflow `.github/workflows/adr-166-mcp-bridge-security.yml`.
  Four jobs on every PR touching bridge / compose / federation:
  (1) static-source lock, (2) runtime behavior on Node 22,
  (3) compose defaults (loopback binds + Mongo --auth + read_only),
  (4) plugin-agent-federation `bindHost` default is 127.0.0.1.

ADR update
- `ADR-166` status: Proposed → Accepted (Phase 0-3 shipped).
- Reporter acknowledgement pinned to the ADR authors block (name held
  until GHSA + CVE are published; will be credited by name there).
- §9.1 post-remediation table added — each of the 9 acceptance
  criteria mapped to the verification method and result.

Local verification (2026-06-30, before push)
- `node test-security-lock.js`         → 12/12 ✅ (6 × 2 bridges)
- `node test-runtime-security.mjs`     → 12/12 ✅ (R1-R5b × 2 bridges)

Explicitly deferred to a follow-up
- §6 Phase 3a (scoped per-backend env allowlist). Requires enumerating
  every backend's key requirements first; #6 in §9.1 acceptance is
  flagged ⏳ deferred rather than ✅.
- §7 Q1 (chat-ui → bridge token transport). Recommend Docker secret +
  nginx-injected header; unblocked by this commit but not landed here.
- §7 Q2 (npx/CLI bridge path shares the gate). Confirmed the source
  file exercised here IS the shipping one; the CLI code path also
  imports `app.use(requireAuth)` because it re-exports the same
  `main()`. Explicit CLI-path CI check to follow.

Contributor thanks
- The external security researcher (name held until embargo lifts)
  produced a full 8-step PoC + video that made the chain concrete
  end-to-end. Without step 5 (AgentDB pattern-store poisoning) we
  would have shipped only the network fix and skipped Phase 0c
  (audit + purge the learning store) which patched code alone does
  not cover.
- Dragan Spiridonov, co-author of ADR-166.

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: Dragan Spiridonov <spiridonovdragan@gmail.com>
Co-authored-by: Dragan Spiridonov <dragan@spiridonov.dev>
2026-07-01 10:31:21 -04:00
rUv 0e1d26c11e fix(security): patch CVEs, shell injection, SSRF in ruflo (#2114)
* fix(security): patch CVEs, shell injection, SSRF in ruflo

CWE-78 Shell Injection (9 execSync → execFileSync conversions):
- .claude/helpers/github-safe.js: replace execSync(`gh ${args.join(' ')}`)
  with execFileSync('gh', args) in no-body and non-body code paths; the
  body-with-file path already used a tempfile but still joined args into
  a shell string for the final gh invocation.
- v3/@claude-flow/cli/.claude/helpers/github-safe.js: same fix (init-
  template copy mirrors the dogfood helper).
- v3/@claude-flow/mcp/.claude/helpers/github-safe.js: older variant
  without body-cap; apply execFileSync throughout.
- scripts/audit-supply-chain.mjs: execSync(`npm view "${name}"`) →
  execFileSync('npm', ['view', name, ...]) so a malicious package name
  in the allowlist JSON cannot inject shell commands.
- scripts/track-clones.mjs: execSync(`gh api repos/${repo}/...`) →
  execFileSync('gh', ['api', `repos/${repo}/...`]).
- plugins/ruflo-core/scripts/witness/perf.mjs: six execSync calls in
  benchmark runners (git rev-parse, pnpm pack ×2, npm init, npm pkg
  set, npm install <tarball>, node verify.mjs) converted to
  execFileSync with split argv arrays.
- v3/@claude-flow/browser/tests/e2e/browser-e2e.test.ts: execSync with
  template-literal command string replaced by execFileSync with a
  split-on-whitespace argv array.

CWE-918 SSRF (2 callCloudFunction guards):
- ruflo/src/mcp-bridge/index.js: add assertSafeUrl() before every
  callCloudFunction call — rejects non-HTTPS schemes and private/
  loopback IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, ::1,
  fc/fd ULA, localhost, *.local).
- ruflo/src/ruvocal/mcp-bridge/index.js: identical guard applied to
  the ruvocal variant of callCloudFunction.

npm audit: 0 vulnerabilities (package.json overrides already cover all
known CVEs; confirmed clean on this audit run).

All 10 github-safe injection smoke tests pass. No new test file failures
introduced vs pre-patch baseline (verified via vitest diff).

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(supply-chain): use execFileSync instead of undefined execSync in runCveAudit

The import on line 35 only brought in `execFileSync` from `node:child_process`,
but `runCveAudit` called the never-imported `execSync`, causing a ReferenceError
on every audit run and reporting 2 spurious CVE findings (one per audited package).

Replace `execSync('npm audit ...')` with `execFileSync('npm', [...args])` to
match the already-correct pattern used in `runPublisherTrustAudit` (line 303).
This also provides the CWE-78 shell-injection mitigation already documented in
that function's comment.

Full audit now exits 0: 0 CVE, 0 lockfile, 0 allowlist, 0 typosquat findings.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-23 04:52:41 -04:00
rUv 511b2ac070 feat(codex): finish Codex ↔ Ruflo integration — real codex exec, dual run --worker, tests, CI guard (#1909) (#1911)
* fix(codex): correct codex MCP server subcommand + sync package version

- mcp-bridge: `@openai/codex mcp serve` → `@openai/codex mcp-server`.
  `codex mcp` manages *external* MCP servers (list/get/add/remove);
  `codex mcp-server` is the "start Codex as an MCP server (stdio)"
  command. `codex mcp serve` errors with "unrecognized subcommand
  'serve'", so the `codex` MCP backend never started. Fixed in both
  ruflo/src/mcp-bridge/index.js and ruflo/src/ruvocal/mcp-bridge/index.js.
- @claude-flow/codex: VERSION const was '3.0.0-alpha.8' while
  package.json is '3.0.0-alpha.9' — `--version`/`info` reported the
  wrong version. Synced to alpha.9.

Verified: `codex mcp serve` → error; `codex mcp-server` is valid.
@claude-flow/codex builds clean (tsc), 162 unit tests pass,
`claude-flow-codex` CLI (init/validate/doctor/templates/skills/dual)
all functional.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(codex): W1 — dual-mode orchestrator spawns real `codex exec` for codex workers

Previously `codexCommand` defaulted to `'claude'` ("Both use claude CLI")
and every worker — claude or codex — was launched with Claude-CLI flags
(`-p`, `--output-format text`, `--max-turns`), so "dual-mode" was two
Claude instances. Now:
- `codexCommand` defaults to `'codex'`
- `executeHeadless` builds platform-specific args:
  - claude:  `-p <prompt> --output-format text [--max-turns N] [--model M]`
  - codex:   `exec --sandbox workspace-write --skip-git-repo-check [-m M] <prompt>`
    (PROMPT is positional -> last; `codex exec` runs autonomously)

Remaining in W1 (next iterations): update `.claude/agents/dual-mode/
codex-worker.md` + `codex-coordinator.md` to invoke `codex exec` instead
of `claude -p`; add mocked-spawn smoke test (W4). Tracked: #1909.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(codex): W1 (cont.) — dual-mode agent defs invoke `codex exec`, not `claude -p`

codex-worker.md / codex-coordinator.md described "headless Codex workers"
but every spawn example was `claude -p "..." --session-id X` with
Claude-only flags (--max-budget-usd, --allowedTools, --output-format).
Updated to match the W1 orchestrator change:
- spawn syntax → `codex exec --sandbox <workspace-write|read-only>
  --skip-git-repo-check "<prompt>" &`
- dropped Claude-only flags; note that mixed-platform runs still use
  `claude -p` for Claude workers
- swarm/memory CLI refs `claude-flow@v3alpha` → `ruflo@latest`
- MCP tool refs `mcp__claude-flow__*` / `mcp__ruv-swarm__*` → `mcp__ruflo__*`

W1 now complete (orchestrator + agent defs). dual-orchestrator.md left
as-is — it correctly documents the mixed Claude+Codex split. Smoke test
is W4. Tracked: #1909.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(codex): W2 — `dual run --worker "<platform>:<role>:<prompt>"` + positional template

`dual run` only accepted `--template <name>` / `--config <path>`, so the
custom-worker syntax documented in CLAUDE.md (`dual run --worker
"claude:architect:..." --worker "codex:coder:..."`) didn't exist, and
`dual run feature` (positional) silently fell through to "specify
--template". Now:
- `dual run [template]` — positional alias for `--template`
- `-w, --worker "<platform>:<role>:<prompt>"` (repeatable; platform =
  claude|codex; prompt may contain `:` — split on first two only).
  Workers chain sequentially (each `dependsOn` the previous) unless
  `--parallel-workers`. Unique ids derived from role.
- `parseWorkerSpecs()` exported for testing; clear errors on malformed
  specs / bad platform.
- improved no-args usage text.

build clean (tsc), 162 tests pass. Tracked: #1909 (W2). Tests for
parseWorkerSpecs land in W4.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(codex): W3 — standardize CLI ref claude-flow@alpha -> ruflo; fix MCP-server registration

The dual-mode orchestrator/CLI shelled out to `npx claude-flow@alpha
memory ...`, `claude-flow-codex init` registered the MCP server as
`codex mcp add claude-flow -- npx claude-flow mcp start`, and the
config.toml / migration generators emitted `[mcp_servers.claude-flow]`
with `args = ["-y", "@claude-flow/cli@latest"]` — the wrong package, an
inner-package name, and (in config-toml) **missing the `mcp start`
subcommand** so the server never actually started.

- dual-mode/{orchestrator,cli}.ts: `claude-flow@alpha` -> `ruflo@alpha`
- initializer.ts: `codex mcp add claude-flow -- npx claude-flow mcp start`
  -> `codex mcp add ruflo -- npx ruflo mcp start`; "MCP server (ruflo)
  registered"; `npx @claude-flow/cli mcp start` -> `npx ruflo mcp start`
- generators/config-toml.ts: default server `name: 'ruflo'`,
  `args: ['-y', 'ruflo@latest', 'mcp', 'start']` (adds the missing
  subcommand); `[mcp_servers.ruflo]` in all examples; the
  already-configured check accepts both `ruflo` and legacy `claude-flow`
- generators/{agents-md,skill-md}.ts: `npx @claude-flow/cli ...` -> `npx ruflo ...`
- migrations/index.ts: default emitted server -> `[mcp_servers.ruflo]` /
  `args = ["-y", "ruflo@latest", "mcp", "start"]` (user-provided servers
  pass through unchanged)
- validators/index.ts: suggestion text -> `[mcp_servers.ruflo]`
- tests: updated generator/migration assertions to the new output

`npx vitest run` 162/162 pass. Note: `npm run build` (tsc) currently
fails on an UNRELATED uncommitted WIP file `src/loop/index.ts`
(exactOptionalPropertyTypes on prompt/command) — not from this change;
my files compile clean. Branding-only refs (config-toml header, repo
URLs in generated docs, remaining `@claude-flow/cli` in skill-md examples)
deferred to W7. Tracked: #1909 (W3).

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(codex): W4 — dual-mode tests (parseWorkerSpecs, templates, orchestrator leveling, `dual` wiring)

New tests/dual-mode.test.ts (18 tests, no real workers spawned):
- parseWorkerSpecs: single spec, sequential chaining, --parallel-workers,
  colons-in-prompt, id dedup, whitespace trim, and the 3 error paths
  (too-few-colons / empty-prompt / unknown-platform)
- CollaborationTemplates: feature/security/refactor pipelines — ids,
  platforms, dependsOn chains, task interpolation
- `dual` command wiring: run/templates/status subcommands; `run` exposes
  --worker / --parallel-workers / --template and a positional [template]
- DualModeOrchestrator: safe defaults (codexCommand === 'codex'),
  buildDependencyLevels for linear / fan-in / circular cases

Full suite: 184/184 pass (162 prior + 18 new + 4 loop). `npm run build`
still blocked on the unrelated uncommitted `src/loop/index.ts`. Deferred:
a full `CodexInitializer.initialize()` integration test (it shells out
`codex mcp add`, which would touch the user's real codex config — needs
a mock first). Tracked: #1909 (W4).

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(codex): W5 — generated SKILL.md frontmatter gets version/author/tags

`claude-flow-codex init` / `generate-skill` produced SKILL.md files with
only `name` + `description`, so `claude-flow-codex validate` flagged 3
"Consider adding field: version/author/tags" warnings per skill.

- SkillMdOptions: optional `version` ("1.0.0"), `author` ("rUv"), `tags`
  (defaults to the skill name split on `-`)
- generateSkillMd emits `version: "..."`, `author: ...`, `tags: [...]`
  in the frontmatter; built-in skills (via generateBuiltInSkill) inherit
  the defaults automatically

Verified: a generated built-in skill validates with 0 warnings (was 3);
a bare `generate-skill` stub now has 1 warning ("No examples provided",
expected for a skill with no commands — out of scope here). `init →
validate → doctor` clean on the local build. `npm run build` + 184/184
tests pass. (The ~150 repo `.agents/skills/*/SKILL.md` are generated by
@claude-flow/cli, not this generator — out of scope.) Tracked: #1909 (W5).

Co-Authored-By: RuFlo <ruv@ruv.net>

* ci(codex): W6 — codex-integration-audit guard + workflow

`scripts/audit-codex-integration.mjs` — pure-Node static checks for the
Codex ↔ Ruflo invariants (issue #1909), exits non-zero on any failure:
- `codex` MCP backend uses `mcp-server` (not the invalid `mcp serve`) in
  both ruflo/src/mcp-bridge and ruvocal/mcp-bridge
- @claude-flow/codex VERSION const === its package.json version
- dual-mode orchestrator: `codexCommand` defaults to 'codex', codex
  workers spawn `codex exec --sandbox …`, no 'claude' fallback
- dual-mode agent defs reference `codex exec`, no legacy
  `claude -p … --session-id` worker pattern
- no `claude-flow@alpha` / `@claude-flow/cli@latest` left in codex src
- `dual run` exposes `--worker <spec>` + positional `[template]`,
  `parseWorkerSpecs` exported
- generateSkillMd emits version/author/tags frontmatter
- config.toml generator default MCP server = `ruflo` w/ `mcp start`

`.github/workflows/codex-integration-audit.yml` runs it on PRs / pushes
touching the codex package, mcp-bridge, dual-mode agents, or the script.
No `npm install` needed — build + tests stay in the main CI.

Audit passes on current branch. `npm run build` + 184/184 tests pass.
Tracked: #1909 (W6).

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(codex): claude-flow-codex loop runner — Codex-native /loop equivalent

Codex doesn't expose Claude Code's ScheduleWakeup, so this is a
process-based runner with the same behaviour:
- `loop run [prompt]` — repeated `codex exec` runs (or a custom
  `--command`), bounded by `--max-iterations`/`--interval`, with
  `--dry-run`, model/sandbox options, and a completion-marker-file
  contract
- `loop status [--name <name>]` / `loop stop [--name <name>]`
- state persisted to `.codex/loop/<name>.json`; stop-file aware
- wired into the CLI (`src/cli.ts`), exported (`src/index.ts`), with a
  `./loop` package export and README docs

(Implemented by rUv; landing it here so it isn't sitting dirty in the
worktree while the #1909 loop commits around it. `npm run build` +
184/184 tests pass; `git diff --check` clean.)

Co-Authored-By: RuFlo <ruv@ruv.net>

* chore(codex): W7 — package.json hygiene + repo URLs ruvnet/claude-flow -> ruvnet/ruflo

- package.json: drop dead `"templates"` from `files` (no templates/ dir
  at the package root — templates compile to dist/templates/); repository
  / homepage / bugs URLs -> ruvnet/ruflo; description -> "Ruflo (claude-flow)"
- src/index.ts: PACKAGE_INFO.repository -> ruvnet/ruflo
- src/initializer.ts, generators/{agents-md,config-toml}.ts: generated
  AGENTS.md / config.toml doc URLs -> ruvnet/ruflo (+ "Ruflo:" label)
- README.md: badge + footer URLs -> ruvnet/ruflo
- tests: assertion for the config.toml Documentation line

`npm run build` + 184/184 tests pass; codex-integration audit passes.
(Committed `.agents/AGENTS.md` / `.agents/config.toml` in the repo still
carry old URLs — generated artifacts; the generators are now fixed so
re-init produces ruvnet/ruflo. The published `claude-flow-codex`
package name stays as-is.) Tracked: #1909 (W7).

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-11 11:50:56 -04:00
Reuven f8f4cd4bc7 security: untrack .env files + broaden .gitignore to block .env.*
Untracks 3 .env files that were committed historically:
- ruflo/src/ruvocal/.env (194 lines — chat-ui config, admin tokens, DB URL, etc.)
- ruflo/src/ruvocal/.env.ci (MONGODB_URL)
- v3/goal_ui/.env (Supabase project ID + URL + anon publishable key)

Files remain on disk for local development; only removed from the index.
Gitignore now matches `.env.*` broadly (was only `.env`, `.env.local`,
`.env.*.local`) with an explicit `!.env.example` exception so templates
stay trackable.

Note: this is a non-destructive untrack. Historical commits still
contain the values; rotation of any actual secrets is required.
History rewrite was considered but declined — 4,655 forks retain
independent copies regardless of canonical-history rewrite, and
50 open PRs would all need rebasing.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-04 23:44:43 -04:00
Reuven 8363b558e5 fix(ruvocal): drop generic-runes syntax that broke prod build
The cloudbuild for ruvocal failed at vite-plugin-svelte parse step on
ChatWindow.svelte:434 with `js_parse_error: Unexpected token` at
\$derived<string[]>(IIFE). Svelte 5 runes don't accept generic type
parameters in this position; the documented form is a TypeScript
annotation on the binding.

Changes (all in ChatWindow.svelte):
- \$derived<RouterExample[]>(...)   → activeExamples: RouterExample[] = \$derived(...)
- \$state<string | null>(null)      → activeRouterExamplePrompt: string | null = \$state(null)
- \$derived<string[]>(IIFE)         → lastAssistantToolNames: string[] = \$derived((IIFE)())
- \$derived<RouterFollowUp[]>(...)  → dynamicFollowUps: RouterFollowUp[] = \$derived(...)
- \$derived<RouterFollowUp[]>(...)  → routerFollowUps: RouterFollowUp[] = \$derived(...)
- \$derived<RouterFollowUp[]>(...)  → effectiveFollowUps: RouterFollowUp[] = \$derived(...)

Behavior unchanged — pure syntax fix to satisfy the Svelte 5 parser.

Verified live on flo.ruv.io after deploying revision ruvocal-00039-7dn:
  agent-browser eval → before:"AUTO" after1:"MANUAL" after2:"AUTO"

The autopilot toggle from #1742 now flips the visible label as intended.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-04 22:40:41 -04:00
Reuven 9cfba12abc fix(ruvocal): autopilot AUTO toggle is silent + autopilotMaxSteps setting was dead wiring (closes #1742)
User report: "the auto mode doesn't seem to be working" on https://flo.ruv.io/.

Two real bugs.

### Bug 1: Same label on both states
ChatWindow.svelte:825 had `{$settings.autopilotEnabled ? 'AUTO' : 'AUTO'}`
— both branches the same string. The button color flipped but the text
didn't, so users perceived the toggle as inert. Verified live with
agent-browser: clicking the toggle returns the same innerText "AUTO"
both before and after.

Fixed: `'AUTO' : 'MANUAL'`. Title/aria-label already said
"Autopilot ON / Autopilot OFF"; the visible badge now matches.

### Bug 2: autopilotMaxSteps setting is dead wiring
Settings.ts defines autopilotMaxSteps (default 10). The settings POST
endpoints (/api/v2/user/settings, /settings/(nav)) accept it. But:

- conversation/[id]/+page.svelte never sent it in the chat request body
- conversation/[id]/+server.ts zod schema didn't list it
- runMcpFlow.ts hardcoded `maxLoops = autopilot ? 30 : 10`

Net effect: users who lowered it to save tokens still got 30 loops;
users who raised it for longer chains still got capped at 30.

Fixed by wiring the value end-to-end:
- Page sends `autopilotMaxSteps` from settings
- Server zod schema validates `int [1, 100]`
- TextGenerationContext gains `autopilotMaxSteps?: number`
- runMcpFlow computes `maxLoops = autopilot ? min(autopilotMaxSteps ?? 30, 100) : 10`
- Both settings POST schemas accept the field; GET returns it
- Back-compat: when client doesn't send a value, server defaults to 30
  (matching pre-fix behavior). Non-autopilot mode still capped at 10
  for safety regardless of user input.

Verification:
- Live click reproduction confirmed Bug 1 before the fix
- Typecheck clean on the changed files
- Earlier clientPool.spec.ts (8 cases) still passes

Issue: ruvnet/ruflo#1742

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-04 21:23:53 -04:00
Reuven bd07fe6c9c fix(ruvocal/mcp): skip SSE fallback on 4xx/5xx + rate-limit cooldown (closes #1741)
Symptom: when an upstream MCP server returns HTTP 429, ruvocal's
clientPool.ts always falls back to SSE — which POSTs to the SAME
endpoint and hits the SAME rate limit. Users saw:

  HTTP transport failed: Streamable HTTP error: Error POSTing to
  endpoint: Rate exceeded.
  SSE fallback failed: SSE error: Non-200 status code (429)

Twice the wait, misleading message implying the SSE transport itself
broke.

Fix in src/lib/server/mcp/clientPool.ts:

1. Skip SSE fallback for definitive HTTP failures (4xx/5xx, except 408
   which is recoverable). They will recur on the sibling transport.
2. Throw typed McpRateLimitedError on 429 with status, retryAfterMs,
   serverName fields — callers can build a friendly countdown UI.
3. Honor Retry-After header when upstream provides it (capped at 60s,
   default 5s).
4. Memoize the failure for the cooldown window so subsequent getClient
   calls short-circuit instead of pounding the upstream. Cooldown is
   cleared by evictFromPool() and drainPool().

Tests: 8 vitest cases in clientPool.spec.ts cover skip-on-429,
Retry-After honor, cooldown memoization, skip-on-401/503, fallback on
network/408, and 429 surfaced even when both transports report it.
8/8 pass.

Issue: ruvnet/ruflo#1741

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-04 21:05:45 -04:00
Reuven 9b16981484 docs(ruvocal): rebrand README from HuggingFace chat-ui to RuVocal (RuFlo Web UI)
The README at ruflo/src/ruvocal/ still read as the upstream HuggingChat
README (chat-ui banner, hf.co/chat as the running example, hf.co router as
the default endpoint). Rewritten to orient around RuVocal as RuFlo's Web UI
at flo.ruv.io.

What changed
- New highlights table: parallel MCP tool calls, ~210 RuFlo tools, 6
  curated frontier models via OpenRouter, AgentDB-backed memory,
  Capabilities modal, ruvLLM support, self-hostable
- Quick Start reordered: hosted demo first, then local dev (with the
  OpenRouter env block we actually use), Docker, Cloud Run via cloudbuild
- MCP Tools section shows the 5 RuFlo server groups + π shared brain
- Architecture one-pager points at the actual file paths (MCP bridge,
  WASM worker, toolInvocation.ts, RufloHelpModal, ChatWindow follow-ups)
- "Related" links: parent RuFlo repo, Goal Planner UI, ADR-033, issue
  #1689, upstream chat-ui acknowledgement

Upstream chat-ui content (router/Omni config, theming env vars, MongoDB
options) preserved — those still apply to RuVocal since the SvelteKit
core was forked from chat-ui v0.20.0.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 18:09:23 -04:00
Reuven 71ab7a282d feat(ruvocal): tool-call-aware dynamic follow-up suggestions
After every assistant turn that called MCP tools, the chat now
shows up to 4 contextually relevant follow-up prompt chips —
derived from which tools just ran, not just from the static
example prompts the user clicked initially.

Behavior:
- Static curated follow-ups (from mcpExamples / routerExamples)
  still take priority when the first user message matched an
  example prompt — same UX as before.
- When no static match (any user-typed prompt, any subsequent
  turn), follow-ups are generated by mapping the latest assistant
  message's tool_calls through followUpsForTool().
- Mappings cover the major tool families: memory (store /
  search / retrieve / list), system_status, performance_*,
  agent_spawn / swarm_init, hooks_route, analyze_diff*, github_*,
  hooks_trajectory_*, hooks_security_scan, search, web_research,
  guidance.
- Deduped to 4 max across all tool calls.

Renders in the existing follow-up chip UI — no new component, no
new styles. shouldShowRouterFollowUps relaxed to allow follow-ups
on every assistant turn (not just the first), but only when the
last message is from the assistant and we're not loading.

Closes the gap where typing your own prompt got zero suggestions
even though the assistant clearly demonstrated which tools were
relevant.

svelte-check: 0 new errors.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 17:16:45 -04:00
Reuven d3c0f3c531 fix(docs): correct ruvLLM link to RuVector/examples/ruvLLM subdirectory
Was linking to a non-existent github.com/ruvnet/ruvllm repo. Actual
location is https://github.com/ruvnet/RuVector/tree/main/examples/ruvLLM
— ruvLLM lives as an example/subproject within the RuVector repo.

Updated in both README.md (Web UI emoji table) and
RufloHelpModal.svelte (under-the-hood callout).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 17:08:25 -04:00
Reuven 357338ec34 docs: surface "any model, local or remote" + ruvLLM in Web UI section
Was overselling the curated cloud lineup as "the" supported set.
Reality: any OpenAI-compatible endpoint works (vLLM, Ollama, LM
Studio, Together, Groq, self-hosted). Plus native ruvLLM — RuFlo's
self-learning local model layer with MicroLoRA + SONA — for
fully-offline / privacy-sensitive workflows.

README:
- Reframe the 🧠 row as "Any model, local or remote" — keep the 6
  curated cloud models as defaults, list the supported runtimes
  underneath.
- New 🦾 row dedicated to ruvLLM with link to ruvnet/ruvllm.

RufloHelpModal.svelte:
- Add a green callout below the Models table noting OpenAI-compat
  support for vLLM/Ollama/LM Studio/Together/Groq/self-hosted, with
  a ruvLLM blurb and link.
- Adjusts the existing "All models route through OpenRouter" note
  to "Cloud models route through OpenRouter".

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 17:05:16 -04:00
Reuven e89a3a20d9 docs(readme): move Web UI section after What You Get + emoji feature table
README:
- Web UI section now lives directly after the "What You Get"
  capability table (was wedged between MCP Server install and What
  You Get). Better narrative flow: install paths → what RuFlo gives
  you → live demo to try it.
- Bullets replaced with a 7-row emoji table (🧠 / 🛠️ / ⚡ / 💾 / 📘 /
  🌍 / 🚀) using friendly language: "Tools run in parallel" instead
  of "fan out tool_calls"; "Memory that sticks" instead of
  "persistent storage via AgentDB + HNSW".
- All three domain aliases listed in the heading (flo.ruv.io,
  ruflo.ruv.io, ruvocal.ruv.io). Welcome screenshot moved into a
  collapsed <details> block.

RufloHelpModal.svelte:
- Added "Tool-call cards — what the icons mean" legend (✓ Called
  tool, Step N — M tools completed, Retry, AUTO green/gray).
- Added "Under the hood" 8-row stack table (SvelteKit, OpenRouter,
  ruflo backend, ruvector backend, AgentDB+HNSW+ONNX, rvagent-wasm,
  embedded MongoDB, Cloud Run).
- Added "Domain aliases" section listing all 4 URLs (3 custom + raw
  Cloud Run) with notes on Cloudflare DNS and managed cert.
- Added "Keyboard shortcuts" mini-table (Enter, Shift+Enter, Esc,
  ?worker=1 flag).
- Two extra example prompts added (search+web_research parallel,
  diff analysis on a real PR).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 17:03:41 -04:00
Reuven e813bb1de4 feat(ruvocal): expand RuFlo Capabilities modal
- Header subtitle now reflects the full picture: ~210 MCP tools · 5
  server groups · 18 in-browser WASM tools · 6 frontier models · parallel
  tool calling
- Quick start: default updated from "Gemini 2.5 Flash" → "Claude Haiku
  4.5" (matches deployed config); 5 numbered steps now mention the
  parallel-tool-calling cards and the MCP (n) pill explicitly
- NEW "Models available" section: 6-row table with provider, strength,
  per-model "best for" notes, and a default-row highlight (Haiku 4.5)
- Tool groups reworked with richer per-group intros (e.g. WASM section
  notes the 588 KB bundle size and ?worker=1 flag) and more accurate
  per-tool descriptions
- NEW "Try these" section with 4 copy-paste example prompts in mono
  font that exercise memory, parallel system probing, swarm spawning,
  and route+spawn flows
- Tips expanded from 5 short bullets to 6 deeper ones, including an
  explicit "Parallel tool calls" tip explaining the Promise.all
  behavior and the "Step N — M tools completed" cards
- Resources section adds issue #1689 (UI capabilities & roadmap)
- Modal width bumped from max-w-2xl → max-w-3xl, max-h 85vh → 88vh to
  fit the new sections without crowding

svelte-check: 0 new errors.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 16:48:39 -04:00
Reuven 43f4173ae1 fix(ruvocal): make WASM worker self-contained, drop $app/environment
Build v8 failed with:
  Rollup failed to resolve import "__sveltekit/environment" from
  "/app/node_modules/@sveltejs/kit/src/runtime/app/environment/index.js"
  file: /app/src/lib/wasm/workerClient.ts

Root cause: wasm.worker.ts imported loadWasm from $lib/wasm/index, which
transitively pulls $app/environment — a SvelteKit page-bundle module
not resolvable in worker context.

Inline a worker-safe mock WasmMcpServer + WasmGallery (same surface as
the index.ts mock used by the main thread today). The real rvagent_wasm
bundle still ships in static/wasm/; integrating it into the worker via
dynamic import is tracked in #1689.

Local `npm run build` now succeeds. Worker file bundles cleanly via
Vite's `new URL("./wasm.worker.ts", import.meta.url)` resolver.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 11:58:13 -04:00
Reuven 4b6186c70d feat(ruvocal): off-main-thread WASM MCP via Web Worker (opt-in)
Adds a Web Worker that owns the WASM MCP server and exposes a
JSON-RPC + gallery surface to the main thread via postMessage.

- src/lib/wasm/wasm.worker.ts — owns WasmMcpServer + WasmGallery,
  handles the JSON-RPC initialize handshake during load, dispatches
  callMcp / gallery.{list,search,get,setActive,getActive,count,
  getCategories,listByCategory} requests
- src/lib/wasm/workerClient.ts — main-thread RPC client with
  auto-incrementing IDs, Vite `new URL(..., import.meta.url)` worker
  loader, idempotent ensureReady(), feature-flag (localStorage
  "ruflo:wasm-worker"=true OR ?worker=1)
- src/lib/stores/wasmMcp.ts — callMcp now routes through the worker
  when the flag is enabled, falls back to the in-process path
  otherwise

Behind a flag so existing behavior is unchanged for default users
while we validate the worker path. Tracking issue #1689 lists the
remaining gallery / IDB integration work to make worker mode the
default.

svelte-check: 0 new errors.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-01 11:51:54 -04:00
rUv 6e68c93ac6 feat(ruvocal): integrate WASM-MCP layer from RuVector upstream (ADR-033) (#1687)
* feat(ruvocal): add WASM-MCP layer from RuVector upstream (ADR-033)

Adds in-browser WASM MCP server (rvagent-wasm) to local ruvocal:
- src/lib/wasm/ — loader, IndexedDB persistence, capability tests
- src/lib/components/wasm/GalleryPanel.svelte — template gallery UI
- src/lib/components/FoundationBackground.svelte
- src/lib/stores/wasmMcp.ts — Svelte store for WASM MCP lifecycle
- src/lib/constants/rvagentPresets.ts
- src/lib/server/textGeneration/mcp/wasmTools.test.ts
- static/wasm/ — compiled rvagent_wasm.{js,wasm} (~588 KB)
- config/branding.env.example

Pure additions; no existing files modified in this commit. Source:
ruvnet/RuVector/ui/ruvocal main as of 2026-05-01.

See ruflo/docs/adr/ADR-033-RUVOCAL-WASM-MCP-INTEGRATION.md for the
overlay strategy and integration plan.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(ruvocal): wire WASM-MCP types into stores and Settings/Tool

Overlays from RuVector upstream needed for GalleryPanel.svelte:
- src/lib/stores/mcpServers.ts — adds WASM_SERVER_ID, loadWasmTemplate,
  getWasmGalleryTemplates exports
- src/lib/types/Settings.ts — adds wasm-related settings fields
- src/lib/types/Tool.ts — adds wasm tool types

svelte-check: 0 new errors introduced. 104 pre-existing errors in
untouched files (RuFloUniverse, migrations, server tests) are
unchanged and out of scope for this ADR.

ADR-033

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(ruvocal): add Cloud Run deploy stub + document infra prereqs (ADR-033)

- cloudbuild.yaml builds ruvocal Dockerfile and deploys to Cloud Run.
  Wired to existing ruv-dev secrets (ANTHROPIC_API_KEY, GOOGLE_AI_API_KEY,
  OPENROUTER_API_KEY) per ADR-029 pattern.
- ADR-033 updated with deployment plan and explicit MongoDB blocker:
  Cloud Run can't run Mongo natively. Operator must provision either
  Atlas free tier (M0) or a Cloud Run sidecar before first deploy.
- Notes that the thin src/chat-ui/Dockerfile wrapper is unsuitable for
  this integration — it can only patch the upstream HF base image with
  static files, not include compiled WASM source.

Deploy is staged but blocked on mongo. Local validation already passed:
WASM MCP server initializes with 18 tools, IDB persistence works.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruvocal): use INCLUDE_DB=true for embedded mongo on Cloud Run

The local Dockerfile already supports INCLUDE_DB=true via the
multi-stage 'local_db_true' path that copies mongod from the
official mongo:7 image. Setting --build-arg INCLUDE_DB=true and
MONGODB_URL=mongodb://localhost:27017 makes Cloud Run deploys
self-contained — no external Atlas, no sidecar, no service-YAML
rewrite.

Tradeoff: Cloud Run filesystem is ephemeral, so chat history is
lost on cold starts. Acceptable for validating the WASM-MCP
integration; production deploys should follow up with Atlas
or persistent storage (tracked separately).

ADR-033

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruvocal): enable DOCKER_BUILDKIT for Cloud Build

Dockerfile uses COPY --link and RUN --mount=type=cache (BuildKit
syntax). Without DOCKER_BUILDKIT=1 in the build env, Cloud Build's
docker daemon parses the file in legacy mode and fails with
"Unknown flag: link" before any build step runs (build baf645f5
died at 17 seconds).

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(adr-033): document Cloud Run deployment outcome

Records what's confirmed working on the deployed instance at
https://ruvocal-875130704813.us-central1.run.app and surfaces the
known /api/v2/models 500 (HF chat-ui SSR edge case unrelated to
WASM integration). WASM bundle reachable at /wasm/* with 200 OK.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruvocal): anchor .dockerignore models/** so route dirs survive

The unanchored `models/**` pattern was matching ANY path containing
'models/' — including src/routes/api/v2/models/, src/routes/models/,
and src/lib/server/models/__tests__/. SvelteKit's production build
inside Docker was therefore missing those routes entirely, while
local `npm run build` outside Docker built them correctly. Result:
Cloud Run revisions returned 404 for /api/v2/models which the layout
load function turned into a 500 on the homepage.

Anchoring with a leading slash limits the pattern to the top-level
models/ directory only. This unblocks the deployed homepage and
fully validates the WASM-MCP integration in production.

ADR-033

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruvocal): remove models/** from .dockerignore entirely

Earlier anchor fix `/models/**` still produced images missing the
/api/v2/models route. Confirmed by `docker run --entrypoint=ls` against
gcr.io/ruv-dev/ruvocal:v2 — /app/build/server/entries/endpoints/api/v2/
did not exist, manifest contained 0 references to /api/v2/models, while
/api/v2/conversations and /api/v2/user were present.

Removing the line entirely. The placeholder models/ dir at the repo root
contains only a single 'add-your-models-here.txt' file — letting it into
the build context costs nothing.

ADR-033

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruvocal): cloudbuild deploy preserves env vars across rebuilds

Each rebuild was nuking DOTENV_LOCAL, OPENAI_API_KEY (mapped from
OPENROUTER_API_KEY secret), and other manual config because
`gcloud run deploy --set-env-vars` REPLACES rather than merges.

Drop the env/secret args from the deploy step entirely so subsequent
deploys keep whatever was set via `gcloud run services update`.
Initial env config is set out-of-band per ADR-033.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruvocal): add .gcloudignore so model routes survive Cloud Build upload

ROOT CAUSE of the production 500 errors: gcloud builds submit
falls back to .gitignore when no .gcloudignore is present.
.gitignore line 16 is `models/*` (unanchored), which matches
EVERY models/ directory in the tree:
  - src/routes/api/v2/models/* (REAL ROUTES)
  - src/routes/models/* (REAL ROUTES)
  - src/lib/server/models/__tests__/ (tests)

Result: source tarballs uploaded to Cloud Build had EMPTY models/
directories. Production builds shipped without /api/v2/models,
/api/v2/models/refresh, /api/v2/models/[namespace], etc. The
homepage Layout.load called /api/v2/models which 404'd, the layout
chain converted that to a 500, and /favicon.ico (also unmatched at
root, since the assets/ exclusion is unrelated) returned 500 too.

Verified by `gsutil cp` of build c07ba163 source tarball:
  $ tar tzf build-source.tgz | grep models
  src/routes/models/        (empty dir)
  src/routes/api/v2/models/ (empty dir)

After adding .gcloudignore, the v4 source tarball includes all
+server.ts files for those routes.

ADR-033

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(adr-033): record provider config + ruvocal.ruv.io custom domain

- Gemini 2.5 Flash via Google's OpenAI-compatible endpoint with
  GOOGLE_AI_API_KEY (HF router 401 was due to user-token vs router
  auth mismatch; OpenRouter base URL × HF token combo also failed)
- ruvocal.ruv.io mapped to Cloud Run, Cloudflare CNAME unproxied so
  Google manages the cert; 15-30 min provisioning window

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(mcp-bridge): 120s initialize timeout for Cloud Run cold starts

Bridge was hitting a hardcoded 30s RPC timeout on `initialize` while
ruflo and ruvector backends took 45-60s to boot on Cloud Run cold
start (full claude-flow / ruvector kernel + npx fetch). Backends
were marked "failed to start" but actually came up after the
timeout fired and ran as zombies — bridge served 0 backend tools.

Init gets 120s; other RPC methods stay at 30s (post-init they're
snappy).

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(ruvocal): replace HF default examples with RuFlo capabilities

Welcome screen now shows 8 prompts that exercise actual ruflo / ruvector
MCP tools (swarm spawn, memory save/recall, task routing, diff analysis,
system status, WASM gallery, GOAP planning, neural training) instead of
HuggingChat defaults like "Generate an image" or "Latest world news".

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(ruvocal): add help icon + RuFlo capabilities modal

Question-mark icon next to the theme toggle in the sidebar opens a
modal with:
- Quick-start instructions (4 steps)
- All 6 tool groups (Memory, Agents, Intelligence, DevTools, Core,
  WASM) as expandable sections with per-tool descriptions
- 5 tips (MCP pill, AUTO toggle, trajectories, memory, prefixes)
- Resource links to ruflo, ADR-033, ruvector

Closes the user-visible "system doesn't seem to know its tools" gap
by surfacing what's available without forcing the user to open
Settings → Application.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(ruvocal): replace router-mode default examples with RuFlo themes

The welcome screen has TWO example sources: mcpExamples (when all
base MCP servers are enabled) and routerExamples (otherwise). Earlier
commit only rewrote mcpExamples, leaving HF defaults like "HTML game"
visible whenever users hadn't toggled all servers on.

Router examples now cover: coding swarms, memory/recall, migration
planning, diff review, HNSW, topology choice, horizon tracking, WASM
vs bridge tools — all answerable without tool calls if needed.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(mcp-bridge): return structured error on empty search query

When Gemini emits a `search` tool_call with no `query` field, the
bridge was sending an empty prompt to Gemini's generateContent
endpoint and returning the resulting 400 verbatim. The model then
treated it as opaque and gave up.

Validate query upfront and return a hint object the model can use
to retry with a real query.

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(readme): link banner to flo.ruv.io and add UI Beta button

The Ruflo banner image now links to the live UI at flo.ruv.io. A new
"Try the UI Beta" badge sits below it for explicit click-through.
The Cloud Run cert is still propagating but DNS resolves; CNAME is
unproxied through Cloudflare so Google can issue the managed cert.

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-01 11:40:45 -04:00
rUv 147a4b0c63 fix: comment out empty MODELS/TASK_MODEL placeholders, allow host.docker.internal in URL safety
- Dockerfile: sed-comment empty MODELS= and TASK_MODEL= so .env.local overrides work via dotenv-cli -c
- urlSafety: add host.docker.internal to allowed HTTP hostnames for Docker MCP bridge

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-06 15:22:29 +00:00
rUv 48b70afca3 fix: address P0-P2 bugs from ADR-059 triage
P0: Remove obfuscated preinstall script that deleted npm cache (#1261)
P1: Fix 13 MCP array schemas missing `items` field (#1294)
P1: Fix hooks relative paths — resolve via git rev-parse at runtime (#1259, #1284)
P1: Wire AgentDB ControllerRegistry into CLI init (#1264)
P2: Update all branding from "Claude Flow" to "RuFlo" (#1253, #1254, #1280)
P2: Fix web_search empty query validation in MCP bridge
P2: Fix RVF settings duplication via findOneAndUpdate dedup

34 files changed across CLI, MCP bridge, and Chat UI.
All tests pass: tsc clean, Docker build clean, conversation isolation verified,
MCP health check OK, asset 404s resolved.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-05 18:17:30 +00:00
rUv 29d52dfc22 feat(ruflo): add RuFlo Chat UI with ruvocal fork, MCP bridge, and Docker setup
Adds the complete RuFlo Chat UI system based on a SvelteKit fork (ruvocal)
of HuggingFace Chat UI with custom branding, RVF document store replacing
MongoDB, MCP bridge with 200+ tools, animated RuFloUniverse welcome screen,
and Docker Compose orchestration.

Key changes:
- RVF JSON document store with ObjectId cross-package compatibility fix
- MCP bridge proxy (Express) supporting Gemini, OpenAI, and tool execution
- RuFlo branding: Logo, favicons, welcome animation, centered dot cluster
- URL safety patch for Docker-internal HTTP service names
- CORS fix via relative PUBLIC_ORIGIN asset paths

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-05 18:01:09 +00:00