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.
Files changed (42) hide show
  1. package/dist/analytics/index.d.ts +31 -0
  2. package/dist/analytics/index.js +52 -0
  3. package/dist/cli.js +66 -4
  4. package/dist/core/room.d.ts +24 -0
  5. package/dist/core/room.js +97 -0
  6. package/dist/core/sandbox.d.ts +28 -0
  7. package/dist/core/sandbox.js +118 -0
  8. package/dist/i18n/index.d.ts +13 -0
  9. package/dist/i18n/index.js +73 -0
  10. package/dist/index.d.ts +12 -0
  11. package/dist/index.js +19 -1
  12. package/dist/plugins/index.d.ts +47 -0
  13. package/dist/plugins/index.js +59 -0
  14. package/dist/schema/oad.d.ts +131 -0
  15. package/dist/schema/oad.js +13 -1
  16. package/dist/templates/content-writer.d.ts +36 -0
  17. package/dist/templates/content-writer.js +52 -0
  18. package/dist/templates/hr-recruiter.d.ts +36 -0
  19. package/dist/templates/hr-recruiter.js +52 -0
  20. package/dist/templates/project-manager.d.ts +36 -0
  21. package/dist/templates/project-manager.js +52 -0
  22. package/dist/tools/mcp.d.ts +32 -0
  23. package/dist/tools/mcp.js +49 -0
  24. package/package.json +1 -1
  25. package/src/analytics/index.ts +66 -0
  26. package/src/cli.ts +76 -7
  27. package/src/core/room.ts +109 -0
  28. package/src/core/sandbox.ts +101 -0
  29. package/src/i18n/index.ts +79 -0
  30. package/src/index.ts +14 -0
  31. package/src/plugins/index.ts +87 -0
  32. package/src/schema/oad.ts +14 -0
  33. package/src/templates/content-writer.ts +58 -0
  34. package/src/templates/hr-recruiter.ts +58 -0
  35. package/src/templates/project-manager.ts +58 -0
  36. package/src/tools/mcp.ts +76 -0
  37. package/tests/analytics.test.ts +50 -0
  38. package/tests/i18n.test.ts +41 -0
  39. package/tests/mcp.test.ts +54 -0
  40. package/tests/plugin.test.ts +74 -0
  41. package/tests/room.test.ts +106 -0
  42. 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.2.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 (event, filename) => {
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();
@@ -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
+ }