opc-agent 2.0.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/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/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 +712 -11
- package/dist/core/a2a.d.ts +17 -0
- package/dist/core/a2a.js +43 -1
- package/dist/core/agent.d.ts +16 -0
- package/dist/core/agent.js +108 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +161 -2
- package/dist/core/sandbox.d.ts +26 -0
- package/dist/core/sandbox.js +117 -0
- package/dist/core/workflow-graph.d.ts +93 -0
- package/dist/core/workflow-graph.js +247 -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 +30 -6
- package/dist/index.js +60 -4
- 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/publish/index.d.ts +45 -0
- package/dist/publish/index.js +350 -0
- package/dist/schema/oad.d.ts +682 -65
- package/dist/schema/oad.js +36 -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/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/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/websocket.ts +399 -87
- package/src/channels/wechat.ts +329 -149
- package/src/cli.ts +783 -12
- package/src/core/a2a.ts +60 -0
- package/src/core/agent.ts +125 -0
- package/src/core/runtime.ts +127 -0
- package/src/core/sandbox.ts +143 -0
- package/src/core/workflow-graph.ts +365 -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 +54 -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/publish/index.ts +376 -0
- package/src/schema/oad.ts +39 -2
- package/src/security/approval.ts +131 -0
- package/src/security/index.ts +3 -0
- package/src/security/keys.ts +87 -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/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/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/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 +63 -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
package/dist/channels/email.d.ts
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import { BaseChannel } from './index';
|
|
2
|
-
import type { Message } from '../core/types';
|
|
3
2
|
/**
|
|
4
|
-
* Email Channel —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Email Channel — v1.0.0
|
|
4
|
+
*
|
|
5
|
+
* Supports two modes:
|
|
6
|
+
* - webhook: Receives emails via HTTP POST (works with email forwarding services)
|
|
7
|
+
* - imap: TODO - Full IMAP polling (complex, requires raw socket IMAP protocol)
|
|
8
|
+
*
|
|
9
|
+
* Sends via SMTP with STARTTLS support using Node.js built-in tls module.
|
|
10
|
+
* No external dependencies (no nodemailer).
|
|
7
11
|
*/
|
|
8
12
|
export interface EmailChannelConfig {
|
|
9
|
-
|
|
13
|
+
/** Mode: 'webhook' (recommended) or 'imap' (TODO) */
|
|
14
|
+
mode?: 'webhook' | 'imap';
|
|
15
|
+
smtp?: {
|
|
10
16
|
host: string;
|
|
11
17
|
port: number;
|
|
12
18
|
user: string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/** Mailbox to monitor (default: INBOX) */
|
|
16
|
-
mailbox?: string;
|
|
17
|
-
/** Poll interval in ms (default: 30000) */
|
|
18
|
-
pollInterval?: number;
|
|
19
|
+
pass: string;
|
|
20
|
+
from?: string;
|
|
19
21
|
};
|
|
20
|
-
|
|
22
|
+
imap?: {
|
|
21
23
|
host: string;
|
|
22
24
|
port: number;
|
|
23
25
|
user: string;
|
|
24
26
|
password: string;
|
|
25
27
|
tls?: boolean;
|
|
26
|
-
|
|
28
|
+
mailbox?: string;
|
|
29
|
+
pollInterval?: number;
|
|
27
30
|
};
|
|
28
|
-
/**
|
|
31
|
+
/** Webhook server port (default: 8082) */
|
|
32
|
+
webhookPort?: number;
|
|
33
|
+
/** Filter: only process emails from these addresses */
|
|
29
34
|
filters?: {
|
|
30
35
|
from?: string[];
|
|
31
36
|
subject?: string[];
|
|
@@ -47,23 +52,24 @@ export interface EmailMessage {
|
|
|
47
52
|
export declare class EmailChannel extends BaseChannel {
|
|
48
53
|
type: string;
|
|
49
54
|
private config;
|
|
50
|
-
private
|
|
51
|
-
private running;
|
|
55
|
+
private server;
|
|
52
56
|
private processedIds;
|
|
53
57
|
constructor(config: EmailChannelConfig);
|
|
54
58
|
start(): Promise<void>;
|
|
55
59
|
stop(): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
private
|
|
58
|
-
/**
|
|
60
|
+
/** Start webhook HTTP server */
|
|
61
|
+
private startWebhook;
|
|
62
|
+
/** Parse webhook payload into EmailMessage */
|
|
63
|
+
static parseWebhookPayload(payload: any): EmailMessage | null;
|
|
64
|
+
/** Convert email to internal Message */
|
|
59
65
|
private emailToMessage;
|
|
60
66
|
/** Check if email matches configured filters */
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
|
|
67
|
+
matchesFilters(email: EmailMessage): boolean;
|
|
68
|
+
/** Send email via SMTP with STARTTLS */
|
|
69
|
+
sendEmail(to: string, subject: string, body: string, inReplyTo?: string): Promise<void>;
|
|
70
|
+
/** Raw SMTP send with STARTTLS */
|
|
71
|
+
private smtpSend;
|
|
72
|
+
/** Read request body */
|
|
73
|
+
private readBody;
|
|
68
74
|
}
|
|
69
75
|
//# sourceMappingURL=email.d.ts.map
|
package/dist/channels/email.js
CHANGED
|
@@ -1,54 +1,163 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.EmailChannel = void 0;
|
|
4
37
|
const index_1 = require("./index");
|
|
38
|
+
const http = __importStar(require("http"));
|
|
39
|
+
const net = __importStar(require("net"));
|
|
40
|
+
const tls = __importStar(require("tls"));
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
5
42
|
class EmailChannel extends index_1.BaseChannel {
|
|
6
43
|
type = 'email';
|
|
7
44
|
config;
|
|
8
|
-
|
|
9
|
-
running = false;
|
|
45
|
+
server = null;
|
|
10
46
|
processedIds = new Set();
|
|
11
47
|
constructor(config) {
|
|
12
48
|
super();
|
|
13
49
|
this.config = config;
|
|
14
50
|
}
|
|
15
51
|
async start() {
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.poll().catch(console.error);
|
|
24
|
-
}, interval);
|
|
52
|
+
const mode = this.config.mode ?? 'webhook';
|
|
53
|
+
if (mode === 'webhook') {
|
|
54
|
+
await this.startWebhook();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.warn('[EmailChannel] IMAP mode is not yet implemented. Use webhook mode.');
|
|
58
|
+
}
|
|
25
59
|
}
|
|
26
60
|
async stop() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
if (!this.server)
|
|
63
|
+
return resolve();
|
|
64
|
+
this.server.close((err) => (err ? reject(err) : resolve()));
|
|
65
|
+
this.server = null;
|
|
66
|
+
});
|
|
32
67
|
}
|
|
33
|
-
/**
|
|
34
|
-
async
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.processedIds.add(email.messageId);
|
|
42
|
-
if (!this.matchesFilters(email))
|
|
43
|
-
continue;
|
|
44
|
-
const message = this.emailToMessage(email);
|
|
45
|
-
if (this.handler) {
|
|
46
|
-
const reply = await this.handler(message);
|
|
47
|
-
await this.sendReply(email, reply);
|
|
68
|
+
/** Start webhook HTTP server */
|
|
69
|
+
async startWebhook() {
|
|
70
|
+
const port = this.config.webhookPort ?? 8082;
|
|
71
|
+
this.server = http.createServer(async (req, res) => {
|
|
72
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
73
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
74
|
+
res.end(JSON.stringify({ status: 'ok', channel: 'email', mode: 'webhook' }));
|
|
75
|
+
return;
|
|
48
76
|
}
|
|
49
|
-
|
|
77
|
+
if (req.method === 'POST' && (req.url === '/email/incoming' || req.url === '/')) {
|
|
78
|
+
try {
|
|
79
|
+
const body = await this.readBody(req);
|
|
80
|
+
const parsed = JSON.parse(body);
|
|
81
|
+
const email = EmailChannel.parseWebhookPayload(parsed);
|
|
82
|
+
if (!email) {
|
|
83
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
84
|
+
res.end(JSON.stringify({ error: 'Invalid email payload' }));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Deduplicate
|
|
88
|
+
if (this.processedIds.has(email.messageId)) {
|
|
89
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
90
|
+
res.end(JSON.stringify({ ok: true, duplicate: true }));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.processedIds.add(email.messageId);
|
|
94
|
+
if (this.processedIds.size > 5000) {
|
|
95
|
+
const arr = [...this.processedIds];
|
|
96
|
+
this.processedIds = new Set(arr.slice(-2500));
|
|
97
|
+
}
|
|
98
|
+
// Apply filters
|
|
99
|
+
if (!this.matchesFilters(email)) {
|
|
100
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
101
|
+
res.end(JSON.stringify({ ok: true, filtered: true }));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
105
|
+
res.end(JSON.stringify({ ok: true }));
|
|
106
|
+
// Process async
|
|
107
|
+
if (this.handler) {
|
|
108
|
+
const msg = this.emailToMessage(email);
|
|
109
|
+
try {
|
|
110
|
+
const reply = await this.handler(msg);
|
|
111
|
+
if (this.config.smtp) {
|
|
112
|
+
await this.sendEmail(email.from, `Re: ${email.subject}`, reply.content, email.messageId);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error('[EmailChannel] Handler error:', err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.error('[EmailChannel] Webhook error:', err);
|
|
122
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
123
|
+
res.end(JSON.stringify({ error: 'Internal error' }));
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
res.writeHead(404);
|
|
128
|
+
res.end('Not Found');
|
|
129
|
+
});
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
this.server.listen(port, () => {
|
|
132
|
+
console.log(`[EmailChannel] Webhook listening on port ${port}`);
|
|
133
|
+
resolve();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
50
136
|
}
|
|
51
|
-
/**
|
|
137
|
+
/** Parse webhook payload into EmailMessage */
|
|
138
|
+
static parseWebhookPayload(payload) {
|
|
139
|
+
// Support common email webhook formats (SendGrid, Mailgun, generic)
|
|
140
|
+
const from = payload.from ?? payload.sender ?? payload.envelope?.from;
|
|
141
|
+
const subject = payload.subject ?? '(no subject)';
|
|
142
|
+
const body = payload.body ?? payload.text ?? payload['body-plain'] ?? payload.content ?? '';
|
|
143
|
+
const to = payload.to ?? payload.recipient ?? payload.envelope?.to;
|
|
144
|
+
if (!from)
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
messageId: payload.messageId ?? payload['Message-Id'] ?? payload.id ?? `email-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
|
|
148
|
+
from: typeof from === 'string' ? from : String(from),
|
|
149
|
+
to: Array.isArray(to) ? to : (typeof to === 'string' ? [to] : []),
|
|
150
|
+
cc: payload.cc ? (Array.isArray(payload.cc) ? payload.cc : [payload.cc]) : undefined,
|
|
151
|
+
subject,
|
|
152
|
+
body,
|
|
153
|
+
html: payload.html ?? payload['body-html'],
|
|
154
|
+
date: payload.date ? new Date(payload.date) : new Date(),
|
|
155
|
+
inReplyTo: payload.inReplyTo ?? payload['In-Reply-To'],
|
|
156
|
+
references: payload.references ? (Array.isArray(payload.references) ? payload.references : [payload.references]) : undefined,
|
|
157
|
+
threadId: payload.threadId,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Convert email to internal Message */
|
|
52
161
|
emailToMessage(email) {
|
|
53
162
|
return {
|
|
54
163
|
id: email.messageId,
|
|
@@ -81,37 +190,105 @@ class EmailChannel extends index_1.BaseChannel {
|
|
|
81
190
|
}
|
|
82
191
|
return true;
|
|
83
192
|
}
|
|
84
|
-
/**
|
|
85
|
-
async
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
193
|
+
/** Send email via SMTP with STARTTLS */
|
|
194
|
+
async sendEmail(to, subject, body, inReplyTo) {
|
|
195
|
+
const smtp = this.config.smtp;
|
|
196
|
+
if (!smtp)
|
|
197
|
+
throw new Error('[EmailChannel] SMTP not configured');
|
|
198
|
+
const from = smtp.from ?? smtp.user;
|
|
199
|
+
const messageId = `<${crypto.randomBytes(16).toString('hex')}@opc-agent>`;
|
|
200
|
+
let headers = `From: ${from}\r\n`;
|
|
201
|
+
headers += `To: ${to}\r\n`;
|
|
202
|
+
headers += `Subject: ${subject}\r\n`;
|
|
203
|
+
headers += `Message-ID: ${messageId}\r\n`;
|
|
204
|
+
headers += `Date: ${new Date().toUTCString()}\r\n`;
|
|
205
|
+
if (inReplyTo) {
|
|
206
|
+
headers += `In-Reply-To: ${inReplyTo}\r\n`;
|
|
207
|
+
headers += `References: ${inReplyTo}\r\n`;
|
|
208
|
+
}
|
|
209
|
+
headers += `MIME-Version: 1.0\r\n`;
|
|
210
|
+
headers += `Content-Type: text/plain; charset=UTF-8\r\n`;
|
|
211
|
+
headers += `\r\n`;
|
|
212
|
+
headers += body;
|
|
213
|
+
await this.smtpSend(smtp.host, smtp.port, smtp.user, smtp.pass, from, to, headers);
|
|
92
214
|
}
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
215
|
+
/** Raw SMTP send with STARTTLS */
|
|
216
|
+
smtpSend(host, port, user, pass, from, to, message) {
|
|
217
|
+
return new Promise((resolve, reject) => {
|
|
218
|
+
let socket = net.createConnection(port, host);
|
|
219
|
+
let upgraded = false;
|
|
220
|
+
let step = 0;
|
|
221
|
+
const commands = [
|
|
222
|
+
`EHLO opc-agent\r\n`,
|
|
223
|
+
`STARTTLS\r\n`,
|
|
224
|
+
// After TLS upgrade:
|
|
225
|
+
`EHLO opc-agent\r\n`,
|
|
226
|
+
`AUTH LOGIN\r\n`,
|
|
227
|
+
`${Buffer.from(user).toString('base64')}\r\n`,
|
|
228
|
+
`${Buffer.from(pass).toString('base64')}\r\n`,
|
|
229
|
+
`MAIL FROM:<${from}>\r\n`,
|
|
230
|
+
`RCPT TO:<${to}>\r\n`,
|
|
231
|
+
`DATA\r\n`,
|
|
232
|
+
`${message}\r\n.\r\n`,
|
|
233
|
+
`QUIT\r\n`,
|
|
234
|
+
];
|
|
235
|
+
const sendNext = () => {
|
|
236
|
+
if (step < commands.length) {
|
|
237
|
+
socket.write(commands[step]);
|
|
238
|
+
step++;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
socket.on('data', (data) => {
|
|
242
|
+
const response = data.toString();
|
|
243
|
+
const code = parseInt(response.substring(0, 3), 10);
|
|
244
|
+
if (code >= 400) {
|
|
245
|
+
socket.destroy();
|
|
246
|
+
reject(new Error(`SMTP error: ${response.trim()}`));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// After STARTTLS response (220), upgrade to TLS
|
|
250
|
+
if (step === 2 && !upgraded && response.startsWith('220')) {
|
|
251
|
+
upgraded = true;
|
|
252
|
+
const tlsSocket = tls.connect({ socket, host, servername: host }, () => {
|
|
253
|
+
socket = tlsSocket;
|
|
254
|
+
sendNext(); // EHLO again after TLS
|
|
255
|
+
});
|
|
256
|
+
tlsSocket.on('data', (d) => {
|
|
257
|
+
const resp = d.toString();
|
|
258
|
+
const c = parseInt(resp.substring(0, 3), 10);
|
|
259
|
+
if (c >= 400) {
|
|
260
|
+
tlsSocket.destroy();
|
|
261
|
+
reject(new Error(`SMTP error: ${resp.trim()}`));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (step === commands.length && resp.startsWith('221')) {
|
|
265
|
+
tlsSocket.destroy();
|
|
266
|
+
resolve();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
sendNext();
|
|
270
|
+
});
|
|
271
|
+
tlsSocket.on('error', reject);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
sendNext();
|
|
275
|
+
});
|
|
276
|
+
socket.on('error', reject);
|
|
277
|
+
socket.on('timeout', () => {
|
|
278
|
+
socket.destroy();
|
|
279
|
+
reject(new Error('SMTP connection timeout'));
|
|
280
|
+
});
|
|
281
|
+
socket.setTimeout(30000);
|
|
282
|
+
});
|
|
108
283
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
284
|
+
/** Read request body */
|
|
285
|
+
readBody(req) {
|
|
286
|
+
return new Promise((resolve, reject) => {
|
|
287
|
+
const chunks = [];
|
|
288
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
289
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
290
|
+
req.on('error', reject);
|
|
291
|
+
});
|
|
115
292
|
}
|
|
116
293
|
}
|
|
117
294
|
exports.EmailChannel = EmailChannel;
|
|
@@ -8,11 +8,8 @@ import { BaseChannel } from './index';
|
|
|
8
8
|
* - URL verification challenge
|
|
9
9
|
* - Message card (interactive) responses
|
|
10
10
|
* - Group chat & P2P messaging
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* FEISHU_APP_ID, FEISHU_APP_SECRET — app credentials
|
|
14
|
-
* FEISHU_VERIFICATION_TOKEN — event subscription verification
|
|
15
|
-
* FEISHU_ENCRYPT_KEY — (optional) event encryption key
|
|
11
|
+
* - Event deduplication
|
|
12
|
+
* - No external dependencies (uses Node.js built-in http/https)
|
|
16
13
|
*/
|
|
17
14
|
export interface FeishuChannelConfig {
|
|
18
15
|
/** Feishu App ID */
|
|
@@ -23,7 +20,7 @@ export interface FeishuChannelConfig {
|
|
|
23
20
|
verificationToken?: string;
|
|
24
21
|
/** Encrypt key (optional, for encrypted events) */
|
|
25
22
|
encryptKey?: string;
|
|
26
|
-
/** Webhook server port (default:
|
|
23
|
+
/** Webhook server port (default: 8081) */
|
|
27
24
|
port?: number;
|
|
28
25
|
/** API base URL (use 'https://open.larksuite.com' for Lark international) */
|
|
29
26
|
apiBase?: string;
|
|
@@ -37,11 +34,29 @@ export declare class FeishuChannel extends BaseChannel {
|
|
|
37
34
|
constructor(config?: FeishuChannelConfig);
|
|
38
35
|
start(): Promise<void>;
|
|
39
36
|
stop(): Promise<void>;
|
|
37
|
+
/** Handle Feishu event */
|
|
38
|
+
private handleEvent;
|
|
39
|
+
/** Parse Feishu event body (exported for testing) */
|
|
40
|
+
static parseEventBody(body: any): {
|
|
41
|
+
type: string;
|
|
42
|
+
challenge?: string;
|
|
43
|
+
eventType?: string;
|
|
44
|
+
content?: string;
|
|
45
|
+
chatId?: string;
|
|
46
|
+
senderId?: string;
|
|
47
|
+
messageId?: string;
|
|
48
|
+
};
|
|
40
49
|
/** Get tenant access token (cached) */
|
|
41
50
|
private getAccessToken;
|
|
42
51
|
/** Send a text message to a chat */
|
|
43
52
|
sendTextMessage(chatId: string, text: string): Promise<void>;
|
|
44
53
|
/** Send an interactive card message */
|
|
45
54
|
sendCardMessage(chatId: string, card: Record<string, unknown>): Promise<void>;
|
|
55
|
+
/** Read request body */
|
|
56
|
+
private readBody;
|
|
57
|
+
/** HTTPS POST */
|
|
58
|
+
private httpsPost;
|
|
59
|
+
/** HTTPS POST with Bearer auth */
|
|
60
|
+
private httpsPostWithAuth;
|
|
46
61
|
}
|
|
47
62
|
//# sourceMappingURL=feishu.d.ts.map
|