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.
- package/CONTRIBUTING.md +75 -75
- package/README.md +235 -358
- package/README.zh-CN.md +415 -415
- package/dist/channels/web.js +256 -256
- package/dist/core/knowledge.d.ts +5 -0
- package/dist/core/knowledge.js +39 -2
- package/dist/deploy/hermes.js +22 -22
- package/dist/deploy/openclaw.js +31 -31
- package/dist/index.d.ts +0 -4
- package/dist/index.js +1 -7
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +158 -14
- package/dist/schema/oad.d.ts +3 -3
- package/dist/templates/code-reviewer.js +5 -5
- package/dist/templates/customer-service.js +2 -2
- package/dist/templates/data-analyst.js +5 -5
- package/dist/templates/knowledge-base.js +2 -2
- package/dist/templates/sales-assistant.js +4 -4
- package/dist/templates/teacher.js +6 -6
- package/docs/.vitepress/config.ts +103 -103
- package/docs/api/cli.md +48 -48
- package/docs/api/oad-schema.md +64 -64
- package/docs/api/sdk.md +80 -80
- package/docs/guide/concepts.md +51 -51
- package/docs/guide/configuration.md +79 -79
- package/docs/guide/deployment.md +42 -42
- package/docs/guide/getting-started.md +44 -44
- package/docs/guide/templates.md +28 -28
- package/docs/guide/testing.md +84 -84
- package/docs/index.md +27 -27
- package/docs/zh/api/cli.md +54 -54
- package/docs/zh/api/oad-schema.md +87 -87
- package/docs/zh/api/sdk.md +102 -102
- package/docs/zh/guide/concepts.md +104 -104
- package/docs/zh/guide/configuration.md +135 -135
- package/docs/zh/guide/deployment.md +81 -81
- package/docs/zh/guide/getting-started.md +82 -82
- package/docs/zh/guide/templates.md +84 -84
- package/docs/zh/guide/testing.md +88 -88
- package/docs/zh/index.md +27 -27
- package/examples/customer-service-demo/README.md +90 -90
- package/examples/customer-service-demo/oad.yaml +107 -107
- package/package.json +50 -50
- package/src/analytics/index.ts +66 -66
- package/src/channels/discord.ts +192 -192
- package/src/channels/email.ts +177 -177
- package/src/channels/feishu.ts +236 -236
- package/src/channels/index.ts +15 -15
- package/src/channels/slack.ts +160 -160
- package/src/channels/telegram.ts +90 -90
- package/src/channels/voice.ts +106 -106
- package/src/channels/webhook.ts +199 -199
- package/src/channels/websocket.ts +87 -87
- package/src/channels/wechat.ts +149 -149
- package/src/cli.ts +119 -1
- package/src/core/a2a.ts +143 -143
- package/src/core/agent.ts +152 -152
- package/src/core/analytics-engine.ts +186 -186
- package/src/core/auth.ts +57 -57
- package/src/core/cache.ts +141 -141
- package/src/core/compose.ts +77 -77
- package/src/core/config.ts +14 -14
- package/src/core/errors.ts +148 -148
- package/src/core/hitl.ts +138 -138
- package/src/core/logger.ts +57 -57
- package/src/core/orchestrator.ts +215 -215
- package/src/core/performance.ts +187 -187
- package/src/core/rate-limiter.ts +128 -128
- package/src/core/room.ts +109 -109
- package/src/core/runtime.ts +152 -152
- package/src/core/sandbox.ts +101 -101
- package/src/core/security.ts +171 -171
- package/src/core/types.ts +68 -68
- package/src/core/versioning.ts +106 -106
- package/src/core/watch.ts +178 -178
- package/src/core/workflow.ts +235 -235
- package/src/deploy/hermes.ts +156 -156
- package/src/deploy/openclaw.ts +200 -200
- package/src/i18n/index.ts +216 -216
- package/src/index.ts +6 -2
- package/src/memory/deepbrain.ts +108 -108
- package/src/memory/index.ts +34 -34
- package/src/plugins/index.ts +208 -208
- package/src/schema/oad.ts +154 -155
- package/src/skills/base.ts +16 -16
- package/src/skills/document.ts +100 -100
- package/src/skills/http.ts +35 -35
- package/src/skills/index.ts +27 -27
- package/src/skills/scheduler.ts +80 -80
- package/src/skills/webhook-trigger.ts +59 -59
- package/src/templates/code-reviewer.ts +30 -34
- package/src/templates/customer-service.ts +76 -80
- package/src/templates/data-analyst.ts +66 -70
- package/src/templates/executive-assistant.ts +71 -71
- package/src/templates/financial-advisor.ts +60 -60
- package/src/templates/knowledge-base.ts +27 -31
- package/src/templates/legal-assistant.ts +71 -71
- package/src/templates/sales-assistant.ts +75 -79
- package/src/templates/teacher.ts +75 -79
- package/src/testing/index.ts +181 -181
- package/src/tools/calculator.ts +73 -73
- package/src/tools/datetime.ts +149 -149
- package/src/tools/json-transform.ts +187 -187
- package/src/tools/mcp.ts +76 -76
- package/src/tools/text-analysis.ts +116 -116
- package/src/traces/index.ts +132 -0
- package/templates/Dockerfile +15 -15
- package/templates/code-reviewer/README.md +27 -27
- package/templates/code-reviewer/oad.yaml +41 -41
- package/templates/customer-service/README.md +22 -22
- package/templates/customer-service/oad.yaml +36 -36
- package/templates/docker-compose.yml +21 -21
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/knowledge-base/README.md +28 -28
- package/templates/knowledge-base/oad.yaml +38 -38
- package/templates/sales-assistant/README.md +26 -26
- package/templates/sales-assistant/oad.yaml +43 -43
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/tests/a2a.test.ts +66 -66
- package/tests/agent.test.ts +72 -72
- package/tests/analytics.test.ts +50 -50
- package/tests/channel.test.ts +39 -39
- package/tests/e2e.test.ts +134 -134
- package/tests/errors.test.ts +83 -83
- package/tests/hitl.test.ts +71 -71
- package/tests/i18n.test.ts +41 -41
- package/tests/mcp.test.ts +54 -54
- package/tests/oad.test.ts +68 -68
- package/tests/performance.test.ts +115 -115
- package/tests/plugin.test.ts +74 -74
- package/tests/room.test.ts +106 -106
- package/tests/runtime.test.ts +42 -42
- package/tests/sandbox.test.ts +46 -46
- package/tests/security.test.ts +60 -60
- package/tests/templates.test.ts +77 -77
- package/tests/v070.test.ts +76 -76
- package/tests/versioning.test.ts +75 -75
- package/tests/voice.test.ts +61 -61
- package/tests/webhook.test.ts +29 -29
- package/tests/workflow.test.ts +143 -143
- package/tsconfig.json +19 -19
- package/vitest.config.ts +9 -9
- package/dist/core/streaming.d.ts +0 -56
- package/dist/core/streaming.js +0 -160
- package/dist/tools/gateway.d.ts +0 -28
- package/dist/tools/gateway.js +0 -177
- package/src/dtv/data.ts +0 -29
- package/src/dtv/trust.ts +0 -43
- package/src/dtv/value.ts +0 -47
- package/src/marketplace/index.ts +0 -223
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import type { Message } from '../core/types';
|
|
2
|
-
import { BaseChannel } from './index';
|
|
3
|
-
import { WebSocketServer, type WebSocket } from 'ws';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* WebSocket channel — real-time bidirectional communication.
|
|
7
|
-
*/
|
|
8
|
-
export class WebSocketChannel extends BaseChannel {
|
|
9
|
-
readonly type = 'websocket';
|
|
10
|
-
private wss: WebSocketServer | null = null;
|
|
11
|
-
private port: number;
|
|
12
|
-
private clients: Set<WebSocket> = new Set();
|
|
13
|
-
|
|
14
|
-
constructor(port: number = 3002) {
|
|
15
|
-
super();
|
|
16
|
-
this.port = port;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async start(): Promise<void> {
|
|
20
|
-
return new Promise((resolve) => {
|
|
21
|
-
this.wss = new WebSocketServer({ port: this.port });
|
|
22
|
-
|
|
23
|
-
this.wss.on('connection', (ws) => {
|
|
24
|
-
this.clients.add(ws);
|
|
25
|
-
|
|
26
|
-
ws.on('message', async (data) => {
|
|
27
|
-
if (!this.handler) return;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const parsed = JSON.parse(data.toString());
|
|
31
|
-
const msg: Message = {
|
|
32
|
-
id: parsed.id ?? `ws_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
33
|
-
role: 'user',
|
|
34
|
-
content: parsed.content ?? parsed.message ?? data.toString(),
|
|
35
|
-
timestamp: Date.now(),
|
|
36
|
-
metadata: {
|
|
37
|
-
sessionId: parsed.sessionId ?? 'ws-default',
|
|
38
|
-
platform: 'websocket',
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const response = await this.handler(msg);
|
|
43
|
-
ws.send(JSON.stringify({
|
|
44
|
-
id: response.id,
|
|
45
|
-
content: response.content,
|
|
46
|
-
timestamp: response.timestamp,
|
|
47
|
-
}));
|
|
48
|
-
} catch (err) {
|
|
49
|
-
ws.send(JSON.stringify({ error: 'Invalid message format' }));
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
ws.on('close', () => {
|
|
54
|
-
this.clients.delete(ws);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
ws.send(JSON.stringify({ type: 'connected', timestamp: Date.now() }));
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
this.wss.on('listening', () => {
|
|
61
|
-
console.log(`[WebSocketChannel] Listening on port ${this.port}`);
|
|
62
|
-
resolve();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async stop(): Promise<void> {
|
|
68
|
-
for (const client of this.clients) {
|
|
69
|
-
client.close();
|
|
70
|
-
}
|
|
71
|
-
this.clients.clear();
|
|
72
|
-
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
74
|
-
if (!this.wss) return resolve();
|
|
75
|
-
this.wss.close((err) => (err ? reject(err) : resolve()));
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
broadcast(content: string): void {
|
|
80
|
-
const msg = JSON.stringify({ type: 'broadcast', content, timestamp: Date.now() });
|
|
81
|
-
for (const client of this.clients) {
|
|
82
|
-
if (client.readyState === 1) { // OPEN
|
|
83
|
-
client.send(msg);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
import type { Message } from '../core/types';
|
|
2
|
+
import { BaseChannel } from './index';
|
|
3
|
+
import { WebSocketServer, type WebSocket } from 'ws';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WebSocket channel — real-time bidirectional communication.
|
|
7
|
+
*/
|
|
8
|
+
export class WebSocketChannel extends BaseChannel {
|
|
9
|
+
readonly type = 'websocket';
|
|
10
|
+
private wss: WebSocketServer | null = null;
|
|
11
|
+
private port: number;
|
|
12
|
+
private clients: Set<WebSocket> = new Set();
|
|
13
|
+
|
|
14
|
+
constructor(port: number = 3002) {
|
|
15
|
+
super();
|
|
16
|
+
this.port = port;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async start(): Promise<void> {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
this.wss = new WebSocketServer({ port: this.port });
|
|
22
|
+
|
|
23
|
+
this.wss.on('connection', (ws) => {
|
|
24
|
+
this.clients.add(ws);
|
|
25
|
+
|
|
26
|
+
ws.on('message', async (data) => {
|
|
27
|
+
if (!this.handler) return;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(data.toString());
|
|
31
|
+
const msg: Message = {
|
|
32
|
+
id: parsed.id ?? `ws_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
33
|
+
role: 'user',
|
|
34
|
+
content: parsed.content ?? parsed.message ?? data.toString(),
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
metadata: {
|
|
37
|
+
sessionId: parsed.sessionId ?? 'ws-default',
|
|
38
|
+
platform: 'websocket',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const response = await this.handler(msg);
|
|
43
|
+
ws.send(JSON.stringify({
|
|
44
|
+
id: response.id,
|
|
45
|
+
content: response.content,
|
|
46
|
+
timestamp: response.timestamp,
|
|
47
|
+
}));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
ws.send(JSON.stringify({ error: 'Invalid message format' }));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
ws.on('close', () => {
|
|
54
|
+
this.clients.delete(ws);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
ws.send(JSON.stringify({ type: 'connected', timestamp: Date.now() }));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.wss.on('listening', () => {
|
|
61
|
+
console.log(`[WebSocketChannel] Listening on port ${this.port}`);
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async stop(): Promise<void> {
|
|
68
|
+
for (const client of this.clients) {
|
|
69
|
+
client.close();
|
|
70
|
+
}
|
|
71
|
+
this.clients.clear();
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
if (!this.wss) return resolve();
|
|
75
|
+
this.wss.close((err) => (err ? reject(err) : resolve()));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
broadcast(content: string): void {
|
|
80
|
+
const msg = JSON.stringify({ type: 'broadcast', content, timestamp: Date.now() });
|
|
81
|
+
for (const client of this.clients) {
|
|
82
|
+
if (client.readyState === 1) { // OPEN
|
|
83
|
+
client.send(msg);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/channels/wechat.ts
CHANGED
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
import { BaseChannel } from './index';
|
|
2
|
-
import type { Message } from '../core/types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* WeChat Channel (Stub) — v0.8.0
|
|
6
|
-
* WeChat Official Account message handling, template messages, QR code login.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export interface WeChatChannelConfig {
|
|
10
|
-
/** WeChat Official Account AppID */
|
|
11
|
-
appId: string;
|
|
12
|
-
/** WeChat Official Account AppSecret */
|
|
13
|
-
appSecret: string;
|
|
14
|
-
/** Verification token for message validation */
|
|
15
|
-
token: string;
|
|
16
|
-
/** AES encoding key for encrypted messages */
|
|
17
|
-
encodingAESKey?: string;
|
|
18
|
-
/** HTTP server port (default: 3002) */
|
|
19
|
-
port?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface WeChatMessage {
|
|
23
|
-
toUserName: string;
|
|
24
|
-
fromUserName: string;
|
|
25
|
-
createTime: number;
|
|
26
|
-
msgType: 'text' | 'image' | 'voice' | 'video' | 'event';
|
|
27
|
-
content?: string;
|
|
28
|
-
msgId?: string;
|
|
29
|
-
event?: string;
|
|
30
|
-
eventKey?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface TemplateMessageData {
|
|
34
|
-
toUser: string;
|
|
35
|
-
templateId: string;
|
|
36
|
-
url?: string;
|
|
37
|
-
data: Record<string, { value: string; color?: string }>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class WeChatChannel extends BaseChannel {
|
|
41
|
-
type = 'wechat';
|
|
42
|
-
private config: WeChatChannelConfig;
|
|
43
|
-
private accessToken: string | null = null;
|
|
44
|
-
private tokenExpiry = 0;
|
|
45
|
-
|
|
46
|
-
constructor(config: WeChatChannelConfig) {
|
|
47
|
-
super();
|
|
48
|
-
this.config = config;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async start(): Promise<void> {
|
|
52
|
-
// TODO: Start HTTP server to receive WeChat push messages
|
|
53
|
-
// 1. Verify signature on GET requests
|
|
54
|
-
// 2. Parse XML messages on POST requests
|
|
55
|
-
// 3. Route to handler and reply with XML
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async stop(): Promise<void> {
|
|
59
|
-
// TODO: Stop HTTP server
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Get or refresh access token */
|
|
63
|
-
async getAccessToken(): Promise<string> {
|
|
64
|
-
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
65
|
-
return this.accessToken;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// TODO: Implement token refresh
|
|
69
|
-
// const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.config.appId}&secret=${this.config.appSecret}`;
|
|
70
|
-
// const res = await fetch(url);
|
|
71
|
-
// const data = await res.json();
|
|
72
|
-
// this.accessToken = data.access_token;
|
|
73
|
-
// this.tokenExpiry = Date.now() + (data.expires_in - 300) * 1000;
|
|
74
|
-
|
|
75
|
-
return this.accessToken ?? '';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Handle incoming WeChat message */
|
|
79
|
-
async handleMessage(wxMsg: WeChatMessage): Promise<string> {
|
|
80
|
-
if (wxMsg.msgType === 'event') {
|
|
81
|
-
return this.handleEvent(wxMsg);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const message = this.wechatToMessage(wxMsg);
|
|
85
|
-
if (this.handler) {
|
|
86
|
-
const reply = await this.handler(message);
|
|
87
|
-
return reply.content;
|
|
88
|
-
}
|
|
89
|
-
return '';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Handle WeChat events (subscribe, scan, etc.) */
|
|
93
|
-
private handleEvent(wxMsg: WeChatMessage): string {
|
|
94
|
-
switch (wxMsg.event) {
|
|
95
|
-
case 'subscribe':
|
|
96
|
-
return 'Welcome! How can I help you?';
|
|
97
|
-
case 'SCAN':
|
|
98
|
-
return `QR code scanned: ${wxMsg.eventKey}`;
|
|
99
|
-
default:
|
|
100
|
-
return '';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Convert WeChat message to internal Message */
|
|
105
|
-
private wechatToMessage(wxMsg: WeChatMessage): Message {
|
|
106
|
-
return {
|
|
107
|
-
id: wxMsg.msgId ?? `wx-${wxMsg.createTime}`,
|
|
108
|
-
role: 'user',
|
|
109
|
-
content: wxMsg.content ?? '',
|
|
110
|
-
timestamp: wxMsg.createTime * 1000,
|
|
111
|
-
metadata: {
|
|
112
|
-
channel: 'wechat',
|
|
113
|
-
fromUser: wxMsg.fromUserName,
|
|
114
|
-
toUser: wxMsg.toUserName,
|
|
115
|
-
msgType: wxMsg.msgType,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Send template message */
|
|
121
|
-
async sendTemplateMessage(data: TemplateMessageData): Promise<boolean> {
|
|
122
|
-
// TODO: Implement
|
|
123
|
-
// const token = await this.getAccessToken();
|
|
124
|
-
// const url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`;
|
|
125
|
-
// const res = await fetch(url, {
|
|
126
|
-
// method: 'POST',
|
|
127
|
-
// body: JSON.stringify({
|
|
128
|
-
// touser: data.toUser,
|
|
129
|
-
// template_id: data.templateId,
|
|
130
|
-
// url: data.url,
|
|
131
|
-
// data: data.data,
|
|
132
|
-
// }),
|
|
133
|
-
// });
|
|
134
|
-
void data;
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Generate QR code for login (stub) */
|
|
139
|
-
async generateLoginQR(): Promise<{ ticket: string; url: string; expireSeconds: number }> {
|
|
140
|
-
// TODO: Implement with WeChat QR code API
|
|
141
|
-
// const token = await this.getAccessToken();
|
|
142
|
-
// POST to https://api.weixin.qq.com/cgi-bin/qrcode/create
|
|
143
|
-
return {
|
|
144
|
-
ticket: 'stub-ticket',
|
|
145
|
-
url: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=stub-ticket',
|
|
146
|
-
expireSeconds: 300,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
1
|
+
import { BaseChannel } from './index';
|
|
2
|
+
import type { Message } from '../core/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* WeChat Channel (Stub) — v0.8.0
|
|
6
|
+
* WeChat Official Account message handling, template messages, QR code login.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface WeChatChannelConfig {
|
|
10
|
+
/** WeChat Official Account AppID */
|
|
11
|
+
appId: string;
|
|
12
|
+
/** WeChat Official Account AppSecret */
|
|
13
|
+
appSecret: string;
|
|
14
|
+
/** Verification token for message validation */
|
|
15
|
+
token: string;
|
|
16
|
+
/** AES encoding key for encrypted messages */
|
|
17
|
+
encodingAESKey?: string;
|
|
18
|
+
/** HTTP server port (default: 3002) */
|
|
19
|
+
port?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WeChatMessage {
|
|
23
|
+
toUserName: string;
|
|
24
|
+
fromUserName: string;
|
|
25
|
+
createTime: number;
|
|
26
|
+
msgType: 'text' | 'image' | 'voice' | 'video' | 'event';
|
|
27
|
+
content?: string;
|
|
28
|
+
msgId?: string;
|
|
29
|
+
event?: string;
|
|
30
|
+
eventKey?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TemplateMessageData {
|
|
34
|
+
toUser: string;
|
|
35
|
+
templateId: string;
|
|
36
|
+
url?: string;
|
|
37
|
+
data: Record<string, { value: string; color?: string }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class WeChatChannel extends BaseChannel {
|
|
41
|
+
type = 'wechat';
|
|
42
|
+
private config: WeChatChannelConfig;
|
|
43
|
+
private accessToken: string | null = null;
|
|
44
|
+
private tokenExpiry = 0;
|
|
45
|
+
|
|
46
|
+
constructor(config: WeChatChannelConfig) {
|
|
47
|
+
super();
|
|
48
|
+
this.config = config;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async start(): Promise<void> {
|
|
52
|
+
// TODO: Start HTTP server to receive WeChat push messages
|
|
53
|
+
// 1. Verify signature on GET requests
|
|
54
|
+
// 2. Parse XML messages on POST requests
|
|
55
|
+
// 3. Route to handler and reply with XML
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async stop(): Promise<void> {
|
|
59
|
+
// TODO: Stop HTTP server
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Get or refresh access token */
|
|
63
|
+
async getAccessToken(): Promise<string> {
|
|
64
|
+
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
65
|
+
return this.accessToken;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// TODO: Implement token refresh
|
|
69
|
+
// const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.config.appId}&secret=${this.config.appSecret}`;
|
|
70
|
+
// const res = await fetch(url);
|
|
71
|
+
// const data = await res.json();
|
|
72
|
+
// this.accessToken = data.access_token;
|
|
73
|
+
// this.tokenExpiry = Date.now() + (data.expires_in - 300) * 1000;
|
|
74
|
+
|
|
75
|
+
return this.accessToken ?? '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Handle incoming WeChat message */
|
|
79
|
+
async handleMessage(wxMsg: WeChatMessage): Promise<string> {
|
|
80
|
+
if (wxMsg.msgType === 'event') {
|
|
81
|
+
return this.handleEvent(wxMsg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const message = this.wechatToMessage(wxMsg);
|
|
85
|
+
if (this.handler) {
|
|
86
|
+
const reply = await this.handler(message);
|
|
87
|
+
return reply.content;
|
|
88
|
+
}
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Handle WeChat events (subscribe, scan, etc.) */
|
|
93
|
+
private handleEvent(wxMsg: WeChatMessage): string {
|
|
94
|
+
switch (wxMsg.event) {
|
|
95
|
+
case 'subscribe':
|
|
96
|
+
return 'Welcome! How can I help you?';
|
|
97
|
+
case 'SCAN':
|
|
98
|
+
return `QR code scanned: ${wxMsg.eventKey}`;
|
|
99
|
+
default:
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Convert WeChat message to internal Message */
|
|
105
|
+
private wechatToMessage(wxMsg: WeChatMessage): Message {
|
|
106
|
+
return {
|
|
107
|
+
id: wxMsg.msgId ?? `wx-${wxMsg.createTime}`,
|
|
108
|
+
role: 'user',
|
|
109
|
+
content: wxMsg.content ?? '',
|
|
110
|
+
timestamp: wxMsg.createTime * 1000,
|
|
111
|
+
metadata: {
|
|
112
|
+
channel: 'wechat',
|
|
113
|
+
fromUser: wxMsg.fromUserName,
|
|
114
|
+
toUser: wxMsg.toUserName,
|
|
115
|
+
msgType: wxMsg.msgType,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Send template message */
|
|
121
|
+
async sendTemplateMessage(data: TemplateMessageData): Promise<boolean> {
|
|
122
|
+
// TODO: Implement
|
|
123
|
+
// const token = await this.getAccessToken();
|
|
124
|
+
// const url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`;
|
|
125
|
+
// const res = await fetch(url, {
|
|
126
|
+
// method: 'POST',
|
|
127
|
+
// body: JSON.stringify({
|
|
128
|
+
// touser: data.toUser,
|
|
129
|
+
// template_id: data.templateId,
|
|
130
|
+
// url: data.url,
|
|
131
|
+
// data: data.data,
|
|
132
|
+
// }),
|
|
133
|
+
// });
|
|
134
|
+
void data;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Generate QR code for login (stub) */
|
|
139
|
+
async generateLoginQR(): Promise<{ ticket: string; url: string; expireSeconds: number }> {
|
|
140
|
+
// TODO: Implement with WeChat QR code API
|
|
141
|
+
// const token = await this.getAccessToken();
|
|
142
|
+
// POST to https://api.weixin.qq.com/cgi-bin/qrcode/create
|
|
143
|
+
return {
|
|
144
|
+
ticket: 'stub-ticket',
|
|
145
|
+
url: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=stub-ticket',
|
|
146
|
+
expireSeconds: 300,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -27,9 +27,9 @@ import { WorkflowEngine } from './core/workflow';
|
|
|
27
27
|
import { VersionManager } from './core/versioning';
|
|
28
28
|
import { createProvider } from './providers';
|
|
29
29
|
import { KnowledgeBase } from './core/knowledge';
|
|
30
|
-
import { publishAgent, installAgent } from './marketplace';
|
|
31
30
|
|
|
32
31
|
import { PluginManager, createLoggingPlugin, createAnalyticsPlugin, createRateLimitPlugin } from './plugins';
|
|
32
|
+
import type { Span } from './traces';
|
|
33
33
|
|
|
34
34
|
const program = new Command();
|
|
35
35
|
|
|
@@ -873,4 +873,122 @@ program
|
|
|
873
873
|
}
|
|
874
874
|
});
|
|
875
875
|
|
|
876
|
+
// ── Brain command ────────────────────────────────────────────
|
|
877
|
+
|
|
878
|
+
program
|
|
879
|
+
.command('brain')
|
|
880
|
+
.description('Show agent memory/brain status from DeepBrain')
|
|
881
|
+
.option('--url <url>', 'DeepBrain server URL', 'http://localhost:3333')
|
|
882
|
+
.action(async (opts: { url: string }) => {
|
|
883
|
+
console.log(`\n${icon.gear} ${color.bold('DeepBrain Status')} — ${color.dim(opts.url)}\n`);
|
|
884
|
+
try {
|
|
885
|
+
const res = await fetch(`${opts.url}/api/stats`);
|
|
886
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
887
|
+
const stats = (await res.json()) as Record<string, any>;
|
|
888
|
+
const rows: [string, string][] = [
|
|
889
|
+
['Total Pages', String(stats.totalPages ?? stats.pages ?? '-')],
|
|
890
|
+
['Total Chunks', String(stats.totalChunks ?? stats.chunks ?? '-')],
|
|
891
|
+
['Memory Tiers', String(stats.memoryTiers ?? stats.tiers ?? '-')],
|
|
892
|
+
['Index Size', stats.indexSize ?? '-'],
|
|
893
|
+
['Last Updated', stats.lastUpdated ?? stats.updatedAt ?? '-'],
|
|
894
|
+
];
|
|
895
|
+
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
896
|
+
for (const [key, val] of rows) {
|
|
897
|
+
console.log(` ${color.cyan(key.padEnd(maxKey))} ${val}`);
|
|
898
|
+
}
|
|
899
|
+
console.log();
|
|
900
|
+
} catch (err) {
|
|
901
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
902
|
+
if (msg.includes('ECONNREFUSED') || msg.includes('fetch failed')) {
|
|
903
|
+
console.log(` ${icon.warn} Cannot connect to DeepBrain at ${opts.url}`);
|
|
904
|
+
console.log(` ${color.dim('Is the server running? Start with: deepbrain serve')}\n`);
|
|
905
|
+
} else {
|
|
906
|
+
console.error(` ${icon.error} ${msg}\n`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// ── Logs command ─────────────────────────────────────────────
|
|
912
|
+
|
|
913
|
+
program
|
|
914
|
+
.command('logs')
|
|
915
|
+
.description('Show recent agent traces')
|
|
916
|
+
.option('-n, --limit <n>', 'Number of spans to show', '20')
|
|
917
|
+
.option('-f, --follow', 'Keep watching for new spans')
|
|
918
|
+
.action(async (opts: { limit: string; follow?: boolean }) => {
|
|
919
|
+
const { TraceCollector } = await import('./traces');
|
|
920
|
+
const collector = new TraceCollector();
|
|
921
|
+
const limit = parseInt(opts.limit) || 20;
|
|
922
|
+
|
|
923
|
+
const printSpans = (spans: readonly Span[]) => {
|
|
924
|
+
const slice = spans.slice(-limit);
|
|
925
|
+
if (slice.length === 0) {
|
|
926
|
+
console.log(` ${icon.info} No traces yet. Interact with the agent to generate traces.`);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
for (const span of slice) {
|
|
930
|
+
const duration = span.endTime
|
|
931
|
+
? `${span.endTime.getTime() - span.startTime.getTime()}ms`
|
|
932
|
+
: 'ongoing';
|
|
933
|
+
const statusIcon = span.status === 'ok' ? icon.success : span.status === 'error' ? icon.error : color.dim('○');
|
|
934
|
+
const time = span.startTime.toLocaleTimeString();
|
|
935
|
+
console.log(` ${statusIcon} ${color.dim(time)} ${color.bold(span.name)} ${color.dim(duration)}`);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
console.log(`\n${icon.gear} ${color.bold('Agent Traces')}\n`);
|
|
940
|
+
const spans = collector.getBufferedSpans();
|
|
941
|
+
printSpans(spans);
|
|
942
|
+
|
|
943
|
+
if (opts.follow) {
|
|
944
|
+
console.log(`\n ${color.dim('Watching for new traces... (Ctrl+C to stop)')}\n`);
|
|
945
|
+
let lastCount = spans.length;
|
|
946
|
+
const interval = setInterval(() => {
|
|
947
|
+
const current = collector.getBufferedSpans();
|
|
948
|
+
if (current.length > lastCount) {
|
|
949
|
+
const newSpans = current.slice(lastCount);
|
|
950
|
+
printSpans(newSpans);
|
|
951
|
+
lastCount = current.length;
|
|
952
|
+
}
|
|
953
|
+
}, 1000);
|
|
954
|
+
process.on('SIGINT', () => { clearInterval(interval); process.exit(0); });
|
|
955
|
+
} else {
|
|
956
|
+
console.log();
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
// ── Score command ────────────────────────────────────────────
|
|
961
|
+
|
|
962
|
+
program
|
|
963
|
+
.command('score')
|
|
964
|
+
.description('Show agent performance score')
|
|
965
|
+
.action(async () => {
|
|
966
|
+
console.log(`\n${icon.gear} ${color.bold('Agent Performance Score')}\n`);
|
|
967
|
+
try {
|
|
968
|
+
const engine = new AnalyticsEngine('.');
|
|
969
|
+
const stats = engine.getStats();
|
|
970
|
+
if (!stats || stats.totalMessages === 0) {
|
|
971
|
+
console.log(` ${icon.info} No score data yet. Run the agent first.\n`);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const errorRate = stats.totalMessages > 0 ? (stats.totalErrors / stats.totalMessages) : 0;
|
|
975
|
+
const rows: [string, string][] = [
|
|
976
|
+
['Total Messages', String(stats.totalMessages)],
|
|
977
|
+
['Total LLM Calls', String(stats.totalLLMCalls)],
|
|
978
|
+
['Total Tool Uses', String(stats.totalToolUses)],
|
|
979
|
+
['Avg Response Time', `${stats.avgResponseTimeMs}ms`],
|
|
980
|
+
['Error Rate', `${(errorRate * 100).toFixed(1)}%`],
|
|
981
|
+
['Token Usage', `${stats.totalTokens.total} tokens (in: ${stats.totalTokens.input}, out: ${stats.totalTokens.output})`],
|
|
982
|
+
];
|
|
983
|
+
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
984
|
+
for (const [key, val] of rows) {
|
|
985
|
+
console.log(` ${color.cyan(key.padEnd(maxKey))} ${val}`);
|
|
986
|
+
}
|
|
987
|
+
console.log();
|
|
988
|
+
} catch {
|
|
989
|
+
console.log(` ${icon.info} No score data yet. Run the agent first.\n`);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
|
|
876
993
|
program.parse();
|
|
994
|
+
|