@gmickel/gno 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/assets/skill/SKILL.md +54 -12
- package/assets/skill/mcp-reference.md +7 -2
- package/assets/skill/recipes/citation-and-provenance.md +32 -9
- package/package.json +2 -1
- package/spec/AGENTS.md +83 -0
- package/spec/CLAUDE.md +83 -0
- package/spec/bench-fixture.schema.json +137 -0
- package/spec/cli.md +2919 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +592 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2279 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +564 -0
- package/spec/output-schemas/backlinks.schema.json +131 -0
- package/spec/output-schemas/bench-result.schema.json +120 -0
- package/spec/output-schemas/capture-receipt.schema.json +143 -0
- package/spec/output-schemas/claim-verification.schema.json +291 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +726 -0
- package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
- package/spec/output-schemas/context-list.schema.json +21 -0
- package/spec/output-schemas/doctor.schema.json +313 -0
- package/spec/output-schemas/error.schema.json +30 -0
- package/spec/output-schemas/expansion.schema.json +37 -0
- package/spec/output-schemas/get.schema.json +140 -0
- package/spec/output-schemas/graph-query.schema.json +99 -0
- package/spec/output-schemas/graph.schema.json +371 -0
- package/spec/output-schemas/links-list.schema.json +186 -0
- package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
- package/spec/output-schemas/mcp-http-error.schema.json +30 -0
- package/spec/output-schemas/mcp-job-list.schema.json +58 -0
- package/spec/output-schemas/mcp-job-status.schema.json +224 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
- package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
- package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
- package/spec/output-schemas/models-list.schema.json +93 -0
- package/spec/output-schemas/multi-get.schema.json +103 -0
- package/spec/output-schemas/process-status.schema.json +119 -0
- package/spec/output-schemas/query-diagnose.schema.json +123 -0
- package/spec/output-schemas/resident-status.schema.json +154 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
- package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
- package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
- package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
- package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
- package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
- package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
- package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
- package/spec/output-schemas/search-result.schema.json +154 -0
- package/spec/output-schemas/search-results.schema.json +338 -0
- package/spec/output-schemas/similar.schema.json +84 -0
- package/spec/output-schemas/status.schema.json +676 -0
- package/spec/output-schemas/tags-list.schema.json +48 -0
- package/src/app/context-runtime-contract.ts +10 -5
- package/src/app/context-runtime-input.ts +29 -1
- package/src/app/context-runtime-types.ts +7 -0
- package/src/app/context-runtime.ts +20 -2
- package/src/app/context-surface.ts +4 -0
- package/src/app/verified-ask.ts +291 -0
- package/src/cli/commands/ask-format.ts +255 -0
- package/src/cli/commands/ask.ts +144 -183
- package/src/cli/commands/context-build.ts +56 -9
- package/src/cli/commands/get.ts +64 -3
- package/src/cli/commands/query.ts +62 -23
- package/src/cli/commands/replay.ts +140 -0
- package/src/cli/commands/search.ts +48 -3
- package/src/cli/commands/shared.ts +3 -1
- package/src/cli/commands/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/program.ts +287 -1
- package/src/config/index.ts +9 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +4 -0
- package/src/core/context-budget.ts +6 -0
- package/src/core/context-capsule-retrieval-schema.ts +4 -0
- package/src/core/context-capsule-schema.ts +17 -0
- package/src/core/context-capsule-validation.ts +3 -2
- package/src/core/context-capsule.ts +18 -0
- package/src/core/context-compiler.ts +44 -25
- package/src/core/context-evidence.ts +6 -0
- package/src/core/retrieval-qrels.ts +405 -0
- package/src/core/retrieval-replay-candidate.ts +368 -0
- package/src/core/retrieval-replay-types.ts +109 -0
- package/src/core/retrieval-replay-validation.ts +89 -0
- package/src/core/retrieval-replay.ts +441 -0
- package/src/core/retrieval-trace-evidence-origin.ts +178 -0
- package/src/core/retrieval-trace-export.ts +113 -0
- package/src/core/retrieval-trace-filter-normalization.ts +27 -0
- package/src/core/retrieval-trace-filters.ts +19 -0
- package/src/core/retrieval-trace-management-helpers.ts +247 -0
- package/src/core/retrieval-trace-management-types.ts +132 -0
- package/src/core/retrieval-trace-management.ts +422 -0
- package/src/core/retrieval-trace-request.ts +141 -0
- package/src/core/retrieval-trace-session.ts +507 -0
- package/src/core/retrieval-trace.ts +472 -0
- package/src/llm/errors.ts +10 -1
- package/src/llm/httpGeneration.ts +11 -1
- package/src/llm/nodeLlamaCpp/generation.ts +54 -10
- package/src/llm/types.ts +6 -0
- package/src/mcp/tools/ask.ts +228 -0
- package/src/mcp/tools/context.ts +87 -15
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index.ts +83 -0
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- package/src/pipeline/answer.ts +167 -26
- package/src/pipeline/claim-verification-schema.ts +235 -0
- package/src/pipeline/claim-verification.ts +487 -0
- package/src/pipeline/claim-verifier.ts +474 -0
- package/src/pipeline/graph-retrieval.ts +15 -1
- package/src/pipeline/hybrid.ts +151 -43
- package/src/pipeline/search.ts +36 -3
- package/src/pipeline/trace-metadata.ts +47 -0
- package/src/pipeline/types.ts +68 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +415 -73
- package/src/sdk/documents.ts +48 -1
- package/src/sdk/index.ts +17 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/context-capsule.ts +67 -8
- package/src/serve/public/app.tsx +12 -1
- package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Ask.tsx +42 -4
- package/src/serve/public/pages/Dashboard.tsx +10 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -0
- package/src/serve/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +508 -68
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +87 -2
- package/src/store/index.ts +31 -0
- package/src/store/migrations/014-retrieval-traces.ts +303 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/retrieval-trace-codec.ts +384 -0
- package/src/store/sqlite/adapter.ts +153 -1
- package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
- package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
- package/src/store/sqlite/retrieval-trace-store.ts +515 -0
- package/src/store/types.ts +297 -0
- package/src/store/vector/sqlite-vec.ts +76 -1
- package/src/store/vector/types.ts +1 -1
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
SourcesTrigger,
|
|
22
22
|
} from "../components/ai-elements/sources";
|
|
23
23
|
import { AIModelSelector } from "../components/AIModelSelector";
|
|
24
|
+
import {
|
|
25
|
+
type AskVerification,
|
|
26
|
+
AskVerificationPanel,
|
|
27
|
+
} from "../components/AskVerificationPanel";
|
|
24
28
|
import {
|
|
25
29
|
ThoroughnessSelector,
|
|
26
30
|
type Thoroughness,
|
|
@@ -57,6 +61,7 @@ interface PageProps {
|
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
interface Citation {
|
|
64
|
+
evidenceId?: string;
|
|
60
65
|
docid: string;
|
|
61
66
|
uri: string;
|
|
62
67
|
startLine?: number;
|
|
@@ -88,12 +93,15 @@ interface AskResponse {
|
|
|
88
93
|
vectorsUsed: boolean;
|
|
89
94
|
answerGenerated: boolean;
|
|
90
95
|
totalResults: number;
|
|
96
|
+
verificationRequested?: boolean;
|
|
97
|
+
abstained?: boolean;
|
|
91
98
|
queryModes?: {
|
|
92
99
|
term: number;
|
|
93
100
|
intent: number;
|
|
94
101
|
hyde: boolean;
|
|
95
102
|
};
|
|
96
103
|
};
|
|
104
|
+
verification?: AskVerification;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
interface Capabilities {
|
|
@@ -139,7 +147,7 @@ function renderAnswer(
|
|
|
139
147
|
const parts: React.ReactNode[] = [];
|
|
140
148
|
let key = 0;
|
|
141
149
|
|
|
142
|
-
const citationRegex = /\[(\d+)\]/g;
|
|
150
|
+
const citationRegex = /\[(?:evidence:([a-f0-9]{64})|(\d+))\]/g;
|
|
143
151
|
let lastIndex = 0;
|
|
144
152
|
let match: RegExpExecArray | null;
|
|
145
153
|
|
|
@@ -149,8 +157,13 @@ function renderAnswer(
|
|
|
149
157
|
parts.push(answer.slice(lastIndex, match.index));
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
const
|
|
153
|
-
const
|
|
160
|
+
const evidenceId = match[1];
|
|
161
|
+
const numericCitation = match[2] ? Number(match[2]) : undefined;
|
|
162
|
+
const citationIndex = evidenceId
|
|
163
|
+
? citations.findIndex((citation) => citation.evidenceId === evidenceId)
|
|
164
|
+
: (numericCitation ?? 0) - 1;
|
|
165
|
+
const citationNum = citationIndex + 1;
|
|
166
|
+
const citation = citations[citationIndex];
|
|
154
167
|
|
|
155
168
|
if (citation) {
|
|
156
169
|
parts.push(
|
|
@@ -186,6 +199,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
186
199
|
const [collections, setCollections] = useState<Collection[]>([]);
|
|
187
200
|
const [thoroughness, setThoroughness] = useState<Thoroughness>("balanced");
|
|
188
201
|
const [activePreset, setActivePreset] = useState("slim-tuned");
|
|
202
|
+
const [verify, setVerify] = useState(false);
|
|
189
203
|
|
|
190
204
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
191
205
|
const [selectedCollection, setSelectedCollection] = useState("");
|
|
@@ -318,6 +332,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
318
332
|
const requestBody: Record<string, unknown> = {
|
|
319
333
|
query: currentQuery,
|
|
320
334
|
limit: 5,
|
|
335
|
+
verify,
|
|
321
336
|
};
|
|
322
337
|
|
|
323
338
|
if (selectedCollection) {
|
|
@@ -394,6 +409,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
394
409
|
tagsInput,
|
|
395
410
|
thoroughness,
|
|
396
411
|
until,
|
|
412
|
+
verify,
|
|
397
413
|
]
|
|
398
414
|
);
|
|
399
415
|
|
|
@@ -443,6 +459,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
443
459
|
parseTagsCsv(tagsInput).length > 0
|
|
444
460
|
? `${tagMode}:${parseTagsCsv(tagsInput).join(",")}`
|
|
445
461
|
: null,
|
|
462
|
+
verify ? "verified" : null,
|
|
446
463
|
].filter((pill): pill is string => Boolean(pill));
|
|
447
464
|
|
|
448
465
|
return (
|
|
@@ -475,6 +492,16 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
475
492
|
value={thoroughness}
|
|
476
493
|
/>
|
|
477
494
|
|
|
495
|
+
<Button
|
|
496
|
+
aria-pressed={verify}
|
|
497
|
+
onClick={() => setVerify((value) => !value)}
|
|
498
|
+
size="sm"
|
|
499
|
+
type="button"
|
|
500
|
+
variant={verify ? "default" : "outline"}
|
|
501
|
+
>
|
|
502
|
+
Verify
|
|
503
|
+
</Button>
|
|
504
|
+
|
|
478
505
|
<div className="h-6 w-px bg-border/40" />
|
|
479
506
|
|
|
480
507
|
<AIModelSelector onPresetChange={setActivePreset} />
|
|
@@ -878,8 +905,19 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
878
905
|
</Sources>
|
|
879
906
|
)}
|
|
880
907
|
|
|
908
|
+
{entry.response.verification && (
|
|
909
|
+
<AskVerificationPanel
|
|
910
|
+
navigate={navigate}
|
|
911
|
+
verification={entry.response.verification}
|
|
912
|
+
/>
|
|
913
|
+
)}
|
|
914
|
+
|
|
881
915
|
<div className="flex items-center gap-2 text-muted-foreground/60 text-xs">
|
|
882
|
-
<span>
|
|
916
|
+
<span>
|
|
917
|
+
{entry.response.verification
|
|
918
|
+
? `${entry.response.verification.capsule.evidence.length} evidence spans`
|
|
919
|
+
: `${entry.response.results.length} results`}
|
|
920
|
+
</span>
|
|
883
921
|
{entry.response.meta.vectorsUsed && (
|
|
884
922
|
<Badge
|
|
885
923
|
className="font-mono text-[9px]"
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
FolderHeartIcon,
|
|
8
8
|
FolderIcon,
|
|
9
9
|
GitForkIcon,
|
|
10
|
+
HistoryIcon,
|
|
10
11
|
Layers,
|
|
11
12
|
Loader2Icon,
|
|
12
13
|
MessageSquare,
|
|
@@ -499,6 +500,15 @@ export default function Dashboard({ navigate }: PageProps) {
|
|
|
499
500
|
<GitForkIcon className="size-4" />
|
|
500
501
|
Graph
|
|
501
502
|
</Button>
|
|
503
|
+
<Button
|
|
504
|
+
className="gap-2"
|
|
505
|
+
onClick={() => navigate("/traces")}
|
|
506
|
+
size="lg"
|
|
507
|
+
variant="outline"
|
|
508
|
+
>
|
|
509
|
+
<HistoryIcon className="size-4" />
|
|
510
|
+
Trace history
|
|
511
|
+
</Button>
|
|
502
512
|
</nav>
|
|
503
513
|
|
|
504
514
|
{error && (
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowLeftIcon,
|
|
3
|
+
DownloadIcon,
|
|
4
|
+
Loader2Icon,
|
|
5
|
+
RefreshCwIcon,
|
|
6
|
+
ShieldCheckIcon,
|
|
7
|
+
Trash2Icon,
|
|
8
|
+
} from "lucide-react";
|
|
9
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
RetrievalTraceDetail,
|
|
13
|
+
RetrievalTraceExportResult,
|
|
14
|
+
RetrievalTraceLabelResult,
|
|
15
|
+
RetrievalTraceListResult,
|
|
16
|
+
RetrievalTracePurgeResult,
|
|
17
|
+
} from "../../../core/retrieval-trace-management";
|
|
18
|
+
|
|
19
|
+
import { Badge } from "../components/ui/badge";
|
|
20
|
+
import { Button } from "../components/ui/button";
|
|
21
|
+
import {
|
|
22
|
+
Card,
|
|
23
|
+
CardContent,
|
|
24
|
+
CardHeader,
|
|
25
|
+
CardTitle,
|
|
26
|
+
} from "../components/ui/card";
|
|
27
|
+
import {
|
|
28
|
+
Dialog,
|
|
29
|
+
DialogContent,
|
|
30
|
+
DialogDescription,
|
|
31
|
+
DialogFooter,
|
|
32
|
+
DialogHeader,
|
|
33
|
+
DialogTitle,
|
|
34
|
+
} from "../components/ui/dialog";
|
|
35
|
+
import {
|
|
36
|
+
Table,
|
|
37
|
+
TableBody,
|
|
38
|
+
TableCell,
|
|
39
|
+
TableHead,
|
|
40
|
+
TableHeader,
|
|
41
|
+
TableRow,
|
|
42
|
+
} from "../components/ui/table";
|
|
43
|
+
import { apiFetch } from "../hooks/use-api";
|
|
44
|
+
import {
|
|
45
|
+
TraceEvidenceList,
|
|
46
|
+
type TraceEvidenceSelection,
|
|
47
|
+
TraceLabelForm,
|
|
48
|
+
TracePurgeNotice,
|
|
49
|
+
} from "./trace-history-detail";
|
|
50
|
+
|
|
51
|
+
interface PageProps {
|
|
52
|
+
navigate: (to: string | number) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type ConfirmAction =
|
|
56
|
+
| { kind: "delete"; traceId: string }
|
|
57
|
+
| { kind: "purge" }
|
|
58
|
+
| null;
|
|
59
|
+
|
|
60
|
+
const statusVariant = (
|
|
61
|
+
status: string
|
|
62
|
+
): "default" | "destructive" | "outline" | "secondary" => {
|
|
63
|
+
if (status === "completed") return "default";
|
|
64
|
+
if (status === "failed" || status === "cancelled") return "destructive";
|
|
65
|
+
if (status === "partial") return "secondary";
|
|
66
|
+
return "outline";
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const downloadJson = (value: unknown, filename: string): void => {
|
|
70
|
+
const blob = new Blob([`${JSON.stringify(value, null, 2)}\n`], {
|
|
71
|
+
type: "application/json",
|
|
72
|
+
});
|
|
73
|
+
const url = URL.createObjectURL(blob);
|
|
74
|
+
const anchor = document.createElement("a");
|
|
75
|
+
anchor.href = url;
|
|
76
|
+
anchor.download = filename;
|
|
77
|
+
anchor.click();
|
|
78
|
+
URL.revokeObjectURL(url);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default function TraceHistory({ navigate }: PageProps) {
|
|
82
|
+
const [history, setHistory] = useState<RetrievalTraceListResult | null>(null);
|
|
83
|
+
const [detail, setDetail] = useState<RetrievalTraceDetail | null>(null);
|
|
84
|
+
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
85
|
+
const [busy, setBusy] = useState<string | null>("list");
|
|
86
|
+
const [error, setError] = useState<string | null>(null);
|
|
87
|
+
const [confirmAction, setConfirmAction] = useState<ConfirmAction>(null);
|
|
88
|
+
const [label, setLabel] = useState<
|
|
89
|
+
"relevant" | "irrelevant" | "missing_expected"
|
|
90
|
+
>("relevant");
|
|
91
|
+
const [targetRef, setTargetRef] = useState("");
|
|
92
|
+
const [sourceHash, setSourceHash] = useState("");
|
|
93
|
+
const [selectedEvidence, setSelectedEvidence] =
|
|
94
|
+
useState<TraceEvidenceSelection | null>(null);
|
|
95
|
+
const [purgeReceipt, setPurgeReceipt] =
|
|
96
|
+
useState<RetrievalTracePurgeResult | null>(null);
|
|
97
|
+
|
|
98
|
+
const loadHistory = useCallback(async (): Promise<void> => {
|
|
99
|
+
setBusy("list");
|
|
100
|
+
setError(null);
|
|
101
|
+
const result = await apiFetch<RetrievalTraceListResult>(
|
|
102
|
+
"/api/traces?limit=100"
|
|
103
|
+
);
|
|
104
|
+
if (result.error) {
|
|
105
|
+
setError(result.error);
|
|
106
|
+
} else {
|
|
107
|
+
setHistory(result.data);
|
|
108
|
+
const ids = new Set(result.data?.traces.map((trace) => trace.traceId));
|
|
109
|
+
setSelected(
|
|
110
|
+
(current) => new Set([...current].filter((traceId) => ids.has(traceId)))
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
setBusy(null);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
void loadHistory();
|
|
118
|
+
}, [loadHistory]);
|
|
119
|
+
|
|
120
|
+
const inspect = useCallback(async (traceId: string): Promise<void> => {
|
|
121
|
+
setBusy(`show:${traceId}`);
|
|
122
|
+
setError(null);
|
|
123
|
+
const result = await apiFetch<RetrievalTraceDetail>(
|
|
124
|
+
`/api/traces/${encodeURIComponent(traceId)}?detailLimit=500`
|
|
125
|
+
);
|
|
126
|
+
setSelectedEvidence(null);
|
|
127
|
+
setTargetRef("");
|
|
128
|
+
setSourceHash("");
|
|
129
|
+
setDetail(result.data);
|
|
130
|
+
setError(result.error);
|
|
131
|
+
setBusy(null);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const submitLabel = async (): Promise<void> => {
|
|
135
|
+
if (!detail || !targetRef.trim()) return;
|
|
136
|
+
setBusy("label");
|
|
137
|
+
setError(null);
|
|
138
|
+
const result = await apiFetch<RetrievalTraceLabelResult>(
|
|
139
|
+
`/api/traces/${encodeURIComponent(detail.trace.traceId)}/judgments`,
|
|
140
|
+
{
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
label,
|
|
144
|
+
targetRef: targetRef.trim(),
|
|
145
|
+
...(label !== "missing_expected" &&
|
|
146
|
+
selectedEvidence?.ref === targetRef.trim()
|
|
147
|
+
? {
|
|
148
|
+
targetKind: selectedEvidence.targetKind,
|
|
149
|
+
startLine: selectedEvidence.startLine,
|
|
150
|
+
endLine: selectedEvidence.endLine,
|
|
151
|
+
sourceHash: selectedEvidence.sourceHash,
|
|
152
|
+
docid: selectedEvidence.docid,
|
|
153
|
+
}
|
|
154
|
+
: {}),
|
|
155
|
+
...(label === "missing_expected" && sourceHash.trim()
|
|
156
|
+
? { sourceHash: sourceHash.trim() }
|
|
157
|
+
: {}),
|
|
158
|
+
}),
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
if (result.error) {
|
|
162
|
+
setError(result.error);
|
|
163
|
+
} else {
|
|
164
|
+
setTargetRef("");
|
|
165
|
+
setSourceHash("");
|
|
166
|
+
setSelectedEvidence(null);
|
|
167
|
+
await inspect(detail.trace.traceId);
|
|
168
|
+
}
|
|
169
|
+
setBusy(null);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const exportSelected = async (): Promise<void> => {
|
|
173
|
+
if (selected.size === 0) return;
|
|
174
|
+
setBusy("export");
|
|
175
|
+
setError(null);
|
|
176
|
+
const result = await apiFetch<RetrievalTraceExportResult>(
|
|
177
|
+
"/api/traces/export",
|
|
178
|
+
{
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
traceIds: [...selected],
|
|
182
|
+
format: "agentic-receipt",
|
|
183
|
+
}),
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
if (result.data) {
|
|
187
|
+
downloadJson(
|
|
188
|
+
result.data.artifact,
|
|
189
|
+
`gno-traces-${result.data.manifest.artifactHash.slice(0, 12)}.json`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
setError(result.error);
|
|
193
|
+
setBusy(null);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const runConfirmedAction = async (): Promise<void> => {
|
|
197
|
+
if (!confirmAction) return;
|
|
198
|
+
const action = confirmAction;
|
|
199
|
+
setConfirmAction(null);
|
|
200
|
+
setBusy(action.kind);
|
|
201
|
+
setError(null);
|
|
202
|
+
const endpoint =
|
|
203
|
+
action.kind === "purge"
|
|
204
|
+
? "/api/traces"
|
|
205
|
+
: `/api/traces/${encodeURIComponent(action.traceId)}`;
|
|
206
|
+
const result = await apiFetch<RetrievalTracePurgeResult>(endpoint, {
|
|
207
|
+
method: "DELETE",
|
|
208
|
+
});
|
|
209
|
+
if (result.error) {
|
|
210
|
+
setError(result.error);
|
|
211
|
+
} else {
|
|
212
|
+
if (action.kind === "purge") setPurgeReceipt(result.data);
|
|
213
|
+
setDetail(null);
|
|
214
|
+
setSelected(new Set());
|
|
215
|
+
await loadHistory();
|
|
216
|
+
}
|
|
217
|
+
setBusy(null);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const terminalSelected = useMemo(() => {
|
|
221
|
+
const byId = new Map(
|
|
222
|
+
history?.traces.map((trace) => [trace.traceId, trace]) ?? []
|
|
223
|
+
);
|
|
224
|
+
return [...selected].every(
|
|
225
|
+
(traceId) => byId.get(traceId)?.status !== "open"
|
|
226
|
+
);
|
|
227
|
+
}, [history, selected]);
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<div className="min-h-screen bg-background">
|
|
231
|
+
<header className="border-b border-border/50 bg-card/50 backdrop-blur-sm">
|
|
232
|
+
<div className="mx-auto flex max-w-7xl items-center justify-between p-6">
|
|
233
|
+
<div className="flex items-center gap-4">
|
|
234
|
+
<Button
|
|
235
|
+
aria-label="Back"
|
|
236
|
+
onClick={() => navigate(-1)}
|
|
237
|
+
size="icon"
|
|
238
|
+
variant="ghost"
|
|
239
|
+
>
|
|
240
|
+
<ArrowLeftIcon className="size-4" />
|
|
241
|
+
</Button>
|
|
242
|
+
<div>
|
|
243
|
+
<h1 className="font-display text-2xl font-semibold">
|
|
244
|
+
Trace history
|
|
245
|
+
</h1>
|
|
246
|
+
<p className="text-sm text-muted-foreground">
|
|
247
|
+
Private receipts. Local evidence. Explicit feedback only.
|
|
248
|
+
</p>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<div className="flex gap-2">
|
|
252
|
+
<Button
|
|
253
|
+
disabled={busy !== null}
|
|
254
|
+
onClick={() => void loadHistory()}
|
|
255
|
+
variant="outline"
|
|
256
|
+
>
|
|
257
|
+
<RefreshCwIcon className="mr-2 size-4" />
|
|
258
|
+
Refresh
|
|
259
|
+
</Button>
|
|
260
|
+
<Button
|
|
261
|
+
disabled={
|
|
262
|
+
busy !== null || selected.size === 0 || !terminalSelected
|
|
263
|
+
}
|
|
264
|
+
onClick={() => void exportSelected()}
|
|
265
|
+
>
|
|
266
|
+
<DownloadIcon className="mr-2 size-4" />
|
|
267
|
+
Export {selected.size || ""}
|
|
268
|
+
</Button>
|
|
269
|
+
<Button
|
|
270
|
+
disabled={busy !== null || !history?.traces.length}
|
|
271
|
+
onClick={() => setConfirmAction({ kind: "purge" })}
|
|
272
|
+
variant="destructive"
|
|
273
|
+
>
|
|
274
|
+
<Trash2Icon className="mr-2 size-4" />
|
|
275
|
+
Purge all
|
|
276
|
+
</Button>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</header>
|
|
280
|
+
|
|
281
|
+
<main className="mx-auto grid max-w-7xl gap-6 p-6 lg:grid-cols-[1.35fr_1fr]">
|
|
282
|
+
<div className="lg:col-span-2">
|
|
283
|
+
<TracePurgeNotice receipt={purgeReceipt} />
|
|
284
|
+
</div>
|
|
285
|
+
<Card>
|
|
286
|
+
<CardHeader>
|
|
287
|
+
<CardTitle className="flex items-center gap-2">
|
|
288
|
+
<ShieldCheckIcon className="size-5 text-primary" />
|
|
289
|
+
Local receipts
|
|
290
|
+
</CardTitle>
|
|
291
|
+
</CardHeader>
|
|
292
|
+
<CardContent>
|
|
293
|
+
{error && (
|
|
294
|
+
<div className="mb-4 rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
|
|
295
|
+
{error}
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
{busy === "list" && !history ? (
|
|
299
|
+
<div className="flex items-center justify-center py-16 text-muted-foreground">
|
|
300
|
+
<Loader2Icon className="mr-2 size-5 animate-spin" />
|
|
301
|
+
Loading traces
|
|
302
|
+
</div>
|
|
303
|
+
) : (
|
|
304
|
+
<Table>
|
|
305
|
+
<TableHeader>
|
|
306
|
+
<TableRow>
|
|
307
|
+
<TableHead className="w-10">Use</TableHead>
|
|
308
|
+
<TableHead>Trace</TableHead>
|
|
309
|
+
<TableHead>Status</TableHead>
|
|
310
|
+
<TableHead>Mode</TableHead>
|
|
311
|
+
<TableHead>Created</TableHead>
|
|
312
|
+
</TableRow>
|
|
313
|
+
</TableHeader>
|
|
314
|
+
<TableBody>
|
|
315
|
+
{history?.traces.map((trace) => (
|
|
316
|
+
<TableRow
|
|
317
|
+
className="cursor-pointer"
|
|
318
|
+
key={trace.traceId}
|
|
319
|
+
onClick={() => void inspect(trace.traceId)}
|
|
320
|
+
>
|
|
321
|
+
<TableCell>
|
|
322
|
+
<input
|
|
323
|
+
aria-label={`Select ${trace.traceId}`}
|
|
324
|
+
checked={selected.has(trace.traceId)}
|
|
325
|
+
onChange={(event) => {
|
|
326
|
+
event.stopPropagation();
|
|
327
|
+
setSelected((current) => {
|
|
328
|
+
const next = new Set(current);
|
|
329
|
+
if (next.has(trace.traceId)) {
|
|
330
|
+
next.delete(trace.traceId);
|
|
331
|
+
} else {
|
|
332
|
+
next.add(trace.traceId);
|
|
333
|
+
}
|
|
334
|
+
return next;
|
|
335
|
+
});
|
|
336
|
+
}}
|
|
337
|
+
onClick={(event) => event.stopPropagation()}
|
|
338
|
+
type="checkbox"
|
|
339
|
+
/>
|
|
340
|
+
</TableCell>
|
|
341
|
+
<TableCell className="max-w-48 truncate font-mono text-xs">
|
|
342
|
+
{trace.traceId}
|
|
343
|
+
</TableCell>
|
|
344
|
+
<TableCell>
|
|
345
|
+
<Badge variant={statusVariant(trace.status)}>
|
|
346
|
+
{trace.status}
|
|
347
|
+
</Badge>
|
|
348
|
+
</TableCell>
|
|
349
|
+
<TableCell>{trace.redactionMode}</TableCell>
|
|
350
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
351
|
+
{new Date(trace.createdAtMs).toLocaleString()}
|
|
352
|
+
</TableCell>
|
|
353
|
+
</TableRow>
|
|
354
|
+
))}
|
|
355
|
+
</TableBody>
|
|
356
|
+
</Table>
|
|
357
|
+
)}
|
|
358
|
+
</CardContent>
|
|
359
|
+
</Card>
|
|
360
|
+
|
|
361
|
+
<Card>
|
|
362
|
+
<CardHeader>
|
|
363
|
+
<CardTitle>Receipt detail</CardTitle>
|
|
364
|
+
</CardHeader>
|
|
365
|
+
<CardContent>
|
|
366
|
+
{!detail ? (
|
|
367
|
+
<p className="py-12 text-center text-sm text-muted-foreground">
|
|
368
|
+
Select a trace to inspect bounded evidence and add explicit
|
|
369
|
+
feedback.
|
|
370
|
+
</p>
|
|
371
|
+
) : (
|
|
372
|
+
<div className="space-y-5">
|
|
373
|
+
<div className="space-y-1 text-sm">
|
|
374
|
+
<div className="break-all font-mono text-xs">
|
|
375
|
+
{detail.trace.traceId}
|
|
376
|
+
</div>
|
|
377
|
+
<div className="flex gap-2">
|
|
378
|
+
<Badge variant={statusVariant(detail.trace.status)}>
|
|
379
|
+
{detail.trace.status}
|
|
380
|
+
</Badge>
|
|
381
|
+
<Badge variant="outline">
|
|
382
|
+
{detail.trace.redactionMode}
|
|
383
|
+
</Badge>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div className="grid grid-cols-4 gap-2 text-center text-xs">
|
|
388
|
+
{(
|
|
389
|
+
[
|
|
390
|
+
["Runs", detail.runs.length],
|
|
391
|
+
["Events", detail.events.length],
|
|
392
|
+
["Labels", detail.judgments.length],
|
|
393
|
+
["Exports", detail.exports.length],
|
|
394
|
+
] as const
|
|
395
|
+
).map(([name, count]) => (
|
|
396
|
+
<div className="rounded-md bg-muted p-2" key={name}>
|
|
397
|
+
<div className="font-semibold">{count}</div>
|
|
398
|
+
<div className="text-muted-foreground">{name}</div>
|
|
399
|
+
</div>
|
|
400
|
+
))}
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<TraceEvidenceList
|
|
404
|
+
detail={detail}
|
|
405
|
+
onSelect={(selection) => {
|
|
406
|
+
setSelectedEvidence(selection);
|
|
407
|
+
setTargetRef(selection.ref);
|
|
408
|
+
setSourceHash(selection.sourceHash ?? "");
|
|
409
|
+
}}
|
|
410
|
+
/>
|
|
411
|
+
|
|
412
|
+
<TraceLabelForm
|
|
413
|
+
busy={busy === "label"}
|
|
414
|
+
label={label}
|
|
415
|
+
onLabelChange={setLabel}
|
|
416
|
+
onSourceHashChange={setSourceHash}
|
|
417
|
+
onSubmit={() => void submitLabel()}
|
|
418
|
+
onTargetRefChange={(value) => {
|
|
419
|
+
setTargetRef(value);
|
|
420
|
+
setSelectedEvidence(null);
|
|
421
|
+
}}
|
|
422
|
+
sourceHash={sourceHash}
|
|
423
|
+
targetRef={targetRef}
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
<Button
|
|
427
|
+
className="w-full"
|
|
428
|
+
onClick={() =>
|
|
429
|
+
setConfirmAction({
|
|
430
|
+
kind: "delete",
|
|
431
|
+
traceId: detail.trace.traceId,
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
variant="destructive"
|
|
435
|
+
>
|
|
436
|
+
<Trash2Icon className="mr-2 size-4" />
|
|
437
|
+
Delete this trace
|
|
438
|
+
</Button>
|
|
439
|
+
</div>
|
|
440
|
+
)}
|
|
441
|
+
</CardContent>
|
|
442
|
+
</Card>
|
|
443
|
+
</main>
|
|
444
|
+
|
|
445
|
+
<Dialog
|
|
446
|
+
onOpenChange={(open) => {
|
|
447
|
+
if (!open) setConfirmAction(null);
|
|
448
|
+
}}
|
|
449
|
+
open={confirmAction !== null}
|
|
450
|
+
>
|
|
451
|
+
<DialogContent>
|
|
452
|
+
<DialogHeader>
|
|
453
|
+
<DialogTitle>
|
|
454
|
+
{confirmAction?.kind === "purge"
|
|
455
|
+
? "Purge every retrieval trace?"
|
|
456
|
+
: "Delete this retrieval trace?"}
|
|
457
|
+
</DialogTitle>
|
|
458
|
+
<DialogDescription>
|
|
459
|
+
This removes local receipt data and cannot be undone. Source
|
|
460
|
+
documents are not changed.
|
|
461
|
+
</DialogDescription>
|
|
462
|
+
</DialogHeader>
|
|
463
|
+
<DialogFooter>
|
|
464
|
+
<Button onClick={() => setConfirmAction(null)} variant="outline">
|
|
465
|
+
Cancel
|
|
466
|
+
</Button>
|
|
467
|
+
<Button
|
|
468
|
+
onClick={() => void runConfirmedAction()}
|
|
469
|
+
variant="destructive"
|
|
470
|
+
>
|
|
471
|
+
Delete local receipts
|
|
472
|
+
</Button>
|
|
473
|
+
</DialogFooter>
|
|
474
|
+
</DialogContent>
|
|
475
|
+
</Dialog>
|
|
476
|
+
</div>
|
|
477
|
+
);
|
|
478
|
+
}
|