@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagents-org/agent-launcher",
3
- "version": "0.2.130",
3
+ "version": "0.2.132",
4
4
  "description": "OpenAgents Launcher — install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/registry.json CHANGED
@@ -209,14 +209,16 @@
209
209
  "order": 4,
210
210
  "builtin": true,
211
211
  "install": {
212
- "binary": "agent",
213
- "verify": "agent --version 2>/dev/null | head -1",
214
- "verify_win": "agent --version 2>nul",
212
+ "binary": "cursor-agent",
213
+ "binary_aliases": [
214
+ "agent"
215
+ ],
216
+ "verify": "cursor-agent --version 2>/dev/null | head -1",
217
+ "verify_win": "cursor-agent --version 2>nul",
215
218
  "requires": [],
216
219
  "macos": "curl https://cursor.com/install -fsSL | bash",
217
220
  "linux": "curl https://cursor.com/install -fsSL | bash",
218
- "windows": "irm 'https://cursor.com/install?win32=true' | iex",
219
- "npm": "npm install -g @cursor/cli"
221
+ "windows": "\"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoProfile -ExecutionPolicy Bypass -Command \"Invoke-RestMethod -UseBasicParsing 'https://cursor.com/install?win32=true' | Invoke-Expression\""
220
222
  },
221
223
  "adapter": {
222
224
  "module": "openagents.adapters.cursor",
@@ -53,6 +53,11 @@ class BaseAdapter {
53
53
  // Per-channel task tracking for parallel execution
54
54
  this._channelBusy = new Set();
55
55
  this._channelQueues = {};
56
+ // Cached workspace.browser_enabled. Populated lazily on first read so we
57
+ // don't pay an HTTP roundtrip per message — adapters that toggle the
58
+ // workspace flag must reconnect/restart to pick up the change (matches
59
+ // the Python adapter behavior in workspace_prompt.py).
60
+ this._browserEnabledCache = null;
56
61
  // Wall-clock timestamp of adapter init, used by the `status` control
57
62
  // action to report uptime back to the channel. Reset on reinstantiation
58
63
  // (e.g. after a `restart` IPC bounce) so uptime tracks "time since last
@@ -85,6 +90,19 @@ class BaseAdapter {
85
90
  this._log(`Warning: join failed: ${e.message} \nStack: ${e.stack}`);
86
91
  }
87
92
 
93
+ // Sync workspace-managed skills into disabledModules
94
+ try {
95
+ const agents = await this.client.getAgents(this.workspaceId, this.token);
96
+ const self = agents.find((a) => a.agentName === this.agentName);
97
+ if (self && self.enabledSkills) {
98
+ const { skillsToDisabledModules } = require('../skill-catalog');
99
+ this.disabledModules = skillsToDisabledModules(self.enabledSkills);
100
+ this._log(`Synced skills from workspace: disabled=[${[...this.disabledModules].join(',')}]`);
101
+ }
102
+ } catch (e) {
103
+ this._log(`Warning: skill sync failed (non-fatal): ${e.message}`);
104
+ }
105
+
88
106
  // Fast-path operations (control-event cursor + heartbeat + control poll)
89
107
  // run BEFORE the message-cursor advance. Even though _skipExistingEvents
90
108
  // is fast on a healthy backend, we don't want slash commands gated on
@@ -210,6 +228,8 @@ class BaseAdapter {
210
228
  async _onControlAction(action, payload) {
211
229
  if (action === 'status') {
212
230
  await this._postStatusReport(payload);
231
+ } else if (action === 'routines') {
232
+ await this._postRoutinesReport(payload);
213
233
  }
214
234
  }
215
235
 
@@ -262,6 +282,74 @@ class BaseAdapter {
262
282
  }
263
283
  }
264
284
 
285
+ /**
286
+ * Post a markdown table of the agent's active routines back to the
287
+ * requesting channel. Used by the `/routines` slash command. Each agent
288
+ * reports only routines it owns (created_by === openagents:<agentName>)
289
+ * so the user sees a clear "my routines" view per agent, mirroring how
290
+ * /status reports per-agent uptime.
291
+ */
292
+ async _postRoutinesReport(payload) {
293
+ const channel = (payload && typeof payload === 'object') ? payload.channel : null;
294
+ if (!channel) return;
295
+
296
+ let routines = [];
297
+ try {
298
+ const data = await this.client.listRoutines(this.workspaceId, channel, this.token);
299
+ // Accept both the canonical `openagents:<name>` source and the bare
300
+ // `<name>` form. Agents that follow the workspace prompt verbatim
301
+ // produce the prefixed form, but some agents send the bare name when
302
+ // they construct the POST body themselves.
303
+ const prefixed = `openagents:${this.agentName}`;
304
+ routines = ((data && data.routines) || []).filter(
305
+ (r) => r.created_by === prefixed || r.created_by === this.agentName,
306
+ );
307
+ } catch (e) {
308
+ this._log(`Routines: failed to list: ${e && e.message ? e.message : e}`);
309
+ try {
310
+ await this.client.sendMessage(
311
+ this.workspaceId, channel, this.token,
312
+ `**Routines for \`${this.agentName}\`**\n\n_Failed to fetch routines._`,
313
+ { senderType: 'agent', senderName: this.agentName, messageType: 'chat', sessionId: this._sessionId },
314
+ );
315
+ } catch {}
316
+ return;
317
+ }
318
+
319
+ let content;
320
+ if (!routines.length) {
321
+ content = `**Routines for \`${this.agentName}\`**\n\n_No active routines._`;
322
+ } else {
323
+ const rows = routines.map((r) => {
324
+ const schedule = (r.schedule_interval_minutes != null)
325
+ ? `every ${r.schedule_interval_minutes} min`
326
+ : `${String(r.schedule_hour ?? 0).padStart(2, '0')}:${String(r.schedule_minute ?? 0).padStart(2, '0')} UTC` +
327
+ (r.schedule_days ? ` (days [${r.schedule_days.join(',')}])` : ' daily');
328
+ const next = r.next_fires_at || '—';
329
+ const name = String(r.name || '').replace(/\|/g, '\\|');
330
+ const id = String(r.id || '').slice(0, 8);
331
+ return `| \`${id}\` | ${name} | ${schedule} | ${next} |`;
332
+ });
333
+ content =
334
+ `**Routines for \`${this.agentName}\`** (${routines.length})\n\n` +
335
+ '| ID | Name | Schedule | Next fires |\n' +
336
+ '|---|---|---|---|\n' +
337
+ rows.join('\n');
338
+ }
339
+
340
+ try {
341
+ await this.client.sendMessage(this.workspaceId, channel, this.token, content, {
342
+ senderType: 'agent',
343
+ senderName: this.agentName,
344
+ messageType: 'chat',
345
+ metadata: { agent_mode: this._mode },
346
+ sessionId: this._sessionId,
347
+ });
348
+ } catch (e) {
349
+ this._log(`Routines: failed to post: ${e && e.message ? e.message : e}`);
350
+ }
351
+ }
352
+
265
353
  _hasActiveWork() {
266
354
  return this._channelBusy.size > 0;
267
355
  }
@@ -306,7 +394,7 @@ class BaseAdapter {
306
394
 
307
395
  while (this._running) {
308
396
  pollCount++;
309
- let messages, rawCursor;
397
+ let messages, rawCursor, composingActive = false;
310
398
  try {
311
399
  const result = await this.client.pollPending(
312
400
  this.workspaceId, this.agentName, this.token,
@@ -314,8 +402,9 @@ class BaseAdapter {
314
402
  );
315
403
  messages = result.messages;
316
404
  rawCursor = result.cursor;
405
+ composingActive = !!result.composing;
317
406
  if (pollCount <= 3 || pollCount % 20 === 0) {
318
- this._log(`Poll #${pollCount}: ${messages.length} messages, cursor=${rawCursor || 'none'}`);
407
+ this._log(`Poll #${pollCount}: ${messages.length} messages, cursor=${rawCursor || 'none'}${composingActive ? ' composing' : ''}`);
319
408
  }
320
409
  } catch (e) {
321
410
  this._log(`Poll #${pollCount} failed: ${e.message} \nStack: ${e.stack}`);
@@ -384,11 +473,26 @@ class BaseAdapter {
384
473
  }
385
474
  }
386
475
 
387
- // Adaptive polling: 2s active, up to 15s idle.
388
- // Each connected agent runs this loop, so faster rates multiply across
389
- // every workspace member — keep this conservative and tune separately
390
- // with a load-impact analysis on workspace-endpoint.
391
- const delay = incoming.length > 0 ? 2000 : Math.min(2000 + idleCount * 1000, 15000);
476
+ // Adaptive polling with warm plateau:
477
+ // Active (messages incoming): 2s
478
+ // Warm (≤5 min since last msg): 5s
479
+ // Cooldown (5-7 min): 5s → 15s (ramp 1s per idle poll)
480
+ // Cold (>7 min): 15s
481
+ // The warm plateau keeps the agent responsive during typical user
482
+ // think-time between messages without hammering the backend.
483
+ const WARM_INTERVAL = 5000;
484
+ const WARM_POLLS = 60; // 60 × 5s = 5 minutes warm plateau
485
+ let delay;
486
+ if (incoming.length > 0) {
487
+ delay = 2000;
488
+ } else if (composingActive) {
489
+ delay = 2000;
490
+ idleCount = Math.min(idleCount, WARM_POLLS);
491
+ } else if (idleCount <= WARM_POLLS) {
492
+ delay = WARM_INTERVAL;
493
+ } else {
494
+ delay = Math.min(WARM_INTERVAL + (idleCount - WARM_POLLS) * 1000, 15000);
495
+ }
392
496
  await this._sleep(delay);
393
497
  }
394
498
  }
@@ -636,6 +740,25 @@ class BaseAdapter {
636
740
  _sleep(ms) {
637
741
  return new Promise((resolve) => setTimeout(resolve, ms));
638
742
  }
743
+
744
+ /**
745
+ * Return whether the workspace has the Browser Fabric viewer toggle on.
746
+ * Cached for the lifetime of the adapter — restart to pick up a flip.
747
+ * Falls back to false on error so the prompt builders don't accidentally
748
+ * inject the strong directive against an older backend that can't route
749
+ * to Browser Fabric.
750
+ */
751
+ async getBrowserEnabled() {
752
+ if (this._browserEnabledCache === null) {
753
+ try {
754
+ const meta = await this.client.getWorkspaceMetadata(this.workspaceId, this.token);
755
+ this._browserEnabledCache = !!(meta && meta.browserEnabled);
756
+ } catch (e) {
757
+ this._browserEnabledCache = false;
758
+ }
759
+ }
760
+ return this._browserEnabledCache;
761
+ }
639
762
  }
640
763
 
641
764
  // ------------------------------------------------------------------