diff --git a/AGENTS.md b/AGENTS.md index 6eed3651c..bfc943f1d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,67 +1,69 @@ -# Workbench — Agent Guide - -Workbench is a client of [Interchange](https://github.com/faremeter/interchange). -The hub mounts only stock `@intx/hub-api` routes plus a set of Corbits -libraries, each mounted once (`@corbits/mailbox`, `@corbits/memory`, -`@corbits/artifacts`, `@corbits/webhooks`, `@corbits/cron`, `@corbits/mcp`, `@corbits/agent-token`, an -OAuth library on `@corbits/oauth-core`); any other hub mount is cutover debt with a Linear -issue, never a pattern to extend. A workbench is a plain -Interchange tenant, nothing more. The web client sets itself up over stock -routes on start (auth → tenant → definitions/credentials/grants); the hub -never seeds data on a client's behalf. - -## Ground rules - -- **Interchange is the platform.** Never reimplement credential resolution, - agent launch, session orchestration, inference, ID generation, or the - workflow runtime — consume `@intx/*` as published packages. `vendor/intx` - holds hand-copied fallbacks for capabilities not yet published, tracked - with kill dates in [VENDORED.md](VENDORED.md); the upstream repository is - never modified. -- **Apps stay generic; packages own the domain.** A product rule inside - `apps/*` belongs in a package. `workflows/*` are plain npm-shaped packages, - deployable to any stock Interchange hub through the stock deploy route — - this repo does not publish them to npm. -- **`packages/worker` is the default harness** — the one definition package a - bench's worker, and any agent Workbench creates, deploys on; it is trim - (index.ts + system-prompt.ts) with no hub-calling code. -- **No fallbacks.** Cut over cleanly — never leave a legacy path beside a - new one. Config/manifest objects are explicit literals; the one exception - is an optional key under `exactOptionalPropertyTypes` (see below), where - `...(x !== undefined ? { k: x } : {})` is the only correct way to omit it. -- **Parse at every trust boundary.** arktype schemas for env, request - bodies, and external data; never `as T` untrusted input. -- **Custom DB tables live on their own Postgres schema**, with foreign keys - back to Interchange's tenant/principal tables. Each package ships one - idempotent migration, which the hub applies itself at boot. -- **Ancestor-walkable except grants.** Credentials, tools, and definitions - inherit down the tenant tree; grants never do. -- **This repo is public.** No secrets or credentials, ever — `.env.example` - is the only tracked env file. Anything sensitive (client names, internal - context, infra/deploy rulings) goes in Linear, not in commits, PRs, or - docs. -- **Core UI components live in - [corbitsdev/react-ui](https://github.com/corbitsdev/react-ui).** Build - reusable components there; only workbench-specific composition lives here. - -## Working conventions - -- `bun run check` (typecheck, lint, fmt, test) must pass before every - commit. There are no custom structural check scripts; a rule lives in - the code or schema itself or it does not exist. +# AGENTS.md + +Repository-wide rules. `apps/*` have their own AGENTS.md with the rules for that area. + +## Commands + +- `bun install` +- `bun run dev`: hub + web client (needs `.env`, see `.env.example` and [docs/local-dev.md](docs/local-dev.md)) +- `bun run check`: typecheck, lint (`oxlint`), format check (`oxfmt --check`), and unit tests +- `bun run test`: unit tests across workspaces +- `bun run test:e2e`: integration and browser e2e suites (need `DATABASE_URL`) +- `bun run reset`: wipe local state (local database only) + +## Boundaries + +- Interchange is stock. Code lives in `apps/*`, `packages/*`, and `workflows/*`. +- `vendor/intx` holds hand-copied fallbacks for capabilities not yet published to npm, tracked with kill dates in [VENDORED.md](VENDORED.md); the upstream repository is never modified. + +## Architecture + +- **Stock first.** Before writing logic, look for an Interchange route or primitive that already does it (see the reference below). Never reimplement credential resolution, agent launch, session orchestration, inference, ID generation, or the workflow runtime — consume `@intx/*` as published packages. +- **Hub mounts only stock + Corbits libraries.** The hub mounts stock `@intx/hub-api` routes plus one Corbits mount block (`@corbits/mailbox`, `@corbits/memory`, `@corbits/artifacts`, `@corbits/webhooks`, `@corbits/cron`, `@corbits/mcp`, `@corbits/agent-token`, and OAuth login/refresh on `@corbits/oauth-core`); any other hub mount is cutover debt with a Linear issue, never a pattern to extend. +- **A workbench is a plain Interchange tenant, nothing more.** The web client converges its own tenant, workflow deployments, credentials, and grants over stock routes on start; the hub never seeds data on a client's behalf. +- **Apps stay generic; packages own the domain.** A product rule inside `apps/*` belongs in a package. `workflows/*` are plain npm-shaped packages, deployable to any stock Interchange hub through the stock deploy route — this repo does not publish them to npm. +- **`packages/worker` is the default harness** — the one definition package a bench's worker, and any agent Workbench creates, deploys on; it is trim (`index.ts` + `system-prompt.ts`) with no hub-calling code. +- **Ancestor-walkable except grants.** Credentials, tools, and definitions inherit down the tenant tree; grants never do. +- **Custom DB tables live on their own Postgres schema**, with foreign keys back to Interchange's tenant/principal tables. Each package ships one idempotent migration, which the hub applies itself at boot. A custom table that gets deleted is hard-removed: delete the schema code, no drop migration. Cutovers are breaking; reset the database. +- **Core UI components live in [corbitsdev/react-ui](https://github.com/corbitsdev/react-ui).** Build reusable components there; only workbench-specific composition lives here. +- **This repo is public.** No secrets or credentials, ever — `.env.example` is the only tracked env file. Anything sensitive (client names, internal context, infra/deploy rulings) goes in Linear, not in commits, PRs, or docs. + +## Interchange reference + +Check these for a stock route or primitive before touching the hub, sidecars, or workflows. Links follow upstream main; the commit we run is the stock ref in [VENDORED.md](VENDORED.md). + +- [LAYOUT.md](https://github.com/faremeter/interchange/blob/main/LAYOUT.md), [CONVENTIONS.md](https://github.com/faremeter/interchange/blob/main/CONVENTIONS.md): where things live and how upstream writes code +- [ARCHITECTURE.md](https://github.com/faremeter/interchange/blob/main/docs/ARCHITECTURE.md): hub, sidecar, and workflow run model +- [WORKFLOW_AUTHORING.md](https://github.com/faremeter/interchange/blob/main/docs/WORKFLOW_AUTHORING.md): how workflows are written +- [workflow-lifecycle-policy.md](https://github.com/faremeter/interchange/blob/main/docs/workflow-lifecycle-policy.md), [SIDECAR_PLACEMENT.md](https://github.com/faremeter/interchange/blob/main/docs/SIDECAR_PLACEMENT.md): lifetimes, capacity release, and where sidecars run +- [API.md](https://github.com/faremeter/interchange/blob/main/docs/API.md), [ROUTES.md](https://github.com/faremeter/interchange/blob/main/docs/ROUTES.md): the stock routes the web client and hub use +- [INBOUND_MAIL_POLICY.md](https://github.com/faremeter/interchange/blob/main/docs/INBOUND_MAIL_POLICY.md), [MESSAGE.md](https://github.com/faremeter/interchange/blob/main/docs/MESSAGE.md): mail routing behind the webhook and cron bridges +- [AUTH.md](https://github.com/faremeter/interchange/blob/main/docs/AUTH.md), [CREDENTIALS.md](https://github.com/faremeter/interchange/blob/main/docs/CREDENTIALS.md), [GIT_ACCESS.md](https://github.com/faremeter/interchange/blob/main/docs/GIT_ACCESS.md): sessions, the vault, and git tokens + +## Code + +- Match existing style. Every package under `packages/*` needs a `LICENSE` file (LGPL-2.1). +- Comments say why, when the code cannot; never what it does. +- `async`/`await` with `try`/`catch`; report every caught error through `reportError` from `@corbits/error-sink` — never a bare `catch {}`. +- Parse at every trust boundary: arktype schemas for env, request bodies, and external data; never `as T` untrusted input. +- No fallbacks. Cut over cleanly — never leave a legacy path beside a new one. Config/manifest objects are explicit literals; the one exception is an optional key under `exactOptionalPropertyTypes: true`, where `...(x !== undefined ? { k: x } : {})` is the only correct way to omit it rather than assigning `undefined`. +- A package's `browser-safe` subpath never imports a server-only dependency (`postgres`, `drizzle-orm`, `hono`, any `@intx/*`). +- A dependency declared in the root `catalog` is consumed as `catalog:`. +- Leave no dead code, unused exports, or aliases behind. + +## Commits and pull requests + - Worktrees live in `.worktrees/`; branch = `cl--`. -- Commit sequence per change: tests first ("Add tests for X"), then - implementation ("X: what changed"), then docs ("Update docs: X"). One - logical change per commit; commit messages are written for a public - audience. -- Bun loads repo-root `.env`, so an unset test inherits - `HUB_DATA_DIR=.data/hub` inside this work tree — the hub's git-on-disk - init can then walk up to the enclosing `.git` and land a genesis commit - on the working branch. Tests that boot the hub or touch seed/deploy git - paths must call `installDisposableHubDataDir()` from - `e2e/lib/disposable-hub-data-dir.ts`; never delete the `HUB_DATA_DIR` key. -- Deployment mechanics are not settled enough to state here — see - [IMPLEMENTATION.md](IMPLEMENTATION.md)'s Deployment section first. +- `bun run check` (typecheck, lint, fmt, test) must pass before every commit. There are no custom structural check scripts; a rule lives in the code or schema itself or it does not exist. +- Pull request titles and every commit subject must follow **Conventional Commits**: `type(scope): description` or `type: description` with an allowed type (`feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `ci`, `build`, `style`, `revert`) — enforced by `.corbits-triage.yml`. +- Commit sequence per change: tests first (`test(scope): add tests for X`), then implementation (`feat(scope): ...` or `fix(scope): ...`), then docs (`docs(scope): update docs for X`). One logical change per commit; commit messages are written for a public audience. +- Deployment mechanics are not settled enough to state here — see [IMPLEMENTATION.md](IMPLEMENTATION.md)'s Deployment section first. + +## Tests + +- Two homes only. A load-bearing unit test sits beside the module it covers (`src/**/*.test.ts`); a load-bearing integration or DB-backed suite lives under root `e2e//`, with shared helpers in `e2e/lib/`. No `test/` directory exists anywhere — not in apps, packages, agents, or tools. +- Meaningful red/green tests only — no coverage theater. A test that isn't load-bearing (pins a literal, a shape, a re-export, or re-proves Interchange's own behavior) is deleted, not moved. An outdated test is deleted in the same PR that breaks it, not adapted to keep passing; rebuilding coverage for the area it covered happens under CL-8150. +- Bun loads repo-root `.env`, so an unset test inherits `HUB_DATA_DIR=.data/hub` inside this work tree — the hub's git-on-disk init can then walk up to the enclosing `.git` and land a genesis commit on the working branch. Tests that boot the hub or touch seed/deploy git paths must call `installDisposableHubDataDir()` from `e2e/lib/disposable-hub-data-dir.ts`; never delete the `HUB_DATA_DIR` key. Env flags (unset behavior): @@ -72,44 +74,8 @@ Env flags (unset behavior): | `CI` | set by GitHub Actions; not a caller flag | | `E2E_PROVIDER` / `E2E_PROVIDER_API_KEY` / `OLLAMA_BASE_URL` | live-inference e2e stays on the noop/stub path | -## Tests - -Two homes only. A load-bearing unit test sits beside the module it covers -(`src/**/*.test.ts`); a load-bearing integration or DB-backed suite lives -under root `e2e//`, with shared helpers in `e2e/lib/`. No `test/` -directory exists anywhere — not in apps, packages, agents, or tools. A test -that isn't load-bearing (pins a literal, a shape, a re-export, or re-proves -Interchange's own behavior) is deleted, not moved. - -Tests are meaningful red/green tests only — no coverage theater. An -outdated test is deleted in the same PR that breaks it, not adapted to keep -passing; rebuilding coverage for the area it covered happens under CL-8150. +## Docs -## Conventions - -- Report every caught error through `reportError` from - `@corbits/error-sink` — never a bare `catch {}`. -- A package's `browser-safe` subpath never imports a server-only - dependency (`postgres`, `drizzle-orm`, `hono`, any `@intx/*`). -- Every package needs a `LICENSE` file (LGPL-2.1). -- A dependency declared in the root `catalog` is consumed as `catalog:`. -- `exactOptionalPropertyTypes: true` — omit an optional key rather than - assigning it `undefined`. -- A custom table that gets deleted is hard-removed: delete the schema code, - no drop migration. Cutovers are breaking; reset the database. - -## Docs map - -- [README.md](README.md) — quickstart and repo layout -- [PRODUCT.md](PRODUCT.md) — what Workbench is and why -- [ARCHITECTURE.md](ARCHITECTURE.md) — system structure -- [IMPLEMENTATION.md](IMPLEMENTATION.md) — concrete stack, deployment, open - questions -- [CONTRIBUTING.md](CONTRIBUTING.md) — contribution flow and CLA -- [LICENSE.md](LICENSE.md) — GPLv2 with AI Exception -- [SECURITY.md](SECURITY.md) — how to report vulnerabilities -- [VENDORED.md](VENDORED.md) — the vendoring ledger and its rules -- [DESIGN.md](DESIGN.md) — the UI design system canon; a screen that - disagrees with it is wrong until a review changes the doc -- [docs/GLOSSARY.md](docs/GLOSSARY.md) — product-term to platform-term mapping -- [docs/local-dev.md](docs/local-dev.md) — running fully local with Ollama +- Docs follow the change: a pull request that changes behavior, options, routes, or screens updates the affected `README.md` and docs in the same pull request, or says in its description why none needed a change. +- User docs say what a person sees and does, not how the code does it; mechanics belong in [ARCHITECTURE.md](ARCHITECTURE.md) and [IMPLEMENTATION.md](IMPLEMENTATION.md). +- Map: [README.md](README.md) (quickstart and layout), [PRODUCT.md](PRODUCT.md) (what Workbench is), [ARCHITECTURE.md](ARCHITECTURE.md) (system structure), [IMPLEMENTATION.md](IMPLEMENTATION.md) (stack and deployment), [DESIGN.md](DESIGN.md) (visual canon), [VENDORED.md](VENDORED.md) (vendoring ledger), [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), [docs/GLOSSARY.md](docs/GLOSSARY.md), [docs/local-dev.md](docs/local-dev.md). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 373d54117..7fea8a8e6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -9,15 +9,16 @@ handful of domain packages built on top. - **`apps/hub`** — the Interchange hub server. It mounts stock `@intx/hub-api` routes (auth, tenants, principals, grants, workflow - deploy/run) plus one composition block that adds Corbits libraries — - `@corbits/mailbox` (mail), `@corbits/memory` (recall), `@corbits/artifacts` - (files), `@corbits/cron` (scheduling), `@corbits/webhooks` (inbound - webhook ingress) — and the remaining in-repo packages: `workflows` - (author routes) and `agent-directory`. Credentials are stored through - Interchange's own stock credential routes; nothing in this repo wraps - or replaces them. + deploy/run) plus one composition block (`apps/hub/src/server.ts`) that adds + Corbits libraries: `@corbits/mailbox` (mail), `@corbits/memory` (recall), + `@corbits/artifacts` (files), `@corbits/cron` (scheduling), + `@corbits/webhooks` (inbound webhook ingress), `@corbits/mcp` (MCP catalog + discovery), `@corbits/agent-token` (run-scoped bearer tokens for deployed + agents), and `@corbits/oauth-core` (loopback OAuth login and token refresh + for Codex and xAI). Credentials are stored through Interchange's own stock + credential routes; nothing in this repo wraps or replaces them. - **`apps/sidecar`** — a copy of Interchange's own sidecar. It executes - workflow runs and agent turns on the hub's behalf; its few local deltas + workflow runs and agent turns on the hub's behalf; its local deltas are listed in [VENDORED.md](VENDORED.md) with kill conditions. - **`apps/web`** — the React client. On sign-in it converges its own tenant, workflow deployments, credentials, and grants over stock hub diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77b4fff86..e0a735879 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,45 +2,35 @@ Thanks for your interest in Corbits Workbench. -> **`vendor/intx` is read-only vendored code.** Never open a PR that -> changes anything under `vendor/intx` — it is hand-copied, ledgered -> `@intx/*` source, not code this repo owns. See [VENDORED.md](VENDORED.md) -> for the ledger and how to propose a change upstream instead. +> **`vendor/intx` is vendored code.** Do not open a PR that changes anything +> under `vendor/intx` unless it adds a ledgered upstream delta with a kill date. +> See [VENDORED.md](VENDORED.md) for the ledger and propose changes upstream at +> [faremeter/interchange](https://github.com/faremeter/interchange). ## Before you start - Contributions are accepted under the terms of the [Contributor License Agreement](CLA.md) and the project license: the - application ([GPLv2 with AI Exception](LICENSE.md)), or LGPL-2.1 - for contributions to a library under `packages/` or `workflows/`. -- Read [AGENTS.md](AGENTS.md) — the ground rules there apply to human and - agent contributors alike. + application ([GPLv2 with AI Exception](LICENSE.md)), or LGPL-2.1 for + contributions to a library under `packages/` or `workflows/`. +- [docs/local-dev.md](docs/local-dev.md) covers running the stack locally. +- [AGENTS.md](AGENTS.md), and the `AGENTS.md` files under `apps/`, hold the + architecture and code rules every change follows, whether a person or a + coding agent writes it. ## The short version -1. `bun install` (or `bun run hooks:install`) sets a repo-local pre-push - hook. That hook is the local stand-in for CI's jobs: typecheck, lint - (oxlint + `oxfmt --check`), structural checks, and unit tests. Skip - the hook with `git push --no-verify` or `SKIP_WORKBENCH_HOOKS=1`. - Typecheck and - tests narrow to the packages your change touches; a change under - `package.json`, `bun.lock`, a shared tsconfig, `scripts/`, or - `.github/` falls back to checking every package instead, which takes - much longer — that's expected for those paths, not a hang. -2. `bun run check` — everything must be green before and after your - change. -3. Tests first: add failing tests, then the implementation, then docs. -4. One logical change per commit; write messages for a public audience. -5. Never commit secrets. `.env.example` is the only tracked env file. -6. Never vendor code without a ledger row and kill date in +1. `bun run check` (typecheck, lint, format check, unit tests) must be green + before and after your change. +2. Pull request titles and every commit subject must follow Conventional + Commits (`feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `ci`, + `build`, `style`, `revert`). +3. Tests first: `test(scope): add tests for X`, then the implementation + (`feat(scope): ...` or `fix(scope): ...`), then docs (`docs(scope): ...`). + One logical change per commit; write messages for a public audience. +4. Never commit secrets. `.env.example` is the only tracked env file. +5. Never vendor code without a ledger row and kill date in [VENDORED.md](VENDORED.md). -7. A package that owns its own product tables ships one idempotent, - literal-SQL migration, applied by the hub itself at boot. -8. Security issues go through [SECURITY.md](SECURITY.md), never a public - issue. -9. Changed a publishable `workflows/*` or `packages/*-tools` package's - `src/`? Bump its `package.json` version — the tool-package-freshness - check enforces this. Nothing publishes to npm from this monorepo; a - package moves to its own repository before it can be published. - -This document will grow as the project does. +6. Each package that owns product tables ships one idempotent SQL migration, + applied by the hub at boot. +7. Security issues go through [SECURITY.md](SECURITY.md), never a public issue. diff --git a/README.md b/README.md index 3f3d84847..06c99f773 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ local [Ollama](https://ollama.com) works with no key, see | ------------- | ---------------------------------------------------------------- | | `apps/` | `hub` (API), `web` (React client), `sidecar` (execution host) | | `packages/` | Corbits libraries, and `worker`, the default agent harness | +| `workflows/` | Deployable workflow packages | | `vendor/intx` | Hand-copied Interchange packages, see [VENDORED.md](VENDORED.md) | ## Develop @@ -41,9 +42,15 @@ bun run check # typecheck, lint, fmt:check, test bun run test:e2e # end-to-end suites, need DATABASE_URL ``` -Conventions live in [AGENTS.md](AGENTS.md). +## Read next + +- [PRODUCT.md](PRODUCT.md): what a workbench, its worker, tools, and skills are. +- [ARCHITECTURE.md](ARCHITECTURE.md) and [IMPLEMENTATION.md](IMPLEMENTATION.md): how the hub, sidecar, and web client are built. +- [docs/local-dev.md](docs/local-dev.md): running locally with Ollama, memory embeddings, and OAuth providers. +- [docs/GLOSSARY.md](docs/GLOSSARY.md): product-term to platform-term mapping. +- [CONTRIBUTING.md](CONTRIBUTING.md), [AGENTS.md](AGENTS.md), and [DESIGN.md](DESIGN.md): contributing, code rules, and the visual design system. ## License The application is GPLv2 with the [AI Exception](GPLv2-AI-Exception.md); -see [LICENSE.md](LICENSE.md). Libraries under `packages/`\nare LGPL-2.1. +see [LICENSE.md](LICENSE.md). Libraries under `packages/` are LGPL-2.1. Contributions require the [CLA](CLA.md); see [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues per [SECURITY.md](SECURITY.md). diff --git a/apps/hub/AGENTS.md b/apps/hub/AGENTS.md new file mode 100644 index 000000000..b98f96916 --- /dev/null +++ b/apps/hub/AGENTS.md @@ -0,0 +1,6 @@ +Follow the [root AGENTS.md](../../AGENTS.md). + +- Everything above the Corbits mount block in `src/server.ts` mirrors upstream Interchange's `server.ts`. +- Mount only stock `@intx/hub-api` routes plus the Corbits mount block in `src/server.ts`; any other hub mount is cutover debt with a Linear issue. +- Boot applies `@intx/db` platform migrations and each mounted Corbits library's idempotent migration in `src/migrate.ts`. +- Gate every tenant route with `createRequireGrant` and report background or callback errors through `reportError` from `@corbits/error-sink`. diff --git a/apps/hub/README.md b/apps/hub/README.md index 47765b08a..71daac8e2 100644 --- a/apps/hub/README.md +++ b/apps/hub/README.md @@ -1,55 +1,44 @@ # @workbench/hub -Composition root that mounts the platform API, extension routes, and the -user interface on one origin. Apps stay generic — every product rule -belongs in a package (see [AGENTS.md](../../AGENTS.md)) — so this app's -job is wiring, not deciding: config, then database, then auth, then the -platform app, in the platform's own idiom, following Interchange's -native tenant middleware. +Composition root that mounts the platform API and the Corbits mount block on +one origin. Apps stay generic — every product rule belongs in a package (see +[AGENTS.md](../../AGENTS.md) and [apps/hub/AGENTS.md](AGENTS.md)). ## What it wires -- Interchange's `@intx/hub-api` (`createApp`) and `@intx/hub-sessions` - provide the platform's own routes, auth, and tenant middleware; this - app never re-implements them. -- Every `@corbits/*` and `@workbench/*` package route (chat, approvals, - bench, insights, mailbox, memory, skills, artifacts, - routines, tasks, webhooks, and more) mounts here as one - explicit import plus one `app.route(...)` call, inside or outside the - tenant prefix as the extension requires. `src/index.ts` is the single - file where all of this comes together. -- There are no local `src/*-mount.ts` wiring helpers: each extension's - routes mount directly in `src/index.ts` with the shared DB - handle/config passed inline at the `app.route(...)` call site. -- Serves no static files. In dev, `vite dev` (apps/web) serves the SPA and +- Everything above the Corbits mount block in `src/server.ts` is upstream + Interchange's `server.ts`, providing `@intx/hub-api` (`createApp`), + `@intx/hub-sessions`, auth, and tenant middleware. +- Boot runs `migrateHub` (`src/migrate.ts`), applying `@intx/db` platform + migrations and each mounted Corbits library's idempotent migration under a + Postgres advisory lock. +- The Corbits mount block in `src/server.ts` mounts: + - `@corbits/artifacts` (`mountArtifacts` and `mountWorkflowArtifacts`) + - Live workflow-run SSE stream routes (`src/run-stream.ts`) + - `@corbits/agent-token` (`mountAgentTokens` and `createAgentTokenVerifier`) + - `@corbits/mailbox` (`mountMailbox` with `src/mailbox-persist.ts` and `src/mailbox-send.ts`) + - `@corbits/cron` (`createCronRoutes` and `createCronTicker`) + - `@corbits/memory` (`createMemory` and `mountWorkflowMemory`) + - `@corbits/oauth-core/hub` (`mountOAuthCallback`, `mountOAuthLogin`, and `createOAuthTokenRefresher` for Codex and xAI) + - `@corbits/mcp/hub` (`mountMcpDiscovery`) + - `@corbits/webhooks` (`installWebhooks`) +- Serves no static files. In dev, `vite dev` (`apps/web`) serves the SPA and proxies `/api` to the hub; in e2e, `e2e/lib/browser.ts` serves the built - `apps/web/dist` itself, in front of the hub, on one origin. -- `credential-expiry-sweep.ts`, `workflow-scheduler.ts`, and - `inbox-unsnooze-sweep.ts` are host-level periodic sweeps wired at boot, - alongside the route mounts; `cron-due.ts` is a re-export seam onto - `@corbits/workflows`' own cron matcher, not a second implementation. - Tenancy is the stock platform's own middleware — there is no local - tenant-create guard. -- `in-flight-requests.ts` and `shutdown.ts` bound SIGINT/SIGTERM: the hub - waits for Hono handlers that have not yet returned a Response (a - request still in a Postgres transaction, a git write), then - `server.stop(true)` so a live SSE bridge or sidecar websocket cannot - hang the drain. The sequence is capped at 10s; a lingering stream is - not a shutdown fault. + `apps/web/dist` in front of the hub on one origin. ## Running -``` -bun run dev # apps/hub/package.json, loads ../../.env +```sh +bun run dev # from repo root, or `cd apps/hub && bun run dev` bun run start ``` ## Tests -``` +Unit tests sit next to the modules they cover (`src/**/*.test.ts`); DB-backed +hub integration suites live in `e2e/hub/`. + +```sh cd apps/hub && bun test +bun test e2e/hub ``` - -Suites under `test/` exercise composition and wiring; the DB-backed -ones run against a real database: -`DATABASE_URL=postgres://localhost:5432/workbench_e2e bun test`. diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md new file mode 100644 index 000000000..8b46a06a7 --- /dev/null +++ b/apps/web/AGENTS.md @@ -0,0 +1,7 @@ +Follow the [root AGENTS.md](../../AGENTS.md). + +- Server data goes through TanStack Query, never `useEffect`. +- Build UI from `@corbits/react-ui`; tokens, layout, and visual rules live in [DESIGN.md](../../DESIGN.md). +- Keep product rules in packages (`packages/*`); `apps/web` is routing, shell composition, and client convergence over stock hub routes. +- UI copy says what a person sees and does; never how it works inside. +- Never ship UI for a feature that is not built. diff --git a/apps/web/README.md b/apps/web/README.md index c20b25963..2f7c5f67a 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,101 +1,56 @@ # `@workbench/web` The workbench's single-page interface: every screen and route lives here, -composed from the published `@corbits/react-ui` component library, and -builds to a static bundle in `dist/` (`vite build`). The hub serves no -static files. It is a Vite SPA over the hub's `/api` — every product rule -(what a screen may show, how data is shaped) lives in a package; this app -stays a generic composition of package UI and routing, per -[AGENTS.md](../../AGENTS.md). In dev, `vite dev` serves the SPA and proxies -`/api` to a locally running hub, so the interface is developed against real -data without a build step. In e2e, `e2e/lib/browser.ts` builds and serves -`dist/` itself on the same origin as the hub. +composed from `@corbits/react-ui`, and builds to a static bundle in `dist/` +(`vite build`). It is a Vite SPA over the hub's `/api` — product rules live in +packages, while this app stays a generic composition of routing, shell chrome, +and client convergence over stock hub routes (see [AGENTS.md](../../AGENTS.md) +and [apps/web/AGENTS.md](AGENTS.md)). In dev, `vite dev` serves the SPA and +proxies `/api` to a locally running hub; in e2e, `e2e/lib/browser.ts` builds +and serves `dist/` on the same origin as the hub. ## Layout -Every signed-in screen renders inside the same four-column shell -(`src/shell/`), built from `@corbits/react-ui`'s sidebar panel pieces plus a -workbench-composed rail: +Signed-in screens render inside `AppShell` (`src/shell/app-shell.tsx`), +following [DESIGN.md](../../DESIGN.md): -1. **Rail** — global and stable: it never changes with navigation or with - the selected bench. One icon per screen with its name captioned - underneath (not tooltip-only), plus the bench switcher and the - signed-in account's settings/sign-out at the bottom. Answers "where am I - in the product, and which bench am I in". Fixed width at every - breakpoint — it never joins the columns that withdraw as the viewport - narrows. -2. **Contextual column** — bench-scoped and live: workbenches, chats, running - routines, and notifications for the _currently selected_ bench. It - refetches when the bench changes, not when the route does, so its - contents can persist or travel across page navigation rather than being - a per-page list. Answers "what is happening in this bench right now". -3. **Main pane** — whatever the route renders, taking all remaining width. -4. **Canvas** — an optional fourth column for running agents, live - workflow walkthroughs and analytics. Collapsed by default and collapsed - to zero width, so the main pane holds the space until it is opened. - -The shell reflows as the viewport narrows rather than scrolling sideways: -the canvas column (and its toggle) is withdrawn first below roughly 1100px, -and the contextual column follows below roughly 700px, leaving the rail and -the main pane. The widths themselves are fluid (`clamp`/`vw`), so a future -chat dock squeezing the content area resizes the columns instead of -clipping them. - -A new page needs one entry in `NAV_ROUTES` (`src/routes.tsx`) — the rail's -icon and the route switch both read from that single table, so a page -cannot appear in one without the other. The contextual column no longer -reads `NAV_ROUTES` at all: it has nothing to do with which pages exist. - -Running routines in the contextual column are sourced today from -`@/chat`'s workflow-run listing (`src/shell/routine-activity.ts`) -— the column depends only on that file's `RoutineActivityItem` shape. +1. **Sidebar** (`src/shell/sidebar.tsx`) — `Workbench` wordmark with search + (command palette) and `+` (`/new`), the **Workbenches** list, the + **Workers** list, and a footer with the account menu (Settings, Light / + Dark / Canvas theme picker, Sign out) and the **Tools** button. +2. **Main stage** — the active route (`src/routes.tsx`), with a `StageTopBar` + on non-bench pages and a centered conversation column on workbenches. +3. **Canvas column** (`src/shell/canvas-column.tsx`) — an optional right-hand + side panel for artifact previews/editing, worker profiles, and workflow + details, plus the bench drawer and thread replies inside a workbench. ## Screens -| Path | What it shows | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/` | Home — a welcome summary of the signed-in account's benches and runs. | -| `/c` | Workbench deep-link surface. On wide layouts the conversation opens in the right canvas; on compact layouts it fills the main pane. Legacy `/chat` links still resolve here. | -| `/workflows` | Workflow runs executing across your benches. | -| `/library` | The artifact gallery. See "Library" below. | -| `/settings` | Account, bench membership, and Agents/Skills. See "Agents" and "Skills" below. | - -Agents and Skills are Settings sections, not rail destinations — old -`/agents[/:id]` and `/skills[/:id]` links still resolve, redirecting to -`/settings/agents[/:id]` and `/settings/skills[/:id]`. - -Approvals are not a page: pending permission requests land as actionable -cards in the contextual panel's Notifications band (and, when a workbench is -open, inline in that workbench). The `/approvals` route is gone. +| Path | What it shows | +| ------------- | ------------------------------------------------------------------------------------------------- | +| `/` | Ensures a personal bench and redirects to the active workbench. | +| `/new` | New workbench picker. | +| `/w/:bench` | Workbench conversation thread, inline approvals, voice mode, and bench drawer. | +| `/workers` | Workspace workers roster (`/workers/:worker` for a worker's detail page). | +| `/workflows` | Workflows and scheduled routines (`/workflows/:routine` for a workflow's detail page). | +| `/artifacts` | Artifact gallery and viewer (`/artifacts/:id`), backed by `/api/tenants/:id/artifacts`. | +| `/skills` | Skills catalog and editor (`/skills/:slug`). | +| `/tools` | Connected MCP servers, built-in tools, and credentials. | +| `/insights` | Bench-scoped run volume, success rate, median duration, approvals, and recent failures. | +| `/settings` | Account, workspace members, inference providers, and access policy (presented as a modal dialog). | +| `/login` | Sign-in and sign-up screen. | +| `/onboarding` | First-login provider setup flow. | + +Retired path prefixes (`/mission-control`, `/inbox`, `/routines`, `/files`, +`/library`, `/settings/agents`, `/settings/skills`) redirect to their current +locations in `redirectTargetFor` (`src/routes.tsx`). ## Tests -- **Unit tests** for pure modules (path helpers, reducers, parsers) sit next - to the source file under `src/` as `*.test.ts`. -- **Integration / composition / shell probes** stay under `test/`. -- `bun test` (via the package script) runs both `./src` and `./test`. - -## Library - -`/library` (`src/pages/library-page.tsx`) is the artifact gallery: search, -sort, a grid/rows view toggle, kind-colored cards, and upload, built -against the `ArtifactSummary` type from `@/library`. -`LibraryRoute` reads and searches the tenant's artifacts from the real -`/api/tenants/:id/artifacts` (list/detail) endpoint; a chat artifact -chip's "Open in Library" link (`/library/a/:id`) deep-links into the same -route. - -## Agents - -Settings · Agents (`src/pages/agents-settings-section.tsx`, registered via -`src/settings-workspace-sections.tsx`) is definitions only — a bench's -directory of agent definitions and their deployed instances, with Start -chat / Open in workbench as its only reach into chat. Talking to an agent is a -chat; looping one into a conversation is a workbench mention — neither lives -here. See `docs/AGENTS-PAGE.md`. - -## Skills +Unit tests sit next to the source files they cover under `src/**/*.test.ts`; +browser end-to-end suites live in `e2e/browser/`. -Settings · Skills (`src/pages/skills-settings-section.tsx`) is an honest -stub: there is no skill registry in the hub yet, so the section states what -a skill will be instead of rendering invented rows. +```sh +cd apps/web && bun test +bun test e2e/browser +```