opc-agent 1.4.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +91 -32
- package/dist/channels/email.d.ts +32 -26
- package/dist/channels/email.js +239 -62
- package/dist/channels/feishu.d.ts +21 -6
- package/dist/channels/feishu.js +225 -126
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/channels/websocket.d.ts +46 -3
- package/dist/channels/websocket.js +306 -37
- package/dist/channels/wechat.d.ts +33 -13
- package/dist/channels/wechat.js +229 -42
- package/dist/cli.js +1127 -19
- package/dist/core/a2a.d.ts +17 -0
- package/dist/core/a2a.js +43 -1
- package/dist/core/agent.d.ts +39 -0
- package/dist/core/agent.js +228 -3
- package/dist/core/runtime.d.ts +7 -0
- package/dist/core/runtime.js +205 -2
- package/dist/core/sandbox.d.ts +26 -0
- package/dist/core/sandbox.js +117 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/core/workflow-graph.d.ts +93 -0
- package/dist/core/workflow-graph.js +247 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.js +183 -0
- package/dist/eval/index.d.ts +65 -0
- package/dist/eval/index.js +191 -0
- package/dist/index.d.ts +37 -6
- package/dist/index.js +75 -3
- package/dist/plugins/content-filter.d.ts +7 -0
- package/dist/plugins/content-filter.js +25 -0
- package/dist/plugins/index.d.ts +42 -0
- package/dist/plugins/index.js +108 -2
- package/dist/plugins/logger.d.ts +6 -0
- package/dist/plugins/logger.js +20 -0
- package/dist/plugins/rate-limiter.d.ts +7 -0
- package/dist/plugins/rate-limiter.js +35 -0
- package/dist/protocols/a2a/client.d.ts +25 -0
- package/dist/protocols/a2a/client.js +115 -0
- package/dist/protocols/a2a/index.d.ts +6 -0
- package/dist/protocols/a2a/index.js +12 -0
- package/dist/protocols/a2a/server.d.ts +41 -0
- package/dist/protocols/a2a/server.js +295 -0
- package/dist/protocols/a2a/types.d.ts +91 -0
- package/dist/protocols/a2a/types.js +15 -0
- package/dist/protocols/a2a/utils.d.ts +6 -0
- package/dist/protocols/a2a/utils.js +47 -0
- package/dist/protocols/agui/client.d.ts +10 -0
- package/dist/protocols/agui/client.js +75 -0
- package/dist/protocols/agui/index.d.ts +4 -0
- package/dist/protocols/agui/index.js +25 -0
- package/dist/protocols/agui/server.d.ts +37 -0
- package/dist/protocols/agui/server.js +191 -0
- package/dist/protocols/agui/types.d.ts +107 -0
- package/dist/protocols/agui/types.js +17 -0
- package/dist/protocols/index.d.ts +2 -0
- package/dist/protocols/index.js +19 -0
- package/dist/protocols/mcp/agent-tools.d.ts +11 -0
- package/dist/protocols/mcp/agent-tools.js +129 -0
- package/dist/protocols/mcp/index.d.ts +5 -0
- package/dist/protocols/mcp/index.js +11 -0
- package/dist/protocols/mcp/server.d.ts +31 -0
- package/dist/protocols/mcp/server.js +248 -0
- package/dist/protocols/mcp/types.d.ts +92 -0
- package/dist/protocols/mcp/types.js +17 -0
- package/dist/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/publish/index.d.ts +45 -0
- package/dist/publish/index.js +350 -0
- package/dist/schema/oad.d.ts +859 -67
- package/dist/schema/oad.js +47 -3
- package/dist/security/approval.d.ts +36 -0
- package/dist/security/approval.js +113 -0
- package/dist/security/index.d.ts +4 -0
- package/dist/security/index.js +8 -0
- package/dist/security/keys.d.ts +16 -0
- package/dist/security/keys.js +117 -0
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -0
- package/dist/studio/server.d.ts +63 -0
- package/dist/studio/server.js +625 -0
- package/dist/studio-ui/index.html +662 -0
- package/dist/telemetry/index.d.ts +93 -0
- package/dist/telemetry/index.js +285 -0
- package/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -0
- package/package.json +5 -3
- package/scripts/install.ps1 +31 -0
- package/scripts/install.sh +40 -0
- package/src/channels/email.ts +351 -177
- package/src/channels/feishu.ts +349 -236
- package/src/channels/telegram.ts +212 -90
- package/src/channels/websocket.ts +399 -87
- package/src/channels/wechat.ts +329 -149
- package/src/cli.ts +1201 -20
- package/src/core/a2a.ts +60 -0
- package/src/core/agent.ts +420 -152
- package/src/core/runtime.ts +174 -0
- package/src/core/sandbox.ts +143 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/core/workflow-graph.ts +365 -0
- package/src/daemon.ts +96 -0
- package/src/doctor.ts +156 -0
- package/src/eval/index.ts +211 -0
- package/src/eval/suites/basic.json +16 -0
- package/src/eval/suites/memory.json +12 -0
- package/src/eval/suites/safety.json +14 -0
- package/src/index.ts +65 -6
- package/src/plugins/content-filter.ts +23 -0
- package/src/plugins/index.ts +133 -2
- package/src/plugins/logger.ts +18 -0
- package/src/plugins/rate-limiter.ts +38 -0
- package/src/protocols/a2a/client.ts +132 -0
- package/src/protocols/a2a/index.ts +8 -0
- package/src/protocols/a2a/server.ts +333 -0
- package/src/protocols/a2a/types.ts +88 -0
- package/src/protocols/a2a/utils.ts +50 -0
- package/src/protocols/agui/client.ts +83 -0
- package/src/protocols/agui/index.ts +4 -0
- package/src/protocols/agui/server.ts +218 -0
- package/src/protocols/agui/types.ts +153 -0
- package/src/protocols/index.ts +2 -0
- package/src/protocols/mcp/agent-tools.ts +134 -0
- package/src/protocols/mcp/index.ts +8 -0
- package/src/protocols/mcp/server.ts +262 -0
- package/src/protocols/mcp/types.ts +69 -0
- package/src/providers/index.ts +354 -339
- package/src/publish/index.ts +376 -0
- package/src/schema/oad.ts +204 -154
- package/src/security/approval.ts +131 -0
- package/src/security/index.ts +3 -0
- package/src/security/keys.ts +87 -0
- package/src/skills/auto-learn.ts +262 -0
- package/src/studio/server.ts +629 -0
- package/src/studio-ui/index.html +662 -0
- package/src/telemetry/index.ts +324 -0
- package/src/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/mcp-client.ts +131 -0
- package/src/types/agent-workstation.d.ts +2 -0
- package/tests/a2a-protocol.test.ts +285 -0
- package/tests/agui-protocol.test.ts +246 -0
- package/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/channels/discord.test.ts +79 -0
- package/tests/channels/email.test.ts +148 -0
- package/tests/channels/feishu.test.ts +123 -0
- package/tests/channels/telegram.test.ts +129 -0
- package/tests/channels/websocket.test.ts +53 -0
- package/tests/channels/wechat.test.ts +170 -0
- package/tests/chat-cli.test.ts +160 -0
- package/tests/cli.test.ts +46 -0
- package/tests/daemon.test.ts +135 -0
- package/tests/deepbrain-wire.test.ts +234 -0
- package/tests/doctor.test.ts +38 -0
- package/tests/eval.test.ts +173 -0
- package/tests/init-role.test.ts +124 -0
- package/tests/mcp-client.test.ts +92 -0
- package/tests/mcp-server.test.ts +178 -0
- package/tests/plugin-a2a-enhanced.test.ts +230 -0
- package/tests/publish.test.ts +231 -0
- package/tests/scheduler.test.ts +200 -0
- package/tests/security-enhanced.test.ts +233 -0
- package/tests/skill-learner.test.ts +161 -0
- package/tests/studio.test.ts +229 -0
- package/tests/subagent.test.ts +193 -0
- package/tests/telegram-discord.test.ts +60 -0
- package/tests/telemetry.test.ts +186 -0
- package/tests/tools/builtin-extended.test.ts +138 -0
- package/tests/workflow-graph.test.ts +279 -0
- package/tutorial/customer-service-agent/README.md +612 -0
- package/tutorial/customer-service-agent/SOUL.md +26 -0
- package/tutorial/customer-service-agent/agent.yaml +63 -0
- package/tutorial/customer-service-agent/package.json +19 -0
- package/tutorial/customer-service-agent/src/index.ts +69 -0
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
- package/tutorial/customer-service-agent/tsconfig.json +14 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { createServer, IncomingMessage, ServerResponse, request as httpRequest } from 'http';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join, extname } from 'path';
|
|
4
|
+
import * as net from 'net';
|
|
5
|
+
import { Tracer } from '../telemetry';
|
|
6
|
+
|
|
7
|
+
interface StudioConfig {
|
|
8
|
+
port: number;
|
|
9
|
+
agentDir: string;
|
|
10
|
+
staticDir: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ModuleInfo {
|
|
14
|
+
name: string;
|
|
15
|
+
path: string;
|
|
16
|
+
port: number;
|
|
17
|
+
icon: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const MODULE_REGISTRY: ModuleInfo[] = [
|
|
21
|
+
{ name: 'DeepBrain', path: 'brain', port: 4001, icon: '🧠' },
|
|
22
|
+
{ name: 'AgentKits', path: 'kits', port: 4002, icon: '📊' },
|
|
23
|
+
{ name: 'Workstation', path: 'workstation', port: 4003, icon: '👤' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
class StudioServer {
|
|
27
|
+
private server: any;
|
|
28
|
+
private config: StudioConfig;
|
|
29
|
+
private tracer?: Tracer;
|
|
30
|
+
|
|
31
|
+
constructor(config: Partial<StudioConfig> = {}) {
|
|
32
|
+
this.config = {
|
|
33
|
+
port: config.port || 4000,
|
|
34
|
+
agentDir: config.agentDir || process.cwd(),
|
|
35
|
+
staticDir: config.staticDir || join(__dirname, '../studio-ui'),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setTracer(tracer: Tracer): void {
|
|
40
|
+
this.tracer = tracer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getTracer(): Tracer | undefined {
|
|
44
|
+
return this.tracer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getConfig(): StudioConfig {
|
|
48
|
+
return { ...this.config };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async start(): Promise<void> {
|
|
52
|
+
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
53
|
+
this.server.listen(this.config.port);
|
|
54
|
+
console.log(`🎨 OPC Studio: http://localhost:${this.config.port}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async stop(): Promise<void> {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
if (this.server) {
|
|
60
|
+
this.server.close(() => resolve());
|
|
61
|
+
} else {
|
|
62
|
+
resolve();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async handleRequest(req: IncomingMessage, res: ServerResponse) {
|
|
68
|
+
const url = new URL(req.url || '/', `http://localhost`);
|
|
69
|
+
|
|
70
|
+
// Handle CORS preflight
|
|
71
|
+
if (req.method === 'OPTIONS') {
|
|
72
|
+
res.writeHead(204, {
|
|
73
|
+
'Access-Control-Allow-Origin': '*',
|
|
74
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
75
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
76
|
+
});
|
|
77
|
+
res.end();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// API routes
|
|
82
|
+
if (url.pathname.startsWith('/api/')) {
|
|
83
|
+
return this.handleAPI(req, res, url);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Module proxy routes
|
|
87
|
+
for (const mod of MODULE_REGISTRY) {
|
|
88
|
+
if (url.pathname.startsWith(`/${mod.path}/`) || url.pathname === `/${mod.path}`) {
|
|
89
|
+
return this.proxyToModule(req, res, mod, url);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Static files
|
|
94
|
+
return this.serveStatic(req, res, url);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async handleAPI(req: IncomingMessage, res: ServerResponse, url: URL) {
|
|
98
|
+
const route = url.pathname.replace('/api/', '');
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
let data: any;
|
|
102
|
+
|
|
103
|
+
switch (route) {
|
|
104
|
+
case 'modules':
|
|
105
|
+
data = await this.getModulesStatus();
|
|
106
|
+
break;
|
|
107
|
+
case 'agent/info':
|
|
108
|
+
data = await this.getAgentInfo();
|
|
109
|
+
break;
|
|
110
|
+
case 'agent/config':
|
|
111
|
+
if (req.method === 'GET') data = await this.getAgentConfig();
|
|
112
|
+
else if (req.method === 'PUT') data = await this.saveConfig(req);
|
|
113
|
+
break;
|
|
114
|
+
case 'agent/chat':
|
|
115
|
+
data = await this.handleChat(req);
|
|
116
|
+
break;
|
|
117
|
+
case 'memory/list':
|
|
118
|
+
data = await this.getMemoryList();
|
|
119
|
+
break;
|
|
120
|
+
case 'memory/search':
|
|
121
|
+
data = await this.searchMemory(url.searchParams.get('q') || '');
|
|
122
|
+
break;
|
|
123
|
+
case 'memory/stats':
|
|
124
|
+
data = await this.getMemoryStats();
|
|
125
|
+
break;
|
|
126
|
+
case 'skills/list':
|
|
127
|
+
data = await this.getSkills();
|
|
128
|
+
break;
|
|
129
|
+
case 'tools/list':
|
|
130
|
+
data = await this.getTools();
|
|
131
|
+
break;
|
|
132
|
+
case 'workflows/list':
|
|
133
|
+
data = await this.getWorkflows();
|
|
134
|
+
break;
|
|
135
|
+
case 'jobs/list':
|
|
136
|
+
data = await this.getJobs();
|
|
137
|
+
break;
|
|
138
|
+
case 'logs/recent':
|
|
139
|
+
data = await this.getRecentLogs();
|
|
140
|
+
break;
|
|
141
|
+
case 'analytics/overview':
|
|
142
|
+
data = await this.getAnalytics();
|
|
143
|
+
break;
|
|
144
|
+
case 'doctor/check':
|
|
145
|
+
data = await this.runDoctor();
|
|
146
|
+
break;
|
|
147
|
+
case 'channels/list':
|
|
148
|
+
data = await this.getChannels();
|
|
149
|
+
break;
|
|
150
|
+
case 'plugins/list':
|
|
151
|
+
data = await this.getPlugins();
|
|
152
|
+
break;
|
|
153
|
+
case 'security/approvals':
|
|
154
|
+
data = await this.getPendingApprovals();
|
|
155
|
+
break;
|
|
156
|
+
case 'eval/suites':
|
|
157
|
+
data = await this.getEvalSuites();
|
|
158
|
+
break;
|
|
159
|
+
case 'eval/run':
|
|
160
|
+
if (req.method === 'POST') data = await this.runEvalSuite(req);
|
|
161
|
+
else { res.writeHead(405); res.end(); return; }
|
|
162
|
+
break;
|
|
163
|
+
case 'a2a/card':
|
|
164
|
+
data = this.getA2ACard();
|
|
165
|
+
break;
|
|
166
|
+
case 'a2a/tasks':
|
|
167
|
+
data = this.getA2ATasks();
|
|
168
|
+
break;
|
|
169
|
+
case 'a2a/discover':
|
|
170
|
+
if (req.method === 'POST') data = await this.discoverA2AAgent(req);
|
|
171
|
+
else { res.writeHead(405); res.end(); return; }
|
|
172
|
+
break;
|
|
173
|
+
case 'protocols':
|
|
174
|
+
data = await this.getProtocols();
|
|
175
|
+
break;
|
|
176
|
+
case 'protocols/mcp':
|
|
177
|
+
data = this.getMCPServerStatus();
|
|
178
|
+
break;
|
|
179
|
+
case 'eval/reports':
|
|
180
|
+
data = await this.getEvalReports();
|
|
181
|
+
break;
|
|
182
|
+
case 'telemetry/stats':
|
|
183
|
+
data = this.tracer ? this.tracer.getStats() : { error: 'Telemetry not enabled' };
|
|
184
|
+
break;
|
|
185
|
+
case 'telemetry/traces':
|
|
186
|
+
data = this.getTelemetryTraces(url);
|
|
187
|
+
break;
|
|
188
|
+
case 'telemetry/metrics':
|
|
189
|
+
data = this.tracer ? this.tracer.getMetrics() : [];
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
193
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
res.writeHead(200, {
|
|
198
|
+
'Content-Type': 'application/json',
|
|
199
|
+
'Access-Control-Allow-Origin': '*',
|
|
200
|
+
});
|
|
201
|
+
res.end(JSON.stringify(data));
|
|
202
|
+
} catch (e: any) {
|
|
203
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
204
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- API Implementations ---
|
|
209
|
+
|
|
210
|
+
private async getAgentInfo() {
|
|
211
|
+
const oad = this.loadOAD();
|
|
212
|
+
const pkg = this.loadPackageJson();
|
|
213
|
+
return {
|
|
214
|
+
name: oad?.metadata?.name || pkg?.name || 'unknown',
|
|
215
|
+
version: oad?.metadata?.version || pkg?.version || '0.0.0',
|
|
216
|
+
description: oad?.metadata?.description || pkg?.description || '',
|
|
217
|
+
model: oad?.spec?.model || 'unknown',
|
|
218
|
+
provider: oad?.spec?.provider?.default || 'unknown',
|
|
219
|
+
channels: oad?.spec?.channels?.map((c: any) => c.type) || [],
|
|
220
|
+
skills: oad?.spec?.skills?.map((s: any) => s.name) || [],
|
|
221
|
+
status: 'running',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private async getAgentConfig() {
|
|
226
|
+
const yamlPath = join(this.config.agentDir, 'agent.yaml');
|
|
227
|
+
if (existsSync(yamlPath)) {
|
|
228
|
+
return { content: readFileSync(yamlPath, 'utf-8') };
|
|
229
|
+
}
|
|
230
|
+
return { content: '', error: 'agent.yaml not found' };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async saveConfig(req: IncomingMessage) {
|
|
234
|
+
const body = await this.readBody(req);
|
|
235
|
+
const { content } = JSON.parse(body);
|
|
236
|
+
const yamlPath = join(this.config.agentDir, 'agent.yaml');
|
|
237
|
+
const { writeFileSync } = require('fs');
|
|
238
|
+
writeFileSync(yamlPath, content, 'utf-8');
|
|
239
|
+
return { success: true };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async handleChat(req: IncomingMessage) {
|
|
243
|
+
const body = await this.readBody(req);
|
|
244
|
+
const { message, sessionId } = JSON.parse(body);
|
|
245
|
+
try {
|
|
246
|
+
const { BaseAgent, InMemoryStore } = require('../index');
|
|
247
|
+
const oad = this.loadOAD();
|
|
248
|
+
const agent = new BaseAgent({
|
|
249
|
+
name: oad?.metadata?.name || 'studio-agent',
|
|
250
|
+
systemPrompt: oad?.spec?.systemPrompt || 'You are a helpful assistant.',
|
|
251
|
+
provider: oad?.spec?.provider?.default || 'ollama',
|
|
252
|
+
model: oad?.spec?.model || 'qwen2.5',
|
|
253
|
+
memory: new InMemoryStore(),
|
|
254
|
+
});
|
|
255
|
+
await agent.init();
|
|
256
|
+
const response = await agent.handleMessage({
|
|
257
|
+
id: String(Date.now()),
|
|
258
|
+
content: message,
|
|
259
|
+
sender: 'studio-user',
|
|
260
|
+
channel: 'studio',
|
|
261
|
+
sessionId: sessionId || 'studio-session',
|
|
262
|
+
timestamp: new Date(),
|
|
263
|
+
});
|
|
264
|
+
return { response: response.content };
|
|
265
|
+
} catch (e: any) {
|
|
266
|
+
return { response: `Error: ${e.message}` };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private async getMemoryList() {
|
|
271
|
+
try {
|
|
272
|
+
const { Brain } = require('deepbrain');
|
|
273
|
+
const oad = this.loadOAD();
|
|
274
|
+
const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
|
|
275
|
+
const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
|
|
276
|
+
await brain.connect();
|
|
277
|
+
const pages = await brain.list({ limit: 50 });
|
|
278
|
+
await brain.disconnect();
|
|
279
|
+
return { pages };
|
|
280
|
+
} catch {
|
|
281
|
+
return { pages: [], error: 'DeepBrain not available' };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private async searchMemory(query: string) {
|
|
286
|
+
try {
|
|
287
|
+
const { Brain } = require('deepbrain');
|
|
288
|
+
const oad = this.loadOAD();
|
|
289
|
+
const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
|
|
290
|
+
const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
|
|
291
|
+
await brain.connect();
|
|
292
|
+
const results = await brain.search(query);
|
|
293
|
+
await brain.disconnect();
|
|
294
|
+
return { results };
|
|
295
|
+
} catch {
|
|
296
|
+
return { results: [], error: 'Search failed' };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async getMemoryStats() {
|
|
301
|
+
try {
|
|
302
|
+
const { Brain } = require('deepbrain');
|
|
303
|
+
const oad = this.loadOAD();
|
|
304
|
+
const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
|
|
305
|
+
const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
|
|
306
|
+
await brain.connect();
|
|
307
|
+
const stats = await brain.stats();
|
|
308
|
+
await brain.disconnect();
|
|
309
|
+
return stats;
|
|
310
|
+
} catch {
|
|
311
|
+
return { pages: 0, chunks: 0, error: 'Stats unavailable' };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private async getSkills() {
|
|
316
|
+
try {
|
|
317
|
+
const { SkillLearner } = require('../index');
|
|
318
|
+
const learner = new SkillLearner('.opc/skills');
|
|
319
|
+
const skills = learner.loadSkills();
|
|
320
|
+
return { skills };
|
|
321
|
+
} catch {
|
|
322
|
+
return { skills: [] };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private async getTools() {
|
|
327
|
+
try {
|
|
328
|
+
const { getBuiltinTools } = require('../index');
|
|
329
|
+
const tools = getBuiltinTools(this.config.agentDir);
|
|
330
|
+
return { tools: tools.map((t: any) => ({ name: t.definition.name, description: t.definition.description })) };
|
|
331
|
+
} catch {
|
|
332
|
+
return { tools: [] };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private async getWorkflows() {
|
|
337
|
+
const oad = this.loadOAD();
|
|
338
|
+
return { workflows: oad?.spec?.workflows || [] };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private async getJobs() {
|
|
342
|
+
const oad = this.loadOAD();
|
|
343
|
+
return { jobs: oad?.spec?.scheduler?.jobs || [] };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private async getRecentLogs() {
|
|
347
|
+
const logPath = join(this.config.agentDir, '.opc', 'agent.log');
|
|
348
|
+
if (existsSync(logPath)) {
|
|
349
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
350
|
+
const lines = content.split('\n').slice(-100);
|
|
351
|
+
return { lines };
|
|
352
|
+
}
|
|
353
|
+
return { lines: [] };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private async getAnalytics() {
|
|
357
|
+
return {
|
|
358
|
+
totalMessages: 0,
|
|
359
|
+
totalSessions: 0,
|
|
360
|
+
avgResponseTime: 0,
|
|
361
|
+
topSkills: [],
|
|
362
|
+
note: 'Analytics tracking starts when agent is running via opc run/start',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private async runDoctor() {
|
|
367
|
+
try {
|
|
368
|
+
const { runDoctor } = require('../doctor');
|
|
369
|
+
const results = await runDoctor();
|
|
370
|
+
return results;
|
|
371
|
+
} catch {
|
|
372
|
+
return { error: 'Doctor not available' };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private async getChannels() {
|
|
377
|
+
const oad = this.loadOAD();
|
|
378
|
+
return { channels: oad?.spec?.channels || [] };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private async getPlugins() {
|
|
382
|
+
const oad = this.loadOAD();
|
|
383
|
+
return { plugins: oad?.spec?.plugins || [] };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private async getProtocols() {
|
|
387
|
+
const oad = this.loadOAD();
|
|
388
|
+
const protocols = (oad?.spec as any)?.protocols || {};
|
|
389
|
+
return {
|
|
390
|
+
protocols: [
|
|
391
|
+
{ name: 'a2a', description: 'Agent-to-Agent', enabled: !!protocols.a2a?.enabled, config: protocols.a2a || {} },
|
|
392
|
+
{ name: 'agui', description: 'AG-UI — Agent-User Interaction (SSE)', enabled: !!protocols.agui?.enabled, config: protocols.agui || {} },
|
|
393
|
+
{ name: 'mcp', description: 'MCP Server — Expose as MCP tools', enabled: !!protocols.mcp?.enabled, config: protocols.mcp || {} },
|
|
394
|
+
],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private async getPendingApprovals() {
|
|
399
|
+
return { approvals: [] };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private getMCPServerStatus() {
|
|
403
|
+
const oad = this.loadOAD();
|
|
404
|
+
const mcpConfig = (oad?.spec as any)?.protocols?.mcp;
|
|
405
|
+
const { agentToMCPTools } = require('../protocols/mcp/agent-tools');
|
|
406
|
+
const agentName = oad?.metadata?.name || 'opc-agent';
|
|
407
|
+
const tools = agentToMCPTools({ name: agentName });
|
|
408
|
+
return {
|
|
409
|
+
enabled: !!mcpConfig?.enabled,
|
|
410
|
+
mode: mcpConfig?.mode || 'stdio',
|
|
411
|
+
port: mcpConfig?.port || 3002,
|
|
412
|
+
tools: tools.map((t: any) => ({ name: t.name, description: t.description })),
|
|
413
|
+
toolCount: tools.length,
|
|
414
|
+
exposedTools: mcpConfig?.exposedTools || tools.map((t: any) => t.name),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private getTelemetryTraces(url: URL) {
|
|
419
|
+
if (!this.tracer) return { traces: [] };
|
|
420
|
+
const traceId = url.searchParams.get('id');
|
|
421
|
+
if (traceId) {
|
|
422
|
+
return { spans: this.tracer.getTrace(traceId) };
|
|
423
|
+
}
|
|
424
|
+
const limit = parseInt(url.searchParams.get('limit') || '50');
|
|
425
|
+
const spans = this.tracer.getSpans({ limit });
|
|
426
|
+
// Group by traceId for trace list
|
|
427
|
+
const traceMap = new Map<string, { traceId: string; rootSpan: string; startTime: number; spanCount: number; status: string }>();
|
|
428
|
+
for (const s of spans) {
|
|
429
|
+
if (!traceMap.has(s.traceId)) {
|
|
430
|
+
traceMap.set(s.traceId, { traceId: s.traceId, rootSpan: s.name, startTime: s.startTime, spanCount: 0, status: s.status });
|
|
431
|
+
}
|
|
432
|
+
traceMap.get(s.traceId)!.spanCount++;
|
|
433
|
+
}
|
|
434
|
+
return { traces: Array.from(traceMap.values()) };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private async getEvalSuites() {
|
|
438
|
+
const { AgentEvaluator } = require('../eval');
|
|
439
|
+
return { suites: AgentEvaluator.builtinSuites() };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private async runEvalSuite(req: IncomingMessage): Promise<any> {
|
|
443
|
+
const body = await this.readBody(req);
|
|
444
|
+
const { suite: suiteName } = JSON.parse(body || '{}');
|
|
445
|
+
const { AgentEvaluator } = require('../eval');
|
|
446
|
+
const suite = AgentEvaluator.loadBuiltinSuite(suiteName || 'basic');
|
|
447
|
+
// Use a mock agent for studio eval (no real agent loaded)
|
|
448
|
+
const mockAgent = { chat: async (input: string) => `[mock response to: ${input}]` };
|
|
449
|
+
const evaluator = new AgentEvaluator(mockAgent);
|
|
450
|
+
const report = await evaluator.evalSuite(suite);
|
|
451
|
+
// Save report
|
|
452
|
+
const reportsDir = join(this.config.agentDir, '.eval-reports');
|
|
453
|
+
const reportPath = join(reportsDir, `${suiteName || 'basic'}-${Date.now()}.json`);
|
|
454
|
+
AgentEvaluator.saveReport(report, reportPath);
|
|
455
|
+
return report;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private async getEvalReports() {
|
|
459
|
+
const reportsDir = join(this.config.agentDir, '.eval-reports');
|
|
460
|
+
if (!existsSync(reportsDir)) return { reports: [] };
|
|
461
|
+
const files = require('fs').readdirSync(reportsDir).filter((f: string) => f.endsWith('.json'));
|
|
462
|
+
return {
|
|
463
|
+
reports: files.map((f: string) => {
|
|
464
|
+
try {
|
|
465
|
+
return JSON.parse(readFileSync(join(reportsDir, f), 'utf-8'));
|
|
466
|
+
} catch { return null; }
|
|
467
|
+
}).filter(Boolean)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// --- A2A Protocol ---
|
|
472
|
+
|
|
473
|
+
private getA2ACard() {
|
|
474
|
+
try {
|
|
475
|
+
const { oadToAgentCard } = require('../protocols/a2a');
|
|
476
|
+
const yaml = require('js-yaml');
|
|
477
|
+
for (const name of ['agent.yaml', 'agent.yml']) {
|
|
478
|
+
const p = join(this.config.agentDir, name);
|
|
479
|
+
if (existsSync(p)) {
|
|
480
|
+
const oad = yaml.load(readFileSync(p, 'utf-8'));
|
|
481
|
+
return oadToAgentCard(oad, `http://localhost:${this.config.port}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return { error: 'No agent.yaml found' };
|
|
485
|
+
} catch { return { error: 'Failed to generate agent card' }; }
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private getA2ATasks() {
|
|
489
|
+
// In-memory tasks from A2A server if running
|
|
490
|
+
return { tasks: [] };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private async discoverA2AAgent(req: IncomingMessage): Promise<any> {
|
|
494
|
+
const body = await this.readBody(req);
|
|
495
|
+
const { url } = JSON.parse(body || '{}');
|
|
496
|
+
if (!url) return { error: 'url required' };
|
|
497
|
+
try {
|
|
498
|
+
const { A2AClient } = require('../protocols/a2a');
|
|
499
|
+
const client = new A2AClient(url);
|
|
500
|
+
return await client.getAgentCard();
|
|
501
|
+
} catch (err: any) {
|
|
502
|
+
return { error: err.message };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// --- Module Proxy & Health ---
|
|
507
|
+
|
|
508
|
+
private proxyToModule(req: IncomingMessage, res: ServerResponse, mod: ModuleInfo, url: URL) {
|
|
509
|
+
const targetPath = url.pathname.slice(`/${mod.path}`.length) || '/';
|
|
510
|
+
const proxyReq = httpRequest(
|
|
511
|
+
{
|
|
512
|
+
hostname: 'localhost',
|
|
513
|
+
port: mod.port,
|
|
514
|
+
path: targetPath + (url.search || ''),
|
|
515
|
+
method: req.method,
|
|
516
|
+
headers: { ...req.headers, host: `localhost:${mod.port}` },
|
|
517
|
+
},
|
|
518
|
+
(proxyRes) => {
|
|
519
|
+
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
|
|
520
|
+
proxyRes.pipe(res, { end: true });
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
proxyReq.on('error', () => {
|
|
524
|
+
res.writeHead(502, { 'Content-Type': 'text/html' });
|
|
525
|
+
res.end(`<html><body style="font-family:system-ui;padding:40px;color:#999;background:#1a1a2e;text-align:center"><h2>${mod.icon} ${mod.name}</h2><p>Module not running on port ${mod.port}</p></body></html>`);
|
|
526
|
+
});
|
|
527
|
+
req.pipe(proxyReq, { end: true });
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private checkPort(port: number): Promise<boolean> {
|
|
531
|
+
return new Promise((resolve) => {
|
|
532
|
+
const sock = new net.Socket();
|
|
533
|
+
sock.setTimeout(500);
|
|
534
|
+
sock.once('connect', () => { sock.destroy(); resolve(true); });
|
|
535
|
+
sock.once('error', () => { sock.destroy(); resolve(false); });
|
|
536
|
+
sock.once('timeout', () => { sock.destroy(); resolve(false); });
|
|
537
|
+
sock.connect(port, 'localhost');
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async getModulesStatus() {
|
|
542
|
+
const modules = await Promise.all(
|
|
543
|
+
MODULE_REGISTRY.map(async (mod) => ({
|
|
544
|
+
name: mod.name,
|
|
545
|
+
path: `/${mod.path}/`,
|
|
546
|
+
port: mod.port,
|
|
547
|
+
icon: mod.icon,
|
|
548
|
+
running: await this.checkPort(mod.port),
|
|
549
|
+
})),
|
|
550
|
+
);
|
|
551
|
+
return { modules };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// --- Helpers ---
|
|
555
|
+
|
|
556
|
+
private loadOAD(): any {
|
|
557
|
+
try {
|
|
558
|
+
const yamlPath = join(this.config.agentDir, 'agent.yaml');
|
|
559
|
+
if (!existsSync(yamlPath)) return null;
|
|
560
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
561
|
+
try {
|
|
562
|
+
const { loadOAD } = require('../index');
|
|
563
|
+
return loadOAD(yamlPath);
|
|
564
|
+
} catch {
|
|
565
|
+
// Fallback: simple yaml parse
|
|
566
|
+
const yaml = require('js-yaml');
|
|
567
|
+
return yaml.load(content);
|
|
568
|
+
}
|
|
569
|
+
} catch {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private loadPackageJson(): any {
|
|
575
|
+
try {
|
|
576
|
+
const pkgPath = join(this.config.agentDir, 'package.json');
|
|
577
|
+
if (!existsSync(pkgPath)) return null;
|
|
578
|
+
return JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
579
|
+
} catch {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
serveStatic(req: IncomingMessage, res: ServerResponse, url: URL) {
|
|
585
|
+
let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
|
|
586
|
+
const fullPath = join(this.config.staticDir, filePath);
|
|
587
|
+
|
|
588
|
+
if (!existsSync(fullPath)) {
|
|
589
|
+
// SPA fallback
|
|
590
|
+
const indexPath = join(this.config.staticDir, 'index.html');
|
|
591
|
+
if (existsSync(indexPath)) {
|
|
592
|
+
const content = readFileSync(indexPath, 'utf-8');
|
|
593
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
594
|
+
res.end(content);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
res.writeHead(404);
|
|
598
|
+
res.end('Not found');
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const mimeTypes: Record<string, string> = {
|
|
603
|
+
'.html': 'text/html',
|
|
604
|
+
'.css': 'text/css',
|
|
605
|
+
'.js': 'application/javascript',
|
|
606
|
+
'.json': 'application/json',
|
|
607
|
+
'.png': 'image/png',
|
|
608
|
+
'.svg': 'image/svg+xml',
|
|
609
|
+
'.ico': 'image/x-icon',
|
|
610
|
+
};
|
|
611
|
+
const ext = extname(fullPath);
|
|
612
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
613
|
+
|
|
614
|
+
const content = readFileSync(fullPath);
|
|
615
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
616
|
+
res.end(content);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private readBody(req: IncomingMessage): Promise<string> {
|
|
620
|
+
return new Promise((resolve, reject) => {
|
|
621
|
+
let body = '';
|
|
622
|
+
req.on('data', (chunk: any) => (body += chunk));
|
|
623
|
+
req.on('end', () => resolve(body));
|
|
624
|
+
req.on('error', reject);
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export { StudioServer, StudioConfig };
|