@agentbean/daemon 0.1.4 → 0.1.5

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 CHANGED
@@ -14,7 +14,7 @@ npm test # 运行测试
14
14
  ### 带配置文件启动
15
15
 
16
16
  ```bash
17
- npx tsx src/index.ts ~/.agentbean/device-agent.yaml
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/index.ts
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` | 所属团队 |
@@ -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
- main().catch((err) => {
4
- console.error('fatal:', err.message);
5
- process.exit(1);
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
+ }
@@ -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
- const agent = agents.get(req.agentId);
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 Network ID (default: default)
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 which(bin) {
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
- const child = execFile('which', [bin], { timeout: 5_000, env: { ...process.env, PATH: [
10
- process.env.PATH,
11
- '/usr/local/bin',
12
- '/opt/homebrew/bin',
13
- join(os.homedir(), '.nvm/versions/node', ...getAllNodeVersions(), 'bin'),
14
- ].filter(Boolean).join(':') } }, (err, stdout) => {
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.replace(/\s+/g, "-"),
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",
4
+ "version": "0.1.5",
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/index.ts",
15
+ "dev": "tsx watch src/bin.ts",
16
16
  "start": "node dist/bin.js",
17
17
  "test": "vitest run",
18
18
  "test:watch": "vitest",