@efengx/openclaw-channel-dragon 0.5.16 → 0.5.18
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/dist/components/channel/ChannelComponent.d.ts +1 -0
- package/dist/components/channel/ChannelComponent.js +140 -1
- package/dist/components/telemetry/TelemetryComponent.d.ts +11 -0
- package/dist/components/telemetry/TelemetryComponent.js +33 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/components/bridge/BridgeComponent.d.ts +0 -17
- package/dist/components/bridge/BridgeComponent.js +0 -45
- package/dist/components/sync/PollingComponent.d.ts +0 -19
- package/dist/components/sync/PollingComponent.js +0 -42
|
@@ -15,6 +15,7 @@ export declare class ChannelComponent implements IComponent {
|
|
|
15
15
|
constructor(options: ChannelOptions, telemetry: TelemetryComponent);
|
|
16
16
|
start(): Promise<void>;
|
|
17
17
|
stop(): Promise<void>;
|
|
18
|
+
private stringifyProgressDetail;
|
|
18
19
|
deliverToOpenClaw: (content: string, sessionId?: string, modelId?: string, attachments?: any[], messageId?: string | number) => Promise<void>;
|
|
19
20
|
handleOutboundText: (ctx: any) => Promise<{
|
|
20
21
|
ok: boolean;
|
|
@@ -10,6 +10,21 @@ export class ChannelComponent {
|
|
|
10
10
|
}
|
|
11
11
|
async start() { }
|
|
12
12
|
async stop() { }
|
|
13
|
+
stringifyProgressDetail(value, maxLength = 240) {
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
const text = value.trim();
|
|
16
|
+
return text ? text.slice(0, maxLength) : undefined;
|
|
17
|
+
}
|
|
18
|
+
if (value == null)
|
|
19
|
+
return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const text = JSON.stringify(value);
|
|
22
|
+
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return String(value).slice(0, maxLength);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
13
28
|
deliverToOpenClaw = async (content, sessionId = 'default', modelId, attachments, messageId) => {
|
|
14
29
|
const replyMsgId = messageId ? `dragon_msg_${messageId}` : `dragon_msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
15
30
|
if (messageId && this.processedMessageIds.has(messageId))
|
|
@@ -32,6 +47,11 @@ export class ChannelComponent {
|
|
|
32
47
|
.catch(() => { })
|
|
33
48
|
.then(async () => {
|
|
34
49
|
logger?.info?.(`[Dragon Plugin] Sending to OpenClaw: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}" [Session: ${sessionId}]`);
|
|
50
|
+
const progress = (payload) => this.telemetry.reportProgress({
|
|
51
|
+
sessionId,
|
|
52
|
+
msgId: replyMsgId,
|
|
53
|
+
...payload,
|
|
54
|
+
});
|
|
35
55
|
await replyDispatcher({
|
|
36
56
|
ctx: {
|
|
37
57
|
Body: content,
|
|
@@ -61,7 +81,126 @@ export class ChannelComponent {
|
|
|
61
81
|
msgId: replyMsgId,
|
|
62
82
|
});
|
|
63
83
|
}
|
|
64
|
-
}
|
|
84
|
+
},
|
|
85
|
+
replyOptions: {
|
|
86
|
+
onReplyStart: async () => {
|
|
87
|
+
await progress({ kind: 'lifecycle', phase: 'start', status: 'running', title: '开始处理请求' });
|
|
88
|
+
},
|
|
89
|
+
onAssistantMessageStart: async () => {
|
|
90
|
+
await progress({ kind: 'assistant', phase: 'start', status: 'running', title: '开始生成回复' });
|
|
91
|
+
},
|
|
92
|
+
onPartialReply: async (payload) => {
|
|
93
|
+
await progress({
|
|
94
|
+
kind: 'assistant_delta',
|
|
95
|
+
phase: 'update',
|
|
96
|
+
status: 'running',
|
|
97
|
+
title: '正在生成回复',
|
|
98
|
+
detail: this.stringifyProgressDetail(payload?.delta || payload?.text),
|
|
99
|
+
data: { replace: payload?.replace === true },
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
onReasoningStream: async (payload) => {
|
|
103
|
+
await progress({
|
|
104
|
+
kind: 'reasoning',
|
|
105
|
+
phase: 'update',
|
|
106
|
+
status: 'running',
|
|
107
|
+
title: '正在推理',
|
|
108
|
+
detail: this.stringifyProgressDetail(payload?.text || payload),
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
onReasoningEnd: async () => {
|
|
112
|
+
await progress({ kind: 'reasoning', phase: 'end', status: 'completed', title: '推理完成' });
|
|
113
|
+
},
|
|
114
|
+
onToolStart: async (payload) => {
|
|
115
|
+
await progress({
|
|
116
|
+
kind: 'tool',
|
|
117
|
+
phase: payload?.phase || 'start',
|
|
118
|
+
status: 'running',
|
|
119
|
+
title: payload?.name ? `调用工具:${payload.name}` : '调用工具',
|
|
120
|
+
detail: this.stringifyProgressDetail(payload?.args),
|
|
121
|
+
data: payload,
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
onToolResult: async (payload) => {
|
|
125
|
+
await progress({
|
|
126
|
+
kind: 'tool',
|
|
127
|
+
phase: 'end',
|
|
128
|
+
status: 'completed',
|
|
129
|
+
title: '工具执行完成',
|
|
130
|
+
detail: this.stringifyProgressDetail(payload?.text || payload),
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
onItemEvent: async (payload) => {
|
|
134
|
+
await progress({
|
|
135
|
+
kind: payload?.kind || 'item',
|
|
136
|
+
phase: payload?.phase,
|
|
137
|
+
status: payload?.status,
|
|
138
|
+
title: payload?.title || payload?.name || '任务步骤更新',
|
|
139
|
+
detail: this.stringifyProgressDetail(payload?.progressText || payload?.summary || payload?.meta),
|
|
140
|
+
data: payload,
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
onPlanUpdate: async (payload) => {
|
|
144
|
+
await progress({
|
|
145
|
+
kind: 'plan',
|
|
146
|
+
phase: payload?.phase || 'update',
|
|
147
|
+
status: 'running',
|
|
148
|
+
title: payload?.title || '更新执行计划',
|
|
149
|
+
detail: this.stringifyProgressDetail(payload?.explanation || payload?.steps),
|
|
150
|
+
data: payload,
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
onApprovalEvent: async (payload) => {
|
|
154
|
+
await progress({
|
|
155
|
+
kind: 'approval',
|
|
156
|
+
phase: payload?.phase || 'update',
|
|
157
|
+
status: payload?.status || 'running',
|
|
158
|
+
title: payload?.title || '等待审批',
|
|
159
|
+
detail: this.stringifyProgressDetail(payload?.message || payload?.reason || payload?.command),
|
|
160
|
+
data: payload,
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
onCommandOutput: async (payload) => {
|
|
164
|
+
await progress({
|
|
165
|
+
kind: 'command',
|
|
166
|
+
phase: payload?.phase || 'update',
|
|
167
|
+
status: payload?.status || 'running',
|
|
168
|
+
title: payload?.title || payload?.name || '命令执行中',
|
|
169
|
+
detail: this.stringifyProgressDetail(payload?.output || payload?.cwd),
|
|
170
|
+
data: payload,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
onPatchSummary: async (payload) => {
|
|
174
|
+
await progress({
|
|
175
|
+
kind: 'patch',
|
|
176
|
+
phase: payload?.phase || 'end',
|
|
177
|
+
status: payload?.status || 'completed',
|
|
178
|
+
title: payload?.title || '代码变更完成',
|
|
179
|
+
detail: this.stringifyProgressDetail(payload?.summary || {
|
|
180
|
+
added: payload?.added,
|
|
181
|
+
modified: payload?.modified,
|
|
182
|
+
deleted: payload?.deleted,
|
|
183
|
+
}),
|
|
184
|
+
data: payload,
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
onCompactionStart: async () => {
|
|
188
|
+
await progress({ kind: 'compaction', phase: 'start', status: 'running', title: '正在压缩上下文' });
|
|
189
|
+
},
|
|
190
|
+
onCompactionEnd: async () => {
|
|
191
|
+
await progress({ kind: 'compaction', phase: 'end', status: 'completed', title: '上下文压缩完成' });
|
|
192
|
+
},
|
|
193
|
+
onModelSelected: (model) => {
|
|
194
|
+
void progress({
|
|
195
|
+
kind: 'model',
|
|
196
|
+
phase: 'selected',
|
|
197
|
+
status: 'completed',
|
|
198
|
+
title: '已选择模型',
|
|
199
|
+
detail: [model?.provider, model?.model].filter(Boolean).join('/'),
|
|
200
|
+
data: model,
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
},
|
|
65
204
|
});
|
|
66
205
|
})
|
|
67
206
|
.finally(() => {
|
|
@@ -7,6 +7,7 @@ export declare class TelemetryComponent implements IComponent {
|
|
|
7
7
|
start(): Promise<void>;
|
|
8
8
|
stop(): Promise<void>;
|
|
9
9
|
private lastReplies;
|
|
10
|
+
private lastProgress;
|
|
10
11
|
reportReply(payload: {
|
|
11
12
|
content: string;
|
|
12
13
|
sessionId: string;
|
|
@@ -16,4 +17,14 @@ export declare class TelemetryComponent implements IComponent {
|
|
|
16
17
|
msgId?: string;
|
|
17
18
|
}): Promise<void>;
|
|
18
19
|
reportEvent(stream: string, data: any, ts: number): Promise<void>;
|
|
20
|
+
reportProgress(payload: {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
msgId?: string;
|
|
23
|
+
kind: string;
|
|
24
|
+
phase?: string;
|
|
25
|
+
status?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
detail?: string;
|
|
28
|
+
data?: any;
|
|
29
|
+
}): Promise<void>;
|
|
19
30
|
}
|
|
@@ -8,6 +8,7 @@ export class TelemetryComponent {
|
|
|
8
8
|
async start() { }
|
|
9
9
|
async stop() { }
|
|
10
10
|
lastReplies = new Map();
|
|
11
|
+
lastProgress = new Map();
|
|
11
12
|
async reportReply(payload) {
|
|
12
13
|
try {
|
|
13
14
|
const source = payload.source || "unknown";
|
|
@@ -69,4 +70,36 @@ export class TelemetryComponent {
|
|
|
69
70
|
catch (e) {
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
async reportProgress(payload) {
|
|
74
|
+
try {
|
|
75
|
+
const sessionId = payload.sessionId || "default";
|
|
76
|
+
const key = `${sessionId}:${payload.kind}:${payload.title || ""}`;
|
|
77
|
+
const signature = JSON.stringify({
|
|
78
|
+
phase: payload.phase,
|
|
79
|
+
status: payload.status,
|
|
80
|
+
detail: payload.detail,
|
|
81
|
+
data: payload.data,
|
|
82
|
+
});
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const last = this.lastProgress.get(key);
|
|
85
|
+
if (last && last.signature === signature && now - last.timestamp < 1000) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if ((payload.kind === "assistant_delta" || payload.kind === "reasoning") && last && now - last.timestamp < 350) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.lastProgress.set(key, { signature, timestamp: now });
|
|
92
|
+
await this.http.fetch(`/api/agents/${this.agentId}/messages/progress`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "Content-Type": "application/json" },
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
...payload,
|
|
97
|
+
sessionId,
|
|
98
|
+
ts: now,
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
72
105
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type DragonBridgeClient } from "../../ws.js";
|
|
2
|
-
import { IComponent } from "../../core/IComponent.js";
|
|
3
|
-
export declare class BridgeComponent implements IComponent {
|
|
4
|
-
private options;
|
|
5
|
-
client: DragonBridgeClient | undefined;
|
|
6
|
-
constructor(options: {
|
|
7
|
-
host?: string;
|
|
8
|
-
port: number;
|
|
9
|
-
token: string;
|
|
10
|
-
agentId: string;
|
|
11
|
-
gatewayPort: number;
|
|
12
|
-
gatewayToken: string;
|
|
13
|
-
logger?: any;
|
|
14
|
-
});
|
|
15
|
-
start(): Promise<void>;
|
|
16
|
-
stop(): Promise<void>;
|
|
17
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { connectToDragonBridge } from "../../ws.js";
|
|
2
|
-
export class BridgeComponent {
|
|
3
|
-
options;
|
|
4
|
-
client;
|
|
5
|
-
constructor(options) {
|
|
6
|
-
this.options = options;
|
|
7
|
-
}
|
|
8
|
-
async start() {
|
|
9
|
-
const { host, port, token, agentId, gatewayPort, gatewayToken, logger } = this.options;
|
|
10
|
-
this.client = connectToDragonBridge({
|
|
11
|
-
host,
|
|
12
|
-
port,
|
|
13
|
-
token,
|
|
14
|
-
onConnected: () => {
|
|
15
|
-
logger?.info?.("dragon channel: connected to dragon bridge");
|
|
16
|
-
this.client?.sendJson({
|
|
17
|
-
type: "hello",
|
|
18
|
-
channel: "dragon",
|
|
19
|
-
agentId,
|
|
20
|
-
port: gatewayPort,
|
|
21
|
-
token: gatewayToken,
|
|
22
|
-
version: "0.1.27",
|
|
23
|
-
timestamp: Date.now()
|
|
24
|
-
});
|
|
25
|
-
},
|
|
26
|
-
onDisconnected: () => {
|
|
27
|
-
logger?.info?.("dragon channel: disconnected from dragon bridge");
|
|
28
|
-
},
|
|
29
|
-
onMessage: (msg) => {
|
|
30
|
-
const t = msg?.type;
|
|
31
|
-
if (t === "ping") {
|
|
32
|
-
this.client?.sendJson({ type: "pong", t: Date.now() });
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (t === "hot_control") {
|
|
36
|
-
const { command } = msg;
|
|
37
|
-
logger?.info?.(`dragon channel: received hot control: ${command}`);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
async stop() {
|
|
43
|
-
// WebSocket close logic if needed
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { IComponent } from "../../core/IComponent.js";
|
|
2
|
-
import { HttpComponent } from "../http/HttpComponent.js";
|
|
3
|
-
import { ChannelComponent } from "../channel/ChannelComponent.js";
|
|
4
|
-
export declare class PollingComponent implements IComponent {
|
|
5
|
-
private http;
|
|
6
|
-
private channel;
|
|
7
|
-
private options;
|
|
8
|
-
private pollInterval;
|
|
9
|
-
constructor(http: HttpComponent, channel: ChannelComponent, options: {
|
|
10
|
-
agentId: string;
|
|
11
|
-
accountId: string;
|
|
12
|
-
channelRuntime?: any;
|
|
13
|
-
abortSignal: AbortSignal;
|
|
14
|
-
logger?: any;
|
|
15
|
-
});
|
|
16
|
-
start(): Promise<void>;
|
|
17
|
-
stop(): Promise<void>;
|
|
18
|
-
private consumePendingMessages;
|
|
19
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export class PollingComponent {
|
|
2
|
-
http;
|
|
3
|
-
channel;
|
|
4
|
-
options;
|
|
5
|
-
pollInterval = null;
|
|
6
|
-
constructor(http, channel, options) {
|
|
7
|
-
this.http = http;
|
|
8
|
-
this.channel = channel;
|
|
9
|
-
this.options = options;
|
|
10
|
-
}
|
|
11
|
-
async start() {
|
|
12
|
-
// 1. Initial consume
|
|
13
|
-
void this.consumePendingMessages();
|
|
14
|
-
// 2. Periodic recovery polling (every 60s)
|
|
15
|
-
this.pollInterval = setInterval(() => {
|
|
16
|
-
void this.consumePendingMessages();
|
|
17
|
-
}, 60_000);
|
|
18
|
-
}
|
|
19
|
-
async stop() {
|
|
20
|
-
if (this.pollInterval)
|
|
21
|
-
clearInterval(this.pollInterval);
|
|
22
|
-
}
|
|
23
|
-
async consumePendingMessages() {
|
|
24
|
-
const { agentId, logger } = this.options;
|
|
25
|
-
try {
|
|
26
|
-
const res = await this.http.fetch(`/api/agents/${agentId}/messages/poll`);
|
|
27
|
-
if (res.ok) {
|
|
28
|
-
const data = (await res.json());
|
|
29
|
-
const messages = data.messages || [];
|
|
30
|
-
if (messages.length > 0) {
|
|
31
|
-
logger?.debug?.(`[Dragon Plugin] [Recovery] Polled ${messages.length} pending messages.`);
|
|
32
|
-
for (const m of messages) {
|
|
33
|
-
await this.channel.deliverToOpenClaw(String(m.content || ''), String(m.sessionId || 'default'), m.modelId, m.attachments, m.externalId || m.id);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
catch (e) {
|
|
39
|
-
// Quiet fail for recovery polling
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|