opc-agent 1.4.0 → 2.0.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 (58) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +91 -32
  3. package/dist/channels/telegram.d.ts +30 -9
  4. package/dist/channels/telegram.js +125 -33
  5. package/dist/cli.js +415 -8
  6. package/dist/core/agent.d.ts +23 -0
  7. package/dist/core/agent.js +120 -3
  8. package/dist/core/runtime.d.ts +1 -0
  9. package/dist/core/runtime.js +44 -0
  10. package/dist/core/scheduler.d.ts +52 -0
  11. package/dist/core/scheduler.js +168 -0
  12. package/dist/core/subagent.d.ts +28 -0
  13. package/dist/core/subagent.js +65 -0
  14. package/dist/daemon.d.ts +3 -0
  15. package/dist/daemon.js +134 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.js +17 -1
  18. package/dist/providers/index.d.ts +5 -1
  19. package/dist/providers/index.js +16 -9
  20. package/dist/schema/oad.d.ts +179 -4
  21. package/dist/schema/oad.js +12 -1
  22. package/dist/skills/auto-learn.d.ts +28 -0
  23. package/dist/skills/auto-learn.js +257 -0
  24. package/dist/tools/builtin/datetime.d.ts +3 -0
  25. package/dist/tools/builtin/datetime.js +44 -0
  26. package/dist/tools/builtin/file.d.ts +3 -0
  27. package/dist/tools/builtin/file.js +151 -0
  28. package/dist/tools/builtin/index.d.ts +15 -0
  29. package/dist/tools/builtin/index.js +30 -0
  30. package/dist/tools/builtin/shell.d.ts +3 -0
  31. package/dist/tools/builtin/shell.js +43 -0
  32. package/dist/tools/builtin/web.d.ts +3 -0
  33. package/dist/tools/builtin/web.js +37 -0
  34. package/dist/tools/mcp-client.d.ts +24 -0
  35. package/dist/tools/mcp-client.js +119 -0
  36. package/package.json +1 -1
  37. package/src/channels/telegram.ts +212 -90
  38. package/src/cli.ts +418 -8
  39. package/src/core/agent.ts +295 -152
  40. package/src/core/runtime.ts +47 -0
  41. package/src/core/scheduler.ts +187 -0
  42. package/src/core/subagent.ts +98 -0
  43. package/src/daemon.ts +96 -0
  44. package/src/index.ts +11 -0
  45. package/src/providers/index.ts +354 -339
  46. package/src/schema/oad.ts +167 -154
  47. package/src/skills/auto-learn.ts +262 -0
  48. package/src/tools/builtin/datetime.ts +41 -0
  49. package/src/tools/builtin/file.ts +107 -0
  50. package/src/tools/builtin/index.ts +28 -0
  51. package/src/tools/builtin/shell.ts +43 -0
  52. package/src/tools/builtin/web.ts +35 -0
  53. package/src/tools/mcp-client.ts +131 -0
  54. package/tests/auto-learn.test.ts +105 -0
  55. package/tests/builtin-tools.test.ts +83 -0
  56. package/tests/cli.test.ts +46 -0
  57. package/tests/subagent.test.ts +130 -0
  58. package/tests/telegram-discord.test.ts +60 -0
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MCPClient = void 0;
4
+ const child_process_1 = require("child_process");
5
+ class MCPClient {
6
+ process = null;
7
+ config = null;
8
+ nextId = 1;
9
+ pending = new Map();
10
+ buffer = '';
11
+ connected = false;
12
+ async connect(config) {
13
+ this.config = config;
14
+ this.process = (0, child_process_1.spawn)(config.command, config.args ?? [], {
15
+ stdio: ['pipe', 'pipe', 'pipe'],
16
+ env: { ...process.env, ...config.env },
17
+ });
18
+ this.process.stdout.on('data', (data) => {
19
+ this.buffer += data.toString();
20
+ this.processBuffer();
21
+ });
22
+ this.process.on('error', (err) => {
23
+ for (const [, p] of this.pending)
24
+ p.reject(err);
25
+ this.pending.clear();
26
+ });
27
+ this.process.on('exit', () => {
28
+ this.connected = false;
29
+ for (const [, p] of this.pending)
30
+ p.reject(new Error('MCP server exited'));
31
+ this.pending.clear();
32
+ });
33
+ // Send initialize
34
+ await this.sendRequest('initialize', {
35
+ protocolVersion: '2024-11-05',
36
+ capabilities: {},
37
+ clientInfo: { name: 'opc-agent', version: '0.7.0' },
38
+ });
39
+ // Send initialized notification
40
+ this.sendNotification('notifications/initialized', {});
41
+ this.connected = true;
42
+ }
43
+ processBuffer() {
44
+ const lines = this.buffer.split('\n');
45
+ this.buffer = lines.pop() ?? '';
46
+ for (const line of lines) {
47
+ const trimmed = line.trim();
48
+ if (!trimmed)
49
+ continue;
50
+ try {
51
+ const msg = JSON.parse(trimmed);
52
+ if (msg.id !== undefined && this.pending.has(msg.id)) {
53
+ const p = this.pending.get(msg.id);
54
+ this.pending.delete(msg.id);
55
+ if (msg.error) {
56
+ p.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
57
+ }
58
+ else {
59
+ p.resolve(msg.result);
60
+ }
61
+ }
62
+ }
63
+ catch { /* skip non-JSON lines */ }
64
+ }
65
+ }
66
+ sendRequest(method, params) {
67
+ return new Promise((resolve, reject) => {
68
+ if (!this.process?.stdin?.writable) {
69
+ reject(new Error('MCP server not connected'));
70
+ return;
71
+ }
72
+ const id = this.nextId++;
73
+ this.pending.set(id, { resolve, reject });
74
+ const msg = JSON.stringify({ jsonrpc: '2.0', method, params: params ?? {}, id });
75
+ this.process.stdin.write(msg + '\n');
76
+ // Timeout after 30s
77
+ setTimeout(() => {
78
+ if (this.pending.has(id)) {
79
+ this.pending.delete(id);
80
+ reject(new Error(`MCP request timed out: ${method}`));
81
+ }
82
+ }, 30000);
83
+ });
84
+ }
85
+ sendNotification(method, params) {
86
+ if (!this.process?.stdin?.writable)
87
+ return;
88
+ const msg = JSON.stringify({ jsonrpc: '2.0', method, params });
89
+ this.process.stdin.write(msg + '\n');
90
+ }
91
+ async listTools() {
92
+ const result = await this.sendRequest('tools/list');
93
+ return (result.tools ?? []).map((t) => ({
94
+ name: t.name,
95
+ description: t.description ?? '',
96
+ inputSchema: t.inputSchema ?? {},
97
+ }));
98
+ }
99
+ async callTool(name, input) {
100
+ const result = await this.sendRequest('tools/call', { name, arguments: input });
101
+ const content = (result.content ?? [])
102
+ .map((c) => c.text ?? JSON.stringify(c))
103
+ .join('\n');
104
+ return { content, isError: result.isError ?? false };
105
+ }
106
+ async disconnect() {
107
+ if (this.process) {
108
+ this.process.kill();
109
+ this.process = null;
110
+ }
111
+ this.connected = false;
112
+ this.pending.clear();
113
+ }
114
+ isConnected() {
115
+ return this.connected;
116
+ }
117
+ }
118
+ exports.MCPClient = MCPClient;
119
+ //# sourceMappingURL=mcp-client.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opc-agent",
3
- "version": "1.4.0",
3
+ "version": "2.0.0",
4
4
  "description": "Open Agent Framework — Build, test, and run AI Agents for business workstations",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,90 +1,212 @@
1
- import type { Message } from '../core/types';
2
- import { BaseChannel } from './index';
3
-
4
- /**
5
- * Telegram channel — basic webhook handler for Telegram Bot API.
6
- * Set TELEGRAM_BOT_TOKEN env var or pass in config.
7
- */
8
- export class TelegramChannel extends BaseChannel {
9
- readonly type = 'telegram';
10
- private token: string;
11
- private webhookUrl?: string;
12
- private server: import('http').Server | null = null;
13
- private port: number;
14
-
15
- constructor(options: { token?: string; webhookUrl?: string; port?: number } = {}) {
16
- super();
17
- this.token = options.token ?? process.env.TELEGRAM_BOT_TOKEN ?? '';
18
- this.webhookUrl = options.webhookUrl;
19
- this.port = options.port ?? 3001;
20
- }
21
-
22
- async start(): Promise<void> {
23
- if (!this.token) {
24
- console.warn('[TelegramChannel] No bot token provided. Set TELEGRAM_BOT_TOKEN or pass token in config.');
25
- return;
26
- }
27
-
28
- const express = (await import('express')).default;
29
- const app = express();
30
- app.use(express.json());
31
-
32
- app.post(`/webhook/${this.token}`, async (req, res) => {
33
- try {
34
- const update = req.body;
35
- if (update.message?.text && this.handler) {
36
- const msg: Message = {
37
- id: `tg_${update.message.message_id}`,
38
- role: 'user',
39
- content: update.message.text,
40
- timestamp: update.message.date * 1000,
41
- metadata: {
42
- sessionId: `tg_${update.message.chat.id}`,
43
- chatId: update.message.chat.id,
44
- userId: update.message.from?.id,
45
- platform: 'telegram',
46
- },
47
- };
48
-
49
- const response = await this.handler(msg);
50
- await this.sendMessage(update.message.chat.id, response.content);
51
- }
52
- res.json({ ok: true });
53
- } catch (err) {
54
- console.error('[TelegramChannel] Error handling update:', err);
55
- res.status(500).json({ error: 'Internal error' });
56
- }
57
- });
58
-
59
- app.get('/health', (_req, res) => {
60
- res.json({ status: 'ok', channel: 'telegram' });
61
- });
62
-
63
- return new Promise((resolve) => {
64
- this.server = app.listen(this.port, () => {
65
- console.log(`[TelegramChannel] Webhook server on port ${this.port}`);
66
- resolve();
67
- });
68
- });
69
- }
70
-
71
- async stop(): Promise<void> {
72
- return new Promise((resolve, reject) => {
73
- if (!this.server) return resolve();
74
- this.server.close((err) => (err ? reject(err) : resolve()));
75
- });
76
- }
77
-
78
- private async sendMessage(chatId: number, text: string): Promise<void> {
79
- const url = `https://api.telegram.org/bot${this.token}/sendMessage`;
80
- try {
81
- await fetch(url, {
82
- method: 'POST',
83
- headers: { 'Content-Type': 'application/json' },
84
- body: JSON.stringify({ chat_id: chatId, text, parse_mode: 'Markdown' }),
85
- });
86
- } catch (err) {
87
- console.error('[TelegramChannel] Failed to send message:', err);
88
- }
89
- }
90
- }
1
+ import type { Message } from '../core/types';
2
+ import { BaseChannel } from './index';
3
+
4
+ /**
5
+ * Telegram channel — supports both long-polling and webhook modes.
6
+ *
7
+ * Config:
8
+ * token: bot token (or TELEGRAM_BOT_TOKEN env var)
9
+ * mode: 'polling' | 'webhook' (default: 'polling')
10
+ * webhookUrl: required for webhook mode
11
+ * port: webhook server port (default: 3001)
12
+ *
13
+ * Polling mode requires no public URL — ideal for dev/local.
14
+ * Webhook mode is more efficient for production.
15
+ */
16
+
17
+ export interface TelegramChannelConfig {
18
+ token?: string;
19
+ mode?: 'polling' | 'webhook';
20
+ webhookUrl?: string;
21
+ port?: number;
22
+ }
23
+
24
+ export class TelegramChannel extends BaseChannel {
25
+ readonly type = 'telegram';
26
+ private token: string;
27
+ private mode: 'polling' | 'webhook';
28
+ private webhookUrl?: string;
29
+ private port: number;
30
+
31
+ // Polling state
32
+ private offset: number = 0;
33
+ private polling: boolean = false;
34
+
35
+ // Webhook state
36
+ private server: import('http').Server | null = null;
37
+
38
+ constructor(config: TelegramChannelConfig = {}) {
39
+ super();
40
+ this.token = config.token ?? process.env.TELEGRAM_BOT_TOKEN ?? '';
41
+ this.mode = config.mode ?? 'polling';
42
+ this.webhookUrl = config.webhookUrl;
43
+ this.port = config.port ?? 3001;
44
+ }
45
+
46
+ async start(): Promise<void> {
47
+ if (!this.token) {
48
+ console.warn('[TelegramChannel] No bot token provided. Set TELEGRAM_BOT_TOKEN or pass token in config.');
49
+ return;
50
+ }
51
+
52
+ if (this.mode === 'webhook') {
53
+ await this.startWebhook();
54
+ } else {
55
+ await this.startPolling();
56
+ }
57
+ }
58
+
59
+ async stop(): Promise<void> {
60
+ if (this.mode === 'webhook') {
61
+ await this.stopWebhook();
62
+ } else {
63
+ this.polling = false;
64
+ }
65
+ }
66
+
67
+ // ─── Polling Mode ────────────────────────────────────────
68
+
69
+ private async startPolling(): Promise<void> {
70
+ // Delete any existing webhook so polling works
71
+ await this.apiCall('deleteWebhook');
72
+ console.log(`[TelegramChannel] Started long-polling mode`);
73
+ this.polling = true;
74
+ this.poll();
75
+ }
76
+
77
+ private async poll(): Promise<void> {
78
+ while (this.polling) {
79
+ try {
80
+ const updates = await this.getUpdates();
81
+ for (const update of updates) {
82
+ await this.processUpdate(update);
83
+ }
84
+ } catch (err) {
85
+ console.error('[TelegramChannel] Polling error:', err);
86
+ // Back off on error
87
+ if (this.polling) {
88
+ await new Promise((r) => setTimeout(r, 5000));
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ private async getUpdates(): Promise<any[]> {
95
+ const url = `https://api.telegram.org/bot${this.token}/getUpdates?offset=${this.offset}&timeout=30&allowed_updates=["message"]`;
96
+ const controller = new AbortController();
97
+ const timeout = setTimeout(() => controller.abort(), 40000); // 30s long-poll + 10s buffer
98
+
99
+ try {
100
+ const res = await fetch(url, { signal: controller.signal });
101
+ const data = (await res.json()) as { ok: boolean; result: any[] };
102
+ if (data.ok && data.result.length > 0) {
103
+ this.offset = data.result[data.result.length - 1].update_id + 1;
104
+ }
105
+ return data.result || [];
106
+ } finally {
107
+ clearTimeout(timeout);
108
+ }
109
+ }
110
+
111
+ // ─── Webhook Mode ────────────────────────────────────────
112
+
113
+ private async startWebhook(): Promise<void> {
114
+ if (this.webhookUrl) {
115
+ await this.apiCall('setWebhook', { url: `${this.webhookUrl}/webhook/${this.token}` });
116
+ }
117
+
118
+ const express = (await import('express')).default;
119
+ const app = express();
120
+ app.use(express.json());
121
+
122
+ app.post(`/webhook/${this.token}`, async (req, res) => {
123
+ try {
124
+ await this.processUpdate(req.body);
125
+ res.json({ ok: true });
126
+ } catch (err) {
127
+ console.error('[TelegramChannel] Webhook error:', err);
128
+ res.status(500).json({ error: 'Internal error' });
129
+ }
130
+ });
131
+
132
+ app.get('/health', (_req, res) => {
133
+ res.json({ status: 'ok', channel: 'telegram', mode: 'webhook' });
134
+ });
135
+
136
+ return new Promise((resolve) => {
137
+ this.server = app.listen(this.port, () => {
138
+ console.log(`[TelegramChannel] Webhook server on port ${this.port}`);
139
+ resolve();
140
+ });
141
+ });
142
+ }
143
+
144
+ private async stopWebhook(): Promise<void> {
145
+ return new Promise((resolve, reject) => {
146
+ if (!this.server) return resolve();
147
+ this.server.close((err) => (err ? reject(err) : resolve()));
148
+ });
149
+ }
150
+
151
+ // ─── Shared ──────────────────────────────────────────────
152
+
153
+ private async processUpdate(update: any): Promise<void> {
154
+ const message = update.message || update.edited_message;
155
+ if (!message?.text || !this.handler) return;
156
+
157
+ const msg: Message = {
158
+ id: `tg_${message.message_id}`,
159
+ role: 'user',
160
+ content: message.text,
161
+ timestamp: message.date * 1000,
162
+ metadata: {
163
+ sessionId: `tg_${message.chat.id}`,
164
+ chatId: message.chat.id,
165
+ userId: message.from?.id,
166
+ username: message.from?.username,
167
+ firstName: message.from?.first_name,
168
+ platform: 'telegram',
169
+ chatType: message.chat.type,
170
+ },
171
+ };
172
+
173
+ const response = await this.handler(msg);
174
+ await this.sendMessage(message.chat.id, response.content);
175
+ }
176
+
177
+ async sendMessage(chatId: number | string, text: string): Promise<void> {
178
+ // Telegram max message length is 4096
179
+ const chunks = this.splitText(text, 4096);
180
+ for (const chunk of chunks) {
181
+ await this.apiCall('sendMessage', {
182
+ chat_id: chatId,
183
+ text: chunk,
184
+ parse_mode: 'Markdown',
185
+ });
186
+ }
187
+ }
188
+
189
+ private async apiCall(method: string, body?: Record<string, unknown>): Promise<any> {
190
+ const url = `https://api.telegram.org/bot${this.token}/${method}`;
191
+ try {
192
+ const res = await fetch(url, {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: body ? JSON.stringify(body) : undefined,
196
+ });
197
+ return await res.json();
198
+ } catch (err) {
199
+ console.error(`[TelegramChannel] API call ${method} failed:`, err);
200
+ throw err;
201
+ }
202
+ }
203
+
204
+ private splitText(text: string, maxLen: number): string[] {
205
+ if (text.length <= maxLen) return [text];
206
+ const parts: string[] = [];
207
+ for (let i = 0; i < text.length; i += maxLen) {
208
+ parts.push(text.slice(i, i + maxLen));
209
+ }
210
+ return parts;
211
+ }
212
+ }