@agentbean/daemon 0.1.35 → 0.2.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/apps/daemon-next/src/bin.d.ts +2 -0
- package/dist/apps/daemon-next/src/bin.js +6 -0
- package/dist/apps/daemon-next/src/cli.d.ts +26 -0
- package/dist/apps/daemon-next/src/cli.js +124 -0
- package/dist/apps/daemon-next/src/executor.d.ts +6 -0
- package/dist/apps/daemon-next/src/executor.js +51 -0
- package/dist/apps/daemon-next/src/index.d.ts +60 -0
- package/dist/apps/daemon-next/src/index.js +87 -0
- package/dist/apps/daemon-next/src/scanner.d.ts +15 -0
- package/dist/apps/daemon-next/src/scanner.js +94 -0
- package/dist/packages/contracts/src/agent.d.ts +69 -0
- package/dist/packages/contracts/src/agent.js +4 -0
- package/dist/packages/contracts/src/auth.d.ts +20 -0
- package/dist/packages/contracts/src/auth.js +1 -0
- package/dist/packages/contracts/src/channel.d.ts +58 -0
- package/dist/packages/contracts/src/channel.js +1 -0
- package/dist/packages/contracts/src/common.d.ts +17 -0
- package/dist/packages/contracts/src/common.js +27 -0
- package/dist/packages/contracts/src/device.d.ts +27 -0
- package/dist/packages/contracts/src/device.js +1 -0
- package/dist/packages/contracts/src/dispatch.d.ts +46 -0
- package/dist/packages/contracts/src/dispatch.js +1 -0
- package/dist/packages/contracts/src/index.d.ts +9 -0
- package/dist/packages/contracts/src/index.js +9 -0
- package/dist/packages/contracts/src/message.d.ts +20 -0
- package/dist/packages/contracts/src/message.js +1 -0
- package/dist/packages/contracts/src/socket.d.ts +74 -0
- package/dist/packages/contracts/src/socket.js +74 -0
- package/dist/packages/contracts/src/team.d.ts +13 -0
- package/dist/packages/contracts/src/team.js +1 -0
- package/package.json +14 -25
- package/README.md +0 -158
- package/dist/adapters/adapter.js +0 -9
- package/dist/adapters/claude-code.js +0 -83
- package/dist/adapters/codex.js +0 -280
- package/dist/adapters/factory.js +0 -38
- package/dist/adapters/hermes.js +0 -178
- package/dist/adapters/openclaw.js +0 -129
- package/dist/agent-instance.js +0 -181
- package/dist/auth-store.js +0 -44
- package/dist/bin.js +0 -6
- package/dist/config.js +0 -148
- package/dist/connection.js +0 -211
- package/dist/device-daemon.js +0 -530
- package/dist/index.js +0 -368
- package/dist/log.js +0 -7
- package/dist/post-process.js +0 -177
- package/dist/profile-paths.js +0 -39
- package/dist/sandbox.js +0 -53
- package/dist/scanner.js +0 -423
- package/dist/uploader.js +0 -46
- package/dist/workspace-manager.js +0 -196
- package/dist/workspace-sync.js +0 -69
package/dist/adapters/codex.js
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { spawn as spawnChild } from 'node:child_process';
|
|
2
|
-
import { spawn as spawnPty } from 'node-pty';
|
|
3
|
-
import { accessSync, constants, existsSync, mkdtempSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { homedir, tmpdir } from 'node:os';
|
|
5
|
-
import { delimiter, join } from 'node:path';
|
|
6
|
-
function renderPayload(input, systemPrompt) {
|
|
7
|
-
const parts = [];
|
|
8
|
-
if (systemPrompt)
|
|
9
|
-
parts.push(`# system\n${systemPrompt}`);
|
|
10
|
-
for (const turn of input.history) {
|
|
11
|
-
parts.push(`# ${turn.role}: ${turn.speaker}\n${turn.body}`);
|
|
12
|
-
}
|
|
13
|
-
parts.push(`# user\n${input.prompt}`);
|
|
14
|
-
return parts.join('\n\n');
|
|
15
|
-
}
|
|
16
|
-
function stripAnsi(s) {
|
|
17
|
-
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
18
|
-
}
|
|
19
|
-
function removeEchoedPayload(output, payload) {
|
|
20
|
-
if (!payload)
|
|
21
|
-
return output;
|
|
22
|
-
const normalizedPayload = payload.replace(/\r\n/g, '\n');
|
|
23
|
-
const idx = output.lastIndexOf(normalizedPayload);
|
|
24
|
-
if (idx < 0)
|
|
25
|
-
return output;
|
|
26
|
-
const boundaryBefore = idx === 0 || output[idx - 1] === '\n' || output[idx - 1] === '\r';
|
|
27
|
-
if (!boundaryBefore)
|
|
28
|
-
return output;
|
|
29
|
-
const tail = output.slice(idx + normalizedPayload.length).trim();
|
|
30
|
-
return tail || output;
|
|
31
|
-
}
|
|
32
|
-
export function extractCodexReply(output, payload) {
|
|
33
|
-
const clean = removeEchoedPayload(stripAnsi(output).replace(/\r\n/g, '\n'), payload);
|
|
34
|
-
// Match "codex" label followed by reply content, ending before next hook or end
|
|
35
|
-
const match = clean.match(/(?:^|\n)codex\n([\s\S]*?)(?:\nhook:|$)/i);
|
|
36
|
-
if (match)
|
|
37
|
-
return match[1].trim();
|
|
38
|
-
// Fallback: everything after last "user" prompt
|
|
39
|
-
const userIdx = clean.lastIndexOf('\nuser\n');
|
|
40
|
-
if (userIdx > 0) {
|
|
41
|
-
const after = clean.slice(userIdx).split('\n').slice(2).join('\n').trim();
|
|
42
|
-
if (after)
|
|
43
|
-
return after;
|
|
44
|
-
}
|
|
45
|
-
return clean.trim();
|
|
46
|
-
}
|
|
47
|
-
function hasFlag(args, ...flags) {
|
|
48
|
-
return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
49
|
-
}
|
|
50
|
-
function flagValue(args, ...flags) {
|
|
51
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
52
|
-
const arg = args[i];
|
|
53
|
-
for (const flag of flags) {
|
|
54
|
-
if (arg === flag)
|
|
55
|
-
return args[i + 1];
|
|
56
|
-
if (arg.startsWith(`${flag}=`))
|
|
57
|
-
return arg.slice(flag.length + 1);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
function createOutputLastMessagePath() {
|
|
63
|
-
return join(mkdtempSync(join(tmpdir(), 'agentbean-codex-')), 'last-message.txt');
|
|
64
|
-
}
|
|
65
|
-
function normalizeExecArgs(args, outputLastMessagePath) {
|
|
66
|
-
const baseArgs = args && args.length > 0 ? args : ['exec'];
|
|
67
|
-
const subcommand = baseArgs[0];
|
|
68
|
-
if (subcommand === 'exec' || subcommand === 'e') {
|
|
69
|
-
const rest = baseArgs.slice(1);
|
|
70
|
-
const normalized = [subcommand];
|
|
71
|
-
if (!hasFlag(rest, '--skip-git-repo-check')) {
|
|
72
|
-
normalized.push('--skip-git-repo-check');
|
|
73
|
-
}
|
|
74
|
-
const configuredOutputPath = flagValue(rest, '--output-last-message', '-o');
|
|
75
|
-
if (!configuredOutputPath) {
|
|
76
|
-
normalized.push('--output-last-message', outputLastMessagePath);
|
|
77
|
-
}
|
|
78
|
-
if (!hasFlag(rest, '--json', '--experimental-json')) {
|
|
79
|
-
normalized.push('--json');
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
args: [...normalized, ...rest],
|
|
83
|
-
outputLastMessagePath: configuredOutputPath ?? outputLastMessagePath,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
return { args: baseArgs };
|
|
87
|
-
}
|
|
88
|
-
function adapterTimeoutMs() {
|
|
89
|
-
const fromEnv = Number.parseInt(process.env.AGENTBEAN_CODEX_TIMEOUT_MS ?? '', 10);
|
|
90
|
-
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : 900_000;
|
|
91
|
-
}
|
|
92
|
-
function buildRuntimeEnv(extra) {
|
|
93
|
-
const pathEntries = [
|
|
94
|
-
process.env.PATH,
|
|
95
|
-
'/opt/homebrew/bin',
|
|
96
|
-
'/usr/local/bin',
|
|
97
|
-
join(homedir(), '.local/bin'),
|
|
98
|
-
join(homedir(), '.bun/bin'),
|
|
99
|
-
join(homedir(), '.npm-global/bin'),
|
|
100
|
-
join(homedir(), '.asdf/shims'),
|
|
101
|
-
join(homedir(), '.local/share/mise/shims'),
|
|
102
|
-
].filter(Boolean).join(':');
|
|
103
|
-
return { ...process.env, PATH: pathEntries, ...(extra ?? {}) };
|
|
104
|
-
}
|
|
105
|
-
function assertExecutable(command, env, label) {
|
|
106
|
-
const trimmed = command.trim();
|
|
107
|
-
if (!trimmed) {
|
|
108
|
-
throw new Error(`${label} command is empty`);
|
|
109
|
-
}
|
|
110
|
-
if (trimmed.includes('/')) {
|
|
111
|
-
try {
|
|
112
|
-
accessSync(trimmed, constants.X_OK);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
throw new Error(`${label} command is not executable: ${trimmed}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const pathEntries = (env.PATH ?? '').split(delimiter).filter(Boolean);
|
|
120
|
-
for (const dir of pathEntries) {
|
|
121
|
-
try {
|
|
122
|
-
accessSync(join(dir, trimmed), constants.X_OK);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
catch { }
|
|
126
|
-
}
|
|
127
|
-
throw new Error(`${label} command was not found on PATH: ${trimmed}. PATH=${env.PATH ?? ''}`);
|
|
128
|
-
}
|
|
129
|
-
function readOutputLastMessage(path) {
|
|
130
|
-
if (!path || !existsSync(path))
|
|
131
|
-
return null;
|
|
132
|
-
const text = readFileSync(path, 'utf8').trim();
|
|
133
|
-
return text || null;
|
|
134
|
-
}
|
|
135
|
-
function spawnRuntimeProcess(command, args, opts) {
|
|
136
|
-
try {
|
|
137
|
-
const pty = spawnPty(command, args, {
|
|
138
|
-
name: 'xterm-color',
|
|
139
|
-
cols: 80,
|
|
140
|
-
rows: 30,
|
|
141
|
-
cwd: opts.cwd,
|
|
142
|
-
env: opts.env,
|
|
143
|
-
});
|
|
144
|
-
return {
|
|
145
|
-
onData: (cb) => pty.onData(cb),
|
|
146
|
-
onExit: (cb) => pty.onExit(({ exitCode }) => cb({ exitCode })),
|
|
147
|
-
kill: (signal) => pty.kill(signal),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
const child = spawnChild(command, args, {
|
|
152
|
-
cwd: opts.cwd,
|
|
153
|
-
env: opts.env,
|
|
154
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
155
|
-
});
|
|
156
|
-
const dataHandlers = [];
|
|
157
|
-
const exitHandlers = [];
|
|
158
|
-
let exited = false;
|
|
159
|
-
const emitExit = (exitCode) => {
|
|
160
|
-
if (exited)
|
|
161
|
-
return;
|
|
162
|
-
exited = true;
|
|
163
|
-
for (const handler of exitHandlers)
|
|
164
|
-
handler({ exitCode });
|
|
165
|
-
};
|
|
166
|
-
child.stdout?.on('data', (data) => {
|
|
167
|
-
for (const handler of dataHandlers)
|
|
168
|
-
handler(String(data));
|
|
169
|
-
});
|
|
170
|
-
child.stderr?.on('data', (data) => {
|
|
171
|
-
for (const handler of dataHandlers)
|
|
172
|
-
handler(String(data));
|
|
173
|
-
});
|
|
174
|
-
child.on('error', (childErr) => {
|
|
175
|
-
const message = childErr instanceof Error ? childErr.message : String(childErr);
|
|
176
|
-
for (const handler of dataHandlers)
|
|
177
|
-
handler(message);
|
|
178
|
-
emitExit(1);
|
|
179
|
-
});
|
|
180
|
-
child.on('exit', (code) => emitExit(code ?? 1));
|
|
181
|
-
return {
|
|
182
|
-
onData: (cb) => { dataHandlers.push(cb); },
|
|
183
|
-
onExit: (cb) => { exitHandlers.push(cb); },
|
|
184
|
-
kill: (signal) => { child.kill(signal); },
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
export class CodexAdapter {
|
|
189
|
-
opts;
|
|
190
|
-
kind = 'codex';
|
|
191
|
-
constructor(opts) {
|
|
192
|
-
this.opts = opts;
|
|
193
|
-
}
|
|
194
|
-
async ask(input, signal) {
|
|
195
|
-
return new Promise((resolve, reject) => {
|
|
196
|
-
const payload = renderPayload(input, this.opts.systemPrompt ?? input.systemPrompt);
|
|
197
|
-
const cwd = input.workspace ?? this.opts.cwd ?? process.cwd();
|
|
198
|
-
const baseCommand = this.opts.command || 'codex';
|
|
199
|
-
const defaultOutputLastMessagePath = createOutputLastMessagePath();
|
|
200
|
-
const normalizedExec = normalizeExecArgs(this.opts.args, defaultOutputLastMessagePath);
|
|
201
|
-
const configuredArgs = normalizedExec.args;
|
|
202
|
-
const baseArgs = [...configuredArgs, payload];
|
|
203
|
-
const command = input.sandboxProfilePath ? 'sandbox-exec' : baseCommand;
|
|
204
|
-
const args = input.sandboxProfilePath
|
|
205
|
-
? ['-f', input.sandboxProfilePath, '--', baseCommand, ...baseArgs]
|
|
206
|
-
: baseArgs;
|
|
207
|
-
const env = buildRuntimeEnv(input.env);
|
|
208
|
-
try {
|
|
209
|
-
assertExecutable(command, env, input.sandboxProfilePath ? 'Sandbox launcher' : 'Codex runtime');
|
|
210
|
-
if (input.sandboxProfilePath) {
|
|
211
|
-
assertExecutable(baseCommand, env, 'Codex runtime');
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
reject(err);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const runtime = spawnRuntimeProcess(command, args, { cwd, env });
|
|
219
|
-
const chunks = [];
|
|
220
|
-
let finished = false;
|
|
221
|
-
const MAX_EXEC_MS = adapterTimeoutMs();
|
|
222
|
-
const onAbort = () => {
|
|
223
|
-
if (finished)
|
|
224
|
-
return;
|
|
225
|
-
finished = true;
|
|
226
|
-
clearTimeout(maxTimer);
|
|
227
|
-
signal.removeEventListener('abort', onAbort);
|
|
228
|
-
runtime.kill('SIGTERM');
|
|
229
|
-
setTimeout(() => {
|
|
230
|
-
try {
|
|
231
|
-
runtime.kill('SIGKILL');
|
|
232
|
-
}
|
|
233
|
-
catch { }
|
|
234
|
-
}, 2_000).unref();
|
|
235
|
-
reject(new Error('aborted'));
|
|
236
|
-
};
|
|
237
|
-
signal.addEventListener('abort', onAbort);
|
|
238
|
-
const maxTimer = setTimeout(() => {
|
|
239
|
-
if (finished)
|
|
240
|
-
return;
|
|
241
|
-
finished = true;
|
|
242
|
-
runtime.kill('SIGKILL');
|
|
243
|
-
signal.removeEventListener('abort', onAbort);
|
|
244
|
-
reject(new Error('codex adapter timeout'));
|
|
245
|
-
}, MAX_EXEC_MS).unref();
|
|
246
|
-
runtime.onData((data) => chunks.push(data));
|
|
247
|
-
runtime.onExit(({ exitCode }) => {
|
|
248
|
-
clearTimeout(maxTimer);
|
|
249
|
-
signal.removeEventListener('abort', onAbort);
|
|
250
|
-
if (finished)
|
|
251
|
-
return;
|
|
252
|
-
finished = true;
|
|
253
|
-
if (signal.aborted)
|
|
254
|
-
return reject(new Error('aborted'));
|
|
255
|
-
const raw = chunks.join('');
|
|
256
|
-
if (exitCode !== 0) {
|
|
257
|
-
const detail = stripAnsi(raw).trim();
|
|
258
|
-
return reject(new Error(detail ? `codex exit ${exitCode}: ${detail}` : `codex exit ${exitCode}`));
|
|
259
|
-
}
|
|
260
|
-
const reply = readOutputLastMessage(normalizedExec.outputLastMessagePath) ?? extractCodexReply(raw, payload);
|
|
261
|
-
resolve(reply || '(Codex 已完成处理)');
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
async health() {
|
|
266
|
-
return new Promise((resolve) => {
|
|
267
|
-
try {
|
|
268
|
-
const pty = spawnPty('bash', ['-c', 'codex --version'], {
|
|
269
|
-
name: 'xterm-color', cols: 80, rows: 30,
|
|
270
|
-
cwd: this.opts.cwd ?? process.cwd(),
|
|
271
|
-
env: buildRuntimeEnv(),
|
|
272
|
-
});
|
|
273
|
-
pty.onExit(({ exitCode }) => resolve({ ok: exitCode === 0, detail: exitCode === 0 ? undefined : `exit ${exitCode}` }));
|
|
274
|
-
}
|
|
275
|
-
catch (err) {
|
|
276
|
-
resolve({ ok: false, detail: err.message });
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
package/dist/adapters/factory.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { CodexAdapter } from './codex.js';
|
|
2
|
-
import { ClaudeCodeAdapter } from './claude-code.js';
|
|
3
|
-
import { OpenClawAdapter } from './openclaw.js';
|
|
4
|
-
import { HermesAdapter } from './hermes.js';
|
|
5
|
-
export function pickAdapter(cfg) {
|
|
6
|
-
switch (cfg.kind) {
|
|
7
|
-
case 'codex':
|
|
8
|
-
return new CodexAdapter({
|
|
9
|
-
command: cfg.command,
|
|
10
|
-
args: cfg.args,
|
|
11
|
-
cwd: cfg.cwd,
|
|
12
|
-
systemPrompt: cfg.systemPrompt,
|
|
13
|
-
});
|
|
14
|
-
case 'claude-code':
|
|
15
|
-
return new ClaudeCodeAdapter({
|
|
16
|
-
command: cfg.command,
|
|
17
|
-
args: cfg.args,
|
|
18
|
-
cwd: cfg.cwd,
|
|
19
|
-
systemPrompt: cfg.systemPrompt,
|
|
20
|
-
});
|
|
21
|
-
case 'openclaw':
|
|
22
|
-
return new OpenClawAdapter({
|
|
23
|
-
command: cfg.command,
|
|
24
|
-
args: cfg.args,
|
|
25
|
-
cwd: cfg.cwd,
|
|
26
|
-
systemPrompt: cfg.systemPrompt,
|
|
27
|
-
});
|
|
28
|
-
case 'hermes':
|
|
29
|
-
return new HermesAdapter({
|
|
30
|
-
command: cfg.command,
|
|
31
|
-
args: cfg.args,
|
|
32
|
-
cwd: cfg.cwd,
|
|
33
|
-
systemPrompt: cfg.systemPrompt,
|
|
34
|
-
});
|
|
35
|
-
default:
|
|
36
|
-
throw new Error(`adapter '${cfg.kind}' not yet implemented`);
|
|
37
|
-
}
|
|
38
|
-
}
|
package/dist/adapters/hermes.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
function runtimeArgs(args = []) {
|
|
3
|
-
if (args[0] === 'gateway' && args[1] === 'run') {
|
|
4
|
-
return args.slice(2);
|
|
5
|
-
}
|
|
6
|
-
return args;
|
|
7
|
-
}
|
|
8
|
-
function buildArgs(baseArgs, prompt) {
|
|
9
|
-
// If user already configured args with chat -q, just append the prompt
|
|
10
|
-
// Otherwise default to: hermes chat -q "<prompt>"
|
|
11
|
-
const hasChat = baseArgs.includes('chat');
|
|
12
|
-
const hasQ = baseArgs.includes('-q');
|
|
13
|
-
if (hasChat && hasQ) {
|
|
14
|
-
return [...baseArgs, prompt];
|
|
15
|
-
}
|
|
16
|
-
return [...baseArgs, 'chat', '-q', prompt];
|
|
17
|
-
}
|
|
18
|
-
function buildPrompt(input, systemPrompt) {
|
|
19
|
-
const parts = [];
|
|
20
|
-
if (systemPrompt)
|
|
21
|
-
parts.push(systemPrompt);
|
|
22
|
-
for (const h of input.history.slice(-10)) {
|
|
23
|
-
parts.push(`${h.speaker} (${h.role}): ${h.body}`);
|
|
24
|
-
}
|
|
25
|
-
parts.push(input.prompt);
|
|
26
|
-
return parts.join('\n\n---\n\n');
|
|
27
|
-
}
|
|
28
|
-
const ANSI_RE = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
29
|
-
const BOX_ONLY_RE = /^[\s─━═╭╮╰╯│┃┌┐└┘├┤┬┴┼]+$/;
|
|
30
|
-
function stripEchoedQueryPreamble(lines) {
|
|
31
|
-
const initIdx = lines.findIndex((line) => {
|
|
32
|
-
const trimmed = line.trim();
|
|
33
|
-
return trimmed === 'Initializing agent...' || trimmed === 'Initializing agent…';
|
|
34
|
-
});
|
|
35
|
-
if (initIdx < 0)
|
|
36
|
-
return lines;
|
|
37
|
-
if (!lines.slice(0, initIdx).some((line) => line.trim().startsWith('Query:')))
|
|
38
|
-
return lines;
|
|
39
|
-
return lines.slice(initIdx + 1);
|
|
40
|
-
}
|
|
41
|
-
export function extractHermesReply(output) {
|
|
42
|
-
const lines = stripEchoedQueryPreamble(output
|
|
43
|
-
.replace(ANSI_RE, '')
|
|
44
|
-
.replace(/\r\n?/g, '\n')
|
|
45
|
-
.split('\n'));
|
|
46
|
-
let boxStart = -1;
|
|
47
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
48
|
-
if (lines[i]?.trim().startsWith('╭')) {
|
|
49
|
-
boxStart = i;
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (boxStart >= 0) {
|
|
54
|
-
const boxLines = [];
|
|
55
|
-
for (const line of lines.slice(boxStart + 1)) {
|
|
56
|
-
const trimmed = line.trim();
|
|
57
|
-
if (trimmed.startsWith('╰'))
|
|
58
|
-
break;
|
|
59
|
-
if (BOX_ONLY_RE.test(trimmed))
|
|
60
|
-
continue;
|
|
61
|
-
boxLines.push(line);
|
|
62
|
-
}
|
|
63
|
-
const boxedReply = boxLines
|
|
64
|
-
.map((line) => line.trimEnd())
|
|
65
|
-
.filter((line) => {
|
|
66
|
-
const trimmed = line.trim();
|
|
67
|
-
return !(trimmed.startsWith('╭') || trimmed.startsWith('╰') || BOX_ONLY_RE.test(trimmed));
|
|
68
|
-
})
|
|
69
|
-
.map((line) => line.replace(/^[│┃]\s?/, '').replace(/\s?[│┃]$/, '').replace(/^\s{2,}/, ''))
|
|
70
|
-
.join('\n')
|
|
71
|
-
.trim();
|
|
72
|
-
if (boxedReply)
|
|
73
|
-
return boxedReply;
|
|
74
|
-
}
|
|
75
|
-
const cleaned = lines
|
|
76
|
-
.map((line) => line.trimEnd())
|
|
77
|
-
.filter((line) => {
|
|
78
|
-
const trimmed = line.trim();
|
|
79
|
-
if (!trimmed)
|
|
80
|
-
return true;
|
|
81
|
-
if (trimmed.startsWith('Query:'))
|
|
82
|
-
return false;
|
|
83
|
-
if (trimmed === 'Initializing agent...' || trimmed === 'Initializing agent…')
|
|
84
|
-
return false;
|
|
85
|
-
if (trimmed.startsWith('Resume this session with:'))
|
|
86
|
-
return false;
|
|
87
|
-
if (/^hermes\s+--resume\b/.test(trimmed))
|
|
88
|
-
return false;
|
|
89
|
-
if (/^(Session|Duration|Messages):\s+/.test(trimmed))
|
|
90
|
-
return false;
|
|
91
|
-
if (trimmed.startsWith('╭') || trimmed.startsWith('╰'))
|
|
92
|
-
return false;
|
|
93
|
-
if (BOX_ONLY_RE.test(trimmed))
|
|
94
|
-
return false;
|
|
95
|
-
return true;
|
|
96
|
-
})
|
|
97
|
-
.map((line) => line.replace(/^[│┃]\s?/, '').replace(/\s?[│┃]$/, '').replace(/^\s{2,}/, ''))
|
|
98
|
-
.join('\n')
|
|
99
|
-
.trim();
|
|
100
|
-
return cleaned || output.trim();
|
|
101
|
-
}
|
|
102
|
-
export class HermesAdapter {
|
|
103
|
-
opts;
|
|
104
|
-
kind = 'hermes';
|
|
105
|
-
constructor(opts) {
|
|
106
|
-
this.opts = opts;
|
|
107
|
-
}
|
|
108
|
-
async ask(input, signal) {
|
|
109
|
-
return new Promise((resolve, reject) => {
|
|
110
|
-
const prompt = buildPrompt(input, this.opts.systemPrompt ?? input.systemPrompt);
|
|
111
|
-
const cwd = input.workspace ?? this.opts.cwd ?? process.cwd();
|
|
112
|
-
const child = spawn(this.opts.command, buildArgs(runtimeArgs(this.opts.args), prompt), {
|
|
113
|
-
cwd,
|
|
114
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
115
|
-
env: { ...process.env, ...(input.env ?? {}) },
|
|
116
|
-
});
|
|
117
|
-
const stdoutChunks = [];
|
|
118
|
-
const stderrChunks = [];
|
|
119
|
-
let finished = false;
|
|
120
|
-
const MAX_EXEC_MS = 600_000;
|
|
121
|
-
const onAbort = () => {
|
|
122
|
-
if (finished)
|
|
123
|
-
return;
|
|
124
|
-
finished = true;
|
|
125
|
-
child.kill('SIGTERM');
|
|
126
|
-
setTimeout(() => { try {
|
|
127
|
-
child.kill('SIGKILL');
|
|
128
|
-
}
|
|
129
|
-
catch { } }, 2_000).unref();
|
|
130
|
-
};
|
|
131
|
-
signal.addEventListener('abort', onAbort);
|
|
132
|
-
const maxTimer = setTimeout(() => {
|
|
133
|
-
if (finished)
|
|
134
|
-
return;
|
|
135
|
-
finished = true;
|
|
136
|
-
child.kill('SIGKILL');
|
|
137
|
-
signal.removeEventListener('abort', onAbort);
|
|
138
|
-
reject(new Error('hermes adapter timeout'));
|
|
139
|
-
}, MAX_EXEC_MS).unref();
|
|
140
|
-
child.stdout.on('data', (b) => stdoutChunks.push(b));
|
|
141
|
-
child.stderr.on('data', (b) => stderrChunks.push(b));
|
|
142
|
-
child.on('error', (err) => {
|
|
143
|
-
clearTimeout(maxTimer);
|
|
144
|
-
signal.removeEventListener('abort', onAbort);
|
|
145
|
-
reject(err);
|
|
146
|
-
});
|
|
147
|
-
child.on('exit', (code) => {
|
|
148
|
-
clearTimeout(maxTimer);
|
|
149
|
-
signal.removeEventListener('abort', onAbort);
|
|
150
|
-
if (finished)
|
|
151
|
-
return;
|
|
152
|
-
finished = true;
|
|
153
|
-
if (signal.aborted)
|
|
154
|
-
return reject(new Error('aborted'));
|
|
155
|
-
const out = Buffer.concat(stdoutChunks).toString('utf8');
|
|
156
|
-
const err = Buffer.concat(stderrChunks).toString('utf8');
|
|
157
|
-
const stdout = out.trim();
|
|
158
|
-
const stderr = err.trim();
|
|
159
|
-
if (code !== 0 && stdout.length === 0) {
|
|
160
|
-
const detail = stderr.length > 0 ? stderr.slice(0, 400) : 'no stderr';
|
|
161
|
-
return reject(new Error(`hermes exit ${code}: ${detail}`));
|
|
162
|
-
}
|
|
163
|
-
const reply = extractHermesReply(stdout || stderr);
|
|
164
|
-
if (!reply) {
|
|
165
|
-
return reject(new Error('hermes produced empty output'));
|
|
166
|
-
}
|
|
167
|
-
resolve(reply);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
async health() {
|
|
172
|
-
return new Promise((resolve) => {
|
|
173
|
-
const child = spawn(this.opts.command, ['--version'], { stdio: 'ignore' });
|
|
174
|
-
child.on('error', (err) => resolve({ ok: false, detail: err.message }));
|
|
175
|
-
child.on('exit', (code) => resolve({ ok: code === 0, detail: code === 0 ? undefined : `exit ${code}` }));
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
function runtimeArgs(args = []) {
|
|
3
|
-
if (args[0] === 'gateway' && args[1] === 'run') {
|
|
4
|
-
return args.slice(2);
|
|
5
|
-
}
|
|
6
|
-
return args;
|
|
7
|
-
}
|
|
8
|
-
function hasMessageFlag(args) {
|
|
9
|
-
return args.includes('--message') || args.includes('-m');
|
|
10
|
-
}
|
|
11
|
-
function hasTargetSelector(args) {
|
|
12
|
-
return args.includes('--agent')
|
|
13
|
-
|| args.includes('--session-id')
|
|
14
|
-
|| args.includes('--session-key')
|
|
15
|
-
|| args.includes('--to')
|
|
16
|
-
|| args.includes('-t');
|
|
17
|
-
}
|
|
18
|
-
function ensureTargetSelector(args) {
|
|
19
|
-
if (hasTargetSelector(args))
|
|
20
|
-
return args;
|
|
21
|
-
const messageIdx = args.findIndex((arg) => arg === '--message' || arg === '-m');
|
|
22
|
-
if (messageIdx >= 0) {
|
|
23
|
-
return [
|
|
24
|
-
...args.slice(0, messageIdx),
|
|
25
|
-
'--agent',
|
|
26
|
-
'main',
|
|
27
|
-
...args.slice(messageIdx),
|
|
28
|
-
];
|
|
29
|
-
}
|
|
30
|
-
return [...args, '--agent', 'main'];
|
|
31
|
-
}
|
|
32
|
-
function buildArgs(baseArgs, prompt) {
|
|
33
|
-
// Current OpenClaw one-shot agent turns require an explicit session selector.
|
|
34
|
-
// Preserve explicit custom args when the message flag is already configured.
|
|
35
|
-
const agentArgs = baseArgs.includes('agent')
|
|
36
|
-
? ensureTargetSelector(baseArgs)
|
|
37
|
-
: [...baseArgs, 'agent', '--agent', 'main'];
|
|
38
|
-
if (hasMessageFlag(agentArgs)) {
|
|
39
|
-
return [...agentArgs, prompt];
|
|
40
|
-
}
|
|
41
|
-
return [...agentArgs, '--message', prompt];
|
|
42
|
-
}
|
|
43
|
-
function buildPrompt(input, systemPrompt) {
|
|
44
|
-
const parts = [];
|
|
45
|
-
if (systemPrompt)
|
|
46
|
-
parts.push(systemPrompt);
|
|
47
|
-
for (const h of input.history.slice(-10)) {
|
|
48
|
-
parts.push(`${h.speaker} (${h.role}): ${h.body}`);
|
|
49
|
-
}
|
|
50
|
-
parts.push(input.prompt);
|
|
51
|
-
return parts.join('\n\n---\n\n');
|
|
52
|
-
}
|
|
53
|
-
export class OpenClawAdapter {
|
|
54
|
-
opts;
|
|
55
|
-
kind = 'openclaw';
|
|
56
|
-
constructor(opts) {
|
|
57
|
-
this.opts = opts;
|
|
58
|
-
}
|
|
59
|
-
async ask(input, signal) {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
const prompt = buildPrompt(input, this.opts.systemPrompt ?? input.systemPrompt);
|
|
62
|
-
const cwd = input.workspace ?? this.opts.cwd ?? process.cwd();
|
|
63
|
-
const child = spawn(this.opts.command, buildArgs(runtimeArgs(this.opts.args), prompt), {
|
|
64
|
-
cwd,
|
|
65
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
66
|
-
env: { ...process.env, ...(input.env ?? {}) },
|
|
67
|
-
});
|
|
68
|
-
const stdoutChunks = [];
|
|
69
|
-
const stderrChunks = [];
|
|
70
|
-
let finished = false;
|
|
71
|
-
const MAX_EXEC_MS = 600_000;
|
|
72
|
-
const onAbort = () => {
|
|
73
|
-
if (finished)
|
|
74
|
-
return;
|
|
75
|
-
finished = true;
|
|
76
|
-
child.kill('SIGTERM');
|
|
77
|
-
setTimeout(() => { try {
|
|
78
|
-
child.kill('SIGKILL');
|
|
79
|
-
}
|
|
80
|
-
catch { } }, 2_000).unref();
|
|
81
|
-
};
|
|
82
|
-
signal.addEventListener('abort', onAbort);
|
|
83
|
-
const maxTimer = setTimeout(() => {
|
|
84
|
-
if (finished)
|
|
85
|
-
return;
|
|
86
|
-
finished = true;
|
|
87
|
-
child.kill('SIGKILL');
|
|
88
|
-
signal.removeEventListener('abort', onAbort);
|
|
89
|
-
reject(new Error('openclaw adapter timeout'));
|
|
90
|
-
}, MAX_EXEC_MS).unref();
|
|
91
|
-
child.stdout.on('data', (b) => stdoutChunks.push(b));
|
|
92
|
-
child.stderr.on('data', (b) => stderrChunks.push(b));
|
|
93
|
-
child.on('error', (err) => {
|
|
94
|
-
clearTimeout(maxTimer);
|
|
95
|
-
signal.removeEventListener('abort', onAbort);
|
|
96
|
-
reject(err);
|
|
97
|
-
});
|
|
98
|
-
child.on('exit', (code) => {
|
|
99
|
-
clearTimeout(maxTimer);
|
|
100
|
-
signal.removeEventListener('abort', onAbort);
|
|
101
|
-
if (finished)
|
|
102
|
-
return;
|
|
103
|
-
finished = true;
|
|
104
|
-
if (signal.aborted)
|
|
105
|
-
return reject(new Error('aborted'));
|
|
106
|
-
const out = Buffer.concat(stdoutChunks).toString('utf8');
|
|
107
|
-
const err = Buffer.concat(stderrChunks).toString('utf8');
|
|
108
|
-
const stdout = out.trim();
|
|
109
|
-
const stderr = err.trim();
|
|
110
|
-
if (code !== 0 && stdout.length === 0) {
|
|
111
|
-
const detail = stderr.length > 0 ? stderr.slice(0, 400) : 'no stderr';
|
|
112
|
-
return reject(new Error(`openclaw exit ${code}: ${detail}`));
|
|
113
|
-
}
|
|
114
|
-
const reply = stdout || stderr;
|
|
115
|
-
if (!reply) {
|
|
116
|
-
return reject(new Error('openclaw produced empty output'));
|
|
117
|
-
}
|
|
118
|
-
resolve(reply);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
async health() {
|
|
123
|
-
return new Promise((resolve) => {
|
|
124
|
-
const child = spawn(this.opts.command, ['--version'], { stdio: 'ignore' });
|
|
125
|
-
child.on('error', (err) => resolve({ ok: false, detail: err.message }));
|
|
126
|
-
child.on('exit', (code) => resolve({ ok: code === 0, detail: code === 0 ? undefined : `exit ${code}` }));
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|