@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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
kind: '
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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.
|
|
1
|
+
export declare const dragonChannelPluginVersion = "0.5.24";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export const dragonChannelPluginVersion = "0.5.24";
|