@efengx/openclaw-channel-dragon 0.5.32 → 0.5.34

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.
@@ -16,6 +16,8 @@ export declare class ChannelComponent implements IComponent {
16
16
  start(): Promise<void>;
17
17
  stop(): Promise<void>;
18
18
  private normalizeSessionSegment;
19
+ private encodeWorkbenchTarget;
20
+ private decodeWorkbenchTarget;
19
21
  private resolveOpenClawAgentId;
20
22
  private buildOpenClawSessionKey;
21
23
  private resolveWorkbenchSessionIdFromOpenClawSessionKey;
@@ -27,6 +29,12 @@ export declare class ChannelComponent implements IComponent {
27
29
  handleOutboundText: (ctx: any) => Promise<{
28
30
  ok: boolean;
29
31
  messageId: string;
32
+ error: any;
33
+ } | {
34
+ ok: boolean;
35
+ messageId: string;
36
+ error?: undefined;
30
37
  }>;
38
+ reportTargetProtocolError: (message: string, rawTarget: unknown) => Promise<void>;
31
39
  handleAgentEvent: (evt: any) => Promise<void>;
32
40
  }
@@ -1,5 +1,6 @@
1
1
  import { dragonChannelPluginVersion } from "../../version.js";
2
2
  const channelId = "dragon";
3
+ const workbenchTargetPrefix = "dragon-workbench:";
3
4
  export class ChannelComponent {
4
5
  options;
5
6
  telemetry;
@@ -20,6 +21,21 @@ export class ChannelComponent {
20
21
  .slice(0, 64);
21
22
  return normalized || fallback;
22
23
  }
24
+ encodeWorkbenchTarget(sessionId) {
25
+ const value = String(sessionId || 'default').trim() || 'default';
26
+ return `${workbenchTargetPrefix}${value}`;
27
+ }
28
+ decodeWorkbenchTarget(value) {
29
+ const target = String(value || '').trim();
30
+ if (!target.startsWith(workbenchTargetPrefix)) {
31
+ throw new Error(`Invalid dragon workbench target "${target || '<empty>'}"; expected "${workbenchTargetPrefix}<sessionId>"`);
32
+ }
33
+ const sessionId = target.slice(workbenchTargetPrefix.length).trim();
34
+ if (!sessionId) {
35
+ throw new Error(`Invalid dragon workbench target "${target}"; sessionId is empty`);
36
+ }
37
+ return sessionId;
38
+ }
23
39
  resolveOpenClawAgentId() {
24
40
  const agents = this.options.cfg?.agents?.list;
25
41
  if (Array.isArray(agents)) {
@@ -136,14 +152,14 @@ export class ChannelComponent {
136
152
  ctx: {
137
153
  Body: content,
138
154
  From: sessionId === 'default' ? "workbench-user" : `workbench-user-${sessionId}`,
139
- To: sessionId === 'default' ? "dragon-workbench" : `dragon-workbench-${sessionId}`,
155
+ To: this.encodeWorkbenchTarget(sessionId),
140
156
  ChatType: "direct",
141
157
  Provider: channelId,
142
158
  ChannelId: channelId,
143
159
  AccountId: accountId,
144
160
  SessionKey: sessionKey,
145
161
  Timestamp: Date.now(),
146
- Model: modelId,
162
+ Model: modelId || "google/gemini-3.5-flash",
147
163
  Attachments: attachments,
148
164
  },
149
165
  cfg,
@@ -355,10 +371,34 @@ export class ChannelComponent {
355
371
  handleOutboundText = async (ctx) => {
356
372
  const text = ctx?.text || "";
357
373
  const { logger } = this.options;
358
- logger?.info?.(`[dragon channels][Dragon Plugin] Outbound Text (Action): "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
359
- await this.telemetry.reportReply({ content: text, sessionId: ctx?.peer?.id || "default", source: "channel_outbound" });
374
+ let sessionId;
375
+ try {
376
+ sessionId = this.decodeWorkbenchTarget(ctx?.peer?.id);
377
+ }
378
+ catch (error) {
379
+ const message = error?.message || String(error);
380
+ logger?.error?.(`[dragon channels][Dragon Plugin] outbound target protocol error: ${message}`);
381
+ await this.reportTargetProtocolError(message, ctx?.peer?.id);
382
+ return { ok: false, messageId: Date.now().toString(), error: message };
383
+ }
384
+ logger?.info?.(`[dragon channels][Dragon Plugin] Outbound Text (Action): "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}" [Session: ${sessionId}] [RawPeer: ${ctx?.peer?.id || 'none'}]`);
385
+ await this.telemetry.reportReply({ content: text, sessionId, source: "channel_outbound" });
360
386
  return { ok: true, messageId: Date.now().toString() };
361
387
  };
388
+ reportTargetProtocolError = async (message, rawTarget) => {
389
+ const content = [
390
+ '⚠️ Dragon channel target 协议错误。',
391
+ `错误信息:${message}`,
392
+ `收到的 target:${String(rawTarget || '<empty>')}`,
393
+ `期望格式:${workbenchTargetPrefix}<sessionId>`,
394
+ ].join('\n');
395
+ await this.telemetry.reportReply({
396
+ content,
397
+ sessionId: 'default',
398
+ source: 'target_protocol_error',
399
+ msgId: `dragon_target_error_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
400
+ });
401
+ };
362
402
  handleAgentEvent = async (evt) => {
363
403
  const sessionId = evt?.sessionId ||
364
404
  this.resolveWorkbenchSessionIdFromOpenClawSessionKey(evt?.sessionKey);
@@ -56,7 +56,13 @@ export class SseComponent {
56
56
  if (!this.shouldReconnect)
57
57
  return;
58
58
  const { agentId, logger } = this.options;
59
- const url = `${this.http.options.baseURL}/api/agents/events`;
59
+ const params = new URLSearchParams({
60
+ client: 'dragon-channel',
61
+ agentId,
62
+ accountId: this.options.accountId,
63
+ version: this.options.version,
64
+ });
65
+ const url = `${this.http.options.baseURL}/api/agents/events?${params.toString()}`;
60
66
  const connectionId = `${Date.now()}-${++this.connectionSeq}`;
61
67
  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}`);
62
68
  try {
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { ChannelComponent } from "./components/channel/ChannelComponent.js";
8
8
  import { SseComponent } from "./components/sync/SseComponent.js";
9
9
  import { dragonChannelPluginVersion } from "./version.js";
10
10
  const channelId = "dragon";
11
+ const workbenchTargetPrefix = "dragon-workbench:";
11
12
  let cachedRuntime;
12
13
  const containers = new Map();
13
14
  function isDragonSessionKey(sessionKey) {
@@ -22,6 +23,21 @@ function resolveAccountIdFromSessionKey(sessionKey) {
22
23
  }
23
24
  return "default";
24
25
  }
26
+ function encodeWorkbenchTarget(sessionId) {
27
+ const value = String(sessionId || "default").trim() || "default";
28
+ return `${workbenchTargetPrefix}${value}`;
29
+ }
30
+ function decodeWorkbenchTarget(raw) {
31
+ const target = String(raw || "").trim();
32
+ if (!target.startsWith(workbenchTargetPrefix)) {
33
+ throw new Error(`Invalid dragon workbench target "${target || "<empty>"}"; expected "${workbenchTargetPrefix}<sessionId>"`);
34
+ }
35
+ const sessionId = target.slice(workbenchTargetPrefix.length).trim();
36
+ if (!sessionId) {
37
+ throw new Error(`Invalid dragon workbench target "${target}"; sessionId is empty`);
38
+ }
39
+ return sessionId;
40
+ }
25
41
  function containerKey(account) {
26
42
  return `${account.accountId}:${account.agentId}:${account.orchestratorUrl}`;
27
43
  }
@@ -161,25 +177,13 @@ const plugin = createChatChannelPlugin({
161
177
  }
162
178
  },
163
179
  messaging: {
164
- targetPrefixes: ["dragon-workbench", "dragon"],
180
+ targetPrefixes: [workbenchTargetPrefix],
165
181
  normalizeTarget: (raw) => {
166
- let target = raw || "";
167
- for (const prefix of ["dragon-workbench-", "dragon-workbench:", "dragon-", "dragon:"]) {
168
- if (target.startsWith(prefix)) {
169
- target = target.substring(prefix.length);
170
- }
171
- }
172
- return target ? `dragon-workbench-${target}` : "";
182
+ return encodeWorkbenchTarget(decodeWorkbenchTarget(raw));
173
183
  },
174
184
  resolveOutboundSessionRoute: (params) => {
175
- let target = params.target || "";
176
- for (const prefix of ["dragon-workbench-", "dragon-workbench:", "dragon-", "dragon:"]) {
177
- if (target.startsWith(prefix)) {
178
- target = target.substring(prefix.length);
179
- }
180
- }
181
- if (!target)
182
- return null;
185
+ const sessionId = decodeWorkbenchTarget(params.target);
186
+ const target = encodeWorkbenchTarget(sessionId);
183
187
  return buildChannelOutboundSessionRoute({
184
188
  cfg: params.cfg,
185
189
  agentId: params.agentId,
@@ -187,27 +191,22 @@ const plugin = createChatChannelPlugin({
187
191
  accountId: params.accountId,
188
192
  peer: {
189
193
  kind: "direct",
190
- id: target
194
+ id: sessionId
191
195
  },
192
196
  chatType: "direct",
193
- from: `dragon-workbench-${target}`,
194
- to: `dragon-workbench-${target}`
197
+ from: target,
198
+ to: target
195
199
  });
196
200
  },
197
201
  targetResolver: {
198
202
  looksLikeId: (raw) => {
199
- return raw.startsWith("dragon-workbench-") || raw.startsWith("session-");
203
+ return raw.startsWith(workbenchTargetPrefix);
200
204
  },
201
- hint: "<sessionId>",
205
+ hint: "dragon-workbench:<sessionId>",
202
206
  resolveTarget: async ({ input }) => {
203
- let sessionId = input;
204
- for (const prefix of ["dragon-workbench-", "dragon-workbench:", "dragon-", "dragon:"]) {
205
- if (sessionId.startsWith(prefix)) {
206
- sessionId = sessionId.substring(prefix.length);
207
- }
208
- }
207
+ const sessionId = decodeWorkbenchTarget(input);
209
208
  return {
210
- to: `dragon-workbench-${sessionId}`,
209
+ to: encodeWorkbenchTarget(sessionId),
211
210
  kind: "direct",
212
211
  display: `Dragon Workbench User (${sessionId})`,
213
212
  source: "normalized"
@@ -227,15 +226,28 @@ const plugin = createChatChannelPlugin({
227
226
  const account = base.config.resolveAccount(cfg, accountId);
228
227
  const container = await getOrCreateContainer(account, ctx);
229
228
  const channel = container.get('channel');
230
- let target = params.to || params.target || ctx.toolContext?.currentChannelId || "default";
231
- logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolution input: ${target}`);
232
- for (const prefix of ["dragon-workbench-", "dragon-workbench:", "dragon-", "dragon:"]) {
233
- if (target.startsWith(prefix)) {
234
- target = target.substring(prefix.length);
235
- }
229
+ const rawTarget = params.to || params.target || ctx.toolContext?.currentChannelId || "default";
230
+ logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolution input: ${rawTarget}`);
231
+ let sessionId;
232
+ let target;
233
+ try {
234
+ sessionId = decodeWorkbenchTarget(rawTarget);
235
+ target = encodeWorkbenchTarget(sessionId);
236
+ }
237
+ catch (error) {
238
+ const message = error?.message || String(error);
239
+ logger?.error?.(`[dragon channels][Dragon Plugin] handleAction target protocol error: ${message}`);
240
+ await channel.reportTargetProtocolError(message, rawTarget);
241
+ return {
242
+ ok: false,
243
+ error: message,
244
+ didSendViaMessagingTool: false,
245
+ messagingToolSentTexts: [],
246
+ messagingToolSentMediaUrls: [],
247
+ messagingToolSentTargets: [],
248
+ };
236
249
  }
237
- const sessionId = target || "default";
238
- logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolved: sessionId=${sessionId}, rawTarget=${target}`);
250
+ logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolved: sessionId=${sessionId}, target=${target}, rawTarget=${rawTarget}`);
239
251
  // Collect ALL media URLs from the params (mirrors what OpenClaw tracks internally
240
252
  // via collectMessagingMediaUrlsFromRecord when the tool call starts, so that
241
253
  // hasGatewayAgentDeliveredExpectedMedia can match them against expectedMediaUrls).
@@ -282,7 +294,7 @@ const plugin = createChatChannelPlugin({
282
294
  logger?.info?.(`[dragon channels][Dragon Plugin] handleAction sending text over channel: text_len=${text.length}, targetSession=${sessionId}`);
283
295
  const result = await channel.handleOutboundText({
284
296
  text,
285
- peer: { id: sessionId }
297
+ peer: { id: target }
286
298
  });
287
299
  logger?.info?.(`[dragon channels][Dragon Plugin] handleAction channel send result: ok=${result.ok}, messageId=${result.messageId || "none"}`);
288
300
  const evidence = {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const dragonChannelPluginVersion = "0.5.32";
1
+ export declare const dragonChannelPluginVersion = "0.5.34";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const dragonChannelPluginVersion = "0.5.32";
1
+ export const dragonChannelPluginVersion = "0.5.34";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efengx/openclaw-channel-dragon",
3
- "version": "0.5.32",
3
+ "version": "0.5.34",
4
4
  "description": "Dragon workbench channel for OpenClaw",
5
5
  "author": "feng xiang <ofengx@gmail.com>",
6
6
  "type": "module",