Wire AI assistant to arcadia: domain primer, tool calling, admin context
Make /ai and /assistant operate as the platform admin's assistant
against arcadia-app's API:
- Add `arcadia-knowledge.ts` — domain primer (multi-tenant Phoenix
backend, tenant lifecycle, platform_admins identity, etc.) baked into
every system prompt.
- Add `admin-tools.ts` — curated tool registry exposing `list_tenants`
and `get_tenant`, callable via OpenAI-native function calling. Tools
hit arcadia through `useArcadiaClient()` and inherit the operator's
JWT + tenant header. `runLLMToolCalls()` returns `tool` role messages
ready to push back into history.
- Add `admin-context.ts` — runtime registry pages publish to so the
assistant can answer factual questions about live UI state without
scraping the DOM. Tenants page registers its summary on mount.
- Replace generic Vibespace personas (Atlas/Forge/Inkwell/Pilot/Cursor)
with arcadia-flavoured ones: Operator, Auditor, Triage, Analyst,
UI Operator. Auto-migrate stored agents from the legacy set.
- /assistant: build admin preface (role + primer + persona + ctx) and
pass it as the `useChat` system at construction. Pass `tools` on every
`send()`. Auto-loop reads `toolCalls` off the streaming assistant
message and uses `continueChat()` to push tool results.
- /ai: same wiring (this is the canonical admin chat surface; the user
prefers its look).
- MessageBody renders tool-result cards (role: "tool") and a "Called X"
pill on assistant messages with toolCalls. Strips Qwen-style
`<tool_call>` XML from prose when the tags were converted to
structured calls.
- Extend ThreadMessage with the `tool` role + tool-call metadata so
conversations round-trip through localStorage.
- Tenants page: row actions get `data-action="tenant-<slug>-{suspend,
activate,deactivate}"` (via lib-table-ui's new dataAction prop);
registers tenant summary into admin-context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
||||
} from "~/lib/arcadia/tenants"
|
||||
import { pageTitle } from "~/lib/page-meta"
|
||||
import { useSession } from "~/lib/session"
|
||||
import { useRegisterAdminContext } from "~/lib/admin-context"
|
||||
|
||||
export const meta = () => pageTitle("Tenants")
|
||||
|
||||
@@ -131,13 +132,36 @@ export default function TenantsRoute() {
|
||||
header: "",
|
||||
align: "right",
|
||||
cell: (t) => (
|
||||
<ActionsCell items={rowActions(t, arcadia, refresh, setPending, setError)} />
|
||||
<ActionsCell
|
||||
items={rowActions(t, arcadia, refresh, setPending, setError)}
|
||||
triggerDataAction={`tenant-${t.slug}-actions`}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[arcadia, refresh],
|
||||
)
|
||||
|
||||
const tenantSummary = useMemo(
|
||||
() => ({
|
||||
total: tenants.length,
|
||||
byStatus: tenants.reduce<Record<string, number>>((acc, t) => {
|
||||
acc[t.status] = (acc[t.status] ?? 0) + 1
|
||||
return acc
|
||||
}, {}),
|
||||
tenants: tenants.map((t) => ({
|
||||
id: t.id,
|
||||
slug: t.slug,
|
||||
name: t.name,
|
||||
status: t.status,
|
||||
plan: t.plan?.name ?? null,
|
||||
inserted_at: t.inserted_at,
|
||||
})),
|
||||
}),
|
||||
[tenants],
|
||||
)
|
||||
useRegisterAdminContext("tenants", tenantSummary)
|
||||
|
||||
const table = useTable<Tenant>({
|
||||
data: tenants,
|
||||
columns,
|
||||
@@ -304,6 +328,7 @@ function rowActions(
|
||||
id: "suspend",
|
||||
label: "Suspend",
|
||||
icon: <Pause className="size-4" />,
|
||||
dataAction: `tenant-${t.slug}-suspend`,
|
||||
onSelect: () => setPending({ kind: "suspend", tenant: t }),
|
||||
})
|
||||
} else {
|
||||
@@ -311,6 +336,7 @@ function rowActions(
|
||||
id: "activate",
|
||||
label: "Activate",
|
||||
icon: <Play className="size-4" />,
|
||||
dataAction: `tenant-${t.slug}-activate`,
|
||||
onSelect: async () => {
|
||||
try {
|
||||
await activateTenant(arcadia, t.id)
|
||||
@@ -325,6 +351,7 @@ function rowActions(
|
||||
id: "deactivate",
|
||||
label: "Deactivate",
|
||||
destructive: true,
|
||||
dataAction: `tenant-${t.slug}-deactivate`,
|
||||
onSelect: () => setPending({ kind: "deactivate", tenant: t }),
|
||||
})
|
||||
return items
|
||||
|
||||
Reference in New Issue
Block a user