@agentbean/daemon 0.1.34 → 0.1.35
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/auth-store.js +34 -14
- package/dist/device-daemon.js +19 -18
- package/dist/index.js +60 -21
- package/dist/profile-paths.js +39 -0
- package/dist/scanner.js +8 -6
- package/package.json +1 -1
package/dist/auth-store.js
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { homedir } from 'node:os';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!existsSync(
|
|
3
|
+
import { agentbeanHome, authFile, ensureProfileRoot, profileIdForNetwork } from './profile-paths.js';
|
|
4
|
+
export function loadAuth(options = {}) {
|
|
5
|
+
const file = authFile(options.profileId);
|
|
6
|
+
if (!existsSync(file))
|
|
8
7
|
return null;
|
|
9
8
|
try {
|
|
10
|
-
return JSON.parse(readFileSync(
|
|
9
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
11
10
|
}
|
|
12
11
|
catch {
|
|
13
12
|
return null;
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
|
-
export function saveAuth(data) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
writeFileSync(
|
|
15
|
+
export function saveAuth(data, options = {}) {
|
|
16
|
+
const profileId = options.profileId ?? null;
|
|
17
|
+
ensureProfileRoot(profileId);
|
|
18
|
+
writeFileSync(authFile(profileId), JSON.stringify(data, null, 2));
|
|
20
19
|
}
|
|
21
|
-
export function clearAuth() {
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
export function clearAuth(options = {}) {
|
|
21
|
+
const file = authFile(options.profileId);
|
|
22
|
+
if (existsSync(file))
|
|
23
|
+
unlinkSync(file);
|
|
24
|
+
}
|
|
25
|
+
export function listAuthProfiles() {
|
|
26
|
+
const profiles = [];
|
|
27
|
+
const teamsDir = join(agentbeanHome(), 'teams');
|
|
28
|
+
if (existsSync(teamsDir)) {
|
|
29
|
+
for (const entry of readdirSync(teamsDir, { withFileTypes: true })) {
|
|
30
|
+
if (!entry.isDirectory())
|
|
31
|
+
continue;
|
|
32
|
+
const auth = loadAuth({ profileId: entry.name });
|
|
33
|
+
if (auth?.networkId)
|
|
34
|
+
profiles.push({ ...auth, profileId: entry.name });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const legacy = loadAuth();
|
|
38
|
+
if (legacy?.networkId) {
|
|
39
|
+
const profileId = profileIdForNetwork(legacy.networkId);
|
|
40
|
+
if (!profiles.some((profile) => profile.profileId === profileId))
|
|
41
|
+
profiles.push({ ...legacy, profileId });
|
|
42
|
+
}
|
|
43
|
+
return profiles;
|
|
24
44
|
}
|
package/dist/device-daemon.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { io } from 'socket.io-client';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
3
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { basename, isAbsolute,
|
|
5
|
-
import { homedir } from 'node:os';
|
|
4
|
+
import { basename, isAbsolute, dirname } from 'node:path';
|
|
6
5
|
import { promisify } from 'node:util';
|
|
7
6
|
import { logger } from './log.js';
|
|
8
7
|
import { AgentInstance } from './agent-instance.js';
|
|
9
8
|
import { pickAdapter } from './adapters/factory.js';
|
|
10
9
|
import { scanRuntimes, scanAgentOSAgents, scanLocalAgents, collectSystemInfo } from './scanner.js';
|
|
11
10
|
import { syncWorkspaceArtifacts } from './workspace-sync.js';
|
|
11
|
+
import { scanCacheFile } from './profile-paths.js';
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
function errorMessage(err) {
|
|
14
14
|
if (err instanceof Error && err.message)
|
|
@@ -86,10 +86,6 @@ export function nativeDirectoryPickerCommands(platform = process.platform) {
|
|
|
86
86
|
return [{
|
|
87
87
|
command: 'osascript',
|
|
88
88
|
args: [
|
|
89
|
-
'-e',
|
|
90
|
-
'tell application "Finder" to activate',
|
|
91
|
-
'-e',
|
|
92
|
-
'delay 0.2',
|
|
93
89
|
'-e',
|
|
94
90
|
'POSIX path of (choose folder with prompt "选择项目目录" default location (path to home folder))',
|
|
95
91
|
],
|
|
@@ -140,8 +136,6 @@ export async function selectNativeDirectory(commands = nativeDirectoryPickerComm
|
|
|
140
136
|
}
|
|
141
137
|
throw new Error(lastError ? `directory picker command not available: ${errorMessage(lastError)}` : 'directory picker command not available');
|
|
142
138
|
}
|
|
143
|
-
const CACHE_DIR = join(homedir(), '.agentbean');
|
|
144
|
-
const CACHE_FILE = join(CACHE_DIR, 'scanned-agents.json');
|
|
145
139
|
function isRuntimeEntry(entry) {
|
|
146
140
|
return entry.category === 'executor-hosted' &&
|
|
147
141
|
['codex', 'claude-code', 'kimi-cli', 'Kimi-cli'].includes(entry.adapterKind);
|
|
@@ -164,11 +158,12 @@ function splitLegacyCache(entries) {
|
|
|
164
158
|
}
|
|
165
159
|
return { agents, runtimes };
|
|
166
160
|
}
|
|
167
|
-
function loadCache() {
|
|
161
|
+
function loadCache(profileId) {
|
|
168
162
|
try {
|
|
169
|
-
|
|
163
|
+
const cacheFile = scanCacheFile(profileId);
|
|
164
|
+
if (!existsSync(cacheFile))
|
|
170
165
|
return null;
|
|
171
|
-
const parsed = JSON.parse(readFileSync(
|
|
166
|
+
const parsed = JSON.parse(readFileSync(cacheFile, 'utf-8'));
|
|
172
167
|
if (Array.isArray(parsed))
|
|
173
168
|
return splitLegacyCache(parsed);
|
|
174
169
|
return {
|
|
@@ -180,11 +175,13 @@ function loadCache() {
|
|
|
180
175
|
return null;
|
|
181
176
|
}
|
|
182
177
|
}
|
|
183
|
-
function saveCache(payload) {
|
|
178
|
+
function saveCache(payload, profileId) {
|
|
184
179
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
180
|
+
const cacheFile = scanCacheFile(profileId);
|
|
181
|
+
const cacheDir = dirname(cacheFile);
|
|
182
|
+
if (!existsSync(cacheDir))
|
|
183
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
184
|
+
writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
|
|
188
185
|
}
|
|
189
186
|
catch (err) {
|
|
190
187
|
logger.warn({ err: err?.message }, 'failed to save scan cache');
|
|
@@ -195,6 +192,8 @@ export function createDeviceSocketOptions(input) {
|
|
|
195
192
|
auth: {
|
|
196
193
|
token: input.token,
|
|
197
194
|
deviceId: input.deviceId,
|
|
195
|
+
machineId: input.machineId,
|
|
196
|
+
profileId: input.profileId,
|
|
198
197
|
networkId: input.networkId,
|
|
199
198
|
agents: input.agents,
|
|
200
199
|
systemInfo: input.systemInfo,
|
|
@@ -298,13 +297,13 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
298
297
|
}
|
|
299
298
|
async function scanAndRegister(sock, useCache) {
|
|
300
299
|
if (useCache) {
|
|
301
|
-
const cached = loadCache();
|
|
300
|
+
const cached = loadCache(cfg.profileId);
|
|
302
301
|
if (cached) {
|
|
303
302
|
logger.info({ count: cached.agents.length + cached.runtimes.length }, 'using cached scan results');
|
|
304
303
|
emitRegister(sock, cached);
|
|
305
304
|
// Background refresh — only emit if results differ
|
|
306
305
|
scanAll().then((fresh) => {
|
|
307
|
-
saveCache(fresh);
|
|
306
|
+
saveCache(fresh, cfg.profileId);
|
|
308
307
|
const cachedKey = JSON.stringify([
|
|
309
308
|
...cached.agents.map((a) => a.command),
|
|
310
309
|
...cached.runtimes.map((rt) => rt.command),
|
|
@@ -326,7 +325,7 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
326
325
|
// Full scan (no cache or cache miss)
|
|
327
326
|
try {
|
|
328
327
|
const scanned = await scanAll();
|
|
329
|
-
saveCache(scanned);
|
|
328
|
+
saveCache(scanned, cfg.profileId);
|
|
330
329
|
emitRegister(sock, scanned);
|
|
331
330
|
}
|
|
332
331
|
catch (err) {
|
|
@@ -339,6 +338,8 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
339
338
|
socket = io(agentUrl, createDeviceSocketOptions({
|
|
340
339
|
token: cfg.server.token,
|
|
341
340
|
deviceId: cfg.deviceId,
|
|
341
|
+
machineId: cfg.machineId,
|
|
342
|
+
profileId: cfg.profileId,
|
|
342
343
|
networkId: cfg.networkId,
|
|
343
344
|
agents: publicAgents,
|
|
344
345
|
systemInfo,
|
package/dist/index.js
CHANGED
|
@@ -7,16 +7,17 @@ import { AgentInstance } from './agent-instance.js';
|
|
|
7
7
|
import { pickAdapter } from './adapters/factory.js';
|
|
8
8
|
import { logger } from './log.js';
|
|
9
9
|
import { scanRuntimes, scanAgentOSAgents, scanLocalAgents, getDeviceId } from './scanner.js';
|
|
10
|
-
import { loadAuth, saveAuth } from './auth-store.js';
|
|
10
|
+
import { listAuthProfiles, loadAuth, saveAuth } from './auth-store.js';
|
|
11
|
+
import { deviceInstanceId, localAgentsDir, profileIdForNetwork } from './profile-paths.js';
|
|
11
12
|
export function discoveredAgentId(name, deviceId) {
|
|
12
13
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
13
14
|
return deviceId ? `scan-${deviceId}-${slug}` : slug;
|
|
14
15
|
}
|
|
15
|
-
async function discoverAgents(deviceId) {
|
|
16
|
+
async function discoverAgents(deviceId, profileId) {
|
|
16
17
|
const [_runtimes, agentos, local] = await Promise.all([
|
|
17
18
|
scanRuntimes(),
|
|
18
19
|
scanAgentOSAgents(),
|
|
19
|
-
scanLocalAgents(),
|
|
20
|
+
scanLocalAgents(profileId ? localAgentsDir(profileId) : undefined),
|
|
20
21
|
]);
|
|
21
22
|
const seen = new Set();
|
|
22
23
|
const results = [];
|
|
@@ -60,6 +61,24 @@ async function startDeviceDaemon(cfg) {
|
|
|
60
61
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
61
62
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
62
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
|
+
}
|
|
63
82
|
async function runDeviceMode(cfgPath) {
|
|
64
83
|
let cfg;
|
|
65
84
|
let scannedEntries;
|
|
@@ -129,38 +148,62 @@ async function runCliMode() {
|
|
|
129
148
|
'invite': { type: 'string' },
|
|
130
149
|
'device-id': { type: 'string' },
|
|
131
150
|
'network-id': { type: 'string' },
|
|
151
|
+
'profile': { type: 'string' },
|
|
152
|
+
'all-profiles': { type: 'boolean' },
|
|
132
153
|
'help': { type: 'boolean' },
|
|
133
154
|
},
|
|
134
155
|
strict: true,
|
|
135
156
|
});
|
|
136
157
|
if (values.help) {
|
|
137
|
-
console.log(`Usage: agentbean-daemon --server-url <url> --token <token> [--device-id <id>] [--network-id <id>]
|
|
158
|
+
console.log(`Usage: agentbean-daemon --server-url <url> --token <token> [--device-id <id>] [--network-id <id>] [--profile <id>]
|
|
138
159
|
|
|
139
160
|
Options:
|
|
140
161
|
--server-url AgentBean Server URL (required)
|
|
141
162
|
--token Authentication token (required)
|
|
142
163
|
--device-id Device ID (default: auto-detected from hardware)
|
|
143
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
|
|
144
167
|
`);
|
|
145
168
|
process.exit(0);
|
|
146
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
|
+
}
|
|
147
187
|
let serverUrl = values['server-url'] ?? process.env.AGENT_BEAN_SERVER_URL;
|
|
148
188
|
let token = values['token'] ?? process.env.AGENT_BEAN_AGENT_TOKEN;
|
|
149
189
|
let savedAuth = null;
|
|
150
190
|
let networkId = values['network-id'] ?? 'default';
|
|
191
|
+
let profileId = values.profile ?? process.env.AGENTBEAN_PROFILE;
|
|
151
192
|
if (values.invite) {
|
|
152
193
|
if (!serverUrl) {
|
|
153
194
|
console.error('Error: --server-url is required with --invite.');
|
|
154
195
|
process.exit(1);
|
|
155
196
|
}
|
|
156
|
-
const
|
|
157
|
-
const auth = await runInviteMode(serverUrl, values.invite,
|
|
197
|
+
const machineId = values['device-id'] ?? await getDeviceId();
|
|
198
|
+
const auth = await runInviteMode(serverUrl, values.invite, machineId);
|
|
158
199
|
serverUrl = auth.serverUrl;
|
|
159
200
|
token = auth.token;
|
|
160
201
|
networkId = auth.networkId ?? networkId;
|
|
202
|
+
profileId = profileId ?? profileIdForNetwork(networkId);
|
|
203
|
+
saveAuth(auth, { profileId });
|
|
161
204
|
}
|
|
162
205
|
else if (!token) {
|
|
163
|
-
savedAuth = loadAuth();
|
|
206
|
+
savedAuth = loadAuth({ profileId });
|
|
164
207
|
if (savedAuth) {
|
|
165
208
|
serverUrl = serverUrl ?? savedAuth.serverUrl;
|
|
166
209
|
token = savedAuth.token;
|
|
@@ -183,19 +226,16 @@ Options:
|
|
|
183
226
|
savedNetworkId: savedAuth?.networkId,
|
|
184
227
|
fallbackNetworkId: networkId,
|
|
185
228
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
const cfg = {
|
|
193
|
-
deviceId,
|
|
229
|
+
profileId = profileId ?? profileIdForNetwork(networkId);
|
|
230
|
+
const machineId = values['device-id'] ?? await getDeviceId();
|
|
231
|
+
const cfg = await buildCliDeviceConfig({
|
|
232
|
+
serverUrl,
|
|
233
|
+
token,
|
|
194
234
|
networkId,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
235
|
+
machineId,
|
|
236
|
+
explicitDeviceId: values['device-id'] ? values['device-id'] : undefined,
|
|
237
|
+
profileId,
|
|
238
|
+
});
|
|
199
239
|
await startDeviceDaemon(cfg);
|
|
200
240
|
}
|
|
201
241
|
function normalizeBaseUrl(serverUrl) {
|
|
@@ -291,7 +331,6 @@ async function runInviteMode(serverUrl, inviteCode, deviceId) {
|
|
|
291
331
|
userId: payload.userId,
|
|
292
332
|
networkId: payload.networkId,
|
|
293
333
|
};
|
|
294
|
-
saveAuth(auth);
|
|
295
334
|
logger.info({ networkId: auth.networkId }, 'invite mode: token received and saved');
|
|
296
335
|
console.log('Registration complete! Starting daemon...');
|
|
297
336
|
socket.disconnect();
|
|
@@ -301,7 +340,7 @@ async function runInviteMode(serverUrl, inviteCode, deviceId) {
|
|
|
301
340
|
}
|
|
302
341
|
export async function main() {
|
|
303
342
|
// Check for CLI flags first (npx mode)
|
|
304
|
-
const hasCliFlags = process.argv.some((a) => a === '--server-url' || a === '--token' || a === '--invite' || a === '--help');
|
|
343
|
+
const hasCliFlags = process.argv.some((a) => a === '--server-url' || a === '--token' || a === '--invite' || a === '--profile' || a === '--all-profiles' || a === '--help');
|
|
305
344
|
if (hasCliFlags) {
|
|
306
345
|
await runCliMode();
|
|
307
346
|
return;
|
|
@@ -0,0 +1,39 @@
|
|
|
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/scanner.js
CHANGED
|
@@ -4,6 +4,7 @@ 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";
|
|
7
|
+
import { agentbeanHome, localAgentsDir } from "./profile-paths.js";
|
|
7
8
|
function readDaemonVersion() {
|
|
8
9
|
try {
|
|
9
10
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
@@ -126,7 +127,7 @@ export function parseOpenClawAgentId(output) {
|
|
|
126
127
|
return null;
|
|
127
128
|
}
|
|
128
129
|
// --- Machine ID (stable per-device identifier) ---
|
|
129
|
-
const MACHINE_ID_FILE = join(
|
|
130
|
+
const MACHINE_ID_FILE = () => join(agentbeanHome(), "device-id");
|
|
130
131
|
function getFirstMacAddress() {
|
|
131
132
|
const ifaces = os.networkInterfaces();
|
|
132
133
|
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
@@ -185,8 +186,9 @@ async function readPlatformMachineId() {
|
|
|
185
186
|
*/
|
|
186
187
|
export async function getDeviceId() {
|
|
187
188
|
// 1. Read cached ID
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
const machineIdFile = MACHINE_ID_FILE();
|
|
190
|
+
if (existsSync(machineIdFile)) {
|
|
191
|
+
const cached = readFileSync(machineIdFile, "utf-8").trim();
|
|
190
192
|
if (cached)
|
|
191
193
|
return cached;
|
|
192
194
|
}
|
|
@@ -221,10 +223,10 @@ export async function getDeviceId() {
|
|
|
221
223
|
}
|
|
222
224
|
// 3. Cache to file
|
|
223
225
|
try {
|
|
224
|
-
const dir =
|
|
226
|
+
const dir = agentbeanHome();
|
|
225
227
|
if (!existsSync(dir))
|
|
226
228
|
mkdirSync(dir, { recursive: true });
|
|
227
|
-
writeFileSync(
|
|
229
|
+
writeFileSync(machineIdFile, deviceId);
|
|
228
230
|
}
|
|
229
231
|
catch {
|
|
230
232
|
// non-fatal
|
|
@@ -308,7 +310,7 @@ export async function scanAgentOSAgents() {
|
|
|
308
310
|
return [hermes, openclaw].filter((a) => a !== null);
|
|
309
311
|
}
|
|
310
312
|
// --- Scan local agent definitions from filesystem ---
|
|
311
|
-
export async function scanLocalAgents(scanDir =
|
|
313
|
+
export async function scanLocalAgents(scanDir = localAgentsDir(process.env.AGENTBEAN_PROFILE)) {
|
|
312
314
|
if (!existsSync(scanDir)) {
|
|
313
315
|
return [];
|
|
314
316
|
}
|