opc-agent 4.1.0 → 4.1.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/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
- package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
- package/CHANGELOG.md +48 -48
- package/CONTRIBUTING.md +36 -36
- package/README.zh-CN.md +497 -497
- package/dist/channels/wechat.js +6 -6
- package/dist/deploy/index.js +56 -56
- package/dist/studio/server.js +30 -1
- package/dist/studio-ui/index.html +230 -10
- package/dist/ui/components.js +105 -105
- package/examples/README.md +22 -22
- package/examples/basic-agent.ts +90 -90
- package/examples/brain-integration.ts +71 -71
- package/examples/multi-channel.ts +74 -74
- package/fix-sidebar.mjs +188 -188
- package/install.ps1 +154 -154
- package/install.sh +164 -164
- package/package.json +1 -1
- package/scripts/install.ps1 +31 -31
- package/scripts/install.sh +40 -40
- package/serve-studio.js +13 -13
- package/serve-test.js +25 -25
- package/src/channels/dingtalk.ts +46 -46
- package/src/channels/email.ts +351 -351
- package/src/channels/feishu.ts +349 -349
- package/src/channels/googlechat.ts +42 -42
- package/src/channels/imessage.ts +31 -31
- package/src/channels/irc.ts +82 -82
- package/src/channels/line.ts +32 -32
- package/src/channels/matrix.ts +33 -33
- package/src/channels/mattermost.ts +57 -57
- package/src/channels/msteams.ts +32 -32
- package/src/channels/nostr.ts +32 -32
- package/src/channels/qq.ts +33 -33
- package/src/channels/signal.ts +32 -32
- package/src/channels/sms.ts +33 -33
- package/src/channels/telegram.ts +616 -616
- package/src/channels/twitch.ts +65 -65
- package/src/channels/voice-call.ts +100 -100
- package/src/channels/websocket.ts +399 -399
- package/src/channels/wechat.ts +329 -329
- package/src/channels/whatsapp.ts +32 -32
- package/src/cli/chat.ts +99 -99
- package/src/cli/setup.ts +314 -314
- package/src/core/agent.ts +476 -476
- package/src/core/api-server.ts +277 -277
- package/src/core/audio.ts +98 -98
- package/src/core/collaboration.ts +275 -275
- package/src/core/context-discovery.ts +85 -85
- package/src/core/context-refs.ts +140 -140
- package/src/core/gateway.ts +106 -106
- package/src/core/heartbeat.ts +51 -51
- package/src/core/hooks.ts +105 -105
- package/src/core/ide-bridge.ts +133 -133
- package/src/core/node-network.ts +86 -86
- package/src/core/profiles.ts +122 -122
- package/src/core/scheduler.ts +187 -187
- package/src/core/session-manager.ts +137 -137
- package/src/core/subagent.ts +98 -98
- package/src/core/vision.ts +180 -180
- package/src/core/workflow-graph.ts +365 -365
- package/src/daemon.ts +96 -96
- package/src/deploy/index.ts +255 -255
- package/src/doctor.ts +156 -156
- package/src/eval/index.ts +211 -211
- package/src/eval/suites/basic.json +16 -16
- package/src/eval/suites/memory.json +12 -12
- package/src/eval/suites/safety.json +14 -14
- package/src/hub/brain-seed.ts +54 -54
- package/src/hub/client.ts +60 -60
- package/src/mcp/servers/calculator-mcp.ts +65 -65
- package/src/mcp/servers/crypto-mcp.ts +73 -73
- package/src/mcp/servers/database-mcp.ts +72 -72
- package/src/mcp/servers/datetime-mcp.ts +69 -69
- package/src/mcp/servers/filesystem.ts +66 -66
- package/src/mcp/servers/github-mcp.ts +58 -58
- package/src/mcp/servers/index.ts +63 -63
- package/src/mcp/servers/json-mcp.ts +102 -102
- package/src/mcp/servers/memory-mcp.ts +56 -56
- package/src/mcp/servers/regex-mcp.ts +53 -53
- package/src/mcp/servers/web-mcp.ts +49 -49
- package/src/memory/context-compressor.ts +189 -189
- package/src/memory/seed-loader.ts +212 -212
- package/src/memory/user-profiler.ts +215 -215
- package/src/plugins/content-filter.ts +23 -23
- package/src/plugins/logger.ts +18 -18
- package/src/plugins/rate-limiter.ts +38 -38
- package/src/protocols/a2a/client.ts +132 -132
- package/src/protocols/a2a/index.ts +8 -8
- package/src/protocols/a2a/server.ts +333 -333
- package/src/protocols/a2a/types.ts +88 -88
- package/src/protocols/a2a/utils.ts +50 -50
- package/src/protocols/agui/client.ts +83 -83
- package/src/protocols/agui/index.ts +4 -4
- package/src/protocols/agui/server.ts +218 -218
- package/src/protocols/agui/types.ts +153 -153
- package/src/protocols/index.ts +2 -2
- package/src/protocols/mcp/agent-tools.ts +134 -134
- package/src/protocols/mcp/index.ts +8 -8
- package/src/protocols/mcp/server.ts +262 -262
- package/src/protocols/mcp/types.ts +69 -69
- package/src/providers/index.ts +632 -632
- package/src/publish/index.ts +376 -376
- package/src/scheduler/cron-engine.ts +191 -191
- package/src/scheduler/index.ts +2 -2
- package/src/schema/oad.ts +217 -217
- package/src/security/approval.ts +131 -131
- package/src/security/approvals.ts +143 -143
- package/src/security/elevated.ts +105 -105
- package/src/security/guardrails.ts +248 -248
- package/src/security/index.ts +9 -9
- package/src/security/keys.ts +87 -87
- package/src/security/secrets.ts +129 -129
- package/src/skills/builtin/index.ts +408 -408
- package/src/skills/marketplace.ts +113 -113
- package/src/skills/types.ts +42 -42
- package/src/studio/server.ts +31 -1
- package/src/studio/templates-data.ts +178 -178
- package/src/studio-ui/index.html +230 -10
- package/src/telemetry/index.ts +324 -324
- package/src/tools/builtin/browser.ts +299 -299
- package/src/tools/builtin/datetime.ts +41 -41
- package/src/tools/builtin/file.ts +107 -107
- package/src/tools/builtin/home-assistant.ts +116 -116
- package/src/tools/builtin/rl-tools.ts +243 -243
- package/src/tools/builtin/shell.ts +43 -43
- package/src/tools/builtin/vision.ts +64 -64
- package/src/tools/builtin/web-search.ts +126 -126
- package/src/tools/builtin/web.ts +35 -35
- package/src/tools/document-processor.ts +213 -213
- package/src/tools/image-generator.ts +150 -150
- package/src/tools/integrations/calendar.ts +73 -73
- package/src/tools/integrations/code-exec.ts +39 -39
- package/src/tools/integrations/csv-analyzer.ts +92 -92
- package/src/tools/integrations/database.ts +44 -44
- package/src/tools/integrations/email-send.ts +76 -76
- package/src/tools/integrations/git-tool.ts +42 -42
- package/src/tools/integrations/github-tool.ts +76 -76
- package/src/tools/integrations/image-gen.ts +56 -56
- package/src/tools/integrations/index.ts +92 -92
- package/src/tools/integrations/jira.ts +83 -83
- package/src/tools/integrations/notion.ts +71 -71
- package/src/tools/integrations/npm-tool.ts +48 -48
- package/src/tools/integrations/pdf-reader.ts +58 -58
- package/src/tools/integrations/slack.ts +65 -65
- package/src/tools/integrations/summarizer.ts +49 -49
- package/src/tools/integrations/translator.ts +48 -48
- package/src/tools/integrations/trello.ts +60 -60
- package/src/tools/integrations/vector-search.ts +42 -42
- package/src/tools/integrations/web-scraper.ts +47 -47
- package/src/tools/integrations/web-search.ts +58 -58
- package/src/tools/integrations/webhook.ts +38 -38
- package/src/tools/mcp-client.ts +131 -131
- package/src/tools/web-scraper.ts +179 -179
- package/src/tools/web-search.ts +180 -180
- package/src/ui/components.ts +127 -127
- package/srv-out.txt +1 -1
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/test-agent/Dockerfile +9 -9
- package/test-agent/README.md +50 -50
- package/test-agent/agent.yaml +23 -23
- package/test-agent/docker-compose.yml +11 -11
- package/test-agent/oad.yaml +31 -31
- package/test-agent/package-lock.json +1492 -1492
- package/test-agent/package.json +17 -17
- package/test-agent/src/index.ts +24 -24
- package/test-agent/src/skills/echo.ts +15 -15
- package/test-agent/tsconfig.json +24 -24
- package/test-full.js +43 -43
- package/test-sidebar.js +22 -22
- package/test-studio3.js +75 -75
- package/test-studio4.js +41 -41
- package/tests/a2a-protocol.test.ts +285 -285
- package/tests/agui-protocol.test.ts +246 -246
- package/tests/api-server.test.ts +148 -148
- package/tests/approvals.test.ts +89 -89
- package/tests/audio.test.ts +40 -40
- package/tests/brain-seed-extended.test.ts +490 -490
- package/tests/brain-seed.test.ts +239 -239
- package/tests/browser.test.ts +179 -179
- package/tests/channels/discord.test.ts +79 -79
- package/tests/channels/email.test.ts +148 -148
- package/tests/channels/feishu.test.ts +123 -123
- package/tests/channels/telegram.test.ts +129 -129
- package/tests/channels/websocket.test.ts +53 -53
- package/tests/channels/wechat.test.ts +170 -170
- package/tests/channels-extra.test.ts +45 -45
- package/tests/chat-cli.test.ts +160 -160
- package/tests/cli.test.ts +46 -46
- package/tests/context-compressor.test.ts +172 -172
- package/tests/context-refs.test.ts +121 -121
- package/tests/cron-engine.test.ts +101 -101
- package/tests/daemon.test.ts +135 -135
- package/tests/deepbrain-wire.test.ts +234 -234
- package/tests/deploy-and-dag.test.ts +196 -196
- package/tests/doctor.test.ts +38 -38
- package/tests/document-processor.test.ts +69 -69
- package/tests/e2e-nocode.test.ts +442 -442
- package/tests/elevated.test.ts +69 -69
- package/tests/eval.test.ts +173 -173
- package/tests/gateway.test.ts +63 -63
- package/tests/guardrails.test.ts +177 -177
- package/tests/home-assistant.test.ts +40 -40
- package/tests/hooks.test.ts +79 -79
- package/tests/ide-bridge.test.ts +38 -38
- package/tests/image-generator.test.ts +84 -84
- package/tests/init-role.test.ts +124 -124
- package/tests/integrations.test.ts +249 -249
- package/tests/mcp-client.test.ts +92 -92
- package/tests/mcp-server.test.ts +178 -178
- package/tests/mcp-servers.test.ts +260 -260
- package/tests/node-network.test.ts +74 -74
- package/tests/plugin-a2a-enhanced.test.ts +230 -230
- package/tests/profiles.test.ts +61 -61
- package/tests/publish.test.ts +231 -231
- package/tests/rl-tools.test.ts +93 -93
- package/tests/sandbox-manager.test.ts +46 -46
- package/tests/scheduler.test.ts +200 -200
- package/tests/secrets.test.ts +107 -107
- package/tests/security-enhanced.test.ts +233 -233
- package/tests/settings-api.test.ts +148 -148
- package/tests/setup.test.ts +73 -73
- package/tests/subagent.test.ts +193 -193
- package/tests/telegram-discord.test.ts +60 -60
- package/tests/telemetry.test.ts +186 -186
- package/tests/user-profiler.test.ts +169 -169
- package/tests/v090-features.test.ts +254 -254
- package/tests/vision.test.ts +61 -61
- package/tests/voice-call.test.ts +47 -47
- package/tests/voice-enhanced.test.ts +169 -169
- package/tests/voice-interaction.test.ts +38 -38
- package/tests/web-search.test.ts +155 -155
- package/tests/workflow-graph.test.ts +279 -279
- package/tutorial/customer-service-agent/README.md +612 -612
- package/tutorial/customer-service-agent/SOUL.md +26 -26
- package/tutorial/customer-service-agent/agent.yaml +63 -63
- package/tutorial/customer-service-agent/package.json +19 -19
- package/tutorial/customer-service-agent/src/index.ts +69 -69
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
- package/tutorial/customer-service-agent/tsconfig.json +14 -14
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { EmailChannel } from '../../src/channels/email';
|
|
3
|
-
|
|
4
|
-
describe('EmailChannel', () => {
|
|
5
|
-
// ── Webhook Payload Parsing ──────────────────────
|
|
6
|
-
|
|
7
|
-
describe('parseWebhookPayload', () => {
|
|
8
|
-
it('should parse standard payload', () => {
|
|
9
|
-
const payload = {
|
|
10
|
-
from: 'alice@example.com',
|
|
11
|
-
to: ['bob@example.com'],
|
|
12
|
-
subject: 'Test Email',
|
|
13
|
-
body: 'Hello from email',
|
|
14
|
-
messageId: '<msg123@example.com>',
|
|
15
|
-
date: '2026-01-15T10:00:00Z',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const email = EmailChannel.parseWebhookPayload(payload);
|
|
19
|
-
expect(email).not.toBeNull();
|
|
20
|
-
expect(email!.from).toBe('alice@example.com');
|
|
21
|
-
expect(email!.to).toEqual(['bob@example.com']);
|
|
22
|
-
expect(email!.subject).toBe('Test Email');
|
|
23
|
-
expect(email!.body).toBe('Hello from email');
|
|
24
|
-
expect(email!.messageId).toBe('<msg123@example.com>');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should parse SendGrid-style payload', () => {
|
|
28
|
-
const payload = {
|
|
29
|
-
from: 'sender@test.com',
|
|
30
|
-
to: 'recipient@test.com',
|
|
31
|
-
subject: 'SG Test',
|
|
32
|
-
text: 'Body from SendGrid',
|
|
33
|
-
html: '<p>Body from SendGrid</p>',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const email = EmailChannel.parseWebhookPayload(payload);
|
|
37
|
-
expect(email).not.toBeNull();
|
|
38
|
-
expect(email!.body).toBe('Body from SendGrid');
|
|
39
|
-
expect(email!.html).toBe('<p>Body from SendGrid</p>');
|
|
40
|
-
expect(email!.to).toEqual(['recipient@test.com']);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should parse Mailgun-style payload', () => {
|
|
44
|
-
const payload = {
|
|
45
|
-
sender: 'mg@example.com',
|
|
46
|
-
recipient: 'user@example.com',
|
|
47
|
-
subject: 'MG Test',
|
|
48
|
-
'body-plain': 'Plain text body',
|
|
49
|
-
'body-html': '<p>HTML body</p>',
|
|
50
|
-
'Message-Id': '<mg123@mailgun>',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const email = EmailChannel.parseWebhookPayload(payload);
|
|
54
|
-
expect(email).not.toBeNull();
|
|
55
|
-
expect(email!.from).toBe('mg@example.com');
|
|
56
|
-
expect(email!.body).toBe('Plain text body');
|
|
57
|
-
expect(email!.messageId).toBe('<mg123@mailgun>');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should return null for payload without from', () => {
|
|
61
|
-
const email = EmailChannel.parseWebhookPayload({ subject: 'No sender' });
|
|
62
|
-
expect(email).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should handle envelope format', () => {
|
|
66
|
-
const payload = {
|
|
67
|
-
envelope: { from: 'env@test.com', to: ['a@b.com'] },
|
|
68
|
-
subject: 'Envelope test',
|
|
69
|
-
body: 'content',
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const email = EmailChannel.parseWebhookPayload(payload);
|
|
73
|
-
expect(email).not.toBeNull();
|
|
74
|
-
expect(email!.from).toBe('env@test.com');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should generate messageId if missing', () => {
|
|
78
|
-
const payload = { from: 'a@b.com', body: 'test' };
|
|
79
|
-
const email = EmailChannel.parseWebhookPayload(payload);
|
|
80
|
-
expect(email).not.toBeNull();
|
|
81
|
-
expect(email!.messageId).toMatch(/^email-/);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should handle inReplyTo and references', () => {
|
|
85
|
-
const payload = {
|
|
86
|
-
from: 'a@b.com',
|
|
87
|
-
body: 'reply',
|
|
88
|
-
inReplyTo: '<orig@b.com>',
|
|
89
|
-
references: ['<orig@b.com>', '<prev@b.com>'],
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const email = EmailChannel.parseWebhookPayload(payload);
|
|
93
|
-
expect(email!.inReplyTo).toBe('<orig@b.com>');
|
|
94
|
-
expect(email!.references).toEqual(['<orig@b.com>', '<prev@b.com>']);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// ── Filter Matching ──────────────────────────────
|
|
99
|
-
|
|
100
|
-
describe('matchesFilters', () => {
|
|
101
|
-
it('should match all when no filters', () => {
|
|
102
|
-
const channel = new EmailChannel({ mode: 'webhook' });
|
|
103
|
-
const result = channel.matchesFilters({
|
|
104
|
-
messageId: 'x', from: 'a@b.com', to: [], subject: 'test', body: '', date: new Date(),
|
|
105
|
-
});
|
|
106
|
-
expect(result).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should filter by from address', () => {
|
|
110
|
-
const channel = new EmailChannel({
|
|
111
|
-
mode: 'webhook',
|
|
112
|
-
filters: { from: ['allowed@example.com'] },
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(channel.matchesFilters({
|
|
116
|
-
messageId: 'x', from: 'allowed@example.com', to: [], subject: '', body: '', date: new Date(),
|
|
117
|
-
})).toBe(true);
|
|
118
|
-
|
|
119
|
-
expect(channel.matchesFilters({
|
|
120
|
-
messageId: 'x', from: 'blocked@other.com', to: [], subject: '', body: '', date: new Date(),
|
|
121
|
-
})).toBe(false);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should filter by subject', () => {
|
|
125
|
-
const channel = new EmailChannel({
|
|
126
|
-
mode: 'webhook',
|
|
127
|
-
filters: { subject: ['[SUPPORT]'] },
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
expect(channel.matchesFilters({
|
|
131
|
-
messageId: 'x', from: 'a@b.com', to: [], subject: '[SUPPORT] Help me', body: '', date: new Date(),
|
|
132
|
-
})).toBe(true);
|
|
133
|
-
|
|
134
|
-
expect(channel.matchesFilters({
|
|
135
|
-
messageId: 'x', from: 'a@b.com', to: [], subject: 'Random email', body: '', date: new Date(),
|
|
136
|
-
})).toBe(false);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// ── Constructor ──────────────────────────────────
|
|
141
|
-
|
|
142
|
-
describe('constructor', () => {
|
|
143
|
-
it('should create with webhook mode', () => {
|
|
144
|
-
const channel = new EmailChannel({ mode: 'webhook', webhookPort: 9090 });
|
|
145
|
-
expect(channel.type).toBe('email');
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { EmailChannel } from '../../src/channels/email';
|
|
3
|
+
|
|
4
|
+
describe('EmailChannel', () => {
|
|
5
|
+
// ── Webhook Payload Parsing ──────────────────────
|
|
6
|
+
|
|
7
|
+
describe('parseWebhookPayload', () => {
|
|
8
|
+
it('should parse standard payload', () => {
|
|
9
|
+
const payload = {
|
|
10
|
+
from: 'alice@example.com',
|
|
11
|
+
to: ['bob@example.com'],
|
|
12
|
+
subject: 'Test Email',
|
|
13
|
+
body: 'Hello from email',
|
|
14
|
+
messageId: '<msg123@example.com>',
|
|
15
|
+
date: '2026-01-15T10:00:00Z',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const email = EmailChannel.parseWebhookPayload(payload);
|
|
19
|
+
expect(email).not.toBeNull();
|
|
20
|
+
expect(email!.from).toBe('alice@example.com');
|
|
21
|
+
expect(email!.to).toEqual(['bob@example.com']);
|
|
22
|
+
expect(email!.subject).toBe('Test Email');
|
|
23
|
+
expect(email!.body).toBe('Hello from email');
|
|
24
|
+
expect(email!.messageId).toBe('<msg123@example.com>');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should parse SendGrid-style payload', () => {
|
|
28
|
+
const payload = {
|
|
29
|
+
from: 'sender@test.com',
|
|
30
|
+
to: 'recipient@test.com',
|
|
31
|
+
subject: 'SG Test',
|
|
32
|
+
text: 'Body from SendGrid',
|
|
33
|
+
html: '<p>Body from SendGrid</p>',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const email = EmailChannel.parseWebhookPayload(payload);
|
|
37
|
+
expect(email).not.toBeNull();
|
|
38
|
+
expect(email!.body).toBe('Body from SendGrid');
|
|
39
|
+
expect(email!.html).toBe('<p>Body from SendGrid</p>');
|
|
40
|
+
expect(email!.to).toEqual(['recipient@test.com']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should parse Mailgun-style payload', () => {
|
|
44
|
+
const payload = {
|
|
45
|
+
sender: 'mg@example.com',
|
|
46
|
+
recipient: 'user@example.com',
|
|
47
|
+
subject: 'MG Test',
|
|
48
|
+
'body-plain': 'Plain text body',
|
|
49
|
+
'body-html': '<p>HTML body</p>',
|
|
50
|
+
'Message-Id': '<mg123@mailgun>',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const email = EmailChannel.parseWebhookPayload(payload);
|
|
54
|
+
expect(email).not.toBeNull();
|
|
55
|
+
expect(email!.from).toBe('mg@example.com');
|
|
56
|
+
expect(email!.body).toBe('Plain text body');
|
|
57
|
+
expect(email!.messageId).toBe('<mg123@mailgun>');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return null for payload without from', () => {
|
|
61
|
+
const email = EmailChannel.parseWebhookPayload({ subject: 'No sender' });
|
|
62
|
+
expect(email).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle envelope format', () => {
|
|
66
|
+
const payload = {
|
|
67
|
+
envelope: { from: 'env@test.com', to: ['a@b.com'] },
|
|
68
|
+
subject: 'Envelope test',
|
|
69
|
+
body: 'content',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const email = EmailChannel.parseWebhookPayload(payload);
|
|
73
|
+
expect(email).not.toBeNull();
|
|
74
|
+
expect(email!.from).toBe('env@test.com');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should generate messageId if missing', () => {
|
|
78
|
+
const payload = { from: 'a@b.com', body: 'test' };
|
|
79
|
+
const email = EmailChannel.parseWebhookPayload(payload);
|
|
80
|
+
expect(email).not.toBeNull();
|
|
81
|
+
expect(email!.messageId).toMatch(/^email-/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle inReplyTo and references', () => {
|
|
85
|
+
const payload = {
|
|
86
|
+
from: 'a@b.com',
|
|
87
|
+
body: 'reply',
|
|
88
|
+
inReplyTo: '<orig@b.com>',
|
|
89
|
+
references: ['<orig@b.com>', '<prev@b.com>'],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const email = EmailChannel.parseWebhookPayload(payload);
|
|
93
|
+
expect(email!.inReplyTo).toBe('<orig@b.com>');
|
|
94
|
+
expect(email!.references).toEqual(['<orig@b.com>', '<prev@b.com>']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── Filter Matching ──────────────────────────────
|
|
99
|
+
|
|
100
|
+
describe('matchesFilters', () => {
|
|
101
|
+
it('should match all when no filters', () => {
|
|
102
|
+
const channel = new EmailChannel({ mode: 'webhook' });
|
|
103
|
+
const result = channel.matchesFilters({
|
|
104
|
+
messageId: 'x', from: 'a@b.com', to: [], subject: 'test', body: '', date: new Date(),
|
|
105
|
+
});
|
|
106
|
+
expect(result).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should filter by from address', () => {
|
|
110
|
+
const channel = new EmailChannel({
|
|
111
|
+
mode: 'webhook',
|
|
112
|
+
filters: { from: ['allowed@example.com'] },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(channel.matchesFilters({
|
|
116
|
+
messageId: 'x', from: 'allowed@example.com', to: [], subject: '', body: '', date: new Date(),
|
|
117
|
+
})).toBe(true);
|
|
118
|
+
|
|
119
|
+
expect(channel.matchesFilters({
|
|
120
|
+
messageId: 'x', from: 'blocked@other.com', to: [], subject: '', body: '', date: new Date(),
|
|
121
|
+
})).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should filter by subject', () => {
|
|
125
|
+
const channel = new EmailChannel({
|
|
126
|
+
mode: 'webhook',
|
|
127
|
+
filters: { subject: ['[SUPPORT]'] },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(channel.matchesFilters({
|
|
131
|
+
messageId: 'x', from: 'a@b.com', to: [], subject: '[SUPPORT] Help me', body: '', date: new Date(),
|
|
132
|
+
})).toBe(true);
|
|
133
|
+
|
|
134
|
+
expect(channel.matchesFilters({
|
|
135
|
+
messageId: 'x', from: 'a@b.com', to: [], subject: 'Random email', body: '', date: new Date(),
|
|
136
|
+
})).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── Constructor ──────────────────────────────────
|
|
141
|
+
|
|
142
|
+
describe('constructor', () => {
|
|
143
|
+
it('should create with webhook mode', () => {
|
|
144
|
+
const channel = new EmailChannel({ mode: 'webhook', webhookPort: 9090 });
|
|
145
|
+
expect(channel.type).toBe('email');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { FeishuChannel } from '../../src/channels/feishu';
|
|
3
|
-
|
|
4
|
-
describe('FeishuChannel', () => {
|
|
5
|
-
// ── Event Parsing ────────────────────────────────
|
|
6
|
-
|
|
7
|
-
describe('parseEventBody', () => {
|
|
8
|
-
it('should parse url_verification challenge', () => {
|
|
9
|
-
const body = { type: 'url_verification', challenge: 'abc123' };
|
|
10
|
-
const result = FeishuChannel.parseEventBody(body);
|
|
11
|
-
expect(result.type).toBe('url_verification');
|
|
12
|
-
expect(result.challenge).toBe('abc123');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should parse text message event', () => {
|
|
16
|
-
const body = {
|
|
17
|
-
header: {
|
|
18
|
-
event_id: 'evt_123',
|
|
19
|
-
event_type: 'im.message.receive_v1',
|
|
20
|
-
token: 'tok',
|
|
21
|
-
},
|
|
22
|
-
event: {
|
|
23
|
-
message: {
|
|
24
|
-
message_id: 'msg_123',
|
|
25
|
-
chat_id: 'oc_456',
|
|
26
|
-
message_type: 'text',
|
|
27
|
-
content: JSON.stringify({ text: 'Hello Feishu' }),
|
|
28
|
-
create_time: '1699999999',
|
|
29
|
-
chat_type: 'p2p',
|
|
30
|
-
},
|
|
31
|
-
sender: {
|
|
32
|
-
sender_id: { open_id: 'ou_789' },
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const result = FeishuChannel.parseEventBody(body);
|
|
38
|
-
expect(result.type).toBe('message');
|
|
39
|
-
expect(result.content).toBe('Hello Feishu');
|
|
40
|
-
expect(result.chatId).toBe('oc_456');
|
|
41
|
-
expect(result.senderId).toBe('ou_789');
|
|
42
|
-
expect(result.messageId).toBe('msg_123');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should strip @bot mentions from content', () => {
|
|
46
|
-
const body = {
|
|
47
|
-
header: { event_type: 'im.message.receive_v1' },
|
|
48
|
-
event: {
|
|
49
|
-
message: {
|
|
50
|
-
message_id: 'msg_1',
|
|
51
|
-
chat_id: 'oc_1',
|
|
52
|
-
message_type: 'text',
|
|
53
|
-
content: JSON.stringify({ text: '@_user_123 Hello bot' }),
|
|
54
|
-
},
|
|
55
|
-
sender: { sender_id: { open_id: 'ou_1' } },
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const result = FeishuChannel.parseEventBody(body);
|
|
60
|
-
expect(result.content).toBe('Hello bot');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should return empty content for non-text messages', () => {
|
|
64
|
-
const body = {
|
|
65
|
-
header: { event_type: 'im.message.receive_v1' },
|
|
66
|
-
event: {
|
|
67
|
-
message: {
|
|
68
|
-
message_id: 'msg_1',
|
|
69
|
-
chat_id: 'oc_1',
|
|
70
|
-
message_type: 'image',
|
|
71
|
-
content: '{}',
|
|
72
|
-
},
|
|
73
|
-
sender: { sender_id: { open_id: 'ou_1' } },
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const result = FeishuChannel.parseEventBody(body);
|
|
78
|
-
expect(result.content).toBe('');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should handle unknown event types', () => {
|
|
82
|
-
const body = { header: { event_type: 'some.other.event' }, event: {} };
|
|
83
|
-
const result = FeishuChannel.parseEventBody(body);
|
|
84
|
-
expect(result.type).toBe('unknown');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should handle malformed content JSON gracefully', () => {
|
|
88
|
-
const body = {
|
|
89
|
-
header: { event_type: 'im.message.receive_v1' },
|
|
90
|
-
event: {
|
|
91
|
-
message: {
|
|
92
|
-
message_id: 'msg_1',
|
|
93
|
-
chat_id: 'oc_1',
|
|
94
|
-
message_type: 'text',
|
|
95
|
-
content: 'not-json',
|
|
96
|
-
},
|
|
97
|
-
sender: { sender_id: { open_id: 'ou_1' } },
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const result = FeishuChannel.parseEventBody(body);
|
|
102
|
-
expect(result.content).toBe('not-json');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ── Constructor ──────────────────────────────────
|
|
107
|
-
|
|
108
|
-
describe('constructor', () => {
|
|
109
|
-
it('should use defaults', () => {
|
|
110
|
-
const channel = new FeishuChannel();
|
|
111
|
-
expect(channel.type).toBe('feishu');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should accept config', () => {
|
|
115
|
-
const channel = new FeishuChannel({
|
|
116
|
-
appId: 'myapp',
|
|
117
|
-
appSecret: 'secret',
|
|
118
|
-
port: 9999,
|
|
119
|
-
});
|
|
120
|
-
expect(channel.type).toBe('feishu');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { FeishuChannel } from '../../src/channels/feishu';
|
|
3
|
+
|
|
4
|
+
describe('FeishuChannel', () => {
|
|
5
|
+
// ── Event Parsing ────────────────────────────────
|
|
6
|
+
|
|
7
|
+
describe('parseEventBody', () => {
|
|
8
|
+
it('should parse url_verification challenge', () => {
|
|
9
|
+
const body = { type: 'url_verification', challenge: 'abc123' };
|
|
10
|
+
const result = FeishuChannel.parseEventBody(body);
|
|
11
|
+
expect(result.type).toBe('url_verification');
|
|
12
|
+
expect(result.challenge).toBe('abc123');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should parse text message event', () => {
|
|
16
|
+
const body = {
|
|
17
|
+
header: {
|
|
18
|
+
event_id: 'evt_123',
|
|
19
|
+
event_type: 'im.message.receive_v1',
|
|
20
|
+
token: 'tok',
|
|
21
|
+
},
|
|
22
|
+
event: {
|
|
23
|
+
message: {
|
|
24
|
+
message_id: 'msg_123',
|
|
25
|
+
chat_id: 'oc_456',
|
|
26
|
+
message_type: 'text',
|
|
27
|
+
content: JSON.stringify({ text: 'Hello Feishu' }),
|
|
28
|
+
create_time: '1699999999',
|
|
29
|
+
chat_type: 'p2p',
|
|
30
|
+
},
|
|
31
|
+
sender: {
|
|
32
|
+
sender_id: { open_id: 'ou_789' },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const result = FeishuChannel.parseEventBody(body);
|
|
38
|
+
expect(result.type).toBe('message');
|
|
39
|
+
expect(result.content).toBe('Hello Feishu');
|
|
40
|
+
expect(result.chatId).toBe('oc_456');
|
|
41
|
+
expect(result.senderId).toBe('ou_789');
|
|
42
|
+
expect(result.messageId).toBe('msg_123');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should strip @bot mentions from content', () => {
|
|
46
|
+
const body = {
|
|
47
|
+
header: { event_type: 'im.message.receive_v1' },
|
|
48
|
+
event: {
|
|
49
|
+
message: {
|
|
50
|
+
message_id: 'msg_1',
|
|
51
|
+
chat_id: 'oc_1',
|
|
52
|
+
message_type: 'text',
|
|
53
|
+
content: JSON.stringify({ text: '@_user_123 Hello bot' }),
|
|
54
|
+
},
|
|
55
|
+
sender: { sender_id: { open_id: 'ou_1' } },
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = FeishuChannel.parseEventBody(body);
|
|
60
|
+
expect(result.content).toBe('Hello bot');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return empty content for non-text messages', () => {
|
|
64
|
+
const body = {
|
|
65
|
+
header: { event_type: 'im.message.receive_v1' },
|
|
66
|
+
event: {
|
|
67
|
+
message: {
|
|
68
|
+
message_id: 'msg_1',
|
|
69
|
+
chat_id: 'oc_1',
|
|
70
|
+
message_type: 'image',
|
|
71
|
+
content: '{}',
|
|
72
|
+
},
|
|
73
|
+
sender: { sender_id: { open_id: 'ou_1' } },
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const result = FeishuChannel.parseEventBody(body);
|
|
78
|
+
expect(result.content).toBe('');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle unknown event types', () => {
|
|
82
|
+
const body = { header: { event_type: 'some.other.event' }, event: {} };
|
|
83
|
+
const result = FeishuChannel.parseEventBody(body);
|
|
84
|
+
expect(result.type).toBe('unknown');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle malformed content JSON gracefully', () => {
|
|
88
|
+
const body = {
|
|
89
|
+
header: { event_type: 'im.message.receive_v1' },
|
|
90
|
+
event: {
|
|
91
|
+
message: {
|
|
92
|
+
message_id: 'msg_1',
|
|
93
|
+
chat_id: 'oc_1',
|
|
94
|
+
message_type: 'text',
|
|
95
|
+
content: 'not-json',
|
|
96
|
+
},
|
|
97
|
+
sender: { sender_id: { open_id: 'ou_1' } },
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = FeishuChannel.parseEventBody(body);
|
|
102
|
+
expect(result.content).toBe('not-json');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── Constructor ──────────────────────────────────
|
|
107
|
+
|
|
108
|
+
describe('constructor', () => {
|
|
109
|
+
it('should use defaults', () => {
|
|
110
|
+
const channel = new FeishuChannel();
|
|
111
|
+
expect(channel.type).toBe('feishu');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should accept config', () => {
|
|
115
|
+
const channel = new FeishuChannel({
|
|
116
|
+
appId: 'myapp',
|
|
117
|
+
appSecret: 'secret',
|
|
118
|
+
port: 9999,
|
|
119
|
+
});
|
|
120
|
+
expect(channel.type).toBe('feishu');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|