opc-agent 0.9.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +145 -144
- package/dist/channels/discord.d.ts +44 -0
- package/dist/channels/discord.js +189 -0
- package/dist/channels/feishu.d.ts +47 -0
- package/dist/channels/feishu.js +221 -0
- package/dist/channels/web.js +118 -39
- package/dist/cli.js +109 -1
- package/dist/core/errors.d.ts +68 -0
- package/dist/core/errors.js +149 -0
- package/dist/core/security.d.ts +48 -0
- package/dist/core/security.js +146 -0
- package/dist/core/watch.d.ts +73 -0
- package/dist/core/watch.js +106 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +32 -1
- package/dist/plugins/index.d.ts +24 -3
- package/dist/plugins/index.js +109 -4
- package/dist/schema/oad.d.ts +54 -0
- package/dist/schema/oad.js +6 -1
- package/package.json +1 -1
- package/src/channels/discord.ts +192 -0
- package/src/channels/feishu.ts +236 -0
- package/src/channels/web.ts +118 -39
- package/src/cli.ts +108 -1
- package/src/core/errors.ts +148 -0
- package/src/core/security.ts +171 -0
- package/src/core/watch.ts +178 -0
- package/src/index.ts +15 -0
- package/src/plugins/index.ts +128 -7
- package/src/schema/oad.ts +6 -0
- package/tests/errors.test.ts +83 -0
- package/tests/security.test.ts +60 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to OPC Agent will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.1.0] - 2026-04-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Feishu/Lark Channel**: Full Feishu bot integration with event subscription webhook, tenant access token caching, text & interactive card messaging, group + P2P support. Also works with Lark international via `apiBase` config. (`src/channels/feishu.ts`)
|
|
9
|
+
- **Discord Channel**: Discord bot via Gateway WebSocket with auto-reconnect, heartbeat, message content intent, and 2000-char message splitting. (`src/channels/discord.ts`)
|
|
10
|
+
- **ProcessWatcher**: Background process output monitoring with regex pattern matching — watch stdout/stderr for specific patterns (errors, "server ready", build completion) and get instant callbacks without polling. Supports `once` patterns, match history, and dynamic pattern add/remove. Inspired by Hermes Agent's `watch_patterns`. (`src/core/watch.ts`)
|
|
11
|
+
|
|
5
12
|
## [0.2.0] - 2026-04-15
|
|
6
13
|
|
|
7
14
|
### Added
|
package/README.md
CHANGED
|
@@ -1,188 +1,189 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">🤖 OPC Agent</h1>
|
|
3
|
+
<p align="center"><strong>Open Agent Framework — Build, test, and run AI Agents for business workstations</strong></p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://www.npmjs.com/package/opc-agent"><img src="https://img.shields.io/npm/v/opc-agent?color=blue" alt="npm"></a>
|
|
6
|
+
<a href="https://github.com/anthropic-lab/opc-agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="license"></a>
|
|
7
|
+
<a href="https://github.com/anthropic-lab/opc-agent/actions"><img src="https://img.shields.io/badge/tests-passing-brightgreen" alt="tests"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/opc-agent"><img src="https://img.shields.io/npm/dm/opc-agent?color=orange" alt="downloads"></a>
|
|
9
|
+
</p>
|
|
10
|
+
</p>
|
|
2
11
|
|
|
3
|
-
|
|
12
|
+
---
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
[](LICENSE)
|
|
14
|
+
OPC Agent is a **TypeScript-first framework** for building production AI agents. Define your agent in a single YAML file (OAD — Open Agent Definition), connect any LLM provider, deploy to any channel.
|
|
7
15
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
- 🤖 **Agent Framework** — BaseAgent with lifecycle management, skills, and LLM integration
|
|
11
|
-
- 📋 **OAD Schema** — Declarative agent definition (YAML/JSON) with validation
|
|
12
|
-
- 🧠 **Memory System** — Short-term + long-term memory with DeepBrain integration
|
|
13
|
-
- 🔌 **Multi-Channel** — Web, WebSocket, and Telegram channels
|
|
14
|
-
- 🛡️ **DTV Framework** — Data, Trust, and Value tracking for agents
|
|
15
|
-
- 🎯 **Skill System** — Pluggable skills with registry and priority execution
|
|
16
|
-
- 📦 **Templates** — Customer service, sales assistant, knowledge base, code reviewer
|
|
17
|
-
- 🚀 **CLI** — Interactive project creation, dev mode, build, test, run
|
|
18
|
-
|
|
19
|
-
## Quick Start
|
|
16
|
+
## ⚡ Quick Start (30 seconds)
|
|
20
17
|
|
|
21
18
|
```bash
|
|
22
|
-
# Install
|
|
19
|
+
# Install
|
|
23
20
|
npm install -g opc-agent
|
|
24
21
|
|
|
25
|
-
# Create
|
|
22
|
+
# Create your first agent
|
|
26
23
|
opc init my-agent
|
|
27
|
-
|
|
28
|
-
# Or with a specific template
|
|
29
|
-
opc init my-bot --template sales-assistant
|
|
30
|
-
|
|
31
|
-
# Run the agent
|
|
32
24
|
cd my-agent
|
|
25
|
+
|
|
26
|
+
# Run it
|
|
33
27
|
opc run
|
|
34
28
|
```
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| Template | Description |
|
|
39
|
-
|----------|-------------|
|
|
40
|
-
| `customer-service` | FAQ lookup + human handoff |
|
|
41
|
-
| `sales-assistant` | Product Q&A + lead capture + appointment booking |
|
|
42
|
-
| `knowledge-base` | RAG with DeepBrain semantic search |
|
|
43
|
-
| `code-reviewer` | Bug detection + style checking |
|
|
30
|
+
Your agent is now live at `http://localhost:3000` with a beautiful web chat UI.
|
|
44
31
|
|
|
45
|
-
##
|
|
32
|
+
## ✨ Features
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
### 🔌 Multi-Provider LLM Support
|
|
35
|
+
```yaml
|
|
36
|
+
# oad.yaml
|
|
37
|
+
spec:
|
|
38
|
+
provider:
|
|
39
|
+
default: deepseek
|
|
40
|
+
allowed: [openai, deepseek, qwen, anthropic, ollama]
|
|
41
|
+
model: deepseek-chat
|
|
42
|
+
```
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
opc deploy --target openclaw --install
|
|
44
|
+
Supports **OpenAI**, **DeepSeek**, **Anthropic**, **Qwen**, **Ollama** (local), and any OpenAI-compatible API.
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
### 📡 Multi-Channel Deployment
|
|
47
|
+
```yaml
|
|
48
|
+
spec:
|
|
49
|
+
channels:
|
|
50
|
+
- type: web # Beautiful chat UI
|
|
51
|
+
port: 3000
|
|
52
|
+
- type: telegram # Telegram bot
|
|
53
|
+
- type: websocket # Real-time WebSocket
|
|
54
|
+
- type: slack # Slack integration
|
|
55
|
+
- type: email # Email channel
|
|
56
|
+
- type: wechat # WeChat Official Account
|
|
57
|
+
- type: voice # Voice (STT/TTS)
|
|
58
|
+
- type: webhook # Incoming webhooks
|
|
58
59
|
```
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
### 🧠 Knowledge Base (RAG)
|
|
62
|
+
```typescript
|
|
63
|
+
import { KnowledgeBase } from 'opc-agent';
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
const kb = new KnowledgeBase('./docs');
|
|
66
|
+
await kb.addFile('product-manual.pdf');
|
|
67
|
+
// Agent automatically uses KB for context
|
|
68
|
+
```
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
### 🔧 Plugin System
|
|
71
|
+
```yaml
|
|
72
|
+
spec:
|
|
73
|
+
plugins:
|
|
74
|
+
- name: logging
|
|
75
|
+
- name: analytics
|
|
76
|
+
- name: rate-limit
|
|
77
|
+
config: { maxPerMinute: 60 }
|
|
78
|
+
```
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
|---------|-------------|
|
|
68
|
-
| `opc init [name]` | Create new project (interactive) |
|
|
69
|
-
| `opc create <name>` | Create agent from template |
|
|
70
|
-
| `opc info` | Show agent info from OAD |
|
|
71
|
-
| `opc build` | Validate OAD |
|
|
72
|
-
| `opc test` | Run in sandbox mode |
|
|
73
|
-
| `opc run` | Start agent with channels |
|
|
74
|
-
| `opc dev` | Hot-reload development mode |
|
|
75
|
-
| `opc deploy` | **Deploy to OpenClaw runtime** |
|
|
76
|
-
| `opc publish` | Validate and generate manifest |
|
|
77
|
-
| `opc search <query>` | Search OPC Registry (coming soon) |
|
|
80
|
+
Built-in plugins: `logging`, `analytics`, `rate-limit`. Custom plugins support lifecycle hooks: `onInit`, `onMessage`, `onResponse`, `onError`, `onShutdown`.
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
### 🔒 Security
|
|
83
|
+
- Input sanitization (XSS, injection prevention)
|
|
84
|
+
- API key rotation & management
|
|
85
|
+
- CORS configuration
|
|
86
|
+
- Helmet-style security headers
|
|
87
|
+
- Content Security Policy
|
|
88
|
+
- Auth middleware with session isolation
|
|
80
89
|
|
|
81
|
-
|
|
90
|
+
### 🧪 Agent Testing
|
|
91
|
+
```bash
|
|
92
|
+
opc test # Run test cases
|
|
93
|
+
opc test --watch # Watch mode
|
|
94
|
+
```
|
|
82
95
|
|
|
83
96
|
```yaml
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
description: "My AI agent"
|
|
90
|
-
marketplace:
|
|
91
|
-
category: support
|
|
92
|
-
pricing: free
|
|
93
|
-
tags: [ai, support]
|
|
94
|
-
spec:
|
|
95
|
-
provider:
|
|
96
|
-
default: deepseek
|
|
97
|
-
allowed: [openai, deepseek, qwen]
|
|
98
|
-
model: deepseek-chat
|
|
99
|
-
systemPrompt: "You are a helpful assistant."
|
|
100
|
-
skills:
|
|
101
|
-
- name: faq-lookup
|
|
102
|
-
description: "Answer FAQs"
|
|
103
|
-
channels:
|
|
104
|
-
- type: web
|
|
105
|
-
port: 3000
|
|
106
|
-
- type: telegram
|
|
107
|
-
config:
|
|
108
|
-
token: "BOT_TOKEN"
|
|
109
|
-
- type: websocket
|
|
110
|
-
port: 3002
|
|
111
|
-
memory:
|
|
112
|
-
shortTerm: true
|
|
113
|
-
longTerm:
|
|
114
|
-
provider: deepbrain
|
|
115
|
-
collection: my-knowledge
|
|
116
|
-
dtv:
|
|
117
|
-
trust:
|
|
118
|
-
level: sandbox
|
|
119
|
-
value:
|
|
120
|
-
metrics: [response_time]
|
|
97
|
+
# tests/greeting.yaml
|
|
98
|
+
- input: "Hello"
|
|
99
|
+
expect:
|
|
100
|
+
contains: ["hello", "hi"]
|
|
101
|
+
maxLatencyMs: 5000
|
|
121
102
|
```
|
|
122
103
|
|
|
123
|
-
|
|
104
|
+
### 🎭 Multi-Agent Orchestration
|
|
105
|
+
```typescript
|
|
106
|
+
import { Orchestrator } from 'opc-agent';
|
|
124
107
|
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
const orchestrator = new Orchestrator({
|
|
109
|
+
agents: [triageAgent, salesAgent, supportAgent],
|
|
110
|
+
strategy: 'route-by-intent',
|
|
111
|
+
});
|
|
112
|
+
```
|
|
127
113
|
|
|
128
|
-
###
|
|
129
|
-
|
|
114
|
+
### 📊 Built-in Analytics & Monitoring
|
|
115
|
+
- `/api/health` — Health check
|
|
116
|
+
- `/api/metrics` — Prometheus-compatible metrics
|
|
117
|
+
- `/api/dashboard` — Real-time dashboard UI
|
|
118
|
+
- Conversation export (JSON, Markdown, CSV)
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
npm install deepbrain
|
|
133
|
-
```
|
|
120
|
+
## 🏗️ Architecture
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
122
|
+
```
|
|
123
|
+
┌─────────────────────────────────────────────────┐
|
|
124
|
+
│ OAD (YAML) │
|
|
125
|
+
│ Agent Definition & Config │
|
|
126
|
+
├─────────────────────────────────────────────────┤
|
|
127
|
+
│ │
|
|
128
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
129
|
+
│ │ Channels │ │ Plugins │ │ Security │ │
|
|
130
|
+
│ │ web,tg, │ │ logging, │ │ sanitize, │ │
|
|
131
|
+
│ │ ws,slack │ │ analytics│ │ CORS, auth │ │
|
|
132
|
+
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
|
|
133
|
+
│ │ │ │ │
|
|
134
|
+
│ ┌────▼──────────────▼───────────────▼────────┐ │
|
|
135
|
+
│ │ Agent Runtime │ │
|
|
136
|
+
│ │ ┌─────────┐ ┌────────┐ ┌─────────────┐ │ │
|
|
137
|
+
│ │ │ Memory │ │ Skills │ │ Knowledge │ │ │
|
|
138
|
+
│ │ └─────────┘ └────────┘ └─────────────┘ │ │
|
|
139
|
+
│ └────────────────────┬───────────────────────┘ │
|
|
140
|
+
│ │ │
|
|
141
|
+
│ ┌────────────────────▼───────────────────────┐ │
|
|
142
|
+
│ │ LLM Providers │ │
|
|
143
|
+
│ │ OpenAI · DeepSeek · Anthropic · Ollama │ │
|
|
144
|
+
│ └─────────────────────────────────────────────┘│
|
|
145
|
+
└─────────────────────────────────────────────────┘
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
|
|
148
|
+
## 📖 CLI Reference
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
| Command | Description |
|
|
151
|
+
|---------|-------------|
|
|
152
|
+
| `opc init [name]` | Create a new agent project |
|
|
153
|
+
| `opc run` | Start the agent |
|
|
154
|
+
| `opc dev` | Start in development mode (auto-reload) |
|
|
155
|
+
| `opc test` | Run agent test cases |
|
|
156
|
+
| `opc validate` | Validate OAD configuration |
|
|
157
|
+
| `opc deploy hermes` | Deploy to Hermes cloud |
|
|
158
|
+
| `opc plugin list` | List available plugins |
|
|
159
|
+
| `opc plugin add <name>` | Add a plugin to config |
|
|
160
|
+
| `opc migrate` | Migrate OAD to latest schema |
|
|
161
|
+
| `opc marketplace publish` | Publish to marketplace |
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
- **WebSocket** — Real-time bidirectional communication with broadcast
|
|
149
|
-
- **Telegram** — Webhook handler for Telegram Bot API
|
|
163
|
+
## 🤝 Contributing
|
|
150
164
|
|
|
151
|
-
|
|
165
|
+
We welcome contributions! Here's how:
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
1. Fork the repository
|
|
168
|
+
2. Create a feature branch: `git checkout -b feat/my-feature`
|
|
169
|
+
3. Make your changes with tests
|
|
170
|
+
4. Run tests: `npm test`
|
|
171
|
+
5. Submit a pull request
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
const agent = new BaseAgent({
|
|
158
|
-
name: 'my-agent',
|
|
159
|
-
systemPrompt: 'You are helpful.',
|
|
160
|
-
});
|
|
161
|
-
await agent.init();
|
|
162
|
-
|
|
163
|
-
// With skills
|
|
164
|
-
agent.registerSkill({
|
|
165
|
-
name: 'greeter',
|
|
166
|
-
description: 'Greet users',
|
|
167
|
-
execute: async (ctx, msg) => {
|
|
168
|
-
if (msg.content.includes('hello')) {
|
|
169
|
-
return { handled: true, response: 'Hi!', confidence: 1.0 };
|
|
170
|
-
}
|
|
171
|
-
return { handled: false, confidence: 0 };
|
|
172
|
-
},
|
|
173
|
-
});
|
|
173
|
+
### Development Setup
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/anthropic-lab/opc-agent.git
|
|
177
|
+
cd opc-agent
|
|
178
|
+
npm install
|
|
179
|
+
npm run build
|
|
180
|
+
npm test
|
|
180
181
|
```
|
|
181
182
|
|
|
182
|
-
##
|
|
183
|
+
## 📄 License
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
[Apache License 2.0](LICENSE) — Use it freely in commercial and open source projects.
|
|
185
186
|
|
|
186
|
-
|
|
187
|
+
---
|
|
187
188
|
|
|
188
|
-
|
|
189
|
+
<p align="center">Built with ❤️ by the OPC team</p>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BaseChannel } from './index';
|
|
2
|
+
/**
|
|
3
|
+
* Discord Channel — v1.1.0
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Discord Bot via Gateway (WebSocket) or HTTP interactions
|
|
7
|
+
* - Slash commands, message content intent
|
|
8
|
+
* - Thread-based conversations
|
|
9
|
+
* - Reactions, embeds
|
|
10
|
+
*
|
|
11
|
+
* Env vars:
|
|
12
|
+
* DISCORD_BOT_TOKEN — bot token
|
|
13
|
+
* DISCORD_APPLICATION_ID — application ID for slash commands
|
|
14
|
+
*/
|
|
15
|
+
export interface DiscordChannelConfig {
|
|
16
|
+
/** Bot token */
|
|
17
|
+
botToken?: string;
|
|
18
|
+
/** Application ID */
|
|
19
|
+
applicationId?: string;
|
|
20
|
+
/** Guild IDs to register slash commands (empty = global) */
|
|
21
|
+
guildIds?: string[];
|
|
22
|
+
/** Whether to use threads for conversations */
|
|
23
|
+
useThreads?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare class DiscordChannel extends BaseChannel {
|
|
26
|
+
readonly type = "discord";
|
|
27
|
+
private config;
|
|
28
|
+
private ws;
|
|
29
|
+
private heartbeatInterval;
|
|
30
|
+
private sequenceNumber;
|
|
31
|
+
private sessionId;
|
|
32
|
+
private resumeUrl;
|
|
33
|
+
constructor(config?: DiscordChannelConfig);
|
|
34
|
+
start(): Promise<void>;
|
|
35
|
+
stop(): Promise<void>;
|
|
36
|
+
private identify;
|
|
37
|
+
private startHeartbeat;
|
|
38
|
+
private stopHeartbeat;
|
|
39
|
+
private sendHeartbeat;
|
|
40
|
+
private handleMessage;
|
|
41
|
+
sendMessage(channelId: string, content: string): Promise<void>;
|
|
42
|
+
private splitMessage;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=discord.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DiscordChannel = void 0;
|
|
37
|
+
const index_1 = require("./index");
|
|
38
|
+
class DiscordChannel extends index_1.BaseChannel {
|
|
39
|
+
type = 'discord';
|
|
40
|
+
config;
|
|
41
|
+
ws = null;
|
|
42
|
+
heartbeatInterval = null;
|
|
43
|
+
sequenceNumber = null;
|
|
44
|
+
sessionId = null;
|
|
45
|
+
resumeUrl = null;
|
|
46
|
+
constructor(config = {}) {
|
|
47
|
+
super();
|
|
48
|
+
this.config = {
|
|
49
|
+
botToken: config.botToken ?? process.env.DISCORD_BOT_TOKEN ?? '',
|
|
50
|
+
applicationId: config.applicationId ?? process.env.DISCORD_APPLICATION_ID ?? '',
|
|
51
|
+
guildIds: config.guildIds ?? [],
|
|
52
|
+
useThreads: config.useThreads ?? true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async start() {
|
|
56
|
+
if (!this.config.botToken) {
|
|
57
|
+
console.warn('[DiscordChannel] No bot token. Set DISCORD_BOT_TOKEN.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Get gateway URL
|
|
61
|
+
const gatewayResp = await fetch('https://discord.com/api/v10/gateway/bot', {
|
|
62
|
+
headers: { Authorization: `Bot ${this.config.botToken}` },
|
|
63
|
+
});
|
|
64
|
+
const gatewayData = await gatewayResp.json();
|
|
65
|
+
const wsUrl = `${gatewayData.url}?v=10&encoding=json`;
|
|
66
|
+
const { WebSocket } = await Promise.resolve().then(() => __importStar(require('ws')));
|
|
67
|
+
this.ws = new WebSocket(wsUrl);
|
|
68
|
+
this.ws.on('message', async (data) => {
|
|
69
|
+
const payload = JSON.parse(data.toString());
|
|
70
|
+
this.sequenceNumber = payload.s ?? this.sequenceNumber;
|
|
71
|
+
switch (payload.op) {
|
|
72
|
+
case 10: // Hello
|
|
73
|
+
this.startHeartbeat(payload.d.heartbeat_interval);
|
|
74
|
+
this.identify();
|
|
75
|
+
break;
|
|
76
|
+
case 11: // Heartbeat ACK
|
|
77
|
+
break;
|
|
78
|
+
case 0: // Dispatch
|
|
79
|
+
if (payload.t === 'READY') {
|
|
80
|
+
this.sessionId = payload.d.session_id;
|
|
81
|
+
this.resumeUrl = payload.d.resume_gateway_url;
|
|
82
|
+
console.log(`[DiscordChannel] Connected as ${payload.d.user.username}`);
|
|
83
|
+
}
|
|
84
|
+
else if (payload.t === 'MESSAGE_CREATE') {
|
|
85
|
+
await this.handleMessage(payload.d);
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
this.ws.on('close', (code) => {
|
|
91
|
+
console.log(`[DiscordChannel] WebSocket closed: ${code}`);
|
|
92
|
+
this.stopHeartbeat();
|
|
93
|
+
// Auto-reconnect after 5s for resumable codes
|
|
94
|
+
if (code !== 4004 && code !== 4014) {
|
|
95
|
+
setTimeout(() => this.start(), 5000);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
this.ws.on('error', (err) => {
|
|
99
|
+
console.error('[DiscordChannel] WebSocket error:', err.message);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async stop() {
|
|
103
|
+
this.stopHeartbeat();
|
|
104
|
+
if (this.ws) {
|
|
105
|
+
this.ws.close(1000);
|
|
106
|
+
this.ws = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
identify() {
|
|
110
|
+
this.ws?.send(JSON.stringify({
|
|
111
|
+
op: 2,
|
|
112
|
+
d: {
|
|
113
|
+
token: this.config.botToken,
|
|
114
|
+
intents: (1 << 9) | (1 << 15), // GUILD_MESSAGES | MESSAGE_CONTENT
|
|
115
|
+
properties: {
|
|
116
|
+
os: process.platform,
|
|
117
|
+
browser: 'opc-agent',
|
|
118
|
+
device: 'opc-agent',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
startHeartbeat(intervalMs) {
|
|
124
|
+
this.stopHeartbeat();
|
|
125
|
+
// Send first heartbeat with jitter
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
this.sendHeartbeat();
|
|
128
|
+
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), intervalMs);
|
|
129
|
+
}, intervalMs * Math.random());
|
|
130
|
+
}
|
|
131
|
+
stopHeartbeat() {
|
|
132
|
+
if (this.heartbeatInterval) {
|
|
133
|
+
clearInterval(this.heartbeatInterval);
|
|
134
|
+
this.heartbeatInterval = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
sendHeartbeat() {
|
|
138
|
+
this.ws?.send(JSON.stringify({ op: 1, d: this.sequenceNumber }));
|
|
139
|
+
}
|
|
140
|
+
async handleMessage(d) {
|
|
141
|
+
// Ignore bot messages
|
|
142
|
+
const author = d.author;
|
|
143
|
+
if (author?.bot)
|
|
144
|
+
return;
|
|
145
|
+
if (!d.content || !this.handler)
|
|
146
|
+
return;
|
|
147
|
+
const msg = {
|
|
148
|
+
id: `discord_${d.id}`,
|
|
149
|
+
role: 'user',
|
|
150
|
+
content: d.content,
|
|
151
|
+
timestamp: new Date(d.timestamp).getTime(),
|
|
152
|
+
metadata: {
|
|
153
|
+
sessionId: `discord_${d.channel_id}`,
|
|
154
|
+
chatId: d.channel_id,
|
|
155
|
+
userId: author.id,
|
|
156
|
+
platform: 'discord',
|
|
157
|
+
guildId: d.guild_id,
|
|
158
|
+
threadId: d.thread?.toString(),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const response = await this.handler(msg);
|
|
162
|
+
await this.sendMessage(d.channel_id, response.content);
|
|
163
|
+
}
|
|
164
|
+
async sendMessage(channelId, content) {
|
|
165
|
+
// Discord max message length is 2000
|
|
166
|
+
const chunks = this.splitMessage(content, 2000);
|
|
167
|
+
for (const chunk of chunks) {
|
|
168
|
+
await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: {
|
|
171
|
+
'Content-Type': 'application/json',
|
|
172
|
+
Authorization: `Bot ${this.config.botToken}`,
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({ content: chunk }),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
splitMessage(text, maxLen) {
|
|
179
|
+
if (text.length <= maxLen)
|
|
180
|
+
return [text];
|
|
181
|
+
const parts = [];
|
|
182
|
+
for (let i = 0; i < text.length; i += maxLen) {
|
|
183
|
+
parts.push(text.slice(i, i + maxLen));
|
|
184
|
+
}
|
|
185
|
+
return parts;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
exports.DiscordChannel = DiscordChannel;
|
|
189
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BaseChannel } from './index';
|
|
2
|
+
/**
|
|
3
|
+
* Feishu / Lark Channel — v1.1.0
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Event Subscription (webhook) mode for receiving messages
|
|
7
|
+
* - Bot send via Feishu Open API
|
|
8
|
+
* - URL verification challenge
|
|
9
|
+
* - Message card (interactive) responses
|
|
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
|
|
16
|
+
*/
|
|
17
|
+
export interface FeishuChannelConfig {
|
|
18
|
+
/** Feishu App ID */
|
|
19
|
+
appId?: string;
|
|
20
|
+
/** Feishu App Secret */
|
|
21
|
+
appSecret?: string;
|
|
22
|
+
/** Verification token for event subscription */
|
|
23
|
+
verificationToken?: string;
|
|
24
|
+
/** Encrypt key (optional, for encrypted events) */
|
|
25
|
+
encryptKey?: string;
|
|
26
|
+
/** Webhook server port (default: 3002) */
|
|
27
|
+
port?: number;
|
|
28
|
+
/** API base URL (use 'https://open.larksuite.com' for Lark international) */
|
|
29
|
+
apiBase?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class FeishuChannel extends BaseChannel {
|
|
32
|
+
readonly type = "feishu";
|
|
33
|
+
private config;
|
|
34
|
+
private server;
|
|
35
|
+
private tokenCache;
|
|
36
|
+
private processedEvents;
|
|
37
|
+
constructor(config?: FeishuChannelConfig);
|
|
38
|
+
start(): Promise<void>;
|
|
39
|
+
stop(): Promise<void>;
|
|
40
|
+
/** Get tenant access token (cached) */
|
|
41
|
+
private getAccessToken;
|
|
42
|
+
/** Send a text message to a chat */
|
|
43
|
+
sendTextMessage(chatId: string, text: string): Promise<void>;
|
|
44
|
+
/** Send an interactive card message */
|
|
45
|
+
sendCardMessage(chatId: string, card: Record<string, unknown>): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=feishu.d.ts.map
|