@sanqianx/claude-ai-workbench 0.13.2 → 0.13.3
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/LICENSE +201 -201
- package/README.md +156 -156
- package/apps/agent-terminal/app.css +636 -613
- package/apps/agent-terminal/app.js +125 -65
- package/apps/agent-terminal/conversation-composer.js +44 -0
- package/apps/agent-terminal/index.html +171 -170
- package/apps/agent-terminal/panel-host-proof.html +152 -152
- package/apps/agent-terminal/panel-proof-content.html +41 -41
- package/apps/agent-terminal/right-workspace-dock.css +187 -159
- package/apps/agent-terminal/right-workspace-dock.js +623 -519
- package/apps/ai-profile-settings-host/index.html +25 -25
- package/apps/ai-profile-settings-host/page.css +1 -1
- package/apps/ai-profile-settings-host/page.js +113 -113
- package/apps/minimal-test-host/README.md +15 -15
- package/apps/minimal-test-host/host.css +2 -2
- package/apps/minimal-test-host/host.js +35 -35
- package/apps/minimal-test-host/index.html +58 -58
- package/apps/minimal-test-host/optional-mock/fault-injection.js +15 -15
- package/docs/sdk/FILE_LAYOUT.md +31 -31
- package/docs/sdk/INTEGRATION.md +126 -126
- package/docs/sdk/README.md +21 -21
- package/docs/sdk/SESSION_EVENTS.md +13 -13
- package/docs/sdk/openapi.yaml +8 -1
- package/package.json +1 -1
- package/packages/client/README.md +29 -29
- package/packages/client/index.d.ts +122 -122
- package/packages/client/index.js +260 -260
- package/packages/client/package.json +2 -2
- package/packages/contracts/README.md +9 -9
- package/packages/contracts/index.d.ts +519 -517
- package/packages/contracts/index.js +312 -312
- package/packages/contracts/package.json +1 -1
- package/packages/core/README.md +8 -8
- package/packages/core/index.d.ts +76 -76
- package/packages/core/index.js +222 -222
- package/packages/core/package.json +2 -2
- package/packages/server/README.md +17 -17
- package/packages/server/bin/agent-terminal-server.js +42 -42
- package/packages/server/bin/claude-workbench.js +225 -225
- package/packages/server/index.d.ts +171 -171
- package/packages/server/index.js +230 -230
- package/packages/server/lib/agent-discovery.js +133 -133
- package/packages/server/lib/agent-registry.js +143 -143
- package/packages/server/lib/agent-spawn-env.js +17 -17
- package/packages/server/lib/attachment-store.js +78 -78
- package/packages/server/lib/bridges/app-server-bridge.js +373 -373
- package/packages/server/lib/bridges/opencode-server-bridge.js +506 -506
- package/packages/server/lib/capture-settings-store.js +35 -35
- package/packages/server/lib/claude-history-recovery.js +87 -87
- package/packages/server/lib/cli-session-recovery.js +28 -28
- package/packages/server/lib/context-resolver.js +55 -55
- package/packages/server/lib/conversation-capture.js +51 -51
- package/packages/server/lib/delegation-phase-store.js +357 -357
- package/packages/server/lib/delegation-store.js +474 -468
- package/packages/server/lib/diagnostics.js +32 -32
- package/packages/server/lib/drivers/cli-json-runner.js +974 -974
- package/packages/server/lib/drivers/codex-driver.js +1442 -1442
- package/packages/server/lib/drivers/opencode-driver.js +1554 -1554
- package/packages/server/lib/drivers/zcode-driver.js +1250 -1250
- package/packages/server/lib/drivers/zcode-profile-runtime.js +71 -71
- package/packages/server/lib/event-stream.js +138 -138
- package/packages/server/lib/folder-picker.js +623 -623
- package/packages/server/lib/http-handler.js +339 -339
- package/packages/server/lib/json-file.js +26 -26
- package/packages/server/lib/knowledge-context.js +33 -33
- package/packages/server/lib/model-detection.js +295 -295
- package/packages/server/lib/parent-context.js +44 -0
- package/packages/server/lib/profile-store.js +283 -283
- package/packages/server/lib/project-store.js +102 -102
- package/packages/server/lib/runtime-loader.js +120 -120
- package/packages/server/lib/session-archive-store.js +53 -53
- package/packages/server/lib/session-history.js +612 -612
- package/packages/server/lib/system-doctor.js +125 -125
- package/packages/server/lib/ui-preferences-store.js +48 -48
- package/packages/server/lib/workbench-service.js +1696 -1675
- package/packages/server/package.json +2 -2
- package/packages/ui/README.md +30 -30
- package/packages/ui/ai-profile-settings.d.ts +45 -45
- package/packages/ui/ai-profile-settings.js +283 -283
- package/packages/ui/claude-terminal.d.ts +31 -31
- package/packages/ui/claude-terminal.js +343 -343
- package/packages/ui/index.d.ts +18 -18
- package/packages/ui/index.js +51 -51
- package/packages/ui/package.json +2 -2
- package/packages/ui/theme.css +13 -13
- package/src/assets/claude-prompts.json +21 -21
- package/src/backend/http/ai-profile-routes.js +61 -61
- package/src/backend/http/claude-routes.js +170 -170
- package/src/backend/http/node-helpers.js +45 -45
- package/src/backend/index.js +21 -21
- package/src/backend/lib/ai-adapter.js +144 -144
- package/src/backend/lib/ai-profile-store.js +259 -259
- package/src/backend/lib/ai-vendor-presets.js +83 -83
- package/src/backend/lib/ai-workspace.js +140 -140
- package/src/backend/lib/automation-config.js +253 -253
- package/src/backend/lib/claude-cli-runner.js +2392 -2392
- package/src/backend/lib/data-dir.js +169 -169
- package/src/backend/lib/llm-client.js +176 -176
- package/src/backend/lib/model-context-windows.js +82 -82
- package/src/backend/lib/native-task-control.js +217 -217
- package/src/backend/lib/native-task-history-index.js +475 -475
- package/src/backend/lib/native-task-history-reader.js +526 -526
- package/src/backend/lib/native-task-messages.js +457 -457
- package/src/backend/lib/native-task-registry.js +325 -325
- package/src/backend/lib/native-task-runtime.js +242 -242
- package/src/backend/lib/native-task-trace.js +440 -440
- package/src/backend/lib/native-task-transcript.js +393 -393
- package/src/backend/lib/prompt-registry.js +166 -166
- package/src/backend/lib/runtime-config.js +121 -121
- package/src/backend/lib/sdk-session-stream.js +143 -143
- package/src/backend/lib/sdk-turn-stream.js +16 -16
- package/src/backend/lib/store-scope.js +131 -131
- package/src/backend/lib/task-identity.js +104 -104
- package/src/backend/lib/task-state-projection.js +58 -58
- package/src/index.js +7 -7
- package/src/sdk-wave-a.ts +1 -1
- package/src/sdk-wave-b.ts +1 -1
- package/src/sdk-wave-c.ts +1 -1
- package/src/sdk-wave-d.ts +1 -1
- package/src/sdk-wave-e.ts +1 -1
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { spawn, spawnSync } = require('child_process');
|
|
4
|
-
const { AGENTS, AGENT_IDS } = require('../../contracts');
|
|
5
|
-
const { discoverZCodeCli } = require('./drivers/zcode-driver');
|
|
6
|
-
|
|
7
|
-
const PROBE_TIMEOUT_MS = 5000;
|
|
8
|
-
const CACHE_TTL_MS = 30000;
|
|
9
|
-
|
|
10
|
-
// Probes locally installed agent CLIs. Results are cached briefly so
|
|
11
|
-
// GET /agents stays cheap; a failed probe is a valid (negative) result.
|
|
12
|
-
function parseVersionToken(text) {
|
|
13
|
-
const trimmed = String(text || '').trim();
|
|
14
|
-
const match = trimmed.match(/(\d+\.\d+[\w.-]*)/);
|
|
15
|
-
return match ? match[1] : null;
|
|
16
|
-
}
|
|
17
|
-
// Spawns `<executable> --version` directly (no shell) and returns the first
|
|
18
|
-
// version-looking token, or null when the probe fails. This is the only
|
|
19
|
-
// version source: nothing here invents or hardcodes one.
|
|
20
|
-
function probeExecutable(cmd) {
|
|
21
|
-
try {
|
|
22
|
-
const script = /\.(?:[cm]?js)$/i.test(cmd);
|
|
23
|
-
const result = spawnSync(script ? process.execPath : cmd, script ? [cmd, '--version'] : ['--version'], {
|
|
24
|
-
timeout: PROBE_TIMEOUT_MS,
|
|
25
|
-
windowsHide: true,
|
|
26
|
-
encoding: 'utf8',
|
|
27
|
-
});
|
|
28
|
-
if (result.error || result.status !== 0) return { available: false, version: null, code: result.error?.code || 'CLI_PROBE_FAILED', reason: String(result.error?.message || result.stderr || result.stdout || `exit ${result.status}`).slice(0, 2000), exitCode: result.status };
|
|
29
|
-
const version = parseVersionToken(String(result.stdout || result.stderr || ''));
|
|
30
|
-
return { available: Boolean(version), version, reason: version ? '' : 'Claude Code 未返回版本号。' };
|
|
31
|
-
} catch {
|
|
32
|
-
return { available: false, version: null, code: 'CLI_PROBE_FAILED', reason: 'Claude Code 版本探测失败。' };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function probeExecutableVersion(cmd) { return probeExecutable(cmd).version; }
|
|
36
|
-
function runProbe(command, args, { signal, timeoutMs = PROBE_TIMEOUT_MS, shell = false } = {}) {
|
|
37
|
-
return new Promise(resolve => {
|
|
38
|
-
if (signal?.aborted) return resolve({ available: false, code: 'PROBE_TIMEOUT', reason: '探测超时,保留上次结果。' });
|
|
39
|
-
let child, output = '', errors = '', done = false;
|
|
40
|
-
const finish = result => { if (done) return; done = true; clearTimeout(timer); signal?.removeEventListener('abort', cancel); resolve(result); };
|
|
41
|
-
const cancel = () => {
|
|
42
|
-
if (child?.pid && child.exitCode === null && process.platform === 'win32' && shell) {
|
|
43
|
-
const killer = spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }); killer.once('error', () => child.kill());
|
|
44
|
-
} else child?.kill();
|
|
45
|
-
finish({ available: false, code: 'PROBE_TIMEOUT', reason: '探测超时,保留上次结果。' });
|
|
46
|
-
};
|
|
47
|
-
const timer = setTimeout(cancel, timeoutMs);
|
|
48
|
-
signal?.addEventListener('abort', cancel, { once: true });
|
|
49
|
-
try {
|
|
50
|
-
child = spawn(command, args, { windowsHide: true, shell, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
51
|
-
child.stdout.on('data', b => { output = (output + b).slice(-8192); });
|
|
52
|
-
child.stderr.on('data', b => { errors = (errors + b).slice(-8192); });
|
|
53
|
-
child.once('error', error => finish({ available: false, version: null, code: error.code || 'CLI_PROBE_FAILED', reason: String(error.message).slice(0, 2000) }));
|
|
54
|
-
child.once('close', code => {
|
|
55
|
-
const version = code === 0 ? parseVersionToken(output || errors) : null;
|
|
56
|
-
finish({ available: !!version, version, exitCode: code, code: code === 0 ? undefined : 'CLI_PROBE_FAILED', reason: version ? '' : (errors || output || `exit ${code}`).slice(0, 2000), stdout: output });
|
|
57
|
-
});
|
|
58
|
-
} catch (error) { finish({ available: false, code: error.code || 'CLI_PROBE_FAILED', reason: String(error.message).slice(0, 2000) }); }
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
async function probeExecutableAsync(command, options = {}) {
|
|
62
|
-
const script = /\.(?:[cm]?js)$/i.test(command);
|
|
63
|
-
return runProbe(script ? process.execPath : command, script ? [command, '--version'] : ['--version'], { ...options, shell: !script && process.platform === 'win32' && !require('path').isAbsolute(command) });
|
|
64
|
-
}
|
|
65
|
-
async function resolveClaudeAsync(driver, options = {}) {
|
|
66
|
-
if (typeof driver.findClaudeExecutableForSdkAsync === 'function') return driver.findClaudeExecutableForSdkAsync(options);
|
|
67
|
-
let found = driver.findClaudeExecutableForSdk({ pathHits: [] });
|
|
68
|
-
if (found?.cmd || typeof found === 'string' || process.platform !== 'win32') return found;
|
|
69
|
-
const where = require('path').join(process.env.WINDIR || 'C:/Windows', 'System32', 'where.exe');
|
|
70
|
-
const result = await runProbe(where, ['claude'], options);
|
|
71
|
-
return driver.findClaudeExecutableForSdk({ pathHits: (result.stdout || '').split(/\r?\n/).map(s => s.trim()).filter(Boolean) });
|
|
72
|
-
}
|
|
73
|
-
async function resolveZCodeAsync({ signal, discover = discoverZCodeCli, probe = runProbe, platform = process.platform } = {}) {
|
|
74
|
-
const direct = discover({ regQuery: () => null });
|
|
75
|
-
if (direct || platform !== 'win32') return direct;
|
|
76
|
-
const entries = new Map();
|
|
77
|
-
for (const hive of ['HKCU', 'HKLM']) {
|
|
78
|
-
const key = `${hive}\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\ZCode.exe`;
|
|
79
|
-
entries.set(key, (await probe('reg', ['query', key], { signal })).stdout || '');
|
|
80
|
-
}
|
|
81
|
-
return discover({ regQuery: key => entries.get(key) });
|
|
82
|
-
}
|
|
83
|
-
function createAgentDiscovery(options = {}) {
|
|
84
|
-
const clock = options.now || Date.now;
|
|
85
|
-
const claudeProbe = options.claudeProbe || (() => ({ available: false, reason: 'Claude Code executable was not found' }));
|
|
86
|
-
let cache = Object.fromEntries(AGENT_IDS.map(id => [id, { ...AGENTS[id], available: false, version: null, scanState: 'unknown', reason: '尚未检查' }]));
|
|
87
|
-
let cacheAt = 0, pending = null, generation = 0, closed = false;
|
|
88
|
-
let scan = { state: 'idle', generation, updatedAt: null };
|
|
89
|
-
const active = new Set();
|
|
90
|
-
|
|
91
|
-
async function inspect(id, signal) {
|
|
92
|
-
if (options.probes?.[id]) return options.probes[id]({ signal });
|
|
93
|
-
if (id === 'claude-code') return claudeProbe({ signal });
|
|
94
|
-
if (id !== 'zcode') return probeExecutableAsync(id, { signal });
|
|
95
|
-
const cli = await resolveZCodeAsync({ signal });
|
|
96
|
-
if (!cli) return { available: false, reason: 'ZCode was not found on this machine' };
|
|
97
|
-
return probeExecutableAsync(cli.path, { signal });
|
|
98
|
-
}
|
|
99
|
-
function discover() {
|
|
100
|
-
if (!closed && clock() - cacheAt >= (options.cacheTtlMs ?? CACHE_TTL_MS)) void discover.refresh().catch(() => {});
|
|
101
|
-
return cache;
|
|
102
|
-
}
|
|
103
|
-
discover.snapshot = () => ({ ...scan });
|
|
104
|
-
discover.refresh = () => {
|
|
105
|
-
if (pending) return pending;
|
|
106
|
-
if (closed) return Promise.resolve(cache);
|
|
107
|
-
scan = { ...scan, state: 'scanning', generation: ++generation };
|
|
108
|
-
let cursor = 0;
|
|
109
|
-
const deadline = Date.now() + (options.scanBudgetMs ?? 12000);
|
|
110
|
-
const worker = async () => {
|
|
111
|
-
while (cursor < AGENT_IDS.length && !closed) {
|
|
112
|
-
const id = AGENT_IDS[cursor++], controller = new AbortController(); active.add(controller);
|
|
113
|
-
const budget = Math.max(0, Math.min(options.probeTimeoutMs ?? 5000, deadline - Date.now()));
|
|
114
|
-
let timer;
|
|
115
|
-
const timeout = new Promise(resolve => { const expire = () => resolve({ code: 'PROBE_TIMEOUT', reason: '检查超时,保留上次结果。' }); controller.signal.addEventListener('abort', expire, { once: true }); timer = setTimeout(() => controller.abort(), budget); });
|
|
116
|
-
let result;
|
|
117
|
-
try { result = await Promise.race([Promise.resolve().then(() => inspect(id, controller.signal)), timeout]); }
|
|
118
|
-
catch { result = { code: 'PROBE_FAILED', reason: '检查失败,保留上次结果。' }; }
|
|
119
|
-
finally { clearTimeout(timer); active.delete(controller); }
|
|
120
|
-
if (closed) return;
|
|
121
|
-
const uncertain = ['PROBE_TIMEOUT', 'ETIMEDOUT', 'PROBE_FAILED'].includes(result.code);
|
|
122
|
-
cache = { ...cache, [id]: { ...cache[id], ...(uncertain ? {} : { available: !!result.available, version: result.version || null }), reason: result.reason || '', scanState: uncertain ? 'error' : 'ready', checkedAt: new Date().toISOString(), errorCode: result.code || null } };
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
pending = Promise.all([worker(), worker()]).then(() => { cacheAt = clock(); scan = { state: 'ready', generation, updatedAt: new Date(cacheAt).toISOString() }; return cache; }).finally(() => { pending = null; });
|
|
126
|
-
return pending;
|
|
127
|
-
};
|
|
128
|
-
discover.invalidate = () => { cacheAt = 0; };
|
|
129
|
-
discover.close = () => { closed = true; for (const controller of active) controller.abort(); };
|
|
130
|
-
return discover;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = { createAgentDiscovery, probeExecutableVersion, probeExecutable, probeExecutableAsync, resolveClaudeAsync, resolveZCodeAsync, runProbe };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('child_process');
|
|
4
|
+
const { AGENTS, AGENT_IDS } = require('../../contracts');
|
|
5
|
+
const { discoverZCodeCli } = require('./drivers/zcode-driver');
|
|
6
|
+
|
|
7
|
+
const PROBE_TIMEOUT_MS = 5000;
|
|
8
|
+
const CACHE_TTL_MS = 30000;
|
|
9
|
+
|
|
10
|
+
// Probes locally installed agent CLIs. Results are cached briefly so
|
|
11
|
+
// GET /agents stays cheap; a failed probe is a valid (negative) result.
|
|
12
|
+
function parseVersionToken(text) {
|
|
13
|
+
const trimmed = String(text || '').trim();
|
|
14
|
+
const match = trimmed.match(/(\d+\.\d+[\w.-]*)/);
|
|
15
|
+
return match ? match[1] : null;
|
|
16
|
+
}
|
|
17
|
+
// Spawns `<executable> --version` directly (no shell) and returns the first
|
|
18
|
+
// version-looking token, or null when the probe fails. This is the only
|
|
19
|
+
// version source: nothing here invents or hardcodes one.
|
|
20
|
+
function probeExecutable(cmd) {
|
|
21
|
+
try {
|
|
22
|
+
const script = /\.(?:[cm]?js)$/i.test(cmd);
|
|
23
|
+
const result = spawnSync(script ? process.execPath : cmd, script ? [cmd, '--version'] : ['--version'], {
|
|
24
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
});
|
|
28
|
+
if (result.error || result.status !== 0) return { available: false, version: null, code: result.error?.code || 'CLI_PROBE_FAILED', reason: String(result.error?.message || result.stderr || result.stdout || `exit ${result.status}`).slice(0, 2000), exitCode: result.status };
|
|
29
|
+
const version = parseVersionToken(String(result.stdout || result.stderr || ''));
|
|
30
|
+
return { available: Boolean(version), version, reason: version ? '' : 'Claude Code 未返回版本号。' };
|
|
31
|
+
} catch {
|
|
32
|
+
return { available: false, version: null, code: 'CLI_PROBE_FAILED', reason: 'Claude Code 版本探测失败。' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function probeExecutableVersion(cmd) { return probeExecutable(cmd).version; }
|
|
36
|
+
function runProbe(command, args, { signal, timeoutMs = PROBE_TIMEOUT_MS, shell = false } = {}) {
|
|
37
|
+
return new Promise(resolve => {
|
|
38
|
+
if (signal?.aborted) return resolve({ available: false, code: 'PROBE_TIMEOUT', reason: '探测超时,保留上次结果。' });
|
|
39
|
+
let child, output = '', errors = '', done = false;
|
|
40
|
+
const finish = result => { if (done) return; done = true; clearTimeout(timer); signal?.removeEventListener('abort', cancel); resolve(result); };
|
|
41
|
+
const cancel = () => {
|
|
42
|
+
if (child?.pid && child.exitCode === null && process.platform === 'win32' && shell) {
|
|
43
|
+
const killer = spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }); killer.once('error', () => child.kill());
|
|
44
|
+
} else child?.kill();
|
|
45
|
+
finish({ available: false, code: 'PROBE_TIMEOUT', reason: '探测超时,保留上次结果。' });
|
|
46
|
+
};
|
|
47
|
+
const timer = setTimeout(cancel, timeoutMs);
|
|
48
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
49
|
+
try {
|
|
50
|
+
child = spawn(command, args, { windowsHide: true, shell, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
51
|
+
child.stdout.on('data', b => { output = (output + b).slice(-8192); });
|
|
52
|
+
child.stderr.on('data', b => { errors = (errors + b).slice(-8192); });
|
|
53
|
+
child.once('error', error => finish({ available: false, version: null, code: error.code || 'CLI_PROBE_FAILED', reason: String(error.message).slice(0, 2000) }));
|
|
54
|
+
child.once('close', code => {
|
|
55
|
+
const version = code === 0 ? parseVersionToken(output || errors) : null;
|
|
56
|
+
finish({ available: !!version, version, exitCode: code, code: code === 0 ? undefined : 'CLI_PROBE_FAILED', reason: version ? '' : (errors || output || `exit ${code}`).slice(0, 2000), stdout: output });
|
|
57
|
+
});
|
|
58
|
+
} catch (error) { finish({ available: false, code: error.code || 'CLI_PROBE_FAILED', reason: String(error.message).slice(0, 2000) }); }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function probeExecutableAsync(command, options = {}) {
|
|
62
|
+
const script = /\.(?:[cm]?js)$/i.test(command);
|
|
63
|
+
return runProbe(script ? process.execPath : command, script ? [command, '--version'] : ['--version'], { ...options, shell: !script && process.platform === 'win32' && !require('path').isAbsolute(command) });
|
|
64
|
+
}
|
|
65
|
+
async function resolveClaudeAsync(driver, options = {}) {
|
|
66
|
+
if (typeof driver.findClaudeExecutableForSdkAsync === 'function') return driver.findClaudeExecutableForSdkAsync(options);
|
|
67
|
+
let found = driver.findClaudeExecutableForSdk({ pathHits: [] });
|
|
68
|
+
if (found?.cmd || typeof found === 'string' || process.platform !== 'win32') return found;
|
|
69
|
+
const where = require('path').join(process.env.WINDIR || 'C:/Windows', 'System32', 'where.exe');
|
|
70
|
+
const result = await runProbe(where, ['claude'], options);
|
|
71
|
+
return driver.findClaudeExecutableForSdk({ pathHits: (result.stdout || '').split(/\r?\n/).map(s => s.trim()).filter(Boolean) });
|
|
72
|
+
}
|
|
73
|
+
async function resolveZCodeAsync({ signal, discover = discoverZCodeCli, probe = runProbe, platform = process.platform } = {}) {
|
|
74
|
+
const direct = discover({ regQuery: () => null });
|
|
75
|
+
if (direct || platform !== 'win32') return direct;
|
|
76
|
+
const entries = new Map();
|
|
77
|
+
for (const hive of ['HKCU', 'HKLM']) {
|
|
78
|
+
const key = `${hive}\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\ZCode.exe`;
|
|
79
|
+
entries.set(key, (await probe('reg', ['query', key], { signal })).stdout || '');
|
|
80
|
+
}
|
|
81
|
+
return discover({ regQuery: key => entries.get(key) });
|
|
82
|
+
}
|
|
83
|
+
function createAgentDiscovery(options = {}) {
|
|
84
|
+
const clock = options.now || Date.now;
|
|
85
|
+
const claudeProbe = options.claudeProbe || (() => ({ available: false, reason: 'Claude Code executable was not found' }));
|
|
86
|
+
let cache = Object.fromEntries(AGENT_IDS.map(id => [id, { ...AGENTS[id], available: false, version: null, scanState: 'unknown', reason: '尚未检查' }]));
|
|
87
|
+
let cacheAt = 0, pending = null, generation = 0, closed = false;
|
|
88
|
+
let scan = { state: 'idle', generation, updatedAt: null };
|
|
89
|
+
const active = new Set();
|
|
90
|
+
|
|
91
|
+
async function inspect(id, signal) {
|
|
92
|
+
if (options.probes?.[id]) return options.probes[id]({ signal });
|
|
93
|
+
if (id === 'claude-code') return claudeProbe({ signal });
|
|
94
|
+
if (id !== 'zcode') return probeExecutableAsync(id, { signal });
|
|
95
|
+
const cli = await resolveZCodeAsync({ signal });
|
|
96
|
+
if (!cli) return { available: false, reason: 'ZCode was not found on this machine' };
|
|
97
|
+
return probeExecutableAsync(cli.path, { signal });
|
|
98
|
+
}
|
|
99
|
+
function discover() {
|
|
100
|
+
if (!closed && clock() - cacheAt >= (options.cacheTtlMs ?? CACHE_TTL_MS)) void discover.refresh().catch(() => {});
|
|
101
|
+
return cache;
|
|
102
|
+
}
|
|
103
|
+
discover.snapshot = () => ({ ...scan });
|
|
104
|
+
discover.refresh = () => {
|
|
105
|
+
if (pending) return pending;
|
|
106
|
+
if (closed) return Promise.resolve(cache);
|
|
107
|
+
scan = { ...scan, state: 'scanning', generation: ++generation };
|
|
108
|
+
let cursor = 0;
|
|
109
|
+
const deadline = Date.now() + (options.scanBudgetMs ?? 12000);
|
|
110
|
+
const worker = async () => {
|
|
111
|
+
while (cursor < AGENT_IDS.length && !closed) {
|
|
112
|
+
const id = AGENT_IDS[cursor++], controller = new AbortController(); active.add(controller);
|
|
113
|
+
const budget = Math.max(0, Math.min(options.probeTimeoutMs ?? 5000, deadline - Date.now()));
|
|
114
|
+
let timer;
|
|
115
|
+
const timeout = new Promise(resolve => { const expire = () => resolve({ code: 'PROBE_TIMEOUT', reason: '检查超时,保留上次结果。' }); controller.signal.addEventListener('abort', expire, { once: true }); timer = setTimeout(() => controller.abort(), budget); });
|
|
116
|
+
let result;
|
|
117
|
+
try { result = await Promise.race([Promise.resolve().then(() => inspect(id, controller.signal)), timeout]); }
|
|
118
|
+
catch { result = { code: 'PROBE_FAILED', reason: '检查失败,保留上次结果。' }; }
|
|
119
|
+
finally { clearTimeout(timer); active.delete(controller); }
|
|
120
|
+
if (closed) return;
|
|
121
|
+
const uncertain = ['PROBE_TIMEOUT', 'ETIMEDOUT', 'PROBE_FAILED'].includes(result.code);
|
|
122
|
+
cache = { ...cache, [id]: { ...cache[id], ...(uncertain ? {} : { available: !!result.available, version: result.version || null }), reason: result.reason || '', scanState: uncertain ? 'error' : 'ready', checkedAt: new Date().toISOString(), errorCode: result.code || null } };
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
pending = Promise.all([worker(), worker()]).then(() => { cacheAt = clock(); scan = { state: 'ready', generation, updatedAt: new Date(cacheAt).toISOString() }; return cache; }).finally(() => { pending = null; });
|
|
126
|
+
return pending;
|
|
127
|
+
};
|
|
128
|
+
discover.invalidate = () => { cacheAt = 0; };
|
|
129
|
+
discover.close = () => { closed = true; for (const controller of active) controller.abort(); };
|
|
130
|
+
return discover;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { createAgentDiscovery, probeExecutableVersion, probeExecutable, probeExecutableAsync, resolveClaudeAsync, resolveZCodeAsync, runProbe };
|
|
@@ -1,143 +1,143 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { AGENT_IDS, normalizeAgentId } = require('../../contracts');
|
|
4
|
-
|
|
5
|
-
// Routes every WorkbenchRunner call to the driver that owns the session.
|
|
6
|
-
// The claude-code driver is whatever runner the host injected (legacy
|
|
7
|
-
// claude-cli-runner in production, fake runners in tests); CLI drivers plug
|
|
8
|
-
// in beside it with the same interface.
|
|
9
|
-
class AgentRegistry {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.drivers = new Map();
|
|
12
|
-
this.sessionAgents = new Map();
|
|
13
|
-
for (const [agentId, driver] of Object.entries(options.drivers || {})) {
|
|
14
|
-
this.register(agentId, driver);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
register(agentId, driver) {
|
|
19
|
-
const id = normalizeAgentId(agentId);
|
|
20
|
-
if (!driver) throw new Error(`driver for ${id} is required`);
|
|
21
|
-
this.drivers.set(id, driver);
|
|
22
|
-
return driver;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
driverFor(agentId) {
|
|
26
|
-
const id = normalizeAgentId(agentId);
|
|
27
|
-
const driver = this.drivers.get(id);
|
|
28
|
-
if (!driver) {
|
|
29
|
-
throw Object.assign(new Error(`agent "${id}" has no registered driver`), { status: 501 });
|
|
30
|
-
}
|
|
31
|
-
return driver;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
agentOf(sessionId) {
|
|
35
|
-
return this.sessionAgents.get(sessionId) || 'claude-code';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
_route(sessionId) {
|
|
39
|
-
try {
|
|
40
|
-
return this.driverFor(this.agentOf(sessionId));
|
|
41
|
-
} catch {
|
|
42
|
-
throw Object.assign(new Error('session not found'), { status: 404 });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
asRunner() {
|
|
47
|
-
const registry = this;
|
|
48
|
-
return {
|
|
49
|
-
async destroy() {
|
|
50
|
-
await Promise.all([...registry.drivers.values()].map(driver => typeof driver.destroy === 'function' ? driver.destroy() : undefined));
|
|
51
|
-
},
|
|
52
|
-
startChatSession(args = {}) {
|
|
53
|
-
// Reserved delegated identity reaches sessions only through the
|
|
54
|
-
// trusted startDelegatedSession route below — never through ordinary
|
|
55
|
-
// user-driven session creation, whatever the caller forwarded.
|
|
56
|
-
if (args.reservedSessionId !== undefined || args.delegation !== undefined) {
|
|
57
|
-
throw Object.assign(new Error('reserved delegated identity is not accepted on the ordinary session path'), {
|
|
58
|
-
status: 400, code: 'DELEGATION_IDENTITY_FORBIDDEN',
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
const agentId = normalizeAgentId(args.agentId);
|
|
62
|
-
const driver = registry.driverFor(agentId);
|
|
63
|
-
const started = driver.startChatSession({ ...args, agentId });
|
|
64
|
-
registry.sessionAgents.set(started.sessionId, agentId);
|
|
65
|
-
return started;
|
|
66
|
-
},
|
|
67
|
-
// Trusted reserved-identity factory route. A driver that cannot honor a
|
|
68
|
-
// server-reserved childSessionId fails explicitly here — it must never
|
|
69
|
-
// fall back to minting its own random child.
|
|
70
|
-
startDelegatedSession(args = {}) {
|
|
71
|
-
const agentId = normalizeAgentId(args.agentId);
|
|
72
|
-
const driver = registry.driverFor(agentId);
|
|
73
|
-
if (typeof driver.startDelegatedSession !== 'function') {
|
|
74
|
-
throw Object.assign(new Error(`agent "${agentId}" cannot run delegated sessions with a server-reserved identity`), {
|
|
75
|
-
status: 501, code: 'DELEGATION_AGENT_UNSUPPORTED',
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const started = driver.startDelegatedSession({ ...args, agentId });
|
|
79
|
-
registry.sessionAgents.set(started.sessionId, agentId);
|
|
80
|
-
return started;
|
|
81
|
-
},
|
|
82
|
-
// Capability probe that creates nothing: validates the command BEFORE
|
|
83
|
-
// the ledger reserves an identity for an agent that could never run it.
|
|
84
|
-
supportsDelegatedSessions(agentId) {
|
|
85
|
-
const id = normalizeAgentId(agentId);
|
|
86
|
-
const driver = registry.drivers.get(id);
|
|
87
|
-
return Boolean(driver && typeof driver.startDelegatedSession === 'function');
|
|
88
|
-
},
|
|
89
|
-
listSessions(filter = {}) {
|
|
90
|
-
const all = [];
|
|
91
|
-
for (const id of AGENT_IDS) {
|
|
92
|
-
const driver = registry.drivers.get(id);
|
|
93
|
-
if (!driver) continue;
|
|
94
|
-
for (const session of driver.listSessions(filter)) {
|
|
95
|
-
registry.sessionAgents.set(session.sessionId, id);
|
|
96
|
-
all.push({ ...session, agentId: registry.agentOf(session.sessionId) });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return all;
|
|
100
|
-
},
|
|
101
|
-
getState(id) { return registry._route(id).getState(id); },
|
|
102
|
-
getSession(id) { return registry._route(id).getSession(id); },
|
|
103
|
-
getSessionTokenUsage(id) { return registry._route(id).getSessionTokenUsage(id); },
|
|
104
|
-
subscribe(id, listener) { return registry._route(id).subscribe(id, listener); },
|
|
105
|
-
sendInput(id, text, profile, options) { return registry._route(id).sendInput(id, text, profile, options); },
|
|
106
|
-
updateSelection(id, profile, selectedModel) {
|
|
107
|
-
const driver = registry._route(id);
|
|
108
|
-
if (typeof driver.updateSelection === 'function') return driver.updateSelection(id, profile, selectedModel);
|
|
109
|
-
// Bare runners without the method still get rebound so the service
|
|
110
|
-
// fallback and the proxy stay interchangeable.
|
|
111
|
-
const session = driver.getSession && driver.getSession(id);
|
|
112
|
-
if (!session) throw Object.assign(new Error('session not found'), { status: 404 });
|
|
113
|
-
session.aiProfileId = profile && profile.id || null;
|
|
114
|
-
session.selectedModel = selectedModel || null;
|
|
115
|
-
return session;
|
|
116
|
-
},
|
|
117
|
-
resolvePermission(id, requestId, decision) { return registry._route(id).resolvePermission(id, requestId, decision); },
|
|
118
|
-
abort(id, reason) { return registry._route(id).abort(id, reason); },
|
|
119
|
-
deleteSession(id) { return registry._route(id).deleteSession(id); },
|
|
120
|
-
attachNativeTaskRuntime(id, identity) {
|
|
121
|
-
// Native-task scope binding is claude-code-only today; CLI drivers
|
|
122
|
-
// answer with an explicit unavailable result instead of throwing.
|
|
123
|
-
const driver = registry._route(id);
|
|
124
|
-
if (typeof driver.attachNativeTaskRuntime !== 'function') return { attached: false, reason: 'runner-without-native-tasks' };
|
|
125
|
-
return driver.attachNativeTaskRuntime(id, identity);
|
|
126
|
-
},
|
|
127
|
-
detachNativeTaskRuntime(id, identity) {
|
|
128
|
-
const driver = registry._route(id);
|
|
129
|
-
if (typeof driver.detachNativeTaskRuntime !== 'function') return { detached: false, reason: 'runner-without-native-tasks' };
|
|
130
|
-
return driver.detachNativeTaskRuntime(id, identity);
|
|
131
|
-
},
|
|
132
|
-
listSupportedCommands(id, profile) { return registry._route(id).listSupportedCommands(id, profile); },
|
|
133
|
-
findClaudeExecutableForSdk() {
|
|
134
|
-
const driver = registry.drivers.get('claude-code');
|
|
135
|
-
return driver && typeof driver.findClaudeExecutableForSdk === 'function'
|
|
136
|
-
? driver.findClaudeExecutableForSdk()
|
|
137
|
-
: null;
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
module.exports = { AgentRegistry };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { AGENT_IDS, normalizeAgentId } = require('../../contracts');
|
|
4
|
+
|
|
5
|
+
// Routes every WorkbenchRunner call to the driver that owns the session.
|
|
6
|
+
// The claude-code driver is whatever runner the host injected (legacy
|
|
7
|
+
// claude-cli-runner in production, fake runners in tests); CLI drivers plug
|
|
8
|
+
// in beside it with the same interface.
|
|
9
|
+
class AgentRegistry {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.drivers = new Map();
|
|
12
|
+
this.sessionAgents = new Map();
|
|
13
|
+
for (const [agentId, driver] of Object.entries(options.drivers || {})) {
|
|
14
|
+
this.register(agentId, driver);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
register(agentId, driver) {
|
|
19
|
+
const id = normalizeAgentId(agentId);
|
|
20
|
+
if (!driver) throw new Error(`driver for ${id} is required`);
|
|
21
|
+
this.drivers.set(id, driver);
|
|
22
|
+
return driver;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
driverFor(agentId) {
|
|
26
|
+
const id = normalizeAgentId(agentId);
|
|
27
|
+
const driver = this.drivers.get(id);
|
|
28
|
+
if (!driver) {
|
|
29
|
+
throw Object.assign(new Error(`agent "${id}" has no registered driver`), { status: 501 });
|
|
30
|
+
}
|
|
31
|
+
return driver;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
agentOf(sessionId) {
|
|
35
|
+
return this.sessionAgents.get(sessionId) || 'claude-code';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_route(sessionId) {
|
|
39
|
+
try {
|
|
40
|
+
return this.driverFor(this.agentOf(sessionId));
|
|
41
|
+
} catch {
|
|
42
|
+
throw Object.assign(new Error('session not found'), { status: 404 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
asRunner() {
|
|
47
|
+
const registry = this;
|
|
48
|
+
return {
|
|
49
|
+
async destroy() {
|
|
50
|
+
await Promise.all([...registry.drivers.values()].map(driver => typeof driver.destroy === 'function' ? driver.destroy() : undefined));
|
|
51
|
+
},
|
|
52
|
+
startChatSession(args = {}) {
|
|
53
|
+
// Reserved delegated identity reaches sessions only through the
|
|
54
|
+
// trusted startDelegatedSession route below — never through ordinary
|
|
55
|
+
// user-driven session creation, whatever the caller forwarded.
|
|
56
|
+
if (args.reservedSessionId !== undefined || args.delegation !== undefined) {
|
|
57
|
+
throw Object.assign(new Error('reserved delegated identity is not accepted on the ordinary session path'), {
|
|
58
|
+
status: 400, code: 'DELEGATION_IDENTITY_FORBIDDEN',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const agentId = normalizeAgentId(args.agentId);
|
|
62
|
+
const driver = registry.driverFor(agentId);
|
|
63
|
+
const started = driver.startChatSession({ ...args, agentId });
|
|
64
|
+
registry.sessionAgents.set(started.sessionId, agentId);
|
|
65
|
+
return started;
|
|
66
|
+
},
|
|
67
|
+
// Trusted reserved-identity factory route. A driver that cannot honor a
|
|
68
|
+
// server-reserved childSessionId fails explicitly here — it must never
|
|
69
|
+
// fall back to minting its own random child.
|
|
70
|
+
startDelegatedSession(args = {}) {
|
|
71
|
+
const agentId = normalizeAgentId(args.agentId);
|
|
72
|
+
const driver = registry.driverFor(agentId);
|
|
73
|
+
if (typeof driver.startDelegatedSession !== 'function') {
|
|
74
|
+
throw Object.assign(new Error(`agent "${agentId}" cannot run delegated sessions with a server-reserved identity`), {
|
|
75
|
+
status: 501, code: 'DELEGATION_AGENT_UNSUPPORTED',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const started = driver.startDelegatedSession({ ...args, agentId });
|
|
79
|
+
registry.sessionAgents.set(started.sessionId, agentId);
|
|
80
|
+
return started;
|
|
81
|
+
},
|
|
82
|
+
// Capability probe that creates nothing: validates the command BEFORE
|
|
83
|
+
// the ledger reserves an identity for an agent that could never run it.
|
|
84
|
+
supportsDelegatedSessions(agentId) {
|
|
85
|
+
const id = normalizeAgentId(agentId);
|
|
86
|
+
const driver = registry.drivers.get(id);
|
|
87
|
+
return Boolean(driver && typeof driver.startDelegatedSession === 'function');
|
|
88
|
+
},
|
|
89
|
+
listSessions(filter = {}) {
|
|
90
|
+
const all = [];
|
|
91
|
+
for (const id of AGENT_IDS) {
|
|
92
|
+
const driver = registry.drivers.get(id);
|
|
93
|
+
if (!driver) continue;
|
|
94
|
+
for (const session of driver.listSessions(filter)) {
|
|
95
|
+
registry.sessionAgents.set(session.sessionId, id);
|
|
96
|
+
all.push({ ...session, agentId: registry.agentOf(session.sessionId) });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return all;
|
|
100
|
+
},
|
|
101
|
+
getState(id) { return registry._route(id).getState(id); },
|
|
102
|
+
getSession(id) { return registry._route(id).getSession(id); },
|
|
103
|
+
getSessionTokenUsage(id) { return registry._route(id).getSessionTokenUsage(id); },
|
|
104
|
+
subscribe(id, listener) { return registry._route(id).subscribe(id, listener); },
|
|
105
|
+
sendInput(id, text, profile, options) { return registry._route(id).sendInput(id, text, profile, options); },
|
|
106
|
+
updateSelection(id, profile, selectedModel) {
|
|
107
|
+
const driver = registry._route(id);
|
|
108
|
+
if (typeof driver.updateSelection === 'function') return driver.updateSelection(id, profile, selectedModel);
|
|
109
|
+
// Bare runners without the method still get rebound so the service
|
|
110
|
+
// fallback and the proxy stay interchangeable.
|
|
111
|
+
const session = driver.getSession && driver.getSession(id);
|
|
112
|
+
if (!session) throw Object.assign(new Error('session not found'), { status: 404 });
|
|
113
|
+
session.aiProfileId = profile && profile.id || null;
|
|
114
|
+
session.selectedModel = selectedModel || null;
|
|
115
|
+
return session;
|
|
116
|
+
},
|
|
117
|
+
resolvePermission(id, requestId, decision) { return registry._route(id).resolvePermission(id, requestId, decision); },
|
|
118
|
+
abort(id, reason) { return registry._route(id).abort(id, reason); },
|
|
119
|
+
deleteSession(id) { return registry._route(id).deleteSession(id); },
|
|
120
|
+
attachNativeTaskRuntime(id, identity) {
|
|
121
|
+
// Native-task scope binding is claude-code-only today; CLI drivers
|
|
122
|
+
// answer with an explicit unavailable result instead of throwing.
|
|
123
|
+
const driver = registry._route(id);
|
|
124
|
+
if (typeof driver.attachNativeTaskRuntime !== 'function') return { attached: false, reason: 'runner-without-native-tasks' };
|
|
125
|
+
return driver.attachNativeTaskRuntime(id, identity);
|
|
126
|
+
},
|
|
127
|
+
detachNativeTaskRuntime(id, identity) {
|
|
128
|
+
const driver = registry._route(id);
|
|
129
|
+
if (typeof driver.detachNativeTaskRuntime !== 'function') return { detached: false, reason: 'runner-without-native-tasks' };
|
|
130
|
+
return driver.detachNativeTaskRuntime(id, identity);
|
|
131
|
+
},
|
|
132
|
+
listSupportedCommands(id, profile) { return registry._route(id).listSupportedCommands(id, profile); },
|
|
133
|
+
findClaudeExecutableForSdk() {
|
|
134
|
+
const driver = registry.drivers.get('claude-code');
|
|
135
|
+
return driver && typeof driver.findClaudeExecutableForSdk === 'function'
|
|
136
|
+
? driver.findClaudeExecutableForSdk()
|
|
137
|
+
: null;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { AgentRegistry };
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// KB_KNOWLEDGE_DATA_DIR names the product knowledge root the host product
|
|
4
|
-
// launches the workbench with (explicit and non-secret by contract). The
|
|
5
|
-
// workbench itself repoints KB_DATA_DIR at its own session storage; an agent
|
|
6
|
-
// inheriting that value resolves project-knowledge-kb against the workbench
|
|
7
|
-
// store and fails with PROJECT_NOT_FOUND. Map the product root onto KB_DATA_DIR
|
|
8
|
-
// only in spawned agent environments: the workbench's own storage resolution
|
|
9
|
-
// stays untouched, and a standalone workbench without the variable keeps the
|
|
10
|
-
// inherited behavior.
|
|
11
|
-
function knowledgeRootEnv(source) {
|
|
12
|
-
const env = source || process.env;
|
|
13
|
-
const root = typeof env.KB_KNOWLEDGE_DATA_DIR === 'string' ? env.KB_KNOWLEDGE_DATA_DIR.trim() : '';
|
|
14
|
-
return root ? { KB_DATA_DIR: root } : {};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
module.exports = { knowledgeRootEnv };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// KB_KNOWLEDGE_DATA_DIR names the product knowledge root the host product
|
|
4
|
+
// launches the workbench with (explicit and non-secret by contract). The
|
|
5
|
+
// workbench itself repoints KB_DATA_DIR at its own session storage; an agent
|
|
6
|
+
// inheriting that value resolves project-knowledge-kb against the workbench
|
|
7
|
+
// store and fails with PROJECT_NOT_FOUND. Map the product root onto KB_DATA_DIR
|
|
8
|
+
// only in spawned agent environments: the workbench's own storage resolution
|
|
9
|
+
// stays untouched, and a standalone workbench without the variable keeps the
|
|
10
|
+
// inherited behavior.
|
|
11
|
+
function knowledgeRootEnv(source) {
|
|
12
|
+
const env = source || process.env;
|
|
13
|
+
const root = typeof env.KB_KNOWLEDGE_DATA_DIR === 'string' ? env.KB_KNOWLEDGE_DATA_DIR.trim() : '';
|
|
14
|
+
return root ? { KB_DATA_DIR: root } : {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { knowledgeRootEnv };
|