The operator surface for the integration registry: manage platform/pooled
external-API credentials across every scope and inspect cross-tenant usage
(metadata only — secrets are write-only). Talks to arcadia-llm-gateway's
/api/v1/integrations* endpoints via a gateway-pointed ArcadiaClient.
- gateway.ts: second ArcadiaClient at VITE_LLM_GATEWAY_URL, reusing the
arcadia-app JWT (the gateway validates it via the shared Guardian secret;
CORS already allows *.sky-ai.com + localhost — no proxy).
- lib/arcadia/integrations.ts: operator API client (any-scope create, scope
filter, cross-tenant usage). Pure functions over an injected client —
extraction-ready to share with arcadia-console.
- routes/integrations.tsx: scope filter + per-card scope badge, create
platform/pooled credentials, credentials/usage, Test (surfaces the
expiry/budget gate), enable toggle, delete.
The route/nav/capability wiring (routes.ts, app-shell, capabilities.ts) lands
with the in-flight capability framework, not here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New /organizations route under Tenancy. Lists every org in the current
tenant (via GET /api/v1/admin/organizations), with per-row Manage
members and Settings dialogs.
- Members dialog: invite by email, add restricted sub-user, change role,
transfer ownership, remove member (owner removal honors the org's
on_owner_removal policy server-side)
- Settings dialog: edit name, status (active/frozen/pending_deletion),
and on_owner_removal policy
- app/lib/arcadia/organizations.ts: typed client for the new endpoints
- Nav entry added under Tenancy group
Tenant admins bypass per-org membership checks via the backend's
OrganizationContext plug, so the per-org REST endpoints work for any
org in the tenant without an explicit /admin/* surface.
The variant pipeline is async, so right after upload all four URLs in
profile.avatar_urls are still null. The first wiring attempt called
pickAvatarUrl() which returned null, and nothing visible changed even
though the upload + PATCH succeeded.
Fixes:
- pickAvatarUrl: use the actual backend keys (small/medium/large/
original — there's no "thumbnail").
- After upload, when no variant URL is ready, fetch the raw object
via /api/v1/digital_objects/:id/content as a blob URL for immediate
display. Persist that URL to localStorage so the appbar's
useProfile() picks it up via the storage event.
- ProfileBootstrap: detect stale blob: URLs cached from previous
sessions, clear them, and refetch a fresh blob URL when variants
still aren't ready. Eventually the persistent variant URLs land
and overwrite.
- Force-remount AvatarImage via key={src} in the profile page and
appbar — base-ui's Avatar.Image keeps internal load state that
doesn't always reset on src change.
- Diagnostic logs in fetchDigitalObjectAsBlobUrl + the upload flow
to make next debug round one step easier (kept; cheap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a digital-objects client (uploadFile: open session → PUT to
presigned URL → complete) and a profile client (getProfile,
updateProfile, pickAvatarUrl variant resolver).
- Wire profile.tsx avatar upload to use the real flow: validate
image+size, upload to digital_objects tagged "avatar", PATCH
/api/v1/profile with avatar_digital_object_id, mirror the resolved
URL into local prefs so the existing <AvatarImage> binding keeps
working. Show Uploading… state and an inline error banner. Clear
detaches via avatar_digital_object_id: null.
- Fix the storage form sending the wrong field name for the local
backend — arcadia's StorageConfig changeset requires `base_path`,
not `path`. The 422 was silent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls the reasoning storage out of ai.tsx and into the shared
llm-configs.ts helpers so Settings → LLM and the /ai composer
coordinate via one localStorage key (crema.ai.reasoning):
- loadActiveReasoning / saveActiveReasoning: read/write helpers.
- subscribeActiveReasoning: dispatches a CustomEvent on writes
(same-tab) plus a storage-event listener (cross-tab), so the
chip updates live when the operator stars a different config in
another tab or in the settings panel.
Wiring:
- Settings panel onMakeActive() now also calls
saveActiveReasoning(c.reasoning_effort ?? "off"). Starring a
config seeds the chip with that config's default.
- /ai chip useEffect subscribes to changes; a star in Settings
while /ai is open flips the chip in real time.
- resetAndClear no longer wipes reasoningEffort. Clearing the
conversation shouldn't silently undo the operator's stated
intent for thinking-mode (which is bound to their active config,
not to the conversation).
Net behaviour:
- Star a config with reasoning_effort=medium → chip on /ai shows
THINK MEDIUM next time you visit (or immediately if /ai is open).
- Cycle the chip while on /ai → just an override for the current
conversation, not back-propagated to the saved config.
- Edit the config in Settings to change its default → propagates to
the chip on next star (intentional — direct edits don't auto-
re-activate).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two layers for thinking-mode control:
1. Per-config default (Settings → LLM)
New "Reasoning effort" Select in the Add/Edit dialog with
off/low/medium/high/max + a budget hint per option (~2k, ~8k,
~24k, ~64k thinking tokens). Saved row meta line surfaces the
level inline so it's visible without opening the editor.
2. Per-message override (composer chip)
New ReasoningChip next to the model picker. Click cycles through
the same five levels. Hidden chrome when off (muted "think" pill);
sodium-amber active style with the level label when set.
Persisted to crema.ai.reasoning so a refresh keeps the operator's
intent, wiped together with the conversation on Clear.
When sending, withReasoning() merges reasoning_effort into the request
body as a top-level field. The proxy forwards it untouched to OpenAI /
DeepSeek (native field) and translates to Anthropic's thinking block
server-side.
reasoningEffortRef sidesteps a useCallback ordering issue —
regenerateLast/continueLast are declared before the state hook, so
they read the ref instead of a stale closure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reworks the LLM settings surface based on UX feedback. Drops the
separate "Active LLM (this session)" card — its functionality is now
inline on each saved config as a star toggle (writes the same
localStorage key the Assistant reads via @crema/llm-providers-ui's
saveSettings, so the existing assistant code picks the change up
without any plumbing).
Per-row controls now include:
- Star: make this config active for the current browser
- Switch: enable/disable server-side
- Pencil: edit (modal, not inline-expand)
- Trash: delete (with confirm)
- Spend (30d): cost + request count, sourced from
/api/v1/ai/llm/usage/by-model and matched on (provider, model)
Other improvements:
- Add wizard moved to a Dialog modal instead of pushing the list
around. Same form handles edit.
- Empty state: "Seed from catalog" button creates a curated starter
set (GPT-4o mini/4o, Sonnet 4.6, Haiku 4.5, DeepSeek V4 Flash, LM
Studio) so first-time operators don't face a blank panel.
- Catalog dropdown picks now auto-fill input/output costs as you
switch models, so the rates always reflect the chosen model unless
manually overridden.
- The lib's full settings card (system prompt, transport, context
budget) is still reachable for advanced cases — collapsed into a
<details> below the panel.
Adds llm-configs.ts: getUsageByModel + findSpend helper for the
per-row spend lookup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the localStorage-only LLM settings with a persisted catalogue
backed by /api/v1/admin/llm-configurations. The Settings → LLM screen
now has two cards:
- "Saved configurations" — full CRUD against the server. Each row shows
provider/model/secret/published per-1M-token costs. Add wizard
auto-fills costs from the curated catalog. One-click "Import local"
button promotes any pre-existing localStorage settings into a server
row, then clears the local store.
- "Active LLM (this session)" — the existing LLMProvidersSettingsCard,
scoped down to "what does the Assistant use right now" (still
localStorage; per-operator).
Spend (30d) tile in the configurations card header reads
/api/v1/ai/llm/usage/summary and surfaces total cost / requests /
tokens. First visible cost roll-up in the admin UI.
New module app/lib/arcadia/llm-configs.ts: typed CRUD client,
catalog lookup, computeCostCents helper (mirrors the server's
LlmConfiguration.compute_cost_cents/3), and getUsageSummary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three things from the latest arcadia-app pull:
- health.ts: client for /api/v1/health{,/:service,/detailed,/host}.
monitoring.tsx now reads real per-subsystem probe state instead of
synthesizing it from indirect signals (rate limits, sessions, jobs).
- New Host tab on Monitoring with KPI tiles + per-core CPU bars,
load-avg cards, memory + swap usage, and per-mount disk bars,
backed by /api/v1/health/host.
- llm-proxy.ts: typed errors (secret_disabled, ip_not_allowed, etc.)
and a probeProxy() that round-trips a 1-token chat. settings.tsx's
"Test connection" in proxy mode now exercises the real endpoint
instead of just confirming the adapter built. Contract doc flipped
from "not yet implemented" to "implemented".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full set of admin surfaces on top of /platform/* and /admin/* endpoints,
plus a migration of /assistant onto @crema/llm-providers-ui.
Buckets (/buckets):
S3-level CRUD over /platform/buckets — list, create, delete (with the
6-digit confirmation flow the backend enforces), per-bucket configure
for versioning / CORS rules / policy JSON, plus an object browser
with FileGrid/FileList from @crema/file-ui and presigned-URL reveal.
Storage-config picker scopes the view to one credential at a time.
Monitoring (/monitoring):
Live dashboard. Service health board derived from indirect signals
(status-ui OverallStatus + ComponentRow). KPI tiles for sessions,
jobs, audit. Tabs: background jobs (Donut + BarChart + retry recent),
sessions (Sparkline of last 24h sign-ins), audit activity (BarChart
of severity / top resource types), infrastructure (DO summary +
WorldMapSvg coloured by droplet region + droplet list + Spaces),
rate limits. 30s auto-refresh.
Memberships (/memberships):
M:N glue between users and tenants over /admin/memberships. Add /
edit / suspend / activate / remove with role multi-select.
Networking (/networking):
Tabs over /platform/{firewalls,vpcs,domains,floating_ips}.
Read/delete on firewalls, read on VPCs, full DNS-record CRUD, and
inline assign/unassign for floating IPs.
SSO (/sso):
/sso/identity-providers CRUD with PEM cert as write-only field, plus
/sso/sessions list with destroy.
Announcements (/announcements):
/admin/announcements CRUD. Platform-wide vs per-tenant audience,
schedule windows, dismissible + active toggles.
Status page (/status-page):
/admin/status-page/{components,incidents,subscribers}. Components
CRUD, incidents with timeline + post-update + resolve flow,
subscriber list. Public preview at the top using StatusBoard +
IncidentTimeline from @crema/status-ui.
Assistant migration:
/assistant now uses @crema/llm-providers-ui (provider catalog +
vault key resolution) instead of ~/lib/llm-settings. Same async
buildAdapter() flow used by /ai. The legacy lib file is now
unreferenced and can be removed when ready.
New sibling libs wired (cloned from CremaUIStudio):
lib-file-ui, lib-card-ui, lib-dashboard-ui, lib-chart-ui,
lib-map-ui, lib-status-ui.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full management surfaces for the platform-admin tenant, mirroring the
existing Tenants pattern (DataTable + row actions + create/edit dialogs +
ConfirmDialog for destructive ops, all data-action tagged for the
command bus, useRegisterAdminContext publishing for the assistant).
- Storage (/storage): backends + credentials. Write-only secret fields,
Validate/Activate/Deactivate/Set-default/Mark-degraded/Maintenance.
- Users (/users): tabs for Users, Invitations, Roles. Per-user View
drawer with profile, role add/remove, API keys (one-time reveal on
create), usage + quota.
- Secrets (/secrets): /api/v1/admin/secrets — create/rotate/rollback,
versions dialog, enable/disable, generate-value helper.
- Webhooks (/webhooks): CRUD, pause/resume, regenerate-secret with
one-time reveal, send test event, deliveries dialog.
- Scheduled tasks (/scheduled-tasks): cron CRUD, run-now trigger,
enable/disable, expandable run history.
- Audit log (/activity): replaces the empty stub. Filter by severity,
resource type, date range; click for full JSON detail.
All endpoints are hand-rolled HTTP because most aren't covered by the
generated OpenAPI typed paths yet — switch to arcadia.typed.* when the
backend wires them into OpenApiSpex.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the first admin screen — /tenants — listing tenants from
GET /api/v1/admin/tenants with search, status badges, plan, created
date, and a per-row menu with suspend / activate / deactivate actions.
Hand-typed shapes in app/lib/arcadia/tenants.ts because arcadia's admin
endpoints aren't yet covered by /api/openapi (same 'ok'-placeholder
issue documented in lib-arcadia-client/scripts/sync-spec.mjs). When
the spec gains coverage, switch to arcadia.typed.GET(...) and drop the
manual types.
Trims the inherited consumer-app sidenav (Resources / Assistant / AI /
Library) down to admin-shaped items: Overview, Tenants, Audit log,
Settings. The unused route files stay in place; they just aren't linked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>