@sanqianx/project-knowledge 5.0.1 → 5.0.2
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 +12 -0
- package/README.md +5 -0
- package/_site/_test/agent-capture-ui-test.js +85 -0
- package/_site/_test/zcode-capture-integration-test.js +107 -0
- package/_site/lib/integration-installer.js +74 -33
- package/_site/lib/server-app.js +9 -8
- package/bin/project-knowledge-integrations.js +13 -2
- package/docs/README.zh-CN.md +17 -0
- package/module-runtime-manifest.json +13 -13
- 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/index.html +1 -0
- package/ui/product.css +1 -0
- package/ui/product.js +33 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.0.2] - 2026-10-10
|
|
4
|
+
|
|
5
|
+
- Add ZCode capture to the unified CLI and web System Status connector setup,
|
|
6
|
+
including repair, status and safe removal. Keep unverified ZCode knowledge
|
|
7
|
+
plugin support explicitly separate from working capture hooks.
|
|
8
|
+
- Correct command/config discovery labels and stop treating consumer
|
|
9
|
+
registration or disabled hooks as proof of capture. Report partial failures
|
|
10
|
+
with their operation ID and persist connector errors from both web and CLI.
|
|
11
|
+
- Pin event-bridge 0.7.2: external hooks resolve their own Bridge home without
|
|
12
|
+
inherited environment variables; ZCode events preserve their source. Repair
|
|
13
|
+
older entries in place and retain third-party hooks within mixed groups.
|
|
14
|
+
|
|
3
15
|
## [5.0.1] - 2026-10-10
|
|
4
16
|
|
|
5
17
|
- Fix project import on Git versions before 2.31: query hooks and common Git
|
package/README.md
CHANGED
|
@@ -30,6 +30,11 @@ project-knowledge
|
|
|
30
30
|
agent conversations in real Git workspaces are captured into each
|
|
31
31
|
imported project's Development Conversation.
|
|
32
32
|
It also registers the host-level `project-knowledge` Bridge consumer.
|
|
33
|
+
ZCode is supported for Development Capture only; an unverified knowledge
|
|
34
|
+
plugin is never reported as installed. Use `install --ide zcode --capture-only`
|
|
35
|
+
or System Status → 外部 Agent 采集 in the web UI. Per-client repair/removal
|
|
36
|
+
preserves unrelated hooks and existing records. Failures include an operation
|
|
37
|
+
ID and persist to the local system diagnostic log.
|
|
33
38
|
No client UI needs to be opened; third-party hooks and configs are
|
|
34
39
|
preserved, and a Codex notify conflict is reported instead of overwritten.
|
|
35
40
|
3. `project-knowledge` starts a fresh v5 local backend. Import projects again;
|
|
@@ -0,0 +1,85 @@
|
|
|
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 click('[data-agent-client=zcode][data-agent-capture=install]');
|
|
66
|
+
await waitFor(() => run("document.getElementById('agentCaptureFeedback').textContent.includes('操作 ID')"), 'partial failure rendered');
|
|
67
|
+
assert(await run("document.getElementById('agentCaptureFeedback').classList.contains('issue')"));
|
|
68
|
+
const logs = (await (await fetch(base + '/api/logs?event=integration.capture.failed')).json()).entries;
|
|
69
|
+
assert(logs.length > 0 && logs[0].error.message.includes('valid JSON'));
|
|
70
|
+
fs.writeFileSync(configFile, JSON.stringify(config));
|
|
71
|
+
await click('#refreshAgentCapture');
|
|
72
|
+
await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') !== null"), 'manual status retry');
|
|
73
|
+
await click('[data-agent-client=zcode][data-agent-capture=uninstall]');
|
|
74
|
+
await waitFor(() => run("!document.querySelector('[data-agent-client=zcode]').disabled && document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') === null"), 'removed capture status');
|
|
75
|
+
assert.equal(JSON.parse(fs.readFileSync(configFile)).provider.name, 'retain');
|
|
76
|
+
assert.equal((await new Journal(path.join(eventsHome, 'journal')).readEvents({})).length, 1, 'removal retains prior evidence');
|
|
77
|
+
assert.equal(browser.exceptions.length, 0, JSON.stringify(browser.exceptions));
|
|
78
|
+
console.log(`agent-capture-ui-test PASS (1920x1080; real install/repair/Hook/journal/error logs/removal/light+dark; core=${coreRoot})`);
|
|
79
|
+
} finally {
|
|
80
|
+
await browser?.close(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve));
|
|
81
|
+
offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve)); await runtime.logger.close();
|
|
82
|
+
fs.rmSync(temp, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
main().catch(error => { console.error(error.stack); process.exitCode = 1; });
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
const { IntegrationManager, normalizeClient, SUPPORTED_CLIENTS } = require('../lib/integration-installer');
|
|
8
|
+
const { createRuntime, createRequestHandler } = require('../lib/server-app');
|
|
9
|
+
const { BridgeAdapter } = require('../lib/bridge-adapter');
|
|
10
|
+
const { Logger, LogRepository } = require('../lib/structured-logger');
|
|
11
|
+
const { StorageLayout } = require('../lib/storage-layout');
|
|
12
|
+
const { runCli } = require('../../bin/project-knowledge-integrations');
|
|
13
|
+
|
|
14
|
+
(async () => {
|
|
15
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-zcode-capture-'));
|
|
16
|
+
const homeDir = path.join(temp, '用户 空格');
|
|
17
|
+
const bridgeHomeDir = path.join(temp, 'events');
|
|
18
|
+
const configFile = path.join(homeDir, '.zcode', 'cli', 'config.json');
|
|
19
|
+
const layout = new StorageLayout({ dataDir: path.join(temp, 'data') });
|
|
20
|
+
const logger = new Logger({ layout });
|
|
21
|
+
const options = { homeDir, env: {}, bridgeHomeDir, zcodeConfigFile: configFile, commandResolver: () => '', logger };
|
|
22
|
+
const manager = new IntegrationManager(options);
|
|
23
|
+
let server;
|
|
24
|
+
try {
|
|
25
|
+
assert(SUPPORTED_CLIENTS.includes('zcode'));
|
|
26
|
+
assert.equal(normalizeClient(' ZCode '), 'zcode');
|
|
27
|
+
assert.deepEqual(manager.detectClients(), []);
|
|
28
|
+
const before = await manager.statusAll();
|
|
29
|
+
assert.equal(before.clients.length, 4, 'status includes tools not found without installing them');
|
|
30
|
+
assert.equal(before.clients.find(a => a.client === 'zcode').developmentCapture.installed, false);
|
|
31
|
+
assert(!fs.existsSync(configFile), 'status and uninstall must not manufacture user config');
|
|
32
|
+
await manager.operateCapture('uninstall', ['zcode']);
|
|
33
|
+
assert(!fs.existsSync(configFile));
|
|
34
|
+
|
|
35
|
+
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
|
36
|
+
const thirdParty = { type: 'process', command: 'other', args: ['third-party'] };
|
|
37
|
+
fs.writeFileSync(configFile, JSON.stringify({ provider: { name: '保留' }, hooks: { enabled: false, events: { Stop: [{ hooks: [thirdParty] }] } } }));
|
|
38
|
+
assert.deepEqual(manager.detectClients(), ['zcode'], 'desktop ZCode config is discoverable without a PATH CLI');
|
|
39
|
+
const installed = await manager.installAll({ clients: ['zcode'], captureOnly: true });
|
|
40
|
+
assert(installed.bridge.ok && installed.clients[0].developmentCapture.ok);
|
|
41
|
+
assert.equal(installed.clients[0].knowledgeIntegration, undefined);
|
|
42
|
+
const config = JSON.parse(fs.readFileSync(configFile));
|
|
43
|
+
assert.equal(config.hooks.enabled, true);
|
|
44
|
+
assert.equal(config.provider.name, '保留');
|
|
45
|
+
assert(config.hooks.events.Stop[0].hooks.some(h => h.command === 'other'));
|
|
46
|
+
await manager.installAll({ clients: ['zcode'], captureOnly: true });
|
|
47
|
+
const twice = JSON.parse(fs.readFileSync(configFile));
|
|
48
|
+
assert.equal(twice.hooks.events.UserPromptSubmit.length, 1);
|
|
49
|
+
assert.equal(twice.hooks.events.Stop.length, 2);
|
|
50
|
+
const status = await manager.statusAll({ clients: ['zcode'] });
|
|
51
|
+
assert(status.clients[0].configFound && status.clients[0].developmentCapture.installed);
|
|
52
|
+
assert.equal(status.clients[0].knowledgeIntegration.installed, false);
|
|
53
|
+
assert.equal(status.clients[0].knowledgeIntegration.supported, false, 'capture never claims an unverified retrieval plugin');
|
|
54
|
+
assert.equal(await runCli(['update', '--ide', 'zcode', '--capture-only', '--json'], { manager }), 0);
|
|
55
|
+
const dry = new IntegrationManager({ ...options, dryRun: true });
|
|
56
|
+
const untouched = fs.readFileSync(configFile, 'utf8');
|
|
57
|
+
await runCli(['uninstall', '--ide', 'zcode', '--capture-only', '--dry-run'], { manager: dry });
|
|
58
|
+
assert.equal(fs.readFileSync(configFile, 'utf8'), untouched);
|
|
59
|
+
|
|
60
|
+
const runtime = createRuntime({ dataDir: layout.getDataDir(), logger, analysisOwner: 'engine', bridgeAdapter: new BridgeAdapter({ dataDir: layout.getDataDir(), bridgeHomeDir }) });
|
|
61
|
+
server = http.createServer(createRequestHandler(runtime, { integrationOptions: options }));
|
|
62
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
63
|
+
const base = `http://127.0.0.1:${server.address().port}`;
|
|
64
|
+
const request = async (route, body) => {
|
|
65
|
+
const response = await fetch(base + route, body ? { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) } : {});
|
|
66
|
+
assert.equal(response.status, 200);
|
|
67
|
+
return response.json();
|
|
68
|
+
};
|
|
69
|
+
const apiInstall = await request('/api/integrations/install', { clients: ['zcode'], captureOnly: true });
|
|
70
|
+
assert.equal(apiInstall.complete, true);
|
|
71
|
+
assert(apiInstall.operationId);
|
|
72
|
+
const apiStatus = await request('/api/integrations/status');
|
|
73
|
+
assert(apiStatus.clients.find(a => a.client === 'zcode').developmentCapture.installed);
|
|
74
|
+
fs.writeFileSync(configFile, '{invalid JSON');
|
|
75
|
+
const failed = await request('/api/integrations/install', { clients: ['zcode'], captureOnly: true });
|
|
76
|
+
assert.equal(failed.complete, false, 'HTTP 200 does not imply capture succeeded');
|
|
77
|
+
assert.equal(failed.clients[0].developmentCapture.ok, false);
|
|
78
|
+
assert(failed.clients[0].developmentCapture.error.code);
|
|
79
|
+
await logger.flush();
|
|
80
|
+
const errors = new LogRepository({ layout }).query({ event: 'integration.capture.failed' }).entries;
|
|
81
|
+
assert(errors.some(entry => entry.operationId === failed.operationId && entry.context.client === 'zcode' && entry.error.message.includes('valid JSON')), 'actual failed phase and operation ID are persisted');
|
|
82
|
+
fs.writeFileSync(configFile, JSON.stringify(twice));
|
|
83
|
+
const removed = await request('/api/integrations/uninstall', { clients: ['zcode'], captureOnly: true });
|
|
84
|
+
assert.equal(removed.complete, true);
|
|
85
|
+
assert.deepEqual(removed.clients, [], 'capture-only removal does not invoke knowledge plugin operations');
|
|
86
|
+
assert.equal(removed.capture[0].state, 'removed');
|
|
87
|
+
const after = JSON.parse(fs.readFileSync(configFile));
|
|
88
|
+
assert.equal(after.provider.name, '保留');
|
|
89
|
+
assert.deepEqual(after.hooks.events.Stop[0].hooks, [thirdParty]);
|
|
90
|
+
assert.equal((await manager.statusAll({ clients: ['zcode'] })).clients[0].developmentCapture.installed, false);
|
|
91
|
+
const facade = manager._bridgeFacade();
|
|
92
|
+
assert(await facade.getConsumer('project-knowledge'), 'removing one connector retains the host consumer');
|
|
93
|
+
|
|
94
|
+
const cliEnv = { ...process.env, USERPROFILE: homeDir, HOME: homeDir, KB_DATA_DIR: layout.getDataDir(), AI_CODING_EVENT_BRIDGE_HOME: bridgeHomeDir };
|
|
95
|
+
const cli = spawnSync(process.execPath, [path.resolve(__dirname, '../../bin/project-knowledge-integrations.js'), 'install', '--ide', 'zcode', '--capture-only', '--json'], { env: cliEnv, encoding: 'utf8', timeout: 15000 });
|
|
96
|
+
assert.equal(cli.status, 0, cli.stderr);
|
|
97
|
+
assert(JSON.parse(cli.stdout).clients[0].developmentCapture.installed);
|
|
98
|
+
await logger.flush();
|
|
99
|
+
assert(new LogRepository({ layout }).query({ event: 'integration.capture.completed' }).entries.length > 0, 'CLI operations also write durable logs');
|
|
100
|
+
console.log('zcode-capture-integration-test PASS');
|
|
101
|
+
} finally {
|
|
102
|
+
if (server) { server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); }
|
|
103
|
+
await logger.close();
|
|
104
|
+
// This test owns a single mkdtemp sandbox, never real Agent configs.
|
|
105
|
+
fs.rmSync(temp, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
106
|
+
}
|
|
107
|
+
})().catch(error => { console.error(error.stack); process.exitCode = 1; });
|
|
@@ -20,17 +20,19 @@ try {
|
|
|
20
20
|
const INTEGRATION_NAME = 'project-knowledge';
|
|
21
21
|
const MARKETPLACE_NAME = 'project-knowledge';
|
|
22
22
|
const DEFAULT_MARKETPLACE_SOURCE = 'SanQianX/project-knowledge-base';
|
|
23
|
-
const SUPPORTED_CLIENTS = ['claude', 'opencode', 'codex'];
|
|
23
|
+
const SUPPORTED_CLIENTS = ['claude', 'opencode', 'codex', 'zcode'];
|
|
24
24
|
const DEFAULT_BRIDGE_CONSUMER = 'project-knowledge';
|
|
25
25
|
const CLIENT_LABELS = {
|
|
26
26
|
claude: 'Claude Code',
|
|
27
27
|
opencode: 'OpenCode',
|
|
28
28
|
codex: 'Codex',
|
|
29
|
+
zcode: 'ZCode',
|
|
29
30
|
};
|
|
30
31
|
const CLIENT_METHOD_SUFFIX = {
|
|
31
32
|
claude: 'Claude',
|
|
32
33
|
opencode: 'OpenCode',
|
|
33
34
|
codex: 'Codex',
|
|
35
|
+
zcode: 'ZCode',
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
function normalizeClient(value) {
|
|
@@ -323,26 +325,28 @@ class IntegrationManager {
|
|
|
323
325
|
this.commandResolver = options.commandResolver || defaultCommandResolver;
|
|
324
326
|
this.dryRun = Boolean(options.dryRun);
|
|
325
327
|
this.logger = options.logger || null;
|
|
328
|
+
this.operationId = options.operationId || '';
|
|
326
329
|
this.planned = [];
|
|
327
330
|
this.bridge = options.bridgeModule || bridgeModule;
|
|
328
331
|
this.bridgeConsumerName = options.bridgeConsumerName || DEFAULT_BRIDGE_CONSUMER;
|
|
329
332
|
this.bridgeHomeDir = options.bridgeHomeDir
|
|
330
333
|
|| this.env.AI_CODING_EVENT_BRIDGE_HOME
|
|
331
334
|
|| path.join(resolveDataDirPath({ dataDir: this.env.KB_DATA_DIR || '', homeDir: this.homeDir }), 'events');
|
|
332
|
-
this.claudeSettingsFile = options.claudeSettingsFile || '';
|
|
333
|
-
this.codexConfigFile = options.codexConfigFile || '';
|
|
334
|
-
this.openCodePluginsDir = options.openCodePluginsDir || '';
|
|
335
|
+
this.claudeSettingsFile = options.claudeSettingsFile || path.join(this.homeDir, '.claude', 'settings.json');
|
|
336
|
+
this.codexConfigFile = options.codexConfigFile || path.join(this.homeDir, '.codex', 'config.toml');
|
|
337
|
+
this.openCodePluginsDir = options.openCodePluginsDir || path.join(openCodeConfigDir(this.homeDir, this.env), 'plugin');
|
|
338
|
+
this.zcodeConfigFile = options.zcodeConfigFile || path.join(this.homeDir, '.zcode', 'cli', 'config.json');
|
|
335
339
|
}
|
|
336
340
|
|
|
337
341
|
_bridgeFacade() {
|
|
338
342
|
return this.bridge.createBridge({ homeDir: this.bridgeHomeDir });
|
|
339
343
|
}
|
|
340
344
|
|
|
341
|
-
_captureInstallerOptions() {
|
|
345
|
+
_captureInstallerOptions(client) {
|
|
342
346
|
return {
|
|
343
347
|
homeDir: this.bridgeHomeDir,
|
|
344
348
|
settingsFile: this.claudeSettingsFile || undefined,
|
|
345
|
-
configFile: this.
|
|
349
|
+
configFile: client === 'zcode' ? this.zcodeConfigFile : this.codexConfigFile,
|
|
346
350
|
pluginsDir: this.openCodePluginsDir || undefined,
|
|
347
351
|
};
|
|
348
352
|
}
|
|
@@ -382,6 +386,42 @@ class IntegrationManager {
|
|
|
382
386
|
|
|
383
387
|
// ---- Development Capture connectors (Bridge hooks / notify / plugin) ----
|
|
384
388
|
|
|
389
|
+
async captureOperation(operation, client, options = {}) {
|
|
390
|
+
const startedAt = Date.now();
|
|
391
|
+
const context = { client, operation };
|
|
392
|
+
try {
|
|
393
|
+
const result = await this[`${operation}Capture${CLIENT_METHOD_SUFFIX[client]}`](options);
|
|
394
|
+
const ok = !result.conflict && result.state !== 'failed';
|
|
395
|
+
if (operation !== 'status' || !ok) this.log(ok ? 'info' : 'error', `integration.capture.${ok ? 'completed' : 'failed'}`, ok ? 'Agent 采集接线操作完成。' : 'Agent 采集接线未完成。', { operationId: this.operationId, durationMs: Date.now() - startedAt, error: ok ? undefined : Object.assign(new Error(result.detail), { code: 'CAPTURE_CONFLICT' }), context });
|
|
396
|
+
return { ok, ...result, ...(!ok ? { error: { code: 'CAPTURE_CONFLICT', message: result.detail } } : {}) };
|
|
397
|
+
} catch (error) {
|
|
398
|
+
this.log('error', 'integration.capture.failed', 'Agent 采集接线操作失败。', { operationId: this.operationId, durationMs: Date.now() - startedAt, error, context });
|
|
399
|
+
return { ok: false, state: 'failed', installed: false, detail: error.message, error: { code: error.code || 'CAPTURE_FAILED', message: error.message } };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async installCaptureZCode() {
|
|
404
|
+
if (this.dryRun) {
|
|
405
|
+
this.planned.push({ command: 'bridge', args: ['installZcodeHook', this.bridgeHomeDir] });
|
|
406
|
+
return { state: 'planned', installed: false, detail: '计划接入 ZCode 会话采集,未修改配置。' };
|
|
407
|
+
}
|
|
408
|
+
await this.bridge.installers.zcode.installZcodeHook(this._captureInstallerOptions('zcode'));
|
|
409
|
+
const status = await this.statusCaptureZCode();
|
|
410
|
+
if (!status.installed) throw new Error('ZCode Hook 写入后校验失败,请检查 hooks.enabled 与事件配置。');
|
|
411
|
+
return status;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async statusCaptureZCode() {
|
|
415
|
+
const status = await this.bridge.installers.zcode.statusZcodeHook(this._captureInstallerOptions('zcode'));
|
|
416
|
+
return { state: status.installed ? 'installed' : 'not-installed', installed: status.installed, conflict: false, detail: status.installed ? '已配置用户输入与回答采集;请重新打开 ZCode 会话。' : 'ZCode 会话采集尚未接入。' };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async uninstallCaptureZCode() {
|
|
420
|
+
if (this.dryRun) return { state: 'planned', detail: '计划移除 ZCode 采集,未修改配置。' };
|
|
421
|
+
await this.bridge.installers.zcode.uninstallZcodeHook(this._captureInstallerOptions('zcode'));
|
|
422
|
+
return { state: 'removed', installed: false, detail: '已移除 ZCode 托管采集,保留第三方配置与已有记录。' };
|
|
423
|
+
}
|
|
424
|
+
|
|
385
425
|
async installCaptureClaude() {
|
|
386
426
|
if (this.dryRun) {
|
|
387
427
|
this.planned.push({ command: 'bridge', args: ['installClaudeCodeHook', this.bridgeHomeDir] });
|
|
@@ -397,7 +437,7 @@ class IntegrationManager {
|
|
|
397
437
|
state: status.installed ? 'installed' : 'not-installed',
|
|
398
438
|
installed: status.installed,
|
|
399
439
|
conflict: false,
|
|
400
|
-
detail: status.installed ?
|
|
440
|
+
detail: status.installed ? '已配置用户输入与回答采集。' : '会话采集 Hook 尚未配置。',
|
|
401
441
|
};
|
|
402
442
|
}
|
|
403
443
|
|
|
@@ -453,7 +493,7 @@ class IntegrationManager {
|
|
|
453
493
|
state: status.installed ? 'installed' : status.thirdParty ? 'conflict' : 'not-installed',
|
|
454
494
|
installed: Boolean(status.installed),
|
|
455
495
|
conflict: Boolean(status.thirdParty),
|
|
456
|
-
detail: status.installed ? '
|
|
496
|
+
detail: status.installed ? '已配置提交回合后的会话采集。' : status.thirdParty ? '已有第三方通知配置,不会覆盖;请查看诊断记录。' : '会话采集通知尚未配置。',
|
|
457
497
|
};
|
|
458
498
|
}
|
|
459
499
|
|
|
@@ -481,7 +521,7 @@ class IntegrationManager {
|
|
|
481
521
|
state: status.installed ? 'installed' : 'not-installed',
|
|
482
522
|
installed: Boolean(status.installed),
|
|
483
523
|
conflict: false,
|
|
484
|
-
detail: status.installed ? '
|
|
524
|
+
detail: status.installed ? '已配置会话采集插件。' : '会话采集插件尚未配置,保留其他插件。',
|
|
485
525
|
};
|
|
486
526
|
}
|
|
487
527
|
|
|
@@ -510,9 +550,21 @@ class IntegrationManager {
|
|
|
510
550
|
}
|
|
511
551
|
|
|
512
552
|
detectClients() {
|
|
513
|
-
return SUPPORTED_CLIENTS.filter(client => Boolean(this.executable(client)));
|
|
553
|
+
return SUPPORTED_CLIENTS.filter(client => Boolean(this.executable(client)) || client === 'zcode' && this.captureConfigFound(client));
|
|
514
554
|
}
|
|
515
555
|
|
|
556
|
+
captureConfigFound(client) {
|
|
557
|
+
const target = { claude: this.claudeSettingsFile, codex: this.codexConfigFile, opencode: path.dirname(this.openCodePluginsDir), zcode: this.zcodeConfigFile }[client];
|
|
558
|
+
try { const stat = fs.statSync(target); return client === 'opencode' ? stat.isDirectory() : stat.isFile(); } catch { return false; }
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Capture support does not imply a verified MCP/plugin installer. Never
|
|
562
|
+
// present ZCode knowledge retrieval as installed merely because hooks exist.
|
|
563
|
+
statusZCode() { return { state: 'not-supported', supported: false, installed: false, detail: 'ZCode 当前支持会话采集;知识检索插件未接入,不会自动安装。' }; }
|
|
564
|
+
installZCode() { return this.statusZCode(); }
|
|
565
|
+
updateZCode() { return this.statusZCode(); }
|
|
566
|
+
uninstallZCode() { return this.statusZCode(); }
|
|
567
|
+
|
|
516
568
|
execute(client, args, options = {}) {
|
|
517
569
|
const startedAt = Date.now();
|
|
518
570
|
const command = this.executable(client);
|
|
@@ -805,13 +857,7 @@ class IntegrationManager {
|
|
|
805
857
|
const results = [];
|
|
806
858
|
for (const rawClient of clients) {
|
|
807
859
|
const client = normalizeClient(rawClient);
|
|
808
|
-
|
|
809
|
-
try {
|
|
810
|
-
const result = await this[`${operation}Capture${suffix}`](options);
|
|
811
|
-
results.push({ client, label: CLIENT_LABELS[client], ok: true, ...result });
|
|
812
|
-
} catch (error) {
|
|
813
|
-
results.push({ client, label: CLIENT_LABELS[client], ok: false, state: 'failed', detail: error.message });
|
|
814
|
-
}
|
|
860
|
+
results.push({ client, label: CLIENT_LABELS[client], ...(await this.captureOperation(operation, client, options)) });
|
|
815
861
|
}
|
|
816
862
|
return results;
|
|
817
863
|
}
|
|
@@ -839,11 +885,7 @@ class IntegrationManager {
|
|
|
839
885
|
}
|
|
840
886
|
}
|
|
841
887
|
if (options.knowledgeOnly !== true) {
|
|
842
|
-
|
|
843
|
-
entry.developmentCapture = { ok: true, ...(await this[`installCapture${suffix}`]()) };
|
|
844
|
-
} catch (error) {
|
|
845
|
-
entry.developmentCapture = { ok: false, state: 'failed', detail: error.message };
|
|
846
|
-
}
|
|
888
|
+
entry.developmentCapture = await this.captureOperation('install', client);
|
|
847
889
|
}
|
|
848
890
|
summary.clients.push(entry);
|
|
849
891
|
}
|
|
@@ -851,7 +893,7 @@ class IntegrationManager {
|
|
|
851
893
|
}
|
|
852
894
|
|
|
853
895
|
async statusAll(options = {}) {
|
|
854
|
-
const clients = options.clients && options.clients.length ? options.clients.map(normalizeClient) :
|
|
896
|
+
const clients = options.clients && options.clients.length ? options.clients.map(normalizeClient) : SUPPORTED_CLIENTS;
|
|
855
897
|
const summary = { clients: [], bridge: null };
|
|
856
898
|
try {
|
|
857
899
|
summary.bridge = { ok: true, ...(await this.bridgeStatus()) };
|
|
@@ -860,17 +902,15 @@ class IntegrationManager {
|
|
|
860
902
|
}
|
|
861
903
|
for (const client of clients) {
|
|
862
904
|
const suffix = CLIENT_METHOD_SUFFIX[client];
|
|
863
|
-
const entry = { client, label: CLIENT_LABELS[client], available: Boolean(this.executable(client)) };
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
entry.developmentCapture = { ok: true, ...(await this[`statusCapture${suffix}`]()) };
|
|
871
|
-
} catch (error) {
|
|
872
|
-
entry.developmentCapture = { ok: false, state: 'failed', detail: error.message };
|
|
905
|
+
const entry = { client, label: CLIENT_LABELS[client], available: Boolean(this.executable(client)), configFound: this.captureConfigFound(client) };
|
|
906
|
+
if (!options.captureOnly) {
|
|
907
|
+
try {
|
|
908
|
+
entry.knowledgeIntegration = { ok: true, ...(await this[`status${suffix}`]()) };
|
|
909
|
+
} catch (error) {
|
|
910
|
+
entry.knowledgeIntegration = { ok: false, state: 'failed', detail: error.message };
|
|
911
|
+
}
|
|
873
912
|
}
|
|
913
|
+
entry.developmentCapture = await this.captureOperation('status', client);
|
|
874
914
|
summary.clients.push(entry);
|
|
875
915
|
}
|
|
876
916
|
return summary;
|
|
@@ -881,6 +921,7 @@ class IntegrationManager {
|
|
|
881
921
|
async disableAllCapture(options = {}) {
|
|
882
922
|
const clients = options.clients && options.clients.length ? options.clients.map(normalizeClient) : SUPPORTED_CLIENTS;
|
|
883
923
|
const results = await this.operateCapture('uninstall', clients, { unregisterConsumer: false });
|
|
924
|
+
if (results.some(result => !result.ok)) return { clients: results, consumer: { ok: false, state: 'kept', detail: '部分采集未能移除,保留宿主消费者,请修复失败项后重试。' } };
|
|
884
925
|
let consumer = null;
|
|
885
926
|
try {
|
|
886
927
|
consumer = { ok: true, ...(await this.unregisterBridgeConsumer()) };
|
package/_site/lib/server-app.js
CHANGED
|
@@ -1292,13 +1292,13 @@ function createRequestHandler(runtime, options = {}) {
|
|
|
1292
1292
|
// Unified Integration Setup surface: one action manages Knowledge
|
|
1293
1293
|
// Integration and Development Capture per client, with separate status.
|
|
1294
1294
|
if (method === 'GET' && pathname === '/api/integrations/status') {
|
|
1295
|
-
const manager = new IntegrationManager({ rootDir: runtime.rootDir });
|
|
1296
|
-
const summary = await manager.statusAll({});
|
|
1295
|
+
const manager = new IntegrationManager({ ...options.integrationOptions, rootDir: runtime.rootDir, logger: runtime.logger, operationId: requestOperationId, bridgeHomeDir: runtime.bridgeAdapter.hookBinding().bridgeHomeDir });
|
|
1296
|
+
const summary = await manager.statusAll({ captureOnly: url.searchParams.get('captureOnly') === '1' });
|
|
1297
1297
|
return send(res, 200, { ok: true, ...summary });
|
|
1298
1298
|
}
|
|
1299
1299
|
if (method === 'POST' && pathname === '/api/integrations/install') {
|
|
1300
1300
|
const body = await readJsonBody(req);
|
|
1301
|
-
const manager = new IntegrationManager({ rootDir: runtime.rootDir });
|
|
1301
|
+
const manager = new IntegrationManager({ ...options.integrationOptions, rootDir: runtime.rootDir, logger: runtime.logger, operationId: requestOperationId, bridgeHomeDir: runtime.bridgeAdapter.hookBinding().bridgeHomeDir });
|
|
1302
1302
|
const clients = Array.isArray(body.clients) && body.clients.length
|
|
1303
1303
|
? body.clients.map(client => String(client))
|
|
1304
1304
|
: undefined;
|
|
@@ -1308,12 +1308,13 @@ function createRequestHandler(runtime, options = {}) {
|
|
|
1308
1308
|
captureOnly: body.captureOnly === true,
|
|
1309
1309
|
scope: body.scope || 'user',
|
|
1310
1310
|
});
|
|
1311
|
-
|
|
1312
|
-
|
|
1311
|
+
const complete = summary.bridge.ok && summary.clients.every(entry => (!entry.knowledgeIntegration || entry.knowledgeIntegration.ok) && (!entry.developmentCapture || entry.developmentCapture.ok));
|
|
1312
|
+
await runtime.logger[complete ? 'info' : 'error'](`integrations.install_${complete ? 'completed' : 'failed'}`, complete ? 'Agent 接入完成。' : 'Agent 接入未完成,请查看各工具的失败原因。', { operationId: requestOperationId, context: { clients: summary.clients.map(entry => entry.client) } });
|
|
1313
|
+
return send(res, 200, { ok: true, complete, operationId: requestOperationId, ...summary });
|
|
1313
1314
|
}
|
|
1314
1315
|
if (method === 'POST' && pathname === '/api/integrations/uninstall') {
|
|
1315
1316
|
const body = await readJsonBody(req);
|
|
1316
|
-
const manager = new IntegrationManager({ rootDir: runtime.rootDir });
|
|
1317
|
+
const manager = new IntegrationManager({ ...options.integrationOptions, rootDir: runtime.rootDir, logger: runtime.logger, operationId: requestOperationId, bridgeHomeDir: runtime.bridgeAdapter.hookBinding().bridgeHomeDir });
|
|
1317
1318
|
if (body.disableAllCapture === true) {
|
|
1318
1319
|
const summary = await manager.disableAllCapture({});
|
|
1319
1320
|
return send(res, 200, { ok: true, ...summary });
|
|
@@ -1321,9 +1322,9 @@ function createRequestHandler(runtime, options = {}) {
|
|
|
1321
1322
|
const clients = Array.isArray(body.clients) && body.clients.length
|
|
1322
1323
|
? body.clients.map(client => String(client))
|
|
1323
1324
|
: undefined;
|
|
1324
|
-
const knowledgeResults = await manager.operate('uninstall', clients && clients.length ? clients : manager.detectClients(), { scope: body.scope || 'user' });
|
|
1325
|
+
const knowledgeResults = body.captureOnly === true ? [] : await manager.operate('uninstall', clients && clients.length ? clients : manager.detectClients(), { scope: body.scope || 'user' });
|
|
1325
1326
|
const captureResults = body.knowledgeOnly === true ? [] : await manager.operateCapture('uninstall', clients && clients.length ? clients : manager.detectClients(), {});
|
|
1326
|
-
return send(res, 200, { ok: true, clients: knowledgeResults, capture: captureResults });
|
|
1327
|
+
return send(res, 200, { ok: true, complete: [...knowledgeResults, ...captureResults].every(entry => entry.ok), operationId: requestOperationId, clients: knowledgeResults, capture: captureResults });
|
|
1327
1328
|
}
|
|
1328
1329
|
|
|
1329
1330
|
if (method === 'GET' && pathname === '/api/ai-profiles') {
|
|
@@ -5,6 +5,8 @@ const {
|
|
|
5
5
|
SUPPORTED_CLIENTS,
|
|
6
6
|
normalizeClient,
|
|
7
7
|
} = require('../_site/lib/integration-installer');
|
|
8
|
+
const { Logger } = require('../_site/lib/structured-logger');
|
|
9
|
+
const { createId } = require('../_site/lib/contracts');
|
|
8
10
|
|
|
9
11
|
function printHelp() {
|
|
10
12
|
console.log(`project-knowledge-integrations
|
|
@@ -23,6 +25,7 @@ Clients:
|
|
|
23
25
|
claude Claude Code plugin (MCP + Skill) + managed Bridge hooks
|
|
24
26
|
opencode OpenCode MCP + global Skill + managed Bridge plugin
|
|
25
27
|
codex Codex plugin (MCP + Skill) + managed Bridge notify
|
|
28
|
+
zcode ZCode managed Bridge hooks (capture only; no verified MCP installer)
|
|
26
29
|
all All supported clients
|
|
27
30
|
|
|
28
31
|
Options:
|
|
@@ -99,7 +102,7 @@ function selectClients(requested, manager) {
|
|
|
99
102
|
|
|
100
103
|
function printCapabilityLine(prefix, capability) {
|
|
101
104
|
if (!capability) return;
|
|
102
|
-
const mark = capability.ok ? 'OK' : 'FAIL';
|
|
105
|
+
const mark = capability.supported === false ? 'INFO' : capability.ok ? 'OK' : 'FAIL';
|
|
103
106
|
console.log(` [${mark}] ${prefix}: ${capability.state} - ${capability.detail}`);
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -109,10 +112,18 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
109
112
|
printHelp();
|
|
110
113
|
return 0;
|
|
111
114
|
}
|
|
115
|
+
const logger = dependencies.manager ? null : new Logger();
|
|
112
116
|
const manager = dependencies.manager || new IntegrationManager({
|
|
117
|
+
logger,
|
|
118
|
+
operationId: createId('op'),
|
|
113
119
|
dryRun: options.dryRun,
|
|
114
120
|
marketplaceSource: options.marketplaceSource || undefined,
|
|
115
121
|
});
|
|
122
|
+
try { return await runOperation(options, manager); }
|
|
123
|
+
finally { if (logger) await logger.close(); }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function runOperation(options, manager) {
|
|
116
127
|
const clients = selectClients(options.clients, manager);
|
|
117
128
|
|
|
118
129
|
if (options.operation === 'disable-capture') {
|
|
@@ -130,7 +141,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
130
141
|
return summary.clients.some(result => !result.ok) || !summary.consumer.ok ? 1 : 0;
|
|
131
142
|
}
|
|
132
143
|
|
|
133
|
-
if (options.operation === 'install') {
|
|
144
|
+
if (options.operation === 'install' || options.operation === 'update' && options.captureOnly) {
|
|
134
145
|
// One action configures Knowledge Integration + Development Capture
|
|
135
146
|
// together (per-client flags narrow the scope). No client UI is needed.
|
|
136
147
|
const summary = await manager.installAll({ clients, knowledgeOnly: options.knowledgeOnly, captureOnly: options.captureOnly, scope: options.scope });
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -85,6 +85,23 @@ vector-hub 是唯一派生索引服务,外壳不再执行第二套索引写入
|
|
|
85
85
|
|
|
86
86
|
## 需求记录与查询
|
|
87
87
|
|
|
88
|
+
### 外部 Agent 会话采集
|
|
89
|
+
|
|
90
|
+
在网页左下角打开“系统状态 → 外部 Agent 采集”,对使用的工具点击“接入”或
|
|
91
|
+
“修复”;“移除采集”只移除托管 Hook,不删除历史记录或第三方配置。
|
|
92
|
+
“检测到命令/检测到配置”与“已配置采集”是不同状态,未检测到不等于没有安装。
|
|
93
|
+
配置完成后重新打开外部 Agent 会话,再到活动记录验证新会话是否出现。
|
|
94
|
+
|
|
95
|
+
```powershell
|
|
96
|
+
project-knowledge-integrations status --all
|
|
97
|
+
project-knowledge-integrations install --ide zcode --capture-only
|
|
98
|
+
project-knowledge-integrations uninstall --ide zcode --capture-only
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
ZCode 接入当前仅包含会话采集,不会冒充已安装 MCP/知识检索插件。
|
|
102
|
+
接入失败会显示操作 ID,并写入数据目录 `logs/system/YYYY-MM-DD.jsonl`;
|
|
103
|
+
网页“系统诊断记录”可查看和导出。注册消费者本身不能代替安装 Agent Hook。
|
|
104
|
+
|
|
88
105
|
Claude Code、Codex、OpenCode 集成可在编码前追加用户真实需求元数据。记录写入
|
|
89
106
|
项目 `requirements.jsonl`,不会触发分析或写知识。
|
|
90
107
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "project-knowledge/module-content/v1",
|
|
3
3
|
"productFiles": {
|
|
4
|
-
"ui/index.html": "
|
|
5
|
-
"ui/product.js": "
|
|
6
|
-
"ui/product.css": "
|
|
4
|
+
"ui/index.html": "e7291caab778eba3c5499d8286804ad1f4ef31c87a957f69c8fb77befc01321b",
|
|
5
|
+
"ui/product.js": "1a6ddedd97b00c3f798c13a0a55b0f63790e701483492bc97d28cae4034efaf8",
|
|
6
|
+
"ui/product.css": "73554abf3f951ea01a2bfe2df81748b702a6ca4488eea16bfe5afe04ef1dea70"
|
|
7
7
|
},
|
|
8
8
|
"modules": {
|
|
9
9
|
"terminal": {
|
|
@@ -174,17 +174,17 @@
|
|
|
174
174
|
"eventBridge": {
|
|
175
175
|
"name": "@sanqianx/ai-coding-event-bridge-console",
|
|
176
176
|
"installName": "@sanqianx/ai-coding-event-bridge-console",
|
|
177
|
-
"version": "0.7.
|
|
177
|
+
"version": "0.7.2",
|
|
178
178
|
"files": {
|
|
179
179
|
"LICENSE": "94a3bded67f76ec9f6333ad3370111bcb6e5cadbeb6f306a8112f9543320ecc2",
|
|
180
|
-
"README.md": "
|
|
181
|
-
"console/index.html": "
|
|
182
|
-
"package.json": "
|
|
183
|
-
"src/agent-detect.js": "
|
|
180
|
+
"README.md": "4699558a9498f93a83587523ef5cae019d2146867f355134ea27100ac6245769",
|
|
181
|
+
"console/index.html": "bdc61db0ca9e6b5d8886418c1c03df141f39ab722f50d0c16bba75fe57ac46f0",
|
|
182
|
+
"package.json": "f6b965a9ce9440039d96767c7d126a8149cffa56559693955cca15b0707bcd60",
|
|
183
|
+
"src/agent-detect.js": "2a4752218e6cdb4151ae9796b23558c2dd94b55217a20fa1f1a76f506bb2f781",
|
|
184
184
|
"src/bin.js": "e443cfabb8df1a694c9e2415a292db81f183d9e31b7fe8f5f9a14d2a09d03052",
|
|
185
185
|
"src/bridge/bin/commit-boundary.js": "2c87126d9d6467ddae13acc1d0997f2124a68174738b8cd6f8614e7eeccb3d0d",
|
|
186
186
|
"src/bridge/connectors/capture-guard.js": "aa9c51a6012966c5b6eb66a40b61ada61fd2cfd43fd4f6985d7b655421e8a1e3",
|
|
187
|
-
"src/bridge/connectors/claude-code/hook-entry.js": "
|
|
187
|
+
"src/bridge/connectors/claude-code/hook-entry.js": "c88b9ace27e4d148ee7b04724ec2f990a0c71c6e59a4d22afb4c9a8e2e72048e",
|
|
188
188
|
"src/bridge/connectors/claude-code/notify.js": "4325684ac92c1a2e531aefc5cbcbc886dc2b58080c410ee5e879574593f077cd",
|
|
189
189
|
"src/bridge/connectors/codex/cursor-store.js": "077784e9c6b895dde95247706381dbaca68892e1382f3a957c3e90efb51059f5",
|
|
190
190
|
"src/bridge/connectors/codex/hook-entry.js": "d180a3713609b251ba8a9fdffbcf202346b1098ce4e07cd3ae696216a9f56e96",
|
|
@@ -199,21 +199,21 @@
|
|
|
199
199
|
"src/bridge/core/journal-router.js": "5fe48ae4240034f030807c80bdba35c6ef2cb7bfa3fa310105ce0fb77d272131",
|
|
200
200
|
"src/bridge/core/journal.js": "13c72396c970e6acd9a97900a926daa108aded01d8bb3735213e0feff8aa20b6",
|
|
201
201
|
"src/bridge/core/lock.js": "484988ce593d138183e7d94648ece97c88661e7fa8379f0ef7335a634fe15ba3",
|
|
202
|
-
"src/bridge/core/normalizer.js": "
|
|
202
|
+
"src/bridge/core/normalizer.js": "e8eaa3582538c8f45dfc8d3ceb21c1e2f46347917d086436cfd361d32f62302e",
|
|
203
203
|
"src/bridge/core/paths.js": "6e829c00105d3b9214942f33af6225b338c668ff4cdda5c3ab92cb759d54be30",
|
|
204
204
|
"src/bridge/core/project-registry.js": "8c5cc8e18d55af1a0c2d682c25ea1c37eb6f4687cf3eaa5997fc57532560d866",
|
|
205
205
|
"src/bridge/core/repo-context.js": "1627d416fd6aaa43d8c4be8ce5097bb69244b1afe7f1ce5e9f71956c5542ce3f",
|
|
206
|
-
"src/bridge/core/runtime-home.js": "
|
|
206
|
+
"src/bridge/core/runtime-home.js": "f2bfffba20c8256259cecc1014261897a4d6f721807c04b2e4d2b3dc973f14e1",
|
|
207
207
|
"src/bridge/core/semver.js": "7f87ed40f3af3ae0480abc9934946358f0caa5d951472d5892b088aa468ec0f1",
|
|
208
208
|
"src/bridge/core/turn-identity.js": "4ee79410d0122ca93cea565591c183db6bff64b0a467f50a4e590de4ee78c399",
|
|
209
209
|
"src/bridge/index.js": "7f56d08fdcfa815a45f50c73b27d0627497ac364da55cd8e6635351f734b57b2",
|
|
210
210
|
"src/bridge/installers/claude-code/installer.js": "c0a8a39d654e886fad838a54a2acb535096384d6307c88eb5dc813992efefbd2",
|
|
211
211
|
"src/bridge/installers/codex/installer.js": "0c8542c2320b7e7b654ad22163bd5ee26f3792545f521e096ee5c795f2ec9303",
|
|
212
212
|
"src/bridge/installers/opencode/installer.js": "27f2ed3629e55b4ee083c6d0d4ceafc614145102ead2ba99630ad979c120ab08",
|
|
213
|
-
"src/bridge/installers/zcode/installer.js": "
|
|
213
|
+
"src/bridge/installers/zcode/installer.js": "625343d6a9fc81b81fb3d1fcd12ac6cdf6d33fae45925631db88aaf74fe497ff",
|
|
214
214
|
"src/bridge/query/conversation-query.js": "89db83f3f4bf5e2807603589da9fa03b4b494e1cb94afc772e57572ccdcb0854",
|
|
215
215
|
"src/pick-folder.js": "70f55df999262b90433f2b4251aa69dd70a23b55a84756b5f1d24dbeacfbda31",
|
|
216
|
-
"src/server.js": "
|
|
216
|
+
"src/server.js": "d521073673b4e1cc84cc9fab46aae5c07f3274f7cdcf20f78696b7808be5103c"
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanqianx/project-knowledge",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
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": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@huggingface/transformers": "3.8.1",
|
|
81
81
|
"@lancedb/lancedb": "0.31.0",
|
|
82
82
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
83
|
-
"@sanqianx/ai-coding-event-bridge-console": "0.7.
|
|
83
|
+
"@sanqianx/ai-coding-event-bridge-console": "0.7.2",
|
|
84
84
|
"@sanqianx/vector-hub": "1.0.7",
|
|
85
85
|
"buffer": "^6.0.3",
|
|
86
86
|
"cheerio": "^1.2.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.
|
|
4
|
+
"version": "5.0.2",
|
|
5
5
|
"description": "Read durable project decisions and implementation history from Project Knowledge.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SanQianX",
|
package/ui/index.html
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
<header><h2>系统状态</h2><button id="closeSystem" class="icon-button" aria-label="关闭系统状态">×</button></header>
|
|
38
38
|
<div class="drawer-content">
|
|
39
39
|
<section><h3>模块连接</h3><div id="moduleHealth"></div><button id="repairWiring">修复当前项目接线</button><p class="muted" id="wiringFeedback"></p><p class="muted" id="captureStatus"></p></section>
|
|
40
|
+
<section id="agentCaptureSection"><div class="actions"><h3>外部 Agent 采集</h3><button id="refreshAgentCapture">检查状态</button></div><p class="muted">只接入你使用的工具。采集外部开发会话,不会重新安装 Agent,也不影响已有记录。</p><div id="agentCaptureList"></div><p id="agentCaptureFeedback" class="feedback" role="status"></p></section>
|
|
40
41
|
<section><h3>后台维护模型</h3><p class="muted">使用持久化默认模型,不跟随聊天中的临时切换。</p><select id="maintenanceModel" aria-label="后台维护默认模型"></select><div class="actions"><button id="saveMaintenance">保存默认模型</button><button id="configureProviders">管理模型供应商</button></div><p id="configurationStatus" class="muted"></p></section>
|
|
41
42
|
<section><h3>向量检索模型</h3><p id="embeddingStatus" class="muted">正在读取配置。</p><button id="openEmbedding">配置向量模型</button><p class="muted">凭据由工作台统一保存。未配置时仍可使用文本检索。</p></section>
|
|
42
43
|
<section><h3>智脑设置</h3><label>名称<input id="brainName" maxlength="100"></label><label>知识根目录<div class="input-with-action"><input id="knowledgeRoot"><button id="pickRoot">选择</button></div></label><p class="muted">修改根目录不会移动已有项目知识或共享知识。</p><button id="saveBrainSettings">保存设置</button></section>
|
package/ui/product.css
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
:root{--page:#f3f5f7;--surface:#fff;--surface2:#f8f9fb;--surface3:#edf1f4;--line:#dce2e8;--text:#202733;--muted:#687587;--faint:#8e99a7;--nav:#eef1f4;--accent:#516f89;--accent-soft:#e6edf3;--good:#397761;--bad:#a05252;--bad-soft:#fbeeee;color-scheme:light}
|
|
2
|
+
.agent-capture-row{display:flex;align-items:center;gap:16px;padding:16px 0;border-bottom:1px solid var(--line)}.agent-capture-row>div:first-child{flex:1;min-width:0}.agent-capture-row small{display:block;color:var(--muted);margin-top:8px;overflow-wrap:anywhere}.agent-capture-row .actions{flex-shrink:0}#agentCaptureFeedback{white-space:pre-wrap}
|
|
2
3
|
#logsDialog{display:flex;flex-direction:column;overflow:hidden}#logsDialog:not([open]){display:none}#logsContent{overflow:auto;min-height:0;flex:1;margin-top:16px}#logsContent .row{align-items:flex-start;cursor:pointer}#logsContent pre{flex:1;min-width:0;margin:0}#logsContent .log-entry{border-bottom:1px solid var(--line)}#logsContent .log-detail{padding:16px;background:var(--surface2);max-height:320px;overflow:auto}#logsContent [data-level=warn],#logsContent [data-level=error],#logsContent [data-level=fatal]{color:var(--bad)}#logsContent .row time,#logsContent .row pre{color:inherit}
|
|
3
4
|
@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation:none!important;transition:none!important}}
|
|
4
5
|
.knowledge-context{overflow-x:hidden}.reference{overflow-wrap:anywhere}.reference small{min-width:0;max-width:100%}
|
package/ui/product.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const label = run => ({ queued: '排队', running: '维护中', succeeded: '已更新 · 可检索', 'index-pending': '知识已更新 · 索引待修复', failed: '维护失败', cancelled: '已取消' })[run.state] || run.state;
|
|
20
20
|
const badge = (text, status = '') => `<span class="status ${esc(status)}">${esc(text)}</span>`;
|
|
21
21
|
let toastTimer, refreshTimer, preflightTimer, editingMemoryId, logStream, logEpoch = 0, referenceEpoch = 0, snapshotEpoch = 0, registryEpoch = 0, registryPromise = null, resumeImport = false, unbindProjectId = '', unbinding = false;
|
|
22
|
+
let captureEpoch = 0, captureBusy = false;
|
|
22
23
|
async function api(url, options = {}) {
|
|
23
24
|
const { allowNotReady, ...init } = options;
|
|
24
25
|
if (init.body && typeof init.body !== 'string') init.body = JSON.stringify(init.body);
|
|
@@ -182,7 +183,35 @@
|
|
|
182
183
|
async function openSystem() {
|
|
183
184
|
$('systemDrawer').hidden = false; $('drawerBackdrop').hidden = false;
|
|
184
185
|
$('brainName').value = state.brain.name; $('knowledgeRoot').value = state.root;
|
|
185
|
-
await loadMaintenance();
|
|
186
|
+
await Promise.all([loadMaintenance(), loadAgentCapture()]);
|
|
187
|
+
}
|
|
188
|
+
async function loadAgentCapture() {
|
|
189
|
+
const epoch = ++captureEpoch;
|
|
190
|
+
try {
|
|
191
|
+
const result = await api('/api/integrations/status?captureOnly=1');
|
|
192
|
+
if (epoch !== captureEpoch) return;
|
|
193
|
+
$('agentCaptureList').innerHTML = result.clients.map(agent => {
|
|
194
|
+
const capture = agent.developmentCapture;
|
|
195
|
+
const found = agent.available || agent.configFound;
|
|
196
|
+
const software = agent.available ? '检测到命令' : agent.configFound ? '检测到配置' : '未检测到';
|
|
197
|
+
const status = !capture.ok ? '接线异常' : capture.installed ? '已配置采集' : '未接入';
|
|
198
|
+
return `<div class="agent-capture-row"><div><b>${esc(agent.label)}</b><small>${esc(software)} · ${esc(status)}</small><small>${esc(capture.detail)}</small></div><div class="actions"><button data-agent-capture="install" data-agent-client="${esc(agent.client)}" ${!found || captureBusy ? 'disabled' : ''}>${capture.installed ? '修复' : '接入'}</button>${capture.installed ? `<button data-agent-capture="uninstall" data-agent-client="${esc(agent.client)}" ${captureBusy ? 'disabled' : ''}>移除采集</button>` : ''}</div></div>`;
|
|
199
|
+
}).join('');
|
|
200
|
+
} catch (error) { if (epoch === captureEpoch) $('agentCaptureList').textContent = `无法检查采集状态:${error.message}`; }
|
|
201
|
+
}
|
|
202
|
+
async function changeAgentCapture(client, operation) {
|
|
203
|
+
if (captureBusy) return;
|
|
204
|
+
captureBusy = true; captureEpoch++;
|
|
205
|
+
all('[data-agent-capture]').forEach(button => { button.disabled = true; });
|
|
206
|
+
$('agentCaptureFeedback').textContent = operation === 'install' ? '正在配置采集 Hook…' : '正在移除托管采集,已有记录保留…';
|
|
207
|
+
try {
|
|
208
|
+
const result = await api(`/api/integrations/${operation}`, { method: 'POST', body: { clients: [client], captureOnly: true } });
|
|
209
|
+
const capture = operation === 'install' ? result.clients[0]?.developmentCapture : result.capture[0];
|
|
210
|
+
const success = result.complete === true && capture?.ok;
|
|
211
|
+
$('agentCaptureFeedback').textContent = success ? operation === 'install' ? '采集配置已保存。请重新打开外部 Agent 会话,实际记录可在活动页面查看。' : '已移除托管采集,第三方配置与已有记录保留。' : `接入操作未完成:${capture?.detail || result.bridge?.detail || '请检查系统诊断记录。'}\n操作 ID:${result.operationId || '—'}`;
|
|
212
|
+
$('agentCaptureFeedback').classList.toggle('issue', !success);
|
|
213
|
+
} catch (error) { $('agentCaptureFeedback').textContent = `操作失败:${error.message}`; $('agentCaptureFeedback').classList.add('issue'); }
|
|
214
|
+
finally { captureBusy = false; await loadAgentCapture(); post('eventBridge', { type: 'kb:refresh-agents' }); }
|
|
186
215
|
}
|
|
187
216
|
async function loadLogs() {
|
|
188
217
|
const epoch = ++logEpoch;
|
|
@@ -307,6 +336,7 @@
|
|
|
307
336
|
if (button.dataset.action === 'import') openImport();
|
|
308
337
|
if (button.dataset.action === 'repair') await repair();
|
|
309
338
|
if (button.dataset.action === 'system') await openSystem();
|
|
339
|
+
if (button.dataset.agentCapture) await changeAgentCapture(button.dataset.agentClient, button.dataset.agentCapture);
|
|
310
340
|
if (button.dataset.action === 'unbind') { const p = activeProject(); if (p && !unbinding) { unbindProjectId = p.projectId; $('unbindProjectName').textContent = `解绑「${p.name}」?`; $('unbindFeedback').textContent = ''; $('unbindDialog').showModal(); } }
|
|
311
341
|
if (button.dataset.retryRun) { await api(`/api/engine/runs/${encodeURIComponent(button.dataset.retryRun)}/retry`, { method: 'POST', body: {} }); await refreshSnapshot(); }
|
|
312
342
|
if (button.dataset.contextIndex !== undefined) await openReference(state.contexts.get(state.projectId).references[Number(button.dataset.contextIndex)]);
|
|
@@ -325,6 +355,7 @@
|
|
|
325
355
|
if (event.origin !== location.origin || !event.data || typeof event.data !== 'object') return;
|
|
326
356
|
const key = Object.keys(frames).find(k => $(frames[k].id).contentWindow === event.source); if (!key) return;
|
|
327
357
|
const message = event.data;
|
|
358
|
+
if (message.type === 'kb:open-integrations' && key === 'eventBridge') { openSystem().then(() => $('agentCaptureSection').scrollIntoView({ block: 'start' })).catch(error => toast(error.message)); return; }
|
|
328
359
|
if (message.type === 'kb:ready') { frames[key].ready = true; frameContext(key); if (frames[key].openSettings) { post(key, { type: 'kb:open-settings' }); frames[key].openSettings = false; } }
|
|
329
360
|
if (message.requestId && message.requestId !== frames[key].requestId) return;
|
|
330
361
|
if (message.type === 'kb:knowledge' && key === 'terminal') {
|
|
@@ -336,6 +367,7 @@
|
|
|
336
367
|
on('themeToggle', 'click', () => { document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; localStorage.setItem('kb.theme', document.documentElement.dataset.theme); Object.keys(frames).forEach(key => post(key, { type: 'kb:theme', theme: document.documentElement.dataset.theme })); });
|
|
337
368
|
on('openSystem', 'click', openSystem); on('closeSystem', 'click', closeSystem); on('drawerBackdrop', 'click', closeSystem);
|
|
338
369
|
on('repairWiring', 'click', repair);
|
|
370
|
+
on('refreshAgentCapture', 'click', () => captureBusy ? undefined : loadAgentCapture());
|
|
339
371
|
on('configureProviders', 'click', () => { closeSystem(); setView('work'); initializeFrame('terminal'); if (frames.terminal.ready) post('terminal', { type: 'kb:open-settings' }); else frames.terminal.openSettings = true; });
|
|
340
372
|
on('saveMaintenance', 'click', async () => { await api('/api/terminal/maintenance-default', { method: 'PUT', body: { profileId: $('maintenanceModel').value || null } }); const result = await api('/api/modules/configure', { method: 'POST', body: {} }); $('configurationStatus').textContent = result.configuration.model ? '后台默认模型已保存并同步。' : '配置已保存,后台运行配置尚未同步,请检查模块连接。'; });
|
|
341
373
|
on('openEmbedding', 'click', openEmbedding);
|