opc-agent 2.0.0 → 2.0.2

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 (157) hide show
  1. package/README.md +545 -365
  2. package/dist/channels/email.d.ts +32 -26
  3. package/dist/channels/email.js +239 -62
  4. package/dist/channels/feishu.d.ts +21 -6
  5. package/dist/channels/feishu.js +225 -126
  6. package/dist/channels/websocket.d.ts +46 -3
  7. package/dist/channels/websocket.js +306 -37
  8. package/dist/channels/wechat.d.ts +33 -13
  9. package/dist/channels/wechat.js +229 -42
  10. package/dist/cli.js +712 -11
  11. package/dist/core/a2a.d.ts +17 -0
  12. package/dist/core/a2a.js +43 -1
  13. package/dist/core/agent.d.ts +16 -0
  14. package/dist/core/agent.js +108 -0
  15. package/dist/core/runtime.d.ts +6 -0
  16. package/dist/core/runtime.js +161 -2
  17. package/dist/core/sandbox.d.ts +26 -0
  18. package/dist/core/sandbox.js +117 -0
  19. package/dist/core/workflow-graph.d.ts +93 -0
  20. package/dist/core/workflow-graph.js +247 -0
  21. package/dist/doctor.d.ts +15 -0
  22. package/dist/doctor.js +183 -0
  23. package/dist/eval/index.d.ts +65 -0
  24. package/dist/eval/index.js +191 -0
  25. package/dist/index.d.ts +32 -6
  26. package/dist/index.js +63 -4
  27. package/dist/plugins/content-filter.d.ts +7 -0
  28. package/dist/plugins/content-filter.js +25 -0
  29. package/dist/plugins/index.d.ts +42 -0
  30. package/dist/plugins/index.js +108 -2
  31. package/dist/plugins/logger.d.ts +6 -0
  32. package/dist/plugins/logger.js +20 -0
  33. package/dist/plugins/rate-limiter.d.ts +7 -0
  34. package/dist/plugins/rate-limiter.js +35 -0
  35. package/dist/protocols/a2a/client.d.ts +25 -0
  36. package/dist/protocols/a2a/client.js +115 -0
  37. package/dist/protocols/a2a/index.d.ts +6 -0
  38. package/dist/protocols/a2a/index.js +12 -0
  39. package/dist/protocols/a2a/server.d.ts +41 -0
  40. package/dist/protocols/a2a/server.js +295 -0
  41. package/dist/protocols/a2a/types.d.ts +91 -0
  42. package/dist/protocols/a2a/types.js +15 -0
  43. package/dist/protocols/a2a/utils.d.ts +6 -0
  44. package/dist/protocols/a2a/utils.js +47 -0
  45. package/dist/protocols/agui/client.d.ts +10 -0
  46. package/dist/protocols/agui/client.js +75 -0
  47. package/dist/protocols/agui/index.d.ts +4 -0
  48. package/dist/protocols/agui/index.js +25 -0
  49. package/dist/protocols/agui/server.d.ts +37 -0
  50. package/dist/protocols/agui/server.js +191 -0
  51. package/dist/protocols/agui/types.d.ts +107 -0
  52. package/dist/protocols/agui/types.js +17 -0
  53. package/dist/protocols/index.d.ts +2 -0
  54. package/dist/protocols/index.js +19 -0
  55. package/dist/protocols/mcp/agent-tools.d.ts +11 -0
  56. package/dist/protocols/mcp/agent-tools.js +129 -0
  57. package/dist/protocols/mcp/index.d.ts +5 -0
  58. package/dist/protocols/mcp/index.js +11 -0
  59. package/dist/protocols/mcp/server.d.ts +31 -0
  60. package/dist/protocols/mcp/server.js +248 -0
  61. package/dist/protocols/mcp/types.d.ts +92 -0
  62. package/dist/protocols/mcp/types.js +17 -0
  63. package/dist/publish/index.d.ts +45 -0
  64. package/dist/publish/index.js +350 -0
  65. package/dist/schema/oad.d.ts +682 -65
  66. package/dist/schema/oad.js +36 -3
  67. package/dist/security/approval.d.ts +36 -0
  68. package/dist/security/approval.js +113 -0
  69. package/dist/security/index.d.ts +4 -0
  70. package/dist/security/index.js +8 -0
  71. package/dist/security/keys.d.ts +16 -0
  72. package/dist/security/keys.js +117 -0
  73. package/dist/studio/server.d.ts +63 -0
  74. package/dist/studio/server.js +625 -0
  75. package/dist/studio-ui/index.html +662 -0
  76. package/dist/telemetry/index.d.ts +93 -0
  77. package/dist/telemetry/index.js +285 -0
  78. package/package.json +5 -3
  79. package/scripts/install.ps1 +31 -0
  80. package/scripts/install.sh +40 -0
  81. package/src/channels/email.ts +351 -177
  82. package/src/channels/feishu.ts +349 -236
  83. package/src/channels/websocket.ts +399 -87
  84. package/src/channels/wechat.ts +329 -149
  85. package/src/cli.ts +783 -12
  86. package/src/core/a2a.ts +60 -0
  87. package/src/core/agent.ts +125 -0
  88. package/src/core/runtime.ts +127 -0
  89. package/src/core/sandbox.ts +143 -0
  90. package/src/core/workflow-graph.ts +365 -0
  91. package/src/doctor.ts +156 -0
  92. package/src/eval/index.ts +211 -0
  93. package/src/eval/suites/basic.json +16 -0
  94. package/src/eval/suites/memory.json +12 -0
  95. package/src/eval/suites/safety.json +14 -0
  96. package/src/index.ts +58 -6
  97. package/src/plugins/content-filter.ts +23 -0
  98. package/src/plugins/index.ts +133 -2
  99. package/src/plugins/logger.ts +18 -0
  100. package/src/plugins/rate-limiter.ts +38 -0
  101. package/src/protocols/a2a/client.ts +132 -0
  102. package/src/protocols/a2a/index.ts +8 -0
  103. package/src/protocols/a2a/server.ts +333 -0
  104. package/src/protocols/a2a/types.ts +88 -0
  105. package/src/protocols/a2a/utils.ts +50 -0
  106. package/src/protocols/agui/client.ts +83 -0
  107. package/src/protocols/agui/index.ts +4 -0
  108. package/src/protocols/agui/server.ts +218 -0
  109. package/src/protocols/agui/types.ts +153 -0
  110. package/src/protocols/index.ts +2 -0
  111. package/src/protocols/mcp/agent-tools.ts +134 -0
  112. package/src/protocols/mcp/index.ts +8 -0
  113. package/src/protocols/mcp/server.ts +262 -0
  114. package/src/protocols/mcp/types.ts +69 -0
  115. package/src/publish/index.ts +376 -0
  116. package/src/schema/oad.ts +39 -2
  117. package/src/security/approval.ts +131 -0
  118. package/src/security/index.ts +3 -0
  119. package/src/security/keys.ts +87 -0
  120. package/src/studio/server.ts +629 -0
  121. package/src/studio-ui/index.html +662 -0
  122. package/src/telemetry/index.ts +324 -0
  123. package/src/types/agent-workstation.d.ts +2 -0
  124. package/tests/a2a-protocol.test.ts +285 -0
  125. package/tests/agui-protocol.test.ts +246 -0
  126. package/tests/channels/discord.test.ts +79 -0
  127. package/tests/channels/email.test.ts +148 -0
  128. package/tests/channels/feishu.test.ts +123 -0
  129. package/tests/channels/telegram.test.ts +129 -0
  130. package/tests/channels/websocket.test.ts +53 -0
  131. package/tests/channels/wechat.test.ts +170 -0
  132. package/tests/chat-cli.test.ts +160 -0
  133. package/tests/daemon.test.ts +135 -0
  134. package/tests/deepbrain-wire.test.ts +234 -0
  135. package/tests/doctor.test.ts +38 -0
  136. package/tests/eval.test.ts +173 -0
  137. package/tests/init-role.test.ts +124 -0
  138. package/tests/mcp-client.test.ts +92 -0
  139. package/tests/mcp-server.test.ts +178 -0
  140. package/tests/plugin-a2a-enhanced.test.ts +230 -0
  141. package/tests/publish.test.ts +231 -0
  142. package/tests/scheduler.test.ts +200 -0
  143. package/tests/security-enhanced.test.ts +233 -0
  144. package/tests/skill-learner.test.ts +161 -0
  145. package/tests/studio.test.ts +229 -0
  146. package/tests/subagent.test.ts +63 -0
  147. package/tests/telemetry.test.ts +186 -0
  148. package/tests/tools/builtin-extended.test.ts +138 -0
  149. package/tests/workflow-graph.test.ts +279 -0
  150. package/tutorial/customer-service-agent/README.md +612 -0
  151. package/tutorial/customer-service-agent/SOUL.md +26 -0
  152. package/tutorial/customer-service-agent/agent.yaml +63 -0
  153. package/tutorial/customer-service-agent/package.json +19 -0
  154. package/tutorial/customer-service-agent/src/index.ts +69 -0
  155. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
  156. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
  157. package/tutorial/customer-service-agent/tsconfig.json +14 -0
@@ -1,31 +1,36 @@
1
1
  import { BaseChannel } from './index';
2
- import type { Message } from '../core/types';
3
2
  /**
4
- * Email Channel — v0.8.0
5
- * IMAP polling for incoming emails, SMTP for sending replies.
6
- * Parses email threads as conversations.
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
- imap: {
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
- password: string;
14
- tls?: boolean;
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
- smtp: {
22
+ imap?: {
21
23
  host: string;
22
24
  port: number;
23
25
  user: string;
24
26
  password: string;
25
27
  tls?: boolean;
26
- from: string;
28
+ mailbox?: string;
29
+ pollInterval?: number;
27
30
  };
28
- /** Filter: only process emails matching these patterns */
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 pollTimer;
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
- /** Poll IMAP for new emails */
57
- private poll;
58
- /** Convert email to Message format */
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
- private matchesFilters;
62
- /** Fetch emails via IMAP stub for actual implementation */
63
- private fetchEmails;
64
- /** Send reply via SMTP stub for actual implementation */
65
- sendReply(originalEmail: EmailMessage, reply: Message): Promise<void>;
66
- /** Send a standalone email */
67
- send(to: string, subject: string, body: string): Promise<void>;
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
@@ -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
- pollTimer = null;
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.running = true;
17
- const interval = this.config.imap.pollInterval ?? 30000;
18
- // Initial poll
19
- await this.poll();
20
- // Set up recurring poll
21
- this.pollTimer = setInterval(() => {
22
- if (this.running)
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
- this.running = false;
28
- if (this.pollTimer) {
29
- clearInterval(this.pollTimer);
30
- this.pollTimer = null;
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
- /** Poll IMAP for new emails */
34
- async poll() {
35
- // In production, use a library like `imapflow` or `imap-simple`
36
- // This is the integration point — actual IMAP connection logic goes here
37
- const emails = await this.fetchEmails();
38
- for (const email of emails) {
39
- if (this.processedIds.has(email.messageId))
40
- continue;
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
- /** Convert email to Message format */
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
- /** Fetch emails via IMAP stub for actual implementation */
85
- async fetchEmails() {
86
- // TODO: Implement with imapflow or similar library
87
- // const { ImapFlow } = await import('imapflow');
88
- // const client = new ImapFlow({ ...this.config.imap });
89
- // await client.connect();
90
- // ...
91
- return [];
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
- /** Send reply via SMTP stub for actual implementation */
94
- async sendReply(originalEmail, reply) {
95
- // TODO: Implement with nodemailer or similar library
96
- // const nodemailer = await import('nodemailer');
97
- // const transport = nodemailer.createTransport({ ...this.config.smtp });
98
- // await transport.sendMail({
99
- // from: this.config.smtp.from,
100
- // to: originalEmail.from,
101
- // subject: `Re: ${originalEmail.subject}`,
102
- // text: reply.content,
103
- // inReplyTo: originalEmail.messageId,
104
- // references: [originalEmail.messageId],
105
- // });
106
- void originalEmail;
107
- void reply;
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
- /** Send a standalone email */
110
- async send(to, subject, body) {
111
- void to;
112
- void subject;
113
- void body;
114
- // TODO: Implement with nodemailer
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
- * Env vars:
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: 3002) */
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