@winmatrix/supervisor 1.0.5 → 1.0.7
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/package.json +1 -5
- package/src/claude-session-lib.mjs +84 -0
- package/src/engine-run-success.mjs +118 -0
- package/src/env-sanitizer.mjs +4 -0
- package/src/openclaw-device-store.mjs +208 -0
- package/src/openclaw-pair-workstation.mjs +190 -0
- package/src/run-agent-engine.mjs +318 -77
- package/src/run-engine.mjs +139 -7
package/src/run-engine.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* --engine <claude-code|codex|hermes|openclaw> \
|
|
7
7
|
* [--model <model>] [--permission-mode <mode>] \
|
|
8
8
|
* [--allowed-tools <tool1,tool2>] [--resume <sessionId>|--fork <sessionId>] \
|
|
9
|
+
* [--context-budget <json>] \
|
|
9
10
|
* [--timeout <seconds>] [--work-dir <dir>] \
|
|
10
11
|
* <prompt>
|
|
11
12
|
*
|
|
@@ -13,11 +14,76 @@
|
|
|
13
14
|
* 输出到 stdout,供 run-agent-engine.mjs 解析并写入 events.jsonl。
|
|
14
15
|
*/
|
|
15
16
|
import { createRequire } from 'node:module';
|
|
17
|
+
import { isSdkFailureSubtype } from './engine-run-success.mjs';
|
|
18
|
+
import { createWorkstationOpenClawAdapterConfig } from './openclaw-device-store.mjs';
|
|
16
19
|
|
|
17
20
|
const require = createRequire(import.meta.url);
|
|
18
21
|
|
|
19
22
|
const SDK_PATH = process.env.WINMATRIX_AGENT_SDK_PATH
|
|
20
23
|
|| '/home/node/.local/lib/node_modules/@winmatrix/agent-sdk';
|
|
24
|
+
const MAX_PRIVATE_BINDINGS_BYTES = 64 * 1024;
|
|
25
|
+
const MCP_DYNAMIC_SECRET_KEYS = new Set([
|
|
26
|
+
'WINMATRIX_TOOL_PROXY_URL',
|
|
27
|
+
'WINMATRIX_TOOL_PROXY_TOKEN',
|
|
28
|
+
'WINMATRIX_TOOL_PROXY_PROJECT_ID',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function normalizeEventText(value) {
|
|
32
|
+
if (typeof value === 'string') return value;
|
|
33
|
+
if (value === null || value === undefined) return '';
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return value.map(normalizeEventText).filter(Boolean).join('\n');
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === 'object') {
|
|
38
|
+
if (typeof value.text === 'string') return value.text;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.stringify(value);
|
|
41
|
+
} catch {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return String(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readMcpBinding() {
|
|
49
|
+
const chunks = [];
|
|
50
|
+
let size = 0;
|
|
51
|
+
for await (const chunk of process.stdin) {
|
|
52
|
+
size += chunk.length;
|
|
53
|
+
if (size > MAX_PRIVATE_BINDINGS_BYTES) {
|
|
54
|
+
return { error: 'MCP_BINDING_REQUIRED: 私有控制绑定超过允许大小。' };
|
|
55
|
+
}
|
|
56
|
+
chunks.push(chunk);
|
|
57
|
+
}
|
|
58
|
+
if (chunks.length === 0) return {};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const raw = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
62
|
+
if (!raw || raw.hostKind !== 'workstation') {
|
|
63
|
+
return { error: 'MCP_BINDING_REQUIRED: 私有控制绑定 hostKind 无效。' };
|
|
64
|
+
}
|
|
65
|
+
const dynamicSecrets = raw.dynamicSecrets;
|
|
66
|
+
if (dynamicSecrets === undefined) {
|
|
67
|
+
return { error: 'MCP_BINDING_REQUIRED: 工作站缺少 MCP 绑定。' };
|
|
68
|
+
}
|
|
69
|
+
if (!dynamicSecrets || typeof dynamicSecrets !== 'object' || Array.isArray(dynamicSecrets)) {
|
|
70
|
+
return { error: 'MCP_BINDING_REQUIRED: MCP 绑定格式无效。' };
|
|
71
|
+
}
|
|
72
|
+
for (const [key, value] of Object.entries(dynamicSecrets)) {
|
|
73
|
+
if (!MCP_DYNAMIC_SECRET_KEYS.has(key) || typeof value !== 'string') {
|
|
74
|
+
return { error: 'MCP_BINDING_REQUIRED: MCP 绑定包含不允许字段。' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const url = dynamicSecrets.WINMATRIX_TOOL_PROXY_URL?.trim();
|
|
78
|
+
const token = dynamicSecrets.WINMATRIX_TOOL_PROXY_TOKEN?.trim();
|
|
79
|
+
if (!url || !token) {
|
|
80
|
+
return { error: 'MCP_BINDING_REQUIRED: MCP 绑定不完整。' };
|
|
81
|
+
}
|
|
82
|
+
return { binding: { url, token } };
|
|
83
|
+
} catch {
|
|
84
|
+
return { error: 'MCP_BINDING_REQUIRED: 私有控制绑定不是有效 JSON。' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
21
87
|
|
|
22
88
|
function parseArgs(argv) {
|
|
23
89
|
const options = {
|
|
@@ -29,6 +95,7 @@ function parseArgs(argv) {
|
|
|
29
95
|
fork: undefined,
|
|
30
96
|
timeoutSec: undefined,
|
|
31
97
|
workDir: undefined,
|
|
98
|
+
contextBudget: undefined,
|
|
32
99
|
};
|
|
33
100
|
const positionals = [];
|
|
34
101
|
|
|
@@ -50,6 +117,18 @@ function parseArgs(argv) {
|
|
|
50
117
|
options.timeoutSec = argv[++i];
|
|
51
118
|
} else if (arg === '--work-dir') {
|
|
52
119
|
options.workDir = argv[++i];
|
|
120
|
+
} else if (arg === '--context-budget') {
|
|
121
|
+
const raw = argv[++i];
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(raw);
|
|
124
|
+
if (parsed && typeof parsed === 'object') {
|
|
125
|
+
options.contextBudget = parsed;
|
|
126
|
+
} else {
|
|
127
|
+
console.error('[run-engine] --context-budget 不是有效 JSON 对象, 已忽略');
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
console.error('[run-engine] --context-budget JSON 解析失败, 已忽略');
|
|
131
|
+
}
|
|
53
132
|
} else if (arg.startsWith('--')) {
|
|
54
133
|
// 忽略未来可能透传的 engine-specific 选项;后续可按 engineId 路由
|
|
55
134
|
console.error(`[run-engine] 忽略未知选项: ${arg}`);
|
|
@@ -78,6 +157,13 @@ async function main() {
|
|
|
78
157
|
process.exit(1);
|
|
79
158
|
}
|
|
80
159
|
|
|
160
|
+
const mcpBinding = await readMcpBinding();
|
|
161
|
+
if (mcpBinding.error) {
|
|
162
|
+
writeEvent({ type: 'error', error: mcpBinding.error, code: 'MCP_CONNECTION_FAILED' });
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
81
167
|
let sdk;
|
|
82
168
|
try {
|
|
83
169
|
sdk = require(SDK_PATH);
|
|
@@ -92,8 +178,19 @@ async function main() {
|
|
|
92
178
|
CodexAdapter,
|
|
93
179
|
HermesAdapter,
|
|
94
180
|
OpenClawAdapter,
|
|
181
|
+
validateMcpBridgeUrl,
|
|
95
182
|
} = sdk;
|
|
96
183
|
|
|
184
|
+
if (mcpBinding.binding) {
|
|
185
|
+
try {
|
|
186
|
+
validateMcpBridgeUrl(mcpBinding.binding.url);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
writeEvent({ type: 'error', error: `MCP_BINDING_REQUIRED: MCP URL 无效: ${err.message}`, code: 'MCP_CONNECTION_FAILED' });
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
97
194
|
const registry = new AdapterRegistry();
|
|
98
195
|
registry.register('claude-code', ClaudeAdapter);
|
|
99
196
|
registry.register('codex', CodexAdapter);
|
|
@@ -105,7 +202,12 @@ async function main() {
|
|
|
105
202
|
process.exit(1);
|
|
106
203
|
}
|
|
107
204
|
|
|
108
|
-
const adapterConfig =
|
|
205
|
+
const adapterConfig = options.engineId === 'openclaw'
|
|
206
|
+
? {
|
|
207
|
+
...createWorkstationOpenClawAdapterConfig(),
|
|
208
|
+
...(mcpBinding.binding ? { mcpBridgeUrl: mcpBinding.binding.url } : {}),
|
|
209
|
+
}
|
|
210
|
+
: (mcpBinding.binding ? { mcpBridgeUrl: mcpBinding.binding.url } : {});
|
|
109
211
|
const adapter = registry.create(options.engineId, adapterConfig);
|
|
110
212
|
|
|
111
213
|
/** @type {import('@winmatrix/agent-sdk').AgentTaskContext} */
|
|
@@ -116,9 +218,13 @@ async function main() {
|
|
|
116
218
|
engineId: options.engineId,
|
|
117
219
|
workDir: options.workDir,
|
|
118
220
|
};
|
|
221
|
+
if (mcpBinding.binding) task.mcpToken = mcpBinding.binding.token;
|
|
222
|
+
if (options.contextBudget) task.contextBudget = options.contextBudget;
|
|
119
223
|
|
|
120
224
|
if (options.engineId === 'claude-code') {
|
|
121
|
-
|
|
225
|
+
// Coding workstation 使用 Agent SDK 路径:任务 MCP 仅随 query() 显式传入,
|
|
226
|
+
// 不落盘到 artifact,也不改写 Pod 注入的持久 agent-home。
|
|
227
|
+
task.claude = { useAgentSdk: true };
|
|
122
228
|
if (options.permissionMode) task.claude.permissionMode = options.permissionMode;
|
|
123
229
|
if (options.allowedTools) task.claude.tools = options.allowedTools.split(',').map((s) => s.trim());
|
|
124
230
|
} else if (options.engineId === 'hermes') {
|
|
@@ -150,28 +256,53 @@ async function main() {
|
|
|
150
256
|
}
|
|
151
257
|
|
|
152
258
|
try {
|
|
259
|
+
const startedAt = Date.now();
|
|
260
|
+
// 链路日志走 stderr:stdout 是 JSONL 协议通道
|
|
261
|
+
console.error(`[run-engine] pid=${process.pid} adapter.execute 开始: engineId=${options.engineId} workDir=${options.workDir ?? '(cwd)'} resume=${options.resume ?? 'none'} fork=${options.fork ?? 'none'} timeoutSec=${options.timeoutSec ?? 'none'}`);
|
|
262
|
+
let eventCount = 0;
|
|
153
263
|
for await (const event of adapter.execute(task, abortController?.signal)) {
|
|
264
|
+
eventCount += 1;
|
|
154
265
|
switch (event.type) {
|
|
155
266
|
case 'delta':
|
|
156
|
-
|
|
267
|
+
if (event.engineEvent) {
|
|
268
|
+
writeEvent(event.engineEvent);
|
|
269
|
+
} else {
|
|
270
|
+
writeEvent({ type: 'content_delta', text: normalizeEventText(event.content) });
|
|
271
|
+
}
|
|
157
272
|
break;
|
|
158
273
|
case 'thinking':
|
|
159
|
-
|
|
274
|
+
if (event.engineEvent) {
|
|
275
|
+
writeEvent(event.engineEvent);
|
|
276
|
+
} else {
|
|
277
|
+
writeEvent({ type: 'thinking_delta', text: normalizeEventText(event.content) });
|
|
278
|
+
}
|
|
160
279
|
break;
|
|
161
280
|
case 'tool_call':
|
|
162
281
|
writeEvent({ type: 'tool_call', name: event.name, input: event.args });
|
|
163
282
|
break;
|
|
164
|
-
case 'result':
|
|
283
|
+
case 'result': {
|
|
284
|
+
const isError = event.isError === true;
|
|
285
|
+
const subtype = typeof event.sdkSubtype === 'string' ? event.sdkSubtype : undefined;
|
|
286
|
+
const sdkFailed = isError || isSdkFailureSubtype(subtype);
|
|
287
|
+
const text = normalizeEventText(event.content);
|
|
288
|
+
const error = sdkFailed
|
|
289
|
+
? (text.trim() || (subtype ? `SDK result subtype=${subtype}` : 'SDK result is_error=true'))
|
|
290
|
+
: undefined;
|
|
291
|
+
console.error(`[run-engine] pid=${process.pid} adapter.execute 结束: engineId=${options.engineId} events=${eventCount} durationMs=${Date.now() - startedAt} sessionId=${event.sessionId ?? 'none'} success=${!sdkFailed} subtype=${subtype ?? 'none'}`);
|
|
165
292
|
writeEvent({
|
|
166
293
|
type: 'result',
|
|
167
294
|
result: {
|
|
168
|
-
success:
|
|
169
|
-
text
|
|
295
|
+
success: !sdkFailed,
|
|
296
|
+
text,
|
|
170
297
|
...(event.sessionId ? { sessionId: event.sessionId } : {}),
|
|
171
298
|
...(event.usage ? { usage: event.usage } : {}),
|
|
299
|
+
...(isError ? { is_error: true } : {}),
|
|
300
|
+
...(subtype ? { subtype } : {}),
|
|
301
|
+
...(error ? { error } : {}),
|
|
172
302
|
},
|
|
173
303
|
});
|
|
174
304
|
break;
|
|
305
|
+
}
|
|
175
306
|
case 'error':
|
|
176
307
|
writeEvent({ type: 'error', error: event.message, code: event.code });
|
|
177
308
|
break;
|
|
@@ -182,6 +313,7 @@ async function main() {
|
|
|
182
313
|
}
|
|
183
314
|
process.exit(0);
|
|
184
315
|
} catch (e) {
|
|
316
|
+
console.error(`[run-engine] pid=${process.pid} adapter.execute 异常: engineId=${options.engineId} error=${e instanceof Error ? e.message : String(e)}`);
|
|
185
317
|
writeEvent({ type: 'error', error: e instanceof Error ? e.message : String(e) });
|
|
186
318
|
process.exit(1);
|
|
187
319
|
} finally {
|