opc-agent 0.2.0 → 0.3.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/dist/analytics/index.d.ts +31 -0
- package/dist/analytics/index.js +52 -0
- package/dist/cli.js +66 -4
- package/dist/core/room.d.ts +24 -0
- package/dist/core/room.js +97 -0
- package/dist/core/sandbox.d.ts +28 -0
- package/dist/core/sandbox.js +118 -0
- package/dist/i18n/index.d.ts +13 -0
- package/dist/i18n/index.js +73 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +19 -1
- package/dist/plugins/index.d.ts +47 -0
- package/dist/plugins/index.js +59 -0
- package/dist/schema/oad.d.ts +131 -0
- package/dist/schema/oad.js +13 -1
- package/dist/templates/content-writer.d.ts +36 -0
- package/dist/templates/content-writer.js +52 -0
- package/dist/templates/hr-recruiter.d.ts +36 -0
- package/dist/templates/hr-recruiter.js +52 -0
- package/dist/templates/project-manager.d.ts +36 -0
- package/dist/templates/project-manager.js +52 -0
- package/dist/tools/mcp.d.ts +32 -0
- package/dist/tools/mcp.js +49 -0
- package/package.json +1 -1
- package/src/analytics/index.ts +66 -0
- package/src/cli.ts +76 -7
- package/src/core/room.ts +109 -0
- package/src/core/sandbox.ts +101 -0
- package/src/i18n/index.ts +79 -0
- package/src/index.ts +14 -0
- package/src/plugins/index.ts +87 -0
- package/src/schema/oad.ts +14 -0
- package/src/templates/content-writer.ts +58 -0
- package/src/templates/hr-recruiter.ts +58 -0
- package/src/templates/project-manager.ts +58 -0
- package/src/tools/mcp.ts +76 -0
- package/tests/analytics.test.ts +50 -0
- package/tests/i18n.test.ts +41 -0
- package/tests/mcp.test.ts +54 -0
- package/tests/plugin.test.ts +74 -0
- package/tests/room.test.ts +106 -0
- package/tests/sandbox.test.ts +46 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Analytics — track messages, response times, skill usage, errors, tokens.
|
|
3
|
+
*/
|
|
4
|
+
export interface AnalyticsSnapshot {
|
|
5
|
+
messagesProcessed: number;
|
|
6
|
+
avgResponseTimeMs: number;
|
|
7
|
+
skillUsage: Record<string, number>;
|
|
8
|
+
errorCount: number;
|
|
9
|
+
tokenUsage: { input: number; output: number; total: number };
|
|
10
|
+
uptime: number;
|
|
11
|
+
startedAt: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Analytics {
|
|
15
|
+
private messagesProcessed = 0;
|
|
16
|
+
private totalResponseTimeMs = 0;
|
|
17
|
+
private skillUsage: Record<string, number> = {};
|
|
18
|
+
private errorCount = 0;
|
|
19
|
+
private tokenUsage = { input: 0, output: 0 };
|
|
20
|
+
private startedAt = Date.now();
|
|
21
|
+
|
|
22
|
+
recordMessage(responseTimeMs: number): void {
|
|
23
|
+
this.messagesProcessed++;
|
|
24
|
+
this.totalResponseTimeMs += responseTimeMs;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
recordSkillUsage(skillName: string): void {
|
|
28
|
+
this.skillUsage[skillName] = (this.skillUsage[skillName] ?? 0) + 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
recordError(): void {
|
|
32
|
+
this.errorCount++;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
recordTokens(input: number, output: number): void {
|
|
36
|
+
this.tokenUsage.input += input;
|
|
37
|
+
this.tokenUsage.output += output;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getSnapshot(): AnalyticsSnapshot {
|
|
41
|
+
return {
|
|
42
|
+
messagesProcessed: this.messagesProcessed,
|
|
43
|
+
avgResponseTimeMs: this.messagesProcessed > 0
|
|
44
|
+
? Math.round(this.totalResponseTimeMs / this.messagesProcessed)
|
|
45
|
+
: 0,
|
|
46
|
+
skillUsage: { ...this.skillUsage },
|
|
47
|
+
errorCount: this.errorCount,
|
|
48
|
+
tokenUsage: {
|
|
49
|
+
input: this.tokenUsage.input,
|
|
50
|
+
output: this.tokenUsage.output,
|
|
51
|
+
total: this.tokenUsage.input + this.tokenUsage.output,
|
|
52
|
+
},
|
|
53
|
+
uptime: Date.now() - this.startedAt,
|
|
54
|
+
startedAt: this.startedAt,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
reset(): void {
|
|
59
|
+
this.messagesProcessed = 0;
|
|
60
|
+
this.totalResponseTimeMs = 0;
|
|
61
|
+
this.skillUsage = {};
|
|
62
|
+
this.errorCount = 0;
|
|
63
|
+
this.tokenUsage = { input: 0, output: 0 };
|
|
64
|
+
this.startedAt = Date.now();
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -9,12 +9,14 @@ import { createCustomerServiceConfig } from './templates/customer-service';
|
|
|
9
9
|
import { createSalesAssistantConfig } from './templates/sales-assistant';
|
|
10
10
|
import { createKnowledgeBaseConfig } from './templates/knowledge-base';
|
|
11
11
|
import { createCodeReviewerConfig } from './templates/code-reviewer';
|
|
12
|
+
import { createHRRecruiterConfig } from './templates/hr-recruiter';
|
|
13
|
+
import { createProjectManagerConfig } from './templates/project-manager';
|
|
14
|
+
import { createContentWriterConfig } from './templates/content-writer';
|
|
12
15
|
import { FAQSkill, HandoffSkill } from './templates/customer-service';
|
|
16
|
+
import { Analytics } from './analytics';
|
|
13
17
|
|
|
14
18
|
const program = new Command();
|
|
15
19
|
|
|
16
|
-
// ── Colorful output helpers ──────────────────────────────────
|
|
17
|
-
|
|
18
20
|
const color = {
|
|
19
21
|
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
20
22
|
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
@@ -42,6 +44,9 @@ const TEMPLATES: Record<string, { label: string; factory: () => any }> = {
|
|
|
42
44
|
'sales-assistant': { label: 'Sales Assistant — product Q&A + lead capture', factory: createSalesAssistantConfig },
|
|
43
45
|
'knowledge-base': { label: 'Knowledge Base — RAG with DeepBrain', factory: createKnowledgeBaseConfig },
|
|
44
46
|
'code-reviewer': { label: 'Code Reviewer — bug detection + style checks', factory: createCodeReviewerConfig },
|
|
47
|
+
'hr-recruiter': { label: 'HR Recruiter — resume screening + interview scheduling', factory: createHRRecruiterConfig },
|
|
48
|
+
'project-manager': { label: 'Project Manager — task tracking + meeting notes', factory: createProjectManagerConfig },
|
|
49
|
+
'content-writer': { label: 'Content Writer — blog posts + social media + SEO', factory: createContentWriterConfig },
|
|
45
50
|
};
|
|
46
51
|
|
|
47
52
|
async function prompt(question: string, defaultValue?: string): Promise<string> {
|
|
@@ -68,7 +73,7 @@ async function select(question: string, options: { value: string; label: string
|
|
|
68
73
|
program
|
|
69
74
|
.name('opc')
|
|
70
75
|
.description('OPC Agent — Open Agent Framework for business workstations')
|
|
71
|
-
.version('0.
|
|
76
|
+
.version('0.3.0');
|
|
72
77
|
|
|
73
78
|
program
|
|
74
79
|
.command('init')
|
|
@@ -80,7 +85,6 @@ program
|
|
|
80
85
|
console.log(`\n${icon.rocket} ${color.bold('OPC Agent — Create New Project')}\n`);
|
|
81
86
|
|
|
82
87
|
const name = opts.yes ? (nameArg ?? 'my-agent') : (nameArg ?? await prompt('Project name', 'my-agent'));
|
|
83
|
-
|
|
84
88
|
const template = opts.yes
|
|
85
89
|
? (opts.template ?? 'customer-service')
|
|
86
90
|
: (opts.template ?? await select('Select a template:', Object.entries(TEMPLATES).map(([value, { label }]) => ({ value, label }))));
|
|
@@ -92,7 +96,6 @@ program
|
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
fs.mkdirSync(dir, { recursive: true });
|
|
95
|
-
|
|
96
99
|
const factory = TEMPLATES[template]?.factory ?? createCustomerServiceConfig;
|
|
97
100
|
const config = factory();
|
|
98
101
|
config.metadata.name = name;
|
|
@@ -146,6 +149,11 @@ program
|
|
|
146
149
|
console.log(` Channels: ${s.channels.map(c => c.type).join(', ') || color.dim('(none)')}`);
|
|
147
150
|
console.log(` Skills: ${s.skills.map(sk => sk.name).join(', ') || color.dim('(none)')}`);
|
|
148
151
|
console.log(` Trust: ${s.dtv?.trust?.level ?? 'sandbox'}`);
|
|
152
|
+
console.log(` Streaming: ${s.streaming ? 'enabled' : 'disabled'}`);
|
|
153
|
+
console.log(` Locale: ${s.locale ?? 'en'}`);
|
|
154
|
+
if (s.room) {
|
|
155
|
+
console.log(` Room: ${s.room.name}`);
|
|
156
|
+
}
|
|
149
157
|
if (m.marketplace) {
|
|
150
158
|
console.log(` Category: ${m.marketplace.category ?? color.dim('(none)')}`);
|
|
151
159
|
console.log(` Pricing: ${m.marketplace.pricing ?? 'free'}`);
|
|
@@ -239,12 +247,11 @@ program
|
|
|
239
247
|
|
|
240
248
|
await startAgent();
|
|
241
249
|
|
|
242
|
-
// Watch for file changes
|
|
243
250
|
const watchPaths = [opts.file, 'src'];
|
|
244
251
|
for (const watchPath of watchPaths) {
|
|
245
252
|
if (fs.existsSync(watchPath)) {
|
|
246
253
|
const isDir = fs.statSync(watchPath).isDirectory();
|
|
247
|
-
fs.watch(watchPath, { recursive: isDir }, async (
|
|
254
|
+
fs.watch(watchPath, { recursive: isDir }, async (_event, filename) => {
|
|
248
255
|
console.log(`\n${icon.info} ${color.dim(`Change detected: ${filename}`)} — restarting...`);
|
|
249
256
|
await startAgent();
|
|
250
257
|
});
|
|
@@ -312,4 +319,66 @@ program
|
|
|
312
319
|
console.log(`\n Available templates: ${Object.keys(TEMPLATES).map(t => color.cyan(t)).join(', ')}\n`);
|
|
313
320
|
});
|
|
314
321
|
|
|
322
|
+
// ── Tool commands ────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
const toolCmd = program.command('tool').description('Manage MCP tools');
|
|
325
|
+
|
|
326
|
+
toolCmd
|
|
327
|
+
.command('list')
|
|
328
|
+
.description('List available MCP tools')
|
|
329
|
+
.action(() => {
|
|
330
|
+
console.log(`\n${icon.gear} ${color.bold('MCP Tools')}\n`);
|
|
331
|
+
console.log(` ${color.dim('No tools installed yet.')}`);
|
|
332
|
+
console.log(`\n Add tools with: ${color.cyan('opc tool add <name>')}\n`);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
toolCmd
|
|
336
|
+
.command('add')
|
|
337
|
+
.description('Add an MCP tool from registry')
|
|
338
|
+
.argument('<name>', 'Tool name')
|
|
339
|
+
.action((name: string) => {
|
|
340
|
+
console.log(`\n🚧 Tool registry coming soon!`);
|
|
341
|
+
console.log(` Would add tool: ${color.cyan(name)}\n`);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// ── Plugin commands ──────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
const pluginCmd = program.command('plugin').description('Manage plugins');
|
|
347
|
+
|
|
348
|
+
pluginCmd
|
|
349
|
+
.command('list')
|
|
350
|
+
.description('List installed plugins')
|
|
351
|
+
.action(() => {
|
|
352
|
+
console.log(`\n${icon.gear} ${color.bold('Installed Plugins')}\n`);
|
|
353
|
+
console.log(` ${color.dim('No plugins installed yet.')}`);
|
|
354
|
+
console.log(`\n Add plugins with: ${color.cyan('opc plugin add <name>')}\n`);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
pluginCmd
|
|
358
|
+
.command('add')
|
|
359
|
+
.description('Add a plugin')
|
|
360
|
+
.argument('<name>', 'Plugin name')
|
|
361
|
+
.action((name: string) => {
|
|
362
|
+
console.log(`\n🚧 Plugin registry coming soon!`);
|
|
363
|
+
console.log(` Would add plugin: ${color.cyan(name)}\n`);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// ── Stats command ────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
program
|
|
369
|
+
.command('stats')
|
|
370
|
+
.description('Show agent analytics')
|
|
371
|
+
.action(() => {
|
|
372
|
+
const analytics = new Analytics();
|
|
373
|
+
// Show demo stats
|
|
374
|
+
const snap = analytics.getSnapshot();
|
|
375
|
+
console.log(`\n${icon.gear} ${color.bold('Agent Analytics')}\n`);
|
|
376
|
+
console.log(` Messages Processed: ${snap.messagesProcessed}`);
|
|
377
|
+
console.log(` Avg Response Time: ${snap.avgResponseTimeMs}ms`);
|
|
378
|
+
console.log(` Error Count: ${snap.errorCount}`);
|
|
379
|
+
console.log(` Token Usage: ${snap.tokenUsage.total} (in: ${snap.tokenUsage.input}, out: ${snap.tokenUsage.output})`);
|
|
380
|
+
console.log(` Uptime: ${Math.round(snap.uptime / 1000)}s`);
|
|
381
|
+
console.log(`\n ${color.dim('Run an agent to collect analytics data.')}\n`);
|
|
382
|
+
});
|
|
383
|
+
|
|
315
384
|
program.parse();
|
package/src/core/room.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Message, IAgent } from './types';
|
|
3
|
+
|
|
4
|
+
export interface RoomMessage {
|
|
5
|
+
from: string;
|
|
6
|
+
to?: string; // undefined = broadcast
|
|
7
|
+
topic?: string;
|
|
8
|
+
message: Message;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Room extends EventEmitter {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
private agents: Map<string, IAgent> = new Map();
|
|
14
|
+
private subscriptions: Map<string, Set<string>> = new Map(); // topic -> agentNames
|
|
15
|
+
|
|
16
|
+
constructor(name: string) {
|
|
17
|
+
super();
|
|
18
|
+
this.name = name;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
addAgent(agent: IAgent): void {
|
|
22
|
+
this.agents.set(agent.name, agent);
|
|
23
|
+
this.emit('agent:join', agent.name);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
removeAgent(name: string): void {
|
|
27
|
+
this.agents.delete(name);
|
|
28
|
+
// Remove from all subscriptions
|
|
29
|
+
for (const [, subscribers] of this.subscriptions) {
|
|
30
|
+
subscribers.delete(name);
|
|
31
|
+
}
|
|
32
|
+
this.emit('agent:leave', name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getAgents(): string[] {
|
|
36
|
+
return Array.from(this.agents.keys());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
subscribe(agentName: string, topic: string): void {
|
|
40
|
+
if (!this.subscriptions.has(topic)) {
|
|
41
|
+
this.subscriptions.set(topic, new Set());
|
|
42
|
+
}
|
|
43
|
+
this.subscriptions.get(topic)!.add(agentName);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
unsubscribe(agentName: string, topic: string): void {
|
|
47
|
+
this.subscriptions.get(topic)?.delete(agentName);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getSubscribers(topic: string): string[] {
|
|
51
|
+
return Array.from(this.subscriptions.get(topic) ?? []);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async send(roomMessage: RoomMessage): Promise<Message[]> {
|
|
55
|
+
const responses: Message[] = [];
|
|
56
|
+
this.emit('message', roomMessage);
|
|
57
|
+
|
|
58
|
+
if (roomMessage.to) {
|
|
59
|
+
// Direct message
|
|
60
|
+
const agent = this.agents.get(roomMessage.to);
|
|
61
|
+
if (agent) {
|
|
62
|
+
const response = await agent.handleMessage(roomMessage.message);
|
|
63
|
+
responses.push(response);
|
|
64
|
+
}
|
|
65
|
+
} else if (roomMessage.topic) {
|
|
66
|
+
// Topic-based pub/sub
|
|
67
|
+
const subscribers = this.subscriptions.get(roomMessage.topic) ?? new Set();
|
|
68
|
+
for (const name of subscribers) {
|
|
69
|
+
if (name === roomMessage.from) continue;
|
|
70
|
+
const agent = this.agents.get(name);
|
|
71
|
+
if (agent) {
|
|
72
|
+
const response = await agent.handleMessage(roomMessage.message);
|
|
73
|
+
responses.push(response);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// Broadcast to all except sender
|
|
78
|
+
for (const [name, agent] of this.agents) {
|
|
79
|
+
if (name === roomMessage.from) continue;
|
|
80
|
+
const response = await agent.handleMessage(roomMessage.message);
|
|
81
|
+
responses.push(response);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return responses;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async broadcast(from: string, content: string): Promise<Message[]> {
|
|
89
|
+
const message: Message = {
|
|
90
|
+
id: `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
91
|
+
role: 'user',
|
|
92
|
+
content,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
metadata: { room: this.name, from },
|
|
95
|
+
};
|
|
96
|
+
return this.send({ from, message });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async publishToTopic(from: string, topic: string, content: string): Promise<Message[]> {
|
|
100
|
+
const message: Message = {
|
|
101
|
+
id: `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
102
|
+
role: 'user',
|
|
103
|
+
content,
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
metadata: { room: this.name, from, topic },
|
|
106
|
+
};
|
|
107
|
+
return this.send({ from, topic, message });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { TrustLevelType } from '../schema/oad';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export interface SandboxConfig {
|
|
5
|
+
trustLevel: TrustLevelType;
|
|
6
|
+
agentDir: string;
|
|
7
|
+
networkAllowlist?: string[];
|
|
8
|
+
shellAllowed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SandboxRestrictions {
|
|
12
|
+
fileSystem: { read: string[]; write: string[] };
|
|
13
|
+
network: { allowed: string[] };
|
|
14
|
+
shell: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TRUST_RESTRICTIONS: Record<string, SandboxRestrictions> = {
|
|
18
|
+
sandbox: {
|
|
19
|
+
fileSystem: { read: ['.'], write: ['.'] },
|
|
20
|
+
network: { allowed: [] },
|
|
21
|
+
shell: false,
|
|
22
|
+
},
|
|
23
|
+
verified: {
|
|
24
|
+
fileSystem: { read: ['.', '..'], write: ['.'] },
|
|
25
|
+
network: { allowed: ['*.deepleaper.com', 'api.openai.com', 'api.deepseek.com'] },
|
|
26
|
+
shell: false,
|
|
27
|
+
},
|
|
28
|
+
certified: {
|
|
29
|
+
fileSystem: { read: ['*'], write: ['.', '..'] },
|
|
30
|
+
network: { allowed: ['*'] },
|
|
31
|
+
shell: true,
|
|
32
|
+
},
|
|
33
|
+
listed: {
|
|
34
|
+
fileSystem: { read: ['*'], write: ['*'] },
|
|
35
|
+
network: { allowed: ['*'] },
|
|
36
|
+
shell: true,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export class Sandbox {
|
|
41
|
+
private config: SandboxConfig;
|
|
42
|
+
private restrictions: SandboxRestrictions;
|
|
43
|
+
|
|
44
|
+
constructor(config: SandboxConfig) {
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.restrictions = {
|
|
47
|
+
...TRUST_RESTRICTIONS[config.trustLevel] ?? TRUST_RESTRICTIONS.sandbox,
|
|
48
|
+
};
|
|
49
|
+
if (config.networkAllowlist) {
|
|
50
|
+
this.restrictions.network.allowed = config.networkAllowlist;
|
|
51
|
+
}
|
|
52
|
+
if (config.shellAllowed !== undefined) {
|
|
53
|
+
this.restrictions.shell = config.shellAllowed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get trustLevel(): TrustLevelType {
|
|
58
|
+
return this.config.trustLevel;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getRestrictions(): SandboxRestrictions {
|
|
62
|
+
return { ...this.restrictions };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
checkFileAccess(filePath: string, mode: 'read' | 'write'): boolean {
|
|
66
|
+
const resolved = path.resolve(filePath);
|
|
67
|
+
const agentDir = path.resolve(this.config.agentDir);
|
|
68
|
+
const allowedPaths = mode === 'read' ? this.restrictions.fileSystem.read : this.restrictions.fileSystem.write;
|
|
69
|
+
|
|
70
|
+
if (allowedPaths.includes('*')) return true;
|
|
71
|
+
|
|
72
|
+
for (const allowed of allowedPaths) {
|
|
73
|
+
const allowedResolved = path.resolve(this.config.agentDir, allowed);
|
|
74
|
+
if (resolved.startsWith(allowedResolved)) return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Always allow access within agent's own directory
|
|
78
|
+
return resolved.startsWith(agentDir);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
checkNetworkAccess(url: string): boolean {
|
|
82
|
+
if (this.restrictions.network.allowed.includes('*')) return true;
|
|
83
|
+
if (this.restrictions.network.allowed.length === 0) return false;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const hostname = new URL(url).hostname;
|
|
87
|
+
return this.restrictions.network.allowed.some((pattern) => {
|
|
88
|
+
if (pattern.startsWith('*.')) {
|
|
89
|
+
return hostname.endsWith(pattern.slice(1));
|
|
90
|
+
}
|
|
91
|
+
return hostname === pattern;
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
checkShellAccess(): boolean {
|
|
99
|
+
return this.restrictions.shell;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization (i18n) support for OPC Agent.
|
|
3
|
+
*/
|
|
4
|
+
export type Locale = 'en' | 'zh-CN';
|
|
5
|
+
|
|
6
|
+
export interface I18nMessages {
|
|
7
|
+
[key: string]: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const messages: Record<Locale, I18nMessages> = {
|
|
11
|
+
'en': {
|
|
12
|
+
'agent.started': 'Agent "{name}" started successfully',
|
|
13
|
+
'agent.stopped': 'Agent "{name}" stopped',
|
|
14
|
+
'agent.error': 'An error occurred: {error}',
|
|
15
|
+
'agent.greeting': 'Hello! How can I help you?',
|
|
16
|
+
'agent.farewell': 'Goodbye! Have a great day.',
|
|
17
|
+
'agent.notUnderstood': 'I\'m not sure I understand. Could you rephrase?',
|
|
18
|
+
'agent.handoff': 'Let me connect you with a human agent.',
|
|
19
|
+
'cli.init.success': 'Created agent project: {name}',
|
|
20
|
+
'cli.build.success': 'Build successful: {name} v{version}',
|
|
21
|
+
'cli.test.pass': 'All tests passed',
|
|
22
|
+
'cli.stats.title': 'Agent Analytics',
|
|
23
|
+
'cli.stats.messages': 'Messages Processed',
|
|
24
|
+
'cli.stats.avgTime': 'Avg Response Time',
|
|
25
|
+
'cli.stats.errors': 'Errors',
|
|
26
|
+
'cli.stats.uptime': 'Uptime',
|
|
27
|
+
'plugin.loaded': 'Plugin "{name}" loaded',
|
|
28
|
+
'plugin.error': 'Plugin "{name}" failed: {error}',
|
|
29
|
+
},
|
|
30
|
+
'zh-CN': {
|
|
31
|
+
'agent.started': '智能体 "{name}" 启动成功',
|
|
32
|
+
'agent.stopped': '智能体 "{name}" 已停止',
|
|
33
|
+
'agent.error': '发生错误: {error}',
|
|
34
|
+
'agent.greeting': '您好!有什么可以帮您的?',
|
|
35
|
+
'agent.farewell': '再见!祝您愉快。',
|
|
36
|
+
'agent.notUnderstood': '抱歉,我没有理解您的意思。能换个方式描述吗?',
|
|
37
|
+
'agent.handoff': '我来为您转接人工客服。',
|
|
38
|
+
'cli.init.success': '已创建智能体项目: {name}',
|
|
39
|
+
'cli.build.success': '构建成功: {name} v{version}',
|
|
40
|
+
'cli.test.pass': '所有测试通过',
|
|
41
|
+
'cli.stats.title': '智能体分析',
|
|
42
|
+
'cli.stats.messages': '已处理消息',
|
|
43
|
+
'cli.stats.avgTime': '平均响应时间',
|
|
44
|
+
'cli.stats.errors': '错误数',
|
|
45
|
+
'cli.stats.uptime': '运行时间',
|
|
46
|
+
'plugin.loaded': '插件 "{name}" 已加载',
|
|
47
|
+
'plugin.error': '插件 "{name}" 失败: {error}',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let currentLocale: Locale = 'en';
|
|
52
|
+
|
|
53
|
+
export function setLocale(locale: Locale): void {
|
|
54
|
+
currentLocale = locale;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getLocale(): Locale {
|
|
58
|
+
return currentLocale;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function t(key: string, params?: Record<string, string>): string {
|
|
62
|
+
let msg = messages[currentLocale]?.[key] ?? messages['en']?.[key] ?? key;
|
|
63
|
+
if (params) {
|
|
64
|
+
for (const [k, v] of Object.entries(params)) {
|
|
65
|
+
msg = msg.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return msg;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function detectLocale(): Locale {
|
|
72
|
+
const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LANGUAGE ?? '';
|
|
73
|
+
if (env.startsWith('zh')) return 'zh-CN';
|
|
74
|
+
return 'en';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function addMessages(locale: Locale, newMessages: I18nMessages): void {
|
|
78
|
+
messages[locale] = { ...messages[locale], ...newMessages };
|
|
79
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,3 +18,17 @@ export { TrustManager } from './dtv/trust';
|
|
|
18
18
|
export { ValueTracker } from './dtv/value';
|
|
19
19
|
export { MRGConfigReader } from './dtv/data';
|
|
20
20
|
export { createProvider, SUPPORTED_PROVIDERS } from './providers';
|
|
21
|
+
|
|
22
|
+
// v0.3.0 new modules
|
|
23
|
+
export { Room } from './core/room';
|
|
24
|
+
export type { RoomMessage } from './core/room';
|
|
25
|
+
export { MCPToolRegistry, createMCPTool } from './tools/mcp';
|
|
26
|
+
export type { MCPTool, MCPToolDefinition, MCPToolResult } from './tools/mcp';
|
|
27
|
+
export { PluginManager } from './plugins';
|
|
28
|
+
export type { IPlugin, PluginHooks } from './plugins';
|
|
29
|
+
export { Sandbox } from './core/sandbox';
|
|
30
|
+
export type { SandboxConfig, SandboxRestrictions } from './core/sandbox';
|
|
31
|
+
export { Analytics } from './analytics';
|
|
32
|
+
export type { AnalyticsSnapshot } from './analytics';
|
|
33
|
+
export { t, setLocale, getLocale, detectLocale, addMessages } from './i18n';
|
|
34
|
+
export type { Locale } from './i18n';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ISkill, IChannel } from '../core/types';
|
|
2
|
+
import type { MCPTool } from '../tools/mcp';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plugin lifecycle hooks.
|
|
6
|
+
*/
|
|
7
|
+
export interface PluginHooks {
|
|
8
|
+
beforeInit?: () => Promise<void>;
|
|
9
|
+
afterInit?: () => Promise<void>;
|
|
10
|
+
beforeMessage?: (message: { content: string }) => Promise<void>;
|
|
11
|
+
afterMessage?: (message: { content: string }, response: { content: string }) => Promise<void>;
|
|
12
|
+
beforeShutdown?: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Plugin interface — extend agent with skills, tools, channels, and lifecycle hooks.
|
|
17
|
+
*/
|
|
18
|
+
export interface IPlugin {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
hooks?: PluginHooks;
|
|
23
|
+
skills?: ISkill[];
|
|
24
|
+
tools?: MCPTool[];
|
|
25
|
+
channels?: IChannel[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PluginManager {
|
|
29
|
+
private plugins: Map<string, IPlugin> = new Map();
|
|
30
|
+
|
|
31
|
+
register(plugin: IPlugin): void {
|
|
32
|
+
this.plugins.set(plugin.name, plugin);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
unregister(name: string): void {
|
|
36
|
+
this.plugins.delete(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(name: string): IPlugin | undefined {
|
|
40
|
+
return this.plugins.get(name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
list(): { name: string; version: string; description?: string }[] {
|
|
44
|
+
return Array.from(this.plugins.values()).map(({ name, version, description }) => ({
|
|
45
|
+
name,
|
|
46
|
+
version,
|
|
47
|
+
description,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
has(name: string): boolean {
|
|
52
|
+
return this.plugins.has(name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async runHook(hookName: keyof PluginHooks, ...args: unknown[]): Promise<void> {
|
|
56
|
+
for (const plugin of this.plugins.values()) {
|
|
57
|
+
const hook = plugin.hooks?.[hookName];
|
|
58
|
+
if (hook) {
|
|
59
|
+
await (hook as (...a: unknown[]) => Promise<void>)(...args);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getAllSkills(): ISkill[] {
|
|
65
|
+
const skills: ISkill[] = [];
|
|
66
|
+
for (const plugin of this.plugins.values()) {
|
|
67
|
+
if (plugin.skills) skills.push(...plugin.skills);
|
|
68
|
+
}
|
|
69
|
+
return skills;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getAllTools(): MCPTool[] {
|
|
73
|
+
const tools: MCPTool[] = [];
|
|
74
|
+
for (const plugin of this.plugins.values()) {
|
|
75
|
+
if (plugin.tools) tools.push(...plugin.tools);
|
|
76
|
+
}
|
|
77
|
+
return tools;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getAllChannels(): IChannel[] {
|
|
81
|
+
const channels: IChannel[] = [];
|
|
82
|
+
for (const plugin of this.plugins.values()) {
|
|
83
|
+
if (plugin.channels) channels.push(...plugin.channels);
|
|
84
|
+
}
|
|
85
|
+
return channels;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/schema/oad.ts
CHANGED
|
@@ -58,6 +58,17 @@ export const MetadataSchema = z.object({
|
|
|
58
58
|
marketplace: MarketplaceSchema.optional(),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
export const RoomSchema = z.object({
|
|
62
|
+
name: z.string(),
|
|
63
|
+
agents: z.array(z.string()).default([]),
|
|
64
|
+
topics: z.array(z.string()).default([]),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const StreamingSchema = z.object({
|
|
68
|
+
enabled: z.boolean().default(false),
|
|
69
|
+
chunkSize: z.number().optional(),
|
|
70
|
+
});
|
|
71
|
+
|
|
61
72
|
export const SpecSchema = z.object({
|
|
62
73
|
provider: ProviderSchema.optional(),
|
|
63
74
|
model: z.string().default('deepseek-chat'),
|
|
@@ -66,6 +77,9 @@ export const SpecSchema = z.object({
|
|
|
66
77
|
channels: z.array(ChannelSchema).default([]),
|
|
67
78
|
memory: MemorySchema.optional(),
|
|
68
79
|
dtv: DTVSchema.optional(),
|
|
80
|
+
room: RoomSchema.optional(),
|
|
81
|
+
streaming: z.union([z.boolean(), StreamingSchema]).default(false),
|
|
82
|
+
locale: z.enum(['en', 'zh-CN']).optional(),
|
|
69
83
|
});
|
|
70
84
|
|
|
71
85
|
export const OADSchema = z.object({
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BaseSkill } from '../skills/base';
|
|
2
|
+
import type { AgentContext, Message, SkillResult } from '../core/types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class BlogWriterSkill extends BaseSkill {
|
|
6
|
+
name = 'blog-writer';
|
|
7
|
+
description = 'Help write blog posts with SEO optimization';
|
|
8
|
+
|
|
9
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
10
|
+
const lower = message.content.toLowerCase();
|
|
11
|
+
if (lower.includes('blog') || lower.includes('article') || lower.includes('post')) {
|
|
12
|
+
return this.match(
|
|
13
|
+
'I can help write blog posts! Please share the topic, target audience, and any keywords you\'d like to include for SEO.',
|
|
14
|
+
0.8,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return this.noMatch();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class SocialMediaSkill extends BaseSkill {
|
|
22
|
+
name = 'social-media';
|
|
23
|
+
description = 'Create social media content';
|
|
24
|
+
|
|
25
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
26
|
+
const lower = message.content.toLowerCase();
|
|
27
|
+
if (lower.includes('social') || lower.includes('tweet') || lower.includes('linkedin')) {
|
|
28
|
+
return this.match(
|
|
29
|
+
'I can create social media content! Tell me the platform (Twitter/LinkedIn/etc.), topic, and tone you prefer.',
|
|
30
|
+
0.8,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return this.noMatch();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createContentWriterConfig() {
|
|
38
|
+
return {
|
|
39
|
+
apiVersion: 'opc/v1',
|
|
40
|
+
kind: 'Agent',
|
|
41
|
+
metadata: {
|
|
42
|
+
name: 'content-writer',
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
description: 'Content Writer — blog posts, social media, SEO optimization',
|
|
45
|
+
author: 'Deepleaper',
|
|
46
|
+
license: 'Apache-2.0',
|
|
47
|
+
},
|
|
48
|
+
spec: {
|
|
49
|
+
model: 'deepseek-chat',
|
|
50
|
+
systemPrompt: 'You are a content writing assistant. Help create blog posts, social media content, and optimize for SEO. Be creative, engaging, and audience-aware.',
|
|
51
|
+
skills: [
|
|
52
|
+
{ name: 'blog-writer', description: 'Write blog posts' },
|
|
53
|
+
{ name: 'social-media', description: 'Create social media content' },
|
|
54
|
+
],
|
|
55
|
+
channels: [{ type: 'web', port: 3000 }],
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|