Files
arcadia-admin/app/components/assistant/confirm-card.tsx
jules e5cd85fff3 Add 5 more admin tools + inline write confirmation flow
New tools in admin-tools.ts:
- list_audit_log({limit?}) — recent audit entries (terse: actor, action,
  target, timestamp). Hits /api/v1/admin/audit-log.
- get_platform_stats() — aggregate counts (tenants by status + by plan),
  composed locally from list_tenants until arcadia exposes a real stats
  endpoint.
- list_users({limit?}) — users in the currently-selected tenant via
  /api/v1/users.
- suspend_tenant({slug}) — write tool, suspends a tenant by slug.
- activate_tenant({slug}) — write tool, restores a suspended/deactivated
  tenant.

Inline write confirmation:
- New ConfirmCard component renders below the assistant message that
  proposed a write. Shows tool(args) and Confirm/Deny buttons.
- classifyCalls() splits LLM tool calls into reads/writes. Auto-loop
  runs reads immediately; for any writes, holds them in pendingConfirm
  state instead of dispatching.
- On Confirm: runs writes with allowWrites:true, prepends prior read
  results, continueChat to produce the final answer.
- On Deny: synthesises tool-result messages telling the model the user
  declined; continueChat so it can acknowledge.
- Arcadia-knowledge primer updated to tell the model the user sees an
  inline confirm card automatically — it shouldn't ask in prose first.

Wired into both /ai and /assistant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:16:41 +10:00

53 lines
1.6 KiB
TypeScript

// Inline transcript card asking the operator to confirm or deny one or more
// write tool calls the model has proposed. Renders below the assistant
// message that emitted them; the auto-loop is paused until the user picks.
import { ShieldAlert } from "lucide-react"
import type { ToolCall } from "@crema/llm-ui"
import { formatToolCallArgs } from "~/lib/admin-tools"
import { Button } from "~/components/ui/button"
export function ConfirmCard({
calls,
onConfirm,
onDeny,
busy,
}: {
calls: ToolCall[]
onConfirm: () => void
onDeny: () => void
busy?: boolean
}) {
return (
<div className="self-start max-w-[80ch] rounded-2xl border border-amber-400/40 bg-amber-50 px-4 py-3 text-sm shadow-sm dark:border-amber-300/40 dark:bg-amber-950/30">
<div className="mb-2 flex items-center gap-2 font-medium text-amber-900 dark:text-amber-200">
<ShieldAlert className="size-4" />
Confirm write{calls.length > 1 ? "s" : ""}
</div>
<ul className="mb-3 list-disc pl-5 text-amber-900/90 dark:text-amber-100/90">
{calls.map((c) => (
<li key={c.id} className="font-mono text-[12px]">
{c.name}({formatToolCallArgs(c)})
</li>
))}
</ul>
<div className="flex items-center gap-2">
<Button size="sm" onClick={onConfirm} disabled={busy} data-action="confirm-tool-call">
Confirm
</Button>
<Button
size="sm"
variant="outline"
onClick={onDeny}
disabled={busy}
data-action="deny-tool-call"
>
Deny
</Button>
</div>
</div>
)
}