@gmickel/gno 2.7.0 → 2.8.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 +26 -30
- package/assets/skill/SKILL.md +8 -1
- package/assets/skill/cli-reference.md +8 -1
- package/assets/skill/examples.md +2 -1
- package/assets/skill/mcp-reference.md +3 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.7.0.zip → gno-browser-clipper-v2.8.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.8.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +48 -6
- package/spec/db/schema.sql +0 -1
- package/spec/mcp.md +18 -3
- package/spec/output-schemas/audit-report.schema.json +18 -4
- package/spec/output-schemas/backlinks.schema.json +4 -0
- package/spec/output-schemas/collection-list.schema.json +15 -2
- package/spec/output-schemas/graph.schema.json +2 -0
- package/spec/output-schemas/links-list.schema.json +4 -0
- package/spec/output-schemas/status.schema.json +27 -16
- package/src/cli/commands/ask.ts +31 -12
- package/src/cli/commands/audit.ts +23 -4
- package/src/cli/commands/collection/list.ts +39 -5
- package/src/cli/commands/embed.ts +3 -3
- package/src/cli/commands/links.ts +34 -131
- package/src/cli/commands/ls.ts +6 -1
- package/src/cli/commands/shared.ts +7 -0
- package/src/cli/commands/status.ts +5 -0
- package/src/cli/program.ts +12 -2
- package/src/config/loader.ts +43 -0
- package/src/config/types.ts +8 -0
- package/src/core/audit-contract.ts +16 -4
- package/src/core/audit-freshness.ts +11 -1
- package/src/core/audit-links.ts +145 -25
- package/src/core/audit-provenance.ts +11 -4
- package/src/core/audit-workspace.ts +19 -4
- package/src/core/audit.ts +67 -15
- package/src/core/context-compiler.ts +3 -0
- package/src/core/context-evidence.ts +11 -0
- package/src/core/graph-edge-confidence.ts +23 -1
- package/src/core/host-paths.ts +24 -5
- package/src/core/knowledge-impact.ts +28 -0
- package/src/core/link-workspace.ts +324 -0
- package/src/core/request-receipts.ts +63 -9
- package/src/core/retrieval-replay-candidate.ts +6 -0
- package/src/core/retrieval-trace-request.ts +3 -0
- package/src/core/windows-private-path.ts +136 -1
- package/src/embed/backlog.ts +22 -10
- package/src/embed/variant-backlog.ts +34 -9
- package/src/index.ts +14 -1
- package/src/ingestion/graph-reconciliation.ts +77 -15
- package/src/ingestion/source-availability/darwin-path.ts +9 -3
- package/src/ingestion/sync.ts +22 -1
- package/src/ingestion/types.ts +14 -0
- package/src/llm/inference-scope.ts +19 -0
- package/src/mcp/http-egress.ts +42 -3
- package/src/mcp/tools/audit.ts +11 -2
- package/src/mcp/tools/changes.ts +1 -0
- package/src/mcp/tools/links.ts +3 -0
- package/src/mcp/tools/sessions.ts +33 -4
- package/src/mcp/tools/status.ts +22 -6
- package/src/pipeline/expansion.ts +19 -31
- package/src/pipeline/graph-retrieval.ts +22 -2
- package/src/pipeline/hybrid.ts +1 -1
- package/src/pipeline/types.ts +6 -3
- package/src/serve/embed-scheduler.ts +2 -2
- package/src/serve/findings-pass.ts +1 -1
- package/src/serve/host-path-redaction.ts +51 -14
- package/src/serve/public/components/BootstrapStatus.tsx +5 -3
- package/src/serve/public/components/CaptureModal.tsx +1 -1
- package/src/serve/public/components/CollectionModelDialog.tsx +16 -13
- package/src/serve/public/components/CollectionsEmptyState.tsx +5 -3
- package/src/serve/public/components/FirstRunWizard.tsx +4 -2
- package/src/serve/public/components/sessions/SourcesPanel.tsx +77 -60
- package/src/serve/public/pages/Collections.tsx +16 -13
- package/src/serve/public/pages/Connectors.tsx +7 -4
- package/src/serve/public/pages/Dashboard.tsx +8 -6
- package/src/serve/public/pages/GraphView.tsx +2 -0
- package/src/serve/resident-runtime.ts +7 -0
- package/src/serve/routes/changes.ts +6 -1
- package/src/serve/routes/links.ts +13 -0
- package/src/serve/routes/sessions.ts +81 -37
- package/src/serve/server.ts +5 -3
- package/src/serve/status-model.ts +10 -6
- package/src/serve/status.ts +2 -7
- package/src/sessions/config-refresh.ts +111 -0
- package/src/store/migrations/033-drop-documents-active-index.ts +30 -0
- package/src/store/migrations/034-collection-link-workspace.ts +47 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +392 -233
- package/src/store/sqlite/change-journal-store.ts +1 -1
- package/src/store/sqlite/eligibility.ts +8 -2
- package/src/store/sqlite/graph-link-resolver.ts +252 -5
- package/src/store/sqlite/graph-neighbors.ts +147 -40
- package/src/store/sqlite/graph-reference-state.ts +13 -2
- package/src/store/sqlite/legacy-vector-ownership.ts +2 -1
- package/src/store/sqlite/workspace-link-resolver.ts +654 -0
- package/src/store/types.ts +49 -3
- package/src/store/vector/stats.ts +1 -1
- package/browser-extension/artifacts/gno-browser-clipper-v2.7.0.zip.sha256 +0 -1
|
@@ -394,8 +394,12 @@ export function SourcesPanel({
|
|
|
394
394
|
const [notice, setNotice] = useState<{ text: string } | null>(null);
|
|
395
395
|
const noticeRef = useRef<HTMLParagraphElement>(null);
|
|
396
396
|
useFocusOnChange(notice, noticeRef);
|
|
397
|
+
// Bumped when the Discover button's run finishes: focus moves to its outcome.
|
|
398
|
+
const [discoveredCount, setDiscoveredCount] = useState(0);
|
|
399
|
+
const discoveryRef = useRef<HTMLDivElement>(null);
|
|
400
|
+
useFocusOnChange(discoveredCount, discoveryRef);
|
|
397
401
|
|
|
398
|
-
const discover = async () => {
|
|
402
|
+
const discover = async (focusOutcome = false) => {
|
|
399
403
|
setDiscovering(true);
|
|
400
404
|
const result = await sessionsApi<SessionsDiscovery>(
|
|
401
405
|
"/api/sessions/discover"
|
|
@@ -403,6 +407,7 @@ export function SourcesPanel({
|
|
|
403
407
|
setDiscovering(false);
|
|
404
408
|
setDiscoverError(result.error);
|
|
405
409
|
setDiscovery(result.data);
|
|
410
|
+
if (focusOutcome) setDiscoveredCount((count) => count + 1);
|
|
406
411
|
};
|
|
407
412
|
|
|
408
413
|
const registered = async (sourceId: string, collection: string) => {
|
|
@@ -425,7 +430,7 @@ export function SourcesPanel({
|
|
|
425
430
|
{localClient && (
|
|
426
431
|
<Button
|
|
427
432
|
disabled={discovering}
|
|
428
|
-
onClick={() => void discover()}
|
|
433
|
+
onClick={() => void discover(true)}
|
|
429
434
|
size="sm"
|
|
430
435
|
variant="outline"
|
|
431
436
|
>
|
|
@@ -473,67 +478,79 @@ export function SourcesPanel({
|
|
|
473
478
|
</p>
|
|
474
479
|
)}
|
|
475
480
|
|
|
476
|
-
{discoverError && (
|
|
477
|
-
<
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
</p>
|
|
488
|
-
{discovery.candidates.length === 0 && (
|
|
489
|
-
<p className="text-muted-foreground text-sm">
|
|
490
|
-
No supported session stores were found.
|
|
481
|
+
{(discoverError || discovery) && (
|
|
482
|
+
<div
|
|
483
|
+
aria-label="Discovery results"
|
|
484
|
+
className={`space-y-3 ${FOCUS_RING}`}
|
|
485
|
+
ref={discoveryRef}
|
|
486
|
+
role="region"
|
|
487
|
+
tabIndex={-1}
|
|
488
|
+
>
|
|
489
|
+
{discoverError && (
|
|
490
|
+
<p className="break-words text-destructive text-sm" role="alert">
|
|
491
|
+
{discoverError}
|
|
491
492
|
</p>
|
|
492
493
|
)}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
<span className="min-w-0 break-all font-mono text-xs">
|
|
504
|
-
{candidate.path}
|
|
505
|
-
</span>
|
|
506
|
-
</div>
|
|
507
|
-
<p className="text-muted-foreground text-xs">
|
|
508
|
-
{candidate.units}
|
|
509
|
-
{candidate.truncated ? "+" : ""} units ·{" "}
|
|
510
|
-
{formatBytes(candidate.bytes)}
|
|
511
|
-
{candidate.formatVersions.length > 0 &&
|
|
512
|
-
` · format ${candidate.formatVersions.join(", ")}`}
|
|
494
|
+
{discovery && (
|
|
495
|
+
<div className="space-y-3 rounded-lg border border-dashed border-border/70 p-4">
|
|
496
|
+
<h3 className="font-medium">Discovered on this machine</h3>
|
|
497
|
+
<p className="text-muted-foreground text-xs">
|
|
498
|
+
Preview only: discovery reads nothing into the archive. Register
|
|
499
|
+
a source to permit manual imports from it.
|
|
500
|
+
</p>
|
|
501
|
+
{discovery.candidates.length === 0 && (
|
|
502
|
+
<p className="text-muted-foreground text-sm">
|
|
503
|
+
No supported session stores were found.
|
|
513
504
|
</p>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
505
|
+
)}
|
|
506
|
+
<ul className="space-y-4">
|
|
507
|
+
{discovery.candidates.map((candidate) => (
|
|
508
|
+
<li
|
|
509
|
+
className="min-w-0 space-y-2"
|
|
510
|
+
key={`${candidate.harness}:${candidate.path}`}
|
|
511
|
+
>
|
|
512
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
513
|
+
<Badge variant="secondary">
|
|
514
|
+
{SESSION_HARNESS_LABELS[candidate.harness]}
|
|
515
|
+
</Badge>
|
|
516
|
+
<span className="min-w-0 break-all font-mono text-xs">
|
|
517
|
+
{candidate.path}
|
|
518
|
+
</span>
|
|
519
|
+
</div>
|
|
520
|
+
<p className="text-muted-foreground text-xs">
|
|
521
|
+
{candidate.units}
|
|
522
|
+
{candidate.truncated ? "+" : ""} units ·{" "}
|
|
523
|
+
{formatBytes(candidate.bytes)}
|
|
524
|
+
{candidate.formatVersions.length > 0 &&
|
|
525
|
+
` · format ${candidate.formatVersions.join(", ")}`}
|
|
526
|
+
</p>
|
|
527
|
+
{candidate.registeredAs ? (
|
|
528
|
+
<p className="text-sm">
|
|
529
|
+
Registered as{" "}
|
|
530
|
+
<span className="font-mono">
|
|
531
|
+
{candidate.registeredAs}
|
|
532
|
+
</span>
|
|
533
|
+
</p>
|
|
534
|
+
) : (
|
|
535
|
+
<RegisterForm
|
|
536
|
+
candidate={candidate}
|
|
537
|
+
archiveCollections={archiveCollections}
|
|
538
|
+
onRegistered={registered}
|
|
539
|
+
/>
|
|
540
|
+
)}
|
|
541
|
+
</li>
|
|
542
|
+
))}
|
|
543
|
+
</ul>
|
|
544
|
+
{discovery.warnings.map((warning) => (
|
|
545
|
+
<p
|
|
546
|
+
className="text-amber-800 text-xs dark:text-amber-200"
|
|
547
|
+
key={warning}
|
|
548
|
+
>
|
|
549
|
+
{warning}
|
|
550
|
+
</p>
|
|
551
|
+
))}
|
|
552
|
+
</div>
|
|
553
|
+
)}
|
|
537
554
|
</div>
|
|
538
555
|
)}
|
|
539
556
|
</section>
|
|
@@ -76,7 +76,8 @@ interface PageProps {
|
|
|
76
76
|
interface CollectionStats {
|
|
77
77
|
activePresetId?: string;
|
|
78
78
|
name: string;
|
|
79
|
-
path
|
|
79
|
+
/** Host path; omitted for remote callers. */
|
|
80
|
+
path?: string;
|
|
80
81
|
documentCount: number;
|
|
81
82
|
chunkCount: number;
|
|
82
83
|
embeddedCount: number;
|
|
@@ -262,18 +263,20 @@ function CollectionCard({
|
|
|
262
263
|
</DropdownMenu>
|
|
263
264
|
</div>
|
|
264
265
|
|
|
265
|
-
|
|
266
|
-
<
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
266
|
+
{collection.path && (
|
|
267
|
+
<TooltipProvider>
|
|
268
|
+
<Tooltip>
|
|
269
|
+
<TooltipTrigger asChild>
|
|
270
|
+
<p className="truncate font-mono text-muted-foreground text-xs">
|
|
271
|
+
{truncatePath(collection.path)}
|
|
272
|
+
</p>
|
|
273
|
+
</TooltipTrigger>
|
|
274
|
+
<TooltipContent className="max-w-xs break-all">
|
|
275
|
+
<p className="font-mono text-xs">{collection.path}</p>
|
|
276
|
+
</TooltipContent>
|
|
277
|
+
</Tooltip>
|
|
278
|
+
</TooltipProvider>
|
|
279
|
+
)}
|
|
277
280
|
</CardHeader>
|
|
278
281
|
|
|
279
282
|
<CardContent className="pt-2">
|
|
@@ -30,7 +30,8 @@ interface ConnectorStatus {
|
|
|
30
30
|
target: string;
|
|
31
31
|
scope: "user" | "project";
|
|
32
32
|
installed: boolean;
|
|
33
|
-
path
|
|
33
|
+
/** Host path; omitted for remote callers. */
|
|
34
|
+
path?: string;
|
|
34
35
|
summary: string;
|
|
35
36
|
nextAction: string;
|
|
36
37
|
mode: {
|
|
@@ -234,9 +235,11 @@ export default function Connectors({ navigate }: PageProps) {
|
|
|
234
235
|
<span className="font-medium">Scope:</span>{" "}
|
|
235
236
|
{connector.scope}
|
|
236
237
|
</div>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
{connector.path && (
|
|
239
|
+
<div className="font-mono text-muted-foreground text-xs">
|
|
240
|
+
{connector.path}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
240
243
|
</div>
|
|
241
244
|
{connector.error && (
|
|
242
245
|
<p className="text-destructive text-sm">
|
|
@@ -801,12 +801,14 @@ export default function Dashboard({ navigate }: PageProps) {
|
|
|
801
801
|
<div className="truncate font-medium text-lg transition-colors group-hover:text-primary">
|
|
802
802
|
{collection.name}
|
|
803
803
|
</div>
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
804
|
+
{collection.path && (
|
|
805
|
+
<div
|
|
806
|
+
className="truncate font-mono text-muted-foreground text-sm"
|
|
807
|
+
title={collection.path}
|
|
808
|
+
>
|
|
809
|
+
{collection.path}
|
|
810
|
+
</div>
|
|
811
|
+
)}
|
|
810
812
|
</div>
|
|
811
813
|
</div>
|
|
812
814
|
<div className="flex shrink-0 items-center gap-3 text-right">
|
|
@@ -377,6 +377,13 @@ export async function startResidentRuntime(
|
|
|
377
377
|
acquireWriteLease: leaseFor(`gno ${mode} (watch sync)`),
|
|
378
378
|
});
|
|
379
379
|
watchService.start();
|
|
380
|
+
// A backlog left by `--no-embed` or an earlier run gets a pass without
|
|
381
|
+
// waiting for a file change.
|
|
382
|
+
const startupStatus = await store.getStatus({
|
|
383
|
+
embedModel: getActivePreset(initialConfig).embed,
|
|
384
|
+
});
|
|
385
|
+
if (startupStatus.ok && startupStatus.value.embeddingBacklog > 0)
|
|
386
|
+
scheduler.notifySyncComplete([]);
|
|
380
387
|
ctxHolder.watchService = watchService;
|
|
381
388
|
ctxHolder.current.watchService = watchService;
|
|
382
389
|
|
|
@@ -79,7 +79,12 @@ export async function handleImpact(
|
|
|
79
79
|
): Promise<Response> {
|
|
80
80
|
const ref = url.searchParams.get("ref")?.trim();
|
|
81
81
|
if (!ref) return errorResponse("VALIDATION", "ref is required");
|
|
82
|
-
const
|
|
82
|
+
const collections = url.searchParams
|
|
83
|
+
.getAll("collection")
|
|
84
|
+
.map((value) => value.trim())
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
const input: KnowledgeImpactInput =
|
|
87
|
+
collections.length > 0 ? { collections } : {};
|
|
83
88
|
for (const [queryName, inputName] of [
|
|
84
89
|
["maxDepth", "maxDepth"],
|
|
85
90
|
["maxNodes", "maxNodes"],
|
|
@@ -34,6 +34,8 @@ export interface LinkResponse {
|
|
|
34
34
|
resolvedUri?: string;
|
|
35
35
|
/** Resolved target title (if found) */
|
|
36
36
|
resolvedTitle?: string;
|
|
37
|
+
/** Collection of the resolved target (may differ from the source's) */
|
|
38
|
+
resolvedCollection?: string;
|
|
37
39
|
}>;
|
|
38
40
|
meta: {
|
|
39
41
|
docid: string;
|
|
@@ -49,6 +51,8 @@ export interface BacklinkResponse {
|
|
|
49
51
|
sourceDocid: string;
|
|
50
52
|
sourceUri: string;
|
|
51
53
|
sourceTitle?: string;
|
|
54
|
+
/** Collection of the linking document */
|
|
55
|
+
sourceCollection?: string;
|
|
52
56
|
linkText?: string;
|
|
53
57
|
startLine: number;
|
|
54
58
|
startCol: number;
|
|
@@ -189,6 +193,11 @@ export async function handleDocLinks(
|
|
|
189
193
|
targetRefNorm: l.targetRefNorm,
|
|
190
194
|
targetCollection: l.targetCollection || doc.collection,
|
|
191
195
|
linkType: l.linkType,
|
|
196
|
+
source: {
|
|
197
|
+
collection: doc.collection,
|
|
198
|
+
relPath: doc.relPath,
|
|
199
|
+
explicit: Boolean(l.targetCollection),
|
|
200
|
+
},
|
|
192
201
|
}))
|
|
193
202
|
);
|
|
194
203
|
const resolutionAvailable = resolvedResult.ok;
|
|
@@ -217,6 +226,9 @@ export async function handleDocLinks(
|
|
|
217
226
|
resolvedDocid: resolved.docid,
|
|
218
227
|
resolvedUri: resolved.uri,
|
|
219
228
|
resolvedTitle: resolved.title ?? undefined,
|
|
229
|
+
...(resolved.collection && {
|
|
230
|
+
resolvedCollection: resolved.collection,
|
|
231
|
+
}),
|
|
220
232
|
}),
|
|
221
233
|
}),
|
|
222
234
|
};
|
|
@@ -273,6 +285,7 @@ export async function handleDocBacklinks(
|
|
|
273
285
|
backlinks: backlinks.map((b) => ({
|
|
274
286
|
sourceDocid: b.sourceDocid,
|
|
275
287
|
sourceUri: b.sourceDocUri,
|
|
288
|
+
...(b.sourceCollection && { sourceCollection: b.sourceCollection }),
|
|
276
289
|
...(b.sourceDocTitle && { sourceTitle: b.sourceDocTitle }),
|
|
277
290
|
...(b.linkText && { linkText: b.linkText }),
|
|
278
291
|
startLine: b.startLine,
|
|
@@ -16,7 +16,6 @@ import type { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
|
16
16
|
import type { RequestPeerServer } from "../request-locality";
|
|
17
17
|
import type { ContextHolder } from "./api";
|
|
18
18
|
|
|
19
|
-
import { getIndexDbPath } from "../../app/constants";
|
|
20
19
|
import { getConfigPaths, loadConfig } from "../../config";
|
|
21
20
|
import { withContentTypeRules } from "../../ingestion";
|
|
22
21
|
import {
|
|
@@ -28,8 +27,14 @@ import {
|
|
|
28
27
|
runAutomationProfile,
|
|
29
28
|
setAutomationProfile,
|
|
30
29
|
} from "../../sessions/automation";
|
|
31
|
-
import { assertSessionBinding } from "../../sessions/binding";
|
|
32
30
|
import { SessionSourceSchema, watchedCollections } from "../../sessions/config";
|
|
31
|
+
import {
|
|
32
|
+
adoptServedConfig,
|
|
33
|
+
assertInstanceBinding as assertConfigBinding,
|
|
34
|
+
readInstanceConfig,
|
|
35
|
+
refreshServedConfig,
|
|
36
|
+
type ServedSessionsConfig,
|
|
37
|
+
} from "../../sessions/config-refresh";
|
|
33
38
|
import { importInChildProcess } from "../../sessions/import-child";
|
|
34
39
|
import { SessionsService } from "../../sessions/service";
|
|
35
40
|
import {
|
|
@@ -42,6 +47,7 @@ import {
|
|
|
42
47
|
type SessionHarness,
|
|
43
48
|
type SessionImportReceipt,
|
|
44
49
|
remoteSafeSessionsError,
|
|
50
|
+
SessionsError,
|
|
45
51
|
type SessionsErrorCode,
|
|
46
52
|
SESSIONS_VALIDATION_CODES,
|
|
47
53
|
} from "../../sessions/types";
|
|
@@ -185,15 +191,11 @@ function instanceIdentity(ctxHolder: ContextHolder): {
|
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
/** Refuse an archive config opened against a different index (and vice versa). */
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
configPath,
|
|
194
|
-
indexName,
|
|
195
|
-
dbPath: getIndexDbPath(indexName),
|
|
196
|
-
});
|
|
194
|
+
function assertInstanceBinding(
|
|
195
|
+
ctxHolder: ContextHolder,
|
|
196
|
+
config: Config = ctxHolder.config
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
return assertConfigBinding(instanceIdentity(ctxHolder), config);
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
/** Service over the instance's own config/index pair, binding checked. */
|
|
@@ -211,44 +213,72 @@ async function archiveService(
|
|
|
211
213
|
});
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
/** This instance's served config, as the shared config refresh sees it. */
|
|
217
|
+
function servedConfig(
|
|
218
|
+
ctxHolder: ContextHolder,
|
|
219
|
+
store: SqliteAdapter
|
|
220
|
+
): ServedSessionsConfig {
|
|
221
|
+
return {
|
|
222
|
+
...instanceIdentity(ctxHolder),
|
|
223
|
+
store,
|
|
224
|
+
config: ctxHolder.config,
|
|
225
|
+
setConfig: (config) => {
|
|
226
|
+
ctxHolder.config = config;
|
|
227
|
+
ctxHolder.current = { ...ctxHolder.current, config };
|
|
228
|
+
ctxHolder.watchService?.updateCollections(
|
|
229
|
+
watchedCollections(config),
|
|
230
|
+
withContentTypeRules({}, config)
|
|
231
|
+
);
|
|
232
|
+
},
|
|
233
|
+
invalidateEgressPolicy: async () => {
|
|
234
|
+
await ctxHolder.invalidateEgressPolicy?.();
|
|
235
|
+
},
|
|
236
|
+
markContentMutation: () => ctxHolder.markContentMutation?.(),
|
|
237
|
+
markIndexMutation: () => ctxHolder.markIndexMutation?.(),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
214
241
|
/**
|
|
215
242
|
* Adopt a config the sessions service already persisted: project collections
|
|
216
243
|
* and contexts into the open store, swap the in-memory context, and refresh
|
|
217
|
-
* the watcher, egress policy and mutation generations
|
|
218
|
-
* config-sync route helpers).
|
|
244
|
+
* the watcher, egress policy and mutation generations.
|
|
219
245
|
*/
|
|
220
|
-
|
|
246
|
+
function adoptConfig(
|
|
221
247
|
ctxHolder: ContextHolder,
|
|
222
248
|
store: SqliteAdapter,
|
|
223
249
|
config: Config
|
|
224
250
|
): Promise<void> {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const contexts = await store.syncContexts(config.contexts ?? []);
|
|
232
|
-
if (!contexts.ok) {
|
|
233
|
-
throw new Error(
|
|
234
|
-
`Config saved but context sync failed: ${contexts.error.message}`
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
ctxHolder.config = config;
|
|
238
|
-
ctxHolder.current = { ...ctxHolder.current, config };
|
|
239
|
-
ctxHolder.watchService?.updateCollections(
|
|
240
|
-
watchedCollections(config),
|
|
241
|
-
withContentTypeRules({}, config)
|
|
242
|
-
);
|
|
243
|
-
await ctxHolder.invalidateEgressPolicy?.();
|
|
244
|
-
ctxHolder.markContentMutation?.();
|
|
245
|
-
ctxHolder.markIndexMutation?.();
|
|
251
|
+
return adoptServedConfig(servedConfig(ctxHolder, store), config);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Read this instance's config file, binding checked (see readInstanceConfig). */
|
|
255
|
+
function readConfigFile(ctxHolder: ContextHolder): Promise<Config> {
|
|
256
|
+
return readInstanceConfig(instanceIdentity(ctxHolder));
|
|
246
257
|
}
|
|
247
258
|
|
|
248
259
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
260
|
// Handlers
|
|
250
261
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
251
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Adopt the config file when it changed underneath the running server, for
|
|
265
|
+
* example after `gno sessions source add/remove` on the same pair. Runs
|
|
266
|
+
* before the status read is admitted: adopting new collections moves the
|
|
267
|
+
* authorization epoch, which would void a read already in flight. Returns an
|
|
268
|
+
* error response, or null when the served config is current.
|
|
269
|
+
*/
|
|
270
|
+
export async function refreshSessionsConfig(
|
|
271
|
+
ctxHolder: ContextHolder,
|
|
272
|
+
store: SqliteAdapter
|
|
273
|
+
): Promise<Response | null> {
|
|
274
|
+
try {
|
|
275
|
+
await refreshServedConfig(servedConfig(ctxHolder, store));
|
|
276
|
+
return null;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return sessionsErrorResponse(error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
252
282
|
/** GET /api/sessions/status */
|
|
253
283
|
export async function handleSessionsStatus(
|
|
254
284
|
ctxHolder: ContextHolder
|
|
@@ -427,8 +457,22 @@ export async function handleSessionsRemoveSource(
|
|
|
427
457
|
try {
|
|
428
458
|
const { configPath } = instanceIdentity(ctxHolder);
|
|
429
459
|
await assertInstanceBinding(ctxHolder);
|
|
430
|
-
const config = await removeSessionSource({ configPath, id })
|
|
431
|
-
|
|
460
|
+
const config = await removeSessionSource({ configPath, id }).catch(
|
|
461
|
+
(error: unknown) => {
|
|
462
|
+
// Already unregistered (for example from the CLI): the goal holds.
|
|
463
|
+
if (
|
|
464
|
+
error instanceof SessionsError &&
|
|
465
|
+
error.code === "SESSIONS_UNKNOWN_SOURCE"
|
|
466
|
+
) {
|
|
467
|
+
return readConfigFile(ctxHolder);
|
|
468
|
+
}
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
// A no-op remove leaves the served config current; adopting it anyway
|
|
473
|
+
// would reset the egress policy under concurrent reads.
|
|
474
|
+
if (!Bun.deepEquals(config, ctxHolder.config))
|
|
475
|
+
await adoptConfig(ctxHolder, store, config);
|
|
432
476
|
} catch (error) {
|
|
433
477
|
return sessionsErrorResponse(error);
|
|
434
478
|
}
|
package/src/serve/server.ts
CHANGED
|
@@ -116,6 +116,7 @@ import {
|
|
|
116
116
|
handleSessionsInit,
|
|
117
117
|
handleSessionsRemoveSource,
|
|
118
118
|
handleSessionsStatus,
|
|
119
|
+
refreshSessionsConfig,
|
|
119
120
|
} from "./routes/sessions";
|
|
120
121
|
|
|
121
122
|
/** `/api/sessions/automation/:id[/...]` path parameter. */
|
|
@@ -861,9 +862,10 @@ export async function startServer(
|
|
|
861
862
|
"/api/sessions/status": {
|
|
862
863
|
GET: async (req: Request) =>
|
|
863
864
|
withSecurityHeaders(
|
|
864
|
-
await
|
|
865
|
-
|
|
866
|
-
|
|
865
|
+
(await refreshSessionsConfig(ctxHolder, store)) ??
|
|
866
|
+
(await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
867
|
+
handleSessionsStatus(ctxHolder)
|
|
868
|
+
)),
|
|
867
869
|
isDev
|
|
868
870
|
),
|
|
869
871
|
},
|
|
@@ -18,9 +18,13 @@ export type HealthActionKind =
|
|
|
18
18
|
|
|
19
19
|
export type OnboardingStepStatus = "complete" | "current" | "upcoming";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Host path fields below (`path`, `configPath`, `dbPath`) reach same-host
|
|
23
|
+
* callers only; a remote caller's response omits them.
|
|
24
|
+
*/
|
|
21
25
|
export interface StatusCollection {
|
|
22
26
|
name: string;
|
|
23
|
-
path
|
|
27
|
+
path?: string;
|
|
24
28
|
documentCount: number;
|
|
25
29
|
chunkCount: number;
|
|
26
30
|
embeddedCount: number;
|
|
@@ -28,7 +32,7 @@ export interface StatusCollection {
|
|
|
28
32
|
|
|
29
33
|
export interface SuggestedCollection {
|
|
30
34
|
label: string;
|
|
31
|
-
path
|
|
35
|
+
path?: string;
|
|
32
36
|
reason: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -167,7 +171,7 @@ export interface BootstrapState {
|
|
|
167
171
|
summary: string;
|
|
168
172
|
};
|
|
169
173
|
cache: {
|
|
170
|
-
path
|
|
174
|
+
path?: string;
|
|
171
175
|
totalSizeBytes: number;
|
|
172
176
|
totalSizeLabel: string;
|
|
173
177
|
};
|
|
@@ -183,7 +187,7 @@ export interface BootstrapState {
|
|
|
183
187
|
role: "embed" | "rerank" | "expand" | "gen";
|
|
184
188
|
uri: string;
|
|
185
189
|
cached: boolean;
|
|
186
|
-
path
|
|
190
|
+
path?: string | null;
|
|
187
191
|
sizeBytes: number | null;
|
|
188
192
|
statusLabel: string;
|
|
189
193
|
}>;
|
|
@@ -194,8 +198,8 @@ export interface AppStatusResponse {
|
|
|
194
198
|
chunking?: ChunkingStatus;
|
|
195
199
|
resident: ResidentStatus;
|
|
196
200
|
indexName: string;
|
|
197
|
-
configPath
|
|
198
|
-
dbPath
|
|
201
|
+
configPath?: string;
|
|
202
|
+
dbPath?: string;
|
|
199
203
|
collections: StatusCollection[];
|
|
200
204
|
totalDocuments: number;
|
|
201
205
|
totalChunks: number;
|
package/src/serve/status.ts
CHANGED
|
@@ -95,11 +95,6 @@ function summarizeCount(
|
|
|
95
95
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function toDisplayPath(path: string): string {
|
|
99
|
-
const home = homedir();
|
|
100
|
-
return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
98
|
function extractEstimatedFootprint(name: string): string | null {
|
|
104
99
|
const match = name.match(SIZE_REGEX);
|
|
105
100
|
return match ? match[0] : null;
|
|
@@ -432,11 +427,11 @@ async function buildDiskCheck(
|
|
|
432
427
|
title: "Disk",
|
|
433
428
|
status: "warn",
|
|
434
429
|
summary: "Disk space could not be inspected",
|
|
435
|
-
detail:
|
|
430
|
+
detail: "GNO could not read filesystem capacity for the model cache.",
|
|
436
431
|
};
|
|
437
432
|
}
|
|
438
433
|
|
|
439
|
-
const summary = `${formatBytes(snapshot.freeBytes)} free
|
|
434
|
+
const summary = `${formatBytes(snapshot.freeBytes)} free for the model cache`;
|
|
440
435
|
|
|
441
436
|
if (snapshot.freeBytes < DISK_ERROR_BYTES) {
|
|
442
437
|
return {
|