@sanqianx/project-knowledge 5.0.9-rc.16 → 5.0.9-rc.18
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +23 -0
- package/_site/_test/mcp-server-test.js +1 -1
- package/_site/_test/product-progress-status-test.js +70 -0
- package/_site/_test/vector-storage-root-test.js +16 -0
- package/_site/lib/knowledge-tool-runtime.js +1 -1
- package/module-runtime-manifest.json +11 -11
- package/package.json +2 -2
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/ui/product.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.0.9-rc.18] - 2026-10-12
|
|
4
|
+
|
|
5
|
+
- Project resolution for MCP and CLI now reports the vector-hub storage root
|
|
6
|
+
selected in settings, not the retired shell database path. A running tool
|
|
7
|
+
rereads root changes; resolution does not move files or rebuild indexes.
|
|
8
|
+
- Preserve the reproduced rc.17 diagnostic mismatch and add Node 22/24
|
|
9
|
+
coverage for configured, changed and unset roots and MCP output.
|
|
10
|
+
- Retain existing v5 data and all accepted module pins. Publish to next;
|
|
11
|
+
stable latest remains behind official-package and actual-user acceptance.
|
|
12
|
+
|
|
13
|
+
## [5.0.9-rc.17] - 2026-10-11
|
|
14
|
+
|
|
15
|
+
- Pin Workbench 0.13.2-rc.6. Repeated embedded notifications for the current
|
|
16
|
+
project no longer abandon a newly created session; real project changes and
|
|
17
|
+
late project lookups still preserve isolation and per-project drafts.
|
|
18
|
+
- Keep a healthy progress stream labelled as connected on redundant online
|
|
19
|
+
events. Verify actual offline cleanup, snapshot recovery and exactly one
|
|
20
|
+
restored SSE connection with a real browser and HTTP proxy.
|
|
21
|
+
- Preserve the rc.16 real Workbench failure and require official-package
|
|
22
|
+
streaming/citation acceptance before upgrading the actual user instance.
|
|
23
|
+
- No data reset or third-party configuration overwrite. Stable latest remains
|
|
24
|
+
behind the full candidate and installed-product acceptance gates.
|
|
25
|
+
|
|
3
26
|
## [5.0.9-rc.16] - 2026-10-11
|
|
4
27
|
|
|
5
28
|
- Pin Workbench 0.13.2-rc.5. Bound OpenCode's health request at both
|
|
@@ -113,7 +113,7 @@ async function createMcpClient() {
|
|
|
113
113
|
let response = await client.request('tools/call', { name: 'project_knowledge_resolve', arguments: { repoPath: repo } });
|
|
114
114
|
assert.strictEqual(response.result.structuredContent.projectId, 'project-demo');
|
|
115
115
|
assert.strictEqual(response.result.structuredContent.knowledgePath, markdownKb);
|
|
116
|
-
assert.strictEqual(response.result.structuredContent.indexPath, layout.
|
|
116
|
+
assert.strictEqual(response.result.structuredContent.indexPath, layout.getVectorRootPath(settingsStore.read()));
|
|
117
117
|
|
|
118
118
|
response = await client.request('tools/call', { name: 'project_knowledge_search', arguments: { projectId: 'project-demo', query: 'refresh tokens' } });
|
|
119
119
|
assert.strictEqual(response.result.structuredContent.source, 'knowledge-retrieval-service');
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const http = require('node:http');
|
|
7
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
8
|
+
const { findChrome } = require('./helpers/find-chrome');
|
|
9
|
+
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
10
|
+
|
|
11
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
12
|
+
async function main() {
|
|
13
|
+
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-product-progress-'));
|
|
14
|
+
const snapshot = { projects: [], runs: [], events: [], memories: [], sharedScopes: [] };
|
|
15
|
+
let connections = 0, snapshots = 0, healthRequests = 0;
|
|
16
|
+
const streams = new Set();
|
|
17
|
+
const upstream = http.createServer((req, res) => {
|
|
18
|
+
if (req.url === '/api/progress') {
|
|
19
|
+
connections++; streams.add(res); res.on('close', () => streams.delete(res));
|
|
20
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' });
|
|
21
|
+
res.write(`event: snapshot\ndata: ${JSON.stringify(snapshot)}\n\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
req.resume();
|
|
25
|
+
if (req.url === '/api/snapshot') snapshots++;
|
|
26
|
+
if (req.url.includes('/health')) healthRequests++;
|
|
27
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
28
|
+
res.end(JSON.stringify(req.url === '/api/snapshot' ? snapshot : req.url === '/api/projects' ? { projects: [] } : { ok: true, name: 'knowledge-engine', dataSchemaVersion: 3 }));
|
|
29
|
+
});
|
|
30
|
+
await new Promise(resolve => upstream.listen(0, '127.0.0.1', resolve));
|
|
31
|
+
const moduleUrl = `http://127.0.0.1:${upstream.address().port}`;
|
|
32
|
+
const port = 8400 + process.pid % 200;
|
|
33
|
+
const base = `http://127.0.0.1:${port}`;
|
|
34
|
+
const server = spawnServer({ root: ROOT, port, dataDir: path.join(fixture, 'data'), analysisOwner: 'engine', tag: 'product-progress', extraEnv: {
|
|
35
|
+
KB_TERMINAL_URL: moduleUrl, KB_VECTORHUB_URL: moduleUrl,
|
|
36
|
+
KB_EVENTBRIDGE_URL: moduleUrl, KB_KNOWLEDGE_ENGINE_URL: moduleUrl,
|
|
37
|
+
} });
|
|
38
|
+
let browser;
|
|
39
|
+
try {
|
|
40
|
+
await waitFor(async () => { try { return (await fetch(base + '/api/health')).ok; } catch { return false; } }, 'isolated product', 20000);
|
|
41
|
+
browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(fixture, 'browser'), url: base, width: 1920, height: 1080 });
|
|
42
|
+
await browser.setViewport(1920, 1080);
|
|
43
|
+
const run = expression => browser.evaluate(expression);
|
|
44
|
+
const status = () => run("document.getElementById('activityConnection').textContent");
|
|
45
|
+
await waitFor(async () => await status() === '已连接实时更新', 'initial SSE snapshot');
|
|
46
|
+
assert.equal(connections, 1);
|
|
47
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
48
|
+
const beforeHealth = healthRequests, beforeSnapshot = snapshots;
|
|
49
|
+
await run("window.dispatchEvent(new Event('online'))");
|
|
50
|
+
await waitFor(() => healthRequests > beforeHealth && snapshots > beforeSnapshot, 'online registry and snapshot refresh');
|
|
51
|
+
assert.equal(await status(), '已连接实时更新', 'a duplicate online event must not leave a healthy stream labelled as recovering');
|
|
52
|
+
assert.equal(connections, 1, 'a redundant online receipt cannot create another progress subscription');
|
|
53
|
+
}
|
|
54
|
+
await browser.send('Network.enable');
|
|
55
|
+
await browser.send('Network.emulateNetworkConditions', { offline: true, latency: 0, downloadThroughput: -1, uploadThroughput: -1 });
|
|
56
|
+
await waitFor(async () => (await status()).includes('连接中断'), 'genuine offline feedback');
|
|
57
|
+
await waitFor(() => streams.size === 0, 'offline closes the upstream stream');
|
|
58
|
+
await browser.send('Network.emulateNetworkConditions', { offline: false, latency: 0, downloadThroughput: -1, uploadThroughput: -1 });
|
|
59
|
+
await waitFor(async () => await status() === '已连接实时更新' && connections === 2, 'new snapshot and stream after real reconnection');
|
|
60
|
+
assert.equal(streams.size, 1);
|
|
61
|
+
assert.equal(browser.exceptions.length, 0);
|
|
62
|
+
console.log('product-progress-status-test PASS (real product/browser/proxy/SSE; duplicate online stays connected; real offline reconnects once)');
|
|
63
|
+
} finally {
|
|
64
|
+
await browser?.close(); await server.cleanup();
|
|
65
|
+
upstream.closeAllConnections(); await new Promise(resolve => upstream.close(resolve));
|
|
66
|
+
assert.equal(path.dirname(fixture), path.resolve(os.tmpdir())); assert.ok(path.basename(fixture).startsWith('pk-product-progress-'));
|
|
67
|
+
fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|
|
@@ -3,11 +3,27 @@ const assert = require('node:assert/strict');
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { StorageLayout } = require('../lib/storage-layout');
|
|
5
5
|
const { ModuleBridge } = require('../lib/module-bridge');
|
|
6
|
+
const { KnowledgeToolRuntime } = require('../lib/knowledge-tool-runtime');
|
|
6
7
|
const root = path.resolve('D:/knowledge storage 中文');
|
|
7
8
|
const layout = new StorageLayout({ dataDir: path.resolve('D:/metadata') });
|
|
8
9
|
assert.equal(layout.getVectorRootPath({ knowledge: { rootPath: root } }), path.join(root, '.indexes'));
|
|
9
10
|
assert.equal(layout.getVectorRootPath({ knowledge: { rootPath: '' } }), path.join(layout.getDataDir(), 'vectorhub'));
|
|
10
11
|
const settingsStore = { read: () => ({ knowledge: { rootPath: root }, brain: { id: 'brain-default', domains: [] } }) };
|
|
12
|
+
let knowledgeRoot = root;
|
|
13
|
+
const tools = new KnowledgeToolRuntime({
|
|
14
|
+
layout,
|
|
15
|
+
settingsStore: { read: () => ({ knowledge: { rootPath: knowledgeRoot } }) },
|
|
16
|
+
registryStore: { listIds: () => ['project-storage'] },
|
|
17
|
+
projectStore: {
|
|
18
|
+
readConfig: () => ({ displayName: 'Storage', repoPath: path.join(root, 'repo'), knowledgePath: path.join(root, 'project') }),
|
|
19
|
+
readState: () => ({ index: { generation: 3 } }),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
assert.equal(tools.resolveProject({ projectId: 'project-storage' }).indexPath, path.join(root, '.indexes'), 'MCP/CLI resolution must report the vector-hub location, not the retired shell database');
|
|
23
|
+
knowledgeRoot = path.join(root, 'changed root');
|
|
24
|
+
assert.equal(tools.resolveProject({ projectId: 'project-storage' }).indexPath, path.join(knowledgeRoot, '.indexes'), 'a running MCP process must reread the selected knowledge root');
|
|
25
|
+
knowledgeRoot = '';
|
|
26
|
+
assert.equal(tools.resolveProject({ projectId: 'project-storage' }).indexPath, path.join(layout.getDataDir(), 'vectorhub'));
|
|
11
27
|
const bridge = new ModuleBridge({ settingsStore, dataDir: layout.getDataDir() });
|
|
12
28
|
const spec = bridge._supervisedSpecs().find(spec => spec.name === 'vector-hub');
|
|
13
29
|
assert.equal(spec.env.VECTOR_HUB_ROOT, path.join(root, '.indexes'));
|
|
@@ -163,7 +163,7 @@ class KnowledgeToolRuntime {
|
|
|
163
163
|
repoPath: path.resolve(resolvedRepoPath || config.repoPath),
|
|
164
164
|
knowledgePath: path.resolve(config.knowledgePath),
|
|
165
165
|
kbPath: path.resolve(config.knowledgePath),
|
|
166
|
-
indexPath: this.layout.
|
|
166
|
+
indexPath: this.layout.getVectorRootPath(this.settingsStore.read({ allowMissing: true })),
|
|
167
167
|
primarySpaceId: `project:${projectId}`,
|
|
168
168
|
relatedProjectIds: Array.isArray(config.relatedProjectIds) ? [...config.relatedProjectIds] : [],
|
|
169
169
|
index: { ...state.index },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema": "project-knowledge/module-content/v1",
|
|
3
3
|
"productFiles": {
|
|
4
4
|
"ui/index.html": "5cc3f9519a0671e7d5b24a94987a96b7ee81d3ed3367e3c02cd70934f08e64c8",
|
|
5
|
-
"ui/product.js": "
|
|
5
|
+
"ui/product.js": "e7ac0fc1d1d515039f981ed8481155d112034dc4961a2e7259864720f54c83ea",
|
|
6
6
|
"ui/product.css": "70547d3f2c53b6cc5c73cb95eb8cb3faf0dc8ccee7cd6b11a89a88885de7c3f8",
|
|
7
7
|
"ui/favicon.svg": "c48be946b224112ebb0238b44263f7a61bda31e531afa895c2e9801c0ff24f32"
|
|
8
8
|
},
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"terminal": {
|
|
11
11
|
"name": "@sanqianx/claude-ai-workbench",
|
|
12
12
|
"installName": "claude-ai-workbench",
|
|
13
|
-
"version": "0.13.2-rc.
|
|
13
|
+
"version": "0.13.2-rc.6",
|
|
14
14
|
"files": {
|
|
15
15
|
"LICENSE": "c1b9df1275e769f3dbab000d1e457a2d4b0f28eb5da6c77e48dc37eeba202ed7",
|
|
16
|
-
"README.md": "
|
|
16
|
+
"README.md": "5a382c531372deaa63b3e014f4dbd077d41ce02efee40347973463081848cb86",
|
|
17
17
|
"apps/agent-terminal/app.css": "39cc65ac65bcb390f4aa21d9b783182ca229557f6bec15090f69598120161917",
|
|
18
|
-
"apps/agent-terminal/app.js": "
|
|
18
|
+
"apps/agent-terminal/app.js": "16486a5def42174f6ce75fcf3866b6eb965a56eae827b0b4f91882346ab6f1df",
|
|
19
19
|
"apps/agent-terminal/index.html": "6a52ba210696c8aed99e2786e21d2771340070d51583199dd74c8da41edf0257",
|
|
20
20
|
"apps/ai-profile-settings-host/index.html": "bc158f68954506d2b8e43c690f28dbe931eff9a02b1524855ac17e74ea4df736",
|
|
21
21
|
"apps/ai-profile-settings-host/page.css": "8b75cbf7f1ea5aaa92f2889390720ece310a8fd4b9ca38c890fa447d4323a557",
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
"apps/minimal-test-host/README.md": "fa73136f4ee3865c2d91eb97b2e8c03679aa4688e44c1e1c8ec6e27d604b0de5",
|
|
28
28
|
"docs/sdk/FILE_LAYOUT.md": "17882f96505c8161bfb66876684b9d2d3f50021a111b8b844502d3cb4e97870a",
|
|
29
29
|
"docs/sdk/INTEGRATION.md": "d4c0c80d2d2fc610205102d703a429bfaa20f9f3a48f1718f5bab02ee90138df",
|
|
30
|
-
"docs/sdk/openapi.yaml": "
|
|
30
|
+
"docs/sdk/openapi.yaml": "43f4476ce8511efb5668f3236cd3e27de890c171eedb2366670626754b9247e6",
|
|
31
31
|
"docs/sdk/README.md": "7ab591d39c36a3b84cfdf487ff567040ec453975f3ef0fba86dc822094667b61",
|
|
32
|
-
"package.json": "
|
|
32
|
+
"package.json": "34e4bf492c3095d74ed04d9e3d8c9d288e07f681566622ce12621a27e83533c1",
|
|
33
33
|
"packages/client/index.d.ts": "fb66b7b21ac936cddcb7f74543cf4f3bb7e80c169534ec249ab8ef2356224162",
|
|
34
34
|
"packages/client/index.js": "92fe5ddd32289d729f336f669e47bd9601e562f5a4309ee4fa7f7f15b567edc2",
|
|
35
|
-
"packages/client/package.json": "
|
|
35
|
+
"packages/client/package.json": "287b0669ef32b04986596ef0fede6d3b07025814548127b9d1edda0dca9c6fc5",
|
|
36
36
|
"packages/client/README.md": "342dff9b1dcbc85fccc9b980ed3e4e455aa71133af1d0805b8431ea2ac564ef8",
|
|
37
37
|
"packages/contracts/index.d.ts": "edc717067bfb864c6c95df0c408a799fbf90b7552519131e6dc35af3f9eb9006",
|
|
38
38
|
"packages/contracts/index.js": "565b4a88190dbb8994cd0a9309b30b00f64305d65505fff9fa360d7ce69dfb58",
|
|
39
|
-
"packages/contracts/package.json": "
|
|
39
|
+
"packages/contracts/package.json": "0902c2cd6e262823d774d8d2cd07c0726680e8f26d4f06afc80aa385c9a4524b",
|
|
40
40
|
"packages/contracts/README.md": "44d83a3cc508cec6c5c20dfa1f2c08d5ea03e3afb2853639fdc0a9cc8eb5b9c8",
|
|
41
41
|
"packages/core/index.d.ts": "c95b24a13f8aeeb128497833cf298790563baca6036ea5d29a9d8daff53199d7",
|
|
42
42
|
"packages/core/index.js": "5579ceb1a63d3f962f5656c75f82cf60cd834fd270b4aa95e581bf38e4432448",
|
|
43
|
-
"packages/core/package.json": "
|
|
43
|
+
"packages/core/package.json": "0d4672540365af4a40028e0cd0f2f838e73936d4ae3d11fa71f7770289a36b38",
|
|
44
44
|
"packages/core/README.md": "e222c546104a168f1038d95c6c4efb8e5603edd6145ab2cab79cab977fb710e5",
|
|
45
45
|
"packages/server/bin/agent-terminal-server.js": "043dff4d76e3fd213b666a55e581ed1e72f9e5b8de6cb798fbf42bd39ed26861",
|
|
46
46
|
"packages/server/bin/claude-workbench-server.js": "b03c730b6d86faf6974afea26c74109d59b1bbc1ecc656c1d6270258708694ac",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"packages/server/lib/system-doctor.js": "2b0e858d69d8fb268eaed950c9883473ee9850684b532bc2db8e14674a974919",
|
|
75
75
|
"packages/server/lib/ui-preferences-store.js": "e108cb22e7cc1f3438704913d5401e01de2c0e717eb2abd3bdc4727209393819",
|
|
76
76
|
"packages/server/lib/workbench-service.js": "92b9323db8742bbdd69abd111c81cab86b225103596f6818b007fbfe2376fc6f",
|
|
77
|
-
"packages/server/package.json": "
|
|
77
|
+
"packages/server/package.json": "5d0256b88560fb71b7ed544d0799749e3bee3915326c3513a605a5574283b2b9",
|
|
78
78
|
"packages/server/README.md": "a34cfaa4f4e859008b44e2be8e1aafb56e97fe83e6129c968a8562945fa843f3",
|
|
79
79
|
"packages/ui/ai-profile-settings.d.ts": "0551b6c6eda6d18ea89a976daddf01b2161195f4874fe2d67a57cd9ecc6ce643",
|
|
80
80
|
"packages/ui/ai-profile-settings.js": "e92bbac67a1199d473e4e44b32d1cc76c5b6c79306d4fef9d9067a1bb6ec42ca",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"packages/ui/claude-terminal.js": "96bd217a4a7e4792ae3fbaaf46249ecee13b0f4e01fc6d25a1b8b22d8f8fe00c",
|
|
83
83
|
"packages/ui/index.d.ts": "e547ab39640fe7519fb367b78442dc8a2c51b2efd17b66f8b0a003b154b36ead",
|
|
84
84
|
"packages/ui/index.js": "f4394aabd395938f5c3fe587e430471d4033e83945a9b9c7e0149ad4040982c9",
|
|
85
|
-
"packages/ui/package.json": "
|
|
85
|
+
"packages/ui/package.json": "3710b0d80c29435e9845cea8e03ec056cee5818d360a653c2c6fbc9de47b7ab1",
|
|
86
86
|
"packages/ui/README.md": "793c4a9c286fee23f39062a1976ca3afe9d2816dcdb55e156ba940e1ef514f89",
|
|
87
87
|
"packages/ui/theme.css": "8f03a081272a7db11b196f7a70d0fafb1f8d954778f329f1ba53d110d9d5fd98",
|
|
88
88
|
"src/assets/claude-prompts.json": "cb111f2e0a45afe8a31c251a504d1ab5375966ed61291660e61fd2efd77f6d9f",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanqianx/project-knowledge",
|
|
3
|
-
"version": "5.0.9-rc.
|
|
3
|
+
"version": "5.0.9-rc.18",
|
|
4
4
|
"description": "Knowledge base manager with Git integration, AI-driven analysis, and bilingual (zh-CN/en-US) knowledge output",
|
|
5
5
|
"main": "_site/server.js",
|
|
6
6
|
"scripts": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@sanqianx/vector-hub": "1.0.8-rc.3",
|
|
85
85
|
"buffer": "^6.0.3",
|
|
86
86
|
"cheerio": "^1.2.0",
|
|
87
|
-
"claude-ai-workbench": "npm:@sanqianx/claude-ai-workbench@0.13.2-rc.
|
|
87
|
+
"claude-ai-workbench": "npm:@sanqianx/claude-ai-workbench@0.13.2-rc.6",
|
|
88
88
|
"cross-spawn": "7.0.6",
|
|
89
89
|
"dotenv": "^17.4.2",
|
|
90
90
|
"gpt-tokenizer": "^3.4.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "project-knowledge",
|
|
4
|
-
"version": "5.0.9-rc.
|
|
4
|
+
"version": "5.0.9-rc.18",
|
|
5
5
|
"description": "Read durable project decisions and implementation history from Project Knowledge.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SanQianX",
|
package/ui/product.js
CHANGED
|
@@ -451,7 +451,7 @@
|
|
|
451
451
|
renderSystem(); render();
|
|
452
452
|
});
|
|
453
453
|
window.addEventListener('online', () => {
|
|
454
|
-
$('activityConnection').textContent = '正在恢复快照'; refreshRegistry();
|
|
454
|
+
$('activityConnection').textContent = state.engineAvailable && state.stream?.readyState === EventSource.OPEN ? '已连接实时更新' : '正在恢复快照'; refreshRegistry();
|
|
455
455
|
if ($('logsDialog').open) loadLogs().catch(error => toast(error.message));
|
|
456
456
|
});
|
|
457
457
|
window.addEventListener('beforeunload', () => { state.stream?.close(); logStream?.close(); });
|