@winmatrix/agent-sdk 0.1.0
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/adapters/claude/ClaudeAdapter.d.ts +40 -0
- package/dist/adapters/claude/ClaudeAdapter.d.ts.map +1 -0
- package/dist/adapters/claude/ClaudeAdapter.js +515 -0
- package/dist/adapters/claude/ClaudeAdapter.js.map +1 -0
- package/dist/adapters/claude/ClaudeStreamParser.d.ts +39 -0
- package/dist/adapters/claude/ClaudeStreamParser.d.ts.map +1 -0
- package/dist/adapters/claude/ClaudeStreamParser.js +80 -0
- package/dist/adapters/claude/ClaudeStreamParser.js.map +1 -0
- package/dist/adapters/hermes/HermesAdapter.d.ts +37 -0
- package/dist/adapters/hermes/HermesAdapter.d.ts.map +1 -0
- package/dist/adapters/hermes/HermesAdapter.js +342 -0
- package/dist/adapters/hermes/HermesAdapter.js.map +1 -0
- package/dist/adapters/openclaw/OpenClawAdapter.d.ts +77 -0
- package/dist/adapters/openclaw/OpenClawAdapter.d.ts.map +1 -0
- package/dist/adapters/openclaw/OpenClawAdapter.js +798 -0
- package/dist/adapters/openclaw/OpenClawAdapter.js.map +1 -0
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.d.ts +6 -0
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.d.ts.map +1 -0
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.js +24 -0
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.js.map +1 -0
- package/dist/core/AdapterRegistry.d.ts +21 -0
- package/dist/core/AdapterRegistry.d.ts.map +1 -0
- package/dist/core/AdapterRegistry.js +37 -0
- package/dist/core/AdapterRegistry.js.map +1 -0
- package/dist/core/AgentAdapter.d.ts +165 -0
- package/dist/core/AgentAdapter.d.ts.map +1 -0
- package/dist/core/AgentAdapter.js +23 -0
- package/dist/core/AgentAdapter.js.map +1 -0
- package/dist/core/DaemonClient.d.ts +102 -0
- package/dist/core/DaemonClient.d.ts.map +1 -0
- package/dist/core/DaemonClient.js +225 -0
- package/dist/core/DaemonClient.js.map +1 -0
- package/dist/core/Protocol.d.ts +257 -0
- package/dist/core/Protocol.d.ts.map +1 -0
- package/dist/core/Protocol.js +140 -0
- package/dist/core/Protocol.js.map +1 -0
- package/dist/core/RuntimeDetector.d.ts +7 -0
- package/dist/core/RuntimeDetector.d.ts.map +1 -0
- package/dist/core/RuntimeDetector.js +45 -0
- package/dist/core/RuntimeDetector.js.map +1 -0
- package/dist/core/WinMatrixAgentClient.d.ts +134 -0
- package/dist/core/WinMatrixAgentClient.d.ts.map +1 -0
- package/dist/core/WinMatrixAgentClient.js +382 -0
- package/dist/core/WinMatrixAgentClient.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/DualRuntimeBridge.d.ts +38 -0
- package/dist/runtime/DualRuntimeBridge.d.ts.map +1 -0
- package/dist/runtime/DualRuntimeBridge.js +305 -0
- package/dist/runtime/DualRuntimeBridge.js.map +1 -0
- package/dist/runtime/bridgeConfigLoader.d.ts +14 -0
- package/dist/runtime/bridgeConfigLoader.d.ts.map +1 -0
- package/dist/runtime/bridgeConfigLoader.js +133 -0
- package/dist/runtime/bridgeConfigLoader.js.map +1 -0
- package/dist/runtime/bridgeConfigTypes.d.ts +26 -0
- package/dist/runtime/bridgeConfigTypes.d.ts.map +1 -0
- package/dist/runtime/bridgeConfigTypes.js +6 -0
- package/dist/runtime/bridgeConfigTypes.js.map +1 -0
- package/dist/runtime/bridgeHome.d.ts +19 -0
- package/dist/runtime/bridgeHome.d.ts.map +1 -0
- package/dist/runtime/bridgeHome.js +61 -0
- package/dist/runtime/bridgeHome.js.map +1 -0
- package/dist/runtime/bridgeReconnectPolicy.d.ts +8 -0
- package/dist/runtime/bridgeReconnectPolicy.d.ts.map +1 -0
- package/dist/runtime/bridgeReconnectPolicy.js +16 -0
- package/dist/runtime/bridgeReconnectPolicy.js.map +1 -0
- package/dist/runtime/bridgeTaskJournal.d.ts +7 -0
- package/dist/runtime/bridgeTaskJournal.d.ts.map +1 -0
- package/dist/runtime/bridgeTaskJournal.js +66 -0
- package/dist/runtime/bridgeTaskJournal.js.map +1 -0
- package/dist/runtime/localRuntimeAvailabilityMonitor.d.ts +22 -0
- package/dist/runtime/localRuntimeAvailabilityMonitor.d.ts.map +1 -0
- package/dist/runtime/localRuntimeAvailabilityMonitor.js +81 -0
- package/dist/runtime/localRuntimeAvailabilityMonitor.js.map +1 -0
- package/dist/runtime/runDualRuntimeBridge.d.ts +2 -0
- package/dist/runtime/runDualRuntimeBridge.d.ts.map +1 -0
- package/dist/runtime/runDualRuntimeBridge.js +32 -0
- package/dist/runtime/runDualRuntimeBridge.js.map +1 -0
- package/dist/utils/parseSkillMd.d.ts +10 -0
- package/dist/utils/parseSkillMd.d.ts.map +1 -0
- package/dist/utils/parseSkillMd.js +34 -0
- package/dist/utils/parseSkillMd.js.map +1 -0
- package/dist/utils/processUtils.d.ts +8 -0
- package/dist/utils/processUtils.d.ts.map +1 -0
- package/dist/utils/processUtils.js +31 -0
- package/dist/utils/processUtils.js.map +1 -0
- package/dist/utils/spawnHelper.d.ts +38 -0
- package/dist/utils/spawnHelper.d.ts.map +1 -0
- package/dist/utils/spawnHelper.js +122 -0
- package/dist/utils/spawnHelper.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClawAdapter — 通过 WebSocket-RPC 连接本地 OpenClaw Gateway (port 18789)
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw 是 Gateway 架构的智能体运行时,通过 WS-RPC 协议通信。
|
|
5
|
+
* 协议:connect.challenge → connect 握手 → chat.send 发消息 → chat/agent 事件流
|
|
6
|
+
*
|
|
7
|
+
* 使用 client.id: 'gateway-client' + client.mode: 'backend' 作为 loopback backend 客户端,
|
|
8
|
+
* 无需 device identity 签名。
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { join, dirname } from 'node:path';
|
|
14
|
+
import WebSocket from 'ws';
|
|
15
|
+
import { parseSkillMd } from '../../utils/parseSkillMd.js';
|
|
16
|
+
// ── constants ──
|
|
17
|
+
const PROTOCOL_VERSION = 3;
|
|
18
|
+
const OPENCLAW_DIR = join(homedir(), '.openclaw');
|
|
19
|
+
const OPENCLAW_CONFIG = join(OPENCLAW_DIR, 'openclaw.json');
|
|
20
|
+
/** eventQueue 上限,防止 wakeUp 丢失导致的内存泄漏 */
|
|
21
|
+
const MAX_EVENT_QUEUE = 10_000;
|
|
22
|
+
/** 判断事件帧是否为流终止事件(final/error),终止事件不应被丢弃 */
|
|
23
|
+
function isStreamEndEvent(frame) {
|
|
24
|
+
if (frame.event === 'chat') {
|
|
25
|
+
const state = frame.payload?.state;
|
|
26
|
+
return state === 'final' || state === 'error';
|
|
27
|
+
}
|
|
28
|
+
if (frame.event === 'agent') {
|
|
29
|
+
const stream = typeof frame.payload?.stream === 'string'
|
|
30
|
+
? frame.payload.stream
|
|
31
|
+
: '';
|
|
32
|
+
const data = frame.payload?.data;
|
|
33
|
+
if (stream === 'lifecycle') {
|
|
34
|
+
const phase = data?.phase;
|
|
35
|
+
return phase === 'end' || phase === 'error';
|
|
36
|
+
}
|
|
37
|
+
if (stream === 'error')
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/** 递归提取嵌套对象或数组中的 text 字符串 */
|
|
43
|
+
function extractText(obj) {
|
|
44
|
+
if (typeof obj === 'string')
|
|
45
|
+
return obj;
|
|
46
|
+
if (Array.isArray(obj)) {
|
|
47
|
+
const parts = [];
|
|
48
|
+
for (const item of obj) {
|
|
49
|
+
if (typeof item === 'string') {
|
|
50
|
+
parts.push(item);
|
|
51
|
+
}
|
|
52
|
+
else if (item && typeof item === 'object' && typeof item.text === 'string') {
|
|
53
|
+
parts.push(item.text);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return parts.join('');
|
|
57
|
+
}
|
|
58
|
+
if (!obj || typeof obj !== 'object')
|
|
59
|
+
return '';
|
|
60
|
+
const o = obj;
|
|
61
|
+
if (typeof o.text === 'string')
|
|
62
|
+
return o.text;
|
|
63
|
+
if (typeof o.content === 'string')
|
|
64
|
+
return o.content;
|
|
65
|
+
if (typeof o.delta === 'string')
|
|
66
|
+
return o.delta;
|
|
67
|
+
if (Array.isArray(o.content)) {
|
|
68
|
+
const parts = [];
|
|
69
|
+
for (const item of o.content) {
|
|
70
|
+
if (typeof item === 'string') {
|
|
71
|
+
parts.push(item);
|
|
72
|
+
}
|
|
73
|
+
else if (item && typeof item === 'object' && typeof item.text === 'string') {
|
|
74
|
+
parts.push(item.text);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return parts.join('');
|
|
78
|
+
}
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
/** 从 chat 事件 payload 提取文本 */
|
|
82
|
+
function extractTextFromPayload(payload) {
|
|
83
|
+
const fromMessage = extractText(payload.message);
|
|
84
|
+
if (fromMessage)
|
|
85
|
+
return fromMessage;
|
|
86
|
+
if (Array.isArray(payload.messages)) {
|
|
87
|
+
const parts = [];
|
|
88
|
+
for (const m of payload.messages) {
|
|
89
|
+
const t = extractText(m);
|
|
90
|
+
if (t)
|
|
91
|
+
parts.push(t);
|
|
92
|
+
}
|
|
93
|
+
if (parts.length > 0)
|
|
94
|
+
return parts.join('');
|
|
95
|
+
}
|
|
96
|
+
if (typeof payload.content === 'string')
|
|
97
|
+
return payload.content;
|
|
98
|
+
if (typeof payload.text === 'string')
|
|
99
|
+
return payload.text;
|
|
100
|
+
if (typeof payload.delta === 'string')
|
|
101
|
+
return payload.delta;
|
|
102
|
+
const fromContent = extractText(payload.content);
|
|
103
|
+
if (fromContent)
|
|
104
|
+
return fromContent;
|
|
105
|
+
if (typeof payload.result === 'string')
|
|
106
|
+
return payload.result;
|
|
107
|
+
if (typeof payload.output === 'string')
|
|
108
|
+
return payload.output;
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 从 agent assistant 事件 data 提取文本。
|
|
113
|
+
* 参照 server 端 extractAgentAssistantText — 覆盖 text/delta/content/chunk/messages/message/payload/result/response/output。
|
|
114
|
+
*/
|
|
115
|
+
export function extractAgentAssistantText(data) {
|
|
116
|
+
if (typeof data.text === 'string')
|
|
117
|
+
return data.text;
|
|
118
|
+
if (typeof data.delta === 'string')
|
|
119
|
+
return data.delta;
|
|
120
|
+
if (typeof data.content === 'string')
|
|
121
|
+
return data.content;
|
|
122
|
+
if (typeof data.chunk === 'string')
|
|
123
|
+
return data.chunk;
|
|
124
|
+
const fromMessages = extractTextFromMessages(data.messages);
|
|
125
|
+
if (fromMessages)
|
|
126
|
+
return fromMessages;
|
|
127
|
+
const fromMessage = extractText(data.message);
|
|
128
|
+
if (fromMessage)
|
|
129
|
+
return fromMessage;
|
|
130
|
+
const fromContent = extractText(data.content);
|
|
131
|
+
if (fromContent)
|
|
132
|
+
return fromContent;
|
|
133
|
+
const fromDelta = extractText(data.delta);
|
|
134
|
+
if (fromDelta)
|
|
135
|
+
return fromDelta;
|
|
136
|
+
const fromChunk = extractText(data.chunk);
|
|
137
|
+
if (fromChunk)
|
|
138
|
+
return fromChunk;
|
|
139
|
+
const fromPayload = extractText(data.payload);
|
|
140
|
+
if (fromPayload)
|
|
141
|
+
return fromPayload;
|
|
142
|
+
const fromResult = extractText(data.result);
|
|
143
|
+
if (fromResult)
|
|
144
|
+
return fromResult;
|
|
145
|
+
const fromResponse = extractText(data.response);
|
|
146
|
+
if (fromResponse)
|
|
147
|
+
return fromResponse;
|
|
148
|
+
const fromOutput = extractText(data.output);
|
|
149
|
+
if (fromOutput)
|
|
150
|
+
return fromOutput;
|
|
151
|
+
// deep extraction
|
|
152
|
+
const fromDeep = extractTextDeep(data);
|
|
153
|
+
if (fromDeep)
|
|
154
|
+
return fromDeep;
|
|
155
|
+
return '';
|
|
156
|
+
}
|
|
157
|
+
export function extractTextFromMessages(messages) {
|
|
158
|
+
if (!Array.isArray(messages))
|
|
159
|
+
return '';
|
|
160
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
161
|
+
const item = messages[i];
|
|
162
|
+
if (!item || typeof item !== 'object')
|
|
163
|
+
continue;
|
|
164
|
+
const msg = item;
|
|
165
|
+
const role = typeof msg.role === 'string' ? msg.role.toLowerCase() : '';
|
|
166
|
+
if (role !== 'assistant' && role !== 'model')
|
|
167
|
+
continue;
|
|
168
|
+
const text = extractText(msg.content) || extractTextDeep(msg);
|
|
169
|
+
if (text)
|
|
170
|
+
return text;
|
|
171
|
+
}
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
export function extractTextDeep(obj, depth = 0) {
|
|
175
|
+
if (!obj || typeof obj !== 'object' || depth > 4)
|
|
176
|
+
return '';
|
|
177
|
+
const o = obj;
|
|
178
|
+
for (const key of ['text', 'content', 'delta', 'chunk', 'result', 'output', 'response', 'message']) {
|
|
179
|
+
const v = o[key];
|
|
180
|
+
if (typeof v === 'string' && v.trim())
|
|
181
|
+
return v;
|
|
182
|
+
if (v && typeof v === 'object') {
|
|
183
|
+
const inner = extractTextDeep(v, depth + 1);
|
|
184
|
+
if (inner)
|
|
185
|
+
return inner;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
/** 从 ~/.openclaw/openclaw.json 读取 gateway token */
|
|
191
|
+
function readGatewayTokenFromConfig() {
|
|
192
|
+
try {
|
|
193
|
+
if (!existsSync(OPENCLAW_CONFIG))
|
|
194
|
+
return undefined;
|
|
195
|
+
const raw = readFileSync(OPENCLAW_CONFIG, 'utf-8');
|
|
196
|
+
const cfg = JSON.parse(raw);
|
|
197
|
+
return cfg.gateway?.auth?.token || undefined;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* sessionKey 编码: agent:<agentId>:direct:<sessionKey>(参照 server toAgentSessionKey)。
|
|
205
|
+
* 若 sessionKey 已是 agent: 格式则不重复编码,避免双重包装。
|
|
206
|
+
*/
|
|
207
|
+
export function toAgentSessionKey(sessionKey, agentId) {
|
|
208
|
+
if (sessionKey.toLowerCase().startsWith('agent:'))
|
|
209
|
+
return sessionKey;
|
|
210
|
+
return `agent:${agentId}:direct:${sessionKey}`;
|
|
211
|
+
}
|
|
212
|
+
export class OpenClawAdapter {
|
|
213
|
+
meta = {
|
|
214
|
+
agentType: 'openclaw',
|
|
215
|
+
runtime: 'openclaw',
|
|
216
|
+
capabilities: {
|
|
217
|
+
streaming: true,
|
|
218
|
+
toolCall: true,
|
|
219
|
+
session: true,
|
|
220
|
+
cancellation: false,
|
|
221
|
+
},
|
|
222
|
+
tools: [],
|
|
223
|
+
skills: [],
|
|
224
|
+
toolCategories: {},
|
|
225
|
+
};
|
|
226
|
+
gatewayHost;
|
|
227
|
+
gatewayPort;
|
|
228
|
+
token;
|
|
229
|
+
mcpBridgeUrl;
|
|
230
|
+
mcpApiKey;
|
|
231
|
+
agentId;
|
|
232
|
+
constructor(config) {
|
|
233
|
+
const opts = config;
|
|
234
|
+
this.gatewayHost = opts?.gatewayHost ?? '127.0.0.1';
|
|
235
|
+
this.gatewayPort = opts?.gatewayPort ?? 18789;
|
|
236
|
+
this.token = opts?.token ?? readGatewayTokenFromConfig();
|
|
237
|
+
this.mcpBridgeUrl = opts?.mcpBridgeUrl;
|
|
238
|
+
this.mcpApiKey = opts?.mcpApiKey;
|
|
239
|
+
this.agentId = opts?.agentId;
|
|
240
|
+
}
|
|
241
|
+
get gatewayUrl() {
|
|
242
|
+
return `ws://${this.gatewayHost}:${this.gatewayPort}`;
|
|
243
|
+
}
|
|
244
|
+
// ── MCP config helpers ──
|
|
245
|
+
/**
|
|
246
|
+
* Write MCP server config to openclaw.json, merge with existing keys.
|
|
247
|
+
*
|
|
248
|
+
* 注意:本方法直接写 ~/.openclaw/openclaw.json 共享文件,不适用于同一 OpenClaw
|
|
249
|
+
* 实例上并发多任务场景。当前 daemon 方案 B 下每 agent 单任务串行执行,不会出现
|
|
250
|
+
* 并发写入竞态。若未来支持并发任务,需改为 per-task session 级 MCP 注入。
|
|
251
|
+
*/
|
|
252
|
+
writeMCPConfig(token, configPath) {
|
|
253
|
+
if (!this.mcpBridgeUrl)
|
|
254
|
+
return;
|
|
255
|
+
const actualConfigPath = configPath ?? OPENCLAW_CONFIG;
|
|
256
|
+
try {
|
|
257
|
+
const cfgDir = dirname(actualConfigPath);
|
|
258
|
+
if (!existsSync(cfgDir))
|
|
259
|
+
mkdirSync(cfgDir, { recursive: true });
|
|
260
|
+
let cfg = {};
|
|
261
|
+
if (existsSync(actualConfigPath)) {
|
|
262
|
+
try {
|
|
263
|
+
cfg = JSON.parse(readFileSync(actualConfigPath, 'utf-8'));
|
|
264
|
+
}
|
|
265
|
+
catch { /* overwrite */ }
|
|
266
|
+
}
|
|
267
|
+
const mcp = (cfg.mcp ?? {});
|
|
268
|
+
const servers = (mcp.servers ?? {});
|
|
269
|
+
const effectiveToken = token ?? this.mcpApiKey;
|
|
270
|
+
servers.winmatrix = {
|
|
271
|
+
url: this.mcpBridgeUrl,
|
|
272
|
+
transport: 'streamable-http',
|
|
273
|
+
...(effectiveToken ? { headers: { Authorization: `Bearer ${effectiveToken}` } } : {}),
|
|
274
|
+
};
|
|
275
|
+
cfg.mcp = { ...mcp, servers };
|
|
276
|
+
writeFileSync(actualConfigPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// best-effort: Gateway 可能不在本机
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// ── execute ──
|
|
283
|
+
async *execute(task, signal) {
|
|
284
|
+
const isOneshot = task.mode === 'oneshot';
|
|
285
|
+
let sessionId = task.sessionId;
|
|
286
|
+
// Write MCP config to openclaw.json before connect (per-task mcpToken takes priority).
|
|
287
|
+
// MCP 配置通过文件注入而非 chat.send params,因为 Gateway 从 openclaw.json 中
|
|
288
|
+
// mcp.servers.winmatrix 读取插件配置。前提:Gateway 支持热加载或 MCP 插件按需读取。
|
|
289
|
+
this.writeMCPConfig(task.mcpToken);
|
|
290
|
+
const gatewayHost = task.openclaw?.gatewayHost ?? this.gatewayHost;
|
|
291
|
+
const gatewayPort = task.openclaw?.gatewayPort ?? this.gatewayPort;
|
|
292
|
+
const gatewayUrl = `ws://${gatewayHost}:${gatewayPort}`;
|
|
293
|
+
let ws;
|
|
294
|
+
try {
|
|
295
|
+
ws = await this.authenticatedConnect(gatewayUrl, gatewayHost, signal);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
yield { type: 'error', message: `OpenClaw Gateway connection failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Build persistent handler infrastructure BEFORE chat.send
|
|
302
|
+
const pendingRequests = new Map();
|
|
303
|
+
const eventQueue = [];
|
|
304
|
+
let wakeUp = null;
|
|
305
|
+
let streamDone = false;
|
|
306
|
+
let finalized = false;
|
|
307
|
+
const persistHandler = (data) => {
|
|
308
|
+
try {
|
|
309
|
+
const frame = JSON.parse(data.toString());
|
|
310
|
+
// Route res frames to pendingRequests
|
|
311
|
+
if (frame.type === 'res' && frame.id) {
|
|
312
|
+
const pending = pendingRequests.get(frame.id);
|
|
313
|
+
if (pending) {
|
|
314
|
+
clearTimeout(pending.timer);
|
|
315
|
+
pendingRequests.delete(frame.id);
|
|
316
|
+
pending.resolve({
|
|
317
|
+
ok: frame.ok === true,
|
|
318
|
+
payload: frame.payload,
|
|
319
|
+
error: frame.error,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// Route event frames to event queue (capped to prevent leaks).
|
|
325
|
+
// Never drop final/error events — they're required to terminate the stream loop.
|
|
326
|
+
if (frame.type === 'event') {
|
|
327
|
+
if (eventQueue.length >= MAX_EVENT_QUEUE) {
|
|
328
|
+
let dropIdx = -1;
|
|
329
|
+
for (let i = 0; i < eventQueue.length; i++) {
|
|
330
|
+
if (!isStreamEndEvent(eventQueue[i])) {
|
|
331
|
+
dropIdx = i;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (dropIdx >= 0) {
|
|
336
|
+
eventQueue.splice(dropIdx, 1);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
eventQueue.push(frame);
|
|
340
|
+
wakeUp?.();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// skip malformed
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
ws.on('message', persistHandler);
|
|
348
|
+
const onClose = () => {
|
|
349
|
+
streamDone = true;
|
|
350
|
+
// Reject all pending requests
|
|
351
|
+
for (const [, p] of pendingRequests) {
|
|
352
|
+
clearTimeout(p.timer);
|
|
353
|
+
p.reject(new Error('WebSocket closed'));
|
|
354
|
+
}
|
|
355
|
+
pendingRequests.clear();
|
|
356
|
+
wakeUp?.();
|
|
357
|
+
};
|
|
358
|
+
ws.on('close', onClose);
|
|
359
|
+
const onAbort = () => {
|
|
360
|
+
// Best-effort abort via ws send
|
|
361
|
+
try {
|
|
362
|
+
ws.send(JSON.stringify({ type: 'req', id: `abort-${Date.now()}`, method: 'chat.abort', params: { sessionKey: sessionId } }));
|
|
363
|
+
}
|
|
364
|
+
catch { /* ignore */ }
|
|
365
|
+
ws.close();
|
|
366
|
+
};
|
|
367
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
368
|
+
try {
|
|
369
|
+
const baseSessionKey = !isOneshot && sessionId ? sessionId : `wm-${randomUUID().slice(0, 8)}`;
|
|
370
|
+
const sessionKey = this.agentId ? toAgentSessionKey(baseSessionKey, this.agentId) : baseSessionKey;
|
|
371
|
+
const message = task.systemPrompt
|
|
372
|
+
? `${task.systemPrompt}\n\n---\n\n${task.input}`
|
|
373
|
+
: task.input;
|
|
374
|
+
// Send chat.send via pendingRequests map (not the generic sendRequest)
|
|
375
|
+
const chatSendResult = await this.sendRequestViaMap(ws, pendingRequests, 'chat.send', {
|
|
376
|
+
sessionKey,
|
|
377
|
+
message,
|
|
378
|
+
idempotencyKey: `wm-${randomUUID()}`,
|
|
379
|
+
});
|
|
380
|
+
if (!chatSendResult.ok) {
|
|
381
|
+
const errMsg = typeof chatSendResult.error === 'string'
|
|
382
|
+
? chatSendResult.error
|
|
383
|
+
: JSON.stringify(chatSendResult.error);
|
|
384
|
+
yield { type: 'error', message: `chat.send failed: ${errMsg}` };
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const ack = chatSendResult.payload;
|
|
388
|
+
const runId = ack?.runId;
|
|
389
|
+
sessionId = sessionKey;
|
|
390
|
+
// Stream events from the persistent handler's queue
|
|
391
|
+
let resultText = '';
|
|
392
|
+
let msgIdx = 0;
|
|
393
|
+
while (!streamDone) {
|
|
394
|
+
if (signal?.aborted)
|
|
395
|
+
break;
|
|
396
|
+
while (msgIdx < eventQueue.length) {
|
|
397
|
+
const frame = eventQueue[msgIdx++];
|
|
398
|
+
if (frame.event === 'chat') {
|
|
399
|
+
const payload = frame.payload;
|
|
400
|
+
if (!payload)
|
|
401
|
+
continue;
|
|
402
|
+
if (runId && payload.runId && payload.runId !== runId)
|
|
403
|
+
continue;
|
|
404
|
+
const state = typeof payload.state === 'string' ? payload.state : undefined;
|
|
405
|
+
if (state === 'delta') {
|
|
406
|
+
const text = extractTextFromPayload(payload);
|
|
407
|
+
if (text) {
|
|
408
|
+
resultText += text;
|
|
409
|
+
yield { type: 'delta', content: text };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else if (state === 'final' && !finalized) {
|
|
413
|
+
finalized = true;
|
|
414
|
+
const text = extractTextFromPayload(payload);
|
|
415
|
+
if (text)
|
|
416
|
+
resultText += text;
|
|
417
|
+
streamDone = true;
|
|
418
|
+
}
|
|
419
|
+
else if (state === 'error' && !finalized) {
|
|
420
|
+
finalized = true;
|
|
421
|
+
const errMsg = typeof payload.errorMessage === 'string'
|
|
422
|
+
? payload.errorMessage
|
|
423
|
+
: 'chat error';
|
|
424
|
+
yield { type: 'error', message: errMsg };
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (frame.event === 'agent') {
|
|
429
|
+
const payload = frame.payload;
|
|
430
|
+
if (!payload)
|
|
431
|
+
continue;
|
|
432
|
+
if (runId && payload.runId && payload.runId !== runId)
|
|
433
|
+
continue;
|
|
434
|
+
const stream = typeof payload.stream === 'string' ? payload.stream : '';
|
|
435
|
+
const data = (payload.data ?? {});
|
|
436
|
+
if (stream === 'assistant') {
|
|
437
|
+
const delta = extractAgentAssistantText(data);
|
|
438
|
+
if (delta) {
|
|
439
|
+
resultText += delta;
|
|
440
|
+
yield { type: 'delta', content: delta };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else if (stream === 'tool' && typeof data.name === 'string') {
|
|
444
|
+
// Observability only — Gateway executes tools via MCP
|
|
445
|
+
const argsStr = typeof data.arguments === 'string'
|
|
446
|
+
? data.arguments
|
|
447
|
+
: JSON.stringify(data.arguments ?? {});
|
|
448
|
+
yield { type: 'thinking', content: `[工具] ${data.name}(${argsStr.slice(0, 200)})` };
|
|
449
|
+
}
|
|
450
|
+
else if (stream === 'result') {
|
|
451
|
+
const resultTxt = extractAgentAssistantText(data);
|
|
452
|
+
if (resultTxt) {
|
|
453
|
+
resultText += resultTxt;
|
|
454
|
+
yield { type: 'delta', content: resultTxt };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else if (stream === 'error' && !finalized) {
|
|
458
|
+
finalized = true;
|
|
459
|
+
const errMsg = typeof data.error === 'string'
|
|
460
|
+
? data.error
|
|
461
|
+
: typeof data.message === 'string'
|
|
462
|
+
? data.message
|
|
463
|
+
: 'agent error';
|
|
464
|
+
yield { type: 'error', message: errMsg };
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
else if (stream === 'lifecycle') {
|
|
468
|
+
const phase = data.phase;
|
|
469
|
+
if (phase === 'end' && !finalized) {
|
|
470
|
+
finalized = true;
|
|
471
|
+
streamDone = true;
|
|
472
|
+
}
|
|
473
|
+
else if (phase === 'error' && !finalized) {
|
|
474
|
+
finalized = true;
|
|
475
|
+
const errMsg = (typeof data.error === 'string' ? data.error : undefined)
|
|
476
|
+
?? (typeof data.message === 'string' ? data.message : undefined)
|
|
477
|
+
?? 'agent lifecycle error';
|
|
478
|
+
yield { type: 'error', message: errMsg };
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
if (!streamDone) {
|
|
488
|
+
await new Promise((resolve) => {
|
|
489
|
+
wakeUp = resolve;
|
|
490
|
+
});
|
|
491
|
+
wakeUp = null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
yield {
|
|
495
|
+
type: 'result',
|
|
496
|
+
content: resultText.trim() || (ack?.status === 'started' ? '任务已提交,等待执行...' : ''),
|
|
497
|
+
sessionId: isOneshot ? undefined : sessionId,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
yield { type: 'error', message: err instanceof Error ? err.message : String(err) };
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
signal?.removeEventListener('abort', onAbort);
|
|
505
|
+
ws.off('message', persistHandler);
|
|
506
|
+
ws.off('close', onClose);
|
|
507
|
+
ws.close();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// ── discoverSkills ──
|
|
511
|
+
async discoverSkills(context) {
|
|
512
|
+
const result = [];
|
|
513
|
+
const seen = new Set();
|
|
514
|
+
const layers = [];
|
|
515
|
+
if (context.workDir) {
|
|
516
|
+
layers.push({ dir: join(context.workDir, 'skills'), source: 'project' });
|
|
517
|
+
layers.push({ dir: join(context.workDir, '.agents', 'skills'), source: 'project' });
|
|
518
|
+
}
|
|
519
|
+
layers.push({ dir: join(homedir(), '.agents', 'skills'), source: 'user' });
|
|
520
|
+
layers.push({ dir: join(OPENCLAW_DIR, 'skills'), source: 'bundled' });
|
|
521
|
+
for (const layer of layers) {
|
|
522
|
+
if (!existsSync(layer.dir))
|
|
523
|
+
continue;
|
|
524
|
+
try {
|
|
525
|
+
const skills = this.scanSkillDir(layer.dir, layer.source);
|
|
526
|
+
for (const skill of skills) {
|
|
527
|
+
if (!seen.has(skill.name)) {
|
|
528
|
+
seen.add(skill.name);
|
|
529
|
+
result.push(skill);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch { /* silently skip */ }
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
scanSkillDir(dir, source) {
|
|
538
|
+
const skills = [];
|
|
539
|
+
try {
|
|
540
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
541
|
+
for (const entry of entries) {
|
|
542
|
+
if (!entry.isDirectory())
|
|
543
|
+
continue;
|
|
544
|
+
const skillMdPath = join(dir, entry.name, 'SKILL.md');
|
|
545
|
+
if (!existsSync(skillMdPath))
|
|
546
|
+
continue;
|
|
547
|
+
const parsed = parseSkillMd(skillMdPath);
|
|
548
|
+
if (parsed) {
|
|
549
|
+
skills.push({ ...parsed, source });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
catch { /* silently skip */ }
|
|
554
|
+
return skills;
|
|
555
|
+
}
|
|
556
|
+
// ── authenticated WebSocket connect ──
|
|
557
|
+
authenticatedConnect(url, host, signal) {
|
|
558
|
+
return new Promise((resolve, reject) => {
|
|
559
|
+
const ws = new WebSocket(url, {
|
|
560
|
+
headers: { Origin: `http://${host}` },
|
|
561
|
+
});
|
|
562
|
+
let handshakeDone = false;
|
|
563
|
+
const onAbort = () => {
|
|
564
|
+
ws.close();
|
|
565
|
+
if (!handshakeDone)
|
|
566
|
+
reject(new Error('Aborted'));
|
|
567
|
+
};
|
|
568
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
569
|
+
const cleanup = () => {
|
|
570
|
+
handshakeDone = true;
|
|
571
|
+
signal?.removeEventListener('abort', onAbort);
|
|
572
|
+
ws.off('message', onMessage);
|
|
573
|
+
};
|
|
574
|
+
const onMessage = (data) => {
|
|
575
|
+
try {
|
|
576
|
+
const frame = JSON.parse(data.toString());
|
|
577
|
+
if (frame.event === 'connect.challenge') {
|
|
578
|
+
const connectId = `connect-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
579
|
+
const role = 'operator';
|
|
580
|
+
const scopes = ['operator.admin', 'operator.write', 'operator.read'];
|
|
581
|
+
const connectParams = {
|
|
582
|
+
minProtocol: PROTOCOL_VERSION,
|
|
583
|
+
maxProtocol: PROTOCOL_VERSION,
|
|
584
|
+
client: {
|
|
585
|
+
// 'gateway-client' + mode:'backend' = loopback backend 客户端(官方支持,无需 device identity)。
|
|
586
|
+
// 服务端 workstation 使用 'openclaw-control-ui' + mode:'webchat',两者 client.id 不同是因为
|
|
587
|
+
// daemon 走本地 loopback(同机部署),server 走容器远程连接。
|
|
588
|
+
id: 'gateway-client',
|
|
589
|
+
displayName: 'WinMatrix Agent',
|
|
590
|
+
version: '1.0.0',
|
|
591
|
+
platform: process.platform,
|
|
592
|
+
mode: 'backend',
|
|
593
|
+
},
|
|
594
|
+
role,
|
|
595
|
+
scopes,
|
|
596
|
+
};
|
|
597
|
+
if (this.token) {
|
|
598
|
+
connectParams.auth = { token: this.token };
|
|
599
|
+
}
|
|
600
|
+
ws.send(JSON.stringify({ type: 'req', id: connectId, method: 'connect', params: connectParams }));
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (frame.type === 'res' && frame.id?.startsWith('connect-')) {
|
|
604
|
+
if (frame.ok) {
|
|
605
|
+
cleanup();
|
|
606
|
+
resolve(ws);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
cleanup();
|
|
610
|
+
const errDetail = typeof frame.error === 'string' ? frame.error : JSON.stringify(frame.error);
|
|
611
|
+
reject(new Error(`Gateway connect rejected: ${errDetail}`));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// skip malformed frames during handshake
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
ws.on('message', onMessage);
|
|
620
|
+
ws.on('error', (err) => {
|
|
621
|
+
cleanup();
|
|
622
|
+
reject(new Error(`OpenClaw Gateway not reachable at ${url}: ${err.message}`));
|
|
623
|
+
});
|
|
624
|
+
ws.on('close', () => {
|
|
625
|
+
if (!handshakeDone) {
|
|
626
|
+
cleanup();
|
|
627
|
+
reject(new Error('WebSocket closed before handshake completed'));
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
if (signal?.aborted) {
|
|
631
|
+
ws.close();
|
|
632
|
+
reject(new Error('Aborted'));
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
// ── RPC helpers ──
|
|
637
|
+
/**
|
|
638
|
+
* Generic sendRequest — temporary onMessage handler pattern.
|
|
639
|
+
* Used by healthCheck/listSessions/getSession (short-lived connections).
|
|
640
|
+
*/
|
|
641
|
+
sendRequest(ws, method, params) {
|
|
642
|
+
return new Promise((resolve, reject) => {
|
|
643
|
+
const id = `${method}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
644
|
+
let resolved = false;
|
|
645
|
+
const timer = setTimeout(() => {
|
|
646
|
+
if (resolved)
|
|
647
|
+
return;
|
|
648
|
+
resolved = true;
|
|
649
|
+
ws.off('message', onMessage);
|
|
650
|
+
reject(new Error(`OpenClaw request timeout: ${method}`));
|
|
651
|
+
}, 30_000);
|
|
652
|
+
const onMessage = (data) => {
|
|
653
|
+
try {
|
|
654
|
+
const frame = JSON.parse(data.toString());
|
|
655
|
+
if (frame.type === 'res' && frame.id === id) {
|
|
656
|
+
if (resolved)
|
|
657
|
+
return;
|
|
658
|
+
resolved = true;
|
|
659
|
+
clearTimeout(timer);
|
|
660
|
+
ws.off('message', onMessage);
|
|
661
|
+
resolve({
|
|
662
|
+
ok: frame.ok === true,
|
|
663
|
+
payload: frame.payload,
|
|
664
|
+
error: frame.error,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
// skip malformed frames
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
ws.on('message', onMessage);
|
|
673
|
+
ws.send(JSON.stringify({ type: 'req', id, method, params }));
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Send a request using a pendingRequests map (persistent handler multiplexing).
|
|
678
|
+
* Used by execute() to multiplex req/res with event streaming on the same ws.
|
|
679
|
+
*/
|
|
680
|
+
sendRequestViaMap(ws, pendingRequests, method, params) {
|
|
681
|
+
return new Promise((resolve, reject) => {
|
|
682
|
+
const id = `${method}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
683
|
+
const timer = setTimeout(() => {
|
|
684
|
+
pendingRequests.delete(id);
|
|
685
|
+
reject(new Error(`OpenClaw request timeout: ${method}`));
|
|
686
|
+
}, 30_000);
|
|
687
|
+
pendingRequests.set(id, { resolve, reject, timer });
|
|
688
|
+
ws.send(JSON.stringify({ type: 'req', id, method, params }));
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
// ── healthCheck ──
|
|
692
|
+
async healthCheck() {
|
|
693
|
+
try {
|
|
694
|
+
const ws = await this.authenticatedConnect(this.gatewayUrl, this.gatewayHost);
|
|
695
|
+
const result = await this.sendRequest(ws, 'health', {});
|
|
696
|
+
ws.close();
|
|
697
|
+
if (result.ok) {
|
|
698
|
+
const version = typeof result.payload?.version === 'string' ? result.payload.version : undefined;
|
|
699
|
+
return { available: true, version };
|
|
700
|
+
}
|
|
701
|
+
return { available: true };
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
return { available: false, error: err instanceof Error ? err.message : String(err) };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// ── listSessions ──
|
|
708
|
+
async listSessions(options) {
|
|
709
|
+
try {
|
|
710
|
+
const ws = await this.authenticatedConnect(this.gatewayUrl, this.gatewayHost);
|
|
711
|
+
const result = await this.sendRequest(ws, 'sessions.list', {
|
|
712
|
+
dir: options.dir,
|
|
713
|
+
limit: options.limit ?? 50,
|
|
714
|
+
});
|
|
715
|
+
ws.close();
|
|
716
|
+
if (!result.ok && result.error) {
|
|
717
|
+
return { sessions: [], error: typeof result.error === 'string' ? result.error : JSON.stringify(result.error) };
|
|
718
|
+
}
|
|
719
|
+
const data = result.payload;
|
|
720
|
+
const raw = data?.sessions ?? [];
|
|
721
|
+
const sessions = raw.map((s) => ({
|
|
722
|
+
id: String(s.id ?? s.session_id ?? s.sessionKey ?? ''),
|
|
723
|
+
cwd: typeof s.cwd === 'string' ? s.cwd : undefined,
|
|
724
|
+
summary: typeof s.summary === 'string' ? s.summary : undefined,
|
|
725
|
+
createdAt: typeof s.created_at === 'string' ? s.created_at : undefined,
|
|
726
|
+
lastActivityAt: typeof s.last_activity_at === 'string' ? s.last_activity_at : undefined,
|
|
727
|
+
messageCount: typeof s.message_count === 'number' ? s.message_count : undefined,
|
|
728
|
+
status: typeof s.status === 'string' ? s.status : undefined,
|
|
729
|
+
}));
|
|
730
|
+
return { sessions };
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
return { sessions: [], error: err instanceof Error ? err.message : String(err) };
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// ── getSession ──
|
|
737
|
+
async getSession(sessionId, options = {}) {
|
|
738
|
+
try {
|
|
739
|
+
const ws = await this.authenticatedConnect(this.gatewayUrl, this.gatewayHost);
|
|
740
|
+
const result = await this.sendRequest(ws, 'sessions.preview', {
|
|
741
|
+
sessionKey: sessionId,
|
|
742
|
+
messageLimit: options.messageLimit ?? 100,
|
|
743
|
+
includeRaw: options.includeRawMessages === true,
|
|
744
|
+
});
|
|
745
|
+
ws.close();
|
|
746
|
+
if (!result.ok && result.error) {
|
|
747
|
+
return {
|
|
748
|
+
session: { id: sessionId },
|
|
749
|
+
messageCount: 0,
|
|
750
|
+
truncated: false,
|
|
751
|
+
userPrompts: [],
|
|
752
|
+
assistantText: '',
|
|
753
|
+
lastAssistantText: '',
|
|
754
|
+
error: typeof result.error === 'string' ? result.error : JSON.stringify(result.error),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const data = result.payload;
|
|
758
|
+
const messages = data?.messages ?? [];
|
|
759
|
+
let assistantText = '';
|
|
760
|
+
let lastAssistantText = '';
|
|
761
|
+
const userPrompts = [];
|
|
762
|
+
for (const msg of messages) {
|
|
763
|
+
if (msg.role === 'user' && msg.content) {
|
|
764
|
+
userPrompts.push(msg.content);
|
|
765
|
+
}
|
|
766
|
+
else if (msg.role === 'assistant' && msg.content) {
|
|
767
|
+
assistantText += msg.content + '\n';
|
|
768
|
+
lastAssistantText = msg.content;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
session: {
|
|
773
|
+
id: sessionId,
|
|
774
|
+
...(data?.session ?? {}),
|
|
775
|
+
},
|
|
776
|
+
messageCount: messages.length,
|
|
777
|
+
truncated: messages.length >= (options.messageLimit ?? 100),
|
|
778
|
+
userPrompts,
|
|
779
|
+
assistantText: assistantText.trim(),
|
|
780
|
+
lastAssistantText,
|
|
781
|
+
rawMessages: options.includeRawMessages ? messages : undefined,
|
|
782
|
+
rawMessagesIncluded: options.includeRawMessages === true,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
catch (err) {
|
|
786
|
+
return {
|
|
787
|
+
session: { id: sessionId },
|
|
788
|
+
messageCount: 0,
|
|
789
|
+
truncated: false,
|
|
790
|
+
userPrompts: [],
|
|
791
|
+
assistantText: '',
|
|
792
|
+
lastAssistantText: '',
|
|
793
|
+
error: err instanceof Error ? err.message : String(err),
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
//# sourceMappingURL=OpenClawAdapter.js.map
|