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.
- package/CHANGELOG.md +25 -0
- package/README.md +91 -32
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/cli.js +415 -8
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +120 -3
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +44 -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/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -1
- package/dist/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/schema/oad.d.ts +179 -4
- package/dist/schema/oad.js +12 -1
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -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 +1 -1
- package/src/channels/telegram.ts +212 -90
- package/src/cli.ts +418 -8
- package/src/core/agent.ts +295 -152
- package/src/core/runtime.ts +47 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/daemon.ts +96 -0
- package/src/index.ts +11 -0
- package/src/providers/index.ts +354 -339
- package/src/schema/oad.ts +167 -154
- package/src/skills/auto-learn.ts +262 -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/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/cli.test.ts +46 -0
- package/tests/subagent.test.ts +130 -0
- 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
package/src/channels/telegram.ts
CHANGED
|
@@ -1,90 +1,212 @@
|
|
|
1
|
-
import type { Message } from '../core/types';
|
|
2
|
-
import { BaseChannel } from './index';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Telegram channel —
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
}
|