@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/index.js
DELETED
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
import { parseArgs } from 'node:util';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import { loadConfig, loadDeviceConfig } from './config.js';
|
|
4
|
-
import { createConnection } from './connection.js';
|
|
5
|
-
import { createDeviceDaemon } from './device-daemon.js';
|
|
6
|
-
import { AgentInstance } from './agent-instance.js';
|
|
7
|
-
import { pickAdapter } from './adapters/factory.js';
|
|
8
|
-
import { logger } from './log.js';
|
|
9
|
-
import { scanRuntimes, scanAgentOSAgents, scanLocalAgents, getDeviceId } from './scanner.js';
|
|
10
|
-
import { listAuthProfiles, loadAuth, saveAuth } from './auth-store.js';
|
|
11
|
-
import { deviceInstanceId, localAgentsDir, profileIdForNetwork } from './profile-paths.js';
|
|
12
|
-
export function discoveredAgentId(name, deviceId) {
|
|
13
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
14
|
-
return deviceId ? `scan-${deviceId}-${slug}` : slug;
|
|
15
|
-
}
|
|
16
|
-
async function discoverAgents(deviceId, profileId) {
|
|
17
|
-
const [_runtimes, agentos, local] = await Promise.all([
|
|
18
|
-
scanRuntimes(),
|
|
19
|
-
scanAgentOSAgents(),
|
|
20
|
-
scanLocalAgents(profileId ? localAgentsDir(profileId) : undefined),
|
|
21
|
-
]);
|
|
22
|
-
const seen = new Set();
|
|
23
|
-
const results = [];
|
|
24
|
-
for (const s of [...agentos, ...local]) {
|
|
25
|
-
if (seen.has(s.command))
|
|
26
|
-
continue;
|
|
27
|
-
seen.add(s.command);
|
|
28
|
-
results.push({
|
|
29
|
-
id: discoveredAgentId(s.name, deviceId),
|
|
30
|
-
name: s.name,
|
|
31
|
-
role: s.category === 'executor-hosted' ? 'executor-agent' : 'gateway-agent',
|
|
32
|
-
category: s.category,
|
|
33
|
-
adapter: {
|
|
34
|
-
kind: s.adapterKind,
|
|
35
|
-
command: s.command,
|
|
36
|
-
args: s.args,
|
|
37
|
-
cwd: s.cwd,
|
|
38
|
-
},
|
|
39
|
-
visibility: 'public',
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
logger.info({ discovered: results.map((r) => r.name) }, 'agents discovered via scanning');
|
|
43
|
-
return results;
|
|
44
|
-
}
|
|
45
|
-
async function startDeviceDaemon(cfg) {
|
|
46
|
-
const agents = new Map();
|
|
47
|
-
for (const entry of cfg.agents) {
|
|
48
|
-
const adapter = pickAdapter(entry.adapter);
|
|
49
|
-
const instance = new AgentInstance(entry, adapter);
|
|
50
|
-
agents.set(entry.id, instance);
|
|
51
|
-
logger.info({ id: entry.id, kind: entry.adapter.kind, visibility: entry.visibility }, 'agent instance created');
|
|
52
|
-
}
|
|
53
|
-
logger.info({ deviceId: cfg.deviceId, agentCount: agents.size }, 'device daemon starting');
|
|
54
|
-
const daemon = createDeviceDaemon(cfg, agents);
|
|
55
|
-
await daemon.start();
|
|
56
|
-
const shutdown = async (signal) => {
|
|
57
|
-
logger.info({ signal }, 'shutting down device daemon');
|
|
58
|
-
await daemon.stop();
|
|
59
|
-
process.exit(0);
|
|
60
|
-
};
|
|
61
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
62
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
63
|
-
}
|
|
64
|
-
async function buildCliDeviceConfig(input) {
|
|
65
|
-
const machineId = input.machineId ?? await getDeviceId();
|
|
66
|
-
const deviceId = input.explicitDeviceId ?? deviceInstanceId(machineId, input.networkId);
|
|
67
|
-
logger.info({ serverUrl: input.serverUrl, deviceId, machineId, networkId: input.networkId, profileId: input.profileId }, 'CLI mode: auto-discovering agents');
|
|
68
|
-
const agents = await discoverAgents(deviceId, input.profileId);
|
|
69
|
-
if (agents.length === 0) {
|
|
70
|
-
logger.warn('no agents discovered on this machine. Daemon will start with no agents.');
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
deviceId,
|
|
74
|
-
machineId,
|
|
75
|
-
profileId: input.profileId,
|
|
76
|
-
networkId: input.networkId,
|
|
77
|
-
server: { url: input.serverUrl, token: input.token },
|
|
78
|
-
heartbeatIntervalMs: 10_000,
|
|
79
|
-
agents,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
async function runDeviceMode(cfgPath) {
|
|
83
|
-
let cfg;
|
|
84
|
-
let scannedEntries;
|
|
85
|
-
try {
|
|
86
|
-
cfg = loadDeviceConfig(cfgPath);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
const shouldScan = err.message?.includes('agents array is required');
|
|
90
|
-
if (!shouldScan)
|
|
91
|
-
throw err;
|
|
92
|
-
let fileSettings = {};
|
|
93
|
-
try {
|
|
94
|
-
const { readFileSync } = await import('node:fs');
|
|
95
|
-
const { load: parseYaml } = await import('js-yaml');
|
|
96
|
-
const raw = parseYaml(readFileSync(cfgPath, 'utf8'));
|
|
97
|
-
fileSettings = {
|
|
98
|
-
deviceId: raw.deviceId,
|
|
99
|
-
networkId: raw.networkId,
|
|
100
|
-
server: raw.server,
|
|
101
|
-
heartbeatIntervalMs: raw.heartbeatIntervalMs,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
catch { /* ignore */ }
|
|
105
|
-
const deviceId = fileSettings.deviceId ?? process.env.DEVICE_ID ?? await getDeviceId();
|
|
106
|
-
scannedEntries = await discoverAgents(deviceId);
|
|
107
|
-
if (scannedEntries.length === 0) {
|
|
108
|
-
throw new Error('device config missing and no agents discovered via scanning');
|
|
109
|
-
}
|
|
110
|
-
cfg = {
|
|
111
|
-
deviceId,
|
|
112
|
-
networkId: fileSettings.networkId ?? process.env.NETWORK_ID ?? 'default',
|
|
113
|
-
server: fileSettings.server ?? {
|
|
114
|
-
url: process.env.SERVER_URL ?? 'http://localhost:3000/agent',
|
|
115
|
-
token: process.env.SERVER_TOKEN ?? '',
|
|
116
|
-
},
|
|
117
|
-
heartbeatIntervalMs: fileSettings.heartbeatIntervalMs ?? 10_000,
|
|
118
|
-
agents: scannedEntries,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
if (cfg.scan === true) {
|
|
122
|
-
scannedEntries = await discoverAgents(cfg.deviceId);
|
|
123
|
-
if (scannedEntries.length > 0) {
|
|
124
|
-
cfg = { ...cfg, agents: scannedEntries };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
await startDeviceDaemon(cfg);
|
|
128
|
-
}
|
|
129
|
-
async function runSingleAgentMode(cfgPath) {
|
|
130
|
-
const cfg = loadConfig(cfgPath);
|
|
131
|
-
const adapter = pickAdapter(cfg.adapter);
|
|
132
|
-
logger.info({ id: cfg.id, kind: cfg.adapter.kind }, 'agent daemon starting (single-agent mode)');
|
|
133
|
-
const conn = createConnection(cfg, adapter);
|
|
134
|
-
await conn.start();
|
|
135
|
-
const shutdown = async (signal) => {
|
|
136
|
-
logger.info({ signal }, 'shutting down');
|
|
137
|
-
await conn.stop();
|
|
138
|
-
process.exit(0);
|
|
139
|
-
};
|
|
140
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
141
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
142
|
-
}
|
|
143
|
-
async function runCliMode() {
|
|
144
|
-
const { values } = parseArgs({
|
|
145
|
-
options: {
|
|
146
|
-
'server-url': { type: 'string' },
|
|
147
|
-
'token': { type: 'string' },
|
|
148
|
-
'invite': { type: 'string' },
|
|
149
|
-
'device-id': { type: 'string' },
|
|
150
|
-
'network-id': { type: 'string' },
|
|
151
|
-
'profile': { type: 'string' },
|
|
152
|
-
'all-profiles': { type: 'boolean' },
|
|
153
|
-
'help': { type: 'boolean' },
|
|
154
|
-
},
|
|
155
|
-
strict: true,
|
|
156
|
-
});
|
|
157
|
-
if (values.help) {
|
|
158
|
-
console.log(`Usage: agentbean-daemon --server-url <url> --token <token> [--device-id <id>] [--network-id <id>] [--profile <id>]
|
|
159
|
-
|
|
160
|
-
Options:
|
|
161
|
-
--server-url AgentBean Server URL (required)
|
|
162
|
-
--token Authentication token (required)
|
|
163
|
-
--device-id Device ID (default: auto-detected from hardware)
|
|
164
|
-
--network-id Team ID (default: default)
|
|
165
|
-
--profile Team profile for local auth/cache isolation
|
|
166
|
-
--all-profiles Start one connection for every saved team profile
|
|
167
|
-
`);
|
|
168
|
-
process.exit(0);
|
|
169
|
-
}
|
|
170
|
-
if (values['all-profiles']) {
|
|
171
|
-
const profiles = listAuthProfiles();
|
|
172
|
-
if (profiles.length === 0) {
|
|
173
|
-
console.error('Error: no saved AgentBean team profiles found.');
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
176
|
-
const machineId = values['device-id'] ?? await getDeviceId();
|
|
177
|
-
const configs = await Promise.all(profiles.map((profile) => buildCliDeviceConfig({
|
|
178
|
-
serverUrl: profile.serverUrl,
|
|
179
|
-
token: profile.token,
|
|
180
|
-
networkId: profile.networkId ?? networkIdFromToken(profile.token) ?? 'default',
|
|
181
|
-
machineId,
|
|
182
|
-
profileId: profile.profileId,
|
|
183
|
-
})));
|
|
184
|
-
await Promise.all(configs.map((cfg) => startDeviceDaemon(cfg)));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
let serverUrl = values['server-url'] ?? process.env.AGENT_BEAN_SERVER_URL;
|
|
188
|
-
let token = values['token'] ?? process.env.AGENT_BEAN_AGENT_TOKEN;
|
|
189
|
-
let savedAuth = null;
|
|
190
|
-
let networkId = values['network-id'] ?? 'default';
|
|
191
|
-
let profileId = values.profile ?? process.env.AGENTBEAN_PROFILE;
|
|
192
|
-
if (values.invite) {
|
|
193
|
-
if (!serverUrl) {
|
|
194
|
-
console.error('Error: --server-url is required with --invite.');
|
|
195
|
-
process.exit(1);
|
|
196
|
-
}
|
|
197
|
-
const machineId = values['device-id'] ?? await getDeviceId();
|
|
198
|
-
const auth = await runInviteMode(serverUrl, values.invite, machineId);
|
|
199
|
-
serverUrl = auth.serverUrl;
|
|
200
|
-
token = auth.token;
|
|
201
|
-
networkId = auth.networkId ?? networkId;
|
|
202
|
-
profileId = profileId ?? profileIdForNetwork(networkId);
|
|
203
|
-
saveAuth(auth, { profileId });
|
|
204
|
-
}
|
|
205
|
-
else if (!token) {
|
|
206
|
-
savedAuth = loadAuth({ profileId });
|
|
207
|
-
if (savedAuth) {
|
|
208
|
-
serverUrl = serverUrl ?? savedAuth.serverUrl;
|
|
209
|
-
token = savedAuth.token;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (!serverUrl || !token) {
|
|
213
|
-
console.error('Error: --server-url and --token are required.');
|
|
214
|
-
console.error('Usage: agentbean-daemon --server-url <url> --token <token>');
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
const tokenNetworkId = networkIdFromToken(token);
|
|
218
|
-
if (values['network-id'] && tokenNetworkId && values['network-id'] !== tokenNetworkId) {
|
|
219
|
-
console.error('Error: --network-id does not match the provided token.');
|
|
220
|
-
console.error('Use the team ID embedded in the token, or omit --network-id and let the daemon detect it.');
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
networkId = resolveCliNetworkId({
|
|
224
|
-
explicitNetworkId: values['network-id'],
|
|
225
|
-
token,
|
|
226
|
-
savedNetworkId: savedAuth?.networkId,
|
|
227
|
-
fallbackNetworkId: networkId,
|
|
228
|
-
});
|
|
229
|
-
profileId = profileId ?? profileIdForNetwork(networkId);
|
|
230
|
-
const machineId = values['device-id'] ?? await getDeviceId();
|
|
231
|
-
const cfg = await buildCliDeviceConfig({
|
|
232
|
-
serverUrl,
|
|
233
|
-
token,
|
|
234
|
-
networkId,
|
|
235
|
-
machineId,
|
|
236
|
-
explicitDeviceId: values['device-id'] ? values['device-id'] : undefined,
|
|
237
|
-
profileId,
|
|
238
|
-
});
|
|
239
|
-
await startDeviceDaemon(cfg);
|
|
240
|
-
}
|
|
241
|
-
function normalizeBaseUrl(serverUrl) {
|
|
242
|
-
return serverUrl.replace(/\/agent\/?$/, '').replace(/\/web\/?$/, '');
|
|
243
|
-
}
|
|
244
|
-
function normalizeAgentUrl(serverUrl) {
|
|
245
|
-
const base = normalizeBaseUrl(serverUrl);
|
|
246
|
-
return `${base}/agent`;
|
|
247
|
-
}
|
|
248
|
-
export function networkIdFromToken(token) {
|
|
249
|
-
const parts = String(token ?? '').split(':');
|
|
250
|
-
if (parts.length !== 3)
|
|
251
|
-
return undefined;
|
|
252
|
-
const networkId = parts[1]?.trim();
|
|
253
|
-
return networkId || undefined;
|
|
254
|
-
}
|
|
255
|
-
export function resolveCliNetworkId(input) {
|
|
256
|
-
return input.explicitNetworkId
|
|
257
|
-
?? networkIdFromToken(input.token)
|
|
258
|
-
?? input.savedNetworkId
|
|
259
|
-
?? input.fallbackNetworkId
|
|
260
|
-
?? 'default';
|
|
261
|
-
}
|
|
262
|
-
export const INVITE_CONNECTION_TIMEOUT_MS = 20_000;
|
|
263
|
-
export function createInviteSocketOptions() {
|
|
264
|
-
return {
|
|
265
|
-
auth: { invite: true },
|
|
266
|
-
reconnection: false,
|
|
267
|
-
timeout: INVITE_CONNECTION_TIMEOUT_MS,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
export function socketErrorMessage(err) {
|
|
271
|
-
const details = [
|
|
272
|
-
err?.message,
|
|
273
|
-
err?.description?.message,
|
|
274
|
-
err?.context?.statusText?.code,
|
|
275
|
-
err?.context?.statusText?.message,
|
|
276
|
-
err?.context?.responseText,
|
|
277
|
-
]
|
|
278
|
-
.filter((value) => typeof value === 'string' && value.trim())
|
|
279
|
-
.map((value) => value.trim());
|
|
280
|
-
return [...new Set(details)].join(': ') || 'unknown socket error';
|
|
281
|
-
}
|
|
282
|
-
async function runInviteMode(serverUrl, inviteCode, deviceId) {
|
|
283
|
-
const { io } = await import('socket.io-client');
|
|
284
|
-
const { execFile } = await import('node:child_process');
|
|
285
|
-
const baseUrl = normalizeBaseUrl(serverUrl);
|
|
286
|
-
const webSocketUrl = `${baseUrl}/web`;
|
|
287
|
-
console.log(`Connecting to AgentBean at ${baseUrl}...`);
|
|
288
|
-
logger.info({ serverUrl: baseUrl, inviteCode }, 'invite mode: connecting to server');
|
|
289
|
-
const socket = io(webSocketUrl, createInviteSocketOptions());
|
|
290
|
-
return new Promise((resolve, reject) => {
|
|
291
|
-
let connectTimer;
|
|
292
|
-
const fail = (err) => {
|
|
293
|
-
clearTimeout(connectTimer);
|
|
294
|
-
socket.disconnect();
|
|
295
|
-
reject(err);
|
|
296
|
-
};
|
|
297
|
-
connectTimer = setTimeout(() => {
|
|
298
|
-
fail(new Error(`connection timed out after 20s. Check network access to ${baseUrl} and try again.`));
|
|
299
|
-
}, INVITE_CONNECTION_TIMEOUT_MS);
|
|
300
|
-
socket.on('connect_error', (err) => {
|
|
301
|
-
const message = socketErrorMessage(err);
|
|
302
|
-
logger.error({ err: message }, 'invite mode: connection failed');
|
|
303
|
-
fail(new Error(`connection failed: ${message}`));
|
|
304
|
-
});
|
|
305
|
-
socket.on('connect', () => {
|
|
306
|
-
clearTimeout(connectTimer);
|
|
307
|
-
logger.info('invite mode: connected, validating invite code');
|
|
308
|
-
console.log('Connected. Validating invite code...');
|
|
309
|
-
socket.emit('auth:invite:validate', { code: inviteCode, deviceId }, (res) => {
|
|
310
|
-
if (!res?.ok) {
|
|
311
|
-
fail(new Error(res?.error ?? 'invalid invite code'));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const registerUrl = res.registerUrl;
|
|
315
|
-
logger.info({ registerUrl }, 'invite mode: opening browser');
|
|
316
|
-
console.log(`\nOpen this URL to finish joining AgentBean:\n${registerUrl}\n`);
|
|
317
|
-
execFile('open', [registerUrl], (err) => {
|
|
318
|
-
if (err) {
|
|
319
|
-
logger.info({ registerUrl }, 'invite mode: could not open browser automatically');
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
console.log('Waiting for registration to complete...');
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
socket.on('auth:token:deliver', (payload) => {
|
|
326
|
-
if (!payload?.token)
|
|
327
|
-
return;
|
|
328
|
-
const auth = {
|
|
329
|
-
token: payload.token,
|
|
330
|
-
serverUrl: normalizeAgentUrl(serverUrl),
|
|
331
|
-
userId: payload.userId,
|
|
332
|
-
networkId: payload.networkId,
|
|
333
|
-
};
|
|
334
|
-
logger.info({ networkId: auth.networkId }, 'invite mode: token received and saved');
|
|
335
|
-
console.log('Registration complete! Starting daemon...');
|
|
336
|
-
socket.disconnect();
|
|
337
|
-
resolve(auth);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
export async function main() {
|
|
342
|
-
// Check for CLI flags first (npx mode)
|
|
343
|
-
const hasCliFlags = process.argv.some((a) => a === '--server-url' || a === '--token' || a === '--invite' || a === '--profile' || a === '--all-profiles' || a === '--help');
|
|
344
|
-
if (hasCliFlags) {
|
|
345
|
-
await runCliMode();
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
// Fallback: YAML config mode
|
|
349
|
-
const cfgPath = process.env.DEVICE_CONFIG ?? process.env.AGENT_CONFIG ?? './agent.config.yaml';
|
|
350
|
-
try {
|
|
351
|
-
await runDeviceMode(cfgPath);
|
|
352
|
-
}
|
|
353
|
-
catch (deviceErr) {
|
|
354
|
-
if (deviceErr.message?.includes('deviceId') || deviceErr.message?.includes('agents')) {
|
|
355
|
-
logger.info({ reason: deviceErr.message }, 'not a device config, falling back to single-agent mode');
|
|
356
|
-
await runSingleAgentMode(cfgPath);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
throw deviceErr;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
364
|
-
main().catch((err) => {
|
|
365
|
-
console.error('fatal:', err.message);
|
|
366
|
-
process.exit(1);
|
|
367
|
-
});
|
|
368
|
-
}
|
package/dist/log.js
DELETED
package/dist/post-process.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { readdirSync, realpathSync, statSync, existsSync } from 'node:fs';
|
|
2
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { logger } from './log.js';
|
|
5
|
-
const CODE_BLOCK_RE = /```python\n([\s\S]*?)```/g;
|
|
6
|
-
const CODEX_IMG_DIR = join(homedir(), '.codex', 'generated_images');
|
|
7
|
-
const OUTPUT_FILE_EXT_RE = /\.(png|jpe?g|gif|webp|svg|pdf|txt|csv|json|md|mp4|mov|zip)$/i;
|
|
8
|
-
const IGNORED_OUTPUT_DIRS = new Set([
|
|
9
|
-
'.git',
|
|
10
|
-
'.hg',
|
|
11
|
-
'.svn',
|
|
12
|
-
'.cache',
|
|
13
|
-
'.next',
|
|
14
|
-
'.nuxt',
|
|
15
|
-
'.turbo',
|
|
16
|
-
'node_modules',
|
|
17
|
-
'vendor',
|
|
18
|
-
]);
|
|
19
|
-
const MAX_OUTPUT_FILES_PER_ROOT = 2000;
|
|
20
|
-
const OUTPUT_DIR_ENV = 'AGENT_BEAN_OUTPUT_DIRS';
|
|
21
|
-
export function listAllFiles(dir, maxDepth = 10, depth = 0) {
|
|
22
|
-
if (!existsSync(dir) || depth > maxDepth)
|
|
23
|
-
return [];
|
|
24
|
-
const results = [];
|
|
25
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
-
const full = join(dir, entry.name);
|
|
27
|
-
if (entry.isSymbolicLink())
|
|
28
|
-
continue;
|
|
29
|
-
if (entry.isDirectory()) {
|
|
30
|
-
if (IGNORED_OUTPUT_DIRS.has(entry.name))
|
|
31
|
-
continue;
|
|
32
|
-
results.push(...listAllFiles(full, maxDepth, depth + 1));
|
|
33
|
-
}
|
|
34
|
-
else
|
|
35
|
-
results.push(full);
|
|
36
|
-
if (results.length >= MAX_OUTPUT_FILES_PER_ROOT)
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
return results;
|
|
40
|
-
}
|
|
41
|
-
function normalizeCandidatePath(raw) {
|
|
42
|
-
const cleaned = raw
|
|
43
|
-
.trim()
|
|
44
|
-
.replace(/^file:\/\//, '')
|
|
45
|
-
.replace(/^["'`<({\[]+/, '')
|
|
46
|
-
.replace(/["'`>)}\].,;:]+$/, '');
|
|
47
|
-
if (!cleaned || !OUTPUT_FILE_EXT_RE.test(cleaned))
|
|
48
|
-
return null;
|
|
49
|
-
return cleaned.replace(/^~(?=$|\/)/, homedir());
|
|
50
|
-
}
|
|
51
|
-
function extractMentionedFiles(reply, workspace, dispatchStart) {
|
|
52
|
-
const candidates = new Set();
|
|
53
|
-
const markdownLinkRe = /!?\[[^\]]*]\(([^)\s]+)\)/g;
|
|
54
|
-
const plainPathRe = /(?:^|[\s"'`(<::])((?:~?\/|\.{1,2}\/)?[\w@%+=:,./-]+\.(?:png|jpe?g|gif|webp|svg|pdf|txt|csv|json|md|mp4|mov|zip))(?:$|[\s"'`)>.,;::])/gim;
|
|
55
|
-
let match;
|
|
56
|
-
while ((match = markdownLinkRe.exec(reply)) !== null) {
|
|
57
|
-
const normalized = normalizeCandidatePath(match[1]);
|
|
58
|
-
if (normalized)
|
|
59
|
-
candidates.add(normalized);
|
|
60
|
-
}
|
|
61
|
-
while ((match = plainPathRe.exec(reply)) !== null) {
|
|
62
|
-
const normalized = normalizeCandidatePath(match[1]);
|
|
63
|
-
if (normalized)
|
|
64
|
-
candidates.add(normalized);
|
|
65
|
-
}
|
|
66
|
-
const files = [];
|
|
67
|
-
for (const candidate of candidates) {
|
|
68
|
-
const abs = isAbsolute(candidate) ? candidate : workspace ? resolve(workspace, candidate) : null;
|
|
69
|
-
if (!abs)
|
|
70
|
-
continue;
|
|
71
|
-
try {
|
|
72
|
-
const st = statSync(abs);
|
|
73
|
-
if (st.isFile())
|
|
74
|
-
files.push(abs);
|
|
75
|
-
}
|
|
76
|
-
catch { }
|
|
77
|
-
}
|
|
78
|
-
return files;
|
|
79
|
-
}
|
|
80
|
-
function outputDirsFromEnv() {
|
|
81
|
-
const raw = process.env[OUTPUT_DIR_ENV];
|
|
82
|
-
if (!raw?.trim())
|
|
83
|
-
return [];
|
|
84
|
-
return raw
|
|
85
|
-
.split(/[,:;]/)
|
|
86
|
-
.map((item) => item.trim())
|
|
87
|
-
.filter(Boolean);
|
|
88
|
-
}
|
|
89
|
-
function canonicalPath(path) {
|
|
90
|
-
try {
|
|
91
|
-
return realpathSync(path);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return path;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function resolveOutputRoots(workspace, outputDirs = [], includeWorkspace = false) {
|
|
98
|
-
const roots = new Set();
|
|
99
|
-
if (workspace && includeWorkspace)
|
|
100
|
-
roots.add(resolve(workspace));
|
|
101
|
-
for (const raw of [...outputDirs, ...outputDirsFromEnv()]) {
|
|
102
|
-
const expanded = raw.replace(/^~(?=$|\/)/, homedir());
|
|
103
|
-
const root = isAbsolute(expanded)
|
|
104
|
-
? expanded
|
|
105
|
-
: workspace
|
|
106
|
-
? resolve(workspace, expanded)
|
|
107
|
-
: resolve(expanded);
|
|
108
|
-
roots.add(root);
|
|
109
|
-
}
|
|
110
|
-
return [...roots].filter((root) => {
|
|
111
|
-
try {
|
|
112
|
-
return statSync(root).isDirectory();
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
function collectRecentOutputFiles(roots, dispatchStart) {
|
|
120
|
-
const files = new Set();
|
|
121
|
-
for (const root of roots) {
|
|
122
|
-
for (const filePath of listAllFiles(root, 8)) {
|
|
123
|
-
if (!OUTPUT_FILE_EXT_RE.test(filePath))
|
|
124
|
-
continue;
|
|
125
|
-
try {
|
|
126
|
-
const st = statSync(filePath);
|
|
127
|
-
if (st.isFile() && st.mtimeMs > dispatchStart) {
|
|
128
|
-
files.add(canonicalPath(filePath));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch { }
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return [...files];
|
|
135
|
-
}
|
|
136
|
-
export async function postProcess(reply, workspace, kind, dispatchStart, options = {}) {
|
|
137
|
-
const outputFiles = new Set();
|
|
138
|
-
// Codex native image detection
|
|
139
|
-
if (kind === 'codex') {
|
|
140
|
-
const allCodexFiles = listAllFiles(CODEX_IMG_DIR, 4);
|
|
141
|
-
for (const f of allCodexFiles) {
|
|
142
|
-
if (!OUTPUT_FILE_EXT_RE.test(f))
|
|
143
|
-
continue;
|
|
144
|
-
try {
|
|
145
|
-
const st = statSync(f);
|
|
146
|
-
if (st.mtimeMs > dispatchStart) {
|
|
147
|
-
outputFiles.add(canonicalPath(f));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
catch { }
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
for (const filePath of extractMentionedFiles(reply, workspace, dispatchStart)) {
|
|
154
|
-
outputFiles.add(canonicalPath(filePath));
|
|
155
|
-
}
|
|
156
|
-
for (const filePath of collectRecentOutputFiles(resolveOutputRoots(workspace, options.outputDirs, options.scanWorkspace), dispatchStart)) {
|
|
157
|
-
outputFiles.add(filePath);
|
|
158
|
-
}
|
|
159
|
-
// Extract code blocks for logging but do NOT auto-execute (security)
|
|
160
|
-
if (workspace) {
|
|
161
|
-
const codeBlocks = [];
|
|
162
|
-
let m;
|
|
163
|
-
const re = new RegExp(CODE_BLOCK_RE.source, 'g');
|
|
164
|
-
while ((m = re.exec(reply)) !== null) {
|
|
165
|
-
codeBlocks.push(m[1]);
|
|
166
|
-
}
|
|
167
|
-
if (codeBlocks.length > 0) {
|
|
168
|
-
logger.info({ count: codeBlocks.length }, 'code blocks detected but not executed (auto-exec disabled)');
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
let replyText = reply;
|
|
172
|
-
const files = [...outputFiles];
|
|
173
|
-
if (files.length > 0) {
|
|
174
|
-
replyText += '\n\n已生成文件:\n' + files.map((f) => `- ${f}`).join('\n');
|
|
175
|
-
}
|
|
176
|
-
return { replyText, outputFiles: files };
|
|
177
|
-
}
|
package/dist/profile-paths.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
export function agentbeanHome() {
|
|
6
|
-
return process.env.AGENTBEAN_HOME?.trim() || join(homedir(), '.agentbean');
|
|
7
|
-
}
|
|
8
|
-
export function profileIdForNetwork(networkId) {
|
|
9
|
-
const source = networkId?.trim() || 'default';
|
|
10
|
-
const slug = source.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
11
|
-
return slug || 'default';
|
|
12
|
-
}
|
|
13
|
-
export function profileRoot(profileId) {
|
|
14
|
-
const explicitDir = process.env.AGENTBEAN_PROFILE_DIR?.trim();
|
|
15
|
-
if (explicitDir && profileId)
|
|
16
|
-
return explicitDir;
|
|
17
|
-
if (!profileId)
|
|
18
|
-
return agentbeanHome();
|
|
19
|
-
return join(agentbeanHome(), 'teams', profileIdForNetwork(profileId));
|
|
20
|
-
}
|
|
21
|
-
export function ensureProfileRoot(profileId) {
|
|
22
|
-
const root = profileRoot(profileId);
|
|
23
|
-
if (!existsSync(root))
|
|
24
|
-
mkdirSync(root, { recursive: true });
|
|
25
|
-
return root;
|
|
26
|
-
}
|
|
27
|
-
export function authFile(profileId) {
|
|
28
|
-
return join(profileRoot(profileId), 'auth.json');
|
|
29
|
-
}
|
|
30
|
-
export function scanCacheFile(profileId) {
|
|
31
|
-
return join(profileRoot(profileId), 'scanned-agents.json');
|
|
32
|
-
}
|
|
33
|
-
export function localAgentsDir(profileId) {
|
|
34
|
-
return join(profileRoot(profileId), 'agents');
|
|
35
|
-
}
|
|
36
|
-
export function deviceInstanceId(machineId, networkId) {
|
|
37
|
-
const hash = createHash('sha256').update(`${networkId}:${machineId}`).digest('hex');
|
|
38
|
-
return `dev_${hash.slice(0, 24)}`;
|
|
39
|
-
}
|
package/dist/sandbox.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
function escapeSchemeString(value) {
|
|
5
|
-
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
6
|
-
}
|
|
7
|
-
export function getWorkspaceDir(agentId) {
|
|
8
|
-
const dir = join(homedir(), '.agentbean', 'workspaces', agentId);
|
|
9
|
-
if (!existsSync(dir))
|
|
10
|
-
mkdirSync(dir, { recursive: true });
|
|
11
|
-
return dir;
|
|
12
|
-
}
|
|
13
|
-
export function isSandboxAvailable() {
|
|
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
|
-
}
|
|
23
|
-
}
|
|
24
|
-
export function generateSandboxProfile(agentId, runtimePath, writableDirs = []) {
|
|
25
|
-
const workspaceDir = getWorkspaceDir(agentId);
|
|
26
|
-
const runtimeDir = runtimePath.includes('/') ? dirname(runtimePath) : '/usr/bin';
|
|
27
|
-
const profilePath = `/tmp/agentbean-sandbox-${agentId}.sb`;
|
|
28
|
-
const extraWritableRules = writableDirs
|
|
29
|
-
.filter(Boolean)
|
|
30
|
-
.map((dir) => `(allow file-read* file-write*
|
|
31
|
-
(subpath "${escapeSchemeString(dir)}"))`)
|
|
32
|
-
.join('\n');
|
|
33
|
-
const profile = `(version 1)
|
|
34
|
-
(allow file-read* file-write*
|
|
35
|
-
(subpath "${escapeSchemeString(workspaceDir)}"))
|
|
36
|
-
(allow file-read* file-write*
|
|
37
|
-
(subpath "/tmp"))
|
|
38
|
-
${extraWritableRules ? `${extraWritableRules}\n` : ''}(allow file-read*
|
|
39
|
-
(subpath "${escapeSchemeString(runtimeDir)}"))
|
|
40
|
-
(allow file-read*
|
|
41
|
-
(subpath "/bin")
|
|
42
|
-
(subpath "/usr/bin")
|
|
43
|
-
(subpath "/usr/local/bin")
|
|
44
|
-
(subpath "/opt/homebrew/bin"))
|
|
45
|
-
(allow network-outbound
|
|
46
|
-
(remote tcp "api.anthropic.com" 443))
|
|
47
|
-
(allow network-outbound
|
|
48
|
-
(remote tcp "api.openai.com" 443))
|
|
49
|
-
(deny default)
|
|
50
|
-
`;
|
|
51
|
-
writeFileSync(profilePath, profile);
|
|
52
|
-
return profilePath;
|
|
53
|
-
}
|