opc-agent 1.1.1 → 1.1.3

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 (142) hide show
  1. package/CHANGELOG.md +51 -51
  2. package/CONTRIBUTING.md +75 -75
  3. package/README.md +222 -126
  4. package/README.zh-CN.md +129 -80
  5. package/dist/channels/web.js +256 -256
  6. package/dist/deploy/hermes.js +22 -22
  7. package/dist/deploy/openclaw.js +31 -31
  8. package/dist/providers/index.d.ts +1 -1
  9. package/dist/providers/index.js +148 -13
  10. package/dist/schema/oad.d.ts +3 -3
  11. package/dist/templates/code-reviewer.js +5 -5
  12. package/dist/templates/customer-service.js +2 -2
  13. package/dist/templates/data-analyst.js +5 -5
  14. package/dist/templates/knowledge-base.js +2 -2
  15. package/dist/templates/sales-assistant.js +4 -4
  16. package/dist/templates/teacher.js +6 -6
  17. package/docs/.vitepress/config.ts +103 -103
  18. package/docs/api/cli.md +48 -48
  19. package/docs/api/oad-schema.md +64 -64
  20. package/docs/api/sdk.md +80 -80
  21. package/docs/guide/concepts.md +51 -51
  22. package/docs/guide/configuration.md +79 -79
  23. package/docs/guide/deployment.md +42 -42
  24. package/docs/guide/getting-started.md +44 -44
  25. package/docs/guide/templates.md +28 -28
  26. package/docs/guide/testing.md +84 -84
  27. package/docs/index.md +27 -27
  28. package/docs/zh/api/cli.md +54 -54
  29. package/docs/zh/api/oad-schema.md +87 -87
  30. package/docs/zh/api/sdk.md +102 -102
  31. package/docs/zh/guide/concepts.md +104 -104
  32. package/docs/zh/guide/configuration.md +135 -135
  33. package/docs/zh/guide/deployment.md +81 -81
  34. package/docs/zh/guide/getting-started.md +82 -82
  35. package/docs/zh/guide/templates.md +84 -84
  36. package/docs/zh/guide/testing.md +88 -88
  37. package/docs/zh/index.md +27 -27
  38. package/examples/customer-service-demo/README.md +90 -90
  39. package/examples/customer-service-demo/oad.yaml +107 -107
  40. package/package.json +1 -1
  41. package/src/analytics/index.ts +66 -66
  42. package/src/channels/discord.ts +192 -192
  43. package/src/channels/email.ts +177 -177
  44. package/src/channels/feishu.ts +236 -236
  45. package/src/channels/index.ts +15 -15
  46. package/src/channels/slack.ts +160 -160
  47. package/src/channels/telegram.ts +90 -90
  48. package/src/channels/voice.ts +106 -106
  49. package/src/channels/web.ts +596 -596
  50. package/src/channels/webhook.ts +199 -199
  51. package/src/channels/websocket.ts +87 -87
  52. package/src/channels/wechat.ts +149 -149
  53. package/src/core/a2a.ts +143 -143
  54. package/src/core/agent.ts +152 -152
  55. package/src/core/analytics-engine.ts +186 -186
  56. package/src/core/auth.ts +57 -57
  57. package/src/core/cache.ts +141 -141
  58. package/src/core/compose.ts +77 -77
  59. package/src/core/config.ts +14 -14
  60. package/src/core/errors.ts +148 -148
  61. package/src/core/hitl.ts +138 -138
  62. package/src/core/knowledge.ts +210 -210
  63. package/src/core/logger.ts +57 -57
  64. package/src/core/orchestrator.ts +215 -215
  65. package/src/core/performance.ts +187 -187
  66. package/src/core/rate-limiter.ts +128 -128
  67. package/src/core/room.ts +109 -109
  68. package/src/core/runtime.ts +152 -152
  69. package/src/core/sandbox.ts +101 -101
  70. package/src/core/security.ts +171 -171
  71. package/src/core/types.ts +68 -68
  72. package/src/core/versioning.ts +106 -106
  73. package/src/core/watch.ts +178 -178
  74. package/src/core/workflow.ts +235 -235
  75. package/src/deploy/hermes.ts +156 -156
  76. package/src/deploy/openclaw.ts +200 -200
  77. package/src/dtv/data.ts +29 -29
  78. package/src/dtv/trust.ts +43 -43
  79. package/src/dtv/value.ts +47 -47
  80. package/src/i18n/index.ts +216 -216
  81. package/src/index.ts +110 -110
  82. package/src/marketplace/index.ts +223 -223
  83. package/src/memory/deepbrain.ts +108 -108
  84. package/src/memory/index.ts +34 -34
  85. package/src/plugins/index.ts +208 -208
  86. package/src/providers/index.ts +322 -183
  87. package/src/schema/oad.ts +155 -155
  88. package/src/skills/base.ts +16 -16
  89. package/src/skills/document.ts +100 -100
  90. package/src/skills/http.ts +35 -35
  91. package/src/skills/index.ts +27 -27
  92. package/src/skills/scheduler.ts +80 -80
  93. package/src/skills/webhook-trigger.ts +59 -59
  94. package/src/templates/code-reviewer.ts +34 -34
  95. package/src/templates/customer-service.ts +80 -80
  96. package/src/templates/data-analyst.ts +70 -70
  97. package/src/templates/executive-assistant.ts +71 -71
  98. package/src/templates/financial-advisor.ts +60 -60
  99. package/src/templates/knowledge-base.ts +31 -31
  100. package/src/templates/legal-assistant.ts +71 -71
  101. package/src/templates/sales-assistant.ts +79 -79
  102. package/src/templates/teacher.ts +79 -79
  103. package/src/testing/index.ts +181 -181
  104. package/src/tools/calculator.ts +73 -73
  105. package/src/tools/datetime.ts +149 -149
  106. package/src/tools/json-transform.ts +187 -187
  107. package/src/tools/mcp.ts +76 -76
  108. package/src/tools/text-analysis.ts +116 -116
  109. package/templates/Dockerfile +15 -15
  110. package/templates/code-reviewer/README.md +27 -27
  111. package/templates/code-reviewer/oad.yaml +41 -41
  112. package/templates/customer-service/README.md +22 -22
  113. package/templates/customer-service/oad.yaml +36 -36
  114. package/templates/docker-compose.yml +21 -21
  115. package/templates/knowledge-base/README.md +28 -28
  116. package/templates/knowledge-base/oad.yaml +38 -38
  117. package/templates/sales-assistant/README.md +26 -26
  118. package/templates/sales-assistant/oad.yaml +43 -43
  119. package/tests/a2a.test.ts +66 -66
  120. package/tests/agent.test.ts +72 -72
  121. package/tests/analytics.test.ts +50 -50
  122. package/tests/channel.test.ts +39 -39
  123. package/tests/e2e.test.ts +134 -134
  124. package/tests/errors.test.ts +83 -83
  125. package/tests/hitl.test.ts +71 -71
  126. package/tests/i18n.test.ts +41 -41
  127. package/tests/mcp.test.ts +54 -54
  128. package/tests/oad.test.ts +68 -68
  129. package/tests/performance.test.ts +115 -115
  130. package/tests/plugin.test.ts +74 -74
  131. package/tests/room.test.ts +106 -106
  132. package/tests/runtime.test.ts +42 -42
  133. package/tests/sandbox.test.ts +46 -46
  134. package/tests/security.test.ts +60 -60
  135. package/tests/templates.test.ts +77 -77
  136. package/tests/v070.test.ts +76 -76
  137. package/tests/versioning.test.ts +75 -75
  138. package/tests/voice.test.ts +61 -61
  139. package/tests/webhook.test.ts +29 -29
  140. package/tests/workflow.test.ts +143 -143
  141. package/tsconfig.json +19 -19
  142. package/vitest.config.ts +9 -9
@@ -1,192 +1,192 @@
1
- import { BaseChannel } from './index';
2
- import type { Message } from '../core/types';
3
-
4
- /**
5
- * Discord Channel — v1.1.0
6
- *
7
- * Supports:
8
- * - Discord Bot via Gateway (WebSocket) or HTTP interactions
9
- * - Slash commands, message content intent
10
- * - Thread-based conversations
11
- * - Reactions, embeds
12
- *
13
- * Env vars:
14
- * DISCORD_BOT_TOKEN — bot token
15
- * DISCORD_APPLICATION_ID — application ID for slash commands
16
- */
17
-
18
- export interface DiscordChannelConfig {
19
- /** Bot token */
20
- botToken?: string;
21
- /** Application ID */
22
- applicationId?: string;
23
- /** Guild IDs to register slash commands (empty = global) */
24
- guildIds?: string[];
25
- /** Whether to use threads for conversations */
26
- useThreads?: boolean;
27
- }
28
-
29
- export class DiscordChannel extends BaseChannel {
30
- readonly type = 'discord';
31
- private config: DiscordChannelConfig;
32
- private ws: import('ws').WebSocket | null = null;
33
- private heartbeatInterval: ReturnType<typeof setInterval> | null = null;
34
- private sequenceNumber: number | null = null;
35
- private sessionId: string | null = null;
36
- private resumeUrl: string | null = null;
37
-
38
- constructor(config: DiscordChannelConfig = {}) {
39
- super();
40
- this.config = {
41
- botToken: config.botToken ?? process.env.DISCORD_BOT_TOKEN ?? '',
42
- applicationId: config.applicationId ?? process.env.DISCORD_APPLICATION_ID ?? '',
43
- guildIds: config.guildIds ?? [],
44
- useThreads: config.useThreads ?? true,
45
- };
46
- }
47
-
48
- async start(): Promise<void> {
49
- if (!this.config.botToken) {
50
- console.warn('[DiscordChannel] No bot token. Set DISCORD_BOT_TOKEN.');
51
- return;
52
- }
53
-
54
- // Get gateway URL
55
- const gatewayResp = await fetch('https://discord.com/api/v10/gateway/bot', {
56
- headers: { Authorization: `Bot ${this.config.botToken}` },
57
- });
58
- const gatewayData = await gatewayResp.json() as { url: string };
59
- const wsUrl = `${gatewayData.url}?v=10&encoding=json`;
60
-
61
- const { WebSocket } = await import('ws');
62
- this.ws = new WebSocket(wsUrl);
63
-
64
- this.ws.on('message', async (data: Buffer) => {
65
- const payload = JSON.parse(data.toString());
66
- this.sequenceNumber = payload.s ?? this.sequenceNumber;
67
-
68
- switch (payload.op) {
69
- case 10: // Hello
70
- this.startHeartbeat(payload.d.heartbeat_interval);
71
- this.identify();
72
- break;
73
- case 11: // Heartbeat ACK
74
- break;
75
- case 0: // Dispatch
76
- if (payload.t === 'READY') {
77
- this.sessionId = payload.d.session_id;
78
- this.resumeUrl = payload.d.resume_gateway_url;
79
- console.log(`[DiscordChannel] Connected as ${payload.d.user.username}`);
80
- } else if (payload.t === 'MESSAGE_CREATE') {
81
- await this.handleMessage(payload.d);
82
- }
83
- break;
84
- }
85
- });
86
-
87
- this.ws.on('close', (code: number) => {
88
- console.log(`[DiscordChannel] WebSocket closed: ${code}`);
89
- this.stopHeartbeat();
90
- // Auto-reconnect after 5s for resumable codes
91
- if (code !== 4004 && code !== 4014) {
92
- setTimeout(() => this.start(), 5000);
93
- }
94
- });
95
-
96
- this.ws.on('error', (err: Error) => {
97
- console.error('[DiscordChannel] WebSocket error:', err.message);
98
- });
99
- }
100
-
101
- async stop(): Promise<void> {
102
- this.stopHeartbeat();
103
- if (this.ws) {
104
- this.ws.close(1000);
105
- this.ws = null;
106
- }
107
- }
108
-
109
- private identify(): void {
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
-
124
- private startHeartbeat(intervalMs: number): void {
125
- this.stopHeartbeat();
126
- // Send first heartbeat with jitter
127
- setTimeout(() => {
128
- this.sendHeartbeat();
129
- this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), intervalMs);
130
- }, intervalMs * Math.random());
131
- }
132
-
133
- private stopHeartbeat(): void {
134
- if (this.heartbeatInterval) {
135
- clearInterval(this.heartbeatInterval);
136
- this.heartbeatInterval = null;
137
- }
138
- }
139
-
140
- private sendHeartbeat(): void {
141
- this.ws?.send(JSON.stringify({ op: 1, d: this.sequenceNumber }));
142
- }
143
-
144
- private async handleMessage(d: Record<string, unknown>): Promise<void> {
145
- // Ignore bot messages
146
- const author = d.author as Record<string, unknown>;
147
- if (author?.bot) return;
148
- if (!d.content || !this.handler) return;
149
-
150
- const msg: Message = {
151
- id: `discord_${d.id}`,
152
- role: 'user',
153
- content: d.content as string,
154
- timestamp: new Date(d.timestamp as string).getTime(),
155
- metadata: {
156
- sessionId: `discord_${d.channel_id}`,
157
- chatId: d.channel_id as string,
158
- userId: author.id as string,
159
- platform: 'discord',
160
- guildId: d.guild_id as string | undefined,
161
- threadId: (d as Record<string, unknown>).thread?.toString(),
162
- },
163
- };
164
-
165
- const response = await this.handler(msg);
166
- await this.sendMessage(d.channel_id as string, response.content);
167
- }
168
-
169
- async sendMessage(channelId: string, content: string): Promise<void> {
170
- // Discord max message length is 2000
171
- const chunks = this.splitMessage(content, 2000);
172
- for (const chunk of chunks) {
173
- await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
174
- method: 'POST',
175
- headers: {
176
- 'Content-Type': 'application/json',
177
- Authorization: `Bot ${this.config.botToken}`,
178
- },
179
- body: JSON.stringify({ content: chunk }),
180
- });
181
- }
182
- }
183
-
184
- private splitMessage(text: string, maxLen: number): string[] {
185
- if (text.length <= maxLen) return [text];
186
- const parts: string[] = [];
187
- for (let i = 0; i < text.length; i += maxLen) {
188
- parts.push(text.slice(i, i + maxLen));
189
- }
190
- return parts;
191
- }
192
- }
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ /**
5
+ * Discord Channel — v1.1.0
6
+ *
7
+ * Supports:
8
+ * - Discord Bot via Gateway (WebSocket) or HTTP interactions
9
+ * - Slash commands, message content intent
10
+ * - Thread-based conversations
11
+ * - Reactions, embeds
12
+ *
13
+ * Env vars:
14
+ * DISCORD_BOT_TOKEN — bot token
15
+ * DISCORD_APPLICATION_ID — application ID for slash commands
16
+ */
17
+
18
+ export interface DiscordChannelConfig {
19
+ /** Bot token */
20
+ botToken?: string;
21
+ /** Application ID */
22
+ applicationId?: string;
23
+ /** Guild IDs to register slash commands (empty = global) */
24
+ guildIds?: string[];
25
+ /** Whether to use threads for conversations */
26
+ useThreads?: boolean;
27
+ }
28
+
29
+ export class DiscordChannel extends BaseChannel {
30
+ readonly type = 'discord';
31
+ private config: DiscordChannelConfig;
32
+ private ws: import('ws').WebSocket | null = null;
33
+ private heartbeatInterval: ReturnType<typeof setInterval> | null = null;
34
+ private sequenceNumber: number | null = null;
35
+ private sessionId: string | null = null;
36
+ private resumeUrl: string | null = null;
37
+
38
+ constructor(config: DiscordChannelConfig = {}) {
39
+ super();
40
+ this.config = {
41
+ botToken: config.botToken ?? process.env.DISCORD_BOT_TOKEN ?? '',
42
+ applicationId: config.applicationId ?? process.env.DISCORD_APPLICATION_ID ?? '',
43
+ guildIds: config.guildIds ?? [],
44
+ useThreads: config.useThreads ?? true,
45
+ };
46
+ }
47
+
48
+ async start(): Promise<void> {
49
+ if (!this.config.botToken) {
50
+ console.warn('[DiscordChannel] No bot token. Set DISCORD_BOT_TOKEN.');
51
+ return;
52
+ }
53
+
54
+ // Get gateway URL
55
+ const gatewayResp = await fetch('https://discord.com/api/v10/gateway/bot', {
56
+ headers: { Authorization: `Bot ${this.config.botToken}` },
57
+ });
58
+ const gatewayData = await gatewayResp.json() as { url: string };
59
+ const wsUrl = `${gatewayData.url}?v=10&encoding=json`;
60
+
61
+ const { WebSocket } = await import('ws');
62
+ this.ws = new WebSocket(wsUrl);
63
+
64
+ this.ws.on('message', async (data: Buffer) => {
65
+ const payload = JSON.parse(data.toString());
66
+ this.sequenceNumber = payload.s ?? this.sequenceNumber;
67
+
68
+ switch (payload.op) {
69
+ case 10: // Hello
70
+ this.startHeartbeat(payload.d.heartbeat_interval);
71
+ this.identify();
72
+ break;
73
+ case 11: // Heartbeat ACK
74
+ break;
75
+ case 0: // Dispatch
76
+ if (payload.t === 'READY') {
77
+ this.sessionId = payload.d.session_id;
78
+ this.resumeUrl = payload.d.resume_gateway_url;
79
+ console.log(`[DiscordChannel] Connected as ${payload.d.user.username}`);
80
+ } else if (payload.t === 'MESSAGE_CREATE') {
81
+ await this.handleMessage(payload.d);
82
+ }
83
+ break;
84
+ }
85
+ });
86
+
87
+ this.ws.on('close', (code: number) => {
88
+ console.log(`[DiscordChannel] WebSocket closed: ${code}`);
89
+ this.stopHeartbeat();
90
+ // Auto-reconnect after 5s for resumable codes
91
+ if (code !== 4004 && code !== 4014) {
92
+ setTimeout(() => this.start(), 5000);
93
+ }
94
+ });
95
+
96
+ this.ws.on('error', (err: Error) => {
97
+ console.error('[DiscordChannel] WebSocket error:', err.message);
98
+ });
99
+ }
100
+
101
+ async stop(): Promise<void> {
102
+ this.stopHeartbeat();
103
+ if (this.ws) {
104
+ this.ws.close(1000);
105
+ this.ws = null;
106
+ }
107
+ }
108
+
109
+ private identify(): void {
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
+
124
+ private startHeartbeat(intervalMs: number): void {
125
+ this.stopHeartbeat();
126
+ // Send first heartbeat with jitter
127
+ setTimeout(() => {
128
+ this.sendHeartbeat();
129
+ this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), intervalMs);
130
+ }, intervalMs * Math.random());
131
+ }
132
+
133
+ private stopHeartbeat(): void {
134
+ if (this.heartbeatInterval) {
135
+ clearInterval(this.heartbeatInterval);
136
+ this.heartbeatInterval = null;
137
+ }
138
+ }
139
+
140
+ private sendHeartbeat(): void {
141
+ this.ws?.send(JSON.stringify({ op: 1, d: this.sequenceNumber }));
142
+ }
143
+
144
+ private async handleMessage(d: Record<string, unknown>): Promise<void> {
145
+ // Ignore bot messages
146
+ const author = d.author as Record<string, unknown>;
147
+ if (author?.bot) return;
148
+ if (!d.content || !this.handler) return;
149
+
150
+ const msg: Message = {
151
+ id: `discord_${d.id}`,
152
+ role: 'user',
153
+ content: d.content as string,
154
+ timestamp: new Date(d.timestamp as string).getTime(),
155
+ metadata: {
156
+ sessionId: `discord_${d.channel_id}`,
157
+ chatId: d.channel_id as string,
158
+ userId: author.id as string,
159
+ platform: 'discord',
160
+ guildId: d.guild_id as string | undefined,
161
+ threadId: (d as Record<string, unknown>).thread?.toString(),
162
+ },
163
+ };
164
+
165
+ const response = await this.handler(msg);
166
+ await this.sendMessage(d.channel_id as string, response.content);
167
+ }
168
+
169
+ async sendMessage(channelId: string, content: string): Promise<void> {
170
+ // Discord max message length is 2000
171
+ const chunks = this.splitMessage(content, 2000);
172
+ for (const chunk of chunks) {
173
+ await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'application/json',
177
+ Authorization: `Bot ${this.config.botToken}`,
178
+ },
179
+ body: JSON.stringify({ content: chunk }),
180
+ });
181
+ }
182
+ }
183
+
184
+ private splitMessage(text: string, maxLen: number): string[] {
185
+ if (text.length <= maxLen) return [text];
186
+ const parts: string[] = [];
187
+ for (let i = 0; i < text.length; i += maxLen) {
188
+ parts.push(text.slice(i, i + maxLen));
189
+ }
190
+ return parts;
191
+ }
192
+ }