15 KiB
AI Connections
AI accounts use the existing Apps/Connections substrate. Manage them at
/:company/apps; select their use beside an agent's harness/model settings.
Onboarding, new-agent setup, account creation/reconnect, and inline task requests
reuse AdapterLoginPanel, its existing login controllers, and AdapterLoginChrome.
Onboarding and new-agent setup retain upstream's SavedProviderKeySelect and
useSavedProviderKeys, including saved-key references and account-specific Codex
homes. Managed default/shared accounts are additional choices in that same
selector. Selecting “Sign in to another account” survives background refreshes;
Claude authorization paste keeps upstream's immediate Connecting feedback.
Storybook's simulated controllers and page annotations do not run in the app.
Compatibility and selection
The shared AI_CONNECTION_CAPABILITIES contract defines these combinations:
| Provider | Sign-in method | Existing harness |
|---|---|---|
| Claude / Anthropic | Claude subscription token or Anthropic API key | Claude |
| OpenAI | ChatGPT/Codex subscription or OpenAI API key | Codex |
| OpenRouter | API key | OpenCode, with an openrouter/ model |
| Grok / xAI | Grok subscription or xAI API key | Grok |
Native runner supports the corresponding existing Codex, OpenCode, and Claude
ACP profiles. Connections creation and reconnect mount AgentProviderConnection,
the same provider tiles, method controls, API entry, and AdapterLoginPanel used
by agent setup. Supported sandbox environments use onboarding's existing browser
sign-in controllers. Self-hosted installations use the shared terminal sign-in
instructions described below and require no sandbox. Environment selection does
not change agent execution settings.
API keys are validated against fixed provider endpoints; redirects
and caller-supplied validation URLs are rejected.
runtimeConfig.aiConnection contains provider, mode, and method. For responsible-user selections, method is a legacy wire hint retained for rolling upgrades; the resolver uses the selected account’s actual method:
responsible_user: resolve the run's responsible user's personal provider default, using that account's subscription or API key. Themethodhint does not restrict the responsible user's account.shared: use the namedconnectionIdandgrantId, with audience and agent access checks.delegated: retained only to read legacy bindings. It cannot bypass human access; a personal credential remains available only for its owner's tasks. New configuration offers personal defaults or shared accounts.
“Which humans can use this credential?” is the sole permission for whose work can use the account. “Just me” means the personal owner; shared accounts allow selected company members or every company member. The separate agent-access setting determines which agents can use it. There is no additional AI agent authorization, and old delegation records do not override the human audience.
A connection choice never changes the harness, model, or provider routing. Changing those separately may make a binding incompatible; saving then requires a compatible choice. Agent configuration cannot grant access to another account.
Personal defaults are unique per company, user, and provider. A Claude bot can use one user’s subscription and another user’s API key without changing its harness or model. Explicit shared selections remain pinned to the selected account and method.
The first successful personal connection sets a default only when none exists.
The additive ai_provider_defaults table preserves the legacy per-method preferences. Migration selects each user’s most recently updated provider preference (including unavailable accounts), and rerunning it never overwrites a provider default. New writes maintain the legacy table for older servers. A database trigger propagates older servers’ explicit default updates to the provider default. Inserting an additional method default does not replace an existing provider default.
Revocation retains the unavailable default; connecting another account does not silently replace it. Change it explicitly on the account detail page.
Storage and API
AI connections pair connectionPurpose: ai with transport: runtime_auth.
Database checks and the shared discriminator enforce the pair. These entries
cannot participate in tool discovery, MCP gateways, execution, or channels.
Anthropic offers Claude subscription and Claude API key; the unsupported duplicate REST API option is excluded. Catalog
validation also pairs AI metadata with runtime authentication and rejects unsupported
sign-in methods. Provider artwork and source provenance live in
ui/public/brands/apps/manifest.json; OpenRouter uses its official sign-in assets,
and OpenAI/Grok reuse the repository's pinned Lobe Icons source and license.
Provider/method metadata lives in config.ai. Credentials live on the existing
grant through encrypted vault secret references, with existing consumer bindings.
Safe provider-reported account identity is optional; secret references, tokens,
and authentication paths are never account labels.
Company-scoped /api/companies/:companyId/ai-connections operations provide list,
API-key creation/reconnect, personal defaults, completed login references, and
active-run attribution. Existing Connections operations handle naming, access,
and revocation. Mutation authorization is enforced server-side. OpenAPI documents the new board-only
operations. Agent-originated configuration and environment tests resolve the
authenticated request’s responsible user; an agent ID is never a personal-account
owner. A missing responsible identity blocks personal-default resolution.
Subscription login attempts retain their company, owner, method, access intent, and reconnect target in the existing durable authentication session. Duplicate completion returns the same connection/grant. Abandoned or expired attempts cannot save a healthy connection. Reconnect preserves the connection ID, bindings, customized name, and access settings. A completed connection remains even if subsequent agent creation fails or is cancelled.
Runtime isolation
prepareManagedAiRuntime is shared by runs, environment tests, and adoption.
It checks responsible identity, membership, compatibility, connection health,
human audience and agent installation before reading credentials.
Missing credentials produce an actionable configuration failure; responsible-user
task runs use the existing connection-request interaction, marked purpose: ai.
A runtime-auth request cannot satisfy, reuse, or supersede a tool request for
the same provider. AI-only methods are excluded from agent tool discovery.
Each invocation receives a private authentication home and only the selected grant's credentials. Inherited credential variables are cleared. Conflicting project authentication and provider-routing overrides are rejected. Managed failure cannot reactivate host or legacy credentials.
Subscription invocations take a grant-scoped transaction advisory lease. The reserved database client keeps one transaction open until cleanup, including on transaction-pooling proxies such as PgBouncer. Session-level advisory locks must not be used here: a pooled connection can return to a different backend for cleanup and leave the original lock behind. The lease transaction disables its idle timeout and contains no application data writes; cleanup rolls it back. Two different users' grants can run concurrently; a second invocation of the same subscription receives a retryable busy response while it is in use. Refreshes are merged only into the originating active grant, with reconnect/revocation version checks. Temporary homes are removed on normal completion or failure.
Session reuse includes grant identity, responsible user, and credential generation. A changed identity starts a fresh provider session. Managed native executions use per-turn lifecycle cleanup; a suspended native execution whose credential identity changed must restart as a new execution.
Revocation blocks new invocations and refresh persistence. A running provider process may already hold credentials. The revoke confirmation lists attributed active runs and exposes the existing Stop action; it does not promise immediate provider-side revocation.
Legacy adoption
Migration 0273 indexes only explicitly owned personal secrets with a recognized
provider/method and matching agent configuration. It keeps original secret
references and leaves every agent's legacy authentication unchanged. Reconnecting
an indexed account creates a private grant credential instead of rotating the
legacy secret. Subsequent reconnects rotate that private credential. Unknown
ownership and filesystem-only subscriptions remain unresolved. The migration is
repeatable and does not classify unknown credentials as company-shared.
Imported accounts initially need validation. Agent settings show “Existing authentication — not managed by Connections” until adoption. The adoption confirmation names the binding and affected agent. Saving runs a provider hello test in that agent's environment before replacing authentication. After adoption, the server preserves the managed binding and will not restore legacy fallback.
Local subscription sign-in
Local installations do not need a sandbox to connect a subscription. Connections,
onboarding, and agent setup share LocalProviderLoginInstructions and
useLocalAiLogin. In local-trusted mode, Claude checks the operator’s existing
Claude Code login. Authenticated self-hosted users instead get a separate
CLAUDE_CONFIG_DIR for claude auth login; checking and saving only read that
attempt’s credential files, never the server operator’s account or Keychain.
Codex and Grok start a separate terminal sign-in for each connection or reconnect.
The shared component shows a server-generated command with a fresh CODEX_HOME
or GROK_HOME. Codex uses file credential storage in that home and login --device-auth, so
signing in from another computer does not depend on a localhost callback. The home is never
seeded with the operator's existing login: copying a rotating refresh token would
allow managed runs to invalidate credentials still used by legacy agents or the
operator's terminal. The user completes browser sign-in from that command, then
clicks Connect. This does not require a sandbox or change the host login.
Attempts reuse adapter_auth_sessions, binding company, owner, provider, access
intent, reconnect target, and a 30-minute expiry. Validation and completion are
serialized; duplicate completion returns the saved connection. Restart retains
the attempt. Cancellation and expiry remove the attempt home, and the startup/
periodic cleanup sweep retries expired directories. Successful completion persists
credentials to the encrypted grant and removes the temporary login home. Refreshes
subsequently update only that grant. Reconnect preserves IDs and access settings.
Starting an isolated attempt requires normal company-scoped AI-connection creation permission. Checks, completion, cancellation, and resumption are owner-bound. Authenticated users cannot import host credentials or use another user’s attempt. Claude Keychain reads remain limited to the explicit local-trusted default-home import. A failed verification creates no healthy connection. Preview-era Codex/Grok managed connections without the isolated-subscription marker require reconnect before another managed execution; unmanaged legacy agents retain their existing authentication paths.
Verification
server/src/__tests__/ai-connections.test.ts exercises storage, isolation,
defaults, human audiences, agent access, reconnect races, refresh ownership,
subscription locking, migration replay, and redacted API failures against a real
embedded database. Existing login, adapter, tool, and channel suites cover their
shared integration paths. The onboarding tests cover managed reuse and keeping a
successfully connected account after failed agent creation.
The Storybook review index
retains deterministic authentication states and interaction checks. Run
pnpm build-storybook, then
pnpm exec playwright test --config tests/ai-connections-review/playwright.config.ts.
Also run token gates, repository typecheck, tests, and build before handoff.
Live connect → reuse → run → reconnect verification still requires valid provider
credentials and a supported login/runtime environment; fixtures do not prove it.
For an isolated running test drive, also run:
AI_CONNECTIONS_TEST_COMPANY_ID=<company-id> pnpm exec playwright test --config tests/ai-connections-app/playwright.config.ts
Set AI_CONNECTIONS_TEST_URL when the test drive uses a port other than 3100.
These browser checks exercise the production list/detail pages, rejected API-key validation, cancellation, focus restoration, and adoption without saving agent changes. They submit an explicitly invalid fixture key and do not prove successful authentication with a live account.
Local sign-in checks
Local subscription screens share the same credential check on entry and when the window regains focus. Waiting screens also poll until sign-in verifies. A successful check shows the account is signed in; only Connect creates or reconnects the grant. In local-trusted mode, Claude checks the local operator’s Claude Code login. Authenticated Claude users, plus all Codex and Grok users, check only their connection-specific login home. The health response selects credential isolation, not whether a self-hosted user may sign in.
Leaving and returning to a local sign-in screen resumes its active attempt. Navigation does not delete a directory referenced by a copied command. Start sign-in again explicitly cancels the old attempt; abandoned attempts expire after 30 minutes. Commands create their directory if necessary, and completed/expired attempts are cleaned up through the existing lifecycle.
Disposable live inline-repair test
The normal app test configuration excludes *.live.spec.ts. To run the destructive
inline-repair scenario, set AI_REPAIR_TEST_ALLOW_DESTRUCTIVE=1 and use a separate
loopback local_trusted instance. Set AI_REPAIR_TEST_DISPOSABLE_MARKER to a fresh
32-character lowercase hexadecimal value. The company, single Codex agent, single
personal OpenAI API connection, and issue must all be named AI Repair QA <marker>
(the issue uses that title). Supply their IDs with AI_CONNECTIONS_TEST_COMPANY_ID,
AI_REPAIR_TEST_CONNECTION_ID, and AI_REPAIR_TEST_ISSUE_ID, and the disposable
provider key with AI_REPAIR_TEST_KEY. The test verifies these boundaries before
revoking credentials or submitting work. Delete the disposable instance and revoke
its provider key after the test; failed tests may leave a paused task for inspection.
Authenticated public deployments must configure a trusted runtime host (PAPERCLIP_TRUSTED_MCP_RUNTIME_HOST or PAPERCLIP_TOOL_RUNTIME_TRUSTED_HOST) before offering server-host subscription login, matching the local stdio runtime boundary. Health reports this capability so setup can offer a supported environment or API key instead of an unusable terminal command. Private authenticated self-hosted instances support isolated local login without that extra setting. Isolated Claude credential files must be private, owned by the server user, bounded, and free of symlinks.