@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 +1 -1
- package/registry.json +7 -5
- package/src/adapters/base.js +130 -7
- package/src/adapters/claude.js +342 -255
- package/src/adapters/cursor.js +27 -8
- package/src/adapters/workspace-prompt.js +133 -28
- package/src/cli.js +85 -0
- package/src/daemon.js +12 -3
- package/src/github-client.js +165 -0
- package/src/index.js +13 -1
- package/src/installer.js +151 -19
- package/src/mcp-server.js +279 -91
- package/src/paths.js +31 -3
- package/src/registry.js +4 -4
- package/src/skill-catalog.js +125 -0
- package/src/tui.js +1 -16
- package/src/update-check.js +5 -25
- package/src/workspace-client.js +101 -6
package/src/mcp-server.js
CHANGED
|
@@ -235,106 +235,202 @@ function buildToolDefs(disabledModules) {
|
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
// -- Todos
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
238
|
+
// -- Todos --
|
|
239
|
+
if (!disabledModules.has('todos')) {
|
|
240
|
+
tools.push(
|
|
241
|
+
{
|
|
242
|
+
name: 'workspace_put_todos',
|
|
243
|
+
description: 'Update your to-do list. Replaces the entire list each time (send full list with current statuses). Channel is auto-resolved.',
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
todos: {
|
|
248
|
+
type: 'array',
|
|
249
|
+
items: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
content: { type: 'string', description: 'Task description' },
|
|
253
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'], description: 'Task status' },
|
|
254
|
+
assignee: { type: 'string', description: 'Agent name to assign to (defaults to self)' },
|
|
255
|
+
},
|
|
256
|
+
required: ['content', 'status'],
|
|
254
257
|
},
|
|
255
|
-
|
|
258
|
+
description: 'Full to-do list with current statuses',
|
|
256
259
|
},
|
|
257
|
-
description: 'Full to-do list with current statuses',
|
|
258
260
|
},
|
|
261
|
+
required: ['todos'],
|
|
259
262
|
},
|
|
260
|
-
required: ['todos'],
|
|
261
263
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
264
|
+
{
|
|
265
|
+
name: 'workspace_get_todos',
|
|
266
|
+
description: 'Get to-do items for the current channel. Use all=true to see all agents\' todos.',
|
|
267
|
+
inputSchema: {
|
|
268
|
+
type: 'object',
|
|
269
|
+
properties: {
|
|
270
|
+
agent: { type: 'string', description: 'Filter by agent name' },
|
|
271
|
+
all: { type: 'boolean', description: 'Get all agents\' todos (default: own only)' },
|
|
272
|
+
},
|
|
271
273
|
},
|
|
272
274
|
},
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// -- Timers --
|
|
279
|
+
if (!disabledModules.has('timers')) {
|
|
280
|
+
tools.push(
|
|
281
|
+
{
|
|
282
|
+
name: 'workspace_create_timer',
|
|
283
|
+
description: 'Set a timer that posts a message to the channel after the specified delay.',
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
delay: { type: 'integer', description: 'Seconds until the timer fires (1-86400)' },
|
|
288
|
+
message: { type: 'string', description: 'Message to post when the timer fires' },
|
|
289
|
+
},
|
|
290
|
+
required: ['delay', 'message'],
|
|
282
291
|
},
|
|
283
|
-
required: ['delay', 'message'],
|
|
284
292
|
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
{
|
|
294
|
+
name: 'workspace_list_timers',
|
|
295
|
+
description: 'List active timers in the current channel.',
|
|
296
|
+
inputSchema: { type: 'object', properties: {} },
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'workspace_cancel_timer',
|
|
300
|
+
description: 'Cancel an active timer by its ID.',
|
|
301
|
+
inputSchema: {
|
|
302
|
+
type: 'object',
|
|
303
|
+
properties: {
|
|
304
|
+
timer_id: { type: 'string', description: 'Timer ID to cancel' },
|
|
305
|
+
},
|
|
306
|
+
required: ['timer_id'],
|
|
298
307
|
},
|
|
299
|
-
required: ['timer_id'],
|
|
300
308
|
},
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
type: '
|
|
314
|
-
|
|
315
|
-
description: '
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// -- Routines --
|
|
313
|
+
if (!disabledModules.has('routines')) {
|
|
314
|
+
tools.push(
|
|
315
|
+
{
|
|
316
|
+
name: 'workspace_create_routine',
|
|
317
|
+
description: 'Create a recurring scheduled routine. Each routine gets its own dedicated thread with full context. Two schedule modes: daily (hour+minute, optional days) or interval (every N minutes).',
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: 'object',
|
|
320
|
+
properties: {
|
|
321
|
+
name: { type: 'string', description: 'Human-readable label for the routine (e.g. "Daily PR Review")' },
|
|
322
|
+
message: { type: 'string', description: 'Short trigger message posted each time the routine fires' },
|
|
323
|
+
context: { type: 'string', description: 'Comprehensive context for the routine: what it should do, background info, goals, relevant details from the current conversation. This is posted at the start of the routine\'s dedicated thread so you have full background every time it fires. Be thorough — this is your only memory of why this routine exists.' },
|
|
324
|
+
hour: { type: 'integer', description: 'Daily mode: hour in UTC (0-23). Omit if using interval_minutes.' },
|
|
325
|
+
minute: { type: 'integer', description: 'Daily mode: minute (0-59). Omit if using interval_minutes.' },
|
|
326
|
+
days: {
|
|
327
|
+
type: 'array',
|
|
328
|
+
items: { type: 'integer' },
|
|
329
|
+
description: 'Daily mode: days of week to fire (0=Mon, 6=Sun). Omit for every day. Not allowed in interval mode.',
|
|
330
|
+
},
|
|
331
|
+
interval_minutes: {
|
|
332
|
+
type: 'integer',
|
|
333
|
+
description: 'Interval mode: fire every N minutes (1-1440). Mutually exclusive with hour/minute.',
|
|
334
|
+
},
|
|
316
335
|
},
|
|
336
|
+
required: ['name', 'message', 'context'],
|
|
317
337
|
},
|
|
318
|
-
required: ['name', 'message', 'hour', 'minute'],
|
|
319
338
|
},
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
{
|
|
340
|
+
name: 'workspace_list_routines',
|
|
341
|
+
description: 'List active routines in the current channel.',
|
|
342
|
+
inputSchema: { type: 'object', properties: {} },
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'workspace_cancel_routine',
|
|
346
|
+
description: 'Cancel a recurring routine by its ID.',
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: 'object',
|
|
349
|
+
properties: {
|
|
350
|
+
routine_id: { type: 'string', description: 'Routine ID to cancel' },
|
|
351
|
+
},
|
|
352
|
+
required: ['routine_id'],
|
|
333
353
|
},
|
|
334
|
-
required: ['routine_id'],
|
|
335
354
|
},
|
|
336
|
-
|
|
337
|
-
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// -- Notifications --
|
|
359
|
+
if (!disabledModules.has('notifications')) {
|
|
360
|
+
tools.push(
|
|
361
|
+
{
|
|
362
|
+
name: 'workspace_send_notification',
|
|
363
|
+
description: 'Send a notification to the workspace inbox. Use this to alert humans about completed tasks, important findings, or anything that needs their attention outside the chat stream.',
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
title: { type: 'string', description: 'Short notification title (e.g. "News scan complete")' },
|
|
368
|
+
message: { type: 'string', description: 'Notification body with details' },
|
|
369
|
+
priority: { type: 'string', enum: ['low', 'normal', 'high'], description: 'Notification priority (default: normal)' },
|
|
370
|
+
channel: { type: 'string', description: 'Related channel/thread name (for navigation link)' },
|
|
371
|
+
},
|
|
372
|
+
required: ['title', 'message'],
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: 'workspace_get_notifications',
|
|
377
|
+
description: 'List notifications you have sent to the workspace inbox.',
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: 'object',
|
|
380
|
+
properties: {
|
|
381
|
+
limit: { type: 'integer', description: 'Number of notifications to return (default 20)' },
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// -- Knowledge Base --
|
|
389
|
+
if (!disabledModules.has('knowledge')) {
|
|
390
|
+
tools.push(
|
|
391
|
+
{
|
|
392
|
+
name: 'workspace_list_knowledge',
|
|
393
|
+
description: 'List knowledge base entries shared in the workspace.',
|
|
394
|
+
inputSchema: { type: 'object', properties: {} },
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: 'workspace_read_knowledge',
|
|
398
|
+
description: 'Read a knowledge base entry by ID or slug. Returns the full markdown content.',
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: 'object',
|
|
401
|
+
properties: {
|
|
402
|
+
entry_id: { type: 'string', description: 'Knowledge entry ID' },
|
|
403
|
+
slug: { type: 'string', description: 'Knowledge entry slug (alternative to entry_id)' },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: 'workspace_write_knowledge',
|
|
409
|
+
description: 'Create or update a knowledge base entry. Knowledge entries are workspace-global markdown documents accessible to all agents via @knowledge:slug mentions.',
|
|
410
|
+
inputSchema: {
|
|
411
|
+
type: 'object',
|
|
412
|
+
properties: {
|
|
413
|
+
title: { type: 'string', description: 'Entry title' },
|
|
414
|
+
content: { type: 'string', description: 'Markdown content of the knowledge entry' },
|
|
415
|
+
description: { type: 'string', description: 'Short summary (optional)' },
|
|
416
|
+
entry_id: { type: 'string', description: 'Existing entry ID to update (omit to create new)' },
|
|
417
|
+
},
|
|
418
|
+
required: ['title', 'content'],
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: 'workspace_delete_knowledge',
|
|
423
|
+
description: 'Delete a knowledge base entry by ID.',
|
|
424
|
+
inputSchema: {
|
|
425
|
+
type: 'object',
|
|
426
|
+
properties: {
|
|
427
|
+
entry_id: { type: 'string', description: 'Knowledge entry ID to delete' },
|
|
428
|
+
},
|
|
429
|
+
required: ['entry_id'],
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
);
|
|
433
|
+
}
|
|
338
434
|
|
|
339
435
|
return tools;
|
|
340
436
|
}
|
|
@@ -760,25 +856,41 @@ class McpServer {
|
|
|
760
856
|
{
|
|
761
857
|
name: args.name,
|
|
762
858
|
message: args.message,
|
|
859
|
+
context: args.context,
|
|
763
860
|
hour: args.hour,
|
|
764
861
|
minute: args.minute,
|
|
765
862
|
days: args.days,
|
|
863
|
+
interval_minutes: args.interval_minutes,
|
|
766
864
|
source: `openagents:${this.agentName}`,
|
|
767
865
|
},
|
|
768
866
|
);
|
|
769
|
-
|
|
770
|
-
|
|
867
|
+
let scheduleStr;
|
|
868
|
+
if (args.interval_minutes != null) {
|
|
869
|
+
scheduleStr = `every ${args.interval_minutes} min`;
|
|
870
|
+
} else {
|
|
871
|
+
const daysStr = args.days ? `days [${args.days.join(',')}]` : 'every day';
|
|
872
|
+
scheduleStr = `at ${String(args.hour).padStart(2,'0')}:${String(args.minute).padStart(2,'0')} UTC, ${daysStr}`;
|
|
873
|
+
}
|
|
874
|
+
const channel = (result && result.channel_name) || `routine:${result.id}`;
|
|
875
|
+
return text(
|
|
876
|
+
`Routine created: "${args.name}" ${scheduleStr} in dedicated thread \`${channel}\` (id: ${result.id})`,
|
|
877
|
+
);
|
|
771
878
|
}
|
|
772
879
|
|
|
773
880
|
case 'workspace_list_routines': {
|
|
774
881
|
const data = await this.ws.listRoutines(this.workspaceId, this.channelName, this.token);
|
|
775
882
|
const routines = (data && data.routines) || [];
|
|
776
883
|
if (!routines.length) return text('No active routines.');
|
|
777
|
-
const lines = routines.map((r) =>
|
|
778
|
-
|
|
779
|
-
(r.
|
|
780
|
-
|
|
781
|
-
|
|
884
|
+
const lines = routines.map((r) => {
|
|
885
|
+
let when;
|
|
886
|
+
if (r.schedule_interval_minutes != null) {
|
|
887
|
+
when = `every ${r.schedule_interval_minutes} min`;
|
|
888
|
+
} else {
|
|
889
|
+
when = `at ${String(r.schedule_hour).padStart(2,'0')}:${String(r.schedule_minute).padStart(2,'0')} UTC` +
|
|
890
|
+
(r.schedule_days ? ` [days: ${r.schedule_days.join(',')}]` : ' (daily)');
|
|
891
|
+
}
|
|
892
|
+
return `- ${r.id}: "${r.name}" ${when} — next: ${r.next_fires_at} (by ${r.created_by})`;
|
|
893
|
+
});
|
|
782
894
|
return text(lines.join('\n'));
|
|
783
895
|
}
|
|
784
896
|
|
|
@@ -787,6 +899,82 @@ class McpServer {
|
|
|
787
899
|
return text(`Routine cancelled: ${args.routine_id}`);
|
|
788
900
|
}
|
|
789
901
|
|
|
902
|
+
case 'workspace_send_notification': {
|
|
903
|
+
const result = await this.ws.createNotification(
|
|
904
|
+
this.workspaceId, this.token,
|
|
905
|
+
{
|
|
906
|
+
title: args.title,
|
|
907
|
+
message: args.message,
|
|
908
|
+
priority: args.priority || 'normal',
|
|
909
|
+
channel: args.channel || this.channelName,
|
|
910
|
+
source: `openagents:${this.agentName}`,
|
|
911
|
+
},
|
|
912
|
+
);
|
|
913
|
+
return text(`Notification sent: "${args.title}" (id: ${result.id})`);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
case 'workspace_get_notifications': {
|
|
917
|
+
const data = await this.ws.listNotifications(this.workspaceId, this.token, {
|
|
918
|
+
limit: args.limit || 20,
|
|
919
|
+
});
|
|
920
|
+
const notifications = (data && data.notifications) || [];
|
|
921
|
+
if (!notifications.length) return text('No notifications.');
|
|
922
|
+
const lines = notifications.map((n) => {
|
|
923
|
+
const readStatus = n.is_read ? '[read]' : '[unread]';
|
|
924
|
+
const prio = n.priority !== 'normal' ? ` [${n.priority}]` : '';
|
|
925
|
+
return `- ${readStatus}${prio} ${n.title}: ${n.message.slice(0, 80)}${n.message.length > 80 ? '...' : ''} (${n.created_at})`;
|
|
926
|
+
});
|
|
927
|
+
return text(lines.join('\n'));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// ── Knowledge Base ──
|
|
931
|
+
|
|
932
|
+
case 'workspace_list_knowledge': {
|
|
933
|
+
const data = await this.ws.listKnowledge(this.workspaceId, this.token);
|
|
934
|
+
const entries = (data && data.entries) || [];
|
|
935
|
+
if (!entries.length) return text('No knowledge entries yet.');
|
|
936
|
+
const lines = entries.map((e) =>
|
|
937
|
+
`- ${e.title} (slug: ${e.slug}, id: ${e.id})`
|
|
938
|
+
);
|
|
939
|
+
return text(lines.join('\n'));
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
case 'workspace_read_knowledge': {
|
|
943
|
+
let data;
|
|
944
|
+
if (args.slug) {
|
|
945
|
+
data = await this.ws.getKnowledgeBySlug(this.workspaceId, this.token, args.slug);
|
|
946
|
+
} else if (args.entry_id) {
|
|
947
|
+
data = await this.ws.getKnowledge(this.workspaceId, this.token, args.entry_id);
|
|
948
|
+
} else {
|
|
949
|
+
throw new Error('Either entry_id or slug is required');
|
|
950
|
+
}
|
|
951
|
+
return text(`# ${data.title}\n\n${data.content || '(empty)'}`);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
case 'workspace_write_knowledge': {
|
|
955
|
+
let result;
|
|
956
|
+
if (args.entry_id) {
|
|
957
|
+
result = await this.ws.updateKnowledge(
|
|
958
|
+
this.workspaceId, this.token, args.entry_id,
|
|
959
|
+
{ title: args.title, content: args.content, description: args.description,
|
|
960
|
+
source: `openagents:${this.agentName}` },
|
|
961
|
+
);
|
|
962
|
+
return text(`Knowledge updated: "${result.title}" (slug: ${result.slug})`);
|
|
963
|
+
} else {
|
|
964
|
+
result = await this.ws.createKnowledge(
|
|
965
|
+
this.workspaceId, this.token,
|
|
966
|
+
{ title: args.title, content: args.content, description: args.description,
|
|
967
|
+
source: `openagents:${this.agentName}` },
|
|
968
|
+
);
|
|
969
|
+
return text(`Knowledge created: "${result.title}" (slug: ${result.slug}, id: ${result.id})`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
case 'workspace_delete_knowledge': {
|
|
974
|
+
await this.ws.deleteKnowledge(this.workspaceId, this.token, args.entry_id);
|
|
975
|
+
return text(`Knowledge entry deleted: ${args.entry_id}`);
|
|
976
|
+
}
|
|
977
|
+
|
|
790
978
|
default:
|
|
791
979
|
throw new Error(`Unknown tool: ${name}`);
|
|
792
980
|
}
|
package/src/paths.js
CHANGED
|
@@ -16,12 +16,25 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
16
16
|
const IS_MACOS = process.platform === 'darwin';
|
|
17
17
|
const SEP = IS_WINDOWS ? ';' : ':';
|
|
18
18
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
19
|
+
const PATH_LOOKUP_CACHE_TTL_MS = 30 * 1000;
|
|
20
|
+
|
|
21
|
+
let extraBinDirsCache = { value: null, at: 0, path: '' };
|
|
22
|
+
const whichBinaryCache = new Map();
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Get all extra binary directories that should be checked beyond process.env.PATH.
|
|
22
26
|
* Returns deduplicated list of existing directories.
|
|
23
27
|
*/
|
|
24
28
|
function getExtraBinDirs() {
|
|
29
|
+
const currentPATH = process.env.PATH || '';
|
|
30
|
+
if (
|
|
31
|
+
extraBinDirsCache.value &&
|
|
32
|
+
extraBinDirsCache.path === currentPATH &&
|
|
33
|
+
Date.now() - extraBinDirsCache.at < PATH_LOOKUP_CACHE_TTL_MS
|
|
34
|
+
) {
|
|
35
|
+
return [...extraBinDirsCache.value];
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
const dirs = [];
|
|
26
39
|
|
|
27
40
|
if (IS_WINDOWS) {
|
|
@@ -62,8 +75,7 @@ function getExtraBinDirs() {
|
|
|
62
75
|
|
|
63
76
|
// Filter to existing directories only, deduplicate
|
|
64
77
|
const seen = new Set();
|
|
65
|
-
const
|
|
66
|
-
return dirs.filter(d => {
|
|
78
|
+
const value = dirs.filter(d => {
|
|
67
79
|
if (!d || seen.has(d)) return false;
|
|
68
80
|
// Skip if already in PATH (case-insensitive on Windows)
|
|
69
81
|
if (IS_WINDOWS ? currentPATH.toLowerCase().includes(d.toLowerCase()) : currentPATH.includes(d)) return false;
|
|
@@ -74,6 +86,8 @@ function getExtraBinDirs() {
|
|
|
74
86
|
return false;
|
|
75
87
|
}
|
|
76
88
|
});
|
|
89
|
+
extraBinDirsCache = { value, at: Date.now(), path: currentPATH };
|
|
90
|
+
return [...value];
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
/**
|
|
@@ -114,6 +128,13 @@ function getEnhancedEnv(baseEnv) {
|
|
|
114
128
|
*/
|
|
115
129
|
function whichBinary(name) {
|
|
116
130
|
if (!name) return null;
|
|
131
|
+
const currentPATH = process.env.PATH || '';
|
|
132
|
+
const cacheKey = `${name}\0${currentPATH}`;
|
|
133
|
+
const cached = whichBinaryCache.get(cacheKey);
|
|
134
|
+
if (cached && Date.now() - cached.at < PATH_LOOKUP_CACHE_TTL_MS) {
|
|
135
|
+
return cached.value;
|
|
136
|
+
}
|
|
137
|
+
|
|
117
138
|
const cmd = IS_WINDOWS ? `where ${name}` : `which ${name}`;
|
|
118
139
|
try {
|
|
119
140
|
const result = execSync(cmd, {
|
|
@@ -121,9 +142,13 @@ function whichBinary(name) {
|
|
|
121
142
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
143
|
env: { ...process.env, PATH: getEnhancedPATH() },
|
|
123
144
|
timeout: 5000,
|
|
145
|
+
windowsHide: true,
|
|
124
146
|
}).trim();
|
|
125
|
-
|
|
147
|
+
const value = result.split(/\r?\n/)[0] || null;
|
|
148
|
+
whichBinaryCache.set(cacheKey, { value, at: Date.now() });
|
|
149
|
+
return value;
|
|
126
150
|
} catch {
|
|
151
|
+
whichBinaryCache.set(cacheKey, { value: null, at: Date.now() });
|
|
127
152
|
return null;
|
|
128
153
|
}
|
|
129
154
|
}
|
|
@@ -154,6 +179,9 @@ function _addWindowsPaths(dirs) {
|
|
|
154
179
|
// Portable Node.js installed by OpenAgents Launcher
|
|
155
180
|
_push(dirs, path.join(HOME, '.openagents', 'nodejs'));
|
|
156
181
|
|
|
182
|
+
// Cursor CLI native installer
|
|
183
|
+
_push(dirs, path.join(HOME, '.cursor', 'bin'));
|
|
184
|
+
|
|
157
185
|
// Node.js install
|
|
158
186
|
_push(dirs, path.join(programFiles, 'nodejs'));
|
|
159
187
|
|
package/src/registry.js
CHANGED
|
@@ -85,7 +85,7 @@ class Registry {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Merge bundled env_config/resolve_env/install into catalog entries.
|
|
88
|
-
* Remote/cached entries may lack these fields.
|
|
88
|
+
* Remote/cached entries may lack these fields or carry stale install data.
|
|
89
89
|
*/
|
|
90
90
|
_mergeBundled(catalog) {
|
|
91
91
|
const bundled = this._loadBundled();
|
|
@@ -94,7 +94,7 @@ class Registry {
|
|
|
94
94
|
if (b) {
|
|
95
95
|
if ((!entry.env_config || entry.env_config.length === 0) && b.env_config && b.env_config.length > 0) entry.env_config = b.env_config;
|
|
96
96
|
if (!entry.resolve_env && b.resolve_env) entry.resolve_env = b.resolve_env;
|
|
97
|
-
if (
|
|
97
|
+
if (b.install) entry.install = { ...b.install };
|
|
98
98
|
if (!entry.check_ready && b.check_ready) entry.check_ready = b.check_ready;
|
|
99
99
|
if (!entry.launch && b.launch) entry.launch = b.launch;
|
|
100
100
|
// Always take featured/order/support from bundled (source of truth for ordering)
|
|
@@ -136,11 +136,11 @@ class Registry {
|
|
|
136
136
|
const catalog = this.getCatalogSync();
|
|
137
137
|
const entry = catalog.find((e) => e.name === agentType) || null;
|
|
138
138
|
if (!entry) return null;
|
|
139
|
-
// Merge
|
|
139
|
+
// Merge bundled registry fields into cached/remote entries.
|
|
140
140
|
const bundled = this._loadBundled();
|
|
141
141
|
const b = bundled.find((e) => e.name === agentType);
|
|
142
142
|
if (b) {
|
|
143
|
-
if (
|
|
143
|
+
if (b.install) entry.install = { ...b.install };
|
|
144
144
|
if (!entry.check_ready && b.check_ready) entry.check_ready = b.check_ready;
|
|
145
145
|
if (!entry.launch && b.launch) entry.launch = b.launch;
|
|
146
146
|
if ((!entry.env_config || !entry.env_config.length) && b.env_config?.length) entry.env_config = b.env_config;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Skill Hub catalog — JavaScript mirror of the backend skill_catalog.py.
|
|
5
|
+
*
|
|
6
|
+
* Provides the same skill definitions and conversion helpers so the
|
|
7
|
+
* launcher CLI and daemon can work offline without hitting the backend.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const SKILL_CATALOG = [
|
|
11
|
+
{
|
|
12
|
+
id: 'workspace-core',
|
|
13
|
+
name: 'Workspace Core',
|
|
14
|
+
description: 'Messaging, agent discovery, and status — always available',
|
|
15
|
+
category: 'core',
|
|
16
|
+
icon: 'message-square',
|
|
17
|
+
moduleKey: null,
|
|
18
|
+
defaultEnabled: true,
|
|
19
|
+
toggleable: false,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'files',
|
|
23
|
+
name: 'Shared Files',
|
|
24
|
+
description: 'Upload, download, and share files across workspace agents and users',
|
|
25
|
+
category: 'collaboration',
|
|
26
|
+
icon: 'file-text',
|
|
27
|
+
moduleKey: 'files',
|
|
28
|
+
defaultEnabled: true,
|
|
29
|
+
toggleable: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'browser',
|
|
33
|
+
name: 'Shared Browser',
|
|
34
|
+
description: 'Browse websites through the workspace\'s shared browser session',
|
|
35
|
+
category: 'collaboration',
|
|
36
|
+
icon: 'globe',
|
|
37
|
+
moduleKey: 'browser',
|
|
38
|
+
defaultEnabled: true,
|
|
39
|
+
toggleable: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'tunnel',
|
|
43
|
+
name: 'Cloudflare Tunnel',
|
|
44
|
+
description: 'Expose local services to the internet via secure tunnels',
|
|
45
|
+
category: 'collaboration',
|
|
46
|
+
icon: 'cloud',
|
|
47
|
+
moduleKey: 'tunnel',
|
|
48
|
+
defaultEnabled: true,
|
|
49
|
+
toggleable: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'todos',
|
|
53
|
+
name: 'Task Planning',
|
|
54
|
+
description: 'Create and manage to-do lists for coordinated agent work',
|
|
55
|
+
category: 'productivity',
|
|
56
|
+
icon: 'check-square',
|
|
57
|
+
moduleKey: 'todos',
|
|
58
|
+
defaultEnabled: true,
|
|
59
|
+
toggleable: true,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'timers',
|
|
63
|
+
name: 'Timers',
|
|
64
|
+
description: 'Set countdown timers with callback messages',
|
|
65
|
+
category: 'productivity',
|
|
66
|
+
icon: 'timer',
|
|
67
|
+
moduleKey: 'timers',
|
|
68
|
+
defaultEnabled: true,
|
|
69
|
+
toggleable: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'routines',
|
|
73
|
+
name: 'Routines',
|
|
74
|
+
description: 'Schedule recurring tasks on a daily or interval basis',
|
|
75
|
+
category: 'productivity',
|
|
76
|
+
icon: 'repeat',
|
|
77
|
+
moduleKey: 'routines',
|
|
78
|
+
defaultEnabled: true,
|
|
79
|
+
toggleable: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'knowledge',
|
|
83
|
+
name: 'Knowledge Base',
|
|
84
|
+
description: 'Create, read, and share knowledge base entries across workspace agents',
|
|
85
|
+
category: 'collaboration',
|
|
86
|
+
icon: 'book-open',
|
|
87
|
+
moduleKey: 'knowledge',
|
|
88
|
+
defaultEnabled: true,
|
|
89
|
+
toggleable: true,
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const _TOGGLEABLE = SKILL_CATALOG.filter(s => s.toggleable);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convert a per-agent skills config object to a disabledModules Set.
|
|
97
|
+
*
|
|
98
|
+
* @param {Object.<string, boolean>|null|undefined} skills
|
|
99
|
+
* @returns {Set<string>}
|
|
100
|
+
*/
|
|
101
|
+
function skillsToDisabledModules(skills) {
|
|
102
|
+
if (!skills || typeof skills !== 'object') return new Set();
|
|
103
|
+
|
|
104
|
+
const disabled = new Set();
|
|
105
|
+
for (const s of _TOGGLEABLE) {
|
|
106
|
+
const enabled = skills[s.id] !== undefined ? skills[s.id] : s.defaultEnabled;
|
|
107
|
+
if (!enabled && s.moduleKey) {
|
|
108
|
+
disabled.add(s.moduleKey);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return disabled;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Return default skill state: {id: defaultEnabled} for all toggleable skills.
|
|
116
|
+
*/
|
|
117
|
+
function getSkillDefaults() {
|
|
118
|
+
const defaults = {};
|
|
119
|
+
for (const s of _TOGGLEABLE) {
|
|
120
|
+
defaults[s.id] = s.defaultEnabled;
|
|
121
|
+
}
|
|
122
|
+
return defaults;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { SKILL_CATALOG, skillsToDisabledModules, getSkillDefaults };
|