Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-24.04]
go: ["1.25.x"]
go: ["1.26.x"]
steps:
- uses: actions/checkout@v6

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cli-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.25.x"
go-version: "1.26.x"
cache: false
- name: go vet (cli + internal)
run: go vet ./cli/... ./internal/...
Expand All @@ -77,7 +77,7 @@ jobs:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.25.x"
go-version: "1.26.x"
cache: false
- name: build matrix
run: bash test/cli/build-matrix.sh
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Comprehensive programmatic E2E suite (test/e2e). Builds Orva, runs it, and
# drives every domain + the CLI + the AI agentic loop (via a mock LLM — no real
# provider key needed). Scenarios that require sandboxed function invocation
# (real nsjail) self-skip in CI, where nested sandboxing is unavailable.
name: e2e

on:
push:
branches: [main, dev]
pull_request:
workflow_dispatch:

jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Build orva (server + CLI)
run: make build

- name: Start Orva
run: |
mkdir -p "$RUNNER_TEMP/orva-data"
ORVA_DATA_DIR="$RUNNER_TEMP/orva-data" ./build/orva serve > "$RUNNER_TEMP/orva.log" 2>&1 &
echo $! > "$RUNNER_TEMP/orva.pid"
# Wait for health (the API/DB come up without nsjail).
for i in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8443/api/v1/system/health >/dev/null 2>&1; then
echo "orva healthy"; break
fi
sleep 1
done
curl -fsS http://127.0.0.1:8443/api/v1/system/health || (cat "$RUNNER_TEMP/orva.log"; exit 1)

- name: Run E2E suite
run: |
ADMIN_KEY="$(cat "$RUNNER_TEMP/orva-data/.admin-key")"
python3 test/e2e/run.py --url http://127.0.0.1:8443 --api-key "$ADMIN_KEY"

- name: Show checklist
if: always()
run: cat test/e2e/CHECKLIST.md || true

- name: Upload server log
if: always()
uses: actions/upload-artifact@v4
with:
name: orva-server-log
path: ${{ runner.temp }}/orva.log
if-no-files-found: ignore
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:

- uses: actions/setup-go@v6
with:
go-version: "1.25.x"
go-version: "1.26.x"
# No build cache — every release builds from scratch so we
# can't accidentally ship a stale toolchain artifact.
cache: false
Expand Down Expand Up @@ -137,7 +137,7 @@ jobs:

- uses: actions/setup-go@v6
with:
go-version: "1.25.x"
go-version: "1.26.x"
# No build cache — every release builds from scratch so we
# can't accidentally ship a stale toolchain artifact.
cache: false
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ modelcontextprotocol-sdk-*.tgz
orva-backup-*
reddit.md
**/__pycache__/
.gstack/

# Design-tooling artifacts (impeccable critique snapshots)
.impeccable/
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Orva

Self-hosted Function-as-a-Service (FaaS) for homelab and on-premises use. Users write JavaScript (Node 22/24), Python (3.13/3.14), or TypeScript functions; Orva deploys them into nsjail sandboxes and exposes them over HTTP with a built-in dashboard, CLI, and MCP server.
Self-hosted Function-as-a-Service (FaaS) for homelab and on-premises use. Users write JavaScript (Node 22/24), Python (3.13/3.14), or TypeScript functions; Orva deploys them into nsjail sandboxes and exposes them over HTTP with a built-in dashboard, CLI, MCP server, and an in-product AI chat assistant (the **AI** sidebar section) that operates the instance end-to-end via in-process tool calling (BYO provider keys, embedded Bifrost gateway).

## Quick Start

Expand Down
1 change: 1 addition & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ A single muted violet accent on a deep violet-tinted near-black, with cool gray-
- **Border** (`#2D2B42`, ≈`oklch(28% 0.045 277)`): every divider, every card outline, every input stroke. The system reads layers through borders, not shadows.
- **Foreground** (`#FFFFFF`): currently pure white. **Tint to `#F4F2FA` (≈`oklch(96% 0.01 290)`)** to match the rest of the palette's hue discipline; the eye reads pure white as harsh against a violet-tinted dark. The `--color-foreground-strong: #FFFFFF` token is reserved for text on saturated brand-colour surfaces (primary CTA labels, on-violet badges); the default `--color-foreground` may shift to a tinted off-white in a future palette pass without breaking those high-contrast sites.
- **Foreground Muted** (`#A3A3B3`, ≈`oklch(70% 0.018 280)`): all secondary text, icon defaults, table cell content one rank below primary. The cool gray-violet is intentional.
- **Link** (`--color-link: #8b7bd8`): hyperlinks inside rendered prose (the AI chat markdown, Docs). A lighter, less-saturated lift of the violet accent so links read as interactive without competing with primary CTAs. Inline code in the same prose uses `--color-surface-hover` as its chip background.

### Status (semantic, used at low chroma)
- **Success** (`#22C55E`, ≈`oklch(73% 0.21 144)`): only for terminal-success states. Pair with the recommended `success/15` tint background and `success/30` border (see Named Rule below).
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RUN npm ci --no-audit --no-fund
COPY frontend/ ./
RUN npm run build

FROM golang:1.25-bookworm AS go
FROM golang:1.26-bookworm AS go
WORKDIR /src
# go.mod / go.sum live at the repo root since the v2026.05.12 CLI split.
COPY go.mod go.sum ./
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ The Build info card at the top of Settings shows the running release's version,
| **Version diff** | Side-by-side source diff between any two past deployments — in the dashboard (CodeMirror merge view) or `orva diff <fn>` for git-style unified output in the terminal. |
| **MCP server** | 70 tools at `/mcp` — any MCP client (Claude Code, Cursor, etc.) can create functions, deploy code, manage secrets, browse KV, and read logs. |
| **OAuth 2.1** | Add Orva as a custom connector in claude.ai or other OAuth-capable MCP clients — no API key copy-paste needed. |
| **Built-in AI assistant** | An in-product agentic chat (the dashboard's **AI** section) that operates your instance end-to-end — create and deploy functions, read logs, manage secrets and routes — using the same tools as the MCP server, in-process. Bring your own provider key (OpenAI, Anthropic, or any OpenAI-compatible endpoint); writes can be gated behind per-conversation approval. |
| **16 templates** | Stripe webhooks, GitHub events, JWT auth, OAuth, CSV→JSON, URL shortener, and more — pickable in the editor. |

---
Expand Down Expand Up @@ -303,6 +304,11 @@ From there an AI agent can create functions, deploy code, invoke them, read logs
browse KV state, and pull the full Orva reference docs — all without leaving the chat. Works
with Claude Code, Cursor, and any OAuth-capable MCP client like the claude.ai web UI.

Prefer not to wire up an external client? The dashboard has a **built-in AI assistant** (the
**AI** section) that does the same thing in-product: bring your own provider key (OpenAI,
Anthropic, or any OpenAI-compatible endpoint) and chat with an agent that operates your instance
through the same tools, with optional human approval before any write.

---

## CLI
Expand Down
10 changes: 9 additions & 1 deletion backend/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Backend

Go 1.25. Module: `github.com/Harsh-2002/Orva` (rooted at the repo, not at
Go 1.26 (bumped from 1.25 — the embedded Bifrost AI gateway requires go ≥1.26; the Dockerfile + all CI workflows pin golang 1.26 in lock-step). Module: `github.com/Harsh-2002/Orva` (rooted at the repo, not at
`backend/`). SQLite with no CGO (`modernc.org/sqlite`).

The server binary built from `backend/cmd/orva` is the daemon (`orva serve`)
Expand Down Expand Up @@ -53,6 +53,9 @@ go vet ./...
| `cli` | Shared `Client` + `Config` for CLI subcommands |
| `backup` | `SnapshotDB` / `ArchiveTo` / `RestoreFrom` helpers |
| `version` | Single source of truth for the version string |
| `ai` | In-product AI chat assistant. `Manager` (service layer) wires the SQLite store, the secrets cipher (provider-key encryption), the in-process tool registry, the embedded Bifrost LLM gateway (`ai/llm`), and the agentic loop (`ai/agent`). Served at `/api/v1/ai/*` by `server/ai_handler.go` (SSE for chat/approval). The agent's `defaultSystemPrompt` const lives in `ai/manager.go` — it's a Go **raw string, so it must stay backtick-free** (escape any fenced-code examples by description, not literal ```). |
| `ai/llm` | Wraps the embedded Bifrost gateway (`github.com/maximhq/bifrost/core`) behind neutral types + a normalized event stream. `Account` resolves BYO keys from `ai_provider_configs` live. Thinking levels → `ChatReasoning`. |
| `ai/agent` | Agentic loop (≈25-iteration budget): stream model turn → emit SSE deltas → detect tool calls → approval-gate writes → in-process dispatch → feed results back. Decoupled from `mcp` (takes tools + a dispatcher). Two independent gates: the per-conversation **approval policy** (`all_writes` / `destructive_only` / `auto`, checked in `approvalNeeded`) and a separate code-enforced `confirm=true` requirement on destructive tools. |

The canonical UUIDv7 generator (`ids`) and HTTP client (`client`) live at **repo-root** `internal/ids/` and `internal/client/` — shared with the slim CLI codebase, not under `backend/internal/`.

Expand All @@ -74,6 +77,8 @@ Key files: `deploy.go`, `diff.go`, `functions.go`, `invoke.go`, `logs.go`, `cron

**Streaming wire protocol**: `response_start` → `chunk` (base64 body data) → `response_end` frames over the worker's stdin/stdout pipe. `proxy.Forward()` owns the write-loop.

**Shared tool registry (single source of truth)**: every operator tool is declared once via `regAddTool` (`mcp/agent_registry.go`). That call registers the tool with BOTH the external MCP server (`server.go`, unchanged behavior) AND the in-process agent registry (`mcp.BuildAgentRegistry(deps, perms)`, gated to the principal's perms). The internal AI agent dispatches these tools directly as Go calls — no MCP transport, no HTTP. Same name/description/schema/destructive-hint feed both fronts, so they never drift. When adding a tool, use `regAddTool(rc, perm, def, handler)` inside a `register*Tools(rc *regCtx)` function — never bare `mcpsdk.AddTool`.

## Middleware Chain

`CORS → BodySizeLimit → Auth → RequestID → Logger → Handler`
Expand All @@ -90,4 +95,7 @@ SQLite WAL mode. All migrations in `internal/database/migrations.go` — additiv
- `execution_requests` has **no FK** to `executions` (intentionally dropped to fix async insert ordering); manual cascade runs in `DeleteExecution`.
- TypeScript deploys: after a successful `tsc`, the function's `Entrypoint` is updated to `dist/handler.js` in the DB. The validator on re-deploy checks for the source `.ts` file, not the compiled output.
- Zombie nsjail fix: `cmd.Wait()` is centralized in `Spawn` via `waitDone chan struct{}`; never call `Wait()` on the sandbox `cmd` anywhere else.
- **AI conversation editing is destructive-tail:** editing or deleting a chat message (`EditMessage` / `DeleteMessage` in `server/ai_handler.go`, backed by `database.DeleteMessagesFromSeq`) truncates the conversation at that message's `seq` — it deletes that message and every message + tool call after it, then (for edit) re-runs the turn. There is no branching history; the tail is gone. `Regenerate` is the same truncate-then-rerun on the last assistant turn.
- **AI turns are one-per-conversation:** the `ai.Manager` holds a keyed try-lock (`tryLockConv`/`unlockConv`) acquired by every mutating entry point (Chat, Resume, RegenerateLast, EditAndResend, DeleteMessageFrom). An overlapping turn on the same conversation is rejected — SSE `error` for streaming paths, `ai.ErrConversationBusy` → 409 for the JSON delete. `database.InsertMessage` assigns `seq` atomically inside the INSERT (`MAX(seq)+1` subquery); never split it back into a SELECT-then-INSERT.
- **AI gateway lifecycle:** `ai.Manager.Close()` releases the embedded Bifrost pools and is called from `Server.Shutdown` (via `s.router.ai`). The gateway is built lazily and rebuilt on provider-config change (`invalidateClient`).
- **Docs single source:** `docs/reference.md` is the canonical Orva reference markdown (~53 KB). `make docs-embed` syncs it to `backend/internal/mcp/reference.md` (embedded by the `get_orva_docs` MCP tool) and `frontend/public/docs.md` (served at `/docs.md` for the dashboard's Copy as Markdown button). Edit the canonical file then run `make docs-embed`; the Vue Docs page is the rendered version (separate templates) and must be updated alongside if content changes.
Loading
Loading