vde-open 0.1.0 → 0.1.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 +19 -10
- package/dist/cli.js +2 -2
- package/dist/daemon.js +2 -2
- package/dist/{heap-Dev_gSQn.js → heap-C3qHm7SE.js} +9 -4
- package/dist/{main-lxBamyBU.js → main-CLzVgxSL.js} +179 -40
- package/dist/{search-index-C0szMSlZ.js → search-index-CNsfwB72.js} +30 -5
- package/dist/workers/parse-worker.js +5 -1
- package/dist/workers/search-worker.js +8 -5
- package/package.json +1 -1
- package/skills/vde-open/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -11,21 +11,27 @@ A local document viewer for agents and people who work from the same material. O
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
-
Node.js 24 or later is required
|
|
14
|
+
Node.js 24 or later is required.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
pnpm test:pack # builds artifacts/vde-open-0.1.0.tgz and verifies an install in a separate directory
|
|
20
|
-
bun add -g ./artifacts/vde-open-0.1.0.tgz # recommended: user-level install into ~/.bun/bin
|
|
17
|
+
bun add -g vde-open # recommended: user-level install into ~/.bun/bin
|
|
18
|
+
npm install -g vde-open # also works
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
- We recommend installing it once per user with Bun. `~/.bun/bin` does not depend on which Node.js version is active, so switching Node.js versions (with mise and similar tools) does not remove `vo`. Bun is only used to install; the commands run on Node.js (`#!/usr/bin/env node`), so Node.js 24 or later must be on your `PATH`. Running it on the Bun runtime (`bun --bun`) is not tested.
|
|
24
|
-
- `npm install -g
|
|
22
|
+
- `npm install -g` installs into the prefix of the active Node.js version.
|
|
25
23
|
- Installing it per project is not recommended. There is one daemon per user, so different versions in different projects would talk to the same daemon.
|
|
26
|
-
- Always start the path with `./`. Without it, the path is treated as a GitHub repository name.
|
|
27
24
|
|
|
28
|
-
Installing does not change shell files such as `.zshrc`, and runs no build or install scripts (the
|
|
25
|
+
Installing does not change shell files such as `.zshrc`, and runs no build or install scripts (the package bundles every dependency). Releases are published from GitHub Actions with provenance (see "Releasing").
|
|
26
|
+
|
|
27
|
+
To install from a clone of this repository (pins Node.js 24.21.0 in `mise.toml`):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm install --frozen-lockfile
|
|
31
|
+
pnpm build
|
|
32
|
+
pnpm test:pack # builds artifacts/vde-open-<version>.tgz and verifies an install in a separate directory
|
|
33
|
+
bun add -g ./artifacts/vde-open-0.1.1.tgz # always start the path with ./ (otherwise it is read as a GitHub repository)
|
|
34
|
+
```
|
|
29
35
|
|
|
30
36
|
## `vde-open` and `vo`
|
|
31
37
|
|
|
@@ -59,8 +65,11 @@ How agents should use it is described in [docs/agent-usage.md](docs/agent-usage.
|
|
|
59
65
|
[`skills/vde-open/SKILL.md`](skills/vde-open/SKILL.md) is a skill that teaches an agent (Claude Code, Codex, and others that read `SKILL.md`) when and how to use `vo`. It is also included in the package. To use it, link or copy the directory into your agent's skill directory:
|
|
60
66
|
|
|
61
67
|
```bash
|
|
62
|
-
|
|
63
|
-
ln -s
|
|
68
|
+
# Installed with Bun: the skill is in the global package directory.
|
|
69
|
+
ln -s ~/.bun/install/global/node_modules/vde-open/skills/vde-open ~/.claude/skills/vde-open # Claude Code
|
|
70
|
+
ln -s ~/.bun/install/global/node_modules/vde-open/skills/vde-open ~/.codex/skills/vde-open # Codex
|
|
71
|
+
# Installed with npm: use "$(npm root -g)/vde-open/skills/vde-open" instead.
|
|
72
|
+
# From a clone of this repository: use "$PWD/skills/vde-open".
|
|
64
73
|
```
|
|
65
74
|
|
|
66
75
|
## Search scope
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as decodeStateFile, D as daemonEntryPath, E as resolveStateRoot, O as referencedBlobs, S as renameWithRetry, T as resolveRuntimeLocation, a as DAEMON_LOCK_NAME, b as proofMatches, c as readIpcKey, d as inspectOwner, f as readCurrentLock, g as encodeFrame, h as FrameDecoder, i as requirePrivateDirectoryIfExists, k as version, l as readPointer, m as systemProcessProbe, n as ensurePrivateDirectory, o as POINTER_FILE, p as releaseLock, r as inspectPrivateDirectory, s as START_LOCK_NAME, t as startDaemon, u as acquireLock, v as computeProof, w as currentPathEnvironment, x as removeWithRetry, y as createNonce } from "./main-
|
|
3
|
-
import { A as successEnvelope, C as VdeError, D as ExitCode, E as isVdeError, O as envelopeSchema, T as isErrorCode, Y as LIMITS, k as errorEnvelope, w as exitCodeForError } from "./search-index-
|
|
2
|
+
import { C as decodeStateFile, D as daemonEntryPath, E as resolveStateRoot, O as referencedBlobs, S as renameWithRetry, T as resolveRuntimeLocation, a as DAEMON_LOCK_NAME, b as proofMatches, c as readIpcKey, d as inspectOwner, f as readCurrentLock, g as encodeFrame, h as FrameDecoder, i as requirePrivateDirectoryIfExists, k as version, l as readPointer, m as systemProcessProbe, n as ensurePrivateDirectory, o as POINTER_FILE, p as releaseLock, r as inspectPrivateDirectory, s as START_LOCK_NAME, t as startDaemon, u as acquireLock, v as computeProof, w as currentPathEnvironment, x as removeWithRetry, y as createNonce } from "./main-CLzVgxSL.js";
|
|
3
|
+
import { A as successEnvelope, C as VdeError, D as ExitCode, E as isVdeError, O as envelopeSchema, T as isErrorCode, Y as LIMITS, k as errorEnvelope, w as exitCodeForError } from "./search-index-CNsfwB72.js";
|
|
4
4
|
import fs, { fstatSync } from "node:fs";
|
|
5
5
|
import { lstat, readFile, readdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
6
6
|
import path, { join, resolve } from "node:path";
|
package/dist/daemon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { k as version, t as startDaemon, w as currentPathEnvironment } from "./main-
|
|
2
|
-
import { E as isVdeError } from "./search-index-
|
|
1
|
+
import { k as version, t as startDaemon, w as currentPathEnvironment } from "./main-CLzVgxSL.js";
|
|
2
|
+
import { E as isVdeError } from "./search-index-CNsfwB72.js";
|
|
3
3
|
//#region src/daemon.ts
|
|
4
4
|
function notifyParent(message) {
|
|
5
5
|
if (!process.send) return;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { getHeapStatistics, setFlagsFromString } from "node:v8";
|
|
2
2
|
import { runInNewContext } from "node:vm";
|
|
3
3
|
//#region src/diagnostics/heap.ts
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
let gc = null;
|
|
5
|
+
function collectGarbage() {
|
|
6
|
+
if (gc === null) {
|
|
6
7
|
setFlagsFromString("--expose-gc");
|
|
7
|
-
runInNewContext("gc")
|
|
8
|
+
gc = runInNewContext("gc");
|
|
8
9
|
}
|
|
10
|
+
gc();
|
|
11
|
+
}
|
|
12
|
+
function measureHeap(collect) {
|
|
13
|
+
if (collect) collectGarbage();
|
|
9
14
|
return getHeapStatistics().used_heap_size;
|
|
10
15
|
}
|
|
11
16
|
//#endregion
|
|
12
|
-
export { measureHeap as t };
|
|
17
|
+
export { measureHeap as n, collectGarbage as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "./rolldown-runtime-DC62tzP2.js";
|
|
2
|
-
import { $ as literal, A as successEnvelope, B as readParamsSchema, C as VdeError, D as ExitCode, E as isVdeError, F as htmlModeChangeParamsSchema, G as sha256Schema, H as reorderParamsSchema, I as htmlModeSchema, J as watchRuleSchema, K as sourceKindSchema, L as linkOpenParamsSchema, M as closeParamsSchema, N as documentFormatSchema, P as documentIdSchema, Q as boolean, R as listParamsSchema, S as validateAnswers, U as revisionSchema, V as renderGrantParamsSchema, W as searchParamsSchema, X as _enum, Y as LIMITS, Z as array, _ as feedbackWaitParamsSchema, a as uniqueTokens, at as canonicalJson, b as submissionSchema, c as feedbackAckParamsSchema, d as feedbackDraftParamsSchema, et as number, f as feedbackForgetParamsSchema, g as feedbackSubmitParamsSchema, h as feedbackStatusSchema, i as codePointLength, it as ZodError, j as assetRecordSchema, k as errorEnvelope, l as feedbackCancelParamsSchema, m as feedbackListParamsSchema, n as batchesOf, nt as strictObject, o as answersSchema, p as feedbackIdParamsSchema, q as sourceStateSchema, r as partsOf, rt as string, s as cancellationSchema, tt as record, u as feedbackCreateParamsSchema, v as questionnaireSchema, w as exitCodeForError, x as utf8Length, y as requestIdSchema, z as openParamsSchema } from "./search-index-
|
|
2
|
+
import { $ as literal, A as successEnvelope, B as readParamsSchema, C as VdeError, D as ExitCode, E as isVdeError, F as htmlModeChangeParamsSchema, G as sha256Schema, H as reorderParamsSchema, I as htmlModeSchema, J as watchRuleSchema, K as sourceKindSchema, L as linkOpenParamsSchema, M as closeParamsSchema, N as documentFormatSchema, P as documentIdSchema, Q as boolean, R as listParamsSchema, S as validateAnswers, U as revisionSchema, V as renderGrantParamsSchema, W as searchParamsSchema, X as _enum, Y as LIMITS, Z as array, _ as feedbackWaitParamsSchema, a as uniqueTokens, at as canonicalJson, b as submissionSchema, c as feedbackAckParamsSchema, d as feedbackDraftParamsSchema, et as number, f as feedbackForgetParamsSchema, g as feedbackSubmitParamsSchema, h as feedbackStatusSchema, i as codePointLength, it as ZodError, j as assetRecordSchema, k as errorEnvelope, l as feedbackCancelParamsSchema, m as feedbackListParamsSchema, n as batchesOf, nt as strictObject, o as answersSchema, p as feedbackIdParamsSchema, q as sourceStateSchema, r as partsOf, rt as string, s as cancellationSchema, tt as record, u as feedbackCreateParamsSchema, v as questionnaireSchema, w as exitCodeForError, x as utf8Length, y as requestIdSchema, z as openParamsSchema } from "./search-index-CNsfwB72.js";
|
|
3
3
|
import { a as analyzeDocument, c as classifyReference, d as hasHiddenSegment, f as isValidLogicalPath, i as ParseLimitError, l as dirnameOfLogicalPath, n as scanReferences, o as assetTypeOf, p as roleAllowed, r as extractTitle, s as classifyLink, u as encodeLogicalPath } from "./render-yCayIbhf.js";
|
|
4
|
-
import {
|
|
4
|
+
import { n as measureHeap, t as collectGarbage } from "./heap-C3qHm7SE.js";
|
|
5
5
|
import { constants, existsSync, stat, unwatchFile, watch, watchFile } from "node:fs";
|
|
6
6
|
import { appendFile, chmod, link, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, rmdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
7
7
|
import * as sp from "node:path";
|
|
@@ -17,6 +17,8 @@ import { fileURLToPath } from "url";
|
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
import { fileURLToPath as fileURLToPath$1 } from "node:url";
|
|
19
19
|
import { Worker } from "node:worker_threads";
|
|
20
|
+
import { performance } from "node:perf_hooks";
|
|
21
|
+
import { getHeapStatistics } from "node:v8";
|
|
20
22
|
import { STATUS_CODES, ServerResponse, createServer } from "node:http";
|
|
21
23
|
import { Http2ServerRequest, constants as constants$1 } from "node:http2";
|
|
22
24
|
import { Readable } from "node:stream";
|
|
@@ -152,7 +154,7 @@ function parseStrictJson(text) {
|
|
|
152
154
|
}
|
|
153
155
|
//#endregion
|
|
154
156
|
//#region package.json
|
|
155
|
-
var version = "0.1.
|
|
157
|
+
var version = "0.1.1";
|
|
156
158
|
//#endregion
|
|
157
159
|
//#region src/documents/cursor.ts
|
|
158
160
|
function invalidCursor(reason) {
|
|
@@ -6063,6 +6065,108 @@ function createRenderService(options) {
|
|
|
6063
6065
|
};
|
|
6064
6066
|
}
|
|
6065
6067
|
//#endregion
|
|
6068
|
+
//#region src/diagnostics/idle-collect.ts
|
|
6069
|
+
const COLLECT_AFTER_IDLE_MS = 2e3;
|
|
6070
|
+
function createIdleTrigger(run, delayMs = COLLECT_AFTER_IDLE_MS) {
|
|
6071
|
+
let timer = null;
|
|
6072
|
+
const cancel = () => {
|
|
6073
|
+
if (timer) clearTimeout(timer);
|
|
6074
|
+
timer = null;
|
|
6075
|
+
};
|
|
6076
|
+
return {
|
|
6077
|
+
touch() {
|
|
6078
|
+
cancel();
|
|
6079
|
+
timer = setTimeout(() => {
|
|
6080
|
+
timer = null;
|
|
6081
|
+
run();
|
|
6082
|
+
}, delayMs);
|
|
6083
|
+
timer.unref();
|
|
6084
|
+
},
|
|
6085
|
+
cancel
|
|
6086
|
+
};
|
|
6087
|
+
}
|
|
6088
|
+
function createWorkerCollect(options) {
|
|
6089
|
+
let collecting = null;
|
|
6090
|
+
let closed = false;
|
|
6091
|
+
const held = [];
|
|
6092
|
+
const release = () => {
|
|
6093
|
+
if (collecting) clearTimeout(collecting.timer);
|
|
6094
|
+
collecting = null;
|
|
6095
|
+
for (const request of held.splice(0)) request.send();
|
|
6096
|
+
};
|
|
6097
|
+
const idle = createIdleTrigger(() => {
|
|
6098
|
+
const worker = options.worker();
|
|
6099
|
+
if (closed || worker === null || collecting) return;
|
|
6100
|
+
if (options.busy()) {
|
|
6101
|
+
idle.touch();
|
|
6102
|
+
return;
|
|
6103
|
+
}
|
|
6104
|
+
const id = options.nextId();
|
|
6105
|
+
const timer = setTimeout(release, options.waitLimitMs ?? 1e4);
|
|
6106
|
+
timer.unref();
|
|
6107
|
+
collecting = {
|
|
6108
|
+
id,
|
|
6109
|
+
timer
|
|
6110
|
+
};
|
|
6111
|
+
worker.postMessage({
|
|
6112
|
+
id,
|
|
6113
|
+
op: "collect"
|
|
6114
|
+
});
|
|
6115
|
+
}, options.delayMs);
|
|
6116
|
+
return {
|
|
6117
|
+
dispatch(send, reject) {
|
|
6118
|
+
if (collecting) held.push({
|
|
6119
|
+
send,
|
|
6120
|
+
reject
|
|
6121
|
+
});
|
|
6122
|
+
else send();
|
|
6123
|
+
},
|
|
6124
|
+
touch: () => idle.touch(),
|
|
6125
|
+
settle(id) {
|
|
6126
|
+
if (collecting?.id !== id) return false;
|
|
6127
|
+
release();
|
|
6128
|
+
return true;
|
|
6129
|
+
},
|
|
6130
|
+
reset: () => {
|
|
6131
|
+
if (collecting) release();
|
|
6132
|
+
},
|
|
6133
|
+
close(error) {
|
|
6134
|
+
closed = true;
|
|
6135
|
+
idle.cancel();
|
|
6136
|
+
if (collecting) clearTimeout(collecting.timer);
|
|
6137
|
+
collecting = null;
|
|
6138
|
+
for (const request of held.splice(0)) request.reject(error);
|
|
6139
|
+
}
|
|
6140
|
+
};
|
|
6141
|
+
}
|
|
6142
|
+
function utilizationSincePreviousCall() {
|
|
6143
|
+
let previous = performance.eventLoopUtilization();
|
|
6144
|
+
return () => {
|
|
6145
|
+
const current = performance.eventLoopUtilization();
|
|
6146
|
+
const { utilization } = performance.eventLoopUtilization(current, previous);
|
|
6147
|
+
previous = current;
|
|
6148
|
+
return utilization;
|
|
6149
|
+
};
|
|
6150
|
+
}
|
|
6151
|
+
function startHeapWatch(options = {}) {
|
|
6152
|
+
const growthBytes = options.growthBytes ?? 8388608;
|
|
6153
|
+
const idleUtilization = options.idleUtilization ?? .05;
|
|
6154
|
+
const heapUsed = options.heapUsed ?? (() => getHeapStatistics().used_heap_size);
|
|
6155
|
+
const utilization = options.utilization ?? utilizationSincePreviousCall();
|
|
6156
|
+
const collect = options.collect ?? collectGarbage;
|
|
6157
|
+
let baseline = heapUsed();
|
|
6158
|
+
const timer = setInterval(() => {
|
|
6159
|
+
const busy = utilization() >= idleUtilization;
|
|
6160
|
+
const used = heapUsed();
|
|
6161
|
+
baseline = Math.min(baseline, used);
|
|
6162
|
+
if (busy || used - baseline < growthBytes) return;
|
|
6163
|
+
collect();
|
|
6164
|
+
baseline = heapUsed();
|
|
6165
|
+
}, options.intervalMs ?? 2e3);
|
|
6166
|
+
timer.unref();
|
|
6167
|
+
return { stop: () => clearInterval(timer) };
|
|
6168
|
+
}
|
|
6169
|
+
//#endregion
|
|
6066
6170
|
//#region src/diagnostics/until-aborted.ts
|
|
6067
6171
|
function untilAborted(promise, signal) {
|
|
6068
6172
|
if (!signal) return promise;
|
|
@@ -6148,6 +6252,15 @@ function createSearchService(options) {
|
|
|
6148
6252
|
let closed = false;
|
|
6149
6253
|
let indexedAt = now().toISOString();
|
|
6150
6254
|
let syncing = null;
|
|
6255
|
+
const collector = createWorkerCollect({
|
|
6256
|
+
worker: () => worker,
|
|
6257
|
+
busy: () => pending.size > 0,
|
|
6258
|
+
nextId: () => {
|
|
6259
|
+
const id = nextId;
|
|
6260
|
+
nextId += 1;
|
|
6261
|
+
return id;
|
|
6262
|
+
}
|
|
6263
|
+
});
|
|
6151
6264
|
let syncRequested = false;
|
|
6152
6265
|
const failAll = (error) => {
|
|
6153
6266
|
for (const waiter of pending.values()) {
|
|
@@ -6168,6 +6281,7 @@ function createSearchService(options) {
|
|
|
6168
6281
|
const created = new Worker(workerPath);
|
|
6169
6282
|
created.unref();
|
|
6170
6283
|
created.on("message", (reply) => {
|
|
6284
|
+
if (collector.settle(reply.id)) return;
|
|
6171
6285
|
const waiter = pending.get(reply.id);
|
|
6172
6286
|
if (!waiter) return;
|
|
6173
6287
|
pending.delete(reply.id);
|
|
@@ -6178,31 +6292,36 @@ function createSearchService(options) {
|
|
|
6178
6292
|
created.on("error", () => {
|
|
6179
6293
|
if (worker === created) discardWorker();
|
|
6180
6294
|
failAll(new VdeError("E_INDEX_NOT_READY", "The search index is not available."));
|
|
6295
|
+
collector.reset();
|
|
6181
6296
|
});
|
|
6182
6297
|
worker = created;
|
|
6183
6298
|
return created;
|
|
6184
6299
|
};
|
|
6185
6300
|
const request = (work) => {
|
|
6186
6301
|
if (closed) return Promise.reject(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6302
|
+
const result = new Promise((resolve, reject) => {
|
|
6303
|
+
collector.dispatch(() => {
|
|
6304
|
+
const id = nextId;
|
|
6305
|
+
nextId += 1;
|
|
6306
|
+
const timer = setTimeout(() => {
|
|
6307
|
+
pending.delete(id);
|
|
6308
|
+
discardWorker();
|
|
6309
|
+
failAll(new VdeError("E_INDEX_NOT_READY", "The search index is not available."));
|
|
6310
|
+
reject(new VdeError("E_INDEX_NOT_READY", "The search index operation did not finish in time.", { reason: "timeout" }));
|
|
6311
|
+
}, timeoutMs);
|
|
6312
|
+
pending.set(id, {
|
|
6313
|
+
resolve,
|
|
6314
|
+
reject,
|
|
6315
|
+
timer
|
|
6316
|
+
});
|
|
6317
|
+
ensureWorker().postMessage({
|
|
6318
|
+
id,
|
|
6319
|
+
...work
|
|
6320
|
+
});
|
|
6321
|
+
}, reject);
|
|
6205
6322
|
});
|
|
6323
|
+
if (work.op !== "diagnostics") result.then(collector.touch, collector.touch);
|
|
6324
|
+
return result;
|
|
6206
6325
|
};
|
|
6207
6326
|
const analyzeOnce = async (format, text) => {
|
|
6208
6327
|
try {
|
|
@@ -6513,6 +6632,7 @@ function createSearchService(options) {
|
|
|
6513
6632
|
},
|
|
6514
6633
|
async close() {
|
|
6515
6634
|
closed = true;
|
|
6635
|
+
collector.close(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
6516
6636
|
failAll(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
6517
6637
|
const current = worker;
|
|
6518
6638
|
worker = null;
|
|
@@ -12505,6 +12625,15 @@ function createParseService(options = {}) {
|
|
|
12505
12625
|
let nextId = 1;
|
|
12506
12626
|
let closed = false;
|
|
12507
12627
|
const pending = /* @__PURE__ */ new Map();
|
|
12628
|
+
const collector = createWorkerCollect({
|
|
12629
|
+
worker: () => worker,
|
|
12630
|
+
busy: () => pending.size > 0,
|
|
12631
|
+
nextId: () => {
|
|
12632
|
+
const id = nextId;
|
|
12633
|
+
nextId += 1;
|
|
12634
|
+
return id;
|
|
12635
|
+
}
|
|
12636
|
+
});
|
|
12508
12637
|
const failAll = (error) => {
|
|
12509
12638
|
for (const waiter of pending.values()) {
|
|
12510
12639
|
clearTimeout(waiter.timer);
|
|
@@ -12522,6 +12651,7 @@ function createParseService(options = {}) {
|
|
|
12522
12651
|
const created = new Worker(workerPath);
|
|
12523
12652
|
created.unref();
|
|
12524
12653
|
created.on("message", (reply) => {
|
|
12654
|
+
if (collector.settle(reply.id)) return;
|
|
12525
12655
|
const waiter = pending.get(reply.id);
|
|
12526
12656
|
if (!waiter) return;
|
|
12527
12657
|
pending.delete(reply.id);
|
|
@@ -12532,32 +12662,37 @@ function createParseService(options = {}) {
|
|
|
12532
12662
|
created.on("error", () => {
|
|
12533
12663
|
if (worker === created) worker = null;
|
|
12534
12664
|
failAll(new VdeError("E_PARSE_FAILED", "The document could not be parsed.", { reason: "worker" }));
|
|
12665
|
+
collector.reset();
|
|
12535
12666
|
});
|
|
12536
12667
|
worker = created;
|
|
12537
12668
|
return created;
|
|
12538
12669
|
};
|
|
12539
12670
|
const request = (work) => {
|
|
12540
12671
|
if (closed) return Promise.reject(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12672
|
+
const result = new Promise((resolve, reject) => {
|
|
12673
|
+
collector.dispatch(() => {
|
|
12674
|
+
const id = nextId;
|
|
12675
|
+
nextId += 1;
|
|
12676
|
+
const timer = setTimeout(() => {
|
|
12677
|
+
discardWorker();
|
|
12678
|
+
failAll(new VdeError("E_PARSE_FAILED", "Parsing the document did not finish in time.", {
|
|
12679
|
+
reason: "timeout",
|
|
12680
|
+
timeoutMs
|
|
12681
|
+
}));
|
|
12682
|
+
}, timeoutMs);
|
|
12683
|
+
pending.set(id, {
|
|
12684
|
+
resolve,
|
|
12685
|
+
reject,
|
|
12686
|
+
timer
|
|
12687
|
+
});
|
|
12688
|
+
ensureWorker().postMessage({
|
|
12689
|
+
id,
|
|
12690
|
+
...work
|
|
12691
|
+
});
|
|
12692
|
+
}, reject);
|
|
12560
12693
|
});
|
|
12694
|
+
if (work.op !== "diagnostics") result.then(collector.touch, collector.touch);
|
|
12695
|
+
return result;
|
|
12561
12696
|
};
|
|
12562
12697
|
return {
|
|
12563
12698
|
diagnostics: (collectGarbage, signal) => worker === null ? Promise.resolve(null) : untilAborted(request({
|
|
@@ -12580,6 +12715,7 @@ function createParseService(options = {}) {
|
|
|
12580
12715
|
}),
|
|
12581
12716
|
async close() {
|
|
12582
12717
|
closed = true;
|
|
12718
|
+
collector.close(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
12583
12719
|
failAll(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
12584
12720
|
const current = worker;
|
|
12585
12721
|
worker = null;
|
|
@@ -12944,6 +13080,7 @@ async function startDaemon(options) {
|
|
|
12944
13080
|
let feedback = null;
|
|
12945
13081
|
let announceStopping = () => void 0;
|
|
12946
13082
|
let lockTimer = null;
|
|
13083
|
+
let heapWatch = null;
|
|
12947
13084
|
let stopping = null;
|
|
12948
13085
|
const stopRequested = new AbortController();
|
|
12949
13086
|
const owned = {
|
|
@@ -12977,6 +13114,7 @@ async function startDaemon(options) {
|
|
|
12977
13114
|
};
|
|
12978
13115
|
stopRequested.abort();
|
|
12979
13116
|
if (lockTimer) clearInterval(lockTimer);
|
|
13117
|
+
heapWatch?.stop();
|
|
12980
13118
|
await step("watcher", () => watcher?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
12981
13119
|
await step("feedback", () => feedback?.close());
|
|
12982
13120
|
await step("drain", () => ipc?.drain());
|
|
@@ -13238,6 +13376,7 @@ async function startDaemon(options) {
|
|
|
13238
13376
|
};
|
|
13239
13377
|
lockTimer = setInterval(() => void checkLock(), LOCK_CHECK_INTERVAL_MS);
|
|
13240
13378
|
lockTimer.unref();
|
|
13379
|
+
heapWatch = startHeapWatch();
|
|
13241
13380
|
logger.log("daemon.started", { openDocuments: openedStore.payload.openOrder.length });
|
|
13242
13381
|
} catch (error) {
|
|
13243
13382
|
logger.log("daemon.start-failed", { code: error instanceof VdeError ? error.code : "E_INTERNAL" });
|
|
@@ -8454,6 +8454,10 @@ function excerptOf(text, normalized, needles) {
|
|
|
8454
8454
|
return sliceCodePoints(text.slice(start, start + LIMITS.searchExcerptCodePoints * 2 + EXCERPT_LEAD), 0, LIMITS.searchExcerptCodePoints).replace(/\s+/g, " ").trim();
|
|
8455
8455
|
}
|
|
8456
8456
|
const sectionKeyOf = (entry) => `${entry.documentId}\n${String(entry.sectionIndex)}`;
|
|
8457
|
+
function matchedInBody(match) {
|
|
8458
|
+
for (const term in match) if (match[term]?.includes("body") === true) return true;
|
|
8459
|
+
return false;
|
|
8460
|
+
}
|
|
8457
8461
|
function emptyMatch(stored, sectionIndex) {
|
|
8458
8462
|
return {
|
|
8459
8463
|
stored,
|
|
@@ -8658,11 +8662,24 @@ var SearchIndex = class {
|
|
|
8658
8662
|
}
|
|
8659
8663
|
};
|
|
8660
8664
|
if (input.mode === "text" && terms.length > 0) {
|
|
8665
|
+
const restricted = input.documents !== null || this.#staging.size > 0;
|
|
8661
8666
|
const run = (kind, options) => {
|
|
8667
|
+
const decided = /* @__PURE__ */ new Map();
|
|
8668
|
+
for (const candidate of candidates.values()) {
|
|
8669
|
+
const sections = decided.get(candidate.stored) ?? /* @__PURE__ */ new Set();
|
|
8670
|
+
sections.add(candidate.entry.sectionIndex);
|
|
8671
|
+
decided.set(candidate.stored, sections);
|
|
8672
|
+
}
|
|
8673
|
+
const boostDocument = restricted || decided.size > 0 ? (id) => {
|
|
8674
|
+
const found = this.#entries.get(id);
|
|
8675
|
+
if (!found || !usable(found.stored)) return 0;
|
|
8676
|
+
return decided.get(found.stored)?.has(found.entry.sectionIndex) === true ? 0 : 1;
|
|
8677
|
+
} : void 0;
|
|
8662
8678
|
let matched = null;
|
|
8663
8679
|
for (const term of terms) {
|
|
8664
8680
|
const results = this.#mini.search(term, {
|
|
8665
8681
|
boost: BOOST,
|
|
8682
|
+
...boostDocument ? { boostDocument } : {},
|
|
8666
8683
|
prefix: options.prefix ? (candidate) => candidate.length >= PREFIX_MIN_LENGTH : false,
|
|
8667
8684
|
fuzzy: options.fuzzy ? (candidate) => allowsFuzzy(candidate) ? FUZZY_DISTANCE : false : false,
|
|
8668
8685
|
tokenize: (text) => [text],
|
|
@@ -8674,14 +8691,13 @@ var SearchIndex = class {
|
|
|
8674
8691
|
if (!found || !usable(found.stored)) continue;
|
|
8675
8692
|
const { stored, entry } = found;
|
|
8676
8693
|
const key = sectionKeyOf(entry);
|
|
8677
|
-
const fields = new Set(Object.values(result.match).flat());
|
|
8678
8694
|
const current = satisfied.get(key) ?? emptyMatch(stored, entry.sectionIndex);
|
|
8679
8695
|
current.score = Math.max(current.score, result.score);
|
|
8680
8696
|
if (result.score > current.entryScore) {
|
|
8681
8697
|
current.entry = entry;
|
|
8682
8698
|
current.entryScore = result.score;
|
|
8683
8699
|
}
|
|
8684
|
-
if (
|
|
8700
|
+
if (result.score > current.bodyScore && matchedInBody(result.match)) {
|
|
8685
8701
|
current.bodyEntry = entry;
|
|
8686
8702
|
current.bodyScore = result.score;
|
|
8687
8703
|
}
|
|
@@ -8723,7 +8739,7 @@ var SearchIndex = class {
|
|
|
8723
8739
|
value.bodyScore = found.bodyScore;
|
|
8724
8740
|
}
|
|
8725
8741
|
}
|
|
8726
|
-
if (matched.size === 0) return;
|
|
8742
|
+
if (matched.size === 0 && decided.size === 0) return;
|
|
8727
8743
|
}
|
|
8728
8744
|
for (const [key, value] of matched ?? []) {
|
|
8729
8745
|
if (candidates.has(key)) continue;
|
|
@@ -8775,7 +8791,16 @@ var SearchIndex = class {
|
|
|
8775
8791
|
const shape = stored.sections.get(entry.sectionIndex);
|
|
8776
8792
|
if (!shape) continue;
|
|
8777
8793
|
perDocument.set(meta.documentId, count + 1);
|
|
8778
|
-
|
|
8794
|
+
let body = entry.body;
|
|
8795
|
+
let normalized = entry.normalizedBody;
|
|
8796
|
+
if (body === "") {
|
|
8797
|
+
body = entry.heading;
|
|
8798
|
+
normalized = entry.normalizedHeading;
|
|
8799
|
+
}
|
|
8800
|
+
if (body === "") {
|
|
8801
|
+
body = shape.title || meta.title;
|
|
8802
|
+
normalized = normalizeForSearch(body);
|
|
8803
|
+
}
|
|
8779
8804
|
hits.push({
|
|
8780
8805
|
documentId: meta.documentId,
|
|
8781
8806
|
revision: meta.revision,
|
|
@@ -8783,7 +8808,7 @@ var SearchIndex = class {
|
|
|
8783
8808
|
displayPath: meta.displayPath,
|
|
8784
8809
|
sectionId: shape.sectionId,
|
|
8785
8810
|
headingPath: this.#headingPathOf(stored, entry.sectionIndex),
|
|
8786
|
-
excerpt: excerptOf(body,
|
|
8811
|
+
excerpt: excerptOf(body, normalized, [query, ...terms]),
|
|
8787
8812
|
matchKind: kind,
|
|
8788
8813
|
score: Math.round(score * 1e3) / 1e3,
|
|
8789
8814
|
sourceRange: null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as analyzeDocument, i as ParseLimitError, n as scanReferences, t as renderDocument } from "../render-yCayIbhf.js";
|
|
2
|
-
import {
|
|
2
|
+
import { n as measureHeap, t as collectGarbage } from "../heap-C3qHm7SE.js";
|
|
3
3
|
import { parentPort } from "node:worker_threads";
|
|
4
4
|
//#region src/workers/parse-worker.ts
|
|
5
5
|
function run(request) {
|
|
@@ -7,6 +7,10 @@ function run(request) {
|
|
|
7
7
|
heapUsedBytes: measureHeap(request.collectGarbage),
|
|
8
8
|
retained: {}
|
|
9
9
|
};
|
|
10
|
+
if (request.op === "collect") {
|
|
11
|
+
collectGarbage();
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
10
14
|
if (request.op === "analyze") return analyzeDocument(request.text, request.format);
|
|
11
15
|
if (request.op === "scan") return scanReferences(request.kind, request.text);
|
|
12
16
|
return renderDocument(request.input);
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { t as SearchIndex } from "../search-index-
|
|
2
|
-
import {
|
|
1
|
+
import { t as SearchIndex } from "../search-index-CNsfwB72.js";
|
|
2
|
+
import { n as measureHeap, t as collectGarbage } from "../heap-C3qHm7SE.js";
|
|
3
3
|
import { parentPort } from "node:worker_threads";
|
|
4
4
|
//#region src/workers/search-worker.ts
|
|
5
5
|
const index = new SearchIndex();
|
|
6
|
-
async function diagnostics(
|
|
7
|
-
const retained = await index.retainedCounts(
|
|
6
|
+
async function diagnostics(collect) {
|
|
7
|
+
const retained = await index.retainedCounts(collect);
|
|
8
8
|
return {
|
|
9
|
-
heapUsedBytes: measureHeap(
|
|
9
|
+
heapUsedBytes: measureHeap(collect),
|
|
10
10
|
retained
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
function run(request) {
|
|
14
14
|
switch (request.op) {
|
|
15
15
|
case "diagnostics": return diagnostics(request.collectGarbage);
|
|
16
|
+
case "collect":
|
|
17
|
+
collectGarbage();
|
|
18
|
+
return null;
|
|
16
19
|
case "begin":
|
|
17
20
|
index.begin(request.meta);
|
|
18
21
|
return null;
|
package/package.json
CHANGED
package/skills/vde-open/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ vde-open is a local document viewer shared by a person and agents. The person re
|
|
|
10
10
|
## Before you start
|
|
11
11
|
|
|
12
12
|
- Check that the CLI exists: `command -v vo || command -v vde-open`. Both names are the same CLI; if `vo` belongs to another tool on this machine, use `vde-open`.
|
|
13
|
-
- If neither exists, do not install it on your own. Tell the user it is not installed and point them to the install steps (Node.js 24 or later;
|
|
13
|
+
- If neither exists, do not install it on your own. Tell the user it is not installed and point them to the install steps (Node.js 24 or later; `bun add -g vde-open` or `npm install -g vde-open`).
|
|
14
14
|
- The daemon starts automatically on the first command. You do not need to start it.
|
|
15
15
|
|
|
16
16
|
## Rules
|