vde-open 0.1.8 → 0.1.10
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 +14 -6
- package/dist/cli.js +10 -15
- package/dist/daemon.js +2 -2
- package/dist/{main-001DhaNk.js → main-BI-4X6_3.js} +32 -166
- package/dist/{search-index-B7DqaWh6.js → search-index-NiN4l6Hg.js} +1 -11
- package/dist/web/assets/index-DOobMckY.js +163 -0
- package/dist/web/assets/index-HOxygmIG.css +2 -0
- package/dist/web/index.html +2 -2
- package/dist/workers/search-worker.js +1 -1
- package/package.json +1 -1
- package/skills/vde-open/SKILL.md +1 -1
- package/dist/web/assets/index-BKrIIMW1.js +0 -163
- package/dist/web/assets/index-BfusO5QE.css +0 -2
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ To install from a clone of this repository (Node.js 24.21.0 is pinned in `mise.t
|
|
|
28
28
|
pnpm install --frozen-lockfile
|
|
29
29
|
pnpm build
|
|
30
30
|
pnpm test:pack # builds artifacts/vde-open-<version>.tgz and verifies an install in a separate directory
|
|
31
|
-
bun add -g "$PWD/artifacts/vde-open-0.1.
|
|
31
|
+
bun add -g "$PWD/artifacts/vde-open-0.1.10.tgz" # bun add -g needs an absolute path
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## `vde-open` and `vo`
|
|
@@ -45,7 +45,7 @@ Installing never overwrites or removes an existing `vo` (another tool's command,
|
|
|
45
45
|
```bash
|
|
46
46
|
vo open README.md docs/design.md # open documents (starts the daemon if needed)
|
|
47
47
|
vo open docs -w # open a directory and follow new documents
|
|
48
|
-
vo ui # open the management UI (
|
|
48
|
+
vo ui # open the management UI (no authentication)
|
|
49
49
|
vo list --json # list open documents
|
|
50
50
|
vo search "authentication design" --json # search open documents
|
|
51
51
|
vo read <documentId> --section sec_0003 --json # read a section
|
|
@@ -53,6 +53,8 @@ vo close docs/design.md # remove from the list (the file is not
|
|
|
53
53
|
vo daemon stop # stop the daemon
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
The management UI does not require authentication. The URL printed by `vo ui --print-url` can be copied to another browser or tab on the same machine while the daemon is running. URLs that select a document work directly too. Both listeners bind to `127.0.0.1`; viewing from another machine is not supported.
|
|
57
|
+
|
|
56
58
|
For how agents search, read, and ask questions, see [docs/agent-usage.md](docs/agent-usage.md).
|
|
57
59
|
|
|
58
60
|
## Agent skill
|
|
@@ -80,7 +82,13 @@ ln -s ~/.bun/install/global/node_modules/vde-open/skills/vde-open ~/.codex/skill
|
|
|
80
82
|
- Scripts run only in HTML opened with `--html-mode interactive`. Use it only with HTML that you or the agent prepared and trust: scripts can load only registered files and cannot reach the management UI, the management API, or other files, but this does not block every outbound request (navigation inside the view, for example). After the daemon restarts, the HTML is static until you allow scripts again in the management UI.
|
|
81
83
|
- "Differences from the original document" in the management UI lists what the view changed, why, and what to do.
|
|
82
84
|
|
|
83
|
-
## Markdown display
|
|
85
|
+
## Markdown display
|
|
86
|
+
|
|
87
|
+
Use "Color palette" in the management header to choose Standard (the default), GitHub, Gruvbox, Catppuccin, or GitHub High Contrast. Light, Dark, and Match OS setting are separate controls. Both preferences are saved in the browser. The palette applies to the management UI and Markdown, including code highlighting; HTML keeps its own styling and PDFs keep their print styling.
|
|
88
|
+
|
|
89
|
+
"Wide view" in the document header expands Markdown to the available width. Press it again to return to the standard width. The width preference is saved in the browser. Tables keep their column widths and scroll horizontally when they do not fit. To scroll with the keyboard, focus the table and press the left or right arrow key.
|
|
90
|
+
|
|
91
|
+
### Display limits
|
|
84
92
|
|
|
85
93
|
Markdown is rendered with TanStack Markdown 1.0.0. It is not fully compatible with CommonMark or GFM.
|
|
86
94
|
|
|
@@ -125,7 +133,7 @@ The person answers in the answer panel of the management UI. The answers are sub
|
|
|
125
133
|
| Symptom | What to do |
|
|
126
134
|
|---|---|
|
|
127
135
|
| `vo` runs a different command | Use `vde-open`, or check the order of `PATH` |
|
|
128
|
-
|
|
|
136
|
+
| An earlier URL stops working after the daemon restarts | Open the current URL with `vo ui` (the port may change on restart) |
|
|
129
137
|
| Exit code 8 (cannot connect to or start the daemon) | Check with `vo daemon status`, and look for leftover files with `vo doctor` |
|
|
130
138
|
| Images or CSS are not shown | Open "Differences from the original document" and register them with `--assets-root` or `--asset` |
|
|
131
139
|
| Search does not find a document | Check with `vo list --json` that the document is open and its `searchState` is `ready` |
|
|
@@ -137,7 +145,7 @@ The person answers in the answer panel of the management UI. The answers are sub
|
|
|
137
145
|
|---|---|
|
|
138
146
|
| Linux and macOS on CI (GitHub Actions `ubuntu-latest` and `macos-latest`, Node.js 24.21.0) | Verified (format, lint, typecheck, unit/integration, build, pack, e2e; `.github/workflows/ci.yml`) |
|
|
139
147
|
| Windows on CI (`windows-latest`, Node.js 24.21.0) | Verified: build, pack smoke (install, both bins, IPC, starting and stopping the daemon, JSON output, the UI and workers), and the daemon and document integration tests. The other unit and integration tests and the e2e tests are not run on Windows |
|
|
140
|
-
| Browsers | Chromium (Playwright's Chrome Headless Shell): the full suite is verified. Firefox and WebKit (Playwright 1.63.0): the view isolation, CSP, HTML bridge, and
|
|
148
|
+
| Browsers | Chromium (Playwright's Chrome Headless Shell): the full suite is verified. Firefox and WebKit (Playwright 1.63.0): the view isolation, CSP, HTML bridge, and direct URL tests (`pnpm test:e2e:cross`) are verified. Other UI interactions in Firefox and WebKit (search, answer panel, narrow screens, a list of 1,000 documents) are not verified |
|
|
141
149
|
| PDF export | macOS (development machine): verified with Google Chrome 154 and Microsoft Edge 154. Linux on CI: verified with the runner's Google Chrome (e2e). Windows: printing with a real browser is not verified (the pack smoke test checks rendering the print document) |
|
|
142
150
|
|
|
143
151
|
More details: [docs/performance.md](docs/performance.md) (measurements), [docs/architecture.md](docs/architecture.md) and [docs/security-model.md](docs/security-model.md) (design). Development records (in Japanese): [docs/implementation-status.md](docs/implementation-status.md), [docs/dependency-validation.md](docs/dependency-validation.md), and [docs/adr/](docs/adr/).
|
|
@@ -156,7 +164,7 @@ cd ../..
|
|
|
156
164
|
pnpm build:watch
|
|
157
165
|
```
|
|
158
166
|
|
|
159
|
-
`build:watch` rebuilds when the sources, the build configuration, or the packaged files change; a failed build keeps the last successful output. The next linked CLI invocation uses the new build, but the watcher does not restart your daemon: run `vo daemon restart`, then `vo ui`. Restarting keeps registered documents and saved answers, but changes the UI address and invalidates earlier
|
|
167
|
+
`build:watch` rebuilds when the sources, the build configuration, or the packaged files change; a failed build keeps the last successful output. The next linked CLI invocation uses the new build, but the watcher does not restart your daemon: run `vo daemon restart`, then `vo ui`. Restarting keeps registered documents and saved answers, but changes the UI address and invalidates earlier render grants and interactive HTML permissions.
|
|
160
168
|
|
|
161
169
|
For frequent UI changes, use `pnpm dev` instead. It runs the daemon from source with a separate `.dev-home` and serves the UI with Vite HMR. It does not rebuild the linked CLI or restart the backend when its source changes. To run CLI commands against it, set `VDE_OPEN_HOME` to the absolute path of `.dev-home` (for example, `VDE_OPEN_HOME="$PWD/.dev-home" vo list` from the repository root).
|
|
162
170
|
|
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-BI-4X6_3.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-NiN4l6Hg.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";
|
|
@@ -3815,23 +3815,22 @@ async function runCli(rawArgv, context) {
|
|
|
3815
3815
|
}
|
|
3816
3816
|
};
|
|
3817
3817
|
const openBrowser = async (connection) => {
|
|
3818
|
-
const { data } = unwrap(await connection.request("
|
|
3819
|
-
if (await browser.open(data.
|
|
3818
|
+
const { data } = unwrap(await connection.request("daemon.status", {}));
|
|
3819
|
+
if (await browser.open(data.uiUrl ?? "")) return [];
|
|
3820
3820
|
return [{
|
|
3821
3821
|
code: "W_BROWSER_OPEN_FAILED",
|
|
3822
3822
|
message: "The browser could not be opened. Open the URL shown by `vde-open ui --print-url` in your browser.",
|
|
3823
3823
|
details: { uiUrl: data.uiUrl }
|
|
3824
3824
|
}];
|
|
3825
3825
|
};
|
|
3826
|
-
const showUi = async (printUrl) => {
|
|
3826
|
+
const showUi = async (printUrl, json = false) => {
|
|
3827
3827
|
const connection = await control.ensure();
|
|
3828
3828
|
try {
|
|
3829
3829
|
if (printUrl) {
|
|
3830
|
-
const { data } = unwrap(await connection.request("
|
|
3831
|
-
context.
|
|
3832
|
-
context.stdout(`${data.bootstrapUrl}\n`);
|
|
3830
|
+
const { data } = unwrap(await connection.request("daemon.status", {}));
|
|
3831
|
+
if (!json) context.stdout(`${data.uiUrl ?? ""}\n`);
|
|
3833
3832
|
return { data: {
|
|
3834
|
-
uiUrl: data.uiUrl,
|
|
3833
|
+
uiUrl: data.uiUrl ?? "",
|
|
3835
3834
|
opened: false
|
|
3836
3835
|
} };
|
|
3837
3836
|
}
|
|
@@ -4051,12 +4050,8 @@ async function runCli(rawArgv, context) {
|
|
|
4051
4050
|
watchCommand.command("remove").description("Remove a watch rule. Open documents stay open").argument("<watchId>", "Watch rule ID").option("--json", "Output the result as JSON", false).action(async (watchId, options) => {
|
|
4052
4051
|
await execute("watch.remove", options.json, () => viaDaemon("watch.remove", { watchId }), renderRules);
|
|
4053
4052
|
});
|
|
4054
|
-
program.command("ui").description("Open the management UI in the browser").option("--print-url", "Print
|
|
4055
|
-
|
|
4056
|
-
fail(new VdeError("E_INVALID_ARGUMENT", "--print-url cannot be combined with --json because its output contains a secret."), true);
|
|
4057
|
-
return;
|
|
4058
|
-
}
|
|
4059
|
-
await execute("ui", options.json, () => showUi(options.printUrl), () => "");
|
|
4053
|
+
program.command("ui").description("Open the management UI in the browser").option("--print-url", "Print the UI URL instead of opening the browser", false).option("--json", "Output the result as JSON", false).action(async (options) => {
|
|
4054
|
+
await execute("ui", options.json, () => showUi(options.printUrl, options.json), () => "");
|
|
4060
4055
|
});
|
|
4061
4056
|
const renderStatus = (status) => status.state === "running" ? `running pid=${String(status.pid)} open documents: ${String(status.openDocuments)} ${status.uiUrl ?? ""}` : "stopped";
|
|
4062
4057
|
const daemon = program.command("daemon").description("Start, inspect, and stop the daemon");
|
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-BI-4X6_3.js";
|
|
2
|
+
import { E as isVdeError } from "./search-index-NiN4l6Hg.js";
|
|
3
3
|
//#region src/daemon.ts
|
|
4
4
|
function notifyParent(message) {
|
|
5
5
|
if (!process.send) return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as roleAllowed, N as __commonJSMin, P as __toESM, S as isValidLogicalPath, _ as classifyLink, b as encodeLogicalPath, d as analyzeDocument, g as assetTypeOf, u as ParseLimitError, v as classifyReference, x as hasHiddenSegment, y as dirnameOfLogicalPath } from "./html-static-BDV6MgA7.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-NiN4l6Hg.js";
|
|
3
3
|
import { n as scanReferences, r as extractTitle } from "./render-BX3arVZj.js";
|
|
4
4
|
import { n as measureHeap, t as collectGarbage } from "./heap-C3qHm7SE.js";
|
|
5
5
|
import { accessSync, constants, existsSync, stat, statSync, unwatchFile, watch, watchFile } from "node:fs";
|
|
@@ -154,7 +154,7 @@ function parseStrictJson(text) {
|
|
|
154
154
|
}
|
|
155
155
|
//#endregion
|
|
156
156
|
//#region package.json
|
|
157
|
-
var version = "0.1.
|
|
157
|
+
var version = "0.1.10";
|
|
158
158
|
//#endregion
|
|
159
159
|
//#region src/documents/cursor.ts
|
|
160
160
|
function invalidCursor(reason) {
|
|
@@ -6900,12 +6900,9 @@ function decodeUtf8(bytes) {
|
|
|
6900
6900
|
}
|
|
6901
6901
|
}
|
|
6902
6902
|
function createRenderService(options) {
|
|
6903
|
-
const { store, documents,
|
|
6903
|
+
const { store, documents, parse } = options;
|
|
6904
6904
|
const grants = /* @__PURE__ */ new Map();
|
|
6905
6905
|
const snapshots = /* @__PURE__ */ new Map();
|
|
6906
|
-
sessions.onAnyRevoke((sessionId) => {
|
|
6907
|
-
for (const [key, grant] of grants) if (grant.sessionId === sessionId) grants.delete(key);
|
|
6908
|
-
});
|
|
6909
6906
|
const snapshotOf = async (documentId, revision, mode = "static", withSdk = false) => {
|
|
6910
6907
|
const { record, entry } = documents.describeRevision(documentId, revision);
|
|
6911
6908
|
const cacheKey = `${entry.revision}\n${entry.documentLogicalPath}\n${mode}\n${String(withSdk)}`;
|
|
@@ -7004,14 +7001,13 @@ function createRenderService(options) {
|
|
|
7004
7001
|
const liveGrant = (key) => {
|
|
7005
7002
|
const grant = grants.get(key);
|
|
7006
7003
|
if (!grant) return null;
|
|
7007
|
-
|
|
7008
|
-
if (!sessions.isActiveId(grant.sessionId) || !record?.isOpen || documents.openEpoch(grant.documentId) !== grant.openEpoch || grant.mode === "interactive" && documents.interactiveGeneration(grant.documentId) !== grant.permission) {
|
|
7004
|
+
if (!documents.state.documents[grant.documentId]?.isOpen || documents.openEpoch(grant.documentId) !== grant.openEpoch || grant.mode === "interactive" && documents.interactiveGeneration(grant.documentId) !== grant.permission) {
|
|
7009
7005
|
grants.delete(key);
|
|
7010
7006
|
return null;
|
|
7011
7007
|
}
|
|
7012
7008
|
return grant;
|
|
7013
7009
|
};
|
|
7014
|
-
const issue = async (
|
|
7010
|
+
const issue = async (params, context) => {
|
|
7015
7011
|
const interactive = params.mode === "interactive";
|
|
7016
7012
|
const permissionOf = () => {
|
|
7017
7013
|
if (!interactive) return null;
|
|
@@ -7054,7 +7050,6 @@ function createRenderService(options) {
|
|
|
7054
7050
|
}
|
|
7055
7051
|
const key = randomBytes(32).toString("base64url");
|
|
7056
7052
|
grants.set(key, {
|
|
7057
|
-
sessionId,
|
|
7058
7053
|
documentId: record.documentId,
|
|
7059
7054
|
openEpoch,
|
|
7060
7055
|
revision: entry.revision,
|
|
@@ -7064,8 +7059,11 @@ function createRenderService(options) {
|
|
|
7064
7059
|
bridge,
|
|
7065
7060
|
missing: /* @__PURE__ */ new Set()
|
|
7066
7061
|
});
|
|
7067
|
-
|
|
7068
|
-
|
|
7062
|
+
while (grants.size > LIMITS.renderGrants) {
|
|
7063
|
+
const oldest = grants.keys().next().value;
|
|
7064
|
+
if (oldest === void 0) break;
|
|
7065
|
+
grants.delete(oldest);
|
|
7066
|
+
}
|
|
7069
7067
|
const filesBaseUrl = `${options.previewOrigin()}/r/${key}/files/`;
|
|
7070
7068
|
return {
|
|
7071
7069
|
grant: key,
|
|
@@ -7100,14 +7098,14 @@ function createRenderService(options) {
|
|
|
7100
7098
|
snapshots: snapshots.size
|
|
7101
7099
|
};
|
|
7102
7100
|
},
|
|
7103
|
-
async createGrant(
|
|
7101
|
+
async createGrant(rawParams, context) {
|
|
7104
7102
|
const params = renderGrantParamsSchema.parse(rawParams);
|
|
7105
|
-
return issue(
|
|
7103
|
+
return issue({
|
|
7106
7104
|
...params,
|
|
7107
7105
|
requestId: null
|
|
7108
7106
|
}, context);
|
|
7109
7107
|
},
|
|
7110
|
-
async createGrantForRequest(
|
|
7108
|
+
async createGrantForRequest(requestId, context) {
|
|
7111
7109
|
const request = Object.hasOwn(store.payload.feedbackRequests, requestId) ? store.payload.feedbackRequests[requestId] : void 0;
|
|
7112
7110
|
if (!request) throw new VdeError("E_REQUEST_NOT_FOUND", "The question was not found.", { requestId });
|
|
7113
7111
|
if (request.status !== "pending") throw new VdeError("E_REQUEST_NOT_PENDING", "The question is not pending.", {
|
|
@@ -7115,16 +7113,16 @@ function createRenderService(options) {
|
|
|
7115
7113
|
status: request.status
|
|
7116
7114
|
});
|
|
7117
7115
|
const interactive = request.renderMode === "interactive" && documents.interactiveAllowed(request.documentId);
|
|
7118
|
-
return issue(
|
|
7116
|
+
return issue({
|
|
7119
7117
|
documentId: request.documentId,
|
|
7120
7118
|
revision: request.revision,
|
|
7121
7119
|
mode: interactive ? "interactive" : "static",
|
|
7122
7120
|
requestId: interactive ? request.requestId : null
|
|
7123
7121
|
}, context);
|
|
7124
7122
|
},
|
|
7125
|
-
bridgeOf(
|
|
7123
|
+
bridgeOf(key) {
|
|
7126
7124
|
const grant = liveGrant(key);
|
|
7127
|
-
if (!grant?.bridge
|
|
7125
|
+
if (!grant?.bridge) return null;
|
|
7128
7126
|
if (store.payload.feedbackRequests[grant.bridge.requestId]?.status !== "pending") return null;
|
|
7129
7127
|
return {
|
|
7130
7128
|
requestId: grant.bridge.requestId,
|
|
@@ -7132,9 +7130,9 @@ function createRenderService(options) {
|
|
|
7132
7130
|
revision: grant.revision
|
|
7133
7131
|
};
|
|
7134
7132
|
},
|
|
7135
|
-
release(
|
|
7133
|
+
release(keys) {
|
|
7136
7134
|
let released = 0;
|
|
7137
|
-
for (const key of keys) if (grants.
|
|
7135
|
+
for (const key of keys) if (grants.delete(key)) released += 1;
|
|
7138
7136
|
return released;
|
|
7139
7137
|
},
|
|
7140
7138
|
async resolve(key, logicalPath) {
|
|
@@ -7162,9 +7160,9 @@ function createRenderService(options) {
|
|
|
7162
7160
|
mode: grant.mode
|
|
7163
7161
|
};
|
|
7164
7162
|
},
|
|
7165
|
-
missingOf(
|
|
7163
|
+
missingOf(key) {
|
|
7166
7164
|
const grant = liveGrant(key);
|
|
7167
|
-
if (!grant
|
|
7165
|
+
if (!grant) return [];
|
|
7168
7166
|
return [...grant.missing];
|
|
7169
7167
|
},
|
|
7170
7168
|
pruneClosed() {
|
|
@@ -11016,7 +11014,7 @@ function createEventQueue(options) {
|
|
|
11016
11014
|
if (pending === 0) progressAt = now();
|
|
11017
11015
|
pending += 1;
|
|
11018
11016
|
writes = writes.then(() => {
|
|
11019
|
-
if (stopped
|
|
11017
|
+
if (stopped) return void 0;
|
|
11020
11018
|
return write();
|
|
11021
11019
|
}).then(() => {
|
|
11022
11020
|
progressAt = now();
|
|
@@ -11216,27 +11214,9 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11216
11214
|
if (Number(c.req.header("content-length") ?? "0") > 0 && !(c.req.header("content-type") ?? "").startsWith("application/json")) return fail(c, new VdeError("E_INVALID_ARGUMENT", "The body must be sent as JSON."));
|
|
11217
11215
|
}
|
|
11218
11216
|
if (deps.isStopping()) return fail(c, new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
11219
|
-
if (c.req.path === `${API_PREFIX}/sessions/bootstrap` && method === "POST") return next();
|
|
11220
|
-
const authorization = c.req.header("authorization") ?? "";
|
|
11221
|
-
const token = authorization.startsWith("Bearer ") ? authorization.slice(7) : "";
|
|
11222
|
-
if (token === "" || !deps.sessions.authenticate(token)) return fail(c, new VdeError("E_UNAUTHORIZED", "Authentication is required. Open again from the CLI."));
|
|
11223
|
-
c.set("token", token);
|
|
11224
11217
|
return next();
|
|
11225
11218
|
});
|
|
11226
11219
|
const api = new Hono();
|
|
11227
|
-
api.post("/sessions/bootstrap", async (c) => {
|
|
11228
|
-
const body = strictObject({ ticket: string().min(1).max(256) }).parse(await c.req.json());
|
|
11229
|
-
const token = deps.sessions.exchange(body.ticket);
|
|
11230
|
-
if (!token) return fail(c, new VdeError("E_UNAUTHORIZED", "This URL has already been used or has expired."));
|
|
11231
|
-
return c.json(successEnvelope({
|
|
11232
|
-
token,
|
|
11233
|
-
idleTimeoutSeconds: LIMITS.sessionIdleMs / 1e3
|
|
11234
|
-
}, { command: "sessions.bootstrap" }));
|
|
11235
|
-
});
|
|
11236
|
-
api.delete("/session", (c) => {
|
|
11237
|
-
deps.sessions.revoke(c.get("token"));
|
|
11238
|
-
return c.json(successEnvelope({ revoked: true }, { command: "session.delete" }));
|
|
11239
|
-
});
|
|
11240
11220
|
api.get("/status", (c) => {
|
|
11241
11221
|
const state = deps.documents.state;
|
|
11242
11222
|
const status = {
|
|
@@ -11322,28 +11302,27 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11322
11302
|
});
|
|
11323
11303
|
api.post("/feedback/:id/render-grants", async (c) => {
|
|
11324
11304
|
const requestId = requestIdSchema.parse(c.req.param("id"));
|
|
11325
|
-
const result = await deps.render.createGrantForRequest(
|
|
11305
|
+
const result = await deps.render.createGrantForRequest(requestId, { origin: c.req.header("origin") ?? origin });
|
|
11326
11306
|
return c.json(successEnvelope(result, {
|
|
11327
11307
|
command: "feedback.render-grant",
|
|
11328
11308
|
catalogVersion: deps.documents.state.catalogVersion
|
|
11329
11309
|
}));
|
|
11330
11310
|
});
|
|
11331
|
-
const bridgeOf = (
|
|
11332
|
-
const found = typeof grant === "string" ? deps.render.bridgeOf(
|
|
11311
|
+
const bridgeOf = (grant) => {
|
|
11312
|
+
const found = typeof grant === "string" ? deps.render.bridgeOf(grant) : null;
|
|
11333
11313
|
if (!found) throw new VdeError("E_RENDER_GRANT_INVALID", "The render grant has expired, so draft answers from the HTML are not accepted.");
|
|
11334
11314
|
return found;
|
|
11335
11315
|
};
|
|
11336
11316
|
api.post("/render-grants/bridge/ready", async (c) => {
|
|
11337
11317
|
const body = strictObject({ grant: string().min(1).max(128) }).parse(await c.req.json());
|
|
11338
|
-
const { requestId } = bridgeOf(
|
|
11318
|
+
const { requestId } = bridgeOf(body.grant);
|
|
11339
11319
|
return ok(c, "render-grants.bridge-ready", deps.feedback.getForUi(requestId));
|
|
11340
11320
|
});
|
|
11341
11321
|
api.put("/render-grants/bridge/draft", async (c) => {
|
|
11342
11322
|
const { grant, ...draft } = parseAnswersBody(await c.req.text());
|
|
11343
|
-
const
|
|
11344
|
-
const { requestId } = bridgeOf(token, grant);
|
|
11323
|
+
const { requestId } = bridgeOf(grant);
|
|
11345
11324
|
return ok(c, "render-grants.bridge-draft", await deps.feedback.updateDraft(requestId, draft, { authorize: () => {
|
|
11346
|
-
if (bridgeOf(
|
|
11325
|
+
if (bridgeOf(grant).requestId !== requestId) throw new VdeError("E_RENDER_GRANT_INVALID", "The render grant has changed.");
|
|
11347
11326
|
} }));
|
|
11348
11327
|
});
|
|
11349
11328
|
api.post("/feedback/:id/submit", async (c) => ok(c, "feedback.submit", await deps.feedback.submit(requestIdSchema.parse(c.req.param("id")), await c.req.json())));
|
|
@@ -11356,7 +11335,7 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11356
11335
|
revision: string().optional(),
|
|
11357
11336
|
mode: string().optional()
|
|
11358
11337
|
}).parse(await c.req.json());
|
|
11359
|
-
const result = await deps.render.createGrant(
|
|
11338
|
+
const result = await deps.render.createGrant({
|
|
11360
11339
|
documentId: c.req.param("id"),
|
|
11361
11340
|
...body
|
|
11362
11341
|
}, { origin: c.req.header("origin") ?? origin });
|
|
@@ -11367,7 +11346,7 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11367
11346
|
});
|
|
11368
11347
|
api.post("/render-grants/missing", async (c) => {
|
|
11369
11348
|
const body = strictObject({ grant: string().min(1).max(128) }).parse(await c.req.json());
|
|
11370
|
-
const missing = deps.render.missingOf(
|
|
11349
|
+
const missing = deps.render.missingOf(body.grant);
|
|
11371
11350
|
return c.json(successEnvelope({ missing }, { command: "render-grants.missing" }));
|
|
11372
11351
|
});
|
|
11373
11352
|
api.post("/documents/:id/html-mode", async (c) => {
|
|
@@ -11379,7 +11358,7 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11379
11358
|
});
|
|
11380
11359
|
api.post("/render-grants/release", async (c) => {
|
|
11381
11360
|
const body = strictObject({ grants: array(string().min(1).max(128)).max(256) }).parse(await c.req.json());
|
|
11382
|
-
const released = deps.render.release(
|
|
11361
|
+
const released = deps.render.release(body.grants);
|
|
11383
11362
|
return c.json(successEnvelope({ released }, { command: "render-grants.release" }));
|
|
11384
11363
|
});
|
|
11385
11364
|
api.post("/documents/:id/links/:linkId/open", async (c) => {
|
|
@@ -11408,7 +11387,6 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11408
11387
|
closed = true;
|
|
11409
11388
|
wake();
|
|
11410
11389
|
};
|
|
11411
|
-
const token = c.get("token");
|
|
11412
11390
|
const queue = createEventQueue({
|
|
11413
11391
|
writeEvent: (event) => stream.writeSSE({
|
|
11414
11392
|
event: event.type,
|
|
@@ -11416,11 +11394,6 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11416
11394
|
data: JSON.stringify(event)
|
|
11417
11395
|
}),
|
|
11418
11396
|
writeHeartbeat: () => stream.write(": heartbeat\n\n"),
|
|
11419
|
-
beforeWrite: () => {
|
|
11420
|
-
if (deps.sessions.isActive(token)) return true;
|
|
11421
|
-
drop();
|
|
11422
|
-
return false;
|
|
11423
|
-
},
|
|
11424
11397
|
onError: finish
|
|
11425
11398
|
});
|
|
11426
11399
|
const drop = () => {
|
|
@@ -11431,7 +11404,6 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11431
11404
|
};
|
|
11432
11405
|
streams.set(finish, queue);
|
|
11433
11406
|
stream.onAbort(finish);
|
|
11434
|
-
const stopWatchingSession = deps.sessions.onRevoke(token, drop);
|
|
11435
11407
|
const send = (event) => {
|
|
11436
11408
|
if (!closed) queue.send(event);
|
|
11437
11409
|
};
|
|
@@ -11443,10 +11415,6 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11443
11415
|
});
|
|
11444
11416
|
const unsubscribe = deps.events.subscribe(send);
|
|
11445
11417
|
const heartbeat = setInterval(() => {
|
|
11446
|
-
if (!deps.sessions.isActive(token)) {
|
|
11447
|
-
drop();
|
|
11448
|
-
return;
|
|
11449
|
-
}
|
|
11450
11418
|
if (queue.stalledFor() >= (deps.stallMs ?? LIMITS.sseStallMs)) {
|
|
11451
11419
|
drop();
|
|
11452
11420
|
return;
|
|
@@ -11457,7 +11425,6 @@ async function startManagementServer(deps, options = {}) {
|
|
|
11457
11425
|
await done;
|
|
11458
11426
|
} finally {
|
|
11459
11427
|
clearInterval(heartbeat);
|
|
11460
|
-
stopWatchingSession();
|
|
11461
11428
|
unsubscribe();
|
|
11462
11429
|
await queue.settled();
|
|
11463
11430
|
streams.delete(finish);
|
|
@@ -11864,99 +11831,6 @@ async function startIpcServer(options) {
|
|
|
11864
11831
|
};
|
|
11865
11832
|
}
|
|
11866
11833
|
//#endregion
|
|
11867
|
-
//#region src/server/session-service.ts
|
|
11868
|
-
function digest(secret) {
|
|
11869
|
-
return createHash("sha256").update(secret).digest("hex");
|
|
11870
|
-
}
|
|
11871
|
-
function newSecret() {
|
|
11872
|
-
return randomBytes(32).toString("base64url");
|
|
11873
|
-
}
|
|
11874
|
-
function createSessionService(now = Date.now) {
|
|
11875
|
-
const tickets = /* @__PURE__ */ new Map();
|
|
11876
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
11877
|
-
const revokeListeners = /* @__PURE__ */ new Map();
|
|
11878
|
-
const anyRevokeListeners = /* @__PURE__ */ new Set();
|
|
11879
|
-
const drop = (key) => {
|
|
11880
|
-
if (!sessions.delete(key)) return;
|
|
11881
|
-
const listeners = revokeListeners.get(key);
|
|
11882
|
-
revokeListeners.delete(key);
|
|
11883
|
-
if (listeners) for (const listener of listeners) listener();
|
|
11884
|
-
for (const listener of anyRevokeListeners) listener(key);
|
|
11885
|
-
};
|
|
11886
|
-
const isActiveKey = (key) => {
|
|
11887
|
-
const lastUsedAt = sessions.get(key);
|
|
11888
|
-
if (lastUsedAt === void 0) return false;
|
|
11889
|
-
if (lastUsedAt + LIMITS.sessionIdleMs < now()) {
|
|
11890
|
-
drop(key);
|
|
11891
|
-
return false;
|
|
11892
|
-
}
|
|
11893
|
-
return true;
|
|
11894
|
-
};
|
|
11895
|
-
const sweep = () => {
|
|
11896
|
-
const current = now();
|
|
11897
|
-
for (const [key, expiresAt] of tickets) if (expiresAt < current) tickets.delete(key);
|
|
11898
|
-
for (const [key, lastUsedAt] of sessions) if (lastUsedAt + LIMITS.sessionIdleMs < current) drop(key);
|
|
11899
|
-
};
|
|
11900
|
-
return {
|
|
11901
|
-
retainedCounts() {
|
|
11902
|
-
let listeners = 0;
|
|
11903
|
-
for (const set of revokeListeners.values()) listeners += set.size;
|
|
11904
|
-
return {
|
|
11905
|
-
tickets: tickets.size,
|
|
11906
|
-
sessions: sessions.size,
|
|
11907
|
-
revokeListeners: listeners,
|
|
11908
|
-
anyRevokeListeners: anyRevokeListeners.size
|
|
11909
|
-
};
|
|
11910
|
-
},
|
|
11911
|
-
createBootstrapTicket() {
|
|
11912
|
-
sweep();
|
|
11913
|
-
const ticket = newSecret();
|
|
11914
|
-
tickets.set(digest(ticket), now() + LIMITS.bootstrapTicketTtlMs);
|
|
11915
|
-
return ticket;
|
|
11916
|
-
},
|
|
11917
|
-
exchange(ticket) {
|
|
11918
|
-
sweep();
|
|
11919
|
-
const key = digest(ticket);
|
|
11920
|
-
const expiresAt = tickets.get(key);
|
|
11921
|
-
tickets.delete(key);
|
|
11922
|
-
if (expiresAt === void 0 || expiresAt < now()) return null;
|
|
11923
|
-
const token = newSecret();
|
|
11924
|
-
sessions.set(digest(token), now());
|
|
11925
|
-
return token;
|
|
11926
|
-
},
|
|
11927
|
-
authenticate(token) {
|
|
11928
|
-
const key = digest(token);
|
|
11929
|
-
const lastUsedAt = sessions.get(key);
|
|
11930
|
-
if (lastUsedAt === void 0) return false;
|
|
11931
|
-
if (lastUsedAt + LIMITS.sessionIdleMs < now()) {
|
|
11932
|
-
drop(key);
|
|
11933
|
-
return false;
|
|
11934
|
-
}
|
|
11935
|
-
sessions.set(key, now());
|
|
11936
|
-
return true;
|
|
11937
|
-
},
|
|
11938
|
-
isActive: (token) => isActiveKey(digest(token)),
|
|
11939
|
-
idOf: digest,
|
|
11940
|
-
isActiveId: isActiveKey,
|
|
11941
|
-
onAnyRevoke(listener) {
|
|
11942
|
-
anyRevokeListeners.add(listener);
|
|
11943
|
-
},
|
|
11944
|
-
revoke(token) {
|
|
11945
|
-
drop(digest(token));
|
|
11946
|
-
},
|
|
11947
|
-
onRevoke(token, listener) {
|
|
11948
|
-
const key = digest(token);
|
|
11949
|
-
const listeners = revokeListeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
11950
|
-
revokeListeners.set(key, listeners);
|
|
11951
|
-
listeners.add(listener);
|
|
11952
|
-
return () => {
|
|
11953
|
-
listeners.delete(listener);
|
|
11954
|
-
if (listeners.size === 0 && revokeListeners.get(key) === listeners) revokeListeners.delete(key);
|
|
11955
|
-
};
|
|
11956
|
-
}
|
|
11957
|
-
};
|
|
11958
|
-
}
|
|
11959
|
-
//#endregion
|
|
11960
11834
|
//#region ../../node_modules/.pnpm/readdirp@5.1.1/node_modules/readdirp/index.js
|
|
11961
11835
|
const EntryTypes = {
|
|
11962
11836
|
FILE_TYPE: "files",
|
|
@@ -14342,7 +14216,6 @@ async function startDaemon(options) {
|
|
|
14342
14216
|
emit
|
|
14343
14217
|
});
|
|
14344
14218
|
const answering = feedback;
|
|
14345
|
-
const sessions = createSessionService();
|
|
14346
14219
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14347
14220
|
const devOrigin = isSourceRun ? environment.env["VDE_OPEN_DEV_UI_ORIGIN"] ?? null : null;
|
|
14348
14221
|
let managementOrigin = null;
|
|
@@ -14350,7 +14223,6 @@ async function startDaemon(options) {
|
|
|
14350
14223
|
const render = createRenderService({
|
|
14351
14224
|
store: openedStore,
|
|
14352
14225
|
documents,
|
|
14353
|
-
sessions,
|
|
14354
14226
|
parse: parser,
|
|
14355
14227
|
previewOrigin: () => previewOrigin,
|
|
14356
14228
|
onMissing: (documentId) => emit({
|
|
@@ -14379,7 +14251,6 @@ async function startDaemon(options) {
|
|
|
14379
14251
|
daemonId,
|
|
14380
14252
|
version,
|
|
14381
14253
|
documents,
|
|
14382
|
-
sessions,
|
|
14383
14254
|
events,
|
|
14384
14255
|
render,
|
|
14385
14256
|
search: searching,
|
|
@@ -14441,8 +14312,7 @@ async function startDaemon(options) {
|
|
|
14441
14312
|
documents: documents.retainedCounts(),
|
|
14442
14313
|
search: search?.retainedCounts() ?? {},
|
|
14443
14314
|
render: render.retainedCounts(),
|
|
14444
|
-
feedback: answering.retainedCounts()
|
|
14445
|
-
sessions: sessions.retainedCounts()
|
|
14315
|
+
feedback: answering.retainedCounts()
|
|
14446
14316
|
},
|
|
14447
14317
|
workers,
|
|
14448
14318
|
activeResources,
|
|
@@ -14486,11 +14356,7 @@ async function startDaemon(options) {
|
|
|
14486
14356
|
const result = await documents.removeWatchRule(params);
|
|
14487
14357
|
watching.sync();
|
|
14488
14358
|
return result;
|
|
14489
|
-
}
|
|
14490
|
-
"ui.bootstrap": () => ({ data: {
|
|
14491
|
-
bootstrapUrl: `${uiUrl}#bootstrap=${sessions.createBootstrapTicket()}`,
|
|
14492
|
-
uiUrl
|
|
14493
|
-
} })
|
|
14359
|
+
}
|
|
14494
14360
|
};
|
|
14495
14361
|
ipc = await startIpcServer({
|
|
14496
14362
|
socketPath: location.socketPath,
|
|
@@ -5601,8 +5601,6 @@ const LIMITS = {
|
|
|
5601
5601
|
printRenderTimeoutMs: 1e4,
|
|
5602
5602
|
pdfExportTimeoutMs: 6e4,
|
|
5603
5603
|
watchDebounceMs: 200,
|
|
5604
|
-
bootstrapTicketTtlMs: 6e4,
|
|
5605
|
-
sessionIdleMs: 432e5,
|
|
5606
5604
|
sseHeartbeatMs: 15e3,
|
|
5607
5605
|
ssePendingEvents: 256,
|
|
5608
5606
|
sseStallMs: 6e4,
|
|
@@ -5610,7 +5608,7 @@ const LIMITS = {
|
|
|
5610
5608
|
documentAssetBytes: 100 * MiB,
|
|
5611
5609
|
documentAssets: 500,
|
|
5612
5610
|
cssReferenceDepth: 8,
|
|
5613
|
-
|
|
5611
|
+
renderGrants: 64,
|
|
5614
5612
|
searchLimitDefault: 5,
|
|
5615
5613
|
searchLimitMax: 50,
|
|
5616
5614
|
searchQueryCodePoints: 512,
|
|
@@ -5876,14 +5874,6 @@ strictObject({
|
|
|
5876
5874
|
openDocuments: number().int().nonnegative(),
|
|
5877
5875
|
previewOrigin: string()
|
|
5878
5876
|
});
|
|
5879
|
-
strictObject({
|
|
5880
|
-
bootstrapUrl: string(),
|
|
5881
|
-
uiUrl: string()
|
|
5882
|
-
});
|
|
5883
|
-
strictObject({
|
|
5884
|
-
token: string(),
|
|
5885
|
-
idleTimeoutSeconds: number().int().positive()
|
|
5886
|
-
});
|
|
5887
5877
|
const renderGrantParamsSchema = strictObject({
|
|
5888
5878
|
documentId: documentIdSchema,
|
|
5889
5879
|
revision: revisionSchema.optional(),
|