opc-agent 1.4.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/CHANGELOG.md +25 -0
- package/README.md +91 -32
- 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/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- 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 +1127 -19
- package/dist/core/a2a.d.ts +17 -0
- package/dist/core/a2a.js +43 -1
- package/dist/core/agent.d.ts +39 -0
- package/dist/core/agent.js +228 -3
- package/dist/core/runtime.d.ts +7 -0
- package/dist/core/runtime.js +205 -2
- package/dist/core/sandbox.d.ts +26 -0
- package/dist/core/sandbox.js +117 -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/core/workflow-graph.d.ts +93 -0
- package/dist/core/workflow-graph.js +247 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -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 +37 -6
- package/dist/index.js +75 -3
- 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/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/publish/index.d.ts +45 -0
- package/dist/publish/index.js +350 -0
- package/dist/schema/oad.d.ts +859 -67
- package/dist/schema/oad.js +47 -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/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -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/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 +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/telegram.ts +212 -90
- package/src/channels/websocket.ts +399 -87
- package/src/channels/wechat.ts +329 -149
- package/src/cli.ts +1201 -20
- package/src/core/a2a.ts +60 -0
- package/src/core/agent.ts +420 -152
- package/src/core/runtime.ts +174 -0
- package/src/core/sandbox.ts +143 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/core/workflow-graph.ts +365 -0
- package/src/daemon.ts +96 -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 +65 -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/providers/index.ts +354 -339
- package/src/publish/index.ts +376 -0
- package/src/schema/oad.ts +204 -154
- package/src/security/approval.ts +131 -0
- package/src/security/index.ts +3 -0
- package/src/security/keys.ts +87 -0
- package/src/skills/auto-learn.ts +262 -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/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/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/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -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/cli.test.ts +46 -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 +193 -0
- package/tests/telegram-discord.test.ts +60 -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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.0] - 2026-04-18
|
|
4
|
+
|
|
5
|
+
### Major Features
|
|
6
|
+
- **Interactive CLI** (`opc chat`) — Full TUI with streaming, slash commands, history
|
|
7
|
+
- **Daemon Mode** (`opc start/stop/status`) — Run agents as background processes
|
|
8
|
+
- **Cron Scheduler** — Built-in job scheduling with cron expressions
|
|
9
|
+
- **Autonomous Skill Learning** — Agents create and improve skills from experience
|
|
10
|
+
- **Sub-Agent System** — Spawn parallel sub-agents for task delegation
|
|
11
|
+
- **Built-in Tools** — File operations, web fetch, shell exec, datetime
|
|
12
|
+
- **MCP Client** — Connect to external MCP servers via JSON-RPC
|
|
13
|
+
- **Telegram Channel** — Dual-mode (polling + webhook) with Markdown support
|
|
14
|
+
- **Discord Channel** — Gateway WebSocket with auto-reconnect
|
|
15
|
+
- **Slack Channel** — Real Events API + chat.postMessage
|
|
16
|
+
- **SOUL.md + CONTEXT.md** — Agent personality and project context files
|
|
17
|
+
- **Analytics** — Wired into runtime for message tracking, skill usage, errors
|
|
18
|
+
|
|
19
|
+
### Enhanced
|
|
20
|
+
- `/health` endpoint returns comprehensive agent info
|
|
21
|
+
- `opc init` generates SOUL.md, CONTEXT.md, and richer project templates
|
|
22
|
+
- OAD config supports scheduler, learning, and tools sections
|
|
23
|
+
- 204 tests passing
|
|
24
|
+
|
|
25
|
+
### CLI Commands
|
|
26
|
+
init, chat, run, dev, start, stop, status, jobs, skills, info, build, test, analytics, brain, logs, score, search, deploy, publish, install, plugin, tool, workflow, migrate
|
|
27
|
+
|
|
3
28
|
## 1.4.0 (2026-04-18)
|
|
4
29
|
- feat: wire Analytics into AgentRuntime (message timing, skill usage, error tracking)
|
|
5
30
|
- feat: expose analytics snapshot on /health and /api/dashboard endpoints
|
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/opc-agent)
|
|
8
8
|
[](LICENSE)
|
|
9
|
-
[]()
|
|
10
10
|
[](https://www.typescriptlang.org/)
|
|
11
|
+
[]()
|
|
11
12
|
|
|
12
13
|
[快速开始](#快速开始) · [CLI 命令](#cli-命令) · [渠道](#11-个渠道) · [English](#english)
|
|
13
14
|
|
|
@@ -20,38 +21,76 @@
|
|
|
20
21
|
> **不只是 Harness,是比 Harness 高一维的 Agent OS。**
|
|
21
22
|
> 从创建到运行到监控,一个工具搞定 Agent 全生命周期。
|
|
22
23
|
|
|
23
|
-
## 🎯
|
|
24
|
-
|
|
25
|
-
| | LangChain | CrewAI | AutoGen | **OPC Agent** |
|
|
26
|
-
|
|
27
|
-
| 创建 | 写代码 | 写代码 | 写代码 | **`opc init` 一键** |
|
|
28
|
-
| 配置 | Python/代码 | Python | Python | **YAML 声明式** |
|
|
29
|
-
| 测试 | 自己搭 | 无 | 无 | **内置测试框架** |
|
|
30
|
-
| 渠道 | 自己接 | 无 | 无 | **11 渠道开箱即用** |
|
|
31
|
-
| 监控 | 自己搭 | 无 | 无 | **Traces + Score** |
|
|
32
|
-
| 记忆 | 自己管 | 简单 | 简单 | **DeepBrain 集成** |
|
|
24
|
+
## 🎯 和竞品的区别
|
|
25
|
+
|
|
26
|
+
| | LangChain | CrewAI | AutoGen | Hermes Agent | **OPC Agent** |
|
|
27
|
+
|---|---|---|---|---|---|
|
|
28
|
+
| 创建 | 写代码 | 写代码 | 写代码 | CLI | **`opc init` 一键** |
|
|
29
|
+
| 配置 | Python/代码 | Python | Python | YAML | **YAML 声明式** |
|
|
30
|
+
| 测试 | 自己搭 | 无 | 无 | 基础 | **内置测试框架** |
|
|
31
|
+
| 渠道 | 自己接 | 无 | 无 | 有限 | **11 渠道开箱即用** |
|
|
32
|
+
| 监控 | 自己搭 | 无 | 无 | 基础 | **Traces + Score** |
|
|
33
|
+
| 记忆 | 自己管 | 简单 | 简单 | 无 | **DeepBrain 集成** |
|
|
34
|
+
| 守护进程 | 无 | 无 | 无 | 无 | **`opc start/stop/status`** |
|
|
35
|
+
| 定时任务 | 无 | 无 | 无 | 无 | **内置 Cron 调度** |
|
|
36
|
+
| 技能学习 | 无 | 无 | 无 | 无 | **自主技能习得** |
|
|
37
|
+
| 子 Agent | 无 | 有 | 有 | 无 | **并行子 Agent 系统** |
|
|
38
|
+
| MCP | 无 | 无 | 无 | 无 | **MCP Client 集成** |
|
|
33
39
|
|
|
34
40
|
**框架管"怎么跑",Agent OS 管"全过程"。**
|
|
35
41
|
|
|
36
42
|
## 快速开始
|
|
37
43
|
|
|
38
44
|
```bash
|
|
39
|
-
|
|
45
|
+
# 最快方式(无需全局安装)
|
|
46
|
+
npx opc-agent init my-agent
|
|
47
|
+
cd my-agent
|
|
48
|
+
npm install
|
|
49
|
+
opc chat
|
|
40
50
|
|
|
41
|
-
#
|
|
51
|
+
# 或全局安装
|
|
52
|
+
npm install -g opc-agent
|
|
42
53
|
opc init my-agent
|
|
43
54
|
cd my-agent
|
|
44
|
-
|
|
45
|
-
# 开发
|
|
46
55
|
opc dev
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 🆕 v2.0.0 新特性
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
### 🖥️ 交互式 CLI (`opc chat`)
|
|
61
|
+
全功能 TUI:流式输出、斜杠命令、历史记录,直接在终端和 Agent 对话。
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
### 🔄 守护进程模式
|
|
64
|
+
```bash
|
|
65
|
+
opc start # 后台启动 Agent
|
|
66
|
+
opc status # 查看运行状态
|
|
67
|
+
opc stop # 停止 Agent
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### ⏰ Cron 调度器
|
|
71
|
+
OAD 配置中声明定时任务,Agent 自动按计划执行:
|
|
72
|
+
```yaml
|
|
73
|
+
scheduler:
|
|
74
|
+
jobs:
|
|
75
|
+
- cron: "0 9 * * *"
|
|
76
|
+
task: daily-report
|
|
53
77
|
```
|
|
54
78
|
|
|
79
|
+
### 🧠 自主技能学习
|
|
80
|
+
Agent 从经验中自动创建和改进技能,越用越强。
|
|
81
|
+
|
|
82
|
+
### 🤖 子 Agent 系统
|
|
83
|
+
并行派生子 Agent 处理复杂任务,支持任务委派和结果汇总。
|
|
84
|
+
|
|
85
|
+
### 🔧 内置工具
|
|
86
|
+
文件操作、Web 抓取、Shell 执行、日期时间 — 开箱即用,无需额外配置。
|
|
87
|
+
|
|
88
|
+
### 🔌 MCP Client
|
|
89
|
+
通过 JSON-RPC 连接外部 MCP 服务器,扩展 Agent 能力边界。
|
|
90
|
+
|
|
91
|
+
### 📄 SOUL.md + CONTEXT.md
|
|
92
|
+
用 Markdown 定义 Agent 人格和项目上下文,人性化配置。
|
|
93
|
+
|
|
55
94
|
## OAD 声明式配置
|
|
56
95
|
|
|
57
96
|
不用写代码,用 YAML 定义 Agent:
|
|
@@ -87,12 +126,29 @@ memory:
|
|
|
87
126
|
|
|
88
127
|
```bash
|
|
89
128
|
opc init <name> # 创建新 Agent
|
|
129
|
+
opc chat # 交互式对话(TUI)
|
|
90
130
|
opc dev # 开发模式(热重载)
|
|
91
|
-
opc test # 运行测试
|
|
92
131
|
opc run # 生产运行
|
|
93
|
-
opc
|
|
132
|
+
opc start # 守护进程启动
|
|
133
|
+
opc stop # 停止守护进程
|
|
134
|
+
opc status # 查看运行状态
|
|
135
|
+
opc jobs # 查看定时任务
|
|
136
|
+
opc skills # 查看已学技能
|
|
137
|
+
opc info # Agent 信息
|
|
138
|
+
opc build # 构建
|
|
139
|
+
opc test # 运行测试
|
|
140
|
+
opc analytics # 数据分析
|
|
94
141
|
opc brain [--url ...] # 查看记忆状态
|
|
142
|
+
opc logs [-f] # 查看 Traces 日志
|
|
95
143
|
opc score # 查看性能评分
|
|
144
|
+
opc search <query> # 搜索
|
|
145
|
+
opc deploy # 部署
|
|
146
|
+
opc publish # 发布
|
|
147
|
+
opc install <skill> # 安装技能
|
|
148
|
+
opc plugin <name> # 管理插件
|
|
149
|
+
opc tool <name> # 管理工具
|
|
150
|
+
opc workflow <name> # 工作流
|
|
151
|
+
opc migrate # 迁移
|
|
96
152
|
```
|
|
97
153
|
|
|
98
154
|
## 11 个渠道
|
|
@@ -184,20 +240,17 @@ Apache-2.0
|
|
|
184
240
|
## Quick Start
|
|
185
241
|
|
|
186
242
|
```bash
|
|
187
|
-
|
|
243
|
+
# Fastest way (no global install)
|
|
244
|
+
npx opc-agent init my-agent
|
|
245
|
+
cd my-agent
|
|
246
|
+
npm install
|
|
247
|
+
opc chat
|
|
188
248
|
|
|
189
|
-
#
|
|
249
|
+
# Or install globally
|
|
250
|
+
npm install -g opc-agent
|
|
190
251
|
opc init my-agent
|
|
191
252
|
cd my-agent
|
|
192
|
-
|
|
193
|
-
# Develop
|
|
194
253
|
opc dev
|
|
195
|
-
|
|
196
|
-
# Test
|
|
197
|
-
opc test
|
|
198
|
-
|
|
199
|
-
# Run
|
|
200
|
-
opc run
|
|
201
254
|
```
|
|
202
255
|
|
|
203
256
|
## OAD Declarative Configuration
|
|
@@ -267,7 +320,7 @@ One codebase, deploy to any channel:
|
|
|
267
320
|
|----------|----------|
|
|
268
321
|
| 📋 **Configuration** | OAD declarative definition, YAML config |
|
|
269
322
|
| 📡 **Channels** | 11 channels, unified access |
|
|
270
|
-
| 🧪 **Testing** | Built-in test framework,
|
|
323
|
+
| 🧪 **Testing** | Built-in test framework, 204 tests |
|
|
271
324
|
| 🔌 **Plugins** | Extensible skills and tools system |
|
|
272
325
|
| 📊 **Monitoring** | Traces behavior collection, Score rating |
|
|
273
326
|
| 🧠 **Memory** | DeepBrain integration, auto-learning |
|
|
@@ -304,3 +357,9 @@ One codebase, deploy to any channel:
|
|
|
304
357
|
## License
|
|
305
358
|
|
|
306
359
|
Apache-2.0
|
|
360
|
+
thub.com/Deepleaper/agentkits) | OpenRouter with Memory | Model call layer |
|
|
361
|
+
| [agent-workstation](https://github.com/Deepleaper/agent-workstation) | Virtual Role Templates | `opc init --template` |
|
|
362
|
+
|
|
363
|
+
## License
|
|
364
|
+
|
|
365
|
+
Apache-2.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;
|