@gmickel/gno 2.11.0 → 2.12.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 +1 -1
- package/assets/skill/SKILL.md +3 -2
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.11.0.zip → gno-browser-clipper-v2.12.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.12.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +8 -5
- package/spec/cli.md +100 -34
- package/spec/evals-agentic.md +3 -1
- package/spec/mcp.md +9 -3
- package/spec/output-schemas/status.schema.json +28 -0
- package/src/app/context-agent-projection.ts +10 -1
- package/src/cli/commands/agents/block.ts +2 -2
- package/src/cli/commands/agents/commands.ts +6 -5
- package/src/cli/commands/agents/engine.ts +15 -2
- package/src/cli/commands/agents/harnesses.ts +21 -14
- package/src/cli/commands/cleanup.ts +46 -6
- package/src/cli/commands/collection/remove.ts +1 -0
- package/src/cli/commands/collection/rename.ts +1 -0
- package/src/cli/commands/doctor.ts +37 -0
- package/src/cli/commands/mcp/paths.ts +2 -1
- package/src/cli/commands/setup-semantic.ts +6 -1
- package/src/cli/commands/shared.ts +26 -1
- package/src/cli/commands/skill/paths.ts +20 -2
- package/src/cli/commands/status.ts +89 -15
- package/src/cli/program.ts +25 -5
- package/src/core/activation-probe-plan.ts +0 -1
- package/src/core/config-mutation.ts +72 -55
- package/src/core/memory-remember.ts +0 -1
- package/src/core/module-path.ts +33 -0
- package/src/core/runtime-entrypoint.ts +3 -1
- package/src/ingestion/file-processor.ts +6 -3
- package/src/ingestion/prepare-file.ts +14 -4
- package/src/ingestion/source-availability/root.ts +138 -0
- package/src/ingestion/sync.ts +5 -19
- package/src/llm/native-worker/client.ts +8 -4
- package/src/llm/native-worker/errors.ts +26 -3
- package/src/mcp/http-transport.ts +7 -2
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-descriptions-core.ts +2 -2
- package/src/mcp/tools/index.ts +30 -1
- package/src/mcp/tools/status.ts +17 -2
- package/src/pipeline/query-snippet.ts +266 -0
- package/src/pipeline/search.ts +149 -19
- package/src/sdk/client.ts +14 -2
- package/src/sdk/types.ts +6 -1
- package/src/serve/public/components/ThoroughnessSelector.tsx +1 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/index.html +5 -0
- package/src/serve/public/pages/Collections.tsx +8 -0
- package/src/serve/public/pages/Search.tsx +9 -1
- package/src/serve/resident-request.ts +8 -0
- package/src/serve/resident-runtime.ts +25 -3
- package/src/serve/spa-production-build.ts +7 -3
- package/src/serve/status-model.ts +2 -0
- package/src/serve/status.ts +29 -0
- package/src/sessions/config-refresh.ts +6 -0
- package/src/sessions/import-child.ts +4 -1
- package/src/store/sqlite/adapter.ts +349 -200
- package/src/store/types.ts +45 -7
- package/browser-extension/artifacts/gno-browser-clipper-v2.11.0.zip.sha256 +0 -1
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="referrer" content="no-referrer" />
|
|
7
7
|
<title>GNO - Local Knowledge Index</title>
|
|
8
|
+
<link
|
|
9
|
+
rel="icon"
|
|
10
|
+
type="image/svg+xml"
|
|
11
|
+
href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0IiBmaWxsPSJub25lIiA+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnbm8tZmF2aWNvbi1ncmFkaWVudCIgeDE9IjEyIiB5MT0iOCIgeDI9IjU0IiB5Mj0iNTgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjZDA4YTNjIiAvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzhhNGExYSIgLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSI2IiB5PSI2IiB3aWR0aD0iNTIiIGhlaWdodD0iNTIiIHJ4PSIxNCIgZmlsbD0idXJsKCNnbm8tZmF2aWNvbi1ncmFkaWVudCkiIC8+PHBhdGggZmlsbD0iI2ZmZjhlZiIgZD0iTTM1LjYxOSAxOC42OTRjMy41NDYgMCA2LjAzIDEuNDMgNy42ODggMy41MzZsLTIuOTQ0IDIuMjk3Yy0xLjExLTEuMzk2LTIuNjMtMi40NTktNC45MzQtMi40NTktMy45MyAwLTYuNzA1IDMuMTU4LTYuNzA1IDcuMzYxIDAgNC4zMTYgMi43MTkgNy40MTggNi45MTkgNy40MTggMS43MSAwIDMuMzA1LS41MSA0LjI4NC0xLjM5NlYzMi4xaC01LjM3N3YtMy4yODloOS4xOTR2OC4wNTNjLTIuMTI2IDIuMzU0LTUuMTI2IDMuMzU0LTguNTEgMy4zNTQtNi4xOTggMC0xMC40MDctNC40MjItMTAuNDA3LTEwLjc4OSAwLTYuMzkgNC40ODMtMTAuNzM1IDEwLjc5Mi0xMC43MzVaIiAvPjwvc3ZnPg=="
|
|
12
|
+
/>
|
|
8
13
|
<link rel="stylesheet" href="./globals.built.css" />
|
|
9
14
|
</head>
|
|
10
15
|
<body>
|
|
@@ -81,6 +81,8 @@ interface CollectionStats {
|
|
|
81
81
|
documentCount: number;
|
|
82
82
|
chunkCount: number;
|
|
83
83
|
embeddedCount: number;
|
|
84
|
+
/** Set while the folder is missing or unreadable; documents stay indexed. */
|
|
85
|
+
rootError?: { code: string; message: string };
|
|
84
86
|
include?: string[];
|
|
85
87
|
models?: Partial<Record<"embed" | "rerank" | "expand" | "gen", string>>;
|
|
86
88
|
effectiveModels?: Record<"embed" | "rerank" | "expand" | "gen", string>;
|
|
@@ -277,6 +279,12 @@ function CollectionCard({
|
|
|
277
279
|
</Tooltip>
|
|
278
280
|
</TooltipProvider>
|
|
279
281
|
)}
|
|
282
|
+
{collection.rootError && (
|
|
283
|
+
<p className="flex items-start gap-1.5 text-amber-500 text-xs">
|
|
284
|
+
<AlertCircleIcon className="mt-0.5 size-3.5 shrink-0" />
|
|
285
|
+
<span>{collection.rootError.message}</span>
|
|
286
|
+
</p>
|
|
287
|
+
)}
|
|
280
288
|
</CardHeader>
|
|
281
289
|
|
|
282
290
|
<CardContent className="pt-2">
|
|
@@ -174,8 +174,12 @@ export default function Search({ navigate }: PageProps) {
|
|
|
174
174
|
query.trim().length > 0 && !structuredQueryState.ok
|
|
175
175
|
? structuredQueryState.error.message
|
|
176
176
|
: null;
|
|
177
|
+
// Without vectors, Fast is keyword search unless modes or intent need the
|
|
178
|
+
// hybrid endpoint. With vectors, Fast is always hybrid (no expansion,
|
|
179
|
+
// rerank or graph), the same path as CLI `--fast`.
|
|
177
180
|
const forceHybridForModes =
|
|
178
181
|
thoroughness === "fast" &&
|
|
182
|
+
!hybridAvailable &&
|
|
179
183
|
(queryModes.length > 0 ||
|
|
180
184
|
intent.trim().length > 0 ||
|
|
181
185
|
(structuredQueryState.ok &&
|
|
@@ -325,6 +329,7 @@ export default function Search({ navigate }: PageProps) {
|
|
|
325
329
|
|
|
326
330
|
const useBm25 =
|
|
327
331
|
thoroughness === "fast" &&
|
|
332
|
+
!hybridAvailable &&
|
|
328
333
|
queryModes.length === 0 &&
|
|
329
334
|
intent.trim().length === 0 &&
|
|
330
335
|
!structuredQueryState.value.usedStructuredQuerySyntax;
|
|
@@ -403,6 +408,7 @@ export default function Search({ navigate }: PageProps) {
|
|
|
403
408
|
activePreset,
|
|
404
409
|
category,
|
|
405
410
|
exclude,
|
|
411
|
+
hybridAvailable,
|
|
406
412
|
intent,
|
|
407
413
|
query,
|
|
408
414
|
queryModes,
|
|
@@ -448,7 +454,9 @@ export default function Search({ navigate }: PageProps) {
|
|
|
448
454
|
|
|
449
455
|
const thoroughnessDesc =
|
|
450
456
|
thoroughness === "fast"
|
|
451
|
-
?
|
|
457
|
+
? hybridAvailable
|
|
458
|
+
? "Hybrid, no expansion or reranking"
|
|
459
|
+
: "Keyword search (no vectors)"
|
|
452
460
|
: thoroughness === "balanced"
|
|
453
461
|
? resolveDepthPolicy({ presetId: activePreset })
|
|
454
462
|
.balancedExpansionEnabled
|
|
@@ -2,6 +2,7 @@ import type { ResidentRuntime } from "./resident-runtime";
|
|
|
2
2
|
/** Admission and bounded-reader boundary for resident REST reads. */
|
|
3
3
|
|
|
4
4
|
import { withInferenceScope } from "../llm/inference-scope";
|
|
5
|
+
import { StaleCollectionsError } from "../store/sqlite/adapter";
|
|
5
6
|
import { sessionsErrorResponse } from "./routes/sessions";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -16,6 +17,13 @@ export async function refreshResidentConfig(
|
|
|
16
17
|
await runtime.refreshConfig?.();
|
|
17
18
|
return null;
|
|
18
19
|
} catch (error) {
|
|
20
|
+
// Names collections only (no paths), and says how to resolve it.
|
|
21
|
+
if (error instanceof StaleCollectionsError) {
|
|
22
|
+
return Response.json(
|
|
23
|
+
{ error: { code: "STALE_COLLECTIONS", message: error.message } },
|
|
24
|
+
{ status: 409 }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
19
27
|
return sessionsErrorResponse(error);
|
|
20
28
|
}
|
|
21
29
|
}
|
|
@@ -61,7 +61,11 @@ import {
|
|
|
61
61
|
readInstanceConfig,
|
|
62
62
|
} from "../sessions/config-refresh";
|
|
63
63
|
import { remoteSafeSessionsError } from "../sessions/types";
|
|
64
|
-
import {
|
|
64
|
+
import {
|
|
65
|
+
SqliteAdapter,
|
|
66
|
+
StaleCollectionsError,
|
|
67
|
+
staleCollectionsMessage,
|
|
68
|
+
} from "../store/sqlite/adapter";
|
|
65
69
|
import {
|
|
66
70
|
createServerContext,
|
|
67
71
|
type CreateServerContextOptions,
|
|
@@ -519,10 +523,23 @@ export async function startResidentRuntime(
|
|
|
519
523
|
return undefined;
|
|
520
524
|
}
|
|
521
525
|
};
|
|
526
|
+
// Another client may index a collection the served config omits after this
|
|
527
|
+
// runtime started; requests fail instead of reading it.
|
|
528
|
+
const assertServedCollections = (): void => {
|
|
529
|
+
const stale = store.staleIndexedCollections(
|
|
530
|
+
ctxHolder.config.collections.map(({ name }) => name)
|
|
531
|
+
);
|
|
532
|
+
if (stale.length > 0) {
|
|
533
|
+
throw new StaleCollectionsError(staleCollectionsMessage(stale));
|
|
534
|
+
}
|
|
535
|
+
};
|
|
522
536
|
const refreshConfig = (): Promise<void> => {
|
|
523
537
|
configRefresh ??= (async () => {
|
|
524
538
|
const stamp = await configStamp();
|
|
525
|
-
if (stamp !== undefined && stamp === loadedConfigStamp)
|
|
539
|
+
if (stamp !== undefined && stamp === loadedConfigStamp) {
|
|
540
|
+
assertServedCollections();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
526
543
|
const file = await readInstanceConfig(
|
|
527
544
|
instance,
|
|
528
545
|
deps.loadConfig ?? loadConfig
|
|
@@ -535,6 +552,7 @@ export async function startResidentRuntime(
|
|
|
535
552
|
};
|
|
536
553
|
if (Bun.deepEquals(next, served)) {
|
|
537
554
|
loadedConfigStamp = stamp;
|
|
555
|
+
assertServedCollections();
|
|
538
556
|
return;
|
|
539
557
|
}
|
|
540
558
|
await adoptServedConfig(
|
|
@@ -816,7 +834,11 @@ export async function startResidentRuntime(
|
|
|
816
834
|
await refreshConfig();
|
|
817
835
|
lastRefreshError = undefined;
|
|
818
836
|
} catch (error) {
|
|
819
|
-
|
|
837
|
+
// The stale-collections message names collections, never paths.
|
|
838
|
+
const message =
|
|
839
|
+
error instanceof StaleCollectionsError
|
|
840
|
+
? error.message
|
|
841
|
+
: remoteSafeSessionsError(error).message;
|
|
820
842
|
if (message !== lastRefreshError)
|
|
821
843
|
console.warn(`gno ${mode}: config not reloaded: ${message}`);
|
|
822
844
|
lastRefreshError = message;
|
|
@@ -6,6 +6,8 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
// node:path — no Bun path utils.
|
|
7
7
|
import { basename, join, relative } from "node:path";
|
|
8
8
|
|
|
9
|
+
import { moduleDir, modulePath } from "../core/module-path";
|
|
10
|
+
|
|
9
11
|
export const ROOT_MOUNT_MARKER = 'getElementById("root")';
|
|
10
12
|
|
|
11
13
|
export type ProductionSpaFile = {
|
|
@@ -19,7 +21,8 @@ export type ProductionSpaAssets = {
|
|
|
19
21
|
sourceHash: string;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
const productionSpaPublicDir = (): string =>
|
|
24
|
+
const productionSpaPublicDir = (): string =>
|
|
25
|
+
join(moduleDir(import.meta), "public");
|
|
23
26
|
|
|
24
27
|
export const productionSpaEntryPath = (): string =>
|
|
25
28
|
join(productionSpaPublicDir(), "index.html");
|
|
@@ -79,13 +82,14 @@ const contentTypeFor = (path: string): string => {
|
|
|
79
82
|
return "application/octet-stream";
|
|
80
83
|
};
|
|
81
84
|
|
|
82
|
-
export const isBunfsPath = (path: string): boolean => {
|
|
85
|
+
export const isBunfsPath = (path: string | undefined): boolean => {
|
|
86
|
+
if (path === undefined) return false;
|
|
83
87
|
const normalized = path.replaceAll("\\", "/");
|
|
84
88
|
return normalized.includes("/$bunfs/") || normalized.startsWith("B:/~BUN/");
|
|
85
89
|
};
|
|
86
90
|
|
|
87
91
|
export const isStandaloneExecutable = (): boolean =>
|
|
88
|
-
isBunfsPath(import.meta
|
|
92
|
+
isBunfsPath(modulePath(import.meta));
|
|
89
93
|
|
|
90
94
|
const rewriteProductionHtml = (html: string, jsEntryPath: string): string => {
|
|
91
95
|
const script = `<script type="module" src="/${basename(jsEntryPath)}"></script>`;
|
|
@@ -28,6 +28,8 @@ export interface StatusCollection {
|
|
|
28
28
|
documentCount: number;
|
|
29
29
|
chunkCount: number;
|
|
30
30
|
embeddedCount: number;
|
|
31
|
+
/** Present while the collection root is missing or unreadable (path-free). */
|
|
32
|
+
rootError?: { code: string; message: string };
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export interface SuggestedCollection {
|
package/src/serve/status.ts
CHANGED
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
type ActivationStatus,
|
|
21
21
|
buildActivationStatus,
|
|
22
22
|
} from "../core/activation-status";
|
|
23
|
+
import {
|
|
24
|
+
checkCollectionRoots,
|
|
25
|
+
type CollectionRootError,
|
|
26
|
+
} from "../ingestion/source-availability/root";
|
|
23
27
|
import { ModelCache } from "../llm/cache";
|
|
24
28
|
import { envIsSet, resolveDownloadPolicy } from "../llm/policy";
|
|
25
29
|
import { getActivePreset, resolveModelUri } from "../llm/registry";
|
|
@@ -263,6 +267,25 @@ function buildCollectionCheck(status: IndexStatus): HealthCheck {
|
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
269
|
|
|
270
|
+
/** Folders whose root is missing or unreadable right now; null when none. */
|
|
271
|
+
function buildFolderAccessCheck(
|
|
272
|
+
rootErrors: Record<string, CollectionRootError>
|
|
273
|
+
): HealthCheck | null {
|
|
274
|
+
const names = Object.keys(rootErrors);
|
|
275
|
+
if (names.length === 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
id: "folder-access",
|
|
280
|
+
title: "Folder access",
|
|
281
|
+
status: "warn",
|
|
282
|
+
summary: `${names.join(", ")}: folder missing or unreadable`,
|
|
283
|
+
detail: `${names.map((name) => `${name}: ${rootErrors[name]?.message}.`).join(" ")} Reconnect the drive or restore the folder; this clears as soon as it is back. If the folder moved, change the collection's path.`,
|
|
284
|
+
actionLabel: "Manage folders",
|
|
285
|
+
actionKind: "open-collections",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
266
289
|
function buildIndexingCheck(status: IndexStatus): HealthCheck {
|
|
267
290
|
if (status.recentErrors > 0) {
|
|
268
291
|
return {
|
|
@@ -688,6 +711,7 @@ export async function buildAppStatus(
|
|
|
688
711
|
bootstrap,
|
|
689
712
|
embedCached,
|
|
690
713
|
connectorTargets,
|
|
714
|
+
rootErrors,
|
|
691
715
|
] = await Promise.all([
|
|
692
716
|
buildModelCheck(ctx, deps),
|
|
693
717
|
buildDiskCheck(status, deps),
|
|
@@ -695,6 +719,7 @@ export async function buildAppStatus(
|
|
|
695
719
|
buildBootstrapState(ctx),
|
|
696
720
|
isModelCached(preset.embed),
|
|
697
721
|
(deps.listConnectorTargets ?? getConnectorVerificationTargets)(),
|
|
722
|
+
checkCollectionRoots(ctx.config.collections),
|
|
698
723
|
]);
|
|
699
724
|
const activation = await (deps.buildActivation ?? buildActivationStatus)(
|
|
700
725
|
ctx.store,
|
|
@@ -712,6 +737,7 @@ export async function buildAppStatus(
|
|
|
712
737
|
const backgroundCheck = buildBackgroundCheck(ctx, status);
|
|
713
738
|
const checks = [
|
|
714
739
|
buildCollectionCheck(status),
|
|
740
|
+
buildFolderAccessCheck(rootErrors),
|
|
715
741
|
buildIndexingCheck(status),
|
|
716
742
|
buildActivationCheck(activation),
|
|
717
743
|
buildConnectorActivationCheck(activation),
|
|
@@ -746,6 +772,9 @@ export async function buildAppStatus(
|
|
|
746
772
|
documentCount: collection.activeDocuments,
|
|
747
773
|
chunkCount: collection.totalChunks,
|
|
748
774
|
embeddedCount: collection.embeddedChunks,
|
|
775
|
+
...(rootErrors[collection.name]
|
|
776
|
+
? { rootError: rootErrors[collection.name] }
|
|
777
|
+
: {}),
|
|
749
778
|
})),
|
|
750
779
|
totalDocuments: status.activeDocuments,
|
|
751
780
|
totalChunks: status.totalChunks,
|
|
@@ -13,6 +13,7 @@ import type { SqliteAdapter } from "../store/sqlite/adapter";
|
|
|
13
13
|
import { getIndexDbPath } from "../app/constants";
|
|
14
14
|
import { loadConfig } from "../config";
|
|
15
15
|
import { collectionEgressPolicyEpoch } from "../core/collection-egress-policy-service";
|
|
16
|
+
import { StaleCollectionsError } from "../store/sqlite/adapter";
|
|
16
17
|
import { assertSessionBinding } from "./binding";
|
|
17
18
|
import { SessionsError } from "./types";
|
|
18
19
|
|
|
@@ -76,7 +77,12 @@ export async function adoptServedConfig(
|
|
|
76
77
|
served: ServedSessionsConfig,
|
|
77
78
|
config: Config
|
|
78
79
|
): Promise<void> {
|
|
80
|
+
// A config edit never deletes indexed collections; one it dropped fails
|
|
81
|
+
// the refresh until it is added back or `gno cleanup` deletes it.
|
|
79
82
|
const collections = await served.store.syncCollections(config.collections);
|
|
83
|
+
if (!collections.ok && collections.error.code === "STALE_COLLECTIONS") {
|
|
84
|
+
throw new StaleCollectionsError(collections.error.message);
|
|
85
|
+
}
|
|
80
86
|
if (!collections.ok) {
|
|
81
87
|
throw new Error(
|
|
82
88
|
`Config saved but collection sync failed: ${collections.error.message}`
|
|
@@ -26,6 +26,7 @@ import type { Config } from "../config/types";
|
|
|
26
26
|
import type { SessionImportReceipt } from "./types";
|
|
27
27
|
|
|
28
28
|
import { getIndexDbPath } from "../app/constants";
|
|
29
|
+
import { modulePath } from "../core/module-path";
|
|
29
30
|
import { isStandaloneExecutable } from "../serve/spa-production-build";
|
|
30
31
|
import { SqliteAdapter } from "../store/sqlite/adapter";
|
|
31
32
|
import { assertSessionBinding } from "./binding";
|
|
@@ -113,7 +114,9 @@ export async function importInChildProcess(
|
|
|
113
114
|
// Shared check: covers the POSIX `/$bunfs/` and Windows `B:/~BUN/` roots.
|
|
114
115
|
const compiled = isStandaloneExecutable();
|
|
115
116
|
const child = Bun.spawn({
|
|
116
|
-
cmd: compiled
|
|
117
|
+
cmd: compiled
|
|
118
|
+
? [process.execPath]
|
|
119
|
+
: [process.execPath, modulePath(import.meta)],
|
|
117
120
|
env: compiled ? { ...process.env, [IMPORT_CHILD_ENV]: "1" } : process.env,
|
|
118
121
|
// The request line, then held open as the child's lifeline.
|
|
119
122
|
stdin: "pipe",
|