@sanqianx/project-knowledge 5.0.17-rc.3 → 5.0.18-rc.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.
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "project-knowledge",
12
- "version": "5.0.17-rc.3",
12
+ "version": "5.0.18-rc.1",
13
13
  "source": "./plugins/project-knowledge",
14
14
  "description": "Search durable project decisions and implementation history through MCP."
15
15
  }
package/CHANGELOG.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Changelog
2
2
 
3
- ## [5.0.17-rc.3] - 2026-10-15
3
+ ## [5.0.17] - 2026-10-15
4
4
 
5
5
  - 修复网页首屏被慢模块拖住、开始工作 Agent 探测同步阻塞与返回页面状态不刷新的问题;独立读取、明确失败状态和缓存保留,iframe 不重建。
6
6
  - 同源实时连接合并为每标签一条 WebSocket,保留独立模块 SSE,恢复时先读取快照并按游标续接;项目接线失败不再阻塞其他项目。
7
7
  - 工作台输出时向上滚轮立即暂停跟随,完整历史独立持久化并向前分页;旧 Claude 历史只读恢复,不再把有限事件缓存冒充全会话。
8
- - 精确锁定 Workbench 0.13.2-rc.17 与引擎 0.2.7-rc.10;补齐诊断和任务启动的异步发现旁路,保留 ZCode 注册表安装路径,并阻止已中止或被替换的初始化迟到启动任务。候选验收不改变 latest,正式结果见 5.0.17 验收记录。
8
+ - 精确锁定 Workbench 0.13.2-rc.17 与引擎 0.2.7-rc.10;补齐诊断和任务启动的异步发现旁路,保留 ZCode 注册表安装路径,并阻止已中止或被替换的初始化迟到启动任务。最终候选已通过独立下载安装、真实 MiniMax 与向量闭环,正式包先发 next 验收后再晋升 latest,详见 5.0.17 验收记录。
9
9
 
10
10
  ## [5.0.16] - 2026-10-15
11
11
 
@@ -1,87 +1,100 @@
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 { createRequire } = require('node:module');
8
- const coreRoot = path.resolve(process.env.KB_CAPTURE_TEST_CORE_ROOT || path.join(__dirname, '../..'));
9
- const fromCore = createRequire(path.join(coreRoot, 'package.json'));
10
- const { createRuntime, initializeRuntime, createRequestHandler } = fromCore('./_site/lib/server-app');
11
- const { BridgeAdapter } = fromCore('./_site/lib/bridge-adapter');
12
- const { ModuleBridge } = fromCore('./_site/lib/module-bridge');
13
- const { findChrome } = require('./helpers/find-chrome');
14
- const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
15
- const { Journal } = fromCore('@sanqianx/ai-coding-event-bridge-console/src/bridge');
16
- const { spawnSync } = require('node:child_process');
17
-
18
- async function main() {
19
- const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-agent-capture-ui-'));
20
- const dataDir = path.join(temp, 'data');
21
- const homeDir = path.join(temp, '用户 空格');
22
- const eventsHome = path.join(dataDir, 'events');
23
- const configFile = path.join(homeDir, '.zcode', 'cli', 'config.json');
24
- fs.mkdirSync(path.dirname(configFile), { recursive: true });
25
- fs.writeFileSync(configFile, JSON.stringify({ provider: { name: 'retain' } }));
26
- const offline = http.createServer((req, res) => { res.writeHead(503, { 'content-type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { message: '本轮测试中非采集模块离线' } })); });
27
- await new Promise(resolve => offline.listen(0, '127.0.0.1', resolve));
28
- const offlineUrl = `http://127.0.0.1:${offline.address().port}`;
29
- const runtime = createRuntime({ dataDir, analysisOwner: 'engine', bridgeAdapter: new BridgeAdapter({ dataDir, bridgeHomeDir: eventsHome }), moduleBridge: new ModuleBridge({ terminalUrl: offlineUrl, engineUrl: offlineUrl, eventBridgeUrl: offlineUrl, vectorHubUrl: offlineUrl }) });
30
- await initializeRuntime(runtime);
31
- const server = http.createServer(createRequestHandler(runtime, { integrationOptions: { homeDir, env: {}, commandResolver: () => '' } }));
32
- await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
33
- const base = `http://127.0.0.1:${server.address().port}`;
34
- let browser;
35
- try {
36
- browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(temp, 'browser'), url: base, width: 1920, height: 1080 });
37
- await browser.setViewport(1920, 1080);
38
- const run = expression => browser.evaluate(expression);
39
- const click = selector => run(`document.querySelector(${JSON.stringify(selector)}).click()`);
40
- await click('#openSystem');
41
- await waitFor(() => run("document.querySelectorAll('#agentCaptureList .agent-capture-row').length === 4"), 'four capture statuses');
42
- assert.equal(await run("document.querySelector('[data-agent-client=claude]').disabled"), true);
43
- assert.equal(await run("document.querySelector('[data-agent-client=zcode]').disabled"), false);
44
- assert(await run("document.getElementById('agentCaptureList').textContent.includes('检测到配置')"));
45
- await click('[data-agent-client=zcode][data-agent-capture=install]');
46
- await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') !== null"), 'installed status reloaded from backend');
47
- const config = JSON.parse(fs.readFileSync(configFile));
48
- assert.equal(config.hooks.enabled, true);
49
- const hook = config.hooks.events.UserPromptSubmit[0].hooks[0];
50
- const env = { ...process.env }; delete env.AI_CODING_EVENT_BRIDGE_HOME; delete env.AI_CODING_EVENT_BRIDGE_CAPTURE;
51
- const child = spawnSync(process.execPath, hook.args, { env, input: JSON.stringify({ hook_event_name: 'UserPromptSubmit', session_id: 'ui-zcode-session', cwd: temp, prompt: '从网页接入后写入真实 journal' }), encoding: 'utf8', timeout: 15000 });
52
- assert.equal(child.status, 0, child.stderr);
53
- const events = await new Journal(path.join(eventsHome, 'journal')).readEvents({});
54
- assert.equal(events.length, 1); assert.equal(events[0].source, 'zcode');
55
- await click('[data-agent-client=zcode][data-agent-capture=install]');
56
- await waitFor(() => run("!document.querySelector('[data-agent-client=zcode]').disabled"), 'repair response');
57
- assert.equal(JSON.parse(fs.readFileSync(configFile)).hooks.events.UserPromptSubmit.length, 1);
58
- assert.equal(await run('document.documentElement.scrollWidth > innerWidth'), false);
59
- const artifacts = path.resolve(process.env.KB_CAPTURE_TEST_ARTIFACTS || path.join(__dirname, '../..', '.agent-state/acceptance/agent-capture'));
60
- await browser.screenshot(path.join(artifacts, '1920-light.png'));
61
- await click('#closeSystem'); await click('#themeToggle'); await click('#openSystem');
62
- await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') !== null"), 'theme retains capture status');
63
- await browser.screenshot(path.join(artifacts, '1920-dark.png'));
64
- fs.writeFileSync(configFile, '{invalid JSON');
65
- await waitFor(() => run("(() => { const b = document.querySelector('[data-agent-client=zcode][data-agent-capture=install]'); return b !== null && !b.disabled; })()"), 'capture row settled before invalid config');
66
- await click('[data-agent-client=zcode][data-agent-capture=install]');
67
- await waitFor(() => run("document.getElementById('agentCaptureFeedback').textContent.includes('操作 ID')"), 'partial failure rendered');
68
- assert(await run("document.getElementById('agentCaptureFeedback').classList.contains('issue')"));
69
- const logs = (await (await fetch(base + '/api/logs?event=integration.capture.failed')).json()).entries;
70
- assert(logs.length > 0 && logs[0].error.message.includes('valid JSON'));
71
- fs.writeFileSync(configFile, JSON.stringify(config));
72
- await click('#refreshAgentCapture');
73
- await waitFor(() => run("(() => { const b = document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]'); return b !== null && !b.disabled; })()"), 'manual status retry');
74
- await click('[data-agent-client=zcode][data-agent-capture=uninstall]');
75
- await waitFor(() => run("!document.querySelector('[data-agent-client=zcode]').disabled && document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') === null"), 'removed capture status');
76
- assert.equal(JSON.parse(fs.readFileSync(configFile)).provider.name, 'retain');
77
- assert.equal((await new Journal(path.join(eventsHome, 'journal')).readEvents({})).length, 1, 'removal retains prior evidence');
78
- assert.equal(browser.exceptions.length, 0, JSON.stringify(browser.exceptions));
79
- console.log(`agent-capture-ui-test PASS (1920x1080; real install/repair/Hook/journal/error logs/removal/light+dark; core=${coreRoot})`);
80
- } finally {
81
- await browser?.close(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve));
82
- offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve)); await runtime.logger.close();
83
- assert.equal(path.dirname(temp), path.resolve(os.tmpdir())); assert.ok(path.basename(temp).startsWith('kb-agent-capture-ui-'));
84
- fs.rmSync(temp, { recursive: true, force: true, maxRetries: 12, retryDelay: 200 });
85
- }
86
- }
87
- main().catch(error => { console.error(error.stack); process.exitCode = 1; });
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 { createRequire } = require('node:module');
8
+ const coreRoot = path.resolve(process.env.KB_CAPTURE_TEST_CORE_ROOT || path.join(__dirname, '../..'));
9
+ const fromCore = createRequire(path.join(coreRoot, 'package.json'));
10
+ const { createRuntime, initializeRuntime, createRequestHandler } = fromCore('./_site/lib/server-app');
11
+ const { BridgeAdapter } = fromCore('./_site/lib/bridge-adapter');
12
+ const { ModuleBridge } = fromCore('./_site/lib/module-bridge');
13
+ const { findChrome } = require('./helpers/find-chrome');
14
+ const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
15
+ const { Journal } = fromCore('@sanqianx/ai-coding-event-bridge-console/src/bridge');
16
+ const { spawnSync } = require('node:child_process');
17
+
18
+ async function main() {
19
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-agent-capture-ui-'));
20
+ const dataDir = path.join(temp, 'data');
21
+ const homeDir = path.join(temp, '用户 空格');
22
+ const eventsHome = path.join(dataDir, 'events');
23
+ const configFile = path.join(homeDir, '.zcode', 'cli', 'config.json');
24
+ const intentFile = path.join(dataDir, 'integration-intent.json');
25
+ fs.mkdirSync(path.dirname(configFile), { recursive: true });
26
+ fs.writeFileSync(configFile, JSON.stringify({ provider: { name: 'retain' } }));
27
+ const offline = http.createServer((req, res) => { res.writeHead(503, { 'content-type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { message: '本轮测试中非采集模块离线' } })); });
28
+ await new Promise(resolve => offline.listen(0, '127.0.0.1', resolve));
29
+ const offlineUrl = `http://127.0.0.1:${offline.address().port}`;
30
+ const runtime = createRuntime({ dataDir, analysisOwner: 'engine', bridgeAdapter: new BridgeAdapter({ dataDir, bridgeHomeDir: eventsHome }), moduleBridge: new ModuleBridge({ terminalUrl: offlineUrl, engineUrl: offlineUrl, eventBridgeUrl: offlineUrl, vectorHubUrl: offlineUrl }) });
31
+ await initializeRuntime(runtime);
32
+ const server = http.createServer(createRequestHandler(runtime, { integrationOptions: { homeDir, env: {}, commandResolver: () => '' } }));
33
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
34
+ const base = `http://127.0.0.1:${server.address().port}`;
35
+ let browser;
36
+ try {
37
+ browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(temp, 'browser'), url: base, width: 1920, height: 1080 });
38
+ await browser.setViewport(1920, 1080);
39
+ const run = expression => browser.evaluate(expression);
40
+ const click = selector => run(`document.querySelector(${JSON.stringify(selector)}).click()`);
41
+ await click('#openSystem');
42
+ await waitFor(() => run("document.querySelectorAll('#agentCaptureList .agent-capture-row').length === 4"), 'four capture statuses');
43
+ // Single-switch rows: claude undetected -> disabled switch; zcode configured -> enabled, badge 未接入.
44
+ assert.equal(await run("document.querySelector('[data-agent-client=claude][data-agent-switch]').disabled"), true);
45
+ assert.equal(await run("document.querySelector('[data-agent-client=zcode][data-agent-switch]').disabled"), false);
46
+ await waitFor(() => run("document.querySelector('[data-agent-client=zcode]').closest('.agent-capture-row').textContent.includes('未接入')"), 'zcode badge settles to disconnected');
47
+ assert(await run("document.getElementById('agentCaptureList').textContent.includes('检测到配置')"));
48
+ assert.equal(await run("document.querySelector('[data-agent-client=zcode]').closest('.agent-capture-row').querySelector('details.agent-detail').open"), false, 'details must be collapsed by default');
49
+ assert.equal(await run("document.getElementById('refreshAgentCapture')"), null, 'manual status button removed');
50
+ // Switch ON: full install (knowledge + capture), real config written.
51
+ await click('[data-agent-client=zcode][data-agent-switch=install]');
52
+ await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-switch=uninstall]') !== null"), 'connected status reloaded from backend');
53
+ assert(await run("document.querySelector('[data-agent-client=zcode]').closest('.agent-capture-row').textContent.includes('已接入')"));
54
+ const config = JSON.parse(fs.readFileSync(configFile));
55
+ assert.equal(config.hooks.enabled, true);
56
+ const intent = JSON.parse(fs.readFileSync(intentFile));
57
+ assert.equal(intent.clients.zcode, 'connected', 'successful install records intent');
58
+ const env = { ...process.env }; delete env.AI_CODING_EVENT_BRIDGE_HOME; delete env.AI_CODING_EVENT_BRIDGE_CAPTURE;
59
+ // Full install wires every UserPromptSubmit hook (context pre-read first,
60
+ // capture second) — run them all like the real client does.
61
+ for (const hook of config.hooks.events.UserPromptSubmit.flatMap(entry => entry.hooks)) {
62
+ const child = spawnSync(process.execPath, hook.args, { env, input: JSON.stringify({ hook_event_name: 'UserPromptSubmit', session_id: 'ui-zcode-session', cwd: temp, prompt: '从网页接入后写入真实 journal' }), encoding: 'utf8', timeout: 15000 });
63
+ assert.equal(child.status, 0, child.stderr);
64
+ }
65
+ const events = await new Journal(path.join(eventsHome, 'journal')).readEvents({});
66
+ assert.equal(events.length, 1); assert.equal(events[0].source, 'zcode');
67
+ assert.equal(await run('document.documentElement.scrollWidth > innerWidth'), false);
68
+ const artifacts = path.resolve(process.env.KB_CAPTURE_TEST_ARTIFACTS || path.join(__dirname, '../..', '.agent-state/acceptance/agent-capture'));
69
+ await browser.screenshot(path.join(artifacts, '1920-light.png'));
70
+ await click('#closeSystem'); await click('#themeToggle'); await click('#openSystem');
71
+ await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-switch=uninstall]') !== null"), 'theme retains capture status');
72
+ await browser.screenshot(path.join(artifacts, '1920-dark.png'));
73
+ // Broken config: the single switch acts as the explicit repair path.
74
+ fs.writeFileSync(configFile, '{invalid JSON');
75
+ await click('#closeSystem'); await click('#openSystem');
76
+ await waitFor(() => run("(() => { const b = document.querySelector('[data-agent-client=zcode][data-agent-switch=install]'); return b !== null && !b.disabled; })()"), 'capture row settled before invalid config');
77
+ await click('[data-agent-client=zcode][data-agent-switch=install]');
78
+ await waitFor(() => run("document.getElementById('agentCaptureFeedback').textContent.includes('操作未完成')"), 'partial failure rendered');
79
+ assert(await run("document.getElementById('agentCaptureFeedback').classList.contains('issue')"));
80
+ const logs = (await (await fetch(base + '/api/logs?event=integration.capture.failed')).json()).entries;
81
+ assert(logs.length > 0 && logs[0].error.message.includes('valid JSON'));
82
+ fs.writeFileSync(configFile, JSON.stringify(config));
83
+ await click('#closeSystem'); await click('#openSystem');
84
+ await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-switch=uninstall]') !== null"), 'switch repair succeeds');
85
+ // Switch OFF: removal keeps third-party config and prior evidence.
86
+ await click('[data-agent-client=zcode][data-agent-switch=uninstall]');
87
+ await waitFor(() => run("!document.querySelector('[data-agent-client=zcode][data-agent-switch]').disabled && document.querySelector('[data-agent-client=zcode][data-agent-switch=uninstall]') === null"), 'removed capture status');
88
+ assert.equal(JSON.parse(fs.readFileSync(configFile)).provider.name, 'retain');
89
+ assert.equal(JSON.parse(fs.readFileSync(intentFile)).clients.zcode, 'disconnected', 'uninstall records disconnected intent');
90
+ assert.equal((await new Journal(path.join(eventsHome, 'journal')).readEvents({})).length, 1, 'removal retains prior evidence');
91
+ assert.equal(browser.exceptions.length, 0, JSON.stringify(browser.exceptions));
92
+ console.log(`agent-capture-ui-test PASS (1920x1080; single-switch install/repair/Hook/journal/error logs/intent/removal/light+dark; core=${coreRoot})`);
93
+ } finally {
94
+ await browser?.close(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve));
95
+ offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve)); await runtime.logger.close();
96
+ assert.equal(path.dirname(temp), path.resolve(os.tmpdir())); assert.ok(path.basename(temp).startsWith('kb-agent-capture-ui-'));
97
+ fs.rmSync(temp, { recursive: true, force: true, maxRetries: 12, retryDelay: 200 });
98
+ }
99
+ }
100
+ main().catch(error => { console.error(error.stack); process.exitCode = 1; });
@@ -20,7 +20,7 @@ assert(css.includes('@media(prefers-reduced-motion:reduce)'), 'reduced-motion co
20
20
  assert(css.includes('grid-template-columns:232px minmax(0,1fr)') && css.includes('min-width:0'), 'desktop shell must bound its content layout');
21
21
 
22
22
  assert(preload.includes('project-knowledge:open-external') && preload.includes('openExternal:'), 'desktop preload bounded external-link bridge regressed');
23
- assert(main.includes('externalLink.registerExternalLink'), 'desktop main external-link policy regressed');
23
+ assert(main.includes("ipcMain.handle('project-knowledge:open-external'") && main.includes('runtime.isAllowedExternalUrl(url)'), 'desktop main external-link policy regressed');
24
24
  assert(runtime.isAllowedExternalUrl('https://github.com/login/device'), 'HTTPS authorization URL policy regressed');
25
25
  assert(!runtime.isAllowedExternalUrl('file:///C:/Users/test/token.txt'), 'local files must not cross the desktop boundary');
26
26
  assert(!runtime.isAllowedExternalUrl('javascript:alert(1)'), 'script URLs must not cross the desktop boundary');