diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2232495b5d..7fdeb02eea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,110 +6,37 @@ on: pull_request: concurrency: - # PRs group by number so a newer push supersedes and cancels the stale - # run. Pushes to main group by commit SHA, giving every commit its own - # group: a constant branch-wide key would let a newer push evict the - # queued run of a commit still waiting behind an in-flight one, and - # that commit would never be validated. cancel-in-progress therefore - # only governs PR supersession. group: ci-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - # The four jobs below were one serial `checks` job. Splitting them lets - # GitHub run them concurrently, so a PR's wall-clock is the slowest job - # rather than the sum of all of them, and a lint or structural failure - # reports in about a minute instead of waiting behind the build. - lint: + # Warms the bun install cache once so the parallel jobs below mostly + # restore rather than reinstall from the network. + setup: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-workbench - - uses: actions/cache@v4 - with: - path: | - .eslintcache - node_modules/.cache/prettier - key: lint-${{ runner.os }}-${{ hashFiles('bun.lock', 'eslint.config.ts', '.prettierrc.json', '.bun-version') }} - - run: bun run lint typecheck: + needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/setup-workbench - - uses: actions/cache@v4 - with: - # Restores each package's incremental tsbuildinfo (composite build - # and combined --noEmit check now write separate files, so caching - # both means the next run's `tsc` calls only recheck what actually - # changed since the cached commit, instead of every file every PR. - # Paths are the workspace roots spelled out rather than `**/dist`: - # a recursive glob walks node_modules and follows every workspace - # symlink in it, and the save step ran for over 40 minutes. - path: | - apps/*/dist - packages/*/dist - tools/*/dist - workflows/*/dist - vendor/intx/*/dist - apps/*/tsconfig.tsbuildinfo - packages/*/tsconfig.tsbuildinfo - tools/*/tsconfig.tsbuildinfo - workflows/*/tsconfig.tsbuildinfo - vendor/intx/*/tsconfig.tsbuildinfo - tsconfig.tsbuildinfo - test/tsconfig.tsbuildinfo - test/isolation/tsconfig.tsbuildinfo - key: typecheck-${{ runner.os }}-${{ hashFiles('bun.lock', '.bun-version', 'tsconfig.base.json', 'scripts/generate-tsconfig-references.ts') }}-${{ github.sha }} - restore-keys: | - typecheck-${{ runner.os }}-${{ hashFiles('bun.lock', '.bun-version', 'tsconfig.base.json', 'scripts/generate-tsconfig-references.ts') }}- - run: bun run typecheck - # A hosted runner's 4 vCPUs cap how much a single job's package fan-out - # (scripts/run-all.ts's own concurrency) can cut wall-clock: the total - # CPU-seconds across every package's test script divided by 4 was still - # over 90s. Splitting the same total work across 3 runners, each with - # its own 4 vCPUs, is what actually buys more parallelism. --shard i/3 - # (scripts/run-all.ts) round-robins the workspace's stably name-sorted - # package list across shards, so a run's set of packages-per-shard is - # reproducible and every package lands in exactly one shard. - build-test-shard: - strategy: - fail-fast: false - matrix: - shard: [1, 2, 3] + lint: + needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/setup-workbench - - run: bun run build - # The root-level scripts/*.test.ts suite (scripts/run-all.test.ts - # among them) isn't itself sharded — it only needs to run once, so - # it rides along on shard 1. - - if: matrix.shard == 1 - run: bun test ./scripts/*.test.ts - - run: bun run scripts/run-all.ts test --shard ${{ matrix.shard }}/3 - - # The required check every branch protection rule and PR template - # names is "build-test" (predating the shard split above); this job - # keeps that name live by depending on every shard and failing if any - # of them did. - build-test: - needs: build-test-shard - if: always() - runs-on: ubuntu-latest - steps: - - if: contains(needs.build-test-shard.result, 'failure') || contains(needs.build-test-shard.result, 'cancelled') - run: | - echo "at least one build-test shard failed" >&2 - exit 1 + - run: bun run lint + - run: bun run fmt:check structural: + needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -118,225 +45,14 @@ jobs: - uses: ./.github/actions/setup-workbench - run: bun run check:structural - e2e-plan: - runs-on: ubuntu-latest - outputs: - suites: ${{ steps.list-suites.outputs.suites }} - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup-workbench - - name: List e2e suites - id: list-suites - run: echo "suites=$(bun run scripts/e2e/list-suites.ts)" >> "$GITHUB_OUTPUT" - - # One job per e2e suite so a PR's e2e wall-clock is the slowest suite - # rather than the sum of all of them, and a failing suite names itself - # instead of hiding behind "e2e failed". fail-fast is off so one flaky - # suite doesn't cancel every other suite's run. - e2e-suite: - needs: e2e-plan + # A single fast pass over every workspace's tests. DB-gated suites + # (scripts/e2e/* included) skip themselves when DATABASE_URL is unset — + # see scripts/e2e/db-gate.ts — so this job needs no Postgres service. + unit: + needs: setup runs-on: ubuntu-latest timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - suite: ${{ fromJson(needs.e2e-plan.outputs.suites) }} - - services: - postgres: - # pgvector-enabled Postgres 17, matching the local development - # database (brew postgresql@17 + pgvector). - image: pgvector/pgvector:pg17 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U postgres -d postgres" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup-workbench - - # The committed template ships a placeholder secret and a - # credential-less local DATABASE_URL; point the URL at the service - # container's superuser and mint a real session secret in place. - - name: Write env file - run: | - cp .env.example .env - secret=$(openssl rand -hex 32) - sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${secret}|" .env - sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench|" .env - - # The e2e suite skips itself when DATABASE_URL is absent, so a - # wiring mistake here could otherwise drop the whole suite while - # CI stays green. Fail loudly instead: the env file must exist - # with a usable DATABASE_URL and SESSION_SECRET, Postgres must be - # reachable, and git (the workflow-asset publication path) must be - # present. CI=true turns any remaining skip into a hard failure. - - name: Assert the e2e test env is wired - run: | - test -f .env - grep -q '^DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench$' .env - grep -Eq '^SESSION_SECRET=.{32,}$' .env - pg_isready -h localhost -p 5432 - git --version - - - name: Run the suite - run: | - if [ "${{ matrix.suite }}" = "unit" ]; then - bun test scripts/e2e/harness.test.ts scripts/e2e/db-gate.test.ts scripts/e2e/db-setup.test.ts scripts/e2e/list-suites.test.ts --max-concurrency=1 - else - bun test "scripts/e2e/${{ matrix.suite }}.test.ts" --max-concurrency=1 - fi - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench - E2E_LOG_DIR: e2e-logs - - - name: Upload e2e logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: e2e-logs-${{ matrix.suite }} - path: e2e-logs - if-no-files-found: ignore - - # Branch protection's required check is named "e2e"; this summary job - # keeps that name while the actual work runs as per-suite e2e-suite jobs. - e2e: - needs: e2e-suite - if: always() - runs-on: ubuntu-latest - steps: - - name: Check e2e-suite matrix result - run: | - if [ "${{ needs.e2e-suite.result }}" != "success" ]; then - echo "one or more e2e suites failed" - exit 1 - fi - - isolation: - runs-on: ubuntu-latest - timeout-minutes: 15 - - services: - postgres: - # pgvector-enabled Postgres 17, matching the local development - # database (brew postgresql@17 + pgvector). - image: pgvector/pgvector:pg17 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U postgres -d postgres" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-workbench - - - name: Write env file - run: | - cp .env.example .env - secret=$(openssl rand -hex 32) - sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${secret}|" .env - sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench|" .env - - - name: Assert the e2e test env is wired - run: | - test -f .env - grep -q '^DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench$' .env - grep -Eq '^SESSION_SECRET=.{32,}$' .env - pg_isready -h localhost -p 5432 - git --version - - - name: Run the two-org isolation suite - run: bun test test/isolation - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench - - db-suites: - runs-on: ubuntu-latest - timeout-minutes: 20 - - services: - postgres: - # pgvector-enabled Postgres 17, matching the local development - # database (brew postgresql@17 + pgvector). - image: pgvector/pgvector:pg17 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U postgres -d postgres" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup-workbench - - - name: Write env file - run: | - cp .env.example .env - secret=$(openssl rand -hex 32) - sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${secret}|" .env - sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench|" .env - - - name: Assert the e2e test env is wired - run: | - test -f .env - grep -q '^DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench$' .env - grep -Eq '^SESSION_SECRET=.{32,}$' .env - pg_isready -h localhost -p 5432 - git --version - - # apps/hub/test and most of the package suites below connect - # straight to DATABASE_URL (or its `_e2e`-suffixed sibling from - # `e2eDatabaseUrl()`) rather than booting through the harness, so - # — unlike e2e and isolation, which provision their own schema — - # this job must create and migrate both databases itself before - # those suites run. - - name: Set up the databases - run: | - DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench bun scripts/db-setup.ts - DATABASE_URL=postgres://postgres:postgres@localhost:5432/workbench_e2e bun scripts/db-setup.ts - - # apps/hub's own DB-backed suites (e.g. the sign-up rate-limit - # proof) never run under the plain `checks` job, which has no - # Postgres. CI=true turns a would-be skip here into a hard - # failure, so a misconfigured invocation can't pass vacuously. - - name: Run the hub's database-backed suites - run: | - set -a - . ./.env - set +a - bun test apps/hub/test - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench - - # Every DB-gated suite under packages/* and apps/hub/src gates on - # DATABASE_URL and describe.skip's without one, so build-test (no - # Postgres) never runs them. Find them by the same signal they - # gate on rather than hardcoding a file list, so a newly added - # suite gets picked up automatically. CI=true turns a would-be - # skip into a hard failure. - - name: Run the database-backed package suites - run: | - set -a - . ./.env - set +a - bun --env-file="${GITHUB_WORKSPACE}/.env" test $(grep -rl DATABASE_URL --include='*.test.ts' packages apps | grep -v apps/hub/test) - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/workbench + - run: bun test apps packages workflows scripts diff --git a/.gitignore b/.gitignore index a6df290e6d..1f1ea395ab 100644 --- a/.gitignore +++ b/.gitignore @@ -26,16 +26,13 @@ tmp/ # Local runtime state (hub and sidecar data dirs) .data/ -# Local agent scratch (not product source; breaks prettier --check .) +# Local agent scratch (not product source) .mb-scratch/ .corbits/ # Browser walkthrough harness screenshots + run summary (CL-6072) scripts/e2e/browser/shots/ -# Lint caches — local only; CI installs fresh and rebuilds them. -.eslintcache - # `bun pm pack` / `bun run release` tarballs — built on demand, never # committed (CL-8167). *.tgz diff --git a/.prettierrc.json b/.oxfmtrc.json similarity index 100% rename from .prettierrc.json rename to .oxfmtrc.json diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000000..2fc8671f55 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,82 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": [ + "typescript", + "unicorn", + "oxc" + ], + "categories": { + "correctness": "error", + "suspicious": "warn" + }, + "rules": { + "unicorn/consistent-function-scoping": "off", + "unicorn/prefer-string-starts-ends-with": "off", + "unicorn/no-array-sort": "off", + "unicorn/no-array-reverse": "off", + "unicorn/prefer-add-event-listener": "off", + "unicorn/no-useless-spread": "off", + "unicorn/no-useless-fallback-in-spread": "off", + "no-shadow": "off", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "off", + "no-unused-expressions": ["error", { "allowTaggedTemplates": true }], + "no-unsafe-optional-chaining": "off", + "no-useless-concat": "off", + "unicorn/no-thenable": "off", + "unicorn/no-new-array": "off", + "unicorn/require-module-specifiers": "off", + "no-console": "off" + }, + "env": { + "builtin": true + }, + "ignorePatterns": [ + "vendor/**", + "node_modules/**", + "dist/**", + ".worktrees/**", + ".data/**", + "*.gen.*" + ], + "overrides": [ + { + "files": ["**/src/**/*.ts", "**/src/**/*.tsx"], + "excludeFiles": [ + "**/src/**/*.test.ts", + "**/src/**/*.test.tsx", + "packages/client-log/src/index.ts" + ], + "rules": { + "no-console": "error" + } + }, + { + "files": ["**/*.ts", "**/*.tsx"], + "excludeFiles": ["packages/icons/src/**"], + "rules": { + "no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "lucide-react", + "message": "lucide-react is retired — import from @corbits/icons instead." + }, + { + "name": "@phosphor-icons/react", + "message": "Import icons from @corbits/icons, not @phosphor-icons/react directly." + } + ], + "patterns": [ + { + "group": ["@phosphor-icons/react/*"], + "message": "Import icons from @corbits/icons, not @phosphor-icons/react directly." + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index d136272417..0000000000 --- a/.prettierignore +++ /dev/null @@ -1,15 +0,0 @@ -node_modules/ -dist/ -vendor/ -.claude/ -coverage/ -bun.lock -GPL-2.0.txt -.worktrees/ -.agent-state/ -dispatch/ -specs/ -plans/ -tmp/ -.mb-scratch/ -.corbits/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8892d5987..f9cb17d89d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,10 +19,10 @@ Thanks for your interest in Corbits Workbench. ## 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 the cheap CI jobs: lint, - typecheck, and unit tests. GitHub still runs walking-skeleton, e2e, - isolation, and database-backed suites. Skip the hook with - `git push --no-verify` or `SKIP_WORKBENCH_HOOKS=1`. Typecheck and + 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 diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index b10830afdc..09c8e97b10 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -69,10 +69,10 @@ recorded per-package in each vendored package's own `VENDORED-FROM` file. | `bun run dev` | Validates `.env`, verifies the database, applies pending migrations, builds the web UI if needed, starts the hub and one sidecar | | `bun run setup` | Applies pending database migrations (`scripts/db-setup.ts`) | | `bun run reset` | Drops the platform database schema and clears on-disk asset directories (local `DATABASE_URL` only, unrecoverable) | -| `bun run check` | The full gate: `typecheck && lint && test` — must pass before every commit | +| `bun run check` | The full gate: `typecheck && lint && fmt:check && check:structural && test` — must pass before every commit | | `bun run test` | Workspace unit/integration tests | | `bun run test:e2e` | End-to-end smoke tests (`scripts/e2e/*.test.ts`) | -| `bun run format` | `prettier --write .` | +| `bun run fmt` | `oxfmt` write | `bun run dev` seeds the administrator account and ensures the root tenant; it does not insert agents, tools, workflows, or skills. An empty diff --git a/README.md b/README.md index 21c59f9381..0f66363cb9 100644 --- a/README.md +++ b/README.md @@ -146,10 +146,11 @@ documented separately in [docs/CHAT.md](docs/CHAT.md). ## Development -- `bun run check` — the full gate: typecheck, lint, test +- `bun run check` — the full gate: typecheck, lint (oxlint + `oxfmt + --check`), structural checks, test - `bun run test` — workspace tests - `bun run test:e2e` — end-to-end smoke tests (see below) -- `bun run format` — prettier write +- `bun run fmt` — oxfmt write Conventions and agent guidance: [AGENTS.md](AGENTS.md). Contributions: [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/apps/hub/package.json b/apps/hub/package.json index 32e3732f4d..341ddf4b97 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -1,8 +1,8 @@ { "name": "@workbench/hub", + "version": "0.0.1", "private": true, "description": "Composition root that mounts the platform API, extension routes, and the user interface on one origin", - "version": "0.0.1", "license": "SEE LICENSE IN ../../LICENSE.md", "type": "module", "scripts": { diff --git a/apps/hub/src/asset-service-factory.test.ts b/apps/hub/src/asset-service-factory.test.ts index 0cdef5dbf4..0fba5c0e5d 100644 --- a/apps/hub/src/asset-service-factory.test.ts +++ b/apps/hub/src/asset-service-factory.test.ts @@ -1,11 +1,7 @@ import { afterAll, expect, test } from "bun:test"; import { mkdirSync } from "node:fs"; import path from "node:path"; -import { - cleanupGitFixtures, - gitFixture, - scratchGitFixtureDir, -} from "../test/git-fixture"; +import { cleanupGitFixtures, gitFixture, scratchGitFixtureDir } from "../test/git-fixture"; import { createBootAssetWiring } from "./asset-service-factory"; import { HubDataDirInsideGitWorkTreeError } from "./hub-data-dir-git-safety"; @@ -16,9 +12,9 @@ test("createBootAssetWiring refuses a data dir inside an existing git work tree" gitFixture(workTree, ["init", "-b", "main"]); const dataDir = path.join(workTree, ".data", "hub"); mkdirSync(dataDir, { recursive: true }); - await expect( - createBootAssetWiring({ db: {} as never, dataDir }), - ).rejects.toBeInstanceOf(HubDataDirInsideGitWorkTreeError); + await expect(createBootAssetWiring({ db: {} as never, dataDir })).rejects.toBeInstanceOf( + HubDataDirInsideGitWorkTreeError, + ); }); test("createBootAssetWiring opt-in skips the work-tree refusal", async () => { diff --git a/apps/hub/src/asset-service-factory.ts b/apps/hub/src/asset-service-factory.ts index 00e8857f54..939991e82e 100644 --- a/apps/hub/src/asset-service-factory.ts +++ b/apps/hub/src/asset-service-factory.ts @@ -16,9 +16,7 @@ import { assertHubDataDirGitSafety } from "./hub-data-dir-git-safety"; // Host policy constant, not configuration — shared by boot (tool-package // registry routing) and asset-service construction. -export const REGISTRIES = new Map([ - ["npmjs", { url: "https://registry.npmjs.org" }], -]); +export const REGISTRIES = new Map([["npmjs", { url: "https://registry.npmjs.org" }]]); export interface BootAssetWiring { readonly signingKey: Awaited>; diff --git a/apps/hub/src/config.ts b/apps/hub/src/config.ts index 15f1f8d06a..e844c2f2ab 100644 --- a/apps/hub/src/config.ts +++ b/apps/hub/src/config.ts @@ -43,9 +43,7 @@ const HubEnv = type({ DATABASE_URL: type(/^postgres(ql)?:\/\/.+$/).describe( "a Postgres connection URL, e.g. postgres://hub:hub@localhost:5432/hub", ), - BASE_URL: type(HTTP_URL).describe( - "an http(s) origin, e.g. http://localhost:3000", - ), + BASE_URL: type(HTTP_URL).describe("an http(s) origin, e.g. http://localhost:3000"), "PORT?": type(/^\d{1,5}$/).describe( "the local port to listen on when it differs from BASE_URL's — set this when a reverse proxy (Tailscale serve, nginx) fronts the hub and BASE_URL is the public https origin", ), @@ -228,9 +226,7 @@ export type HubConfig = { readonly windowSeconds: number; readonly max: number; }; - readonly socialProviders: Readonly< - Partial> - >; + readonly socialProviders: Readonly>>; readonly huggingfaceOAuthClientId?: string; readonly githubAppClientId?: string; readonly githubAppClientSecret?: string; @@ -282,8 +278,7 @@ const SOCIAL_PROVIDER_ENV_KEYS: Record< function socialProvidersFrom( parsed: ParsedHubEnv, ): Readonly>> { - const providers: Partial> = - {}; + const providers: Partial> = {}; const errors: string[] = []; for (const [providerId, keys] of Object.entries(SOCIAL_PROVIDER_ENV_KEYS) as [ SocialProviderId, @@ -330,9 +325,7 @@ type SidecarProvisionersConfig = { * one variable — `SIDECAR_PROVISIONERS` — and listing it explicitly * (including `process`) behaves exactly as any other explicit list. */ -function sidecarProvisionersFrom( - parsed: ParsedHubEnv, -): SidecarProvisionersConfig { +function sidecarProvisionersFrom(parsed: ParsedHubEnv): SidecarProvisionersConfig { const ids = (parsed.SIDECAR_PROVISIONERS ?? "") .split(",") .map((id) => id.trim()) @@ -357,9 +350,7 @@ function sidecarProvisionersFrom( const provisioners: SidecarProvisionerConfig[] = []; for (const id of ids) { if (seen.has(id)) { - throw new Error( - `invalid hub environment: SIDECAR_PROVISIONERS lists ${id} more than once`, - ); + throw new Error(`invalid hub environment: SIDECAR_PROVISIONERS lists ${id} more than once`); } seen.add(id); if (!isSidecarProvisionerId(id)) { @@ -384,9 +375,7 @@ function sidecarProvisionersFrom( ); } const onlyId = provisioners[0]?.id; - return onlyId === undefined - ? { provisioners } - : { provisioners, defaultProvisionerId: onlyId }; + return onlyId === undefined ? { provisioners } : { provisioners, defaultProvisionerId: onlyId }; } if (!seen.has(defaultProvisionerId)) { throw new Error( @@ -426,10 +415,7 @@ function sidecarProvisionerConfigFor( return { id: "docker", image: parsed.DOCKER_PROVISIONER_IMAGE }; } case "e2b": { - if ( - parsed.E2B_API_KEY === undefined || - parsed.E2B_TEMPLATE === undefined - ) { + if (parsed.E2B_API_KEY === undefined || parsed.E2B_TEMPLATE === undefined) { throw new Error( [ "invalid hub environment: E2B_API_KEY and E2B_TEMPLATE must both be set when SIDECAR_PROVISIONERS includes e2b", @@ -460,9 +446,7 @@ function sidecarProvisionerConfigFor( * have, so a misconfigured process dies at boot instead of failing at * first use. */ -export function readHubConfig( - env: Record, -): HubConfig { +export function readHubConfig(env: Record): HubConfig { const parsed = HubEnv(env); if (parsed instanceof type.errors) { throw new Error( @@ -502,8 +486,7 @@ export function readHubConfig( : DEFAULT_SIGNIN_RATE_LIMIT_MAX, }, }; - if (parsed.HUB_ALLOW_GIT_INSIDE_WORK_TREE !== undefined) - hubConfig.allowGitInsideWorkTree = true; + if (parsed.HUB_ALLOW_GIT_INSIDE_WORK_TREE !== undefined) hubConfig.allowGitInsideWorkTree = true; if (parsed.PORT !== undefined) hubConfig.listenPort = Number(parsed.PORT); if (parsed.HUGGINGFACE_OAUTH_CLIENT_ID !== undefined) hubConfig.huggingfaceOAuthClientId = parsed.HUGGINGFACE_OAUTH_CLIENT_ID; @@ -511,8 +494,7 @@ export function readHubConfig( hubConfig.githubAppClientId = parsed.GITHUB_APP_CLIENT_ID; if (parsed.GITHUB_APP_CLIENT_SECRET !== undefined) hubConfig.githubAppClientSecret = parsed.GITHUB_APP_CLIENT_SECRET; - if (parsed.GMAIL_CLIENT_ID !== undefined) - hubConfig.gmailClientId = parsed.GMAIL_CLIENT_ID; + if (parsed.GMAIL_CLIENT_ID !== undefined) hubConfig.gmailClientId = parsed.GMAIL_CLIENT_ID; if (parsed.GMAIL_CLIENT_SECRET !== undefined) hubConfig.gmailClientSecret = parsed.GMAIL_CLIENT_SECRET; if (parsed.GITHUB_API_BASE_URL !== undefined) @@ -520,18 +502,13 @@ export function readHubConfig( if (parsed.CREDENTIAL_ENCRYPTION_KEY !== undefined) hubConfig.credentialEncryptionKeyHex = parsed.CREDENTIAL_ENCRYPTION_KEY; if (parsed.PRINCIPAL_KEY_ENCRYPTION_KEY !== undefined) - hubConfig.principalKeyEncryptionKeyHex = - parsed.PRINCIPAL_KEY_ENCRYPTION_KEY; + hubConfig.principalKeyEncryptionKeyHex = parsed.PRINCIPAL_KEY_ENCRYPTION_KEY; if (parsed.HUB_SIDECAR_WEBSOCKET_URL !== undefined) hubConfig.sidecarWebSocketUrl = parsed.HUB_SIDECAR_WEBSOCKET_URL; if (sidecarProvisioners.defaultProvisionerId !== undefined) - hubConfig.defaultSidecarProvisionerId = - sidecarProvisioners.defaultProvisionerId; + hubConfig.defaultSidecarProvisionerId = sidecarProvisioners.defaultProvisionerId; - if ( - hubConfig.allowPlaintextSecrets && - !isLoopbackBaseUrl(hubConfig.baseUrl) - ) { + if (hubConfig.allowPlaintextSecrets && !isLoopbackBaseUrl(hubConfig.baseUrl)) { throw new Error( [ `ALLOW_PLAINTEXT_SECRETS is set, but BASE_URL (${hubConfig.baseUrl}) is not a loopback address.`, diff --git a/apps/hub/src/connect-github-mount-gone.test.ts b/apps/hub/src/connect-github-mount-gone.test.ts index 8a6feff2e3..a1485d332e 100644 --- a/apps/hub/src/connect-github-mount-gone.test.ts +++ b/apps/hub/src/connect-github-mount-gone.test.ts @@ -8,16 +8,7 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import path from "node:path"; -const HUB_INDEX = path.join( - import.meta.dir, - "..", - "..", - "..", - "apps", - "hub", - "src", - "index.ts", -); +const HUB_INDEX = path.join(import.meta.dir, "..", "..", "..", "apps", "hub", "src", "index.ts"); const WB = "work" + "benches"; const GH = "git" + "hub"; @@ -37,9 +28,7 @@ describe("hub-zero T3: the workbench-scoped connect mount is gone", () => { test("index.ts passes no settings-row port to the deleted mount's options", () => { const source = readFileSync(HUB_INDEX, "utf8"); - const optionsPattern = new RegExp( - `${MOUNT_PORT_A}|${MOUNT_PORT_B}|${MOUNT_PORT_C}`, - ); + const optionsPattern = new RegExp(`${MOUNT_PORT_A}|${MOUNT_PORT_B}|${MOUNT_PORT_C}`); expect( optionsPattern.test(source), "index.ts still passes a settings-row port to the deleted mount's options", diff --git a/apps/hub/src/credential-material-refresh.test.ts b/apps/hub/src/credential-material-refresh.test.ts index fd3c17884c..03198f9136 100644 --- a/apps/hub/src/credential-material-refresh.test.ts +++ b/apps/hub/src/credential-material-refresh.test.ts @@ -37,9 +37,7 @@ function store(initial: NonNullable) { ...row, secret: `enc:${tokens.secret}`, refreshSecret: - tokens.refreshSecret === undefined - ? row.refreshSecret - : `enc:${tokens.refreshSecret}`, + tokens.refreshSecret === undefined ? row.refreshSecret : `enc:${tokens.refreshSecret}`, expiresAt: tokens.expiresAt, status: "active", }; @@ -95,9 +93,7 @@ function harness( describe("createServingRefresh", () => { test("an api_key credential passes through untouched — never refreshed, never pushed", async () => { - const h = harness( - row({ type: "api_key", refreshSecret: null, expiresAt: null }), - ); + const h = harness(row({ type: "api_key", refreshSecret: null, expiresAt: null })); const result = await h.refresh(servingOf(h)); expect(result).toEqual({ ok: true }); expect(h.calls.grantArgs).toHaveLength(0); @@ -114,20 +110,15 @@ describe("createServingRefresh", () => { }); test("an expiring oauth_token refreshes, persists, and pushes the updated frame", async () => { - const h = harness( - row({ expiresAt: new Date(NOW + 1000) }), - async (args) => ({ - accessToken: `fresh:${args.refreshToken}`, - refreshToken: "rotated-refresh", - expiresIn: 3600, - }), - ); + const h = harness(row({ expiresAt: new Date(NOW + 1000) }), async (args) => ({ + accessToken: `fresh:${args.refreshToken}`, + refreshToken: "rotated-refresh", + expiresIn: 3600, + })); const result = await h.refresh(servingOf(h)); expect(result).toEqual({ ok: true }); expect(h.calls.refreshedTokens).toHaveLength(1); - expect(h.calls.refreshedTokens[0]?.secret).toBe( - "fresh:plain:enc:refresh-1", - ); + expect(h.calls.refreshedTokens[0]?.secret).toBe("fresh:plain:enc:refresh-1"); expect(h.calls.refreshedTokens[0]?.refreshSecret).toBe("rotated-refresh"); expect(h.calls.pushes).toEqual(["ten_1"]); }); @@ -149,14 +140,11 @@ describe("createServingRefresh", () => { }); test("a grant that states no expires_in persists a NULL expiry — the row never re-enters the due set", async () => { - const h = harness( - row({ expiresAt: new Date(NOW + 1000) }), - async (args) => ({ - accessToken: `fresh:${args.refreshToken}`, - refreshToken: "rotated-refresh", - // no expiresIn: provider stated no lifetime - }), - ); + const h = harness(row({ expiresAt: new Date(NOW + 1000) }), async (args) => ({ + accessToken: `fresh:${args.refreshToken}`, + refreshToken: "rotated-refresh", + // no expiresIn: provider stated no lifetime + })); const result = await h.refresh(servingOf(h)); expect(result).toEqual({ ok: true }); expect(h.row()?.expiresAt).toBeNull(); @@ -189,9 +177,7 @@ describe("createServingRefresh", () => { function servingOf( h: { - row: () => NonNullable< - Awaited> - > | null; + row: () => NonNullable>> | null; }, status: string = "active", ): Parameters>[0] { diff --git a/apps/hub/src/credential-material-refresh.ts b/apps/hub/src/credential-material-refresh.ts index de86a1e2fd..4f5c4de08b 100644 --- a/apps/hub/src/credential-material-refresh.ts +++ b/apps/hub/src/credential-material-refresh.ts @@ -94,10 +94,7 @@ export type ServingRefreshStore = { >; /** Persists a successful refresh (encrypting the new pair) — only an * `active` row is updated; false means it was claimed meanwhile. */ - applyRefreshedTokens( - credentialId: string, - tokens: RefreshedTokens, - ): Promise; + applyRefreshedTokens(credentialId: string, tokens: RefreshedTokens): Promise; /** Marks the credential re-auth-required: visible to the settings * Connections card as needs-attention. */ markReauthRequired(credentialId: string, reason: string): Promise; @@ -105,11 +102,7 @@ export type ServingRefreshStore = { * running instances, so sidecars pick the new material up live. */ pushUpdates(tenantId: string): Promise; /** Decrypts a stored secret column for the grant. */ - decrypt( - credentialId: string, - column: "secret" | "refreshSecret", - value: string, - ): Promise; + decrypt(credentialId: string, column: "secret" | "refreshSecret", value: string): Promise; /** Every `active` `oauth_token` credential in the tenant whose stored * `expiresAt` is at or before `cutoff` — the tenant-wide serving-time * sweep `refreshTenantServingCredentials` runs ahead of a dial. */ @@ -170,9 +163,7 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { ...(result.tokens.refresh_token === undefined ? {} : { refreshToken: result.tokens.refresh_token }), - ...(result.tokens.expires_in === undefined - ? {} - : { expiresIn: result.tokens.expires_in }), + ...(result.tokens.expires_in === undefined ? {} : { expiresIn: result.tokens.expires_in }), }; }); @@ -193,11 +184,7 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { refreshSecret: row.refreshSecret === null ? null - : await deps.store.decrypt( - row.id, - "refreshSecret", - row.refreshSecret, - ), + : await deps.store.decrypt(row.id, "refreshSecret", row.refreshSecret), expiresAt: row.expiresAt, } satisfies CredentialTokenRow; }, @@ -205,10 +192,7 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { // False = another writer claimed the row (claimed-elsewhere): a // lost race, not an error — skip the follow-up push and let the // next resolution re-read whatever was persisted. - const applied = await deps.store.applyRefreshedTokens( - credentialId, - tokens, - ); + const applied = await deps.store.applyRefreshedTokens(credentialId, tokens); lastApplyWon = applied; }, refresh: async (row) => { @@ -226,9 +210,7 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { const refreshed = await refreshCodexTokens(row.refreshSecret, now(), { access: row.secret, refresh: row.refreshSecret, - ...(row.expiresAt === null - ? {} - : { expiresAt: row.expiresAt.getTime() }), + ...(row.expiresAt === null ? {} : { expiresAt: row.expiresAt.getTime() }), // The account id rides the credential's metadata (written at // connect from the id_token); carry it forward so a refresh // response without an id_token never drops chatgpt-account-id. @@ -236,32 +218,21 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { }); return { secret: refreshed.access, - ...(refreshed.refresh === undefined - ? {} - : { refreshSecret: refreshed.refresh }), - expiresAt: - refreshed.expiresAt === undefined - ? null - : new Date(refreshed.expiresAt), + ...(refreshed.refresh === undefined ? {} : { refreshSecret: refreshed.refresh }), + expiresAt: refreshed.expiresAt === undefined ? null : new Date(refreshed.expiresAt), }; } if (full.providerName === "xai-oauth") { const refreshed = await refreshXaiTokens(row.refreshSecret, now()); return { secret: refreshed.access, - ...(refreshed.refresh === undefined - ? {} - : { refreshSecret: refreshed.refresh }), - expiresAt: - refreshed.expiresAt === undefined - ? null - : new Date(refreshed.expiresAt), + ...(refreshed.refresh === undefined ? {} : { refreshSecret: refreshed.refresh }), + expiresAt: refreshed.expiresAt === undefined ? null : new Date(refreshed.expiresAt), }; } const parsed = CredentialMetadata(full.metadata ?? {}); const serverUrl = - full.apiBaseUrl ?? - (parsed instanceof type.errors ? undefined : parsed.url); + full.apiBaseUrl ?? (parsed instanceof type.errors ? undefined : parsed.url); if (serverUrl === undefined) { throw new Error( `credential ${row.id} has neither a provider API base URL nor a metadata URL to refresh against`, @@ -274,12 +245,10 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { serverUrl, accessToken: row.secret, refreshToken: row.refreshSecret, - ...(parsed instanceof type.errors || - parsed.clientInformation === undefined + ...(parsed instanceof type.errors || parsed.clientInformation === undefined ? {} : { - clientInformation: - parsed.clientInformation as OAuthClientInformationMixed, + clientInformation: parsed.clientInformation as OAuthClientInformationMixed, }), }); return { @@ -291,9 +260,7 @@ export function createServingRefresh(deps: ServingRefreshDeps): ServingRefresh { // the oauth-core stance. Never a short artificial // timer that would re-refresh on every dial. expiresAt: - refreshed.expiresIn === undefined - ? null - : new Date(now() + refreshed.expiresIn * 1000), + refreshed.expiresIn === undefined ? null : new Date(now() + refreshed.expiresIn * 1000), }; }, }); @@ -394,9 +361,7 @@ export function createDrizzleServingRefreshStore( expiresAt: tokens.expiresAt, updatedAt: now, }) - .where( - and(eq(credential.id, credentialId), eq(credential.status, "active")), - ) + .where(and(eq(credential.id, credentialId), eq(credential.status, "active"))) .returning({ id: credential.id }); return updated.length > 0; }, @@ -409,8 +374,7 @@ export function createDrizzleServingRefreshStore( }) .where(eq(credential.id, credentialId)); }, - pushUpdates: (tenantId) => - pushSourceUpdates(db, sidecarRouter, tenantId, credentialCipher), + pushUpdates: (tenantId) => pushSourceUpdates(db, sidecarRouter, tenantId, credentialCipher), decrypt: (credentialId, column, value) => credentialCipher.decrypt(value, credentialAad(credentialId, column)), async loadDueRows(tenantId, cutoff) { @@ -451,10 +415,7 @@ export function createTenantServingRefresh( const skewLeadMs = deps.skewLeadMs ?? 60 * 1000; const now = deps.now ?? Date.now; return async (tenantId: string) => { - const due = await deps.store.loadDueRows( - tenantId, - new Date(now() + skewLeadMs), - ); + const due = await deps.store.loadDueRows(tenantId, new Date(now() + skewLeadMs)); await Promise.all(due.map((row) => refresh(row))); }; } diff --git a/apps/hub/src/crypto-provider-cache-wiring.test.ts b/apps/hub/src/crypto-provider-cache-wiring.test.ts index 4b65746ff0..ceb57c4dce 100644 --- a/apps/hub/src/crypto-provider-cache-wiring.test.ts +++ b/apps/hub/src/crypto-provider-cache-wiring.test.ts @@ -23,9 +23,7 @@ function productionTsFilesUnder(dir: string): string[] { if (entry.name === "node_modules") return []; return productionTsFilesUnder(full); } - return entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") - ? [full] - : []; + return entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") ? [full] : []; }); } @@ -34,9 +32,7 @@ function firstCall(source: string, callee: string): string { const token = `${callee}(`; const start = source.indexOf(token); if (start < 0) { - throw new Error( - `expected ${callee}(...) in ${path.relative(HUB_DIR, HUB_INDEX)}`, - ); + throw new Error(`expected ${callee}(...) in ${path.relative(HUB_DIR, HUB_INDEX)}`); } let depth = 1; let i = start + token.length; @@ -52,28 +48,19 @@ function firstCall(source: string, callee: string): string { describe("hub crypto-provider cache wiring", () => { test("createHub constructs one cache and passes it to every mail sender", () => { const sites = productionTsFilesUnder(HUB_DIR).flatMap((file) => { - const matches = readFileSync(file, "utf8").match( - /createCryptoProviderCache\s*\(/g, - ); + const matches = readFileSync(file, "utf8").match(/createCryptoProviderCache\s*\(/g); return matches === null ? [] : matches.map(() => file); }); expect(sites).toEqual([HUB_INDEX]); const source = readFileSync(HUB_INDEX, "utf8"); - const assigned = - /const\s+(\w+)\s*=\s*createCryptoProviderCache\s*\(\s*\)/.exec( - source, - )?.[1]; + const assigned = /const\s+(\w+)\s*=\s*createCryptoProviderCache\s*\(\s*\)/.exec(source)?.[1]; if (assigned === undefined) { - throw new Error( - "createHub must assign createCryptoProviderCache() to a const", - ); + throw new Error("createHub must assign createCryptoProviderCache() to a const"); } expect(firstCall(source, "createHubChatPlatform")).toContain(assigned); - expect(firstCall(source, "launchWebhookTrigger")).toContain( - `cryptoProviderCache: ${assigned}`, - ); + expect(firstCall(source, "launchWebhookTrigger")).toContain(`cryptoProviderCache: ${assigned}`); // The planner draft route's `runOneShotPrompt` mail sender was cut // with the stock-hub cutover (its inventory read through the deleted // memory/skills mounts), so only the two senders above remain pinned. diff --git a/apps/hub/src/grant-allowance.ts b/apps/hub/src/grant-allowance.ts index dce564155e..9c09dd9fdf 100644 --- a/apps/hub/src/grant-allowance.ts +++ b/apps/hub/src/grant-allowance.ts @@ -22,11 +22,7 @@ import { credentialAad } from "@intx/types"; import { resolveApproval, type CreateApprovalRoutesDeps } from "@intx/hub-api"; import type { GrantRule } from "@intx/types/authz"; import type { GrantAllowanceGateDeps } from "@corbits/approvals"; -import { - listMcpTools, - withMcpConnection, - type McpServerToolsLoader, -} from "@corbits/mcp-tools"; +import { listMcpTools, withMcpConnection, type McpServerToolsLoader } from "@corbits/mcp-tools"; import { listMcpServerConnections } from "@corbits/connections"; import { MCP_NO_TOKEN_SENTINEL } from "@corbits/credential-providers"; @@ -48,11 +44,7 @@ export function createMcpServerToolsAllowanceLoader(deps: { const connection = connections.find((c) => c.slug === serverSlug); if (connection === undefined || connection.url.length === 0) return null; - const provider = await resolveProviderByName( - deps.db, - tenantId, - `mcp:${serverSlug}`, - ); + const provider = await resolveProviderByName(deps.db, tenantId, `mcp:${serverSlug}`); if (provider === null) return null; const credential = await deps.db.query.credential.findFirst({ where: and( @@ -77,9 +69,7 @@ export function createMcpServerToolsAllowanceLoader(deps: { }; } -export function createTenantGrantLister( - db: DB["db"], -): GrantAllowanceGateDeps["listTenantGrants"] { +export function createTenantGrantLister(db: DB["db"]): GrantAllowanceGateDeps["listTenantGrants"] { return async (tenantId) => { const rows = await db.query.grant.findMany({ where: eq(schema.grant.tenantId, tenantId), @@ -120,9 +110,7 @@ export function createAllowanceAutoApprover( }, }); if (result.kind !== "resolved") { - log( - `grant-allowance: approval ${approvalId} auto-resolve returned "${result.kind}"`, - ); + log(`grant-allowance: approval ${approvalId} auto-resolve returned "${result.kind}"`); return false; } return true; diff --git a/apps/hub/src/hub-data-dir-git-safety.test.ts b/apps/hub/src/hub-data-dir-git-safety.test.ts index 1e5a60af0d..e496535216 100644 --- a/apps/hub/src/hub-data-dir-git-safety.test.ts +++ b/apps/hub/src/hub-data-dir-git-safety.test.ts @@ -1,11 +1,7 @@ import { afterAll, expect, test } from "bun:test"; import { mkdirSync, realpathSync, writeFileSync } from "node:fs"; import path from "node:path"; -import { - cleanupGitFixtures, - gitFixture, - scratchGitFixtureDir, -} from "../test/git-fixture"; +import { cleanupGitFixtures, gitFixture, scratchGitFixtureDir } from "../test/git-fixture"; import { assertHubDataDirGitSafety, enclosingGitWorkTree, @@ -30,9 +26,7 @@ test("a directory inside an existing git work tree fails loud", () => { mkdirSync(dataDir, { recursive: true }); expect(enclosingGitWorkTree(dataDir)).toBe(realpathSync(workTree)); - expect(() => assertHubDataDirGitSafety(dataDir)).toThrow( - HubDataDirInsideGitWorkTreeError, - ); + expect(() => assertHubDataDirGitSafety(dataDir)).toThrow(HubDataDirInsideGitWorkTreeError); try { assertHubDataDirGitSafety(dataDir); throw new Error("expected assertHubDataDirGitSafety to throw"); @@ -50,7 +44,5 @@ test("opt-in allows initializing inside an existing git work tree", () => { const workTree = scratchGitFixtureDir("hub-data-opt-in-"); gitFixture(workTree, ["init", "-b", "main"]); const dataDir = path.join(workTree, ".data", "hub"); - expect(() => - assertHubDataDirGitSafety(dataDir, { allowInsideWorkTree: true }), - ).not.toThrow(); + expect(() => assertHubDataDirGitSafety(dataDir, { allowInsideWorkTree: true })).not.toThrow(); }); diff --git a/apps/hub/src/hub-data-dir-git-safety.ts b/apps/hub/src/hub-data-dir-git-safety.ts index d4f530c127..ff0dcb7876 100644 --- a/apps/hub/src/hub-data-dir-git-safety.ts +++ b/apps/hub/src/hub-data-dir-git-safety.ts @@ -15,9 +15,7 @@ const GIT_DIR_VARS = new Set([ "GIT_ALTERNATE_OBJECT_DIRECTORIES", ]); -export function envWithoutGitDir( - env: NodeJS.ProcessEnv = process.env, -): NodeJS.ProcessEnv { +export function envWithoutGitDir(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv { const next: NodeJS.ProcessEnv = {}; for (const [key, value] of Object.entries(env)) { if (value === undefined) continue; @@ -75,8 +73,5 @@ export function assertHubDataDirGitSafety( if (opts?.allowInsideWorkTree === true) return; const enclosing = enclosingGitWorkTree(dir, opts?.env ?? process.env); if (enclosing === null) return; - throw new HubDataDirInsideGitWorkTreeError( - realpathSync(path.resolve(dir)), - enclosing, - ); + throw new HubDataDirInsideGitWorkTreeError(realpathSync(path.resolve(dir)), enclosing); } diff --git a/apps/hub/src/hub-error-handler.test.ts b/apps/hub/src/hub-error-handler.test.ts index 2b0d250c0d..7aa11a80c2 100644 --- a/apps/hub/src/hub-error-handler.test.ts +++ b/apps/hub/src/hub-error-handler.test.ts @@ -15,8 +15,7 @@ import { hubErrorHandler } from "./hub-error-handler"; * stands in for the real `InferenceResolutionError` (`@corbits/chat`'s * `model-unavailable.ts`) without importing it. */ -const MODEL_UNAVAILABLE_CONSUMER_MESSAGE = - "This agent's model isn't available here."; +const MODEL_UNAVAILABLE_CONSUMER_MESSAGE = "This agent's model isn't available here."; class FakeInferenceResolutionError extends Error { readonly guidance: string; @@ -67,9 +66,7 @@ describe("hubErrorHandler", () => { error: { code: string; userMessage: string; refId: string }; }; expect(body.error.code).toBe("internal_error"); - expect(body.error.userMessage).toBe( - "Something went wrong. Please try again.", - ); + expect(body.error.userMessage).toBe("Something went wrong. Please try again."); expect(typeof body.error.refId).toBe("string"); expect(body.error.refId.length).toBeGreaterThan(0); @@ -101,9 +98,7 @@ describe("hubErrorHandler", () => { error: { code: string; userMessage: string; refId: string }; }; expect(body.error.code).toBe("MultiStepFoldUnsupportedError"); - expect(body.error.userMessage).toBe( - "definition wfd_research is not single-step (2 steps)", - ); + expect(body.error.userMessage).toBe("definition wfd_research is not single-step (2 steps)"); expect(typeof body.error.refId).toBe("string"); expect(body.error.refId.length).toBeGreaterThan(0); expect(records).toHaveLength(1); @@ -127,9 +122,7 @@ describe("hubErrorHandler", () => { error: { code: string; userMessage: string; refId: string }; }; expect(body.error.code).toBe("InferenceResolutionError"); - expect(body.error.userMessage).toBe( - "This agent's model isn't available here.", - ); + expect(body.error.userMessage).toBe("This agent's model isn't available here."); expect(body.error.userMessage).not.toContain("claude-sonnet-5"); expect(body.error.userMessage).not.toMatch(/cannot resolve an inference/); expect(typeof body.error.refId).toBe("string"); diff --git a/apps/hub/src/hub-error-handler.ts b/apps/hub/src/hub-error-handler.ts index 384004edc3..a0423ec41d 100644 --- a/apps/hub/src/hub-error-handler.ts +++ b/apps/hub/src/hub-error-handler.ts @@ -51,8 +51,7 @@ export function hubErrorHandler() { }); if (hasGuidance(err)) { - const userMessage = - err.name === "InferenceResolutionError" ? err.guidance : err.message; + const userMessage = err.name === "InferenceResolutionError" ? err.guidance : err.message; return c.json( makeErrorEnvelope({ code: err.name, diff --git a/apps/hub/src/hub-zero-t4-registry.test.ts b/apps/hub/src/hub-zero-t4-registry.test.ts index b1e0724ac6..606d20b3f8 100644 --- a/apps/hub/src/hub-zero-t4-registry.test.ts +++ b/apps/hub/src/hub-zero-t4-registry.test.ts @@ -16,15 +16,7 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import path from "node:path"; -const HUB_SRC = path.join( - import.meta.dir, - "..", - "..", - "..", - "apps", - "hub", - "src", -); +const HUB_SRC = path.join(import.meta.dir, "..", "..", "..", "apps", "hub", "src"); function readHub(rel: string): string { return readFileSync(path.join(HUB_SRC, rel), "utf8"); @@ -46,10 +38,7 @@ describe("hub-zero T4: no legacy template import in the hub", () => { "native-workflow-routine-launch.ts", ]) { test(`${rel} imports no legacy template package`, () => { - expect( - readHub(rel).includes(LEGACY_PKG), - `${rel} still imports ${LEGACY_PKG}`, - ).toBe(false); + expect(readHub(rel).includes(LEGACY_PKG), `${rel} still imports ${LEGACY_PKG}`).toBe(false); }); } }); @@ -57,10 +46,9 @@ describe("hub-zero T4: no legacy template import in the hub", () => { describe("hub-zero T4: the scheduled delivery join is cut", () => { test("no settings-row list read survives in the hub", () => { for (const rel of ["index.ts", "native-workflow-routine-launch.ts"]) { - expect( - readHub(rel).includes(LIST_SETTINGS), - `${rel} still reads ${LIST_SETTINGS}`, - ).toBe(false); + expect(readHub(rel).includes(LIST_SETTINGS), `${rel} still reads ${LIST_SETTINGS}`).toBe( + false, + ); } }); @@ -77,9 +65,7 @@ describe("hub-zero T4: the scheduled delivery join is cut", () => { test("no delivery-join seam survives in index.ts", () => { const source = readHub("index.ts"); for (const needle of [JOIN_DEPS, JOIN_FN, JOIN_PORT]) { - expect(source.includes(needle), `index.ts still contains ${needle}`).toBe( - false, - ); + expect(source.includes(needle), `index.ts still contains ${needle}`).toBe(false); } }); @@ -119,9 +105,7 @@ describe("hub-zero T4: the native connector registry", () => { "gmail", "github", ]) { - expect(ids.has(id), `native registry is missing connector ${id}`).toBe( - true, - ); + expect(ids.has(id), `native registry is missing connector ${id}`).toBe(true); } }); @@ -129,12 +113,8 @@ describe("hub-zero T4: the native connector registry", () => { const { CONNECTOR_REGISTRY } = await import("./native-connector-registry"); for (const [key, descriptor] of Object.entries(CONNECTOR_REGISTRY)) { expect(descriptor.id, `${key} id`).toBe(key); - expect(descriptor.displayName.length > 0, `${key} displayName`).toBe( - true, - ); - expect(Array.isArray(descriptor.feedsTools), `${key} feedsTools`).toBe( - true, - ); + expect(descriptor.displayName.length > 0, `${key} displayName`).toBe(true); + expect(Array.isArray(descriptor.feedsTools), `${key} feedsTools`).toBe(true); } }); @@ -169,9 +149,7 @@ describe("hub-zero T4: the native connector registry", () => { } for (const preset of MCP_PRESETS) { expect(preset.url.length > 0, `${preset.slug} url`).toBe(true); - expect(preset.connectionMode.length > 0, `${preset.slug} mode`).toBe( - true, - ); + expect(preset.connectionMode.length > 0, `${preset.slug} mode`).toBe(true); } }); }); diff --git a/apps/hub/src/index.ts b/apps/hub/src/index.ts index 8edca89c2b..0e26e0a1be 100644 --- a/apps/hub/src/index.ts +++ b/apps/hub/src/index.ts @@ -17,16 +17,9 @@ import { createWorkflowRunDispatchStore, listAssetsForTenant, } from "@intx/db"; -import { - tenant as tenantTable, - user as userTable, - workflowDefinition, -} from "@intx/db/schema"; +import { tenant as tenantTable, user as userTable, workflowDefinition } from "@intx/db/schema"; import { and, eq, inArray } from "drizzle-orm"; -import { - createEnvKeyCredentialCipher, - createNoopCredentialCipher, -} from "@intx/crypto"; +import { createEnvKeyCredentialCipher, createNoopCredentialCipher } from "@intx/crypto"; import type { CredentialCipher } from "@intx/types"; import { createApp, @@ -78,18 +71,12 @@ import { createInMemoryTurnClaimStore, createWorkbenchTurnQueue, } from "@corbits/agent-runtime"; -import { - createDrizzleMailboxWriter, - type MailboxFanoutDeps, -} from "@corbits/chat/mailbox-fanout"; +import { createDrizzleMailboxWriter, type MailboxFanoutDeps } from "@corbits/chat/mailbox-fanout"; import type { RelaunchNoticePort } from "@corbits/chat"; import { reportError } from "@corbits/error-sink"; import type { FinalizedTurnToolCall } from "@corbits/turn-artifacts"; import { decodedOrNull } from "@corbits/url-path"; -import { - createInboxRoutes, - WORKBENCH_MAILBOX_VOCABULARY, -} from "@corbits/inbox"; +import { createInboxRoutes, WORKBENCH_MAILBOX_VOCABULARY } from "@corbits/inbox"; import { createInMemoryMailboxEventBus, createMailboxDb, @@ -113,10 +100,7 @@ import { createWebhookTriggerRoutes, launchWebhookTrigger, } from "@corbits/webhook-triggers"; -import { - ensureRunSession, - isConversationalWorkflowName, -} from "@corbits/workflows"; +import { ensureRunSession, isConversationalWorkflowName } from "@corbits/workflows"; import { createSidecarProvisioner as createE2BSidecarProvisioner, readProvisionerConfig as readE2BProvisionerConfig, @@ -143,15 +127,8 @@ import { grantConditionRegistry } from "./grant-conditions"; import { wireMailRedelivery } from "./mail-redelivery"; import { getLogger, setup } from "@intx/log"; import { hexEncode } from "@intx/types"; -import { - createToolAllowanceRegistry, - withGrantAllowance, -} from "@corbits/approvals"; -import { - createMcpCallClassifier, - MCP_CALL_TOOL, - mcpTools, -} from "@corbits/mcp-tools"; +import { createToolAllowanceRegistry, withGrantAllowance } from "@corbits/approvals"; +import { createMcpCallClassifier, MCP_CALL_TOOL, mcpTools } from "@corbits/mcp-tools"; import { createAllowanceAutoApprover, createMcpServerToolsAllowanceLoader, @@ -211,11 +188,7 @@ import { type Context, Hono, type Next } from "hono"; import { upgradeWebSocket, websocket } from "hono/bun"; import { CORBITS_TOOLS_REGISTRY } from "@corbits/tool-registry-publish"; -import { - readHubConfig, - type HubConfig, - type SidecarProvisionerConfig, -} from "./config"; +import { readHubConfig, type HubConfig, type SidecarProvisionerConfig } from "./config"; import type { SidecarProvisioner } from "@intx/hub-sessions"; import { withTurnPartWriteDefaults } from "./turn-part-content-default"; import { createDrizzleTurnTextSnapshotReader } from "./turn-text-snapshot"; @@ -224,10 +197,7 @@ import { triggerNativeWorkflowRoutineRun } from "./native-workflow-routine-launc import { createCronEmitter } from "@corbits/workflow-schedule/emitter"; import { createToolGrantsForPins } from "./tool-grants"; import { drainHubServer, shutdownHub } from "./shutdown"; -import { - createInFlightRequestTracker, - withInFlightRequestTracking, -} from "./in-flight-requests"; +import { createInFlightRequestTracker, withInFlightRequestTracking } from "./in-flight-requests"; import { createWorkflowRunAuthenticator, withWorkflowRunTenantAuth, @@ -339,9 +309,7 @@ export function credentialCipherFrom( log.warn`No CREDENTIAL_ENCRYPTION_KEY configured; secrets (e.g. webhook-trigger signing secrets) will NOT be encrypted at rest. ALLOW_PLAINTEXT_SECRETS is set — expected in dev/test only, never for a real deployment.`; return createNoopCredentialCipher(); } - return createEnvKeyCredentialCipher( - Buffer.from(config.credentialEncryptionKeyHex, "hex"), - ); + return createEnvKeyCredentialCipher(Buffer.from(config.credentialEncryptionKeyHex, "hex")); } /** @@ -384,9 +352,7 @@ export function principalKeyStoreFrom( } return createPrincipalKeyStore({ db, - cipher: createEnvKeyCredentialCipher( - Buffer.from(config.principalKeyEncryptionKeyHex, "hex"), - ), + cipher: createEnvKeyCredentialCipher(Buffer.from(config.principalKeyEncryptionKeyHex, "hex")), }); } @@ -436,9 +402,7 @@ function buildSidecarProvisioner( }, dataDir: path.resolve( hubDataDir, - role === "probe" - ? "process-provisioner-probe" - : "process-provisioner", + role === "probe" ? "process-provisioner-probe" : "process-provisioner", ), hubWebSocketUrl, }), @@ -447,11 +411,7 @@ function buildSidecarProvisioner( return createDockerSidecarProvisioner({ config: { image: config.image, - stateFilePath: path.resolve( - hubDataDir, - "docker-provisioner", - "state.json", - ), + stateFilePath: path.resolve(hubDataDir, "docker-provisioner", "state.json"), }, }); case "e2b": @@ -476,9 +436,7 @@ function buildSidecarProvisioner( export async function createHub(config: HubConfig) { const { db, close } = createDB(dbConfigFromUrl(config.databaseUrl)); - const { db: mailboxDb, close: closeMailbox } = createMailboxDb( - config.databaseUrl, - ); + const { db: mailboxDb, close: closeMailbox } = createMailboxDb(config.databaseUrl); const mailboxBus = createInMemoryMailboxEventBus(); const log = getLogger(["hub", "auth"]); // Built once, tagged, and shared by every secret-at-rest seam in this @@ -540,14 +498,11 @@ export async function createHub(config: HubConfig) { config.signInRateLimit.windowSeconds, config.signInRateLimit.max, ); - const { signingKey, agentRepoStore, assetService } = - await createBootAssetWiring({ - db, - dataDir: config.hubDataDir, - ...(config.allowGitInsideWorkTree === true - ? { allowGitInsideWorkTree: true } - : {}), - }); + const { signingKey, agentRepoStore, assetService } = await createBootAssetWiring({ + db, + dataDir: config.hubDataDir, + ...(config.allowGitInsideWorkTree === true ? { allowGitInsideWorkTree: true } : {}), + }); const baseLookups = createHubSessionLookups({ db, agentRepoStore }); // A chat agent is a native provisioned deployment. Reconnect // ownership is Interchange's live run. Completed means dead; wake is a @@ -561,9 +516,7 @@ export async function createHub(config: HubConfig) { // assigned once they do; until then every registration takes the plain // parked path. const grantAllowanceGateRef: { - current?: ( - args: Parameters[0], - ) => Promise; + current?: (args: Parameters[0]) => Promise; } = {}; // Forward reference: `eventCollectors` (the wrapped // `EventCollectorRegistry`) isn't constructed until later in this @@ -648,8 +601,7 @@ export async function createHub(config: HubConfig) { oauthLogin: { isLocalSidecar: async (identity) => { const allocation = await db.query.sidecarAllocation.findFirst({ - where: (allocation, { eq: equals }) => - equals(allocation.id, identity.allocationId), + where: (allocation, { eq: equals }) => equals(allocation.id, identity.allocationId), columns: { provisionerId: true }, }); return allocation?.provisionerId === "process"; @@ -800,8 +752,7 @@ export async function createHub(config: HubConfig) { }, }); const hubWebSocketUrl = - config.sidecarWebSocketUrl ?? - `${config.baseUrl.replace(/^http/, "ws")}/api/sidecars/ws`; + config.sidecarWebSocketUrl ?? `${config.baseUrl.replace(/^http/, "ws")}/api/sidecars/ws`; // Provisioner plugins are injected at the application composition // boundary, mirroring @intx/hub-sessions's own reference wiring. An // install that configures nothing registers the `process` backend @@ -822,12 +773,7 @@ export async function createHub(config: HubConfig) { const buildSidecarPlugins = (role: ProcessProvisionerRole) => createSidecarPluginRegistry({ provisioners: config.sidecarProvisioners.map((provisionerConfig) => - buildSidecarProvisioner( - provisionerConfig, - config.hubDataDir, - hubWebSocketUrl, - role, - ), + buildSidecarProvisioner(provisionerConfig, config.hubDataDir, hubWebSocketUrl, role), ), ...(config.defaultSidecarProvisionerId !== undefined ? { defaultProvisionerId: config.defaultSidecarProvisionerId } @@ -863,9 +809,7 @@ export async function createHub(config: HubConfig) { hubWebSocketUrl, onReady: async (allocation) => { await workflowAllocationService.deployReadyAllocation(allocation); - await workflowDispatchService.requeueForReadyAllocation( - allocation.anchorRunId, - ); + await workflowDispatchService.requeueForReadyAllocation(allocation.anchorRunId); }, }); await workflowAllocationService.initialize?.(); @@ -877,21 +821,16 @@ export async function createHub(config: HubConfig) { sidecarRouter.events.on("sidecar.allocated.connected", (allocated) => sidecarAllocationReconciler.handleConnected(allocated), ); - sidecarRouter.events.on( - "mail.inbound.acknowledged", - ({ messageId, allocated }) => { - if (allocated === undefined) return; - return workflowDispatchService.acknowledge({ ...allocated, messageId }); - }, - ); + sidecarRouter.events.on("mail.inbound.acknowledged", ({ messageId, allocated }) => { + if (allocated === undefined) return; + return workflowDispatchService.acknowledge({ ...allocated, messageId }); + }); const sidecarAllocationLog = getLogger(["hub", "sidecar-allocation"]); const ALLOCATION_RECONCILIATION_INTERVAL_MS = 1_000; const ALLOCATION_CONNECTION_REPAIR_INTERVAL_MS = 30_000; - let nextAllocationConnectionRepairAt = - Date.now() + ALLOCATION_CONNECTION_REPAIR_INTERVAL_MS; + let nextAllocationConnectionRepairAt = Date.now() + ALLOCATION_CONNECTION_REPAIR_INTERVAL_MS; let sidecarAllocationReconciliationStopped = false; - let sidecarAllocationReconciliationTimer: - ReturnType | undefined; + let sidecarAllocationReconciliationTimer: ReturnType | undefined; function scheduleAllocationReconciliation(delayMs: number): void { if (sidecarAllocationReconciliationStopped) return; const timer = setTimeout(() => { @@ -905,8 +844,7 @@ export async function createHub(config: HubConfig) { await sidecarAllocationReconciler.reconcileUntilIdle(); await workflowDispatchService.reconcileUntilIdle(); if (Date.now() >= nextAllocationConnectionRepairAt) { - nextAllocationConnectionRepairAt = - Date.now() + ALLOCATION_CONNECTION_REPAIR_INTERVAL_MS; + nextAllocationConnectionRepairAt = Date.now() + ALLOCATION_CONNECTION_REPAIR_INTERVAL_MS; await sidecarAllocationReconciler.repairUnscheduledConnections(); } } catch (error) { @@ -936,14 +874,9 @@ export async function createHub(config: HubConfig) { // session, the same posture `workflow-run-deploy-auth` documents for // its own bearer mirror in `vendor/intx/hub-api`. getSession: async (headers) => { - const internalRunTriggerToken = headers.get( - "x-corbits-internal-run-trigger", - ); + const internalRunTriggerToken = headers.get("x-corbits-internal-run-trigger"); if (internalRunTriggerToken !== null) { - const userId = verifyInternalRunTriggerToken( - config.sessionSecret, - internalRunTriggerToken, - ); + const userId = verifyInternalRunTriggerToken(config.sessionSecret, internalRunTriggerToken); if (userId === null) return null; const userRow = await db.query.user.findFirst({ where: eq(userTable.id, userId), @@ -1024,8 +957,7 @@ export async function createHub(config: HubConfig) { sidecarRouter.handleOpen(handle); }, onMessage(evt, _ws) { - if (typeof evt.data === "string") - sidecarRouter.handleMessage(handle, evt.data); + if (typeof evt.data === "string") sidecarRouter.handleMessage(handle, evt.data); }, onClose: () => sidecarRouter.handleClose(handle), }; @@ -1077,11 +1009,7 @@ export async function createHub(config: HubConfig) { db, sidecarRouter, workflowDispatchService, - readRunLifecycles: async ( - agentAddress, - topLevelRunId, - targetRunId, - ) => { + readRunLifecycles: async (agentAddress, topLevelRunId, targetRunId) => { const lifecycles = await readDurableWorkflowRunLifecycles( agentRepoStore.repoStore, agentAddress, @@ -1118,10 +1046,9 @@ export async function createHub(config: HubConfig) { const turnMailCorrelation = createDrizzleTurnMailCorrelationStore(db); // The chat platform's invite-launch fallback: a definition with no // model requirements of its own resolves the tenant-catalog default. - const chatHostInferencePreferencesResolver = - createWorkbenchHostInferencePreferencesResolver((tenantId) => - listDefaultInferencePreferences(db, tenantId), - ); + const chatHostInferencePreferencesResolver = createWorkbenchHostInferencePreferencesResolver( + (tenantId) => listDefaultInferencePreferences(db, tenantId), + ); // Where a relaunch announces itself in the room (see `@corbits/chat`'s // `relaunch-notice.ts`). Armed further down, once the room-message // store the poster writes through exists — the platform that fires @@ -1162,11 +1089,7 @@ export async function createHub(config: HubConfig) { // refreshed frames, so the run dials on a live token or fails over // past a credential that just went re-auth-required. refreshServingCredentials: createTenantServingRefresh({ - store: createDrizzleServingRefreshStore( - db, - credentialCipher, - sidecarRouter, - ), + store: createDrizzleServingRefreshStore(db, credentialCipher, sidecarRouter), hubUrl: config.baseUrl, }), // A hand-authored definition with no model requirements of its own @@ -1299,9 +1222,7 @@ export async function createHub(config: HubConfig) { }); // Now that `chatStore`/`chatPlatform` exist, arm the finalized-turn // artifact-delivery ref declared beside `eventCollectors` above. - const artifactDeliveryHandlerDeps: Parameters< - typeof createArtifactDeliveryHandler - >[0] = { + const artifactDeliveryHandlerDeps: Parameters[0] = { db, store: chatStore, roomMessages, @@ -1316,9 +1237,7 @@ export async function createHub(config: HubConfig) { providerHealth: createProviderHealthPort(providerHealthStore), listConnectedProviders: (tenantId) => listConnectedProviders(db, tenantId), }; - artifactDeliveryHandlerRef.current = createArtifactDeliveryHandler( - artifactDeliveryHandlerDeps, - ); + artifactDeliveryHandlerRef.current = createArtifactDeliveryHandler(artifactDeliveryHandlerDeps); // The "/name args" and "@name args" command registry: every tenant's // invitable workflow definitions, exposed as commands by // `createWorkflowCommandPlugin`, resolved fresh on every list/lookup @@ -1331,8 +1250,7 @@ export async function createHub(config: HubConfig) { const commandRegistry = createCommandRegistry(); commandRegistry.registerCommandPlugin( createWorkflowCommandPlugin({ - listInvitableDefinitions: (tenantId) => - chatPlatform.listInvitableDefinitions(tenantId), + listInvitableDefinitions: (tenantId) => chatPlatform.listInvitableDefinitions(tenantId), startWorkflow: (input) => startWorkflowCommand( { @@ -1369,8 +1287,7 @@ export async function createHub(config: HubConfig) { // keeps around. Wired into the picker surface: chat's invite/new-chat // dialogs (`chatDeps.isInvitableDefinition` below). const isPickerListableDefinition = (definition: { name: string }) => - isConversationalAgentDefinition(definition) && - !isPlannerCreatedDefinitionName(definition.name); + isConversationalAgentDefinition(definition) && !isPlannerCreatedDefinitionName(definition.name); const chatDeps: Parameters[0] = { store: chatStore, @@ -1380,8 +1297,7 @@ export async function createHub(config: HubConfig) { threads: threadStore, turnMailCorrelation, agentTurns, - turnTextSnapshot: (input) => - createDrizzleTurnTextSnapshotReader(db).read(input), + turnTextSnapshot: (input) => createDrizzleTurnTextSnapshotReader(db).read(input), blockResponses: blockResponseStore, reactions: reactionStore, pins: pinStore, @@ -1414,8 +1330,7 @@ export async function createHub(config: HubConfig) { // instance down when it is removed from a chat's participants, // rather than leaving it deployed with nothing routing messages to // it. - releaseAgentInstance: (address, reason) => - sidecarRouter.sendAgentUndeploy(address, reason), + releaseAgentInstance: (address, reason) => sidecarRouter.sendAgentUndeploy(address, reason), // CL-7450: fans a sent human message into every human participant's // `@corbits/mailbox` inbox, on the same `mailboxDb`/`mailboxBus` every // other mailbox consumer in this file shares. `resolveKnownPrincipalIds` @@ -1476,10 +1391,7 @@ export async function createHub(config: HubConfig) { // (mentions + deliveries only), clear-done. The raw package surface // (including SSE events) mounts under `/mailbox` for hosts and tools // that need the universal API. - app.route( - `${TENANT_PREFIX}/inbox`, - createInboxRoutes({ db: mailboxDb, bus: mailboxBus }), - ); + app.route(`${TENANT_PREFIX}/inbox`, createInboxRoutes({ db: mailboxDb, bus: mailboxBus })); // Library/artifacts plane (CL-8188): `@corbits/artifacts` mounted directly // rather than through the retired `@corbits/artifacts-hub` wrapper — the // library now owns counts, preview, and every other Library HTTP surface @@ -1500,8 +1412,7 @@ export async function createHub(config: HubConfig) { countSegments: Object.fromEntries( LIBRARY_KIND_SEGMENTS.map((segment) => [ segment, - (row: { kind: string; title: string }) => - artifactMatchesLibraryKindSegment(row, segment), + (row: { kind: string; title: string }) => artifactMatchesLibraryKindSegment(row, segment), ]), ), }); @@ -1522,8 +1433,7 @@ export async function createHub(config: HubConfig) { mountWorkflowArtifacts(workflowArtifactsApi, { db, contentStore: artifactContentStore, - resolveRunScope: (token, runAddress) => - workflowRunAuthenticator.resolve(token, runAddress), + resolveRunScope: (token, runAddress) => workflowRunAuthenticator.resolve(token, runAddress), }); app.route("/api/workflow-artifacts", workflowArtifactsApi); } @@ -1602,8 +1512,8 @@ export async function createHub(config: HubConfig) { conditionRegistry: grantConditionRegistry, }), workbenchBelongsToTenant: async (tenantId, chatId) => - (await chatStore.getWorkbenchSettings(tenantId, chatId)) !== - undefined || (await chatStore.hasLaunchedInstance(tenantId, chatId)), + (await chatStore.getWorkbenchSettings(tenantId, chatId)) !== undefined || + (await chatStore.hasLaunchedInstance(tenantId, chatId)), }), ); @@ -1618,10 +1528,7 @@ export async function createHub(config: HubConfig) { // comes from the trigger row the id resolves to, and the only trust // it is granted comes from the HMAC signature check in // `createWebhookIngressRoutes` itself. - const webhookTriggerStore = createDrizzleWebhookTriggerStore( - db, - credentialCipher, - ); + const webhookTriggerStore = createDrizzleWebhookTriggerStore(db, credentialCipher); app.route( `${TENANT_PREFIX}/webhook-triggers`, createWebhookTriggerRoutes({ @@ -1736,17 +1643,14 @@ export async function createHub(config: HubConfig) { gmailClientSecret: config.gmailClientSecret, }, providerHealth: providerHealthStore, - listConnectedProviders: (tenantId) => - listConnectedProviders(db, tenantId), + listConnectedProviders: (tenantId) => listConnectedProviders(db, tenantId), // CL-6403: an operator-set GITHUB_API_BASE_URL lets a fake server // stand in for api.github.com for the `github` connector's PAT // probe and stored provider origin; unset in every real deployment, // so `probeBaseUrls` is empty and every connector probes its own // fixed production origin. probeBaseUrls: - config.githubApiBaseUrl !== undefined - ? { github: config.githubApiBaseUrl } - : {}, + config.githubApiBaseUrl !== undefined ? { github: config.githubApiBaseUrl } : {}, onConnected: settleServiceConnection, }), ); @@ -1784,11 +1688,7 @@ export async function createHub(config: HubConfig) { defaultReturnPath: "/settings/connections", // `/w/` is the chat room prefix: the in-room connect card // (CL-6393) starts OAuth from a room and must land back in it. - returnPathAllowlist: [ - ...DEFAULT_RETURN_PATH_ALLOWLIST, - "/plugins", - "/w/", - ], + returnPathAllowlist: [...DEFAULT_RETURN_PATH_ALLOWLIST, "/plugins", "/w/"], }), ); // Loopback OAuth connect (CL-7508): codex/xai-oauth connect through a @@ -1840,11 +1740,7 @@ export async function createHub(config: HubConfig) { presets: MCP_PRESETS, onConnected: settleServiceConnection, // `/w/` for the same reason as the connections/oauth mount above. - returnPathAllowlist: [ - ...DEFAULT_RETURN_PATH_ALLOWLIST, - "/plugins", - "/w/", - ], + returnPathAllowlist: [...DEFAULT_RETURN_PATH_ALLOWLIST, "/plugins", "/w/"], }), ); // Myra's own connections-visibility surface @@ -1947,10 +1843,7 @@ export async function createHub(config: HubConfig) { // died with the process must never stall shutdown, so bound it. // (CL-7584: a fire-and-forget reconcile's request can be cut // mid-query by this very teardown.) - await Promise.race([ - close(), - new Promise((resolve) => setTimeout(resolve, 5_000)), - ]); + await Promise.race([close(), new Promise((resolve) => setTimeout(resolve, 5_000))]); }, }; } @@ -1963,11 +1856,7 @@ if (import.meta.main) { const url = new URL(config.baseUrl); const port = config.listenPort ?? - (url.port === "" - ? url.protocol === "https:" - ? 443 - : 80 - : Number(url.port)); + (url.port === "" ? (url.protocol === "https:" ? 443 : 80) : Number(url.port)); const server = Bun.serve({ fetch: hub.app.fetch, websocket, diff --git a/apps/hub/src/launch-caches.test.ts b/apps/hub/src/launch-caches.test.ts index 20668d7f63..bf5901a2bd 100644 --- a/apps/hub/src/launch-caches.test.ts +++ b/apps/hub/src/launch-caches.test.ts @@ -181,12 +181,8 @@ describe("createLaunchCaches: assetService reads", () => { repoStore, }); - expect(caches.assetService.createAsset).toBe( - inner.assetService.createAsset, - ); - expect(caches.assetService.populateAsset).toBe( - inner.assetService.populateAsset, - ); + expect(caches.assetService.createAsset).toBe(inner.assetService.createAsset); + expect(caches.assetService.populateAsset).toBe(inner.assetService.populateAsset); }); }); @@ -202,16 +198,8 @@ describe("createLaunchCaches: repoStore packs", () => { repoStore, }); - const first = await caches.repoStore.createPack( - principal, - repoId, - HEAD_REF, - ); - const second = await caches.repoStore.createPack( - principal, - repoId, - HEAD_REF, - ); + const first = await caches.repoStore.createPack(principal, repoId, HEAD_REF); + const second = await caches.repoStore.createPack(principal, repoId, HEAD_REF); expect(createPackCalls.length).toBe(1); expect(second).toEqual(first); @@ -242,9 +230,7 @@ describe("createLaunchCaches: repoStore packs", () => { expect(caches.repoStore.initRepo).toBe(repoStore.initRepo); expect(caches.repoStore.writeTree).toBe(repoStore.writeTree); - expect(caches.repoStore.writeTreePreservingPrefix).toBe( - repoStore.writeTreePreservingPrefix, - ); + expect(caches.repoStore.writeTreePreservingPrefix).toBe(repoStore.writeTreePreservingPrefix); expect(caches.repoStore.writeTreeDelta).toBe(repoStore.writeTreeDelta); expect(caches.repoStore.receivePack).toBe(repoStore.receivePack); expect(caches.repoStore.commitPackedTip).toBe(repoStore.commitPackedTip); @@ -252,12 +238,8 @@ describe("createLaunchCaches: repoStore packs", () => { expect(caches.repoStore.listRefs).toBe(repoStore.listRefs); expect(caches.repoStore.resolveHead).toBe(repoStore.resolveHead); expect(caches.repoStore.getRepoDir).toBe(repoStore.getRepoDir); - expect(caches.repoStore.openCommittedReads).toBe( - repoStore.openCommittedReads, - ); - expect(caches.repoStore.openCommittedReadsAtCommit).toBe( - repoStore.openCommittedReadsAtCommit, - ); + expect(caches.repoStore.openCommittedReads).toBe(repoStore.openCommittedReads); + expect(caches.repoStore.openCommittedReadsAtCommit).toBe(repoStore.openCommittedReadsAtCommit); expect(caches.repoStore.subscribe).toBe(repoStore.subscribe); }); }); diff --git a/apps/hub/src/launch-caches.ts b/apps/hub/src/launch-caches.ts index a030627ed0..f80bd582c5 100644 --- a/apps/hub/src/launch-caches.ts +++ b/apps/hub/src/launch-caches.ts @@ -99,9 +99,7 @@ class BoundedCache { } #isOverBudget(): boolean { - return ( - this.#entries.size > this.#maxEntries || this.#bytes > this.#maxBytes - ); + return this.#entries.size > this.#maxEntries || this.#bytes > this.#maxBytes; } } @@ -141,9 +139,7 @@ export function createLaunchCaches(deps: { return repoStore.resolveRef(LAUNCH_CACHE_PRINCIPAL, repoId, effectiveRef); } - async function readAssetBlob( - params: ReadAssetBlobParams, - ): Promise { + async function readAssetBlob(params: ReadAssetBlobParams): Promise { const sha = await resolvePackageRegistryHeadSha(params.assetId, params.ref); if (sha === null) return assetService.readAssetBlob(params); const key = `${params.assetId}:${sha}:${params.path}`; @@ -154,9 +150,7 @@ export function createLaunchCaches(deps: { return blob; } - async function listAssetBlobs( - params: ListAssetBlobsParams, - ): Promise { + async function listAssetBlobs(params: ListAssetBlobsParams): Promise { const sha = await resolvePackageRegistryHeadSha(params.assetId, params.ref); // No resolvable `main` means no tarballs yet. Match the tarball REST // list, which returns [] on this same not_found rather than failing diff --git a/apps/hub/src/mail-redelivery.ts b/apps/hub/src/mail-redelivery.ts index ece570eef9..ab6ba14e4f 100644 --- a/apps/hub/src/mail-redelivery.ts +++ b/apps/hub/src/mail-redelivery.ts @@ -43,26 +43,23 @@ export type WireMailRedeliveryDeps = { export function wireMailRedelivery(deps: WireMailRedeliveryDeps): () => void { const log = getLogger(["hub", "mail-redelivery"]); - return deps.sidecarRouter.events.on( - "mail.outbound.undelivered", - ({ rawMessage, recipients }) => { - for (const recipient of recipients) { - void (async () => { - if (!deps.sidecarRouter.getRoutableAddresses().includes(recipient)) { - try { - await deps.chatPlatform.ensureAwake(recipient); - } catch (cause) { - log.warn`could not wake ${recipient} ahead of redelivery: ${ - cause instanceof Error ? cause.message : String(cause) - }`; - } + return deps.sidecarRouter.events.on("mail.outbound.undelivered", ({ rawMessage, recipients }) => { + for (const recipient of recipients) { + void (async () => { + if (!deps.sidecarRouter.getRoutableAddresses().includes(recipient)) { + try { + await deps.chatPlatform.ensureAwake(recipient); + } catch (cause) { + log.warn`could not wake ${recipient} ahead of redelivery: ${ + cause instanceof Error ? cause.message : String(cause) + }`; } - const delivered = deps.sidecarRouter.routeMail(recipient, rawMessage); - if (!delivered) { - log.warn`redelivery to ${recipient} failed: still unroutable after wake`; - } - })(); - } - }, - ); + } + const delivered = deps.sidecarRouter.routeMail(recipient, rawMessage); + if (!delivered) { + log.warn`redelivery to ${recipient} failed: still unroutable after wake`; + } + })(); + } + }); } diff --git a/apps/hub/src/mailbox-persist.ts b/apps/hub/src/mailbox-persist.ts index 9ea178f71c..e94cf98cd3 100644 --- a/apps/hub/src/mailbox-persist.ts +++ b/apps/hub/src/mailbox-persist.ts @@ -32,9 +32,7 @@ const logger = getLogger(["hub", "mailbox-persist"]); * type from the one place it is public rather than re-declaring its shape * by hand and drifting from the package's own definition. */ -type ResolveMailboxRefs = NonNullable< - CreateMailboxPersistOpts["resolveRefs"] ->; +type ResolveMailboxRefs = NonNullable["resolveRefs"]>; /** * Resolve a `mail.outbound` frame's sender run address to the mailbox @@ -78,9 +76,7 @@ type ResolveMailboxRefs = NonNullable< * constructed until after `lookups` is, so this only reads `.current` * inside the returned closure, never at wiring time. */ -export function createHubPersistMailWithSessionEnsure< - R extends readonly unknown[], ->( +export function createHubPersistMailWithSessionEnsure( db: DB["db"], eventCollectorsRef: { current?: Pick }, upstream: (args: MailboxPersistArgs) => Promise, @@ -107,10 +103,10 @@ export function createHubPersistMailWithSessionEnsure< // record for this frame; skip the vendored delegate rather than let it // throw "no session for address" past the caller. if (sender !== undefined && sender.sessionId === null) { - logger.debug( - "skipping vendored persistMail for run {runId}: no session yet", - { runId: sender.id, senderAddress: args.senderAddress }, - ); + logger.debug("skipping vendored persistMail for run {runId}: no session yet", { + runId: sender.id, + senderAddress: args.senderAddress, + }); // No `session_mail` rows were written for this frame: an empty // result, not a thrown error, so a caller iterating this like // `handleMailPersist` does simply emits no `mail.persisted` events. @@ -120,9 +116,7 @@ export function createHubPersistMailWithSessionEnsure< }; } -export function createHubMailboxAuthorizeSender( - db: DB["db"], -): AuthorizeMailboxSender { +export function createHubMailboxAuthorizeSender(db: DB["db"]): AuthorizeMailboxSender { return async (senderAddress) => { const sender = await resolveRoutableAddress(db, senderAddress); if (sender === undefined) return null; @@ -146,9 +140,7 @@ export function createHubMailboxAuthorizeSender( * under. There is no second id to chase and no header or participant scan * to disambiguate: an agent run is launched in the workbench it serves. */ -export const hubMailboxResolveRefs: ResolveMailboxRefs = ({ - senderAuthorization, -}) => { +export const hubMailboxResolveRefs: ResolveMailboxRefs = ({ senderAuthorization }) => { const ref: MailboxRef = { kind: "workbench", id: senderAuthorization.tenantId, diff --git a/apps/hub/src/mcp-credential-bindings.ts b/apps/hub/src/mcp-credential-bindings.ts index 57bf582f97..6f7c07c708 100644 --- a/apps/hub/src/mcp-credential-bindings.ts +++ b/apps/hub/src/mcp-credential-bindings.ts @@ -17,15 +17,11 @@ import type { DB } from "@intx/db"; import type { CredentialBinding } from "@intx/types"; import { listMcpServerConnections } from "@corbits/connections"; -export type McpCredentialBindingsFor = ( - tenantId: string, -) => Promise; +export type McpCredentialBindingsFor = (tenantId: string) => Promise; const MCP_TOOLS_PACKAGE = "@corbits/mcp-tools"; -export function createMcpCredentialBindingsFor( - db: DB["db"], -): McpCredentialBindingsFor { +export function createMcpCredentialBindingsFor(db: DB["db"]): McpCredentialBindingsFor { return async (tenantId: string) => { const connections = await listMcpServerConnections(db, tenantId); return connections.map((connection): CredentialBinding => ({ diff --git a/apps/hub/src/native-connector-registry.ts b/apps/hub/src/native-connector-registry.ts index 54a6897b1a..d499a39f31 100644 --- a/apps/hub/src/native-connector-registry.ts +++ b/apps/hub/src/native-connector-registry.ts @@ -54,9 +54,10 @@ function chatgptAccountIdFromIdToken(idToken: string): string | undefined { let claims: Record; try { const base64 = payload.replaceAll("-", "+").replaceAll("_", "/"); - claims = JSON.parse( - atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "=")), - ) as Record; + claims = JSON.parse(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))) as Record< + string, + unknown + >; } catch { // report-error-ignore: a malformed id_token payload degrades to "no // account id" — the account id is a label, never a gate, so there is @@ -67,11 +68,7 @@ function chatgptAccountIdFromIdToken(idToken: string): string | undefined { return claims.chatgpt_account_id; } const auth: unknown = claims["https://api.openai.com/auth"]; - if ( - typeof auth === "object" && - auth !== null && - "chatgpt_account_id" in auth - ) { + if (typeof auth === "object" && auth !== null && "chatgpt_account_id" in auth) { const nested = auth.chatgpt_account_id; if (typeof nested === "string") return nested; } @@ -121,10 +118,7 @@ import { XAI_SCOPES, XAI_TOKEN_URL, } from "@corbits/xai-provider/constants"; -import { - exchangeCodeForKey, - OPENROUTER_AUTH_URL, -} from "@corbits/connections/openrouter-connect"; +import { exchangeCodeForKey, OPENROUTER_AUTH_URL } from "@corbits/connections/openrouter-connect"; import { exchangeCodeForGithubToken, GITHUB_AUTHORIZE_URL, @@ -138,22 +132,21 @@ import { testScrapeCreatorsCredential, } from "@corbits/connections/probes"; -const INFERENCE_PROVIDER_DOCS_URL: Record = - { - anthropic: "https://console.anthropic.com/settings/keys", - openai: "https://platform.openai.com/api-keys", - "google-genai": "https://aistudio.google.com/apikey", - xai: "https://console.x.ai", - "xai-oauth": "https://x.ai", - codex: "https://developers.openai.com/codex", - "opencode-zen": "https://opencode.ai/zen", - groq: "https://console.groq.com/keys", - deepseek: "https://platform.deepseek.com/api_keys", - mistral: "https://console.mistral.ai/api-keys", - openrouter: "https://openrouter.ai", - huggingface: "https://huggingface.co/settings/tokens", - ollama: "https://ollama.com", - }; +const INFERENCE_PROVIDER_DOCS_URL: Record = { + anthropic: "https://console.anthropic.com/settings/keys", + openai: "https://platform.openai.com/api-keys", + "google-genai": "https://aistudio.google.com/apikey", + xai: "https://console.x.ai", + "xai-oauth": "https://x.ai", + codex: "https://developers.openai.com/codex", + "opencode-zen": "https://opencode.ai/zen", + groq: "https://console.groq.com/keys", + deepseek: "https://platform.deepseek.com/api_keys", + mistral: "https://console.mistral.ai/api-keys", + openrouter: "https://openrouter.ai", + huggingface: "https://huggingface.co/settings/tokens", + ollama: "https://ollama.com", +}; // Ollama's onboarding URL field default — the local-machine origin // Ollama listens on out of the box, before anyone points it at a @@ -171,10 +164,7 @@ const EXA_ICON = { // four fall through to the monochrome initial tile every iconless // descriptor already gets. const INFERENCE_PROVIDER_ICONS: Partial< - Record< - SupportedCredentialProvider, - { readonly path: string; readonly hex: string } - > + Record > = { anthropic: { path: siAnthropic.path, hex: siAnthropic.hex }, "google-genai": { path: siGooglegemini.path, hex: siGooglegemini.hex }, @@ -222,8 +212,7 @@ function inferenceProviderDescriptors(): Record { credentialPlugin: "http", docsUrl: INFERENCE_PROVIDER_DOCS_URL[providerId], feedsTools: [], - probe: (apiKey) => - testProviderCredential({ provider: providerId, apiKey }), + probe: (apiKey) => testProviderCredential({ provider: providerId, apiKey }), ...(icon !== undefined ? { icon } : {}), }; } @@ -349,9 +338,7 @@ function inferenceProviderDescriptors(): Record { url.searchParams.set("code_challenge", codeChallenge); } url.searchParams.set("code_challenge_method", "S256"); - for (const [name, value] of Object.entries( - CODEX_AUTHORIZE_EXTRA_PARAMS, - )) { + for (const [name, value] of Object.entries(CODEX_AUTHORIZE_EXTRA_PARAMS)) { url.searchParams.set(name, value); } return url; @@ -397,9 +384,7 @@ function inferenceProviderDescriptors(): Record { : {}), ...(typeof tokens.expires_in === "number" ? { - expiresAt: new Date( - Date.now() + tokens.expires_in * 1000, - ).toISOString(), + expiresAt: new Date(Date.now() + tokens.expires_in * 1000).toISOString(), } : {}), ...(accountId !== undefined ? { accountId } : {}), @@ -477,9 +462,7 @@ function inferenceProviderDescriptors(): Record { : {}), ...(typeof tokens.expires_in === "number" ? { - expiresAt: new Date( - Date.now() + tokens.expires_in * 1000, - ).toISOString(), + expiresAt: new Date(Date.now() + tokens.expires_in * 1000).toISOString(), } : {}), }; @@ -517,8 +500,7 @@ export const CONNECTOR_REGISTRY: ConnectorRegistry = createConnectorRegistry({ docsUrl: "https://open.manus.ai/docs/v2/introduction", feedsTools: ["@corbits/manus-tools"], probe: (apiKey) => testManusCredential(apiKey), - description: - "Have Manus run tasks and produce files — including slide decks.", + description: "Have Manus run tasks and produce files — including slide decks.", }, exa: { id: "exa", @@ -598,13 +580,7 @@ export const CONNECTOR_REGISTRY: ConnectorRegistry = createConnectorRegistry({ } return url; }, - exchange: async ({ - code, - codeVerifier, - redirectUri, - clientId, - clientSecret, - }) => { + exchange: async ({ code, codeVerifier, redirectUri, clientId, clientSecret }) => { if (clientId === undefined || clientSecret === undefined) { return { ok: false, @@ -639,8 +615,7 @@ export const CONNECTOR_REGISTRY: ConnectorRegistry = createConnectorRegistry({ authKind: "api-key", docsUrl: "https://github.com/settings/tokens", feedsTools: ["@corbits/github-tools"], - probe: (apiKey, opts) => - testGitHubCredential(apiKey, undefined, opts?.baseUrl), + probe: (apiKey, opts) => testGitHubCredential(apiKey, undefined, opts?.baseUrl), description: "Read repos, issues, and pull requests.", icon: { path: siGithub.path, hex: siGithub.hex }, oauth: { @@ -834,5 +809,4 @@ export const MCP_PRESETS: readonly McpPreset[] = [ }, ]; -export const MCP_PRESET_CONNECTOR_IDS: readonly string[] = - mcpPresetConnectorIds(MCP_PRESETS); +export const MCP_PRESET_CONNECTOR_IDS: readonly string[] = mcpPresetConnectorIds(MCP_PRESETS); diff --git a/apps/hub/src/native-workflow-routine-launch.test.ts b/apps/hub/src/native-workflow-routine-launch.test.ts index 185807ec4e..41f4287184 100644 --- a/apps/hub/src/native-workflow-routine-launch.test.ts +++ b/apps/hub/src/native-workflow-routine-launch.test.ts @@ -80,9 +80,7 @@ describe("triggerNativeWorkflowRoutineRun", () => { address: "wfr_anchor1@acme.hub.test", }); expect(routeMailCalls).toHaveLength(1); - const [call] = routeMailCalls as [ - { address: string; base64: string; messageId: string }, - ]; + const [call] = routeMailCalls as [{ address: string; base64: string; messageId: string }]; expect(call.address).toBe("wfr_anchor1@acme.hub.test"); expect(typeof call.base64).toBe("string"); expect(call.base64.length).toBeGreaterThan(0); diff --git a/apps/hub/src/native-workflow-routine-launch.ts b/apps/hub/src/native-workflow-routine-launch.ts index dd55f27b48..c332372f1d 100644 --- a/apps/hub/src/native-workflow-routine-launch.ts +++ b/apps/hub/src/native-workflow-routine-launch.ts @@ -66,9 +66,7 @@ export class NativeWorkflowDeploymentMissingError extends Error { "This workflow has multiple steps and must be deployed " + "(POST /workflows/deployments) before a routine can launch it — " + "deploy it once, then run this routine again."; - super( - `definition ${definitionId} has no live native deployment (${guidance})`, - ); + super(`definition ${definitionId} has no live native deployment (${guidance})`); this.name = "NativeWorkflowDeploymentMissingError"; this.definitionId = definitionId; this.guidance = guidance; @@ -132,11 +130,7 @@ export async function triggerNativeWorkflowRoutineRun( .orderBy(desc(workflowRun.createdAt)) .limit(1); - if ( - anchor === undefined || - anchor.address === null || - !isLiveWorkflowRunStatus(anchor.status) - ) { + if (anchor === undefined || anchor.address === null || !isLiveWorkflowRunStatus(anchor.status)) { throw new NativeWorkflowDeploymentMissingError(params.definitionId); } const address = anchor.address; @@ -168,10 +162,7 @@ export async function triggerNativeWorkflowRoutineRun( kind: "conversation", text: params.content, }); - const signature = await createDetachedSignatureFromProvider( - signedContent, - crypto, - ); + const signature = await createDetachedSignatureFromProvider(signedContent, crypto); const rawMessage = assembleMessage(headers, signedContent, signature); const base64 = base64Encode(rawMessage); diff --git a/apps/hub/src/notify-delivery.ts b/apps/hub/src/notify-delivery.ts index 663f13bf0a..93f1778987 100644 --- a/apps/hub/src/notify-delivery.ts +++ b/apps/hub/src/notify-delivery.ts @@ -3,10 +3,7 @@ // same adapter the inbox read-back repairs. Kept here instead of inline in // `index.ts` so the composed path is a single construction site the // crash-window suite can exercise directly. -import { - createResolveExistingMailIds, - createWorkbenchMailboxDelivery, -} from "@corbits/inbox"; +import { createResolveExistingMailIds, createWorkbenchMailboxDelivery } from "@corbits/inbox"; import type { MailboxDb, MailboxEventBus } from "@corbits/mailbox"; import { createInMemoryNotifyDispatchStore, diff --git a/apps/hub/src/pinned-package-credential-bindings.ts b/apps/hub/src/pinned-package-credential-bindings.ts index 0afe7bfeb3..e8eca5c645 100644 --- a/apps/hub/src/pinned-package-credential-bindings.ts +++ b/apps/hub/src/pinned-package-credential-bindings.ts @@ -20,10 +20,7 @@ export type PinnedPackageCredentialBindingsFor = ( pins: readonly ToolPackagePin[], ) => Promise; -export type IsConnectorConnected = ( - tenantId: string, - connectorId: string, -) => Promise; +export type IsConnectorConnected = (tenantId: string, connectorId: string) => Promise; export function bindingsForConnectedPins( pins: readonly ToolPackagePin[], diff --git a/apps/hub/src/routability-wiring.test.ts b/apps/hub/src/routability-wiring.test.ts index 62439bdf60..7e04639e46 100644 --- a/apps/hub/src/routability-wiring.test.ts +++ b/apps/hub/src/routability-wiring.test.ts @@ -40,13 +40,9 @@ describe("hub sidecar routability wiring", () => { expect(assigned).not.toBeNull(); // Exactly one definition: no second, diverging routability source. - const definitionCount = source.match( - /getRoutableAddresses\(\)\.includes\(/g, - ); + const definitionCount = source.match(/getRoutableAddresses\(\)\.includes\(/g); expect(definitionCount).toHaveLength(1); - expect(firstCall(source, "launchWebhookTrigger")).toContain( - "isRoutable: isSidecarRoutable", - ); + expect(firstCall(source, "launchWebhookTrigger")).toContain("isRoutable: isSidecarRoutable"); }); }); diff --git a/apps/hub/src/shutdown.test.ts b/apps/hub/src/shutdown.test.ts index 4f6640e323..3e0e8b7837 100644 --- a/apps/hub/src/shutdown.test.ts +++ b/apps/hub/src/shutdown.test.ts @@ -7,10 +7,7 @@ test("drainWithTimeout resolves drained when the drain completes inside the boun }); test("drainWithTimeout resolves timed-out when the drain outlives the bound", async () => { - const outcome = await drainWithTimeout( - () => new Promise(() => undefined), - 10, - ); + const outcome = await drainWithTimeout(() => new Promise(() => undefined), 10); expect(outcome).toEqual({ kind: "timed-out" }); }); diff --git a/apps/hub/src/sign-in-rate-limit.ts b/apps/hub/src/sign-in-rate-limit.ts index deee304c9d..36d2713e00 100644 --- a/apps/hub/src/sign-in-rate-limit.ts +++ b/apps/hub/src/sign-in-rate-limit.ts @@ -121,9 +121,7 @@ export function createSignInAttemptLimiter( if (bucket.count >= max) { return { allowed: false, - retryAfterSeconds: Math.ceil( - (bucket.windowStart + windowMs - now) / 1000, - ), + retryAfterSeconds: Math.ceil((bucket.windowStart + windowMs - now) / 1000), }; } bucket.count += 1; diff --git a/apps/hub/src/tool-grants.test.ts b/apps/hub/src/tool-grants.test.ts index 15783b76cc..f184e9abba 100644 --- a/apps/hub/src/tool-grants.test.ts +++ b/apps/hub/src/tool-grants.test.ts @@ -5,15 +5,8 @@ // than any source import. import { describe, expect, test } from "bun:test"; import type { AssetWithOrigin } from "@intx/db"; -import type { - AssetService, - ListAssetBlobsParams, - ReadAssetBlobParams, -} from "@intx/hub-sessions"; -import { - packToolPackageTarball, - tarballFilenameFor, -} from "@corbits/tool-registry-publish"; +import type { AssetService, ListAssetBlobsParams, ReadAssetBlobParams } from "@intx/hub-sessions"; +import { packToolPackageTarball, tarballFilenameFor } from "@corbits/tool-registry-publish"; import { createToolGrantsForPins } from "./tool-grants"; async function fakeRegistryAssetService( @@ -27,9 +20,7 @@ async function fakeRegistryAssetService( ); return { listAssetBlobs: (params: ListAssetBlobsParams) => - Promise.resolve( - [...blobs.keys()].filter((path) => path.startsWith(`${params.dir}/`)), - ), + Promise.resolve([...blobs.keys()].filter((path) => path.startsWith(`${params.dir}/`))), readAssetBlob: (params: ReadAssetBlobParams) => { const bytes = blobs.get(params.path); if (bytes === undefined) { @@ -48,12 +39,8 @@ function assetRow(id: string, direct: boolean): AssetWithOrigin { } as AssetWithOrigin; } -const CAPABILITY_DIR = new URL( - "../../../packages/capability-tools", - import.meta.url, -).pathname; -const MANUS_DIR = new URL("../../../packages/manus-tools", import.meta.url) - .pathname; +const CAPABILITY_DIR = new URL("../../../packages/capability-tools", import.meta.url).pathname; +const MANUS_DIR = new URL("../../../packages/manus-tools", import.meta.url).pathname; async function grantsFor( assetService: Pick, @@ -87,24 +74,15 @@ describe("createToolGrantsForPins", () => { }); test('floors an unmarked tool at allow and a `approval: "ask"` tool at ask', async () => { - const assetService = await fakeRegistryAssetService([ - await packToolPackageTarball(MANUS_DIR), - ]); + const assetService = await fakeRegistryAssetService([await packToolPackageTarball(MANUS_DIR)]); const grants = await grantsFor( assetService, [assetRow("asset_1", true)], [{ name: "@corbits/manus-tools", version: "*" }], ); - expect( - grants.find((grant) => grant.resource.endsWith(":webhook_create")) - ?.effect, - ).toBe("ask"); - expect( - grants.find((grant) => grant.resource.endsWith(":create_slides"))?.effect, - ).toBe("allow"); - expect( - grants.find((grant) => grant.resource.endsWith(":task_list"))?.effect, - ).toBe("allow"); + expect(grants.find((grant) => grant.resource.endsWith(":webhook_create"))?.effect).toBe("ask"); + expect(grants.find((grant) => grant.resource.endsWith(":create_slides"))?.effect).toBe("allow"); + expect(grants.find((grant) => grant.resource.endsWith(":task_list"))?.effect).toBe("allow"); }); test("unions grants across every pinned package and skips unknown pins", async () => { @@ -121,15 +99,9 @@ describe("createToolGrantsForPins", () => { { name: "@corbits/unknown-tools", version: "^1" }, ], ); - expect( - grants.some((grant) => grant.resource.includes("capability-tools")), - ).toBe(true); - expect(grants.some((grant) => grant.resource.includes("manus-tools"))).toBe( - true, - ); - expect(grants.some((grant) => grant.resource.includes("unknown"))).toBe( - false, - ); + expect(grants.some((grant) => grant.resource.includes("capability-tools"))).toBe(true); + expect(grants.some((grant) => grant.resource.includes("manus-tools"))).toBe(true); + expect(grants.some((grant) => grant.resource.includes("unknown"))).toBe(false); }); test("resolves an inherited registry, not only a direct one", async () => { @@ -158,8 +130,6 @@ describe("createToolGrantsForPins", () => { const assetService = await fakeRegistryAssetService([ await packToolPackageTarball(CAPABILITY_DIR), ]); - expect( - await grantsFor(assetService, [assetRow("asset_1", true)], []), - ).toEqual([]); + expect(await grantsFor(assetService, [assetRow("asset_1", true)], [])).toEqual([]); }); }); diff --git a/apps/hub/src/tool-grants.ts b/apps/hub/src/tool-grants.ts index c6ecb1cd81..b748413d71 100644 --- a/apps/hub/src/tool-grants.ts +++ b/apps/hub/src/tool-grants.ts @@ -41,27 +41,20 @@ export type CreateToolGrantsForPinsDeps = { * shadows-wins down the ancestor chain. The hub binds * `@intx/db`'s `listAssetsForTenant` to its own db handle. */ - listAssets: ( - tenantId: string, - kind: string, - ) => Promise; + listAssets: (tenantId: string, kind: string) => Promise; /** The launch-path asset service — the launch caches' SHA-keyed wrapper. */ assetService: Pick; }; -export function createToolGrantsForPins( - deps: CreateToolGrantsForPinsDeps, -): ToolGrantsForPins { +export function createToolGrantsForPins(deps: CreateToolGrantsForPinsDeps): ToolGrantsForPins { return async (tenantId, pins) => { const assets = await deps.listAssets(tenantId, "package-registry"); const registry = assets.find((row) => row.name === CORBITS_TOOLS_REGISTRY); if (registry === undefined) return []; const source: ToolSurfaceBlobSource = { - listBlobs: (dir) => - deps.assetService.listAssetBlobs({ assetId: registry.id, dir }), - readBlob: (path) => - deps.assetService.readAssetBlob({ assetId: registry.id, path }), + listBlobs: (dir) => deps.assetService.listAssetBlobs({ assetId: registry.id, dir }), + readBlob: (path) => deps.assetService.readAssetBlob({ assetId: registry.id, path }), rootDir: "tarballs", }; const manifests = await readToolSurfaceManifests(source); diff --git a/apps/hub/src/turn-part-content-default.ts b/apps/hub/src/turn-part-content-default.ts index 537e325cc3..5bef653983 100644 --- a/apps/hub/src/turn-part-content-default.ts +++ b/apps/hub/src/turn-part-content-default.ts @@ -28,9 +28,7 @@ export function withTurnPartWriteDefaults(db: DB["db"]): DB["db"] { if (prop !== "insert") { return Reflect.get(target, prop, receiver); } - const insert = Reflect.get(target, prop, target) as ( - table: unknown, - ) => InsertBuilder; + const insert = Reflect.get(target, prop, target) as (table: unknown) => InsertBuilder; return (table: unknown) => { const builder = insert.call(target, table); if (table !== turnPart) { @@ -42,11 +40,7 @@ export function withTurnPartWriteDefaults(db: DB["db"]): DB["db"] { const originalValues = builder.values.bind(builder); return { values: (values: unknown) => - originalValues( - Array.isArray(values) - ? values.map(fillContent) - : fillContent(values), - ), + originalValues(Array.isArray(values) ? values.map(fillContent) : fillContent(values)), }; }; }, diff --git a/apps/hub/src/turn-text-snapshot.ts b/apps/hub/src/turn-text-snapshot.ts index 0494ed1ba4..c3543f3110 100644 --- a/apps/hub/src/turn-text-snapshot.ts +++ b/apps/hub/src/turn-text-snapshot.ts @@ -18,10 +18,7 @@ export interface TurnTextSnapshotReader { * client reattaching mid-turn replays before the live tail resumes. * `null` for a run with no turns yet (nothing to reconstruct), never for * one that simply hasn't produced text yet (that reads as `""`). */ - read(input: { - readonly tenantId: string; - readonly runId: string; - }): Promise; + read(input: { readonly tenantId: string; readonly runId: string }): Promise; } /** Pure so the reconstruction rule is testable without a database: text @@ -57,16 +54,11 @@ export function snapshotTextFromParts( return text; } -export function createDrizzleTurnTextSnapshotReader( - db: DB["db"], -): TurnTextSnapshotReader { +export function createDrizzleTurnTextSnapshotReader(db: DB["db"]): TurnTextSnapshotReader { return { async read({ tenantId, runId }) { const turns = await db.query.inferenceTurn.findMany({ - where: and( - eq(inferenceTurn.runId, runId), - eq(inferenceTurn.tenantId, tenantId), - ), + where: and(eq(inferenceTurn.runId, runId), eq(inferenceTurn.tenantId, tenantId)), orderBy: asc(inferenceTurn.startedAt), }); if (turns.length === 0) return null; diff --git a/apps/hub/src/workflow-run-tenant-auth.ts b/apps/hub/src/workflow-run-tenant-auth.ts index e9fd82bc63..ca881faaad 100644 --- a/apps/hub/src/workflow-run-tenant-auth.ts +++ b/apps/hub/src/workflow-run-tenant-auth.ts @@ -57,10 +57,7 @@ export type CreateWorkflowRunAuthenticatorDeps = { }; export type ConcreteWorkflowRunAuthenticator = { - resolve( - token: string, - runAddress: string, - ): Promise; + resolve(token: string, runAddress: string): Promise; }; export function createWorkflowRunAuthenticator( diff --git a/apps/hub/test/boot-does-not-seed.test.ts b/apps/hub/test/boot-does-not-seed.test.ts index bd909409ae..fc908f0b90 100644 --- a/apps/hub/test/boot-does-not-seed.test.ts +++ b/apps/hub/test/boot-does-not-seed.test.ts @@ -7,13 +7,7 @@ // DB-gated: boots against its own scratch database so a reachable // DATABASE_URL is required and the suite skips without one. import { afterAll, expect, test } from "bun:test"; -import { - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, - writeFileSync, -} from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import postgres from "postgres"; @@ -78,9 +72,7 @@ async function withScratchDatabase( onnotice: () => undefined, }); try { - await cleanup.unsafe( - `DROP DATABASE IF EXISTS "${scratchDatabase}" WITH (FORCE)`, - ); + await cleanup.unsafe(`DROP DATABASE IF EXISTS "${scratchDatabase}" WITH (FORCE)`); } finally { await cleanup.end(); } @@ -95,9 +87,7 @@ async function countTenants(url: string): Promise { `; const count = rows[0]?.count; if (typeof count !== "number") { - throw new Error( - `tenant count: expected a number, got ${JSON.stringify(rows)}`, - ); + throw new Error(`tenant count: expected a number, got ${JSON.stringify(rows)}`); } return count; } finally { @@ -106,10 +96,7 @@ async function countTenants(url: string): Promise { } test("process boot source does not mention the deleted boot seeder", () => { - const indexSource = readFileSync( - path.join(import.meta.dir, "../src/index.ts"), - "utf8", - ); + const indexSource = readFileSync(path.join(import.meta.dir, "../src/index.ts"), "utf8"); expect(indexSource).not.toContain("runSystem" + "Seed"); expect(indexSource).not.toContain("system" + "-seed"); expect(indexSource).not.toContain("ensureDefault" + "Tenant"); @@ -125,9 +112,7 @@ describeIfDb("hub process boot does not mint a root tenant", () => { startHub({ databaseUrl: url, port: freePort(), - sessionSecret: Buffer.from( - crypto.getRandomValues(new Uint8Array(32)), - ).toString("hex"), + sessionSecret: Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex"), dataDir, }), ); @@ -142,13 +127,11 @@ describeIfDb("hub process boot does not mint a root tenant", () => { expect(await countTenants(url)).toBe(0); - const signIn = await hop( - "sign-in as the former boot admin is not 200", - async () => - api(hub.baseUrl, "POST", "/api/auth/sign-in/email", { - email: ALICE.email, - password: ALICE.password, - }), + const signIn = await hop("sign-in as the former boot admin is not 200", async () => + api(hub.baseUrl, "POST", "/api/auth/sign-in/email", { + email: ALICE.email, + password: ALICE.password, + }), ); expect(signIn.status).not.toBe(200); }); @@ -287,9 +270,7 @@ describeIfDb("boot with provider env vars plants nothing (CL-7579)", () => { startHub({ databaseUrl: url, port: freePort(), - sessionSecret: Buffer.from( - crypto.getRandomValues(new Uint8Array(32)), - ).toString("hex"), + sessionSecret: Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex"), dataDir: await tempDir("hub-boot-env-plant-genesis-"), }), ); @@ -300,18 +281,14 @@ describeIfDb("boot with provider env vars plants nothing (CL-7579)", () => { // Ollama whose probe would have succeeded. const ollama = fakeOllama(); try { - const hub = await hop( - "hub process boot with OLLAMA_BASE_URL", - async () => - startHub({ - databaseUrl: url, - port: freePort(), - sessionSecret: Buffer.from( - crypto.getRandomValues(new Uint8Array(32)), - ).toString("hex"), - dataDir: await tempDir("hub-boot-env-plant-"), - extraEnv: { OLLAMA_BASE_URL: ollama.url }, - }), + const hub = await hop("hub process boot with OLLAMA_BASE_URL", async () => + startHub({ + databaseUrl: url, + port: freePort(), + sessionSecret: Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex"), + dataDir: await tempDir("hub-boot-env-plant-"), + extraEnv: { OLLAMA_BASE_URL: ollama.url }, + }), ); track(hub); await expectZeroRowsFor(url, 15_000); diff --git a/apps/hub/test/chat-mount.test.ts b/apps/hub/test/chat-mount.test.ts index cd35576662..706391defe 100644 --- a/apps/hub/test/chat-mount.test.ts +++ b/apps/hub/test/chat-mount.test.ts @@ -50,12 +50,9 @@ describeIfDb("chat mount", () => { const hub = await createHub(config); closers.push(hub.close); - const gated = await hub.app.request( - "/api/tenants/some-tenant/chat/workbenches", - { - method: "GET", - }, - ); + const gated = await hub.app.request("/api/tenants/some-tenant/chat/workbenches", { + method: "GET", + }); expect(gated.status).toBe(401); expect(await gated.json()).toEqual({ error: { code: "unauthorized", message: "Authentication required" }, diff --git a/apps/hub/test/composition.test.ts b/apps/hub/test/composition.test.ts index 31dc6540c3..7c3484ec22 100644 --- a/apps/hub/test/composition.test.ts +++ b/apps/hub/test/composition.test.ts @@ -90,19 +90,14 @@ describeIfDb("shutdown", () => { // index.ts schedules its reconciliation loop with setTimeout(fn, // 1000) — the one 1000ms setTimeout call site in the module — so // this is the pending timer close() must cancel. - const reconciliationCallIndex = setTimeoutSpy.mock.calls.findIndex( - (call) => call[1] === 1000, - ); + const reconciliationCallIndex = setTimeoutSpy.mock.calls.findIndex((call) => call[1] === 1000); expect(reconciliationCallIndex).toBeGreaterThanOrEqual(0); - const reconciliationTimerId = setTimeoutSpy.mock.results[ - reconciliationCallIndex - ]?.value as ReturnType; + const reconciliationTimerId = setTimeoutSpy.mock.results[reconciliationCallIndex] + ?.value as ReturnType; await hub.close(); - expect(clearTimeoutSpy.mock.calls.map((call) => call[0])).toContain( - reconciliationTimerId, - ); + expect(clearTimeoutSpy.mock.calls.map((call) => call[0])).toContain(reconciliationTimerId); setTimeoutSpy.mockRestore(); clearTimeoutSpy.mockRestore(); @@ -115,9 +110,7 @@ describeIfDb("extension mounting", () => { // Anonymous request to an extension route: the platform's tenant // middleware answers 401 before the extension's handler runs. - const gated = await hub.app.request( - "/api/tenants/some-tenant/chat/workbenches", - ); + const gated = await hub.app.request("/api/tenants/some-tenant/chat/workbenches"); expect(gated.status).toBe(401); expect(await gated.json()).toEqual({ error: { code: "unauthorized", message: "Authentication required" }, @@ -176,11 +169,7 @@ describeIfDb( // Same targeted account, a distinct forged source IP every attempt. await signInAttempt(hub, "victim@example.com", "203.0.113.10"); await signInAttempt(hub, "victim@example.com", "203.0.113.20"); - const throttled = await signInAttempt( - hub, - "victim@example.com", - "203.0.113.30", - ); + const throttled = await signInAttempt(hub, "victim@example.com", "203.0.113.30"); expect(throttled.status).toBe(429); }); @@ -194,19 +183,11 @@ describeIfDb( // Exhausts alice's budget (max: 1), from the same source IP. await signInAttempt(hub, "alice@example.com", "203.0.113.40"); - const throttledAlice = await signInAttempt( - hub, - "alice@example.com", - "203.0.113.40", - ); + const throttledAlice = await signInAttempt(hub, "alice@example.com", "203.0.113.40"); expect(throttledAlice.status).toBe(429); // bob's very first attempt is untouched by alice's exhausted budget. - const freshBob = await signInAttempt( - hub, - "bob@example.com", - "203.0.113.40", - ); + const freshBob = await signInAttempt(hub, "bob@example.com", "203.0.113.40"); expect(freshBob.status).not.toBe(429); }); @@ -218,11 +199,7 @@ describeIfDb( closers.push(hub.close); await signInAttempt(hub, "throttle-me@example.com", "203.0.113.50"); - const throttled = await signInAttempt( - hub, - "throttle-me@example.com", - "203.0.113.60", - ); + const throttled = await signInAttempt(hub, "throttle-me@example.com", "203.0.113.60"); expect(throttled.status).toBe(429); expect(throttled.headers.get("retry-after")).not.toBeNull(); @@ -251,22 +228,13 @@ describeIfDb( // The account owner's correct password still succeeds: only // failures ever consume budget, so a correct attempt is never // gated on how many wrong guesses came before it. - const genuineSignIn = await signInAttempt( - hub, - email, - "203.0.113.73", - password, - ); + const genuineSignIn = await signInAttempt(hub, email, "203.0.113.73", password); expect(genuineSignIn.status).toBe(200); // That success cleared the bucket: the very next wrong guess is a // fresh first failure, not an immediate 429 carried over from the // attacker's earlier attempts. - const freshFailureAfterSuccess = await signInAttempt( - hub, - email, - "203.0.113.74", - ); + const freshFailureAfterSuccess = await signInAttempt(hub, email, "203.0.113.74"); expect(freshFailureAfterSuccess.status).not.toBe(429); }); @@ -290,11 +258,7 @@ describeIfDb( // A real account's first attempt is untouched by the malformed // traffic above. - const fresh = await signInAttempt( - hub, - "never-touched@example.com", - "203.0.113.90", - ); + const fresh = await signInAttempt(hub, "never-touched@example.com", "203.0.113.90"); expect(fresh.status).not.toBe(429); }); }, diff --git a/apps/hub/test/config.test.ts b/apps/hub/test/config.test.ts index 6702da958a..228a7789fa 100644 --- a/apps/hub/test/config.test.ts +++ b/apps/hub/test/config.test.ts @@ -98,10 +98,7 @@ describe("readHubConfig", () => { GITHUB_CLIENT_ID: "github-id", GITHUB_CLIENT_SECRET: "github-secret", }); - expect(Object.keys(config.socialProviders).sort()).toEqual([ - "github", - "google", - ]); + expect(Object.keys(config.socialProviders).sort()).toEqual(["github", "google"]); }); test("a client id with no secret fails loudly at boot", () => { @@ -199,9 +196,7 @@ describe("readHubConfig", () => { }); test("PRINCIPAL_KEY_ENCRYPTION_KEY absent by default", () => { - expect( - readHubConfig(validEnv).principalKeyEncryptionKeyHex, - ).toBeUndefined(); + expect(readHubConfig(validEnv).principalKeyEncryptionKeyHex).toBeUndefined(); }); test("PRINCIPAL_KEY_ENCRYPTION_KEY accepts a 64-char hex key", () => { @@ -241,12 +236,10 @@ describe("readHubConfig", () => { test("HUB_ALLOW_GIT_INSIDE_WORK_TREE='1' or 'true' opts in", () => { expect( - readHubConfig({ ...validEnv, HUB_ALLOW_GIT_INSIDE_WORK_TREE: "1" }) - .allowGitInsideWorkTree, + readHubConfig({ ...validEnv, HUB_ALLOW_GIT_INSIDE_WORK_TREE: "1" }).allowGitInsideWorkTree, ).toBe(true); expect( - readHubConfig({ ...validEnv, HUB_ALLOW_GIT_INSIDE_WORK_TREE: "true" }) - .allowGitInsideWorkTree, + readHubConfig({ ...validEnv, HUB_ALLOW_GIT_INSIDE_WORK_TREE: "true" }).allowGitInsideWorkTree, ).toBe(true); }); @@ -259,13 +252,11 @@ describe("readHubConfig", () => { }); test("ALLOW_PLAINTEXT_SECRETS='1' or 'true' opts in", () => { + expect(readHubConfig({ ...validEnv, ALLOW_PLAINTEXT_SECRETS: "1" }).allowPlaintextSecrets).toBe( + true, + ); expect( - readHubConfig({ ...validEnv, ALLOW_PLAINTEXT_SECRETS: "1" }) - .allowPlaintextSecrets, - ).toBe(true); - expect( - readHubConfig({ ...validEnv, ALLOW_PLAINTEXT_SECRETS: "true" }) - .allowPlaintextSecrets, + readHubConfig({ ...validEnv, ALLOW_PLAINTEXT_SECRETS: "true" }).allowPlaintextSecrets, ).toBe(true); }); @@ -360,9 +351,7 @@ describe("readHubConfig", () => { SIDECAR_PROVISIONERS: "docker", DOCKER_PROVISIONER_IMAGE: "ghcr.io/corbits/sidecar:latest", }); - expect(config.sidecarProvisioners.map((one) => one.id)).toEqual([ - "docker", - ]); + expect(config.sidecarProvisioners.map((one) => one.id)).toEqual(["docker"]); }); test("SIDECAR_PROVISIONERS=docker with an image is wired and becomes the default", () => { @@ -440,12 +429,9 @@ describe("readHubConfig", () => { test("is read from HUB_SIDECAR_WEBSOCKET_URL when set", () => { const config = readHubConfig({ ...validEnv, - HUB_SIDECAR_WEBSOCKET_URL: - "ws://sidecar-host.internal:3000/api/sidecars/ws", + HUB_SIDECAR_WEBSOCKET_URL: "ws://sidecar-host.internal:3000/api/sidecars/ws", }); - expect(config.sidecarWebSocketUrl).toBe( - "ws://sidecar-host.internal:3000/api/sidecars/ws", - ); + expect(config.sidecarWebSocketUrl).toBe("ws://sidecar-host.internal:3000/api/sidecars/ws"); }); test("rejects a value that is not a ws(s):// URL", () => { diff --git a/apps/hub/test/credential-cipher.test.ts b/apps/hub/test/credential-cipher.test.ts index 3e1f8dcf23..2c144f5365 100644 --- a/apps/hub/test/credential-cipher.test.ts +++ b/apps/hub/test/credential-cipher.test.ts @@ -27,9 +27,7 @@ const baseConfig: HubConfig = { describe("credentialCipherFrom", () => { test("no key and no dev opt-in hard-fails boot, naming the variable, what it protects, and how to generate a key", () => { - expect(() => credentialCipherFrom(baseConfig, log)).toThrow( - /CREDENTIAL_ENCRYPTION_KEY/, - ); + expect(() => credentialCipherFrom(baseConfig, log)).toThrow(/CREDENTIAL_ENCRYPTION_KEY/); try { credentialCipherFrom(baseConfig, log); throw new Error("expected credentialCipherFrom to throw"); @@ -43,10 +41,7 @@ describe("credentialCipherFrom", () => { }); test("no key with ALLOW_PLAINTEXT_SECRETS set warns and falls back to the noop cipher", async () => { - const cipher = credentialCipherFrom( - { ...baseConfig, allowPlaintextSecrets: true }, - log, - ); + const cipher = credentialCipherFrom({ ...baseConfig, allowPlaintextSecrets: true }, log); const encrypted = await cipher.encrypt("secret-value", "aad"); expect(encrypted).toBe("secret-value"); }); @@ -79,18 +74,13 @@ describe("hubCredentialCipher", () => { }); test("boot tags the noop cipher when ALLOW_PLAINTEXT_SECRETS is set", async () => { - const cipher = hubCredentialCipher( - { ...baseConfig, allowPlaintextSecrets: true }, - log, - ); + const cipher = hubCredentialCipher({ ...baseConfig, allowPlaintextSecrets: true }, log); expect(cipher).toBe(tagCredentialCipher(cipher)); const encrypted = await cipher.encrypt("secret-value", "aad"); expect(encrypted).toBe("secret-value"); }); test("boot still hard-fails when the key is missing and plaintext is not opted in", () => { - expect(() => hubCredentialCipher(baseConfig, log)).toThrow( - /CREDENTIAL_ENCRYPTION_KEY/, - ); + expect(() => hubCredentialCipher(baseConfig, log)).toThrow(/CREDENTIAL_ENCRYPTION_KEY/); }); }); diff --git a/apps/hub/test/git-fixture.ts b/apps/hub/test/git-fixture.ts index e12732e7c4..5bde502842 100644 --- a/apps/hub/test/git-fixture.ts +++ b/apps/hub/test/git-fixture.ts @@ -20,9 +20,7 @@ function findRepositoryRoot(): string { stderr: "pipe", }); if (result.exitCode !== 0) { - throw new Error( - `git rev-parse --show-toplevel failed: ${result.stderr.toString()}`, - ); + throw new Error(`git rev-parse --show-toplevel failed: ${result.stderr.toString()}`); } repositoryRoot = realpathSync(result.stdout.toString().trim()); } @@ -81,9 +79,7 @@ export function gitFixture(cwd: string, args: readonly string[]): void { }, ); if (result.exitCode !== 0) { - throw new Error( - `git ${args.join(" ")} failed: ${result.stderr.toString()}`, - ); + throw new Error(`git ${args.join(" ")} failed: ${result.stderr.toString()}`); } } diff --git a/apps/hub/test/mail-redelivery.test.ts b/apps/hub/test/mail-redelivery.test.ts index 6ea3f2f429..0d2151f81e 100644 --- a/apps/hub/test/mail-redelivery.test.ts +++ b/apps/hub/test/mail-redelivery.test.ts @@ -12,9 +12,7 @@ function createFakeSidecarRouter(opts: { emit(event: { rawMessage: string; recipients: string[] }): void; routeMailCalls: { agentAddress: string; rawMessage: string }[]; } { - const listeners = new Set< - (event: { rawMessage: string; recipients: string[] }) => void - >(); + const listeners = new Set<(event: { rawMessage: string; recipients: string[] }) => void>(); const routeMailCalls: { agentAddress: string; rawMessage: string }[] = []; return { routeMailCalls, @@ -125,8 +123,9 @@ describe("wireMailRedelivery", () => { await flush(); expect(chatPlatform.ensureAwakeCalls).toEqual(["ins_asleep@ten1.test"]); - expect( - sidecarRouter.routeMailCalls.map((c) => c.agentAddress).sort(), - ).toEqual(["ins_asleep@ten1.test", "ins_routable@ten1.test"]); + expect(sidecarRouter.routeMailCalls.map((c) => c.agentAddress).sort()).toEqual([ + "ins_asleep@ten1.test", + "ins_routable@ten1.test", + ]); }); }); diff --git a/apps/hub/test/mailbox-persist.test.ts b/apps/hub/test/mailbox-persist.test.ts index 681489247b..624fa2374a 100644 --- a/apps/hub/test/mailbox-persist.test.ts +++ b/apps/hub/test/mailbox-persist.test.ts @@ -24,14 +24,8 @@ import { principalMail, type MailboxDb, } from "@corbits/mailbox"; -import { - createHubSessionLookups, - type AgentRepoStore, -} from "@intx/hub-sessions"; -import { - createHubMailboxAuthorizeSender, - hubMailboxResolveRefs, -} from "../src/mailbox-persist"; +import { createHubSessionLookups, type AgentRepoStore } from "@intx/hub-sessions"; +import { createHubMailboxAuthorizeSender, hubMailboxResolveRefs } from "../src/mailbox-persist"; import { dbGate } from "../../../scripts/e2e/db-gate"; const databaseUrl = process.env["DATABASE_URL"] ?? ""; @@ -73,9 +67,10 @@ async function setup(opts: { withSession?: boolean } = {}) { const { db, close: closeDb } = createDB(dbConfigFromUrl(databaseUrl)); closers.push(closeDb); - const { db: mailboxDb, close: closeMailbox } = createMailboxDb( - databaseUrl, - ) as { db: MailboxDb; close: () => Promise }; + const { db: mailboxDb, close: closeMailbox } = createMailboxDb(databaseUrl) as { + db: MailboxDb; + close: () => Promise; + }; closers.push(closeMailbox); const domain = `${uid("mailbox-persist-wrap")}.test`; @@ -189,10 +184,7 @@ describeIfDb("hub persistMail wrapped with createMailboxPersist", () => { .select({ refs: principalMail.refs }) .from(principalMail) .where( - and( - eq(principalMail.tenantId, tenantId), - eq(principalMail.principalId, principalId), - ), + and(eq(principalMail.tenantId, tenantId), eq(principalMail.principalId, principalId)), ); seenRefsByPrincipalId.set(principalId, row?.refs); } @@ -222,11 +214,7 @@ describeIfDb("hub persistMail wrapped with createMailboxPersist", () => { const upstreamResult = await persistMail({ senderAddress, - recipients: [ - `usr_${human1Id}@${domain}`, - `usr_${human2Id}@${domain}`, - agentRecipientAddress, - ], + recipients: [`usr_${human1Id}@${domain}`, `usr_${human2Id}@${domain}`, agentRecipientAddress], raw, }); // The bus fires synchronously inside `persistMail`, but each listener's @@ -237,9 +225,7 @@ describeIfDb("hub persistMail wrapped with createMailboxPersist", () => { // The upstream `session_mail` write still ran, and is durable -- not // just present in the return value. expect( - upstreamResult.some( - (r) => r.direction === "outbound" && r.address === senderAddress, - ), + upstreamResult.some((r) => r.direction === "outbound" && r.address === senderAddress), ).toBe(true); const sessionMailRows = await db .select() @@ -254,9 +240,7 @@ describeIfDb("hub persistMail wrapped with createMailboxPersist", () => { .from(principalMail) .where(eq(principalMail.tenantId, tenantId)); expect(rows).toHaveLength(2); - expect(rows.map((r) => r.principalId).sort()).toEqual( - [human1Id, human2Id].sort(), - ); + expect(rows.map((r) => r.principalId).sort()).toEqual([human1Id, human2Id].sort()); for (const row of rows) { expect(row.refs).toEqual([{ kind: "workbench", id: tenantId }]); } @@ -269,15 +253,9 @@ describeIfDb("hub persistMail wrapped with createMailboxPersist", () => { }); test("dual-write independence: a sender run with no live session makes upstream throw, the mailbox rows still get written, and zero session_mail rows land", async () => { - const { - db, - mailboxDb, - baseLookups, - domain, - tenantId, - senderAddress, - human1Id, - } = await setup({ withSession: false }); + const { db, mailboxDb, baseLookups, domain, tenantId, senderAddress, human1Id } = await setup({ + withSession: false, + }); const persistMail = createMailboxPersist(mailboxDb, { upstream: baseLookups.persistMail, @@ -318,15 +296,7 @@ describeIfDb("hub persistMail wrapped with createMailboxPersist", () => { }); test("redelivery of the same frame is deduped: a retried persist for the same message and recipient writes no second mailbox row", async () => { - const { - db, - mailboxDb, - baseLookups, - domain, - tenantId, - senderAddress, - human1Id, - } = await setup(); + const { db, mailboxDb, baseLookups, domain, tenantId, senderAddress, human1Id } = await setup(); const persistMail = createMailboxPersist(mailboxDb, { upstream: baseLookups.persistMail, diff --git a/apps/hub/test/notify-delivery.test.ts b/apps/hub/test/notify-delivery.test.ts index 35bcedf09f..3d51870a7b 100644 --- a/apps/hub/test/notify-delivery.test.ts +++ b/apps/hub/test/notify-delivery.test.ts @@ -10,11 +10,7 @@ import postgres from "postgres"; import { createDB, schema } from "@intx/db"; import { generateId } from "@intx/hub-common"; -import { - createInMemoryMailboxEventBus, - createMailboxDb, - listUserMailbox, -} from "@corbits/mailbox"; +import { createInMemoryMailboxEventBus, createMailboxDb, listUserMailbox } from "@corbits/mailbox"; import { deliverApprovalMail, type ApprovalNotification, @@ -51,165 +47,153 @@ function dbConfigFromUrl(databaseUrl: string) { const databaseUrl = e2eDatabaseUrl(); const describeIfDb = dbGate(databaseUrl, import.meta.path); -describeIfDb( - "hub notify delivery repairs the CL-7238 crash window on redelivery", - () => { - const scratchUrl = scratchUrlFor( - databaseUrl ?? "postgres://localhost:5432/unused", - ); - const scratchDatabase = new URL(scratchUrl).pathname.replace(/^\//, ""); - - const tenantId = generateId("tenant"); - const principalId = generateId("principal"); - - beforeAll(async () => { - const maintenanceUrl = new URL(scratchUrl); - maintenanceUrl.pathname = "/postgres"; - const maintenance = postgres(maintenanceUrl.toString(), { - max: 1, - onnotice: () => undefined, +describeIfDb("hub notify delivery repairs the CL-7238 crash window on redelivery", () => { + const scratchUrl = scratchUrlFor(databaseUrl ?? "postgres://localhost:5432/unused"); + const scratchDatabase = new URL(scratchUrl).pathname.replace(/^\//, ""); + + const tenantId = generateId("tenant"); + const principalId = generateId("principal"); + + beforeAll(async () => { + const maintenanceUrl = new URL(scratchUrl); + maintenanceUrl.pathname = "/postgres"; + const maintenance = postgres(maintenanceUrl.toString(), { + max: 1, + onnotice: () => undefined, + }); + try { + await maintenance.unsafe(`DROP DATABASE IF EXISTS "${scratchDatabase}"`); + await maintenance.unsafe(`CREATE DATABASE "${scratchDatabase}"`); + } finally { + await maintenance.end(); + } + + // The same path the hub boots with: the platform's own migrations, + // then every installed package's, including `@corbits/notify` and + // `@corbits/mailbox`. + await setupDatabase(scratchUrl); + + const { db, close } = createDB(dbConfigFromUrl(scratchUrl)); + try { + await db.insert(schema.tenant).values({ + id: tenantId, + name: "Hub Notify Test Bench", + slug: `hub-notify-${tenantId}`, + domain: `hub-notify-${tenantId}.localhost`, }); - try { - await maintenance.unsafe( - `DROP DATABASE IF EXISTS "${scratchDatabase}"`, - ); - await maintenance.unsafe(`CREATE DATABASE "${scratchDatabase}"`); - } finally { - await maintenance.end(); - } - - // The same path the hub boots with: the platform's own migrations, - // then every installed package's, including `@corbits/notify` and - // `@corbits/mailbox`. - await setupDatabase(scratchUrl); - - const { db, close } = createDB(dbConfigFromUrl(scratchUrl)); - try { - await db.insert(schema.tenant).values({ - id: tenantId, - name: "Hub Notify Test Bench", - slug: `hub-notify-${tenantId}`, - domain: `hub-notify-${tenantId}.localhost`, - }); - await db.insert(schema.principal).values({ - id: principalId, - tenantId, - kind: "agent", - refId: "not-a-real-agent-instance", - status: "active", - }); - } finally { - await close(); - } - // `setupDatabase` applies the platform's migrations plus every - // installed package's; under `bun run test`'s cross-package - // concurrency that comfortably exceeds bun:test's default 5s hook - // timeout, so it gets an explicit one here rather than a flaky suite. - }, 30000); - - afterAll(async () => { - const maintenanceUrl = new URL(scratchUrl); - maintenanceUrl.pathname = "/postgres"; - const maintenance = postgres(maintenanceUrl.toString(), { - max: 1, - onnotice: () => undefined, + await db.insert(schema.principal).values({ + id: principalId, + tenantId, + kind: "agent", + refId: "not-a-real-agent-instance", + status: "active", }); - try { - await maintenance.unsafe( - `DROP DATABASE IF EXISTS "${scratchDatabase}"`, - ); - } finally { - await maintenance.end(); - } + } finally { + await close(); + } + // `setupDatabase` applies the platform's migrations plus every + // installed package's; under `bun run test`'s cross-package + // concurrency that comfortably exceeds bun:test's default 5s hook + // timeout, so it gets an explicit one here rather than a flaky suite. + }, 30000); + + afterAll(async () => { + const maintenanceUrl = new URL(scratchUrl); + maintenanceUrl.pathname = "/postgres"; + const maintenance = postgres(maintenanceUrl.toString(), { + max: 1, + onnotice: () => undefined, }); - - test("a crash-window redelivery through the composed hub deps queues its dispatch", async () => { - const mailboxDb = createMailboxDb(scratchUrl); - try { - // Exactly what the hub composes: the factory's own dispatch store - // and sink registry are the prod path (no sink registered means no - // fan-out, only the mailbox row the sweep's reader sees). The test - // registers one sink so the repaired dispatch has somewhere to go, - // and faults only the dispatch enqueue to open the crash window. - const deps = createHubNotifyDeliveryDeps({ - mailboxDb: mailboxDb.db, - bus: createInMemoryMailboxEventBus(), - host: "hub.test", - }); - const delivered: SinkDeliveryResult = { status: "delivered" }; - deps.sinks.register({ - name: "always", - isEnabledFor: async () => true, - deliver: async () => delivered, - }); - const dispatch = deps.dispatch; - let crashNext = true; - const crashingDispatch: NotifyDispatchStore = { - ...dispatch, - enqueue: async (inputs) => { - if (crashNext) { - crashNext = false; - throw new Error( - "boom: crash between the mail write and the dispatch enqueue", - ); - } - await dispatch.enqueue(inputs); - }, - }; - const event: ApprovalNotification = { - kind: "approval", - approvalId: generateId("approval"), - tenantId, - runId: generateId("workflowRun"), - deploymentId: generateId("workflowRun"), - toolName: "quarantine_token", - toolArguments: { repo: "corbitsdev/workbench" }, - recipients: [{ tenantId, principalId }], - createdAt: new Date().toISOString(), - }; - - // The first attempt commits its mail row through the real adapter, - // then dies before the dispatch enqueue — the crash window. - await expect( - deliverApprovalMail({ ...deps, dispatch: crashingDispatch }, event), - ).rejects.toThrow("boom"); - - // The redelivery runs through the pristine composed deps: it - // dedupes on the mail key (no new row), resolves the committed - // row's id through the hub's read-back wiring, and queues the - // dispatch the crash swallowed. Without `resolveExistingMailIds` - // in the composed deps this queues nothing. - const redelivery = await deliverApprovalMail(deps, event); - expect(redelivery.deliveredMailboxRowIds).toEqual([]); - expect(redelivery.queuedDispatchCount).toBe(1); - - const page = await listUserMailbox(mailboxDb.db, { - tenantId, - principalId, - limit: 10, - view: "all", - priorities: WORKBENCH_INBOX_PRIORITIES, - }); - // Exactly one mail row for the redelivered event — the crash and - // the redelivery deduped onto the first attempt's row. - const matching = page.items.filter((item) => - (item.subject ?? "").includes("quarantine_token"), - ); - expect(matching).toHaveLength(1); - const mailId = matching[0]?.id; - if (mailId === undefined) - throw new Error("expected the redelivered mail row to list"); - expect(await dispatch.listFor(mailId)).toHaveLength(1); - - // A further benign redelivery repairs the same row again, and the - // dispatch store's (mail row, sink) dedupe holds it to one row. - const third = await deliverApprovalMail(deps, event); - expect(third.deliveredMailboxRowIds).toEqual([]); - expect(third.queuedDispatchCount).toBe(1); - expect(await dispatch.listFor(mailId)).toHaveLength(1); - } finally { - await mailboxDb.close(); - } - }, 15000); - }, -); + try { + await maintenance.unsafe(`DROP DATABASE IF EXISTS "${scratchDatabase}"`); + } finally { + await maintenance.end(); + } + }); + + test("a crash-window redelivery through the composed hub deps queues its dispatch", async () => { + const mailboxDb = createMailboxDb(scratchUrl); + try { + // Exactly what the hub composes: the factory's own dispatch store + // and sink registry are the prod path (no sink registered means no + // fan-out, only the mailbox row the sweep's reader sees). The test + // registers one sink so the repaired dispatch has somewhere to go, + // and faults only the dispatch enqueue to open the crash window. + const deps = createHubNotifyDeliveryDeps({ + mailboxDb: mailboxDb.db, + bus: createInMemoryMailboxEventBus(), + host: "hub.test", + }); + const delivered: SinkDeliveryResult = { status: "delivered" }; + deps.sinks.register({ + name: "always", + isEnabledFor: async () => true, + deliver: async () => delivered, + }); + const dispatch = deps.dispatch; + let crashNext = true; + const crashingDispatch: NotifyDispatchStore = { + ...dispatch, + enqueue: async (inputs) => { + if (crashNext) { + crashNext = false; + throw new Error("boom: crash between the mail write and the dispatch enqueue"); + } + await dispatch.enqueue(inputs); + }, + }; + const event: ApprovalNotification = { + kind: "approval", + approvalId: generateId("approval"), + tenantId, + runId: generateId("workflowRun"), + deploymentId: generateId("workflowRun"), + toolName: "quarantine_token", + toolArguments: { repo: "corbitsdev/workbench" }, + recipients: [{ tenantId, principalId }], + createdAt: new Date().toISOString(), + }; + + // The first attempt commits its mail row through the real adapter, + // then dies before the dispatch enqueue — the crash window. + await expect( + deliverApprovalMail({ ...deps, dispatch: crashingDispatch }, event), + ).rejects.toThrow("boom"); + + // The redelivery runs through the pristine composed deps: it + // dedupes on the mail key (no new row), resolves the committed + // row's id through the hub's read-back wiring, and queues the + // dispatch the crash swallowed. Without `resolveExistingMailIds` + // in the composed deps this queues nothing. + const redelivery = await deliverApprovalMail(deps, event); + expect(redelivery.deliveredMailboxRowIds).toEqual([]); + expect(redelivery.queuedDispatchCount).toBe(1); + + const page = await listUserMailbox(mailboxDb.db, { + tenantId, + principalId, + limit: 10, + view: "all", + priorities: WORKBENCH_INBOX_PRIORITIES, + }); + // Exactly one mail row for the redelivered event — the crash and + // the redelivery deduped onto the first attempt's row. + const matching = page.items.filter((item) => + (item.subject ?? "").includes("quarantine_token"), + ); + expect(matching).toHaveLength(1); + const mailId = matching[0]?.id; + if (mailId === undefined) throw new Error("expected the redelivered mail row to list"); + expect(await dispatch.listFor(mailId)).toHaveLength(1); + + // A further benign redelivery repairs the same row again, and the + // dispatch store's (mail row, sink) dedupe holds it to one row. + const third = await deliverApprovalMail(deps, event); + expect(third.deliveredMailboxRowIds).toEqual([]); + expect(third.queuedDispatchCount).toBe(1); + expect(await dispatch.listFor(mailId)).toHaveLength(1); + } finally { + await mailboxDb.close(); + } + }, 15000); +}); diff --git a/apps/hub/test/rate-limit.test.ts b/apps/hub/test/rate-limit.test.ts index 16de8b5dd0..70014cbd81 100644 --- a/apps/hub/test/rate-limit.test.ts +++ b/apps/hub/test/rate-limit.test.ts @@ -53,16 +53,11 @@ if (!databaseUrl) { const remainingBudget = ISOLATION_SIGNUP_RATE_LIMIT_MAX - 1; const statuses: number[] = []; for (let attempt = 0; attempt < remainingBudget + 1; attempt += 1) { - const response = await signUpAttempt( - app, - `rate-limit-${nonce}-${attempt}@isolation.test`, - ); + const response = await signUpAttempt(app, `rate-limit-${nonce}-${attempt}@isolation.test`); statuses.push(response.status); } - expect(statuses.slice(0, remainingBudget)).toEqual( - new Array(remainingBudget).fill(200), - ); + expect(statuses.slice(0, remainingBudget)).toEqual(new Array(remainingBudget).fill(200)); expect(statuses[remainingBudget]).toBe(429); }); }); diff --git a/apps/hub/test/relaunch-sweep-close.test.ts b/apps/hub/test/relaunch-sweep-close.test.ts index 3a9795a8d8..751cf74407 100644 --- a/apps/hub/test/relaunch-sweep-close.test.ts +++ b/apps/hub/test/relaunch-sweep-close.test.ts @@ -63,9 +63,7 @@ describeIfDb("relaunch sweep teardown", () => { console.error = originalError; } - const leaked = errors.filter((line) => - line.includes("relaunch sweep pass failed"), - ); + const leaked = errors.filter((line) => line.includes("relaunch sweep pass failed")); expect(leaked).toEqual([]); }, 10_000); }); diff --git a/apps/hub/test/shutdown-drain.test.ts b/apps/hub/test/shutdown-drain.test.ts index 79d9159bdf..9b6f44e4c4 100644 --- a/apps/hub/test/shutdown-drain.test.ts +++ b/apps/hub/test/shutdown-drain.test.ts @@ -136,9 +136,7 @@ describe("hub shutdown drain against a real server", () => { }), ); const server = start(); - const response = await fetch( - `http://localhost:${String(server.port)}/stream`, - ); + const response = await fetch(`http://localhost:${String(server.port)}/stream`); expect(response.ok).toBe(true); expect(tracker.pending).toBe(0); diff --git a/apps/sidecar/package.json b/apps/sidecar/package.json index 262a3f4949..7b66a1a006 100644 --- a/apps/sidecar/package.json +++ b/apps/sidecar/package.json @@ -1,13 +1,13 @@ { "name": "@workbench/sidecar", + "version": "0.0.1", "private": true, "description": "Generic execution host that dials in to a hub over WebSocket and hosts whatever it is deployed", - "version": "0.0.1", "license": "SEE LICENSE IN ../../LICENSE.md", - "type": "module", "bin": { "workflow-child": "./bin/workflow-child" }, + "type": "module", "scripts": { "start": "bun src/index.ts", "dev": "bun --watch src/index.ts", diff --git a/apps/sidecar/src/boot-restore-push-hold.test.ts b/apps/sidecar/src/boot-restore-push-hold.test.ts index da9a3434a4..0894be3652 100644 --- a/apps/sidecar/src/boot-restore-push-hold.test.ts +++ b/apps/sidecar/src/boot-restore-push-hold.test.ts @@ -22,10 +22,7 @@ describe("createBootRestorePushHold", () => { hold.onDeploymentRegistered("run_b@bench.localhost"); hold.end(); - expect(store.held).toEqual([ - "run_a@bench.localhost", - "run_b@bench.localhost", - ]); + expect(store.held).toEqual(["run_a@bench.localhost", "run_b@bench.localhost"]); }); test("does not hold a deployment that arrives over a live link", () => { diff --git a/apps/sidecar/src/child-grant-filter.test.ts b/apps/sidecar/src/child-grant-filter.test.ts index 69b29eabcb..e1ec60f3dd 100644 --- a/apps/sidecar/src/child-grant-filter.test.ts +++ b/apps/sidecar/src/child-grant-filter.test.ts @@ -12,18 +12,11 @@ import { evaluateGrants } from "@intx/authz"; import type { GrantRule } from "@intx/authz"; import { defineWorkflow, step } from "@intx/workflow"; -import { - collectDeclaredResources, - filterGrantsToDeclaredResources, -} from "./child-grant-filter"; +import { collectDeclaredResources, filterGrantsToDeclaredResources } from "./child-grant-filter"; // A grant rule in the shape the per-run grants file carries. `effect` and // `action` vary per case; the rest is boilerplate the filter never inspects. -function rule( - resource: string, - effect: GrantRule["effect"], - action = "invoke", -): GrantRule { +function rule(resource: string, effect: GrantRule["effect"], action = "invoke"): GrantRule { return { id: `grant-${effect}-${resource}-${action}`, resource, @@ -39,12 +32,7 @@ function rule( function resources(grants: readonly unknown[]): string[] { return grants.map((g) => { - if ( - typeof g === "object" && - g !== null && - "resource" in g && - typeof g.resource === "string" - ) { + if (typeof g === "object" && g !== null && "resource" in g && typeof g.resource === "string") { return g.resource; } throw new Error(`test grant has no string resource: ${JSON.stringify(g)}`); @@ -54,10 +42,7 @@ function resources(grants: readonly unknown[]): string[] { describe("filterGrantsToDeclaredResources", () => { test("keeps an allow whose exact resource is declared", () => { const declared = new Set(["tool:foo"]); - const filtered = filterGrantsToDeclaredResources( - [rule("tool:foo", "allow")], - declared, - ); + const filtered = filterGrantsToDeclaredResources([rule("tool:foo", "allow")], declared); expect(resources(filtered)).toEqual(["tool:foo"]); }); @@ -65,10 +50,7 @@ describe("filterGrantsToDeclaredResources", () => { // The single grant that authorizes the most must survive: string equality // would drop `tool:*`, but `matchPattern("tool:*", "tool:foo")` is true. const declared = new Set(["tool:foo"]); - const filtered = filterGrantsToDeclaredResources( - [rule("tool:*", "allow")], - declared, - ); + const filtered = filterGrantsToDeclaredResources([rule("tool:*", "allow")], declared); expect(resources(filtered)).toEqual(["tool:*"]); }); @@ -93,11 +75,7 @@ describe("filterGrantsToDeclaredResources", () => { // undeclared -- dropping one would weaken safety. const declared = new Set(["tool:foo"]); const filtered = filterGrantsToDeclaredResources( - [ - rule("tool:bar", "deny"), - rule("effect:whatever", "ask"), - rule("tool:bar", "allow"), - ], + [rule("tool:bar", "deny"), rule("effect:whatever", "ask"), rule("tool:bar", "allow")], declared, ); expect(resources(filtered)).toEqual(["tool:bar", "effect:whatever"]); @@ -114,7 +92,6 @@ describe("filterGrantsToDeclaredResources", () => { declared, ); const decision = await evaluateGrants( - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the filter returns unknown[]; the seeded rows are GrantRule by construction here [...(filtered as readonly GrantRule[])], "tool:foo", "invoke", @@ -178,11 +155,7 @@ describe("collectDeclaredResources", () => { }), }, }); - const declared = collectDeclaredResources( - workflow, - createDefaultDirectorRegistry(), - new Map(), - ); + const declared = collectDeclaredResources(workflow, createDefaultDirectorRegistry(), new Map()); expect(declared.has("inference.source:anthropic:m1")).toBe(true); expect(declared.has("inference.source:anthropic:m2")).toBe(true); }); diff --git a/apps/sidecar/src/child-grant-filter.ts b/apps/sidecar/src/child-grant-filter.ts index c4df26abd9..13440ac464 100644 --- a/apps/sidecar/src/child-grant-filter.ts +++ b/apps/sidecar/src/child-grant-filter.ts @@ -14,10 +14,7 @@ import type { DirectorRegistry } from "@intx/agent"; import { matchPattern } from "@intx/authz"; import type { WorkflowDefinition } from "@intx/workflow"; -import { - walkCapabilities, - type PluginToolDefinitions, -} from "@intx/workflow-deploy"; +import { walkCapabilities, type PluginToolDefinitions } from "@intx/workflow-deploy"; /** * The flat union of every grant-shape resource string the child body declares, @@ -81,10 +78,7 @@ export function filterGrantsToDeclaredResources( return parentGrants.filter((grant) => keepGrantForDeclared(grant, declared)); } -function keepGrantForDeclared( - grant: unknown, - declared: ReadonlySet, -): boolean { +function keepGrantForDeclared(grant: unknown, declared: ReadonlySet): boolean { if (!isAllowRuleWithResource(grant)) { return true; } @@ -96,9 +90,7 @@ function keepGrantForDeclared( return false; } -function isAllowRuleWithResource( - grant: unknown, -): grant is { effect: "allow"; resource: string } { +function isAllowRuleWithResource(grant: unknown): grant is { effect: "allow"; resource: string } { if (typeof grant !== "object" || grant === null) { return false; } diff --git a/apps/sidecar/src/concurrency.test.ts b/apps/sidecar/src/concurrency.test.ts index bdb1053285..3eeeeed74e 100644 --- a/apps/sidecar/src/concurrency.test.ts +++ b/apps/sidecar/src/concurrency.test.ts @@ -34,11 +34,7 @@ describe("runWithConcurrency", () => { }); test("an empty list resolves immediately with no failures", async () => { - const failures = await runWithConcurrency( - [] as number[], - 4, - async () => {}, - ); + const failures = await runWithConcurrency([] as number[], 4, async () => {}); expect(failures).toEqual([]); }); diff --git a/apps/sidecar/src/config.ts b/apps/sidecar/src/config.ts index 2f16a7929c..0ce26eece1 100644 --- a/apps/sidecar/src/config.ts +++ b/apps/sidecar/src/config.ts @@ -106,10 +106,7 @@ const SidecarEnv = type({ * for an unset key so the caller's binding falls back to the vendor's own * default rather than this boundary inventing one. */ -function parsePositiveMsEnv( - raw: string | undefined, - name: string, -): number | undefined { +function parsePositiveMsEnv(raw: string | undefined, name: string): number | undefined { if (raw === undefined) return undefined; const n = Number(raw); if (!Number.isFinite(n) || n <= 0) { @@ -171,18 +168,15 @@ export type SidecarConfig = { * rather than surfacing as a deep-stack `loadAdapterRegistry` import * failure. */ -export function parseSidecarAdapterManifest( - raw: string | undefined, -): AdapterManifest { +export function parseSidecarAdapterManifest(raw: string | undefined): AdapterManifest { if (raw === undefined) return DEFAULT_ADAPTER_MANIFEST; let parsed: unknown; try { parsed = JSON.parse(raw); } catch (cause) { - throw new Error( - "invalid sidecar environment: SIDECAR_ADAPTER_MANIFEST is not valid JSON", - { cause }, - ); + throw new Error("invalid sidecar environment: SIDECAR_ADAPTER_MANIFEST is not valid JSON", { + cause, + }); } const validated = AdapterManifest(parsed); if (validated instanceof type.errors) { @@ -199,9 +193,7 @@ export function parseSidecarAdapterManifest( * variable and the shape it must have, so a misconfigured process dies * at boot instead of failing at first use. */ -export function readSidecarConfig( - env: Record, -): SidecarConfig { +export function readSidecarConfig(env: Record): SidecarConfig { const parsed = SidecarEnv(env); if (parsed instanceof type.errors) { throw new Error(`invalid sidecar environment: ${parsed.summary}`); @@ -221,20 +213,9 @@ export function readSidecarConfig( home: parsed.HOME, tmpdir: parsed.TMPDIR, toolRegistries: parsed.SIDECAR_TOOL_REGISTRIES, - adapterManifest: parseSidecarAdapterManifest( - parsed.SIDECAR_ADAPTER_MANIFEST, - ), - consumedRetentionMs: parsePositiveMsEnv( - parsed.CONSUMED_RETENTION_MS, - "CONSUMED_RETENTION_MS", - ), - readyTimeoutMs: parsePositiveMsEnv( - parsed.CHILD_READY_TIMEOUT_MS, - "CHILD_READY_TIMEOUT_MS", - ), - idleHibernateMs: parsePositiveMsEnv( - parsed.CHAT_IDLE_HIBERNATE_MS, - "CHAT_IDLE_HIBERNATE_MS", - ), + adapterManifest: parseSidecarAdapterManifest(parsed.SIDECAR_ADAPTER_MANIFEST), + consumedRetentionMs: parsePositiveMsEnv(parsed.CONSUMED_RETENTION_MS, "CONSUMED_RETENTION_MS"), + readyTimeoutMs: parsePositiveMsEnv(parsed.CHILD_READY_TIMEOUT_MS, "CHILD_READY_TIMEOUT_MS"), + idleHibernateMs: parsePositiveMsEnv(parsed.CHAT_IDLE_HIBERNATE_MS, "CHAT_IDLE_HIBERNATE_MS"), }; } diff --git a/apps/sidecar/src/conversation-state.test.ts b/apps/sidecar/src/conversation-state.test.ts index a3a4120ce1..b4c9422c89 100644 --- a/apps/sidecar/src/conversation-state.test.ts +++ b/apps/sidecar/src/conversation-state.test.ts @@ -4,11 +4,7 @@ import path from "node:path"; import { afterEach, expect, spyOn, test } from "bun:test"; -import type { - Principal, - RepoId, - RepoStore, -} from "@intx/hub-sessions/substrate"; +import type { Principal, RepoId, RepoStore } from "@intx/hub-sessions/substrate"; import type { ConversationTurn, PendingOperation } from "@intx/types/runtime"; import { @@ -24,9 +20,7 @@ const tmpDirs: string[] = []; afterEach(async () => { await Promise.all( - tmpDirs - .splice(0) - .map((dir) => fs.promises.rm(dir, { recursive: true, force: true })), + tmpDirs.splice(0).map((dir) => fs.promises.rm(dir, { recursive: true, force: true })), ); }); @@ -61,21 +55,21 @@ function fsBackedSubstrate(repoDir: string): RepoStore { ) => { const prefixDir = path.join( repoDir, - ...args.preservePrefix - .split("/") - .filter((seg: string) => seg.length > 0), + ...args.preservePrefix.split("/").filter((seg: string) => seg.length > 0), ); const existing = new Map(); let names: string[] = []; try { names = await fs.promises.readdir(prefixDir); } catch (cause) { - if (!( - typeof cause === "object" && - cause !== null && - "code" in cause && - cause.code === "ENOENT" - )) { + if ( + !( + typeof cause === "object" && + cause !== null && + "code" in cause && + cause.code === "ENOENT" + ) + ) { throw cause; } } @@ -83,19 +77,13 @@ function fsBackedSubstrate(repoDir: string): RepoStore { const full = path.join(prefixDir, name); const st = await fs.promises.stat(full); if (st.isFile()) { - existing.set( - `${args.preservePrefix}${name}`, - await fs.promises.readFile(full), - ); + existing.set(`${args.preservePrefix}${name}`, await fs.promises.readFile(full)); } } const files = await args.merge(existing); await fs.promises.rm(prefixDir, { recursive: true, force: true }); for (const [rel, content] of Object.entries(files)) { - const dest = path.join( - repoDir, - ...rel.split("/").filter((seg: string) => seg.length > 0), - ); + const dest = path.join(repoDir, ...rel.split("/").filter((seg: string) => seg.length > 0)); await fs.promises.mkdir(path.dirname(dest), { recursive: true }); await fs.promises.writeFile(dest, content); } @@ -135,26 +123,14 @@ test("two rooms of one agent keep isolated turns; a new room starts empty", asyn await store.storage.writeTurns([userTurn("room A tool result")]); await store.mirrorToSubstrate(); - const roomADir = durableConversationAgentStateDir( - repoDir, - "default", - "chan_a", - ); - const reconstructedA = await reconstructDurableConversation( - roomADir, - "default/chan_a", - ); + const roomADir = durableConversationAgentStateDir(repoDir, "default", "chan_a"); + const reconstructedA = await reconstructDurableConversation(roomADir, "default/chan_a"); expect(reconstructedA?.turns).toHaveLength(1); expect(await store.bindOriginatingWorkbench("chan_b")).toBe(true); expect(peekTurns(store)).toEqual([]); - const mixedLegacy = path.join( - repoDir, - "agent-state", - "default", - "checkpoint.json", - ); + const mixedLegacy = path.join(repoDir, "agent-state", "default", "checkpoint.json"); expect(fs.existsSync(mixedLegacy)).toBe(false); await store.storage.writeTurns([userTurn("room B first infer")]); @@ -215,10 +191,7 @@ test("a retired signal-kind pending op is dropped on restore, not thrown on", as }), ); - const reconstructed = await reconstructDurableConversation( - agentStateDir, - "default/chan_a", - ); + const reconstructed = await reconstructDurableConversation(agentStateDir, "default/chan_a"); expect(reconstructed?.pendingOperations).toEqual([survivingOp]); }); @@ -240,12 +213,8 @@ test("two queued mails bind and mirror under each message's From not a later ori }); // Both inbound mails exist (enqueued) before the first invokeStep. A // latest-wins origin file would now name chan_b for both binds. - const fromA = originatingWorkbenchIdFromRequest( - mailRequest("chan_a@alice.localhost"), - ); - const fromB = originatingWorkbenchIdFromRequest( - mailRequest("chan_b@alice.localhost"), - ); + const fromA = originatingWorkbenchIdFromRequest(mailRequest("chan_a@alice.localhost")); + const fromB = originatingWorkbenchIdFromRequest(mailRequest("chan_b@alice.localhost")); expect(fromA).toBe("chan_a"); expect(fromB).toBe("chan_b"); diff --git a/apps/sidecar/src/conversation-state.ts b/apps/sidecar/src/conversation-state.ts index 84dfa721ab..b064174cb9 100644 --- a/apps/sidecar/src/conversation-state.ts +++ b/apps/sidecar/src/conversation-state.ts @@ -134,15 +134,8 @@ import { getLogger } from "@intx/log"; import { reportError } from "@corbits/error-sink"; import { createConnectorRouter } from "@intx/harness"; import type { ConnectorReplyParts, RouteDecision } from "@intx/harness"; -import { - createIsogitStorage, - createNodeIsogitRuntime, -} from "@intx/storage-isogit/node"; -import type { - Principal, - RepoId, - RepoStore, -} from "@intx/hub-sessions/substrate"; +import { createIsogitStorage, createNodeIsogitRuntime } from "@intx/storage-isogit/node"; +import type { Principal, RepoId, RepoStore } from "@intx/hub-sessions/substrate"; import { WORKFLOW_RUN_AGENT_STATE_PREFIX } from "@intx/hub-sessions/substrate"; import { SignalKind } from "@intx/types"; @@ -405,10 +398,7 @@ export async function createDurableConversationStore( opts: DurableConversationStoreOpts, ): Promise { await fs.promises.mkdir(opts.localStoreDir, { recursive: true }); - const baseStorage = await isogitStorage.createIsogitStore( - opts.localStoreDir, - opts.signer, - ); + const baseStorage = await isogitStorage.createIsogitStore(opts.localStoreDir, opts.signer); // Reuse the connector router + the harness storage-override seam. The // router's `onStateChanged` is the change-driven commit hook the design @@ -441,10 +431,7 @@ export async function createDurableConversationStore( } function agentStatePrefix(): string { - return durableConversationAgentStatePrefix( - opts.agentKey, - requireBoundWorkbenchId(), - ); + return durableConversationAgentStatePrefix(opts.agentKey, requireBoundWorkbenchId()); } // The number of mirror boundaries already durably committed (the // checkpoint's folded boundaries plus every appended WAL entry). It is @@ -740,11 +727,7 @@ export async function createDurableConversationStore( // with the freshest metadata and truncate the WAL. Amortizes the // unavoidable O(N) full rewrite to O(N/K) per boundary. if (mirroredBoundaryCount - checkpointBoundarySeq >= CHECKPOINT_INTERVAL) { - await writeCheckpoint( - mirroredBoundaryCount, - turns.slice(0, mirroredTurnCount), - metadata, - ); + await writeCheckpoint(mirroredBoundaryCount, turns.slice(0, mirroredTurnCount), metadata); checkpointBoundarySeq = mirroredBoundaryCount; } } @@ -1034,10 +1017,7 @@ const PendingOperationKindEnvelope = type({ kind: "unknown" }); * question is simply lost here -- their eventual reply just becomes the * next ordinary turn (docs/CHAT.md). */ -function dropUnclassifiedPendingOperations( - items: unknown[], - agentKey: string, -): unknown[] { +function dropUnclassifiedPendingOperations(items: unknown[], agentKey: string): unknown[] { return items.filter((item) => { const envelope = PendingOperationKindEnvelope(item); const kind = envelope instanceof type.errors ? undefined : envelope.kind; @@ -1080,11 +1060,7 @@ export async function reconstructDurableConversation( ): Promise { const checkpoint = await readCheckpointFromDir(agentStateDir, agentKey); const baseBoundarySeq = checkpoint?.checkpointSeq ?? 0; - const wal = await readWalTailFromDir( - agentStateDir, - agentKey, - baseBoundarySeq, - ); + const wal = await readWalTailFromDir(agentStateDir, agentKey, baseBoundarySeq); if (checkpoint === null && wal.length === 0) return null; const turns: unknown[] = [...(checkpoint?.turns ?? [])]; @@ -1138,10 +1114,7 @@ async function readCheckpointFromDir( } | null> { let metaRaw: string; try { - metaRaw = await fs.promises.readFile( - path.join(agentStateDir, CHECKPOINT_META_FILE), - "utf8", - ); + metaRaw = await fs.promises.readFile(path.join(agentStateDir, CHECKPOINT_META_FILE), "utf8"); } catch (cause) { if (isErrnoNotFound(cause)) return null; throw cause; @@ -1153,14 +1126,8 @@ async function readCheckpointFromDir( `sidecar conversation-state: ${CHECKPOINT_META_FILE} for ${agentKey} failed validation: ${validatedMeta.summary}; refusing to start the warm agent fresh on a corrupt checkpoint`, ); } - const snapshotRaw = await fs.promises.readFile( - path.join(agentStateDir, CHECKPOINT_FILE), - "utf8", - ); - const snapshot = parseJsonOrThrow( - snapshotRaw, - `${agentKey} ${CHECKPOINT_FILE}`, - ); + const snapshotRaw = await fs.promises.readFile(path.join(agentStateDir, CHECKPOINT_FILE), "utf8"); + const snapshot = parseJsonOrThrow(snapshotRaw, `${agentKey} ${CHECKPOINT_FILE}`); const validatedSnapshot = CheckpointSnapshot(snapshot); if (validatedSnapshot instanceof type.errors) { throw new Error( @@ -1195,9 +1162,7 @@ async function readWalTailFromDir( agentStateDir: string, agentKey: string, fromSeq: number, -): Promise< - { seq: number; turns: unknown[]; metadata: SnapshotMetadataValue }[] -> { +): Promise<{ seq: number; turns: unknown[]; metadata: SnapshotMetadataValue }[]> { const walDir = path.join(agentStateDir, WAL_DIR); let buckets: string[]; try { @@ -1220,14 +1185,8 @@ async function readWalTailFromDir( `sidecar conversation-state: unexpected non-JSON WAL entry ${WAL_DIR}/${bucket}/${file} for ${agentKey}`, ); } - const raw = await fs.promises.readFile( - path.join(bucketDir, file), - "utf8", - ); - const parsed = parseJsonOrThrow( - raw, - `${agentKey} ${WAL_DIR}/${bucket}/${file}`, - ); + const raw = await fs.promises.readFile(path.join(bucketDir, file), "utf8"); + const parsed = parseJsonOrThrow(raw, `${agentKey} ${WAL_DIR}/${bucket}/${file}`); const validated = WalEntry(parsed); if (validated instanceof type.errors) { throw new Error( @@ -1271,10 +1230,5 @@ function parseJsonOrThrow(raw: string, label: string): unknown { } export function isErrnoNotFound(cause: unknown): boolean { - return ( - typeof cause === "object" && - cause !== null && - "code" in cause && - cause.code === "ENOENT" - ); + return typeof cause === "object" && cause !== null && "code" in cause && cause.code === "ENOENT"; } diff --git a/apps/sidecar/src/default-harness.ts b/apps/sidecar/src/default-harness.ts index dac6652271..806fa30002 100644 --- a/apps/sidecar/src/default-harness.ts +++ b/apps/sidecar/src/default-harness.ts @@ -18,15 +18,11 @@ export interface DefaultHarnessBuilderConfig { readonly adapters: AdapterRegistry; } -export function createDefaultHarnessBuilder( - config: DefaultHarnessBuilderConfig, -): HarnessBuilder { +export function createDefaultHarnessBuilder(config: DefaultHarnessBuilderConfig): HarnessBuilder { return { canBuildSource(source: InferenceSource): void { if (!config.adapters.has(source.provider)) { - throw new Error( - `Source provider "${source.provider}" is not registered`, - ); + throw new Error(`Source provider "${source.provider}" is not registered`); } }, }; diff --git a/apps/sidecar/src/hibernated-agent-identity-vault.test.ts b/apps/sidecar/src/hibernated-agent-identity-vault.test.ts index 16571ac690..ea67f39090 100644 --- a/apps/sidecar/src/hibernated-agent-identity-vault.test.ts +++ b/apps/sidecar/src/hibernated-agent-identity-vault.test.ts @@ -8,11 +8,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { - createAgentKeyStore, - createAgentRepoStore, - agentDir, -} from "@intx/hub-agent"; +import { createAgentKeyStore, createAgentRepoStore, agentDir } from "@intx/hub-agent"; import { generateKeyPair, signEd25519, verifySSHSignature } from "@intx/crypto"; import { @@ -51,15 +47,12 @@ describe("hibernated-agent-identity-vault", () => { // The real, unmodified destructive call `hub-link.js` makes on every // undeploy -- exercised here exactly as the PR #291 spike exercised it. await repoStore.remove(address); - expect(await fs.readdir(dataDir)).not.toContain( - path.basename(agentDir(dataDir, address)), - ); + expect(await fs.readdir(dataDir)).not.toContain(path.basename(agentDir(dataDir, address))); const restore = await restoreAgentIdentity(dataDir, address); expect(restore.restored).toBe(true); - const { keyPair: restored, isNew } = - await keyStore.loadOrGenerateKey(address); + const { keyPair: restored, isNew } = await keyStore.loadOrGenerateKey(address); expect(isNew).toBe(false); expect(restored.privateKey).toEqual(original.privateKey); expect(restored.publicKey).toEqual(original.publicKey); @@ -80,8 +73,7 @@ describe("hibernated-agent-identity-vault", () => { const restore = await restoreAgentIdentity(dataDir, address); expect(restore.restored).toBe(false); - const { keyPair: regenerated, isNew } = - await keyStore.loadOrGenerateKey(address); + const { keyPair: regenerated, isNew } = await keyStore.loadOrGenerateKey(address); expect(isNew).toBe(true); expect(regenerated.privateKey).not.toEqual(original.privateKey); }); @@ -110,9 +102,7 @@ describe("hibernated-agent-identity-vault", () => { const dataDir = await makeTmpDataDir(); const address = "run_already-gone@example.com"; - const reportErrorMock = mock( - (_error: unknown, _context: unknown) => "ref-test", - ); + const reportErrorMock = mock((_error: unknown, _context: unknown) => "ref-test"); mock.module("@corbits/error-sink", () => ({ reportError: reportErrorMock, })); @@ -162,9 +152,7 @@ describe("hibernated-agent-identity-vault", () => { retentionMs, }); - expect(result.reapedEntries).toEqual([ - path.basename(agentDir(dataDir, oldAddress)), - ]); + expect(result.reapedEntries).toEqual([path.basename(agentDir(dataDir, oldAddress))]); await expect(fs.stat(oldVaultEntry)).rejects.toThrow(); const freshVaultEntry = path.join( dataDir, diff --git a/apps/sidecar/src/hibernated-agent-identity-vault.ts b/apps/sidecar/src/hibernated-agent-identity-vault.ts index 1239add032..05ac0d92dc 100644 --- a/apps/sidecar/src/hibernated-agent-identity-vault.ts +++ b/apps/sidecar/src/hibernated-agent-identity-vault.ts @@ -46,10 +46,7 @@ function vaultRoot(dataDir: string): string { } function vaultEntryDir(dataDir: string, agentAddress: string): string { - return path.join( - vaultRoot(dataDir), - path.basename(agentDir(dataDir, agentAddress)), - ); + return path.join(vaultRoot(dataDir), path.basename(agentDir(dataDir, agentAddress))); } async function pathExists(target: string): Promise { @@ -127,13 +124,9 @@ export async function snapshotAgentIdentity( await fsp.cp(source, dest, { recursive: true }); await hardenPermissionsRecursive(dest); await fsp.chmod(dest, 0o700); - await fsp.writeFile( - path.join(dest, MARKER_FILENAME), - new Date().toISOString(), - { - mode: 0o600, - }, - ); + await fsp.writeFile(path.join(dest, MARKER_FILENAME), new Date().toISOString(), { + mode: 0o600, + }); return { snapshotted: true }; } @@ -209,11 +202,8 @@ export async function reapExpiredHibernationSnapshots( if (!isErrnoNotFound(cause)) throw cause; } - const snapshottedAtMs = - snapshottedAtIso === undefined ? NaN : Date.parse(snapshottedAtIso); - const expired = Number.isNaN(snapshottedAtMs) - ? true - : nowMs - snapshottedAtMs >= retentionMs; + const snapshottedAtMs = snapshottedAtIso === undefined ? NaN : Date.parse(snapshottedAtIso); + const expired = Number.isNaN(snapshottedAtMs) ? true : nowMs - snapshottedAtMs >= retentionMs; if (expired) { await fsp.rm(entryPath, { recursive: true, force: true }); diff --git a/apps/sidecar/src/hub-link-watchdog.ts b/apps/sidecar/src/hub-link-watchdog.ts index 822dd752dc..a484fe3f5e 100644 --- a/apps/sidecar/src/hub-link-watchdog.ts +++ b/apps/sidecar/src/hub-link-watchdog.ts @@ -30,9 +30,7 @@ export type CreateHubLinkWatchdogOpts = { onStall: () => void; }; -export function createHubLinkWatchdog( - opts: CreateHubLinkWatchdogOpts, -): HubLinkWatchdog { +export function createHubLinkWatchdog(opts: CreateHubLinkWatchdogOpts): HubLinkWatchdog { let deadline: ReturnType | undefined; function arm(): void { diff --git a/apps/sidecar/src/index.ts b/apps/sidecar/src/index.ts index 5ed30d44a8..173155628b 100644 --- a/apps/sidecar/src/index.ts +++ b/apps/sidecar/src/index.ts @@ -18,11 +18,7 @@ // for the first connect attempt, and only then does the link dial out. import path from "node:path"; -import { - createEd25519Crypto, - generateKeyPair, - verifySSHSignature, -} from "@intx/crypto"; +import { createEd25519Crypto, generateKeyPair, verifySSHSignature } from "@intx/crypto"; import { createSidecarOrchestrator, type HubLink } from "@intx/hub-agent"; import { createAgentRepoStore } from "@intx/hub-sessions"; import { loadAdapterRegistry } from "@intx/inference/providers"; @@ -33,10 +29,7 @@ import { hexEncode } from "@intx/types"; import { reportError } from "@corbits/error-sink"; import { readSidecarConfig } from "./config"; -import { - DEFAULT_TOOL_REGISTRIES_JSON, - parseToolRegistries, -} from "./tool-materialization"; +import { DEFAULT_TOOL_REGISTRIES_JSON, parseToolRegistries } from "./tool-materialization"; import { createWorkflowProbeExecutor } from "./workflow-probe-handler"; import { createWorkflowClosureMaterializer } from "./workflow-closure-materialization"; import { MAX_INLINE_ASSET_PAYLOAD_BYTES } from "./source-asset-delivery"; @@ -45,10 +38,7 @@ import { createHubLinkWatchdog } from "./hub-link-watchdog"; import { createOAuthLoopbackLoginService } from "./oauth-login"; import { attachShutdownRejectionHandler, runSidecarShutdown } from "./shutdown"; import { loadOrMintSidecarKeypair } from "./signing-keypair"; -import { - createSidecarDeployRouter, - type SidecarDeployRouter, -} from "./workflow-host-wiring"; +import { createSidecarDeployRouter, type SidecarDeployRouter } from "./workflow-host-wiring"; import { createDeploymentAddressRegistry, createMultistepCredentialsRouter, @@ -97,11 +87,8 @@ await createTarballCache({ // commit with it, and each workflow-process child re-derives it from // its spawn-time env. The public key is logged so an operator can pin // which identity a given process advertises. -const signingKey = await loadOrMintSidecarKeypair( - path.join(config.dataDir, ".sidecar-signing"), -); -getLogger(["sidecar", "boot"]) - .info`Sidecar identity ${hexEncode(signingKey.publicKey)}`; +const signingKey = await loadOrMintSidecarKeypair(path.join(config.dataDir, ".sidecar-signing")); +getLogger(["sidecar", "boot"]).info`Sidecar identity ${hexEncode(signingKey.publicKey)}`; // The substrate-backed RepoStore the supervisors read and write through. // Wrapped below with the pack-pushing facade so a successful @@ -182,8 +169,7 @@ const multistepSubstrateEnv: Record = { // operator pinned none, so the child's per-step tool materialization // resolves the exact registries this boot edge resolved — a child // never falls back to a default of its own. - SIDECAR_TOOL_REGISTRIES: - config.toolRegistries ?? DEFAULT_TOOL_REGISTRIES_JSON, + SIDECAR_TOOL_REGISTRIES: config.toolRegistries ?? DEFAULT_TOOL_REGISTRIES_JSON, }; if (config.home !== undefined) { multistepSubstrateEnv["HOME"] = config.home; @@ -210,9 +196,7 @@ const workflowProbeExecutor = createWorkflowProbeExecutor({ cacheMaxBytes: CACHE_MAX_BYTES, registryMaxTarballBytes: REGISTRY_MAX_TARBALL_BYTES, maxAssetPayloadBytes: MAX_INLINE_ASSET_PAYLOAD_BYTES, - registries: parseToolRegistries( - config.toolRegistries ?? DEFAULT_TOOL_REGISTRIES_JSON, - ), + registries: parseToolRegistries(config.toolRegistries ?? DEFAULT_TOOL_REGISTRIES_JSON), scratchRoot: path.join(config.dataDir, "workflow-probe", "closures"), }), }); @@ -285,14 +269,8 @@ const orchestrator = createSidecarOrchestrator({ wrappedRepoStore.markAddressUnroutable(address); } }, - createDeployRouter: ({ - sessions, - keyStore, - publishWorkflowInferenceEvent, - }) => { - const deployRouterConfigBase: Parameters< - typeof createSidecarDeployRouter - >[0] = { + createDeployRouter: ({ sessions, keyStore, publishWorkflowInferenceEvent }) => { + const deployRouterConfigBase: Parameters[0] = { sessions, keyStore, transport, @@ -353,9 +331,7 @@ const orchestrator = createSidecarOrchestrator({ idleHibernateMs: config.idleHibernateMs, } : deployRouterConfigWithReadyTimeoutMs; - const router = createSidecarDeployRouter( - deployRouterConfigWithIdleHibernateMs, - ); + const router = createSidecarDeployRouter(deployRouterConfigWithIdleHibernateMs); capturedRouter = router; return router; }, @@ -364,9 +340,7 @@ const orchestrator = createSidecarOrchestrator({ resolvedHubLink = orchestrator.hubLink; if (capturedRouter === undefined) { - throw new Error( - "sidecar boot: deploy router was not constructed before deployment restore", - ); + throw new Error("sidecar boot: deploy router was not constructed before deployment restore"); } const deployRouter: SidecarDeployRouter = capturedRouter; diff --git a/apps/sidecar/src/multistep-credentials-router.test.ts b/apps/sidecar/src/multistep-credentials-router.test.ts index 82e524d640..5108961e05 100644 --- a/apps/sidecar/src/multistep-credentials-router.test.ts +++ b/apps/sidecar/src/multistep-credentials-router.test.ts @@ -47,9 +47,9 @@ test("a handler rejection propagates to the caller", async () => { router.register("ins_dep_1@example.com", () => Promise.reject(new Error("child rejected credentials-updated")), ); - await expect( - router.tryRoute(makeFrame("ins_dep_1@example.com")), - ).rejects.toThrow(/child rejected credentials-updated/); + await expect(router.tryRoute(makeFrame("ins_dep_1@example.com"))).rejects.toThrow( + /child rejected credentials-updated/, + ); }); test("unregister stops routing for the address", async () => { diff --git a/apps/sidecar/src/multistep-grants-router.test.ts b/apps/sidecar/src/multistep-grants-router.test.ts index 1320707e36..1631b2778d 100644 --- a/apps/sidecar/src/multistep-grants-router.test.ts +++ b/apps/sidecar/src/multistep-grants-router.test.ts @@ -33,12 +33,10 @@ test("tryRoute dispatches the frame's runId and stepGrants to the handler", asyn test("a handler rejection propagates to the caller", async () => { const router = createMultistepGrantsRouter(); - router.register("ins_dep_1@example.com", () => - Promise.reject(new Error("durable write failed")), + router.register("ins_dep_1@example.com", () => Promise.reject(new Error("durable write failed"))); + await expect(router.tryRoute(makeFrame("ins_dep_1@example.com"))).rejects.toThrow( + /durable write failed/, ); - await expect( - router.tryRoute(makeFrame("ins_dep_1@example.com")), - ).rejects.toThrow(/durable write failed/); }); test("unregister stops routing for the address", async () => { diff --git a/apps/sidecar/src/oauth-login.test.ts b/apps/sidecar/src/oauth-login.test.ts index 63f282c463..57da8034da 100644 --- a/apps/sidecar/src/oauth-login.test.ts +++ b/apps/sidecar/src/oauth-login.test.ts @@ -3,10 +3,7 @@ // `OAuthCallbackPortInUseError`, and never opens a browser sidecar-side. import { afterAll, describe, expect, test } from "bun:test"; import { createServer, type Server } from "node:http"; -import { - CODEX_AUTHORIZE_URL, - CODEX_REDIRECT_URI, -} from "@corbits/codex-provider/constants"; +import { CODEX_AUTHORIZE_URL, CODEX_REDIRECT_URI } from "@corbits/codex-provider/constants"; import { XAI_REDIRECT_URI } from "@corbits/xai-provider"; import { OAuthCallbackPortInUseError } from "@corbits/oauth-core"; import { createOAuthLoopbackLoginService } from "./oauth-login"; diff --git a/apps/sidecar/src/oauth-login.ts b/apps/sidecar/src/oauth-login.ts index ec7a8fc06a..53e7ab5cef 100644 --- a/apps/sidecar/src/oauth-login.ts +++ b/apps/sidecar/src/oauth-login.ts @@ -20,16 +20,8 @@ import { type FetchLike, type OAuthClientConfig, } from "@corbits/oauth-core"; -import { - codexOAuthConfig, - exchangeCodexCode, - CODEX_REDIRECT_URI, -} from "@corbits/codex-provider"; -import { - exchangeXaiCode, - xaiOAuthConfig, - XAI_REDIRECT_URI, -} from "@corbits/xai-provider"; +import { codexOAuthConfig, exchangeCodexCode, CODEX_REDIRECT_URI } from "@corbits/codex-provider"; +import { exchangeXaiCode, xaiOAuthConfig, XAI_REDIRECT_URI } from "@corbits/xai-provider"; /** The sidecar-hosted login outcome: the shape `@intx/types`'s * `OAuthLoginTokens` wire arm carries. */ @@ -62,10 +54,7 @@ function isLoopbackHost(host: string): boolean { * URI, asserting the loopback + pinned-port contract up front: a bind * failure later is genuinely "port already in use", never "we drifted onto * some other port". */ -function callbackConfig( - redirectUri: string, - pinnedPort: number, -): CallbackServerConfig { +function callbackConfig(redirectUri: string, pinnedPort: number): CallbackServerConfig { const url = new URL(redirectUri); if (url.protocol !== "http:" || !isLoopbackHost(url.hostname)) { throw new Error( @@ -124,12 +113,9 @@ export function createOAuthLoopbackLoginService(deps?: { const handle = await startOAuthLogin( { profile: connectorId, signal: new AbortController().signal, now }, { - startCallbackServer: (state) => - startCallbackServer(state, connector.callback), - buildAuthorizeUrl: (pkce, state) => - buildAuthorizeUrl(connector.oauthConfig, pkce, state), - exchangeCode: (code, verifier, at) => - connector.exchange(code, verifier, at), + startCallbackServer: (state) => startCallbackServer(state, connector.callback), + buildAuthorizeUrl: (pkce, state) => buildAuthorizeUrl(connector.oauthConfig, pkce, state), + exchangeCode: (code, verifier, at) => connector.exchange(code, verifier, at), // Persistence is the hub's job: the connect pipeline stores the // credential row, so the staged `commit` here is a no-op that // exists only to satisfy the oauth-core login contract. diff --git a/apps/sidecar/src/originating-workbench.test.ts b/apps/sidecar/src/originating-workbench.test.ts index e560090089..c74d3fd52b 100644 --- a/apps/sidecar/src/originating-workbench.test.ts +++ b/apps/sidecar/src/originating-workbench.test.ts @@ -18,12 +18,10 @@ function mailRequest(from: string) { } test("extractOriginatingWorkbenchId reads the From local-part", () => { - expect(extractOriginatingWorkbenchId("chan_room_a@alice.localhost")).toBe( - "chan_room_a", + expect(extractOriginatingWorkbenchId("chan_room_a@alice.localhost")).toBe("chan_room_a"); + expect(extractOriginatingWorkbenchId("Workbench ")).toBe( + "ins_workbench1", ); - expect( - extractOriginatingWorkbenchId("Workbench "), - ).toBe("ins_workbench1"); }); test("extractOriginatingWorkbenchId is undefined for an empty or bare From", () => { @@ -32,19 +30,13 @@ test("extractOriginatingWorkbenchId is undefined for an empty or bare From", () }); test("resolveOriginatingWorkbenchId uses the unscoped sentinel when From is missing", () => { - expect(resolveOriginatingWorkbenchId(undefined)).toBe( - UNSCOPED_ORIGINATING_WORKBENCH_ID, - ); + expect(resolveOriginatingWorkbenchId(undefined)).toBe(UNSCOPED_ORIGINATING_WORKBENCH_ID); expect(resolveOriginatingWorkbenchId("chan_a")).toBe("chan_a"); }); test("originatingWorkbenchIdFromRequest is a function of this request's mail", () => { - expect( - originatingWorkbenchIdFromRequest(mailRequest("chan_a@alice.localhost")), - ).toBe("chan_a"); - expect( - originatingWorkbenchIdFromRequest(mailRequest("chan_b@alice.localhost")), - ).toBe("chan_b"); + expect(originatingWorkbenchIdFromRequest(mailRequest("chan_a@alice.localhost"))).toBe("chan_a"); + expect(originatingWorkbenchIdFromRequest(mailRequest("chan_b@alice.localhost"))).toBe("chan_b"); }); test("a resumed input delivers the decision's mail, not the original input", () => { @@ -61,9 +53,7 @@ test("a resumed input delivers the decision's mail, not the original input", () }); test("a request without mail names no room", () => { - expect(originatingWorkbenchIdFromRequest({ input: "plain text" })).toBe( - undefined, - ); + expect(originatingWorkbenchIdFromRequest({ input: "plain text" })).toBe(undefined); expect( originatingWorkbenchIdFromRequest({ input: mailRequest("chan_a@alice.localhost").input, diff --git a/apps/sidecar/src/originating-workbench.ts b/apps/sidecar/src/originating-workbench.ts index db20942586..8754d5a573 100644 --- a/apps/sidecar/src/originating-workbench.ts +++ b/apps/sidecar/src/originating-workbench.ts @@ -20,9 +20,7 @@ export const UNSCOPED_ORIGINATING_WORKBENCH_ID = "_unscoped"; * Local-part of a From header value. Chat encodes the originating workbench * id there; anything else (empty, unparseable) is undefined. */ -export function extractOriginatingWorkbenchId( - from: string, -): string | undefined { +export function extractOriginatingWorkbenchId(from: string): string | undefined { if (from.trim() === "") return undefined; try { const spec = extractAddrSpec(from); @@ -34,9 +32,7 @@ export function extractOriginatingWorkbenchId( } } -export function resolveOriginatingWorkbenchId( - fromWorkbenchId: string | undefined, -): string { +export function resolveOriginatingWorkbenchId(fromWorkbenchId: string | undefined): string { return fromWorkbenchId !== undefined && fromWorkbenchId.length > 0 ? fromWorkbenchId : UNSCOPED_ORIGINATING_WORKBENCH_ID; @@ -52,7 +48,5 @@ export function originatingWorkbenchIdFromRequest( ): string | undefined { const delivered = req.resume === undefined ? req.input : req.resume.decision; if (!isMail(delivered)) return undefined; - return resolveOriginatingWorkbenchId( - extractOriginatingWorkbenchId(delivered.headers.from), - ); + return resolveOriginatingWorkbenchId(extractOriginatingWorkbenchId(delivered.headers.from)); } diff --git a/apps/sidecar/src/run-grants.test.ts b/apps/sidecar/src/run-grants.test.ts index 18fdf0cca9..f20774aa9f 100644 --- a/apps/sidecar/src/run-grants.test.ts +++ b/apps/sidecar/src/run-grants.test.ts @@ -9,9 +9,7 @@ import { readRunGrants, runGrantsPath } from "./run-grants"; const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeRepoStore(): Promise<{ store: RepoStore; dir: string }> { diff --git a/apps/sidecar/src/sidecar-materialization-config.ts b/apps/sidecar/src/sidecar-materialization-config.ts index b91480c108..2d26cba8ea 100644 --- a/apps/sidecar/src/sidecar-materialization-config.ts +++ b/apps/sidecar/src/sidecar-materialization-config.ts @@ -54,9 +54,7 @@ export function readRegistries(): ReadonlyMap { } const validated = RegistryConfigEnvArray(parsed); if (validated instanceof type.errors) { - throw new Error( - `SIDECAR_TOOL_REGISTRIES failed validation: ${validated.summary}`, - ); + throw new Error(`SIDECAR_TOOL_REGISTRIES failed validation: ${validated.summary}`); } const out = new Map(); for (const entry of validated) { diff --git a/apps/sidecar/src/signing-keypair.ts b/apps/sidecar/src/signing-keypair.ts index cebc309bbb..8d3932dc48 100644 --- a/apps/sidecar/src/signing-keypair.ts +++ b/apps/sidecar/src/signing-keypair.ts @@ -43,16 +43,11 @@ async function exists(filePath: string): Promise { * which of the two files rotted, so we halt and let an operator decide * (restore the seed, or remove the directory to mint a fresh identity). */ -export async function loadOrMintSidecarKeypair( - signingDir: string, -): Promise { +export async function loadOrMintSidecarKeypair(signingDir: string): Promise { const privateKeyPath = path.join(signingDir, PRIVATE_KEY_FILENAME); const publicKeyPath = path.join(signingDir, PUBLIC_KEY_FILENAME); - const [havePriv, havePub] = await Promise.all([ - exists(privateKeyPath), - exists(publicKeyPath), - ]); + const [havePriv, havePub] = await Promise.all([exists(privateKeyPath), exists(publicKeyPath)]); if (havePriv !== havePub) { throw new Error( `sidecar signing keypair under ${signingDir} is partial: privateKey=${String(havePriv)} publicKey=${String(havePub)}; remove the directory to reset`, diff --git a/apps/sidecar/src/source-asset-delivery.ts b/apps/sidecar/src/source-asset-delivery.ts index dd2080b139..28e87e13a0 100644 --- a/apps/sidecar/src/source-asset-delivery.ts +++ b/apps/sidecar/src/source-asset-delivery.ts @@ -59,12 +59,7 @@ export async function indexAssetPackIntoGitDir(args: { }; try { - await indexPackIntoGitDir( - tempDir, - pack, - commitSha, - DEFAULT_PACK_MATERIALIZATION_LIMITS, - ); + await indexPackIntoGitDir(tempDir, pack, commitSha, DEFAULT_PACK_MATERIALIZATION_LIMITS); } catch (err) { await cleanupTemp(); throw err; @@ -130,9 +125,7 @@ export function sourceAssetGitDir(gitDirRoot: string, assetId: string): string { // resolve to the shared root itself. Mirrors `applyAssetPack`'s all-dots // segment guard. if (!SAFE_ASSET_ID.test(assetId) || /^\.+$/.test(assetId)) { - throw new Error( - `source-asset delivery: unsafe assetId ${JSON.stringify(assetId)}`, - ); + throw new Error(`source-asset delivery: unsafe assetId ${JSON.stringify(assetId)}`); } return path.join(gitDirRoot, assetId); } diff --git a/apps/sidecar/src/step-agent-tools.test.ts b/apps/sidecar/src/step-agent-tools.test.ts index 217b6ec891..b4ea854bdb 100644 --- a/apps/sidecar/src/step-agent-tools.test.ts +++ b/apps/sidecar/src/step-agent-tools.test.ts @@ -21,10 +21,7 @@ import type { GrantRule } from "@intx/authz"; import type { CredentialDelivery } from "@intx/types/sidecar"; import type { CredentialWiring } from "@intx/workflow-host"; -import { - consumerBindings, - type StepCredentialContext, -} from "./step-agent-tools"; +import { consumerBindings, type StepCredentialContext } from "./step-agent-tools"; const CONSUMER = toolConsumer("@corbits/granola-tools"); const OTHER_CONSUMER = toolConsumer("@corbits/linear-tools"); @@ -46,8 +43,7 @@ function providersWithCapturedAuth(): { const providers = createCredentialProviderRegistry([ createHttpCredentialProvider({ fetch: async (_input, init) => { - captured = - (init?.headers as Headers | undefined)?.get("authorization") ?? null; + captured = (init?.headers as Headers | undefined)?.get("authorization") ?? null; return new Response("{}", { status: 200 }); }, }), @@ -57,9 +53,7 @@ function providersWithCapturedAuth(): { function delivery(secret: string): CredentialDelivery { return { - bindings: [ - { handle: "granola", credentialId: CREDENTIAL_ID, consumer: CONSUMER }, - ], + bindings: [{ handle: "granola", credentialId: CREDENTIAL_ID, consumer: CONSUMER }], materials: [ { credentialId: CREDENTIAL_ID, @@ -178,9 +172,7 @@ describe("createCredentialCapability composed over a fake CredentialWiring", () grants: [], }); - await expect(capability.resolve("granola")).rejects.toThrow( - /no credential is bound to handle/, - ); + await expect(capability.resolve("granola")).rejects.toThrow(/no credential is bound to handle/); }); test("a bound handle with no matching grant fails closed even though the binding exists", async () => { @@ -196,8 +188,6 @@ describe("createCredentialCapability composed over a fake CredentialWiring", () grants: [...context.wiring.resolveStepGrants(STEP_ID)] as GrantRule[], }); - await expect(capability.resolve("granola")).rejects.toThrow( - /not authorized to use credential/, - ); + await expect(capability.resolve("granola")).rejects.toThrow(/not authorized to use credential/); }); }); diff --git a/apps/sidecar/src/step-agent-tools.ts b/apps/sidecar/src/step-agent-tools.ts index dca5ccc9f8..ab1806e050 100644 --- a/apps/sidecar/src/step-agent-tools.ts +++ b/apps/sidecar/src/step-agent-tools.ts @@ -94,10 +94,7 @@ const STEP_TOOLS = Symbol("intx.sidecar.step-tools"); // through `Reflect.get`/`Reflect.set` so neither site needs a type // assertion: `BaseEnv` is an interface without a symbol index // signature, so a structural cast would otherwise be required. -function setStepToolSlot( - env: object, - materialization: StepToolMaterialization, -): void { +function setStepToolSlot(env: object, materialization: StepToolMaterialization): void { Reflect.set(env, STEP_TOOLS, materialization); } @@ -112,9 +109,7 @@ function getStepToolSlot(env: object): StepToolMaterialization | undefined { return value; } -function isStepToolMaterialization( - value: unknown, -): value is StepToolMaterialization { +function isStepToolMaterialization(value: unknown): value is StepToolMaterialization { return ( typeof value === "object" && value !== null && @@ -142,15 +137,8 @@ export interface StepCredentialContext { const STEP_CREDENTIALS = Symbol("intx.sidecar.step-credentials"); -function isStepCredentialContext( - value: unknown, -): value is StepCredentialContext { - return ( - typeof value === "object" && - value !== null && - "wiring" in value && - "stepId" in value - ); +function isStepCredentialContext(value: unknown): value is StepCredentialContext { + return typeof value === "object" && value !== null && "wiring" in value && "stepId" in value; } /** @@ -160,10 +148,7 @@ function isStepCredentialContext( * `buildEnv` once the `CredentialWiring` the child's runtime carries is * known. */ -export function attachStepCredentials( - env: object, - context: StepCredentialContext, -): void { +export function attachStepCredentials(env: object, context: StepCredentialContext): void { Reflect.set(env, STEP_CREDENTIALS, context); } @@ -171,9 +156,7 @@ export function attachStepCredentials( * Read back the credential context `attachStepCredentials` set on the * per-step env. */ -export function getStepCredentialContext( - env: object, -): StepCredentialContext | undefined { +export function getStepCredentialContext(env: object): StepCredentialContext | undefined { const value: unknown = Reflect.get(env, STEP_CREDENTIALS); if (value === undefined) return undefined; if (!isStepCredentialContext(value)) { @@ -374,10 +357,7 @@ export function attachStepTools( */ export function createToolBearingAgentFactory(deps: { providers: CredentialProviderRegistry; -}): ( - def: AgentDefinition, - env: EnvReq, -) => Promise { +}): (def: AgentDefinition, env: EnvReq) => Promise { return async ( def: AgentDefinition, env: EnvReq, @@ -406,9 +386,7 @@ export function createToolBearingAgentFactory(deps: { // way. const credentialContext = getStepCredentialContext(env); const credentialCapabilities = new Map(); - function credentialCapabilityFor( - consumer: string, - ): HostCredentialCapability { + function credentialCapabilityFor(consumer: string): HostCredentialCapability { const existing = credentialCapabilities.get(consumer); if (existing !== undefined) return existing; const bindings = consumerBindings(credentialContext, consumer); @@ -449,10 +427,7 @@ export function createToolBearingAgentFactory(deps: { : undefined; const scopedEnv: BaseEnv & { credentials?: HostCredentialCapability; - } = - credentials !== undefined - ? { ...factoryEnv, credentials } - : factoryEnv; + } = credentials !== undefined ? { ...factoryEnv, credentials } : factoryEnv; const bundle = annotated(scopedEnv); if (bundle.dispose !== undefined) { capturedDisposers.add(bundle.dispose); @@ -531,8 +506,8 @@ export function createToolBearingAgentFactory(deps: { // directly, only overridden -- so the first credential-requiring // factory's capability satisfies the presence check for all of // them. - const credentialRequiringFactory = materialization.factories.find( - (factory) => factory.requires.includes("credentials"), + const credentialRequiringFactory = materialization.factories.find((factory) => + factory.requires.includes("credentials"), ); if (credentialRequiringFactory !== undefined) { chainEnv = { @@ -678,9 +653,7 @@ export function stepInferenceMaterialResolver( ): CredentialMaterialResolver { return (credentialId) => { const current = context.wiring.materialRef.current; - const material = current?.materials.find( - (entry) => entry.credentialId === credentialId, - ); + const material = current?.materials.find((entry) => entry.credentialId === credentialId); if (material === undefined) { throw new Error( `sidecar workflow-child step ${JSON.stringify(context.stepId)}: inference source credential ${credentialId} has no delivered material (revoked, rotated away, or never delivered)`, @@ -719,10 +692,7 @@ function wrapAgentClose(agent: Agent, teardown: () => Promise): Agent { }; } -async function disposeAll( - instances: readonly unknown[], - context: string, -): Promise { +async function disposeAll(instances: readonly unknown[], context: string): Promise { const failures: unknown[] = []; for (const instance of instances) { const dispose = pluginDispose(instance); diff --git a/apps/sidecar/src/step-credential-capabilities.test.ts b/apps/sidecar/src/step-credential-capabilities.test.ts index 8addf59f12..847ad00e9a 100644 --- a/apps/sidecar/src/step-credential-capabilities.test.ts +++ b/apps/sidecar/src/step-credential-capabilities.test.ts @@ -33,8 +33,7 @@ function trackingProvider(): { } function grant( - overrides: Partial & - Pick, + overrides: Partial & Pick, ): GrantRule { return { id: "grt_test", @@ -100,21 +99,18 @@ describe("buildCredentialCapabilities", () => { ], }, }; - const caps = buildCredentialCapabilities( - [fac("@intx/pkg-a", []), fac("@intx/pkg-b", [])], - { - materialCell: cell, - resolveGrants: () => [ - grant({ - resource: "credential:c1", - action: "use", - effect: "allow", - conditions: { tool: toolConsumer("@intx/pkg-a") }, - }), - ], - providers: createCredentialProviderRegistry([track.provider]), - }, - ); + const caps = buildCredentialCapabilities([fac("@intx/pkg-a", []), fac("@intx/pkg-b", [])], { + materialCell: cell, + resolveGrants: () => [ + grant({ + resource: "credential:c1", + action: "use", + effect: "allow", + conditions: { tool: toolConsumer("@intx/pkg-a") }, + }), + ], + providers: createCredentialProviderRegistry([track.provider]), + }); const capA = caps.get("@intx/pkg-a"); const capB = caps.get("@intx/pkg-b"); @@ -134,17 +130,12 @@ describe("buildCredentialCapabilities", () => { const track = trackingProvider(); const cell = { current: { bindings: [], materials: [] } }; expect(() => - buildCredentialCapabilities( - [fac("@intx/pkg-a", [{ handle: "needed" }])], - { - materialCell: cell, - resolveGrants: () => [], - providers: createCredentialProviderRegistry([track.provider]), - }, - ), - ).toThrow( - /declares credential handle\(s\) that no binding resolves: needed/, - ); + buildCredentialCapabilities([fac("@intx/pkg-a", [{ handle: "needed" }])], { + materialCell: cell, + resolveGrants: () => [], + providers: createCredentialProviderRegistry([track.provider]), + }), + ).toThrow(/declares credential handle\(s\) that no binding resolves: needed/); }); test("dropping a credential's material starves an already-shaped handle", async () => { @@ -185,8 +176,7 @@ describe("buildCredentialCapabilities", () => { await capA.resolve("cred"); const shaped = track.shapes[0]; - if (shaped === undefined) - throw new Error("expected the handle to be shaped"); + if (shaped === undefined) throw new Error("expected the handle to be shaped"); // Live read works while the material is delivered. expect(shaped.readCurrentMaterial()).toEqual({ secret: "sk-1" }); @@ -239,8 +229,7 @@ describe("buildCredentialCapabilities", () => { if (capA === undefined) throw new Error("expected a capability for pkg-a"); await capA.resolve("cred"); const shaped = track.shapes[0]; - if (shaped === undefined) - throw new Error("expected the handle to be shaped"); + if (shaped === undefined) throw new Error("expected the handle to be shaped"); // A rotation may change the secret, never the provider/origin. A live entry // whose origin drifted is refused rather than followed. @@ -255,9 +244,7 @@ describe("buildCredentialCapabilities", () => { }, ], }; - expect(() => shaped.readCurrentMaterial()).toThrow( - /changed provider\/origin/, - ); + expect(() => shaped.readCurrentMaterial()).toThrow(/changed provider\/origin/); }); test("a descriptor with no backing material fails the build closed", () => { diff --git a/apps/sidecar/src/step-credential-capabilities.ts b/apps/sidecar/src/step-credential-capabilities.ts index b988b4f5ce..0c122b47e3 100644 --- a/apps/sidecar/src/step-credential-capabilities.ts +++ b/apps/sidecar/src/step-credential-capabilities.ts @@ -98,11 +98,7 @@ export function buildCredentialCapabilities( // Fail the launch closed if the package declares a handle no binding // resolves for it -- the tool needs a credential the definition never // bound. Checked against THIS consumer's bound set only (invariant 2). - reconcileDeclaredCredentials( - consumer, - stf.declaredCredentials, - new Set(bindings.keys()), - ); + reconcileDeclaredCredentials(consumer, stf.declaredCredentials, new Set(bindings.keys())); if (stf.declaredCredentials.length === 0 && bindings.size === 0) { continue; @@ -192,9 +188,7 @@ function makeReadCurrentMaterial(args: { `credential material for ${credentialId} (consumer ${consumer}) is not available: the delivery cell is empty`, ); } - const material = delivery.materials.find( - (entry) => entry.credentialId === credentialId, - ); + const material = delivery.materials.find((entry) => entry.credentialId === credentialId); if (material === undefined) { throw new Error( `credential material for ${credentialId} (consumer ${consumer}) is no longer delivered: a re-push dropped it (rotated away or revoked)`, diff --git a/apps/sidecar/src/tool-materialization.test.ts b/apps/sidecar/src/tool-materialization.test.ts index 7838339a3d..d3facada1a 100644 --- a/apps/sidecar/src/tool-materialization.test.ts +++ b/apps/sidecar/src/tool-materialization.test.ts @@ -13,18 +13,14 @@ import { const tempDirs: string[] = []; async function tempDir(): Promise { - const d = await fs.promises.mkdtemp( - path.join(os.tmpdir(), "default-harness-test-"), - ); + const d = await fs.promises.mkdtemp(path.join(os.tmpdir(), "default-harness-test-")); tempDirs.push(d); return d; } afterEach(async () => { const dirs = tempDirs.splice(0); - await Promise.all( - dirs.map((d) => fs.promises.rm(d, { recursive: true, force: true })), - ); + await Promise.all(dirs.map((d) => fs.promises.rm(d, { recursive: true, force: true }))); }); describe("materializeToolPackages — manifest.invalid gate", () => { @@ -80,10 +76,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { // writer must not push the bytes through JSON.stringify, which // would double-encode the string and erase the original-input // evidence the audit file exists to preserve. - const persisted = await fs.promises.readFile( - path.join(attemptDir, "manifest.json"), - "utf-8", - ); + const persisted = await fs.promises.readFile(path.join(attemptDir, "manifest.json"), "utf-8"); expect(persisted).toBe(corrupt); const errorJson = JSON.parse( @@ -131,10 +124,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { // The audit trail records the bytes-as-supplied for both // failure modes, so a future investigator can replay the // same input against a newer validator. - const persisted = await fs.promises.readFile( - path.join(attemptDir, "manifest.json"), - "utf-8", - ); + const persisted = await fs.promises.readFile(path.join(attemptDir, "manifest.json"), "utf-8"); expect(persisted).toBe(wrongShape); const errorJson = JSON.parse( @@ -149,21 +139,15 @@ describe("materializeToolPackages — manifest.invalid gate", () => { }); test("accepts a bare id for backward compatibility with pre-versioned files", () => { - expect(parseActiveDeployId("legacy-deploy-id", "/dummy")).toBe( - "legacy-deploy-id", - ); + expect(parseActiveDeployId("legacy-deploy-id", "/dummy")).toBe("legacy-deploy-id"); }); test("rejects an unknown version prefix loudly", () => { - expect(() => parseActiveDeployId("v2:abc", "/dummy")).toThrow( - /unknown version prefix/, - ); + expect(() => parseActiveDeployId("v2:abc", "/dummy")).toThrow(/unknown version prefix/); }); test("rejects a v1-prefixed but empty id", () => { - expect(() => parseActiveDeployId("v1:", "/dummy")).toThrow( - /carries the v1 prefix but no id/, - ); + expect(() => parseActiveDeployId("v1:", "/dummy")).toThrow(/carries the v1 prefix but no id/); }); test("rejects an empty file", () => { @@ -253,10 +237,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { expect(outcome.degraded).toBe(true); expect(outcome.error).toBeInstanceOf(Error); - const dirtyContents = await fs.promises.readFile( - `${activeIdFile}.dirty`, - "utf-8", - ); + const dirtyContents = await fs.promises.readFile(`${activeIdFile}.dirty`, "utf-8"); expect(dirtyContents).toBe("v1:new-id-42"); }); }); @@ -270,10 +251,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { const storeDir = await tempDir(); const instanceDir = path.join(storeDir, "tool-packages"); await fs.promises.mkdir(instanceDir, { recursive: true }); - await fs.promises.writeFile( - path.join(instanceDir, "active-deploy-id"), - "v1:stale-recorded", - ); + await fs.promises.writeFile(path.join(instanceDir, "active-deploy-id"), "v1:stale-recorded"); await fs.promises.writeFile( path.join(instanceDir, "active-deploy-id.dirty"), "v1:truth-from-marker", @@ -304,10 +282,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { throw new Error("no attempt dir was created"); } const errorJson = JSON.parse( - await fs.promises.readFile( - path.join(auditRoot, attemptDirName, "error.json"), - "utf-8", - ), + await fs.promises.readFile(path.join(auditRoot, attemptDirName, "error.json"), "utf-8"), ); expect(errorJson.previousDeployId).toBe("truth-from-marker"); }); diff --git a/apps/sidecar/src/tool-materialization.ts b/apps/sidecar/src/tool-materialization.ts index ed9c32ae7f..d92c04844f 100644 --- a/apps/sidecar/src/tool-materialization.ts +++ b/apps/sidecar/src/tool-materialization.ts @@ -80,9 +80,7 @@ export const DEFAULT_TOOL_REGISTRIES_JSON = JSON.stringify([ * on malformed JSON, on a wrong-shape payload, and on duplicate * registry names. */ -export function parseToolRegistries( - raw: string, -): ReadonlyMap { +export function parseToolRegistries(raw: string): ReadonlyMap { if (raw.trim() === "") { throw new Error( "SIDECAR_TOOL_REGISTRIES is set but empty — unset the variable to use the default npmjs registry", @@ -99,9 +97,7 @@ export function parseToolRegistries( } const validated = RegistryConfigEnvArray(parsed); if (validated instanceof type.errors) { - throw new Error( - `SIDECAR_TOOL_REGISTRIES failed validation: ${validated.summary}`, - ); + throw new Error(`SIDECAR_TOOL_REGISTRIES failed validation: ${validated.summary}`); } const out = new Map(); for (const entry of validated) { @@ -111,9 +107,7 @@ export function parseToolRegistries( ); } const config: RegistryConfig = - entry.auth !== undefined - ? { url: entry.url, auth: entry.auth } - : { url: entry.url }; + entry.auth !== undefined ? { url: entry.url, auth: entry.auth } : { url: entry.url }; out.set(entry.name, config); } return out; @@ -280,9 +274,7 @@ export async function materializeToolPackages(args: { // committed deploy to "fresh instance". let previousDeployId = NO_PRIOR_DEPLOY_ID; try { - const dirtyRaw = ( - await fs.promises.readFile(activeIdDirtyFile, "utf-8") - ).trim(); + const dirtyRaw = (await fs.promises.readFile(activeIdDirtyFile, "utf-8")).trim(); const dirtyId = parseActiveDeployId(dirtyRaw, activeIdDirtyFile); logger.warn`active-deploy-id dirty marker present at ${activeIdDirtyFile}; the prior apply could not durably record the committed deploy id and the boot is reconciling from ${dirtyId}`; previousDeployId = dirtyId; @@ -328,9 +320,7 @@ export async function materializeToolPackages(args: { occurredAt, }, }); - return new Error( - `tool-package apply rejected (manifest.invalid): ${reason}`, - ); + return new Error(`tool-package apply rejected (manifest.invalid): ${reason}`); }; let parsedManifest: unknown; @@ -343,9 +333,7 @@ export async function materializeToolPackages(args: { } const validated = ToolPackageManifest(parsedManifest); if (validated instanceof type.errors) { - throw await rejectManifestInvalid( - `schema validation failed: ${validated.summary}`, - ); + throw await rejectManifestInvalid(`schema validation failed: ${validated.summary}`); } const cache = createTarballCache({ @@ -391,9 +379,7 @@ export async function materializeToolPackages(args: { manifestBytes: rawManifestBytes, failure: result, }); - throw new Error( - `tool-package apply rejected (${result.category}): ${result.message}`, - ); + throw new Error(`tool-package apply rejected (${result.category}): ${result.message}`); } // Apply staged: the loader built the new deploy at @@ -432,10 +418,9 @@ export async function materializeToolPackages(args: { occurredAt, }, }); - throw new Error( - `tool-package apply rejected (apply.previous-rotation.failed): ${message}`, - { cause: err }, - ); + throw new Error(`tool-package apply rejected (apply.previous-rotation.failed): ${message}`, { + cause: err, + }); } return { factories: collectFactories(result.loaded), @@ -555,10 +540,7 @@ async function persistActiveDeployId( * * Exported for direct unit testing of the cleanup contract. */ -export async function clearDirtyMarker( - activeIdFile: string, - reason: string, -): Promise { +export async function clearDirtyMarker(activeIdFile: string, reason: string): Promise { try { await fs.promises.unlink(`${activeIdFile}.dirty`); } catch (err) { @@ -652,12 +634,7 @@ async function writeRejectedApplyAudit(args: { occurredAt: string; }; }): Promise { - const dir = path.join( - args.storeDir, - "audit", - "rejected-applies", - args.attemptId, - ); + const dir = path.join(args.storeDir, "audit", "rejected-applies", args.attemptId); await fs.promises.mkdir(dir, { recursive: true }); // fsync the files and their parent directory before returning so the // rejection's evidence is durable before the caller throws and tears @@ -669,10 +646,7 @@ async function writeRejectedApplyAudit(args: { // should not turn into a second cascading failure on an already- // failing apply. await fsyncWriteFile(path.join(dir, "manifest.json"), args.manifestBytes); - await fsyncWriteFile( - path.join(dir, "error.json"), - JSON.stringify(args.failure, null, 2), - ); + await fsyncWriteFile(path.join(dir, "error.json"), JSON.stringify(args.failure, null, 2)); try { const dirHandle = await fs.promises.open(dir, "r"); try { @@ -685,10 +659,7 @@ async function writeRejectedApplyAudit(args: { } } -async function fsyncWriteFile( - filePath: string, - contents: string, -): Promise { +async function fsyncWriteFile(filePath: string, contents: string): Promise { const handle = await fs.promises.open(filePath, "w"); try { await handle.writeFile(contents); @@ -702,9 +673,7 @@ async function fsyncWriteFile( } } -function collectFactories( - loaded: readonly LoadedToolPackage[], -): readonly LoadedToolFactory[] { +function collectFactories(loaded: readonly LoadedToolPackage[]): readonly LoadedToolFactory[] { const out: LoadedToolFactory[] = []; for (const pkg of loaded) { for (const f of pkg.factories) out.push(f); diff --git a/apps/sidecar/src/workflow-closure-apply.ts b/apps/sidecar/src/workflow-closure-apply.ts index 76a2aacfa2..fdf6e91a14 100644 --- a/apps/sidecar/src/workflow-closure-apply.ts +++ b/apps/sidecar/src/workflow-closure-apply.ts @@ -165,9 +165,7 @@ export async function applyFrozenWorkflowClosure( registries: args.registries, host: { os: process.platform, cpu: process.arch }, maxRegistryTarballBytes: args.registryMaxTarballBytes, - ...(args.fetchTarball !== undefined - ? { fetchTarball: args.fetchTarball } - : {}), + ...(args.fetchTarball !== undefined ? { fetchTarball: args.fetchTarball } : {}), }); // Apply EXACTLY the frozen entries. `topLevel` is emptied so `applyAtomic` @@ -209,22 +207,17 @@ export async function applyFrozenWorkflowClosure( // under the same name@version reimports rather than resolving to the prior // instance. const workflowEntry = args.closure.entries.find( - (entry) => - entry.name === workflowPin.name && entry.version === workflowPin.version, + (entry) => entry.name === workflowPin.name && entry.version === workflowPin.version, ); const definition = await loadWorkflowDefinitionFromClosure({ packageDir, ...(workflowEntry !== undefined ? { - importCacheKey: getToolPackageSourceContentIdentity( - workflowEntry.source, - ), + importCacheKey: getToolPackageSourceContentIdentity(workflowEntry.source), } : {}), - ...(args.importModule !== undefined - ? { importModule: args.importModule } - : {}), + ...(args.importModule !== undefined ? { importModule: args.importModule } : {}), }); logger.debug`applied frozen workflow closure ${workflowPin.name}@${workflowPin.version}: loaded definition ${definition.id}`; diff --git a/apps/sidecar/src/workflow-closure-materialization.ts b/apps/sidecar/src/workflow-closure-materialization.ts index 789d88db9e..8b6622f58e 100644 --- a/apps/sidecar/src/workflow-closure-materialization.ts +++ b/apps/sidecar/src/workflow-closure-materialization.ts @@ -164,9 +164,7 @@ export function createWorkflowClosureMaterializer( registries: config.registries, host, maxRegistryTarballBytes: config.registryMaxTarballBytes, - ...(config.fetchTarball !== undefined - ? { fetchTarball: config.fetchTarball } - : {}), + ...(config.fetchTarball !== undefined ? { fetchTarball: config.fetchTarball } : {}), }); // Materialize any inline-delivered assets under the probe scratch: @@ -221,11 +219,7 @@ export function createWorkflowClosureMaterializer( }); const storeDir = path.join(scratchDir, "store"); - const packageDir = storeEntryDir( - storeDir, - workflowPin.name, - workflowPin.version, - ); + const packageDir = storeEntryDir(storeDir, workflowPin.name, workflowPin.version); await assertFrameEntryMatchesPackage(packageDir, frame.entry); diff --git a/apps/sidecar/src/workflow-deployment-record.test.ts b/apps/sidecar/src/workflow-deployment-record.test.ts index 28c7e9c702..042d0891e1 100644 --- a/apps/sidecar/src/workflow-deployment-record.test.ts +++ b/apps/sidecar/src/workflow-deployment-record.test.ts @@ -62,9 +62,7 @@ describe("markWorkflowDeploymentRecordParked", () => { await markWorkflowDeploymentRecordParked(dataDir, "dep_missing"); - await expect( - readWorkflowDeploymentRecord(dataDir, "dep_missing"), - ).resolves.toBeUndefined(); + await expect(readWorkflowDeploymentRecord(dataDir, "dep_missing")).resolves.toBeUndefined(); }); }); @@ -128,9 +126,7 @@ describe("scanWorkflowDeploymentRecords reaps pre-cutover records", () => { expect(scanned).toHaveLength(1); expect(scanned[0]?.deploymentId).toBe("dep_live"); - await expect( - readWorkflowDeploymentRecord(dataDir, "dep_live"), - ).resolves.toBeDefined(); + await expect(readWorkflowDeploymentRecord(dataDir, "dep_live")).resolves.toBeDefined(); }); test("a record invalid for a reason other than the old-format shape is warned about, not reaped", async () => { @@ -185,11 +181,10 @@ describe("recordWorkflowDeploymentRestoreFailure", () => { const dataDir = await makeDataDir(); await writeWorkflowDeploymentRecord(dataDir, "dep_1", baseRecord); - const updated = await recordWorkflowDeploymentRestoreFailure( - dataDir, - "dep_1", - { kind: "permanent", reason: "address derives a different slug" }, - ); + const updated = await recordWorkflowDeploymentRestoreFailure(dataDir, "dep_1", { + kind: "permanent", + reason: "address derives a different slug", + }); expect(updated?.restoreFailure).toEqual({ kind: "permanent", @@ -225,14 +220,10 @@ describe("recordWorkflowDeploymentRestoreFailure", () => { kind: "permanent", reason: "malformed", }); - let record = await recordWorkflowDeploymentRestoreFailure( - dataDir, - "dep_1", - { - kind: "permanent", - reason: "still malformed", - }, - ); + let record = await recordWorkflowDeploymentRestoreFailure(dataDir, "dep_1", { + kind: "permanent", + reason: "still malformed", + }); expect(record?.restoreFailure?.attempts).toBe(2); record = await recordWorkflowDeploymentRestoreFailure(dataDir, "dep_1", { @@ -249,11 +240,10 @@ describe("recordWorkflowDeploymentRestoreFailure", () => { // Deliberately never written: simulates a teardown deleting the record // before the boot loop's failure-recording catch acquires the lock. - const updated = await recordWorkflowDeploymentRestoreFailure( - dataDir, - "dep_missing", - { kind: "transient", reason: "provider not registered" }, - ); + const updated = await recordWorkflowDeploymentRestoreFailure(dataDir, "dep_missing", { + kind: "transient", + reason: "provider not registered", + }); expect(updated).toBeUndefined(); const onDisk = await readWorkflowDeploymentRecord(dataDir, "dep_missing"); @@ -265,11 +255,10 @@ describe("clearWorkflowDeploymentRestoreFailure", () => { test("drops restoreFailure after a successful restore", async () => { const dataDir = await makeDataDir(); await writeWorkflowDeploymentRecord(dataDir, "dep_1", baseRecord); - const failed = await recordWorkflowDeploymentRestoreFailure( - dataDir, - "dep_1", - { kind: "transient", reason: "provider not registered" }, - ); + const failed = await recordWorkflowDeploymentRestoreFailure(dataDir, "dep_1", { + kind: "transient", + reason: "provider not registered", + }); await clearWorkflowDeploymentRestoreFailure( dataDir, @@ -328,8 +317,6 @@ describe("isWorkflowDeploymentRestoreQuarantined", () => { // A transient failure never quarantines, no matter how high its own // counter climbs -- it is tracked on a separate counter from the // permanent one. - expect(isWorkflowDeploymentRestoreQuarantined(transientAtSameCount)).toBe( - false, - ); + expect(isWorkflowDeploymentRestoreQuarantined(transientAtSameCount)).toBe(false); }); }); diff --git a/apps/sidecar/src/workflow-deployment-record.ts b/apps/sidecar/src/workflow-deployment-record.ts index 46369da3f7..5239a50d4a 100644 --- a/apps/sidecar/src/workflow-deployment-record.ts +++ b/apps/sidecar/src/workflow-deployment-record.ts @@ -92,9 +92,7 @@ export const WorkflowDeploymentRecord = type({ }); export type WorkflowDeploymentRecord = typeof WorkflowDeploymentRecord.infer; -export type RestoreFailureKind = NonNullable< - WorkflowDeploymentRecord["restoreFailure"] ->["kind"]; +export type RestoreFailureKind = NonNullable["kind"]; /** * Thrown from inside the restore path to mark WHY a specific attempt @@ -259,9 +257,7 @@ export async function recordWorkflowDeploymentRestoreFailure( if (existing === undefined) return undefined; const previous = existing.restoreFailure; const attempts = - previous !== undefined && previous.kind === failure.kind - ? previous.attempts + 1 - : 1; + previous !== undefined && previous.kind === failure.kind ? previous.attempts + 1 : 1; const updated: WorkflowDeploymentRecord = { ...existing, restoreFailure: { @@ -286,7 +282,7 @@ export async function clearWorkflowDeploymentRestoreFailure( record: WorkflowDeploymentRecord, ): Promise { if (record.restoreFailure === undefined) return; - const { restoreFailure, ...rest } = record; + const { restoreFailure: _restoreFailure, ...rest } = record; await writeWorkflowDeploymentRecord(dataDir, deploymentId, rest); } @@ -297,9 +293,7 @@ export async function clearWorkflowDeploymentRestoreFailure( * streak never quarantines, however high its own counter climbs -- see * `WorkflowRestoreFailure`. */ -export function isWorkflowDeploymentRestoreQuarantined( - record: WorkflowDeploymentRecord, -): boolean { +export function isWorkflowDeploymentRestoreQuarantined(record: WorkflowDeploymentRecord): boolean { return ( record.restoreFailure?.kind === "permanent" && record.restoreFailure.attempts >= RESTORE_QUARANTINE_THRESHOLD @@ -332,9 +326,7 @@ export interface ScannedWorkflowDeployment { * deliberately hibernated resumes on the next message or routine fire * that addresses it, not by being pre-spawned at boot. */ -export function partitionScannedDeployments( - scanned: readonly ScannedWorkflowDeployment[], -): { +export function partitionScannedDeployments(scanned: readonly ScannedWorkflowDeployment[]): { live: ScannedWorkflowDeployment[]; parked: ScannedWorkflowDeployment[]; } { diff --git a/apps/sidecar/src/workflow-host-wiring/asset-materialization.ts b/apps/sidecar/src/workflow-host-wiring/asset-materialization.ts index c40a16f0c4..94b3f051d9 100644 --- a/apps/sidecar/src/workflow-host-wiring/asset-materialization.ts +++ b/apps/sidecar/src/workflow-host-wiring/asset-materialization.ts @@ -42,11 +42,13 @@ export async function materializeWorkflowSources( try { existing = await readFile(sourcesAssetPath, "utf8"); } catch (cause) { - if (!( - cause instanceof Error && - "code" in cause && - (cause as { code: unknown }).code === "ENOENT" - )) { + if ( + !( + cause instanceof Error && + "code" in cause && + (cause as { code: unknown }).code === "ENOENT" + ) + ) { throw cause; } } diff --git a/apps/sidecar/src/workflow-host-wiring/closure-staging.ts b/apps/sidecar/src/workflow-host-wiring/closure-staging.ts index 0415bc922e..b2f974a055 100644 --- a/apps/sidecar/src/workflow-host-wiring/closure-staging.ts +++ b/apps/sidecar/src/workflow-host-wiring/closure-staging.ts @@ -11,10 +11,7 @@ import { join as pathJoin } from "node:path"; import { workflowSourceAssetMountPath } from "@intx/hub-sessions"; import type { SourceRefPin } from "@intx/types/sidecar"; -import { - applyFrozenWorkflowClosure, - type AppliedWorkflowClosure, -} from "../workflow-closure-apply"; +import { applyFrozenWorkflowClosure, type AppliedWorkflowClosure } from "../workflow-closure-apply"; import { sourceAssetGitDir } from "../source-asset-delivery"; import { parseToolRegistries } from "../tool-materialization"; @@ -26,10 +23,7 @@ import { parseToolRegistries } from "../tool-materialization"; * and re-materialization needs no re-delivery. The store is reclaimed on * redeploy (at the deploy call site) and on undeploy. */ -export function deploymentSourceAssetRoot( - dataDir: string, - deploymentId: string, -): string { +export function deploymentSourceAssetRoot(dataDir: string, deploymentId: string): string { return pathJoin(dataDir, "workflow-definition-sources", deploymentId); } @@ -38,10 +32,7 @@ export function deploymentSourceAssetRoot( * asset entries are checked out from. Sibling of the plain-file source store; * both survive restart so re-materialization needs no re-delivery. */ -export function deploymentSourceGitRoot( - dataDir: string, - deploymentId: string, -): string { +export function deploymentSourceGitRoot(dataDir: string, deploymentId: string): string { return pathJoin(dataDir, "workflow-definition-source-gits", deploymentId); } @@ -49,43 +40,25 @@ export function deploymentSourceGitRoot( * The per-deployment directory a deployment's closure is laid out under. * Deterministic per deployment id, so a redeploy or a boot restore reuses it. */ -export function deploymentClosureInstanceDir( - dataDir: string, - deploymentId: string, -): string { +export function deploymentClosureInstanceDir(dataDir: string, deploymentId: string): string { return pathJoin(dataDir, "workflow-definition-closures", deploymentId); } function deriveSourceAssetMounts(pin: SourceRefPin): Map { const mounts = new Map(); for (const entry of pin.closure.entries) { - if ( - entry.source.kind === "asset" && - entry.source.package.format === "tarball" - ) { - mounts.set( - entry.source.assetId, - workflowSourceAssetMountPath(entry.source.assetId), - ); + if (entry.source.kind === "asset" && entry.source.package.format === "tarball") { + mounts.set(entry.source.assetId, workflowSourceAssetMountPath(entry.source.assetId)); } } return mounts; } -function deriveSourceGitDirs( - pin: SourceRefPin, - gitRoot: string, -): Map { +function deriveSourceGitDirs(pin: SourceRefPin, gitRoot: string): Map { const gitDirs = new Map(); for (const entry of pin.closure.entries) { - if ( - entry.source.kind === "asset" && - entry.source.package.format === "source" - ) { - gitDirs.set( - entry.source.assetId, - sourceAssetGitDir(gitRoot, entry.source.assetId), - ); + if (entry.source.kind === "asset" && entry.source.package.format === "source") { + gitDirs.set(entry.source.assetId, sourceAssetGitDir(gitRoot, entry.source.assetId)); } } return gitDirs; @@ -150,10 +123,7 @@ export async function resolveDeploymentAssetMounts( * the tarball cache and the per-fetch cap. A missing or non-numeric value is a * boot-edge wiring bug, so it fails loud rather than defaulting. */ -export function requireSubstrateByteCap( - env: Record, - key: string, -): number { +export function requireSubstrateByteCap(env: Record, key: string): number { const raw = env[key]; if (raw === undefined) { throw new Error( @@ -183,28 +153,21 @@ export async function materializeDeploymentClosure(args: { pin: SourceRefPin; substrateEnv: Record; }): Promise { - const instanceDir = deploymentClosureInstanceDir( + const instanceDir = deploymentClosureInstanceDir(args.dataDir, args.deploymentId); + await rm(instanceDir, { recursive: true, force: true }); + + const { assetRoot, assetMounts, gitDirs } = await resolveDeploymentAssetMounts( args.dataDir, args.deploymentId, + args.pin, ); - await rm(instanceDir, { recursive: true, force: true }); - - const { assetRoot, assetMounts, gitDirs } = - await resolveDeploymentAssetMounts( - args.dataDir, - args.deploymentId, - args.pin, - ); return applyFrozenWorkflowClosure({ source: args.pin.source, closure: args.pin.closure, instanceDir, cacheRoot: pathJoin(args.dataDir, "workflow-definition-closure-cache"), - cacheMaxBytes: requireSubstrateByteCap( - args.substrateEnv, - "SIDECAR_CACHE_MAX_BYTES", - ), + cacheMaxBytes: requireSubstrateByteCap(args.substrateEnv, "SIDECAR_CACHE_MAX_BYTES"), registryMaxTarballBytes: requireSubstrateByteCap( args.substrateEnv, "SIDECAR_REGISTRY_MAX_TARBALL_BYTES", @@ -218,10 +181,7 @@ export async function materializeDeploymentClosure(args: { }); } -function requireSubstrateEntry( - env: Record, - key: string, -): string { +function requireSubstrateEntry(env: Record, key: string): string { const raw = env[key]; if (raw === undefined) { throw new Error( diff --git a/apps/sidecar/src/workflow-host-wiring/index.ts b/apps/sidecar/src/workflow-host-wiring/index.ts index 298ac2354e..92b6abd49b 100644 --- a/apps/sidecar/src/workflow-host-wiring/index.ts +++ b/apps/sidecar/src/workflow-host-wiring/index.ts @@ -36,10 +36,7 @@ import { type InferenceSource, type KeyPair, } from "@intx/types/runtime"; -import { - WorkflowProjectionDefinition, - type AgentDeployFrame, -} from "@intx/types/sidecar"; +import { WorkflowProjectionDefinition, type AgentDeployFrame } from "@intx/types/sidecar"; import { projectLiveToInert } from "@intx/workflow"; import type { @@ -70,28 +67,15 @@ import { snapshotAgentIdentity, } from "../hibernated-agent-identity-vault"; import { isErrnoNotFound } from "../conversation-state"; -import { - computeWireDefinitionHash, - validateWorkflowProjection, -} from "./wire-validation"; +import { computeWireDefinitionHash, validateWorkflowProjection } from "./wire-validation"; import { defaultSubprocessSpawner } from "./transport"; export { defaultSubprocessSpawner }; -import { - createSidecarWorkflowSupervisor, - type SidecarWorkflowSupervisor, -} from "./supervisor"; - -export { - deriveSidecarMailAuditRef, - type CreateSidecarWorkflowSupervisorOpts, -} from "./supervisor"; +import { createSidecarWorkflowSupervisor, type SidecarWorkflowSupervisor } from "./supervisor"; + +export { deriveSidecarMailAuditRef, type CreateSidecarWorkflowSupervisorOpts } from "./supervisor"; export { createSidecarWorkflowSupervisor, type SidecarWorkflowSupervisor }; -import { - createStepStrategy, - deriveDeploymentId, - writeStepGrants, -} from "./step-strategy"; +import { createStepStrategy, deriveDeploymentId, writeStepGrants } from "./step-strategy"; export { deriveDeploymentId }; import { materializeWorkflowSources } from "./asset-materialization"; @@ -229,10 +213,7 @@ function withRestoreTimeout( * whose caller has already timed out stops advancing instead of spending * that work on an attempt nobody is waiting on anymore. */ -function throwIfRestoreAborted( - signal: AbortSignal, - deploymentId: string, -): void { +function throwIfRestoreAborted(signal: AbortSignal, deploymentId: string): void { if (signal.aborted) { throw new Error( `restore of ${deploymentId} was cancelled after its boot-restore attempt timed out`, @@ -246,9 +227,7 @@ function throwIfRestoreAborted( * `CHILD_KILL_ESCALATION_MS` -- still awaiting `shutdown()` to completion * either way, never abandoning it. */ -async function shutdownSupervisorWithEscalation( - wired: SidecarWorkflowSupervisor, -): Promise { +async function shutdownSupervisorWithEscalation(wired: SidecarWorkflowSupervisor): Promise { const timer = setTimeout(() => { wired.hardKillChild(); }, CHILD_KILL_ESCALATION_MS); @@ -266,9 +245,7 @@ async function shutdownSupervisorWithEscalation( * with this key; the multi-step branch surfaces it to the link so the * hub records the verifying key for the deployment's signed events. */ -async function derivePrincipalPublicKeyHex( - signingKeySeed: Uint8Array, -): Promise { +async function derivePrincipalPublicKeyHex(signingKeySeed: Uint8Array): Promise { return hexEncode(await derivePublicKeyBytes(signingKeySeed)); } @@ -341,10 +318,7 @@ export interface SidecarDeployRouter extends DeployRouter { * internal `teardownDeployment` for the exact split between the two * flavors. */ - teardownDeployment( - agentAddress: string, - opts: { reclaimDirs: boolean }, - ): Promise; + teardownDeployment(agentAddress: string, opts: { reclaimDirs: boolean }): Promise; } export function createSidecarDeployRouter(deps: { @@ -390,10 +364,7 @@ export function createSidecarDeployRouter(deps: { * the child triggers sees the mapping. Tests that do not exercise * the pack push path may pass a no-op. */ - registerDeployment: (entry: { - deploymentId: string; - agentAddress: string; - }) => void; + registerDeployment: (entry: { deploymentId: string; agentAddress: string }) => void; /** * Symmetric removal hook for `registerDeployment`. Fires from the * link's `agent.undeploy` path so the boot edge's @@ -404,10 +375,7 @@ export function createSidecarDeployRouter(deps: { * silently resolving to the prior address. Tests that do not * exercise the pack push path may pass a no-op. */ - unregisterDeployment: (entry: { - deploymentId: string; - agentAddress: string; - }) => void; + unregisterDeployment: (entry: { deploymentId: string; agentAddress: string }) => void; /** * Control-plane suspension sink threaded verbatim to every deployment's * supervisor as `onSuspensionRegister`. Production wires this to the @@ -666,15 +634,11 @@ export function createSidecarDeployRouter(deps: { const stepStateDataDir = multistepSubstrateEnv.SIDECAR_DATA_DIR; const persistDeploymentRecord = deps.writeWorkflowDeploymentRecord ?? writeWorkflowDeploymentRecord; - const applyClosure = - deps.materializeDeploymentClosure ?? materializeDeploymentClosure; - const restoreAttemptTimeoutMs = - deps.restoreAttemptTimeoutMs ?? RESTORE_ATTEMPT_TIMEOUT_MS; - const multistepSpawner = - deps.multistepSubprocessSpawner ?? defaultSubprocessSpawner; + const applyClosure = deps.materializeDeploymentClosure ?? materializeDeploymentClosure; + const restoreAttemptTimeoutMs = deps.restoreAttemptTimeoutMs ?? RESTORE_ATTEMPT_TIMEOUT_MS; + const multistepSpawner = deps.multistepSubprocessSpawner ?? defaultSubprocessSpawner; const multistepDeriveStepAddress: DeriveStepAddress = - deps.multistepDeriveStepAddress ?? - (({ runId, stepId }) => `${runId}-${stepId}`); + deps.multistepDeriveStepAddress ?? (({ runId, stepId }) => `${runId}-${stepId}`); // Per-deployment supervisor tracking. The multi-step branch // constructs one `SidecarWorkflowSupervisor` per `agent.deploy` @@ -744,10 +708,7 @@ export function createSidecarDeployRouter(deps: { // `activeSupervisors` (see below) while holding the lock, the final // on-disk state is correct regardless of ordering. const deploymentRecordLocks = new Map>(); - function withDeploymentRecordLock( - deploymentId: string, - fn: () => Promise, - ): Promise { + function withDeploymentRecordLock(deploymentId: string, fn: () => Promise): Promise { const prior = deploymentRecordLocks.get(deploymentId) ?? Promise.resolve(); const settled = prior.then(fn, fn); deploymentRecordLocks.set( @@ -922,9 +883,7 @@ export function createSidecarDeployRouter(deps: { principalId: spec.principalId, sources, ...(spec.sessionId !== undefined ? { sessionId: spec.sessionId } : {}), - ...(spec.hubPublicKey !== undefined - ? { hubPublicKey: spec.hubPublicKey } - : {}), + ...(spec.hubPublicKey !== undefined ? { hubPublicKey: spec.hubPublicKey } : {}), approvedWireHash: spec.approvedWireHash, sourceRef: spec.sourceRef, }; @@ -942,9 +901,7 @@ export function createSidecarDeployRouter(deps: { * deploy-only durable state (the source closure, step grants) before * calling. */ - async function spawnWorkflowDeployment( - spec: WorkflowDeploySpec, - ): Promise { + async function spawnWorkflowDeployment(spec: WorkflowDeploySpec): Promise { // Fail loud if this address already has a live supervisor. Both single- // and multi-step now register on the transport, so both carry the // `transport.register` duplicate-throw backstop; this `has()` check is the @@ -1062,24 +1019,16 @@ export function createSidecarDeployRouter(deps: { deploymentId, agentAddress: spec.agentAddress, }), - ...(spec.credentials !== undefined - ? { credentialDelivery: spec.credentials } - : {}), - ...(deps.multistepBinaryPath !== undefined - ? { binaryPath: deps.multistepBinaryPath } - : {}), - ...(deps.onDispatchTiming !== undefined - ? { onDispatchTiming: deps.onDispatchTiming } - : {}), + ...(spec.credentials !== undefined ? { credentialDelivery: spec.credentials } : {}), + ...(deps.multistepBinaryPath !== undefined ? { binaryPath: deps.multistepBinaryPath } : {}), + ...(deps.onDispatchTiming !== undefined ? { onDispatchTiming: deps.onDispatchTiming } : {}), ...(deps.repackEveryMessages !== undefined ? { repackEveryMessages: deps.repackEveryMessages } : {}), ...(deps.consumedRetentionMs !== undefined ? { consumedRetentionMs: deps.consumedRetentionMs } : {}), - ...(deps.readyTimeoutMs !== undefined - ? { readyTimeoutMs: deps.readyTimeoutMs } - : {}), + ...(deps.readyTimeoutMs !== undefined ? { readyTimeoutMs: deps.readyTimeoutMs } : {}), }; const wired = createSidecarWorkflowSupervisor(wiredBaseConfig); @@ -1101,8 +1050,7 @@ export function createSidecarDeployRouter(deps: { // `getTransportFor(senderAddress).send` throws "not registered". // Registration happens before `spawn()` so the address is live the // instant the first reply routes outbound. - const { keyPair, isNew: keyIsNew } = - await deps.keyStore.loadOrGenerateKey(spec.agentAddress); + const { keyPair, isNew: keyIsNew } = await deps.keyStore.loadOrGenerateKey(spec.agentAddress); // A restored vault entry that did not yield an existing on-disk key // means the restore itself is broken (a corrupt or partial // snapshot) -- the exact "wake silently rotates identity" failure @@ -1120,10 +1068,7 @@ export function createSidecarDeployRouter(deps: { }, ); } - deps.transport.register( - spec.agentAddress, - deps.createAgentCrypto(keyPair), - ); + deps.transport.register(spec.agentAddress, deps.createAgentCrypto(keyPair)); agentTransportRegistered = true; // The public key the deploy ack surfaces to the hub is the deployment @@ -1177,12 +1122,7 @@ export function createSidecarDeployRouter(deps: { logger.warn`dropping workflow inference event for ${spec.agentAddress}: ${validated.summary}`; return; } - publishInferenceEvent( - spec.agentAddress, - validated, - spec.sessionId, - childRunId, - ); + publishInferenceEvent(spec.agentAddress, validated, spec.sessionId, childRunId); }, }; @@ -1272,70 +1212,65 @@ export function createSidecarDeployRouter(deps: { // stepId-agnostic. const rotationStepId = spec.definition.stepOrder[0]; if (rotationStepId === undefined) { - throw new Error( - "single-step deploy has no step id for sources rotation", - ); + throw new Error("single-step deploy has no step id for sources rotation"); } - deps.multistepSourcesRouter?.register( - spec.agentAddress, - async (args) => { - const rotated = { [rotationStepId]: args.sources }; - // Swap `currentSources` synchronously BEFORE the durable persist. - // `currentSources` is the process-local respawn hint the - // supervisor reads synchronously through `dynamicSpawnEnv`, so a - // recycle that interleaves the persist `await` must respawn the - // child on the SAME sources being persisted, not the stale prior - // table. The obvious inverse -- persist first, then swap -- is - // rejected: it leaves the child on the OLD sources during the - // persist window while the record has already moved to NEW, so a - // recycle there respawns stale and a restart would "correct" it, - // i.e. the running child contradicts durable intent. Swapping - // first makes the only residual disagreement child-ahead-of- - // durable on a failed persist, which the next recycle heals down - // to the rolled-back durable truth -- the benign direction. The - // wire boundary guarantees `args.sources[0]` is the default, - // which the recycle env form pins as the active source. - const prevSources = currentSources; - currentSources = rotated; - // The durable write still precedes the LIVE swap - // (`deliverSources`), preserving persist-before-externally-visible - // for state that outlives the process; only the process-local - // respawn hint moves ahead. On a failed persist, roll the hint - // back so `currentSources` and the record stay in agreement in the - // common (no interleaved recycle) failure case -- the invariant - // restart consistency depends on. Persistence lets the rotation - // survive a full sidecar restart, not just a recycle: the boot - // scan reseeds spec.sources from record.sources. Overwrites the - // deploy-time record in place. Skipped when no data dir was wired - // (a test router that never persists), matching the restore guard. - if (stepStateDataDir !== undefined) { - try { - await persistDeploymentRecord( - stepStateDataDir, - deploymentId, - buildDeploymentRecord(spec, rotated), - ); - } catch (cause) { - // Restoring unconditionally is safe because rotations for one - // deployment are serialized by the sidecar's per-connection - // inbound-frame queue: each hub frame, sources.update - // included, runs its handler to completion on that queue - // before the next frame's handler starts, so no second - // rotation is in flight whose committed table this rollback - // could clobber. This does NOT rely on the hub pacing its - // sends -- the hub dispatches sources.update fire-and-forget; - // the sidecar frame queue is the sole serializer. Parallelizing - // inbound-frame dispatch would break this rollback. - currentSources = prevSources; - throw cause; - } + deps.multistepSourcesRouter?.register(spec.agentAddress, async (args) => { + const rotated = { [rotationStepId]: args.sources }; + // Swap `currentSources` synchronously BEFORE the durable persist. + // `currentSources` is the process-local respawn hint the + // supervisor reads synchronously through `dynamicSpawnEnv`, so a + // recycle that interleaves the persist `await` must respawn the + // child on the SAME sources being persisted, not the stale prior + // table. The obvious inverse -- persist first, then swap -- is + // rejected: it leaves the child on the OLD sources during the + // persist window while the record has already moved to NEW, so a + // recycle there respawns stale and a restart would "correct" it, + // i.e. the running child contradicts durable intent. Swapping + // first makes the only residual disagreement child-ahead-of- + // durable on a failed persist, which the next recycle heals down + // to the rolled-back durable truth -- the benign direction. The + // wire boundary guarantees `args.sources[0]` is the default, + // which the recycle env form pins as the active source. + const prevSources = currentSources; + currentSources = rotated; + // The durable write still precedes the LIVE swap + // (`deliverSources`), preserving persist-before-externally-visible + // for state that outlives the process; only the process-local + // respawn hint moves ahead. On a failed persist, roll the hint + // back so `currentSources` and the record stay in agreement in the + // common (no interleaved recycle) failure case -- the invariant + // restart consistency depends on. Persistence lets the rotation + // survive a full sidecar restart, not just a recycle: the boot + // scan reseeds spec.sources from record.sources. Overwrites the + // deploy-time record in place. Skipped when no data dir was wired + // (a test router that never persists), matching the restore guard. + if (stepStateDataDir !== undefined) { + try { + await persistDeploymentRecord( + stepStateDataDir, + deploymentId, + buildDeploymentRecord(spec, rotated), + ); + } catch (cause) { + // Restoring unconditionally is safe because rotations for one + // deployment are serialized by the sidecar's per-connection + // inbound-frame queue: each hub frame, sources.update + // included, runs its handler to completion on that queue + // before the next frame's handler starts, so no second + // rotation is in flight whose committed table this rollback + // could clobber. This does NOT rely on the hub pacing its + // sends -- the hub dispatches sources.update fire-and-forget; + // the sidecar frame queue is the sole serializer. Parallelizing + // inbound-frame dispatch would break this rollback. + currentSources = prevSources; + throw cause; } - await wired.supervisor.deliverSources({ - sources: args.sources, - defaultSource: args.defaultSource, - }); - }, - ); + } + await wired.supervisor.deliverSources({ + sources: args.sources, + defaultSource: args.defaultSource, + }); + }); } // Register the credential-delivery handler for EVERY deployment (not @@ -1345,14 +1280,11 @@ export function createSidecarDeployRouter(deps: { // `credentials-updated` control frame to the child where the material // cell is swapped. No durable persist -- credential material never // touches disk. - deps.multistepCredentialsRouter?.register( - spec.agentAddress, - async (args) => { - await wired.supervisor.deliverCredentials({ - delivery: args.delivery, - }); - }, - ); + deps.multistepCredentialsRouter?.register(spec.agentAddress, async (args) => { + await wired.supervisor.deliverCredentials({ + delivery: args.delivery, + }); + }); routersRegistered = true; succeeded = true; @@ -1378,8 +1310,7 @@ export function createSidecarDeployRouter(deps: { } if (wiredForUnwind !== undefined) { await wiredForUnwind.supervisor.shutdown().catch((cause) => { - const message = - cause instanceof Error ? cause.message : String(cause); + const message = cause instanceof Error ? cause.message : String(cause); logger.warn`multi-step deploy unwind: supervisor.shutdown failed: ${message}`; }); } @@ -1435,9 +1366,7 @@ export function createSidecarDeployRouter(deps: { * per-step address is workflow-derived and records no `agent_instance` * key, so the hub discards this value. */ - async function provisionStep( - frame: AgentDeployFrame, - ): Promise { + async function provisionStep(frame: AgentDeployFrame): Promise { await deps.sessions.initRepo(frame.agentAddress); deps.keyStore.recordHubKey(frame.agentAddress, frame.hubPublicKey); return { @@ -1459,9 +1388,7 @@ export function createSidecarDeployRouter(deps: { // every later wake would re-trip this same guard, a permanent // wake/reject loop against a run the sidecar never stopped. if (activeSupervisors.has(frame.agentAddress)) { - const { keyPair } = await deps.keyStore.loadOrGenerateKey( - frame.agentAddress, - ); + const { keyPair } = await deps.keyStore.loadOrGenerateKey(frame.agentAddress); return { publicKey: hexEncode(keyPair.publicKey) }; } // A deploy still in flight for the address stays a hard reject: two @@ -1599,8 +1526,7 @@ export function createSidecarDeployRouter(deps: { sessionId: frame.config.sessionId, tenantId: frame.config.tenantId, principalId: frame.config.principalId, - hubPublicKey: - definition.stepOrder.length === 1 ? frame.hubPublicKey : undefined, + hubPublicKey: definition.stepOrder.length === 1 ? frame.hubPublicKey : undefined, approvedWireHash: projection.approvedWireHash, closurePackageDir: applied.packageDir, sourceRef: projection.sourceRef, @@ -1621,11 +1547,7 @@ export function createSidecarDeployRouter(deps: { // disk (not through env) because the body child is in-process and loses // its env across a restart. for (const referenced of projection.referencedDefinitions ?? []) { - await materializeWorkflowSources( - dataDir, - referenced.definition.id, - referenced.sources, - ); + await materializeWorkflowSources(dataDir, referenced.definition.id, referenced.sources); } // Grants bridge: the spawned child does not see the frame; it reads @@ -1656,10 +1578,7 @@ export function createSidecarDeployRouter(deps: { // `writeTree` without a `clearPrefix` is purely additive, so it // never disturbs the run's events. Guarded on `isRunAddress`: only // a run address names a self-anchored run id. - if ( - definition.stepOrder.length === 1 && - isRunAddress(frame.agentAddress) - ) { + if (definition.stepOrder.length === 1 && isRunAddress(frame.agentAddress)) { await writeStepGrants({ repoStore: deps.repoStore, deploymentId, @@ -1682,10 +1601,7 @@ export function createSidecarDeployRouter(deps: { try { await deleteWorkflowDeploymentRecord(dataDir, deploymentId); } catch (cleanupError) { - const message = - cleanupError instanceof Error - ? cleanupError.message - : String(cleanupError); + const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError); logger.error`deploy cleanup: deleteWorkflowDeploymentRecord failed for ${deploymentId}: ${message}`; } releaseSlug(deploymentId, frame.agentAddress); @@ -1715,9 +1631,7 @@ export function createSidecarDeployRouter(deps: { function isMissingClosureStagingFailure(cause: unknown): boolean { if (!(cause instanceof Error)) return false; if (isErrnoNotFound(cause)) return true; - return ( - "cause" in cause && isErrnoNotFound((cause as { cause?: unknown }).cause) - ); + return "cause" in cause && isErrnoNotFound((cause as { cause?: unknown }).cause); } /** @@ -1884,8 +1798,7 @@ export function createSidecarDeployRouter(deps: { try { deps.assertSourceBuildable(source); } catch (cause) { - const reason = - cause instanceof Error ? cause.message : String(cause); + const reason = cause instanceof Error ? cause.message : String(cause); throw new WorkflowRestoreFailure("transient", reason); } } @@ -1930,8 +1843,7 @@ export function createSidecarDeployRouter(deps: { // no-op for an already-held (deploymentId, address) pair, so the // pre-claim check distinguishes the two. A PARKED deployment keeps // its slug claimed, so a wake respawn lands in this already-held arm. - const slugNewlyClaimed = - slugClaims.get(deploymentId) !== record.agentAddress; + const slugNewlyClaimed = slugClaims.get(deploymentId) !== record.agentAddress; claimSlug(deploymentId, record.agentAddress); try { // CL-7215: checked INSIDE this try, after the slug is claimed, so a @@ -1970,33 +1882,22 @@ export function createSidecarDeployRouter(deps: { // successful (if late) restore. let reclaimedDuringSpawn = false; try { - const corrected = await withDeploymentRecordLock( - deploymentId, - async () => { - const onDisk = await readWorkflowDeploymentRecord( - dataDir, - deploymentId, - ); - if (onDisk === undefined) { - reclaimedDuringSpawn = true; - return false; - } - if (onDisk.restoreFailure === undefined) return false; - await clearWorkflowDeploymentRestoreFailure( - dataDir, - deploymentId, - onDisk, - ); - return true; - }, - ); + const corrected = await withDeploymentRecordLock(deploymentId, async () => { + const onDisk = await readWorkflowDeploymentRecord(dataDir, deploymentId); + if (onDisk === undefined) { + reclaimedDuringSpawn = true; + return false; + } + if (onDisk.restoreFailure === undefined) return false; + await clearWorkflowDeploymentRestoreFailure(dataDir, deploymentId, onDisk); + return true; + }); if (corrected) { logger.warn`Workflow deployment ${record.agentAddress} finished restoring after its boot-restore attempt had already timed out; corrected its record so it no longer claims the deployment failed to restore`; } } catch (correctionError) { reportError(correctionError, { - operation: - "workflow-host-wiring.restoreDeploymentFromRecord.lateRestoreCorrection", + operation: "workflow-host-wiring.restoreDeploymentFromRecord.lateRestoreCorrection", agentId: record.agentAddress, }); } @@ -2100,10 +2001,10 @@ export function createSidecarDeployRouter(deps: { // and is deliberately NOT touched here either way -- a re-deploy on // the same address must restore the prior conversation from it. if (opts.reclaimDirs && stepStateDataDir !== undefined) { - await rm( - pathJoin(stepStateDataDir, "workflow-step-state", deploymentId), - { recursive: true, force: true }, - ); + await rm(pathJoin(stepStateDataDir, "workflow-step-state", deploymentId), { + recursive: true, + force: true, + }); } } // Drop the deployment record so a boot-time restore does not re-spawn a @@ -2211,87 +2112,68 @@ export function createSidecarDeployRouter(deps: { // `try`/`catch` gave. let skippedQuarantinedCount = 0; let deferredToWakeCount = 0; - await runWithConcurrency( - live, - RESTORE_CONCURRENCY, - async ({ deploymentId, record }) => { - // Skip WITHOUT attempting: a permanently unrestorable record that - // has already crossed RESTORE_QUARANTINE_THRESHOLD gets neither a - // spawn attempt nor a per-record warning this boot -- both are - // pointless for a deterministic failure that has already been - // reported that many times. It still counts toward the one - // summary line below, and the record itself is untouched (an - // operator reclaims it by undeploying the address). - if (isWorkflowDeploymentRestoreQuarantined(record)) { - skippedQuarantinedCount += 1; + await runWithConcurrency(live, RESTORE_CONCURRENCY, async ({ deploymentId, record }) => { + // Skip WITHOUT attempting: a permanently unrestorable record that + // has already crossed RESTORE_QUARANTINE_THRESHOLD gets neither a + // spawn attempt nor a per-record warning this boot -- both are + // pointless for a deterministic failure that has already been + // reported that many times. It still counts toward the one + // summary line below, and the record itself is untouched (an + // operator reclaims it by undeploying the address). + if (isWorkflowDeploymentRestoreQuarantined(record)) { + skippedQuarantinedCount += 1; + return; + } + try { + const outcome = await withRestoreTimeout( + (signal) => restoreDeploymentFromRecord(dataDir, deploymentId, record, signal), + deploymentId, + restoreAttemptTimeoutMs, + ); + if (outcome === "deferred-to-wake") { + deferredToWakeCount += 1; return; } - try { - const outcome = await withRestoreTimeout( - (signal) => - restoreDeploymentFromRecord( - dataDir, - deploymentId, - record, - signal, - ), - deploymentId, - restoreAttemptTimeoutMs, - ); - if (outcome === "deferred-to-wake") { - deferredToWakeCount += 1; + if (record.restoreFailure !== undefined) { + await clearWorkflowDeploymentRestoreFailure(dataDir, deploymentId, record); + } + } catch (cause) { + const reason = cause instanceof Error ? cause.message : String(cause); + const kind = cause instanceof WorkflowRestoreFailure ? cause.kind : "transient"; + // CL-7215: serialized against `restoreDeploymentFromRecord`'s + // own late-settle correction AND `teardownDeployment`'s record + // writes via the same lock, and gated on `activeSupervisors` -- + // in-memory, always set synchronously the instant + // `spawnWorkflowDeployment` actually succeeds, unlike a disk + // snapshot -- so a restore that timed out here but has ALREADY + // gone live by the time this runs never gets a boot failure + // recorded against it in the first place. See the lock's own + // doc comment for why every writer must go through it. + await withDeploymentRecordLock(deploymentId, async () => { + if (activeSupervisors.has(record.agentAddress)) { + logger.warn`Workflow deployment ${deploymentId} timed out during boot restore but finished spawning before its failure could be recorded; leaving its record as a live, successful restore`; return; } - if (record.restoreFailure !== undefined) { - await clearWorkflowDeploymentRestoreFailure( - dataDir, - deploymentId, - record, - ); - } - } catch (cause) { - const reason = - cause instanceof Error ? cause.message : String(cause); - const kind = - cause instanceof WorkflowRestoreFailure - ? cause.kind - : "transient"; - // CL-7215: serialized against `restoreDeploymentFromRecord`'s - // own late-settle correction AND `teardownDeployment`'s record - // writes via the same lock, and gated on `activeSupervisors` -- - // in-memory, always set synchronously the instant - // `spawnWorkflowDeployment` actually succeeds, unlike a disk - // snapshot -- so a restore that timed out here but has ALREADY - // gone live by the time this runs never gets a boot failure - // recorded against it in the first place. See the lock's own - // doc comment for why every writer must go through it. - await withDeploymentRecordLock(deploymentId, async () => { - if (activeSupervisors.has(record.agentAddress)) { - logger.warn`Workflow deployment ${deploymentId} timed out during boot restore but finished spawning before its failure could be recorded; leaving its record as a live, successful restore`; - return; - } - const updated = await recordWorkflowDeploymentRestoreFailure( - dataDir, - deploymentId, - { kind, reason }, - ); - if (updated === undefined) { - // The record is gone -- a concurrent reclaiming teardown - // won the lock first and deleted it. Nothing to mark: the - // deployment was torn down on purpose, not left claiming a - // false restore failure. - return; - } - if (isWorkflowDeploymentRestoreQuarantined(updated)) { - const attempts = updated.restoreFailure?.attempts ?? 0; - logger.warn`Workflow deployment ${deploymentId} failed to restore ${attempts} consecutive times and is now quarantined -- it will not be retried again until the address is undeployed. Last failure: ${reason}`; - } else { - logger.warn`Failed to restore workflow deployment ${deploymentId}: ${reason}`; - } + const updated = await recordWorkflowDeploymentRestoreFailure(dataDir, deploymentId, { + kind, + reason, }); - } - }, - ); + if (updated === undefined) { + // The record is gone -- a concurrent reclaiming teardown + // won the lock first and deleted it. Nothing to mark: the + // deployment was torn down on purpose, not left claiming a + // false restore failure. + return; + } + if (isWorkflowDeploymentRestoreQuarantined(updated)) { + const attempts = updated.restoreFailure?.attempts ?? 0; + logger.warn`Workflow deployment ${deploymentId} failed to restore ${attempts} consecutive times and is now quarantined -- it will not be retried again until the address is undeployed. Last failure: ${reason}`; + } else { + logger.warn`Failed to restore workflow deployment ${deploymentId}: ${reason}`; + } + }); + } + }); if (skippedQuarantinedCount > 0) { logger.warn`Skipped ${skippedQuarantinedCount} quarantined workflow deployment record(s) (permanent restore failures, already reported); undeploy an address to clear its record`; } @@ -2329,8 +2211,7 @@ export function createSidecarDeployRouter(deps: { try { await shutdownSupervisorWithEscalation(wired); } catch (cause) { - const reason = - cause instanceof Error ? cause.message : String(cause); + const reason = cause instanceof Error ? cause.message : String(cause); logger.warn`Drain: supervisor shutdown for ${address} failed: ${reason}`; } }), diff --git a/apps/sidecar/src/workflow-host-wiring/step-strategy.ts b/apps/sidecar/src/workflow-host-wiring/step-strategy.ts index f9870e4a84..bad44fabec 100644 --- a/apps/sidecar/src/workflow-host-wiring/step-strategy.ts +++ b/apps/sidecar/src/workflow-host-wiring/step-strategy.ts @@ -3,11 +3,7 @@ // per-step mail address and agent-state repo id, and the pre-spawn // write of every step's grants into its agent-state repo. -import { - parseAgentId, - type Principal, - type RepoStore, -} from "@intx/hub-sessions"; +import { parseAgentId, type Principal, type RepoStore } from "@intx/hub-sessions"; import { STEP_GRANTS_PATH, STEP_GRANTS_REF, @@ -169,14 +165,9 @@ export async function writeStepGrants(args: { runId: args.deploymentId, stepId, }); - await args.repoStore.writeTree( - GRANTS_WRITE_PRINCIPAL, - repoId, - STEP_GRANTS_REF, - { - files: { [STEP_GRANTS_PATH]: serialized }, - message: `Write step grants for ${stepId}`, - }, - ); + await args.repoStore.writeTree(GRANTS_WRITE_PRINCIPAL, repoId, STEP_GRANTS_REF, { + files: { [STEP_GRANTS_PATH]: serialized }, + message: `Write step grants for ${stepId}`, + }); } } diff --git a/apps/sidecar/src/workflow-host-wiring/supervisor.test.ts b/apps/sidecar/src/workflow-host-wiring/supervisor.test.ts index 149dbadd8f..77ef5e5865 100644 --- a/apps/sidecar/src/workflow-host-wiring/supervisor.test.ts +++ b/apps/sidecar/src/workflow-host-wiring/supervisor.test.ts @@ -26,8 +26,7 @@ mock.module("@intx/workflow-host", () => ({ wrapHubTransportAsMailBus: () => ({ routeInbound: async () => {}, }), - hashGrants: async (grants: readonly unknown[]) => - `stub-hash:${JSON.stringify(grants)}`, + hashGrants: async (grants: readonly unknown[]) => `stub-hash:${JSON.stringify(grants)}`, })); let capturedConfig: unknown; @@ -35,9 +34,7 @@ let capturedConfig: unknown; const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeRepoStore(): Promise<{ store: RepoStore; dir: string }> { @@ -49,8 +46,7 @@ async function makeRepoStore(): Promise<{ store: RepoStore; dir: string }> { return { store, dir }; } -const { createSidecarWorkflowSupervisor, DEFAULT_MAX_GRANTS_AGE_MS } = - await import("./supervisor"); +const { createSidecarWorkflowSupervisor, DEFAULT_MAX_GRANTS_AGE_MS } = await import("./supervisor"); test("createSidecarWorkflowSupervisor forwards onSuspensionRegister to the workflow-host supervisor", () => { const registerSuspension = mock(() => {}); @@ -158,9 +154,7 @@ test("createSidecarWorkflowSupervisor omits credentialDelivery when the deploy c dynamicSpawnEnv: () => ({}), }); - expect( - (capturedConfig as { credentialDelivery?: unknown }).credentialDelivery, - ).toBeUndefined(); + expect((capturedConfig as { credentialDelivery?: unknown }).credentialDelivery).toBeUndefined(); }); // CL-6242: the vendor's recycle policy (`RecyclePolicyBounds` / @@ -188,14 +182,10 @@ test("createSidecarWorkflowSupervisor arms the grants-age recycle policy for a w expect(capturedConfig).toMatchObject({ recyclePolicy: { maxGrantsAgeMs: DEFAULT_MAX_GRANTS_AGE_MS }, }); - expect( - typeof (capturedConfig as { readGrantsAgeMs?: unknown }).readGrantsAgeMs, - ).toBe("function"); + expect(typeof (capturedConfig as { readGrantsAgeMs?: unknown }).readGrantsAgeMs).toBe("function"); // No RSS reader: see the module-level comment in `supervisor.ts` on why // `maxRssBytes`/`readRssBytes` are never wired. - expect( - (capturedConfig as { readRssBytes?: unknown }).readRssBytes, - ).toBeUndefined(); + expect((capturedConfig as { readRssBytes?: unknown }).readRssBytes).toBeUndefined(); }); test("createSidecarWorkflowSupervisor omits the recycle policy for a non-warm-keep deployment", () => { @@ -215,12 +205,8 @@ test("createSidecarWorkflowSupervisor omits the recycle policy for a non-warm-ke warmKeep: false, }); - expect( - (capturedConfig as { recyclePolicy?: unknown }).recyclePolicy, - ).toBeUndefined(); - expect( - (capturedConfig as { readGrantsAgeMs?: unknown }).readGrantsAgeMs, - ).toBeUndefined(); + expect((capturedConfig as { recyclePolicy?: unknown }).recyclePolicy).toBeUndefined(); + expect((capturedConfig as { readGrantsAgeMs?: unknown }).readGrantsAgeMs).toBeUndefined(); }); test("readGrantsAgeMs reports undefined until deliverCredentials fires, then a non-negative age", async () => { @@ -240,9 +226,8 @@ test("readGrantsAgeMs reports undefined until deliverCredentials fires, then a n warmKeep: true, }); - const readGrantsAgeMs = ( - capturedConfig as { readGrantsAgeMs: () => number | undefined } - ).readGrantsAgeMs; + const readGrantsAgeMs = (capturedConfig as { readGrantsAgeMs: () => number | undefined }) + .readGrantsAgeMs; expect(readGrantsAgeMs()).toBeUndefined(); await wired.supervisor.deliverCredentials({ @@ -282,10 +267,7 @@ test("createSidecarWorkflowSupervisor wires onRunStart to assemble the run's cre const onRunStart = ( capturedConfig as { - onRunStart: (args: { - runId: string; - anchorRunId: string; - }) => Promise; + onRunStart: (args: { runId: string; anchorRunId: string }) => Promise; } ).onRunStart; const snapshot = await onRunStart({ runId: "run-1", anchorRunId: "dep-8" }); @@ -326,16 +308,13 @@ test("onRunStart fails a run with no per-run grants file closed", async () => { const onRunStart = ( capturedConfig as { - onRunStart: (args: { - runId: string; - anchorRunId: string; - }) => Promise; + onRunStart: (args: { runId: string; anchorRunId: string }) => Promise; } ).onRunStart; - await expect( - onRunStart({ runId: "run-missing", anchorRunId: "dep-9" }), - ).rejects.toThrow(/has no grants file; failing closed/); + await expect(onRunStart({ runId: "run-missing", anchorRunId: "dep-9" })).rejects.toThrow( + /has no grants file; failing closed/, + ); }); test("onRunStart refreshes readGrantsAgeMs, mirroring deliverCredentials's observation point", async () => { @@ -360,15 +339,11 @@ test("onRunStart refreshes readGrantsAgeMs, mirroring deliverCredentials's obser warmKeep: true, }); - const readGrantsAgeMs = ( - capturedConfig as { readGrantsAgeMs: () => number | undefined } - ).readGrantsAgeMs; + const readGrantsAgeMs = (capturedConfig as { readGrantsAgeMs: () => number | undefined }) + .readGrantsAgeMs; const onRunStart = ( capturedConfig as { - onRunStart: (args: { - runId: string; - anchorRunId: string; - }) => Promise; + onRunStart: (args: { runId: string; anchorRunId: string }) => Promise; } ).onRunStart; expect(readGrantsAgeMs()).toBeUndefined(); diff --git a/apps/sidecar/src/workflow-host-wiring/supervisor.ts b/apps/sidecar/src/workflow-host-wiring/supervisor.ts index 75971daec8..8f2cf5ab22 100644 --- a/apps/sidecar/src/workflow-host-wiring/supervisor.ts +++ b/apps/sidecar/src/workflow-host-wiring/supervisor.ts @@ -5,11 +5,7 @@ import { signEd25519 } from "@intx/crypto"; import type { HubTransport } from "@intx/mail-memory"; -import type { - RepoId, - RepoStore, - WorkflowRunSupervisorPrincipal, -} from "@intx/hub-sessions"; +import type { RepoId, RepoStore, WorkflowRunSupervisorPrincipal } from "@intx/hub-sessions"; import { createWorkflowSupervisor, hashGrants, @@ -30,10 +26,7 @@ import { import { getLogger } from "@intx/log"; -import { - defaultSubprocessSpawner, - SIDECAR_WORKFLOW_CHILD_BINARY, -} from "./transport"; +import { defaultSubprocessSpawner, SIDECAR_WORKFLOW_CHILD_BINARY } from "./transport"; import { readRunGrants } from "../run-grants"; const logger = getLogger(["sidecar", "workflow-host-wiring", "supervisor"]); @@ -242,10 +235,7 @@ export type CreateSidecarWorkflowSupervisorOpts = { * (crash-loop latch, channel crash, recycle failure). Production wiring * routes it to the deploy router's address reclaim. */ - onSelfTerminate?: (info: { - phase: "stopped" | "crash-looping"; - reason: string; - }) => void; + onSelfTerminate?: (info: { phase: "stopped" | "crash-looping"; reason: string }) => void; /** * Decrypted credential material from the deploy frame's * `workflow.credentials`, forwarded verbatim to the supervisor's @@ -315,9 +305,7 @@ export function deriveSidecarMailAuditRef(deploymentId: string): ( export function createSidecarWorkflowSupervisor( opts: CreateSidecarWorkflowSupervisorOpts, ): SidecarWorkflowSupervisor { - const mailBus: HubTransportMailBusAdapter = wrapHubTransportAsMailBus( - opts.transport, - ); + const mailBus: HubTransportMailBusAdapter = wrapHubTransportAsMailBus(opts.transport); // Tracks the most recently spawned child's handle so `hardKillChild` can // reach it directly. The `WorkflowSupervisor` interface exposes no raw // handle access, so wrapping the spawner -- the same injection point @@ -400,34 +388,24 @@ export function createSidecarWorkflowSupervisor( ...(opts.onSuspensionRegister !== undefined ? { onSuspensionRegister: opts.onSuspensionRegister } : {}), - ...(opts.onSelfTerminate !== undefined - ? { onSelfTerminate: opts.onSelfTerminate } - : {}), + ...(opts.onSelfTerminate !== undefined ? { onSelfTerminate: opts.onSelfTerminate } : {}), ...(opts.credentialDelivery !== undefined ? { credentialDelivery: opts.credentialDelivery } : {}), - ...(opts.deriveStepRepoId !== undefined - ? { deriveStepRepoId: opts.deriveStepRepoId } - : {}), - ...(opts.onDispatchTiming !== undefined - ? { onDispatchTiming: opts.onDispatchTiming } - : {}), + ...(opts.deriveStepRepoId !== undefined ? { deriveStepRepoId: opts.deriveStepRepoId } : {}), + ...(opts.onDispatchTiming !== undefined ? { onDispatchTiming: opts.onDispatchTiming } : {}), ...(opts.repackEveryMessages !== undefined ? { repackEveryMessages: opts.repackEveryMessages } : {}), ...(opts.consumedRetentionMs !== undefined ? { consumedRetentionMs: opts.consumedRetentionMs } : {}), - ...(opts.readyTimeoutMs !== undefined - ? { readyTimeoutMs: opts.readyTimeoutMs } - : {}), + ...(opts.readyTimeoutMs !== undefined ? { readyTimeoutMs: opts.readyTimeoutMs } : {}), ...(recyclePolicy !== undefined ? { recyclePolicy, readGrantsAgeMs: () => - lastGrantsRefreshAt === undefined - ? undefined - : Date.now() - lastGrantsRefreshAt, + lastGrantsRefreshAt === undefined ? undefined : Date.now() - lastGrantsRefreshAt, } : {}), }; diff --git a/apps/sidecar/src/workflow-host-wiring/transport.ts b/apps/sidecar/src/workflow-host-wiring/transport.ts index 0d6a35faf6..653a75a785 100644 --- a/apps/sidecar/src/workflow-host-wiring/transport.ts +++ b/apps/sidecar/src/workflow-host-wiring/transport.ts @@ -75,9 +75,7 @@ function ndjsonWriterFromFileSink(sink: Bun.FileSink): NdjsonWriter { * receiver's iterator finalises only on EOF, which mirrors the * `defaultControlReader` shape the child wires for `process.stdin`. */ -function ndjsonReaderFromReadableStream( - stream: ReadableStream, -): NdjsonReader { +function ndjsonReaderFromReadableStream(stream: ReadableStream): NdjsonReader { return { read(): AsyncIterableIterator { return (async function* () { diff --git a/apps/sidecar/src/workflow-host-wiring/wire-validation.ts b/apps/sidecar/src/workflow-host-wiring/wire-validation.ts index e312862987..1ea25d7ddf 100644 --- a/apps/sidecar/src/workflow-host-wiring/wire-validation.ts +++ b/apps/sidecar/src/workflow-host-wiring/wire-validation.ts @@ -48,9 +48,7 @@ export function validateWorkflowProjection(projection: { }): void { const def = projection.definition; if (typeof def.id !== "string" || def.id.length === 0) { - throw new Error( - "sidecar deploy router: workflow.definition.id must be a non-empty string", - ); + throw new Error("sidecar deploy router: workflow.definition.id must be a non-empty string"); } if (!Array.isArray(def.stepOrder) || def.stepOrder.length === 0) { throw new Error( @@ -58,14 +56,10 @@ export function validateWorkflowProjection(projection: { ); } if (typeof def.steps !== "object" || def.steps === null) { - throw new Error( - "sidecar deploy router: workflow.definition.steps must be an object", - ); + throw new Error("sidecar deploy router: workflow.definition.steps must be an object"); } if (typeof projection.sources !== "object" || projection.sources === null) { - throw new Error( - "sidecar deploy router: workflow.sources must be an object", - ); + throw new Error("sidecar deploy router: workflow.sources must be an object"); } const steps = def.steps; const sources = projection.sources; @@ -118,9 +112,7 @@ function canonicalJsonStringify(value: unknown): string { if (Array.isArray(value)) { return `[${value.map(canonicalJsonStringify).join(",")}]`; } - const entries = Object.entries(value).sort(([a], [b]) => - a < b ? -1 : a > b ? 1 : 0, - ); + const entries = Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); return `{${entries .map(([k, v]) => `${JSON.stringify(k)}:${canonicalJsonStringify(v)}`) .join(",")}}`; @@ -137,13 +129,8 @@ function canonicalJsonStringify(value: unknown): string { * round-trip the hub for a hash the orchestrator's hand-off task will * also derive deterministically from the same canonical form. */ -export async function computeWireDefinitionHash( - definition: unknown, -): Promise { +export async function computeWireDefinitionHash(definition: unknown): Promise { const canonical = canonicalJsonStringify(definition); - const digest = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(canonical), - ); + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical)); return hexEncode(new Uint8Array(digest)); } diff --git a/apps/sidecar/src/workflow-probe-handler.ts b/apps/sidecar/src/workflow-probe-handler.ts index cc02450b6a..2118a9abef 100644 --- a/apps/sidecar/src/workflow-probe-handler.ts +++ b/apps/sidecar/src/workflow-probe-handler.ts @@ -35,10 +35,7 @@ import type { WorkflowProbeRequestFrame } from "@intx/types/sidecar"; import { WorkflowProjectionDefinition } from "@intx/types/sidecar"; import { computeWireDefinitionHash } from "@intx/types/wire-definition-hash"; import { collectDeclaredPluginNames, projectLiveToInert } from "@intx/workflow"; -import { - walkCapabilities, - type CapabilityWalkResult, -} from "@intx/workflow-deploy"; +import { walkCapabilities, type CapabilityWalkResult } from "@intx/workflow-deploy"; import { DEFAULT_KILL_TIMEOUT_MS, MacedEnvelope, @@ -214,7 +211,6 @@ export const defaultProbeChildSpawner: ProbeChildSpawner = ({ proc.kill(signal); return; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the probe reaper passes "SIGTERM"/"SIGKILL"; Bun's runtime accepts the same "SIG*" strings, narrowed back at the boundary. proc.kill(signal as NodeJS.Signals); }, exited: proc.exited, @@ -257,12 +253,9 @@ export function createWorkflowProbeExecutor(opts: WorkflowProbeExecutorOpts): { const spawnProbeChild = opts.spawnProbeChild ?? defaultProbeChildSpawner; const binaryPath = opts.binaryPath ?? DEFAULT_PROBE_CHILD_BINARY; const childTimeoutMs = opts.childTimeoutMs ?? DEFAULT_PROBE_CHILD_TIMEOUT_MS; - const killTimeoutMs = - opts.killTimeoutMs ?? DEFAULT_PROBE_CHILD_KILL_TIMEOUT_MS; + const killTimeoutMs = opts.killTimeoutMs ?? DEFAULT_PROBE_CHILD_KILL_TIMEOUT_MS; - async function probe( - frame: WorkflowProbeRequestFrame, - ): Promise { + async function probe(frame: WorkflowProbeRequestFrame): Promise { const materialized = await opts.materialize(frame); try { return await runOneShotProbeChild({ @@ -295,9 +288,7 @@ interface RunOneShotProbeChildArgs { * exits without a frame (malformed code / crash), or the self-owned * deadline fires first. */ -async function runOneShotProbeChild( - args: RunOneShotProbeChildArgs, -): Promise { +async function runOneShotProbeChild(args: RunOneShotProbeChildArgs): Promise { const channelId = generateChannelId(); const hmacKey = generateHmacKey(); const env = buildProbeChildEnv({ @@ -317,12 +308,12 @@ async function runOneShotProbeChild( // Attach a catch so a post-reap stdout read error (the kill closes the // pipe mid-read) resolves to null instead of surfacing as an unhandled // rejection on the losing race branch. - const linePromise: Promise = readResultLine( - handle.stdout, - ).catch((err: unknown) => { - logger.debug`probe child ${String(handle.pid)} stdout read errored: ${errorMessage(err)}`; - return null; - }); + const linePromise: Promise = readResultLine(handle.stdout).catch( + (err: unknown) => { + logger.debug`probe child ${String(handle.pid)} stdout read errored: ${errorMessage(err)}`; + return null; + }, + ); const deadline = createDeadline(args.childTimeoutMs); try { @@ -386,10 +377,7 @@ function buildProbeChildEnv(args: { * guaranteed to settle -- a child that traps or ignores SIGTERM cannot * wedge this call. A kill against an already-exited child is a no-op. */ -async function reapChild( - handle: ProbeChildHandle, - killTimeoutMs: number, -): Promise { +async function reapChild(handle: ProbeChildHandle, killTimeoutMs: number): Promise { try { handle.kill("SIGTERM"); } catch (err) { @@ -434,9 +422,7 @@ async function parseProbeResult( } const maced = MacedEnvelope(raw); if (maced instanceof type.errors) { - throw new Error( - `workflow probe child result envelope failed validation: ${maced.summary}`, - ); + throw new Error(`workflow probe child result envelope failed validation: ${maced.summary}`); } const envelopeBytes = encodeEnvelope(maced.envelope); const macBytes = hexDecode(maced.mac); @@ -453,18 +439,14 @@ async function parseProbeResult( } const payload = ProbeResultPayload(maced.envelope.payload); if (payload instanceof type.errors) { - throw new Error( - `workflow probe child result payload failed validation: ${payload.summary}`, - ); + throw new Error(`workflow probe child result payload failed validation: ${payload.summary}`); } if (!payload.ok) { throw new Error(`workflow probe evaluation failed: ${payload.error}`); } const projection = WorkflowProjectionDefinition(payload.projection); if (projection instanceof type.errors) { - throw new Error( - `workflow probe child projection failed validation: ${projection.summary}`, - ); + throw new Error(`workflow probe child projection failed validation: ${projection.summary}`); } return { projection, @@ -525,9 +507,7 @@ export async function runWorkflowProbeChildFromProcessEnv( await writeLine(`${JSON.stringify({ envelope, mac })}\n`); } -async function computeProbePayload( - packageDir: string, -): Promise { +async function computeProbePayload(packageDir: string): Promise { const definition = await loadWorkflowDefinitionFromClosure({ packageDir }); const projection = projectLiveToInert(definition); const wireHash = await computeWireDefinitionHash(projection); @@ -546,11 +526,10 @@ async function computeProbePayload( // an un-approved `tool:`. Loading here (over the frozen closure the // run-child also materializes from) keeps the approved snapshot and the // runtime plugin in lockstep. - const pluginToolDefinitions = - await loadWorkflowPluginToolDefinitionsFromClosure({ - packageDir, - plugins: collectDeclaredPluginNames(definition), - }); + const pluginToolDefinitions = await loadWorkflowPluginToolDefinitionsFromClosure({ + packageDir, + plugins: collectDeclaredPluginNames(definition), + }); const walk = walkCapabilities(definition, directors, pluginToolDefinitions); // Fail closed on an unresolved director: the runtime does not re-gate // `director:` against the approved grant set, so this advertisement is @@ -566,10 +545,7 @@ async function computeProbePayload( ok: true, projection, grants: collectDeploymentGrants(walk), - grantWalkSnapshot: buildGrantWalkSnapshot( - walk, - definition.grantRequirements, - ), + grantWalkSnapshot: buildGrantWalkSnapshot(walk, definition.grantRequirements), wireHash, }; } @@ -621,9 +597,7 @@ interface ProbeChildEnv { const NonEmptyString = type("string > 0"); -function parseProbeChildEnv( - rawEnv: Readonly>, -): ProbeChildEnv { +function parseProbeChildEnv(rawEnv: Readonly>): ProbeChildEnv { const channelId = requireEnv(rawEnv, PROBE_CHANNEL_ID_ENV); const packageDir = requireEnv(rawEnv, PROBE_PACKAGE_DIR_ENV); const hmacKeyHex = requireEnv(rawEnv, PROBE_HMAC_KEY_ENV); @@ -636,15 +610,10 @@ function parseProbeChildEnv( return { channelId, hmacKey, packageDir }; } -function requireEnv( - rawEnv: Readonly>, - key: string, -): string { +function requireEnv(rawEnv: Readonly>, key: string): string { const value = NonEmptyString(rawEnv[key]); if (value instanceof type.errors) { - throw new Error( - `workflow probe child env: required key ${key} is unset or empty`, - ); + throw new Error(`workflow probe child env: required key ${key} is unset or empty`); } return value; } @@ -667,9 +636,7 @@ function defaultStdoutWriteLine(line: string): Promise { * complete line, or `null` when the stream closes without one (the child * exited before writing). Releases the reader lock on every exit. */ -async function readResultLine( - stream: ReadableStream, -): Promise { +async function readResultLine(stream: ReadableStream): Promise { const reader = stream.getReader(); const decoder = new TextDecoder("utf-8"); let pending = ""; diff --git a/apps/sidecar/src/workflow-run-pack-client.test.ts b/apps/sidecar/src/workflow-run-pack-client.test.ts index e5b94f9107..297815668f 100644 --- a/apps/sidecar/src/workflow-run-pack-client.test.ts +++ b/apps/sidecar/src/workflow-run-pack-client.test.ts @@ -81,15 +81,12 @@ function createRecordingUnderlyingRepoStore(): { packedTipCommits.push({ repoId, ref, commitSha }); }, }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- in-test stub; the unused RepoStore methods are guarded by the Proxy below const store = new Proxy(stub as RepoStore, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); if (value !== undefined) return value; return () => { - throw new Error( - `stub RepoStore: ${String(prop)} not implemented for this test`, - ); + throw new Error(`stub RepoStore: ${String(prop)} not implemented for this test`); }; }, }); @@ -98,8 +95,7 @@ function createRecordingUnderlyingRepoStore(): { describe("createWorkflowRunPackClient", () => { test("push builds a pack under the supervisor principal, forwards it to the hub link, and commits the packed tip on the ack", async () => { - const { store, packs, packedTipCommits } = - createRecordingUnderlyingRepoStore(); + const { store, packs, packedTipCommits } = createRecordingUnderlyingRepoStore(); const sent: { agentAddress: string; repoId: RepoId; @@ -142,8 +138,7 @@ describe("createWorkflowRunPackClient", () => { const client = createWorkflowRunPackClient({ substrate: store, hubLink: { - pushWorkflowRunPack: () => - Promise.reject(new Error("transfer cancelled: Connection lost")), + pushWorkflowRunPack: () => Promise.reject(new Error("transfer cancelled: Connection lost")), }, }); @@ -248,16 +243,11 @@ describe("createWorkflowRunPackPushingRepoStore", () => { }); const repoId: RepoId = { kind: "workflow-run", id: "dep-pipeline" }; - await facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r-1/events/", - merge: async () => ({ "runs/r-1/events/0.json": "{}" }), - message: "first", - }, - ); + await facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r-1/events/", + merge: async () => ({ "runs/r-1/events/0.json": "{}" }), + message: "first", + }); // Yield to the microtask queue so the push's `enter` log lands. // With a serialised wrap the write would not resolve until // `exit`; pipelining is the property under test, so we assert @@ -307,18 +297,13 @@ describe("createWorkflowRunPackPushingRepoStore", () => { // slot's `dirty` flag, but only one coalesced follow-up push // runs after the first exits. for (let i = 0; i < 5; i += 1) { - await facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r-1/events/", - merge: async () => ({ - [`runs/r-1/events/${String(i)}.json`]: "{}", - }), - message: `write-${String(i)}`, - }, - ); + await facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r-1/events/", + merge: async () => ({ + [`runs/r-1/events/${String(i)}.json`]: "{}", + }), + message: `write-${String(i)}`, + }); } expect(pushCount).toBe(1); resolveFirst(); @@ -353,16 +338,11 @@ describe("createWorkflowRunPackPushingRepoStore", () => { registry, }); const repoId: RepoId = { kind: "workflow-run", id: "dep-fail" }; - await facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r/events/", - merge: async () => ({ "runs/r/events/0.json": "{}" }), - message: "first", - }, - ); + await facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r/events/", + merge: async () => ({ "runs/r/events/0.json": "{}" }), + message: "first", + }); // Wait for the failed push to settle on the chain without // consuming the latched error; flush would also surface the // error, but the contract being pinned here is that the NEXT @@ -370,16 +350,11 @@ describe("createWorkflowRunPackPushingRepoStore", () => { // code does not call flush between writes. await new Promise((resolve) => setTimeout(resolve, 10)); await expect( - facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r/events/", - merge: async () => ({ "runs/r/events/1.json": "{}" }), - message: "second", - }, - ), + facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r/events/", + merge: async () => ({ "runs/r/events/1.json": "{}" }), + message: "second", + }), ).rejects.toThrow(/non_fast_forward/); }); @@ -475,16 +450,11 @@ describe("createWorkflowRunPackPushingRepoStore", () => { const repoId: RepoId = { kind: "workflow-run", id: "dep-blocked" }; facade.markAddressUnroutable("agent-blocked@example.com"); - await facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r/events/", - merge: async () => ({ "runs/r/events/0.json": "{}" }), - message: "append while blocked", - }, - ); + await facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r/events/", + merge: async () => ({ "runs/r/events/0.json": "{}" }), + message: "append while blocked", + }); // Yield: a wire push would have run by now if the block were not held. await new Promise((r) => setTimeout(r, 0)); expect(pushCount).toBe(0); @@ -520,16 +490,11 @@ describe("createWorkflowRunPackPushingRepoStore", () => { }); const repoId: RepoId = { kind: "workflow-run", id: "dep-cancelled" }; - await facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r/events/", - merge: async () => ({ "runs/r/events/0.json": "{}" }), - message: "single batch", - }, - ); + await facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r/events/", + merge: async () => ({ "runs/r/events/0.json": "{}" }), + message: "single batch", + }); // Let the first push settle and latch "Connection lost" without a // second write to re-arm the loop. await new Promise((r) => setTimeout(r, 10)); @@ -564,16 +529,11 @@ describe("createWorkflowRunPackPushingRepoStore", () => { }); const repoId: RepoId = { kind: "workflow-run", id: "dep-clean" }; - await facade.writeTreePreservingPrefix( - { kind: "supervisor" }, - repoId, - "refs/heads/main", - { - preservePrefix: "runs/r/events/", - merge: async () => ({ "runs/r/events/0.json": "{}" }), - message: "append", - }, - ); + await facade.writeTreePreservingPrefix({ kind: "supervisor" }, repoId, "refs/heads/main", { + preservePrefix: "runs/r/events/", + merge: async () => ({ "runs/r/events/0.json": "{}" }), + message: "append", + }); await facade.flushWorkflowRunPushes(repoId, "refs/heads/main"); expect(pushCount).toBe(1); @@ -586,9 +546,7 @@ describe("createWorkflowRunPackPushingRepoStore", () => { describe("createMultistepMailRouter", () => { test("tryRoute returns null when no handler is registered", () => { const router = createMultistepMailRouter(); - expect( - router.tryRoute("dep@integration.interchange", new Uint8Array([1])), - ).toBeNull(); + expect(router.tryRoute("dep@integration.interchange", new Uint8Array([1]))).toBeNull(); }); test("a registered handler receives the inbound message and tryRoute returns its settlement", async () => { @@ -610,10 +568,7 @@ describe("createMultistepMailRouter", () => { router.register("dep@integration.interchange", async () => { throw new Error("durable write failed"); }); - const durable = router.tryRoute( - "dep@integration.interchange", - new Uint8Array([1]), - ); + const durable = router.tryRoute("dep@integration.interchange", new Uint8Array([1])); expect(durable).not.toBeNull(); await expect(durable).rejects.toThrow(/durable write failed/); }); @@ -624,9 +579,7 @@ describe("createMultistepMailRouter", () => { router.register("dep-a@integration.interchange", async (msg) => { received.push(msg); }); - expect( - router.tryRoute("dep-b@integration.interchange", new Uint8Array([9])), - ).toBeNull(); + expect(router.tryRoute("dep-b@integration.interchange", new Uint8Array([9]))).toBeNull(); expect(received).toHaveLength(0); }); @@ -637,9 +590,7 @@ describe("createMultistepMailRouter", () => { received.push(msg); }); router.unregister("dep@integration.interchange"); - expect( - router.tryRoute("dep@integration.interchange", new Uint8Array([1])), - ).toBeNull(); + expect(router.tryRoute("dep@integration.interchange", new Uint8Array([1]))).toBeNull(); expect(received).toHaveLength(0); }); @@ -737,9 +688,7 @@ describe("createMultistepCredentialsRouter", () => { router.register("dep@integration.interchange", async () => { throw new Error("deliverCredentials failed in a recycling phase"); }); - await expect(router.tryRoute(frame)).rejects.toThrow( - /deliverCredentials failed/, - ); + await expect(router.tryRoute(frame)).rejects.toThrow(/deliverCredentials failed/); }); }); @@ -765,8 +714,7 @@ describe("createMultistepSourcesRouter", () => { test("a registered handler receives the rotation and tryRoute returns true", async () => { const router = createMultistepSourcesRouter(); - const received: { sources: InferenceSource[]; defaultSource: string }[] = - []; + const received: { sources: InferenceSource[]; defaultSource: string }[] = []; router.register("dep@integration.interchange", async (args) => { received.push(args); }); @@ -802,9 +750,9 @@ describe("createMultistepSourcesRouter", () => { }); // Duplicate ids would crash the child's control-channel receiver on // its narrow, so the router rejects before dispatch. - await expect( - router.tryRoute({ ...frame, sources: [source, { ...source }] }), - ).rejects.toThrow(/unique ids/); + await expect(router.tryRoute({ ...frame, sources: [source, { ...source }] })).rejects.toThrow( + /unique ids/, + ); expect(called).toBe(false); }); diff --git a/apps/sidecar/src/workflow-run-pack-client.ts b/apps/sidecar/src/workflow-run-pack-client.ts index e983bb25d8..e8e9193249 100644 --- a/apps/sidecar/src/workflow-run-pack-client.ts +++ b/apps/sidecar/src/workflow-run-pack-client.ts @@ -19,11 +19,7 @@ import { getLogger } from "@intx/log"; import { SourcesUpdatedData } from "@intx/workflow-host"; import type { InferenceSource } from "@intx/types/runtime"; import type { CredentialDelivery } from "@intx/types/sidecar"; -import type { - RepoId, - RepoStore, - WorkflowRunSupervisorPrincipal, -} from "@intx/hub-sessions"; +import type { RepoId, RepoStore, WorkflowRunSupervisorPrincipal } from "@intx/hub-sessions"; import type { HubLink } from "@intx/hub-agent"; const logger = getLogger(["sidecar", "workflow-run-pack-client"]); @@ -37,11 +33,7 @@ export type WorkflowRunPackClient = { * failure shape is intentionally loud per the project's * defensive-coding rule. */ - push(opts: { - agentAddress: string; - repoId: RepoId; - ref: string; - }): Promise; + push(opts: { agentAddress: string; repoId: RepoId; ref: string }): Promise; /** * Seed the acknowledged-tip state after the Hub restores a ref into a fresh * worker. This prevents a reconnect from trying to re-send an empty delta at @@ -96,11 +88,7 @@ export function createWorkflowRunPackClient( if (tip !== null && tip === lastAckedSha.get(ackKey(repoId, ref))) { return; } - const { pack, commitSha } = await substrate.createPack( - principal, - repoId, - ref, - ); + const { pack, commitSha } = await substrate.createPack(principal, repoId, ref); await hubLink.pushWorkflowRunPack({ agentAddress, repoId, @@ -359,9 +347,7 @@ export function createMultistepGrantsRouter(): MultistepGrantsRouter { * the child where the material cell is swapped. No durable persist -- * credential material never touches disk. */ -export type MultistepCredentialsHandler = (args: { - delivery: CredentialDelivery; -}) => Promise; +export type MultistepCredentialsHandler = (args: { delivery: CredentialDelivery }) => Promise; /** * Per-deployment-address credential-delivery handler registry the sidecar @@ -416,9 +402,7 @@ export function createMultistepCredentialsRouter(): MultistepCredentialsRouter { * commits a signed `CancelRequested{origin: "supervisor-drain"}` * against the workflow-run repo when the deadline expires. */ -export type MultistepDrainHandler = (args: { - deadlineMs: number; -}) => Promise; +export type MultistepDrainHandler = (args: { deadlineMs: number }) => Promise; /** * Per-deployment-address drain handler registry the sidecar hub-link @@ -653,10 +637,7 @@ export type WorkflowRunPackPushingRepoStore = RepoStore & { * hook so an undeploy does not leave a slot (and its blocked address) * in this process-wide map forever. */ - reclaimPushState: (entry: { - deploymentId: string; - agentAddress: string; - }) => void; + reclaimPushState: (entry: { deploymentId: string; agentAddress: string }) => void; }; export function createWorkflowRunPackPushingRepoStore( @@ -721,8 +702,7 @@ export function createWorkflowRunPackPushingRepoStore( } catch (cause) { const msg = cause instanceof Error ? cause.message : String(cause); logger.warn`workflow-run pack push failed for deployment ${repoId.id} (${slot.agentAddress}): ${msg}`; - slot.lastError = - cause instanceof Error ? cause : new Error(String(cause)); + slot.lastError = cause instanceof Error ? cause : new Error(String(cause)); } } slot.inFlight = null; @@ -730,11 +710,7 @@ export function createWorkflowRunPackPushingRepoStore( })(); } - function schedulePush( - agentAddress: string, - repoId: RepoId, - ref: string, - ): void { + function schedulePush(agentAddress: string, repoId: RepoId, ref: string): void { const key = slotKey(repoId, ref); let slot = slots.get(key); if (slot === undefined) { @@ -777,10 +753,7 @@ export function createWorkflowRunPackPushingRepoStore( blockedAddresses.add(agentAddress); } - function reclaimPushState(entry: { - deploymentId: string; - agentAddress: string; - }): void { + function reclaimPushState(entry: { deploymentId: string; agentAddress: string }): void { blockedAddresses.delete(entry.agentAddress); for (const [key, slot] of slots) { if (slot.repoId.id === entry.deploymentId) { @@ -814,10 +787,7 @@ export function createWorkflowRunPackPushingRepoStore( } } - async function flushWorkflowRunPushes( - repoId: RepoId, - ref: string, - ): Promise { + async function flushWorkflowRunPushes(repoId: RepoId, ref: string): Promise { const slot = slots.get(slotKey(repoId, ref)); if (slot === undefined) return; if (slot.inFlight === null && !slot.dirty) { @@ -850,8 +820,7 @@ export function createWorkflowRunPackPushingRepoStore( getRepoDir: underlying.getRepoDir.bind(underlying), subscribe: underlying.subscribe.bind(underlying), openCommittedReads: underlying.openCommittedReads.bind(underlying), - openCommittedReadsAtCommit: - underlying.openCommittedReadsAtCommit.bind(underlying), + openCommittedReadsAtCommit: underlying.openCommittedReadsAtCommit.bind(underlying), flushWorkflowRunPushes, notifyAddressRoutable, markAddressUnroutable, @@ -863,12 +832,7 @@ export function createWorkflowRunPackPushingRepoStore( throw latched; } } - const result = await underlying.writeTreePreservingPrefix( - principal, - repoId, - ref, - args, - ); + const result = await underlying.writeTreePreservingPrefix(principal, repoId, ref, args); if (repoId.kind !== "workflow-run") { return result; } @@ -888,12 +852,7 @@ export function createWorkflowRunPackPushingRepoStore( throw latched; } } - const result = await underlying.writeTreeDelta( - principal, - repoId, - ref, - args, - ); + const result = await underlying.writeTreeDelta(principal, repoId, ref, args); if (repoId.kind !== "workflow-run") { return result; } diff --git a/apps/sidecar/src/workflow-run-pack-restore.test.ts b/apps/sidecar/src/workflow-run-pack-restore.test.ts index 4114b866c7..907a1daa81 100644 --- a/apps/sidecar/src/workflow-run-pack-restore.test.ts +++ b/apps/sidecar/src/workflow-run-pack-restore.test.ts @@ -90,9 +90,7 @@ test("restored refs survive replacement and the next sidecar commit fast-forward for (const ref of ["refs/heads/main", "refs/heads/events"] as const) { const pack = await source.repoStore.createPack(hubPrincipal, repoId, ref); await restore({ agentAddress, repoId, ...pack }); - expect(await target.repoStore.resolveRef(hubPrincipal, repoId, ref)).toBe( - pack.commitSha, - ); + expect(await target.repoStore.resolveRef(hubPrincipal, repoId, ref)).toBe(pack.commitSha); } const restoredInbox = path.join( target.repoStore.getRepoDir(repoId), @@ -141,18 +139,8 @@ test("restored refs survive replacement and the next sidecar commit fast-forward }); expect(pushed).toHaveLength(1); - expect( - await source.repoStore.resolveRef( - hubPrincipal, - repoId, - "refs/heads/events", - ), - ).toBe( - await target.repoStore.resolveRef( - hubPrincipal, - repoId, - "refs/heads/events", - ), + expect(await source.repoStore.resolveRef(hubPrincipal, repoId, "refs/heads/events")).toBe( + await target.repoStore.resolveRef(hubPrincipal, repoId, "refs/heads/events"), ); } finally { await fs.rm(root, { recursive: true, force: true }); diff --git a/apps/sidecar/src/workflow-run-pack-restore.ts b/apps/sidecar/src/workflow-run-pack-restore.ts index 8d54672eec..53b4245e59 100644 --- a/apps/sidecar/src/workflow-run-pack-restore.ts +++ b/apps/sidecar/src/workflow-run-pack-restore.ts @@ -1,11 +1,7 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { - WORKFLOW_RUN_RESTORE_REFS, - type RepoId, - type RepoStore, -} from "@intx/hub-sessions"; +import { WORKFLOW_RUN_RESTORE_REFS, type RepoId, type RepoStore } from "@intx/hub-sessions"; import { deriveWorkflowRunRepoId } from "@intx/workflow-deploy"; const RESTORABLE_REFS: readonly string[] = WORKFLOW_RUN_RESTORE_REFS; @@ -18,19 +14,14 @@ export type WorkflowRunPackRestoreArgs = { commitSha: string; }; -export type WorkflowRunPackRestorer = ( - args: WorkflowRunPackRestoreArgs, -) => Promise; +export type WorkflowRunPackRestorer = (args: WorkflowRunPackRestoreArgs) => Promise; type MaterializedFile = { oid: string; read(): Promise; }; -async function materializeRestoredRefs( - substrate: RepoStore, - repoId: RepoId, -): Promise { +async function materializeRestoredRefs(substrate: RepoStore, repoId: RepoId): Promise { const hubPrincipal = { kind: "hub" } as const; const files = new Map(); const directories = new Set(); @@ -64,9 +55,7 @@ async function materializeRestoredRefs( } const existing = files.get(relPath); if (existing !== undefined && existing.oid !== entry.oid) { - throw new Error( - `workflow_run_restore_conflict: ${relPath} differs across restored refs`, - ); + throw new Error(`workflow_run_restore_conflict: ${relPath} differs across restored refs`); } if (existing === undefined) { files.set(relPath, { @@ -98,9 +87,7 @@ async function materializeRestoredRefs( for (const [relPath, file] of files) { const destination = destinations.get(relPath); if (destination === undefined) { - throw new Error( - `workflow_run_restore_invalid: missing validated destination for ${relPath}`, - ); + throw new Error(`workflow_run_restore_invalid: missing validated destination for ${relPath}`); } await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(destination, await file.read()); @@ -133,9 +120,7 @@ export function createWorkflowRunPackRestorer(args: { ); } if (!RESTORABLE_REFS.includes(ref)) { - throw new Error( - `workflow_run_restore_invalid: unsupported workflow-run ref ${ref}`, - ); + throw new Error(`workflow_run_restore_invalid: unsupported workflow-run ref ${ref}`); } // `RepoStore` initializes a signed `.gitignore` genesis on main. Do that @@ -143,20 +128,9 @@ export function createWorkflowRunPackRestorer(args: { // null and then letting `receivePack` initialize it would observe a new // genesis inside the receive lock and reject the stale null pre-image. await substrate.initRepo(repoId); - const expectedOldSha = await substrate.resolveRef( - hubPrincipal, - repoId, - ref, - ); + const expectedOldSha = await substrate.resolveRef(hubPrincipal, repoId, ref); if (expectedOldSha !== commitSha) { - await substrate.receivePack( - hubPrincipal, - repoId, - ref, - pack, - commitSha, - expectedOldSha, - ); + await substrate.receivePack(hubPrincipal, repoId, ref, pack, commitSha, expectedOldSha); } await materializeRestoredRefs(substrate, repoId); diff --git a/apps/sidecar/src/workflow-run-record.test.ts b/apps/sidecar/src/workflow-run-record.test.ts index 2cb675f112..bd7eefc3b5 100644 --- a/apps/sidecar/src/workflow-run-record.test.ts +++ b/apps/sidecar/src/workflow-run-record.test.ts @@ -154,8 +154,7 @@ describe("workflow run record store", () => { // all-missing rejecting while silently admitting a partial record). The // `SourceRefPin` co-requires its `source` + `closure`, so a half-populated // pin must be rejected too, not just an absent one. - const rejects = (r: unknown): boolean => - WorkflowRunRecord(r) instanceof type.errors; + const rejects = (r: unknown): boolean => WorkflowRunRecord(r) instanceof type.errors; const base = { version: 1, @@ -177,12 +176,8 @@ describe("workflow run record store", () => { expect(rejects({ ...base, approvedWireHash })).toBe(true); // A half-populated pin (only one of source/closure) is rejected by the // pin's own co-requirement, even alongside a valid hash. - expect(rejects({ ...base, sourceRef: { source }, approvedWireHash })).toBe( - true, - ); - expect(rejects({ ...base, sourceRef: { closure }, approvedWireHash })).toBe( - true, - ); + expect(rejects({ ...base, sourceRef: { source }, approvedWireHash })).toBe(true); + expect(rejects({ ...base, sourceRef: { closure }, approvedWireHash })).toBe(true); // Full pin + hash -> accepted. expect(rejects({ ...base, sourceRef, approvedWireHash })).toBe(false); }); @@ -295,10 +290,7 @@ describe("scanWorkflowRunRecords", () => { // A directory whose record parses but fails the schema (missing fields). const invalidDir = path.join(dataDir, "workflow-runs", "dep-invalid"); await fs.mkdir(invalidDir, { recursive: true }); - await fs.writeFile( - path.join(invalidDir, "deployment.json"), - JSON.stringify({ version: 1 }), - ); + await fs.writeFile(path.join(invalidDir, "deployment.json"), JSON.stringify({ version: 1 })); // A bare run directory with no record at all. await fs.mkdir(path.join(dataDir, "workflow-runs", "dep-empty"), { diff --git a/apps/sidecar/src/workflow-run-record.ts b/apps/sidecar/src/workflow-run-record.ts index 7421b27ad8..6eaf352f64 100644 --- a/apps/sidecar/src/workflow-run-record.ts +++ b/apps/sidecar/src/workflow-run-record.ts @@ -37,9 +37,7 @@ const RECORD_FILENAME = "deployment.json"; /** True for a `node:fs` rejection whose `code` is `ENOENT`. */ function isENOENT(cause: unknown): boolean { return ( - cause instanceof Error && - "code" in cause && - (cause as { code: unknown }).code === "ENOENT" + cause instanceof Error && "code" in cause && (cause as { code: unknown }).code === "ENOENT" ); } @@ -119,10 +117,7 @@ export async function writeWorkflowRunRecord( * torn-down or never-completed run is not restored on the next boot. A * missing record is not an error (`force`). */ -export async function deleteWorkflowRunRecord( - dataDir: string, - runId: string, -): Promise { +export async function deleteWorkflowRunRecord(dataDir: string, runId: string): Promise { await rm(recordPath(dataDir, runId), { force: true }); } @@ -145,9 +140,7 @@ export interface ScannedWorkflowRun { * The returned `runId` is the directory name; the caller cross-checks it * against the record's own address before trusting it. */ -export async function scanWorkflowRunRecords( - dataDir: string, -): Promise { +export async function scanWorkflowRunRecords(dataDir: string): Promise { const runsDir = pathJoin(dataDir, "workflow-runs"); let entries; try { diff --git a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts index d938b75f98..c2c273e04a 100644 --- a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts +++ b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts @@ -11,11 +11,7 @@ import path from "node:path"; import { createDefaultDirectorRegistry } from "@intx/agent"; import type { DirectorRegistry } from "@intx/agent"; -import type { - Principal, - RepoId, - RepoStore, -} from "@intx/hub-sessions/substrate"; +import type { Principal, RepoId, RepoStore } from "@intx/hub-sessions/substrate"; import { signalName } from "@intx/types"; import type { InferenceEvent } from "@intx/types/runtime"; import { @@ -46,10 +42,7 @@ import { import type { MailPartReader } from "@intx/types/runtime"; import { reportError } from "@corbits/error-sink"; -import { - parseStepInferenceSources, - type StepInferenceSourceTable, -} from "./config"; +import { parseStepInferenceSources, type StepInferenceSourceTable } from "./config"; function fireAndForget( work: Promise, @@ -215,9 +208,7 @@ export interface SidecarRunChildDeps { * either per-call (no handle to dispose) or shared with the parent * (the scheduler). */ -export function createSidecarRunChild( - deps: SidecarRunChildDeps, -): RunChildWorkflow { +export function createSidecarRunChild(deps: SidecarRunChildDeps): RunChildWorkflow { const directors = deps.directors ?? createDefaultDirectorRegistry(); const clock = deps.clock ?? defaultClock; const newId = deps.newId ?? defaultNewId; @@ -342,9 +333,7 @@ export function createSidecarRunChild( * `handle.cancel` exactly as `createSidecarRunChild` does; the child * runtime takes no abort signal of its own. */ -export function createSidecarSpawnSuspendableChild( - deps: SidecarRunChildDeps, -): RunSuspendableChild { +export function createSidecarSpawnSuspendableChild(deps: SidecarRunChildDeps): RunSuspendableChild { const directors = deps.directors ?? createDefaultDirectorRegistry(); const clock = deps.clock ?? defaultClock; const newId = deps.newId ?? defaultNewId; @@ -421,22 +410,12 @@ export function createSidecarSpawnSuspendableChild( ); } const bodySourcesRef: SourcesSnapshotRef = { - current: await readBodyStepInferenceSources( - deps.dataDir, - rewrittenDefinition.id, - ), + current: await readBodyStepInferenceSources(deps.dataDir, rewrittenDefinition.id), }; const bodyInvokeStep = deps.bodyInvokeStep; const authorize = inputAuthorize ?? baseEnv.authorize; invokeStep = (req) => - bodyInvokeStep( - req, - authorize, - bodySourcesRef, - onEvent, - credentialWiring, - mailPartReader, - ); + bodyInvokeStep(req, authorize, bodySourcesRef, onEvent, credentialWiring, mailPartReader); } // FIFO the caller drains via `next()`: each entry is either an approval @@ -629,8 +608,7 @@ function buildChildRunEnv(args: { signalChannel: ReturnType; definition: WorkflowDefinition; } { - const { deps, directors, clock, newId, repoStore, runChild, definition } = - args; + const { deps, directors, clock, newId, repoStore, runChild, definition } = args; const childRunId = args.childRunId; // A rung may itself embed a grandchild as an inline `childWorkflow`. Lift // each to an internal `{ ref }` and run the rewritten definition whose @@ -639,9 +617,7 @@ function buildChildRunEnv(args: { // from, so a grandchild spawns with no on-disk read at any depth. const { workflow: rewrittenDefinition, bodies: grandchildBodies } = rewriteInlineChildWorkflowBodies(definition); - const grandchildMap = new Map( - grandchildBodies.map((body) => [body.ref, body.definition]), - ); + const grandchildMap = new Map(grandchildBodies.map((body) => [body.ref, body.definition])); const blobs = createWorkflowRunBlobSubstrate({ substrate: deps.substrate, repoId: deps.workflowRunRepoId, @@ -649,8 +625,7 @@ function buildChildRunEnv(args: { runId: childRunId, ref: deps.workflowRunRef, }); - const startChannel = - deps.createSignalChannel ?? createWorkflowHostSignalChannel; + const startChannel = deps.createSignalChannel ?? createWorkflowHostSignalChannel; const signalChannel = startChannel({ repoStore: deps.substrate, principal: deps.principal, @@ -688,8 +663,7 @@ function buildChildRunEnv(args: { bodies: grandchildMap, runChild, }); - const spawnChild: SpawnChildWorkflow = (spawnInput) => - hostSpawnChild(spawnInput, args.onEvent); + const spawnChild: SpawnChildWorkflow = (spawnInput) => hostSpawnChild(spawnInput, args.onEvent); const env: WorkflowRuntimeEnv = { repoStore, scheduler: deps.scheduler, @@ -720,13 +694,7 @@ async function readBodyStepInferenceSources( dataDir: string, bodyRef: string, ): Promise { - const sourcesPath = path.join( - dataDir, - "assets", - "workflow", - bodyRef, - "sources.json", - ); + const sourcesPath = path.join(dataDir, "assets", "workflow", bodyRef, "sources.json"); let raw: string; try { raw = await fs.promises.readFile(sourcesPath, "utf8"); diff --git a/apps/sidecar/src/workflow-substrate-factory/compactors.test.ts b/apps/sidecar/src/workflow-substrate-factory/compactors.test.ts index f85bb1c1e8..c459e715e4 100644 --- a/apps/sidecar/src/workflow-substrate-factory/compactors.test.ts +++ b/apps/sidecar/src/workflow-substrate-factory/compactors.test.ts @@ -81,19 +81,14 @@ test("caps each folded turn's contribution so the summary stays bounded regardle maxSummaryChars: 500, }); const turns = Array.from({ length: 200 }, (_, i) => - textTurn( - "user", - `a very long message repeated many times `.repeat(20) + String(i), - i, - ), + textTurn("user", `a very long message repeated many times `.repeat(20) + String(i), i), ); const result = await compactor.apply(turns, makeCtx()); const summary = result.output[0]; expect(summary?.content[0]?.type).toBe("text"); - const text = - summary?.content[0]?.type === "text" ? summary.content[0].text : ""; + const text = summary?.content[0]?.type === "text" ? summary.content[0].text : ""; expect(text.length).toBeLessThanOrEqual(500); }); @@ -111,8 +106,7 @@ test("non-text content blocks fold to a short placeholder rather than being drop const result = await compactor.apply(turns, makeCtx()); const summary = result.output[0]; - const text = - summary?.content[0]?.type === "text" ? summary.content[0].text : ""; + const text = summary?.content[0]?.type === "text" ? summary.content[0].text : ""; expect(text).toContain("tool_call"); }); @@ -126,9 +120,7 @@ test("createBudgetedContextCompactor: a short conversation under budget is untou const turns = Array.from({ length: 6 }, (_, i) => textTurn(i % 2 === 0 ? "user" : "assistant", `turn ${i}`, i), ); - const compactor = createBudgetedContextCompactor( - estimateTurnsChars(turns) + 1_000, - ); + const compactor = createBudgetedContextCompactor(estimateTurnsChars(turns) + 1_000); const result = await compactor.apply(turns, makeCtx()); @@ -177,9 +169,7 @@ test("estimateTurnsChars measures a tool_call's real argument size, not a placeh const turn: ConversationTurn = { role: "assistant", timestamp: 0, - content: [ - { type: "tool_call", id: "c1", name: "search", arguments: largeArgs }, - ], + content: [{ type: "tool_call", id: "c1", name: "search", arguments: largeArgs }], }; expect(estimateTurnsChars([turn])).toBeGreaterThanOrEqual(15_000); @@ -213,11 +203,7 @@ test("createBudgetedContextCompactor: folded output never exceeds the budget it // than `maxSummaryChars` (4000) but not by much, the exact regime // where an uncounted summary previously pushed the total over. const turns = Array.from({ length: 40 }, (_, i) => - textTurn( - i % 2 === 0 ? "user" : "assistant", - `message number ${i} `.repeat(25), - i, - ), + textTurn(i % 2 === 0 ? "user" : "assistant", `message number ${i} `.repeat(25), i), ); const budgetChars = 6_000; const compactor = createBudgetedContextCompactor(budgetChars); diff --git a/apps/sidecar/src/workflow-substrate-factory/compactors.ts b/apps/sidecar/src/workflow-substrate-factory/compactors.ts index ba6b22ddb6..6b64e36dc7 100644 --- a/apps/sidecar/src/workflow-substrate-factory/compactors.ts +++ b/apps/sidecar/src/workflow-substrate-factory/compactors.ts @@ -89,9 +89,7 @@ function mediaSourceChars(source: { function toolResultItemChars( item: Extract["content"][number], ): number { - return item.type === "text" - ? item.text.length - : mediaSourceChars(item.source); + return item.type === "text" ? item.text.length : mediaSourceChars(item.source); } /** @@ -156,10 +154,7 @@ export function estimateTurnsChars(turns: ConversationTurn[]): number { * recent exchange is never folded away even when it alone exceeds * budget (the honest-overflow case a director checks separately). */ -function countTurnsWithinBudget( - turns: ConversationTurn[], - budgetChars: number, -): number { +function countTurnsWithinBudget(turns: ConversationTurn[], budgetChars: number): number { let runningChars = 0; let kept = 0; for (let i = turns.length - 1; i >= 0; i--) { @@ -176,11 +171,7 @@ function countTurnsWithinBudget( } function roleLabel(role: ConversationTurn["role"]): string { - return role === "user" - ? "User" - : role === "assistant" - ? "Assistant" - : "System"; + return role === "user" ? "User" : role === "assistant" ? "Assistant" : "System"; } /** diff --git a/apps/sidecar/src/workflow-substrate-factory/config.test.ts b/apps/sidecar/src/workflow-substrate-factory/config.test.ts index 594b70bfb4..7801c44eb8 100644 --- a/apps/sidecar/src/workflow-substrate-factory/config.test.ts +++ b/apps/sidecar/src/workflow-substrate-factory/config.test.ts @@ -3,19 +3,13 @@ import { expect, test } from "bun:test"; import { deriveHubHttpUrl } from "./config"; test("derives an https origin from a wss:// hub URL", () => { - expect(deriveHubHttpUrl("wss://hub.example.com/api/sidecars/ws")).toBe( - "https://hub.example.com", - ); + expect(deriveHubHttpUrl("wss://hub.example.com/api/sidecars/ws")).toBe("https://hub.example.com"); }); test("derives an http origin from a ws:// hub URL", () => { - expect(deriveHubHttpUrl("ws://localhost:3000/api/sidecars/ws")).toBe( - "http://localhost:3000", - ); + expect(deriveHubHttpUrl("ws://localhost:3000/api/sidecars/ws")).toBe("http://localhost:3000"); }); test("preserves a non-default port", () => { - expect(deriveHubHttpUrl("wss://hub.example.com:8443/ws")).toBe( - "https://hub.example.com:8443", - ); + expect(deriveHubHttpUrl("wss://hub.example.com:8443/ws")).toBe("https://hub.example.com:8443"); }); diff --git a/apps/sidecar/src/workflow-substrate-factory/config.ts b/apps/sidecar/src/workflow-substrate-factory/config.ts index ed85237f1c..5aab3b0640 100644 --- a/apps/sidecar/src/workflow-substrate-factory/config.ts +++ b/apps/sidecar/src/workflow-substrate-factory/config.ts @@ -128,9 +128,7 @@ export type StepInferenceSourceTable = typeof StepInferenceSourceTable.infer; * a structured error rather than being deferred to a deep-stack * `buildEnv` failure. */ -export function parseStepInferenceSources( - raw: string, -): StepInferenceSourceTable { +export function parseStepInferenceSources(raw: string): StepInferenceSourceTable { let parsed: unknown; try { parsed = JSON.parse(raw); diff --git a/apps/sidecar/src/workflow-substrate-factory/context-budget.test.ts b/apps/sidecar/src/workflow-substrate-factory/context-budget.test.ts index f1db773042..e8d6db2282 100644 --- a/apps/sidecar/src/workflow-substrate-factory/context-budget.test.ts +++ b/apps/sidecar/src/workflow-substrate-factory/context-budget.test.ts @@ -33,25 +33,14 @@ test("readNumCtxHint: a top-level numCtx pin is visible without the Ollama bag s }); test("resolveContextBudgetChars: a bigger numCtx yields a bigger budget", () => { - const small = resolveContextBudgetChars( - { default: { numCtx: 32_000 } }, - "qwen3", - ); - const large = resolveContextBudgetChars( - { default: { numCtx: 128_000 } }, - "gpt-oss:20b", - ); + const small = resolveContextBudgetChars({ default: { numCtx: 32_000 } }, "qwen3"); + const large = resolveContextBudgetChars({ default: { numCtx: 128_000 } }, "gpt-oss:20b"); expect(large).toBeGreaterThan(small); }); test("resolveContextWindowTokens: quirks pin wins over the advertised catalog window", () => { - expect( - resolveContextWindowTokens( - { default: { numCtx: 8_192 } }, - "claude-sonnet-5", - ), - ).toBe(8_192); + expect(resolveContextWindowTokens({ default: { numCtx: 8_192 } }, "claude-sonnet-5")).toBe(8_192); }); test("resolveContextWindowTokens: a frontier catalog model gets its advertised window, not a 32k-char cap", () => { @@ -67,15 +56,13 @@ test("resolveContextWindowTokens: a frontier catalog model gets its advertised w test("resolveContextWindowTokens: an Ollama catalog model gets its native window without quirks", () => { expect(resolveContextWindowTokens(undefined, "gpt-oss:20b")).toBe(131_072); expect(resolveContextWindowTokens(undefined, "qwen3.8:27b")).toBe(32_768); - expect( - resolveHardContextLimitChars(undefined, "gpt-oss:20b"), - ).toBeGreaterThan(resolveHardContextLimitChars(undefined, "qwen3.8:27b")); + expect(resolveHardContextLimitChars(undefined, "gpt-oss:20b")).toBeGreaterThan( + resolveHardContextLimitChars(undefined, "qwen3.8:27b"), + ); }); test("advertisedContextWindowTokens: a relay-prefixed name still matches the catalog model", () => { - expect(advertisedContextWindowTokens("anthropic/claude-sonnet-5")).toBe( - 200_000, - ); + expect(advertisedContextWindowTokens("anthropic/claude-sonnet-5")).toBe(200_000); expect(advertisedContextWindowTokens("openai/gpt-4.1")).toBe(1_047_576); }); diff --git a/apps/sidecar/src/workflow-substrate-factory/context-budget.ts b/apps/sidecar/src/workflow-substrate-factory/context-budget.ts index b7b44b91d3..72b8dd121a 100644 --- a/apps/sidecar/src/workflow-substrate-factory/context-budget.ts +++ b/apps/sidecar/src/workflow-substrate-factory/context-budget.ts @@ -60,9 +60,7 @@ const ADVERTISED_CONTEXT_WINDOWS: readonly (readonly [string, number])[] = [ ]; function positiveTokenCount(value: unknown): number | undefined { - return typeof value === "number" && Number.isFinite(value) && value > 0 - ? value - : undefined; + return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined; } function readWindowFromBag(bag: Record): number | undefined { @@ -86,9 +84,7 @@ function catalogModelKey(model: string): string { * family prefix. `undefined` if this module has no published window for * the name. */ -export function advertisedContextWindowTokens( - model: string, -): number | undefined { +export function advertisedContextWindowTokens(model: string): number | undefined { const key = catalogModelKey(model); if (key.length === 0) { return undefined; @@ -98,10 +94,7 @@ export function advertisedContextWindowTokens( if (key === prefix) { return tokens; } - if ( - key.startsWith(prefix) && - (best === undefined || prefix.length > best.prefixLength) - ) { + if (key.startsWith(prefix) && (best === undefined || prefix.length > best.prefixLength)) { best = { prefixLength: prefix.length, tokens }; } } @@ -115,10 +108,7 @@ export function advertisedContextWindowTokens( * { numCtx? } } }`) and a few other common token-window field names. * Returns `undefined` for any unrecognized shape rather than throwing. */ -export function readNumCtxHint( - quirks: unknown, - model: string, -): number | undefined { +export function readNumCtxHint(quirks: unknown, model: string): number | undefined { if (typeof quirks !== "object" || quirks === null) { return undefined; } @@ -149,10 +139,7 @@ export function readNumCtxHint( * fallback. Compact headroom is applied by the char-budget helpers, * not here. */ -export function resolveContextWindowTokens( - quirks: unknown, - model: string, -): number { +export function resolveContextWindowTokens(quirks: unknown, model: string): number { return ( readNumCtxHint(quirks, model) ?? advertisedContextWindowTokens(model) ?? @@ -169,14 +156,8 @@ function toChars(tokens: number, headroom: number): number { * Applies `COMPACTION_HEADROOM` on top of the resolved window so * compaction fires with room left, not at the hard edge. */ -export function resolveContextBudgetChars( - quirks: unknown, - model: string, -): number { - return toChars( - resolveContextWindowTokens(quirks, model), - COMPACTION_HEADROOM, - ); +export function resolveContextBudgetChars(quirks: unknown, model: string): number { + return toChars(resolveContextWindowTokens(quirks, model), COMPACTION_HEADROOM); } /** @@ -185,9 +166,6 @@ export function resolveContextBudgetChars( * at all, used to detect the unrecoverable case where even the turns a * compactor must keep verbatim are already too large. */ -export function resolveHardContextLimitChars( - quirks: unknown, - model: string, -): number { +export function resolveHardContextLimitChars(quirks: unknown, model: string): number { return toChars(resolveContextWindowTokens(quirks, model), 1); } diff --git a/apps/sidecar/src/workflow-substrate-factory/index.ts b/apps/sidecar/src/workflow-substrate-factory/index.ts index 12a4a1410a..271a6ed3cd 100644 --- a/apps/sidecar/src/workflow-substrate-factory/index.ts +++ b/apps/sidecar/src/workflow-substrate-factory/index.ts @@ -65,10 +65,7 @@ import { import { type ReadParkedApprovalOps, type StepInvoker } from "@intx/workflow"; import type { InboundMessage } from "@intx/types/runtime"; -import { - createToolBearingAgentFactory, - type StepToolCacheConfig, -} from "../step-agent-tools"; +import { createToolBearingAgentFactory, type StepToolCacheConfig } from "../step-agent-tools"; import { createDurableConversationRegistry, prepareConversationForOriginatingWorkbench, @@ -103,11 +100,7 @@ import { type SidecarRunChildDeps, } from "./child-runtime"; -export { - createSidecarRunChild, - createSidecarSpawnSuspendableChild, - type SidecarBodyStepInvoker, -}; +export { createSidecarRunChild, createSidecarSpawnSuspendableChild, type SidecarBodyStepInvoker }; export { parseAdapterManifest, SIDECAR_SUBSTRATE_CONFIG_KEYS }; @@ -144,9 +137,7 @@ export class ChildStepNotImplementedError extends Error { function hexDecode(hex: string, name: string): Uint8Array { if (hex.length % 2 !== 0) { - throw new Error( - `${name} must be even-length hex; got ${String(hex.length)} chars`, - ); + throw new Error(`${name} must be even-length hex; got ${String(hex.length)} chars`); } const out = new Uint8Array(hex.length / 2); for (let i = 0; i < out.length; i += 1) { @@ -196,9 +187,7 @@ function createStepStorageSigner(signingKey: { privateKey: Uint8Array; }): (payload: string) => Promise { return (payload: string) => - Promise.resolve( - createSSHSignature(payload, signingKey.privateKey, signingKey.publicKey), - ); + Promise.resolve(createSSHSignature(payload, signingKey.privateKey, signingKey.publicKey)); } /** @@ -233,8 +222,7 @@ export function createSidecarSubstrateFactory( ): SubstrateFactory { const createBareRepoStore = deps.createBareRepoStore ?? - (({ dataDir, signingKey }) => - createAgentRepoStore({ dataDir, signingKey }).repoStore); + (({ dataDir, signingKey }) => createAgentRepoStore({ dataDir, signingKey }).repoStore); return async (env: SubstrateFactoryEnv) => { const validated = SubstrateConfig(env.substrateConfig); @@ -244,9 +232,7 @@ export function createSidecarSubstrateFactory( ); } - const stepInferenceSources = parseStepInferenceSources( - validated.STEP_INFERENCE_SOURCES, - ); + const stepInferenceSources = parseStepInferenceSources(validated.STEP_INFERENCE_SOURCES); // Build the child's adapter registry eagerly at boot from the // operator-supplied manifest. `loadAdapterRegistry` imports every @@ -260,14 +246,8 @@ export function createSidecarSubstrateFactory( ); const signingKey = { - publicKey: hexDecode( - validated.SIDECAR_SIGNING_PUBLIC_KEY, - "SIDECAR_SIGNING_PUBLIC_KEY", - ), - privateKey: hexDecode( - validated.SIDECAR_SIGNING_PRIVATE_KEY, - "SIDECAR_SIGNING_PRIVATE_KEY", - ), + publicKey: hexDecode(validated.SIDECAR_SIGNING_PUBLIC_KEY, "SIDECAR_SIGNING_PUBLIC_KEY"), + privateKey: hexDecode(validated.SIDECAR_SIGNING_PRIVATE_KEY, "SIDECAR_SIGNING_PRIVATE_KEY"), }; const bareStore: RepoStore = createBareRepoStore({ @@ -313,10 +293,7 @@ export function createSidecarSubstrateFactory( // delivers the resolved input as a synthesized inbound message, and // captures the agent's reply as the step output. const stepToolCache: StepToolCacheConfig = { - cacheMaxBytes: parseByteCap( - validated.SIDECAR_CACHE_MAX_BYTES, - "SIDECAR_CACHE_MAX_BYTES", - ), + cacheMaxBytes: parseByteCap(validated.SIDECAR_CACHE_MAX_BYTES, "SIDECAR_CACHE_MAX_BYTES"), registryMaxTarballBytes: parseByteCap( validated.SIDECAR_REGISTRY_MAX_TARBALL_BYTES, "SIDECAR_REGISTRY_MAX_TARBALL_BYTES", @@ -344,8 +321,7 @@ export function createSidecarSubstrateFactory( // and each store restores its prior snapshot from the substrate on // first acquire. const conversationSigner = createStepStorageSigner(signingKey); - const durableConversation: DurableConversationRegistry | undefined = env - .spawn.warmKeep + const durableConversation: DurableConversationRegistry | undefined = env.spawn.warmKeep ? createDurableConversationRegistry({ dataDir: validated.SIDECAR_DATA_DIR, workflowRunRepoId, @@ -460,9 +436,7 @@ export function createSidecarSubstrateFactory( // `runChild` recursion and the sub-namespace scoping around it are // real and exercised right up to this seam. const childInvokeStep: StepInvoker = (req) => - Promise.reject( - new ChildStepNotImplementedError(req.agent.id, req.authzContext.stepId), - ); + Promise.reject(new ChildStepNotImplementedError(req.agent.id, req.authzContext.stepId)); // onTrigger BODY step invoker (CL-6448). Unlike a childWorkflow child, // an onTrigger section body IS staged: its definition and per-step @@ -501,8 +475,7 @@ export function createSidecarSubstrateFactory( try { return await createWorkflowStepInvoker({ workflowAuthorize: authorize, - buildEnv: (buildReq) => - buildStepEnv(buildReq, sourcesRef, credentialWiring), + buildEnv: (buildReq) => buildStepEnv(buildReq, sourcesRef, credentialWiring), agentFactory: stepAgentFactory, sourcesRef, onEvent, @@ -590,22 +563,15 @@ export function createSidecarSubstrateFactory( driveConnectorReplies({ stream, composeReply: () => durableConversation.get(key).composeReply(), - send: (message) => - env.outboundMailBridge.submit( - env.spawn.mailboxAddress, - message, - ), + send: (message) => env.outboundMailBridge.submit(env.spawn.mailboxAddress, message), resolveReferences: async (inReplyTo) => { const store = await transportInbound.reader.open(); - const parent = store.messages.find( - (m) => m.envelope.messageId === inReplyTo, - ); + const parent = store.messages.find((m) => m.envelope.messageId === inReplyTo); return parent === undefined ? undefined : [...parent.envelope.references, parent.envelope.messageId]; }, - onReplySent: (receipt) => - durableConversation.get(key).onReplySent(receipt), + onReplySent: (receipt) => durableConversation.get(key).onReplySent(receipt), }) : undefined; @@ -642,11 +608,7 @@ export function createSidecarSubstrateFactory( })(req); }; - const evaluateGrantsAdapter: GrantEvaluator = async ({ - resource, - action, - grants, - }) => { + const evaluateGrantsAdapter: GrantEvaluator = async ({ resource, action, grants }) => { const result = await evaluateGrants( // The credentialsSnapshot's grants are typed as // `readonly unknown[]` so the workflow-host package does not @@ -689,8 +651,7 @@ export function createSidecarSubstrateFactory( // closure, so a body resolves in-process with no on-disk read and no // separate per-body re-verify -- the parent's re-verify already covers // every inline body. - const runSuspendableChild = - createSidecarSpawnSuspendableChild(childRunDeps); + const runSuspendableChild = createSidecarSpawnSuspendableChild(childRunDeps); // Per-run scratch reclamation for the cold (multi-step) path. The // run-loop fires this once each run reaches its terminal status; it @@ -705,18 +666,17 @@ export function createSidecarSubstrateFactory( // module's intent explicit. `rm -rf` semantics via `recursive + // force` so a run that never wrote scratch (no buildEnv reached) is // a no-op rather than an ENOENT throw. - const cleanupRunStorage: ((runId: string) => Promise) | undefined = - env.spawn.warmKeep - ? undefined - : (runId: string) => - fs.promises.rm( - runStepStorageRoot({ - dataDir: validated.SIDECAR_DATA_DIR, - workflowRunRepoId, - runId, - }), - { recursive: true, force: true }, - ); + const cleanupRunStorage: ((runId: string) => Promise) | undefined = env.spawn.warmKeep + ? undefined + : (runId: string) => + fs.promises.rm( + runStepStorageRoot({ + dataDir: validated.SIDECAR_DATA_DIR, + workflowRunRepoId, + runId, + }), + { recursive: true, force: true }, + ); // Recover a parked correlation's approval snapshot for the child's // re-registration enumeration (ported from upstream Interchange's @@ -726,12 +686,7 @@ export function createSidecarSubstrateFactory( // selects the durable read — cold reads the per-attempt isogit // store, warm reconstructs the agent's durable conversation state // from the substrate. - const loadParkedApproval: LoadParkedApproval = ({ - runId, - stepId, - attempt, - correlationId, - }) => + const loadParkedApproval: LoadParkedApproval = ({ runId, stepId, attempt, correlationId }) => env.spawn.warmKeep ? readWarmParkedApprovalSnapshot({ substrate, @@ -754,11 +709,7 @@ export function createSidecarSubstrateFactory( // correlationId (answering the supervisor's re-registration), this is // the enumeration the classifier needs when the correlationId never // reached the log — the crash-across-park case. - const readParkedApprovalOps: ReadParkedApprovalOps = async ({ - runId, - stepId, - attempt, - }) => + const readParkedApprovalOps: ReadParkedApprovalOps = async ({ runId, stepId, attempt }) => toParkedApprovalOps( env.spawn.warmKeep ? await readWarmParkedPendingOperations({ @@ -805,5 +756,4 @@ export function createSidecarSubstrateFactory( * hub sink (tests, alternate hosts) construct their own via * `createSidecarSubstrateFactory`. */ -export const createSubstrate: SubstrateFactory = - createSidecarSubstrateFactory(); +export const createSubstrate: SubstrateFactory = createSidecarSubstrateFactory(); diff --git a/apps/sidecar/src/workflow-substrate-factory/parked-approvals.test.ts b/apps/sidecar/src/workflow-substrate-factory/parked-approvals.test.ts index 914cf1ef0b..5a05604d1e 100644 --- a/apps/sidecar/src/workflow-substrate-factory/parked-approvals.test.ts +++ b/apps/sidecar/src/workflow-substrate-factory/parked-approvals.test.ts @@ -20,9 +20,7 @@ import { const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); const REPO_ID = { kind: "workflow-run" as const, id: "dep-parked-1" }; @@ -113,12 +111,8 @@ describe("readWarmParkedPendingOperations", () => { test("collects pending ops from nested rooms and ignores a mixed agent-state blob", async () => { const repoDir = await mkdtemp(path.join(tmpdir(), "parked-warm-")); tempDirs.push(repoDir); - await writeRoomCheckpoint(repoDir, "default", "chan_a", [ - approvalOp("corr-a"), - ]); - await writeRoomCheckpoint(repoDir, "default", "chan_b", [ - approvalOp("corr-b"), - ]); + await writeRoomCheckpoint(repoDir, "default", "chan_a", [approvalOp("corr-a")]); + await writeRoomCheckpoint(repoDir, "default", "chan_b", [approvalOp("corr-b")]); await writeFile( path.join(repoDir, "agent-state", "default", "checkpoint.json"), JSON.stringify({ pendingOperations: [approvalOp("corr-legacy")] }), diff --git a/apps/sidecar/src/workflow-substrate-factory/parked-approvals.ts b/apps/sidecar/src/workflow-substrate-factory/parked-approvals.ts index 0c1894d0e9..615cbd5ade 100644 --- a/apps/sidecar/src/workflow-substrate-factory/parked-approvals.ts +++ b/apps/sidecar/src/workflow-substrate-factory/parked-approvals.ts @@ -17,10 +17,7 @@ import { createIsogitStore } from "@intx/storage-isogit/node"; import type { ApprovalSnapshot, PendingOperation } from "@intx/types/runtime"; import type { ParkedApprovalOp } from "@intx/workflow"; -import { - isErrnoNotFound, - reconstructDurableConversation, -} from "../conversation-state"; +import { isErrnoNotFound, reconstructDurableConversation } from "../conversation-state"; import { stepStorageRoot } from "./storage-paths"; async function directoryExists(dir: string): Promise { @@ -36,8 +33,7 @@ export function findApprovalSnapshot( pendingOperations: readonly PendingOperation[], correlationId: string, ): ApprovalSnapshot | undefined { - return pendingOperations.find((op) => op.correlationId === correlationId) - ?.approvalSnapshot; + return pendingOperations.find((op) => op.correlationId === correlationId)?.approvalSnapshot; } /** @@ -82,10 +78,7 @@ export async function readColdParkedApprovalSnapshot(args: { attempt: number; correlationId: string; }): Promise { - return findApprovalSnapshot( - await readColdParkedPendingOperations(args), - args.correlationId, - ); + return findApprovalSnapshot(await readColdParkedPendingOperations(args), args.correlationId); } /** @@ -143,10 +136,7 @@ export async function readWarmParkedApprovalSnapshot(args: { stepId: string; correlationId: string; }): Promise { - return findApprovalSnapshot( - await readWarmParkedPendingOperations(args), - args.correlationId, - ); + return findApprovalSnapshot(await readWarmParkedPendingOperations(args), args.correlationId); } /** @@ -157,9 +147,7 @@ export async function readWarmParkedApprovalSnapshot(args: { * `SignalAwaited` from those alone, and must not see the reactor's * pending-operation internals. */ -export function toParkedApprovalOps( - pendingOperations: PendingOperation[], -): ParkedApprovalOp[] { +export function toParkedApprovalOps(pendingOperations: PendingOperation[]): ParkedApprovalOp[] { return pendingOperations .filter((op) => op.kind === "approval") .map((op) => ({ diff --git a/apps/sidecar/src/workflow-substrate-factory/step-env.test.ts b/apps/sidecar/src/workflow-substrate-factory/step-env.test.ts index 5bf5ec4fb8..7f8b65f836 100644 --- a/apps/sidecar/src/workflow-substrate-factory/step-env.test.ts +++ b/apps/sidecar/src/workflow-substrate-factory/step-env.test.ts @@ -11,24 +11,16 @@ import { afterEach, expect, test } from "bun:test"; import type { AgentDefinition, BaseEnv } from "@intx/agent"; import type { RepoId } from "@intx/hub-sessions/substrate"; import type { StepInvokeRequest } from "@intx/workflow"; -import type { - ChildOutboundMailBridge, - SourcesSnapshotRef, -} from "@intx/workflow-host"; - -import { - SUMMARIZE_BUDGETED_TURNS_NAME, - SUMMARIZE_OLDER_TURNS_NAME, -} from "./compactors"; +import type { ChildOutboundMailBridge, SourcesSnapshotRef } from "@intx/workflow-host"; + +import { SUMMARIZE_BUDGETED_TURNS_NAME, SUMMARIZE_OLDER_TURNS_NAME } from "./compactors"; import { createSidecarStepBuildEnv } from "./step-env"; const tmpDirs: string[] = []; afterEach(async () => { await Promise.all( - tmpDirs - .splice(0) - .map((dir) => fs.promises.rm(dir, { recursive: true, force: true })), + tmpDirs.splice(0).map((dir) => fs.promises.rm(dir, { recursive: true, force: true })), ); }); @@ -79,12 +71,10 @@ test("the built step env carries the deploying definition's own definitionId and const env = await buildEnv(stepInvokeRequest(), sourcesRef); - expect((env as unknown as { definitionId: string }).definitionId).toBe( - "wfd_capability_owner", + expect((env as unknown as { definitionId: string }).definitionId).toBe("wfd_capability_owner"); + expect((env as unknown as { hubCapabilitiesUrl: string }).hubCapabilitiesUrl).toBe( + "https://hub.example.com", ); - expect( - (env as unknown as { hubCapabilitiesUrl: string }).hubCapabilitiesUrl, - ).toBe("https://hub.example.com"); // Same hub origin as the sibling artifacts/skills bindings — one // origin, one name per tool-bundle surface, never overloaded. expect((env as unknown as { hubArtifactsUrl: string }).hubArtifactsUrl).toBe( @@ -93,12 +83,8 @@ test("the built step env carries the deploying definition's own definitionId and expect((env as unknown as { memoryBaseUrl: string }).memoryBaseUrl).toBe( "https://hub.example.com", ); - expect((env as unknown as { memoryTenantId: string }).memoryTenantId).toBe( - "ten_test", - ); - expect((env as unknown as { address: string }).address).toBe( - "run_1@example.com", - ); + expect((env as unknown as { memoryTenantId: string }).memoryTenantId).toBe("ten_test"); + expect((env as unknown as { address: string }).address).toBe("run_1@example.com"); }); test("a body-step env with no staged deploy tree still carries definitionId, so the binding is not tool-materialization-gated", async () => { @@ -116,9 +102,7 @@ test("a body-step env with no staged deploy tree still carries definitionId, so } as unknown as SourcesSnapshotRef; const env = await buildEnv(stepInvokeRequest(), sourcesRef); - expect((env as unknown as { definitionId: string }).definitionId).toBe( - "wfd_parent_definition", - ); + expect((env as unknown as { definitionId: string }).definitionId).toBe("wfd_parent_definition"); }); // CL-6448: the body-turn history seam. A section body runs each message as @@ -196,16 +180,11 @@ test("the built step env forwards the summarize-older-turns compactor (CL-6204) const env = await buildEnv(stepInvokeRequest(), sourcesRef); - const compactors = ( - env as unknown as { compactors: Record } - ).compactors; + const compactors = (env as unknown as { compactors: Record }) + .compactors; expect(Object.keys(compactors).sort()).toEqual( [SUMMARIZE_OLDER_TURNS_NAME, SUMMARIZE_BUDGETED_TURNS_NAME].sort(), ); - expect(compactors[SUMMARIZE_OLDER_TURNS_NAME]?.name).toBe( - SUMMARIZE_OLDER_TURNS_NAME, - ); - expect(compactors[SUMMARIZE_BUDGETED_TURNS_NAME]?.name).toBe( - SUMMARIZE_BUDGETED_TURNS_NAME, - ); + expect(compactors[SUMMARIZE_OLDER_TURNS_NAME]?.name).toBe(SUMMARIZE_OLDER_TURNS_NAME); + expect(compactors[SUMMARIZE_BUDGETED_TURNS_NAME]?.name).toBe(SUMMARIZE_BUDGETED_TURNS_NAME); }); diff --git a/apps/sidecar/src/workflow-substrate-factory/step-env.ts b/apps/sidecar/src/workflow-substrate-factory/step-env.ts index eafab68ae8..511eb991b0 100644 --- a/apps/sidecar/src/workflow-substrate-factory/step-env.ts +++ b/apps/sidecar/src/workflow-substrate-factory/step-env.ts @@ -9,16 +9,9 @@ import path from "node:path"; import type { RepoId } from "@intx/hub-sessions/substrate"; import { createDependencies, type AdapterRegistry } from "@intx/inference"; -import { - createIsogitStorage, - createNodeIsogitRuntime, -} from "@intx/storage-isogit/node"; +import { createIsogitStorage, createNodeIsogitRuntime } from "@intx/storage-isogit/node"; import type { RegistryConfig } from "@intx/tool-packaging"; -import type { - AuditStore, - ContextStore, - MessageTransport, -} from "@intx/types/runtime"; +import type { AuditStore, ContextStore, MessageTransport } from "@intx/types/runtime"; import type { StepInvokeRequest } from "@intx/workflow"; import { createSupervisorBackedTransport, @@ -45,10 +38,7 @@ import { createSummarizeOlderTurnsCompactor, } from "./compactors"; import { createStepInferenceSourceResolver } from "./config"; -import { - resolveContextBudgetChars, - resolveHardContextLimitChars, -} from "./context-budget"; +import { resolveContextBudgetChars, resolveHardContextLimitChars } from "./context-budget"; import { stepStorageRoot, warmStepStorageRoot } from "./storage-paths"; import { createWorkbenchDirectorRegistry } from "./workbench-director"; @@ -221,9 +211,7 @@ export function createSidecarStepBuildEnv( // agent this build constructs. A warm agent that is already built does // not pass through here again, so a rotation does not reach it through // this path -- this ref covers only a build that has not happened yet. - const resolveStepInferenceSource = createStepInferenceSourceResolver( - sourcesRef.current, - ); + const resolveStepInferenceSource = createStepInferenceSourceResolver(sourcesRef.current); const { stepId, runId, attempt } = req.authzContext; if (stepId === undefined) { throw new Error( @@ -256,10 +244,7 @@ export function createSidecarStepBuildEnv( // model and a 32K model compact at different points rather than // sharing one constant. Built per invocation (not module scope) // because the budget depends on the step's own resolved source. - const contextBudgetChars = resolveContextBudgetChars( - activeSource.quirks, - activeSource.model, - ); + const contextBudgetChars = resolveContextBudgetChars(activeSource.quirks, activeSource.model); const contextHardLimitChars = resolveHardContextLimitChars( activeSource.quirks, activeSource.model, @@ -271,8 +256,7 @@ export function createSidecarStepBuildEnv( }); const compactors = { ...stepCompactors, - [SUMMARIZE_BUDGETED_TURNS_NAME]: - createBudgetedContextCompactor(contextBudgetChars), + [SUMMARIZE_BUDGETED_TURNS_NAME]: createBudgetedContextCompactor(contextBudgetChars), }; // Root the per-step scratch (workspace + tool tarball-cache + @@ -331,17 +315,15 @@ export function createSidecarStepBuildEnv( // exactly the body agent's pins the folded launch staged -- and a // deployment that staged no tree for the body's stepId reads ENOENT // into the legitimate empty-tools case. - const materialization: StepToolMaterialization = await materializeStepTools( - { - dataDir: deps.dataDir, - mailboxAddress: deps.mailboxAddress, - stepId, - stepCount: deps.stepCount, - storeDir, - cache: deps.cache, - registries: deps.registries, - }, - ); + const materialization: StepToolMaterialization = await materializeStepTools({ + dataDir: deps.dataDir, + mailboxAddress: deps.mailboxAddress, + stepId, + stepCount: deps.stepCount, + storeDir, + cache: deps.cache, + registries: deps.registries, + }); // Supervisor-backed transport for the step agent's mail tools (both // halves of mailbox ownership). Outbound (`send`) routes over the diff --git a/apps/sidecar/src/workflow-substrate-factory/workbench-director.test.ts b/apps/sidecar/src/workflow-substrate-factory/workbench-director.test.ts index c33d0ffaae..3b4cc337d8 100644 --- a/apps/sidecar/src/workflow-substrate-factory/workbench-director.test.ts +++ b/apps/sidecar/src/workflow-substrate-factory/workbench-director.test.ts @@ -125,11 +125,7 @@ async function seedToolBatch(director: ReactorDirector): Promise { test("empty inference.done retries infer once", async () => { const director = createWorkbenchDirector("you are a test agent"); - const actions = await director.decide( - inferenceDone(emptyTurn()), - state(), - caps, - ); + const actions = await director.decide(inferenceDone(emptyTurn()), state(), caps); expect(typesOf(actions)).toEqual(["checkpoint", "infer"]); }); @@ -137,11 +133,7 @@ test("empty inference.done retries infer once", async () => { test("a second empty inference.done replies honestly instead of waiting", async () => { const director = createWorkbenchDirector("you are a test agent"); await director.decide(inferenceDone(emptyTurn()), state(), caps); - const actions = await director.decide( - inferenceDone(emptyTurn()), - state(), - caps, - ); + const actions = await director.decide(inferenceDone(emptyTurn()), state(), caps); expect(typesOf(actions)).toEqual(["checkpoint", "reply"]); expect(replyOf(actions)).toBe(EMPTY_TURN_REPLY); @@ -163,11 +155,7 @@ test("empty then a real text turn replies with the model text", async () => { test("a text turn on the first inference.done replies without retrying", async () => { const director = createWorkbenchDirector("you are a test agent"); - const actions = await director.decide( - inferenceDone(textTurn("hello")), - state(), - caps, - ); + const actions = await director.decide(inferenceDone(textTurn("hello")), state(), caps); expect(typesOf(actions)).toEqual(["checkpoint", "reply"]); expect(replyOf(actions)).toBe("hello"); @@ -197,11 +185,7 @@ test("reactive mode still waits on an empty turn (no retry)", async () => { const director = createWorkbenchDirector("you are a test agent", [], { mode: "reactive", }); - const actions = await director.decide( - inferenceDone(emptyTurn()), - state(), - caps, - ); + const actions = await director.decide(inferenceDone(emptyTurn()), state(), caps); expect(typesOf(actions)).toEqual(["checkpoint", "wait"]); }); @@ -217,11 +201,7 @@ test("a new message.received resets the empty-turn retry budget", async () => { state(), caps, ); - const actions = await director.decide( - inferenceDone(emptyTurn()), - state(), - caps, - ); + const actions = await director.decide(inferenceDone(emptyTurn()), state(), caps); expect(typesOf(actions)).toEqual(["checkpoint", "infer"]); }); @@ -290,9 +270,7 @@ test("context budget: tool-heavy history past the hard limit is caught even thou }, ); const bigState = stateWithTurns( - Array.from({ length: 10 }, (_, i) => - toolResultTurn("x".repeat(20_000), `call_${String(i)}`), - ), + Array.from({ length: 10 }, (_, i) => toolResultTurn("x".repeat(20_000), `call_${String(i)}`)), ); const actions = await director.decide( @@ -398,7 +376,7 @@ test("context budget: with no contextBudget configured, behavior is unchanged", test("the factory is namespaced and is the sidecar registry default", () => { const registry = createWorkbenchDirectorRegistry(); expect(registry.defaultFactory().id).toBe(WORKBENCH_DIRECTOR_ID); - expect( - registry.resolve({ id: defaultDirectorFactory.id, config: {} }).id, - ).toBe(defaultDirectorFactory.id); + expect(registry.resolve({ id: defaultDirectorFactory.id, config: {} }).id).toBe( + defaultDirectorFactory.id, + ); }); diff --git a/apps/sidecar/src/workflow-substrate-factory/workbench-director.ts b/apps/sidecar/src/workflow-substrate-factory/workbench-director.ts index 9bde5f143d..23e091a948 100644 --- a/apps/sidecar/src/workflow-substrate-factory/workbench-director.ts +++ b/apps/sidecar/src/workflow-substrate-factory/workbench-director.ts @@ -52,10 +52,7 @@ import { defineDirector, type DirectorRegistry, } from "@intx/agent"; -import { - createDefaultDirector, - type DefaultDirectorPolicy, -} from "@intx/inference"; +import { createDefaultDirector, type DefaultDirectorPolicy } from "@intx/inference"; import { formatSafetyRatingText, type AssistantTurn, @@ -149,12 +146,7 @@ export class WorkbenchDirector implements ReactorDirector { const actions = await this.inner.decide(event, state, capabilities); - const budgeted = this.applyContextBudget( - event, - state, - capabilities, - actions, - ); + const budgeted = this.applyContextBudget(event, state, capabilities, actions); if (budgeted !== undefined) { return budgeted; } @@ -163,10 +155,7 @@ export class WorkbenchDirector implements ReactorDirector { return actions; } - if ( - !this.isEmptyConversationalTurn(event.turn) || - !actionsIncludeWait(actions) - ) { + if (!this.isEmptyConversationalTurn(event.turn) || !actionsIncludeWait(actions)) { this.emptyTurnRetried = false; return actions; } @@ -183,20 +172,14 @@ export class WorkbenchDirector implements ReactorDirector { } this.emptyTurnRetried = false; - return [ - capabilities.checkpoint("empty-turn-reply"), - capabilities.reply(EMPTY_TURN_REPLY), - ]; + return [capabilities.checkpoint("empty-turn-reply"), capabilities.reply(EMPTY_TURN_REPLY)]; } private isEmptyConversationalTurn(turn: AssistantTurn): boolean { if (!this.conversational) { return false; } - return ( - extractToolCalls(turn).length === 0 && - extractTextContent(turn).length === 0 - ); + return extractToolCalls(turn).length === 0 && extractTextContent(turn).length === 0; } /** @@ -219,10 +202,7 @@ export class WorkbenchDirector implements ReactorDirector { const chars = estimateTurnsChars(state.turns); if (chars > this.contextBudget.hardLimitChars) { - if ( - list.some((action) => action.type === "infer") || - event.type === "message.received" - ) { + if (list.some((action) => action.type === "infer") || event.type === "message.received") { return [ capabilities.checkpoint("context-overflow"), capabilities.reply(CONTEXT_OVERFLOW_MESSAGE), @@ -234,17 +214,10 @@ export class WorkbenchDirector implements ReactorDirector { return undefined; } - if ( - event.type === "tool.done" && - list.length === 0 && - chars > this.contextBudget.budgetChars - ) { + if (event.type === "tool.done" && list.length === 0 && chars > this.contextBudget.budgetChars) { return [ capabilities.checkpoint("context-budget-compact"), - capabilities.compact( - this.contextBudget.compactorName, - "context-budget", - ), + capabilities.compact(this.contextBudget.compactorName, "context-budget"), ]; } @@ -258,12 +231,7 @@ export function createWorkbenchDirector( policy: DefaultDirectorPolicy = {}, contextBudget?: ContextBudgetOptions, ): ReactorDirector { - return new WorkbenchDirector( - systemPrompt, - toolDefinitions, - policy, - contextBudget, - ); + return new WorkbenchDirector(systemPrompt, toolDefinitions, policy, contextBudget); } const WorkbenchDirectorConfigSchema = type({ @@ -274,9 +242,7 @@ export type WorkbenchDirectorConfig = { mode?: "conversational" | "reactive"; }; -function buildWorkbenchFactory( - contextBudget: ContextBudgetOptions | undefined, -) { +function buildWorkbenchFactory(contextBudget: ContextBudgetOptions | undefined) { return defineDirector({ id: WORKBENCH_DIRECTOR_ID, configSchema: WorkbenchDirectorConfigSchema, diff --git a/apps/sidecar/test/atomic-write.test.ts b/apps/sidecar/test/atomic-write.test.ts index 6967c847eb..11bdd50405 100644 --- a/apps/sidecar/test/atomic-write.test.ts +++ b/apps/sidecar/test/atomic-write.test.ts @@ -7,9 +7,7 @@ import { writeFileAtomicDurable } from "../src/atomic-write"; const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeDir(): Promise { diff --git a/apps/sidecar/test/attachments-only-mail-step.test.ts b/apps/sidecar/test/attachments-only-mail-step.test.ts index 6631204748..b047355f5b 100644 --- a/apps/sidecar/test/attachments-only-mail-step.test.ts +++ b/apps/sidecar/test/attachments-only-mail-step.test.ts @@ -9,10 +9,7 @@ import { expect, test } from "bun:test"; import type { Agent, SendResult } from "@intx/agent"; import type { AuthzCallResult } from "@intx/inference"; import type { StepInvokeRequest } from "@intx/workflow"; -import { - createWorkflowStepInvoker, - type StepEnvBase, -} from "@intx/workflow-host"; +import { createWorkflowStepInvoker, type StepEnvBase } from "@intx/workflow-host"; import type { InboundMessage, Mail } from "@intx/types/runtime"; const ALLOW: AuthzCallResult = { diff --git a/apps/sidecar/test/body-turn-request-assembly.test.ts b/apps/sidecar/test/body-turn-request-assembly.test.ts index 5ade29c0ee..e58e0ce105 100644 --- a/apps/sidecar/test/body-turn-request-assembly.test.ts +++ b/apps/sidecar/test/body-turn-request-assembly.test.ts @@ -14,12 +14,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { - createAgent, - createDefaultDirectorRegistry, - defineAgent, - defineTool, -} from "@intx/agent"; +import { createAgent, createDefaultDirectorRegistry, defineAgent, defineTool } from "@intx/agent"; import { createDependencies } from "@intx/inference"; import { createBuiltinRegistry } from "@intx/inference/providers"; @@ -163,9 +158,7 @@ test("an openai-compatible turn's wire request carries the restored history and await agent.close(); } - expect(captured.url).toBe( - "http://inference.stub.invalid/v1/chat/completions", - ); + expect(captured.url).toBe("http://inference.stub.invalid/v1/chat/completions"); const request = captured.body as { messages: { role: string; content: unknown }[]; tools?: { function: { name: string } }[]; diff --git a/apps/sidecar/test/config.test.ts b/apps/sidecar/test/config.test.ts index f6c53b0f7e..bacfebad49 100644 --- a/apps/sidecar/test/config.test.ts +++ b/apps/sidecar/test/config.test.ts @@ -52,9 +52,7 @@ test("an unset SIDECAR_ADAPTER_MANIFEST defaults to the shipped Ollama adapter, test("the shipped default manifest registers the loopback-OAuth Responses adapters (CL-7510)", () => { const config = readSidecarConfig(VALID_ENV); - const byProvider = new Map( - config.adapterManifest.map((entry) => [entry.provider, entry]), - ); + const byProvider = new Map(config.adapterManifest.map((entry) => [entry.provider, entry])); expect(byProvider.get("codex")).toEqual({ provider: "codex", specifier: "@corbits/codex-provider", @@ -98,9 +96,9 @@ test("carries a valid adapter manifest through as its parsed form", () => { }); test("a malformed adapter manifest fails boot naming the variable", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, SIDECAR_ADAPTER_MANIFEST: "{not json" }), - ).toThrow(/SIDECAR_ADAPTER_MANIFEST/); + expect(() => readSidecarConfig({ ...VALID_ENV, SIDECAR_ADAPTER_MANIFEST: "{not json" })).toThrow( + /SIDECAR_ADAPTER_MANIFEST/, + ); }); test("an adapter manifest entry missing a required field fails boot", () => { @@ -123,21 +121,19 @@ test("carries operator overrides for consumedRetentionMs/readyTimeoutMs through }); test("a non-numeric CONSUMED_RETENTION_MS fails boot naming the variable", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, CONSUMED_RETENTION_MS: "not-a-number" }), - ).toThrow(/CONSUMED_RETENTION_MS/); + expect(() => readSidecarConfig({ ...VALID_ENV, CONSUMED_RETENTION_MS: "not-a-number" })).toThrow( + /CONSUMED_RETENTION_MS/, + ); }); test("a non-positive CHILD_READY_TIMEOUT_MS fails boot naming the variable", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, CHILD_READY_TIMEOUT_MS: "0" }), - ).toThrow(/CHILD_READY_TIMEOUT_MS/); + expect(() => readSidecarConfig({ ...VALID_ENV, CHILD_READY_TIMEOUT_MS: "0" })).toThrow( + /CHILD_READY_TIMEOUT_MS/, + ); }); test("carries a valid tool-registry pin through as the raw JSON", () => { - const pin = JSON.stringify([ - { name: "internal", url: "https://npm.example.com" }, - ]); + const pin = JSON.stringify([{ name: "internal", url: "https://npm.example.com" }]); const config = readSidecarConfig({ ...VALID_ENV, SIDECAR_TOOL_REGISTRIES: pin, @@ -146,15 +142,15 @@ test("carries a valid tool-registry pin through as the raw JSON", () => { }); test("a malformed tool-registry pin fails boot naming the variable", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, SIDECAR_TOOL_REGISTRIES: "{not json" }), - ).toThrow(/SIDECAR_TOOL_REGISTRIES/); + expect(() => readSidecarConfig({ ...VALID_ENV, SIDECAR_TOOL_REGISTRIES: "{not json" })).toThrow( + /SIDECAR_TOOL_REGISTRIES/, + ); }); test("an empty tool-registry pin fails boot instead of defaulting to npmjs", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, SIDECAR_TOOL_REGISTRIES: "" }), - ).toThrow(/SIDECAR_TOOL_REGISTRIES/); + expect(() => readSidecarConfig({ ...VALID_ENV, SIDECAR_TOOL_REGISTRIES: "" })).toThrow( + /SIDECAR_TOOL_REGISTRIES/, + ); }); test("carries the optional child-env OS facts through when present", () => { @@ -185,19 +181,15 @@ test("a missing variable errors naming it", () => { }); test("an empty variable errors naming it", () => { - expect(() => readSidecarConfig({ ...VALID_ENV, SIDECAR_ID: "" })).toThrow( - /SIDECAR_ID/, - ); + expect(() => readSidecarConfig({ ...VALID_ENV, SIDECAR_ID: "" })).toThrow(/SIDECAR_ID/); }); test("a non-websocket hub URL errors naming the expected shape", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, HUB_WS_URL: "https://hub.example.com" }), - ).toThrow(/ws:\/\/ or wss:\/\//); + expect(() => readSidecarConfig({ ...VALID_ENV, HUB_WS_URL: "https://hub.example.com" })).toThrow( + /ws:\/\/ or wss:\/\//, + ); }); test("an unparseable hub URL errors", () => { - expect(() => - readSidecarConfig({ ...VALID_ENV, HUB_WS_URL: "ws://" }), - ).toThrow(/HUB_WS_URL/); + expect(() => readSidecarConfig({ ...VALID_ENV, HUB_WS_URL: "ws://" })).toThrow(/HUB_WS_URL/); }); diff --git a/apps/sidecar/test/credential-bindings-parity.test.ts b/apps/sidecar/test/credential-bindings-parity.test.ts index 437f89a13e..b192b23e81 100644 --- a/apps/sidecar/test/credential-bindings-parity.test.ts +++ b/apps/sidecar/test/credential-bindings-parity.test.ts @@ -13,10 +13,7 @@ import { describe, expect, test } from "bun:test"; import type { CredentialDelivery } from "@intx/types/sidecar"; import { deriveResolvedBindings } from "@corbits/credential-providers"; -import { - consumerBindings, - type StepCredentialContext, -} from "../src/step-agent-tools"; +import { consumerBindings, type StepCredentialContext } from "../src/step-agent-tools"; const CONSUMER = "tool:@corbits/granola-tools"; const OTHER_CONSUMER = "tool:@corbits/linear-tools"; @@ -24,9 +21,7 @@ const CREDENTIAL_ID = "cred_parity_1"; function delivery(secret: string): CredentialDelivery { return { - bindings: [ - { handle: "granola", credentialId: CREDENTIAL_ID, consumer: CONSUMER }, - ], + bindings: [{ handle: "granola", credentialId: CREDENTIAL_ID, consumer: CONSUMER }], materials: [ { credentialId: CREDENTIAL_ID, @@ -52,10 +47,7 @@ describe("consumerBindings (sidecar) vs deriveResolvedBindings (@corbits/credent test("agree on the bound handle's static fields for a matching consumer", () => { const context = contextFor(delivery("s3cr3t")); const fromSidecar = consumerBindings(context, CONSUMER); - const fromGranolaCopy = deriveResolvedBindings( - delivery("s3cr3t"), - CONSUMER, - ); + const fromGranolaCopy = deriveResolvedBindings(delivery("s3cr3t"), CONSUMER); expect([...fromSidecar.keys()]).toEqual([...fromGranolaCopy.keys()]); const sidecarBinding = fromSidecar.get("granola"); @@ -75,9 +67,7 @@ describe("consumerBindings (sidecar) vs deriveResolvedBindings (@corbits/credent stepId: "step_parity", }; const sidecarBinding = consumerBindings(context, CONSUMER).get("granola"); - const copyBinding = deriveResolvedBindings(originalDelivery, CONSUMER).get( - "granola", - ); + const copyBinding = deriveResolvedBindings(originalDelivery, CONSUMER).get("granola"); if (sidecarBinding === undefined || copyBinding === undefined) { throw new Error("expected both derivations to bind the granola handle"); } @@ -93,13 +83,10 @@ describe("consumerBindings (sidecar) vs deriveResolvedBindings (@corbits/credent const rotatedDelivery = delivery("rotated-secret"); ref.current = rotatedDelivery; expect(sidecarBinding.readCurrentMaterial().secret).toBe("rotated-secret"); - const copyBindingAfterRotation = deriveResolvedBindings( - rotatedDelivery, - CONSUMER, - ).get("granola"); - expect(copyBindingAfterRotation?.readCurrentMaterial().secret).toBe( - "rotated-secret", + const copyBindingAfterRotation = deriveResolvedBindings(rotatedDelivery, CONSUMER).get( + "granola", ); + expect(copyBindingAfterRotation?.readCurrentMaterial().secret).toBe("rotated-secret"); }); test("agree on filtering out a binding for a different consumer", () => { @@ -112,8 +99,6 @@ describe("consumerBindings (sidecar) vs deriveResolvedBindings (@corbits/credent test("agree on an empty delivery yielding no bindings", () => { const context = contextFor(null); expect(consumerBindings(context, CONSUMER).size).toBe(0); - expect( - deriveResolvedBindings({ bindings: [], materials: [] }, CONSUMER).size, - ).toBe(0); + expect(deriveResolvedBindings({ bindings: [], materials: [] }, CONSUMER).size).toBe(0); }); }); diff --git a/apps/sidecar/test/deploy-router.test.ts b/apps/sidecar/test/deploy-router.test.ts index d6165983f2..4bc7c10f8b 100644 --- a/apps/sidecar/test/deploy-router.test.ts +++ b/apps/sidecar/test/deploy-router.test.ts @@ -8,11 +8,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, expect, test } from "bun:test"; -import { - createEd25519Crypto, - generateKeyPair, - verifySSHSignature, -} from "@intx/crypto"; +import { createEd25519Crypto, generateKeyPair, verifySSHSignature } from "@intx/crypto"; import { createAgentKeyStore, createAgentRepoStore as createSidecarSideRepoStore, @@ -36,9 +32,7 @@ import { assembleRunCredentialsSnapshot } from "../src/workflow-host-wiring/supe const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeDataDir(): Promise { @@ -99,9 +93,7 @@ async function makeRouter(dataDir: string): Promise { materializeDeploymentClosure: ({ deploymentId }) => { const definition = closureDefinitions.get(deploymentId); if (definition === undefined) { - throw new Error( - `test closure materializer: no definition registered for ${deploymentId}`, - ); + throw new Error(`test closure materializer: no definition registered for ${deploymentId}`); } return Promise.resolve({ definition, @@ -144,10 +136,7 @@ const SOURCE_REF: NonNullable["sourceRef"] = { * Register the definition the pinned closure evaluates to for `agentAddress` * and return the live shape the router's projection gate runs against. */ -function stageClosureDefinition( - agentAddress: string, - stepOrder: string[], -): void { +function stageClosureDefinition(agentAddress: string, stepOrder: string[]): void { const steps: Record> = {}; for (const stepId of stepOrder) { steps[stepId] = step({ @@ -213,15 +202,12 @@ test("a frame with neither provisionStep nor workflow is refused", async () => { hubPublicKey: hexEncode(hubKey.publicKey), }; - await expect(router.deploy(frame)).rejects.toThrow( - /provisionStep or a workflow definition/, - ); + await expect(router.deploy(frame)).rejects.toThrow(/provisionStep or a workflow definition/); }); test("an unbuildable inference provider rejects the deploy before any spawn", async () => { const dataDir = await makeDataDir(); - const { router, spawnedBinaries, rejectedSources } = - await makeRouter(dataDir); + const { router, spawnedBinaries, rejectedSources } = await makeRouter(dataDir); const hubKey = await generateKeyPair(); const frame: AgentDeployFrame = { type: "agent.deploy", @@ -309,9 +295,7 @@ test("a single-step deploy writes the self-anchored run's grants before spawning stageClosureDefinition(agentAddress, ["step-1"]); - await expect(router.deploy(frame)).rejects.toThrow( - /refuses to launch a real child/, - ); + await expect(router.deploy(frame)).rejects.toThrow(/refuses to launch a real child/); const substrate = createAgentRepoStore({ dataDir, diff --git a/apps/sidecar/test/host-boot.test.ts b/apps/sidecar/test/host-boot.test.ts index 2f62f87b9c..45cb92ec47 100644 --- a/apps/sidecar/test/host-boot.test.ts +++ b/apps/sidecar/test/host-boot.test.ts @@ -30,9 +30,7 @@ test("host dials in against a stub hub and exits cleanly on SIGTERM", async () = websocket: { open() {}, message(_ws, message) { - registered.resolve( - typeof message === "string" ? message : message.toString(), - ); + registered.resolve(typeof message === "string" ? message : message.toString()); }, }, }); diff --git a/apps/sidecar/test/shutdown.test.ts b/apps/sidecar/test/shutdown.test.ts index a7baad8006..cbe5424b3e 100644 --- a/apps/sidecar/test/shutdown.test.ts +++ b/apps/sidecar/test/shutdown.test.ts @@ -22,10 +22,7 @@ test("resolves drained when the drain completes inside the bound", async () => { }); test("resolves timed-out when the drain outlives the bound", async () => { - const outcome = await drainWithTimeout( - () => new Promise(() => undefined), - 10, - ); + const outcome = await drainWithTimeout(() => new Promise(() => undefined), 10); expect(outcome).toEqual({ kind: "timed-out" }); }); @@ -113,12 +110,9 @@ test("a rejected shutdown promise is caught by the signal handler binding", asyn process.on("unhandledRejection", onUnhandled); const reported: unknown[] = []; try { - attachShutdownRejectionHandler( - Promise.reject(new Error("escaped")), - (error) => { - reported.push(error); - }, - ); + attachShutdownRejectionHandler(Promise.reject(new Error("escaped")), (error) => { + reported.push(error); + }); await Bun.sleep(20); expect(reported).toHaveLength(1); expect(unhandled).toEqual([]); diff --git a/apps/sidecar/test/signing-keypair.test.ts b/apps/sidecar/test/signing-keypair.test.ts index 6c147ab43e..0758a68692 100644 --- a/apps/sidecar/test/signing-keypair.test.ts +++ b/apps/sidecar/test/signing-keypair.test.ts @@ -7,9 +7,7 @@ import { loadOrMintSidecarKeypair } from "../src/signing-keypair"; const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeSigningDir(): Promise { @@ -38,13 +36,8 @@ test("loads the same identity on a second boot", async () => { test("halts when the public anchor does not match the seed", async () => { const signingDir = await makeSigningDir(); await loadOrMintSidecarKeypair(signingDir); - await writeFile( - path.join(signingDir, "ed25519.public"), - new Uint8Array(32).fill(7), - ); - await expect(loadOrMintSidecarKeypair(signingDir)).rejects.toThrow( - /does not match/, - ); + await writeFile(path.join(signingDir, "ed25519.public"), new Uint8Array(32).fill(7)); + await expect(loadOrMintSidecarKeypair(signingDir)).rejects.toThrow(/does not match/); }); test("halts on a partial keypair directory", async () => { diff --git a/apps/sidecar/test/support/workflow-lifecycle-fixture.ts b/apps/sidecar/test/support/workflow-lifecycle-fixture.ts index 3e926ad462..9fb3c5b881 100644 --- a/apps/sidecar/test/support/workflow-lifecycle-fixture.ts +++ b/apps/sidecar/test/support/workflow-lifecycle-fixture.ts @@ -161,9 +161,7 @@ export function createSpawnTestRepoStore(tempBase: string): RepoStore { const value = Reflect.get(target, prop, receiver); if (value !== undefined) return value; return () => { - throw new Error( - `stub RepoStore: ${String(prop)} not implemented for this test`, - ); + throw new Error(`stub RepoStore: ${String(prop)} not implemented for this test`); }; }, }); @@ -286,12 +284,9 @@ export async function makeLifecycleFixture(opts?: { const transport = createInMemoryTransport(); const keyPair = await generateKeyPair(); - const tempBase = await fs.mkdtemp( - path.join(os.tmpdir(), "sidecar-lifecycle-repos-"), - ); + const tempBase = await fs.mkdtemp(path.join(os.tmpdir(), "sidecar-lifecycle-repos-")); const dataDir = - opts?.dataDir ?? - (await fs.mkdtemp(path.join(os.tmpdir(), "sidecar-lifecycle-data-"))); + opts?.dataDir ?? (await fs.mkdtemp(path.join(os.tmpdir(), "sidecar-lifecycle-data-"))); const repoStore = createSpawnTestRepoStore(tempBase); const multistepMailRouter = createMultistepMailRouter(); @@ -307,9 +302,7 @@ export async function makeLifecycleFixture(opts?: { throw new Error("single-step branch must not invoke provisionAgent"); }, persistHubPublicKey: async () => { - throw new Error( - "single-step branch must not invoke persistHubPublicKey", - ); + throw new Error("single-step branch must not invoke persistHubPublicKey"); }, initRepo: async () => undefined, } as unknown as Parameters[0]["sessions"], @@ -327,9 +320,7 @@ export async function makeLifecycleFixture(opts?: { await fs.mkdir(agentDir(dataDir, address), { recursive: true }); return { keyPair: await generateKeyPair(), isNew: false }; }, - } as unknown as Parameters< - typeof createSidecarDeployRouter - >[0]["keyStore"]), + } as unknown as Parameters[0]["keyStore"]), transport, repoStore, signingKeySeed: keyPair.privateKey, diff --git a/apps/sidecar/test/tool-materialization.test.ts b/apps/sidecar/test/tool-materialization.test.ts index 521132aaaa..d3c8076b29 100644 --- a/apps/sidecar/test/tool-materialization.test.ts +++ b/apps/sidecar/test/tool-materialization.test.ts @@ -16,18 +16,14 @@ import { SIDECAR_SUBSTRATE_CONFIG_KEYS } from "../src/workflow-substrate-factory const tempDirs: string[] = []; async function tempDir(): Promise { - const d = await fs.promises.mkdtemp( - path.join(os.tmpdir(), "default-harness-test-"), - ); + const d = await fs.promises.mkdtemp(path.join(os.tmpdir(), "default-harness-test-")); tempDirs.push(d); return d; } afterEach(async () => { const dirs = tempDirs.splice(0); - await Promise.all( - dirs.map((d) => fs.promises.rm(d, { recursive: true, force: true })), - ); + await Promise.all(dirs.map((d) => fs.promises.rm(d, { recursive: true, force: true }))); }); describe("materializeToolPackages — manifest.invalid gate", () => { @@ -83,10 +79,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { // writer must not push the bytes through JSON.stringify, which // would double-encode the string and erase the original-input // evidence the audit file exists to preserve. - const persisted = await fs.promises.readFile( - path.join(attemptDir, "manifest.json"), - "utf-8", - ); + const persisted = await fs.promises.readFile(path.join(attemptDir, "manifest.json"), "utf-8"); expect(persisted).toBe(corrupt); const errorJson = JSON.parse( @@ -134,10 +127,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { // The audit trail records the bytes-as-supplied for both // failure modes, so a future investigator can replay the // same input against a newer validator. - const persisted = await fs.promises.readFile( - path.join(attemptDir, "manifest.json"), - "utf-8", - ); + const persisted = await fs.promises.readFile(path.join(attemptDir, "manifest.json"), "utf-8"); expect(persisted).toBe(wrongShape); const errorJson = JSON.parse( @@ -152,21 +142,15 @@ describe("materializeToolPackages — manifest.invalid gate", () => { }); test("accepts a bare id for backward compatibility with pre-versioned files", () => { - expect(parseActiveDeployId("legacy-deploy-id", "/dummy")).toBe( - "legacy-deploy-id", - ); + expect(parseActiveDeployId("legacy-deploy-id", "/dummy")).toBe("legacy-deploy-id"); }); test("rejects an unknown version prefix loudly", () => { - expect(() => parseActiveDeployId("v2:abc", "/dummy")).toThrow( - /unknown version prefix/, - ); + expect(() => parseActiveDeployId("v2:abc", "/dummy")).toThrow(/unknown version prefix/); }); test("rejects a v1-prefixed but empty id", () => { - expect(() => parseActiveDeployId("v1:", "/dummy")).toThrow( - /carries the v1 prefix but no id/, - ); + expect(() => parseActiveDeployId("v1:", "/dummy")).toThrow(/carries the v1 prefix but no id/); }); test("rejects an empty file", () => { @@ -256,10 +240,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { expect(outcome.degraded).toBe(true); expect(outcome.error).toBeInstanceOf(Error); - const dirtyContents = await fs.promises.readFile( - `${activeIdFile}.dirty`, - "utf-8", - ); + const dirtyContents = await fs.promises.readFile(`${activeIdFile}.dirty`, "utf-8"); expect(dirtyContents).toBe("v1:new-id-42"); }); }); @@ -273,10 +254,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { const storeDir = await tempDir(); const instanceDir = path.join(storeDir, "tool-packages"); await fs.promises.mkdir(instanceDir, { recursive: true }); - await fs.promises.writeFile( - path.join(instanceDir, "active-deploy-id"), - "v1:stale-recorded", - ); + await fs.promises.writeFile(path.join(instanceDir, "active-deploy-id"), "v1:stale-recorded"); await fs.promises.writeFile( path.join(instanceDir, "active-deploy-id.dirty"), "v1:truth-from-marker", @@ -307,10 +285,7 @@ describe("materializeToolPackages — manifest.invalid gate", () => { throw new Error("no attempt dir was created"); } const errorJson = JSON.parse( - await fs.promises.readFile( - path.join(auditRoot, attemptDirName, "error.json"), - "utf-8", - ), + await fs.promises.readFile(path.join(auditRoot, attemptDirName, "error.json"), "utf-8"), ); expect(errorJson.previousDeployId).toBe("truth-from-marker"); }); @@ -348,9 +323,7 @@ describe("tool-registry threading", () => { test("the default payload the boot edge serializes parses to the public npmjs registry", () => { const registries = parseToolRegistries(DEFAULT_TOOL_REGISTRIES_JSON); - expect([...registries.entries()]).toEqual([ - ["npmjs", { url: "https://registry.npmjs.org" }], - ]); + expect([...registries.entries()]).toEqual([["npmjs", { url: "https://registry.npmjs.org" }]]); }); test("parseToolRegistries collapses the wire array into a map keyed by name", () => { @@ -379,9 +352,7 @@ describe("tool-registry threading", () => { test("malformed JSON, wrong shapes, and duplicate names fail loud", () => { expect(() => parseToolRegistries("{not json")).toThrow(/not valid JSON/); - expect(() => parseToolRegistries(JSON.stringify({ npmjs: {} }))).toThrow( - /failed validation/, - ); + expect(() => parseToolRegistries(JSON.stringify({ npmjs: {} }))).toThrow(/failed validation/); expect(() => parseToolRegistries( JSON.stringify([ diff --git a/apps/sidecar/test/workflow-deploy-lifecycle.test.ts b/apps/sidecar/test/workflow-deploy-lifecycle.test.ts index 0eb3109eaa..067c53ed5c 100644 --- a/apps/sidecar/test/workflow-deploy-lifecycle.test.ts +++ b/apps/sidecar/test/workflow-deploy-lifecycle.test.ts @@ -44,9 +44,7 @@ describe("workflow deployment lifecycle through the deploy router", () => { stepOrder: ["body-step"], steps: { "body-step": { kind: "step" } }, }; - frame.workflow.referencedDefinitions = [ - { definition: bodyDefinition, sources: bodySources }, - ]; + frame.workflow.referencedDefinitions = [{ definition: bodyDefinition, sources: bodySources }]; const deployPromise = router.deploy(frame); await answerReadyHandshake(spawns, 0); @@ -55,16 +53,14 @@ describe("workflow deployment lifecycle through the deploy router", () => { // A body child runs in-process and loses its env across a restart, so its // per-step source pins must be durable on disk. const bodyDir = path.join(dataDir, "assets", "workflow", bodyDefinition.id); - expect( - JSON.parse(await fs.readFile(path.join(bodyDir, "sources.json"), "utf8")), - ).toEqual(bodySources); + expect(JSON.parse(await fs.readFile(path.join(bodyDir, "sources.json"), "utf8"))).toEqual( + bodySources, + ); // The body DEFINITION is never staged: the run child resolves each body // in-memory from the parent's re-verified closure. A staged copy would be // a second, un-verified source of the body's bytes. - await expect( - fs.stat(path.join(bodyDir, "workflow.json")), - ).rejects.toThrow(); + await expect(fs.stat(path.join(bodyDir, "workflow.json"))).rejects.toThrow(); await expect( fs.stat(path.join(dataDir, "assets", "workflow", "wf-lifecycle")), ).rejects.toThrow(); @@ -82,9 +78,7 @@ describe("workflow deployment lifecycle through the deploy router", () => { // The child EVALUATES the pinned code from this dir rather than reading an // inert definition off disk, and re-verifies its projection against the // hub-approved hash -- never a sidecar recompute. - expect(spawn.env.CLOSURE_PACKAGE_DIR).toBe( - path.join(dataDir, "closure-package", deploymentId), - ); + expect(spawn.env.CLOSURE_PACKAGE_DIR).toBe(path.join(dataDir, "closure-package", deploymentId)); expect(spawn.env.DEFINITION_HASH).toBe(LIFECYCLE_APPROVED_WIRE_HASH); expect(spawn.env.WORKFLOW_DEFINITION_REF).toBeUndefined(); expect(spawn.env.REFERENCED_DEFINITION_HASHES).toBeUndefined(); @@ -119,12 +113,7 @@ describe("workflow deployment lifecycle through the deploy router", () => { expect(router.activeAddresses()).toEqual([frame.agentAddress]); // The durable deployment record the next boot restores from. const deploymentId = deriveDeploymentId(frame.agentAddress); - const recordFile = path.join( - dataDir, - "workflow-runs", - deploymentId, - "deployment.json", - ); + const recordFile = path.join(dataDir, "workflow-runs", deploymentId, "deployment.json"); await expect(fs.stat(recordFile)).resolves.toBeDefined(); }); @@ -136,12 +125,7 @@ describe("workflow deployment lifecycle through the deploy router", () => { await deployPromise; const deploymentId = deriveDeploymentId(frame.agentAddress); - const recordFile = path.join( - dataDir, - "workflow-runs", - deploymentId, - "deployment.json", - ); + const recordFile = path.join(dataDir, "workflow-runs", deploymentId, "deployment.json"); const durableConversationFile = path.join( dataDir, "agent-conversation-state", @@ -246,15 +230,11 @@ describe("workflow deployment lifecycle through the deploy router", () => { // The deployment's whole step-state subtree is reclaimed -- warm // stable workspace AND any cold leftover -- now that its supervisor // and child are torn down. - await expect( - fs.stat(path.join(stepStateRoot, deploymentId)), - ).rejects.toThrow(); + await expect(fs.stat(path.join(stepStateRoot, deploymentId))).rejects.toThrow(); // So is the deployment record: an undeployed deployment must not be // restored on the next boot. await expect( - fs.stat( - path.join(dataDir, "workflow-runs", deploymentId, "deployment.json"), - ), + fs.stat(path.join(dataDir, "workflow-runs", deploymentId, "deployment.json")), ).rejects.toThrow(); // A different deployment's scratch is untouched: the sweep is scoped // to this deployment's `` subtree only. @@ -269,8 +249,7 @@ describe("workflow deployment lifecycle through the deploy router", () => { // port, no `MultistepCredentialsRouter` handler was ever installed, so an // inbound `credentials.update` frame was unrouted for every deployment. test("a hub credentials.update frame reaches the child as a credentials-updated control frame", async () => { - const { router, spawns, multistepCredentialsRouter } = - await makeLifecycleFixture(); + const { router, spawns, multistepCredentialsRouter } = await makeLifecycleFixture(); const frame = makeWorkflowFrame("run_lifecycle-credentials@example.com"); const deployPromise = router.deploy(frame); @@ -304,8 +283,8 @@ describe("workflow deployment lifecycle through the deploy router", () => { expect(routed).toBe(true); const credentialsUpdatedLine = spawn.sentControlLines.find( (line) => - (JSON.parse(line) as { envelope: { payload: { type: string } } }) - .envelope.payload.type === "credentials-updated", + (JSON.parse(line) as { envelope: { payload: { type: string } } }).envelope.payload.type === + "credentials-updated", ); if (credentialsUpdatedLine === undefined) { throw new Error( diff --git a/apps/sidecar/test/workflow-grants-barrier.test.ts b/apps/sidecar/test/workflow-grants-barrier.test.ts index cd1a885baa..0002950661 100644 --- a/apps/sidecar/test/workflow-grants-barrier.test.ts +++ b/apps/sidecar/test/workflow-grants-barrier.test.ts @@ -17,8 +17,7 @@ import { createSpawnTestRepoStore } from "./support/workflow-lifecycle-fixture"; const DEPLOYMENT_ID = "dep-barrier"; const STEP_ORDER = ["step-a", "step-b"]; -const deriveStepAddress = ({ stepId }: { stepId: string }) => - `${DEPLOYMENT_ID}-${stepId}@local`; +const deriveStepAddress = ({ stepId }: { stepId: string }) => `${DEPLOYMENT_ID}-${stepId}@local`; // Wrapped in an object: the stub store is a Proxy that throws on any // unknown property, so returning it bare from an async function makes the @@ -87,8 +86,6 @@ describe("assembleRunCredentialsSnapshot", () => { expect(step.grants).toEqual(grants); expect(step.address).toBe(deriveStepAddress({ stepId: step.stepId })); } - expect(new Set(snapshot.steps.map((step) => step.contentHash)).size).toBe( - 1, - ); + expect(new Set(snapshot.steps.map((step) => step.contentHash)).size).toBe(1); }); }); diff --git a/apps/sidecar/test/workflow-restore-defer-to-wake.test.ts b/apps/sidecar/test/workflow-restore-defer-to-wake.test.ts index 6fe56181f6..285fe5bca9 100644 --- a/apps/sidecar/test/workflow-restore-defer-to-wake.test.ts +++ b/apps/sidecar/test/workflow-restore-defer-to-wake.test.ts @@ -19,11 +19,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, expect, test } from "bun:test"; -import { - createEd25519Crypto, - generateKeyPair, - verifySSHSignature, -} from "@intx/crypto"; +import { createEd25519Crypto, generateKeyPair, verifySSHSignature } from "@intx/crypto"; import { createAgentKeyStore, createAgentRepoStore as createSidecarSideRepoStore, @@ -49,9 +45,7 @@ import { const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeDataDir(): Promise { @@ -96,9 +90,7 @@ async function makeRouter( materializeDeploymentClosure: ({ deploymentId }) => { const definition = closureDefinitions.get(deploymentId); if (definition === undefined) { - throw new Error( - `test closure materializer: no definition registered for ${deploymentId}`, - ); + throw new Error(`test closure materializer: no definition registered for ${deploymentId}`); } return Promise.resolve({ definition, diff --git a/apps/sidecar/test/workflow-restore-quarantine.test.ts b/apps/sidecar/test/workflow-restore-quarantine.test.ts index 829f1c4146..35e9c49efb 100644 --- a/apps/sidecar/test/workflow-restore-quarantine.test.ts +++ b/apps/sidecar/test/workflow-restore-quarantine.test.ts @@ -13,11 +13,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, expect, test } from "bun:test"; -import { - createEd25519Crypto, - generateKeyPair, - verifySSHSignature, -} from "@intx/crypto"; +import { createEd25519Crypto, generateKeyPair, verifySSHSignature } from "@intx/crypto"; import { createAgentKeyStore, createAgentRepoStore as createSidecarSideRepoStore, @@ -44,9 +40,7 @@ import { const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeDataDir(): Promise { @@ -101,9 +95,7 @@ async function makeRouter( } const definition = closureDefinitions.get(deploymentId); if (definition === undefined) { - throw new Error( - `test closure materializer: no definition registered for ${deploymentId}`, - ); + throw new Error(`test closure materializer: no definition registered for ${deploymentId}`); } return Promise.resolve({ definition, @@ -191,32 +183,19 @@ test("a permanently unrestorable record is quarantined after RESTORE_QUARANTINE_ for (let boot = 1; boot <= RESTORE_QUARANTINE_THRESHOLD; boot++) { await router.restoreWorkflowDeployments(); - const onDisk = await readWorkflowDeploymentRecord( - dataDir, - mismatchedDeploymentId, - ); + const onDisk = await readWorkflowDeploymentRecord(dataDir, mismatchedDeploymentId); expect(onDisk?.restoreFailure?.kind).toBe("permanent"); expect(onDisk?.restoreFailure?.attempts).toBe(boot); } - const quarantined = await readWorkflowDeploymentRecord( - dataDir, - mismatchedDeploymentId, - ); - expect(quarantined?.restoreFailure?.attempts).toBe( - RESTORE_QUARANTINE_THRESHOLD, - ); + const quarantined = await readWorkflowDeploymentRecord(dataDir, mismatchedDeploymentId); + expect(quarantined?.restoreFailure?.attempts).toBe(RESTORE_QUARANTINE_THRESHOLD); // One more boot: quarantine means the attempt count does NOT move -- // the record is skipped entirely rather than attempted and re-failed. await router.restoreWorkflowDeployments(); - const afterQuarantineBoot = await readWorkflowDeploymentRecord( - dataDir, - mismatchedDeploymentId, - ); - expect(afterQuarantineBoot?.restoreFailure?.attempts).toBe( - RESTORE_QUARANTINE_THRESHOLD, - ); + const afterQuarantineBoot = await readWorkflowDeploymentRecord(dataDir, mismatchedDeploymentId); + expect(afterQuarantineBoot?.restoreFailure?.attempts).toBe(RESTORE_QUARANTINE_THRESHOLD); // The record itself is never deleted -- an operator can still undeploy // the address to reclaim it. @@ -230,9 +209,7 @@ test("a transiently unbuildable provider is retried every boot and never quarant assertSourceBuildable: (source) => { assertCalls += 1; if (source.provider === "unbuildable") { - throw new Error( - `Source provider "${source.provider}" is not registered`, - ); + throw new Error(`Source provider "${source.provider}" is not registered`); } }, }); @@ -310,11 +287,7 @@ test("a missing/incomplete closure staging directory quarantines as a permanent approvedWireHash: "d".repeat(64), sourceRef: SOURCE_REF, }; - await writeWorkflowDeploymentRecord( - dataDir, - brokenDeploymentId, - brokenRecord, - ); + await writeWorkflowDeploymentRecord(dataDir, brokenDeploymentId, brokenRecord); const healthyRecord: WorkflowDeploymentRecord = { version: 2, @@ -335,29 +308,19 @@ test("a missing/incomplete closure staging directory quarantines as a permanent approvedWireHash: "d".repeat(64), sourceRef: SOURCE_REF, }; - await writeWorkflowDeploymentRecord( - dataDir, - healthyDeploymentId, - healthyRecord, - ); + await writeWorkflowDeploymentRecord(dataDir, healthyDeploymentId, healthyRecord); for (let boot = 1; boot <= RESTORE_QUARANTINE_THRESHOLD; boot++) { // Boot completes without throwing -- the ENOENT never escapes the // restore loop, however it is classified. await expect(router.restoreWorkflowDeployments()).resolves.toBeUndefined(); - const broken = await readWorkflowDeploymentRecord( - dataDir, - brokenDeploymentId, - ); + const broken = await readWorkflowDeploymentRecord(dataDir, brokenDeploymentId); expect(broken?.restoreFailure?.kind).toBe("permanent"); expect(broken?.restoreFailure?.attempts).toBe(boot); // The sibling deployment's own restore was attempted on every boot, // unaffected by the broken record ahead of (or behind) it in the scan. - const healthy = await readWorkflowDeploymentRecord( - dataDir, - healthyDeploymentId, - ); + const healthy = await readWorkflowDeploymentRecord(dataDir, healthyDeploymentId); expect(healthy?.restoreFailure?.kind).toBe("transient"); expect(healthy?.restoreFailure?.attempts).toBe(boot); } @@ -365,11 +328,6 @@ test("a missing/incomplete closure staging directory quarantines as a permanent // One more boot: quarantined now, so the attempt count stops moving -- // the record is skipped rather than re-attempted and re-failed. await router.restoreWorkflowDeployments(); - const afterQuarantine = await readWorkflowDeploymentRecord( - dataDir, - brokenDeploymentId, - ); - expect(afterQuarantine?.restoreFailure?.attempts).toBe( - RESTORE_QUARANTINE_THRESHOLD, - ); + const afterQuarantine = await readWorkflowDeploymentRecord(dataDir, brokenDeploymentId); + expect(afterQuarantine?.restoreFailure?.attempts).toBe(RESTORE_QUARANTINE_THRESHOLD); }); diff --git a/apps/sidecar/test/workflow-restore-timeout-cancel.test.ts b/apps/sidecar/test/workflow-restore-timeout-cancel.test.ts index 3e4fc4a1c5..93f248e191 100644 --- a/apps/sidecar/test/workflow-restore-timeout-cancel.test.ts +++ b/apps/sidecar/test/workflow-restore-timeout-cancel.test.ts @@ -21,17 +21,12 @@ import { writeWorkflowDeploymentRecord, type WorkflowDeploymentRecord, } from "../src/workflow-deployment-record"; -import { - answerReadyHandshake, - makeLifecycleFixture, -} from "./support/workflow-lifecycle-fixture"; +import { answerReadyHandshake, makeLifecycleFixture } from "./support/workflow-lifecycle-fixture"; const tempDirs: string[] = []; afterEach(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), - ); + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); async function makeDataDir(): Promise { @@ -121,10 +116,7 @@ test("a restore that outlasts its timeout corrects its record once the spawn act const restorePromise = router.restoreWorkflowDeployments(); await restorePromise; - const afterTimeout = await readWorkflowDeploymentRecord( - dataDir, - deploymentId, - ); + const afterTimeout = await readWorkflowDeploymentRecord(dataDir, deploymentId); expect(afterTimeout?.restoreFailure).toBeDefined(); expect(router.activeAddresses()).toEqual([]); @@ -142,10 +134,7 @@ test("a restore that outlasts its timeout corrects its record once the spawn act let afterLateRestore: WorkflowDeploymentRecord | undefined; const deadline = Date.now() + 5000; while (Date.now() < deadline) { - afterLateRestore = await readWorkflowDeploymentRecord( - dataDir, - deploymentId, - ); + afterLateRestore = await readWorkflowDeploymentRecord(dataDir, deploymentId); if (afterLateRestore?.restoreFailure === undefined) break; await new Promise((r) => setTimeout(r, 5)); } @@ -238,10 +227,7 @@ test("a reclaiming teardown racing a dangling restore's boot-failure write is no // Poll for the unwind: it is a background continuation of the restore // attempt, not something `restoreWorkflowDeployments()` awaits. const deadline = Date.now() + 10_000; - while ( - Date.now() < deadline && - router.activeAddresses().includes(agentAddress) - ) { + while (Date.now() < deadline && router.activeAddresses().includes(agentAddress)) { await new Promise((r) => setTimeout(r, 5)); } diff --git a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts index da126f8fcf..3856f34238 100644 --- a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts +++ b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts @@ -17,10 +17,7 @@ import { WORKFLOW_RUN_GITIGNORE_PATH, } from "@intx/hub-sessions"; import type { AuthorizeFn } from "@intx/hub-sessions"; -import type { - RepoId, - WorkflowRunWorkflowProcessPrincipal, -} from "@intx/hub-sessions/substrate"; +import type { RepoId, WorkflowRunWorkflowProcessPrincipal } from "@intx/hub-sessions/substrate"; import type { KeyPair } from "@intx/types/runtime"; import { createInMemoryRepoStore, @@ -28,10 +25,7 @@ import { defineWorkflow, step, } from "@intx/workflow"; -import { - createWorkflowHostSignalChannel, - type RunChildWorkflow, -} from "@intx/workflow-host"; +import { createWorkflowHostSignalChannel, type RunChildWorkflow } from "@intx/workflow-host"; import { createSidecarRunChild, @@ -68,13 +62,9 @@ afterAll(async () => { test("a cancel rejection during abort is reported and is not unhandled", async () => { const forced = new Error("forced cancel reject"); const cancelMock = mock(() => Promise.reject(forced)); - const reportErrorMock = mock( - (_error: unknown, _context: unknown) => "ref-test", - ); + const reportErrorMock = mock((_error: unknown, _context: unknown) => "ref-test"); - const dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), "run-child-cancel-report-"), - ); + const dataDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "run-child-cancel-report-")); tempDirs.push(dataDir); const substrate = createRepoStore({ dataDir, @@ -157,13 +147,9 @@ test("a cancel rejection during abort is reported and is not unhandled", async ( test("a signalChannel.stop rejection is reported and is not unhandled", async () => { const forced = new Error("forced stop reject"); const stopMock = mock(() => Promise.reject(forced)); - const reportErrorMock = mock( - (_error: unknown, _context: unknown) => "ref-test", - ); + const reportErrorMock = mock((_error: unknown, _context: unknown) => "ref-test"); - const dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), "run-child-stop-report-"), - ); + const dataDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "run-child-stop-report-")); tempDirs.push(dataDir); const substrate = createRepoStore({ dataDir, @@ -253,9 +239,7 @@ test("a signalChannel.stop rejection is reported and is not unhandled", async () test("a spawn-suspendable abort cancel rejection is reported and is not unhandled", async () => { const forced = new Error("forced suspendable cancel reject"); const cancelMock = mock(() => Promise.reject(forced)); - const reportErrorMock = mock( - (_error: unknown, _context: unknown) => "ref-test", - ); + const reportErrorMock = mock((_error: unknown, _context: unknown) => "ref-test"); const dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), "run-suspendable-cancel-report-"), diff --git a/apps/sidecar/test/workflow-substrate-factory-run-child.test.ts b/apps/sidecar/test/workflow-substrate-factory-run-child.test.ts index 311ae67251..75d3cb70f9 100644 --- a/apps/sidecar/test/workflow-substrate-factory-run-child.test.ts +++ b/apps/sidecar/test/workflow-substrate-factory-run-child.test.ts @@ -23,10 +23,7 @@ import { WORKFLOW_RUN_GITIGNORE_PATH, } from "@intx/hub-sessions"; import type { AuthorizeFn } from "@intx/hub-sessions"; -import type { - RepoId, - WorkflowRunWorkflowProcessPrincipal, -} from "@intx/hub-sessions/substrate"; +import type { RepoId, WorkflowRunWorkflowProcessPrincipal } from "@intx/hub-sessions/substrate"; import { createInMemoryRepoStore, createInMemoryScheduler, @@ -66,9 +63,7 @@ afterAll(async () => { } }); -async function makeSubstrate( - prefix: string, -): Promise> { +async function makeSubstrate(prefix: string): Promise> { const dataDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), prefix)); tempDirs.push(dataDir); const substrate = createRepoStore({ @@ -121,10 +116,7 @@ const completeImmediately: StepInvoker = async () => ({ output: { done: true }, }); -async function makeRunChild( - prefix: string, - invokeStep: StepInvoker, -): Promise { +async function makeRunChild(prefix: string, invokeStep: StepInvoker): Promise { const substrate = await makeSubstrate(prefix); return createSidecarRunChild({ substrate, @@ -139,10 +131,7 @@ async function makeRunChild( }); } -function childInput( - id: string, - signal: AbortSignal, -): Parameters[0] { +function childInput(id: string, signal: AbortSignal): Parameters[0] { return { definition: childDefinition(`child-wf-${id}`), definitionRef: REF, @@ -158,10 +147,7 @@ function childInput( describe("createSidecarRunChild", () => { test("an unaborted child runs to a completed terminal", async () => { - const runChild = await makeRunChild( - "run-child-complete-", - completeImmediately, - ); + const runChild = await makeRunChild("run-child-complete-", completeImmediately); const result = await runChild( childInput("complete", new AbortController().signal), () => undefined, @@ -181,10 +167,7 @@ describe("createSidecarRunChild", () => { const runChild = await makeRunChild("run-child-abort-", parkAndAnnounce); const abort = new AbortController(); - const pending = runChild( - childInput("abort", abort.signal), - () => undefined, - ); + const pending = runChild(childInput("abort", abort.signal), () => undefined); // Wait on the step actually being entered, not on a fixed delay: the // cancel has to interrupt a run in flight, and a machine slow enough @@ -203,10 +186,7 @@ describe("createSidecarRunChild", () => { const runChild = await makeRunChild("run-child-preabort-", parkForever); const abort = new AbortController(); abort.abort(); - const result = await runChild( - childInput("preabort", abort.signal), - () => undefined, - ); + const result = await runChild(childInput("preabort", abort.signal), () => undefined); expect(result.terminalStatus).toBe("cancelled"); }); }); diff --git a/apps/sidecar/test/workflow-substrate-factory-suspendable-child.test.ts b/apps/sidecar/test/workflow-substrate-factory-suspendable-child.test.ts index 759d4f3c8b..c068986c4a 100644 --- a/apps/sidecar/test/workflow-substrate-factory-suspendable-child.test.ts +++ b/apps/sidecar/test/workflow-substrate-factory-suspendable-child.test.ts @@ -19,11 +19,7 @@ import path from "node:path"; import { generateKeyPair } from "@intx/crypto"; import type { AuthzCallResult } from "@intx/inference"; -import type { - ApprovalSnapshot, - KeyPair, - MailPartReader, -} from "@intx/types/runtime"; +import type { ApprovalSnapshot, KeyPair, MailPartReader } from "@intx/types/runtime"; import { defineAgent } from "@intx/agent"; import { createRepoStore, @@ -31,10 +27,7 @@ import { WORKFLOW_RUN_GITIGNORE_PATH, } from "@intx/hub-sessions"; import type { AuthorizeFn } from "@intx/hub-sessions"; -import type { - RepoId, - WorkflowRunWorkflowProcessPrincipal, -} from "@intx/hub-sessions/substrate"; +import type { RepoId, WorkflowRunWorkflowProcessPrincipal } from "@intx/hub-sessions/substrate"; import { createInMemoryRepoStore, createInMemoryScheduler, @@ -116,9 +109,7 @@ function bodyDefinition(id: string): WorkflowDefinition { }); } -async function makeSubstrate( - prefix: string, -): Promise> { +async function makeSubstrate(prefix: string): Promise> { const dataDir = await makeTempDir(prefix); const substrate = createRepoStore({ dataDir, @@ -135,9 +126,7 @@ async function makeSubstrate( // An invoker that suspends as an approval on its first invocation and, on // the resume re-invocation, records the delivered decision and completes. -function suspendThenComplete(record: { - resumeDecision?: unknown; -}): StepInvoker { +function suspendThenComplete(record: { resumeDecision?: unknown }): StepInvoker { return async (req) => { if (req.resume === undefined) { return { @@ -234,17 +223,10 @@ describe("createSidecarSpawnSuspendableChild", () => { const dataDir = await makeTempDir("suspendable-body-datadir-"); const sourcesDir = path.join(dataDir, "assets", "workflow", "body-wf-real"); await fs.promises.mkdir(sourcesDir, { recursive: true }); - await fs.promises.writeFile( - path.join(sourcesDir, "sources.json"), - JSON.stringify(bodySources), - ); + await fs.promises.writeFile(path.join(sourcesDir, "sources.json"), JSON.stringify(bodySources)); const seen: { sources?: unknown; agentId?: string } = {}; - const bodyInvokeStep: SidecarBodyStepInvoker = async ( - req, - _authorize, - sourcesRef, - ) => { + const bodyInvokeStep: SidecarBodyStepInvoker = async (req, _authorize, sourcesRef) => { seen.sources = sourcesRef.current; seen.agentId = req.agent.id; return { output: { done: true } }; @@ -299,12 +281,7 @@ describe("createSidecarSpawnSuspendableChild", () => { test("the body invoker receives spawn-input authorize, credentialWiring, and mailPartReader", async () => { const substrate = await makeSubstrate("suspendable-body-authorize-"); const dataDir = await makeTempDir("suspendable-body-authz-datadir-"); - const sourcesDir = path.join( - dataDir, - "assets", - "workflow", - "body-wf-authz", - ); + const sourcesDir = path.join(dataDir, "assets", "workflow", "body-wf-authz"); await fs.promises.mkdir(sourcesDir, { recursive: true }); await fs.promises.writeFile( path.join(sourcesDir, "sources.json"), diff --git a/apps/sidecar/test/workflow-suspend-identity-preservation.test.ts b/apps/sidecar/test/workflow-suspend-identity-preservation.test.ts index 9d1c455fa7..8ae09c0134 100644 --- a/apps/sidecar/test/workflow-suspend-identity-preservation.test.ts +++ b/apps/sidecar/test/workflow-suspend-identity-preservation.test.ts @@ -12,11 +12,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { - createAgentKeyStore, - createAgentRepoStore, - agentDir, -} from "@intx/hub-agent"; +import { createAgentKeyStore, createAgentRepoStore, agentDir } from "@intx/hub-agent"; import { generateKeyPair, verifySSHSignature } from "@intx/crypto"; import { hexEncode } from "@intx/types"; @@ -45,9 +41,7 @@ async function makeFrameWithRealHubKey(agentAddress: string) { describe("hibernate/wake preserves the real deployed agent's identity", () => { test("hibernate teardown, then redeploy: the same keypair comes back (isNew: false)", async () => { - const dataDir = await fs.mkdtemp( - path.join(os.tmpdir(), "sidecar-suspend-identity-"), - ); + const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "sidecar-suspend-identity-")); const keyStore = createAgentKeyStore({ dataDir, ...cryptoOps }); // The real, unmodified package's own repo store -- its `remove` is // exactly the destructive call `@intx/hub-agent`'s `ws/hub-link.js` @@ -71,9 +65,7 @@ describe("hibernate/wake preserves the real deployed agent's identity", () => { await router.teardownDeployment(address, { reclaimDirs: false }); // Models the published package's own post-hook delete. await realRepoStore.remove(address); - expect(await fs.readdir(dataDir)).not.toContain( - path.basename(agentDir(dataDir, address)), - ); + expect(await fs.readdir(dataDir)).not.toContain(path.basename(agentDir(dataDir, address))); const frame2 = await makeFrameWithRealHubKey(address); const deploy2 = router.deploy(frame2); @@ -89,9 +81,7 @@ describe("hibernate/wake preserves the real deployed agent's identity", () => { }); test("reclaiming (non-hibernate) teardown, then redeploy: a fresh keypair comes back", async () => { - const dataDir = await fs.mkdtemp( - path.join(os.tmpdir(), "sidecar-suspend-identity-"), - ); + const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "sidecar-suspend-identity-")); const keyStore = createAgentKeyStore({ dataDir, ...cryptoOps }); const realRepoStore = createAgentRepoStore({ dataDir }); diff --git a/apps/sidecar/test/workflow-teardown-flavor.test.ts b/apps/sidecar/test/workflow-teardown-flavor.test.ts index ecad18a7b9..905795710d 100644 --- a/apps/sidecar/test/workflow-teardown-flavor.test.ts +++ b/apps/sidecar/test/workflow-teardown-flavor.test.ts @@ -11,10 +11,7 @@ import { describe, test, expect } from "bun:test"; import fs from "node:fs/promises"; import path from "node:path"; -import { - deriveDeploymentId, - IDLE_HIBERNATE_UNDEPLOY_REASON, -} from "../src/workflow-host-wiring"; +import { deriveDeploymentId, IDLE_HIBERNATE_UNDEPLOY_REASON } from "../src/workflow-host-wiring"; import { readWorkflowDeploymentRecord } from "../src/workflow-deployment-record"; import { answerReadyHandshake, @@ -31,17 +28,8 @@ describe("teardownDeployment reclaimDirs flavors", () => { await deployPromise; const deploymentId = deriveDeploymentId(frame.agentAddress); - const recordFile = path.join( - dataDir, - "workflow-runs", - deploymentId, - "deployment.json", - ); - const stepStateDir = path.join( - dataDir, - "workflow-step-state", - deploymentId, - ); + const recordFile = path.join(dataDir, "workflow-runs", deploymentId, "deployment.json"); + const stepStateDir = path.join(dataDir, "workflow-step-state", deploymentId); await fs.mkdir(stepStateDir, { recursive: true }); await fs.writeFile(path.join(stepStateDir, "marker.txt"), "x"); @@ -63,17 +51,8 @@ describe("teardownDeployment reclaimDirs flavors", () => { await deployPromise; const deploymentId = deriveDeploymentId(frame.agentAddress); - const recordFile = path.join( - dataDir, - "workflow-runs", - deploymentId, - "deployment.json", - ); - const stepStateDir = path.join( - dataDir, - "workflow-step-state", - deploymentId, - ); + const recordFile = path.join(dataDir, "workflow-runs", deploymentId, "deployment.json"); + const stepStateDir = path.join(dataDir, "workflow-step-state", deploymentId); await fs.mkdir(stepStateDir, { recursive: true }); await fs.writeFile(path.join(stepStateDir, "marker.txt"), "x"); @@ -86,9 +65,7 @@ describe("teardownDeployment reclaimDirs flavors", () => { // ...but every piece of durable state a relaunch resumes from survives. await expect(fs.stat(recordFile)).resolves.toBeDefined(); await expect(fs.stat(stepStateDir)).resolves.toBeDefined(); - expect( - await fs.readFile(path.join(stepStateDir, "marker.txt"), "utf8"), - ).toBe("x"); + expect(await fs.readFile(path.join(stepStateDir, "marker.txt"), "utf8")).toBe("x"); // The address is no longer live/routable: a relaunch re-establishes it // through the ordinary deploy path, not a resumed registration. expect(router.activeAddresses()).toEqual([]); @@ -112,9 +89,7 @@ describe("teardownDeployment reclaimDirs flavors", () => { reclaimDirs: true, }); - await expect( - readWorkflowDeploymentRecord(dataDir, deploymentId), - ).resolves.toBeUndefined(); + await expect(readWorkflowDeploymentRecord(dataDir, deploymentId)).resolves.toBeUndefined(); }); test("both flavors unregister the transport, routers, and deployment-address mapping", async () => { @@ -132,10 +107,7 @@ describe("teardownDeployment reclaimDirs flavors", () => { // No live mail/credentials handler remains registered for the address: // routing a frame against it fails to find a handler. expect( - multistepMailRouter.tryRoute( - frame.agentAddress, - new TextEncoder().encode("post-hibernate"), - ), + multistepMailRouter.tryRoute(frame.agentAddress, new TextEncoder().encode("post-hibernate")), ).toBeNull(); expect( await multistepCredentialsRouter.tryRoute({ @@ -148,15 +120,11 @@ describe("teardownDeployment reclaimDirs flavors", () => { test("undeploy(frame) picks the flavor from frame.reason", async () => { const hibernating = await makeLifecycleFixture(); - const hibernateFrame = makeWorkflowFrame( - "run_undeploy-reason-hibernate@example.com", - ); + const hibernateFrame = makeWorkflowFrame("run_undeploy-reason-hibernate@example.com"); const hibernateDeploy = hibernating.router.deploy(hibernateFrame); await answerReadyHandshake(hibernating.spawns, 0); await hibernateDeploy; - const hibernateDeploymentId = deriveDeploymentId( - hibernateFrame.agentAddress, - ); + const hibernateDeploymentId = deriveDeploymentId(hibernateFrame.agentAddress); const hibernateStepStateDir = path.join( hibernating.dataDir, "workflow-step-state", @@ -174,9 +142,7 @@ describe("teardownDeployment reclaimDirs flavors", () => { await expect(fs.stat(hibernateStepStateDir)).resolves.toBeDefined(); const reclaiming = await makeLifecycleFixture(); - const reclaimFrame = makeWorkflowFrame( - "run_undeploy-reason-reclaim@example.com", - ); + const reclaimFrame = makeWorkflowFrame("run_undeploy-reason-reclaim@example.com"); const reclaimDeploy = reclaiming.router.deploy(reclaimFrame); await answerReadyHandshake(reclaiming.spawns, 0); await reclaimDeploy; @@ -221,10 +187,7 @@ describe("teardownDeployment reclaimDirs flavors", () => { }); const deploymentId = deriveDeploymentId(frame.agentAddress); - const parkedBeforeRedeploy = await readWorkflowDeploymentRecord( - dataDir, - deploymentId, - ); + const parkedBeforeRedeploy = await readWorkflowDeploymentRecord(dataDir, deploymentId); expect(parkedBeforeRedeploy?.parkedAt).toBeDefined(); const secondDeploy = router.deploy(frame); @@ -232,10 +195,7 @@ describe("teardownDeployment reclaimDirs flavors", () => { await expect(secondDeploy).resolves.toBeDefined(); expect(router.activeAddresses()).toEqual([frame.agentAddress]); - const redeployedRecord = await readWorkflowDeploymentRecord( - dataDir, - deploymentId, - ); + const redeployedRecord = await readWorkflowDeploymentRecord(dataDir, deploymentId); expect(redeployedRecord?.parkedAt).toBeUndefined(); }); }); diff --git a/apps/web/package.json b/apps/web/package.json index c57af9e4af..fa2c4cf205 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,8 +1,8 @@ { "name": "@workbench/web", + "version": "0.0.1", "private": true, "description": "Single-page interface for the workbench: every page and route lives here, composed from the published component library, and builds to a static bundle the hub serves from its own origin (point HUB_STATIC_DIR at apps/web/dist)", - "version": "0.0.1", "license": "SEE LICENSE IN ../../LICENSE.md", "repository": { "type": "git", @@ -26,8 +26,10 @@ "@corbits/chat-ui": "workspace:*", "@corbits/client-log": "workspace:*", "@corbits/command-palette": "workspace:*", + "@corbits/connections": "workspace:*", "@corbits/context-menu": "workspace:*", "@corbits/error-sink": "workspace:*", + "@corbits/icons": "workspace:*", "@corbits/inbox": "workspace:*", "@corbits/inference-settings": "workspace:*", "@corbits/plugins-ui": "workspace:*", @@ -38,12 +40,10 @@ "@corbits/text-diff": "workspace:*", "@corbits/url-path": "workspace:*", "@corbits/workflows": "workspace:*", - "@corbits/icons": "workspace:*", "@intx/types": "workspace:*", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-slot": "^1.2.3", "@tanstack/react-query": "catalog:", - "@corbits/connections": "workspace:*", "arktype": "catalog:", "react": "^19.2.0", "react-dom": "^19.2.0" diff --git a/apps/web/src/activatable-row.test.ts b/apps/web/src/activatable-row.test.ts index 95bd78af1b..54bcbadcc7 100644 --- a/apps/web/src/activatable-row.test.ts +++ b/apps/web/src/activatable-row.test.ts @@ -1,10 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test"; -import { - isAdditiveSelectClick, - isRowActivationKey, - rowActivationProps, -} from "./activatable-row"; +import { isAdditiveSelectClick, isRowActivationKey, rowActivationProps } from "./activatable-row"; // `isAdditiveSelectClick`'s Mac/non-Mac branch reads `navigator.platform`, // which happy-dom's `GlobalRegistrator` reports as whatever the *host* OS @@ -33,9 +29,7 @@ describe("isAdditiveSelectClick", () => { test("ctrl-click is not additive on Mac (it's the context-menu gesture)", () => { stubPlatform("MacIntel"); - expect(isAdditiveSelectClick({ metaKey: false, ctrlKey: true })).toBe( - false, - ); + expect(isAdditiveSelectClick({ metaKey: false, ctrlKey: true })).toBe(false); }); test("ctrl-click is additive on non-Mac", () => { @@ -44,9 +38,7 @@ describe("isAdditiveSelectClick", () => { }); test("a plain click is not additive", () => { - expect(isAdditiveSelectClick({ metaKey: false, ctrlKey: false })).toBe( - false, - ); + expect(isAdditiveSelectClick({ metaKey: false, ctrlKey: false })).toBe(false); }); }); diff --git a/apps/web/src/activatable-row.ts b/apps/web/src/activatable-row.ts index a896ad1279..4206187148 100644 --- a/apps/web/src/activatable-row.ts +++ b/apps/web/src/activatable-row.ts @@ -27,9 +27,7 @@ export function isAdditiveSelectClick(event: { function isMacPlatform(): boolean { // Browsers report "MacIntel"; happy-dom (our test DOM) reports // "X11; Darwin arm64" — both are the same Ctrl-click-is-context-menu OS. - return ( - typeof navigator !== "undefined" && /mac|darwin/i.test(navigator.platform) - ); + return typeof navigator !== "undefined" && /mac|darwin/i.test(navigator.platform); } export function rowActivationProps(onSelect: () => void) { diff --git a/apps/web/src/agent-chat-launch.test.ts b/apps/web/src/agent-chat-launch.test.ts index 96f801af0d..4ed0d39c30 100644 --- a/apps/web/src/agent-chat-launch.test.ts +++ b/apps/web/src/agent-chat-launch.test.ts @@ -14,8 +14,7 @@ describe("launchAgentChat", () => { function stubFetch(respond: (path: string) => Response): RecordedCall[] { const calls: RecordedCall[] = []; globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { - const path = - typeof input === "string" ? input : new URL(String(input)).pathname; + const path = typeof input === "string" ? input : new URL(String(input)).pathname; calls.push(init === undefined ? { path } : { path, init }); return Promise.resolve(respond(path)); }) as typeof fetch; diff --git a/apps/web/src/agent-dm-launch.test.ts b/apps/web/src/agent-dm-launch.test.ts index ccab573f02..d5dff8bfce 100644 --- a/apps/web/src/agent-dm-launch.test.ts +++ b/apps/web/src/agent-dm-launch.test.ts @@ -14,8 +14,7 @@ describe("openAgentDmChat", () => { function stubFetch(respond: (path: string) => Response): RecordedCall[] { const calls: RecordedCall[] = []; globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { - const path = - typeof input === "string" ? input : new URL(String(input)).pathname; + const path = typeof input === "string" ? input : new URL(String(input)).pathname; calls.push(init === undefined ? { path } : { path, init }); return Promise.resolve(respond(path)); }) as typeof fetch; @@ -43,9 +42,7 @@ describe("openAgentDmChat", () => { throw new Error(`unexpected fetch: ${path}`); }); - await openAgentDmChat("tnt_root", "wfd_outreach", (to) => - navigated.push(to), - ); + await openAgentDmChat("tnt_root", "wfd_outreach", (to) => navigated.push(to)); const call = calls.find((c) => c.path.endsWith("/chat/workbenches")); expect(call?.path).toBe("/api/tenants/tnt_root/chat/workbenches"); diff --git a/apps/web/src/agents-api.ts b/apps/web/src/agents-api.ts index afe0408662..c8df8a03a4 100644 --- a/apps/web/src/agents-api.ts +++ b/apps/web/src/agents-api.ts @@ -18,11 +18,7 @@ import type { ArkErrors } from "arktype"; import { useQuery } from "@tanstack/react-query"; import type { APIQuery } from "@corbits/api-query"; -import { - ApiQueryError, - UnauthenticatedError, - toAPIQuery, -} from "@corbits/api-query"; +import { ApiQueryError, UnauthenticatedError, toAPIQuery } from "@corbits/api-query"; import { isChatPickerModelName } from "@corbits/connections/model-capability"; import { parseErrorEnvelope } from "@corbits/error-sink"; import { tenantKeys } from "./query-client"; @@ -58,19 +54,11 @@ async function getJSON(path: string, schema: Validator): Promise { throw new ApiQueryError("Not signed in.", 401, path); } if (!response.ok) { - throw new ApiQueryError( - `The server answered ${response.status}.`, - response.status, - path, - ); + throw new ApiQueryError(`The server answered ${response.status}.`, response.status, path); } const parsed = schema(await response.json().catch(() => undefined)); if (parsed instanceof type.errors) { - throw new ApiQueryError( - `Unexpected response shape: ${parsed.summary}`, - undefined, - path, - ); + throw new ApiQueryError(`Unexpected response shape: ${parsed.summary}`, undefined, path); } return parsed; } @@ -107,31 +95,22 @@ async function postJSON( } const parsed = schema(json); if (parsed instanceof type.errors) { - throw new ApiQueryError( - `Unexpected response shape: ${parsed.summary}`, - undefined, - path, - ); + throw new ApiQueryError(`Unexpected response shape: ${parsed.summary}`, undefined, path); } return parsed; } -export function listAgentDefinitions( - tenantId: string, -): Promise { +export function listAgentDefinitions(tenantId: string): Promise { return getJSON( `/api/tenants/${tenantId}/workflows/definitions?limit=${PAGE_LIMIT}`, DefinitionsPage, ).then((page) => page.data); } -export function listAgentInstances( - tenantId: string, -): Promise { - return getJSON( - `/api/tenants/${tenantId}/workflows/runs?limit=${PAGE_LIMIT}`, - InstancesPage, - ).then((page) => page.data); +export function listAgentInstances(tenantId: string): Promise { + return getJSON(`/api/tenants/${tenantId}/workflows/runs?limit=${PAGE_LIMIT}`, InstancesPage).then( + (page) => page.data, + ); } /** @@ -145,13 +124,10 @@ export function listAgentInstances( * `getDescendantTenants`; the native listing filters one tenant, so a * workspace parent sees only its own runs, not its child workbenches'. */ -export function listTopLevelRuns( - tenantId: string, -): Promise { - return getJSON( - `/api/tenants/${tenantId}/workflows/runs?limit=${PAGE_LIMIT}`, - InstancesPage, - ).then((page) => page.data); +export function listTopLevelRuns(tenantId: string): Promise { + return getJSON(`/api/tenants/${tenantId}/workflows/runs?limit=${PAGE_LIMIT}`, InstancesPage).then( + (page) => page.data, + ); } /** The tenant's visible, enabled catalog models for the create-agent form's @@ -162,16 +138,10 @@ export function listTopLevelRuns( * and bare `.gguf` names are also omitted (CL-6744) — this endpoint carries * no offering capability lists, so the name-only * {@link isChatPickerModelName} gate is the available signal. */ -export function listCatalogModels( - tenantId: string, -): Promise { - return getJSON( - `/api/tenants/${tenantId}/catalog/models?limit=${PAGE_LIMIT}`, - ModelsPage, - ).then((page) => - page.data.filter( - (model) => !model.disabled && isChatPickerModelName(model.canonicalName), - ), +export function listCatalogModels(tenantId: string): Promise { + return getJSON(`/api/tenants/${tenantId}/catalog/models?limit=${PAGE_LIMIT}`, ModelsPage).then( + (page) => + page.data.filter((model) => !model.disabled && isChatPickerModelName(model.canonicalName)), ); } @@ -184,8 +154,7 @@ const AgentDefinitionDraftResponse = type({ "skills?": "string[]", }, }); -export type AgentDefinitionDraft = - typeof AgentDefinitionDraftResponse.infer.draft; +export type AgentDefinitionDraft = typeof AgentDefinitionDraftResponse.infer.draft; /** * Asks Myra to draft a starting system prompt (and optionally a @@ -232,11 +201,7 @@ export function createAgentDefinition( tenantId: string, input: CreateAgentDefinitionInput, ): Promise { - return postJSON( - `/api/tenants/${tenantId}/agent-definitions`, - CreatedAgentDefinition, - input, - ); + return postJSON(`/api/tenants/${tenantId}/agent-definitions`, CreatedAgentDefinition, input); } const AgentCapabilitiesResponse = type({ @@ -264,10 +229,7 @@ export function getAgentCapabilities( * immutable slug, server-side. A slug-addressed page reads this instead of * scanning the paginated definitions listing, so an agent past that * listing's ceiling still answers on its own URL. */ -export function getAgentDefinitionBySlug( - tenantId: string, - slug: string, -): Promise { +export function getAgentDefinitionBySlug(tenantId: string, slug: string): Promise { return getJSON( `/api/tenants/${tenantId}/agent-definitions/by-name/${encodeURIComponent(slug)}`, WorkflowDefinitionResponse, @@ -434,9 +396,7 @@ type SkillsOutcome = * native `GET /workflows/runs` listing's own predicate — so this page * never has to derive that exclusion itself from a tenant's workbenches. */ -export async function loadAgentDirectory( - tenantId: string, -): Promise { +export async function loadAgentDirectory(tenantId: string): Promise { const [definitions, instances, modelsOutcome] = await Promise.all([ listAgentDefinitions(tenantId), listTopLevelRuns(tenantId), @@ -478,9 +438,7 @@ export async function loadAgentDirectory( * page keeps a single loading/error envelope. Pass no reloadKey — * invalidate `tenantKeys.agentDirectory(tenantId)` after create. */ -export function useAgentDirectory( - tenantId: string | undefined, -): APIQuery { +export function useAgentDirectory(tenantId: string | undefined): APIQuery { const result = useQuery({ queryKey: tenantId === undefined diff --git a/apps/web/src/agents-directory.ts b/apps/web/src/agents-directory.ts index 75fe0e7608..bb543cf1a8 100644 --- a/apps/web/src/agents-directory.ts +++ b/apps/web/src/agents-directory.ts @@ -26,9 +26,7 @@ export type AgentDefinitionWithDisplayName = WithDisplayName; /** One definition's display name derived the same way the roster's are, so * the same agent never reads under two different names across screens. */ -export function withAgentDisplayName( - definition: AgentDefinition, -): AgentDefinitionWithDisplayName { +export function withAgentDisplayName(definition: AgentDefinition): AgentDefinitionWithDisplayName { return withDisplayNameShared(definition); } diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index 98d208203c..f541f8d065 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -16,11 +16,7 @@ import { type } from "arktype"; import type { ArkErrors } from "arktype"; import type { APIQuery } from "@corbits/api-query"; -import { - ApiQueryError, - UnauthenticatedError, - toAPIQuery, -} from "@corbits/api-query"; +import { ApiQueryError, UnauthenticatedError, toAPIQuery } from "@corbits/api-query"; import { pathToQueryKey } from "./query-client"; export const ProfileSchema = UserProfile; @@ -106,10 +102,7 @@ type Validator = (data: unknown) => T | ArkErrors; * call sites that still pass `""` when a tenant is unresolved cannot hit * the network with a broken URL. */ -export function useAPIQuery( - path: string, - schema: Validator, -): APIQuery { +export function useAPIQuery(path: string, schema: Validator): APIQuery { const enabled = path !== ""; const result = useQuery({ queryKey: pathToQueryKey(path), @@ -122,19 +115,11 @@ export function useAPIQuery( throw new UnauthenticatedError(); } if (!response.ok) { - throw new ApiQueryError( - `The server answered ${response.status}.`, - response.status, - path, - ); + throw new ApiQueryError(`The server answered ${response.status}.`, response.status, path); } const parsed = schema(await response.json()); if (parsed instanceof type.errors) { - throw new ApiQueryError( - `Unexpected response shape: ${parsed.summary}`, - undefined, - path, - ); + throw new ApiQueryError(`Unexpected response shape: ${parsed.summary}`, undefined, path); } return parsed; }, @@ -147,11 +132,7 @@ export function useAPIQuery( * parses its GETs: loud on a non-2xx status and on a response shape that * doesn't match the schema, never a silent fallback. */ -async function postJSON( - path: string, - schema: Validator, - body: unknown, -): Promise { +async function postJSON(path: string, schema: Validator, body: unknown): Promise { let response: Response; try { response = await fetch(path, { @@ -167,19 +148,11 @@ async function postJSON( ); } if (!response.ok) { - throw new ApiQueryError( - `The server answered ${response.status}.`, - response.status, - path, - ); + throw new ApiQueryError(`The server answered ${response.status}.`, response.status, path); } const parsed = schema(await response.json()); if (parsed instanceof type.errors) { - throw new ApiQueryError( - `Unexpected response shape: ${parsed.summary}`, - undefined, - path, - ); + throw new ApiQueryError(`Unexpected response shape: ${parsed.summary}`, undefined, path); } return parsed; } @@ -187,15 +160,10 @@ async function postJSON( /** Approves a pending approval. Scope is always "once": the hub rejects * "always" with a 400 (see `vendor/intx/hub-api/src/routes/approvals.ts`), * so this surface never offers it. */ -export function approveApproval( - tenantId: string, - approvalId: string, -): Promise { - return postJSON( - `/api/tenants/${tenantId}/approvals/${approvalId}/approve`, - ApprovalResponse, - { scope: "once" }, - ); +export function approveApproval(tenantId: string, approvalId: string): Promise { + return postJSON(`/api/tenants/${tenantId}/approvals/${approvalId}/approve`, ApprovalResponse, { + scope: "once", + }); } export function rejectApproval( @@ -217,10 +185,7 @@ export function rejectApproval( * .../artifacts/:id/preview` in `@corbits/artifacts-hub`) answers 415 for * a non-HTML artifact. */ -export function artifactPreviewPath( - tenantId: string, - artifactId: string, -): string { +export function artifactPreviewPath(tenantId: string, artifactId: string): string { return `/api/tenants/${tenantId}/artifacts/${encodeURIComponent(artifactId)}/preview`; } @@ -248,9 +213,7 @@ export async function fetchArtifactDetail( } const parsed = ArtifactDetailSchema(await response.json()); if (parsed instanceof type.errors) { - throw new ApiQueryError( - `Unexpected artifact response shape: ${parsed.summary}`, - ); + throw new ApiQueryError(`Unexpected artifact response shape: ${parsed.summary}`); } return parsed.artifact; } diff --git a/apps/web/src/app-error-boundary.tsx b/apps/web/src/app-error-boundary.tsx index 833733137f..638f544a9d 100644 --- a/apps/web/src/app-error-boundary.tsx +++ b/apps/web/src/app-error-boundary.tsx @@ -24,9 +24,7 @@ export class AppErrorBoundary extends Component< const refId = reportError(error, { operation: "app_render", extra: { - ...(info.componentStack !== null - ? { componentStack: info.componentStack } - : {}), + ...(info.componentStack !== null ? { componentStack: info.componentStack } : {}), }, }); this.setState({ hasError: true, refId }); @@ -47,17 +45,12 @@ export class AppErrorBoundary extends Component< <> Something broke while rendering. Reloading usually fixes it.
- - Reference: {this.state.refId} - + Reference: {this.state.refId} ) } action={ - } diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 5c96f3c512..bd6ce7925a 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -19,8 +19,7 @@ (injected at runtime, unrelated to this stylesheet); this just stops react-ui's override from fighting it. Higher specificity than that rule wins the cascade on its own, so this is safe regardless of load order. */ -[data-sonner-toaster][data-x-position="right"] - [data-sonner-toast].corbits-toast { +[data-sonner-toaster][data-x-position="right"] [data-sonner-toast].corbits-toast { left: auto !important; transform: none !important; } @@ -41,8 +40,7 @@ html { /* Inputs in side drawers already have a visible control boundary. Color that edge on focus so the focus treatment reads as one control, not a second outline outside it. */ -[data-slot="dialog-content"][data-side="right"] - [data-slot="input"]:focus-visible { +[data-slot="dialog-content"][data-side="right"] [data-slot="input"]:focus-visible { outline: none; border-color: var(--accent-rail, var(--primary)); } @@ -115,9 +113,7 @@ select:disabled, Scoped to the sidebar so stage content keeps library defaults. */ .shell-sidebar button:not(:disabled):not([aria-disabled="true"]), .shell-sidebar [data-slot="button"]:not(:disabled):not([aria-disabled="true"]), -.shell-sidebar - [data-slot="sidebar-item-row"] - button:not(:disabled):not([aria-disabled="true"]), +.shell-sidebar [data-slot="sidebar-item-row"] button:not(:disabled):not([aria-disabled="true"]), .shell-sidebar .shell-ch-row:not(:disabled):not([aria-disabled="true"]) { cursor: pointer; } @@ -200,10 +196,7 @@ select:disabled, /* Shared dialog craft — layout/scroll/chrome on react-ui data slots only. Feature forms and dialog body content keep their own styles. */ [data-slot="dialog-content"][data-side="center"] { - width: min( - calc(100vw - (var(--dialog-gutter) * 2)), - var(--dialog-content-max-width) - ); + width: min(calc(100vw - (var(--dialog-gutter) * 2)), var(--dialog-content-max-width)); max-height: var(--dialog-content-max-height); overflow: hidden; } @@ -940,8 +933,7 @@ select:disabled, align-items: center; gap: 0.5rem; padding: 0.45rem 0.7rem; - border-bottom: 1px solid - color-mix(in srgb, var(--foreground) 10%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); } .shell-routine-new { @@ -1115,8 +1107,7 @@ select:disabled, justify-content: space-between; gap: 0.5rem; padding-bottom: 0.55rem; - border-bottom: 1px solid - color-mix(in srgb, var(--foreground) 10%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); } .shell-canvas-pane-heading { @@ -1738,9 +1729,7 @@ select:disabled, } .shell-sidebar .shell-panel-search [data-slot="input"]::placeholder, -.shell-sidebar - [data-slot="sidebar-panel-body"] - [data-slot="input"]::placeholder { +.shell-sidebar [data-slot="sidebar-panel-body"] [data-slot="input"]::placeholder { color: var(--shell-panel-faint); } @@ -1756,9 +1745,7 @@ select:disabled, box-shadow: none; } -.shell-sidebar - [data-slot="sidebar-panel-body"] - [data-slot="input"]:focus-visible { +.shell-sidebar [data-slot="sidebar-panel-body"] [data-slot="input"]:focus-visible { outline: none; border-color: var(--foreground); box-shadow: 0 0 0 1px var(--foreground); @@ -1789,19 +1776,14 @@ select:disabled, left edge. Selector doubles as the kill for react-ui's bg-primary/10 selected wash. */ .shell-sidebar [data-slot="sidebar-item-row"] > div.bg-primary\/10, -.shell-sidebar - [data-slot="sidebar-item-row"] - > div:has(button[aria-current="true"]) { +.shell-sidebar [data-slot="sidebar-item-row"] > div:has(button[aria-current="true"]) { background-color: var(--background); color: var(--shell-accent); box-shadow: inset 2px 0 0 0 var(--shell-accent); } .shell-sidebar [data-slot="sidebar-item-row"] > div.bg-primary\/10 > button, -.shell-sidebar - [data-slot="sidebar-item-row"] - > div:has(button[aria-current="true"]) - > button { +.shell-sidebar [data-slot="sidebar-item-row"] > div:has(button[aria-current="true"]) > button { font-weight: 600; } @@ -1839,12 +1821,7 @@ select:disabled, } /* Mock .count: the trailing meta slot when it is plain text. */ -.shell-sidebar - [data-slot="sidebar-item-row"] - > div - > button - > span - + span:last-child { +.shell-sidebar [data-slot="sidebar-item-row"] > div > button > span + span:last-child { font-size: 0.72rem; color: var(--shell-panel-faint); } @@ -2342,8 +2319,7 @@ select:disabled, attention-seeking animation, fully off under reduced motion. DESIGN.md's motion ceiling is 300ms — stays at 280ms. */ .onboarding-phase { - animation: onboarding-phase-in var(--duration-large, 300ms) - var(--ease-out, ease) both; + animation: onboarding-phase-in var(--duration-large, 300ms) var(--ease-out, ease) both; } @keyframes onboarding-phase-in { @@ -2615,8 +2591,7 @@ select:disabled, .insights-grid > .insights-panel { border: 0; - border-bottom: 1px solid - color-mix(in srgb, var(--foreground) 10%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); } .insights-grid > .insights-panel:last-child { @@ -2914,11 +2889,7 @@ tr.insights-row-clickable:hover { } /* The current row's copy follows the row's accent ink. */ -.shell-sidebar - [data-slot="sidebar-item-row"] - button[aria-current="true"] - .panel-row-copy - strong { +.shell-sidebar [data-slot="sidebar-item-row"] button[aria-current="true"] .panel-row-copy strong { color: inherit; } diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index 1b8f49a5d9..5ae5ec25f5 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -18,12 +18,7 @@ import { NotFoundPage } from "./pages/not-found-page"; import { OnboardingPage } from "./pages/onboarding-page"; import { ProvisioningErrorPage } from "./pages/provisioning-error-page"; import { createAppQueryClient } from "./query-client"; -import { - APP_ROUTES, - LOGIN_PATH, - matchesRoute, - ONBOARDING_PATH, -} from "./routes"; +import { APP_ROUTES, LOGIN_PATH, matchesRoute, ONBOARDING_PATH } from "./routes"; import type { SessionState, SessionUser } from "./session"; import { AppShell } from "./shell/app-shell"; import { ComposerInsertionProvider } from "./shell/composer-insertion"; @@ -34,13 +29,7 @@ import { ShellChromeProvider } from "./shell/shell-chrome-provider"; * there with `?next=` so a successful sign-in returns to where the visitor * meant to go — the URL is the source of truth for "where was I headed", * not an implicit conditional swap in `App`. */ -function LoginRedirect({ - path, - navigate, -}: { - readonly path: string; - readonly navigate: Navigate; -}) { +function LoginRedirect({ path, navigate }: { readonly path: string; readonly navigate: Navigate }) { useEffect(() => { navigate(buildLoginRedirect(path)); }, [path, navigate]); @@ -94,13 +83,8 @@ function Shell({ // DB, a cookie for a deleted user, an expired session) routes the whole // shell back to login instead of leaving one panel stuck showing "sign // in required" beside chrome that still renders as if signed in. - const queryClient = useMemo( - () => createAppQueryClient(onSignOut), - [onSignOut], - ); - const route = APP_ROUTES.find((candidate) => - matchesRoute(candidate.path, path), - ); + const queryClient = useMemo(() => createAppQueryClient(onSignOut), [onSignOut]); + const route = APP_ROUTES.find((candidate) => matchesRoute(candidate.path, path)); return ( @@ -110,11 +94,7 @@ function Shell({ - {route === undefined ? ( - - ) : ( - route.render(path, navigate) - )} + {route === undefined ? : route.render(path, navigate)} @@ -199,14 +179,7 @@ export function App({ if (path === ONBOARDING_PATH) { return ; } - return ( - - ); + return ; } } } diff --git a/apps/web/src/approval-actions.ts b/apps/web/src/approval-actions.ts index 1737f38ed3..963ebf7989 100644 --- a/apps/web/src/approval-actions.ts +++ b/apps/web/src/approval-actions.ts @@ -33,9 +33,7 @@ export function createChatApprovalActions( const key = query.queryKey; if (!Array.isArray(key)) return false; return key.some( - (part) => - typeof part === "string" && - part.includes(`/api/tenants/${tenantId}/inbox`), + (part) => typeof part === "string" && part.includes(`/api/tenants/${tenantId}/inbox`), ); }, }); diff --git a/apps/web/src/auth-screen.tsx b/apps/web/src/auth-screen.tsx index ea7fc67a5d..64295de431 100644 --- a/apps/web/src/auth-screen.tsx +++ b/apps/web/src/auth-screen.tsx @@ -7,12 +7,7 @@ import { LoginForm } from "@corbits/react-ui/blocks/login/login-form"; import { useState } from "react"; import { AuthLayout } from "./auth/auth-layout"; -import { - SOCIAL_SIGN_IN_PROVIDERS, - signIn, - signInSocial, - signUp, -} from "./session"; +import { SOCIAL_SIGN_IN_PROVIDERS, signIn, signInSocial, signUp } from "./session"; import type { SessionUser, SocialProviderId } from "./session"; type Mode = "sign-in" | "sign-up"; @@ -35,19 +30,12 @@ const SOCIAL_PROVIDER_LABEL: Record = { github: "Continue with GitHub", }; -export function AuthScreen({ - onSignedIn, -}: { - readonly onSignedIn: (user: SessionUser) => void; -}) { +export function AuthScreen({ onSignedIn }: { readonly onSignedIn: (user: SessionUser) => void }) { const [mode, setMode] = useState("sign-in"); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); - const submit = async (credentials: { - readonly email: string; - readonly password: string; - }) => { + const submit = async (credentials: { readonly email: string; readonly password: string }) => { setBusy(true); setError(null); const result = diff --git a/apps/web/src/auth/auth-layout.tsx b/apps/web/src/auth/auth-layout.tsx index e8ec2c435a..49228ee935 100644 --- a/apps/web/src/auth/auth-layout.tsx +++ b/apps/web/src/auth/auth-layout.tsx @@ -34,11 +34,7 @@ export function AuthLayout({ children }: { readonly children: ReactNode }) { quote card. The is the no-JS fallback; the 2D-canvas dither (ambient + cursor warp) paints over it. */}
- +
diff --git a/apps/web/src/auth/dither-background.tsx b/apps/web/src/auth/dither-background.tsx index 5b5da6f632..4db66d63aa 100644 --- a/apps/web/src/auth/dither-background.tsx +++ b/apps/web/src/auth/dither-background.tsx @@ -170,9 +170,7 @@ export function DitherBackground({ className }: { className?: string }) { const di = (y * w + x) * 4; const thr = ((BAYER[rowBayer + (x & 7)] ?? 0) + 0.5) / BAYER_DIVISOR; for (let c = 0; c < 3; c++) { - const q = Math.floor( - ((srcPx[si + c] ?? 0) / 255) * (LEVELS - 1) + thr, - ); + const q = Math.floor(((srcPx[si + c] ?? 0) / 255) * (LEVELS - 1) + thr); d[di + c] = (q / (LEVELS - 1)) * 255; } d[di + 3] = 255; diff --git a/apps/web/src/auth/quote-card.tsx b/apps/web/src/auth/quote-card.tsx index 59f84929c9..4a5faa05e0 100644 --- a/apps/web/src/auth/quote-card.tsx +++ b/apps/web/src/auth/quote-card.tsx @@ -71,9 +71,7 @@ export function QuoteCard() { return (
-
- “{current.quote}” -
+
“{current.quote}”
{current.author !== undefined && (
diff --git a/apps/web/src/auto-workbench-title.test.ts b/apps/web/src/auto-workbench-title.test.ts index b8c2390255..87abf2774b 100644 --- a/apps/web/src/auto-workbench-title.test.ts +++ b/apps/web/src/auto-workbench-title.test.ts @@ -9,9 +9,7 @@ import { describe("titleFromFirstMessage (CL-6656)", () => { test("trims and collapses whitespace into a single-line title", () => { - expect(titleFromFirstMessage(" Help me\nplan Q3 ")).toBe( - "Help me plan Q3", - ); + expect(titleFromFirstMessage(" Help me\nplan Q3 ")).toBe("Help me plan Q3"); }); test("returns undefined for blank or whitespace-only input", () => { @@ -20,9 +18,7 @@ describe("titleFromFirstMessage (CL-6656)", () => { }); test("keeps a short message intact", () => { - expect(titleFromFirstMessage("Draft a launch checklist")).toBe( - "Draft a launch checklist", - ); + expect(titleFromFirstMessage("Draft a launch checklist")).toBe("Draft a launch checklist"); }); test("truncates a long message at a word boundary with an ellipsis", () => { @@ -39,23 +35,17 @@ describe("titleFromFirstMessage (CL-6656)", () => { describe("autoNameFromFirstMessage (CL-6656)", () => { test("names an ad-hoc New Workbench from the first message", () => { - expect( - autoNameFromFirstMessage(NEW_WORKBENCH_TITLE, "Plan the Q3 launch"), - ).toBe("Plan the Q3 launch"); + expect(autoNameFromFirstMessage(NEW_WORKBENCH_TITLE, "Plan the Q3 launch")).toBe( + "Plan the Q3 launch", + ); }); test("leaves prefab and already-renamed titles alone", () => { - expect( - autoNameFromFirstMessage("Code review", "Review the auth PR"), - ).toBeUndefined(); - expect( - autoNameFromFirstMessage("My research bench", "Dig into pricing"), - ).toBeUndefined(); + expect(autoNameFromFirstMessage("Code review", "Review the auth PR")).toBeUndefined(); + expect(autoNameFromFirstMessage("My research bench", "Dig into pricing")).toBeUndefined(); }); test("returns undefined when the first message has no usable text", () => { - expect( - autoNameFromFirstMessage(NEW_WORKBENCH_TITLE, " "), - ).toBeUndefined(); + expect(autoNameFromFirstMessage(NEW_WORKBENCH_TITLE, " ")).toBeUndefined(); }); }); diff --git a/apps/web/src/auto-workbench-title.ts b/apps/web/src/auto-workbench-title.ts index a8ba1d0224..d13bda47c5 100644 --- a/apps/web/src/auto-workbench-title.ts +++ b/apps/web/src/auto-workbench-title.ts @@ -28,8 +28,7 @@ export function titleFromFirstMessage( const sliced = collapsed.slice(0, maxLength); const lastSpace = sliced.lastIndexOf(" "); - const cut = - lastSpace > Math.floor(maxLength / 2) ? sliced.slice(0, lastSpace) : sliced; + const cut = lastSpace > Math.floor(maxLength / 2) ? sliced.slice(0, lastSpace) : sliced; return `${cut.replace(/[.,;:!?]+$/u, "")}…`; } diff --git a/apps/web/src/bench-context.test.ts b/apps/web/src/bench-context.test.ts index bc3f350051..35ecaff398 100644 --- a/apps/web/src/bench-context.test.ts +++ b/apps/web/src/bench-context.test.ts @@ -3,9 +3,7 @@ import { describe, expect, test } from "bun:test"; import { resolveSelection } from "./bench-context"; import type { Principal } from "./api"; -function membership( - overrides: Partial & { tenantId: string }, -): Principal { +function membership(overrides: Partial & { tenantId: string }): Principal { return { principalId: `prn_${overrides.tenantId}`, tenantName: overrides.tenantId, @@ -69,9 +67,7 @@ describe("resolveSelection", () => { }); test("a stored selection for a tenant no longer in memberships falls through", () => { - const memberships = [ - membership({ tenantId: "tnt_bench", tenantName: "Launch Team" }), - ]; + const memberships = [membership({ tenantId: "tnt_bench", tenantName: "Launch Team" })]; const resolved = resolveSelection(memberships, "tnt_gone"); @@ -79,9 +75,7 @@ describe("resolveSelection", () => { }); test("undefined when every membership is a raw-id tenant", () => { - const memberships = [ - membership({ tenantId: "tnt_raw", tenantName: "tnt_raw" }), - ]; + const memberships = [membership({ tenantId: "tnt_raw", tenantName: "tnt_raw" })]; const resolved = resolveSelection(memberships, null); diff --git a/apps/web/src/bench-context.tsx b/apps/web/src/bench-context.tsx index f7c622641e..314e29503e 100644 --- a/apps/web/src/bench-context.tsx +++ b/apps/web/src/bench-context.tsx @@ -69,10 +69,7 @@ export function resolveSelection( memberships: readonly Principal[], stored: string | null, ): Principal | undefined { - const storedMatch = - stored !== null - ? memberships.find((m) => m.tenantId === stored) - : undefined; + const storedMatch = stored !== null ? memberships.find((m) => m.tenantId === stored) : undefined; if (storedMatch !== undefined && isBenchMembership(storedMatch)) { return storedMatch; } @@ -82,14 +79,10 @@ export function resolveSelection( export function BenchProvider({ children }: { readonly children: ReactNode }) { const queryClient = useQueryClient(); const memberships = useAPIQuery("/api/me/principals", PrincipalsSchema); - const [stored, setStored] = useState(() => - readStoredTenantId(), - ); + const [stored, setStored] = useState(() => readStoredTenantId()); const resolved = - memberships.kind === "ready" - ? resolveSelection(memberships.data.data, stored) - : undefined; + memberships.kind === "ready" ? resolveSelection(memberships.data.data, stored) : undefined; useEffect(() => { if (resolved !== undefined && resolved.tenantId !== stored) { @@ -122,9 +115,7 @@ export function BenchProvider({ children }: { readonly children: ReactNode }) { [memberships, resolved, stored, queryClient], ); - return ( - {children} - ); + return {children}; } export function useBench(): BenchState { diff --git a/apps/web/src/block-response-actions.ts b/apps/web/src/block-response-actions.ts index a42afc2719..26e1e383f8 100644 --- a/apps/web/src/block-response-actions.ts +++ b/apps/web/src/block-response-actions.ts @@ -5,10 +5,7 @@ // routes (`getBlockResponses`/`submitPollResponse`/`submitFormResponse` in // `api.ts`). -import type { - BlockResponseActions, - BlockResponseSubmitResult, -} from "@corbits/chat-ui"; +import type { BlockResponseActions, BlockResponseSubmitResult } from "@corbits/chat-ui"; import { CHAT_STRINGS, ChatApiError, @@ -22,9 +19,7 @@ export function createChatBlockResponseActions( tenantId: string, workbenchId: string, ): BlockResponseActions { - async function submit( - call: () => Promise, - ): Promise { + async function submit(call: () => Promise): Promise { try { await call(); return { kind: "submitted" }; @@ -37,10 +32,7 @@ export function createChatBlockResponseActions( } return { kind: "error", - message: - cause instanceof Error - ? cause.message - : CHAT_STRINGS.blockFormSubmitError, + message: cause instanceof Error ? cause.message : CHAT_STRINGS.blockFormSubmitError, }; } } @@ -48,12 +40,7 @@ export function createChatBlockResponseActions( return { async getResponses(messageId, blockId) { try { - const result = await getBlockResponses( - tenantId, - workbenchId, - messageId, - blockId, - ); + const result = await getBlockResponses(tenantId, workbenchId, messageId, blockId); return { kind: "ready", tally: result.tally, @@ -66,37 +53,19 @@ export function createChatBlockResponseActions( } return { kind: "error", - message: - cause instanceof Error ? cause.message : "Couldn't load responses.", + message: cause instanceof Error ? cause.message : "Couldn't load responses.", }; } }, submitPoll(messageId, blockId, choiceIds) { - return submit(() => - submitPollResponse( - tenantId, - workbenchId, - messageId, - blockId, - choiceIds, - ), - ); + return submit(() => submitPollResponse(tenantId, workbenchId, messageId, blockId, choiceIds)); }, submitForm(messageId, blockId, values) { - return submit(() => - submitFormResponse(tenantId, workbenchId, messageId, blockId, values), - ); + return submit(() => submitFormResponse(tenantId, workbenchId, messageId, blockId, values)); }, submitQuestion(messageId, blockId, answer, optionIndex) { return submit(() => - submitQuestionResponse( - tenantId, - workbenchId, - messageId, - blockId, - answer, - optionIndex, - ), + submitQuestionResponse(tenantId, workbenchId, messageId, blockId, answer, optionIndex), ); }, }; diff --git a/apps/web/src/chat-artifact-open.ts b/apps/web/src/chat-artifact-open.ts index f6ced4f7cc..a0ee1a75f8 100644 --- a/apps/web/src/chat-artifact-open.ts +++ b/apps/web/src/chat-artifact-open.ts @@ -33,9 +33,7 @@ export function artifactContentFromDetail( // presence `/update` route's own write-grant check is the real gate // — this only decides which pane a capable viewer sees. canEdit: rendererKind === "doc", - ...(rendererKind === "html" - ? { previewSrc: artifactPreviewPath(tenantId, detail.id) } - : {}), + ...(rendererKind === "html" ? { previewSrc: artifactPreviewPath(tenantId, detail.id) } : {}), }; } @@ -64,10 +62,7 @@ export function artifactContentFromBlob( blobId: string, contentBase64: string, ): CanvasArtifactContent { - const rendererKind = resolveRendererKindFromMediaType( - part.mediaType, - part.name, - ); + const rendererKind = resolveRendererKindFromMediaType(part.mediaType, part.name); if (!isTextDecodableMediaType(part.mediaType)) { return { id: blobId, diff --git a/apps/web/src/client-bootstrap.test.ts b/apps/web/src/client-bootstrap.test.ts index dd331713a7..620da6821f 100644 --- a/apps/web/src/client-bootstrap.test.ts +++ b/apps/web/src/client-bootstrap.test.ts @@ -2,11 +2,7 @@ import { describe, expect, test } from "bun:test"; import type { ClientLogger } from "@corbits/client-log"; -import { - bootstrapClientSession, - logBootstrapResult, - logBootstrapThrown, -} from "./client-bootstrap"; +import { bootstrapClientSession, logBootstrapResult, logBootstrapThrown } from "./client-bootstrap"; import type { ClientBootstrapResult } from "./client-bootstrap"; import type { StockHub } from "./needs-converge"; @@ -76,8 +72,7 @@ test("session bootstrap drives the portable client manifest", async () => { deployWorkflow: () => Promise.reject(new Error("unexpected deploy")), sendRunMail: () => Promise.reject(new Error("unexpected send")), listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; diff --git a/apps/web/src/client-bootstrap.ts b/apps/web/src/client-bootstrap.ts index bd575cfdb6..85a48e8271 100644 --- a/apps/web/src/client-bootstrap.ts +++ b/apps/web/src/client-bootstrap.ts @@ -67,8 +67,7 @@ export async function ensurePrimaryTenant( * "Myra" display name. Resolved from the catalog at runtime, never * hardcoded at the call site. */ export function resolveMyraDefinitionRefId(): string | undefined { - return WORKFLOW_CATALOG.find((entry) => entry.displayName === "Myra") - ?.assetName; + return WORKFLOW_CATALOG.find((entry) => entry.displayName === "Myra")?.assetName; } export type ClientBootstrapAccount = { @@ -127,8 +126,7 @@ export const UPSTREAM_GAP_NOTES: Record = { "Myra is absent and the client was not given the exact stock source and offering ids — supply myraDeploy from client config.", "project-workflow-principal": "Stock Interchange cannot carry a workflow identity into a child room by refId, so workbench member setup waits on an upstream capability.", - "principal-roles": - "The stock member invite route cannot assign the requested child-room roles.", + "principal-roles": "The stock member invite route cannot assign the requested child-room roles.", "agent-mailbox-reads": "Stock Interchange exposes no per-agent mailbox search, so participant-filtered thread derivation waits on a stock search route.", "thread-fork-context": @@ -139,8 +137,7 @@ export async function bootstrapClientSession( account: ClientBootstrapAccount, deps: ClientBootstrapDeps, ): Promise { - const myraDefinitionRefId = - deps.myraDefinitionRefId ?? resolveMyraDefinitionRefId(); + const myraDefinitionRefId = deps.myraDefinitionRefId ?? resolveMyraDefinitionRefId(); if (myraDefinitionRefId === undefined) { return { kind: "error", @@ -205,10 +202,7 @@ export async function bootstrapClientSession( * (first-open in main, signup in the onboarding page): a converged lane * logs at info, a stock gap or any other failure logs at warn — never * shown, never gating the shell. */ -export function logBootstrapResult( - log: ClientLogger, - result: ClientBootstrapResult, -): void { +export function logBootstrapResult(log: ClientLogger, result: ClientBootstrapResult): void { if (result.kind === "ready") { log.info("Portable client bootstrap converged", { primaryTenantId: result.primaryTenantId, @@ -252,8 +246,6 @@ export function runPortableClientBootstrap( ...(overrides?.myraDefinitionRefId === undefined ? {} : { myraDefinitionRefId: overrides.myraDefinitionRefId }), - ...(overrides?.myraDeploy === undefined - ? {} - : { myraDeploy: overrides.myraDeploy }), + ...(overrides?.myraDeploy === undefined ? {} : { myraDeploy: overrides.myraDeploy }), }); } diff --git a/apps/web/src/command-palette-actions.test.ts b/apps/web/src/command-palette-actions.test.ts index 2d34f4d489..38ab1d269e 100644 --- a/apps/web/src/command-palette-actions.test.ts +++ b/apps/web/src/command-palette-actions.test.ts @@ -33,8 +33,7 @@ function context(overrides: { const ctx = { path: overrides.path, navigate: (to: string) => navigated.push(to), - tenantId: - overrides.tenantId !== undefined ? overrides.tenantId : "tenant-1", + tenantId: overrides.tenantId !== undefined ? overrides.tenantId : "tenant-1", cycleTheme: () => { themeCycled = true; }, @@ -66,9 +65,7 @@ describe("ACTION_COMMANDS", () => { }); test("exactly one New workbench create row — no duplicate title+destination (CL-6820)", () => { - const newWorkbenchRows = ACTION_COMMANDS.filter( - (c) => c.title === "New workbench", - ); + const newWorkbenchRows = ACTION_COMMANDS.filter((c) => c.title === "New workbench"); expect(newWorkbenchRows).toHaveLength(1); expect(newWorkbenchRows[0]?.id).toBe("new-workbench"); @@ -155,8 +152,7 @@ describe("runActionCommand", () => { test("talk-to-myra opens Myra's DM via kind=chat + definitionId, not a title-match mint", async () => { const calls: { readonly path: string; readonly init?: RequestInit }[] = []; globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { - const path = - typeof input === "string" ? input : new URL(String(input)).pathname; + const path = typeof input === "string" ? input : new URL(String(input)).pathname; calls.push(init === undefined ? { path } : { path, init }); if (path.includes("/workflows/definitions")) { return Promise.resolve( @@ -199,9 +195,7 @@ describe("runActionCommand", () => { const { ctx, navigated } = context({ path: "/" }); await runActionCommand("talk-to-myra", ctx); - const createCall = calls.find((call) => - call.path.endsWith("/chat/workbenches"), - ); + const createCall = calls.find((call) => call.path.endsWith("/chat/workbenches")); expect(createCall?.init?.method).toBe("POST"); expect(JSON.parse(String(createCall?.init?.body))).toEqual({ kind: "chat", diff --git a/apps/web/src/command-palette-actions.ts b/apps/web/src/command-palette-actions.ts index fb26c39be1..5b315b07a4 100644 --- a/apps/web/src/command-palette-actions.ts +++ b/apps/web/src/command-palette-actions.ts @@ -120,8 +120,7 @@ export async function runActionCommand( } case "new-skill": { newSkillRequest.request({ - alreadyOnTargetRoute: - ctx.path === "/skills" || ctx.path.startsWith("/skills/"), + alreadyOnTargetRoute: ctx.path === "/skills" || ctx.path.startsWith("/skills/"), navigateToTargetRoute: () => ctx.navigate("/skills"), dispatch: () => window.dispatchEvent(new CustomEvent(NEW_SKILL_EVENT)), }); @@ -129,8 +128,7 @@ export async function runActionCommand( } case "upload-artifact": { requestLibraryUpload({ - alreadyOnLibrary: - ctx.path === "/files" || ctx.path.startsWith("/files/"), + alreadyOnLibrary: ctx.path === "/files" || ctx.path.startsWith("/files/"), navigateToLibrary: () => ctx.navigate("/files"), }); return; diff --git a/apps/web/src/command-palette-provider.tsx b/apps/web/src/command-palette-provider.tsx index d82e26f594..d31887accb 100644 --- a/apps/web/src/command-palette-provider.tsx +++ b/apps/web/src/command-palette-provider.tsx @@ -1,9 +1,4 @@ -import { - artifactKindLabel, - CommandPalette, - useCommandShortcut, - useTheme, -} from "@corbits/react-ui"; +import { artifactKindLabel, CommandPalette, useCommandShortcut, useTheme } from "@corbits/react-ui"; import type { CommandPaletteGroup } from "@corbits/react-ui"; import { listWorkbenches } from "@corbits/chat-ui"; import { libraryArtifactPath } from "@corbits/artifact-ui"; @@ -20,21 +15,10 @@ import { type RecentEntry, } from "@corbits/command-palette"; import { useQueryClient } from "@tanstack/react-query"; -import { - useCallback, - useEffect, - useMemo, - useRef, - useState, - type ReactNode, -} from "react"; +import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { listAgentDefinitions } from "./agents-api"; -import { - ACTION_COMMANDS, - runActionCommand, - type ActionCommandId, -} from "./command-palette-actions"; +import { ACTION_COMMANDS, runActionCommand, type ActionCommandId } from "./command-palette-actions"; import { openCommandPalette, setCommandPaletteOpen, @@ -49,16 +33,8 @@ import { ArtifactListPageSchema, useAPIQuery } from "./api"; import { isBenchMembership, useBench } from "./bench-context"; import { useCloseCanvas } from "./shell/canvas-availability"; import { listMcpServers } from "@corbits/plugins-ui"; -import { - AGENTS_PATH_PREFIX, - PLUGINS_PATH_PREFIX, - SKILLS_PATH_PREFIX, -} from "./path-ids"; -import { - listScheduledWorkflows, - runScheduledWorkflowNow, - useTenantQuery, -} from "./routines-api"; +import { AGENTS_PATH_PREFIX, PLUGINS_PATH_PREFIX, SKILLS_PATH_PREFIX } from "./path-ids"; +import { listScheduledWorkflows, runScheduledWorkflowNow, useTenantQuery } from "./routines-api"; import { listSkills } from "./skills-api"; import { tenantKeys } from "./query-client"; import type { Navigate } from "./navigation"; @@ -105,8 +81,7 @@ export function CommandPaletteProvider({ const closeCanvas = useCloseCanvas(); const recentsStore = useMemo( - () => - selectedTenantId === null ? null : recentsStoreForBench(selectedTenantId), + () => (selectedTenantId === null ? null : recentsStoreForBench(selectedTenantId)), [selectedTenantId], ); @@ -145,10 +120,7 @@ export function CommandPaletteProvider({ } window.addEventListener(WORKBENCH_NOT_FOUND_EVENT, onWorkbenchNotFound); return () => { - window.removeEventListener( - WORKBENCH_NOT_FOUND_EVENT, - onWorkbenchNotFound, - ); + window.removeEventListener(WORKBENCH_NOT_FOUND_EVENT, onWorkbenchNotFound); }; }, [removeRecent]); @@ -212,12 +184,8 @@ export function CommandPaletteProvider({ // fetches for (by design — the unscoped default view should not dump // every entity on open). The mock shows every item in an active scope for // this input, so fetch that scope's raw list directly instead. - const [bareWorkbenches, setBareWorkbenches] = useState< - readonly PaletteResultItem[] - >([]); - const [bareAgents, setBareAgents] = useState( - [], - ); + const [bareWorkbenches, setBareWorkbenches] = useState([]); + const [bareAgents, setBareAgents] = useState([]); useEffect(() => { if (bareScopeKind !== "workbenches" || !open) { @@ -279,9 +247,7 @@ export function CommandPaletteProvider({ () => listMcpServers(selectedTenantId ?? ""), ); const artifactsQuery = useAPIQuery( - selectedTenantId === null || !open - ? "" - : `/api/tenants/${selectedTenantId}/artifacts`, + selectedTenantId === null || !open ? "" : `/api/tenants/${selectedTenantId}/artifacts`, ArtifactListPageSchema, ); @@ -296,9 +262,7 @@ export function CommandPaletteProvider({ // tenancy is the only thing filtered out here. const workbenchMemberships = memberships.kind === "ready" - ? memberships.data.data.filter((membership) => - isBenchMembership(membership), - ) + ? memberships.data.data.filter((membership) => isBenchMembership(membership)) : []; const nextWorkbench = workbenchMemberships.length > 1 @@ -325,8 +289,7 @@ export function CommandPaletteProvider({ useEffect(() => { const previous = searchScope.current; const routeChanged = previous.path !== path; - const benchSwitched = - previous.tenantId !== null && previous.tenantId !== selectedTenantId; + const benchSwitched = previous.tenantId !== null && previous.tenantId !== selectedTenantId; searchScope.current = { path, tenantId: selectedTenantId }; if (routeChanged || benchSwitched) setCommandPaletteOpen(false); }, [path, selectedTenantId]); @@ -530,21 +493,17 @@ export function CommandPaletteProvider({ }); } else if (id.startsWith("route:")) { const routePath = id.slice("route:".length); - const label = - STATIC_COMMANDS.find((command) => command.id === id)?.title ?? - routePath; + const label = STATIC_COMMANDS.find((command) => command.id === id)?.title ?? routePath; navigate(routePath); pushRecent({ kind: "route", id, title: label }); } else if (id.startsWith("entity:workbenches:")) { const workbenchId = id.slice("entity:workbenches:".length); - const title = - workbenchItems.find((item) => item.id === id)?.title ?? workbenchId; + const title = workbenchItems.find((item) => item.id === id)?.title ?? workbenchId; navigate(`/w/${workbenchId}`); pushRecent({ kind: "workbenches", id, title, subtitle: "Workbench" }); } else if (id.startsWith("entity:agents:")) { const agentId = id.slice("entity:agents:".length); - const title = - agentItems.find((item) => item.id === id)?.title ?? agentId; + const title = agentItems.find((item) => item.id === id)?.title ?? agentId; navigate( detailPath(AGENTS_PATH_PREFIX, { slug: agentHandleById.current.get(agentId) ?? "", @@ -554,18 +513,14 @@ export function CommandPaletteProvider({ pushRecent({ kind: "agents", id, title, subtitle: "Agent" }); } else if (id.startsWith("entity:routines:")) { const routineId = id.slice("entity:routines:".length); - const title = - routineItems.find((item) => item.id === id)?.title ?? routineId; + const title = routineItems.find((item) => item.id === id)?.title ?? routineId; navigate(`/routines/${encodeURIComponent(routineId)}`); pushRecent({ kind: "routines", id, title, subtitle: "Routine" }); } else if (id.startsWith("entity:skills:")) { const skillId = id.slice("entity:skills:".length); - const title = - skillItems.find((item) => item.id === id)?.title ?? skillId; + const title = skillItems.find((item) => item.id === id)?.title ?? skillId; // A skill's name is its slug: the Skills API keys every route on it. - navigate( - detailPath(SKILLS_PATH_PREFIX, { slug: skillId, id: skillId }), - ); + navigate(detailPath(SKILLS_PATH_PREFIX, { slug: skillId, id: skillId })); pushRecent({ kind: "skills", id, title, subtitle: "Skill" }); } else if (id.startsWith("entity:plugins:")) { const slug = id.slice("entity:plugins:".length); @@ -576,8 +531,7 @@ export function CommandPaletteProvider({ pushRecent({ kind: "plugins", id, title, subtitle: "Plugin" }); } else if (id.startsWith("entity:library:")) { const artifactId = id.slice("entity:library:".length); - const title = - libraryItems.find((item) => item.id === id)?.title ?? "Files"; + const title = libraryItems.find((item) => item.id === id)?.title ?? "Files"; navigate(libraryArtifactPath(artifactId)); pushRecent({ kind: "library", id, title, subtitle: "Files" }); } diff --git a/apps/web/src/connect-service-actions.ts b/apps/web/src/connect-service-actions.ts index 27edc03141..01438a35dc 100644 --- a/apps/web/src/connect-service-actions.ts +++ b/apps/web/src/connect-service-actions.ts @@ -15,10 +15,7 @@ // Plugins page) publishes `chat.settings`; ChatWorkspace parses that // event and calls `notifySettingsChanged` so a mounted card flips // without remounting (CL-6476). -import type { - ConnectServiceActions, - ConnectServiceQuery, -} from "@corbits/chat-ui"; +import type { ConnectServiceActions, ConnectServiceQuery } from "@corbits/chat-ui"; import { completeConnectorCredential, ConnectionsApiError, @@ -26,18 +23,12 @@ import { listProviders, oauthStartHref, } from "@corbits/settings-ui"; -import { - connectMcpPreset, - listMcpPresets, - McpServersApiError, -} from "@corbits/plugins-ui"; +import { connectMcpPreset, listMcpPresets, McpServersApiError } from "@corbits/plugins-ui"; import { mcpPresetBySlug, MCP_PRESETS } from "@corbits/connections/mcp-presets"; import { CONNECTOR_REGISTRY } from "@corbits/connections/registry"; function bareConnectorId(connectorId: string): string { - return connectorId.startsWith("mcp:") - ? connectorId.slice("mcp:".length) - : connectorId; + return connectorId.startsWith("mcp:") ? connectorId.slice("mcp:".length) : connectorId; } export function createChatConnectServiceActions( diff --git a/apps/web/src/global-routines.ts b/apps/web/src/global-routines.ts index 2b19988bb6..33fa6ef01e 100644 --- a/apps/web/src/global-routines.ts +++ b/apps/web/src/global-routines.ts @@ -66,10 +66,7 @@ export function useGlobalRoutines(): APIQuery { if (failed !== undefined) { return { kind: "error", - message: - failed.error instanceof Error - ? failed.error.message - : "Couldn't load routines.", + message: failed.error instanceof Error ? failed.error.message : "Couldn't load routines.", retry: () => { for (const result of results) void result.refetch(); }, @@ -102,10 +99,7 @@ export function useInvalidateRoutines(): (tenantId: string) => void { export type RoutineActions = { readonly runNow: (row: GlobalRoutineRow) => Promise; - readonly setEnabled: ( - row: GlobalRoutineRow, - enabled: boolean, - ) => Promise; + readonly setEnabled: (row: GlobalRoutineRow, enabled: boolean) => Promise; }; export function useRoutineActions(): RoutineActions { @@ -113,10 +107,7 @@ export function useRoutineActions(): RoutineActions { return { runNow: async (row) => { try { - await runScheduledWorkflowNow( - row.tenantId, - row.definition.definitionId, - ); + await runScheduledWorkflowNow(row.tenantId, row.definition.definitionId); invalidate(row.tenantId); toast(`${row.definition.name} started`); } catch (cause) { diff --git a/apps/web/src/granola-plugin-availability.test.ts b/apps/web/src/granola-plugin-availability.test.ts index fcb9ff3973..656e0dfdf4 100644 --- a/apps/web/src/granola-plugin-availability.test.ts +++ b/apps/web/src/granola-plugin-availability.test.ts @@ -10,8 +10,7 @@ afterEach(() => { describe("fetchGranolaPluginConnected", () => { test("false on 404 — Granola not connected", async () => { - globalThis.fetch = (async () => - new Response(null, { status: 404 })) as unknown as typeof fetch; + globalThis.fetch = (async () => new Response(null, { status: 404 })) as unknown as typeof fetch; expect(await fetchGranolaPluginConnected("tnt_1")).toBe(false); }); diff --git a/apps/web/src/granola-plugin-availability.ts b/apps/web/src/granola-plugin-availability.ts index 1e0cc31e24..5061d968b8 100644 --- a/apps/web/src/granola-plugin-availability.ts +++ b/apps/web/src/granola-plugin-availability.ts @@ -14,9 +14,7 @@ const ResolvedCredential = type({ /** True when Granola is connected (or needs attention) for `tenantId`. A * missing, revoked, or unreadable credential is not offerable. */ -export async function fetchGranolaPluginConnected( - tenantId: string, -): Promise { +export async function fetchGranolaPluginConnected(tenantId: string): Promise { const response = await fetch( `/api/tenants/${tenantId}/credentials/resolve/${encodeURIComponent("Granola")}`, { headers: { accept: "application/json" } }, @@ -28,11 +26,7 @@ export async function fetchGranolaPluginConnected( if (parsed instanceof type.errors) return false; // Revoked reads as not connected — same rule as // `@corbits/connections/plugins`' resolveOne. - return ( - parsed.status === "active" || - parsed.status === "expired" || - parsed.status === "error" - ); + return parsed.status === "active" || parsed.status === "expired" || parsed.status === "error"; } /** Absent (still fetching) never claims Granola is connected — the trigger diff --git a/apps/web/src/insights-deeplinks.test.ts b/apps/web/src/insights-deeplinks.test.ts index d4ce7348e0..c8d1e4b307 100644 --- a/apps/web/src/insights-deeplinks.test.ts +++ b/apps/web/src/insights-deeplinks.test.ts @@ -33,9 +33,7 @@ describe("runDetailPath", () => { describe("runDeepLinkTarget", () => { test("returns the insights run-detail path for a purpose run", () => { - expect(runDeepLinkTarget(run("run_42"))).toBe( - `${INSIGHTS_RUNS_PATH}/run_42`, - ); + expect(runDeepLinkTarget(run("run_42"))).toBe(`${INSIGHTS_RUNS_PATH}/run_42`); }); test("is stable across two runs that differ only by id", () => { @@ -49,8 +47,6 @@ describe("workbenchInsightsPath", () => { }); test("encodes a workbench id so a slash or space cannot break out of the segment", () => { - expect(workbenchInsightsPath("a/b c")).toBe( - "/insights/workbench/a%2Fb%20c", - ); + expect(workbenchInsightsPath("a/b c")).toBe("/insights/workbench/a%2Fb%20c"); }); }); diff --git a/apps/web/src/insights-path.test.ts b/apps/web/src/insights-path.test.ts index 2f6086412f..4b335c7140 100644 --- a/apps/web/src/insights-path.test.ts +++ b/apps/web/src/insights-path.test.ts @@ -33,9 +33,7 @@ describe("parseInsightsPath", () => { }); test("a malformed escape on a workbench deep link falls back to landing, not a throw", () => { - expect(() => - parseInsightsPath("/insights/workbench/%E0%A4%A"), - ).not.toThrow(); + expect(() => parseInsightsPath("/insights/workbench/%E0%A4%A")).not.toThrow(); expect(parseInsightsPath("/insights/workbench/%E0%A4%A")).toEqual({ mode: "landing", runId: null, diff --git a/apps/web/src/insights-stats.test.ts b/apps/web/src/insights-stats.test.ts index 152ee12650..a749fa1740 100644 --- a/apps/web/src/insights-stats.test.ts +++ b/apps/web/src/insights-stats.test.ts @@ -12,9 +12,7 @@ import { import type { InsightsRun } from "./insights-api"; import type { ScheduledWorkflowDefinition } from "./routines-api"; -function run( - partial: Partial & Pick, -): InsightsRun { +function run(partial: Partial & Pick): InsightsRun { return { tenantId: "t1", definitionId: "def", @@ -92,9 +90,7 @@ describe("computeInsightsStats", () => { run({ id: "stale", status: "running", - createdAt: new Date( - Date.now() - FIRE_RUNNING_WINDOW_MS - 1, - ).toISOString(), + createdAt: new Date(Date.now() - FIRE_RUNNING_WINDOW_MS - 1).toISOString(), }), ], [], @@ -140,9 +136,7 @@ describe("purposeRunsForInsights", () => { const deployment = run({ id: "ins_deployed", status: "running" }); test("drops a workbench-host run by its definition-name pattern", () => { - expect(purposeRunsForInsights([deployment, workbenchHost])).toEqual([ - deployment, - ]); + expect(purposeRunsForInsights([deployment, workbenchHost])).toEqual([deployment]); }); test("leaves an ordinary top-level deployment run alone", () => { diff --git a/apps/web/src/insights-stats.ts b/apps/web/src/insights-stats.ts index 72aa8f3799..f52ff25fe5 100644 --- a/apps/web/src/insights-stats.ts +++ b/apps/web/src/insights-stats.ts @@ -3,10 +3,7 @@ // existing endpoints. import { isWorkbenchHostDefinitionName } from "@corbits/chat-ui/wire/workbench-host-naming"; -import { - runOutcomeStatus, - withListingAbandoned, -} from "@corbits/workflows/client"; +import { runOutcomeStatus, withListingAbandoned } from "@corbits/workflows/client"; import type { InsightsRun } from "./insights-api"; import type { ScheduledWorkflowDefinition } from "./routines-api"; @@ -54,12 +51,8 @@ export const INSIGHTS_RECENT_LIMIT = 12; * creation) with a scoped feed; CL-8087 repointed that scoped feed from * the deleted `feed=fires` route to the native listing. */ -export function purposeRunsForInsights( - runs: readonly InsightsRun[], -): readonly InsightsRun[] { - return runs.filter( - (run) => !isWorkbenchHostDefinitionName(run.definitionName), - ); +export function purposeRunsForInsights(runs: readonly InsightsRun[]): readonly InsightsRun[] { + return runs.filter((run) => !isWorkbenchHostDefinitionName(run.definitionName)); } /** @@ -102,9 +95,7 @@ export type DefinitionRunGroup = { * fires equivalent exists. Group order follows each group's own newest * run, newest overall first. */ -export function groupRunsByDefinition( - runs: readonly InsightsRun[], -): readonly DefinitionRunGroup[] { +export function groupRunsByDefinition(runs: readonly InsightsRun[]): readonly DefinitionRunGroup[] { const byGroupKey = new Map(); for (const run of runs) { const groupKey = run.routineId ?? run.definitionId; @@ -116,9 +107,7 @@ export function groupRunsByDefinition( } } const groups = [...byGroupKey.entries()].map(([groupKey, group]) => { - const runs = [...group].sort((a, b) => - b.createdAt.localeCompare(a.createdAt), - ); + const runs = [...group].sort((a, b) => b.createdAt.localeCompare(a.createdAt)); // Name from the newest run, after sorting — a routine or definition // rename must show the current name in the group header, not // whatever name its oldest fetched run happened to carry. @@ -145,8 +134,7 @@ export function computeInsightsStats( let stopped = 0; let deployed = 0; for (const run of purposeful) { - const outcome = - runOutcomeStatus(withListingAbandoned(run, now), now) ?? run.status; + const outcome = runOutcomeStatus(withListingAbandoned(run, now), now) ?? run.status; switch (outcome) { case "running": case "updating": diff --git a/apps/web/src/insights-timeline.test.ts b/apps/web/src/insights-timeline.test.ts index a9cb416381..d8b05eee64 100644 --- a/apps/web/src/insights-timeline.test.ts +++ b/apps/web/src/insights-timeline.test.ts @@ -25,11 +25,7 @@ describe("bucketRunsByDay", () => { const buckets = bucketRunsByDay([], 3, NOW); expect(buckets).toHaveLength(3); // Oldest first: Jan 13, Jan 14, Jan 15 (UTC days, since NOW is UTC). - expect(buckets.map((b) => b.key)).toEqual([ - "2026-01-13", - "2026-01-14", - "2026-01-15", - ]); + expect(buckets.map((b) => b.key)).toEqual(["2026-01-13", "2026-01-14", "2026-01-15"]); expect(buckets.every((b) => b.count === 0)).toBe(true); }); diff --git a/apps/web/src/insights-timeline.ts b/apps/web/src/insights-timeline.ts index c467eb6209..5ffbe2f166 100644 --- a/apps/web/src/insights-timeline.ts +++ b/apps/web/src/insights-timeline.ts @@ -16,20 +16,7 @@ export type DayBucket = { /** Default window for the timeline strip. */ export const INSIGHTS_TIMELINE_DAYS = 14; -const MONTHS = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", -]; +const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; /** * Buckets `runs` into the last `days` UTC days ending on `now`'s day, oldest @@ -43,11 +30,7 @@ export function bucketRunsByDay( ): readonly DayBucket[] { // Start of the current UTC day, then walk back `days - 1` days so the // window includes today. - const todayUTC = Date.UTC( - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate(), - ); + const todayUTC = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()); const dayMs = 86_400_000; const counts = new Map(); diff --git a/apps/web/src/insights-workbench-scope.test.ts b/apps/web/src/insights-workbench-scope.test.ts index e7b0de8e0e..316b057e56 100644 --- a/apps/web/src/insights-workbench-scope.test.ts +++ b/apps/web/src/insights-workbench-scope.test.ts @@ -39,9 +39,7 @@ describe("resolveWorkbenchInsightsScope", () => { }); test("reports not-found for an id absent from the bench's workbench list", () => { - const workbenches = [ - workbench({ id: "ch_1", tenancy: { tenantId: "tnt_1" } }), - ]; + const workbenches = [workbench({ id: "ch_1", tenancy: { tenantId: "tnt_1" } })]; expect(resolveWorkbenchInsightsScope(workbenches, "tnt_stale")).toEqual({ kind: "not-found", }); diff --git a/apps/web/src/instant-agent-create.test.ts b/apps/web/src/instant-agent-create.test.ts index afb690f471..e7f7a455cc 100644 --- a/apps/web/src/instant-agent-create.test.ts +++ b/apps/web/src/instant-agent-create.test.ts @@ -26,8 +26,7 @@ describe("createWorkbench", () => { function stubFetch(respond: (path: string) => Response): RecordedCall[] { const calls: RecordedCall[] = []; globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { - const path = - typeof input === "string" ? input : new URL(String(input)).pathname; + const path = typeof input === "string" ? input : new URL(String(input)).pathname; calls.push(init === undefined ? { path } : { path, init }); return Promise.resolve(respond(path)); }) as typeof fetch; @@ -71,20 +70,10 @@ describe("createWorkbench", () => { throw new Error(`unexpected fetch: ${path}`); }); - await createWorkbench( - "tnt_1", - (to) => navigated.push(to), - newQueryClient(), - ); - await createWorkbench( - "tnt_1", - (to) => navigated.push(to), - newQueryClient(), - ); + await createWorkbench("tnt_1", (to) => navigated.push(to), newQueryClient()); + await createWorkbench("tnt_1", (to) => navigated.push(to), newQueryClient()); - const createCalls = calls.filter((call) => - call.path.endsWith("/chat/workbenches"), - ); + const createCalls = calls.filter((call) => call.path.endsWith("/chat/workbenches")); expect(createCalls).toHaveLength(2); expect(navigated).toEqual(["/w/chan-1", "/w/chan-2"]); expect(navigated[0]).not.toBe(navigated[1]); @@ -109,9 +98,7 @@ describe("createWorkbench", () => { cause = error; } expect(cause).toBeInstanceOf(WorkbenchPreconditionError); - expect((cause as WorkbenchPreconditionError).kind).toBe( - "setup-agent-missing", - ); + expect((cause as WorkbenchPreconditionError).kind).toBe("setup-agent-missing"); }); test("a first message renames the room off New Workbench via chat/name", async () => { diff --git a/apps/web/src/instant-agent-create.ts b/apps/web/src/instant-agent-create.ts index 416da7eef8..97baa59358 100644 --- a/apps/web/src/instant-agent-create.ts +++ b/apps/web/src/instant-agent-create.ts @@ -22,10 +22,7 @@ import { import { reportError } from "@corbits/error-sink"; import { listAgentDefinitions } from "./agents-api"; -import { - autoNameFromFirstMessage, - NEW_WORKBENCH_TITLE, -} from "./auto-workbench-title"; +import { autoNameFromFirstMessage, NEW_WORKBENCH_TITLE } from "./auto-workbench-title"; import { findMyraDefinition } from "./myra-workbench"; import { workbenchPath } from "./workbench-path"; @@ -120,10 +117,7 @@ export async function createWorkbench( ): Promise { const definitions = await listAgentDefinitions(tenantId); if (findMyraDefinition(definitions) === undefined) { - throw new WorkbenchPreconditionError( - SETUP_AGENT_MISSING_MESSAGE, - "setup-agent-missing", - ); + throw new WorkbenchPreconditionError(SETUP_AGENT_MISSING_MESSAGE, "setup-agent-missing"); } const workbench = await createWorkbenchChannel(tenantId, { kind: "workbench", @@ -131,26 +125,16 @@ export async function createWorkbench( }); if (firstMessage !== undefined && firstMessage.trim() !== "") { - const selectedAgentInvites = [...new Set(selectedAgentDefinitionIds)].map( - (definitionId) => ({ kind: "agent" as const, definitionId }), - ); + const selectedAgentInvites = [...new Set(selectedAgentDefinitionIds)].map((definitionId) => ({ + kind: "agent" as const, + definitionId, + })); try { - await sendMessage( - tenantId, - workbench.id, - partsForSend(firstMessage, []), - { - ...(selectedAgentInvites.length > 0 - ? { invite: selectedAgentInvites } - : {}), - }, - ); + await sendMessage(tenantId, workbench.id, partsForSend(firstMessage, []), { + ...(selectedAgentInvites.length > 0 ? { invite: selectedAgentInvites } : {}), + }); } catch (cause) { - throw new WorkbenchPostCreateError( - workbench.id, - "opening-message", - cause, - ); + throw new WorkbenchPostCreateError(workbench.id, "opening-message", cause); } // Blank / ad-hoc mints stay "New Workbench" until named. When the // prompt box already supplied the opening message, rename via the same diff --git a/apps/web/src/last-workbench.ts b/apps/web/src/last-workbench.ts index 208f020a5c..6440abf650 100644 --- a/apps/web/src/last-workbench.ts +++ b/apps/web/src/last-workbench.ts @@ -8,10 +8,7 @@ const KEY_PREFIX = "workbench.lastWorkbenchId."; -export function recordLastWorkbenchId( - benchTenantId: string, - workbenchId: string, -): void { +export function recordLastWorkbenchId(benchTenantId: string, workbenchId: string): void { try { window.sessionStorage.setItem(`${KEY_PREFIX}${benchTenantId}`, workbenchId); } catch { diff --git a/apps/web/src/library-workbench-scope.test.ts b/apps/web/src/library-workbench-scope.test.ts index aae4cc960c..228a4c16c3 100644 --- a/apps/web/src/library-workbench-scope.test.ts +++ b/apps/web/src/library-workbench-scope.test.ts @@ -35,9 +35,7 @@ describe("resolveLibraryWorkbenchScope", () => { }); test("returns null for an id absent from the bench's workbench list", () => { - const workbenches = [ - workbench({ id: "ch_1", tenancy: { tenantId: "tnt_1" } }), - ]; + const workbenches = [workbench({ id: "ch_1", tenancy: { tenantId: "tnt_1" } })]; expect(resolveLibraryWorkbenchScope(workbenches, "ch_stale")).toBeNull(); }); diff --git a/apps/web/src/login-next.test.ts b/apps/web/src/login-next.test.ts index 7b4564c92d..645a643579 100644 --- a/apps/web/src/login-next.test.ts +++ b/apps/web/src/login-next.test.ts @@ -27,9 +27,7 @@ describe("validatedNextPath", () => { }); test("an absolute URL is rejected", () => { - expect(validatedNextPath("?next=https%3A%2F%2Fevil.example%2Fphish")).toBe( - "/", - ); + expect(validatedNextPath("?next=https%3A%2F%2Fevil.example%2Fphish")).toBe("/"); }); test("a protocol-relative path is rejected", () => { diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index a2569fb590..61b0f08706 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -58,8 +58,7 @@ function Root() { message: string; refId?: string | undefined; } | null>(null); - const provisionedUserId = - session.kind === "signed-in" ? session.user.id : null; + const provisionedUserId = session.kind === "signed-in" ? session.user.id : null; const runProvisioning = useCallback(() => { if (provisionedUserId === null) return () => undefined; let cancelled = false; @@ -83,9 +82,7 @@ function Root() { const handleSignOut = useCallback(() => { setSession({ kind: "signed-out" }); - toast( - "Signed out. If you were on a shared computer, close the browser to be sure.", - ); + toast("Signed out. If you were on a shared computer, close the browser to be sure."); void signOut().then((ok) => { if (ok) return; log.error("Sign-out request to the server failed"); @@ -99,9 +96,7 @@ function Root() { // exposes no onChange hook or externally-supplied initial value a host // could observe or override without forking the component. const themeStorageKey = - session.kind === "signed-in" - ? `corbits-theme:${session.user.id}` - : "corbits-theme"; + session.kind === "signed-in" ? `corbits-theme:${session.user.id}` : "corbits-theme"; return ( diff --git a/apps/web/src/myra-deploy.ts b/apps/web/src/myra-deploy.ts index 88cb83918e..69060d8191 100644 --- a/apps/web/src/myra-deploy.ts +++ b/apps/web/src/myra-deploy.ts @@ -34,9 +34,7 @@ async function readErrorBody(response: Response): Promise { const envelope = type({ error: { code: "string", userMessage: "string", refId: "string" }, })(body); - return envelope instanceof type.errors - ? `HTTP ${response.status}` - : envelope.error.userMessage; + return envelope instanceof type.errors ? `HTTP ${response.status}` : envelope.error.userMessage; } /** Idempotently ensures the `package-registry` asset Myra's tarball is @@ -46,31 +44,24 @@ export async function ensureMyraSourceAsset( tenantId: string, fetchImpl: typeof fetch = fetch, ): Promise { - const created = await fetchImpl( - `/api/tenants/${encodeURIComponent(tenantId)}/assets`, - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - kind: MYRA_SOURCE_CONFIG.assetKind, - name: MYRA_SOURCE_CONFIG.assetName, - displayName: MYRA_SOURCE_CONFIG.displayName, - }), - }, - ); + const created = await fetchImpl(`/api/tenants/${encodeURIComponent(tenantId)}/assets`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + kind: MYRA_SOURCE_CONFIG.assetKind, + name: MYRA_SOURCE_CONFIG.assetName, + displayName: MYRA_SOURCE_CONFIG.displayName, + }), + }); if (created.status === 201) { const parsed = AssetCreatedShape(await created.json()); if (parsed instanceof type.errors) { - throw new MyraDeployError( - `Myra's source came back an unexpected shape: ${parsed.summary}`, - ); + throw new MyraDeployError(`Myra's source came back an unexpected shape: ${parsed.summary}`); } return parsed.id; } if (created.status !== 409) { - throw new MyraDeployError( - `preparing Myra's source failed: ${await readErrorBody(created)}`, - ); + throw new MyraDeployError(`preparing Myra's source failed: ${await readErrorBody(created)}`); } const listed = await fetchImpl( `/api/tenants/${encodeURIComponent(tenantId)}/assets?kind=${MYRA_SOURCE_CONFIG.assetKind}&inherited=false`, @@ -86,9 +77,7 @@ export async function ensureMyraSourceAsset( `this workbench's setup list came back an unexpected shape: ${parsed.summary}`, ); } - const existing = parsed.find( - (asset) => asset.name === MYRA_SOURCE_CONFIG.assetName, - ); + const existing = parsed.find((asset) => asset.name === MYRA_SOURCE_CONFIG.assetName); if (existing === undefined) { throw new MyraDeployError( "Myra's source reported a name conflict but is not listed on this workbench", @@ -141,8 +130,7 @@ function tarEntry(path: string, content: Uint8Array): Uint8Array { for (const byte of header) checksum += byte; writeField(148, padOctal(checksum, 8).slice(0, 7) + "\0 "); - const paddedLength = - Math.ceil(content.length / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE; + const paddedLength = Math.ceil(content.length / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE; const body = new Uint8Array(paddedLength); body.set(content); @@ -155,17 +143,14 @@ function tarEntry(path: string, content: Uint8Array): Uint8Array { /** Packs a `{ path: contents }` tree (as `renderWorkflowSourceTree` * returns) into an npm-shaped tar archive rooted at `package/`, then * gzips it. */ -export async function packTarball( - tree: Readonly>, -): Promise { +export async function packTarball(tree: Readonly>): Promise { const encoder = new TextEncoder(); const entries: Uint8Array[] = []; for (const [path, contents] of Object.entries(tree)) { entries.push(tarEntry(`package/${path}`, encoder.encode(contents))); } const endOfArchive = new Uint8Array(TAR_BLOCK_SIZE * 2); - const totalLength = - entries.reduce((sum, entry) => sum + entry.length, 0) + endOfArchive.length; + const totalLength = entries.reduce((sum, entry) => sum + entry.length, 0) + endOfArchive.length; const tar = new Uint8Array(totalLength); let offset = 0; for (const entry of entries) { @@ -174,17 +159,12 @@ export async function packTarball( } tar.set(endOfArchive, offset); - const gzipStream = new Blob([tar]) - .stream() - .pipeThrough(new CompressionStream("gzip")); + const gzipStream = new Blob([tar]).stream().pipeThrough(new CompressionStream("gzip")); return new Uint8Array(await new Response(gzipStream).arrayBuffer()); } function tarballFilename(): string { - const safeName = MYRA_SOURCE_CONFIG.packageName.replace( - /[^A-Za-z0-9_.@+-]/g, - "-", - ); + const safeName = MYRA_SOURCE_CONFIG.packageName.replace(/[^A-Za-z0-9_.@+-]/g, "-"); return `${safeName}-${MYRA_SOURCE_CONFIG.packageVersion}.tgz`; } @@ -252,9 +232,7 @@ export async function publishMyraTarball( ): Promise { const tree = renderWorkflowSourceTree({ packageName: MYRA_SOURCE_CONFIG.packageName, - workflowJson: JSON.stringify( - buildMyraDefinitionJson(`assistant@${tenantDomain}`), - ), + workflowJson: JSON.stringify(buildMyraDefinitionJson(`assistant@${tenantDomain}`)), }); const tarball = await packTarball(tree); const filename = tarballFilename(); @@ -267,9 +245,7 @@ export async function publishMyraTarball( }, ); if (!response.ok) { - throw new MyraDeployError( - `publishing Myra's tarball failed: ${await readErrorBody(response)}`, - ); + throw new MyraDeployError(`publishing Myra's tarball failed: ${await readErrorBody(response)}`); } const parsed = TarballPutShape(await response.json()); if (parsed instanceof type.errors) { @@ -290,9 +266,7 @@ export function buildMyraDeployInput(args: { defaultSourceOfferingId: string; }): WorkflowDeployInput { if (args.sourceOfferingIds.length === 0) { - throw new MyraDeployError( - "at least one source offering id is required to start Myra", - ); + throw new MyraDeployError("at least one source offering id is required to start Myra"); } if (!args.sourceOfferingIds.includes(args.defaultSourceOfferingId)) { throw new MyraDeployError( @@ -324,12 +298,7 @@ export async function deployMyraSource( fetchImpl: typeof fetch = fetch, ): Promise { const assetId = await ensureMyraSourceAsset(args.tenantId, fetchImpl); - await publishMyraTarball( - args.tenantId, - assetId, - args.tenantDomain, - fetchImpl, - ); + await publishMyraTarball(args.tenantId, assetId, args.tenantDomain, fetchImpl); return buildMyraDeployInput({ assetId, sourceOfferingIds: args.sourceOfferingIds, diff --git a/apps/web/src/myra-workbench.test.ts b/apps/web/src/myra-workbench.test.ts index 2bbdfa53ff..2c7d6de289 100644 --- a/apps/web/src/myra-workbench.test.ts +++ b/apps/web/src/myra-workbench.test.ts @@ -24,10 +24,7 @@ describe("myra-workbench helpers", () => { }); }); -function definition(partial: { - readonly id: string; - readonly name: string; -}): AgentDefinition { +function definition(partial: { readonly id: string; readonly name: string }): AgentDefinition { return { id: partial.id, tenantId: "tnt_1", @@ -49,9 +46,7 @@ describe("findMyraDefinition", () => { }); test("returns undefined when no assistant definition is deployed", () => { - expect( - findMyraDefinition([definition({ id: "def-echo", name: "echo" })]), - ).toBeUndefined(); + expect(findMyraDefinition([definition({ id: "def-echo", name: "echo" })])).toBeUndefined(); }); }); @@ -68,8 +63,7 @@ describe("ensureMyraWorkbench", () => { function stubFetch(respond: (path: string) => Response): RecordedCall[] { const calls: RecordedCall[] = []; globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { - const path = - typeof input === "string" ? input : new URL(String(input)).pathname; + const path = typeof input === "string" ? input : new URL(String(input)).pathname; calls.push(init === undefined ? { path } : { path, init }); return Promise.resolve(respond(path)); }) as typeof fetch; @@ -105,13 +99,9 @@ describe("ensureMyraWorkbench", () => { const result = await ensureMyraWorkbench("tnt_1"); expect(result).toEqual({ kind: "ready", workbenchId: "chat-1" }); - expect(calls.some((call) => call.path.includes("kind=workbench"))).toBe( - false, - ); + expect(calls.some((call) => call.path.includes("kind=workbench"))).toBe(false); expect(calls.some((call) => call.path.includes("kind=chat"))).toBe(false); - const createCall = calls.find((call) => - call.path.endsWith("/chat/workbenches"), - ); + const createCall = calls.find((call) => call.path.endsWith("/chat/workbenches")); expect(createCall?.init?.method).toBe("POST"); expect(JSON.parse(String(createCall?.init?.body))).toEqual({ kind: "chat", diff --git a/apps/web/src/myra-workbench.ts b/apps/web/src/myra-workbench.ts index c3fd26e3ca..85c2df6ed3 100644 --- a/apps/web/src/myra-workbench.ts +++ b/apps/web/src/myra-workbench.ts @@ -3,10 +3,7 @@ // `createDefaultAgentWorkbench`; this file's job is to name Myra as the // configured agent and wire it to this app's agent-definitions fetch. -import { - createDefaultAgentWorkbench, - findDefinitionByAssetName, -} from "@corbits/chat-ui"; +import { createDefaultAgentWorkbench, findDefinitionByAssetName } from "@corbits/chat-ui"; import { WORKFLOW_CATALOG } from "@corbits/workflows/catalog"; import { listAgentDefinitions, type AgentDefinition } from "./agents-api"; diff --git a/apps/web/src/navigation.tsx b/apps/web/src/navigation.tsx index 7777bff1e6..fec5c0b8b8 100644 --- a/apps/web/src/navigation.tsx +++ b/apps/web/src/navigation.tsx @@ -41,9 +41,7 @@ export function NavigationProvider({ return ( - - {children} - + {children} ); @@ -82,11 +80,7 @@ export function handleLinkClick( navigate(to); } -export function Link({ - to, - onClick, - ...props -}: ComponentProps<"a"> & { readonly to: string }) { +export function Link({ to, onClick, ...props }: ComponentProps<"a"> & { readonly to: string }) { const navigate = useNavigate(); return ( { listRunMail: (input) => { calls.push(`listRunMail:${input.tenantId}`); return Promise.resolve([ - mailMessage("", "ada@example.com", [ - "bea@example.com", - ]), + mailMessage("", "ada@example.com", ["bea@example.com"]), ]); }, - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -231,15 +228,10 @@ describe("group workbench creation sequence", () => { }, listRunMail: () => Promise.resolve([ - mailMessage("", "ada@example.com", [ - "bea@example.com", - ]), - mailMessage("", "bea@example.com", [ - "ada@example.com", - ]), + mailMessage("", "ada@example.com", ["bea@example.com"]), + mailMessage("", "bea@example.com", ["ada@example.com"]), ]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -264,12 +256,7 @@ describe("group workbench creation sequence", () => { ], }); - const report = await convergeNeedsList( - manifest, - hub, - store, - snapshotWithChild("tnt_atlas"), - ); + const report = await convergeNeedsList(manifest, hub, store, snapshotWithChild("tnt_atlas")); // No resend: the recorded native Message-ID is the idempotency proof. // The reply carries no In-Reply-To, so it groups as a sub-thread. @@ -314,8 +301,7 @@ describe("group workbench creation sequence", () => { deployWorkflow: () => Promise.reject(new Error("unexpected deploy")), sendRunMail: () => Promise.resolve({ messageId: "" }), listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -325,20 +311,13 @@ describe("group workbench creation sequence", () => { workbenches: [{ localId: "atlas", slug: "ada-atlas", name: "Atlas" }], }); - const report = await convergeNeedsList( - manifest, - hub, - store, - snapshotWithMyra(), - ); + const report = await convergeNeedsList(manifest, hub, store, snapshotWithMyra()); // No initialMessage supplied, so no primary-thread send — and no mail // to read means no primary thread entry either. expect(report.createdTenantIds).toEqual(["tnt_atlas"]); expect(report.primaryThreads).toEqual([]); - expect(store.load()).toEqual([ - { localId: "atlas", tenantId: "tnt_atlas", kind: "workbench" }, - ]); + expect(store.load()).toEqual([{ localId: "atlas", tenantId: "tnt_atlas", kind: "workbench" }]); }); }); @@ -365,8 +344,7 @@ describe("writes after gap checks", () => { return Promise.resolve({ messageId: "" }); }, listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; } @@ -395,9 +373,7 @@ describe("writes after gap checks", () => { (error: unknown) => error, ); expect(failure).toBeInstanceOf(StockHubCapabilityError); - expect((failure as StockHubCapabilityError).capability).toBe( - "deploy-workflow-inputs", - ); + expect((failure as StockHubCapabilityError).capability).toBe("deploy-workflow-inputs"); expect(calls).toEqual([]); }); @@ -471,9 +447,7 @@ describe("writes after gap checks", () => { localId: "bot", slug: "ada-bot", name: "Bot", - principals: [ - { kind: "workflow", refId: "run_bot", roles: ["member"] }, - ], + principals: [{ kind: "workflow", refId: "run_bot", roles: ["member"] }], }, { localId: "bossy", @@ -501,9 +475,7 @@ describe("writes after gap checks", () => { (error: unknown) => error, ); expect(failure).toBeInstanceOf(StockHubCapabilityError); - expect((failure as StockHubCapabilityError).capability).toBe( - "project-workflow-principal", - ); + expect((failure as StockHubCapabilityError).capability).toBe("project-workflow-principal"); expect(calls).toEqual([]); }); }); @@ -530,8 +502,7 @@ describe("thread-native DM derivation", () => { deployWorkflow: () => Promise.reject(new Error("unexpected deploy")), sendRunMail: () => Promise.reject(new Error("unexpected send")), listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -552,10 +523,7 @@ describe("thread-native DM derivation", () => { mailMessage("", "ada@example.com", ["myra@example.com"], { subject: "Myra daily", }), - mailMessage("", "myra@example.com", [ - "ada@example.com", - "bea@example.com", - ]), + mailMessage("", "myra@example.com", ["ada@example.com", "bea@example.com"]), ], ); @@ -585,8 +553,7 @@ describe("sub-thread forking", () => { return Promise.resolve({ messageId: "" }); }, listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -596,30 +563,16 @@ describe("sub-thread forking", () => { messageId: "", references: [] as readonly string[], }; - const first = await forkSubThread( - storage, - hub, - HUB_SCOPE, - ACCOUNT_ID, - parent, - { - to: ["bea@example.com"], - subject: "Atlas delivery", - body: "First delivery update.", - }, - ); - const replay = await forkSubThread( - storage, - hub, - HUB_SCOPE, - ACCOUNT_ID, - parent, - { - to: ["bea@example.com"], - subject: "Atlas delivery", - body: "First delivery update.", - }, - ); + const first = await forkSubThread(storage, hub, HUB_SCOPE, ACCOUNT_ID, parent, { + to: ["bea@example.com"], + subject: "Atlas delivery", + body: "First delivery update.", + }); + const replay = await forkSubThread(storage, hub, HUB_SCOPE, ACCOUNT_ID, parent, { + to: ["bea@example.com"], + subject: "Atlas delivery", + body: "First delivery update.", + }); expect(first).toBe(""); // Same parent + subject replays the recorded id instead of resending. @@ -652,8 +605,7 @@ describe("sub-thread forking", () => { return Promise.resolve({ messageId: `` }); }, listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -663,30 +615,16 @@ describe("sub-thread forking", () => { messageId: "", references: [] as readonly string[], }; - const first = await forkSubThread( - storage, - hub, - HUB_SCOPE, - ACCOUNT_ID, - parent, - { - to: ["bea@example.com"], - subject: "Atlas delivery", - body: "First delivery update.", - }, - ); - const edited = await forkSubThread( - storage, - hub, - HUB_SCOPE, - ACCOUNT_ID, - parent, - { - to: ["bea@example.com"], - subject: "Atlas delivery", - body: "Edited delivery update.", - }, - ); + const first = await forkSubThread(storage, hub, HUB_SCOPE, ACCOUNT_ID, parent, { + to: ["bea@example.com"], + subject: "Atlas delivery", + body: "First delivery update.", + }); + const edited = await forkSubThread(storage, hub, HUB_SCOPE, ACCOUNT_ID, parent, { + to: ["bea@example.com"], + subject: "Atlas delivery", + body: "Edited delivery update.", + }); // An edited body is a new send, never a replay of the recorded id. expect(first).toBe(""); @@ -714,8 +652,7 @@ describe("sub-thread forking", () => { return Promise.resolve({ messageId: `` }); }, listRunMail: () => Promise.resolve([]), - searchAgentMailbox: () => - Promise.reject(new Error("unexpected mailbox search")), + searchAgentMailbox: () => Promise.reject(new Error("unexpected mailbox search")), readMailThread: () => Promise.reject(new Error("unexpected thread read")), }; @@ -725,30 +662,16 @@ describe("sub-thread forking", () => { messageId: "", references: [] as readonly string[], }; - const first = await forkSubThread( - storage, - hub, - HUB_SCOPE, - ACCOUNT_ID, - parent, - { - to: ["bea@example.com"], - subject: "Atlas delivery", - body: "First delivery update.", - }, - ); - const retargeted = await forkSubThread( - storage, - hub, - HUB_SCOPE, - ACCOUNT_ID, - parent, - { - to: ["bea@example.com", "cal@example.com"], - subject: "Atlas delivery", - body: "First delivery update.", - }, - ); + const first = await forkSubThread(storage, hub, HUB_SCOPE, ACCOUNT_ID, parent, { + to: ["bea@example.com"], + subject: "Atlas delivery", + body: "First delivery update.", + }); + const retargeted = await forkSubThread(storage, hub, HUB_SCOPE, ACCOUNT_ID, parent, { + to: ["bea@example.com", "cal@example.com"], + subject: "Atlas delivery", + body: "First delivery update.", + }); expect(first).toBe(""); expect(retargeted).toBe(""); @@ -767,8 +690,7 @@ describe("stock-only fetch hub", () => { return (async (url: unknown, init?: { method?: string; body?: string }) => { const method = init?.method ?? "GET"; const path = String(url).split("?")[0] ?? String(url); - const body = - init?.body === undefined ? undefined : JSON.parse(init.body as string); + const body = init?.body === undefined ? undefined : JSON.parse(init.body as string); seen.push({ method, url: String(url), body }); const key = `${method} ${path}`; const payload = key in routes ? routes[key] : routes[path]; @@ -782,25 +704,19 @@ describe("stock-only fetch hub", () => { test("mailbox search and thread reads stay typed upstream gaps", async () => { const hub = createFetchStockHub(stubFetch([], {})); - const search = await hub - .searchAgentMailbox({ address: "myra@example.com" }) - .then( - () => null, - (error: unknown) => error, - ); - expect(search).toBeInstanceOf(StockHubCapabilityError); - expect((search as StockHubCapabilityError).capability).toBe( - "agent-mailbox-reads", + const search = await hub.searchAgentMailbox({ address: "myra@example.com" }).then( + () => null, + (error: unknown) => error, ); + expect(search).toBeInstanceOf(StockHubCapabilityError); + expect((search as StockHubCapabilityError).capability).toBe("agent-mailbox-reads"); const thread = await hub.readMailThread({ messageId: "" }).then( () => null, (error: unknown) => error, ); expect(thread).toBeInstanceOf(StockHubCapabilityError); - expect((thread as StockHubCapabilityError).capability).toBe( - "thread-fork-context", - ); + expect((thread as StockHubCapabilityError).capability).toBe("thread-fork-context"); }); test("every request stays on stock routes with no custom idempotency keys", async () => { diff --git a/apps/web/src/needs-converge.ts b/apps/web/src/needs-converge.ts index 6c9ca37946..206336d608 100644 --- a/apps/web/src/needs-converge.ts +++ b/apps/web/src/needs-converge.ts @@ -93,11 +93,7 @@ export type StockHub = { * owner — the stock route the first-signup installer step uses to * create the primary tenant; a workbench child tenant always supplies * it. */ - createTenant(input: { - name: string; - slug: string; - parentId?: string; - }): Promise; + createTenant(input: { name: string; slug: string; parentId?: string }): Promise; /** * Invites by email and assigns `role` (a system role name, e.g. * "member") in the same op — the stock invite route's `roleId` is the @@ -105,10 +101,7 @@ export type StockHub = { * principal lands with zero roles and every subsequent read 403s * (CL-8085/CL-8131 fix). */ - inviteMember( - tenantId: string, - input: { email: string; role: string }, - ): Promise; + inviteMember(tenantId: string, input: { email: string; role: string }): Promise; deployWorkflow(tenantId: string, input: WorkflowDeployInput): Promise; sendRunMail(input: SendRunMailInput): Promise<{ messageId: string }>; listRunMail(input: { tenantId: string }): Promise; @@ -179,17 +172,13 @@ export async function findOwnedTenants(hub: StockHub): Promise { membership.roles.some((role) => role.name === "owner"), ); return ( - await Promise.all( - activeOwned.map((membership) => hub.getTenant(membership.tenantId)), - ) + await Promise.all(activeOwned.map((membership) => hub.getTenant(membership.tenantId))) ).filter((tenant): tenant is HubTenant => tenant !== null); } export async function readHubSnapshot(hub: StockHub): Promise { const tenants = await findOwnedTenants(hub); - const primaryCandidates = tenants.filter( - (tenant) => tenant.parentId === null, - ); + const primaryCandidates = tenants.filter((tenant) => tenant.parentId === null); const [primaryTenant] = primaryCandidates; if (primaryCandidates.length !== 1 || primaryTenant === undefined) { throw new StockHubCapabilityError( @@ -197,16 +186,11 @@ export async function readHubSnapshot(hub: StockHub): Promise { `Sign-in must leave exactly one owned top-level home; found ${primaryCandidates.length}.`, ); } - const childTenants = tenants.filter( - (tenant) => tenant.parentId === primaryTenant.id, - ); + const childTenants = tenants.filter((tenant) => tenant.parentId === primaryTenant.id); const [primaryPrincipals, childRows] = await Promise.all([ hub.listPrincipals(primaryTenant.id), Promise.all( - childTenants.map( - async (tenant) => - [tenant.id, await hub.listPrincipals(tenant.id)] as const, - ), + childTenants.map(async (tenant) => [tenant.id, await hub.listPrincipals(tenant.id)] as const), ), ]); return { @@ -248,9 +232,7 @@ async function convergeWorkbenchTenant( workbench: NeedsList["workbenches"][number], ): Promise<{ tenantId: string; created: boolean }> { const stored = store.load().find((row) => row.localId === workbench.localId); - const existing = snapshot.childTenants.find( - (tenant) => tenant.id === stored?.tenantId, - ); + const existing = snapshot.childTenants.find((tenant) => tenant.id === stored?.tenantId); if (existing !== undefined && stored !== undefined) { return { tenantId: existing.id, created: false }; } @@ -355,12 +337,7 @@ export async function convergeNeedsList( // primary-thread first message. const createdTenantIds: string[] = []; for (const workbench of manifest.workbenches) { - const converged = await convergeWorkbenchTenant( - hub, - store, - snapshot, - workbench, - ); + const converged = await convergeWorkbenchTenant(hub, store, snapshot, workbench); if (converged.created) createdTenantIds.push(converged.tenantId); await convergePrimaryThread(hub, store, workbench, converged.tenantId); } @@ -370,9 +347,7 @@ export async function convergeNeedsList( // recorded primary id) plus forked sub-threads. const primaryThreads: PrimaryThread[] = []; for (const workbench of manifest.workbenches) { - const stored = store - .load() - .find((row) => row.localId === workbench.localId); + const stored = store.load().find((row) => row.localId === workbench.localId); if (stored === undefined) continue; const mail = await hub.listRunMail({ tenantId: stored.tenantId }); const grouped = deriveThreads(mail); @@ -388,9 +363,7 @@ export async function convergeNeedsList( primaryThreads.push({ tenantId: stored.tenantId, rootMessageId, - subThreads: grouped.filter( - (thread) => thread.rootMessageId !== rootMessageId, - ), + subThreads: grouped.filter((thread) => thread.rootMessageId !== rootMessageId), }); } @@ -548,10 +521,7 @@ async function resolveRoleId( return found.id; } -async function readJson( - response: Response, - operation: string, -): Promise { +async function readJson(response: Response, operation: string): Promise { if (!response.ok) { throw new StockHubRequestError( operation, @@ -608,23 +578,14 @@ async function fetchAllPages( export function createFetchStockHub(fetchImpl: typeof fetch = fetch): StockHub { return { async listMyPrincipals() { - return fetchAllPages( - fetchImpl, - "/api/me/principals", - "listMyPrincipals", - (body) => parseBoundary(MembershipPageShape, body, "listMyPrincipals"), + return fetchAllPages(fetchImpl, "/api/me/principals", "listMyPrincipals", (body) => + parseBoundary(MembershipPageShape, body, "listMyPrincipals"), ); }, async getTenant(id) { - const response = await fetchImpl( - `/api/tenants/${encodeURIComponent(id)}`, - ); + const response = await fetchImpl(`/api/tenants/${encodeURIComponent(id)}`); if (response.status === 404) return null; - const parsed = parseBoundary( - TenantShape, - await readJson(response, "getTenant"), - "getTenant", - ); + const parsed = parseBoundary(TenantShape, await readJson(response, "getTenant"), "getTenant"); return { ...parsed, parentId: parsed.parentId ?? null }; }, async listPrincipals(tenantId) { @@ -650,41 +611,32 @@ export function createFetchStockHub(fetchImpl: typeof fetch = fetch): StockHub { async inviteMember(tenantId, input) { const roleId = await resolveRoleId(fetchImpl, tenantId, input.role); await readJson( - await fetchImpl( - `/api/tenants/${encodeURIComponent(tenantId)}/members/invite`, - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ email: input.email, roleId }), - }, - ), + await fetchImpl(`/api/tenants/${encodeURIComponent(tenantId)}/members/invite`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ email: input.email, roleId }), + }), "inviteMember", ); }, async deployWorkflow(tenantId, input) { await readJson( - await fetchImpl( - `/api/tenants/${encodeURIComponent(tenantId)}/workflows/deployments`, - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(input), - }, - ), + await fetchImpl(`/api/tenants/${encodeURIComponent(tenantId)}/workflows/deployments`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(input), + }), "deployWorkflow", ); }, async sendRunMail(input) { const { tenantId, ...message } = input; const body = await readJson( - await fetchImpl( - `/api/tenants/${encodeURIComponent(tenantId)}/mailbox/messages`, - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(message), - }, - ), + await fetchImpl(`/api/tenants/${encodeURIComponent(tenantId)}/mailbox/messages`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(message), + }), "sendRunMail", ); return parseBoundary(SentMailShape, body, "sendRunMail"); diff --git a/apps/web/src/needs-list.test.ts b/apps/web/src/needs-list.test.ts index e61318571d..8713668b76 100644 --- a/apps/web/src/needs-list.test.ts +++ b/apps/web/src/needs-list.test.ts @@ -69,9 +69,7 @@ describe("portable needs-list", () => { account: { id: "usr_1", email: "ada@example.com", name: "Ada" }, myraDefinitionRefId: "assistant", }); - expect( - parseNeedsList({ ...manifest, version: 2 }) instanceof type.errors, - ).toBe(true); + expect(parseNeedsList({ ...manifest, version: 2 }) instanceof type.errors).toBe(true); }); }); @@ -80,11 +78,7 @@ describe("account and hub scoped child-tenant store", () => { const storage = memoryStorage(); const ada = childTenantStore(storage, "https://one.example", "usr_ada"); const bea = childTenantStore(storage, "https://one.example", "usr_bea"); - const otherHub = childTenantStore( - storage, - "https://two.example", - "usr_ada", - ); + const otherHub = childTenantStore(storage, "https://two.example", "usr_ada"); ada.record({ localId: "atlas", @@ -118,34 +112,29 @@ describe("account and hub scoped child-tenant store", () => { const storage = memoryStorage({ "workbench.child-tenants:https%3A%2F%2Fone.example:usr_ada": "not json", }); - expect( - childTenantStore(storage, "https://one.example", "usr_ada").load(), - ).toEqual([]); + expect(childTenantStore(storage, "https://one.example", "usr_ada").load()).toEqual([]); }); test("keeps valid rows when one row is corrupt, and drops legacy DM rows", () => { const storage = memoryStorage({ - "workbench.child-tenants:https%3A%2F%2Fone.example:usr_ada": - JSON.stringify([ - { - localId: "atlas", - tenantId: "tnt_atlas", - kind: "workbench", - primaryThreadMessageId: "", - }, - { localId: "", tenantId: "tnt_bad", kind: "workbench" }, - { - localId: "dm:run_myra", - tenantId: "tnt_dm", - kind: "chat", - principalRefId: "run_myra", - }, - null, - ]), + "workbench.child-tenants:https%3A%2F%2Fone.example:usr_ada": JSON.stringify([ + { + localId: "atlas", + tenantId: "tnt_atlas", + kind: "workbench", + primaryThreadMessageId: "", + }, + { localId: "", tenantId: "tnt_bad", kind: "workbench" }, + { + localId: "dm:run_myra", + tenantId: "tnt_dm", + kind: "chat", + principalRefId: "run_myra", + }, + null, + ]), }); - expect( - childTenantStore(storage, "https://one.example", "usr_ada").load(), - ).toEqual([ + expect(childTenantStore(storage, "https://one.example", "usr_ada").load()).toEqual([ { localId: "atlas", tenantId: "tnt_atlas", @@ -188,21 +177,19 @@ describe("account and hub scoped thread-link store", () => { test("treats corrupt client state as empty and skips bad rows", () => { const storage = memoryStorage({ - "workbench.thread-links:https%3A%2F%2Fone.example:usr_ada": - JSON.stringify([ - { workbenchLocalId: "atlas", messageId: "" }, - { workbenchLocalId: "", messageId: "" }, - null, - ]), + "workbench.thread-links:https%3A%2F%2Fone.example:usr_ada": JSON.stringify([ + { workbenchLocalId: "atlas", messageId: "" }, + { workbenchLocalId: "", messageId: "" }, + null, + ]), }); - expect( - threadLinkStore(storage, "https://one.example", "usr_ada").load(), - ).toEqual([{ workbenchLocalId: "atlas", messageId: "" }]); + expect(threadLinkStore(storage, "https://one.example", "usr_ada").load()).toEqual([ + { workbenchLocalId: "atlas", messageId: "" }, + ]); expect( threadLinkStore( memoryStorage({ - "workbench.thread-links:https%3A%2F%2Fone.example:usr_ada": - "not json", + "workbench.thread-links:https%3A%2F%2Fone.example:usr_ada": "not json", }), "https://one.example", "usr_ada", diff --git a/apps/web/src/onboarding.ts b/apps/web/src/onboarding.ts index 46895c6981..af3c993312 100644 --- a/apps/web/src/onboarding.ts +++ b/apps/web/src/onboarding.ts @@ -35,18 +35,14 @@ export type ActiveCredentialProbe = * so the caller treats it as the weaker check it is: only a confirmed * miss means "no key", never a probe failure (CL-6868). */ -export async function hasActiveCredential( - tenantId: string, -): Promise { +export async function hasActiveCredential(tenantId: string): Promise { try { const response = await fetch(`/api/tenants/${tenantId}/credentials`); if (!response.ok) return { kind: "error" }; const body: unknown = await response.json().catch(() => null); const parsed = CredentialsPage(body); if (parsed instanceof type.errors) return { kind: "error" }; - return parsed.data.some((c) => c.status === "active") - ? { kind: "active" } - : { kind: "none" }; + return parsed.data.some((c) => c.status === "active") ? { kind: "active" } : { kind: "none" }; } catch { return { kind: "error" }; } @@ -81,9 +77,7 @@ export type ProvisionOutcome = export async function triggerFirstLoginProvisioning(): Promise { try { const owned = await findOwnedTenants(createFetchStockHub()); - return owned.length === 0 - ? { kind: "needs-onboarding" } - : { kind: "existing-member" }; + return owned.length === 0 ? { kind: "needs-onboarding" } : { kind: "existing-member" }; } catch (cause) { const refId = reportError(cause, { operation: "first_login_provisioning" }); return { kind: "error", message: FALLBACK_ERROR_MESSAGE, refId }; @@ -137,9 +131,7 @@ export type AgentReadiness = * absence stays an explicit, testable outcome — a 404/410 answers * `route-gone`, never the generic agent error below. */ -export async function fetchAgentReadiness( - tenantId: string, -): Promise { +export async function fetchAgentReadiness(tenantId: string): Promise { try { const response = await fetch( `/api/onboarding/provisioning-status?${new URLSearchParams({ tenantId })}`, @@ -159,12 +151,9 @@ export async function fetchAgentReadiness( throw new Error(`Agent readiness request failed (${response.status})`); } const parsed = ProvisioningStatus(body); - if (parsed instanceof type.errors) - throw new Error("Invalid agent readiness response"); + if (parsed instanceof type.errors) throw new Error("Invalid agent readiness response"); if (parsed.kind === "ready") return { kind: "ready" }; - return parsed.setupAgentReady - ? { kind: "chat-ready" } - : { kind: "preparing" }; + return parsed.setupAgentReady ? { kind: "chat-ready" } : { kind: "preparing" }; } catch (cause) { const refId = reportError(cause, { operation: "agent_readiness", diff --git a/apps/web/src/onboarding/onboarding-layout.tsx b/apps/web/src/onboarding/onboarding-layout.tsx index 70a6a681d0..398f9f9db4 100644 --- a/apps/web/src/onboarding/onboarding-layout.tsx +++ b/apps/web/src/onboarding/onboarding-layout.tsx @@ -10,11 +10,7 @@ import { DitherBackground } from "../auth/dither-background"; * the auth form column — the credential step's provider picker and the * guidance cards need more room than a login form does. */ -export function OnboardingLayout({ - children, -}: { - readonly children: ReactNode; -}) { +export function OnboardingLayout({ children }: { readonly children: ReactNode }) { return (
@@ -31,11 +27,7 @@ export function OnboardingLayout({
- +
diff --git a/apps/web/src/onboarding/provider-connect-step.tsx b/apps/web/src/onboarding/provider-connect-step.tsx index 9d2b9c4533..6d1278c91d 100644 --- a/apps/web/src/onboarding/provider-connect-step.tsx +++ b/apps/web/src/onboarding/provider-connect-step.tsx @@ -8,10 +8,7 @@ // an earlier run of this same step) skips this UI entirely: see // `resolveExistingOffering` in `onboarding-page.tsx`. import { Button, Input, RadioGroup, RadioOption } from "@corbits/react-ui"; -import { - getResolvedCatalog, - shadowOffering, -} from "@corbits/inference-settings"; +import { getResolvedCatalog, shadowOffering } from "@corbits/inference-settings"; import { reportError } from "@corbits/error-sink"; import { useState } from "react"; import type { FormEvent } from "react"; @@ -76,9 +73,7 @@ export type ExistingOffering = { * all: every visible offering becomes `sourceOfferingIds`, and the * lowest-priority one is the default — the same rule * `resolveRealSourceOfferingIds` in `packages/onboarding` applies. */ -export async function resolveExistingOffering( - tenantId: string, -): Promise { +export async function resolveExistingOffering(tenantId: string): Promise { const models = await getResolvedCatalog(tenantId); const offerings = models .flatMap((model) => model.offerings) @@ -107,8 +102,7 @@ export function ProviderConnectStep({ const [submitting, setSubmitting] = useState(false); const option = - PROVIDER_OPTIONS.find((candidate) => candidate.plugin === selected) ?? - PROVIDER_OPTIONS[0]; + PROVIDER_OPTIONS.find((candidate) => candidate.plugin === selected) ?? PROVIDER_OPTIONS[0]; async function handleSubmit(event: FormEvent) { event.preventDefault(); @@ -135,19 +129,14 @@ export function ProviderConnectStep({ operation: "onboarding.connect-provider", tenantId, }); - onError( - `${cause instanceof Error ? cause.message : String(cause)} (ref ${refId})`, - ); + onError(`${cause instanceof Error ? cause.message : String(cause)} (ref ${refId})`); } finally { setSubmitting(false); } } return ( -
void handleSubmit(event)} - > + void handleSubmit(event)}> Promise }[] = - [ - ...(instructionsDirty - ? [ - { - part: "instructions" as const, - write: () => - updateAgentInstructions(tenantId, definition.id, { - name: trimmedName, - systemPrompt: trimmedPrompt, - }), - }, - ] - : []), - ...(modelDirty - ? [ - { - part: "model" as const, - write: () => - model === "" - ? clearAgentModel(tenantId, definition.id) - : setAgentModel(tenantId, definition.id, model), - }, - ] - : []), - ...(skillsDirty - ? [ - { - part: "skills" as const, - write: () => - updateAgentSkills(tenantId, definition.id, [...skills]), - }, - ] - : []), - ]; + const parts: readonly { part: SavePart; write: () => Promise }[] = [ + ...(instructionsDirty + ? [ + { + part: "instructions" as const, + write: () => + updateAgentInstructions(tenantId, definition.id, { + name: trimmedName, + systemPrompt: trimmedPrompt, + }), + }, + ] + : []), + ...(modelDirty + ? [ + { + part: "model" as const, + write: () => + model === "" + ? clearAgentModel(tenantId, definition.id) + : setAgentModel(tenantId, definition.id, model), + }, + ] + : []), + ...(skillsDirty + ? [ + { + part: "skills" as const, + write: () => updateAgentSkills(tenantId, definition.id, [...skills]), + }, + ] + : []), + ]; const saved: SavePart[] = []; for (const { part, write } of parts) { @@ -270,10 +266,7 @@ export function AgentDetailPage({ saved, failed: { part, - message: describeApiError( - cause, - `saving this agent's ${SAVE_PART_COPY[part]}`, - ), + message: describeApiError(cause, `saving this agent's ${SAVE_PART_COPY[part]}`), }, }); return; @@ -312,11 +305,7 @@ export function AgentDetailPage({ async function onToggleArchived() { setLifecycle({ kind: "busy" }); try { - await setAgentDefinitionStatus( - tenantId, - definition.id, - archived ? "deployed" : "stopped", - ); + await setAgentDefinitionStatus(tenantId, definition.id, archived ? "deployed" : "stopped"); setLifecycle({ kind: "idle" }); onStatusChanged(); } catch (cause: unknown) { @@ -335,10 +324,7 @@ export function AgentDetailPage({ return (
@@ -440,8 +420,8 @@ export function AgentDetailPage({ {models.length === 0 ? (

- No models in this bench's catalog yet — connect a - provider in Settings and this agent can pick one. + No models in this bench's catalog yet — connect a provider in Settings and + this agent can pick one.

) : ( setName(event.target.value)} />