feat(bookkeeping): show who committed a verifikat, and mark agent work in Granskning history (#1591)
Flows build plan prereq 3 (provenance display). Pure UI over columns that
have existed since migration 20260619120000:
- types: JournalEntry gains committed_actor_type/committed_actor_label
(the detail/chain APIs already select('*'), the type just lacked them)
- voucher detail: new "Bokford av" row in the Details card, derived from
actor type + credential label, with the Bot mark for non-user actors
- Granskning Historik rows get the same actor circle pending rows have
(Bot vs ClipboardCheck) so agent-originated history reads at a glance
- run-turn: correct the staged_operation params comment (tool-use input
is a superset of pending_operations.params, not the same values)
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
857dd575d0
commit
7881f757a9
@@ -8,7 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { AccountNumber } from '@/components/ui/account-number'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X, Copy, ChevronDown, CalendarClock, FileText, Link2, RotateCcw, Scissors, PenLine } from 'lucide-react'
|
||||
import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X, Copy, ChevronDown, CalendarClock, FileText, Link2, RotateCcw, Scissors, PenLine, Bot } from 'lucide-react'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
@@ -65,6 +65,34 @@ type RattelseLogRow = {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Human "who committed this" line from the committed_actor_* snapshot
|
||||
* (WHO relayed the commit; commit_method records HOW). The claude.ai MCP
|
||||
* connector mints a gnubok_sk_ API key, so MCP traffic arrives as
|
||||
* actor_type='api_key' with the key name as the label.
|
||||
*/
|
||||
function committedByLabel(
|
||||
actorType: string,
|
||||
actorLabel: string | null,
|
||||
t: (key: string, values?: Record<string, string>) => string,
|
||||
): string {
|
||||
switch (actorType) {
|
||||
case 'user':
|
||||
return actorLabel || t('committed_by_user')
|
||||
case 'api_key':
|
||||
case 'mcp_oauth':
|
||||
return actorLabel ? t('committed_by_ai', { label: actorLabel }) : t('committed_by_ai_plain')
|
||||
case 'agent_chat':
|
||||
return t('committed_by_agent_chat')
|
||||
case 'cron':
|
||||
return t('committed_by_cron')
|
||||
case 'system':
|
||||
return t('committed_by_system')
|
||||
default:
|
||||
return actorType
|
||||
}
|
||||
}
|
||||
|
||||
export default function JournalEntryDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = use(params)
|
||||
const router = useRouter()
|
||||
@@ -580,6 +608,15 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
<span>{formatDate(entry.committed_at)}</span>
|
||||
</div>
|
||||
)}
|
||||
{entry.committed_at && entry.committed_actor_type && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('field_committed_by')}</span>
|
||||
<span className="flex items-center gap-1.5 text-right">
|
||||
{entry.committed_actor_type !== 'user' && <Bot className="h-3.5 w-3.5 shrink-0" />}
|
||||
{committedByLabel(entry.committed_actor_type, entry.committed_actor_label, t)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('field_type')}</span>
|
||||
<span>{sourceTypeLabels[entry.source_type] || entry.source_type}</span>
|
||||
|
||||
@@ -887,6 +887,14 @@ export default function PendingOperationsPage() {
|
||||
detailOpId === op.id ? 'bg-secondary/25' : 'hover:bg-secondary/35',
|
||||
)}
|
||||
>
|
||||
{/* Same actor mark as the pending rows, minus the curved
|
||||
op-thread (it points at an action row history lacks). */}
|
||||
<span
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground"
|
||||
aria-hidden
|
||||
>
|
||||
{isAgent ? <Bot className="h-3.5 w-3.5" /> : <ClipboardCheck className="h-3.5 w-3.5" />}
|
||||
</span>
|
||||
<Badge
|
||||
variant={
|
||||
isAutoExpired(op)
|
||||
|
||||
@@ -77,9 +77,11 @@ export type StreamEvent =
|
||||
kind: 'staged_operation'
|
||||
tool_use_id: string
|
||||
tool_name: string
|
||||
// The tool-use input: the same values the staging tool stored as
|
||||
// pending_operations.params. Carried so chat previews that need
|
||||
// params (e.g. attach_document's DocumentViewButton) work live.
|
||||
// The tool-use input: a superset of what the staging tool stored as
|
||||
// pending_operations.params (it may also carry transport fields such
|
||||
// as idempotency_key/dry_run). Carried so chat previews that need
|
||||
// params (e.g. attach_document's DocumentViewButton) work live;
|
||||
// previews read only the fields they need.
|
||||
params: Record<string, unknown>
|
||||
staged: StagedOperationResult
|
||||
}
|
||||
|
||||
@@ -4707,6 +4707,13 @@
|
||||
"details_title": "Journal entry details",
|
||||
"field_date": "Date",
|
||||
"field_posted_at": "Posted",
|
||||
"field_committed_by": "Posted by",
|
||||
"committed_by_user": "User",
|
||||
"committed_by_ai": "AI assistant · {label}",
|
||||
"committed_by_ai_plain": "AI assistant",
|
||||
"committed_by_agent_chat": "The AI assistant (chat)",
|
||||
"committed_by_cron": "Automated job",
|
||||
"committed_by_system": "System",
|
||||
"field_type": "Type",
|
||||
"field_source_voucher": "Original voucher",
|
||||
"field_note": "Note",
|
||||
|
||||
@@ -4707,6 +4707,13 @@
|
||||
"details_title": "Verifikationsdetaljer",
|
||||
"field_date": "Datum",
|
||||
"field_posted_at": "Bokförd",
|
||||
"field_committed_by": "Bokförd av",
|
||||
"committed_by_user": "Användare",
|
||||
"committed_by_ai": "AI-assistent · {label}",
|
||||
"committed_by_ai_plain": "AI-assistent",
|
||||
"committed_by_agent_chat": "AI-assistenten (chatt)",
|
||||
"committed_by_cron": "Automatiskt jobb",
|
||||
"committed_by_system": "System",
|
||||
"field_type": "Typ",
|
||||
"field_source_voucher": "Ursprungligt verifikat",
|
||||
"field_note": "Anteckning",
|
||||
|
||||
@@ -258,6 +258,8 @@ export function makeJournalEntry(overrides: Partial<JournalEntry> = {}): Journal
|
||||
attachment_urls: null,
|
||||
notes: null,
|
||||
commit_method: null,
|
||||
committed_actor_type: null,
|
||||
committed_actor_label: null,
|
||||
rubric_version: null,
|
||||
created_at: '2024-06-15T14:30:00Z',
|
||||
updated_at: '2024-06-15T14:30:00Z',
|
||||
|
||||
@@ -1789,6 +1789,13 @@ export interface JournalEntry {
|
||||
attachment_urls: string[] | null
|
||||
notes: string | null
|
||||
commit_method: string | null
|
||||
// WHO relayed the commit; complements commit_method = HOW. Stamped at
|
||||
// commit time since migration 20260619120000. actor_type is NULL or one of
|
||||
// 'user' | 'api_key' | 'mcp_oauth' | 'cron' | 'system' | 'agent_chat'
|
||||
// (the DB CHECK is the authority); actor_label is a credential snapshot
|
||||
// (e.g. the API key name).
|
||||
committed_actor_type: string | null
|
||||
committed_actor_label: string | null
|
||||
rubric_version: string | null
|
||||
source_voucher_series: string | null
|
||||
source_voucher_number: number | null
|
||||
|
||||
Reference in New Issue
Block a user