@agentbean/daemon 0.1.4 → 0.1.6
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/README.md +4 -4
- package/dist/adapters/codex.js +2 -1
- package/dist/adapters/hermes.js +35 -1
- package/dist/bin.js +7 -4
- package/dist/device-daemon.js +30 -1
- package/dist/index.js +8 -1
- package/dist/scanner.js +57 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm test # 运行测试
|
|
|
14
14
|
### 带配置文件启动
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npx tsx src/
|
|
17
|
+
npx tsx src/bin.ts ~/.agentbean/device-agent.yaml
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
### 自动扫描模式
|
|
@@ -22,7 +22,7 @@ npx tsx src/index.ts ~/.agentbean/device-agent.yaml
|
|
|
22
22
|
如果不提供配置文件,或配置文件中 `agents` 数组为空,Daemon 会自动扫描本机 Agent:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npx tsx src/
|
|
25
|
+
npx tsx src/bin.ts
|
|
26
26
|
# 扫描 Coding Agent (which claude-code, codex, kimi...)
|
|
27
27
|
# 扫描 AgentOS Gateway (localhost:PORT)
|
|
28
28
|
# 扫描 ~/.agentbean/agents/ 目录
|
|
@@ -34,7 +34,7 @@ npx tsx src/index.ts
|
|
|
34
34
|
|
|
35
35
|
```yaml
|
|
36
36
|
deviceId: my-macbook-pro # 设备标识
|
|
37
|
-
networkId: default #
|
|
37
|
+
networkId: default # 所属团队
|
|
38
38
|
server:
|
|
39
39
|
url: http://localhost:3000/agent # Server Socket.IO 地址
|
|
40
40
|
token: default:default:dev-token-change-me # 三截 token
|
|
@@ -155,4 +155,4 @@ interface CliAdapter {
|
|
|
155
155
|
| `SERVER_URL` | Server WebSocket 地址 |
|
|
156
156
|
| `SERVER_TOKEN` | 接入令牌 |
|
|
157
157
|
| `DEVICE_ID` | 设备标识 |
|
|
158
|
-
| `NETWORK_ID` |
|
|
158
|
+
| `NETWORK_ID` | 所属团队 |
|
package/dist/adapters/codex.js
CHANGED
|
@@ -38,7 +38,8 @@ export class CodexAdapter {
|
|
|
38
38
|
const payload = renderPayload(input, this.opts.systemPrompt ?? input.systemPrompt);
|
|
39
39
|
const cwd = input.workspace ?? this.opts.cwd ?? process.cwd();
|
|
40
40
|
const baseCommand = this.opts.command || 'codex';
|
|
41
|
-
const
|
|
41
|
+
const configuredArgs = this.opts.args && this.opts.args.length > 0 ? this.opts.args : ['exec'];
|
|
42
|
+
const baseArgs = [...configuredArgs, payload];
|
|
42
43
|
const command = input.sandboxProfilePath ? 'sandbox-exec' : baseCommand;
|
|
43
44
|
const args = input.sandboxProfilePath
|
|
44
45
|
? ['-f', input.sandboxProfilePath, '--', baseCommand, ...baseArgs]
|
package/dist/adapters/hermes.js
CHANGED
|
@@ -25,6 +25,40 @@ function buildPrompt(input, systemPrompt) {
|
|
|
25
25
|
parts.push(input.prompt);
|
|
26
26
|
return parts.join('\n\n---\n\n');
|
|
27
27
|
}
|
|
28
|
+
const ANSI_RE = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
29
|
+
const BOX_ONLY_RE = /^[\s─━═╭╮╰╯│┃┌┐└┘├┤┬┴┼]+$/;
|
|
30
|
+
export function extractHermesReply(output) {
|
|
31
|
+
const lines = output
|
|
32
|
+
.replace(ANSI_RE, '')
|
|
33
|
+
.replace(/\r\n?/g, '\n')
|
|
34
|
+
.split('\n');
|
|
35
|
+
const cleaned = lines
|
|
36
|
+
.map((line) => line.trimEnd())
|
|
37
|
+
.filter((line) => {
|
|
38
|
+
const trimmed = line.trim();
|
|
39
|
+
if (!trimmed)
|
|
40
|
+
return true;
|
|
41
|
+
if (trimmed.startsWith('Query:'))
|
|
42
|
+
return false;
|
|
43
|
+
if (trimmed === 'Initializing agent...' || trimmed === 'Initializing agent…')
|
|
44
|
+
return false;
|
|
45
|
+
if (trimmed.startsWith('Resume this session with:'))
|
|
46
|
+
return false;
|
|
47
|
+
if (/^hermes\s+--resume\b/.test(trimmed))
|
|
48
|
+
return false;
|
|
49
|
+
if (/^(Session|Duration|Messages):\s+/.test(trimmed))
|
|
50
|
+
return false;
|
|
51
|
+
if (trimmed.startsWith('╭') || trimmed.startsWith('╰'))
|
|
52
|
+
return false;
|
|
53
|
+
if (BOX_ONLY_RE.test(trimmed))
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
})
|
|
57
|
+
.map((line) => line.replace(/^[│┃]\s?/, '').replace(/\s?[│┃]$/, '').replace(/^\s{2,}/, ''))
|
|
58
|
+
.join('\n')
|
|
59
|
+
.trim();
|
|
60
|
+
return cleaned || output.trim();
|
|
61
|
+
}
|
|
28
62
|
export class HermesAdapter {
|
|
29
63
|
opts;
|
|
30
64
|
kind = 'hermes';
|
|
@@ -85,7 +119,7 @@ export class HermesAdapter {
|
|
|
85
119
|
const detail = stderr.length > 0 ? stderr.slice(0, 400) : 'no stderr';
|
|
86
120
|
return reject(new Error(`hermes exit ${code}: ${detail}`));
|
|
87
121
|
}
|
|
88
|
-
const reply = stdout || stderr;
|
|
122
|
+
const reply = extractHermesReply(stdout || stderr);
|
|
89
123
|
if (!reply) {
|
|
90
124
|
return reject(new Error('hermes produced empty output'));
|
|
91
125
|
}
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
2
3
|
import { main } from './index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
5
|
+
main().catch((err) => {
|
|
6
|
+
console.error('fatal:', err.message);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
}
|
package/dist/device-daemon.js
CHANGED
|
@@ -201,6 +201,9 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
201
201
|
networkId: cfg.networkId,
|
|
202
202
|
agents: publicAgents,
|
|
203
203
|
systemInfo,
|
|
204
|
+
capabilities: {
|
|
205
|
+
customAgentDispatch: true,
|
|
206
|
+
},
|
|
204
207
|
},
|
|
205
208
|
transports: ['websocket'],
|
|
206
209
|
reconnection: true,
|
|
@@ -234,7 +237,33 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
234
237
|
logger.error({ err: err.message }, 'connect_error');
|
|
235
238
|
});
|
|
236
239
|
socket.on('dispatch', (req) => {
|
|
237
|
-
|
|
240
|
+
let agent = agents.get(req.agentId);
|
|
241
|
+
if (!agent && req.customAgent) {
|
|
242
|
+
const custom = req.customAgent;
|
|
243
|
+
const entry = {
|
|
244
|
+
id: custom.id,
|
|
245
|
+
name: custom.name,
|
|
246
|
+
role: custom.role ?? 'executor-agent',
|
|
247
|
+
category: 'executor-hosted',
|
|
248
|
+
adapter: {
|
|
249
|
+
kind: custom.adapterKind,
|
|
250
|
+
command: custom.command,
|
|
251
|
+
args: custom.args ?? [],
|
|
252
|
+
cwd: custom.cwd ?? undefined,
|
|
253
|
+
workspace: custom.cwd ?? undefined,
|
|
254
|
+
systemPrompt: custom.description ?? undefined,
|
|
255
|
+
},
|
|
256
|
+
visibility: 'public',
|
|
257
|
+
};
|
|
258
|
+
try {
|
|
259
|
+
agent = new AgentInstance(entry, pickAdapter(entry.adapter));
|
|
260
|
+
agents.set(req.agentId, agent);
|
|
261
|
+
logger.info({ agentId: req.agentId, kind: entry.adapter.kind, cwd: entry.adapter.cwd }, 'custom agent instance created for dispatch');
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
logger.warn({ agentId: req.agentId, err: errorMessage(err) }, 'failed to create custom dispatch agent');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
238
267
|
if (!agent) {
|
|
239
268
|
logger.warn({ agentId: req.agentId, requestId: req.requestId }, 'dispatch for unknown agent');
|
|
240
269
|
socket?.emit('error_event', {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
2
3
|
import { loadConfig, loadDeviceConfig } from './config.js';
|
|
3
4
|
import { createConnection } from './connection.js';
|
|
4
5
|
import { createDeviceDaemon } from './device-daemon.js';
|
|
@@ -134,7 +135,7 @@ Options:
|
|
|
134
135
|
--server-url AgentBean Server URL (required)
|
|
135
136
|
--token Authentication token (required)
|
|
136
137
|
--device-id Device ID (default: auto-detected from hardware)
|
|
137
|
-
--network-id
|
|
138
|
+
--network-id Team ID (default: default)
|
|
138
139
|
`);
|
|
139
140
|
process.exit(0);
|
|
140
141
|
}
|
|
@@ -263,3 +264,9 @@ export async function main() {
|
|
|
263
264
|
}
|
|
264
265
|
}
|
|
265
266
|
}
|
|
267
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
268
|
+
main().catch((err) => {
|
|
269
|
+
console.error('fatal:', err.message);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
});
|
|
272
|
+
}
|
package/dist/scanner.js
CHANGED
|
@@ -4,14 +4,35 @@ import { 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
|
-
function
|
|
7
|
+
function isExecutableFile(path) {
|
|
8
|
+
try {
|
|
9
|
+
return existsSync(path) && statSync(path).isFile();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function getExtraPathEntries() {
|
|
16
|
+
return [
|
|
17
|
+
'/usr/local/bin',
|
|
18
|
+
'/opt/homebrew/bin',
|
|
19
|
+
join(os.homedir(), '.local/bin'),
|
|
20
|
+
join(os.homedir(), '.bun/bin'),
|
|
21
|
+
join(os.homedir(), '.npm-global/bin'),
|
|
22
|
+
join(os.homedir(), '.asdf/shims'),
|
|
23
|
+
join(os.homedir(), '.local/share/mise/shims'),
|
|
24
|
+
...getAllNodeVersions().map((version) => join(os.homedir(), '.nvm/versions/node', version, 'bin')),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
function which(bin, candidatePaths = []) {
|
|
8
28
|
return new Promise((resolve) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
for (const candidate of candidatePaths) {
|
|
30
|
+
if (isExecutableFile(candidate)) {
|
|
31
|
+
resolve(candidate);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const child = execFile('which', [bin], { timeout: 5_000, env: { ...process.env, PATH: [process.env.PATH, ...getExtraPathEntries()].filter(Boolean).join(':') } }, (err, stdout) => {
|
|
15
36
|
if (err) {
|
|
16
37
|
resolve(null);
|
|
17
38
|
return;
|
|
@@ -33,6 +54,30 @@ function getAllNodeVersions() {
|
|
|
33
54
|
return [];
|
|
34
55
|
}
|
|
35
56
|
}
|
|
57
|
+
function getClaudeCodeCandidates() {
|
|
58
|
+
const latestDir = join(os.homedir(), '.local/share/claude-latest');
|
|
59
|
+
const legacyDir = join(os.homedir(), '.local/share/claude');
|
|
60
|
+
const candidates = [
|
|
61
|
+
join(latestDir, 'current/claude'),
|
|
62
|
+
join(legacyDir, 'current/claude'),
|
|
63
|
+
'/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js',
|
|
64
|
+
'/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js',
|
|
65
|
+
];
|
|
66
|
+
for (const base of [latestDir, legacyDir]) {
|
|
67
|
+
const versionsDir = join(base, 'versions');
|
|
68
|
+
try {
|
|
69
|
+
if (!existsSync(versionsDir))
|
|
70
|
+
continue;
|
|
71
|
+
const versions = readdirSync(versionsDir).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
72
|
+
for (const version of versions)
|
|
73
|
+
candidates.push(join(versionsDir, version, 'claude'));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Ignore unreadable version directories and continue with other candidates.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return candidates;
|
|
80
|
+
}
|
|
36
81
|
function run(bin, args) {
|
|
37
82
|
return new Promise((resolve) => {
|
|
38
83
|
const child = execFile(bin, args, { timeout: 10_000 }, (err, stdout) => {
|
|
@@ -154,19 +199,21 @@ export async function scanRuntimes() {
|
|
|
154
199
|
bin: "claude",
|
|
155
200
|
name: "Claude Code",
|
|
156
201
|
adapterKind: "claude-code",
|
|
202
|
+
candidates: getClaudeCodeCandidates(),
|
|
157
203
|
},
|
|
158
|
-
{ bin: "codex", name: "Codex CLI", adapterKind: "codex" },
|
|
204
|
+
{ bin: "codex", name: "Codex CLI", adapterKind: "codex", candidates: [] },
|
|
159
205
|
{
|
|
160
206
|
bin: "kimi-cli",
|
|
161
207
|
name: "Kimi CLI",
|
|
162
208
|
adapterKind: "Kimi-cli",
|
|
209
|
+
candidates: [],
|
|
163
210
|
},
|
|
164
211
|
];
|
|
165
212
|
const results = [];
|
|
166
213
|
for (const s of checks) {
|
|
167
|
-
const path = await which(s.bin);
|
|
214
|
+
const path = await which(s.bin, s.candidates);
|
|
168
215
|
results.push({
|
|
169
|
-
name: s.name
|
|
216
|
+
name: s.name,
|
|
170
217
|
adapterKind: s.adapterKind,
|
|
171
218
|
command: path ?? "",
|
|
172
219
|
installed: path !== null,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentbean/daemon",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
|
-
"dev": "tsx watch src/
|
|
15
|
+
"dev": "tsx watch src/bin.ts",
|
|
16
16
|
"start": "node dist/bin.js",
|
|
17
17
|
"test": "vitest run",
|
|
18
18
|
"test:watch": "vitest",
|