@gmickel/gno 2.6.0 → 2.7.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 +1 -1
- package/assets/skill/SKILL.md +5 -3
- package/assets/skill/cli-reference.md +9 -2
- package/assets/skill/mcp-reference.md +2 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.6.0.zip → gno-browser-clipper-v2.7.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.7.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +109 -18
- package/spec/mcp.md +36 -2
- package/spec/output-schemas/ask.schema.json +1 -1
- package/spec/output-schemas/capture-receipt.schema.json +1 -1
- package/spec/output-schemas/doctor.schema.json +88 -0
- package/spec/output-schemas/error.schema.json +11 -2
- package/spec/output-schemas/get.schema.json +1 -1
- package/spec/output-schemas/mcp-capture-result.schema.json +1 -2
- package/spec/output-schemas/memory-remember.schema.json +2 -2
- package/spec/output-schemas/multi-get.schema.json +4 -1
- package/spec/output-schemas/peek.schema.json +2 -9
- package/spec/output-schemas/resident-status.schema.json +22 -0
- package/spec/output-schemas/search-result.schema.json +1 -1
- package/spec/output-schemas/search-results.schema.json +1 -1
- package/spec/output-schemas/status.schema.json +98 -0
- package/src/cli/commands/doctor.ts +54 -20
- package/src/cli/commands/embed.ts +41 -3
- package/src/cli/commands/query.ts +5 -0
- package/src/cli/commands/status.ts +63 -5
- package/src/cli/commands/vec.ts +54 -0
- package/src/cli/detach.ts +29 -1
- package/src/cli/errors.ts +13 -9
- package/src/cli/program.ts +53 -1
- package/src/core/capture-sync.ts +9 -2
- package/src/core/host-paths.ts +31 -0
- package/src/core/memory-remember.ts +4 -3
- package/src/core/shutdown-budget.ts +6 -0
- package/src/core/vector-partition-status.ts +52 -0
- package/src/embed/backlog.ts +124 -18
- package/src/embed/fingerprint.ts +6 -3
- package/src/embed/retry.ts +66 -27
- package/src/embed/variant-backlog.ts +15 -10
- package/src/embed/variant-retry.ts +31 -22
- package/src/index.ts +21 -2
- package/src/llm/native-worker/dispatcher.ts +2 -0
- package/src/llm/native-worker/embedding-identity.ts +42 -0
- package/src/llm/native-worker/protocol.ts +1 -0
- package/src/llm/types.ts +3 -0
- package/src/mcp/context.ts +9 -0
- package/src/mcp/resources/index.ts +6 -5
- package/src/mcp/tool-descriptions-core.ts +1 -1
- package/src/mcp/tools/capture.ts +1 -3
- package/src/mcp/tools/index.ts +11 -4
- package/src/mcp/tools/memory-remember.ts +1 -1
- package/src/mcp/tools/status.ts +4 -0
- package/src/pipeline/hybrid.ts +37 -7
- package/src/pipeline/vsearch.ts +14 -2
- package/src/serve/embed-scheduler.ts +133 -19
- package/src/serve/host-path-redaction.ts +79 -0
- package/src/serve/public/components/sessions/SessionSearch.tsx +2 -2
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +17 -2
- package/src/serve/public/lib/request-intent.ts +8 -0
- package/src/serve/public/{components/sessions → lib}/snippet.tsx +2 -3
- package/src/serve/public/pages/Dashboard.tsx +12 -9
- package/src/serve/public/pages/DocView.tsx +10 -5
- package/src/serve/public/pages/DocumentEditor.tsx +91 -14
- package/src/serve/public/pages/Search.tsx +1 -41
- package/src/serve/resident-runtime.ts +26 -1
- package/src/serve/resident-status.ts +13 -1
- package/src/serve/server.ts +10 -9
- package/src/serve/status-model.ts +16 -0
- package/src/serve/status.ts +2 -0
- package/src/serve/watch-reconciliation-shared.ts +3 -0
- package/src/serve/watch-service-events.ts +3 -2
- package/src/serve/watch-service-run-flush.ts +35 -2
- package/src/serve/watch-service.ts +5 -0
- package/src/store/migrations/031-runtime-independent-vectors.ts +29 -0
- package/src/store/migrations/032-vector-runtime-callers.ts +17 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +22 -1
- package/src/store/types.ts +11 -1
- package/src/store/vector/lazy.ts +46 -43
- package/src/store/vector/runtime-compat.ts +651 -0
- package/src/store/vector/sqlite-vec.ts +20 -2
- package/src/store/vector/status.ts +276 -35
- package/src/store/vector/types.ts +2 -0
- package/src/store/vector/variant-search.ts +71 -23
- package/src/store/vector/variants.ts +49 -14
- package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +0 -1
|
@@ -5,7 +5,9 @@ import type { VectorVariantStore } from "../store/vector/variants";
|
|
|
5
5
|
import { err, ok } from "../store/types";
|
|
6
6
|
import { embedTextsWithRecovery } from "./batch";
|
|
7
7
|
import {
|
|
8
|
+
type AcquireWriteTurn,
|
|
8
9
|
chunkRetryKey,
|
|
10
|
+
inWriteTurn,
|
|
9
11
|
isUpsertLockContention,
|
|
10
12
|
upsertVectorsWithContentionRetry,
|
|
11
13
|
} from "./retry";
|
|
@@ -30,11 +32,14 @@ export async function embedVariantBatch(params: {
|
|
|
30
32
|
identityStillCurrent: () => boolean;
|
|
31
33
|
delays?: number[];
|
|
32
34
|
force?: boolean;
|
|
35
|
+
/** Gate held only around the checkpoint write, never around inference. */
|
|
36
|
+
acquireWriteTurn?: AcquireWriteTurn;
|
|
33
37
|
}): Promise<{
|
|
34
38
|
embedded: number;
|
|
35
39
|
errors: number;
|
|
36
40
|
contentionErrors: number;
|
|
37
41
|
retryOwners: VectorOwnerInput[];
|
|
42
|
+
deferred?: boolean;
|
|
38
43
|
}> {
|
|
39
44
|
const { store, embedPort, identityStillCurrent } = params;
|
|
40
45
|
const empty = {
|
|
@@ -76,30 +81,34 @@ export async function embedVariantBatch(params: {
|
|
|
76
81
|
.map((owner) => ({ owner, embedding: vectors.get(owner.inputHash) }));
|
|
77
82
|
if (!rows.length) return { ...empty, retryOwners };
|
|
78
83
|
let written = 0;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
const turn = await inWriteTurn(params.acquireWriteTurn, () =>
|
|
85
|
+
upsertVectorsWithContentionRetry(
|
|
86
|
+
{
|
|
87
|
+
upsertVectors: () => {
|
|
88
|
+
try {
|
|
89
|
+
// A contention wait may allow document or runtime mutations. Revalidate each attempt.
|
|
90
|
+
if (!identityStillCurrent()) return Promise.resolve(ok(undefined));
|
|
91
|
+
const valid = rows.filter(({ owner }) => isCurrent(store, owner));
|
|
92
|
+
store.write(valid);
|
|
93
|
+
written = valid.length;
|
|
94
|
+
return Promise.resolve(ok(undefined));
|
|
95
|
+
} catch (cause) {
|
|
96
|
+
return Promise.resolve(
|
|
97
|
+
err(
|
|
98
|
+
"QUERY_FAILED",
|
|
99
|
+
cause instanceof Error ? cause.message : String(cause),
|
|
100
|
+
cause
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
98
105
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
[],
|
|
107
|
+
params.delays
|
|
108
|
+
)
|
|
102
109
|
);
|
|
110
|
+
if (turn.deferred) return { ...empty, deferred: true };
|
|
111
|
+
const persisted = turn.value;
|
|
103
112
|
if (!persisted.ok)
|
|
104
113
|
return {
|
|
105
114
|
...empty,
|
package/src/index.ts
CHANGED
|
@@ -11,13 +11,32 @@ import { resetModelManager } from "./llm/nodeLlamaCpp/lifecycle";
|
|
|
11
11
|
import { IMPORT_CHILD_ENV } from "./sessions/import-child-env";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* End `stream` and resolve once its queued writes have reached the OS.
|
|
15
|
+
* process.exit() drops pending asynchronous pipe writes, so a pipe consumer
|
|
16
|
+
* would otherwise see output cut at the pipe buffer size. Bun reports no
|
|
17
|
+
* writableLength and fires an empty write's callback immediately, so end()
|
|
18
|
+
* is the flush that actually waits. A closed consumer (EPIPE) settles
|
|
19
|
+
* through the callback or the 'error' event.
|
|
20
|
+
*/
|
|
21
|
+
function flushStream(stream: NodeJS.WriteStream): Promise<void> {
|
|
22
|
+
if (stream.destroyed || stream.writableEnded) {
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
stream.once("error", () => resolve());
|
|
27
|
+
stream.end(() => resolve());
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Cleanup models, flush output, and exit.
|
|
33
|
+
* Without the explicit exit, llama.cpp native threads can keep the process alive.
|
|
16
34
|
*/
|
|
17
35
|
async function cleanupAndExit(code: number): Promise<never> {
|
|
18
36
|
await resetModelManager().catch(() => {
|
|
19
37
|
// Ignore cleanup errors on exit
|
|
20
38
|
});
|
|
39
|
+
await Promise.all([flushStream(process.stdout), flushStream(process.stderr)]);
|
|
21
40
|
process.exit(code);
|
|
22
41
|
}
|
|
23
42
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
fileIdentity,
|
|
16
16
|
fingerprintModel,
|
|
17
17
|
fingerprintRuntime,
|
|
18
|
+
runtimeLabel,
|
|
18
19
|
} from "./embedding-identity";
|
|
19
20
|
import { NativeWorkerError } from "./errors";
|
|
20
21
|
import { checkEvaluation } from "./evaluation";
|
|
@@ -174,6 +175,7 @@ export class NativeDispatcher {
|
|
|
174
175
|
gpu: llama.gpu,
|
|
175
176
|
cpuMathCores: llama.cpuMathCores,
|
|
176
177
|
}),
|
|
178
|
+
runtimeLabel: runtimeLabel(llama.gpu),
|
|
177
179
|
},
|
|
178
180
|
},
|
|
179
181
|
};
|
|
@@ -31,3 +31,45 @@ export function fingerprintRuntime(settings: Record<string, unknown>): string {
|
|
|
31
31
|
)
|
|
32
32
|
.digest("hex");
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/** Environment that shapes the native runtime identity of an embedding. */
|
|
36
|
+
const RUNTIME_ENV = [
|
|
37
|
+
"GNO_LLAMA_GPU",
|
|
38
|
+
"NODE_LLAMA_CPP_GPU",
|
|
39
|
+
"GNO_EMBED_CONTEXT_SIZE",
|
|
40
|
+
"GNO_EMBED_CONTEXTS",
|
|
41
|
+
"GNO_EMBED_THREADS",
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* What a process can know about its embedding runtime without loading the
|
|
46
|
+
* model: status looks up the identity a caller with this key last resolved.
|
|
47
|
+
*/
|
|
48
|
+
export function runtimeCallerKey(
|
|
49
|
+
model: string,
|
|
50
|
+
env: NodeJS.ProcessEnv = process.env
|
|
51
|
+
): string {
|
|
52
|
+
return new Bun.CryptoHasher("sha256")
|
|
53
|
+
.update(
|
|
54
|
+
JSON.stringify([
|
|
55
|
+
model,
|
|
56
|
+
Bun.version,
|
|
57
|
+
nativePackage.version,
|
|
58
|
+
process.platform,
|
|
59
|
+
process.arch,
|
|
60
|
+
RUNTIME_ENV.map((name) => env[name] ?? null),
|
|
61
|
+
])
|
|
62
|
+
)
|
|
63
|
+
.digest("hex");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const BACKEND_LABELS: Record<string, string> = {
|
|
67
|
+
cuda: "CUDA",
|
|
68
|
+
metal: "Metal",
|
|
69
|
+
vulkan: "Vulkan",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Readable provenance for status output; identity stays the fingerprint. */
|
|
73
|
+
export function runtimeLabel(gpu: string | false): string {
|
|
74
|
+
return `${gpu ? (BACKEND_LABELS[gpu] ?? gpu) : "CPU"}, Bun ${Bun.version}`;
|
|
75
|
+
}
|
|
@@ -112,6 +112,7 @@ export const EmbeddingIdentitySchema = z.strictObject({
|
|
|
112
112
|
truncationPolicy: z.string().min(1),
|
|
113
113
|
modelFingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
114
114
|
runtimeFingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
115
|
+
runtimeLabel: z.string().min(1),
|
|
115
116
|
});
|
|
116
117
|
const metadata = z.strictObject({
|
|
117
118
|
dimensions: id.optional(),
|
package/src/llm/types.ts
CHANGED
|
@@ -95,7 +95,10 @@ export interface EmbeddingIdentity {
|
|
|
95
95
|
/** Versioned policy including the effective token limit. */
|
|
96
96
|
truncationPolicy: string;
|
|
97
97
|
modelFingerprint: string;
|
|
98
|
+
/** Runtime provenance (Bun, binding, backend, threads); not vector identity. */
|
|
98
99
|
runtimeFingerprint: string;
|
|
100
|
+
/** Readable provenance such as "CUDA, Bun 1.4.2". */
|
|
101
|
+
runtimeLabel?: string;
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
export interface EmbeddingPort {
|
package/src/mcp/context.ts
CHANGED
|
@@ -120,6 +120,15 @@ export interface ToolContext {
|
|
|
120
120
|
runWithSnapshot?<T>(operation: () => Promise<T>): Promise<T>;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Host absolute paths reach only stdio callers, which run on the owner's
|
|
125
|
+
* machine. Every Streamable HTTP request runs inside an egress context,
|
|
126
|
+
* whatever its peer, so HTTP callers get URIs and relative paths only.
|
|
127
|
+
*/
|
|
128
|
+
export const exposesHostPaths = (
|
|
129
|
+
ctx: Pick<ToolContext, "getEgressContext">
|
|
130
|
+
): boolean => ctx.getEgressContext?.() === undefined;
|
|
131
|
+
|
|
123
132
|
export interface CreateToolContextOptions {
|
|
124
133
|
store: SqliteAdapter;
|
|
125
134
|
getConfig: () => Config;
|
|
@@ -21,6 +21,7 @@ import { resolveEffectiveIndex } from "../../core/indexed-reference";
|
|
|
21
21
|
import { normalizeTag, validateTag } from "../../core/tags";
|
|
22
22
|
import { normalizeCollectionName } from "../../core/validation";
|
|
23
23
|
import { openScopedIndexStore } from "../../store/sqlite/scoped-index";
|
|
24
|
+
import { exposesHostPaths } from "../context";
|
|
24
25
|
|
|
25
26
|
// Tags resource URI prefix
|
|
26
27
|
const TAGS_URI = `${URI_PREFIX}tags`;
|
|
@@ -53,15 +54,15 @@ function formatResourceContent(
|
|
|
53
54
|
ctx: ToolContext,
|
|
54
55
|
indexName = ctx.indexName
|
|
55
56
|
): string {
|
|
56
|
-
//
|
|
57
|
+
// Host path for stdio callers; HTTP callers see the relative path.
|
|
57
58
|
const uriParsed = parseUri(doc.uri);
|
|
58
|
-
let
|
|
59
|
-
if (uriParsed) {
|
|
59
|
+
let source = doc.relPath;
|
|
60
|
+
if (uriParsed && exposesHostPaths(ctx)) {
|
|
60
61
|
const collection = ctx.collections.find(
|
|
61
62
|
(c) => c.name === uriParsed.collection
|
|
62
63
|
);
|
|
63
64
|
if (collection) {
|
|
64
|
-
|
|
65
|
+
source = pathJoin(collection.path, doc.relPath);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -72,7 +73,7 @@ function formatResourceContent(
|
|
|
72
73
|
const displayUri = decorateUriForIndex(doc.uri, indexName);
|
|
73
74
|
const header = `<!-- ${displayUri}
|
|
74
75
|
docid: ${doc.docid}
|
|
75
|
-
source: ${
|
|
76
|
+
source: ${source}
|
|
76
77
|
mime: ${doc.sourceMime}${langLine}
|
|
77
78
|
-->
|
|
78
79
|
|
|
@@ -32,7 +32,7 @@ export const MCP_CORE_TOOL_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
|
32
32
|
gno_recall:
|
|
33
33
|
"Call before answering about the user's preferences, decisions, people, or prior work, and before gno_remember to find the predecessor of a changed fact. Retrieves current facts from a memory-managed collection for the explicit scopes you pass; superseded facts are excluded. Returns at most 8 facts within 512 tokens by default, each with text, scopes, provenance, gno:// cite, and content hash, plus a content-free receipt. Pass that receipt to gno_remember when a stored fact derives from this recall. An empty result names the command that stores the first fact.",
|
|
34
34
|
gno_capture:
|
|
35
|
-
"Call to create a new note from text the user wants kept: pass collection and content (or a presetId scaffold), optionally title, path or folderPath, tags, and source provenance. Writes the file to disk with source: frontmatter, syncs it for keyword search, and returns a receipt with uri, docid, relPath, absPath, contentHash, collisionPolicyResult, and sync and embed status. Embedding is a separate step (embed.status stays short of completed until gno_index or gno_embed runs), so vector search sees the note later. An existing target follows collisionPolicy: error, open_existing, or create_with_suffix.",
|
|
35
|
+
"Call to create a new note from text the user wants kept: pass collection and content (or a presetId scaffold), optionally title, path or folderPath, tags, and source provenance. Writes the file to disk with source: frontmatter, syncs it for keyword search, and returns a receipt with uri, docid, relPath, absPath (stdio only), contentHash, collisionPolicyResult, and sync and embed status. Embedding is a separate step (embed.status stays short of completed until gno_index or gno_embed runs), so vector search sees the note later. An existing target follows collisionPolicy: error, open_existing, or create_with_suffix.",
|
|
36
36
|
gno_remember:
|
|
37
37
|
"Call when the user states a durable preference, decision, or fact worth recalling later; documents go through gno_capture and existing notes through file edits. Stores one fact in a memory-managed collection under the explicit scopes you pass. Without decision it returns likely matches and writes nothing; decision=add writes a new fact; decision=supersede replaces predecessorUri after a hash check, one successor per fact. Returns outcome (candidates, existing, added, or superseded) with the stored record; an exact duplicate returns the existing record. Text that replays a recall receipt span or declares a gno:// origin is rejected. The fact is lexically searchable when the call returns.",
|
|
38
38
|
};
|
package/src/mcp/tools/capture.ts
CHANGED
|
@@ -47,7 +47,6 @@ interface CaptureInput extends Omit<
|
|
|
47
47
|
|
|
48
48
|
type McpCaptureResult = CaptureReceipt & {
|
|
49
49
|
docid: string;
|
|
50
|
-
absPath: string;
|
|
51
50
|
overwritten: boolean;
|
|
52
51
|
serverInstanceId: string;
|
|
53
52
|
request?: RequestReceiptInfo;
|
|
@@ -80,7 +79,7 @@ function formatCaptureResult(result: McpCaptureResult): string {
|
|
|
80
79
|
const lines: string[] = [];
|
|
81
80
|
lines.push(`Doc: ${result.docid}`);
|
|
82
81
|
lines.push(`URI: ${result.uri}`);
|
|
83
|
-
lines.push(`Path: ${result.absPath}`);
|
|
82
|
+
if (result.absPath) lines.push(`Path: ${result.absPath}`);
|
|
84
83
|
lines.push(`Created: ${result.created ? "yes" : "no"}`);
|
|
85
84
|
lines.push(`Opened existing: ${result.openedExisting ? "yes" : "no"}`);
|
|
86
85
|
lines.push(`Overwritten: ${result.overwritten ? "yes" : "no"}`);
|
|
@@ -216,7 +215,6 @@ export function handleCapture(
|
|
|
216
215
|
return {
|
|
217
216
|
...published.receipt,
|
|
218
217
|
docid: published.receipt.docid ?? "",
|
|
219
|
-
absPath: published.receipt.absPath ?? "",
|
|
220
218
|
overwritten: published.receipt.overwritten ?? false,
|
|
221
219
|
serverInstanceId: ctx.serverInstanceId,
|
|
222
220
|
...(published.request ? { request: published.request } : {}),
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
compiledContextPreviewSchema,
|
|
21
21
|
compiledContextCheckSchema,
|
|
22
22
|
} from "../../core/compiled-context";
|
|
23
|
+
import { withoutHostPaths } from "../../core/host-paths";
|
|
23
24
|
import { NOTE_PRESETS, type NotePresetId } from "../../core/note-presets";
|
|
24
25
|
import { RETRIEVAL_TRACE_METADATA } from "../../core/retrieval-trace-session";
|
|
25
26
|
import { normalizeTag } from "../../core/tags";
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
assertInferenceActive,
|
|
32
33
|
acquireInferencePermit,
|
|
33
34
|
} from "../../llm/inference-scope";
|
|
35
|
+
import { exposesHostPaths } from "../context";
|
|
34
36
|
import { profileToolDescription } from "../tool-descriptions-core";
|
|
35
37
|
import {
|
|
36
38
|
createProfileToolRegistrar,
|
|
@@ -972,6 +974,10 @@ export interface ToolResult {
|
|
|
972
974
|
// DRY Helper: Exception Firewall + Mutex + Response Shaping
|
|
973
975
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
974
976
|
|
|
977
|
+
/** Tool data as the current caller may see it (no host paths over HTTP). */
|
|
978
|
+
const forCaller = <T>(ctx: ToolContext, data: T): T =>
|
|
979
|
+
exposesHostPaths(ctx) ? data : withoutHostPaths(data);
|
|
980
|
+
|
|
975
981
|
export async function runTool<T>(
|
|
976
982
|
ctx: ToolContext,
|
|
977
983
|
name: string,
|
|
@@ -990,12 +996,13 @@ export async function runTool<T>(
|
|
|
990
996
|
const release = await acquireInferencePermit(() => ctx.toolMutex.acquire());
|
|
991
997
|
try {
|
|
992
998
|
assertInferenceActive();
|
|
993
|
-
const
|
|
999
|
+
const raw = await (ctx.runWithSnapshot?.(fn) ?? fn());
|
|
994
1000
|
assertInferenceActive();
|
|
995
1001
|
const traceMetadata =
|
|
996
|
-
|
|
997
|
-
? (
|
|
1002
|
+
raw !== null && typeof raw === "object"
|
|
1003
|
+
? (raw as Record<PropertyKey, unknown>)[RETRIEVAL_TRACE_METADATA]
|
|
998
1004
|
: undefined;
|
|
1005
|
+
const data = forCaller(ctx, raw);
|
|
999
1006
|
return {
|
|
1000
1007
|
content: [{ type: "text", text: formatText(data) }],
|
|
1001
1008
|
structuredContent: data as { [x: string]: unknown },
|
|
@@ -1039,7 +1046,7 @@ export async function runToolNoMutex<T>(
|
|
|
1039
1046
|
|
|
1040
1047
|
try {
|
|
1041
1048
|
assertInferenceActive();
|
|
1042
|
-
const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
|
|
1049
|
+
const data = forCaller(ctx, await (ctx.runWithSnapshot?.(fn) ?? fn()));
|
|
1043
1050
|
assertInferenceActive();
|
|
1044
1051
|
return {
|
|
1045
1052
|
content: [{ type: "text", text: formatText(data) }],
|
|
@@ -127,7 +127,7 @@ export function formatRememberResult(result: RememberResult): string {
|
|
|
127
127
|
lines.push(`Outcome: ${result.outcome}`);
|
|
128
128
|
lines.push(`URI: ${result.record.uri}`);
|
|
129
129
|
lines.push(`Hash: ${result.record.contentHash}`);
|
|
130
|
-
lines.push(`Path: ${result.absPath}`);
|
|
130
|
+
if (result.absPath) lines.push(`Path: ${result.absPath}`);
|
|
131
131
|
lines.push(`Sync: ${result.sync.status}`);
|
|
132
132
|
if (result.record.supersedes.length > 0) {
|
|
133
133
|
lines.push(`Supersedes: ${result.record.supersedes.join(", ")}`);
|
package/src/mcp/tools/status.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { ToolContext } from "../server";
|
|
|
9
9
|
|
|
10
10
|
import { buildContentTypeBoostStatus } from "../../config/content-types";
|
|
11
11
|
import { formatChunkingStatus } from "../../core/chunking-status";
|
|
12
|
+
import { formatVectorPartitionLines } from "../../core/vector-partition-status";
|
|
12
13
|
import { resolveModelUri } from "../../llm/registry";
|
|
13
14
|
import { createStandaloneResidentStatus } from "../../serve/resident-status";
|
|
14
15
|
import { runTool, type ToolResult } from "./index";
|
|
@@ -59,6 +60,9 @@ function formatStatus(status: IndexStatus): string {
|
|
|
59
60
|
if (status.embeddingBacklog > 0) {
|
|
60
61
|
lines.push(`Embedding backlog: ${status.embeddingBacklog} chunks`);
|
|
61
62
|
}
|
|
63
|
+
lines.push(
|
|
64
|
+
...formatVectorPartitionLines(status.vectorPartitions, status.vectorRuntime)
|
|
65
|
+
);
|
|
62
66
|
|
|
63
67
|
const chunking = formatChunkingStatus(status.chunking);
|
|
64
68
|
if (chunking) lines.push(chunking);
|
package/src/pipeline/hybrid.ts
CHANGED
|
@@ -33,7 +33,11 @@ import {
|
|
|
33
33
|
withInferenceScope,
|
|
34
34
|
} from "../llm/inference-scope";
|
|
35
35
|
import { err, ok } from "../store/types";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
lexicalFallbackNotice,
|
|
38
|
+
resolveVectorSearchIdentity,
|
|
39
|
+
VECTOR_RUNTIME_INCOMPATIBLE,
|
|
40
|
+
} from "../store/vector/variant-search";
|
|
37
41
|
import { createChunkLookup } from "./chunk-lookup";
|
|
38
42
|
import {
|
|
39
43
|
attachAuxiliaryScoreMetadata,
|
|
@@ -284,7 +288,10 @@ async function searchVectorChunks(
|
|
|
284
288
|
allowedMirrorHashes?: string[];
|
|
285
289
|
eligibility?: VectorSearchOptions["eligibility"];
|
|
286
290
|
}
|
|
287
|
-
): Promise<
|
|
291
|
+
): Promise<
|
|
292
|
+
| { ok: true; chunks: ChunkId[] }
|
|
293
|
+
| { ok: false; reason: string; notice?: string }
|
|
294
|
+
> {
|
|
288
295
|
if (!vectorIndex.searchAvailable) {
|
|
289
296
|
return { ok: false, reason: "vector_unavailable" };
|
|
290
297
|
}
|
|
@@ -298,12 +305,19 @@ async function searchVectorChunks(
|
|
|
298
305
|
return { ok: false, reason: "vector_embed_error" };
|
|
299
306
|
}
|
|
300
307
|
|
|
308
|
+
const partition = await resolveVectorSearchIdentity(embedPort, vectorIndex);
|
|
309
|
+
if (partition.unavailable)
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
reason: VECTOR_RUNTIME_INCOMPATIBLE,
|
|
313
|
+
notice: lexicalFallbackNotice(partition.unavailable),
|
|
314
|
+
};
|
|
301
315
|
const queryEmbedding = new Float32Array(embedResult.value);
|
|
302
316
|
const searchResult = await vectorIndex.searchNearest(
|
|
303
317
|
queryEmbedding,
|
|
304
318
|
options.limit,
|
|
305
319
|
{
|
|
306
|
-
embeddingIdentity:
|
|
320
|
+
embeddingIdentity: partition.identity,
|
|
307
321
|
minScore: options.minScore,
|
|
308
322
|
allowedMirrorHashes: options.allowedMirrorHashes,
|
|
309
323
|
eligibility: options.eligibility,
|
|
@@ -630,6 +644,7 @@ async function searchHybridWithHydration(
|
|
|
630
644
|
// Vector search
|
|
631
645
|
let vecCount = 0;
|
|
632
646
|
let vectorsUsed = false;
|
|
647
|
+
let vectorNotice: string | undefined;
|
|
633
648
|
const vectorAvailable =
|
|
634
649
|
(vectorIndex?.searchAvailable && embedPort !== null) ?? false;
|
|
635
650
|
if (!vectorAvailable) {
|
|
@@ -662,8 +677,10 @@ async function searchHybridWithHydration(
|
|
|
662
677
|
}
|
|
663
678
|
);
|
|
664
679
|
|
|
665
|
-
if (!vectorResult.ok)
|
|
666
|
-
|
|
680
|
+
if (!vectorResult.ok) {
|
|
681
|
+
counters.fallbackEvents.push(vectorResult.reason);
|
|
682
|
+
vectorNotice = vectorResult.notice;
|
|
683
|
+
} else vectorsUsed = true;
|
|
667
684
|
const vecChunks = vectorResult.ok ? vectorResult.chunks : [];
|
|
668
685
|
vecCount = vecChunks.length;
|
|
669
686
|
vectorTraceChunks.push(...vecChunks);
|
|
@@ -691,8 +708,14 @@ async function searchHybridWithHydration(
|
|
|
691
708
|
);
|
|
692
709
|
|
|
693
710
|
assertInferenceResult(embedResult);
|
|
711
|
+
const partition = embedResult.ok
|
|
712
|
+
? await resolveVectorSearchIdentity(embedPort, vectorIndex)
|
|
713
|
+
: undefined;
|
|
694
714
|
if (!embedResult.ok) {
|
|
695
715
|
counters.fallbackEvents.push("vector_embed_error");
|
|
716
|
+
} else if (partition?.unavailable) {
|
|
717
|
+
counters.fallbackEvents.push(VECTOR_RUNTIME_INCOMPATIBLE);
|
|
718
|
+
vectorNotice = lexicalFallbackNotice(partition.unavailable);
|
|
696
719
|
} else {
|
|
697
720
|
if (embedResult.value.batchFailed) {
|
|
698
721
|
counters.fallbackEvents.push("vector_embed_batch_fallback");
|
|
@@ -709,7 +732,7 @@ async function searchHybridWithHydration(
|
|
|
709
732
|
new Float32Array(embedding),
|
|
710
733
|
variant.limit,
|
|
711
734
|
{
|
|
712
|
-
embeddingIdentity:
|
|
735
|
+
embeddingIdentity: partition?.identity,
|
|
713
736
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
714
737
|
eligibility: vectorEligibility,
|
|
715
738
|
}
|
|
@@ -1390,6 +1413,11 @@ async function searchHybridWithHydration(
|
|
|
1390
1413
|
trace: diagnoseTrace,
|
|
1391
1414
|
},
|
|
1392
1415
|
};
|
|
1416
|
+
if (vectorNotice)
|
|
1417
|
+
output.meta.warnings = [
|
|
1418
|
+
...(output.meta.warnings ?? []),
|
|
1419
|
+
{ code: VECTOR_RUNTIME_INCOMPATIBLE, message: vectorNotice },
|
|
1420
|
+
];
|
|
1393
1421
|
const fallbackCodes = [...new Set(counters.fallbackEvents)].sort();
|
|
1394
1422
|
const capabilityOutcomes = [
|
|
1395
1423
|
{ capability: "lexical_search", status: "used" as const },
|
|
@@ -1400,7 +1428,9 @@ async function searchHybridWithHydration(
|
|
|
1400
1428
|
status: "failed" as const,
|
|
1401
1429
|
reasonCode: fallbackCodes.includes("vector_embed_error")
|
|
1402
1430
|
? "vector_embed_error"
|
|
1403
|
-
:
|
|
1431
|
+
: vectorNotice
|
|
1432
|
+
? VECTOR_RUNTIME_INCOMPATIBLE
|
|
1433
|
+
: "vector_search_error",
|
|
1404
1434
|
}
|
|
1405
1435
|
: { capability: "semantic_search", status: "used" as const }
|
|
1406
1436
|
: {
|
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
} from "../llm/inference-scope";
|
|
22
22
|
import { getContentBatch } from "../store/content-batch";
|
|
23
23
|
import { err, ok } from "../store/types";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
resolveVectorSearchIdentity,
|
|
26
|
+
vectorSearchUnavailableMessage,
|
|
27
|
+
} from "../store/vector/variant-search";
|
|
25
28
|
import { createChunkLookup } from "./chunk-lookup";
|
|
26
29
|
import {
|
|
27
30
|
applyContentTypeBoost,
|
|
@@ -144,7 +147,16 @@ async function searchVectorWithEmbeddingOwned(
|
|
|
144
147
|
|
|
145
148
|
let embeddingIdentity;
|
|
146
149
|
try {
|
|
147
|
-
|
|
150
|
+
const partition = await resolveVectorSearchIdentity(
|
|
151
|
+
deps.embedPort,
|
|
152
|
+
vectorIndex
|
|
153
|
+
);
|
|
154
|
+
if (partition.unavailable)
|
|
155
|
+
return err(
|
|
156
|
+
"VEC_SEARCH_UNAVAILABLE",
|
|
157
|
+
vectorSearchUnavailableMessage(partition.unavailable)
|
|
158
|
+
);
|
|
159
|
+
embeddingIdentity = partition.identity;
|
|
148
160
|
} catch (cause) {
|
|
149
161
|
return err(
|
|
150
162
|
"QUERY_FAILED",
|