@gmickel/gno 2.4.0 → 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 +10 -2
- package/assets/skill/SKILL.md +23 -0
- package/assets/skill/cli-reference.md +17 -0
- package/assets/skill/examples.md +15 -0
- package/assets/skill/mcp-reference.md +10 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.4.0.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 +11 -0
- package/spec/compiled-context.md +68 -0
- package/spec/mcp.md +25 -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/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/audit.ts +4 -0
- package/src/cli/commands/context-compiled.ts +136 -0
- package/src/cli/errors.ts +10 -3
- package/src/cli/program.ts +70 -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/network-boundary-inventory.ts +8 -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 +2 -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/http-egress.ts +11 -3
- package/src/mcp/retrieval-warnings.ts +24 -0
- package/src/mcp/tools/ask.ts +14 -1
- package/src/mcp/tools/context.ts +46 -2
- package/src/mcp/tools/index.ts +53 -7
- package/src/mcp/tools/query.ts +3 -1
- package/src/mcp/tools/search.ts +3 -1
- package/src/mcp/tools/vsearch.ts +3 -1
- package/src/sdk/client.ts +72 -5
- package/src/sdk/index.ts +7 -0
- package/src/sdk/types.ts +28 -1
- package/src/serve/compiled-context.ts +84 -0
- package/src/serve/public/app.tsx +11 -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/CompiledContext.tsx +364 -0
- package/src/serve/public/pages/Dashboard.tsx +7 -0
- package/src/serve/server.ts +43 -2
- package/src/serve/spa-production-build.ts +6 -5
- package/src/store/sqlite/adapter.ts +14 -1
- package/browser-extension/artifacts/gno-browser-clipper-v2.4.0.zip.sha256 +0 -1
|
@@ -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 && (
|
package/src/serve/server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HttpGatewayOverrides } from "../mcp/http-security";
|
|
1
2
|
/**
|
|
2
3
|
* Bun.serve() web server for GNO web UI.
|
|
3
4
|
* Uses Bun's fullstack dev server with HTML imports.
|
|
@@ -5,8 +6,6 @@
|
|
|
5
6
|
*
|
|
6
7
|
* @module src/serve/server
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
-
import type { HttpGatewayOverrides } from "../mcp/http-security";
|
|
10
9
|
import type { RequestPeerServer } from "./request-locality";
|
|
11
10
|
import type { ResidentRuntime } from "./resident-runtime";
|
|
12
11
|
import type { ContextHolder } from "./routes/api";
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
resolveHttpGatewayConfig,
|
|
17
16
|
} from "../mcp/http-security";
|
|
18
17
|
import { startBackgroundRuntime } from "./background-runtime";
|
|
18
|
+
import { handleCompiledContext } from "./compiled-context";
|
|
19
19
|
import { handleContextBuild, handleContextVerify } from "./context-capsule";
|
|
20
20
|
import { DocumentEventBus } from "./doc-events";
|
|
21
21
|
import {
|
|
@@ -566,6 +566,7 @@ export async function startServer(
|
|
|
566
566
|
"/collections": spaPageRoute,
|
|
567
567
|
"/connectors": spaPageRoute,
|
|
568
568
|
"/traces": spaPageRoute,
|
|
569
|
+
"/context/compiled": spaPageRoute,
|
|
569
570
|
"/ask": spaPageRoute,
|
|
570
571
|
"/graph": spaPageRoute,
|
|
571
572
|
"/clipper/pair": spaPageRoute,
|
|
@@ -1164,6 +1165,46 @@ export async function startServer(
|
|
|
1164
1165
|
);
|
|
1165
1166
|
},
|
|
1166
1167
|
},
|
|
1168
|
+
"/api/context/compiled/preview": {
|
|
1169
|
+
POST: async (req: Request, server: RequestPeerServer) => {
|
|
1170
|
+
if (!isRequestAllowed(req, port))
|
|
1171
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
1172
|
+
return withSecurityHeaders(
|
|
1173
|
+
await handleResidentRead(
|
|
1174
|
+
runtime as ResidentRuntime,
|
|
1175
|
+
req,
|
|
1176
|
+
(signal) =>
|
|
1177
|
+
handleCompiledContext(
|
|
1178
|
+
ctxHolder.current,
|
|
1179
|
+
new Request(req, { signal }),
|
|
1180
|
+
{ requestIP: () => server.requestIP(req) },
|
|
1181
|
+
"preview"
|
|
1182
|
+
)
|
|
1183
|
+
),
|
|
1184
|
+
isDev
|
|
1185
|
+
);
|
|
1186
|
+
},
|
|
1187
|
+
},
|
|
1188
|
+
"/api/context/compiled/check": {
|
|
1189
|
+
POST: async (req: Request, server: RequestPeerServer) => {
|
|
1190
|
+
if (!isRequestAllowed(req, port))
|
|
1191
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
1192
|
+
return withSecurityHeaders(
|
|
1193
|
+
await handleResidentRead(
|
|
1194
|
+
runtime as ResidentRuntime,
|
|
1195
|
+
req,
|
|
1196
|
+
(signal) =>
|
|
1197
|
+
handleCompiledContext(
|
|
1198
|
+
ctxHolder.current,
|
|
1199
|
+
new Request(req, { signal }),
|
|
1200
|
+
{ requestIP: () => server.requestIP(req) },
|
|
1201
|
+
"check"
|
|
1202
|
+
)
|
|
1203
|
+
),
|
|
1204
|
+
isDev
|
|
1205
|
+
);
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1167
1208
|
"/api/context/verify": {
|
|
1168
1209
|
POST: async (req: Request) => {
|
|
1169
1210
|
if (!isRequestAllowed(req, port)) {
|
|
@@ -79,12 +79,13 @@ const contentTypeFor = (path: string): string => {
|
|
|
79
79
|
return "application/octet-stream";
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
export const
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
export const isBunfsPath = (path: string): boolean => {
|
|
83
|
+
const normalized = path.replaceAll("\\", "/");
|
|
84
|
+
return normalized.includes("/$bunfs/") || normalized.startsWith("B:/~BUN/");
|
|
85
|
+
};
|
|
85
86
|
|
|
86
|
-
export const
|
|
87
|
-
|
|
87
|
+
export const isStandaloneExecutable = (): boolean =>
|
|
88
|
+
isBunfsPath(import.meta.path);
|
|
88
89
|
|
|
89
90
|
const rewriteProductionHtml = (html: string, jsEntryPath: string): string => {
|
|
90
91
|
const script = `<script type="module" src="/${basename(jsEntryPath)}"></script>`;
|
|
@@ -669,7 +669,20 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
669
669
|
async close(): Promise<void> {
|
|
670
670
|
this.fenceForShutdown();
|
|
671
671
|
if (this.db) {
|
|
672
|
-
|
|
672
|
+
try {
|
|
673
|
+
// Current Bun finalizes all statements and releases Windows handles now.
|
|
674
|
+
this.db.close(true);
|
|
675
|
+
} catch (cause) {
|
|
676
|
+
// Older Bun only finalizes cached statements. Its native SQLITE_BUSY
|
|
677
|
+
// error has no code field; retain deferred close for that exact case.
|
|
678
|
+
if (
|
|
679
|
+
!(cause instanceof Error) ||
|
|
680
|
+
cause.message !== "database is locked"
|
|
681
|
+
) {
|
|
682
|
+
throw cause;
|
|
683
|
+
}
|
|
684
|
+
this.db.close(false);
|
|
685
|
+
}
|
|
673
686
|
this.db = null;
|
|
674
687
|
}
|
|
675
688
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ae739d8da342a7f932f8097e1d4d852ff7c46b722b8e979303b1213eaf2d76a5 gno-browser-clipper-v2.4.0.zip
|