@sanqianx/project-knowledge 5.0.9-rc.9 → 5.0.9

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.
Files changed (34) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +129 -0
  3. package/README.md +11 -4
  4. package/_modules/knowledge-engine/README.md +5 -0
  5. package/_modules/knowledge-engine/package.json +1 -1
  6. package/_modules/knowledge-engine/src/coordinator.js +1 -1
  7. package/_modules/knowledge-engine/src/headless-analyzer.js +1 -0
  8. package/_modules/knowledge-engine/src/retrieval.js +2 -1
  9. package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +6 -2
  10. package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +7 -2
  11. package/_modules/knowledge-engine/src/store.js +4 -6
  12. package/_site/_test/bin-cli-test.js +15 -2
  13. package/_site/_test/mcp-server-test.js +1 -1
  14. package/_site/_test/module-agent-start-proxy-test.js +65 -0
  15. package/_site/_test/module-bridge-eventbridge-test.js +1 -1
  16. package/_site/_test/module-wiring-unbind-race-test.js +59 -0
  17. package/_site/_test/path-consistency-test.js +1 -1
  18. package/_site/_test/product-diagnostics-ui-test.js +5 -1
  19. package/_site/_test/product-progress-status-test.js +70 -0
  20. package/_site/_test/shared-scope-root-binding-test.js +67 -0
  21. package/_site/_test/test-server-cleanup-test.js +5 -1
  22. package/_site/_test/vector-storage-root-test.js +16 -0
  23. package/_site/lib/engine-connection.js +4 -1
  24. package/_site/lib/knowledge-tool-runtime.js +1 -1
  25. package/_site/lib/module-bridge.js +65 -8
  26. package/_site/lib/server-app.js +4 -2
  27. package/_site/lib/settings-store.js +20 -0
  28. package/module-runtime-manifest.json +39 -39
  29. package/package.json +3 -3
  30. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  31. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  32. package/plugins/project-knowledge/.mcp.json +1 -1
  33. package/ui/product.js +4 -2
  34. package/vendor-manifest.json +9 -9
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const os = require('node:os');
6
+ const { createEngineServer } = require('../../_modules/knowledge-engine/src/server');
7
+ const { SettingsStore } = require('../lib/settings-store');
8
+ const { ModuleBridge } = require('../lib/module-bridge');
9
+
10
+ (async () => {
11
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-shared-bindings-'));
12
+ const oldRoot = path.join(root, '原知识根目录'), newRoot = path.join(root, '新知识根目录');
13
+ const settings = new SettingsStore({ dataDir: path.join(root, 'shell') });
14
+ await settings.initialize({ knowledge: { rootPath: oldRoot } });
15
+ await settings.addDomain({ id: 'domain-tools', name: '开发工具' });
16
+ let engine;
17
+ async function start() {
18
+ engine = createEngineServer({ dataDir: path.join(root, 'engine'), consume: false, runtimeToken: 'scope-binding-test' });
19
+ await engine.ready;
20
+ await new Promise(resolve => engine.server.listen(0, '127.0.0.1', resolve));
21
+ return `http://127.0.0.1:${engine.server.address().port}`;
22
+ }
23
+ const logs = [];
24
+ const bridge = new ModuleBridge({ settingsStore: settings, runtimeToken: 'scope-binding-test', logger: { warn: (...args) => logs.push(args) } });
25
+ const upstream = bridge._fetch.bind(bridge);
26
+ bridge._fetch = (url, init) => url.startsWith(bridge.engineUrl) ? upstream(url, init) : Promise.resolve({ ok: false });
27
+ try {
28
+ bridge.engineUrl = await start();
29
+ assert.equal((await bridge.syncEngineConfiguration()).scopes, true);
30
+ const scopes = structuredClone(engine.store.state.sharedScopes);
31
+ const brain = scopes.find(scope => scope.kind === 'brain');
32
+ const domain = scopes.find(scope => scope.kind === 'domain');
33
+ engine.store.addProject({ projectId: 'project-test', repoPath: path.join(root, 'code'), knowledgePath: path.join(root, 'project-knowledge'), domainId: domain.id });
34
+ engine.store.state.memories.push({ id: 'memory-test', brainId: brain.id, status: 'candidate', text: '保持目录绑定稳定', evidence: { projectId: 'project-test', commitSha: 'a'.repeat(40), entryId: 'README.md' } });
35
+ engine.store.save();
36
+ await engine.coordinator.memories.update('memory-test', { status: 'confirmed' });
37
+ fs.writeFileSync(path.join(domain.knowledgePath, 'README.md'), '# 领域经验\n\n领域知识不能随根目录设置隐式移动。\n');
38
+ const memoryFile = engine.coordinator.memories.file(brain);
39
+ const before = fs.readFileSync(memoryFile);
40
+ await settings.updatePatch({ knowledge: { rootPath: newRoot } });
41
+ assert.equal((await bridge.syncEngineConfiguration()).scopes, true, 'changing index root must not invalidate shared memory wiring');
42
+ assert.deepEqual(engine.store.state.sharedScopes.map(scope => scope.knowledgePath), scopes.map(scope => scope.knowledgePath));
43
+ assert.deepEqual(fs.readFileSync(memoryFile), before);
44
+ assert.equal(settings.read().brain.knowledgePath, brain.knowledgePath);
45
+ assert.equal(settings.read().brain.domains[0].knowledgePath, domain.knowledgePath);
46
+ await assert.rejects(settings.updatePatch({ brain: { knowledgePath: newRoot } }), { code: 'INVALID_ARGUMENT' });
47
+ await assert.rejects(settings.bindSharedKnowledgePaths([{ ...brain, knowledgePath: newRoot }]), { code: 'IMMUTABLE_FIELD' });
48
+ await settings.addDomain({ id: 'domain-new', name: '新领域' });
49
+ assert.equal((await bridge.syncEngineConfiguration()).scopes, true);
50
+ assert.equal(engine.store.state.sharedScopes.find(scope => scope.id === 'domain-new').knowledgePath, path.join(newRoot, '_shared', 'domain-new'));
51
+ await engine.close();
52
+ bridge.engineUrl = await start();
53
+ assert.equal((await bridge.syncEngineConfiguration()).scopes, true);
54
+ assert.deepEqual(fs.readFileSync(memoryFile), before);
55
+ const memory = await engine.coordinator.retrieval.get({ projectId: 'project-test', spaceId: 'brain:brain-default', entryId: 'memories/confirmed.md' });
56
+ assert.ok(memory.chunks.some(chunk => chunk.chunk_text.includes('保持目录绑定稳定')));
57
+ const shared = await engine.coordinator.retrieval.get({ projectId: 'project-test', spaceId: 'domain:domain-tools', entryId: 'README.md' });
58
+ assert.ok(shared.chunks.some(chunk => chunk.chunk_text.includes('不能随根目录设置隐式移动')));
59
+ await engine.close(); engine = null;
60
+ const pending = await bridge.syncEngineConfiguration();
61
+ assert.equal(pending.scopes, false);
62
+ assert.equal(pending.scopeStatus.status, 'pending');
63
+ assert.ok(logs.some(entry => entry[0] === 'modules.scopes.pending'));
64
+ assert.equal(settings.read().brain.knowledgePath, brain.knowledgePath);
65
+ console.log('shared-scope-root-binding-test PASS');
66
+ } finally { if (engine) await engine.close(); }
67
+ })().catch(error => { console.error(error); process.exitCode = 1; });
@@ -5,6 +5,7 @@ const path = require('node:path');
5
5
  const { spawnServer } = require('./helpers/spawn-server');
6
6
  const freePort = require('./helpers/free-port');
7
7
  const { readEndpoint } = require('../lib/runtime-endpoint');
8
+ const { spawn } = require('node:child_process');
8
9
 
9
10
  (async () => {
10
11
  const port = await freePort();
@@ -19,7 +20,10 @@ const { readEndpoint } = require('../lib/runtime-endpoint');
19
20
  assert.equal(readEndpoint(spawned.dataDir)?.pid, spawned.child.pid);
20
21
  let output = '';
21
22
  spawned.child.stderr.on('data', bytes => { output += bytes; });
22
- await spawned.cleanup(); stopped = true;
23
+ const locker = process.platform === 'win32' ? spawn(process.execPath, ['-e', 'process.stdout.write("holding-cwd\\n");setTimeout(()=>process.exit(0),2500)'], { cwd: spawned.dataDir, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) : null;
24
+ if (locker) await new Promise((resolve, reject) => { locker.once('error', reject); locker.stdout.once('data', resolve); });
25
+ try { await spawned.cleanup(); stopped = true; }
26
+ finally { if (locker?.exitCode === null) { locker.kill(); await new Promise(resolve => locker.once('close', resolve)); } }
23
27
  assert.notEqual(spawned.child.exitCode, null, output);
24
28
  assert.equal(spawned.child.signalCode, null, 'normal cleanup must drain the server instead of force killing it');
25
29
  assert.equal(spawned.child.exitCode, 0, output);
@@ -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'));
@@ -24,7 +24,10 @@ async function connectEngine(runtime) {
24
24
  step = 'project-wiring';
25
25
  for (const id of runtime.registryStore.listIds()) {
26
26
  projectId = id;
27
- const config = runtime.projectStore.readConfig(id);
27
+ if (!runtime.registryStore.readDisplaySnapshot(id)) continue;
28
+ let config;
29
+ try { config = runtime.projectStore.readConfig(id); }
30
+ catch (error) { if (!runtime.registryStore.readDisplaySnapshot(id)) continue; throw error; }
28
31
  const modules = await bridge.registerProjectLinks({ projectId: id, name: config.displayName, workspacePath: config.repoPath, knowledgePath: config.knowledgePath });
29
32
  if (!modules.engine.ok || !modules.eventBridge.ok) throw new Error('PROJECT_WIRING_PENDING');
30
33
  }
@@ -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.getIndexPath(),
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 },
@@ -31,6 +31,9 @@ const { once } = require('events');
31
31
  const { DomainError } = require('./contracts');
32
32
 
33
33
  const PROXY_TIMEOUT_MS = 10000;
34
+ // Native client startup includes knowledge retrieval and a local server/CLI
35
+ // handshake. It has a bounded setup budget, separate from ordinary reads/SSE.
36
+ const AGENT_START_TIMEOUT_MS = 90000;
34
37
  // Registration drives a full embedding pass in vector-hub (remote API); a
35
38
  // fresh knowledge folder can easily exceed the interactive proxy budget.
36
39
  const REGISTER_TIMEOUT_MS = 60000;
@@ -90,6 +93,7 @@ class ModuleBridge {
90
93
  this.runtimeToken = options.runtimeToken || crypto.randomBytes(32).toString('hex');
91
94
  this.registryStore = options.registryStore || null;
92
95
  this.projectStore = options.projectStore || null;
96
+ this.projectWiringQueue = new Map();
93
97
  this.settingsStore = options.settingsStore || null;
94
98
  this.dataDir = options.dataDir || null;
95
99
  this.supervised = [];
@@ -131,7 +135,11 @@ class ModuleBridge {
131
135
  // The native folder pickers hold the request open until the user closes
132
136
  // the dialog — they need a human-scale budget, not the API budget.
133
137
  const isPicker = /\/(system\/)?pick-folder/.test(target);
134
- const timeoutMs = isPicker ? PICKER_TIMEOUT_MS : PROXY_TIMEOUT_MS;
138
+ const targetUrl = new URL(target);
139
+ const pathname = targetUrl.pathname;
140
+ const isAgentStart = req.method === 'POST' && /^\/api\/claude-workbench\/v1\/sessions(?:\/[^/]+\/input)?\/?$/.test(pathname);
141
+ const timeoutMs = isPicker ? PICKER_TIMEOUT_MS : isAgentStart ? AGENT_START_TIMEOUT_MS : PROXY_TIMEOUT_MS;
142
+ const startedAt = Date.now();
135
143
  let upstream;
136
144
  const controller = new AbortController();
137
145
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -148,8 +156,13 @@ class ModuleBridge {
148
156
  clearTimeout(timer);
149
157
  res.off?.('close', disconnected);
150
158
  if (res.destroyed) return;
159
+ const details = { failureKind: controller.signal.aborted ? 'UPSTREAM_TIMEOUT' : 'NETWORK_ERROR', timeoutMs };
160
+ this.logger.warn('module.proxy.failed', '模块请求未完成,请检查连接或客户端启动。', {
161
+ component: 'module-bridge', phase: 'proxy', durationMs: Date.now() - startedAt,
162
+ context: { ...details, method: req.method, path: pathname },
163
+ });
151
164
  res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
152
- res.end(JSON.stringify({ ok: false, error: 'MODULE_UNREACHABLE', target }));
165
+ res.end(JSON.stringify({ ok: false, error: 'MODULE_UNREACHABLE', target: targetUrl.origin + pathname, details }));
153
166
  return;
154
167
  }
155
168
  const contentType = upstream.headers.get('content-type') || 'application/json; charset=utf-8';
@@ -323,7 +336,7 @@ class ModuleBridge {
323
336
  let indexPath = null;
324
337
  try { indexPath = new StorageLayout({ dataDir }).getVectorRootPath(this.settingsStore?.read()); }
325
338
  catch { /* Corrupt settings must not switch storage or prevent credential revocation. */ }
326
- const status = { scopes: false, model: false, embedding: false, indexStorage: { expectedPath: indexPath, actualPath: null, status: 'pending' } };
339
+ const status = { scopes: false, scopeStatus: { status: 'pending' }, model: false, embedding: false, indexStorage: { expectedPath: indexPath, actualPath: null, status: 'pending' } };
327
340
  const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.runtimeToken}` };
328
341
  try {
329
342
  if (this.settingsStore) {
@@ -331,11 +344,28 @@ class ModuleBridge {
331
344
  if (settings.knowledge.rootPath) {
332
345
  const brain = settings.brain;
333
346
  const root = path.join(settings.knowledge.rootPath, '_shared');
334
- const scopes = [{ id: brain.id, kind: 'brain', brainId: brain.id, name: brain.name, knowledgePath: path.join(root, brain.id) }, ...brain.domains.map(d => ({ id: d.id, kind: 'domain', brainId: brain.id, domainId: d.id, name: d.name, knowledgePath: path.join(root, d.id) }))];
335
- status.scopes = (await this._fetch(`${this.engineUrl}/api/scopes`, { method: 'POST', headers, body: JSON.stringify({ scopes }) })).ok;
347
+ const response = await this._fetch(`${this.engineUrl}/api/snapshot`);
348
+ if (!response.ok) throw new DomainError('ENGINE_UNAVAILABLE', '共享知识接线暂时不可用。');
349
+ const prior = (await response.json()).sharedScopes || [];
350
+ // Root settings govern new directories and derived indexes, never move
351
+ // existing knowledge. Retain engine-acknowledged bindings in the spine.
352
+ const binding = (item, kind) => {
353
+ const existing = prior.find(scope => scope.id === item.id && scope.kind === kind && scope.brainId === brain.id);
354
+ if (existing && !path.isAbsolute(existing.knowledgePath || '')) throw new DomainError('DATA_CORRUPT', '共享目录绑定损坏。');
355
+ return item.knowledgePath || existing?.knowledgePath || path.join(root, item.id);
356
+ };
357
+ const scopes = [{ id: brain.id, kind: 'brain', brainId: brain.id, name: brain.name, knowledgePath: binding(brain, 'brain') }, ...brain.domains.map(d => ({ id: d.id, kind: 'domain', brainId: brain.id, domainId: d.id, name: d.name, knowledgePath: binding(d, 'domain') }))];
358
+ const applied = await this._fetch(`${this.engineUrl}/api/scopes`, { method: 'POST', headers, body: JSON.stringify({ scopes }) });
359
+ if (!applied.ok) throw new DomainError('SHARED_SCOPE_APPLY_FAILED', '共享目录接线未完成。');
360
+ await this.settingsStore.bindSharedKnowledgePaths?.(scopes);
361
+ status.scopes = true; status.scopeStatus = { status: 'ready' };
336
362
  }
337
363
  }
338
- } catch { /* A scope failure must not prevent revoking a stale model key. */ }
364
+ } catch (error) {
365
+ status.scopeStatus = { status: 'pending', code: ['DATA_CORRUPT', 'IMMUTABLE_FIELD', 'ENGINE_UNAVAILABLE', 'SHARED_SCOPE_APPLY_FAILED'].includes(error.code) ? error.code : 'ENGINE_UNAVAILABLE' };
366
+ this.logger.warn('modules.scopes.pending', 'Shared knowledge wiring is pending; existing data is retained.', { context: { code: status.scopeStatus.code } });
367
+ // A scope failure must not prevent revoking a stale model key.
368
+ }
339
369
  try {
340
370
  let profile = null;
341
371
  try {
@@ -369,7 +399,24 @@ class ModuleBridge {
369
399
  * (retry on the next register call or health-check pass), never a hard error.
370
400
  * KB_MODULES_REGISTER=0 disables fan-out entirely (test isolation).
371
401
  */
372
- async registerProjectLinks({ projectId, name, workspacePath, knowledgePath }) {
402
+ _withProjectWiring(projectId, action) {
403
+ const prior = this.projectWiringQueue.get(projectId) || Promise.resolve();
404
+ const operation = prior.catch(() => {}).then(action);
405
+ const settled = operation.finally(() => { if (this.projectWiringQueue.get(projectId) === settled) this.projectWiringQueue.delete(projectId); });
406
+ this.projectWiringQueue.set(projectId, settled);
407
+ return settled;
408
+ }
409
+
410
+ registerProjectLinks(input) {
411
+ return this._withProjectWiring(input.projectId, () => {
412
+ // A supervisor may have captured this project before a concurrent unbind.
413
+ // Recheck the spine after acquiring the same lease as the removal path.
414
+ if (this.registryStore && !this.registryStore.readDisplaySnapshot(input.projectId)) return Object.fromEntries(['terminal', 'vectorHub', 'eventBridge', 'engine'].map(key => [key, { ok: true, status: 'removed', skipped: 'project-not-registered' }]));
415
+ return this._registerProjectLinks(input);
416
+ });
417
+ }
418
+
419
+ async _registerProjectLinks({ projectId, name, workspacePath, knowledgePath }) {
373
420
  const result = { terminal: { ok: false }, vectorHub: { ok: false }, eventBridge: { ok: false }, engine: { ok: false } };
374
421
  if (String(process.env.KB_MODULES_REGISTER || '') === '0') {
375
422
  result.terminal = { ok: false, status: 'disabled' };
@@ -461,7 +508,17 @@ class ModuleBridge {
461
508
  * only drops its derived index (source untouched); terminal removes the project
462
509
  * with keepSessions=1 so session records survive; the event-bridge console only
463
510
  * unregisters the project — journals and sealed commit documents stay on disk. */
464
- async removeProjectLinks({ projectId, requireEngine = true }) {
511
+ removeProjectLinks(input) {
512
+ return this._withProjectWiring(input.projectId, async () => {
513
+ const result = await this._removeProjectLinks(input);
514
+ // The caller owns registry/lifecycle changes. Hold the wiring lease until
515
+ // its transaction settles, so delayed reconfiguration cannot resurrect it.
516
+ await input.onRemoved?.(result);
517
+ return result;
518
+ });
519
+ }
520
+
521
+ async _removeProjectLinks({ projectId, requireEngine = true }) {
465
522
  const wiring = (this.projectStore ? this.projectStore.readState(projectId).modules : null) || {};
466
523
  const result = { terminal: { ok: false }, vectorHub: { ok: false }, eventBridge: { ok: false }, engine: { ok: false } };
467
524
  // Stop the writer first, before removing hooks/index registrations.
@@ -1057,11 +1057,13 @@ function createRequestHandler(runtime, options = {}) {
1057
1057
  throw new DomainError('PROJECT_NOT_FOUND', 'Project was not found.', { status: 404 });
1058
1058
  }
1059
1059
  const body = await readJsonBody(req);
1060
- if (runtime.registryStore.readDisplaySnapshot(projectId)) await runtime.moduleBridge.removeProjectLinks({ projectId, requireEngine: runtime.analysisOwner !== 'legacy' });
1061
- const result = await runtime.lifecycleService.deleteProject(projectId, {
1060
+ const remove = () => runtime.lifecycleService.deleteProject(projectId, {
1062
1061
  deleteKnowledge: body.deleteKnowledge === true,
1063
1062
  confirmationToken: body.confirmationToken || '',
1064
1063
  });
1064
+ let result;
1065
+ if (runtime.registryStore.readDisplaySnapshot(projectId)) await runtime.moduleBridge.removeProjectLinks({ projectId, requireEngine: runtime.analysisOwner !== 'legacy', onRemoved: async () => { result = await remove(); } });
1066
+ else result = await remove();
1065
1067
  return send(res, 200, result);
1066
1068
  }
1067
1069
  const gitStatusMatch = pathname.match(/^\/api\/projects\/([^/]+)\/git-status$/);
@@ -1,6 +1,7 @@
1
1
  const { SCHEMAS, DomainError, publicAiProfilesConfig } = require('./contracts');
2
2
  const AtomicFile = require('./atomic-file');
3
3
  const { StorageLayout } = require('./storage-layout');
4
+ const path = require('node:path');
4
5
 
5
6
  function defaultSettings() {
6
7
  return {
@@ -43,9 +44,12 @@ function validateSettings(settings) {
43
44
  }
44
45
  settings.brain = settings.brain || { id: 'brain-default', name: '我的智脑', domains: [] };
45
46
  if (settings.brain.id !== 'brain-default' || typeof settings.brain.name !== 'string' || !settings.brain.name.trim() || settings.brain.name.length > 100 || !Array.isArray(settings.brain.domains)) throw new DomainError('DATA_CORRUPT', 'Brain metadata is invalid.', { status: 500 });
47
+ const validBinding = value => value === undefined || typeof value === 'string' && path.isAbsolute(value);
48
+ if (!validBinding(settings.brain.knowledgePath)) throw new DomainError('DATA_CORRUPT', 'Brain knowledge binding is invalid.', { status: 500 });
46
49
  const ids = new Set();
47
50
  for (const domain of settings.brain.domains) {
48
51
  if (!domain || !/^domain-[a-z0-9-]+$/.test(domain.id) || ids.has(domain.id) || typeof domain.name !== 'string' || !domain.name.trim() || domain.name.length > 100) throw new DomainError('DATA_CORRUPT', 'Domain metadata is invalid.', { status: 500 });
52
+ if (!validBinding(domain.knowledgePath)) throw new DomainError('DATA_CORRUPT', 'Domain knowledge binding is invalid.', { status: 500 });
49
53
  ids.add(domain.id);
50
54
  }
51
55
  const logging = settings.logging || {};
@@ -92,6 +96,7 @@ class SettingsStore {
92
96
 
93
97
  async updatePatch(patch) {
94
98
  return this.atomic.withFileLock(this.lockPath, async () => {
99
+ if (patch?.brain && (Object.prototype.hasOwnProperty.call(patch.brain, 'knowledgePath') || patch.brain.domains?.some?.(domain => Object.prototype.hasOwnProperty.call(domain, 'knowledgePath')))) throw new DomainError('INVALID_ARGUMENT', '共享知识目录绑定只允许受控接线写入。');
95
100
  if (patch && patch.logging && typeof patch.logging === 'object') {
96
101
  const unknown = Object.keys(patch.logging).find(key => key !== 'levels');
97
102
  if (unknown) throw new DomainError('INVALID_ARGUMENT', `Logging setting is read-only or unsupported: ${unknown}.`, { details: { field: unknown } });
@@ -120,6 +125,21 @@ class SettingsStore {
120
125
  });
121
126
  }
122
127
 
128
+ async bindSharedKnowledgePaths(scopes) {
129
+ return this.atomic.withFileLock(this.lockPath, async () => {
130
+ const settings = this.read();
131
+ let changed = false;
132
+ for (const scope of scopes) {
133
+ const target = scope.kind === 'brain' && scope.id === settings.brain.id ? settings.brain : settings.brain.domains.find(domain => domain.id === scope.id);
134
+ if (!target || !path.isAbsolute(scope.knowledgePath || '')) throw new DomainError('INVALID_ARGUMENT', '共享知识绑定无效。');
135
+ if (target.knowledgePath && !this.layout.pathsEqual(target.knowledgePath, scope.knowledgePath)) throw new DomainError('IMMUTABLE_FIELD', '共享知识目录不能隐式移动。', { status: 409 });
136
+ if (!target.knowledgePath) { target.knowledgePath = path.resolve(scope.knowledgePath); changed = true; }
137
+ }
138
+ if (changed) { settings.updatedAt = new Date().toISOString(); validateSettings(settings); this.atomic.writeJsonAtomic(this.filePath, settings); }
139
+ return settings;
140
+ });
141
+ }
142
+
123
143
  readPublicView(options = {}) {
124
144
  const settings = this.read(options);
125
145
  return {
@@ -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": "1081a5b01f2f46c4828b63873670360de85d78e7830eebbec1721ddb1618e8e1",
5
+ "ui/product.js": "e7ac0fc1d1d515039f981ed8481155d112034dc4961a2e7259864720f54c83ea",
6
6
  "ui/product.css": "70547d3f2c53b6cc5c73cb95eb8cb3faf0dc8ccee7cd6b11a89a88885de7c3f8",
7
7
  "ui/favicon.svg": "c48be946b224112ebb0238b44263f7a61bda31e531afa895c2e9801c0ff24f32"
8
8
  },
@@ -10,13 +10,13 @@
10
10
  "terminal": {
11
11
  "name": "@sanqianx/claude-ai-workbench",
12
12
  "installName": "claude-ai-workbench",
13
- "version": "0.13.2-rc.4",
13
+ "version": "0.13.2-rc.7",
14
14
  "files": {
15
15
  "LICENSE": "c1b9df1275e769f3dbab000d1e457a2d4b0f28eb5da6c77e48dc37eeba202ed7",
16
- "README.md": "6f4176416a3cfa702d17da8987327e014fec93e34ff5bd350175c771a55c1521",
17
- "apps/agent-terminal/app.css": "39cc65ac65bcb390f4aa21d9b783182ca229557f6bec15090f69598120161917",
18
- "apps/agent-terminal/app.js": "2fedcc79f9515ebae89d1c7dc996d732aa1d0946038eaaa045438121bf27f551",
19
- "apps/agent-terminal/index.html": "6a52ba210696c8aed99e2786e21d2771340070d51583199dd74c8da41edf0257",
16
+ "README.md": "5a382c531372deaa63b3e014f4dbd077d41ce02efee40347973463081848cb86",
17
+ "apps/agent-terminal/app.css": "ede36d9fdb1f7036e3453134c0d5a86197bfc30e3ac547a24b4fc1d17847277e",
18
+ "apps/agent-terminal/app.js": "e23fde6529f72031b745b47d5658e62f9079678248aab95599487d1d0660b6e7",
19
+ "apps/agent-terminal/index.html": "cdffd5c535c81f56aba70ec5dd3bc060f59ec2abcdc8676b79c8f327126be605",
20
20
  "apps/ai-profile-settings-host/index.html": "bc158f68954506d2b8e43c690f28dbe931eff9a02b1524855ac17e74ea4df736",
21
21
  "apps/ai-profile-settings-host/page.css": "8b75cbf7f1ea5aaa92f2889390720ece310a8fd4b9ca38c890fa447d4323a557",
22
22
  "apps/ai-profile-settings-host/page.js": "f42755a3ed17bd7e00919cee56875b04035aa3c5526fce889f8ba52f4278a8b8",
@@ -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": "702a9cab4ccc38c186977c4bdcba653443311c676d6c7a70b7661647fa303470",
30
+ "docs/sdk/openapi.yaml": "18581c89859580ba153ca6582691fe87daf9bc37fb9971040bd8c564a0440281",
31
31
  "docs/sdk/README.md": "7ab591d39c36a3b84cfdf487ff567040ec453975f3ef0fba86dc822094667b61",
32
- "package.json": "87a7e1e85554e0d047eac03787c405d7017b5d83648a4968638553c136d0b7d1",
32
+ "package.json": "f2f45324f4bff372fe99e8bcb0af04624162a2f65712f8b8f6479e5630d64de7",
33
33
  "packages/client/index.d.ts": "fb66b7b21ac936cddcb7f74543cf4f3bb7e80c169534ec249ab8ef2356224162",
34
34
  "packages/client/index.js": "92fe5ddd32289d729f336f669e47bd9601e562f5a4309ee4fa7f7f15b567edc2",
35
- "packages/client/package.json": "796d1fb39003b780248a962967ae4f0f84a5d834b2beedeedc10b958291c3645",
35
+ "packages/client/package.json": "2cc2ee4f2305ddb5fe32f0c9bc928f47257e4522f7b2063e84de92d981f7660a",
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": "deaa5929f849f6f994df7fbb0243c77f896b8fc73edcc3c785206b898cfafa71",
39
+ "packages/contracts/package.json": "502ec29cf54dcbc604dc9bc6ccd7c74a553ae9b301616b37e2624c6a038601fc",
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": "6e579a889060bf6fad7709ea89a104fe4ac90854ea39107b649c44b3b83666bf",
43
+ "packages/core/package.json": "56adcbfba15bf283e885032f3d6bab4ff3a8d3ba1ab03e84e44fde04ed2ff445",
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",
@@ -51,7 +51,7 @@
51
51
  "packages/server/lib/agent-registry.js": "fbf4f3e96542502a41e7fdf77037d63c3b0444d26708ad9766bec90fbb6b38bb",
52
52
  "packages/server/lib/attachment-store.js": "5c92c45c7ec5a8b0ee9c14edb42eeda5fce93d8e654718dcd75c12e3f0bfa6e7",
53
53
  "packages/server/lib/bridges/app-server-bridge.js": "85bb644ab570a7a7e6aa26123c8e3cd2e8c11c3ff4e1270954b8e26517968f69",
54
- "packages/server/lib/bridges/opencode-server-bridge.js": "ab3a9330db5a497760b998dbe6af47fbe351683ad92941d7e1a820e72e382bb1",
54
+ "packages/server/lib/bridges/opencode-server-bridge.js": "910e4216b184d35b98fb525c629670007f4eaf52f27cfe58a631764983271ff6",
55
55
  "packages/server/lib/capture-settings-store.js": "eb18885d7bedd3e353287dd7d1cf81e815e5d65d5d82170aa8fe5506a03de8bd",
56
56
  "packages/server/lib/cli-session-recovery.js": "e66dc0ad83af18841bf3a598bb27932af42a20cf9395c16dcbaf9b022a5201b5",
57
57
  "packages/server/lib/context-resolver.js": "d3060bfe35fb184e521847fc0900cf6114f8ebf3120ce13ed39dfc224af54660",
@@ -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": "618af8f48eb5da8f163d061c1832973a0b1e8a733fb795ff9c00f56379a4d1b1",
77
+ "packages/server/package.json": "08195a1f8a78f8a9405f3c5c7e037f4c451c77128fd0f81e7406cada89ca8075",
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": "f5da277437bfb5308bb0cc8c998cc7b02cc92db7dd767a2242eedcad30200b81",
85
+ "packages/ui/package.json": "a07586cb53b6eb77c4c5af51bcc81509c753e4bbf715a7359e466cec2ba80012",
86
86
  "packages/ui/README.md": "793c4a9c286fee23f39062a1976ca3afe9d2816dcdb55e156ba940e1ef514f89",
87
87
  "packages/ui/theme.css": "8f03a081272a7db11b196f7a70d0fafb1f8d954778f329f1ba53d110d9d5fd98",
88
88
  "src/assets/claude-prompts.json": "cb111f2e0a45afe8a31c251a504d1ab5375966ed61291660e61fd2efd77f6d9f",
@@ -112,9 +112,9 @@
112
112
  "vectorHub": {
113
113
  "name": "@sanqianx/vector-hub",
114
114
  "installName": "@sanqianx/vector-hub",
115
- "version": "1.0.8-rc.2",
115
+ "version": "1.0.8-rc.3",
116
116
  "files": {
117
- "README.md": "b0d0fd647aef14c803ac4573f58bd2d425c2dc9bc736187a4684d7caba473184",
117
+ "README.md": "da267117f2f3cf61bc865a6fa3a2097dcb999f7f7e50eb7add0b74494a9f982e",
118
118
  "dist/cjs/bin.d.ts": "43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012",
119
119
  "dist/cjs/bin.js": "8d0317ce087d22074b4ad350d63bf39cd6d218da565496621cd0daf4245cbdc4",
120
120
  "dist/cjs/bin.js.map": "818e872380d9c7ba045238ba65ce77111ba822b356be26327ed05936e5667e5e",
@@ -122,12 +122,12 @@
122
122
  "dist/cjs/core/embeddings.js": "5e199fd6e0de174aad1ba5440dd6358d9b180d4f82d62c7d8cd1fb8a17efb2d7",
123
123
  "dist/cjs/core/embeddings.js.map": "f1b9ec78274cdd7359a59e8a47af7c376de61587bc4b9fc78df970734169ee38",
124
124
  "dist/cjs/core/hub.d.ts": "5173ac7396ffdce780fa0b93428a330142a1a2edf378527b6291036edc9a0d7d",
125
- "dist/cjs/core/hub.js": "c84ddbcde8468ec81dcd07149bfdcc748c650c43d3dc0f35a7f495ccfdb35f97",
126
- "dist/cjs/core/hub.js.map": "9b424cefdc5046c29b0e4aa71e7881bf8499fbf968a2f8591615b9de497e49fc",
127
- "dist/cjs/core/kb-sync.d.ts": "9b0538911c52ac47ae4784a7856aa7d7e15f6b09887a6b3db8aaa12046179d30",
128
- "dist/cjs/core/kb-sync.js": "a03da0f0d5844380a5cbb3307f92472a8ef0cd5185a8b062b4e54c56dfac4032",
129
- "dist/cjs/core/kb-sync.js.map": "fbd2ae982a20b2f7977438eeb71d86a23f25c49f8e5c4d4a6abde2bd46c51065",
130
- "dist/cjs/core/types.d.ts": "293e642a88a592c2f105ac31cbaa800697c357b3d6a039ec68763c028cf70643",
125
+ "dist/cjs/core/hub.js": "f90ee514c0cf9ac67d7aae728d7af2606d7404ab84436fbdc066663006cb67e2",
126
+ "dist/cjs/core/hub.js.map": "10d5d99d4ca8edb6af289b9da28eb63037ffa6762f382434040292d517d887fa",
127
+ "dist/cjs/core/kb-sync.d.ts": "8290ae4c5840dcf98769cfec1734d5ebeb102020b348da2a8382b1cad057da1b",
128
+ "dist/cjs/core/kb-sync.js": "b6bc7f11d3e63894fe8633151ac38c853f30d8675a15e57605bd8ff68bffd4b5",
129
+ "dist/cjs/core/kb-sync.js.map": "687ce74cdf668c1b99809a23c078861e66dacd0050056ff0231d69c3d6e6b85a",
130
+ "dist/cjs/core/types.d.ts": "b54fd90a3aaef7b90fe106857de350b2300b7eaa46a5bfb6b4959bfe09faa554",
131
131
  "dist/cjs/core/types.js": "b0d2bc4142d0c62d43f996aaeb64f22c4889ac853f8a3765758b505d972d0149",
132
132
  "dist/cjs/core/types.js.map": "9c8da204af4d662188e539b27e61f9ee44ecbe68da4211676ebdda14303e6f3b",
133
133
  "dist/cjs/index.d.ts": "96aa5a8b9c843a85c7cdc00901e0e95ecae6f01655e7de2d965f96b1d19181a0",
@@ -136,12 +136,12 @@
136
136
  "dist/cjs/server/folder-picker.d.ts": "b9c59dbc834fff668b1894f516450c2edc985c7bfac31edf9b7b4e766474b32b",
137
137
  "dist/cjs/server/folder-picker.js": "053121152a210c899e8593d60670a2a510b435c3b479300c572aa4bc8443e58e",
138
138
  "dist/cjs/server/folder-picker.js.map": "dce1ad1b03c8a6a4fba7b3cacec5b65e55c1dfd1886c5e70df384efd1a5c68a6",
139
- "dist/cjs/server/manager.d.ts": "d23ee9c3f9b72ff89c511f81f5cfb6e2b62d4ba4963099c3c9f5a65531216097",
140
- "dist/cjs/server/manager.js": "aea5c81a048cf2bd7736c289dbee9cf2d0bea4caf7093a1d8f8179d6fcea48a3",
141
- "dist/cjs/server/manager.js.map": "594ac93b2986160e47f927683f2f5feee84054dbf7255d965ada9dea561845b8",
139
+ "dist/cjs/server/manager.d.ts": "06c0e5c05ad47b3ab0a826303703d1b38d68e45f3686e687d832470dac56a05b",
140
+ "dist/cjs/server/manager.js": "947aba093ae79b294a67d8ebd10ceedcc5ff92a3298dfffbc09681f66eed4448",
141
+ "dist/cjs/server/manager.js.map": "d5236999f576019189c8464c4a9aaf1776902da2c5c09d962b557210cc3d8427",
142
142
  "dist/cjs/server/server.d.ts": "13288ace9aa6ec016a2b06dc036868324ac6c23c7e73771e95d120be778f238a",
143
- "dist/cjs/server/server.js": "43d6e18a1a36fd1af6179ff98d356efaee1da9464c85d76e9bf057b71741534e",
144
- "dist/cjs/server/server.js.map": "d599dbfbe0f98abaf103849eafee70b407d332ff0d767c1d95f54c80e85bf2bb",
143
+ "dist/cjs/server/server.js": "18cb8c4ada3a7b4c8fa20a2bdfcee2d7bff440ab1d0caabf7c6eaed2b5178bed",
144
+ "dist/cjs/server/server.js.map": "e7a4689fdaedcea9644c0c1a3065cce71017af9aa13be6cb4a90889eae0e7885",
145
145
  "dist/esm/bin.d.ts": "43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012",
146
146
  "dist/esm/bin.js": "69f6059ce2336eb7f0d9503f86c83da787aaf74a8bfd804c694ba5b2e1728be0",
147
147
  "dist/esm/bin.js.map": "fc174c2b5c66c20ff88184ddeb00577406185945ccadb0bddbb2014909339e9e",
@@ -149,12 +149,12 @@
149
149
  "dist/esm/core/embeddings.js": "b19e0cf42ca03e50644e39e39da4c003b9e273213c4532bd443c77085d2db521",
150
150
  "dist/esm/core/embeddings.js.map": "043aa6822e6b4a692be025d89efb5d7d44f4b6b39ce36bcfad2e64dde45589bb",
151
151
  "dist/esm/core/hub.d.ts": "5173ac7396ffdce780fa0b93428a330142a1a2edf378527b6291036edc9a0d7d",
152
- "dist/esm/core/hub.js": "1c4c0230ad4e9c51670a9148fd9b2464fec260b449c1a37eda94fe377b1292a8",
153
- "dist/esm/core/hub.js.map": "74beda8c34a6891b60548d84f3d8c5e6a9f5ec1055549674ee51ea6f9c4f40c6",
154
- "dist/esm/core/kb-sync.d.ts": "9b0538911c52ac47ae4784a7856aa7d7e15f6b09887a6b3db8aaa12046179d30",
155
- "dist/esm/core/kb-sync.js": "5b1df6a4d3f2a80d3c59e276590346d81d3f558c01ebb87410ebddc70f50adaa",
156
- "dist/esm/core/kb-sync.js.map": "5b959d1d3da3534c0913b72d2e9160d9e05ed2a3474c8ab8c4d50adbad2ee181",
157
- "dist/esm/core/types.d.ts": "293e642a88a592c2f105ac31cbaa800697c357b3d6a039ec68763c028cf70643",
152
+ "dist/esm/core/hub.js": "ea14e891ce2b1861b2a2a1cc9acf0d4249fab2b3354694cfbe519a26f4199453",
153
+ "dist/esm/core/hub.js.map": "a02798e02a9b4a4c41e7496c23d12c12bee25b3ed6adccb0fde91a5b757f70a6",
154
+ "dist/esm/core/kb-sync.d.ts": "8290ae4c5840dcf98769cfec1734d5ebeb102020b348da2a8382b1cad057da1b",
155
+ "dist/esm/core/kb-sync.js": "cbdfab9fecbdd2d6996744ad0bf88f446012d5616f57fdc60f5a8e8d8c462c5f",
156
+ "dist/esm/core/kb-sync.js.map": "e14d56e04a08642f6fb917310cf296ab2cf05c6a2fc6a2cab8ab929f50300843",
157
+ "dist/esm/core/types.d.ts": "b54fd90a3aaef7b90fe106857de350b2300b7eaa46a5bfb6b4959bfe09faa554",
158
158
  "dist/esm/core/types.js": "01ae2a5b120382f9a648ced7ee8507493a134f216d100fc61600c6c9738235d2",
159
159
  "dist/esm/core/types.js.map": "9c8da204af4d662188e539b27e61f9ee44ecbe68da4211676ebdda14303e6f3b",
160
160
  "dist/esm/index.d.ts": "96aa5a8b9c843a85c7cdc00901e0e95ecae6f01655e7de2d965f96b1d19181a0",
@@ -164,13 +164,13 @@
164
164
  "dist/esm/server/folder-picker.d.ts": "b9c59dbc834fff668b1894f516450c2edc985c7bfac31edf9b7b4e766474b32b",
165
165
  "dist/esm/server/folder-picker.js": "6c8247e431b7181c45fb169b29e5825a0b3589f44e1625b3a5a291d7b8358042",
166
166
  "dist/esm/server/folder-picker.js.map": "f70723213f032a0f10f538289a176a50fc8f02c200b0194479ffc139b5fbb13b",
167
- "dist/esm/server/manager.d.ts": "d23ee9c3f9b72ff89c511f81f5cfb6e2b62d4ba4963099c3c9f5a65531216097",
168
- "dist/esm/server/manager.js": "2f1772ce5885128694e8cd9c89ca7ae74694a1f85758fc8e17b577b7ab54c2e4",
169
- "dist/esm/server/manager.js.map": "f68a87c9763e8c4e3ddfc98e981ada4ea83922793035f19051b88dc3e7f47bc0",
167
+ "dist/esm/server/manager.d.ts": "06c0e5c05ad47b3ab0a826303703d1b38d68e45f3686e687d832470dac56a05b",
168
+ "dist/esm/server/manager.js": "96e1262505a02299e3d6ae5dc1c7ba3a3f69c694f57d89d97e3e4c163b40523e",
169
+ "dist/esm/server/manager.js.map": "10bb43dec7cdf07d1e6260552f40ca347a6eb491ba8bbe81b6ce2a41157a9a87",
170
170
  "dist/esm/server/server.d.ts": "13288ace9aa6ec016a2b06dc036868324ac6c23c7e73771e95d120be778f238a",
171
- "dist/esm/server/server.js": "df4caa8a596a9724c526dab5395e5018f63164ef0d10522d75ca4c44d62c1ee3",
172
- "dist/esm/server/server.js.map": "3743325b664476176931989f2d762d53b48c5eb4bcbf3d81ab222c4231d026be",
173
- "package.json": "812251c1b6f091da2f7ab06c93446e5f7c19bf4715074919e38e956e0eca373b",
171
+ "dist/esm/server/server.js": "fcf74d2d6ddc19be2ceab561efc904a2b703507901a6a35c3646c4032cac56d6",
172
+ "dist/esm/server/server.js.map": "6c19f311146c60a1ccea89ac223a16ca44e690ac5ac4d8408cd19edc7066f83e",
173
+ "package.json": "5010182529b9f1d4624ff4abd02b8eb48dbbeb4ddf92ef4bc45dd6b332aed100",
174
174
  "ui/index.html": "7301a595d625dadcd424069e7bb978004bfb6118eddef7a223bd25449ca6e0eb",
175
175
  "ui/vendor/marked.min.js": "c68075672d976e4777390560baa112194855bd4404b13647da4855aae1f9360c"
176
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/project-knowledge",
3
- "version": "5.0.9-rc.9",
3
+ "version": "5.0.9",
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": {
@@ -81,10 +81,10 @@
81
81
  "@lancedb/lancedb": "0.31.0",
82
82
  "@modelcontextprotocol/sdk": "1.29.0",
83
83
  "@sanqianx/ai-coding-event-bridge-console": "0.7.2",
84
- "@sanqianx/vector-hub": "1.0.8-rc.2",
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.4",
87
+ "claude-ai-workbench": "npm:@sanqianx/claude-ai-workbench@0.13.2-rc.7",
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.9",
4
+ "version": "5.0.9",
5
5
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
6
6
  "author": {
7
7
  "name": "SanQianX",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-knowledge",
3
- "version": "5.0.9-rc.9",
3
+ "version": "5.0.9",
4
4
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
5
5
  "author": {
6
6
  "name": "SanQianX",
@@ -6,7 +6,7 @@
6
6
  "args": [
7
7
  "-y",
8
8
  "--package",
9
- "@sanqianx/project-knowledge@5.0.9-rc.9",
9
+ "@sanqianx/project-knowledge@5.0.9",
10
10
  "project-knowledge-mcp"
11
11
  ]
12
12
  }
package/ui/product.js CHANGED
@@ -160,6 +160,7 @@
160
160
  if (epoch !== registryEpoch) return;
161
161
  state.brain = brain.brain; state.root = brain.knowledgeRoot; state.projects = projects.projects; state.health = health.modules; state.ownership = health.ownership;
162
162
  state.indexStorage = { path: brain.indexStoragePath, ...brain.configuration?.indexStorage };
163
+ state.scopeStatus = brain.configuration?.scopeStatus;
163
164
  if (!activeProject()) { state.projectId = state.projects[0]?.projectId || ''; state.epoch++; Object.keys(frames).forEach(frameContext); }
164
165
  renderProjectOptions(); renderSystem(); connectProgress(); await refreshSnapshot(); setView(state.view);
165
166
  } catch (error) { toast(error.message); }
@@ -177,6 +178,7 @@
177
178
  $('systemTitle').textContent = pending ? '知识引擎连接待恢复' : down.length ? '部分服务需要处理' : '系统连接正常';
178
179
  $('systemDetail').textContent = pending ? `${state.ownership.message || '服务恢复后自动重试。'}${state.ownership.error ? ` · ${state.ownership.error.step} (${state.ownership.error.code})` : ''}` : down.length ? down.map(k => modules[k]).join('、') : '本机知识服务已连接';
179
180
  if (storagePending && !pending && !down.length) { $('systemDot').className = 'dot issue'; $('systemTitle').textContent = '索引目录接线待恢复'; $('systemDetail').textContent = `目标目录:${state.indexStorage.expectedPath}。请在系统状态中修复接线。`; }
181
+ if (state.root && state.scopeStatus?.status === 'pending' && !pending && !down.length) { $('systemDot').className = 'dot issue'; $('systemTitle').textContent = '共享知识接线待恢复'; $('systemDetail').textContent = '已有知识与长期记忆保留,请在系统状态中修复接线。'; }
180
182
  $('moduleHealth').innerHTML = Object.entries(modules).map(([key, name]) => `<div class="row"><div><b>${esc(name)}</b><small>${esc(state.health[key]?.url || '')}</small></div>${badge(state.health[key]?.up ? '在线' : '离线', state.health[key]?.up ? 'up' : 'issue')}</div>`).join('');
181
183
  if (!navigator.onLine) {
182
184
  $('systemDot').className = 'dot issue'; $('systemTitle').textContent = '网页连接中断';
@@ -385,7 +387,7 @@
385
387
  on('embeddingForm', 'submit', async event => { event.preventDefault(); await saveEmbedding(); });
386
388
  on('disableEmbedding', 'click', () => saveEmbedding(false));
387
389
  on('embeddingDialog', 'close', () => { $('embeddingSecret').value = ''; });
388
- on('saveBrainSettings', 'click', async () => { await api('/api/brain', { method: 'PATCH', body: { name: $('brainName').value } }); const saved = await api('/api/settings', { method: 'PATCH', body: { knowledge: { rootPath: $('knowledgeRoot').value } } }); toast(saved.configuration?.indexStorage?.status === 'ready' ? '设置已保存,索引目录已生效;已有知识未移动。' : '设置已保存,索引目录接线待恢复;已有知识保留。'); await refreshRegistry(true); if (resumeImport) closeSystem(); });
390
+ on('saveBrainSettings', 'click', async () => { await api('/api/brain', { method: 'PATCH', body: { name: $('brainName').value } }); const saved = await api('/api/settings', { method: 'PATCH', body: { knowledge: { rootPath: $('knowledgeRoot').value } } }); toast(saved.configuration?.scopeStatus?.status === 'pending' ? '设置已保存,共享知识接线待恢复;已有知识保留。' : saved.configuration?.indexStorage?.status === 'ready' ? '设置已保存,索引目录已生效;已有知识未移动。' : '设置已保存,索引目录接线待恢复;已有知识保留。'); await refreshRegistry(true); if (resumeImport) closeSystem(); });
389
391
  on('setupImportRoot', 'click', async () => { $('importDialog').close(); resumeImport = true; await openSystem(); $('knowledgeRoot').focus(); });
390
392
  on('pickRoot', 'click', () => pick('knowledgeRoot')); on('pickCode', 'click', () => pick('importCode')); on('pickKnowledge', 'click', () => pick('importKnowledge'));
391
393
  on('openEngine', 'click', () => { closeSystem(); initializeFrame('engine'); $('engineDialog').showModal(); post('engine', { type: 'kb:visibility', visible: true }); });
@@ -449,7 +451,7 @@
449
451
  renderSystem(); render();
450
452
  });
451
453
  window.addEventListener('online', () => {
452
- $('activityConnection').textContent = '正在恢复快照'; refreshRegistry();
454
+ $('activityConnection').textContent = state.engineAvailable && state.stream?.readyState === EventSource.OPEN ? '已连接实时更新' : '正在恢复快照'; refreshRegistry();
453
455
  if ($('logsDialog').open) loadLogs().catch(error => toast(error.message));
454
456
  });
455
457
  window.addEventListener('beforeunload', () => { state.stream?.close(); logStream?.close(); });