@gmickel/gno 2.4.0 → 2.6.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 +69 -6
- package/assets/skill/README.md +5 -1
- package/assets/skill/SKILL.md +98 -1
- package/assets/skill/cli-reference.md +140 -0
- package/assets/skill/examples.md +45 -0
- package/assets/skill/mcp-reference.md +62 -0
- package/assets/skill/recipes/capture-and-file.md +6 -0
- package/assets/skill/recipes/memory-file-decision.md +6 -0
- package/assets/skill/recipes/memory-supersede-fact.md +5 -0
- package/assets/skill/recipes/session-evidence-lookup.md +98 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.4.0.zip → gno-browser-clipper-v2.6.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +358 -24
- package/spec/compiled-context.md +68 -0
- package/spec/mcp.md +223 -2
- package/spec/output-schemas/capture-receipt.schema.json +3 -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/mcp-capture-result.schema.json +3 -0
- package/spec/output-schemas/memory-remember.schema.json +8 -2
- package/spec/output-schemas/request-status.schema.json +113 -0
- package/spec/output-schemas/sessions-automation-run.schema.json +46 -0
- package/spec/output-schemas/sessions-discovery.schema.json +38 -0
- package/spec/output-schemas/sessions-import-receipt.schema.json +156 -0
- package/spec/output-schemas/sessions-status.schema.json +432 -0
- package/src/app/compiled-context-files.ts +361 -0
- package/src/app/compiled-context.ts +240 -0
- package/src/app/context-surface.ts +7 -2
- package/src/cli/commands/ask.ts +14 -2
- package/src/cli/commands/audit.ts +4 -0
- package/src/cli/commands/capture.ts +55 -96
- package/src/cli/commands/context-compiled.ts +136 -0
- package/src/cli/commands/daemon.ts +41 -0
- package/src/cli/commands/ls.ts +3 -0
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/request-status.ts +59 -0
- package/src/cli/commands/reset.ts +39 -5
- package/src/cli/commands/sessions.ts +713 -0
- package/src/cli/commands/shared.ts +14 -1
- package/src/cli/errors.ts +10 -3
- package/src/cli/program.ts +458 -1
- package/src/cli/session-binding.ts +49 -0
- package/src/config/types.ts +8 -0
- package/src/core/capture-publish.ts +239 -0
- package/src/core/capture-sync.ts +3 -0
- package/src/core/compiled-context.ts +254 -0
- package/src/core/context-budget.ts +2 -10
- package/src/core/file-lock.ts +22 -5
- package/src/core/folder-setup-planning.ts +2 -1
- package/src/core/memory-remember.ts +233 -122
- package/src/core/memory-types.ts +11 -0
- package/src/core/network-boundary-inventory.ts +16 -0
- package/src/core/request-receipts.ts +671 -0
- package/src/core/setup-receipt.ts +27 -20
- package/src/core/typed-metadata.ts +4 -0
- package/src/core/validation.ts +9 -2
- package/src/core/windows-private-path.ts +96 -0
- package/src/index.ts +11 -2
- package/src/ingestion/compiled-context.ts +15 -0
- package/src/ingestion/sync.ts +40 -8
- package/src/ingestion/walker.ts +5 -4
- package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
- package/src/mcp/context.ts +8 -0
- package/src/mcp/http-egress.ts +15 -3
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/retrieval-warnings.ts +24 -0
- package/src/mcp/tools/ask.ts +14 -1
- package/src/mcp/tools/capture.ts +87 -83
- package/src/mcp/tools/context.ts +46 -2
- package/src/mcp/tools/index.ts +119 -7
- package/src/mcp/tools/memory-remember.ts +7 -0
- package/src/mcp/tools/memory-shared.ts +7 -1
- package/src/mcp/tools/query.ts +3 -1
- package/src/mcp/tools/request-status.ts +73 -0
- package/src/mcp/tools/search.ts +3 -1
- package/src/mcp/tools/sessions.ts +208 -0
- package/src/mcp/tools/vsearch.ts +3 -1
- package/src/sdk/client.ts +252 -89
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +82 -3
- package/src/serve/capture-service.ts +98 -32
- package/src/serve/compiled-context.ts +84 -0
- package/src/serve/config-sync.ts +3 -2
- package/src/serve/public/app.tsx +15 -1
- package/src/serve/public/components/CaptureModal.tsx +26 -8
- package/src/serve/public/components/sessions/AutomationPanel.tsx +800 -0
- package/src/serve/public/components/sessions/ImportReceipt.tsx +238 -0
- package/src/serve/public/components/sessions/SessionSearch.tsx +286 -0
- package/src/serve/public/components/sessions/SourcesPanel.tsx +541 -0
- package/src/serve/public/components/sessions/api.ts +40 -0
- package/src/serve/public/components/sessions/snippet.tsx +53 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +10 -2
- package/src/serve/public/lib/request-intent.ts +69 -0
- package/src/serve/public/lib/workspace-actions.ts +12 -1
- package/src/serve/public/lib/workspace-tabs.ts +4 -0
- package/src/serve/public/pages/CompiledContext.tsx +364 -0
- package/src/serve/public/pages/Dashboard.tsx +17 -0
- package/src/serve/public/pages/DocView.tsx +15 -1
- package/src/serve/public/pages/DocumentEditor.tsx +139 -96
- package/src/serve/public/pages/Sessions.tsx +350 -0
- package/src/serve/resident-runtime.ts +43 -3
- package/src/serve/routes/api.ts +476 -147
- package/src/serve/routes/sessions.ts +766 -0
- package/src/serve/security.ts +9 -0
- package/src/serve/server.ts +248 -3
- package/src/serve/session-automation.ts +146 -0
- package/src/serve/spa-production-build.ts +6 -5
- package/src/sessions/archive.ts +348 -0
- package/src/sessions/automation-state.ts +444 -0
- package/src/sessions/automation-status.ts +239 -0
- package/src/sessions/automation.ts +1169 -0
- package/src/sessions/binding.ts +105 -0
- package/src/sessions/claude-hook.ts +240 -0
- package/src/sessions/config.ts +176 -0
- package/src/sessions/format.ts +191 -0
- package/src/sessions/import-child-env.ts +8 -0
- package/src/sessions/import-child.ts +152 -0
- package/src/sessions/parsers/claude-code.ts +259 -0
- package/src/sessions/parsers/codex.ts +303 -0
- package/src/sessions/parsers/hermes.ts +248 -0
- package/src/sessions/parsers/openclaw.ts +496 -0
- package/src/sessions/parsers/shared.ts +184 -0
- package/src/sessions/sanitize.ts +222 -0
- package/src/sessions/service.ts +1533 -0
- package/src/sessions/setup.ts +477 -0
- package/src/sessions/sources.ts +518 -0
- package/src/sessions/state.ts +118 -0
- package/src/sessions/types.ts +457 -0
- package/src/store/sqlite/adapter.ts +68 -16
- package/src/store/sqlite/scoped-index.ts +9 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.4.0.zip.sha256 +0 -1
|
@@ -81,7 +81,12 @@ export function useApi<T>() {
|
|
|
81
81
|
export async function apiFetch<T>(
|
|
82
82
|
endpoint: string,
|
|
83
83
|
options?: RequestInit
|
|
84
|
-
): Promise<{
|
|
84
|
+
): Promise<{
|
|
85
|
+
data: T | null;
|
|
86
|
+
error: string | null;
|
|
87
|
+
/** `error.details` of the JSON error envelope, when the server sent one. */
|
|
88
|
+
details?: Record<string, unknown>;
|
|
89
|
+
}> {
|
|
85
90
|
try {
|
|
86
91
|
const res = await fetch(endpoint, {
|
|
87
92
|
headers: { "Content-Type": "application/json" },
|
|
@@ -95,10 +100,13 @@ export async function apiFetch<T>(
|
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
if (!res.ok) {
|
|
98
|
-
const apiError = json as {
|
|
103
|
+
const apiError = json as {
|
|
104
|
+
error?: { message?: string; details?: Record<string, unknown> };
|
|
105
|
+
};
|
|
99
106
|
return {
|
|
100
107
|
data: null,
|
|
101
108
|
error: apiError.error?.message || `Request failed: ${res.status}`,
|
|
109
|
+
details: apiError.error?.details,
|
|
102
110
|
};
|
|
103
111
|
}
|
|
104
112
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One request ID per submitted write intent.
|
|
3
|
+
*
|
|
4
|
+
* Retrying the same payload (after a lost response, an error, or a page
|
|
5
|
+
* refresh) reuses the ID, so the server replays the committed outcome instead
|
|
6
|
+
* of writing twice. A changed payload is a new intent and gets a new ID. The
|
|
7
|
+
* unresolved intent is kept in sessionStorage so it survives a refresh of
|
|
8
|
+
* this tab; it is cleared once the write is confirmed.
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestIntent {
|
|
11
|
+
key: string;
|
|
12
|
+
requestId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Compact payload fingerprint (cyrb53); a collision only yields a conflict error. */
|
|
16
|
+
function fingerprint(text: string): string {
|
|
17
|
+
let h1 = 0xde_ad_be_ef;
|
|
18
|
+
let h2 = 0x41_c6_ce_57;
|
|
19
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
20
|
+
const code = text.charCodeAt(index);
|
|
21
|
+
h1 = Math.imul(h1 ^ code, 2_654_435_761);
|
|
22
|
+
h2 = Math.imul(h2 ^ code, 1_597_334_677);
|
|
23
|
+
}
|
|
24
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2_246_822_507);
|
|
25
|
+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3_266_489_909);
|
|
26
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2_246_822_507);
|
|
27
|
+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3_266_489_909);
|
|
28
|
+
return (4_294_967_296 * (2_097_151 & h2) + (h1 >>> 0)).toString(36);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readStored(storageKey: string): RequestIntent | null {
|
|
32
|
+
try {
|
|
33
|
+
const raw = sessionStorage.getItem(storageKey);
|
|
34
|
+
return raw ? (JSON.parse(raw) as RequestIntent) : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function requestIdForIntent(
|
|
41
|
+
slot: { current: RequestIntent | null },
|
|
42
|
+
payload: string,
|
|
43
|
+
storageKey: string
|
|
44
|
+
): string {
|
|
45
|
+
const key = fingerprint(payload);
|
|
46
|
+
slot.current ??= readStored(storageKey);
|
|
47
|
+
if (slot.current?.key !== key) {
|
|
48
|
+
slot.current = { key, requestId: crypto.randomUUID() };
|
|
49
|
+
try {
|
|
50
|
+
sessionStorage.setItem(storageKey, JSON.stringify(slot.current));
|
|
51
|
+
} catch {
|
|
52
|
+
// Storage unavailable: the ID still covers retries in this page.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return slot.current.requestId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The intent's write is confirmed: the next submit is a new intent. */
|
|
59
|
+
export function clearRequestIntent(
|
|
60
|
+
slot: { current: RequestIntent | null },
|
|
61
|
+
storageKey: string
|
|
62
|
+
): void {
|
|
63
|
+
slot.current = null;
|
|
64
|
+
try {
|
|
65
|
+
sessionStorage.removeItem(storageKey);
|
|
66
|
+
} catch {
|
|
67
|
+
// Nothing persisted to clear.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -14,7 +14,8 @@ export type WorkspaceActionId =
|
|
|
14
14
|
| "go-ask"
|
|
15
15
|
| "go-graph"
|
|
16
16
|
| "go-collections"
|
|
17
|
-
| "go-connectors"
|
|
17
|
+
| "go-connectors"
|
|
18
|
+
| "go-sessions";
|
|
18
19
|
|
|
19
20
|
export interface WorkspaceAction {
|
|
20
21
|
id: WorkspaceActionId;
|
|
@@ -155,6 +156,13 @@ export function getWorkspaceActions(
|
|
|
155
156
|
keywords: ["connectors", "mcp", "skills", "agents"],
|
|
156
157
|
available: true,
|
|
157
158
|
},
|
|
159
|
+
{
|
|
160
|
+
id: "go-sessions",
|
|
161
|
+
group: "Go To",
|
|
162
|
+
label: "Agent sessions",
|
|
163
|
+
keywords: ["sessions", "archive", "transcripts", "import", "agents"],
|
|
164
|
+
available: true,
|
|
165
|
+
},
|
|
158
166
|
];
|
|
159
167
|
}
|
|
160
168
|
|
|
@@ -220,6 +228,9 @@ export function runWorkspaceAction(
|
|
|
220
228
|
case "go-connectors":
|
|
221
229
|
handlers.navigate("/connectors");
|
|
222
230
|
break;
|
|
231
|
+
case "go-sessions":
|
|
232
|
+
handlers.navigate("/sessions");
|
|
233
|
+
break;
|
|
223
234
|
}
|
|
224
235
|
|
|
225
236
|
handlers.closePalette();
|
|
@@ -65,8 +65,12 @@ function getLocationLabel(location: string): string {
|
|
|
65
65
|
return "Connectors";
|
|
66
66
|
case "/graph":
|
|
67
67
|
return "Graph";
|
|
68
|
+
case "/context/compiled":
|
|
69
|
+
return "Compiled context";
|
|
68
70
|
case "/traces":
|
|
69
71
|
return "Trace history";
|
|
72
|
+
case "/sessions":
|
|
73
|
+
return "Agent sessions";
|
|
70
74
|
case "/doc":
|
|
71
75
|
case "/edit": {
|
|
72
76
|
const uri = params.get("uri") ?? "";
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArchiveIcon,
|
|
2
3
|
BookOpen,
|
|
3
4
|
CheckCircle2Icon,
|
|
4
5
|
CpuIcon,
|
|
@@ -540,6 +541,22 @@ export default function Dashboard({ navigate }: PageProps) {
|
|
|
540
541
|
<HistoryIcon className="size-4" />
|
|
541
542
|
Trace history
|
|
542
543
|
</Button>
|
|
544
|
+
<Button
|
|
545
|
+
className="gap-2"
|
|
546
|
+
onClick={() => navigate("/sessions")}
|
|
547
|
+
size="lg"
|
|
548
|
+
variant="outline"
|
|
549
|
+
>
|
|
550
|
+
<ArchiveIcon className="size-4" />
|
|
551
|
+
Agent sessions
|
|
552
|
+
</Button>
|
|
553
|
+
<Button
|
|
554
|
+
onClick={() => navigate("/context/compiled")}
|
|
555
|
+
size="lg"
|
|
556
|
+
variant="outline"
|
|
557
|
+
>
|
|
558
|
+
Compiled context
|
|
559
|
+
</Button>
|
|
543
560
|
</nav>
|
|
544
561
|
|
|
545
562
|
{error && (
|
|
@@ -83,6 +83,13 @@ import {
|
|
|
83
83
|
isPdfDocument,
|
|
84
84
|
} from "../lib/doc-asset-url";
|
|
85
85
|
import { waitForDocumentAvailability } from "../lib/document-availability";
|
|
86
|
+
import {
|
|
87
|
+
clearRequestIntent,
|
|
88
|
+
type RequestIntent,
|
|
89
|
+
requestIdForIntent,
|
|
90
|
+
} from "../lib/request-intent";
|
|
91
|
+
|
|
92
|
+
const TAG_INTENT_KEY = "gno:tag-intent";
|
|
86
93
|
import {
|
|
87
94
|
buildReadableSectionUrl,
|
|
88
95
|
createCitationSectionUrl,
|
|
@@ -1201,7 +1208,8 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1201
1208
|
setTagSaveError(null);
|
|
1202
1209
|
}, []);
|
|
1203
1210
|
|
|
1204
|
-
// Save tags
|
|
1211
|
+
// Save tags (one request ID per tag-set intent, reused on retry)
|
|
1212
|
+
const tagIntentRef = useRef<RequestIntent | null>(null);
|
|
1205
1213
|
const handleSaveTags = useCallback(async () => {
|
|
1206
1214
|
if (!doc) return;
|
|
1207
1215
|
|
|
@@ -1218,6 +1226,11 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1218
1226
|
expectedSourceHash: doc.source.sourceHash,
|
|
1219
1227
|
expectedModifiedAt: doc.source.modifiedAt,
|
|
1220
1228
|
uri: doc.uri,
|
|
1229
|
+
requestId: requestIdForIntent(
|
|
1230
|
+
tagIntentRef,
|
|
1231
|
+
`${doc.uri}\u0000${doc.source.sourceHash}\u0000${editedTags.join(",")}`,
|
|
1232
|
+
TAG_INTENT_KEY
|
|
1233
|
+
),
|
|
1221
1234
|
}),
|
|
1222
1235
|
}
|
|
1223
1236
|
);
|
|
@@ -1229,6 +1242,7 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1229
1242
|
return;
|
|
1230
1243
|
}
|
|
1231
1244
|
|
|
1245
|
+
clearRequestIntent(tagIntentRef, TAG_INTENT_KEY);
|
|
1232
1246
|
// Update doc with new tags
|
|
1233
1247
|
setDoc({
|
|
1234
1248
|
...doc,
|