@gmickel/gno 2.3.3 → 2.5.1
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 +22 -3
- package/assets/skill/SKILL.md +53 -0
- package/assets/skill/cli-reference.md +29 -0
- package/assets/skill/examples.md +30 -0
- package/assets/skill/mcp-reference.md +26 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.3.3.zip → gno-browser-clipper-v2.5.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +41 -0
- package/spec/compiled-context.md +68 -0
- package/spec/db/schema.sql +2 -0
- package/spec/evals.md +48 -0
- package/spec/mcp.md +37 -0
- package/spec/output-schemas/ask.schema.json +12 -0
- package/spec/output-schemas/compiled-context-check.schema.json +44 -0
- package/spec/output-schemas/compiled-context-file.schema.json +165 -0
- package/spec/output-schemas/compiled-context-preview.schema.json +142 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +205 -32
- package/spec/output-schemas/context-capsule-verification.schema.json +32 -9
- package/spec/output-schemas/evidence-fixtures.schema.json +176 -0
- package/spec/output-schemas/evidence-report.schema.json +322 -0
- package/spec/output-schemas/evidence-run.schema.json +277 -0
- package/spec/output-schemas/metadata-predicate.schema.json +460 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +13 -4
- package/spec/output-schemas/search-results.schema.json +12 -0
- package/spec/output-schemas/status.schema.json +37 -6
- package/src/app/compiled-context-files.ts +361 -0
- package/src/app/compiled-context.ts +240 -0
- package/src/app/context-runtime-contract.ts +33 -2
- package/src/app/context-runtime-input.ts +17 -0
- package/src/app/context-runtime-types.ts +2 -0
- package/src/app/context-runtime.ts +27 -3
- package/src/app/context-surface.ts +7 -0
- package/src/app/verified-ask.ts +1 -0
- package/src/cli/commands/ask.ts +1 -0
- package/src/cli/commands/audit.ts +4 -0
- package/src/cli/commands/context-build.ts +1 -0
- package/src/cli/commands/context-compiled.ts +136 -0
- package/src/cli/commands/search.ts +1 -0
- package/src/cli/commands/status.ts +10 -0
- package/src/cli/errors.ts +10 -3
- package/src/cli/options.ts +19 -0
- package/src/cli/program.ts +82 -0
- package/src/converters/types.ts +2 -0
- package/src/core/compiled-context.ts +254 -0
- package/src/core/context-budget.ts +2 -10
- package/src/core/context-capsule-retrieval-schema.ts +8 -0
- package/src/core/context-capsule-schema.ts +47 -3
- package/src/core/context-capsule-validation.ts +14 -2
- package/src/core/context-capsule-verification.ts +1 -0
- package/src/core/context-capsule.ts +16 -2
- package/src/core/context-compiler.ts +3 -0
- package/src/core/file-lock.ts +22 -5
- package/src/core/folder-setup-planning.ts +2 -1
- package/src/core/network-boundary-inventory.ts +8 -0
- package/src/core/record-metadata.ts +1 -1
- package/src/core/retrieval-replay-candidate.ts +5 -0
- package/src/core/retrieval-trace-filter-normalization.ts +4 -0
- package/src/core/retrieval-trace-request.ts +1 -0
- package/src/core/retrieval-trace-session.ts +5 -1
- package/src/core/retrieval-trace.ts +2 -0
- package/src/core/setup-receipt.ts +27 -20
- package/src/core/typed-metadata.ts +228 -0
- package/src/core/validation.ts +9 -2
- package/src/core/windows-private-path.ts +96 -0
- package/src/index.ts +2 -2
- package/src/ingestion/compiled-context.ts +15 -0
- package/src/ingestion/record-adapter-canonical.ts +13 -2
- package/src/ingestion/record-container.ts +9 -0
- package/src/ingestion/sync.ts +51 -9
- package/src/ingestion/typed-metadata.ts +55 -0
- package/src/ingestion/walker.ts +5 -4
- package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
- package/src/mcp/http-egress.ts +11 -3
- package/src/mcp/retrieval-warnings.ts +24 -0
- package/src/mcp/tools/ask.ts +23 -2
- package/src/mcp/tools/context.ts +47 -2
- package/src/mcp/tools/index.ts +58 -1
- package/src/mcp/tools/query.ts +16 -1
- package/src/mcp/tools/search.ts +12 -1
- package/src/mcp/tools/status.ts +4 -0
- package/src/mcp/tools/vsearch.ts +12 -1
- package/src/pipeline/diagnose.ts +6 -0
- package/src/pipeline/filters.ts +65 -0
- package/src/pipeline/graph-retrieval.ts +9 -1
- package/src/pipeline/hybrid.ts +22 -2
- package/src/pipeline/search.ts +11 -0
- package/src/pipeline/types.ts +7 -1
- package/src/pipeline/vsearch.ts +21 -1
- package/src/sdk/client.ts +98 -3
- package/src/sdk/index.ts +11 -0
- package/src/sdk/types.ts +35 -1
- package/src/serve/compiled-context.ts +84 -0
- package/src/serve/context-capsule.ts +1 -0
- package/src/serve/public/app.tsx +12 -1
- package/src/serve/public/components/MetadataFilter.tsx +186 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/metadata-filter.ts +28 -0
- package/src/serve/public/lib/retrieval-filters.ts +3 -0
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Ask.tsx +48 -3
- package/src/serve/public/pages/CompiledContext.tsx +364 -0
- package/src/serve/public/pages/Dashboard.tsx +7 -0
- package/src/serve/public/pages/DocView.tsx +33 -0
- package/src/serve/public/pages/Search.tsx +44 -1
- package/src/serve/routes/api.ts +50 -0
- package/src/serve/server.ts +43 -2
- package/src/serve/spa-production-build.ts +6 -5
- package/src/store/migrations/030-typed-metadata.ts +13 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +69 -5
- package/src/store/sqlite/eligibility.ts +12 -0
- package/src/store/sqlite/metadata-predicate.ts +71 -0
- package/src/store/types.ts +11 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.3.3.zip.sha256 +0 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ZodError } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeMetadataPredicate,
|
|
5
|
+
type MetadataPredicate,
|
|
6
|
+
} from "../../../core/typed-metadata";
|
|
7
|
+
|
|
8
|
+
export function parseMetadataFilter(text: string): {
|
|
9
|
+
filter?: MetadataPredicate;
|
|
10
|
+
error?: string;
|
|
11
|
+
} {
|
|
12
|
+
if (!text.trim()) return {};
|
|
13
|
+
try {
|
|
14
|
+
return { filter: normalizeMetadataPredicate(JSON.parse(text)) };
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof ZodError) {
|
|
17
|
+
const issue = error.issues[0];
|
|
18
|
+
const path = issue?.path.length ? `.${issue.path.join(".")}` : "";
|
|
19
|
+
return {
|
|
20
|
+
error: `filter${path}: ${issue?.message ?? "Invalid predicate"}. Check the operator, key, and JSON value type.`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
error:
|
|
25
|
+
"filter: Invalid JSON. Use quoted strings, finite numbers, booleans, or arrays of one type.",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -8,6 +8,7 @@ export interface QueryModeEntry {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface RetrievalFiltersState {
|
|
11
|
+
filter?: string;
|
|
11
12
|
collection: string;
|
|
12
13
|
intent: string;
|
|
13
14
|
candidateLimit: string;
|
|
@@ -123,6 +124,7 @@ export function parseFiltersFromSearch(
|
|
|
123
124
|
const tags = parseTagsCsv(tagsAll ?? tagsAny ?? "");
|
|
124
125
|
|
|
125
126
|
return {
|
|
127
|
+
filter: params.get("filter") ?? defaults.filter ?? "",
|
|
126
128
|
collection: params.get("collection") ?? defaults.collection ?? "",
|
|
127
129
|
intent: params.get("intent") ?? defaults.intent ?? "",
|
|
128
130
|
candidateLimit:
|
|
@@ -151,6 +153,7 @@ export function applyFiltersToUrl(
|
|
|
151
153
|
}
|
|
152
154
|
};
|
|
153
155
|
|
|
156
|
+
setOrDelete("filter", filters.filter ?? "");
|
|
154
157
|
setOrDelete("collection", filters.collection);
|
|
155
158
|
setOrDelete("intent", filters.intent);
|
|
156
159
|
setOrDelete("candidateLimit", filters.candidateLimit);
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type AskVerification,
|
|
26
26
|
AskVerificationPanel,
|
|
27
27
|
} from "../components/AskVerificationPanel";
|
|
28
|
+
import { MetadataFilter } from "../components/MetadataFilter";
|
|
28
29
|
import {
|
|
29
30
|
ThoroughnessSelector,
|
|
30
31
|
type Thoroughness,
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
import { Textarea } from "../components/ui/textarea";
|
|
49
50
|
import { apiFetch } from "../hooks/use-api";
|
|
50
51
|
import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts";
|
|
52
|
+
import { parseMetadataFilter } from "../lib/metadata-filter";
|
|
51
53
|
import {
|
|
52
54
|
parseTagsCsv,
|
|
53
55
|
type QueryModeEntry,
|
|
@@ -92,6 +94,7 @@ interface AskResponse {
|
|
|
92
94
|
citations?: Citation[];
|
|
93
95
|
results: SearchResult[];
|
|
94
96
|
meta: {
|
|
97
|
+
warnings?: { code: string; message: string }[];
|
|
95
98
|
expanded: boolean;
|
|
96
99
|
reranked: boolean;
|
|
97
100
|
vectorsUsed: boolean;
|
|
@@ -200,14 +203,21 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
200
203
|
const [activePreset, setActivePreset] = useState("slim-tuned");
|
|
201
204
|
const [verify, setVerify] = useState(false);
|
|
202
205
|
|
|
203
|
-
const [showAdvanced, setShowAdvanced] = useState(
|
|
204
|
-
|
|
206
|
+
const [showAdvanced, setShowAdvanced] = useState(
|
|
207
|
+
Boolean(new URLSearchParams(window.location.search).get("filter"))
|
|
208
|
+
);
|
|
209
|
+
const [selectedCollection, setSelectedCollection] = useState(
|
|
210
|
+
new URLSearchParams(window.location.search).get("collection") ?? ""
|
|
211
|
+
);
|
|
205
212
|
const [intent, setIntent] = useState("");
|
|
206
213
|
const [candidateLimit, setCandidateLimit] = useState("");
|
|
207
214
|
const [exclude, setExclude] = useState("");
|
|
208
215
|
const [since, setSince] = useState("");
|
|
209
216
|
const [until, setUntil] = useState("");
|
|
210
217
|
const [category, setCategory] = useState("");
|
|
218
|
+
const [metadataFilter, setMetadataFilter] = useState(
|
|
219
|
+
new URLSearchParams(window.location.search).get("filter") ?? ""
|
|
220
|
+
);
|
|
211
221
|
const [author, setAuthor] = useState("");
|
|
212
222
|
const [tagMode, setTagMode] = useState<TagMode>("any");
|
|
213
223
|
const [tagsInput, setTagsInput] = useState("");
|
|
@@ -255,6 +265,16 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
255
265
|
void bootstrap();
|
|
256
266
|
}, []);
|
|
257
267
|
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
const url = new URL(window.location.href);
|
|
270
|
+
if (selectedCollection.trim())
|
|
271
|
+
url.searchParams.set("collection", selectedCollection.trim());
|
|
272
|
+
else url.searchParams.delete("collection");
|
|
273
|
+
if (metadataFilter) url.searchParams.set("filter", metadataFilter);
|
|
274
|
+
else url.searchParams.delete("filter");
|
|
275
|
+
window.history.replaceState({}, "", url.toString());
|
|
276
|
+
}, [metadataFilter, selectedCollection]);
|
|
277
|
+
|
|
258
278
|
const cycleThoroughness = useCallback(() => {
|
|
259
279
|
setThoroughness((current) => {
|
|
260
280
|
if (!hybridAvailable) {
|
|
@@ -262,7 +282,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
262
282
|
}
|
|
263
283
|
const currentIdx = THOROUGHNESS_ORDER.indexOf(current);
|
|
264
284
|
const nextIdx = (currentIdx + 1) % THOROUGHNESS_ORDER.length;
|
|
265
|
-
return THOROUGHNESS_ORDER[nextIdx];
|
|
285
|
+
return THOROUGHNESS_ORDER[nextIdx] ?? current;
|
|
266
286
|
});
|
|
267
287
|
}, [hybridAvailable]);
|
|
268
288
|
|
|
@@ -304,12 +324,19 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
304
324
|
if (!query.trim()) {
|
|
305
325
|
return;
|
|
306
326
|
}
|
|
327
|
+
const parsedFilter = parseMetadataFilter(metadataFilter);
|
|
328
|
+
if (parsedFilter.error) {
|
|
329
|
+
setQueryModeError(parsedFilter.error);
|
|
330
|
+
setShowAdvanced(true);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
307
333
|
if (!structuredQueryState.ok) {
|
|
308
334
|
setQueryModeError(structuredQueryState.error.message);
|
|
309
335
|
setShowAdvanced(true);
|
|
310
336
|
return;
|
|
311
337
|
}
|
|
312
338
|
|
|
339
|
+
setQueryModeError(null);
|
|
313
340
|
const entryId = crypto.randomUUID();
|
|
314
341
|
const currentQuery = query.trim();
|
|
315
342
|
|
|
@@ -334,6 +361,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
334
361
|
verify,
|
|
335
362
|
};
|
|
336
363
|
|
|
364
|
+
if (parsedFilter.filter) requestBody.filter = parsedFilter.filter;
|
|
337
365
|
if (selectedCollection) {
|
|
338
366
|
requestBody.collection = selectedCollection;
|
|
339
367
|
}
|
|
@@ -393,6 +421,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
393
421
|
);
|
|
394
422
|
},
|
|
395
423
|
[
|
|
424
|
+
metadataFilter,
|
|
396
425
|
author,
|
|
397
426
|
candidateLimit,
|
|
398
427
|
activePreset,
|
|
@@ -428,6 +457,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
428
457
|
setUntil("");
|
|
429
458
|
setCategory("");
|
|
430
459
|
setAuthor("");
|
|
460
|
+
setMetadataFilter("");
|
|
431
461
|
setTagsInput("");
|
|
432
462
|
setTagMode("any");
|
|
433
463
|
setQueryModes([]);
|
|
@@ -454,6 +484,7 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
454
484
|
since ? `since:${since}` : null,
|
|
455
485
|
until ? `until:${until}` : null,
|
|
456
486
|
category.trim() ? `category:${category.trim()}` : null,
|
|
487
|
+
metadataFilter.trim() ? "custom metadata" : null,
|
|
457
488
|
author.trim() ? `author:${author.trim()}` : null,
|
|
458
489
|
parseTagsCsv(tagsInput).length > 0
|
|
459
490
|
? `${tagMode}:${parseTagsCsv(tagsInput).join(",")}`
|
|
@@ -687,6 +718,11 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
687
718
|
</div>
|
|
688
719
|
</div>
|
|
689
720
|
|
|
721
|
+
<MetadataFilter
|
|
722
|
+
onChange={setMetadataFilter}
|
|
723
|
+
value={metadataFilter}
|
|
724
|
+
/>
|
|
725
|
+
|
|
690
726
|
<div className="flex flex-wrap items-center gap-2">
|
|
691
727
|
<span className="text-muted-foreground text-xs">
|
|
692
728
|
Tag match:
|
|
@@ -857,6 +893,15 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
857
893
|
|
|
858
894
|
{entry.response && (
|
|
859
895
|
<>
|
|
896
|
+
{entry.response.meta.warnings?.map((warning) => (
|
|
897
|
+
<p
|
|
898
|
+
className="rounded border border-secondary/30 bg-secondary/10 p-3 text-sm"
|
|
899
|
+
key={warning.code}
|
|
900
|
+
role="status"
|
|
901
|
+
>
|
|
902
|
+
{warning.message}
|
|
903
|
+
</p>
|
|
904
|
+
))}
|
|
860
905
|
{entry.response.answer && (
|
|
861
906
|
<div className="prose prose-sm prose-invert max-w-none rounded-lg border border-border/30 bg-card/60 p-5 shadow-[0_0_30px_-10px_hsl(var(--primary)/0.08)]">
|
|
862
907
|
<p className="whitespace-pre-wrap leading-relaxed">
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { ArrowLeftIcon, DownloadIcon } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
CompiledContextPreview as Preview,
|
|
6
|
+
CompiledContextCheck as Check,
|
|
7
|
+
} from "../../../core/compiled-context";
|
|
8
|
+
|
|
9
|
+
import { Button } from "../components/ui/button";
|
|
10
|
+
import {
|
|
11
|
+
Card,
|
|
12
|
+
CardContent,
|
|
13
|
+
CardHeader,
|
|
14
|
+
CardTitle,
|
|
15
|
+
} from "../components/ui/card";
|
|
16
|
+
import { Input } from "../components/ui/input";
|
|
17
|
+
import { Textarea } from "../components/ui/textarea";
|
|
18
|
+
import { apiFetch } from "../hooks/use-api";
|
|
19
|
+
|
|
20
|
+
const MAX_BYTES = 4 * 1024 * 1024;
|
|
21
|
+
const byteLength = (value: string): number =>
|
|
22
|
+
new TextEncoder().encode(value).length;
|
|
23
|
+
|
|
24
|
+
export default function CompiledContext({
|
|
25
|
+
navigate,
|
|
26
|
+
}: {
|
|
27
|
+
navigate: (to: string | number) => void;
|
|
28
|
+
}) {
|
|
29
|
+
const [capsule, setCapsule] = useState("");
|
|
30
|
+
const [markdown, setMarkdown] = useState("");
|
|
31
|
+
const [tokens, setTokens] = useState("12000");
|
|
32
|
+
const [bytes, setBytes] = useState("");
|
|
33
|
+
const [preview, setPreview] = useState<Preview | null>(null);
|
|
34
|
+
const [check, setCheck] = useState<Check | null>(null);
|
|
35
|
+
const [busy, setBusy] = useState(false);
|
|
36
|
+
const [error, setError] = useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
const invalidate = () => {
|
|
39
|
+
setPreview(null);
|
|
40
|
+
setCheck(null);
|
|
41
|
+
setError(null);
|
|
42
|
+
};
|
|
43
|
+
const upload = async (
|
|
44
|
+
file: File | undefined,
|
|
45
|
+
kind: "capsule" | "markdown"
|
|
46
|
+
) => {
|
|
47
|
+
if (!file) return;
|
|
48
|
+
invalidate();
|
|
49
|
+
if (kind === "capsule") setCapsule("");
|
|
50
|
+
else setMarkdown("");
|
|
51
|
+
if (file.size > MAX_BYTES) {
|
|
52
|
+
setError("Files must be 4 MiB or smaller.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setBusy(true);
|
|
56
|
+
try {
|
|
57
|
+
const text = await file.text();
|
|
58
|
+
if (kind === "capsule") setCapsule(text);
|
|
59
|
+
else setMarkdown(text);
|
|
60
|
+
} catch {
|
|
61
|
+
setError("Could not read this file.");
|
|
62
|
+
} finally {
|
|
63
|
+
setBusy(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const run = async (kind: "preview" | "check") => {
|
|
67
|
+
invalidate();
|
|
68
|
+
setBusy(true);
|
|
69
|
+
try {
|
|
70
|
+
if (byteLength(capsule) > MAX_BYTES || byteLength(markdown) > MAX_BYTES)
|
|
71
|
+
throw new Error("Inputs must be 4 MiB or smaller.");
|
|
72
|
+
const parsed: unknown = JSON.parse(capsule);
|
|
73
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
74
|
+
throw new Error("Supply a Capsule JSON object.");
|
|
75
|
+
if (kind === "preview") {
|
|
76
|
+
const budgetTokens = Number(tokens);
|
|
77
|
+
const budgetBytes = bytes === "" ? undefined : Number(bytes);
|
|
78
|
+
if (
|
|
79
|
+
!Number.isInteger(budgetTokens) ||
|
|
80
|
+
budgetTokens < 1 ||
|
|
81
|
+
budgetTokens > 1_000_000 ||
|
|
82
|
+
(budgetBytes !== undefined &&
|
|
83
|
+
(!Number.isInteger(budgetBytes) ||
|
|
84
|
+
budgetBytes < 1 ||
|
|
85
|
+
budgetBytes > MAX_BYTES))
|
|
86
|
+
)
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Choose a positive budget: at most 1,000,000 tokens and 4 MiB."
|
|
89
|
+
);
|
|
90
|
+
const result = await apiFetch<Preview>(
|
|
91
|
+
"/api/context/compiled/preview",
|
|
92
|
+
{
|
|
93
|
+
method: "POST",
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
capsule: parsed,
|
|
96
|
+
budgetTokens,
|
|
97
|
+
budgetBytes,
|
|
98
|
+
}),
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
if (result.error || !result.data)
|
|
102
|
+
throw new Error(result.error ?? "No preview returned.");
|
|
103
|
+
setPreview(result.data);
|
|
104
|
+
} else {
|
|
105
|
+
if (!markdown.trim())
|
|
106
|
+
throw new Error("Supply the compiled Markdown to check.");
|
|
107
|
+
const result = await apiFetch<Check>("/api/context/compiled/check", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: JSON.stringify({ capsule: parsed, markdown }),
|
|
110
|
+
});
|
|
111
|
+
if (result.error || !result.data)
|
|
112
|
+
throw new Error(result.error ?? "No check returned.");
|
|
113
|
+
setCheck(result.data);
|
|
114
|
+
}
|
|
115
|
+
} catch (cause) {
|
|
116
|
+
setError(cause instanceof Error ? cause.message : "Request failed.");
|
|
117
|
+
} finally {
|
|
118
|
+
setBusy(false);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const download = () => {
|
|
122
|
+
if (!preview) return;
|
|
123
|
+
const url = URL.createObjectURL(
|
|
124
|
+
new Blob([preview.markdown], { type: "text/markdown;charset=utf-8" })
|
|
125
|
+
);
|
|
126
|
+
const link = document.createElement("a");
|
|
127
|
+
link.href = url;
|
|
128
|
+
link.download = "project.gno-context.md";
|
|
129
|
+
link.click();
|
|
130
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<main className="container mx-auto max-w-5xl space-y-6 px-4 py-8">
|
|
135
|
+
<Button onClick={() => navigate("/")} variant="ghost">
|
|
136
|
+
<ArrowLeftIcon className="size-4" /> Dashboard
|
|
137
|
+
</Button>
|
|
138
|
+
<header className="space-y-2">
|
|
139
|
+
<h1 className="font-serif text-3xl">Compiled project context</h1>
|
|
140
|
+
<p className="text-muted-foreground">
|
|
141
|
+
Turn a verified Context Capsule into a compact, cited Markdown file.
|
|
142
|
+
Source passages remain untrusted evidence, not agent instructions.
|
|
143
|
+
</p>
|
|
144
|
+
</header>
|
|
145
|
+
<form
|
|
146
|
+
onSubmit={(event) => {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
void run("preview");
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<fieldset className="min-w-0 space-y-4" disabled={busy}>
|
|
152
|
+
<legend className="sr-only">Compile a Context Capsule</legend>
|
|
153
|
+
<Card>
|
|
154
|
+
<CardHeader>
|
|
155
|
+
<CardTitle>1. Supply a Capsule</CardTitle>
|
|
156
|
+
</CardHeader>
|
|
157
|
+
<CardContent className="space-y-3">
|
|
158
|
+
<p className="text-sm text-muted-foreground">
|
|
159
|
+
Use a Capsule from this GNO instance. Verification checks
|
|
160
|
+
current sources and access before returning evidence. Inputs are
|
|
161
|
+
limited to 4 MiB each.
|
|
162
|
+
</p>
|
|
163
|
+
<label className="block space-y-2" htmlFor="capsule-file">
|
|
164
|
+
<span>Upload Capsule JSON</span>
|
|
165
|
+
<Input
|
|
166
|
+
accept=".json,application/json"
|
|
167
|
+
id="capsule-file"
|
|
168
|
+
onChange={(event) =>
|
|
169
|
+
void upload(event.target.files?.[0], "capsule")
|
|
170
|
+
}
|
|
171
|
+
type="file"
|
|
172
|
+
/>
|
|
173
|
+
</label>
|
|
174
|
+
<label className="block space-y-2" htmlFor="capsule-json">
|
|
175
|
+
<span>Or paste Capsule JSON</span>
|
|
176
|
+
<Textarea
|
|
177
|
+
className="h-40 min-w-0 field-sizing-fixed font-mono text-xs"
|
|
178
|
+
id="capsule-json"
|
|
179
|
+
onChange={(event) => {
|
|
180
|
+
invalidate();
|
|
181
|
+
setCapsule(event.target.value);
|
|
182
|
+
}}
|
|
183
|
+
spellCheck={false}
|
|
184
|
+
value={capsule}
|
|
185
|
+
/>
|
|
186
|
+
</label>
|
|
187
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
188
|
+
<label className="space-y-2" htmlFor="context-tokens">
|
|
189
|
+
<span>Token budget</span>
|
|
190
|
+
<Input
|
|
191
|
+
id="context-tokens"
|
|
192
|
+
max={1_000_000}
|
|
193
|
+
min={1}
|
|
194
|
+
onChange={(event) => {
|
|
195
|
+
invalidate();
|
|
196
|
+
setTokens(event.target.value);
|
|
197
|
+
}}
|
|
198
|
+
required
|
|
199
|
+
type="number"
|
|
200
|
+
value={tokens}
|
|
201
|
+
/>
|
|
202
|
+
</label>
|
|
203
|
+
<label className="space-y-2" htmlFor="context-bytes">
|
|
204
|
+
<span>Byte budget (optional)</span>
|
|
205
|
+
<Input
|
|
206
|
+
id="context-bytes"
|
|
207
|
+
max={MAX_BYTES}
|
|
208
|
+
min={1}
|
|
209
|
+
onChange={(event) => {
|
|
210
|
+
invalidate();
|
|
211
|
+
setBytes(event.target.value);
|
|
212
|
+
}}
|
|
213
|
+
type="number"
|
|
214
|
+
value={bytes}
|
|
215
|
+
/>
|
|
216
|
+
</label>
|
|
217
|
+
</div>
|
|
218
|
+
<Button type="submit">
|
|
219
|
+
{busy ? "Verifying…" : "Preview verified context"}
|
|
220
|
+
</Button>
|
|
221
|
+
</CardContent>
|
|
222
|
+
</Card>
|
|
223
|
+
<Card>
|
|
224
|
+
<CardHeader>
|
|
225
|
+
<CardTitle>2. Check an existing artifact</CardTitle>
|
|
226
|
+
</CardHeader>
|
|
227
|
+
<CardContent className="space-y-3">
|
|
228
|
+
<label className="block space-y-2" htmlFor="context-file">
|
|
229
|
+
<span>Upload compiled Markdown</span>
|
|
230
|
+
<Input
|
|
231
|
+
accept=".md,text/markdown"
|
|
232
|
+
id="context-file"
|
|
233
|
+
onChange={(event) =>
|
|
234
|
+
void upload(event.target.files?.[0], "markdown")
|
|
235
|
+
}
|
|
236
|
+
type="file"
|
|
237
|
+
/>
|
|
238
|
+
</label>
|
|
239
|
+
<label className="block space-y-2" htmlFor="context-markdown">
|
|
240
|
+
<span>Or paste compiled Markdown</span>
|
|
241
|
+
<Textarea
|
|
242
|
+
className="h-32 min-w-0 field-sizing-fixed font-mono text-xs"
|
|
243
|
+
id="context-markdown"
|
|
244
|
+
onChange={(event) => {
|
|
245
|
+
invalidate();
|
|
246
|
+
setMarkdown(event.target.value);
|
|
247
|
+
}}
|
|
248
|
+
spellCheck={false}
|
|
249
|
+
value={markdown}
|
|
250
|
+
/>
|
|
251
|
+
</label>
|
|
252
|
+
<Button
|
|
253
|
+
onClick={() => void run("check")}
|
|
254
|
+
type="button"
|
|
255
|
+
variant="outline"
|
|
256
|
+
>
|
|
257
|
+
Check freshness
|
|
258
|
+
</Button>
|
|
259
|
+
</CardContent>
|
|
260
|
+
</Card>
|
|
261
|
+
</fieldset>
|
|
262
|
+
</form>
|
|
263
|
+
{error && (
|
|
264
|
+
<p className="break-words text-destructive" role="alert">
|
|
265
|
+
{error}
|
|
266
|
+
</p>
|
|
267
|
+
)}
|
|
268
|
+
{check && (
|
|
269
|
+
<Card>
|
|
270
|
+
<CardHeader>
|
|
271
|
+
<CardTitle>Artifact: {check.status}</CardTitle>
|
|
272
|
+
</CardHeader>
|
|
273
|
+
<CardContent aria-live="polite">
|
|
274
|
+
<ul className="list-inside list-disc">
|
|
275
|
+
{check.reasons.map((reason) => (
|
|
276
|
+
<li key={reason}>{reason}</li>
|
|
277
|
+
))}
|
|
278
|
+
</ul>
|
|
279
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
280
|
+
Check is read-only. Conflicts require inspecting manual edits;
|
|
281
|
+
stale evidence requires an explicit local refresh.
|
|
282
|
+
</p>
|
|
283
|
+
</CardContent>
|
|
284
|
+
</Card>
|
|
285
|
+
)}
|
|
286
|
+
{preview && (
|
|
287
|
+
<Card>
|
|
288
|
+
<CardHeader>
|
|
289
|
+
<CardTitle>Verified preview</CardTitle>
|
|
290
|
+
</CardHeader>
|
|
291
|
+
<CardContent className="min-w-0 space-y-4 break-words">
|
|
292
|
+
<p>
|
|
293
|
+
{preview.budget.usedTokens.toLocaleString()} tokens ·{" "}
|
|
294
|
+
{preview.budget.usedBytes.toLocaleString()} bytes ·{" "}
|
|
295
|
+
{preview.budget.estimator}
|
|
296
|
+
</p>
|
|
297
|
+
<p>
|
|
298
|
+
{preview.coverage.complete
|
|
299
|
+
? "Required facets covered"
|
|
300
|
+
: "Incomplete coverage"}
|
|
301
|
+
</p>
|
|
302
|
+
<p>
|
|
303
|
+
Covered facets:{" "}
|
|
304
|
+
{preview.coverage.coveredFacets.join(", ") || "None"}
|
|
305
|
+
</p>
|
|
306
|
+
<p>
|
|
307
|
+
Unresolved facets:{" "}
|
|
308
|
+
{preview.coverage.unresolvedFacets.join(", ") || "None"}
|
|
309
|
+
</p>
|
|
310
|
+
<details>
|
|
311
|
+
<summary className="cursor-pointer">
|
|
312
|
+
Evidence and verification
|
|
313
|
+
</summary>
|
|
314
|
+
<div className="space-y-2 break-all font-mono text-xs">
|
|
315
|
+
<p>Output digest: {preview.digest}</p>
|
|
316
|
+
<p>Verification digest: {preview.verificationDigest}</p>
|
|
317
|
+
<p>Evidence: {preview.evidenceIds.join(", ") || "None"}</p>
|
|
318
|
+
<ul>
|
|
319
|
+
{preview.omissions.map((item) => (
|
|
320
|
+
<li key={item.evidenceId}>
|
|
321
|
+
{item.evidenceId}: {item.reason}
|
|
322
|
+
</li>
|
|
323
|
+
))}
|
|
324
|
+
</ul>
|
|
325
|
+
</div>
|
|
326
|
+
</details>
|
|
327
|
+
<pre
|
|
328
|
+
aria-label="Compiled Markdown preview"
|
|
329
|
+
className="max-h-[32rem] overflow-auto whitespace-pre-wrap break-all rounded-md border bg-muted/30 p-4 font-mono text-xs"
|
|
330
|
+
>
|
|
331
|
+
{preview.markdown}
|
|
332
|
+
</pre>
|
|
333
|
+
<Button onClick={download}>
|
|
334
|
+
<DownloadIcon className="size-4" /> Download Markdown
|
|
335
|
+
</Button>
|
|
336
|
+
<p className="text-sm text-muted-foreground">
|
|
337
|
+
Download contains exactly the preview bytes. It does not create a
|
|
338
|
+
local refresh sidecar or update your agent instructions.
|
|
339
|
+
</p>
|
|
340
|
+
</CardContent>
|
|
341
|
+
</Card>
|
|
342
|
+
)}
|
|
343
|
+
<Card>
|
|
344
|
+
<CardHeader>
|
|
345
|
+
<CardTitle>Refresh local files explicitly</CardTitle>
|
|
346
|
+
</CardHeader>
|
|
347
|
+
<CardContent className="space-y-3">
|
|
348
|
+
<p className="text-sm text-muted-foreground">
|
|
349
|
+
For managed refresh, first compile locally with an explicit Capsule
|
|
350
|
+
file. Run gno update after source edits: checks use indexed state.
|
|
351
|
+
Choose a fresh Capsule output filename for each stale refresh.
|
|
352
|
+
Refresh preserves hand edits by reporting a conflict. Nothing
|
|
353
|
+
refreshes in the background.
|
|
354
|
+
</p>
|
|
355
|
+
<pre className="overflow-auto whitespace-pre-wrap break-all rounded-md bg-muted/30 p-4 text-xs">
|
|
356
|
+
{
|
|
357
|
+
"gno context compiled compile --capsule capsule.json --budget 12000 --output project.gno-context.md\ngno context compiled check project.gno-context.md\ngno context compiled refresh project.gno-context.md --capsule-output project.gno-context.capsule.json"
|
|
358
|
+
}
|
|
359
|
+
</pre>
|
|
360
|
+
</CardContent>
|
|
361
|
+
</Card>
|
|
362
|
+
</main>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
@@ -540,6 +540,13 @@ export default function Dashboard({ navigate }: PageProps) {
|
|
|
540
540
|
<HistoryIcon className="size-4" />
|
|
541
541
|
Trace history
|
|
542
542
|
</Button>
|
|
543
|
+
<Button
|
|
544
|
+
onClick={() => navigate("/context/compiled")}
|
|
545
|
+
size="lg"
|
|
546
|
+
variant="outline"
|
|
547
|
+
>
|
|
548
|
+
Compiled context
|
|
549
|
+
</Button>
|
|
543
550
|
</nav>
|
|
544
551
|
|
|
545
552
|
{error && (
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
useState,
|
|
30
30
|
} from "react";
|
|
31
31
|
|
|
32
|
+
import type { TypedMetadata } from "../../../core/typed-metadata";
|
|
32
33
|
import type { PdfFallbackReason } from "../lib/pdf";
|
|
33
34
|
|
|
34
35
|
import {
|
|
@@ -167,6 +168,8 @@ interface PageProps {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
interface DocData {
|
|
171
|
+
typedMetadata?: TypedMetadata | null;
|
|
172
|
+
metadataError?: string | null;
|
|
170
173
|
docid: string;
|
|
171
174
|
uri: string;
|
|
172
175
|
title: string | null;
|
|
@@ -1326,12 +1329,41 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1326
1329
|
</nav>
|
|
1327
1330
|
);
|
|
1328
1331
|
|
|
1332
|
+
const storedMetadata =
|
|
1333
|
+
doc?.metadataError ||
|
|
1334
|
+
(doc?.typedMetadata && Object.keys(doc.typedMetadata).length > 0) ? (
|
|
1335
|
+
<section
|
|
1336
|
+
aria-label="Indexed custom metadata"
|
|
1337
|
+
className="space-y-2 border-border/20 border-t px-3 py-3"
|
|
1338
|
+
>
|
|
1339
|
+
<h3 className="font-mono text-muted-foreground text-xs">
|
|
1340
|
+
Indexed custom metadata
|
|
1341
|
+
</h3>
|
|
1342
|
+
{doc.metadataError ? (
|
|
1343
|
+
<p className="break-words text-destructive text-xs" role="alert">
|
|
1344
|
+
{doc.metadataError}. Correct the source and sync again; this
|
|
1345
|
+
document is excluded from custom metadata filters.
|
|
1346
|
+
</p>
|
|
1347
|
+
) : (
|
|
1348
|
+
<dl className="space-y-1 text-xs">
|
|
1349
|
+
{Object.entries(doc.typedMetadata ?? {}).map(([key, value]) => (
|
|
1350
|
+
<div key={key}>
|
|
1351
|
+
<dt className="break-all text-muted-foreground">{key}</dt>
|
|
1352
|
+
<dd className="break-all font-mono">{JSON.stringify(value)}</dd>
|
|
1353
|
+
</div>
|
|
1354
|
+
))}
|
|
1355
|
+
</dl>
|
|
1356
|
+
)}
|
|
1357
|
+
</section>
|
|
1358
|
+
) : null;
|
|
1359
|
+
|
|
1329
1360
|
/** Left rail — metadata + outline */
|
|
1330
1361
|
const renderDocumentFactsRail = () => (
|
|
1331
1362
|
<nav
|
|
1332
1363
|
aria-label="Document facts"
|
|
1333
1364
|
className="w-full min-w-0 max-w-full space-y-0 overflow-x-hidden"
|
|
1334
1365
|
>
|
|
1366
|
+
{storedMetadata}
|
|
1335
1367
|
{/* Frontmatter + tags */}
|
|
1336
1368
|
{(hasFrontmatter || showStandaloneTags) && (
|
|
1337
1369
|
<>
|
|
@@ -1623,6 +1655,7 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1623
1655
|
</div>
|
|
1624
1656
|
</div>
|
|
1625
1657
|
|
|
1658
|
+
{storedMetadata}
|
|
1626
1659
|
{(hasFrontmatter || showStandaloneTags) && (
|
|
1627
1660
|
<div className="rounded-lg border border-border/40 bg-muted/10 p-2.5">
|
|
1628
1661
|
{hasFrontmatter && (
|