@efengx/openclaw-channel-dragon 0.5.29 → 0.5.32
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.js +5 -5
- package/dist/components/http/HttpComponent.js +2 -2
- package/dist/components/sync/SseComponent.d.ts +14 -1
- package/dist/components/sync/SseComponent.js +172 -23
- package/dist/index.js +92 -30
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -125,7 +125,7 @@ export class ChannelComponent {
|
|
|
125
125
|
const current = previous
|
|
126
126
|
.catch(() => { })
|
|
127
127
|
.then(async () => {
|
|
128
|
-
logger?.info?.(`[Dragon Plugin] v${dragonChannelPluginVersion} Sending to OpenClaw: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}" [WorkbenchSession: ${sessionId}] [OpenClawSessionKey: ${sessionKey}]`);
|
|
128
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] v${dragonChannelPluginVersion} Sending to OpenClaw: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}" [WorkbenchSession: ${sessionId}] [OpenClawSessionKey: ${sessionKey}]`);
|
|
129
129
|
const progress = (payload) => this.telemetry.reportProgress({
|
|
130
130
|
sessionId,
|
|
131
131
|
msgId: replyMsgId,
|
|
@@ -284,7 +284,7 @@ export class ChannelComponent {
|
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
catch (err) {
|
|
287
|
-
logger?.error?.(`[Dragon Plugin] Error during replyDispatcher: ${err?.message || err}`);
|
|
287
|
+
logger?.error?.(`[dragon channels][Dragon Plugin] Error during replyDispatcher: ${err?.message || err}`);
|
|
288
288
|
let resolvedErrorMessage = err?.message || String(err);
|
|
289
289
|
const isTakeoverError = resolvedErrorMessage.includes("session file changed while embedded prompt lock was released");
|
|
290
290
|
if (isTakeoverError) {
|
|
@@ -303,7 +303,7 @@ export class ChannelComponent {
|
|
|
303
303
|
const item = JSON.parse(lines[i]);
|
|
304
304
|
if (item.type === "message" && item.stopReason === "error" && item.errorMessage) {
|
|
305
305
|
resolvedErrorMessage = item.errorMessage;
|
|
306
|
-
logger?.warn?.(`[Dragon Plugin] Extracted real underlying error from session file: ${resolvedErrorMessage}`);
|
|
306
|
+
logger?.warn?.(`[dragon channels][Dragon Plugin] Extracted real underlying error from session file: ${resolvedErrorMessage}`);
|
|
307
307
|
break;
|
|
308
308
|
}
|
|
309
309
|
}
|
|
@@ -314,7 +314,7 @@ export class ChannelComponent {
|
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
catch (fsErr) {
|
|
317
|
-
logger?.error?.(`[Dragon Plugin] Failed to read real error from session file: ${fsErr}`);
|
|
317
|
+
logger?.error?.(`[dragon channels][Dragon Plugin] Failed to read real error from session file: ${fsErr}`);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
// Check for insufficient spending cap/quota error and provide a friendly Chinese message
|
|
@@ -355,7 +355,7 @@ export class ChannelComponent {
|
|
|
355
355
|
handleOutboundText = async (ctx) => {
|
|
356
356
|
const text = ctx?.text || "";
|
|
357
357
|
const { logger } = this.options;
|
|
358
|
-
logger?.info?.(`[Dragon Plugin] Outbound Text (Action): "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
|
|
358
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] Outbound Text (Action): "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
|
|
359
359
|
await this.telemetry.reportReply({ content: text, sessionId: ctx?.peer?.id || "default", source: "channel_outbound" });
|
|
360
360
|
return { ok: true, messageId: Date.now().toString() };
|
|
361
361
|
};
|
|
@@ -4,7 +4,7 @@ export class HttpComponent {
|
|
|
4
4
|
this.options = options;
|
|
5
5
|
}
|
|
6
6
|
async start() {
|
|
7
|
-
this.options.logger?.info?.(`[Dragon-Channel] HttpComponent started for ${this.options.baseURL}`);
|
|
7
|
+
this.options.logger?.info?.(`[dragon channels][Dragon-Channel] HttpComponent started for ${this.options.baseURL}`);
|
|
8
8
|
}
|
|
9
9
|
async stop() { }
|
|
10
10
|
async fetch(path, init) {
|
|
@@ -20,7 +20,7 @@ export class HttpComponent {
|
|
|
20
20
|
headers
|
|
21
21
|
});
|
|
22
22
|
if (!res.ok && res.status !== 404) {
|
|
23
|
-
this.options.logger?.warn?.(`[Dragon-Channel] HTTP Request failed: ${res.status} ${url}`);
|
|
23
|
+
this.options.logger?.warn?.(`[dragon channels][Dragon-Channel] HTTP Request failed: ${res.status} ${url}`);
|
|
24
24
|
}
|
|
25
25
|
return res;
|
|
26
26
|
}
|
|
@@ -5,17 +5,30 @@ export declare class SseComponent implements IComponent {
|
|
|
5
5
|
private http;
|
|
6
6
|
private channel;
|
|
7
7
|
private options;
|
|
8
|
-
private eventSource;
|
|
9
8
|
private shouldReconnect;
|
|
10
9
|
private reconnectTimer;
|
|
10
|
+
private idleTimer;
|
|
11
|
+
private fetchAbort;
|
|
12
|
+
private reader;
|
|
13
|
+
private reconnectAttempts;
|
|
14
|
+
private connectionSeq;
|
|
15
|
+
private lastTransportActivityAt;
|
|
11
16
|
constructor(http: HttpComponent, channel: ChannelComponent, options: {
|
|
12
17
|
agentId: string;
|
|
18
|
+
accountId: string;
|
|
13
19
|
version: string;
|
|
14
20
|
logger?: any;
|
|
21
|
+
abortSignal?: AbortSignal;
|
|
22
|
+
setStatus?: (next: Record<string, unknown>) => void;
|
|
23
|
+
reconnectMs?: number;
|
|
24
|
+
idleTimeoutMs?: number;
|
|
15
25
|
});
|
|
16
26
|
start(): Promise<void>;
|
|
17
27
|
stop(): Promise<void>;
|
|
18
28
|
private connect;
|
|
29
|
+
private touchTransport;
|
|
30
|
+
private armIdleWatchdog;
|
|
31
|
+
private markDisconnected;
|
|
19
32
|
private startSseReader;
|
|
20
33
|
private handleEvent;
|
|
21
34
|
private scheduleReconnect;
|
|
@@ -2,44 +2,117 @@ export class SseComponent {
|
|
|
2
2
|
http;
|
|
3
3
|
channel;
|
|
4
4
|
options;
|
|
5
|
-
eventSource;
|
|
6
5
|
shouldReconnect = true;
|
|
7
6
|
reconnectTimer;
|
|
7
|
+
idleTimer;
|
|
8
|
+
fetchAbort;
|
|
9
|
+
reader;
|
|
10
|
+
reconnectAttempts = 0;
|
|
11
|
+
connectionSeq = 0;
|
|
12
|
+
lastTransportActivityAt = 0;
|
|
8
13
|
constructor(http, channel, options) {
|
|
9
14
|
this.http = http;
|
|
10
15
|
this.channel = channel;
|
|
11
16
|
this.options = options;
|
|
12
17
|
}
|
|
13
18
|
async start() {
|
|
19
|
+
this.options.logger?.info?.(`[dragon channels][SSE] component start: version=${this.options.version}, agent=${this.options.agentId}, account=${this.options.accountId}, baseUrl=${this.http.options.baseURL}`);
|
|
20
|
+
this.options.setStatus?.({
|
|
21
|
+
accountId: this.options.accountId,
|
|
22
|
+
connected: false,
|
|
23
|
+
lastError: null,
|
|
24
|
+
});
|
|
25
|
+
this.options.abortSignal?.addEventListener('abort', () => {
|
|
26
|
+
this.options.logger?.info?.(`[dragon channels][SSE] upstream abort received: agent=${this.options.agentId}, account=${this.options.accountId}`);
|
|
27
|
+
void this.stop();
|
|
28
|
+
}, { once: true });
|
|
14
29
|
this.connect();
|
|
15
30
|
}
|
|
16
31
|
async stop() {
|
|
17
32
|
this.shouldReconnect = false;
|
|
18
33
|
if (this.reconnectTimer)
|
|
19
34
|
clearTimeout(this.reconnectTimer);
|
|
20
|
-
this.
|
|
35
|
+
this.reconnectTimer = undefined;
|
|
36
|
+
if (this.idleTimer)
|
|
37
|
+
clearTimeout(this.idleTimer);
|
|
38
|
+
this.idleTimer = undefined;
|
|
39
|
+
this.fetchAbort?.abort();
|
|
40
|
+
this.fetchAbort = undefined;
|
|
41
|
+
try {
|
|
42
|
+
await this.reader?.cancel?.();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Reader cancellation can race with fetch abort.
|
|
46
|
+
}
|
|
47
|
+
this.reader = undefined;
|
|
48
|
+
this.options.setStatus?.({
|
|
49
|
+
accountId: this.options.accountId,
|
|
50
|
+
connected: false,
|
|
51
|
+
lastStopAt: Date.now(),
|
|
52
|
+
});
|
|
53
|
+
this.options.logger?.info?.(`[dragon channels][SSE] component stopped: agent=${this.options.agentId}, account=${this.options.accountId}`);
|
|
21
54
|
}
|
|
22
55
|
connect() {
|
|
23
56
|
if (!this.shouldReconnect)
|
|
24
57
|
return;
|
|
25
58
|
const { agentId, logger } = this.options;
|
|
26
59
|
const url = `${this.http.options.baseURL}/api/agents/events`;
|
|
27
|
-
|
|
60
|
+
const connectionId = `${Date.now()}-${++this.connectionSeq}`;
|
|
61
|
+
logger?.info?.(`[dragon channels][SSE] connect start: connection=${connectionId}, attempt=${this.reconnectAttempts + 1}, version=${this.options.version}, url=${url}, agent=${agentId}, account=${this.options.accountId}`);
|
|
28
62
|
try {
|
|
29
|
-
|
|
30
|
-
// but for simplicity we'll use the same pattern as Bridge if possible.
|
|
31
|
-
// Actually, standard EventSource is not in Node globals yet.
|
|
32
|
-
// We'll use a simple fetch-based stream reader.
|
|
33
|
-
this.startSseReader(url);
|
|
63
|
+
void this.startSseReader(url, connectionId);
|
|
34
64
|
}
|
|
35
65
|
catch (err) {
|
|
36
|
-
logger?.error?.(`[
|
|
37
|
-
this.
|
|
66
|
+
logger?.error?.(`[dragon channels][SSE] connect init failed: connection=${connectionId}, error=${err.message}`);
|
|
67
|
+
this.markDisconnected(err?.message || String(err));
|
|
68
|
+
this.scheduleReconnect('init_failed');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
touchTransport(connectionId, reason) {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
this.lastTransportActivityAt = now;
|
|
74
|
+
this.options.setStatus?.({
|
|
75
|
+
accountId: this.options.accountId,
|
|
76
|
+
connected: true,
|
|
77
|
+
lastEventAt: now,
|
|
78
|
+
lastTransportActivityAt: now,
|
|
79
|
+
lastError: null,
|
|
80
|
+
});
|
|
81
|
+
this.options.logger?.debug?.(`[dragon channels][SSE] transport activity: connection=${connectionId}, reason=${reason}, at=${now}`);
|
|
82
|
+
this.armIdleWatchdog(connectionId);
|
|
83
|
+
}
|
|
84
|
+
armIdleWatchdog(connectionId) {
|
|
85
|
+
if (this.idleTimer)
|
|
86
|
+
clearTimeout(this.idleTimer);
|
|
87
|
+
const timeoutMs = this.options.idleTimeoutMs ?? 90_000;
|
|
88
|
+
this.idleTimer = setTimeout(() => {
|
|
89
|
+
if (!this.shouldReconnect)
|
|
90
|
+
return;
|
|
91
|
+
const idleMs = Date.now() - this.lastTransportActivityAt;
|
|
92
|
+
this.options.logger?.warn?.(`[dragon channels][SSE] idle timeout: connection=${connectionId}, idleMs=${idleMs}, timeoutMs=${timeoutMs}; aborting fetch for reconnect`);
|
|
93
|
+
this.markDisconnected(`SSE idle timeout after ${idleMs}ms`);
|
|
94
|
+
this.fetchAbort?.abort();
|
|
95
|
+
}, timeoutMs);
|
|
96
|
+
if (typeof this.idleTimer === 'object' && 'unref' in this.idleTimer) {
|
|
97
|
+
this.idleTimer.unref();
|
|
38
98
|
}
|
|
39
99
|
}
|
|
40
|
-
|
|
100
|
+
markDisconnected(error) {
|
|
101
|
+
this.options.setStatus?.({
|
|
102
|
+
accountId: this.options.accountId,
|
|
103
|
+
connected: false,
|
|
104
|
+
lastError: error,
|
|
105
|
+
lastTransportActivityAt: this.lastTransportActivityAt || null,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async startSseReader(url, connectionId) {
|
|
109
|
+
const abort = new AbortController();
|
|
110
|
+
this.fetchAbort = abort;
|
|
111
|
+
const upstreamAbort = () => abort.abort();
|
|
112
|
+
this.options.abortSignal?.addEventListener('abort', upstreamAbort, { once: true });
|
|
41
113
|
try {
|
|
42
114
|
const response = await fetch(url, {
|
|
115
|
+
signal: abort.signal,
|
|
43
116
|
headers: {
|
|
44
117
|
'Accept': 'text/event-stream',
|
|
45
118
|
'Authorization': `Bearer ${this.http.options.authToken || ''}`
|
|
@@ -51,35 +124,87 @@ export class SseComponent {
|
|
|
51
124
|
const reader = response.body?.getReader();
|
|
52
125
|
if (!reader)
|
|
53
126
|
throw new Error("Response body is null");
|
|
54
|
-
this.
|
|
127
|
+
this.reader = reader;
|
|
128
|
+
this.reconnectAttempts = 0;
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
this.lastTransportActivityAt = now;
|
|
131
|
+
this.options.setStatus?.({
|
|
132
|
+
accountId: this.options.accountId,
|
|
133
|
+
connected: true,
|
|
134
|
+
lastConnectedAt: now,
|
|
135
|
+
lastTransportActivityAt: now,
|
|
136
|
+
lastError: null,
|
|
137
|
+
reconnectAttempts: 0,
|
|
138
|
+
});
|
|
139
|
+
this.options.logger?.info?.(`[dragon channels][SSE] stream established: connection=${connectionId}, agent=${this.options.agentId}, account=${this.options.accountId}`);
|
|
140
|
+
this.armIdleWatchdog(connectionId);
|
|
55
141
|
const decoder = new TextDecoder();
|
|
56
142
|
let buffer = "";
|
|
57
143
|
while (this.shouldReconnect) {
|
|
58
144
|
const { value, done } = await reader.read();
|
|
59
|
-
if (done)
|
|
145
|
+
if (done) {
|
|
146
|
+
this.options.logger?.warn?.(`[dragon channels][SSE] stream ended by remote: connection=${connectionId}`);
|
|
60
147
|
break;
|
|
148
|
+
}
|
|
149
|
+
this.touchTransport(connectionId, 'chunk');
|
|
61
150
|
buffer += decoder.decode(value, { stream: true });
|
|
62
151
|
const lines = buffer.split("\n");
|
|
63
152
|
buffer = lines.pop() || "";
|
|
64
153
|
for (const line of lines) {
|
|
65
|
-
|
|
154
|
+
const cleanLine = line.trim();
|
|
155
|
+
if (!cleanLine || cleanLine.startsWith(':')) {
|
|
156
|
+
this.touchTransport(connectionId, cleanLine.startsWith(':') ? 'heartbeat' : 'blank_line');
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (cleanLine.startsWith("data: ")) {
|
|
66
160
|
try {
|
|
67
|
-
const data = JSON.parse(
|
|
161
|
+
const data = JSON.parse(cleanLine.slice(6));
|
|
162
|
+
this.options.logger?.debug?.(`[dragon channels][SSE] event received: connection=${connectionId}, type=${data?.type || 'unknown'}, agent=${data?.agentId || 'unknown'}`);
|
|
68
163
|
void this.handleEvent(data).catch((err) => {
|
|
69
|
-
this.options.logger?.error?.(`[
|
|
164
|
+
this.options.logger?.error?.(`[dragon channels][SSE] event handling failed: connection=${connectionId}, error=${err?.message || err}`);
|
|
70
165
|
});
|
|
71
166
|
}
|
|
72
|
-
catch (e) {
|
|
167
|
+
catch (e) {
|
|
168
|
+
this.options.logger?.error?.(`[dragon channels][SSE] event JSON parse failed: connection=${connectionId}, line="${cleanLine}", error=${e?.message || e}`);
|
|
169
|
+
}
|
|
73
170
|
}
|
|
74
171
|
}
|
|
75
172
|
}
|
|
173
|
+
if (this.shouldReconnect) {
|
|
174
|
+
const message = 'SSE stream ended';
|
|
175
|
+
this.markDisconnected(message);
|
|
176
|
+
this.scheduleReconnect(message);
|
|
177
|
+
}
|
|
76
178
|
}
|
|
77
179
|
catch (err) {
|
|
78
180
|
if (this.shouldReconnect) {
|
|
79
|
-
|
|
80
|
-
|
|
181
|
+
const message = err?.name === 'AbortError'
|
|
182
|
+
? 'SSE fetch aborted'
|
|
183
|
+
: (err?.message || String(err));
|
|
184
|
+
this.options.logger?.warn?.(`[dragon channels][SSE] stream disconnected: connection=${connectionId}, error=${message}`);
|
|
185
|
+
this.markDisconnected(message);
|
|
186
|
+
this.scheduleReconnect(message);
|
|
81
187
|
}
|
|
82
188
|
}
|
|
189
|
+
finally {
|
|
190
|
+
this.options.abortSignal?.removeEventListener('abort', upstreamAbort);
|
|
191
|
+
if (this.reader) {
|
|
192
|
+
try {
|
|
193
|
+
await this.reader.cancel?.();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Ignore cancellation races.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (this.fetchAbort === abort) {
|
|
200
|
+
this.fetchAbort = undefined;
|
|
201
|
+
}
|
|
202
|
+
if (this.idleTimer) {
|
|
203
|
+
clearTimeout(this.idleTimer);
|
|
204
|
+
this.idleTimer = undefined;
|
|
205
|
+
}
|
|
206
|
+
this.reader = undefined;
|
|
207
|
+
}
|
|
83
208
|
}
|
|
84
209
|
async handleEvent(data) {
|
|
85
210
|
const { agentId, logger } = this.options;
|
|
@@ -89,18 +214,42 @@ export class SseComponent {
|
|
|
89
214
|
// 2. Handle specific types
|
|
90
215
|
if (data.type === 'WORKBENCH_MESSAGE') {
|
|
91
216
|
const { content, sessionId, attachments, id, msgId } = data.payload || {};
|
|
92
|
-
logger?.info?.(`[Dragon Plugin] v${this.options.version} [SSE] Received from Workbench: "${content?.substring(0, 50)}${content?.length > 50 ? '...' : ''}" [Session: ${sessionId}]`);
|
|
217
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] v${this.options.version} [SSE] Received from Workbench: "${content?.substring(0, 50)}${content?.length > 50 ? '...' : ''}" [Session: ${sessionId}]`);
|
|
93
218
|
await this.channel.deliverToOpenClaw(content, sessionId, undefined, attachments, msgId || id);
|
|
94
219
|
}
|
|
95
220
|
else if (data.type === 'FETCH_HISTORY') {
|
|
96
221
|
const { sessionId } = data.payload || {};
|
|
97
|
-
logger?.info?.(`[Dragon Plugin] Triggering history sync for session: ${sessionId}`);
|
|
222
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] Triggering history sync for session: ${sessionId}`);
|
|
98
223
|
// Handle history sync if needed, but deliverToOpenClaw usually handles normal chat
|
|
99
224
|
}
|
|
100
225
|
}
|
|
101
|
-
scheduleReconnect() {
|
|
226
|
+
scheduleReconnect(reason) {
|
|
227
|
+
if (!this.shouldReconnect)
|
|
228
|
+
return;
|
|
102
229
|
if (this.reconnectTimer)
|
|
103
230
|
clearTimeout(this.reconnectTimer);
|
|
104
|
-
this.
|
|
231
|
+
this.reconnectAttempts += 1;
|
|
232
|
+
const baseMs = this.options.reconnectMs ?? 5_000;
|
|
233
|
+
const delayMs = Math.min(60_000, baseMs * Math.max(1, this.reconnectAttempts));
|
|
234
|
+
this.options.setStatus?.({
|
|
235
|
+
accountId: this.options.accountId,
|
|
236
|
+
connected: false,
|
|
237
|
+
restartPending: true,
|
|
238
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
239
|
+
lastError: reason,
|
|
240
|
+
});
|
|
241
|
+
this.options.logger?.info?.(`[dragon channels][SSE] reconnect scheduled: attempt=${this.reconnectAttempts}, delayMs=${delayMs}, reason=${reason}, agent=${this.options.agentId}, account=${this.options.accountId}`);
|
|
242
|
+
this.reconnectTimer = setTimeout(() => {
|
|
243
|
+
this.reconnectTimer = undefined;
|
|
244
|
+
this.options.setStatus?.({
|
|
245
|
+
accountId: this.options.accountId,
|
|
246
|
+
restartPending: false,
|
|
247
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
248
|
+
});
|
|
249
|
+
this.connect();
|
|
250
|
+
}, delayMs);
|
|
251
|
+
if (typeof this.reconnectTimer === 'object' && 'unref' in this.reconnectTimer) {
|
|
252
|
+
this.reconnectTimer.unref();
|
|
253
|
+
}
|
|
105
254
|
}
|
|
106
255
|
}
|
package/dist/index.js
CHANGED
|
@@ -22,17 +22,27 @@ function resolveAccountIdFromSessionKey(sessionKey) {
|
|
|
22
22
|
}
|
|
23
23
|
return "default";
|
|
24
24
|
}
|
|
25
|
+
function containerKey(account) {
|
|
26
|
+
return `${account.accountId}:${account.agentId}:${account.orchestratorUrl}`;
|
|
27
|
+
}
|
|
25
28
|
async function getOrCreateContainer(account, ctx) {
|
|
26
|
-
const key =
|
|
27
|
-
if (containers.has(key))
|
|
29
|
+
const key = containerKey(account);
|
|
30
|
+
if (containers.has(key)) {
|
|
31
|
+
const logger = ctx?.logger ?? ctx?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
32
|
+
logger?.debug?.(`[dragon channels][Dragon Plugin] Reusing channel container: key=${key}, agent=${account.agentId}, account=${account.accountId}`);
|
|
28
33
|
return containers.get(key);
|
|
34
|
+
}
|
|
29
35
|
const logger = ctx?.logger ?? ctx?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
30
36
|
const container = new ServiceContainer();
|
|
31
|
-
logger?.info?.(`[Dragon Plugin] Starting channel plugin v${dragonChannelPluginVersion} for agent=${account.agentId}, account=${account.accountId}, orchestrator=${account.orchestratorUrl}`);
|
|
37
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] Starting channel plugin v${dragonChannelPluginVersion} for agent=${account.agentId}, account=${account.accountId}, orchestrator=${account.orchestratorUrl}`);
|
|
32
38
|
// 1. Core Infrastructure
|
|
33
39
|
const http = container.register('http', new HttpComponent({
|
|
34
40
|
baseURL: account.orchestratorUrl,
|
|
35
|
-
authToken: account.orchestratorAuthToken ||
|
|
41
|
+
authToken: account.orchestratorAuthToken ||
|
|
42
|
+
process.env.DRAGON_ORCHESTRATOR_AUTH_TOKEN ||
|
|
43
|
+
process.env.DRAGON_GATEWAY_TOKEN ||
|
|
44
|
+
process.env.GATEWAY_TOKEN ||
|
|
45
|
+
process.env.OPENCLAW_GATEWAY_AUTH_TOKEN,
|
|
36
46
|
logger
|
|
37
47
|
}));
|
|
38
48
|
const telemetry = container.register('telemetry', new TelemetryComponent(http, account.agentId));
|
|
@@ -47,13 +57,43 @@ async function getOrCreateContainer(account, ctx) {
|
|
|
47
57
|
// 3. Sync Infrastructure
|
|
48
58
|
container.register('sse', new SseComponent(http, channel, {
|
|
49
59
|
agentId: account.agentId,
|
|
60
|
+
accountId: account.accountId,
|
|
50
61
|
version: dragonChannelPluginVersion,
|
|
51
|
-
logger
|
|
62
|
+
logger,
|
|
63
|
+
abortSignal: ctx.abortSignal,
|
|
64
|
+
setStatus: ctx.setStatus,
|
|
65
|
+
reconnectMs: 5_000,
|
|
66
|
+
idleTimeoutMs: 90_000,
|
|
52
67
|
}));
|
|
53
68
|
await container.startAll();
|
|
54
69
|
containers.set(key, container);
|
|
70
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] Channel container started: key=${key}, agent=${account.agentId}, account=${account.accountId}`);
|
|
55
71
|
return container;
|
|
56
72
|
}
|
|
73
|
+
async function stopContainer(account, ctx) {
|
|
74
|
+
const key = containerKey(account);
|
|
75
|
+
const logger = ctx?.logger ?? ctx?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
76
|
+
const container = containers.get(key);
|
|
77
|
+
if (!container) {
|
|
78
|
+
logger?.debug?.(`[dragon channels][Dragon Plugin] No channel container to stop: key=${key}, agent=${account.agentId}, account=${account.accountId}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] Stopping channel container: key=${key}, agent=${account.agentId}, account=${account.accountId}`);
|
|
82
|
+
try {
|
|
83
|
+
await container.stopAll();
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
containers.delete(key);
|
|
87
|
+
ctx?.setStatus?.({
|
|
88
|
+
accountId: account.accountId,
|
|
89
|
+
connected: false,
|
|
90
|
+
running: false,
|
|
91
|
+
restartPending: false,
|
|
92
|
+
lastStopAt: Date.now(),
|
|
93
|
+
});
|
|
94
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] Channel container stopped: key=${key}, agent=${account.agentId}, account=${account.accountId}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
57
97
|
const base = createChannelPluginBase({
|
|
58
98
|
id: channelId,
|
|
59
99
|
meta: {
|
|
@@ -76,7 +116,7 @@ const base = createChannelPluginBase({
|
|
|
76
116
|
return {
|
|
77
117
|
accountId,
|
|
78
118
|
agentId: accountConfig.agentId || accountId,
|
|
79
|
-
orchestratorUrl: accountConfig.orchestratorUrl
|
|
119
|
+
orchestratorUrl: accountConfig.orchestratorUrl,
|
|
80
120
|
orchestratorAuthToken: accountConfig.orchestratorAuthToken || accountConfig.authToken,
|
|
81
121
|
};
|
|
82
122
|
},
|
|
@@ -89,13 +129,35 @@ const plugin = createChatChannelPlugin({
|
|
|
89
129
|
gateway: {
|
|
90
130
|
startAccount: async (ctx) => {
|
|
91
131
|
const account = base.config.resolveAccount(ctx.cfg, ctx.accountId);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
const logger = ctx?.logger ?? ctx?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
133
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] startAccount begin: agent=${account.agentId}, account=${account.accountId}, orchestrator=${account.orchestratorUrl}`);
|
|
134
|
+
ctx.setStatus?.({
|
|
135
|
+
accountId: account.accountId,
|
|
136
|
+
enabled: true,
|
|
137
|
+
configured: true,
|
|
138
|
+
running: true,
|
|
139
|
+
connected: false,
|
|
140
|
+
lastStartAt: Date.now(),
|
|
141
|
+
lastError: null,
|
|
98
142
|
});
|
|
143
|
+
try {
|
|
144
|
+
await getOrCreateContainer(account, ctx);
|
|
145
|
+
if (ctx.abortSignal.aborted)
|
|
146
|
+
return;
|
|
147
|
+
await new Promise((resolve) => {
|
|
148
|
+
ctx.abortSignal.addEventListener('abort', resolve, { once: true });
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] startAccount exiting: agent=${account.agentId}, account=${account.accountId}, aborted=${ctx.abortSignal.aborted}`);
|
|
153
|
+
await stopContainer(account, ctx);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
stopAccount: async (ctx) => {
|
|
157
|
+
const account = base.config.resolveAccount(ctx.cfg, ctx.accountId);
|
|
158
|
+
const logger = ctx?.logger ?? ctx?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
159
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] stopAccount requested: agent=${account.agentId}, account=${account.accountId}`);
|
|
160
|
+
await stopContainer(account, ctx);
|
|
99
161
|
}
|
|
100
162
|
},
|
|
101
163
|
messaging: {
|
|
@@ -160,20 +222,20 @@ const plugin = createChatChannelPlugin({
|
|
|
160
222
|
handleAction: async (ctx) => {
|
|
161
223
|
const { action, params, cfg, accountId } = ctx;
|
|
162
224
|
const logger = ctx?.logger ?? ctx?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
163
|
-
logger?.info?.(`[Dragon Plugin] handleAction start: action=${action}, accountId=${accountId}, paramsKeys=${Object.keys(params || {})}`);
|
|
225
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction start: action=${action}, accountId=${accountId}, paramsKeys=${Object.keys(params || {})}`);
|
|
164
226
|
if (action === "send") {
|
|
165
227
|
const account = base.config.resolveAccount(cfg, accountId);
|
|
166
228
|
const container = await getOrCreateContainer(account, ctx);
|
|
167
229
|
const channel = container.get('channel');
|
|
168
230
|
let target = params.to || params.target || ctx.toolContext?.currentChannelId || "default";
|
|
169
|
-
logger?.info?.(`[Dragon Plugin] handleAction target resolution input: ${target}`);
|
|
231
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolution input: ${target}`);
|
|
170
232
|
for (const prefix of ["dragon-workbench-", "dragon-workbench:", "dragon-", "dragon:"]) {
|
|
171
233
|
if (target.startsWith(prefix)) {
|
|
172
234
|
target = target.substring(prefix.length);
|
|
173
235
|
}
|
|
174
236
|
}
|
|
175
237
|
const sessionId = target || "default";
|
|
176
|
-
logger?.info?.(`[Dragon Plugin] handleAction target resolved: sessionId=${sessionId}, rawTarget=${target}`);
|
|
238
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolved: sessionId=${sessionId}, rawTarget=${target}`);
|
|
177
239
|
// Collect ALL media URLs from the params (mirrors what OpenClaw tracks internally
|
|
178
240
|
// via collectMessagingMediaUrlsFromRecord when the tool call starts, so that
|
|
179
241
|
// hasGatewayAgentDeliveredExpectedMedia can match them against expectedMediaUrls).
|
|
@@ -182,7 +244,7 @@ const plugin = createChatChannelPlugin({
|
|
|
182
244
|
if (typeof v === 'string' && v.trim()) {
|
|
183
245
|
const url = v.trim();
|
|
184
246
|
sentMediaUrls.push(url);
|
|
185
|
-
logger?.info?.(`[Dragon Plugin] handleAction extracted media URL from ${source}: ${url}`);
|
|
247
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction extracted media URL from ${source}: ${url}`);
|
|
186
248
|
}
|
|
187
249
|
};
|
|
188
250
|
pushMedia(params.media, "params.media");
|
|
@@ -191,12 +253,12 @@ const plugin = createChatChannelPlugin({
|
|
|
191
253
|
pushMedia(params.filePath, "params.filePath");
|
|
192
254
|
pushMedia(params.fileUrl, "params.fileUrl");
|
|
193
255
|
if (Array.isArray(params.mediaUrls)) {
|
|
194
|
-
logger?.info?.(`[Dragon Plugin] handleAction found params.mediaUrls array, size=${params.mediaUrls.length}`);
|
|
256
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction found params.mediaUrls array, size=${params.mediaUrls.length}`);
|
|
195
257
|
for (const u of params.mediaUrls)
|
|
196
258
|
pushMedia(u, "params.mediaUrls");
|
|
197
259
|
}
|
|
198
260
|
if (Array.isArray(params.attachments)) {
|
|
199
|
-
logger?.info?.(`[Dragon Plugin] handleAction found params.attachments array, size=${params.attachments.length}`);
|
|
261
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction found params.attachments array, size=${params.attachments.length}`);
|
|
200
262
|
for (const a of params.attachments) {
|
|
201
263
|
if (a && typeof a === 'object') {
|
|
202
264
|
pushMedia(a.media, "attachment.media");
|
|
@@ -207,9 +269,9 @@ const plugin = createChatChannelPlugin({
|
|
|
207
269
|
}
|
|
208
270
|
}
|
|
209
271
|
}
|
|
210
|
-
logger?.info?.(`[Dragon Plugin] handleAction total media URLs extracted: count=${sentMediaUrls.length}, urls=${JSON.stringify(sentMediaUrls)}`);
|
|
272
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction total media URLs extracted: count=${sentMediaUrls.length}, urls=${JSON.stringify(sentMediaUrls)}`);
|
|
211
273
|
let text = params.message || params.text || "";
|
|
212
|
-
logger?.info?.(`[Dragon Plugin] handleAction base message text length: ${text.length}`);
|
|
274
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction base message text length: ${text.length}`);
|
|
213
275
|
// Append media path to text so the Workbench can render the image
|
|
214
276
|
for (const url of sentMediaUrls) {
|
|
215
277
|
const mediaLine = `MEDIA: ${url}`;
|
|
@@ -217,12 +279,12 @@ const plugin = createChatChannelPlugin({
|
|
|
217
279
|
text = text ? `${text}\n${mediaLine}` : mediaLine;
|
|
218
280
|
}
|
|
219
281
|
}
|
|
220
|
-
logger?.info?.(`[Dragon Plugin] handleAction sending text over channel: text_len=${text.length}, targetSession=${sessionId}`);
|
|
282
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction sending text over channel: text_len=${text.length}, targetSession=${sessionId}`);
|
|
221
283
|
const result = await channel.handleOutboundText({
|
|
222
284
|
text,
|
|
223
285
|
peer: { id: sessionId }
|
|
224
286
|
});
|
|
225
|
-
logger?.info?.(`[Dragon Plugin] handleAction channel send result: ok=${result.ok}, messageId=${result.messageId || "none"}`);
|
|
287
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction channel send result: ok=${result.ok}, messageId=${result.messageId || "none"}`);
|
|
226
288
|
const evidence = {
|
|
227
289
|
ok: result.ok,
|
|
228
290
|
...result.messageId ? { messageId: result.messageId } : {},
|
|
@@ -233,10 +295,10 @@ const plugin = createChatChannelPlugin({
|
|
|
233
295
|
messagingToolSentMediaUrls: sentMediaUrls,
|
|
234
296
|
messagingToolSentTargets: [{ to: target, ...(sentMediaUrls.length > 0 ? { mediaUrls: sentMediaUrls } : {}) }],
|
|
235
297
|
};
|
|
236
|
-
logger?.info?.(`[Dragon Plugin] handleAction returning evidence to OpenClaw: didSendViaMessagingTool=${evidence.didSendViaMessagingTool}, sentMediaUrlsCount=${evidence.messagingToolSentMediaUrls.length}`);
|
|
298
|
+
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction returning evidence to OpenClaw: didSendViaMessagingTool=${evidence.didSendViaMessagingTool}, sentMediaUrlsCount=${evidence.messagingToolSentMediaUrls.length}`);
|
|
237
299
|
return evidence;
|
|
238
300
|
}
|
|
239
|
-
logger?.warn?.(`[Dragon Plugin] handleAction action "${action}" is not supported. Ignoring.`);
|
|
301
|
+
logger?.warn?.(`[dragon channels][Dragon Plugin] handleAction action "${action}" is not supported. Ignoring.`);
|
|
240
302
|
throw new Error(`Action "${action}" is not supported by the dragon channel plugin.`);
|
|
241
303
|
}
|
|
242
304
|
}
|
|
@@ -268,12 +330,12 @@ const entry = defineChannelPluginEntry({
|
|
|
268
330
|
return;
|
|
269
331
|
const accountId = resolveAccountIdFromSessionKey(sessionKey);
|
|
270
332
|
const account = base.config.resolveAccount(api?.runtime?.cfg, accountId);
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
333
|
+
const logger = api?.runtime?.logger ?? api?.runtime?.log ?? cachedRuntime?.logger ?? cachedRuntime?.log;
|
|
334
|
+
const container = containers.get(containerKey(account));
|
|
335
|
+
if (!container) {
|
|
336
|
+
logger?.warn?.(`[dragon channels][Dragon Plugin] agent event skipped because managed container is not running: sessionKey=${sessionKey}, account=${account.accountId}, agent=${account.agentId}`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
277
339
|
const channel = container.get('channel');
|
|
278
340
|
await channel.handleAgentEvent(evt);
|
|
279
341
|
});
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export declare const dragonChannelPluginVersion = "0.5.32";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export const dragonChannelPluginVersion = "0.5.32";
|