@agentbean/daemon 0.1.29 → 0.1.32
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-code.js +4 -1
- package/dist/adapters/codex.js +161 -21
- package/dist/agent-instance.js +1 -1
- package/dist/config.js +12 -0
- package/dist/device-daemon.js +9 -0
- package/dist/sandbox.js +10 -2
- package/dist/scanner.js +3 -1
- package/package.json +1 -1
|
@@ -11,6 +11,9 @@ function buildPrompt(input, systemPrompt) {
|
|
|
11
11
|
parts.push(input.prompt);
|
|
12
12
|
return parts.join('\n\n---\n\n');
|
|
13
13
|
}
|
|
14
|
+
function normalizeClaudeArgs(args) {
|
|
15
|
+
return ['-p', ...(args ?? [])];
|
|
16
|
+
}
|
|
14
17
|
export class ClaudeCodeAdapter {
|
|
15
18
|
opts;
|
|
16
19
|
kind = 'claude-code';
|
|
@@ -21,7 +24,7 @@ export class ClaudeCodeAdapter {
|
|
|
21
24
|
return new Promise((resolve, reject) => {
|
|
22
25
|
const prompt = buildPrompt(input, this.opts.systemPrompt ?? input.systemPrompt);
|
|
23
26
|
const cwd = input.workspace ?? this.opts.cwd ?? process.cwd();
|
|
24
|
-
const baseArgs =
|
|
27
|
+
const baseArgs = normalizeClaudeArgs(this.opts.args);
|
|
25
28
|
if (input.workspace)
|
|
26
29
|
baseArgs.push('--add-dir', input.workspace);
|
|
27
30
|
baseArgs.push('--add-dir', join(homedir(), '.codex', 'generated_images'));
|
package/dist/adapters/codex.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { spawn } from 'node
|
|
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';
|
|
2
6
|
function renderPayload(input, systemPrompt) {
|
|
3
7
|
const parts = [];
|
|
4
8
|
if (systemPrompt)
|
|
@@ -40,18 +44,147 @@ export function extractCodexReply(output, payload) {
|
|
|
40
44
|
}
|
|
41
45
|
return clean.trim();
|
|
42
46
|
}
|
|
43
|
-
function
|
|
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) {
|
|
44
66
|
const baseArgs = args && args.length > 0 ? args : ['exec'];
|
|
45
67
|
const subcommand = baseArgs[0];
|
|
46
|
-
if (
|
|
47
|
-
|
|
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
|
+
};
|
|
48
85
|
}
|
|
49
|
-
return baseArgs;
|
|
86
|
+
return { args: baseArgs };
|
|
50
87
|
}
|
|
51
88
|
function adapterTimeoutMs() {
|
|
52
89
|
const fromEnv = Number.parseInt(process.env.AGENTBEAN_CODEX_TIMEOUT_MS ?? '', 10);
|
|
53
90
|
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : 900_000;
|
|
54
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
|
+
}
|
|
55
188
|
export class CodexAdapter {
|
|
56
189
|
opts;
|
|
57
190
|
kind = 'codex';
|
|
@@ -63,19 +196,26 @@ export class CodexAdapter {
|
|
|
63
196
|
const payload = renderPayload(input, this.opts.systemPrompt ?? input.systemPrompt);
|
|
64
197
|
const cwd = input.workspace ?? this.opts.cwd ?? process.cwd();
|
|
65
198
|
const baseCommand = this.opts.command || 'codex';
|
|
66
|
-
const
|
|
199
|
+
const defaultOutputLastMessagePath = createOutputLastMessagePath();
|
|
200
|
+
const normalizedExec = normalizeExecArgs(this.opts.args, defaultOutputLastMessagePath);
|
|
201
|
+
const configuredArgs = normalizedExec.args;
|
|
67
202
|
const baseArgs = [...configuredArgs, payload];
|
|
68
203
|
const command = input.sandboxProfilePath ? 'sandbox-exec' : baseCommand;
|
|
69
204
|
const args = input.sandboxProfilePath
|
|
70
205
|
? ['-f', input.sandboxProfilePath, '--', baseCommand, ...baseArgs]
|
|
71
206
|
: baseArgs;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
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 });
|
|
79
219
|
const chunks = [];
|
|
80
220
|
let finished = false;
|
|
81
221
|
const MAX_EXEC_MS = adapterTimeoutMs();
|
|
@@ -85,10 +225,10 @@ export class CodexAdapter {
|
|
|
85
225
|
finished = true;
|
|
86
226
|
clearTimeout(maxTimer);
|
|
87
227
|
signal.removeEventListener('abort', onAbort);
|
|
88
|
-
|
|
228
|
+
runtime.kill('SIGTERM');
|
|
89
229
|
setTimeout(() => {
|
|
90
230
|
try {
|
|
91
|
-
|
|
231
|
+
runtime.kill('SIGKILL');
|
|
92
232
|
}
|
|
93
233
|
catch { }
|
|
94
234
|
}, 2_000).unref();
|
|
@@ -99,12 +239,12 @@ export class CodexAdapter {
|
|
|
99
239
|
if (finished)
|
|
100
240
|
return;
|
|
101
241
|
finished = true;
|
|
102
|
-
|
|
242
|
+
runtime.kill('SIGKILL');
|
|
103
243
|
signal.removeEventListener('abort', onAbort);
|
|
104
244
|
reject(new Error('codex adapter timeout'));
|
|
105
245
|
}, MAX_EXEC_MS).unref();
|
|
106
|
-
|
|
107
|
-
|
|
246
|
+
runtime.onData((data) => chunks.push(data));
|
|
247
|
+
runtime.onExit(({ exitCode }) => {
|
|
108
248
|
clearTimeout(maxTimer);
|
|
109
249
|
signal.removeEventListener('abort', onAbort);
|
|
110
250
|
if (finished)
|
|
@@ -117,7 +257,7 @@ export class CodexAdapter {
|
|
|
117
257
|
const detail = stripAnsi(raw).trim();
|
|
118
258
|
return reject(new Error(detail ? `codex exit ${exitCode}: ${detail}` : `codex exit ${exitCode}`));
|
|
119
259
|
}
|
|
120
|
-
const reply = extractCodexReply(raw, payload);
|
|
260
|
+
const reply = readOutputLastMessage(normalizedExec.outputLastMessagePath) ?? extractCodexReply(raw, payload);
|
|
121
261
|
resolve(reply || '(Codex 已完成处理)');
|
|
122
262
|
});
|
|
123
263
|
});
|
|
@@ -125,10 +265,10 @@ export class CodexAdapter {
|
|
|
125
265
|
async health() {
|
|
126
266
|
return new Promise((resolve) => {
|
|
127
267
|
try {
|
|
128
|
-
const pty =
|
|
268
|
+
const pty = spawnPty('bash', ['-c', 'codex --version'], {
|
|
129
269
|
name: 'xterm-color', cols: 80, rows: 30,
|
|
130
270
|
cwd: this.opts.cwd ?? process.cwd(),
|
|
131
|
-
env:
|
|
271
|
+
env: buildRuntimeEnv(),
|
|
132
272
|
});
|
|
133
273
|
pty.onExit(({ exitCode }) => resolve({ ok: exitCode === 0, detail: exitCode === 0 ? undefined : `exit ${exitCode}` }));
|
|
134
274
|
}
|
package/dist/agent-instance.js
CHANGED
|
@@ -108,7 +108,7 @@ export class AgentInstance {
|
|
|
108
108
|
sandboxProfilePath: req.sandboxed && isSandboxAvailable()
|
|
109
109
|
? generateSandboxProfile(this.id, this.config.adapter.command)
|
|
110
110
|
: undefined,
|
|
111
|
-
env: workspaceEnv(run),
|
|
111
|
+
env: { ...(this.config.adapter.env ?? {}), ...workspaceEnv(run) },
|
|
112
112
|
}, ctl.signal);
|
|
113
113
|
const processed = await postProcess(rawBody, projectWorkspace, this.adapter.kind, dispatchStart, {
|
|
114
114
|
outputDirs: [run.outputDir, run.intermediateDir],
|
package/dist/config.js
CHANGED
|
@@ -23,6 +23,16 @@ function deepInterpolate(node) {
|
|
|
23
23
|
}
|
|
24
24
|
return node;
|
|
25
25
|
}
|
|
26
|
+
function parseEnvMap(value) {
|
|
27
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
28
|
+
return undefined;
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
31
|
+
if (key.trim())
|
|
32
|
+
out[key.trim()] = String(raw ?? '');
|
|
33
|
+
}
|
|
34
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
35
|
+
}
|
|
26
36
|
export function loadConfig(path) {
|
|
27
37
|
const raw = parseYaml(readFileSync(path, 'utf8'));
|
|
28
38
|
if (!raw || typeof raw !== 'object')
|
|
@@ -62,6 +72,7 @@ export function loadConfig(path) {
|
|
|
62
72
|
cwd: typeof a.cwd === 'string' ? a.cwd : undefined,
|
|
63
73
|
workspace: typeof a.workspace === 'string' ? a.workspace : undefined,
|
|
64
74
|
systemPrompt: typeof a.systemPrompt === 'string' ? a.systemPrompt : undefined,
|
|
75
|
+
env: parseEnvMap(a.env),
|
|
65
76
|
},
|
|
66
77
|
server: { url: s.url, token: s.token },
|
|
67
78
|
heartbeatIntervalMs: isValidHeartbeat(interp.heartbeatIntervalMs) ? interp.heartbeatIntervalMs : 10_000,
|
|
@@ -121,6 +132,7 @@ export function loadDeviceConfig(path) {
|
|
|
121
132
|
cwd: typeof ad.cwd === 'string' ? ad.cwd : undefined,
|
|
122
133
|
workspace: typeof ad.workspace === 'string' ? ad.workspace : undefined,
|
|
123
134
|
systemPrompt: typeof ad.systemPrompt === 'string' ? ad.systemPrompt : undefined,
|
|
135
|
+
env: parseEnvMap(ad.env),
|
|
124
136
|
},
|
|
125
137
|
visibility: a.visibility === 'public' ? 'public' : 'private',
|
|
126
138
|
sandboxed: a.sandboxed === true,
|
package/dist/device-daemon.js
CHANGED
|
@@ -71,6 +71,14 @@ export function resolveCustomAgentRuntime(custom, runtimes) {
|
|
|
71
71
|
if (bestRuntime?.command?.trim()) {
|
|
72
72
|
return { command: bestRuntime.command.trim(), runtime: bestRuntime };
|
|
73
73
|
}
|
|
74
|
+
if (configured && isAbsolute(configured) && !configuredAbsoluteExists) {
|
|
75
|
+
const fallback = basename(configured).trim();
|
|
76
|
+
if (fallback)
|
|
77
|
+
return { command: fallback };
|
|
78
|
+
}
|
|
79
|
+
if (!configured && normalizeAdapterKind(custom.adapterKind) === 'codex') {
|
|
80
|
+
return { command: 'codex' };
|
|
81
|
+
}
|
|
74
82
|
return { command: configured };
|
|
75
83
|
}
|
|
76
84
|
export function nativeDirectoryPickerCommands(platform = process.platform) {
|
|
@@ -383,6 +391,7 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
383
391
|
args: custom.args ?? [],
|
|
384
392
|
cwd: custom.cwd ?? undefined,
|
|
385
393
|
workspace: custom.cwd ?? undefined,
|
|
394
|
+
env: custom.env ?? undefined,
|
|
386
395
|
systemPrompt: custom.description ?? undefined,
|
|
387
396
|
},
|
|
388
397
|
visibility: 'public',
|
package/dist/sandbox.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { accessSync, constants, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
function escapeSchemeString(value) {
|
|
@@ -11,7 +11,15 @@ export function getWorkspaceDir(agentId) {
|
|
|
11
11
|
return dir;
|
|
12
12
|
}
|
|
13
13
|
export function isSandboxAvailable() {
|
|
14
|
-
|
|
14
|
+
if (process.platform !== 'darwin')
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
accessSync('/usr/bin/sandbox-exec', constants.X_OK);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
15
23
|
}
|
|
16
24
|
export function generateSandboxProfile(agentId, runtimePath) {
|
|
17
25
|
const workspaceDir = getWorkspaceDir(agentId);
|
package/dist/scanner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { readdirSync, readFileSync, statSync, existsSync, writeFileSync, mkdirSync, } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import { logger } from "./log.js";
|
|
@@ -274,6 +274,7 @@ async function checkHermesGateway() {
|
|
|
274
274
|
adapterKind: "hermes",
|
|
275
275
|
command: path,
|
|
276
276
|
args: [],
|
|
277
|
+
cwd: dirname(path),
|
|
277
278
|
source: "gateway",
|
|
278
279
|
};
|
|
279
280
|
}
|
|
@@ -293,6 +294,7 @@ async function checkOpenClawGateway() {
|
|
|
293
294
|
adapterKind: "openclaw",
|
|
294
295
|
command: path,
|
|
295
296
|
args: ["agent", "--agent", agentId ?? "main"],
|
|
297
|
+
cwd: dirname(path),
|
|
296
298
|
source: "gateway",
|
|
297
299
|
};
|
|
298
300
|
}
|