@winmatrix/agent-sdk 1.0.3 → 1.0.5
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 +60 -1
- package/dist/adapters/claude/ClaudeAdapter.d.ts.map +1 -1
- package/dist/adapters/claude/ClaudeAdapter.js +404 -84
- package/dist/adapters/claude/ClaudeAdapter.js.map +1 -1
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.d.ts +17 -0
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.d.ts.map +1 -0
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.js +79 -0
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.js.map +1 -0
- package/dist/adapters/claude/ClaudeStreamParser.d.ts +19 -0
- package/dist/adapters/claude/ClaudeStreamParser.d.ts.map +1 -1
- package/dist/adapters/claude/ClaudeStreamParser.js +92 -0
- package/dist/adapters/claude/ClaudeStreamParser.js.map +1 -1
- package/dist/adapters/claude/claudeCommand.js +1 -1
- package/dist/adapters/claude/claudeCommand.js.map +1 -1
- package/dist/adapters/codex/CodexAdapter.d.ts +20 -0
- package/dist/adapters/codex/CodexAdapter.d.ts.map +1 -0
- package/dist/adapters/codex/CodexAdapter.js +313 -0
- package/dist/adapters/codex/CodexAdapter.js.map +1 -0
- package/dist/adapters/hermes/HermesAdapter.d.ts +10 -1
- package/dist/adapters/hermes/HermesAdapter.d.ts.map +1 -1
- package/dist/adapters/hermes/HermesAdapter.js +225 -21
- package/dist/adapters/hermes/HermesAdapter.js.map +1 -1
- package/dist/adapters/hermes/HermesInvocationUsageReader.d.ts +26 -0
- package/dist/adapters/hermes/HermesInvocationUsageReader.d.ts.map +1 -0
- package/dist/adapters/hermes/HermesInvocationUsageReader.js +128 -0
- package/dist/adapters/hermes/HermesInvocationUsageReader.js.map +1 -0
- package/dist/adapters/openclaw/OpenClawAdapter.d.ts +12 -37
- package/dist/adapters/openclaw/OpenClawAdapter.d.ts.map +1 -1
- package/dist/adapters/openclaw/OpenClawAdapter.js +191 -635
- package/dist/adapters/openclaw/OpenClawAdapter.js.map +1 -1
- package/dist/core/AgentAdapter.d.ts +75 -1
- package/dist/core/AgentAdapter.d.ts.map +1 -1
- package/dist/core/AgentAdapter.js +25 -0
- package/dist/core/AgentAdapter.js.map +1 -1
- package/dist/core/RuntimeDetector.d.ts.map +1 -1
- package/dist/core/RuntimeDetector.js +52 -19
- package/dist/core/RuntimeDetector.js.map +1 -1
- package/dist/core/WinMatrixAgentClient.d.ts +30 -4
- package/dist/core/WinMatrixAgentClient.d.ts.map +1 -1
- package/dist/core/WinMatrixAgentClient.js +122 -12
- package/dist/core/WinMatrixAgentClient.js.map +1 -1
- package/dist/index.d.ts +15 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -19
- package/dist/index.js.map +1 -1
- package/dist/observability/WorkstationTokenUsageRecorder.d.ts +54 -0
- package/dist/observability/WorkstationTokenUsageRecorder.d.ts.map +1 -0
- package/dist/observability/WorkstationTokenUsageRecorder.js +264 -0
- package/dist/observability/WorkstationTokenUsageRecorder.js.map +1 -0
- package/dist/utils/envSanitizer.d.ts +38 -0
- package/dist/utils/envSanitizer.d.ts.map +1 -0
- package/dist/utils/envSanitizer.js +94 -0
- package/dist/utils/envSanitizer.js.map +1 -0
- package/dist/utils/errorClassification.d.ts +26 -0
- package/dist/utils/errorClassification.d.ts.map +1 -0
- package/dist/utils/errorClassification.js +52 -0
- package/dist/utils/errorClassification.js.map +1 -0
- package/dist/utils/processUtils.d.ts +10 -0
- package/dist/utils/processUtils.d.ts.map +1 -1
- package/dist/utils/processUtils.js +42 -9
- package/dist/utils/processUtils.js.map +1 -1
- package/dist/utils/resumeFallback.d.ts +4 -0
- package/dist/utils/resumeFallback.d.ts.map +1 -0
- package/dist/utils/resumeFallback.js +65 -0
- package/dist/utils/resumeFallback.js.map +1 -0
- package/package.json +8 -4
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.d.ts +0 -6
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.d.ts.map +0 -1
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.js +0 -24
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.js.map +0 -1
- package/dist/runtime/DualRuntimeBridge.d.ts +0 -38
- package/dist/runtime/DualRuntimeBridge.d.ts.map +0 -1
- package/dist/runtime/DualRuntimeBridge.js +0 -321
- package/dist/runtime/DualRuntimeBridge.js.map +0 -1
- package/dist/runtime/bridgeConfigLoader.d.ts +0 -14
- package/dist/runtime/bridgeConfigLoader.d.ts.map +0 -1
- package/dist/runtime/bridgeConfigLoader.js +0 -133
- package/dist/runtime/bridgeConfigLoader.js.map +0 -1
- package/dist/runtime/bridgeConfigTypes.d.ts +0 -26
- package/dist/runtime/bridgeConfigTypes.d.ts.map +0 -1
- package/dist/runtime/bridgeConfigTypes.js +0 -6
- package/dist/runtime/bridgeConfigTypes.js.map +0 -1
- package/dist/runtime/bridgeHome.d.ts +0 -19
- package/dist/runtime/bridgeHome.d.ts.map +0 -1
- package/dist/runtime/bridgeHome.js +0 -61
- package/dist/runtime/bridgeHome.js.map +0 -1
- package/dist/runtime/bridgeReconnectPolicy.d.ts +0 -8
- package/dist/runtime/bridgeReconnectPolicy.d.ts.map +0 -1
- package/dist/runtime/bridgeReconnectPolicy.js +0 -16
- package/dist/runtime/bridgeReconnectPolicy.js.map +0 -1
- package/dist/runtime/bridgeTaskJournal.d.ts +0 -7
- package/dist/runtime/bridgeTaskJournal.d.ts.map +0 -1
- package/dist/runtime/bridgeTaskJournal.js +0 -66
- package/dist/runtime/bridgeTaskJournal.js.map +0 -1
- package/dist/runtime/localRuntimeAvailabilityMonitor.d.ts +0 -22
- package/dist/runtime/localRuntimeAvailabilityMonitor.d.ts.map +0 -1
- package/dist/runtime/localRuntimeAvailabilityMonitor.js +0 -81
- package/dist/runtime/localRuntimeAvailabilityMonitor.js.map +0 -1
- package/dist/runtime/runDualRuntimeBridge.d.ts +0 -2
- package/dist/runtime/runDualRuntimeBridge.d.ts.map +0 -1
- package/dist/runtime/runDualRuntimeBridge.js +0 -32
- package/dist/runtime/runDualRuntimeBridge.js.map +0 -1
|
@@ -1,246 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenClawAdapter —
|
|
2
|
+
* OpenClawAdapter — spawn `openclaw agent --json` CLI,通过 Gateway 执行 agent turn。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 从 WS-RPC gateway 模式改为 CLI spawn 模式(对齐 CodexAdapter/HermesAdapter):
|
|
5
|
+
* - 绕过 gateway protocol mismatch + operator scope 问题(CLI 内部处理 auth)
|
|
6
|
+
* - `--json` 结构化输出(精确解析,不猜正则)
|
|
7
|
+
* - 非流式(一次性 JSON,等进程退出后 yield result)
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
* 无需 device identity 签名。
|
|
9
|
+
* CLI 命令:`openclaw agent --json --agent <id> [--session-id <sid>] -m <prompt>`
|
|
9
10
|
*/
|
|
10
|
-
import {
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
11
12
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
12
13
|
import { homedir } from 'node:os';
|
|
13
14
|
import { join, dirname } from 'node:path';
|
|
14
|
-
import WebSocket from 'ws';
|
|
15
15
|
import { parseSkillMd } from '../../utils/parseSkillMd.js';
|
|
16
|
+
import { waitForExit, killProcess } from '../../utils/processUtils.js';
|
|
17
|
+
import { sanitizeTaskEnv } from '../../utils/envSanitizer.js';
|
|
18
|
+
import { executeWithResumeFallback } from '../../utils/resumeFallback.js';
|
|
19
|
+
import { classifyAdapterError } from '../../utils/errorClassification.js';
|
|
16
20
|
// ── constants ──
|
|
17
|
-
const PROTOCOL_VERSION = 3;
|
|
18
21
|
const OPENCLAW_DIR = join(homedir(), '.openclaw');
|
|
19
22
|
const OPENCLAW_CONFIG = join(OPENCLAW_DIR, 'openclaw.json');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
function
|
|
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() {
|
|
23
|
+
// ── helpers ──
|
|
24
|
+
// ── helpers ──
|
|
25
|
+
/** 解析 openclaw agent --json 的完整输出,提取 answer/sessionId/usage */
|
|
26
|
+
function parseOpenClawCliResult(rawJson) {
|
|
192
27
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
28
|
+
const parsed = JSON.parse(rawJson);
|
|
29
|
+
const result = parsed.result;
|
|
30
|
+
// answer text: result.payloads[*].text join
|
|
31
|
+
let text = '';
|
|
32
|
+
if (result?.payloads && Array.isArray(result.payloads)) {
|
|
33
|
+
const texts = result.payloads
|
|
34
|
+
.map((p) => p.text ?? '')
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
if (texts.length > 0)
|
|
37
|
+
text = texts.join('\n');
|
|
38
|
+
}
|
|
39
|
+
// sessionId: result.meta.agentMeta.sessionId
|
|
40
|
+
const meta = result?.meta;
|
|
41
|
+
const agentMeta = meta?.agentMeta;
|
|
42
|
+
const sessionId = typeof agentMeta?.sessionId === 'string' ? agentMeta.sessionId : undefined;
|
|
43
|
+
// usage: result.meta.agentMeta.usage
|
|
44
|
+
const rawUsage = agentMeta?.usage;
|
|
45
|
+
const usage = rawUsage && typeof rawUsage.input === 'number' && typeof rawUsage.output === 'number'
|
|
46
|
+
? { input: rawUsage.input, output: rawUsage.output }
|
|
47
|
+
: undefined;
|
|
48
|
+
return { text, sessionId, usage };
|
|
198
49
|
}
|
|
199
50
|
catch {
|
|
200
|
-
return
|
|
51
|
+
return { text: rawJson.trim() };
|
|
201
52
|
}
|
|
202
53
|
}
|
|
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
54
|
export class OpenClawAdapter {
|
|
213
55
|
meta = {
|
|
214
56
|
agentType: 'openclaw',
|
|
215
57
|
runtime: 'openclaw',
|
|
216
58
|
capabilities: {
|
|
217
|
-
|
|
59
|
+
// 非流式(一次性 JSON);CLI 无 --stream-json
|
|
60
|
+
streaming: false,
|
|
218
61
|
toolCall: true,
|
|
219
|
-
session:
|
|
220
|
-
|
|
62
|
+
session: false,
|
|
63
|
+
// SIGTERM killProcess 可取消
|
|
64
|
+
cancellation: true,
|
|
65
|
+
supportsCancel: true,
|
|
66
|
+
supportsResume: true,
|
|
221
67
|
},
|
|
222
68
|
tools: [],
|
|
223
69
|
skills: [],
|
|
224
70
|
toolCategories: {},
|
|
225
71
|
};
|
|
226
|
-
|
|
227
|
-
gatewayPort;
|
|
228
|
-
token;
|
|
72
|
+
openclawPath;
|
|
229
73
|
mcpBridgeUrl;
|
|
230
74
|
mcpApiKey;
|
|
231
75
|
agentId;
|
|
232
76
|
constructor(config) {
|
|
233
77
|
const opts = config;
|
|
234
|
-
this.
|
|
235
|
-
this.gatewayPort = opts?.gatewayPort ?? 18789;
|
|
236
|
-
this.token = opts?.token ?? readGatewayTokenFromConfig();
|
|
78
|
+
this.openclawPath = opts?.openclawPath ?? 'openclaw';
|
|
237
79
|
this.mcpBridgeUrl = opts?.mcpBridgeUrl;
|
|
238
80
|
this.mcpApiKey = opts?.mcpApiKey;
|
|
239
81
|
this.agentId = opts?.agentId;
|
|
240
82
|
}
|
|
241
|
-
get gatewayUrl() {
|
|
242
|
-
return `ws://${this.gatewayHost}:${this.gatewayPort}`;
|
|
243
|
-
}
|
|
244
83
|
// ── MCP config helpers ──
|
|
245
84
|
/**
|
|
246
85
|
* Write MCP server config to openclaw.json, merge with existing keys.
|
|
@@ -281,230 +120,121 @@ export class OpenClawAdapter {
|
|
|
281
120
|
}
|
|
282
121
|
// ── execute ──
|
|
283
122
|
async *execute(task, signal) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
123
|
+
yield* executeWithResumeFallback(task, signal, (nextTask, nextSignal) => this.executeOnce(nextTask, nextSignal));
|
|
124
|
+
}
|
|
125
|
+
async *executeOnce(task, signal) {
|
|
126
|
+
const workDir = task.workDir;
|
|
127
|
+
if (workDir && !existsSync(workDir)) {
|
|
128
|
+
yield {
|
|
129
|
+
type: 'error',
|
|
130
|
+
message: `Work directory does not exist: ${workDir}`,
|
|
131
|
+
code: 'cli_crash',
|
|
132
|
+
retryable: false,
|
|
133
|
+
};
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// MCP 注入:Gateway 从 openclaw.json 读 mcp.servers.winmatrix
|
|
289
137
|
this.writeMCPConfig(task.mcpToken);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
138
|
+
// 构造 CLI args: openclaw agent --json --agent main [--session-id <sid>] -m <prompt>
|
|
139
|
+
// 注意:openclaw CLI 的 --agent 要的是 openclaw 内部 agent id(如 'main'),
|
|
140
|
+
// 不是 WinMatrix 的外联 Agent id(ea_xxx)。WinMatrix agentId 仅用于 sessionKey 编码/MCP,
|
|
141
|
+
// openclaw agent id 固定用 'main'(用户可在 ~/.openclaw/agents/ 配置多个,默认 main)。
|
|
142
|
+
const openclawAgentId = 'main';
|
|
143
|
+
const prompt = task.systemPrompt
|
|
144
|
+
? `${task.systemPrompt}\n\n---\n\n${task.input}`
|
|
145
|
+
: task.input;
|
|
146
|
+
const args = ['agent', '--json', '--agent', openclawAgentId, '-m', prompt];
|
|
147
|
+
// resume: --session-id <sessionId>
|
|
148
|
+
if (task.resumeSession && task.sessionId) {
|
|
149
|
+
args.push('--session-id', task.sessionId);
|
|
150
|
+
}
|
|
151
|
+
// model override(临时复用 task.hermes?.model;待 OpenClawTaskOptions 加独立 model 字段后改读 task.openclaw?.model)
|
|
152
|
+
if (task.hermes?.model) {
|
|
153
|
+
args.push('--model', task.hermes.model);
|
|
154
|
+
}
|
|
155
|
+
// Spawn
|
|
156
|
+
let proc;
|
|
294
157
|
try {
|
|
295
|
-
|
|
158
|
+
// 先剥凭据后显式加(harden-external-computer-daemon P1/D3);
|
|
159
|
+
// 旧实现未传 env → 子进程默认继承 daemon 全部环境变量,属同一泄漏面
|
|
160
|
+
const { env, strippedKeys } = sanitizeTaskEnv(task.env);
|
|
161
|
+
if (strippedKeys.length > 0) {
|
|
162
|
+
console.warn(`[OpenClawAdapter] 已剥离 ${strippedKeys.length} 个凭据形态环境变量(仅列 key 名): ${strippedKeys.join(', ')}`);
|
|
163
|
+
}
|
|
164
|
+
proc = spawn(this.openclawPath, args, {
|
|
165
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
166
|
+
env,
|
|
167
|
+
cwd: workDir ?? process.cwd(),
|
|
168
|
+
// POSIX 下独立进程组,killProcess 才能 kill(-pid) 杀整棵树
|
|
169
|
+
detached: process.platform !== 'win32',
|
|
170
|
+
});
|
|
296
171
|
}
|
|
297
172
|
catch (err) {
|
|
298
|
-
yield {
|
|
173
|
+
yield {
|
|
174
|
+
type: 'error',
|
|
175
|
+
message: `Failed to spawn openclaw: ${err instanceof Error ? err.message : String(err)}`,
|
|
176
|
+
code: 'cli_crash',
|
|
177
|
+
retryable: false,
|
|
178
|
+
};
|
|
299
179
|
return;
|
|
300
180
|
}
|
|
301
|
-
|
|
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);
|
|
181
|
+
let completed = false;
|
|
359
182
|
const onAbort = () => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
ws.send(JSON.stringify({ type: 'req', id: `abort-${Date.now()}`, method: 'chat.abort', params: { sessionKey: sessionId } }));
|
|
363
|
-
}
|
|
364
|
-
catch { /* ignore */ }
|
|
365
|
-
ws.close();
|
|
183
|
+
if (!completed)
|
|
184
|
+
killProcess(proc);
|
|
366
185
|
};
|
|
367
186
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
368
187
|
try {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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()}`,
|
|
188
|
+
// 累积 stdout(整块 JSON,非流式)
|
|
189
|
+
let stdoutBuffer = '';
|
|
190
|
+
let stderrBuffer = '';
|
|
191
|
+
proc.stderr?.on('data', (chunk) => {
|
|
192
|
+
stderrBuffer += chunk.toString('utf-8');
|
|
379
193
|
});
|
|
380
|
-
|
|
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) {
|
|
194
|
+
for await (const chunk of proc.stdout) {
|
|
394
195
|
if (signal?.aborted)
|
|
395
196
|
break;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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;
|
|
197
|
+
stdoutBuffer += typeof chunk === 'string' ? chunk : chunk.toString('utf-8');
|
|
198
|
+
}
|
|
199
|
+
const exitResult = await waitForExit(proc);
|
|
200
|
+
completed = true;
|
|
201
|
+
if (exitResult.code === 0 && stdoutBuffer.trim()) {
|
|
202
|
+
const { text, sessionId, usage } = parseOpenClawCliResult(stdoutBuffer);
|
|
203
|
+
if (text) {
|
|
204
|
+
yield {
|
|
205
|
+
type: 'result',
|
|
206
|
+
content: text,
|
|
207
|
+
...(sessionId ? { sessionId } : {}),
|
|
208
|
+
...(usage ? { usage } : {}),
|
|
209
|
+
};
|
|
486
210
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
211
|
+
else {
|
|
212
|
+
// JSON 解析成功但 text 为空
|
|
213
|
+
yield {
|
|
214
|
+
type: 'result',
|
|
215
|
+
content: stdoutBuffer.trim().slice(0, 500),
|
|
216
|
+
};
|
|
492
217
|
}
|
|
493
218
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
219
|
+
else {
|
|
220
|
+
// exit≠0:取 stderr 最后非空行(真实错误通常在末尾,前面可能混 plugin warning 噪声)
|
|
221
|
+
const stderrLines = stderrBuffer.trim().split('\n').filter((l) => l.trim());
|
|
222
|
+
const errMsg = stderrLines[stderrLines.length - 1] || exitResult.error || `openclaw exited with code ${exitResult.code}`;
|
|
223
|
+
const classified = classifyAdapterError({
|
|
224
|
+
message: errMsg,
|
|
225
|
+
exitCode: exitResult.code,
|
|
226
|
+
aborted: signal?.aborted,
|
|
227
|
+
});
|
|
228
|
+
yield { type: 'error', message: `OpenClaw agent failed: ${errMsg}`, code: classified.code, retryable: classified.retryable };
|
|
229
|
+
}
|
|
499
230
|
}
|
|
500
231
|
catch (err) {
|
|
501
|
-
|
|
232
|
+
const errMsg = `OpenClaw agent error: ${err instanceof Error ? err.message : String(err)}`;
|
|
233
|
+
const classified = classifyAdapterError({ message: errMsg, aborted: signal?.aborted });
|
|
234
|
+
yield { type: 'error', message: errMsg, code: classified.code, retryable: classified.retryable };
|
|
502
235
|
}
|
|
503
236
|
finally {
|
|
504
237
|
signal?.removeEventListener('abort', onAbort);
|
|
505
|
-
ws.off('message', persistHandler);
|
|
506
|
-
ws.off('close', onClose);
|
|
507
|
-
ws.close();
|
|
508
238
|
}
|
|
509
239
|
}
|
|
510
240
|
// ── discoverSkills ──
|
|
@@ -553,246 +283,72 @@ export class OpenClawAdapter {
|
|
|
553
283
|
catch { /* silently skip */ }
|
|
554
284
|
return skills;
|
|
555
285
|
}
|
|
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
286
|
// ── healthCheck ──
|
|
692
287
|
async healthCheck() {
|
|
693
288
|
try {
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
289
|
+
// 用 openclaw --version 验 CLI 可执行(轻量,不连 gateway)
|
|
290
|
+
const { spawnSync } = await import('node:child_process');
|
|
291
|
+
const result = spawnSync(this.openclawPath, ['--version'], {
|
|
292
|
+
timeout: 5_000,
|
|
293
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
294
|
+
encoding: 'utf-8',
|
|
295
|
+
});
|
|
296
|
+
if (result.status === 0) {
|
|
297
|
+
const version = result.stdout.trim().split('\n')[0].slice(0, 100);
|
|
699
298
|
return { available: true, version };
|
|
700
299
|
}
|
|
701
|
-
return { available:
|
|
300
|
+
return { available: false, error: `openclaw --version exited with code ${result.status}` };
|
|
702
301
|
}
|
|
703
302
|
catch (err) {
|
|
704
|
-
return {
|
|
303
|
+
return {
|
|
304
|
+
available: false,
|
|
305
|
+
error: err instanceof Error ? err.message : String(err),
|
|
306
|
+
};
|
|
705
307
|
}
|
|
706
308
|
}
|
|
707
|
-
// ──
|
|
309
|
+
// ── session management ──
|
|
708
310
|
async listSessions(options) {
|
|
311
|
+
// CLI: openclaw sessions --json --agent <id> --limit <n>
|
|
709
312
|
try {
|
|
710
|
-
const
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
313
|
+
const { spawnSync } = await import('node:child_process');
|
|
314
|
+
const agentId = this.agentId ?? 'main';
|
|
315
|
+
const limit = options.limit ?? 20;
|
|
316
|
+
const result = spawnSync(this.openclawPath, ['sessions', '--json', '--agent', agentId, '--limit', String(limit)], {
|
|
317
|
+
timeout: 10_000,
|
|
318
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
319
|
+
encoding: 'utf-8',
|
|
320
|
+
cwd: options.dir ?? process.cwd(),
|
|
714
321
|
});
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
322
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
323
|
+
const parsed = JSON.parse(result.stdout);
|
|
324
|
+
const sessions = (parsed.sessions ?? []).map((s) => ({
|
|
325
|
+
id: s.sessionId ?? s.id ?? '',
|
|
326
|
+
customTitle: s.title,
|
|
327
|
+
createdAt: s.createdAt ?? '',
|
|
328
|
+
lastActivityAt: s.lastActivityAt ?? '',
|
|
329
|
+
messageCount: s.messageCount,
|
|
330
|
+
}));
|
|
331
|
+
return { sessions };
|
|
718
332
|
}
|
|
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
333
|
}
|
|
334
|
+
catch { /* fall through to empty */ }
|
|
335
|
+
return { sessions: [] };
|
|
735
336
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
}
|
|
337
|
+
async getSession(sessionId, options) {
|
|
338
|
+
// CLI 无直接 get session 命令;返回 stub(daemon AgentProcessManager 有 fallback)
|
|
339
|
+
return {
|
|
340
|
+
session: {
|
|
341
|
+
id: sessionId,
|
|
342
|
+
customTitle: 'N/A',
|
|
343
|
+
createdAt: '',
|
|
344
|
+
lastActivityAt: '',
|
|
345
|
+
},
|
|
346
|
+
messageCount: 0,
|
|
347
|
+
truncated: false,
|
|
348
|
+
userPrompts: [],
|
|
349
|
+
assistantText: '',
|
|
350
|
+
lastAssistantText: '',
|
|
351
|
+
};
|
|
796
352
|
}
|
|
797
353
|
}
|
|
798
354
|
//# sourceMappingURL=OpenClawAdapter.js.map
|