@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/scanner.js
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { readdirSync, readFileSync, statSync, existsSync, writeFileSync, mkdirSync, } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import * as os from "node:os";
|
|
6
|
-
import { logger } from "./log.js";
|
|
7
|
-
import { agentbeanHome, localAgentsDir } from "./profile-paths.js";
|
|
8
|
-
function readDaemonVersion() {
|
|
9
|
-
try {
|
|
10
|
-
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
11
|
-
return typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return "unknown";
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
const DAEMON_VERSION = readDaemonVersion();
|
|
18
|
-
function isExecutableFile(path) {
|
|
19
|
-
try {
|
|
20
|
-
return existsSync(path) && statSync(path).isFile();
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function getExtraPathEntries() {
|
|
27
|
-
return [
|
|
28
|
-
'/usr/local/bin',
|
|
29
|
-
'/opt/homebrew/bin',
|
|
30
|
-
join(os.homedir(), '.local/bin'),
|
|
31
|
-
join(os.homedir(), '.bun/bin'),
|
|
32
|
-
join(os.homedir(), '.npm-global/bin'),
|
|
33
|
-
join(os.homedir(), '.asdf/shims'),
|
|
34
|
-
join(os.homedir(), '.local/share/mise/shims'),
|
|
35
|
-
...getAllNodeVersions().map((version) => join(os.homedir(), '.nvm/versions/node', version, 'bin')),
|
|
36
|
-
];
|
|
37
|
-
}
|
|
38
|
-
function which(bin, candidatePaths = []) {
|
|
39
|
-
return new Promise((resolve) => {
|
|
40
|
-
for (const candidate of candidatePaths) {
|
|
41
|
-
if (isExecutableFile(candidate)) {
|
|
42
|
-
resolve(candidate);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const child = execFile('which', [bin], { timeout: 5_000, env: { ...process.env, PATH: [process.env.PATH, ...getExtraPathEntries()].filter(Boolean).join(':') } }, (err, stdout) => {
|
|
47
|
-
if (err) {
|
|
48
|
-
resolve(null);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const path = stdout.trim();
|
|
52
|
-
resolve(path.length > 0 ? path : null);
|
|
53
|
-
});
|
|
54
|
-
child.on('error', () => resolve(null));
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
function getAllNodeVersions() {
|
|
58
|
-
try {
|
|
59
|
-
const nvmDir = join(os.homedir(), '.nvm/versions/node');
|
|
60
|
-
if (!existsSync(nvmDir))
|
|
61
|
-
return [];
|
|
62
|
-
return readdirSync(nvmDir);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function getClaudeCodeCandidates() {
|
|
69
|
-
const latestDir = join(os.homedir(), '.local/share/claude-latest');
|
|
70
|
-
const legacyDir = join(os.homedir(), '.local/share/claude');
|
|
71
|
-
const candidates = [
|
|
72
|
-
join(latestDir, 'current/claude'),
|
|
73
|
-
join(legacyDir, 'current/claude'),
|
|
74
|
-
'/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js',
|
|
75
|
-
'/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js',
|
|
76
|
-
];
|
|
77
|
-
for (const base of [latestDir, legacyDir]) {
|
|
78
|
-
const versionsDir = join(base, 'versions');
|
|
79
|
-
try {
|
|
80
|
-
if (!existsSync(versionsDir))
|
|
81
|
-
continue;
|
|
82
|
-
const versions = readdirSync(versionsDir).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
83
|
-
for (const version of versions)
|
|
84
|
-
candidates.push(join(versionsDir, version, 'claude'));
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Ignore unreadable version directories and continue with other candidates.
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return candidates;
|
|
91
|
-
}
|
|
92
|
-
function run(bin, args) {
|
|
93
|
-
return new Promise((resolve) => {
|
|
94
|
-
const child = execFile(bin, args, { timeout: 10_000 }, (err, stdout) => {
|
|
95
|
-
resolve(stdout?.trim() ?? "");
|
|
96
|
-
});
|
|
97
|
-
child.on("error", () => resolve(""));
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
export function parseOpenClawAgentId(output) {
|
|
101
|
-
if (!output.trim())
|
|
102
|
-
return null;
|
|
103
|
-
try {
|
|
104
|
-
const parsed = JSON.parse(output);
|
|
105
|
-
const list = Array.isArray(parsed)
|
|
106
|
-
? parsed
|
|
107
|
-
: Array.isArray(parsed?.agents)
|
|
108
|
-
? parsed.agents
|
|
109
|
-
: Array.isArray(parsed?.items)
|
|
110
|
-
? parsed.items
|
|
111
|
-
: [];
|
|
112
|
-
for (const item of list) {
|
|
113
|
-
const id = typeof item === 'string'
|
|
114
|
-
? item
|
|
115
|
-
: typeof item?.id === 'string'
|
|
116
|
-
? item.id
|
|
117
|
-
: typeof item?.agentId === 'string'
|
|
118
|
-
? item.agentId
|
|
119
|
-
: null;
|
|
120
|
-
if (id?.trim())
|
|
121
|
-
return id.trim();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
// --- Machine ID (stable per-device identifier) ---
|
|
130
|
-
const MACHINE_ID_FILE = () => join(agentbeanHome(), "device-id");
|
|
131
|
-
function getFirstMacAddress() {
|
|
132
|
-
const ifaces = os.networkInterfaces();
|
|
133
|
-
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
134
|
-
if (!addrs)
|
|
135
|
-
continue;
|
|
136
|
-
for (const addr of addrs) {
|
|
137
|
-
// Skip internal (loopback) and zero MAC
|
|
138
|
-
if (addr.internal)
|
|
139
|
-
continue;
|
|
140
|
-
if (addr.mac === "00:00:00:00:00:00")
|
|
141
|
-
continue;
|
|
142
|
-
return addr.mac;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
async function readPlatformMachineId() {
|
|
148
|
-
const platform = os.platform();
|
|
149
|
-
try {
|
|
150
|
-
if (platform === "linux") {
|
|
151
|
-
if (existsSync("/etc/machine-id")) {
|
|
152
|
-
return readFileSync("/etc/machine-id", "utf-8").trim() || null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (platform === "darwin") {
|
|
156
|
-
const output = await run("ioreg", [
|
|
157
|
-
"-rd1",
|
|
158
|
-
"-c",
|
|
159
|
-
"IOPlatformExpertDevice",
|
|
160
|
-
]);
|
|
161
|
-
const match = output.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
162
|
-
if (match)
|
|
163
|
-
return match[1] ?? null;
|
|
164
|
-
}
|
|
165
|
-
else if (platform === "win32") {
|
|
166
|
-
const output = await run("reg", [
|
|
167
|
-
"query",
|
|
168
|
-
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
|
|
169
|
-
"/v",
|
|
170
|
-
"MachineGuid",
|
|
171
|
-
]);
|
|
172
|
-
const match = output.match(/MachineGuid\s+REG_SZ\s+(\S+)/);
|
|
173
|
-
if (match)
|
|
174
|
-
return match[1] ?? null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
// fall through
|
|
179
|
-
}
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Get a stable device ID unique to this machine.
|
|
184
|
-
* Priority: cached file > platform machine-id > MAC address > random UUID
|
|
185
|
-
* Result is cached to ~/.agentbean/device-id
|
|
186
|
-
*/
|
|
187
|
-
export async function getDeviceId() {
|
|
188
|
-
// 1. Read cached ID
|
|
189
|
-
const machineIdFile = MACHINE_ID_FILE();
|
|
190
|
-
if (existsSync(machineIdFile)) {
|
|
191
|
-
const cached = readFileSync(machineIdFile, "utf-8").trim();
|
|
192
|
-
if (cached)
|
|
193
|
-
return cached;
|
|
194
|
-
}
|
|
195
|
-
// 2. Collect hardware fingerprint
|
|
196
|
-
const parts = [];
|
|
197
|
-
const platformId = await readPlatformMachineId();
|
|
198
|
-
if (platformId)
|
|
199
|
-
parts.push(`platform:${platformId}`);
|
|
200
|
-
const mac = getFirstMacAddress();
|
|
201
|
-
if (mac)
|
|
202
|
-
parts.push(`mac:${mac}`);
|
|
203
|
-
parts.push(`hostname:${os.hostname()}`);
|
|
204
|
-
parts.push(`arch:${os.arch()}`);
|
|
205
|
-
parts.push(`platform:${os.platform()}`);
|
|
206
|
-
let deviceId;
|
|
207
|
-
if (parts.length > 2) {
|
|
208
|
-
// We have enough hardware info — generate deterministic ID
|
|
209
|
-
const hash = createHash("sha256").update(parts.join("|")).digest("hex");
|
|
210
|
-
// Format as UUID: 8-4-4-4-12
|
|
211
|
-
deviceId = [
|
|
212
|
-
hash.slice(0, 8),
|
|
213
|
-
hash.slice(8, 12),
|
|
214
|
-
hash.slice(12, 16),
|
|
215
|
-
hash.slice(16, 20),
|
|
216
|
-
hash.slice(20, 32),
|
|
217
|
-
].join("-");
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
// Fallback: random UUID
|
|
221
|
-
const { randomUUID } = await import("node:crypto");
|
|
222
|
-
deviceId = randomUUID();
|
|
223
|
-
}
|
|
224
|
-
// 3. Cache to file
|
|
225
|
-
try {
|
|
226
|
-
const dir = agentbeanHome();
|
|
227
|
-
if (!existsSync(dir))
|
|
228
|
-
mkdirSync(dir, { recursive: true });
|
|
229
|
-
writeFileSync(machineIdFile, deviceId);
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
// non-fatal
|
|
233
|
-
}
|
|
234
|
-
return deviceId;
|
|
235
|
-
}
|
|
236
|
-
// --- Scan Coding Agent Runtimes (Claude Code, Codex, Kimi) ---
|
|
237
|
-
export async function scanRuntimes() {
|
|
238
|
-
const checks = [
|
|
239
|
-
{
|
|
240
|
-
bin: "claude",
|
|
241
|
-
name: "Claude Code",
|
|
242
|
-
adapterKind: "claude-code",
|
|
243
|
-
candidates: getClaudeCodeCandidates(),
|
|
244
|
-
},
|
|
245
|
-
{ bin: "codex", name: "Codex CLI", adapterKind: "codex", candidates: [] },
|
|
246
|
-
{
|
|
247
|
-
bin: "kimi-cli",
|
|
248
|
-
name: "Kimi CLI",
|
|
249
|
-
adapterKind: "Kimi-cli",
|
|
250
|
-
candidates: [],
|
|
251
|
-
},
|
|
252
|
-
];
|
|
253
|
-
const results = [];
|
|
254
|
-
for (const s of checks) {
|
|
255
|
-
const path = await which(s.bin, s.candidates);
|
|
256
|
-
results.push({
|
|
257
|
-
name: s.name,
|
|
258
|
-
adapterKind: s.adapterKind,
|
|
259
|
-
command: path ?? "",
|
|
260
|
-
installed: path !== null,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
return results;
|
|
264
|
-
}
|
|
265
|
-
// --- Scan AgentOS Gateways (Hermes, OpenClaw) ---
|
|
266
|
-
async function checkHermesGateway() {
|
|
267
|
-
const path = await which("hermes");
|
|
268
|
-
if (!path)
|
|
269
|
-
return null;
|
|
270
|
-
const status = await run("hermes", ["gateway", "status"]);
|
|
271
|
-
const running = status.includes("running") || status.includes("✓");
|
|
272
|
-
if (running) {
|
|
273
|
-
return {
|
|
274
|
-
category: "agentos-hosted",
|
|
275
|
-
name: "Hermes-Agent",
|
|
276
|
-
adapterKind: "hermes",
|
|
277
|
-
command: path,
|
|
278
|
-
args: [],
|
|
279
|
-
cwd: dirname(path),
|
|
280
|
-
source: "gateway",
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
async function checkOpenClawGateway() {
|
|
286
|
-
const path = await which("openclaw");
|
|
287
|
-
if (!path)
|
|
288
|
-
return null;
|
|
289
|
-
const status = await run(path, ["gateway", "status"]);
|
|
290
|
-
const running = status.includes("running") || status.includes("✓");
|
|
291
|
-
const agentId = parseOpenClawAgentId(await run(path, ["agents", "list", "--json"]));
|
|
292
|
-
if (running || agentId) {
|
|
293
|
-
return {
|
|
294
|
-
category: "agentos-hosted",
|
|
295
|
-
name: "OpenClaw-Agent",
|
|
296
|
-
adapterKind: "openclaw",
|
|
297
|
-
command: path,
|
|
298
|
-
args: ["agent", "--agent", agentId ?? "main"],
|
|
299
|
-
cwd: dirname(path),
|
|
300
|
-
source: "gateway",
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
export async function scanAgentOSAgents() {
|
|
306
|
-
const [hermes, openclaw] = await Promise.all([
|
|
307
|
-
checkHermesGateway(),
|
|
308
|
-
checkOpenClawGateway(),
|
|
309
|
-
]);
|
|
310
|
-
return [hermes, openclaw].filter((a) => a !== null);
|
|
311
|
-
}
|
|
312
|
-
// --- Scan local agent definitions from filesystem ---
|
|
313
|
-
export async function scanLocalAgents(scanDir = localAgentsDir(process.env.AGENTBEAN_PROFILE)) {
|
|
314
|
-
if (!existsSync(scanDir)) {
|
|
315
|
-
return [];
|
|
316
|
-
}
|
|
317
|
-
const results = [];
|
|
318
|
-
let entries;
|
|
319
|
-
try {
|
|
320
|
-
entries = readdirSync(scanDir);
|
|
321
|
-
}
|
|
322
|
-
catch (err) {
|
|
323
|
-
logger?.warn?.({ err: err?.message }, "scan failed");
|
|
324
|
-
return [];
|
|
325
|
-
}
|
|
326
|
-
for (const entry of entries) {
|
|
327
|
-
const subdir = join(scanDir, entry);
|
|
328
|
-
let st;
|
|
329
|
-
try {
|
|
330
|
-
st = statSync(subdir);
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (!st.isDirectory())
|
|
336
|
-
continue;
|
|
337
|
-
const jsonPath = join(subdir, "agent.json");
|
|
338
|
-
const yamlPath = join(subdir, "agent.yaml");
|
|
339
|
-
const ymlPath = join(subdir, "agent.yml");
|
|
340
|
-
let raw = null;
|
|
341
|
-
let ext = null;
|
|
342
|
-
if (existsSync(jsonPath)) {
|
|
343
|
-
raw = readFileSync(jsonPath, "utf8");
|
|
344
|
-
ext = "json";
|
|
345
|
-
}
|
|
346
|
-
else if (existsSync(yamlPath)) {
|
|
347
|
-
raw = readFileSync(yamlPath, "utf8");
|
|
348
|
-
ext = "yaml";
|
|
349
|
-
}
|
|
350
|
-
else if (existsSync(ymlPath)) {
|
|
351
|
-
raw = readFileSync(ymlPath, "utf8");
|
|
352
|
-
ext = "yaml";
|
|
353
|
-
}
|
|
354
|
-
if (raw === null || ext === null)
|
|
355
|
-
continue;
|
|
356
|
-
let parsed = null;
|
|
357
|
-
try {
|
|
358
|
-
if (ext === "json") {
|
|
359
|
-
parsed = JSON.parse(raw);
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
const { load: parseYaml } = await import("js-yaml");
|
|
363
|
-
parsed = parseYaml(raw);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
catch {
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
if (!parsed || typeof parsed !== "object")
|
|
370
|
-
continue;
|
|
371
|
-
const name = (typeof parsed.name === "string" ? parsed.name : entry).replace(/\s+/g, "-");
|
|
372
|
-
const command = typeof parsed.command === "string" ? parsed.command : "";
|
|
373
|
-
const args = Array.isArray(parsed.args)
|
|
374
|
-
? parsed.args.map(String)
|
|
375
|
-
: [];
|
|
376
|
-
let category;
|
|
377
|
-
if (typeof parsed.category === "string" &&
|
|
378
|
-
["executor-hosted", "agentos-hosted"].includes(parsed.category)) {
|
|
379
|
-
category = parsed.category;
|
|
380
|
-
}
|
|
381
|
-
else if ("executor" in parsed) {
|
|
382
|
-
category = "executor-hosted";
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
category = "executor-hosted";
|
|
386
|
-
}
|
|
387
|
-
const adapterKind = typeof parsed.adapterKind === "string" &&
|
|
388
|
-
["codex", "claude-code", "openclaw", "hermes"].includes(parsed.adapterKind)
|
|
389
|
-
? parsed.adapterKind
|
|
390
|
-
: "codex";
|
|
391
|
-
results.push({
|
|
392
|
-
category,
|
|
393
|
-
name,
|
|
394
|
-
adapterKind,
|
|
395
|
-
command,
|
|
396
|
-
args,
|
|
397
|
-
source: "filesystem",
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
return results;
|
|
401
|
-
}
|
|
402
|
-
export function collectSystemInfo() {
|
|
403
|
-
const totalMem = os.totalmem();
|
|
404
|
-
const freeMem = os.freemem();
|
|
405
|
-
const cpus = os.cpus();
|
|
406
|
-
const platform = os.platform();
|
|
407
|
-
let osVersion = `${os.type()} ${os.release()}`;
|
|
408
|
-
if (platform === "darwin") {
|
|
409
|
-
osVersion = `macOS ${os.release()}`;
|
|
410
|
-
}
|
|
411
|
-
return {
|
|
412
|
-
platform,
|
|
413
|
-
arch: os.arch(),
|
|
414
|
-
osVersion,
|
|
415
|
-
hostname: os.hostname(),
|
|
416
|
-
cpuModel: cpus[0]?.model ?? "unknown",
|
|
417
|
-
cpuCores: cpus.length,
|
|
418
|
-
totalMemoryGB: Math.round((totalMem / 1024 / 1024 / 1024) * 10) / 10,
|
|
419
|
-
freeMemoryGB: Math.round((freeMem / 1024 / 1024 / 1024) * 10) / 10,
|
|
420
|
-
nodeVersion: process.version,
|
|
421
|
-
daemonVersion: DAEMON_VERSION,
|
|
422
|
-
};
|
|
423
|
-
}
|
package/dist/uploader.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { basename } from 'node:path';
|
|
3
|
-
import { logger } from './log.js';
|
|
4
|
-
export async function uploadArtifact(input) {
|
|
5
|
-
const { serverUrl, token, networkId, filePath, channelId, uploaderId, metaJson } = input;
|
|
6
|
-
const filename = basename(filePath);
|
|
7
|
-
let buffer;
|
|
8
|
-
let size;
|
|
9
|
-
try {
|
|
10
|
-
const st = statSync(filePath);
|
|
11
|
-
size = st.size;
|
|
12
|
-
buffer = readFileSync(filePath);
|
|
13
|
-
}
|
|
14
|
-
catch (err) {
|
|
15
|
-
logger.warn({ err: err.message, filePath }, 'artifact read failed');
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
// Node Buffer is a Uint8Array; cast to satisfy strict DOM types
|
|
19
|
-
const blob = new Blob([buffer]);
|
|
20
|
-
const form = new FormData();
|
|
21
|
-
form.append('channelId', channelId);
|
|
22
|
-
form.append('file', blob, filename);
|
|
23
|
-
if (uploaderId)
|
|
24
|
-
form.append('uploaderId', uploaderId);
|
|
25
|
-
if (metaJson)
|
|
26
|
-
form.append('metaJson', metaJson);
|
|
27
|
-
try {
|
|
28
|
-
const resp = await fetch(`${serverUrl}/api/networks/${networkId}/artifacts/upload`, {
|
|
29
|
-
method: 'POST',
|
|
30
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
31
|
-
body: form,
|
|
32
|
-
});
|
|
33
|
-
if (!resp.ok) {
|
|
34
|
-
const text = await resp.text();
|
|
35
|
-
logger.warn({ status: resp.status, body: text, filePath }, 'artifact upload rejected');
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
const result = (await resp.json());
|
|
39
|
-
logger.info({ id: result.id, filename, sizeBytes: size }, 'artifact uploaded');
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
logger.warn({ err: err.message, filePath }, 'artifact upload failed');
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
function rootDir() {
|
|
6
|
-
return resolve(process.env.AGENTBEAN_HOME ?? join(homedir(), '.agentbean'));
|
|
7
|
-
}
|
|
8
|
-
function safeSegment(value) {
|
|
9
|
-
return value.replace(/[^a-zA-Z0-9._-]/g, '-').replace(/^-+|-+$/g, '') || 'unknown';
|
|
10
|
-
}
|
|
11
|
-
function ensureDir(dir) {
|
|
12
|
-
if (!existsSync(dir))
|
|
13
|
-
mkdirSync(dir, { recursive: true });
|
|
14
|
-
return dir;
|
|
15
|
-
}
|
|
16
|
-
export function getAgentWorkspaceDir(teamId, agentId) {
|
|
17
|
-
return ensureDir(join(rootDir(), 'teams', safeSegment(teamId), 'agents', safeSegment(agentId)));
|
|
18
|
-
}
|
|
19
|
-
function writeJson(path, value) {
|
|
20
|
-
ensureDir(dirname(path));
|
|
21
|
-
writeFileSync(path, JSON.stringify(value, null, 2));
|
|
22
|
-
}
|
|
23
|
-
function fileHash(path) {
|
|
24
|
-
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
25
|
-
}
|
|
26
|
-
function uniqueDestination(dir, filename) {
|
|
27
|
-
const ext = extname(filename);
|
|
28
|
-
const stem = ext ? filename.slice(0, -ext.length) : filename;
|
|
29
|
-
let candidate = join(dir, filename);
|
|
30
|
-
let index = 1;
|
|
31
|
-
while (existsSync(candidate)) {
|
|
32
|
-
candidate = join(dir, `${stem}-${index}${ext}`);
|
|
33
|
-
index += 1;
|
|
34
|
-
}
|
|
35
|
-
return candidate;
|
|
36
|
-
}
|
|
37
|
-
function fileNamePreference(path) {
|
|
38
|
-
const name = basename(path).toLowerCase();
|
|
39
|
-
if (/^ig_[a-f0-9]{32,}\.(png|jpe?g|gif|webp)$/i.test(name))
|
|
40
|
-
return 0;
|
|
41
|
-
if (/^(image|output|generated)[._-]?\d*\.(png|jpe?g|gif|webp)$/i.test(name))
|
|
42
|
-
return 1;
|
|
43
|
-
return 2;
|
|
44
|
-
}
|
|
45
|
-
function escapeRegExp(value) {
|
|
46
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
47
|
-
}
|
|
48
|
-
function replaceAllLiteral(value, search, replacement) {
|
|
49
|
-
if (!search || search === replacement)
|
|
50
|
-
return value;
|
|
51
|
-
return value.replace(new RegExp(escapeRegExp(search), 'g'), replacement);
|
|
52
|
-
}
|
|
53
|
-
function stripDevicePath(value, replacement) {
|
|
54
|
-
let body = replaceAllLiteral(value, `file://${replacement}`, basename(replacement));
|
|
55
|
-
body = replaceAllLiteral(body, replacement, basename(replacement));
|
|
56
|
-
return body;
|
|
57
|
-
}
|
|
58
|
-
export function beginAgentWorkspaceRun(input) {
|
|
59
|
-
const teamId = safeSegment(input.teamId);
|
|
60
|
-
const agentId = safeSegment(input.agentId);
|
|
61
|
-
const runId = safeSegment(input.runId);
|
|
62
|
-
const teamDir = ensureDir(join(rootDir(), 'teams', teamId));
|
|
63
|
-
const agentDir = ensureDir(join(teamDir, 'agents', agentId));
|
|
64
|
-
const runDir = ensureDir(join(agentDir, 'runs', runId));
|
|
65
|
-
const inputDir = ensureDir(join(runDir, 'inputs'));
|
|
66
|
-
const outputDir = ensureDir(join(runDir, 'outputs'));
|
|
67
|
-
const intermediateDir = ensureDir(join(runDir, 'intermediates'));
|
|
68
|
-
const logDir = ensureDir(join(runDir, 'logs'));
|
|
69
|
-
writeJson(join(teamDir, 'team.json'), {
|
|
70
|
-
id: input.teamId,
|
|
71
|
-
name: input.teamName ?? input.teamId,
|
|
72
|
-
updatedAt: new Date().toISOString(),
|
|
73
|
-
});
|
|
74
|
-
writeJson(join(agentDir, 'agent.json'), {
|
|
75
|
-
id: input.agentId,
|
|
76
|
-
name: input.agentName ?? input.agentId,
|
|
77
|
-
projectDir: input.projectDir ?? null,
|
|
78
|
-
updatedAt: new Date().toISOString(),
|
|
79
|
-
});
|
|
80
|
-
writeFileSync(join(runDir, 'prompt.md'), input.prompt);
|
|
81
|
-
writeJson(join(runDir, 'manifest.json'), {
|
|
82
|
-
teamId: input.teamId,
|
|
83
|
-
agentId: input.agentId,
|
|
84
|
-
runId: input.runId,
|
|
85
|
-
status: 'running',
|
|
86
|
-
createdAt: new Date().toISOString(),
|
|
87
|
-
files: [],
|
|
88
|
-
});
|
|
89
|
-
return { teamId: input.teamId, agentId: input.agentId, runId: input.runId, agentDir, runDir, inputDir, outputDir, intermediateDir, logDir };
|
|
90
|
-
}
|
|
91
|
-
export function workspaceEnv(run) {
|
|
92
|
-
return {
|
|
93
|
-
AGENTBEAN_TEAM_ID: run.teamId,
|
|
94
|
-
AGENTBEAN_AGENT_ID: run.agentId,
|
|
95
|
-
AGENTBEAN_RUN_ID: run.runId,
|
|
96
|
-
AGENTBEAN_WORKSPACE: run.agentDir,
|
|
97
|
-
AGENTBEAN_INPUT_DIR: run.inputDir,
|
|
98
|
-
AGENTBEAN_OUTPUT_DIR: run.outputDir,
|
|
99
|
-
AGENTBEAN_INTERMEDIATE_DIR: run.intermediateDir,
|
|
100
|
-
AGENT_BEAN_OUTPUT_DIRS: [run.outputDir, run.intermediateDir].join(','),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
export function archiveOutputFiles(run, files) {
|
|
104
|
-
const archived = [];
|
|
105
|
-
const candidates = new Map();
|
|
106
|
-
const hashOrder = [];
|
|
107
|
-
const seenPaths = new Set();
|
|
108
|
-
for (const file of files) {
|
|
109
|
-
const abs = isAbsolute(file) ? file : resolve(file);
|
|
110
|
-
if (seenPaths.has(abs))
|
|
111
|
-
continue;
|
|
112
|
-
seenPaths.add(abs);
|
|
113
|
-
let st;
|
|
114
|
-
try {
|
|
115
|
-
st = statSync(abs);
|
|
116
|
-
if (!st.isFile())
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
const hash = fileHash(abs);
|
|
123
|
-
const current = candidates.get(hash);
|
|
124
|
-
if (!current) {
|
|
125
|
-
candidates.set(hash, { abs, hash });
|
|
126
|
-
hashOrder.push(hash);
|
|
127
|
-
}
|
|
128
|
-
else if (fileNamePreference(abs) > fileNamePreference(current.abs)) {
|
|
129
|
-
candidates.set(hash, { abs, hash });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
for (const hash of hashOrder) {
|
|
133
|
-
const candidate = candidates.get(hash);
|
|
134
|
-
if (!candidate)
|
|
135
|
-
continue;
|
|
136
|
-
const abs = candidate.abs;
|
|
137
|
-
const alreadyInRun = relative(run.runDir, abs);
|
|
138
|
-
const archivedPath = alreadyInRun && !alreadyInRun.startsWith('..') && !isAbsolute(alreadyInRun)
|
|
139
|
-
? abs
|
|
140
|
-
: uniqueDestination(run.outputDir, basename(abs));
|
|
141
|
-
if (archivedPath !== abs)
|
|
142
|
-
copyFileSync(abs, archivedPath);
|
|
143
|
-
const sizeBytes = statSync(archivedPath).size;
|
|
144
|
-
archived.push({
|
|
145
|
-
originalPath: abs,
|
|
146
|
-
archivedPath,
|
|
147
|
-
relativePath: relative(run.agentDir, archivedPath),
|
|
148
|
-
pathKind: 'output',
|
|
149
|
-
sha256: candidate.hash,
|
|
150
|
-
sizeBytes,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
return archived;
|
|
154
|
-
}
|
|
155
|
-
export function formatWorkspaceReply(reply, files, options = {}) {
|
|
156
|
-
const exposeLocalPaths = options.exposeLocalPaths ?? true;
|
|
157
|
-
let body = reply;
|
|
158
|
-
for (const file of files) {
|
|
159
|
-
if (exposeLocalPaths) {
|
|
160
|
-
body = replaceAllLiteral(body, `file://${file.originalPath}`, `file://${file.archivedPath}`);
|
|
161
|
-
body = replaceAllLiteral(body, file.originalPath, file.archivedPath);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
body = stripDevicePath(body, file.originalPath);
|
|
165
|
-
body = stripDevicePath(body, file.archivedPath);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const missingPaths = files.filter((file) => {
|
|
169
|
-
const marker = exposeLocalPaths ? file.archivedPath : basename(file.archivedPath);
|
|
170
|
-
return !body.includes(marker);
|
|
171
|
-
});
|
|
172
|
-
if (missingPaths.length > 0) {
|
|
173
|
-
body += '\n\n已生成文件:\n' + missingPaths.map((file) => `- ${exposeLocalPaths ? file.archivedPath : basename(file.archivedPath)}`).join('\n');
|
|
174
|
-
}
|
|
175
|
-
return body;
|
|
176
|
-
}
|
|
177
|
-
export function finishAgentWorkspaceRun(run, input) {
|
|
178
|
-
if (input.replyText !== undefined) {
|
|
179
|
-
writeFileSync(join(run.runDir, 'response.md'), input.replyText);
|
|
180
|
-
}
|
|
181
|
-
writeJson(join(run.runDir, 'manifest.json'), {
|
|
182
|
-
teamId: run.teamId,
|
|
183
|
-
agentId: run.agentId,
|
|
184
|
-
runId: run.runId,
|
|
185
|
-
status: input.status,
|
|
186
|
-
updatedAt: new Date().toISOString(),
|
|
187
|
-
error: input.error,
|
|
188
|
-
files: input.files.map((file) => ({
|
|
189
|
-
path: file.relativePath,
|
|
190
|
-
sha256: file.sha256,
|
|
191
|
-
sizeBytes: file.sizeBytes,
|
|
192
|
-
kind: file.pathKind,
|
|
193
|
-
originalPath: file.originalPath,
|
|
194
|
-
})),
|
|
195
|
-
});
|
|
196
|
-
}
|