opc-agent 1.2.1 → 1.3.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.
Files changed (152) hide show
  1. package/CONTRIBUTING.md +75 -75
  2. package/README.md +235 -358
  3. package/README.zh-CN.md +415 -415
  4. package/dist/channels/web.js +256 -256
  5. package/dist/core/knowledge.d.ts +5 -0
  6. package/dist/core/knowledge.js +39 -2
  7. package/dist/deploy/hermes.js +22 -22
  8. package/dist/deploy/openclaw.js +31 -31
  9. package/dist/index.d.ts +0 -4
  10. package/dist/index.js +1 -7
  11. package/dist/providers/index.d.ts +1 -1
  12. package/dist/providers/index.js +158 -14
  13. package/dist/schema/oad.d.ts +3 -3
  14. package/dist/templates/code-reviewer.js +5 -5
  15. package/dist/templates/customer-service.js +2 -2
  16. package/dist/templates/data-analyst.js +5 -5
  17. package/dist/templates/knowledge-base.js +2 -2
  18. package/dist/templates/sales-assistant.js +4 -4
  19. package/dist/templates/teacher.js +6 -6
  20. package/docs/.vitepress/config.ts +103 -103
  21. package/docs/api/cli.md +48 -48
  22. package/docs/api/oad-schema.md +64 -64
  23. package/docs/api/sdk.md +80 -80
  24. package/docs/guide/concepts.md +51 -51
  25. package/docs/guide/configuration.md +79 -79
  26. package/docs/guide/deployment.md +42 -42
  27. package/docs/guide/getting-started.md +44 -44
  28. package/docs/guide/templates.md +28 -28
  29. package/docs/guide/testing.md +84 -84
  30. package/docs/index.md +27 -27
  31. package/docs/zh/api/cli.md +54 -54
  32. package/docs/zh/api/oad-schema.md +87 -87
  33. package/docs/zh/api/sdk.md +102 -102
  34. package/docs/zh/guide/concepts.md +104 -104
  35. package/docs/zh/guide/configuration.md +135 -135
  36. package/docs/zh/guide/deployment.md +81 -81
  37. package/docs/zh/guide/getting-started.md +82 -82
  38. package/docs/zh/guide/templates.md +84 -84
  39. package/docs/zh/guide/testing.md +88 -88
  40. package/docs/zh/index.md +27 -27
  41. package/examples/customer-service-demo/README.md +90 -90
  42. package/examples/customer-service-demo/oad.yaml +107 -107
  43. package/package.json +50 -50
  44. package/src/analytics/index.ts +66 -66
  45. package/src/channels/discord.ts +192 -192
  46. package/src/channels/email.ts +177 -177
  47. package/src/channels/feishu.ts +236 -236
  48. package/src/channels/index.ts +15 -15
  49. package/src/channels/slack.ts +160 -160
  50. package/src/channels/telegram.ts +90 -90
  51. package/src/channels/voice.ts +106 -106
  52. package/src/channels/webhook.ts +199 -199
  53. package/src/channels/websocket.ts +87 -87
  54. package/src/channels/wechat.ts +149 -149
  55. package/src/cli.ts +119 -1
  56. package/src/core/a2a.ts +143 -143
  57. package/src/core/agent.ts +152 -152
  58. package/src/core/analytics-engine.ts +186 -186
  59. package/src/core/auth.ts +57 -57
  60. package/src/core/cache.ts +141 -141
  61. package/src/core/compose.ts +77 -77
  62. package/src/core/config.ts +14 -14
  63. package/src/core/errors.ts +148 -148
  64. package/src/core/hitl.ts +138 -138
  65. package/src/core/logger.ts +57 -57
  66. package/src/core/orchestrator.ts +215 -215
  67. package/src/core/performance.ts +187 -187
  68. package/src/core/rate-limiter.ts +128 -128
  69. package/src/core/room.ts +109 -109
  70. package/src/core/runtime.ts +152 -152
  71. package/src/core/sandbox.ts +101 -101
  72. package/src/core/security.ts +171 -171
  73. package/src/core/types.ts +68 -68
  74. package/src/core/versioning.ts +106 -106
  75. package/src/core/watch.ts +178 -178
  76. package/src/core/workflow.ts +235 -235
  77. package/src/deploy/hermes.ts +156 -156
  78. package/src/deploy/openclaw.ts +200 -200
  79. package/src/i18n/index.ts +216 -216
  80. package/src/index.ts +6 -2
  81. package/src/memory/deepbrain.ts +108 -108
  82. package/src/memory/index.ts +34 -34
  83. package/src/plugins/index.ts +208 -208
  84. package/src/schema/oad.ts +154 -155
  85. package/src/skills/base.ts +16 -16
  86. package/src/skills/document.ts +100 -100
  87. package/src/skills/http.ts +35 -35
  88. package/src/skills/index.ts +27 -27
  89. package/src/skills/scheduler.ts +80 -80
  90. package/src/skills/webhook-trigger.ts +59 -59
  91. package/src/templates/code-reviewer.ts +30 -34
  92. package/src/templates/customer-service.ts +76 -80
  93. package/src/templates/data-analyst.ts +66 -70
  94. package/src/templates/executive-assistant.ts +71 -71
  95. package/src/templates/financial-advisor.ts +60 -60
  96. package/src/templates/knowledge-base.ts +27 -31
  97. package/src/templates/legal-assistant.ts +71 -71
  98. package/src/templates/sales-assistant.ts +75 -79
  99. package/src/templates/teacher.ts +75 -79
  100. package/src/testing/index.ts +181 -181
  101. package/src/tools/calculator.ts +73 -73
  102. package/src/tools/datetime.ts +149 -149
  103. package/src/tools/json-transform.ts +187 -187
  104. package/src/tools/mcp.ts +76 -76
  105. package/src/tools/text-analysis.ts +116 -116
  106. package/src/traces/index.ts +132 -0
  107. package/templates/Dockerfile +15 -15
  108. package/templates/code-reviewer/README.md +27 -27
  109. package/templates/code-reviewer/oad.yaml +41 -41
  110. package/templates/customer-service/README.md +22 -22
  111. package/templates/customer-service/oad.yaml +36 -36
  112. package/templates/docker-compose.yml +21 -21
  113. package/templates/ecommerce-assistant/README.md +45 -45
  114. package/templates/ecommerce-assistant/oad.yaml +47 -47
  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/templates/tech-support/README.md +43 -43
  120. package/templates/tech-support/oad.yaml +45 -45
  121. package/tests/a2a.test.ts +66 -66
  122. package/tests/agent.test.ts +72 -72
  123. package/tests/analytics.test.ts +50 -50
  124. package/tests/channel.test.ts +39 -39
  125. package/tests/e2e.test.ts +134 -134
  126. package/tests/errors.test.ts +83 -83
  127. package/tests/hitl.test.ts +71 -71
  128. package/tests/i18n.test.ts +41 -41
  129. package/tests/mcp.test.ts +54 -54
  130. package/tests/oad.test.ts +68 -68
  131. package/tests/performance.test.ts +115 -115
  132. package/tests/plugin.test.ts +74 -74
  133. package/tests/room.test.ts +106 -106
  134. package/tests/runtime.test.ts +42 -42
  135. package/tests/sandbox.test.ts +46 -46
  136. package/tests/security.test.ts +60 -60
  137. package/tests/templates.test.ts +77 -77
  138. package/tests/v070.test.ts +76 -76
  139. package/tests/versioning.test.ts +75 -75
  140. package/tests/voice.test.ts +61 -61
  141. package/tests/webhook.test.ts +29 -29
  142. package/tests/workflow.test.ts +143 -143
  143. package/tsconfig.json +19 -19
  144. package/vitest.config.ts +9 -9
  145. package/dist/core/streaming.d.ts +0 -56
  146. package/dist/core/streaming.js +0 -160
  147. package/dist/tools/gateway.d.ts +0 -28
  148. package/dist/tools/gateway.js +0 -177
  149. package/src/dtv/data.ts +0 -29
  150. package/src/dtv/trust.ts +0 -43
  151. package/src/dtv/value.ts +0 -47
  152. package/src/marketplace/index.ts +0 -223
@@ -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
+ }