@sanqianx/claude-ai-workbench 0.11.0
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 -0
- package/README.md +129 -0
- package/apps/agent-terminal/app.css +567 -0
- package/apps/agent-terminal/app.js +2392 -0
- package/apps/agent-terminal/index.html +158 -0
- package/apps/ai-profile-settings-host/index.html +25 -0
- package/apps/ai-profile-settings-host/page.css +1 -0
- package/apps/ai-profile-settings-host/page.js +113 -0
- package/apps/minimal-test-host/README.md +15 -0
- package/apps/minimal-test-host/host.css +2 -0
- package/apps/minimal-test-host/host.js +35 -0
- package/apps/minimal-test-host/index.html +58 -0
- package/apps/minimal-test-host/optional-mock/fault-injection.js +15 -0
- package/docs/sdk/FILE_LAYOUT.md +31 -0
- package/docs/sdk/INTEGRATION.md +126 -0
- package/docs/sdk/README.md +21 -0
- package/docs/sdk/openapi.yaml +760 -0
- package/package.json +51 -0
- package/packages/client/README.md +11 -0
- package/packages/client/index.d.ts +81 -0
- package/packages/client/index.js +198 -0
- package/packages/client/package.json +29 -0
- package/packages/contracts/README.md +9 -0
- package/packages/contracts/index.d.ts +252 -0
- package/packages/contracts/index.js +295 -0
- package/packages/contracts/package.json +24 -0
- package/packages/core/README.md +8 -0
- package/packages/core/index.d.ts +76 -0
- package/packages/core/index.js +214 -0
- package/packages/core/package.json +29 -0
- package/packages/server/README.md +17 -0
- package/packages/server/bin/agent-terminal-server.js +36 -0
- package/packages/server/bin/claude-workbench-server.js +46 -0
- package/packages/server/bin/claude-workbench.js +225 -0
- package/packages/server/index.d.ts +145 -0
- package/packages/server/index.js +183 -0
- package/packages/server/lib/agent-discovery.js +112 -0
- package/packages/server/lib/agent-registry.js +98 -0
- package/packages/server/lib/attachment-store.js +78 -0
- package/packages/server/lib/bridges/app-server-bridge.js +372 -0
- package/packages/server/lib/bridges/opencode-server-bridge.js +490 -0
- package/packages/server/lib/capture-settings-store.js +34 -0
- package/packages/server/lib/context-resolver.js +40 -0
- package/packages/server/lib/drivers/cli-json-runner.js +960 -0
- package/packages/server/lib/drivers/codex-driver.js +1413 -0
- package/packages/server/lib/drivers/opencode-driver.js +1527 -0
- package/packages/server/lib/drivers/zcode-driver.js +1167 -0
- package/packages/server/lib/event-stream.js +35 -0
- package/packages/server/lib/folder-picker.js +623 -0
- package/packages/server/lib/http-handler.js +254 -0
- package/packages/server/lib/json-file.js +19 -0
- package/packages/server/lib/model-detection.js +295 -0
- package/packages/server/lib/profile-store.js +149 -0
- package/packages/server/lib/project-store.js +102 -0
- package/packages/server/lib/session-archive-store.js +53 -0
- package/packages/server/lib/system-doctor.js +119 -0
- package/packages/server/lib/ui-preferences-store.js +48 -0
- package/packages/server/lib/workbench-service.js +620 -0
- package/packages/server/package.json +34 -0
- package/packages/ui/README.md +30 -0
- package/packages/ui/ai-profile-settings.d.ts +45 -0
- package/packages/ui/ai-profile-settings.js +283 -0
- package/packages/ui/claude-terminal.d.ts +31 -0
- package/packages/ui/claude-terminal.js +343 -0
- package/packages/ui/index.d.ts +18 -0
- package/packages/ui/index.js +51 -0
- package/packages/ui/package.json +42 -0
- package/packages/ui/theme.css +13 -0
- package/src/assets/claude-prompts.json +21 -0
- package/src/backend/http/ai-profile-routes.js +61 -0
- package/src/backend/http/claude-routes.js +170 -0
- package/src/backend/http/node-helpers.js +45 -0
- package/src/backend/index.js +21 -0
- package/src/backend/lib/ai-adapter.js +144 -0
- package/src/backend/lib/ai-profile-store.js +259 -0
- package/src/backend/lib/ai-vendor-presets.js +83 -0
- package/src/backend/lib/ai-workspace.js +140 -0
- package/src/backend/lib/automation-config.js +253 -0
- package/src/backend/lib/claude-cli-runner.js +1749 -0
- package/src/backend/lib/data-dir.js +169 -0
- package/src/backend/lib/llm-client.js +176 -0
- package/src/backend/lib/model-context-windows.js +82 -0
- package/src/backend/lib/prompt-registry.js +166 -0
- package/src/backend/lib/runtime-config.js +121 -0
- package/src/index.js +7 -0
- package/src/sdk-wave-a.ts +1 -0
- package/src/sdk-wave-b.ts +1 -0
- package/src/sdk-wave-c.ts +1 -0
- package/src/sdk-wave-d.ts +1 -0
- package/src/sdk-wave-e.ts +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @claude-ai-workbench/server
|
|
2
|
+
|
|
3
|
+
版本化 REST/SSE Server SDK,同时提供 `claude-workbench-server` 命令。
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const { createWorkbenchServer } = require("@claude-ai-workbench/server");
|
|
7
|
+
const app = createWorkbenchServer({ dataDir: "./workbench-data", serveHost: false });
|
|
8
|
+
app.server.listen(5760, "127.0.0.1");
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
命令行启动:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
claude-workbench-server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
默认只应监听本机地址。若要跨机器部署,应在外层增加 HTTPS、身份认证和访问控制。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Standalone Agent Terminal entry point. Fresh data directory (~/.agent-terminal
|
|
4
|
+
// by default, AGENT_TERMINAL_DATA_DIR overrides) so the terminal product starts
|
|
5
|
+
// independently from any legacy workbench data.
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const { createWorkbenchServer } = require('..');
|
|
10
|
+
const { warmFolderPicker } = require('../lib/folder-picker');
|
|
11
|
+
|
|
12
|
+
const port = Number(process.env.AGENT_TERMINAL_PORT || process.env.CLAUDE_WORKBENCH_PORT || 5760);
|
|
13
|
+
const host = process.env.AGENT_TERMINAL_HOST || '127.0.0.1';
|
|
14
|
+
const dataDir = process.env.AGENT_TERMINAL_DATA_DIR || path.join(os.homedir(), '.agent-terminal');
|
|
15
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
16
|
+
// The agent-session runner resolves its own store through these env vars;
|
|
17
|
+
// without them it falls back to the legacy ~/.claude-ai-workbench layout.
|
|
18
|
+
process.env.CLAUDE_AI_WORKBENCH_DATA_DIR = dataDir;
|
|
19
|
+
process.env.KB_DATA_DIR = dataDir;
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const app = createWorkbenchServer({ dataDir });
|
|
23
|
+
app.server.listen(port, host, () => {
|
|
24
|
+
process.stdout.write(`Agent Terminal listening at http://${host}:${port}/agent-terminal/ (data: ${dataDir})\n`);
|
|
25
|
+
// Compile the folder-picker helper up front so the first "browse…" click
|
|
26
|
+
// pops the dialog in milliseconds instead of paying the powershell +
|
|
27
|
+
// Add-Type startup. Never blocks or crashes the server.
|
|
28
|
+
warmFolderPicker({ title: '选择项目文件夹' }).catch(() => {});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function shutdown() {
|
|
32
|
+
app.server.close(() => process.exit(0));
|
|
33
|
+
setTimeout(() => process.exit(0), 3000).unref();
|
|
34
|
+
}
|
|
35
|
+
process.on('SIGINT', shutdown);
|
|
36
|
+
process.on('SIGTERM', shutdown);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const { createWorkbenchServer } = require('..');
|
|
7
|
+
const { warmFolderPicker } = require('../lib/folder-picker');
|
|
8
|
+
|
|
9
|
+
function configuredWorkspaces() {
|
|
10
|
+
if (!process.env.CLAUDE_WORKBENCH_WORKSPACES) return {};
|
|
11
|
+
const parsed = JSON.parse(process.env.CLAUDE_WORKBENCH_WORKSPACES);
|
|
12
|
+
return Object.fromEntries(Object.entries(parsed).map(([key, value]) => [key, path.resolve(String(value))]));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const port = Number(process.env.CLAUDE_WORKBENCH_PORT || 5760);
|
|
16
|
+
const host = process.env.CLAUDE_WORKBENCH_HOST || '127.0.0.1';
|
|
17
|
+
const repositoryHosts = fs.existsSync(path.resolve(__dirname, '..', '..', '..', 'apps', 'ai-profile-settings-host'));
|
|
18
|
+
const serveHost = process.env.CLAUDE_WORKBENCH_SERVE_HOST === '1'
|
|
19
|
+
|| (process.env.CLAUDE_WORKBENCH_SERVE_HOST !== '0' && repositoryHosts);
|
|
20
|
+
if (process.env.CLAUDE_AI_WORKBENCH_DATA_DIR) {
|
|
21
|
+
// Keep the runner's session store inside the same data dir as the server.
|
|
22
|
+
process.env.KB_DATA_DIR = process.env.CLAUDE_AI_WORKBENCH_DATA_DIR;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const app = createWorkbenchServer({
|
|
26
|
+
dataDir: process.env.CLAUDE_AI_WORKBENCH_DATA_DIR,
|
|
27
|
+
workspaces: configuredWorkspaces(),
|
|
28
|
+
serveHost,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
app.server.listen(port, host, () => {
|
|
32
|
+
process.stdout.write(`Claude AI Workbench Server ${app.service.version} listening at http://${host}:${port}${app.apiPrefix}\n`);
|
|
33
|
+
if (serveHost) {
|
|
34
|
+
process.stdout.write(`Claude Code terminal: http://${host}:${port}/terminal/\n`);
|
|
35
|
+
process.stdout.write(`Minimal test host: http://${host}:${port}/test-host/\n`);
|
|
36
|
+
process.stdout.write(`AI Profile settings: http://${host}:${port}/profile-settings/\n`);
|
|
37
|
+
}
|
|
38
|
+
// Compile the folder-picker helper up front so the first "browse…" click
|
|
39
|
+
// pops the dialog in milliseconds instead of paying the powershell +
|
|
40
|
+
// Add-Type startup. Never blocks or crashes the server.
|
|
41
|
+
warmFolderPicker({ title: '选择项目文件夹' }).catch(() => {});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function shutdown() { app.server.close(() => process.exit(0)); }
|
|
45
|
+
process.on('SIGINT', shutdown);
|
|
46
|
+
process.on('SIGTERM', shutdown);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Standalone CLI for the published claude-ai-workbench package:
|
|
5
|
+
// claude-workbench start the server in the foreground
|
|
6
|
+
// claude-workbench start [-d] start in the foreground, detached with -d
|
|
7
|
+
// claude-workbench stop stop the running instance
|
|
8
|
+
// claude-workbench status report whether an instance is running
|
|
9
|
+
// claude-workbench -v print the package version
|
|
10
|
+
// Wired up via the package.json bin field, so a global `npm i -g
|
|
11
|
+
// claude-ai-workbench` puts the `claude-workbench` command on PATH.
|
|
12
|
+
|
|
13
|
+
const { execFileSync, spawn } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const net = require('net');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const pkg = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
20
|
+
// The standalone product is the Agent Terminal: current dark UI, its own
|
|
21
|
+
// clean data directory — not the legacy workbench data at ~/.claude-ai-workbench.
|
|
22
|
+
const serverScript = path.join(__dirname, 'agent-terminal-server.js');
|
|
23
|
+
const DEFAULT_PORT = 5760;
|
|
24
|
+
|
|
25
|
+
function dataDir() {
|
|
26
|
+
return path.resolve(process.env.AGENT_TERMINAL_DATA_DIR || path.join(os.homedir(), '.agent-terminal'));
|
|
27
|
+
}
|
|
28
|
+
function pidFile() {
|
|
29
|
+
return path.join(dataDir(), 'server.pid');
|
|
30
|
+
}
|
|
31
|
+
function port() {
|
|
32
|
+
return Number(process.env.AGENT_TERMINAL_PORT || process.env.CLAUDE_WORKBENCH_PORT || DEFAULT_PORT);
|
|
33
|
+
}
|
|
34
|
+
function listenUrl() {
|
|
35
|
+
const host = process.env.AGENT_TERMINAL_HOST || process.env.CLAUDE_WORKBENCH_HOST || '127.0.0.1';
|
|
36
|
+
return `http://${host}:${port()}/agent-terminal/`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writePid(pid) {
|
|
40
|
+
fs.mkdirSync(dataDir(), { recursive: true });
|
|
41
|
+
fs.writeFileSync(pidFile(), String(pid), 'utf8');
|
|
42
|
+
}
|
|
43
|
+
function readPid() {
|
|
44
|
+
try {
|
|
45
|
+
const pid = Number(fs.readFileSync(pidFile(), 'utf8').trim());
|
|
46
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function clearPid() {
|
|
52
|
+
try { fs.unlinkSync(pidFile()); } catch { /* already gone */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function alive(pid) {
|
|
56
|
+
if (process.platform === 'win32') {
|
|
57
|
+
try {
|
|
58
|
+
const out = execFileSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
|
|
59
|
+
encoding: 'utf8', windowsHide: true, timeout: 10000,
|
|
60
|
+
});
|
|
61
|
+
return out.includes(`"${pid}"`);
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function killPid(pid) {
|
|
70
|
+
if (process.platform === 'win32') {
|
|
71
|
+
execFileSync('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore', timeout: 15000 });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
process.kill(pid, 'SIGTERM');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Fallback for instances started without this CLI (raw bin script, lost data
|
|
78
|
+
// dir): whoever is LISTENING on the workbench port is the instance.
|
|
79
|
+
function pidsListeningOnPort(portNum) {
|
|
80
|
+
let out = '';
|
|
81
|
+
try {
|
|
82
|
+
out = execFileSync('netstat', ['-ano'], {
|
|
83
|
+
encoding: 'utf8', windowsHide: true, timeout: 15000, maxBuffer: 16 * 1024 * 1024,
|
|
84
|
+
});
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const pids = new Set();
|
|
89
|
+
for (const line of out.split(/\r?\n/)) {
|
|
90
|
+
if (!/LISTENING/i.test(line)) continue;
|
|
91
|
+
const columns = line.trim().split(/\s+/);
|
|
92
|
+
if (!String(columns[1] || '').endsWith(`:${portNum}`)) continue;
|
|
93
|
+
const pid = Number(columns[columns.length - 1]);
|
|
94
|
+
if (Number.isInteger(pid) && pid > 0) pids.add(pid);
|
|
95
|
+
}
|
|
96
|
+
return [...pids];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stopTargets() {
|
|
100
|
+
const pid = readPid();
|
|
101
|
+
if (pid && alive(pid)) return [pid];
|
|
102
|
+
return pidsListeningOnPort(port());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// True when something already listens on the workbench port. Checked before
|
|
106
|
+
// every start so a busy port surfaces as guidance instead of the server's
|
|
107
|
+
// raw EADDRINUSE stack trace.
|
|
108
|
+
function probePortInUse(portNum, host) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const server = net.createServer();
|
|
111
|
+
server.once('error', () => resolve(true));
|
|
112
|
+
server.once('listening', () => server.close(() => resolve(false)));
|
|
113
|
+
server.listen(portNum, host);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function cmdStart(argv) {
|
|
118
|
+
const daemon = argv.includes('-d') || argv.includes('--daemon');
|
|
119
|
+
const existing = readPid();
|
|
120
|
+
if (existing && alive(existing)) {
|
|
121
|
+
process.stdout.write(`claude-workbench is already running (pid ${existing}) at ${listenUrl()}\n`);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
const host = process.env.CLAUDE_WORKBENCH_HOST || '127.0.0.1';
|
|
125
|
+
if (await probePortInUse(port(), host)) {
|
|
126
|
+
const holders = pidsListeningOnPort(port());
|
|
127
|
+
process.stderr.write(
|
|
128
|
+
`port ${port()} is already in use${holders.length ? ` (pid ${holders.join(', ')})` : ''} — `
|
|
129
|
+
+ `another instance may be running there.\n`
|
|
130
|
+
+ `Try: claude-workbench stop, or pick another port via CLAUDE_WORKBENCH_PORT.\n`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
if (daemon) {
|
|
134
|
+
const child = spawn(process.execPath, [serverScript], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
135
|
+
child.unref();
|
|
136
|
+
writePid(child.pid);
|
|
137
|
+
process.stdout.write(`claude-workbench ${pkg.version} started in the background (pid ${child.pid}) at ${listenUrl()}\n`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
writePid(process.pid);
|
|
141
|
+
process.on('exit', clearPid);
|
|
142
|
+
process.stderr.write(`claude-workbench ${pkg.version} starting at ${listenUrl()} (Ctrl+C stops)\n`);
|
|
143
|
+
require(serverScript); // constructs and listens in-process; owns the lifetime
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function cmdStop() {
|
|
148
|
+
const targets = stopTargets();
|
|
149
|
+
if (!targets.length) {
|
|
150
|
+
clearPid(); // stale pid file from a crashed instance
|
|
151
|
+
process.stdout.write(`claude-workbench is not running (port ${port()})\n`);
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
let failed = false;
|
|
155
|
+
for (const pid of targets) {
|
|
156
|
+
try {
|
|
157
|
+
killPid(pid);
|
|
158
|
+
process.stdout.write(`stopped pid ${pid}\n`);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
failed = true;
|
|
161
|
+
process.stderr.write(`failed to stop pid ${pid}: ${error.message}\n`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
clearPid();
|
|
165
|
+
return failed ? 1 : 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function cmdStatus() {
|
|
169
|
+
const pid = readPid();
|
|
170
|
+
if (pid && alive(pid)) {
|
|
171
|
+
process.stdout.write(`claude-workbench is running (pid ${pid}) at ${listenUrl()}\n`);
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
const portPids = pidsListeningOnPort(port());
|
|
175
|
+
if (portPids.length) {
|
|
176
|
+
process.stdout.write(`claude-workbench is running without a pid file (pid ${portPids.join(', ')}) at ${listenUrl()}\n`);
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
clearPid();
|
|
180
|
+
process.stdout.write(`claude-workbench is not running\n`);
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function cmdVersion() {
|
|
185
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function help() {
|
|
190
|
+
return [
|
|
191
|
+
`claude-ai-workbench ${pkg.version} — local workbench server for Claude Code, Codex, OpenCode`,
|
|
192
|
+
'',
|
|
193
|
+
'usage: claude-workbench [command]',
|
|
194
|
+
'',
|
|
195
|
+
'commands:',
|
|
196
|
+
' start [-d|--daemon] start the server (foreground by default, background with -d)',
|
|
197
|
+
' stop stop the running server',
|
|
198
|
+
' status show whether the server is running',
|
|
199
|
+
' -v | --version print the version',
|
|
200
|
+
' -h | --help show this help',
|
|
201
|
+
'',
|
|
202
|
+
'environment:',
|
|
203
|
+
` AGENT_TERMINAL_PORT port to listen on (default ${DEFAULT_PORT}; CLAUDE_WORKBENCH_PORT also honored)`,
|
|
204
|
+
' AGENT_TERMINAL_HOST bind address (default 127.0.0.1)',
|
|
205
|
+
' AGENT_TERMINAL_DATA_DIR data directory (default ~/.agent-terminal)',
|
|
206
|
+
].join('\n');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function main(argv) {
|
|
210
|
+
const command = argv[0];
|
|
211
|
+
if (command === undefined || command === 'start') return cmdStart(argv.slice(1));
|
|
212
|
+
if (command === 'stop') return cmdStop();
|
|
213
|
+
if (command === 'status') return cmdStatus();
|
|
214
|
+
if (command === 'version' || command === '-v' || command === '--version') return cmdVersion();
|
|
215
|
+
if (command === '-h' || command === '--help' || command === 'help') {
|
|
216
|
+
process.stdout.write(`${help()}\n`);
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
process.stderr.write(`unknown command: ${command}\n\n${help()}\n`);
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main(process.argv.slice(2)).then((exitCode) => {
|
|
224
|
+
if (exitCode !== null && exitCode !== undefined) process.exitCode = exitCode;
|
|
225
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
import type { AgentId, AgentStatus, AiProfile, AiProfileInput, AttachmentInput, EffortLevel, PermissionMode, Project, ProjectInput, ProjectPatch, SessionSummary, SlashCommand, WorkbenchContext } from "@claude-ai-workbench/contracts";
|
|
3
|
+
|
|
4
|
+
export interface WorkbenchRunner {
|
|
5
|
+
startChatSession(options: Record<string, unknown>): {sessionId: string};
|
|
6
|
+
listSessions(filter?: Record<string, unknown>): Record<string, unknown>[];
|
|
7
|
+
getState(sessionId: string): Record<string, unknown> | null;
|
|
8
|
+
getSession(sessionId: string): Record<string, unknown> | null;
|
|
9
|
+
getSessionTokenUsage(sessionId: string): Record<string, unknown>;
|
|
10
|
+
subscribe(sessionId: string, listener: (event: Record<string, unknown>) => void): () => void;
|
|
11
|
+
sendInput(sessionId: string, text: string, profile: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
|
|
12
|
+
listSupportedCommands?(sessionId: string, profile: Record<string, unknown>): Promise<SlashCommand[]>;
|
|
13
|
+
resolvePermission(sessionId: string, requestId: string, decision: Record<string, unknown>): unknown;
|
|
14
|
+
abort(sessionId: string): void;
|
|
15
|
+
deleteSession(sessionId: string): unknown;
|
|
16
|
+
findClaudeExecutableForSdk(): unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WorkbenchServerOptions {
|
|
20
|
+
dataDir?: string;
|
|
21
|
+
workspaces?: Record<string, string>;
|
|
22
|
+
runner?: WorkbenchRunner;
|
|
23
|
+
profiles?: ProfileStore;
|
|
24
|
+
attachments?: AttachmentStore;
|
|
25
|
+
contexts?: ContextResolver;
|
|
26
|
+
service?: WorkbenchService;
|
|
27
|
+
projects?: ProjectStore;
|
|
28
|
+
sessionArchive?: SessionArchiveStore;
|
|
29
|
+
registry?: AgentRegistry;
|
|
30
|
+
drivers?: Partial<Record<AgentId, WorkbenchRunner>>;
|
|
31
|
+
agentDiscovery?: AgentDiscovery;
|
|
32
|
+
modelContextWindows?: { resolveContextWindow(model: string): number | null };
|
|
33
|
+
apiPrefix?: string;
|
|
34
|
+
corsOrigin?: string;
|
|
35
|
+
serveHost?: boolean;
|
|
36
|
+
staticRoot?: string;
|
|
37
|
+
profileSettingsRoot?: string;
|
|
38
|
+
terminalHostRoot?: string;
|
|
39
|
+
agentTerminalRoot?: string;
|
|
40
|
+
createMissingWorkspaces?: boolean;
|
|
41
|
+
version?: string;
|
|
42
|
+
maxSessions?: number;
|
|
43
|
+
maxConcurrentRuns?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkbenchServer {
|
|
47
|
+
server: Server;
|
|
48
|
+
service: WorkbenchService;
|
|
49
|
+
profiles: ProfileStore;
|
|
50
|
+
attachments: AttachmentStore;
|
|
51
|
+
contexts: ContextResolver;
|
|
52
|
+
projects: ProjectStore;
|
|
53
|
+
sessionArchive: SessionArchiveStore;
|
|
54
|
+
registry: AgentRegistry;
|
|
55
|
+
agentDiscovery: AgentDiscovery;
|
|
56
|
+
apiPrefix: string;
|
|
57
|
+
dataDir: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare class ProfileStore {
|
|
61
|
+
constructor(options: {dataDir: string; profileFile?: string; credentialFile?: string});
|
|
62
|
+
list(): AiProfile[];
|
|
63
|
+
get(id: string): AiProfile | null;
|
|
64
|
+
save(input: AiProfileInput, idOverride?: string): AiProfile;
|
|
65
|
+
delete(id: string): boolean;
|
|
66
|
+
setCredential(id: string, credential: string | {secret: string}): void;
|
|
67
|
+
runtimeProfile(id: string): Record<string, unknown>;
|
|
68
|
+
runtimeDraft(input: AiProfileInput, credential?: string | {secret: string}): Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export declare class ContextResolver {
|
|
72
|
+
constructor(options?: {workspaces?: Record<string, string>; createMissing?: boolean});
|
|
73
|
+
register(ref: string, target: string): void;
|
|
74
|
+
unregister(ref: string): boolean;
|
|
75
|
+
resolve(context: WorkbenchContext): {context: WorkbenchContext; workspacePath: string};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export declare class ProjectStore {
|
|
79
|
+
constructor(options: {dataDir: string; projectFile?: string});
|
|
80
|
+
list(): Project[];
|
|
81
|
+
get(id: string): Project | null;
|
|
82
|
+
create(input: ProjectInput): Project;
|
|
83
|
+
rename(id: string, name: string): Project;
|
|
84
|
+
update(id: string, patch: ProjectPatch): Project;
|
|
85
|
+
remove(id: string): boolean;
|
|
86
|
+
toWorkspaceMap(): Record<string, string>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export declare class SessionArchiveStore {
|
|
90
|
+
constructor(options: {dataDir: string; archiveFile?: string});
|
|
91
|
+
isArchived(sessionId: string): boolean;
|
|
92
|
+
archive(sessionId: string): {archivedAt: string};
|
|
93
|
+
restore(sessionId: string): boolean;
|
|
94
|
+
forget(sessionId: string): void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export declare class AgentRegistry {
|
|
98
|
+
constructor(options?: {drivers?: Partial<Record<AgentId, WorkbenchRunner>>});
|
|
99
|
+
register(agentId: AgentId, driver: WorkbenchRunner): WorkbenchRunner;
|
|
100
|
+
driverFor(agentId: AgentId): WorkbenchRunner;
|
|
101
|
+
agentOf(sessionId: string): AgentId;
|
|
102
|
+
asRunner(): WorkbenchRunner;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface AgentDiscovery {
|
|
106
|
+
(): Record<AgentId, AgentStatus & {reason?: string}>;
|
|
107
|
+
invalidate?(): void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface CliDriverOptions {
|
|
111
|
+
command?: string;
|
|
112
|
+
spawn?: typeof import("node:child_process").spawn;
|
|
113
|
+
ignoreUserConfig?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export declare class AttachmentStore {
|
|
117
|
+
constructor(options: {root: string});
|
|
118
|
+
saveMany(sessionId: string, attachments: AttachmentInput[]): Record<string, unknown>[];
|
|
119
|
+
save(sessionId: string, attachment: AttachmentInput): Record<string, unknown>;
|
|
120
|
+
read(sessionId: string, attachmentId: string): {metadata: Record<string, unknown>; data: Buffer} | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export declare class WorkbenchService {
|
|
124
|
+
readonly version: string;
|
|
125
|
+
listProjects(): {projects: Project[]};
|
|
126
|
+
getProject(id: string): Project;
|
|
127
|
+
createProject(input: ProjectInput): Project;
|
|
128
|
+
renameProject(id: string, input: ProjectPatch): Project;
|
|
129
|
+
removeProject(id: string): boolean;
|
|
130
|
+
listSessions(contextId: string, options?: {includeArchived?: boolean}): SessionSummary[];
|
|
131
|
+
startSession(context: WorkbenchContext, aiProfileId?: string | null, options?: {agentId?: AgentId; model?: string | null; permissionMode?: PermissionMode; effort?: EffortLevel}): SessionSummary;
|
|
132
|
+
listCommands(sessionId: string): Promise<{commands: SlashCommand[]}>;
|
|
133
|
+
readAttachment(sessionId: string, attachmentId: string): {metadata: Record<string, unknown>; data: Buffer};
|
|
134
|
+
send(sessionId: string, text?: string, options?: Record<string, unknown>, attachments?: AttachmentInput[]): Promise<Record<string, unknown>>;
|
|
135
|
+
testProfile(profileId: string): Promise<Record<string, unknown>>;
|
|
136
|
+
testProfileDraft(input: {profile: AiProfileInput; credential?: string}): Promise<Record<string, unknown>>;
|
|
137
|
+
destroy(): void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export declare function createWorkbenchServer(options?: WorkbenchServerOptions): WorkbenchServer;
|
|
141
|
+
export declare function createAgentDiscovery(options?: {claudeProbe?: () => {available: boolean; version?: string | null}}): AgentDiscovery;
|
|
142
|
+
export declare function createCodexDriver(options?: CliDriverOptions): WorkbenchRunner;
|
|
143
|
+
export declare function createOpenCodeDriver(options?: CliDriverOptions): WorkbenchRunner;
|
|
144
|
+
export declare function createZCodeDriver(): WorkbenchRunner;
|
|
145
|
+
export declare function createHttpHandler(service: WorkbenchService, options?: Record<string, unknown>): (request: unknown, response: unknown) => Promise<boolean>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { configureWorkbench } = loadLegacyRuntime('runtime-config');
|
|
7
|
+
const { getDataDir } = loadLegacyRuntime('data-dir');
|
|
8
|
+
const { DEFAULT_API_PREFIX } = require('../contracts');
|
|
9
|
+
const { ProfileStore } = require('./lib/profile-store');
|
|
10
|
+
const { CaptureSettingsStore } = require('./lib/capture-settings-store');
|
|
11
|
+
const { UiPreferencesStore } = require('./lib/ui-preferences-store');
|
|
12
|
+
const { ContextResolver } = require('./lib/context-resolver');
|
|
13
|
+
const { WorkbenchService } = require('./lib/workbench-service');
|
|
14
|
+
const { AttachmentStore } = require('./lib/attachment-store');
|
|
15
|
+
const { ProjectStore } = require('./lib/project-store');
|
|
16
|
+
const { SessionArchiveStore } = require('./lib/session-archive-store');
|
|
17
|
+
const { AgentRegistry } = require('./lib/agent-registry');
|
|
18
|
+
const { createAgentDiscovery, probeExecutableVersion } = require('./lib/agent-discovery');
|
|
19
|
+
const { createSystemDoctor } = require('./lib/system-doctor');
|
|
20
|
+
const { createModelDetection } = require('./lib/model-detection');
|
|
21
|
+
const { createCodexDriver } = require('./lib/drivers/codex-driver');
|
|
22
|
+
const { createOpenCodeDriver } = require('./lib/drivers/opencode-driver');
|
|
23
|
+
const { createZCodeDriver, discoverZCodeCli, probeZCodeVersion } = require('./lib/drivers/zcode-driver');
|
|
24
|
+
const { createHttpHandler } = require('./lib/http-handler');
|
|
25
|
+
|
|
26
|
+
function createWorkbenchServer(options = {}) {
|
|
27
|
+
if (options.dataDir) configureWorkbench({ dataDir: options.dataDir });
|
|
28
|
+
const dataDir = path.resolve(options.dataDir || getDataDir());
|
|
29
|
+
const apiPrefix = options.apiPrefix || DEFAULT_API_PREFIX;
|
|
30
|
+
const testRoot = path.join(dataDir, 'test-workspaces');
|
|
31
|
+
const projects = options.projects || new ProjectStore({ dataDir });
|
|
32
|
+
const sessionArchive = options.sessionArchive || new SessionArchiveStore({ dataDir });
|
|
33
|
+
const workspaces = {
|
|
34
|
+
'test-workspace-a': path.join(testRoot, 'context-a'),
|
|
35
|
+
'test-workspace-b': path.join(testRoot, 'context-b'),
|
|
36
|
+
'test-workspace-c': path.join(testRoot, 'context-c'),
|
|
37
|
+
...(options.workspaces || {}),
|
|
38
|
+
...projects.toWorkspaceMap(),
|
|
39
|
+
};
|
|
40
|
+
const profiles = options.profiles || new ProfileStore({ dataDir });
|
|
41
|
+
const captureSettings = options.captureSettings || new CaptureSettingsStore({ dataDir });
|
|
42
|
+
const uiPreferences = options.uiPreferences || new UiPreferencesStore({ dataDir });
|
|
43
|
+
const attachments = options.attachments || new AttachmentStore({ root: path.join(dataDir, 'attachments') });
|
|
44
|
+
const contexts = options.contexts || new ContextResolver({ workspaces, createMissing: options.createMissingWorkspaces !== false });
|
|
45
|
+
const claudeDriver = options.runner || loadLegacyRuntime('claude-cli-runner');
|
|
46
|
+
// Bridge capture scope: terminal sessions are exempt from Development
|
|
47
|
+
// Conversation capture unless the 会话捕获 setting says otherwise. Applied to
|
|
48
|
+
// the driver at boot and re-applied on every settings save.
|
|
49
|
+
const applyBridgeCapture = enabled => {
|
|
50
|
+
if (claudeDriver && typeof claudeDriver.setBridgeCaptureConversation === 'function') {
|
|
51
|
+
claudeDriver.setBridgeCaptureConversation(enabled);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
applyBridgeCapture(captureSettings.read().terminalConversations);
|
|
55
|
+
const registry = options.registry || new AgentRegistry({
|
|
56
|
+
drivers: {
|
|
57
|
+
'claude-code': claudeDriver,
|
|
58
|
+
...(options.drivers || {
|
|
59
|
+
codex: createCodexDriver(),
|
|
60
|
+
opencode: createOpenCodeDriver(),
|
|
61
|
+
zcode: createZCodeDriver(),
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const runner = registry.asRunner();
|
|
66
|
+
const agentDiscovery = options.agentDiscovery || createAgentDiscovery({
|
|
67
|
+
claudeProbe: () => {
|
|
68
|
+
try {
|
|
69
|
+
const executable = claudeDriver.findClaudeExecutableForSdk();
|
|
70
|
+
// Handle both shapes: the legacy runner returns {cmd}, test doubles
|
|
71
|
+
// may return a plain string. No usable executable -> not available.
|
|
72
|
+
const cmd = typeof executable === 'string'
|
|
73
|
+
? executable
|
|
74
|
+
: executable && executable.cmd;
|
|
75
|
+
if (!cmd) return { available: false, version: null };
|
|
76
|
+
// Honest probe: spawn the resolved executable with --version. Same
|
|
77
|
+
// contract as every other agent: no version, no availability claim.
|
|
78
|
+
const version = probeExecutableVersion(cmd);
|
|
79
|
+
return { available: version != null, version };
|
|
80
|
+
} catch { return { available: false, version: null }; }
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const modelContextWindows = options.modelContextWindows || safeLoad('model-context-windows');
|
|
84
|
+
const modelDetection = options.modelDetection || createModelDetection({
|
|
85
|
+
contextLookup: modelContextWindows && modelContextWindows.lookupContextWindow,
|
|
86
|
+
});
|
|
87
|
+
const doctor = options.doctor || createSystemDoctor({
|
|
88
|
+
resolveClaudeExecutable: () => {
|
|
89
|
+
try { return claudeDriver.findClaudeExecutableForSdk(); } catch { return { cmd: null }; }
|
|
90
|
+
},
|
|
91
|
+
discoverZCodeCli,
|
|
92
|
+
probeZCodeVersion,
|
|
93
|
+
});
|
|
94
|
+
const service = options.service || new WorkbenchService({
|
|
95
|
+
runner, profiles, contexts, attachments, apiPrefix,
|
|
96
|
+
projects, sessionArchive, agentDiscovery, modelContextWindows, modelDetection,
|
|
97
|
+
archiveRetentionDays: options.archiveRetentionDays,
|
|
98
|
+
captureSettings, uiPreferences, onCaptureSettingsSaved: applyBridgeCapture,
|
|
99
|
+
testWorkspace: path.join(testRoot, 'profile-test'), auditFile: path.join(dataDir, 'audit.jsonl'),
|
|
100
|
+
version: options.version || require('./package.json').version,
|
|
101
|
+
maxSessions: options.maxSessions, maxConcurrentRuns: options.maxConcurrentRuns,
|
|
102
|
+
});
|
|
103
|
+
const handleApi = createHttpHandler(service, { apiPrefix, corsOrigin: options.corsOrigin, doctor });
|
|
104
|
+
const distributionRoot = resolveDistributionRoot();
|
|
105
|
+
const staticRoot = path.resolve(options.staticRoot || path.join(distributionRoot, 'apps', 'minimal-test-host'));
|
|
106
|
+
const profileSettingsRoot = path.resolve(options.profileSettingsRoot || path.join(distributionRoot, 'apps', 'ai-profile-settings-host'));
|
|
107
|
+
const agentTerminalRoot = path.resolve(options.agentTerminalRoot || path.join(distributionRoot, 'apps', 'agent-terminal'));
|
|
108
|
+
const packagesRoot = path.join(distributionRoot, 'packages');
|
|
109
|
+
const server = http.createServer(async (req, res) => {
|
|
110
|
+
if (await handleApi(req, res)) return;
|
|
111
|
+
if (options.serveHost !== false && servePackageAsset(req, res, packagesRoot)) return;
|
|
112
|
+
if (options.serveHost !== false && serveAppStatic(req, res, '/profile-settings/', profileSettingsRoot)) return;
|
|
113
|
+
if (options.serveHost !== false && serveAppStatic(req, res, '/agent-terminal/', agentTerminalRoot)) return;
|
|
114
|
+
if (options.serveHost !== false && serveStatic(req, res, staticRoot)) return;
|
|
115
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
116
|
+
res.end(JSON.stringify({ ok: false, error: 'not found' }));
|
|
117
|
+
});
|
|
118
|
+
server.on('close', () => service.destroy());
|
|
119
|
+
return { server, service, profiles, contexts, attachments, projects, sessionArchive, registry, agentDiscovery, apiPrefix, dataDir };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function loadLegacyRuntime(name) {
|
|
123
|
+
const bundled = path.join(__dirname, 'legacy', 'src', 'backend', 'lib', name);
|
|
124
|
+
if (fs.existsSync(`${bundled}.js`)) return require(bundled);
|
|
125
|
+
return require(path.join(__dirname, '..', '..', 'src', 'backend', 'lib', name));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function safeLoad(name) {
|
|
129
|
+
try { return loadLegacyRuntime(name); } catch { return null; }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveDistributionRoot() {
|
|
133
|
+
try { return path.dirname(require.resolve('claude-ai-workbench/package.json')); }
|
|
134
|
+
catch { return path.resolve(__dirname, '..', '..'); }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function servePackageAsset(req, res, packagesRoot) {
|
|
138
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
139
|
+
if (req.method !== 'GET' || !url.pathname.startsWith('/packages/')) return false;
|
|
140
|
+
const relative = decodeURIComponent(url.pathname.slice('/packages/'.length));
|
|
141
|
+
const file = path.resolve(packagesRoot, relative);
|
|
142
|
+
if (!file.startsWith(`${packagesRoot}${path.sep}`) || !fs.existsSync(file) || !fs.statSync(file).isFile()) return false;
|
|
143
|
+
const type = path.extname(file) === '.css' ? 'text/css' : 'text/javascript';
|
|
144
|
+
res.writeHead(200, { 'Content-Type': `${type}; charset=utf-8`, 'Cache-Control': 'no-store' });
|
|
145
|
+
fs.createReadStream(file).pipe(res); return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function serveStatic(req, res, root) {
|
|
149
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
150
|
+
if (!['GET', 'HEAD'].includes(req.method)) return false;
|
|
151
|
+
let pathname = decodeURIComponent(url.pathname);
|
|
152
|
+
if (pathname === '/') { res.writeHead(302, { Location: '/agent-terminal/' }); res.end(); return true; }
|
|
153
|
+
if (!pathname.startsWith('/test-host/')) return false;
|
|
154
|
+
const relative = pathname.slice('/test-host/'.length) || 'index.html';
|
|
155
|
+
const file = path.resolve(root, relative);
|
|
156
|
+
if (file !== root && !file.startsWith(`${root}${path.sep}`)) return false;
|
|
157
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return false;
|
|
158
|
+
const type = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml' }[path.extname(file)] || 'application/octet-stream';
|
|
159
|
+
res.writeHead(200, { 'Content-Type': `${type}; charset=utf-8`, 'Cache-Control': 'no-store' });
|
|
160
|
+
if (req.method === 'HEAD') res.end(); else fs.createReadStream(file).pipe(res);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function serveAppStatic(req, res, urlPrefix, root) {
|
|
165
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
166
|
+
if (!['GET', 'HEAD'].includes(req.method)) return false;
|
|
167
|
+
if (url.pathname === urlPrefix.slice(0, -1)) { res.writeHead(302, { Location: urlPrefix }); res.end(); return true; }
|
|
168
|
+
if (!url.pathname.startsWith(urlPrefix)) return false;
|
|
169
|
+
const relative = decodeURIComponent(url.pathname.slice(urlPrefix.length)) || 'index.html';
|
|
170
|
+
const file = path.resolve(root, relative);
|
|
171
|
+
if (file !== root && !file.startsWith(`${root}${path.sep}`)) return false;
|
|
172
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return false;
|
|
173
|
+
const type = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml' }[path.extname(file)] || 'application/octet-stream';
|
|
174
|
+
res.writeHead(200, { 'Content-Type': `${type}; charset=utf-8`, 'Cache-Control': 'no-store' });
|
|
175
|
+
if (req.method === 'HEAD') res.end(); else fs.createReadStream(file).pipe(res);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
createWorkbenchServer, ProfileStore, CaptureSettingsStore, ContextResolver, AttachmentStore, WorkbenchService, createHttpHandler,
|
|
181
|
+
ProjectStore, SessionArchiveStore, AgentRegistry, createAgentDiscovery, UiPreferencesStore,
|
|
182
|
+
createCodexDriver, createOpenCodeDriver, createZCodeDriver,
|
|
183
|
+
};
|