grepmax 0.26.70 → 0.26.71

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.70",
13
+ "version": "0.26.71",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -0,0 +1,113 @@
1
+ # Existing-index document search (contract v1)
2
+
3
+ `gmax mcp --existing-index-only` serves two read-only stdio MCP tools. The exact
4
+ entry dispatch precedes the normal CLI. Server identity is
5
+ `gmax-document-search`; the 2025-06-18 initialize exchange is supported by the
6
+ current MCP SDK. Contract version is independent of package version.
7
+
8
+ Every tool returns `structuredContent` with `contractVersion: 1` and capabilities
9
+ `{existingIndexOnly: 1, queryLogging: false, watch: false, runtimeStartup: false}`.
10
+ The text content contains only the result state. No source/snippet bodies are
11
+ returned. Runlist has no gmax, model or SDK runtime dependency.
12
+
13
+ - `document_search_status({paths})` returns `state: ready`, a required positive
14
+ safe-integer `generation`, `project: {root,store,lastIndexed}`,
15
+ actual metadata `covered` paths, `coverage: {requested,indexed,partial}`,
16
+ `embeddingReady`, `queryState` and index state. Readiness does not guarantee
17
+ worker availability when the subsequent query arrives. Coverage is not a
18
+ registry/store-wide chunk count and does not prove current source freshness.
19
+ Covered paths retain a validated `indexedMtimeMs` where available. A missing
20
+ sampled Markdown file reduces coverage instead of discarding other paths;
21
+ malformed or out-of-scope paths still refuse. Counts are recomputed, and
22
+ metadata uses a bounded field allowlist without free-form diagnostics.
23
+ - `semantic_search({query,prefixes})` returns `state: ready`, canonical `root`,
24
+ selected `store`, required `generation`, `matches` and index state. Each pointer is
25
+ `{path,startLine,endLine,score,hash?,hashAlgorithm?}`. Ranges are one-based full
26
+ source ranges, converted from indexed zero-based lines. Score is
27
+ `1/(1+max(0,cosineDistance))`, not a relevance probability.
28
+ - `hashAlgorithm: sha256-bytes` is declared only when the vector row digest agrees
29
+ with current version-1 metadata whose digest hashes the exact indexed bytes.
30
+ Legacy/mismatched provenance retains document-only pointers. Consumers must
31
+ reread and authorize current source, verify the digest and map body lines before
32
+ jumping to a section. This bridge never hashes new bytes to bless old lines.
33
+
34
+ Limits: 500 query characters, 32 explicit prefixes, 2,000 coverage paths and a
35
+ 512 KiB serialized path budget, 50 vector candidates, 1 MiB transport frames and
36
+ a 10-second bridge operation deadline. The most specific registered ancestor
37
+ of the canonical checkout selects the primary store; registered spelling is
38
+ preserved for daemon/row lookups. Coverage and prefixes must resolve inside the
39
+ checkout and selected project. Secondary stores are unavailable. No candidate
40
+ refill broadens scope. Symlink targets are rechecked before pointers are returned.
41
+
42
+ Unavailable states: `unsupported_tool`, `unsupported_daemon`, `no_index`,
43
+ `index_unavailable`, `daemon_unavailable`, `store_unavailable`,
44
+ `embedding_unavailable`, `embedding_mismatch`, `host_pressure`, `busy`,
45
+ `cancelled`, `timeout`, `no_coverage`, `search_unavailable`. Envelopes contain
46
+ stable states, never raw query-bearing diagnostics. Schema/unknown-tool errors
47
+ remain standard MCP errors.
48
+
49
+ The supported bridge negotiates daemon `existingIndexOnlySearch: 1` and resource
50
+ generation. Missing, null, non-integer and changed generations refuse; Runlist
51
+ also fences status against subsequent retrieval. Private daemon commands are `documents.status` and `documents.search`;
52
+ Runlist does not call the socket API. The daemon validates roots, stores, bounds
53
+ and generation again under its shared-operation coordinator, including after
54
+ encoding and native retrieval. Paused/contained resources refuse rather than
55
+ return keyword results.
56
+
57
+ Retrieval in this first slice is **dense-only** against an already-open LanceDB
58
+ connection/table. It never invokes normal hybrid search, FTS creation, reranking,
59
+ ColBERT inference, `ensureTable`, schema evolution, index mutation or local-store
60
+ fallback. Query encoding uses a currently idle, already-warm compatible worker:
61
+ CPU requires a loaded Granite tokenizer/session; GPU requires cached positive
62
+ confirmation of the existing configured MLX model. Idle time alone does not revoke
63
+ that identity: each bounded `/embed` response revalidates model and dimensions,
64
+ and successful inference renews the normal health cache. Failure revokes restricted
65
+ eligibility until external work confirms readiness. A worker rechecks before inference;
66
+ no initialization, health polling, retry or backend fallback is allowed. Cold or
67
+ busy workers refuse immediately without queueing, spawning or scaling.
68
+
69
+ Read-only admission samples existing host/ledger state without creating locks,
70
+ reservations, pruning records or changing containment markers. Existing Darwin
71
+ inference requires known-normal aggregate resources even if ordinary expansion
72
+ uses a looser policy. Other platforms retain the existing no-footprint-probe
73
+ policy; this is not a cross-platform native qualification claim.
74
+ An absent ledger in a fresh `critical-only` home is sampled as empty without being
75
+ created; existing corrupt or symlinked ledgers still refuse. Markdown coverage
76
+ and returned pointers require regular files, including after symlink resolution;
77
+ a directory whose name ends in `.md` can be a prefix but never a source pointer.
78
+
79
+ Cancellation closes only the request/owned bridge. It rejects the caller and
80
+ discards late results; an already executing shared worker remains assigned until
81
+ its terminal response or exit. Caller deadlines/heartbeats cannot trigger worker
82
+ replacement or native session initialization. Idle RSS retirement and consecutive
83
+ oversized completions remain enforced, including after restricted work, without
84
+ filling the worker floor on that path. Further restricted calls then report
85
+ unavailable; ordinary work may expand under ordinary admission. Busy inference is
86
+ never interrupted by RSS retirement. The daemon's independent safety/shutdown
87
+ machinery remains authoritative.
88
+
89
+ Native retrieval receives the remaining overall deadline and holds its shared
90
+ operation until the actual native promise settles. A JavaScript timer cannot
91
+ release resources that native code still uses. Caller cancellation/bridge timeout
92
+ remains bounded and late results are discarded. If the SDK fails to settle even
93
+ after its native deadline, exclusive resource teardown stays blocked rather than
94
+ closing underneath the read; no worker/store replacement is induced to hide the
95
+ failure. Cancellation cannot interrupt an already running ONNX native call.
96
+ Global logging settings are unchanged, and this path never logs query text.
97
+
98
+ ## Isolated qualification
99
+
100
+ Use `node scripts/build-document-search-candidate.cjs /absolute/new/output` to
101
+ compile/package without overwriting `dist`, installing, publishing, or invoking
102
+ lifecycle scripts. The script copies the pinned native JS runtime with provenance.
103
+ `node scripts/audit-document-search.cjs /absolute/extracted/package` exercises a
104
+ synthetic daemon and checks packaged files/import boundaries. The normal consumer
105
+ audit invokes that check against its freshly installed tarball too.
106
+
107
+ The optional Runlist fixture is
108
+ `RUNLIST_GMAX_QUALIFICATION_ENTRY=/absolute/extracted/package/dist/bin.js node --test desktop/test/semantic-gmax-contract.test.mjs`.
109
+ Fixture homes/IPC are private temporary directories; no real daemon, index,
110
+ watcher, model or grammar download is used. Reusing checkout dependencies for a
111
+ local package probe must be reported separately from fresh dependency resolution.
112
+ Installation, a shared-daemon restart, watcher recovery and live retrieval require
113
+ separate explicit actions. Source/package fixture success proves none of those.
package/README.md CHANGED
@@ -233,6 +233,20 @@ Architecture audits omit unresolved same-name definitions from rankings, cycles
233
233
 
234
234
  CLI `gmax skeleton --agent` and MCP `code_skeleton` text show at most 120 lines / 6,000 characters per file and report omitted lines and characters. CLI human output and JSON structure remain complete.
235
235
 
236
+ ## Existing-index document search
237
+
238
+ `gmax mcp --existing-index-only` exposes a separate read-only stdio MCP contract
239
+ for Markdown document coverage and semantic pointers. It uses an existing daemon,
240
+ index and already-warm compatible query worker. It cannot start a daemon, watch a
241
+ project, initialize a model, fall back to another backend or mutate the index.
242
+ Cold, busy or unavailable resources return an explicit unavailable state.
243
+
244
+ Results contain canonical paths and one-based source ranges, without cached source
245
+ bodies. The first slice uses dense retrieval with explicit path scope and bounded
246
+ deadlines. Consumers must reread and authorize current source before using a
247
+ pointer. See [the document-search contract](DOCUMENT-SEARCH.md) for tool schemas,
248
+ limits, hash provenance and isolated qualification.
249
+
236
250
  ## Background Daemon
237
251
 
238
252
  A single daemon watches your projects via native OS file events (FSEvents/inotify). Changes are detected in sub-second and incrementally reindexed. All writes to LanceDB are routed through the daemon via IPC, eliminating lock contention.
package/dist/bin.js CHANGED
@@ -152,6 +152,15 @@ function seedConfig(home) {
152
152
  }
153
153
  }
154
154
  if (require.main === module) {
155
- selectHome();
156
- require("./index");
155
+ if (process.argv.slice(2).join(" ") === "mcp --existing-index-only") {
156
+ require("./lib/mcp/document-search").runDocumentSearchBridge();
157
+ }
158
+ else if (process.argv.includes("--existing-index-only")) {
159
+ process.stderr.write("gmax: use exactly mcp --existing-index-only\n");
160
+ process.exitCode = 2;
161
+ }
162
+ else {
163
+ selectHome();
164
+ require("./index");
165
+ }
157
166
  }
@@ -84,6 +84,7 @@ const root_availability_1 = require("../utils/root-availability");
84
84
  const safety_latch_1 = require("../utils/safety-latch");
85
85
  const watcher_store_1 = require("../utils/watcher-store");
86
86
  const pool_1 = require("../workers/pool");
87
+ const document_search_handler_1 = require("./document-search-handler");
87
88
  const ipc_handler_1 = require("./ipc-handler");
88
89
  const mlx_server_manager_1 = require("./mlx-server-manager");
89
90
  const paused_reads_1 = require("./paused-reads");
@@ -1021,6 +1022,55 @@ class Daemon {
1021
1022
  ? { queue: progress.queue }
1022
1023
  : {})), { failedFiles: Math.max((_d = progress === null || progress === void 0 ? void 0 : progress.failedFiles) !== null && _d !== void 0 ? _d : 0, (_e = health.failedFiles) !== null && _e !== void 0 ? _e : 0) });
1023
1024
  }
1025
+ /** Separate shared-operation path: never paused keyword fallback or runtime setup. */
1026
+ documentSearch(cmd, signal) {
1027
+ return __awaiter(this, void 0, void 0, function* () {
1028
+ const state = () => this.pausedReason !== null || (0, autostart_1.daemonStartDeniedReason)()
1029
+ ? "host_pressure"
1030
+ : this.shuttingDown || this.recycling
1031
+ ? "busy"
1032
+ : !this.vectorDb || !this.metaCache
1033
+ ? "store_unavailable"
1034
+ : this.activeConfigurationError()
1035
+ ? "embedding_mismatch"
1036
+ : "ready";
1037
+ return this.operations.runShared("documents", signal, (operationSignal) => (0, document_search_handler_1.handleDocumentSearch)({
1038
+ home: config_1.PATHS.globalRoot,
1039
+ state,
1040
+ generation: () => this.resourceGenerationId(),
1041
+ embeddingState: (root) => {
1042
+ const project = (0, project_registry_1.getProject)(root);
1043
+ if ((project === null || project === void 0 ? void 0 : project.status) !== "indexed")
1044
+ return "no_index";
1045
+ if (!this.activeGeneration)
1046
+ return "embedding_unavailable";
1047
+ if (this.workerPool &&
1048
+ this.workerPool.generation.fingerprint !==
1049
+ this.activeGeneration.fingerprint)
1050
+ return "embedding_mismatch";
1051
+ try {
1052
+ return (0, embedding_generation_1.compareEmbeddingGeneration)(project, this.activeGeneration)
1053
+ .state === "stale"
1054
+ ? "embedding_mismatch"
1055
+ : "ready";
1056
+ }
1057
+ catch (_a) {
1058
+ return "embedding_mismatch";
1059
+ }
1060
+ },
1061
+ meta: (file) => { var _a; return (_a = this.metaCache) === null || _a === void 0 ? void 0 : _a.get(file); },
1062
+ queryState: () => { var _a; var _b; return (_b = (_a = this.workerPool) === null || _a === void 0 ? void 0 : _a.existingQueryState()) !== null && _b !== void 0 ? _b : "embedding_unavailable"; },
1063
+ encode: (query, requestSignal) => this.workerPool
1064
+ ? this.workerPool.encodeQueryExisting(query, requestSignal)
1065
+ : Promise.reject(new Error("embedding_unavailable")),
1066
+ table: () => this.vectorDb
1067
+ ? this.vectorDb.existingTableForRead()
1068
+ : Promise.reject(new Error("store_unavailable")),
1069
+ indexState: (root) => this.indexState(root),
1070
+ lastIndexed: (root) => { var _a; return (_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.lastIndexed; },
1071
+ }, cmd, operationSignal));
1072
+ });
1073
+ }
1024
1074
  search(payload, signal) {
1025
1075
  return __awaiter(this, void 0, void 0, function* () {
1026
1076
  // Search handling lives in search-handler.ts (Phase 12 split). The daemon
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.documentFilter = documentFilter;
46
+ exports.handleDocumentSearch = handleDocumentSearch;
47
+ const path = __importStar(require("node:path"));
48
+ const document_contract_1 = require("../mcp/document-contract");
49
+ function sqlString(input) {
50
+ return `'${input.replace(/'/g, "''")}'`;
51
+ }
52
+ function documentFilter(prefixes) {
53
+ return `(${prefixes
54
+ .map((prefix) => {
55
+ const dir = prefix.endsWith(path.sep) ? prefix : prefix + path.sep;
56
+ return `(path = ${sqlString(prefix)} OR (path >= ${sqlString(dir)} AND path < ${sqlString(`${dir}\uffff`)}))`;
57
+ })
58
+ .join(" OR ")}) AND lower(path) LIKE '%.md'`;
59
+ }
60
+ function handleDocumentSearch(deps, cmd, signal) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ var _a, _b;
63
+ var _c, _d;
64
+ try {
65
+ const deadline = performance.now() + document_contract_1.DOCUMENT_LIMITS.deadlineMs;
66
+ signal = AbortSignal.any([
67
+ signal,
68
+ AbortSignal.timeout(document_contract_1.DOCUMENT_LIMITS.deadlineMs),
69
+ ]);
70
+ signal.throwIfAborted();
71
+ if (cmd.contractVersion !== document_contract_1.DOCUMENT_CONTRACT_VERSION)
72
+ return (0, document_contract_1.documentFailure)("unsupported_tool");
73
+ if (deps.state() !== "ready")
74
+ return (0, document_contract_1.documentFailure)(deps.state());
75
+ if (typeof cmd.checkout !== "string")
76
+ return (0, document_contract_1.documentFailure)("no_index");
77
+ const context = (0, document_contract_1.selectDocumentContext)(deps.home, cmd.checkout);
78
+ if (cmd.projectRoot !== context.wireRoot || cmd.store !== context.store)
79
+ return (0, document_contract_1.documentFailure)("store_unavailable");
80
+ const embeddingState = deps.embeddingState(context.wireRoot);
81
+ if (embeddingState !== "ready")
82
+ return (0, document_contract_1.documentFailure)(embeddingState);
83
+ const generation = deps.generation();
84
+ if (!(0, document_contract_1.documentGeneration)(generation))
85
+ return (0, document_contract_1.documentFailure)("index_unavailable");
86
+ if (!(0, document_contract_1.documentGeneration)(cmd.generation) || cmd.generation !== generation)
87
+ return (0, document_contract_1.documentFailure)("embedding_mismatch");
88
+ const recheck = () => {
89
+ signal.throwIfAborted();
90
+ if (performance.now() >= deadline)
91
+ throw new Error("timeout");
92
+ if (deps.state() !== "ready")
93
+ throw new Error(deps.state());
94
+ if (deps.generation() !== generation ||
95
+ deps.embeddingState(context.wireRoot) !== "ready")
96
+ throw new Error("embedding_mismatch");
97
+ };
98
+ const base = {
99
+ ok: true,
100
+ root: context.root,
101
+ store: context.store,
102
+ generation,
103
+ };
104
+ if (cmd.cmd === "documents.status") {
105
+ const { paths, requested } = (0, document_contract_1.documentCoveragePaths)(cmd.paths, context);
106
+ const covered = paths.flatMap((file) => {
107
+ const entry = deps.meta(file);
108
+ return (entry === null || entry === void 0 ? void 0 : entry.hasVectors) === true
109
+ ? [
110
+ Object.assign(Object.assign({ path: file }, (entry.hashVersion === 1 && /^[a-f0-9]{64}$/.test(entry.hash)
111
+ ? { hash: entry.hash, hashAlgorithm: "sha256-bytes" }
112
+ : {})), ((0, document_contract_1.documentMtime)(entry.mtimeMs) !== null
113
+ ? { indexedMtimeMs: entry.mtimeMs }
114
+ : {})),
115
+ ]
116
+ : [];
117
+ });
118
+ recheck();
119
+ const state = (0, document_contract_1.documentQueryState)(deps.queryState());
120
+ return Object.assign(Object.assign({}, base), { embeddingReady: state === "ready", queryState: state, lastIndexed: (_c = (0, document_contract_1.documentIndexedAt)((_a = deps.lastIndexed) === null || _a === void 0 ? void 0 : _a.call(deps, context.wireRoot))) !== null && _c !== void 0 ? _c : context.lastIndexed, indexState: (0, document_contract_1.documentIndexState)(deps.indexState(context.wireRoot)), covered, coverage: {
121
+ requested,
122
+ indexed: covered.length,
123
+ partial: covered.length !== requested,
124
+ } });
125
+ }
126
+ if (cmd.cmd !== "documents.search")
127
+ return (0, document_contract_1.documentFailure)("unsupported_tool");
128
+ if (typeof cmd.query !== "string" ||
129
+ !cmd.query.trim() ||
130
+ cmd.query.length > document_contract_1.DOCUMENT_LIMITS.query)
131
+ return (0, document_contract_1.documentFailure)("search_unavailable");
132
+ const prefixes = (0, document_contract_1.documentPaths)(cmd.prefixes, context, "prefixes");
133
+ const state = deps.queryState();
134
+ if (state !== "ready")
135
+ return (0, document_contract_1.documentFailure)(state);
136
+ recheck();
137
+ const encoded = yield deps.encode(cmd.query, signal);
138
+ recheck();
139
+ if (!encoded.dense.length || !encoded.dense.every(Number.isFinite))
140
+ return (0, document_contract_1.documentFailure)("embedding_unavailable");
141
+ const table = yield deps.table();
142
+ recheck();
143
+ const remainingMs = Math.floor(deadline - performance.now());
144
+ if (remainingMs <= 0)
145
+ throw new Error("timeout");
146
+ // Await the actual native promise, not a JS timeout race. The caller's
147
+ // bridge/signal has its own deadline; shared ownership must remain until
148
+ // native completion, even if the SDK fails to honor its native timeout.
149
+ const rows = (yield table
150
+ .vectorSearch(encoded.dense)
151
+ .distanceType("cosine")
152
+ .where(documentFilter(prefixes))
153
+ .select(["path", "hash", "start_line", "end_line", "_distance"])
154
+ .limit(document_contract_1.DOCUMENT_LIMITS.candidates)
155
+ .toArray({ timeoutMs: remainingMs }));
156
+ recheck();
157
+ const matches = rows.slice(0, document_contract_1.DOCUMENT_LIMITS.candidates).flatMap((row) => {
158
+ if (typeof row.path !== "string" ||
159
+ !prefixes.some((prefix) => row.path === prefix || row.path.startsWith(prefix + path.sep)))
160
+ return [];
161
+ const file = (0, document_contract_1.canonicalPointer)(row.path, context);
162
+ if (!file ||
163
+ !Number.isInteger(row.start_line) ||
164
+ row.start_line < 0 ||
165
+ !Number.isInteger(row.end_line) ||
166
+ row.end_line < row.start_line ||
167
+ !Number.isFinite(row._distance))
168
+ return [];
169
+ const entry = deps.meta(row.path);
170
+ // Only a matching current metadata revision proves this row's digest is bytes.
171
+ const hash = (entry === null || entry === void 0 ? void 0 : entry.hashVersion) === 1 &&
172
+ entry.hasVectors === true &&
173
+ row.hash === entry.hash &&
174
+ /^[a-f0-9]{64}$/.test(row.hash)
175
+ ? { hash: row.hash, hashAlgorithm: "sha256-bytes" }
176
+ : {};
177
+ return [
178
+ Object.assign({ path: file, startLine: row.start_line + 1, endLine: row.end_line + 1, score: 1 / (1 + Math.max(0, row._distance)) }, hash),
179
+ ];
180
+ });
181
+ return Object.assign(Object.assign({}, base), { indexState: (0, document_contract_1.documentIndexState)(deps.indexState(context.wireRoot)), matches, retrieval: "dense", candidateLimit: document_contract_1.DOCUMENT_LIMITS.candidates });
182
+ }
183
+ catch (error) {
184
+ const e = error;
185
+ return (0, document_contract_1.documentFailure)(signal.aborted
186
+ ? ((_b = signal.reason) === null || _b === void 0 ? void 0 : _b.name) === "TimeoutError"
187
+ ? "timeout"
188
+ : "cancelled"
189
+ : e.name === "QueryTimeoutError"
190
+ ? "timeout"
191
+ : ((_d = e.code) !== null && _d !== void 0 ? _d : e.message));
192
+ }
193
+ });
194
+ }
@@ -133,13 +133,17 @@ function handleCommand(daemon, cmd, conn) {
133
133
  cmd.cmd !== "shutdown" &&
134
134
  (daemon.operationStatus() === "closing" ||
135
135
  daemon.operationStatus() === "closed")) {
136
- return errorResponse(new operation_coordinator_1.OperationClosedError());
136
+ return cmd.cmd === "documents.status" || cmd.cmd === "documents.search"
137
+ ? { ok: false, state: "busy" }
138
+ : errorResponse(new operation_coordinator_1.OperationClosedError());
137
139
  }
138
140
  // The socket listens before LanceDB/MetaCache are open so liveness probes
139
141
  // succeed during slow startup. Gate resource-dependent commands until those
140
142
  // stores exist; ping/shutdown must always work (probes + restart).
141
143
  if (cmd.cmd !== "ping" && cmd.cmd !== "shutdown" && !daemon.isReady()) {
142
- return { ok: false, error: "daemon initializing" };
144
+ return cmd.cmd === "documents.status" || cmd.cmd === "documents.search"
145
+ ? { ok: false, state: "index_unavailable" }
146
+ : { ok: false, error: "daemon initializing" };
143
147
  }
144
148
  switch (cmd.cmd) {
145
149
  case "ping":
@@ -157,6 +161,7 @@ function handleCommand(daemon, cmd, conn) {
157
161
  exclusiveGenerationRebuild: exports.EXCLUSIVE_GENERATION_REBUILD_PROTOCOL,
158
162
  readVerbs: read_verbs_1.READ_VERBS_PROTOCOL,
159
163
  searchDiagnostics: 1,
164
+ existingIndexOnlySearch: 1,
160
165
  perFileSearch: 1,
161
166
  // `watch`/`unwatch` accept holder/pid/ttlMs. A daemon without this
162
167
  // treats `unwatch` as unwatch-for-everyone, so clients releasing a
@@ -225,6 +230,28 @@ function handleCommand(daemon, cmd, conn) {
225
230
  setImmediate(() => daemon.shutdown());
226
231
  return { ok: true };
227
232
  }
233
+ case "documents.status":
234
+ case "documents.search": {
235
+ const ac = new AbortController();
236
+ const close = () => ac.abort();
237
+ conn.once("close", close);
238
+ try {
239
+ return yield daemon.documentSearch(cmd, ac.signal);
240
+ }
241
+ catch (error) {
242
+ return {
243
+ ok: false,
244
+ state: ac.signal.aborted
245
+ ? "cancelled"
246
+ : error instanceof operation_coordinator_1.OperationBusyError
247
+ ? "busy"
248
+ : "index_unavailable",
249
+ };
250
+ }
251
+ finally {
252
+ conn.off("close", close);
253
+ }
254
+ }
228
255
  case "search":
229
256
  case "search-v2": {
230
257
  const isV2 = cmd.cmd === "search-v2";