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>
This commit is contained in:
52
app/components/assistant/confirm-card.tsx
Normal file
52
app/components/assistant/confirm-card.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user