@openagents-org/agent-connector 0.1.11 → 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/package.json +1 -1
- package/src/adapters/base.js +327 -0
- package/src/adapters/claude.js +420 -0
- package/src/adapters/codex.js +260 -0
- package/src/adapters/index.js +39 -0
- package/src/adapters/openclaw.js +259 -0
- package/src/adapters/utils.js +83 -0
- package/src/adapters/workspace-prompt.js +293 -0
- package/src/daemon.js +38 -291
- package/src/index.js +3 -1
package/src/daemon.js
CHANGED
|
@@ -339,316 +339,63 @@ class Daemon {
|
|
|
339
339
|
// ---------------------------------------------------------------------------
|
|
340
340
|
|
|
341
341
|
async _adapterLoop(name, agentCfg, info, network) {
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
let cursor = null;
|
|
346
|
-
const processedIds = new Set();
|
|
342
|
+
const { createAdapter } = require('./adapters');
|
|
343
|
+
const agentType = agentCfg.type || 'openclaw';
|
|
344
|
+
const endpoint = network.endpoint || 'https://workspace-endpoint.openagents.org';
|
|
347
345
|
|
|
348
|
-
|
|
346
|
+
let adapter;
|
|
349
347
|
try {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
348
|
+
adapter = createAdapter(agentType, {
|
|
349
|
+
workspaceId: network.id,
|
|
350
|
+
channelName: 'general',
|
|
351
|
+
token: network.token,
|
|
352
|
+
agentName: name,
|
|
353
|
+
endpoint,
|
|
354
|
+
openclawAgentId: agentCfg.openclaw_agent_id || 'main',
|
|
355
|
+
disabledModules: new Set(),
|
|
356
|
+
});
|
|
358
357
|
} catch (e) {
|
|
359
|
-
this._log(`${name}
|
|
358
|
+
this._log(`${name} failed to create ${agentType} adapter: ${e.message}`);
|
|
359
|
+
info.state = 'error';
|
|
360
|
+
info.lastError = e.message;
|
|
361
|
+
this._writeStatus();
|
|
362
|
+
return;
|
|
360
363
|
}
|
|
361
364
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
await wsClient.heartbeat(network.id, name, network.token);
|
|
367
|
-
} catch (e) {
|
|
368
|
-
this._log(`${name} heartbeat failed: ${e.message}`);
|
|
369
|
-
}
|
|
370
|
-
}, 30000);
|
|
371
|
-
|
|
372
|
-
// Send initial heartbeat
|
|
373
|
-
try { await wsClient.heartbeat(network.id, name, network.token); } catch {}
|
|
374
|
-
|
|
375
|
-
// Install workspace skill into OpenClaw's skills directory
|
|
376
|
-
try { this._installWorkspaceSkill(agentCfg, network); } catch (e) {
|
|
377
|
-
this._log(`${name} skill install failed: ${e.message}`);
|
|
378
|
-
}
|
|
365
|
+
// Store adapter reference for stop
|
|
366
|
+
this._adapters = this._adapters || {};
|
|
367
|
+
this._adapters[name] = adapter;
|
|
379
368
|
|
|
380
369
|
info.state = 'running';
|
|
381
370
|
info.startedAt = new Date().toISOString();
|
|
382
371
|
this._writeStatus();
|
|
383
|
-
this._log(`${name} adapter online → ${network.slug}
|
|
372
|
+
this._log(`${name} adapter online → ${network.slug} (type: ${agentType})`);
|
|
384
373
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (newCursor) cursor = newCursor;
|
|
393
|
-
|
|
394
|
-
// Filter already-processed messages
|
|
395
|
-
const incoming = messages.filter((m) => {
|
|
396
|
-
const id = m.messageId;
|
|
397
|
-
if (!id || processedIds.has(id)) return false;
|
|
398
|
-
if (m.messageType === 'status') return false;
|
|
399
|
-
return true;
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
if (incoming.length > 0) {
|
|
403
|
-
idleCount = 0;
|
|
404
|
-
for (const msg of incoming) {
|
|
405
|
-
if (msg.messageId) processedIds.add(msg.messageId);
|
|
406
|
-
await this._handleAdapterMessage(name, agentCfg, msg, network, wsClient, binary, env);
|
|
407
|
-
}
|
|
408
|
-
// Cap dedup set
|
|
409
|
-
if (processedIds.size > 2000) {
|
|
410
|
-
const arr = [...processedIds];
|
|
411
|
-
processedIds.clear();
|
|
412
|
-
for (const id of arr.slice(-1000)) processedIds.add(id);
|
|
413
|
-
}
|
|
414
|
-
} else {
|
|
415
|
-
idleCount++;
|
|
374
|
+
try {
|
|
375
|
+
// Run adapter poll loop — stops when adapter.stop() is called
|
|
376
|
+
// or when the daemon shuts down
|
|
377
|
+
const checkStop = setInterval(() => {
|
|
378
|
+
if (this._shuttingDown || this._stoppedAgents.has(name)) {
|
|
379
|
+
adapter.stop();
|
|
380
|
+
clearInterval(checkStop);
|
|
416
381
|
}
|
|
382
|
+
}, 1000);
|
|
417
383
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
info.lastError = (e.message || String(e)).slice(0, 200);
|
|
424
|
-
this._log(`${name} poll error: ${info.lastError}`);
|
|
425
|
-
await this._sleep(5000);
|
|
426
|
-
}
|
|
384
|
+
await adapter.run();
|
|
385
|
+
clearInterval(checkStop);
|
|
386
|
+
} catch (e) {
|
|
387
|
+
info.lastError = (e.message || String(e)).slice(0, 200);
|
|
388
|
+
this._log(`${name} adapter error: ${info.lastError}`);
|
|
427
389
|
}
|
|
428
390
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
// Disconnect from workspace
|
|
432
|
-
try { await wsClient.disconnect(network.id, name, network.token); } catch {}
|
|
433
|
-
|
|
391
|
+
delete this._adapters[name];
|
|
434
392
|
info.state = 'stopped';
|
|
435
393
|
this._writeStatus();
|
|
436
394
|
this._log(`${name} adapter stopped`);
|
|
437
395
|
}
|
|
438
396
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (!content) return;
|
|
442
|
-
|
|
443
|
-
const channel = msg.sessionId || 'general';
|
|
444
|
-
const sender = msg.senderName || msg.senderType || 'user';
|
|
445
|
-
this._log(`${name} message from ${sender} in ${channel}: ${content.slice(0, 80)}`);
|
|
446
|
-
|
|
447
|
-
// Send "thinking..." status
|
|
448
|
-
try {
|
|
449
|
-
await wsClient.sendMessage(network.id, channel, network.token, 'thinking...', {
|
|
450
|
-
senderType: 'agent', senderName: name, messageType: 'status',
|
|
451
|
-
});
|
|
452
|
-
} catch {}
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
let response;
|
|
456
|
-
if (binary) {
|
|
457
|
-
response = await this._runCliAgent(binary, content, channel, agentCfg, network, env);
|
|
458
|
-
} else {
|
|
459
|
-
response = `Agent type '${agentCfg.type}' has no CLI binary — cannot process message.`;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (response) {
|
|
463
|
-
await wsClient.sendMessage(network.id, channel, network.token, response, {
|
|
464
|
-
senderType: 'agent', senderName: name,
|
|
465
|
-
});
|
|
466
|
-
this._log(`${name} responded in ${channel}: ${response.slice(0, 80)}...`);
|
|
467
|
-
}
|
|
468
|
-
} catch (e) {
|
|
469
|
-
const errMsg = `Error: ${(e.message || String(e)).slice(0, 200)}`;
|
|
470
|
-
this._log(`${name} error handling message: ${errMsg}`);
|
|
471
|
-
try {
|
|
472
|
-
await wsClient.sendMessage(network.id, channel, network.token, errMsg, {
|
|
473
|
-
senderType: 'agent', senderName: name,
|
|
474
|
-
});
|
|
475
|
-
} catch {}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Build workspace context preamble for CLI agents.
|
|
481
|
-
* Teaches the agent about shared workspace APIs (browser, files).
|
|
482
|
-
*/
|
|
483
|
-
/**
|
|
484
|
-
* On Windows, resolve a .cmd shim to the underlying node script
|
|
485
|
-
* so we can spawn directly with node (avoiding cmd.exe argument limits).
|
|
486
|
-
*/
|
|
487
|
-
_resolveWindowsBinary(binary, env) {
|
|
488
|
-
const { execSync } = require('child_process');
|
|
489
|
-
try {
|
|
490
|
-
// Find the .cmd shim
|
|
491
|
-
const cmdPath = execSync(`where ${binary}`, {
|
|
492
|
-
encoding: 'utf-8', timeout: 5000, env,
|
|
493
|
-
}).split(/\r?\n/)[0].trim();
|
|
494
|
-
|
|
495
|
-
if (cmdPath.endsWith('.cmd')) {
|
|
496
|
-
// Read the .cmd file to find the target JS script
|
|
497
|
-
const cmdContent = fs.readFileSync(cmdPath, 'utf-8');
|
|
498
|
-
// npm .cmd shims have: "%~dp0\node_modules\...\bin\cli.js" %*
|
|
499
|
-
// or: @IF EXIST "%~dp0\node.exe" ... "%~dp0\node_modules\...\cli.js" %*
|
|
500
|
-
const match = cmdContent.match(/"([^"]+\.js)"/);
|
|
501
|
-
if (match) {
|
|
502
|
-
const jsPath = match[1].replace('%~dp0\\', path.dirname(cmdPath) + '\\');
|
|
503
|
-
return { binary: process.execPath, prefix: [jsPath] };
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
} catch {}
|
|
507
|
-
|
|
508
|
-
// Fallback: use cmd.exe /C (may truncate long args)
|
|
509
|
-
return { binary: process.env.COMSPEC || 'cmd.exe', prefix: ['/C', binary] };
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Install a SKILL.md into OpenClaw's workspace skills directory.
|
|
514
|
-
* OpenClaw auto-discovers skills from <workspace>/skills/ and injects
|
|
515
|
-
* them into the system prompt — same mechanism as the Python adapter.
|
|
516
|
-
*/
|
|
517
|
-
_installWorkspaceSkill(agentCfg, network) {
|
|
518
|
-
const baseUrl = 'https://workspace-endpoint.openagents.org';
|
|
519
|
-
const h = `Authorization: Bearer ${network.token}`;
|
|
520
|
-
const wsId = network.id;
|
|
521
|
-
const name = agentCfg.name;
|
|
522
|
-
const agentId = agentCfg.openclaw_agent_id || 'main';
|
|
523
|
-
|
|
524
|
-
const home = IS_WINDOWS ? process.env.USERPROFILE : process.env.HOME;
|
|
525
|
-
const openclawDir = path.join(home, '.openclaw');
|
|
526
|
-
const wsDir = agentId && agentId !== 'main'
|
|
527
|
-
? path.join(openclawDir, `workspace-${agentId}`)
|
|
528
|
-
: path.join(openclawDir, 'workspace');
|
|
529
|
-
|
|
530
|
-
if (!fs.existsSync(wsDir)) {
|
|
531
|
-
this._log(`OpenClaw workspace not found at ${wsDir}, skipping skill install`);
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const skillName = `openagents-workspace-${name}`;
|
|
536
|
-
const skillDir = path.join(wsDir, 'skills', skillName);
|
|
537
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
538
|
-
|
|
539
|
-
const content = [
|
|
540
|
-
'---',
|
|
541
|
-
'name: openagents-workspace',
|
|
542
|
-
'description: "Share files, browse websites, and collaborate with other agents in an OpenAgents workspace. Use when: (1) sharing results or reports, (2) browsing a website, (3) reading shared files, (4) checking who else is in the workspace."',
|
|
543
|
-
'metadata:',
|
|
544
|
-
' {"openclaw": {"always": true, "emoji": "\\U0001F310"}}',
|
|
545
|
-
'---',
|
|
546
|
-
'',
|
|
547
|
-
`You are agent "${name}" in workspace "${network.slug || network.name || wsId}".`,
|
|
548
|
-
'',
|
|
549
|
-
'## Workspace Tools (MANDATORY)',
|
|
550
|
-
'',
|
|
551
|
-
'Use your `exec` tool to run the `curl` commands below.',
|
|
552
|
-
'Do NOT use native browsing tools for workspace browser tasks — use `exec` + `curl`.',
|
|
553
|
-
'The workspace browser is a *shared* browser visible to all users and agents.',
|
|
554
|
-
'',
|
|
555
|
-
'### Shared Browser',
|
|
556
|
-
'',
|
|
557
|
-
'**To browse a website**, exec these steps:',
|
|
558
|
-
`Step 1 — open tab: curl -s -X POST ${baseUrl}/v1/browser/tabs -H "${h}" -H "Content-Type: application/json" -d '{"url":"https://example.com","network":"${wsId}","source":"openagents:${name}"}'`,
|
|
559
|
-
`Step 2 — read content: curl -s -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID/snapshot`,
|
|
560
|
-
`Step 3 — close tab: curl -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/TAB_ID`,
|
|
561
|
-
'(Replace TAB_ID with the id from step 1 response)',
|
|
562
|
-
'',
|
|
563
|
-
`List tabs: \`curl -s -H "${h}" ${baseUrl}/v1/browser/tabs?network=${wsId}\``,
|
|
564
|
-
`Navigate: \`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ${baseUrl}/v1/browser/tabs/{tab_id}/navigate -d '{"url":"URL"}'\``,
|
|
565
|
-
`Click: \`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ${baseUrl}/v1/browser/tabs/{tab_id}/click -d '{"selector":"CSS"}'\``,
|
|
566
|
-
`Type: \`curl -s -X POST -H "${h}" -H "Content-Type: application/json" ${baseUrl}/v1/browser/tabs/{tab_id}/type -d '{"selector":"CSS","text":"TEXT"}'\``,
|
|
567
|
-
`Close: \`curl -s -X DELETE -H "${h}" ${baseUrl}/v1/browser/tabs/{tab_id}\``,
|
|
568
|
-
'',
|
|
569
|
-
'### Workspace Files',
|
|
570
|
-
'',
|
|
571
|
-
`List: \`curl -s -H "${h}" ${baseUrl}/v1/files?network=${wsId}\``,
|
|
572
|
-
`Read: \`curl -s -H "${h}" ${baseUrl}/v1/files/FILE_PATH?network=${wsId}\``,
|
|
573
|
-
`Write: \`curl -s -X PUT -H "${h}" -H "Content-Type: application/json" ${baseUrl}/v1/files/FILE_PATH -d '{"content":"...","network":"${wsId}"}'\``,
|
|
574
|
-
'',
|
|
575
|
-
].join('\n');
|
|
576
|
-
|
|
577
|
-
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
578
|
-
fs.writeFileSync(skillPath, content, 'utf-8');
|
|
579
|
-
this._log(`Installed workspace skill at ${skillPath}`);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
_runCliAgent(binary, message, channel, agentCfg, network, env) {
|
|
583
|
-
return new Promise((resolve, reject) => {
|
|
584
|
-
const sessionKey = `openagents-${network.id.slice(0, 8)}-${channel.slice(-8)}`;
|
|
585
|
-
const agentId = agentCfg.openclaw_agent_id || 'main';
|
|
586
|
-
|
|
587
|
-
const args = ['agent', '--local', '--agent', agentId,
|
|
588
|
-
'--session-id', sessionKey, '--message', message, '--json'];
|
|
589
|
-
|
|
590
|
-
this._log(`${agentCfg.name} CLI: ${binary} ${args.slice(0, 5).join(' ')} ...`);
|
|
591
|
-
|
|
592
|
-
const spawnEnv = { ...env };
|
|
593
|
-
if (IS_WINDOWS) {
|
|
594
|
-
const npmBin = path.join(process.env.APPDATA || '', 'npm');
|
|
595
|
-
if (npmBin && !(spawnEnv.PATH || '').includes(npmBin)) {
|
|
596
|
-
spawnEnv.PATH = npmBin + ';' + (spawnEnv.PATH || process.env.PATH || '');
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// On Windows, use cmd.exe /C for .cmd shim resolution
|
|
601
|
-
let spawnBinary = binary;
|
|
602
|
-
let spawnArgs = args;
|
|
603
|
-
const spawnOpts = {
|
|
604
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
605
|
-
env: spawnEnv,
|
|
606
|
-
timeout: 600000,
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
if (IS_WINDOWS) {
|
|
610
|
-
spawnBinary = process.env.COMSPEC || 'cmd.exe';
|
|
611
|
-
const quotedArgs = args.map((a) => a.includes(' ') ? `"${a}"` : a);
|
|
612
|
-
spawnArgs = ['/C', binary, ...quotedArgs];
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const proc = spawn(spawnBinary, spawnArgs, spawnOpts);
|
|
617
|
-
let stdout = '';
|
|
618
|
-
let stderr = '';
|
|
619
|
-
|
|
620
|
-
if (proc.stdout) proc.stdout.on('data', (d) => { stdout += d; });
|
|
621
|
-
if (proc.stderr) proc.stderr.on('data', (d) => { stderr += d; });
|
|
622
|
-
|
|
623
|
-
proc.on('error', (err) => reject(err));
|
|
624
|
-
proc.on('exit', (code) => {
|
|
625
|
-
if (code !== 0) {
|
|
626
|
-
reject(new Error(`CLI exited ${code}: ${stderr.slice(0, 300)}`));
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
stdout = stdout.trim();
|
|
631
|
-
if (!stdout) { resolve(''); return; }
|
|
632
|
-
|
|
633
|
-
// Parse JSON output — find first '{'
|
|
634
|
-
const jsonStart = stdout.indexOf('{');
|
|
635
|
-
if (jsonStart < 0) { resolve(stdout); return; }
|
|
636
|
-
|
|
637
|
-
try {
|
|
638
|
-
const data = JSON.parse(stdout.slice(jsonStart));
|
|
639
|
-
const payloads = data.payloads || [];
|
|
640
|
-
if (payloads.length > 0) {
|
|
641
|
-
const texts = payloads.filter((p) => p.text).map((p) => p.text);
|
|
642
|
-
resolve(texts.join('\n\n'));
|
|
643
|
-
} else {
|
|
644
|
-
resolve(stdout.slice(0, jsonStart).trim() || '');
|
|
645
|
-
}
|
|
646
|
-
} catch {
|
|
647
|
-
resolve(stdout);
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
}
|
|
397
|
+
// NOTE: Adapter-specific message handling (openclaw, claude, codex)
|
|
398
|
+
// has been moved to src/adapters/. The daemon delegates via createAdapter().
|
|
652
399
|
|
|
653
400
|
_resolveAgentBinary(agentCfg) {
|
|
654
401
|
const entry = this.registry.getEntry(agentCfg.type);
|