@efengx/openclaw-channel-dragon 0.5.22 → 0.5.24

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.
@@ -18,12 +18,6 @@ export declare class ChannelComponent implements IComponent {
18
18
  private normalizeSessionSegment;
19
19
  private resolveOpenClawAgentId;
20
20
  private buildOpenClawSessionKey;
21
- /**
22
- * Derives a stable, deterministic UUID (v4-shaped) from a sessionKey using SHA-256.
23
- * This ensures each Dragon session maps to a unique physical .jsonl file in OpenClaw,
24
- * preventing concurrent lane lock conflicts (EmbeddedAttemptSessionTakeoverError).
25
- */
26
- private deriveSessionId;
27
21
  private stringifyProgressDetail;
28
22
  deliverToOpenClaw: (content: string, sessionId?: string, modelId?: string, attachments?: any[], messageId?: string | number) => Promise<void>;
29
23
  handleOutboundText: (ctx: any) => Promise<{
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { dragonChannelPluginVersion } from "../../version.js";
3
2
  const channelId = "dragon";
4
3
  export class ChannelComponent {
@@ -35,22 +34,6 @@ export class ChannelComponent {
35
34
  const workbenchSessionId = this.normalizeSessionSegment(sessionId, 'default');
36
35
  return `agent:${openClawAgentId}:dragon:direct:${dragonAgentId}:${workbenchSessionId}`;
37
36
  }
38
- /**
39
- * Derives a stable, deterministic UUID (v4-shaped) from a sessionKey using SHA-256.
40
- * This ensures each Dragon session maps to a unique physical .jsonl file in OpenClaw,
41
- * preventing concurrent lane lock conflicts (EmbeddedAttemptSessionTakeoverError).
42
- */
43
- deriveSessionId(sessionKey) {
44
- const hash = createHash("sha256").update(`dragon:${sessionKey}`).digest("hex");
45
- // Format as UUID v4 (variant bits set for RFC 4122 compliance)
46
- return [
47
- hash.slice(0, 8),
48
- hash.slice(8, 12),
49
- `4${hash.slice(13, 16)}`,
50
- `${(["8", "9", "a", "b"][parseInt(hash[16], 16) >> 2])}${hash.slice(17, 20)}`,
51
- hash.slice(20, 32),
52
- ].join("-");
53
- }
54
37
  stringifyProgressDetail(value, maxLength = 240) {
55
38
  if (typeof value === 'string') {
56
39
  const text = value.trim();
@@ -93,161 +76,207 @@ export class ChannelComponent {
93
76
  msgId: replyMsgId,
94
77
  ...payload,
95
78
  });
96
- await replyDispatcher({
97
- ctx: {
98
- Body: content,
99
- From: sessionId === 'default' ? "workbench-user" : `workbench-user-${sessionId}`,
100
- To: agentId,
101
- ChatType: "direct",
102
- Provider: channelId,
103
- ChannelId: channelId,
104
- AccountId: accountId,
105
- SessionKey: sessionKey,
106
- // Deterministic SessionId derived from SessionKey — ensures each Dragon
107
- // workbench session has its own isolated .jsonl file in OpenClaw,
108
- // preventing EmbeddedAttemptSessionTakeoverError caused by concurrent
109
- // lane access to the same physical session file.
110
- SessionId: this.deriveSessionId(sessionKey),
111
- Timestamp: Date.now(),
112
- Model: modelId,
113
- Attachments: attachments,
114
- },
115
- cfg,
116
- dispatcherOptions: {
117
- deliver: async (payload) => {
118
- const text = payload?.text || "";
119
- if (!text && !payload?.tool_calls?.length)
120
- return;
121
- await this.telemetry.reportReply({
122
- content: text,
123
- sessionId,
124
- tool_calls: payload?.tool_calls,
125
- reasoning_content: payload?.reasoning_content,
126
- source: "telemetry_deliver",
127
- msgId: replyMsgId,
128
- });
129
- }
130
- },
131
- replyOptions: {
132
- onReplyStart: async () => {
133
- await progress({ kind: 'lifecycle', phase: 'start', status: 'running', title: '开始处理请求' });
134
- },
135
- onAssistantMessageStart: async () => {
136
- await progress({ kind: 'assistant', phase: 'start', status: 'running', title: '开始生成回复' });
137
- },
138
- onPartialReply: async (payload) => {
139
- await progress({
140
- kind: 'assistant_delta',
141
- phase: 'update',
142
- status: 'running',
143
- title: '正在生成回复',
144
- detail: this.stringifyProgressDetail(payload?.delta || payload?.text),
145
- data: { replace: payload?.replace === true },
146
- });
147
- },
148
- onReasoningStream: async (payload) => {
149
- await progress({
150
- kind: 'reasoning',
151
- phase: 'update',
152
- status: 'running',
153
- title: '正在推理',
154
- detail: this.stringifyProgressDetail(payload?.text || payload),
155
- });
156
- },
157
- onReasoningEnd: async () => {
158
- await progress({ kind: 'reasoning', phase: 'end', status: 'completed', title: '推理完成' });
159
- },
160
- onToolStart: async (payload) => {
161
- await progress({
162
- kind: 'tool',
163
- phase: payload?.phase || 'start',
164
- status: 'running',
165
- title: payload?.name ? `调用工具:${payload.name}` : '调用工具',
166
- detail: this.stringifyProgressDetail(payload?.args),
167
- data: payload,
168
- });
169
- },
170
- onToolResult: async (payload) => {
171
- await progress({
172
- kind: 'tool',
173
- phase: 'end',
174
- status: 'completed',
175
- title: '工具执行完成',
176
- detail: this.stringifyProgressDetail(payload?.text || payload),
177
- });
178
- },
179
- onItemEvent: async (payload) => {
180
- await progress({
181
- kind: payload?.kind || 'item',
182
- phase: payload?.phase,
183
- status: payload?.status,
184
- title: payload?.title || payload?.name || '任务步骤更新',
185
- detail: this.stringifyProgressDetail(payload?.progressText || payload?.summary || payload?.meta),
186
- data: payload,
187
- });
188
- },
189
- onPlanUpdate: async (payload) => {
190
- await progress({
191
- kind: 'plan',
192
- phase: payload?.phase || 'update',
193
- status: 'running',
194
- title: payload?.title || '更新执行计划',
195
- detail: this.stringifyProgressDetail(payload?.explanation || payload?.steps),
196
- data: payload,
197
- });
79
+ try {
80
+ await replyDispatcher({
81
+ ctx: {
82
+ Body: content,
83
+ From: sessionId === 'default' ? "workbench-user" : `workbench-user-${sessionId}`,
84
+ To: sessionId === 'default' ? "dragon-workbench" : `dragon-workbench-${sessionId}`,
85
+ ChatType: "direct",
86
+ Provider: channelId,
87
+ ChannelId: channelId,
88
+ AccountId: accountId,
89
+ SessionKey: sessionKey,
90
+ Timestamp: Date.now(),
91
+ Model: modelId,
92
+ Attachments: attachments,
198
93
  },
199
- onApprovalEvent: async (payload) => {
200
- await progress({
201
- kind: 'approval',
202
- phase: payload?.phase || 'update',
203
- status: payload?.status || 'running',
204
- title: payload?.title || '等待审批',
205
- detail: this.stringifyProgressDetail(payload?.message || payload?.reason || payload?.command),
206
- data: payload,
207
- });
94
+ cfg,
95
+ dispatcherOptions: {
96
+ deliver: async (payload) => {
97
+ const text = payload?.text || "";
98
+ if (!text && !payload?.tool_calls?.length)
99
+ return;
100
+ await this.telemetry.reportReply({
101
+ content: text,
102
+ sessionId,
103
+ tool_calls: payload?.tool_calls,
104
+ reasoning_content: payload?.reasoning_content,
105
+ source: "telemetry_deliver",
106
+ msgId: replyMsgId,
107
+ });
108
+ }
208
109
  },
209
- onCommandOutput: async (payload) => {
210
- await progress({
211
- kind: 'command',
212
- phase: payload?.phase || 'update',
213
- status: payload?.status || 'running',
214
- title: payload?.title || payload?.name || '命令执行中',
215
- detail: this.stringifyProgressDetail(payload?.output || payload?.cwd),
216
- data: payload,
217
- });
110
+ replyOptions: {
111
+ onReplyStart: async () => {
112
+ await progress({ kind: 'lifecycle', phase: 'start', status: 'running', title: '开始处理请求' });
113
+ },
114
+ onAssistantMessageStart: async () => {
115
+ await progress({ kind: 'assistant', phase: 'start', status: 'running', title: '开始生成回复' });
116
+ },
117
+ onPartialReply: async (payload) => {
118
+ await progress({
119
+ kind: 'assistant_delta',
120
+ phase: 'update',
121
+ status: 'running',
122
+ title: '正在生成回复',
123
+ detail: this.stringifyProgressDetail(payload?.delta || payload?.text),
124
+ data: { replace: payload?.replace === true },
125
+ });
126
+ },
127
+ onReasoningStream: async (payload) => {
128
+ await progress({
129
+ kind: 'reasoning',
130
+ phase: 'update',
131
+ status: 'running',
132
+ title: '正在推理',
133
+ detail: this.stringifyProgressDetail(payload?.text || payload),
134
+ });
135
+ },
136
+ onReasoningEnd: async () => {
137
+ await progress({ kind: 'reasoning', phase: 'end', status: 'completed', title: '推理完成' });
138
+ },
139
+ onToolStart: async (payload) => {
140
+ await progress({
141
+ kind: 'tool',
142
+ phase: payload?.phase || 'start',
143
+ status: 'running',
144
+ title: payload?.name ? `调用工具:${payload.name}` : '调用工具',
145
+ detail: this.stringifyProgressDetail(payload?.args),
146
+ data: payload,
147
+ });
148
+ },
149
+ onToolResult: async (payload) => {
150
+ await progress({
151
+ kind: 'tool',
152
+ phase: 'end',
153
+ status: 'completed',
154
+ title: '工具执行完成',
155
+ detail: this.stringifyProgressDetail(payload?.text || payload),
156
+ });
157
+ },
158
+ onItemEvent: async (payload) => {
159
+ await progress({
160
+ kind: payload?.kind || 'item',
161
+ phase: payload?.phase,
162
+ status: payload?.status,
163
+ title: payload?.title || payload?.name || '任务步骤更新',
164
+ detail: this.stringifyProgressDetail(payload?.progressText || payload?.summary || payload?.meta),
165
+ data: payload,
166
+ });
167
+ },
168
+ onPlanUpdate: async (payload) => {
169
+ await progress({
170
+ kind: 'plan',
171
+ phase: payload?.phase || 'update',
172
+ status: 'running',
173
+ title: payload?.title || '更新执行计划',
174
+ detail: this.stringifyProgressDetail(payload?.explanation || payload?.steps),
175
+ data: payload,
176
+ });
177
+ },
178
+ onApprovalEvent: async (payload) => {
179
+ await progress({
180
+ kind: 'approval',
181
+ phase: payload?.phase || 'update',
182
+ status: payload?.status || 'running',
183
+ title: payload?.title || '等待审批',
184
+ detail: this.stringifyProgressDetail(payload?.message || payload?.reason || payload?.command),
185
+ data: payload,
186
+ });
187
+ },
188
+ onCommandOutput: async (payload) => {
189
+ await progress({
190
+ kind: 'command',
191
+ phase: payload?.phase || 'update',
192
+ status: payload?.status || 'running',
193
+ title: payload?.title || payload?.name || '命令执行中',
194
+ detail: this.stringifyProgressDetail(payload?.output || payload?.cwd),
195
+ data: payload,
196
+ });
197
+ },
198
+ onPatchSummary: async (payload) => {
199
+ await progress({
200
+ kind: 'patch',
201
+ phase: payload?.phase || 'end',
202
+ status: payload?.status || 'completed',
203
+ title: payload?.title || '代码变更完成',
204
+ detail: this.stringifyProgressDetail(payload?.summary || {
205
+ added: payload?.added,
206
+ modified: payload?.modified,
207
+ deleted: payload?.deleted,
208
+ }),
209
+ data: payload,
210
+ });
211
+ },
212
+ onCompactionStart: async () => {
213
+ await progress({ kind: 'compaction', phase: 'start', status: 'running', title: '正在压缩上下文' });
214
+ },
215
+ onCompactionEnd: async () => {
216
+ await progress({ kind: 'compaction', phase: 'end', status: 'completed', title: '上下文压缩完成' });
217
+ },
218
+ onModelSelected: (model) => {
219
+ void progress({
220
+ kind: 'model',
221
+ phase: 'selected',
222
+ status: 'completed',
223
+ title: '已选择模型',
224
+ detail: [model?.provider, model?.model].filter(Boolean).join('/'),
225
+ data: model,
226
+ });
227
+ },
218
228
  },
219
- onPatchSummary: async (payload) => {
220
- await progress({
221
- kind: 'patch',
222
- phase: payload?.phase || 'end',
223
- status: payload?.status || 'completed',
224
- title: payload?.title || '代码变更完成',
225
- detail: this.stringifyProgressDetail(payload?.summary || {
226
- added: payload?.added,
227
- modified: payload?.modified,
228
- deleted: payload?.deleted,
229
- }),
230
- data: payload,
231
- });
232
- },
233
- onCompactionStart: async () => {
234
- await progress({ kind: 'compaction', phase: 'start', status: 'running', title: '正在压缩上下文' });
235
- },
236
- onCompactionEnd: async () => {
237
- await progress({ kind: 'compaction', phase: 'end', status: 'completed', title: '上下文压缩完成' });
238
- },
239
- onModelSelected: (model) => {
240
- void progress({
241
- kind: 'model',
242
- phase: 'selected',
243
- status: 'completed',
244
- title: '已选择模型',
245
- detail: [model?.provider, model?.model].filter(Boolean).join('/'),
246
- data: model,
247
- });
248
- },
249
- },
250
- });
229
+ });
230
+ }
231
+ catch (err) {
232
+ logger?.error?.(`[Dragon Plugin] Error during replyDispatcher: ${err?.message || err}`);
233
+ let resolvedErrorMessage = err?.message || String(err);
234
+ const isTakeoverError = resolvedErrorMessage.includes("session file changed while embedded prompt lock was released");
235
+ if (isTakeoverError) {
236
+ try {
237
+ // Read actual error from the session file!
238
+ const { loadSessionStore, resolveStorePath } = (await import("openclaw/plugin-sdk/session-store-runtime"));
239
+ const { readFileSync } = await import("fs");
240
+ const storePath = resolveStorePath(cfg.session?.store, { agentId: this.resolveOpenClawAgentId() });
241
+ const store = loadSessionStore(storePath, { skipCache: true });
242
+ const entry = store[sessionKey];
243
+ if (entry && entry.sessionFile) {
244
+ const content = readFileSync(entry.sessionFile, "utf8");
245
+ const lines = content.split("\n").filter(line => line.trim());
246
+ for (let i = lines.length - 1; i >= 0; i--) {
247
+ try {
248
+ const item = JSON.parse(lines[i]);
249
+ if (item.type === "message" && item.stopReason === "error" && item.errorMessage) {
250
+ resolvedErrorMessage = item.errorMessage;
251
+ logger?.warn?.(`[Dragon Plugin] Extracted real underlying error from session file: ${resolvedErrorMessage}`);
252
+ break;
253
+ }
254
+ }
255
+ catch {
256
+ // Ignore parse errors for incomplete lines
257
+ }
258
+ }
259
+ }
260
+ }
261
+ catch (fsErr) {
262
+ logger?.error?.(`[Dragon Plugin] Failed to read real error from session file: ${fsErr}`);
263
+ }
264
+ }
265
+ // Report the actual failure via progress and reply
266
+ await progress({
267
+ kind: 'assistant',
268
+ phase: 'error',
269
+ status: 'failed',
270
+ title: '生成回复失败',
271
+ detail: resolvedErrorMessage,
272
+ });
273
+ await this.telemetry.reportReply({
274
+ content: `⚠️ OpenClaw Error: ${resolvedErrorMessage}`,
275
+ sessionId,
276
+ source: "telemetry_deliver",
277
+ msgId: replyMsgId,
278
+ });
279
+ }
251
280
  })
252
281
  .finally(() => {
253
282
  if (this.sessionQueues.get(sessionKey) === current) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const dragonChannelPluginVersion = "0.5.22";
1
+ export declare const dragonChannelPluginVersion = "0.5.24";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const dragonChannelPluginVersion = "0.5.22";
1
+ export const dragonChannelPluginVersion = "0.5.24";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efengx/openclaw-channel-dragon",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "description": "Dragon workbench channel for OpenClaw",
5
5
  "author": "feng xiang <ofengx@gmail.com>",
6
6
  "type": "module",