@openagents-org/agent-launcher 0.2.130 → 0.2.132

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.
@@ -171,40 +171,59 @@ class CursorAdapter extends BaseAdapter {
171
171
  _findCursorBinary() {
172
172
  const home = os.homedir();
173
173
  const ext = IS_WINDOWS ? '.cmd' : '';
174
+ const names = ['cursor-agent', 'agent'];
174
175
 
175
176
  // Tier 0: Isolated runtime prefix
176
- const runtimeCandidate = path.join(home, '.openagents', 'runtimes', 'cursor', 'node_modules', '.bin', `agent${ext}`);
177
- if (fs.existsSync(runtimeCandidate)) return runtimeCandidate;
177
+ for (const name of names) {
178
+ const runtimeCandidate = path.join(home, '.openagents', 'runtimes', 'cursor', 'node_modules', '.bin', `${name}${ext}`);
179
+ if (fs.existsSync(runtimeCandidate)) return runtimeCandidate;
180
+ }
178
181
 
179
182
  // Tier 0b: Legacy portable install
180
- const portableCandidate = path.join(home, '.openagents', 'nodejs', 'node_modules', '.bin', `agent${ext}`);
181
- if (fs.existsSync(portableCandidate)) return portableCandidate;
183
+ for (const name of names) {
184
+ const portableCandidate = path.join(home, '.openagents', 'nodejs', 'node_modules', '.bin', `${name}${ext}`);
185
+ if (fs.existsSync(portableCandidate)) return portableCandidate;
186
+ }
182
187
 
183
188
  // Tier 1: PATH search
184
189
  try {
185
190
  if (IS_WINDOWS) {
186
- const r = execSync('where agent.cmd 2>nul || where agent.exe 2>nul || where agent 2>nul', {
191
+ const r = execSync('where cursor-agent.cmd 2>nul || where cursor-agent.exe 2>nul || where cursor-agent 2>nul || where agent.cmd 2>nul || where agent.exe 2>nul || where agent 2>nul', {
187
192
  encoding: 'utf-8', timeout: 5000,
188
193
  });
189
194
  return r.split(/\r?\n/)[0].trim();
190
195
  } else {
191
- return execSync('which agent', { encoding: 'utf-8', timeout: 5000 }).trim();
196
+ return execSync('which cursor-agent || which agent', { encoding: 'utf-8', timeout: 5000 }).trim();
192
197
  }
193
198
  } catch {}
194
199
 
195
200
  // Tier 2: Next to current Node.js interpreter (npm global)
196
201
  const nodeBinDir = path.dirname(process.execPath);
197
- const nearNode = path.join(nodeBinDir, `agent${ext}`);
198
- if (fs.existsSync(nearNode)) return nearNode;
202
+ for (const name of names) {
203
+ const nearNode = path.join(nodeBinDir, `${name}${ext}`);
204
+ if (fs.existsSync(nearNode)) return nearNode;
205
+ }
199
206
 
200
207
  // Tier 3: Common install locations
201
208
  const candidates = IS_WINDOWS ? [
209
+ path.join(home, '.cursor', 'bin', 'cursor-agent.cmd'),
210
+ path.join(home, '.cursor', 'bin', 'cursor-agent.exe'),
211
+ path.join(home, '.cursor', 'bin', 'cursor-agent'),
212
+ path.join(home, '.cursor', 'bin', 'agent.cmd'),
213
+ path.join(home, '.cursor', 'bin', 'agent.exe'),
214
+ path.join(home, '.cursor', 'bin', 'agent'),
215
+ path.join(process.env.APPDATA || '', 'npm', 'cursor-agent.cmd'),
202
216
  path.join(process.env.APPDATA || '', 'npm', 'agent.cmd'),
203
217
  ] : [
218
+ path.join(home, '.local', 'bin', 'cursor-agent'),
204
219
  path.join(home, '.local', 'bin', 'agent'),
220
+ path.join(home, '.npm-global', 'bin', 'cursor-agent'),
205
221
  path.join(home, '.npm-global', 'bin', 'agent'),
222
+ path.join(home, '.cursor', 'bin', 'cursor-agent'),
206
223
  path.join(home, '.cursor', 'bin', 'agent'),
224
+ '/opt/homebrew/bin/cursor-agent',
207
225
  '/opt/homebrew/bin/agent',
226
+ '/usr/local/bin/cursor-agent',
208
227
  '/usr/local/bin/agent',
209
228
  ];
210
229
  for (const c of candidates) {
@@ -11,6 +11,51 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ /**
15
+ * Strong directive forcing agents to use the workspace browser when the
16
+ * workspace has Browser Fabric enabled. Emitted high in the system prompt
17
+ * so it wins against any earlier guidance that suggests local browsing
18
+ * tools.
19
+ *
20
+ * Returns '' when the toggle is off; the caller can unconditionally
21
+ * concatenate the result.
22
+ *
23
+ * Mirrors `build_browser_directive` in
24
+ * sdk/src/openagents/adapters/workspace_prompt.py.
25
+ */
26
+ function buildBrowserDirective(browserEnabled) {
27
+ if (!browserEnabled) return '';
28
+ return (
29
+ '\n## Browser Use (MANDATORY)\n' +
30
+ 'This workspace has the **shared Browser Fabric session** enabled. ' +
31
+ 'All web browsing MUST go through it so the user can watch the ' +
32
+ 'session live in their right-side panel and so cookies / state ' +
33
+ 'persist across agents.\n\n' +
34
+ '**Use ONLY these tools for any web browsing:**\n' +
35
+ '- `mcp__openagents-workspace__workspace_browser_open`\n' +
36
+ '- `mcp__openagents-workspace__workspace_browser_navigate`\n' +
37
+ '- `mcp__openagents-workspace__workspace_browser_click`\n' +
38
+ '- `mcp__openagents-workspace__workspace_browser_type`\n' +
39
+ '- `mcp__openagents-workspace__workspace_browser_snapshot`\n' +
40
+ '- `mcp__openagents-workspace__workspace_browser_screenshot`\n' +
41
+ '- `mcp__openagents-workspace__workspace_browser_list_tabs`\n' +
42
+ '- `mcp__openagents-workspace__workspace_browser_close`\n' +
43
+ '\n' +
44
+ 'If you don\'t have these MCP tools, use `Bash` + `curl` against ' +
45
+ '`/v1/browser/tabs` (documented below in Shared Browser).\n\n' +
46
+ '**FORBIDDEN — do NOT call any of these:**\n' +
47
+ '- `mcp__browsermcp__*` (any local Browser MCP extension tool)\n' +
48
+ '- `mcp__playwright__*`, `mcp__puppeteer__*`, `mcp__chrome-devtools__*`, or any other local-browser MCP\n' +
49
+ '- `WebFetch`, `WebSearch`, `web_fetch`, `web_search`, or any built-in network/browser tool\n' +
50
+ '\n' +
51
+ 'If a local browser tool errors with "extension isn\'t connected" or ' +
52
+ '"connect your browser", do NOT ask the user to connect anything — ' +
53
+ 'the local extension is irrelevant here. Immediately switch to the ' +
54
+ 'workspace browser tools above. The Browser Fabric session is already ' +
55
+ 'running on the backend.\n'
56
+ );
57
+ }
58
+
14
59
  /**
15
60
  * Build the identity section common to all adapters.
16
61
  */
@@ -91,6 +136,7 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
91
136
  const caps = [];
92
137
  if (!disabled.has('files')) caps.push('share and read files with other agents and users');
93
138
  if (!disabled.has('browser')) caps.push('browse websites in a shared browser');
139
+ if (!disabled.has('knowledge')) caps.push('create and access a shared knowledge base');
94
140
  caps.push('discover other agents in the workspace');
95
141
 
96
142
  sections.push(
@@ -247,7 +293,7 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
247
293
  }
248
294
 
249
295
  // To-Dos (planning)
250
- if (!isPlan) {
296
+ if (!isPlan && !disabled.has('todos')) {
251
297
  sections.push(
252
298
  '\n### To-Do List (Planning)\n\n' +
253
299
  'Create or update your to-do list to track progress. The entire list ' +
@@ -270,7 +316,7 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
270
316
  }
271
317
 
272
318
  // Timers
273
- if (!isPlan) {
319
+ if (!isPlan && !disabled.has('timers')) {
274
320
  sections.push(
275
321
  '\n### Timers\n\n' +
276
322
  'Set a timer that will send you a message after a delay, waking you up ' +
@@ -291,33 +337,82 @@ function buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channel
291
337
  }
292
338
 
293
339
  // Routines (recurring scheduled tasks)
294
- if (!isPlan) {
340
+ if (!isPlan && !disabled.has('routines')) {
295
341
  sections.push(
296
342
  '\n### Routines (Recurring Tasks)\n\n' +
297
- 'Create a recurring routine that fires on a schedule and posts a message ' +
298
- 'to the channel, waking you to do the work. Great for daily standups, ' +
299
- 'periodic reviews, scheduled checks.\n\n' +
300
- '**Schedule:** Specify `hour` (0-23 UTC), `minute` (0-59), and optional ' +
301
- '`days` array (0=Mon, 6=Sun). Omit `days` for every day.\n\n' +
302
- '**Create a routine:**\n' +
343
+ 'Create a recurring routine that fires on a schedule. Each routine gets ' +
344
+ '**its own dedicated thread** (`routine:<id>`) so different routines never ' +
345
+ 'interfere, and the full context is preserved.\n\n' +
346
+ '**`context` is required** — provide a thorough description of what the ' +
347
+ 'routine should do, any background info, and relevant details from the ' +
348
+ 'current conversation. This context is posted at the start of the routine\'s ' +
349
+ 'thread every time it fires, so you have full background.\n\n' +
350
+ '**Two schedule modes:**\n' +
351
+ '- **Daily**: `hour` (0-23 UTC) + `minute` (0-59), optional `days` ' +
352
+ 'array (0=Mon, 6=Sun). Omit `days` for every day.\n' +
353
+ '- **Interval**: `interval_minutes` (1-1440). Fires every N minutes. ' +
354
+ 'Mutually exclusive with `hour`/`minute`.\n\n' +
355
+ '**Create a daily routine:**\n' +
303
356
  `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
304
357
  `${baseUrl}/v1/routines -d '{"name":"Daily PR Review","message":"Review open PRs",` +
358
+ `"context":"Review all open pull requests on the main repo. Check for merge conflicts, ` +
359
+ `CI failures, and stale PRs older than 3 days. Post a summary to the workspace.",` +
305
360
  `"hour":8,"minute":0,` +
306
- `"network":"${workspaceId}","channel":"${channelName}",` +
307
- `"source":"openagents:${agentName}"}'\`\n\n` +
308
- '**Create a weekday-only routine (Mon-Fri):**\n' +
309
- `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
310
- `${baseUrl}/v1/routines -d '{"name":"Morning Standup","message":"Post standup summary",` +
311
- `"hour":9,"minute":0,"days":[0,1,2,3,4],` +
312
- `"network":"${workspaceId}","channel":"${channelName}",` +
361
+ `"network":"${workspaceId}",` +
313
362
  `"source":"openagents:${agentName}"}'\`\n\n` +
314
363
  '**List active routines:**\n' +
315
- `\`${curl} -s -H "${h}" "${baseUrl}/v1/routines?network=${workspaceId}&channel=${channelName}"\`\n\n` +
364
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/routines?network=${workspaceId}"\`\n\n` +
316
365
  '**Cancel a routine:**\n' +
317
366
  `\`${curl} -s -X DELETE -H "${h}" ${baseUrl}/v1/routines/ROUTINE_ID\`\n`
318
367
  );
319
368
  }
320
369
 
370
+ // Notifications / Inbox
371
+ if (!isPlan && !disabled.has('notifications')) {
372
+ sections.push(
373
+ '\n### Notifications (Inbox)\n\n' +
374
+ 'Send notifications to the workspace inbox. Notifications appear in a ' +
375
+ 'dedicated panel separate from the chat stream. Use for task completions, ' +
376
+ 'important findings, or anything that needs human attention.\n\n' +
377
+ '**Send a notification:**\n' +
378
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
379
+ `${baseUrl}/v1/notifications -d '{"title":"Task Complete","message":"The analysis is ready.",` +
380
+ `"priority":"normal","channel":"${channelName}",` +
381
+ `"network":"${workspaceId}",` +
382
+ `"source":"openagents:${agentName}"}'\`\n\n` +
383
+ '**Priority values:** `low`, `normal`, `high`\n\n' +
384
+ '**List notifications:**\n' +
385
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/notifications?network=${workspaceId}"\`\n`
386
+ );
387
+ }
388
+
389
+ // Knowledge Base
390
+ if (!isPlan && !disabled.has('knowledge')) {
391
+ sections.push(
392
+ '\n### Knowledge Base\n\n' +
393
+ 'The workspace has a shared knowledge base of markdown documents. ' +
394
+ 'Use it to store and retrieve shared information like API docs, ' +
395
+ 'design decisions, project conventions, and other reference material. ' +
396
+ 'Knowledge entries are accessible to all agents via @knowledge:slug mentions.\n\n' +
397
+ '**Create a knowledge entry:**\n' +
398
+ `\`${curl} -s -X POST -H "${h}" -H "Content-Type: application/json" ` +
399
+ `${baseUrl}/v1/knowledge -d '{"title":"API Design Patterns","content":"# API Design Patterns\\n\\n...",` +
400
+ `"description":"Common API patterns used in this project",` +
401
+ `"network":"${workspaceId}",` +
402
+ `"source":"openagents:${agentName}"}'\`\n\n` +
403
+ '**List knowledge entries:**\n' +
404
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/knowledge?network=${workspaceId}"\`\n\n` +
405
+ '**Read a knowledge entry by slug:**\n' +
406
+ `\`${curl} -s -H "${h}" "${baseUrl}/v1/knowledge/by-slug/api-design-patterns?network=${workspaceId}"\`\n\n` +
407
+ '**Update a knowledge entry:**\n' +
408
+ `\`${curl} -s -X PUT -H "${h}" -H "Content-Type: application/json" ` +
409
+ `${baseUrl}/v1/knowledge/ENTRY_ID -d '{"title":"Updated Title","content":"# Updated\\n\\n...",` +
410
+ `"network":"${workspaceId}","source":"openagents:${agentName}"}'\`\n\n` +
411
+ '**Delete a knowledge entry:**\n' +
412
+ `\`${curl} -s -X DELETE -H "${h}" "${baseUrl}/v1/knowledge/ENTRY_ID?network=${workspaceId}"\`\n`
413
+ );
414
+ }
415
+
321
416
  // Discovery
322
417
  sections.push(
323
418
  '\n### Discover Agents\n\n' +
@@ -352,7 +447,7 @@ function buildGuardrails() {
352
447
  * Build the system prompt for Claude adapter (MCP-based).
353
448
  * Claude gets identity + collaboration instructions but NOT API skills.
354
449
  */
355
- function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = 'execute' }) {
450
+ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = 'execute', browserEnabled = false }) {
356
451
  const parts = [];
357
452
  parts.push(buildWorkspaceIdentity(agentName, workspaceId, channelName, mode));
358
453
  parts.push(
@@ -360,8 +455,12 @@ function buildClaudeSystemPrompt({ agentName, workspaceId, channelName, mode = '
360
455
  'Use workspace_get_agents to see other agents.\n' +
361
456
  'Use workspace_put_todos to track your progress. ALWAYS create a to-do list when given multiple tasks or multi-step work.\n' +
362
457
  'Use workspace_create_timer to set a reminder that wakes you up later.\n' +
363
- 'Use workspace_create_routine to set up recurring scheduled tasks (e.g. daily reviews).\n'
458
+ 'Use workspace_create_routine to set up recurring scheduled tasks (e.g. daily reviews).\n' +
459
+ 'Use workspace_send_notification to send a notification to the workspace inbox when you complete a task or have important results.\n' +
460
+ 'Use workspace_write_knowledge to create or update shared knowledge base entries that persist across conversations.\n' +
461
+ 'Use workspace_read_knowledge to read knowledge entries by ID or slug (from @knowledge:slug mentions).\n'
364
462
  );
463
+ parts.push(buildBrowserDirective(browserEnabled));
365
464
  parts.push(buildCollaborationPrompt());
366
465
  parts.push(buildA2UIPrompt());
367
466
 
@@ -457,9 +556,10 @@ function buildA2UIPrompt() {
457
556
  /**
458
557
  * Build the full system prompt for OpenClaw/non-MCP agents.
459
558
  */
460
- function buildOpenclawSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules }) {
559
+ function buildOpenclawSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules, browserEnabled = false }) {
461
560
  const parts = [];
462
561
  parts.push(buildWorkspaceIdentity(agentName, workspaceId, channelName, mode));
562
+ parts.push(buildBrowserDirective(browserEnabled));
463
563
  parts.push(buildCollaborationPrompt());
464
564
  parts.push(buildA2UIPrompt());
465
565
  parts.push(buildModePrompt(mode));
@@ -473,12 +573,13 @@ function buildOpenclawSystemPrompt({ agentName, workspaceId, channelName, endpoi
473
573
  /**
474
574
  * Build a SKILL.md file for OpenClaw's skill auto-discovery.
475
575
  */
476
- function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules }) {
576
+ function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules, browserEnabled = false }) {
477
577
  const body = buildApiSkillsPrompt({
478
578
  endpoint, workspaceId, token, agentName, channelName, disabledModules, mode: 'execute',
479
579
  });
480
580
 
481
581
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
582
+ const directive = buildBrowserDirective(browserEnabled);
482
583
  const collab = buildCollaborationPrompt();
483
584
 
484
585
  const frontmatter = (
@@ -495,18 +596,19 @@ function buildOpenclawSkillMd({ endpoint, workspaceId, token, agentName, channel
495
596
  '---\n\n'
496
597
  );
497
598
 
498
- return frontmatter + identity + '\n' + collab + '\n' + body + '\n' + buildGuardrails();
599
+ return frontmatter + identity + directive + '\n' + collab + '\n' + body + '\n' + buildGuardrails();
499
600
  }
500
601
 
501
602
  /**
502
603
  * Build system prompt for OpenCode adapter.
503
604
  */
504
- function buildOpenCodeSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules }) {
605
+ function buildOpenCodeSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules, browserEnabled = false }) {
505
606
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, mode);
607
+ const directive = buildBrowserDirective(browserEnabled);
506
608
  const collab = buildCollaborationPrompt();
507
609
  const modePrompt = buildModePrompt(mode);
508
610
  const api = buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode });
509
- return identity + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
611
+ return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
510
612
  }
511
613
 
512
614
  /**
@@ -540,7 +642,7 @@ function buildOpenCodeSkillMd({ endpoint, workspaceId, token, agentName, channel
540
642
  * of spawning an MCP server. Claude Code discovers the skill via its
541
643
  * .claude/skills/ directory and uses Bash + curl to call workspace APIs.
542
644
  */
543
- function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules }) {
645
+ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules, browserEnabled = false }) {
544
646
  const api = buildApiSkillsPrompt({
545
647
  endpoint, workspaceId, token, agentName,
546
648
  channelName: channelName || 'general',
@@ -549,6 +651,7 @@ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelNa
549
651
  });
550
652
 
551
653
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
654
+ const directive = buildBrowserDirective(browserEnabled);
552
655
  const collab = buildCollaborationPrompt();
553
656
 
554
657
  const frontmatter =
@@ -561,7 +664,7 @@ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelNa
561
664
  ' or collaborating with other agents via @mentions.\n' +
562
665
  '---\n\n';
563
666
 
564
- return frontmatter + identity + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
667
+ return frontmatter + identity + directive + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
565
668
  }
566
669
 
567
670
  /**
@@ -570,7 +673,7 @@ function buildClaudeSkillMd({ endpoint, workspaceId, token, agentName, channelNa
570
673
  * Written to .cursor/skills/openagents-workspace.md before each CLI spawn.
571
674
  * Cursor discovers skills from the .cursor/skills/ directory automatically.
572
675
  */
573
- function buildCursorSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules }) {
676
+ function buildCursorSkillMd({ endpoint, workspaceId, token, agentName, channelName, disabledModules, browserEnabled = false }) {
574
677
  const api = buildApiSkillsPrompt({
575
678
  endpoint, workspaceId, token, agentName,
576
679
  channelName: channelName || 'general',
@@ -579,6 +682,7 @@ function buildCursorSkillMd({ endpoint, workspaceId, token, agentName, channelNa
579
682
  });
580
683
 
581
684
  const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, 'execute');
685
+ const directive = buildBrowserDirective(browserEnabled);
582
686
  const collab = buildCollaborationPrompt();
583
687
 
584
688
  const frontmatter =
@@ -591,11 +695,12 @@ function buildCursorSkillMd({ endpoint, workspaceId, token, agentName, channelNa
591
695
  ' or collaborating with other agents via @mentions.\n' +
592
696
  '---\n\n';
593
697
 
594
- return frontmatter + identity + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
698
+ return frontmatter + identity + directive + '\n' + collab + '\n' + api + '\n' + buildGuardrails();
595
699
  }
596
700
 
597
701
  module.exports = {
598
702
  buildWorkspaceIdentity,
703
+ buildBrowserDirective,
599
704
  buildCollaborationPrompt,
600
705
  buildModePrompt,
601
706
  buildGuardrails,
package/src/cli.js CHANGED
@@ -502,6 +502,89 @@ async function cmdToolMode(connector, _flags, positional) {
502
502
  }
503
503
  }
504
504
 
505
+ async function cmdSkills(connector, _flags, positional) {
506
+ const { SKILL_CATALOG, getSkillDefaults } = require('./skill-catalog');
507
+ const toggleable = SKILL_CATALOG.filter(s => s.toggleable);
508
+ const first = positional[0];
509
+ const second = positional[1];
510
+ const third = positional[2];
511
+
512
+ // agn skills → list skills for all agents
513
+ if (!first) {
514
+ const agents = connector.config.getAgents();
515
+ if (agents.length === 0) { print('No agents configured'); return; }
516
+ for (const a of agents) {
517
+ const defaults = getSkillDefaults();
518
+ const skills = a.skills || {};
519
+ const parts = toggleable.map(s => {
520
+ const enabled = skills[s.id] !== undefined ? skills[s.id] : defaults[s.id];
521
+ return `${enabled ? '+' : '-'}${s.id}`;
522
+ });
523
+ print(` ${a.name}: ${parts.join(' ')}`);
524
+ }
525
+ print('\nUsage: agn skills <agent> [enable|disable <skill>]');
526
+ print('Available skills: ' + toggleable.map(s => s.id).join(', '));
527
+ return;
528
+ }
529
+
530
+ // agn skills catalog → show full catalog
531
+ if (first === 'catalog') {
532
+ print('Skill Hub — Available Skills:\n');
533
+ for (const s of SKILL_CATALOG) {
534
+ const tag = s.toggleable ? (s.defaultEnabled ? '[on]' : '[off]') : '[always]';
535
+ print(` ${s.id.padEnd(16)} ${tag.padEnd(10)} ${s.name}`);
536
+ print(` ${''.padEnd(16)} ${''.padEnd(10)} ${s.description}`);
537
+ print('');
538
+ }
539
+ return;
540
+ }
541
+
542
+ const agent = connector.config.getAgent(first);
543
+ if (!agent) { print(`Agent '${first}' not found`); process.exitCode = 1; return; }
544
+
545
+ // agn skills <agent> → show agent's skills
546
+ if (!second) {
547
+ const defaults = getSkillDefaults();
548
+ const skills = agent.skills || {};
549
+ print(`Skills for ${first}:\n`);
550
+ for (const s of toggleable) {
551
+ const enabled = skills[s.id] !== undefined ? skills[s.id] : defaults[s.id];
552
+ const marker = enabled ? ' ✓' : ' ✗';
553
+ print(`${marker} ${s.id.padEnd(14)} ${s.name} — ${s.description}`);
554
+ }
555
+ print('\nUsage: agn skills <agent> enable|disable <skill>');
556
+ return;
557
+ }
558
+
559
+ // agn skills <agent> enable|disable <skill>
560
+ if (second !== 'enable' && second !== 'disable') {
561
+ print(`Unknown action: ${second}. Use 'enable' or 'disable'.`);
562
+ process.exitCode = 1;
563
+ return;
564
+ }
565
+ if (!third) {
566
+ print(`Usage: agn skills ${first} ${second} <skill>`);
567
+ print('Available skills: ' + toggleable.map(s => s.id).join(', '));
568
+ process.exitCode = 1;
569
+ return;
570
+ }
571
+
572
+ const skillDef = toggleable.find(s => s.id === third);
573
+ if (!skillDef) {
574
+ print(`Unknown skill: ${third}`);
575
+ print('Available skills: ' + toggleable.map(s => s.id).join(', '));
576
+ process.exitCode = 1;
577
+ return;
578
+ }
579
+
580
+ const current = agent.skills || {};
581
+ const updated = { ...current, [third]: second === 'enable' };
582
+ connector.config.updateAgent(first, { skills: updated });
583
+ try { connector.sendDaemonCommand('reload'); } catch {}
584
+ const verb = second === 'enable' ? 'Enabled' : 'Disabled';
585
+ print(`${verb} '${skillDef.name}' for ${first}`);
586
+ }
587
+
505
588
  async function cmdTestLLM(connector, _flags, positional) {
506
589
  const type = positional[0];
507
590
  if (!type) { print('Usage: agn test-llm <type>'); return; }
@@ -566,6 +649,7 @@ Commands:
566
649
  connect <agent> <token> Connect agent to workspace
567
650
  disconnect <agent> Disconnect agent from workspace
568
651
  env <type> [--set K=V] View/set env vars for agent type
652
+ skills [agent] [action] Manage agent skills (enable/disable)
569
653
  tool-mode [agent] [mode] View/set tool mode (mcp or skills)
570
654
  autostart [--disable] Enable/disable auto-start on login
571
655
  test-llm <type> Test LLM connection
@@ -652,6 +736,7 @@ async function main() {
652
736
  autostart: () => cmdAutostart(connector, flags),
653
737
  workspace: () => cmdWorkspace(connector, flags, positional),
654
738
  env: () => cmdEnv(connector, flags, positional),
739
+ skills: () => cmdSkills(connector, flags, positional),
655
740
  'tool-mode': () => cmdToolMode(connector, flags, positional),
656
741
  'test-llm': () => cmdTestLLM(connector, flags, positional),
657
742
  update: () => cmdUpdate(),
package/src/daemon.js CHANGED
@@ -58,12 +58,20 @@ class Daemon {
58
58
  // Write PID file
59
59
  this._writePid();
60
60
 
61
- // Periodic status + command check
61
+ // Periodic status (heavy: JSON serialize + write) every 5s.
62
62
  this._statusInterval = setInterval(() => {
63
63
  this._writeStatus();
64
- this._processCommands();
65
64
  }, 5000);
66
65
 
66
+ // Command file poll (cheap: existsSync on a tiny file) every 200ms so
67
+ // start/stop/restart from the launcher feels responsive. With a 5s
68
+ // combined interval, users saw up to 5s before the daemon even noticed
69
+ // a Stop click — this was especially painful on Windows where there's
70
+ // no SIGHUP shortcut and Stop landed near the end of a tick.
71
+ this._cmdInterval = setInterval(() => {
72
+ this._processCommands();
73
+ }, 200);
74
+
67
75
  // Watch config file for hot-reload
68
76
  this._watchConfig();
69
77
 
@@ -429,6 +437,7 @@ class Daemon {
429
437
 
430
438
  async _adapterLoop(name, agentCfg, info, network) {
431
439
  const { createAdapter } = require('./adapters');
440
+ const { skillsToDisabledModules } = require('./skill-catalog');
432
441
  const agentType = agentCfg.type || 'openclaw';
433
442
  const endpoint = network.endpoint || 'https://workspace-endpoint.openagents.org';
434
443
 
@@ -442,7 +451,7 @@ class Daemon {
442
451
  endpoint,
443
452
  agentType,
444
453
  openclawAgentId: agentCfg.openclaw_agent_id || 'main',
445
- disabledModules: new Set(),
454
+ disabledModules: skillsToDisabledModules(agentCfg.skills),
446
455
  agentEnv: this._buildAgentEnv(agentCfg),
447
456
  workingDir: agentCfg.path || undefined,
448
457
  toolMode: agentCfg.tool_mode || 'skills',