@agentbean/daemon 0.1.25 → 0.1.27

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.
@@ -8,16 +8,37 @@ function runtimeArgs(args = []) {
8
8
  function hasMessageFlag(args) {
9
9
  return args.includes('--message') || args.includes('-m');
10
10
  }
11
+ function hasTargetSelector(args) {
12
+ return args.includes('--agent')
13
+ || args.includes('--session-id')
14
+ || args.includes('--session-key')
15
+ || args.includes('--to')
16
+ || args.includes('-t');
17
+ }
18
+ function ensureTargetSelector(args) {
19
+ if (hasTargetSelector(args))
20
+ return args;
21
+ const messageIdx = args.findIndex((arg) => arg === '--message' || arg === '-m');
22
+ if (messageIdx >= 0) {
23
+ return [
24
+ ...args.slice(0, messageIdx),
25
+ '--agent',
26
+ 'main',
27
+ ...args.slice(messageIdx),
28
+ ];
29
+ }
30
+ return [...args, '--agent', 'main'];
31
+ }
11
32
  function buildArgs(baseArgs, prompt) {
12
- // Current OpenClaw one-shot agent turns use: openclaw agent --message "<prompt>".
33
+ // Current OpenClaw one-shot agent turns require an explicit session selector.
13
34
  // Preserve explicit custom args when the message flag is already configured.
14
- if (hasMessageFlag(baseArgs)) {
15
- return [...baseArgs, prompt];
16
- }
17
- if (baseArgs.includes('agent')) {
18
- return [...baseArgs, '--message', prompt];
35
+ const agentArgs = baseArgs.includes('agent')
36
+ ? ensureTargetSelector(baseArgs)
37
+ : [...baseArgs, 'agent', '--agent', 'main'];
38
+ if (hasMessageFlag(agentArgs)) {
39
+ return [...agentArgs, prompt];
19
40
  }
20
- return [...baseArgs, 'agent', '--message', prompt];
41
+ return [...agentArgs, '--message', prompt];
21
42
  }
22
43
  function buildPrompt(input, systemPrompt) {
23
44
  const parts = [];
@@ -1,12 +1,15 @@
1
1
  import { io } from 'socket.io-client';
2
+ import { execFile } from 'node:child_process';
2
3
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
4
  import { join } from 'node:path';
4
5
  import { homedir } from 'node:os';
6
+ import { promisify } from 'node:util';
5
7
  import { logger } from './log.js';
6
8
  import { AgentInstance } from './agent-instance.js';
7
9
  import { pickAdapter } from './adapters/factory.js';
8
10
  import { scanRuntimes, scanAgentOSAgents, scanLocalAgents, collectSystemInfo } from './scanner.js';
9
11
  import { syncWorkspaceArtifacts } from './workspace-sync.js';
12
+ const execFileAsync = promisify(execFile);
10
13
  function errorMessage(err) {
11
14
  if (err instanceof Error && err.message)
12
15
  return err.message;
@@ -26,6 +29,55 @@ function agentSlug(name) {
26
29
  function scannedAgentId(deviceId, name) {
27
30
  return `scan-${deviceId}-${agentSlug(name)}`;
28
31
  }
32
+ export function nativeDirectoryPickerCommands(platform = process.platform) {
33
+ if (platform === 'darwin') {
34
+ return [{ command: 'osascript', args: ['-e', 'POSIX path of (choose folder with prompt "选择项目目录")'] }];
35
+ }
36
+ if (platform === 'win32') {
37
+ return [{
38
+ command: 'powershell.exe',
39
+ args: [
40
+ '-NoProfile',
41
+ '-STA',
42
+ '-Command',
43
+ 'Add-Type -AssemblyName System.Windows.Forms; $dialog = New-Object System.Windows.Forms.FolderBrowserDialog; if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $dialog.SelectedPath }',
44
+ ],
45
+ }];
46
+ }
47
+ return [
48
+ { command: 'zenity', args: ['--file-selection', '--directory', '--title=选择项目目录'] },
49
+ { command: 'kdialog', args: ['--getexistingdirectory', '.', '选择项目目录'] },
50
+ ];
51
+ }
52
+ function isMissingCommandError(err) {
53
+ return err?.code === 'ENOENT';
54
+ }
55
+ function isDirectoryPickerCancel(err) {
56
+ const message = `${err?.message ?? ''}\n${err?.stderr ?? ''}`;
57
+ return err?.code === 1 || /cancel|canceled|cancelled|User canceled|No file selected/i.test(message);
58
+ }
59
+ export async function selectNativeDirectory(commands = nativeDirectoryPickerCommands()) {
60
+ let lastError = null;
61
+ for (const cmd of commands) {
62
+ try {
63
+ const { stdout } = await execFileAsync(cmd.command, cmd.args, { timeout: 120_000 });
64
+ const selected = stdout.trim();
65
+ if (selected)
66
+ return selected;
67
+ return null;
68
+ }
69
+ catch (err) {
70
+ if (isMissingCommandError(err)) {
71
+ lastError = err;
72
+ continue;
73
+ }
74
+ if (isDirectoryPickerCancel(err))
75
+ return null;
76
+ throw err;
77
+ }
78
+ }
79
+ throw new Error(lastError ? `directory picker command not available: ${errorMessage(lastError)}` : 'directory picker command not available');
80
+ }
29
81
  const CACHE_DIR = join(homedir(), '.agentbean');
30
82
  const CACHE_FILE = join(CACHE_DIR, 'scanned-agents.json');
31
83
  function isRuntimeEntry(entry) {
@@ -88,6 +140,7 @@ export function createDeviceSocketOptions(input) {
88
140
  protocolVersion: 1,
89
141
  capabilities: {
90
142
  customAgentDispatch: true,
143
+ directoryPicker: true,
91
144
  },
92
145
  },
93
146
  reconnection: true,
@@ -347,6 +400,21 @@ export function createDeviceDaemon(cfg, agents) {
347
400
  }
348
401
  logger.info({ agentId: payload.agentId, requestId: payload.requestId, cancelled, reason: payload.reason }, 'dispatch cancel requested');
349
402
  });
403
+ socket.on('device:select-directory', async (_payload, ack) => {
404
+ try {
405
+ const selected = await selectNativeDirectory();
406
+ if (!selected) {
407
+ ack?.({ ok: false, error: 'CANCELLED' });
408
+ return;
409
+ }
410
+ ack?.({ ok: true, path: selected });
411
+ }
412
+ catch (err) {
413
+ const message = errorMessage(err);
414
+ logger.warn({ err: message }, 'failed to select directory on device');
415
+ ack?.({ ok: false, error: message });
416
+ }
417
+ });
350
418
  socket.on('agents:discover', async () => {
351
419
  await scanAndRegister(socket, false);
352
420
  });
package/dist/scanner.js CHANGED
@@ -96,6 +96,35 @@ function run(bin, args) {
96
96
  child.on("error", () => resolve(""));
97
97
  });
98
98
  }
99
+ export function parseOpenClawAgentId(output) {
100
+ if (!output.trim())
101
+ return null;
102
+ try {
103
+ const parsed = JSON.parse(output);
104
+ const list = Array.isArray(parsed)
105
+ ? parsed
106
+ : Array.isArray(parsed?.agents)
107
+ ? parsed.agents
108
+ : Array.isArray(parsed?.items)
109
+ ? parsed.items
110
+ : [];
111
+ for (const item of list) {
112
+ const id = typeof item === 'string'
113
+ ? item
114
+ : typeof item?.id === 'string'
115
+ ? item.id
116
+ : typeof item?.agentId === 'string'
117
+ ? item.agentId
118
+ : null;
119
+ if (id?.trim())
120
+ return id.trim();
121
+ }
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ return null;
127
+ }
99
128
  // --- Machine ID (stable per-device identifier) ---
100
129
  const MACHINE_ID_FILE = join(os.homedir(), ".agentbean", "device-id");
101
130
  function getFirstMacAddress() {
@@ -254,15 +283,16 @@ async function checkOpenClawGateway() {
254
283
  const path = await which("openclaw");
255
284
  if (!path)
256
285
  return null;
257
- const status = await run("openclaw", ["gateway", "status"]);
286
+ const status = await run(path, ["gateway", "status"]);
258
287
  const running = status.includes("running") || status.includes("✓");
259
288
  if (running) {
289
+ const agentId = parseOpenClawAgentId(await run(path, ["agents", "list", "--json"])) ?? "main";
260
290
  return {
261
291
  category: "agentos-hosted",
262
292
  name: "OpenClaw-Agent",
263
293
  adapterKind: "openclaw",
264
294
  command: path,
265
- args: [],
295
+ args: ["agent", "--agent", agentId],
266
296
  source: "gateway",
267
297
  };
268
298
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentbean/daemon",
3
3
  "private": false,
4
- "version": "0.1.25",
4
+ "version": "0.1.27",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {